diff options
Diffstat (limited to 'src/libaudcore/adder.cc')
-rw-r--r-- | src/libaudcore/adder.cc | 260 |
1 files changed, 172 insertions, 88 deletions
diff --git a/src/libaudcore/adder.cc b/src/libaudcore/adder.cc index a689e3b..2866036 100644 --- a/src/libaudcore/adder.cc +++ b/src/libaudcore/adder.cc @@ -1,6 +1,6 @@ /* * adder.c - * Copyright 2011-2013 John Lindgren + * Copyright 2011-2016 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -21,22 +21,30 @@ #include "internal.h" #include <pthread.h> +#include <stdio.h> #include <string.h> -#include <sys/stat.h> - -#include <glib/gstdio.h> #include "audstrings.h" #include "hook.h" #include "i18n.h" -#include "internal.h" #include "list.h" #include "mainloop.h" #include "plugins-internal.h" +#include "probe.h" #include "runtime.h" #include "tuple.h" +#include "interface.h" #include "vfs.h" +#ifdef _WIN32 +// regrettably, strcmp_nocase can't be used directly as a +// callback for Index::sort due to taking a third argument +static int filename_compare (const char * a, const char * b) + { return strcmp_nocase (a, b); } +#else +#define filename_compare strcmp +#endif + struct AddTask : public ListNode { int playlist_id, at; @@ -52,6 +60,7 @@ struct AddResult : public ListNode bool play; String title; Index<PlaylistAddItem> items; + bool saw_folder, filtered; }; static void * add_worker (void * unused); @@ -114,52 +123,54 @@ static void status_done_locked () hook_call ("ui hide progress", nullptr); } -static void add_file (const char * filename, Tuple && tuple, - PluginHandle * decoder, PlaylistFilterFunc filter, void * user, - AddResult * result, bool validate) +static void add_file (PlaylistAddItem && item, PlaylistFilterFunc filter, + void * user, AddResult * result, bool validate) { - if (filter && ! filter (filename, user)) - return; - - AUDINFO ("Adding file: %s\n", filename); - status_update (filename, result->items.len ()); - - if (! tuple) + AUDINFO ("Adding file: %s\n", (const char *) item.filename); + status_update (item.filename, result->items.len ()); + + /* If the item doesn't already have a valid tuple, and isn't a subtune + * itself, then probe it to expand any subtunes. The "validate" check (used + * to skip non-audio files when adding folders) is also nested within this + * block; note that "validate" is always false for subtunes. */ + if (! item.tuple.valid () && ! is_subtune (item.filename)) { VFSFile file; - if (! decoder) + if (! item.decoder) { bool fast = ! aud_get_bool (nullptr, "slow_probe"); - decoder = file_find_decoder (filename, fast, file); - if (validate && ! decoder) + item.decoder = aud_file_find_decoder (item.filename, fast, file); + if (validate && ! item.decoder) return; } - if (decoder && input_plugin_has_subtunes (decoder) && ! strchr (filename, '?')) - file_read_tag (filename, decoder, file, & tuple, nullptr); + if (item.decoder && input_plugin_has_subtunes (item.decoder)) + aud_file_read_tag (item.filename, item.decoder, file, item.tuple); } - int n_subtunes = tuple.get_n_subtunes (); + int n_subtunes = item.tuple.get_n_subtunes (); if (n_subtunes) { for (int sub = 0; sub < n_subtunes; sub ++) { - StringBuf subname = str_printf ("%s?%d", filename, tuple.get_nth_subtune (sub)); - add_file (subname, Tuple (), decoder, filter, user, result, false); + StringBuf subname = str_printf ("%s?%d", + (const char *) item.filename, item.tuple.get_nth_subtune (sub)); + + if (! filter || filter (subname, user)) + add_file ({String (subname), Tuple (), item.decoder}, filter, user, result, false); + else + result->filtered = true; } } else - result->items.append (String (filename), std::move (tuple), decoder); + result->items.append (std::move (item)); } static void add_playlist (const char * filename, PlaylistFilterFunc filter, void * user, AddResult * result, bool is_single) { - if (filter && ! filter (filename, user)) - return; - AUDINFO ("Adding playlist: %s\n", filename); status_update (filename, result->items.len ()); @@ -173,99 +184,156 @@ static void add_playlist (const char * filename, PlaylistFilterFunc filter, result->title = title; for (auto & item : items) - add_file (item.filename, std::move (item.tuple), nullptr, filter, user, result, false); + { + if (! filter || filter (item.filename, user)) + add_file (std::move (item), filter, user, result, false); + else + result->filtered = true; + } } -static void add_folder (const char * filename, PlaylistFilterFunc filter, - void * user, AddResult * result, bool is_single) +static void add_cuesheets (Index<String> & files, PlaylistFilterFunc filter, + void * user, AddResult * result) { - Index<String> cuesheets, files; - GDir * folder; - - if (filter && ! filter (filename, user)) - return; + Index<String> cuesheets; - AUDINFO ("Adding folder: %s\n", filename); - status_update (filename, result->items.len ()); + for (int i = 0; i < files.len ();) + { + if (str_has_suffix_nocase (files[i], ".cue")) + cuesheets.move_from (files, i, -1, 1, true, true); + else + i ++; + } - StringBuf path = uri_to_filename (filename); - if (! path) + if (! cuesheets.len ()) return; - if (! (folder = g_dir_open (path, 0, nullptr))) - return; + // sort cuesheet list in natural order + cuesheets.sort (str_compare_encoded); + + // sort file list in system-dependent order for duplicate removal + files.sort (filename_compare); - const char * name; - while ((name = g_dir_read_name (folder))) + for (String & cuesheet : cuesheets) { - if (str_has_suffix_nocase (name, ".cue")) - cuesheets.append (name); - else - files.append (name); - } + AUDINFO ("Adding cuesheet: %s\n", (const char *) cuesheet); + status_update (cuesheet, result->items.len ()); - g_dir_close (folder); + String title; // ignored + Index<PlaylistAddItem> items; - for (const char * cuesheet : cuesheets) - { - AUDINFO ("Found cuesheet: %s\n", cuesheet); + if (! playlist_load (cuesheet, title, items)) + continue; + + String prev_filename; + for (auto & item : items) + { + String filename = item.tuple.get_str (Tuple::AudioFile); + if (! filename) + continue; // shouldn't happen - auto is_match = [=] (const char * name) - { return same_basename (name, cuesheet); }; + if (! filter || filter (item.filename, user)) + add_file (std::move (item), filter, user, result, false); + else + result->filtered = true; + + // remove duplicates from file list + if (prev_filename && ! filename_compare (filename, prev_filename)) + continue; - files.remove_if (is_match); + int idx = files.bsearch ((const char *) filename, filename_compare); + if (idx >= 0) + files.remove (idx, 1); + + prev_filename = std::move (filename); + } } +} - files.move_from (cuesheets, 0, -1, -1, true, true); +static void add_folder (const char * filename, PlaylistFilterFunc filter, + void * user, AddResult * result, bool is_single) +{ + AUDINFO ("Adding folder: %s\n", filename); + status_update (filename, result->items.len ()); + + String error; + Index<String> files = VFSFile::read_folder (filename, error); + + if (error) + aud_ui_show_error (str_printf (_("Error reading %s:\n%s"), filename, (const char *) error)); if (! files.len ()) return; if (is_single) { - const char * last = last_path_element (path); - result->title = String (last ? last : path); + const char * slash = strrchr (filename, '/'); + if (slash) + result->title = String (str_decode_percent (slash + 1)); } - auto compare_wrapper = [] (const String & a, const String & b, void *) - { return str_compare (a, b); }; + add_cuesheets (files, filter, user, result); - files.sort (compare_wrapper, nullptr); + // sort file list in natural order (must come after add_cuesheets) + files.sort (str_compare_encoded); - for (const char * name : files) + for (const char * file : files) { - StringBuf filepath = filename_build ({path, name}); - StringBuf uri = filename_to_uri (filepath); - if (! uri) + if (filter && ! filter (file, user)) + { + result->filtered = true; continue; + } + + String error; + VFSFileTest mode = VFSFile::test_file (file, + VFSFileTest (VFS_IS_REGULAR | VFS_IS_SYMLINK | VFS_IS_DIR), error); - GStatBuf info; - if (g_lstat (filepath, & info) < 0) + if (error) + AUDERR ("%s: %s\n", file, (const char *) error); + + if (mode & VFS_IS_SYMLINK) continue; - if (S_ISREG (info.st_mode)) - { - if (str_has_suffix_nocase (name, ".cue")) - add_playlist (uri, filter, user, result, false); - else - add_file (uri, Tuple (), nullptr, filter, user, result, true); - } - else if (S_ISDIR (info.st_mode)) - add_folder (uri, filter, user, result, false); + if (mode & VFS_IS_REGULAR) + add_file ({String (file)}, filter, user, result, true); + else if (mode & VFS_IS_DIR) + add_folder (file, filter, user, result, false); } } -static void add_generic (const char * filename, Tuple && tuple, - PlaylistFilterFunc filter, void * user, AddResult * result, bool is_single) +static void add_generic (PlaylistAddItem && item, PlaylistFilterFunc filter, + void * user, AddResult * result, bool is_single) { - if (tuple) - add_file (filename, std::move (tuple), nullptr, filter, user, result, false); - else if (VFSFile::test_file (filename, VFS_IS_DIR)) - add_folder (filename, filter, user, result, is_single); - else if (aud_filename_is_playlist (filename)) - add_playlist (filename, filter, user, result, is_single); + if (filter && ! filter (item.filename, user)) + { + result->filtered = true; + return; + } + + /* If the item has a valid tuple or known decoder, or it's a subtune, then + * assume it's a playable file and skip some checks. */ + if (item.tuple.valid () || item.decoder || is_subtune (item.filename)) + add_file (std::move (item), filter, user, result, false); else - add_file (filename, Tuple (), nullptr, filter, user, result, false); + { + String error; + VFSFileTest mode = VFSFile::test_file (item.filename, + VFSFileTest (VFS_IS_DIR | VFS_NO_ACCESS), error); + + if (mode & VFS_NO_ACCESS) + aud_ui_show_error (str_printf (_("Error reading %s:\n%s"), + (const char *) item.filename, (const char *) error)); + else if (mode & VFS_IS_DIR) + { + add_folder (item.filename, filter, user, result, is_single); + result->saw_folder = true; + } + else if (aud_filename_is_playlist (item.filename)) + add_playlist (item.filename, filter, user, result, is_single); + else + add_file (std::move (item), filter, user, result, false); + } } static void start_thread_locked () @@ -308,10 +376,25 @@ static void add_finish (void * unused) int playlist, count; + if (! result->items.len ()) + { + if (result->saw_folder && ! result->filtered) + aud_ui_show_error (_("No files found.")); + goto FREE; + } + playlist = aud_playlist_by_unique_id (result->playlist_id); if (playlist < 0) /* playlist deleted */ goto FREE; + if (result->play) + { + if (aud_get_bool (nullptr, "clear_playlist")) + aud_playlist_entry_delete (playlist, 0, aud_playlist_entry_count (playlist)); + else + aud_playlist_queue_delete (playlist, 0, aud_playlist_queue_count (playlist)); + } + count = aud_playlist_entry_count (playlist); if (result->at < 0 || result->at > count) result->at = count; @@ -330,7 +413,7 @@ static void add_finish (void * unused) playlist_enable_scan (false); playlist_entry_insert_batch_raw (playlist, result->at, std::move (result->items)); - if (result->play && aud_playlist_entry_count (playlist) > count) + if (result->play) { if (! aud_get_bool (0, "shuffle")) aud_playlist_set_position (playlist, result->at); @@ -367,6 +450,8 @@ static void * add_worker (void * unused) current_playlist_id = task->playlist_id; pthread_mutex_unlock (& mutex); + playlist_cache_load (task->items); + AddResult * result = new AddResult (); result->playlist_id = task->playlist_id; @@ -376,8 +461,7 @@ static void * add_worker (void * unused) bool is_single = (task->items.len () == 1); for (auto & item : task->items) - add_generic (item.filename, std::move (item.tuple), task->filter, - task->user, result, is_single); + add_generic (std::move (item), task->filter, task->user, result, is_single); delete task; |