diff options
Diffstat (limited to 'src/libaudcore')
53 files changed, 1580 insertions, 710 deletions
diff --git a/src/libaudcore/Makefile b/src/libaudcore/Makefile index 7070823..68768ed 100644 --- a/src/libaudcore/Makefile +++ b/src/libaudcore/Makefile @@ -1,5 +1,5 @@ SHARED_LIB = ${LIB_PREFIX}audcore${LIB_SUFFIX} -LIB_MAJOR = 3 +LIB_MAJOR = 4 LIB_MINOR = 1 SRCS = adder.cc \ @@ -9,6 +9,7 @@ SRCS = adder.cc \ audstrings.cc \ charset.cc \ config.cc \ + cue-cache.cc \ drct.cc \ effect.cc \ equalizer.cc \ @@ -27,6 +28,7 @@ SRCS = adder.cc \ output.cc \ playback.cc \ playlist.cc \ + playlist-cache.cc \ playlist-files.cc \ playlist-utils.cc \ plugin-init.cc \ @@ -55,6 +57,7 @@ INCLUDES = audio.h \ audstrings.h \ drct.h \ equalizer.h \ + export.h \ hook.h \ i18n.h \ index.h \ @@ -95,7 +98,8 @@ CPPFLAGS := -I.. -I../.. \ -DHARDCODE_PLUGINDIR=\"${plugindir}\" \ -DHARDCODE_LOCALEDIR=\"${localedir}\" \ -DHARDCODE_DESKTOPFILE=\"${datarootdir}/applications/audacious.desktop\" \ - -DHARDCODE_ICONFILE=\"${datarootdir}/icons/hicolor/48x48/apps/audacious.png\" + -DHARDCODE_ICONFILE=\"${datarootdir}/icons/hicolor/48x48/apps/audacious.png\" \ + -DLIBAUDCORE_BUILD CFLAGS += ${LIB_CFLAGS} 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; diff --git a/src/libaudcore/art-search.cc b/src/libaudcore/art-search.cc index 5188d82..0fe71e9 100644 --- a/src/libaudcore/art-search.cc +++ b/src/libaudcore/art-search.cc @@ -148,8 +148,5 @@ String art_search (const char * filename) cut_path_element (local, elem - local); String image_local = fileinfo_recursive_get_image (local, & params, 0); - if (! image_local) - return String (); - - return String (filename_to_uri (image_local)); + return image_local ? String (filename_to_uri (image_local)) : String (); } diff --git a/src/libaudcore/art.cc b/src/libaudcore/art.cc index f8ff004..6fdbda7 100644 --- a/src/libaudcore/art.cc +++ b/src/libaudcore/art.cc @@ -87,11 +87,6 @@ static void send_requests (void *) for (const String & file : queued) { hook_call ("art ready", (void *) (const char *) file); - - /* this hook is deprecated in 3.7 but kept for compatibility */ - if (file == current_ref) - hook_call ("current art ready", (void *) (const char *) file); - aud_art_unref (file); /* release temporary reference */ } } diff --git a/src/libaudcore/audstrings.cc b/src/libaudcore/audstrings.cc index cb2be04..6a89121 100644 --- a/src/libaudcore/audstrings.cc +++ b/src/libaudcore/audstrings.cc @@ -33,6 +33,10 @@ #include "internal.h" #include "runtime.h" +#define MAX_POW10 9 +static const unsigned int_pow10[MAX_POW10 + 1] = + {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000}; + static const char ascii_to_hex[256] = "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" "\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0\x0" @@ -68,6 +72,12 @@ static const char swap_case[256] = #define IS_LEGAL(c) (uri_legal_table[(unsigned char) (c)]) #define SWAP_CASE(c) (swap_case[(unsigned char) (c)]) +#ifdef _WIN32 +#define IS_SEP(c) ((c) == '/' || (c) == '\\') +#else +#define IS_SEP(c) ((c) == '/') +#endif + /* strcmp() that handles nullptr safely */ EXPORT int strcmp_safe (const char * a, const char * b, int len) { @@ -184,13 +194,13 @@ EXPORT unsigned str_calc_hash (const char * s) while (len >= 8) { h = h * 1954312449 + - s[0] * 3963737313 + - s[1] * 1291467969 + - s[2] * 39135393 + - s[3] * 1185921 + - s[4] * 35937 + - s[5] * 1089 + - s[6] * 33 + + (unsigned) s[0] * 3963737313 + + (unsigned) s[1] * 1291467969 + + (unsigned) s[2] * 39135393 + + (unsigned) s[3] * 1185921 + + (unsigned) s[4] * 35937 + + (unsigned) s[5] * 1089 + + (unsigned) s[6] * 33 + s[7]; s += 8; @@ -200,9 +210,9 @@ EXPORT unsigned str_calc_hash (const char * s) if (len >= 4) { h = h * 1185921 + - s[0] * 35937 + - s[1] * 1089 + - s[2] * 33 + + (unsigned) s[0] * 35937 + + (unsigned) s[1] * 1089 + + (unsigned) s[2] * 33 + s[3]; s += 4; @@ -415,6 +425,43 @@ EXPORT StringBuf filename_normalize (StringBuf && filename) return std::move (filename); } +/* note #1: recommended order is filename_contract(filename_normalize(f)) */ +/* note #2: currently assumes filename is UTF-8 (intended for display) */ +EXPORT StringBuf filename_contract (StringBuf && filename) +{ + /* replace home folder with '~' */ + const char * home = get_home_utf8 (); + int homelen = home ? strlen (home) : 0; + + if (homelen && ! strncmp (filename, home, homelen) && + (! filename[homelen] || IS_SEP (filename[homelen]))) + { + filename[0] = '~'; + filename.remove (1, homelen - 1); + } + + return std::move (filename); +} + +/* note #1: recommended order is filename_normalize(filename_expand(f)) */ +/* note #2: currently assumes filename is UTF-8 (intended for display) */ +EXPORT StringBuf filename_expand (StringBuf && filename) +{ + /* expand leading '~' */ + if (filename[0] == '~' && (! filename[1] || IS_SEP(filename[1]))) + { + const char * home = get_home_utf8 (); + + if (home && home[0]) + { + filename[0] = home[0]; + filename.insert (1, home + 1, -1); + } + } + + return std::move (filename); +} + EXPORT StringBuf filename_get_parent (const char * filename) { StringBuf buf = filename_normalize (str_copy (filename)); @@ -454,25 +501,14 @@ EXPORT StringBuf filename_build (const std::initializer_list<const char *> & ele for (const char * s : elems) { -#ifdef _WIN32 - if (set > str && set[-1] != '/' && set[-1] != '\\') + if (set > str && ! IS_SEP (set[-1])) { if (! left) throw std::bad_alloc (); - * set ++ = '\\'; + * set ++ = G_DIR_SEPARATOR; left --; } -#else - if (set > str && set[-1] != '/') - { - if (! left) - throw std::bad_alloc (); - - * set ++ = '/'; - left --; - } -#endif int len = strlen (s); if (len > left) @@ -547,6 +583,14 @@ EXPORT StringBuf uri_to_filename (const char * uri, bool use_locale) } #endif + /* if UTF-8 was requested, make sure the result is valid */ + if (! use_locale) + { + buf.steal (str_to_utf8 (std::move (buf))); + if (! buf) + return StringBuf (); + } + return filename_normalize (std::move (buf)); } @@ -564,20 +608,10 @@ EXPORT StringBuf uri_to_display (const char * uri) if (! buf) return str_copy (_("(character encoding error)")); - if (strncmp (buf, URI_PREFIX, URI_PREFIX_LEN)) - return buf; - - buf.remove (0, URI_PREFIX_LEN); - buf.steal (filename_normalize (std::move (buf))); - - const char * home = get_home_utf8 (); - int homelen = home ? strlen (home) : 0; - - if (homelen && ! strncmp (buf, home, homelen) && - (! buf[homelen] || buf[homelen] == G_DIR_SEPARATOR)) + if (! strncmp (buf, URI_PREFIX, URI_PREFIX_LEN)) { - buf[0] = '~'; - buf.remove (1, homelen - 1); + buf.remove (0, URI_PREFIX_LEN); + return filename_contract (filename_normalize (std::move (buf))); } return buf; @@ -655,7 +689,7 @@ EXPORT StringBuf uri_construct (const char * path, const char * reference) /* absolute filename */ #ifdef _WIN32 - if (path[0] && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) + if (path[0] && path[1] == ':' && IS_SEP (path[2])) #else if (path[0] == '/') #endif @@ -860,24 +894,44 @@ EXPORT StringBuf index_to_str_list (const Index<String> & index, const char * se * have an accuracy of 6 decimal places. */ -static int str_to_uint (const char * string) +static unsigned str_to_uint (const char * string, const char * * end = nullptr, + const char * stop = nullptr) { - int val = 0; - char c; - - while ((c = * string ++) && c >= '0' && c <= '9') + unsigned val = 0; + for (char c; string != stop && (c = * string) >= '0' && c <= '9'; string ++) val = val * 10 + (c - '0'); + if (end) + * end = string; + return val; } +static int digits_for (unsigned val) +{ + int digits = 1; + + for (; val >= 1000; val /= 1000) + digits += 3; + for (; val >= 10; val /= 10) + digits ++; + + return digits; +} + +static void uint_to_str (unsigned val, char * buf, int digits) +{ + for (char * rev = buf + digits; rev > buf; val /= 10) + * (-- rev) = '0' + val % 10; +} + EXPORT int str_to_int (const char * string) { bool neg = (string[0] == '-'); if (neg || string[0] == '+') string ++; - int val = str_to_uint (string); + unsigned val = str_to_uint (string); return neg ? -val : val; } @@ -887,14 +941,14 @@ EXPORT double str_to_double (const char * string) if (neg || string[0] == '+') string ++; - double val = str_to_uint (string); - const char * p = strchr (string, '.'); + const char * p; + double val = str_to_uint (string, & p); - if (p) + if (* (p ++) == '.') { - char buf[7] = "000000"; - memcpy (buf, p + 1, strlen_bounded (p + 1, 6)); - val += str_to_uint (buf) / 1000000.0; + const char * end; + double decimal = str_to_uint (p, & end, p + MAX_POW10); + val += decimal / int_pow10[end - p]; } return neg ? -val : val; @@ -903,26 +957,18 @@ EXPORT double str_to_double (const char * string) EXPORT StringBuf int_to_str (int val) { bool neg = (val < 0); - if (neg) - val = -val; + unsigned absval = neg ? -val : val; - char buf[16]; - char * rev = buf + sizeof buf; + int digits = digits_for (absval); + StringBuf buf ((neg ? 1 : 0) + digits); - while (rev > buf) - { - * (-- rev) = '0' + val % 10; - if (! (val /= 10)) - break; - } + char * set = buf; + if (neg) + * (set ++) = '-'; - if (neg && rev > buf) - * (-- rev) = '-'; + uint_to_str (absval, set, digits); - int len = buf + sizeof buf - rev; - StringBuf buf2 (len); - memcpy (buf2, rev, len); - return buf2; + return buf; } EXPORT StringBuf double_to_str (double val) @@ -931,8 +977,8 @@ EXPORT StringBuf double_to_str (double val) if (neg) val = -val; - int i = floor (val); - int f = round ((val - i) * 1000000); + unsigned i = floor (val); + unsigned f = round ((val - i) * 1000000); if (f == 1000000) { @@ -940,15 +986,26 @@ EXPORT StringBuf double_to_str (double val) f = 0; } - StringBuf buf = str_printf ("%s%d.%06d", neg ? "-" : "", i, f); + int decimals = f ? 6 : 0; + for (; decimals && ! (f % 10); f /= 10) + decimals --; + + int digits = digits_for (i); + StringBuf buf ((neg ? 1 : 0) + digits + (decimals ? 1 : 0) + decimals); + + char * set = buf; + if (neg) + * (set ++) = '-'; + + uint_to_str (i, set, digits); - char * c = buf + buf.len (); - while (c[-1] == '0') - c --; - if (c[-1] == '.') - c --; + if (decimals) + { + set += digits; + * (set ++) = '.'; + uint_to_str (f, set, decimals); + } - buf.resize (c - buf); return buf; } @@ -1001,11 +1058,11 @@ EXPORT StringBuf double_array_to_str (const double * array, int count) EXPORT StringBuf str_format_time (int64_t milliseconds) { int hours = milliseconds / 3600000; - int minutes = (milliseconds / 60000) % 60; + int minutes = milliseconds / 60000; int seconds = (milliseconds / 1000) % 60; - if (hours) - return str_printf ("%d:%02d:%02d", hours, minutes, seconds); + if (hours && aud_get_bool (nullptr, "show_hours")) + return str_printf ("%d:%02d:%02d", hours, minutes % 60, seconds); else { bool zero = aud_get_bool (nullptr, "leading_zero"); diff --git a/src/libaudcore/audstrings.h b/src/libaudcore/audstrings.h index de47590..679fe5f 100644 --- a/src/libaudcore/audstrings.h +++ b/src/libaudcore/audstrings.h @@ -67,6 +67,8 @@ StringBuf str_to_utf8 (const char * str, int len); // no "len = -1" to avoid amb StringBuf str_to_utf8 (StringBuf && str); StringBuf filename_normalize (StringBuf && filename); +StringBuf filename_contract (StringBuf && filename); +StringBuf filename_expand (StringBuf && filename); StringBuf filename_get_parent (const char * filename); StringBuf filename_get_base (const char * filename); StringBuf filename_build (const std::initializer_list<const char *> & elems); diff --git a/src/libaudcore/config.cc b/src/libaudcore/config.cc index 9db3008..2718a6b 100644 --- a/src/libaudcore/config.cc +++ b/src/libaudcore/config.cc @@ -69,8 +69,9 @@ static const char * const core_defaults[] = { "default_gain", "0", "enable_replay_gain", "TRUE", "enable_clipping_prevention", "TRUE", - "output_bit_depth", "16", + "output_bit_depth", "-1", "output_buffer_size", "500", + "record_stream", aud::numeric_string<(int) OutputStream::AfterReplayGain>::str, "replay_gain_album", "FALSE", "replay_gain_preamp", "0", "soft_clipping", "FALSE", @@ -94,6 +95,7 @@ static const char * const core_defaults[] = { #endif "generic_title_format", "${?artist:${artist} - }${?album:${album} - }${title}", "leading_zero", "FALSE", + "show_hours", "TRUE", "metadata_fallbacks", "TRUE", "metadata_on_play", "FALSE", "show_numbers_in_pl", "FALSE", @@ -134,7 +136,7 @@ struct SaveState { Index<ConfigItem> list; }; -static int item_compare (const ConfigItem & a, const ConfigItem & b, void *) +static int item_compare (const ConfigItem & a, const ConfigItem & b) { if (a.section == b.section) return strcmp (a.key, b.key); @@ -283,7 +285,7 @@ void config_save () SaveState state = SaveState (); config.iterate (add_to_save_list, & state); - state.list.sort (item_compare, nullptr); + state.list.sort (item_compare); StringBuf path = filename_to_uri (aud_get_path (AudPath::UserDir)); path.insert (-1, "/config"); diff --git a/src/libaudcore/cue-cache.cc b/src/libaudcore/cue-cache.cc new file mode 100644 index 0000000..b821173 --- /dev/null +++ b/src/libaudcore/cue-cache.cc @@ -0,0 +1,95 @@ +/* + * cue-cache.cc + * Copyright 2016 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "cue-cache.h" +#include "multihash.h" +#include "playlist-internal.h" + +#include <pthread.h> + +enum NodeState {NotLoaded, Loading, Loaded}; + +struct CueCacheNode { + Index<PlaylistAddItem> items; + NodeState state = NotLoaded; + int refcount = 0; +}; + +static SimpleHash<String, CueCacheNode> cache; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t cond = PTHREAD_COND_INITIALIZER; + +CueCacheRef::CueCacheRef (const char * filename) : + m_filename (filename) +{ + pthread_mutex_lock (& mutex); + + m_node = cache.lookup (m_filename); + if (! m_node) + m_node = cache.add (m_filename, CueCacheNode ()); + + m_node->refcount ++; + + pthread_mutex_unlock (& mutex); +} + +CueCacheRef::~CueCacheRef () +{ + pthread_mutex_lock (& mutex); + + m_node->refcount --; + if (! m_node->refcount) + cache.remove (m_filename); + + pthread_mutex_unlock (& mutex); +} + +const Index<PlaylistAddItem> & CueCacheRef::load () +{ + String title; // not used + pthread_mutex_lock (& mutex); + + switch (m_node->state) + { + case NotLoaded: + // load the cuesheet in this thread + m_node->state = Loading; + pthread_mutex_unlock (& mutex); + playlist_load (m_filename, title, m_node->items); + pthread_mutex_lock (& mutex); + + m_node->state = Loaded; + pthread_cond_broadcast (& cond); + break; + + case Loading: + // wait for cuesheet to load in another thread + while (m_node->state != Loaded) + pthread_cond_wait (& cond, & mutex); + + break; + + case Loaded: + // cuesheet already loaded + break; + } + + pthread_mutex_unlock (& mutex); + return m_node->items; +} diff --git a/src/libaudcore/cue-cache.h b/src/libaudcore/cue-cache.h new file mode 100644 index 0000000..84ab516 --- /dev/null +++ b/src/libaudcore/cue-cache.h @@ -0,0 +1,41 @@ +/* + * cue-cache.h + * Copyright 2016 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_CUE_CACHE_H +#define LIBAUDCORE_CUE_CACHE_H + +#include "index.h" +#include "tuple.h" + +struct CueCacheNode; + +class CueCacheRef +{ +public: + CueCacheRef (const char * filename); + ~CueCacheRef (); + + const Index<PlaylistAddItem> & load (); + +private: + String m_filename; + CueCacheNode * m_node; +}; + +#endif // LIBAUDCORE_CUE_CACHE_H diff --git a/src/libaudcore/drct.cc b/src/libaudcore/drct.cc index 0f957bd..d8a0589 100644 --- a/src/libaudcore/drct.cc +++ b/src/libaudcore/drct.cc @@ -199,19 +199,7 @@ static void add_list (Index<PlaylistAddItem> && items, int at, bool to_temp, boo if (to_temp) aud_playlist_set_active (aud_playlist_get_temporary ()); - int playlist = aud_playlist_get_active (); - - /* queue the new entries before deleting the old ones */ - /* this is to avoid triggering the --quit-after-play condition */ - aud_playlist_entry_insert_batch (playlist, at, std::move (items), play); - - if (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)); - } + aud_playlist_entry_insert_batch (aud_playlist_get_active (), at, std::move (items), play); } EXPORT void aud_drct_pl_add (const char * filename, int at) diff --git a/src/libaudcore/export.h b/src/libaudcore/export.h new file mode 100644 index 0000000..f91ed7c --- /dev/null +++ b/src/libaudcore/export.h @@ -0,0 +1,33 @@ +/* + * export.h + * Copyright 2016 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#ifndef LIBAUDCORE_EXPORT_H +#define LIBAUDCORE_EXPORT_H + +#ifdef _WIN32 + #ifdef LIBAUDCORE_BUILD + #define LIBAUDCORE_PUBLIC __declspec(dllexport) + #else + #define LIBAUDCORE_PUBLIC __declspec(dllimport) + #endif +#else + #define LIBAUDCORE_PUBLIC __attribute__ ((visibility ("default"))) +#endif + +#endif // LIBAUDCORE_EXPORT_H diff --git a/src/libaudcore/index.cc b/src/libaudcore/index.cc index 75aa559..84ca64d 100644 --- a/src/libaudcore/index.cc +++ b/src/libaudcore/index.cc @@ -1,6 +1,6 @@ /* * index.cc - * Copyright 2014 John Lindgren + * Copyright 2014-2016 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -42,6 +42,9 @@ static void do_erase (void * data, int len, aud::EraseFunc erase_func) EXPORT void IndexBase::clear (aud::EraseFunc erase_func) { + if (! m_data) + return; + __sync_sub_and_fetch (& misc_bytes_allocated, m_size); do_erase (m_data, m_len, erase_func); @@ -57,6 +60,9 @@ EXPORT void * IndexBase::insert (int pos, int len) assert (pos <= m_len); assert (len >= 0); + if (! len) + goto out; + if (pos < 0) pos = m_len; /* insert at end */ @@ -86,6 +92,7 @@ EXPORT void * IndexBase::insert (int pos, int len) memmove ((char *) m_data + pos + len, (char *) m_data + pos, m_len - pos); m_len += len; +out: return (char *) m_data + pos; } @@ -93,6 +100,9 @@ EXPORT void IndexBase::insert (int pos, int len, aud::FillFunc fill_func) { void * to = insert (pos, len); + if (! len) + return; + if (fill_func) fill_func (to, len); else @@ -103,6 +113,9 @@ EXPORT void IndexBase::insert (const void * from, int pos, int len, aud::CopyFun { void * to = insert (pos, len); + if (! len) + return; + if (copy_func) copy_func (from, to, len); else @@ -117,6 +130,9 @@ EXPORT void IndexBase::remove (int pos, int len, aud::EraseFunc erase_func) if (len < 0) len = m_len - pos; /* remove all following */ + if (! len) + return; + do_erase ((char *) m_data + pos, len, erase_func); memmove ((char *) m_data + pos, (char *) m_data + pos + len, m_len - pos - len); m_len -= len; @@ -130,6 +146,9 @@ EXPORT void IndexBase::erase (int pos, int len, aud::FillFunc fill_func, aud::Er if (len < 0) len = m_len - pos; /* erase all following */ + if (! len) + return; + do_erase ((char *) m_data + pos, len, erase_func); do_fill ((char *) m_data + pos, len, fill_func); } @@ -140,6 +159,9 @@ EXPORT void IndexBase::shift (int from, int to, int len, aud::FillFunc fill_func assert (from >= 0 && from + len <= m_len); assert (to >= 0 && to + len <= m_len); + if (! len) + return; + int erase_len = aud::min (len, abs (to - from)); if (to < from) @@ -165,6 +187,9 @@ EXPORT void IndexBase::move_from (IndexBase & b, int from, int to, int len, if (len < 0) len = b.m_len - from; /* copy all following */ + if (! len) + return; + if (expand) { assert (to <= m_len); @@ -192,5 +217,31 @@ EXPORT void IndexBase::move_from (IndexBase & b, int from, int to, int len, EXPORT void IndexBase::sort (CompareFunc compare, int elemsize, void * userdata) { + if (! m_len) + return; + + // since we require GLib >= 2.32, g_qsort_with_data performs a stable sort g_qsort_with_data (m_data, m_len / elemsize, elemsize, compare, userdata); } + +EXPORT int IndexBase::bsearch (const void * key, CompareFunc compare, + int elemsize, void * userdata) const +{ + int top = 0; + int bottom = m_len / elemsize; + + while (top < bottom) + { + int middle = top + (bottom - top) / 2; + int match = compare (key, (char *) m_data + middle * elemsize, userdata); + + if (match < 0) + bottom = middle; + else if (match > 0) + top = middle + 1; + else + return middle; + } + + return -1; +} diff --git a/src/libaudcore/index.h b/src/libaudcore/index.h index 56dbdc5..68957c3 100644 --- a/src/libaudcore/index.h +++ b/src/libaudcore/index.h @@ -1,6 +1,6 @@ /* * index.h - * Copyright 2014 John Lindgren + * Copyright 2014-2016 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -85,6 +85,7 @@ public: bool collapse, aud::FillFunc fill_func, aud::EraseFunc erase_func); void sort (CompareFunc compare, int elemsize, void * userdata); + int bsearch (const void * key, CompareFunc search, int elemsize, void * userdata) const; private: void * m_data; @@ -94,9 +95,15 @@ private: template<class T> class Index : private IndexBase { -public: - typedef int (* CompareFunc) (const T & a, const T & b, void * userdata); +private: + // provides C-style callback to generic comparison functor + template<class Key, class F> + struct WrapCompare { + static int run (const void * key, const void * val, void * func) + { return (* (F *) func) (* (const Key *) key, * (const T *) val); } + }; +public: constexpr Index () : IndexBase () {} @@ -164,6 +171,7 @@ public: return -1; } + // func(val) returns true to remove val, false to keep it template<class F> void remove_if (F func, bool clear_if_empty = false) { @@ -180,22 +188,15 @@ public: clear (); } - void sort (CompareFunc compare, void * userdata) - { - struct state_t { - CompareFunc compare; - void * userdata; - }; - - auto wrapper = [] (const void * a, const void * b, void * userdata) -> int - { - auto state = (const state_t *) userdata; - return state->compare (* (const T *) a, * (const T *) b, state->userdata); - }; + // compare(a, b) returns <0 if a<b, 0 if a=b, >0 if a>b + template<class F> + void sort (F compare) + { IndexBase::sort (WrapCompare<T, F>::run, sizeof (T), & compare); } - const state_t state = {compare, userdata}; - IndexBase::sort (wrapper, sizeof (T), (void *) & state); - } + // compare(key, val) returns <0 if key<val, 0 if key=val, >0 if key>val + template<class Key, class F> + int bsearch (const Key & key, F compare) + { return IndexBase::bsearch (& key, WrapCompare<Key, F>::run, sizeof (T), & compare); } // for use of Index as a raw data buffer // unlike insert(), does not zero-fill any added space diff --git a/src/libaudcore/inifile.h b/src/libaudcore/inifile.h index f9ee049..273d2c6 100644 --- a/src/libaudcore/inifile.h +++ b/src/libaudcore/inifile.h @@ -20,9 +20,11 @@ #ifndef LIBAUDCORE_INIFILE_H #define LIBAUDCORE_INIFILE_H +#include <libaudcore/export.h> + class VFSFile; -class IniParser +class LIBAUDCORE_PUBLIC IniParser { public: virtual ~IniParser () {} diff --git a/src/libaudcore/interface.cc b/src/libaudcore/interface.cc index bdea1ec..2d2db3d 100644 --- a/src/libaudcore/interface.cc +++ b/src/libaudcore/interface.cc @@ -115,6 +115,12 @@ EXPORT bool aud_ui_is_shown () return aud_get_bool (0, "show_interface"); } +EXPORT void aud_ui_startup_notify (const char * id) +{ + if (current_interface) + current_interface->startup_notify (id); +} + EXPORT void aud_ui_show_error (const char * message) { if (aud_get_headless_mode ()) diff --git a/src/libaudcore/interface.h b/src/libaudcore/interface.h index 43ccb38..b918a38 100644 --- a/src/libaudcore/interface.h +++ b/src/libaudcore/interface.h @@ -33,6 +33,7 @@ enum class AudMenuID { void aud_ui_show (bool show); bool aud_ui_is_shown (); +void aud_ui_startup_notify (const char * id); void aud_ui_show_error (const char * message); /* thread-safe */ void aud_ui_show_about_window (); diff --git a/src/libaudcore/internal.h b/src/libaudcore/internal.h index 9f05c25..534ba69 100644 --- a/src/libaudcore/internal.h +++ b/src/libaudcore/internal.h @@ -103,13 +103,6 @@ bool open_input_file (const char * filename, const char * mode, InputPlugin * ip, VFSFile & file, String * error = nullptr); InputPlugin * load_input_plugin (PluginHandle * decoder, String * error = nullptr); -/* internal versions of aud_file_* functions; - * these allow reuse of the same file handle during probing */ -PluginHandle * file_find_decoder (const char * filename, bool fast, - VFSFile & file, String * error = nullptr); -bool file_read_tag (const char * filename, PluginHandle * decoder, - VFSFile & file, Tuple * tuple, Index<char> * image, String * error = nullptr); - /* runtime.cc */ extern size_t misc_bytes_allocated; @@ -128,6 +121,10 @@ bool same_basename (const char * a, const char * b); const char * last_path_element (const char * path); void cut_path_element (char * path, int pos); +bool is_cuesheet_entry (const char * filename); +bool is_subtune (const char * filename); +StringBuf strip_subtune (const char * filename); + unsigned int32_hash (unsigned val); unsigned ptr_hash (const void * ptr); diff --git a/src/libaudcore/mainloop.h b/src/libaudcore/mainloop.h index 4d6ec59..dabf9e3 100644 --- a/src/libaudcore/mainloop.h +++ b/src/libaudcore/mainloop.h @@ -49,20 +49,17 @@ public: // true if a periodic timer is running // does not apply to one-time callbacks - bool running () + bool running () const { return _running; } constexpr QueuedFunc () = default; QueuedFunc (const QueuedFunc &) = delete; void operator= (const QueuedFunc &) = delete; - // added in Audacious 3.7 - // previously, all instances had to be declared static ~QueuedFunc () { stop (); } private: - int serial = 0; // no longer used, kept for ABI compatibility bool _running = false; void start (const QueuedFuncParams & params); diff --git a/src/libaudcore/objects.h b/src/libaudcore/objects.h index fd57f5e..4b98cc6 100644 --- a/src/libaudcore/objects.h +++ b/src/libaudcore/objects.h @@ -250,7 +250,7 @@ public: } // only allowed for top (or null) string - ~StringBuf (); + ~StringBuf () noexcept (false); // only allowed for top (or null) string void resize (int size); diff --git a/src/libaudcore/output.cc b/src/libaudcore/output.cc index 50e890f..4ac6ab4 100644 --- a/src/libaudcore/output.cc +++ b/src/libaudcore/output.cc @@ -25,6 +25,9 @@ #include <string.h> #include "equalizer.h" +#include "hook.h" +#include "i18n.h" +#include "interface.h" #include "internal.h" #include "plugin.h" #include "plugins.h" @@ -79,6 +82,8 @@ static pthread_cond_t cond_minor = PTHREAD_COND_INITIALIZER; static OutputPlugin * cop; /* current (primary) output plugin */ static OutputPlugin * sop; /* secondary output plugin */ +static OutputStream record_stream; + static int seek_time; static String in_filename; static Tuple in_tuple; @@ -93,13 +98,18 @@ static ReplayGainInfo gain_info; static Index<float> buffer1; static Index<char> buffer2; -static inline int get_format () +static inline int get_format (bool & automatic) { + automatic = false; + switch (aud_get_int (0, "output_bit_depth")) { case 16: return FMT_S16_NE; case 24: return FMT_S24_NE; case 32: return FMT_S32_NE; + + // return FMT_FLOAT for "auto" as well + case -1: automatic = true; default: return FMT_FLOAT; } } @@ -159,7 +169,8 @@ static void setup_output (bool new_input) if (! cop) return; - int format = get_format (); + bool automatic; + int format = get_format (automatic); AUDINFO ("Setup output, format %d, %d channels, %d Hz.\n", format, effect_channels, effect_rate); @@ -170,8 +181,25 @@ static void setup_output (bool new_input) cleanup_output (); cop->set_info (in_filename, in_tuple); - if (! cop->open_audio (format, effect_rate, effect_channels)) - return; + String error, tmp_error; + while (! cop->open_audio (format, effect_rate, effect_channels, tmp_error)) + { + /* display only the error from the first attempt */ + if (! automatic || format == FMT_FLOAT) + error = std::move (tmp_error); + + if (automatic && format == FMT_FLOAT) + format = FMT_S32_NE; + else if (automatic && format == FMT_S32_NE) + format = FMT_S16_NE; + else + { + aud_ui_show_error (error ? (const char *) error : _("Error opening output stream")); + return; + } + + AUDINFO ("Falling back to format %d.\n", format); + } s_output = true; @@ -195,20 +223,38 @@ static void setup_secondary (bool new_input) if (! sop) return; - if (s_secondary && in_channels == sec_channels && in_rate == sec_rate && + int rate, channels; + record_stream = (OutputStream) aud_get_int (0, "record_stream"); + + if (record_stream < OutputStream::AfterEffects) + { + rate = in_rate; + channels = in_channels; + } + else + { + rate = effect_rate; + channels = effect_channels; + } + + if (s_secondary && channels == sec_channels && rate == sec_rate && ! (new_input && sop->force_reopen)) return; cleanup_secondary (); sop->set_info (in_filename, in_tuple); - if (! sop->open_audio (FMT_FLOAT, in_rate, in_channels)) + String error; + if (! sop->open_audio (FMT_FLOAT, rate, channels, error)) + { + aud_ui_show_error (error ? (const char *) error : _("Error opening output stream")); return; + } s_secondary = true; - sec_channels = in_channels; - sec_rate = in_rate; + sec_channels = channels; + sec_rate = rate; } /* assumes LOCK_MINOR, s_output */ @@ -269,11 +315,16 @@ static void write_output (Index<float> & data) if (! data.len ()) return; + if (s_secondary && record_stream == OutputStream::AfterEffects) + write_secondary (data); + int out_time = aud::rescale<int64_t> (out_bytes_written, out_bytes_per_sec, 1000); vis_runner_pass_audio (out_time, data, out_channels, out_rate); eq_filter (data.begin (), data.len ()); + if (s_secondary && record_stream == OutputStream::AfterEqualizer) + write_secondary (data); if (aud_get_bool (0, "software_volume_control")) { @@ -339,9 +390,12 @@ static bool process_audio (const void * data, int size, int stop_time) else audio_from_int (data, in_format, buffer1.begin (), samples); + if (s_secondary && record_stream == OutputStream::AsDecoded) + write_secondary (buffer1); + apply_replay_gain (buffer1); - if (s_secondary) + if (s_secondary && record_stream == OutputStream::AfterReplayGain) write_secondary (buffer1); write_output (effect_process (buffer1)); @@ -701,3 +755,23 @@ bool output_plugin_set_secondary (PluginHandle * plugin) UNLOCK_MINOR; return (! plugin || sop); } + +static void record_stream_changed (void *, void *) +{ + LOCK_MINOR; + + if (s_input) + setup_secondary (false); + + UNLOCK_MINOR; +} + +void output_init () +{ + hook_associate ("set record_stream", record_stream_changed, nullptr); +} + +void output_cleanup () +{ + hook_dissociate ("set record_stream", record_stream_changed, nullptr); +} diff --git a/src/libaudcore/output.h b/src/libaudcore/output.h index 391d06a..a9fafee 100644 --- a/src/libaudcore/output.h +++ b/src/libaudcore/output.h @@ -26,6 +26,9 @@ class PluginHandle; class Tuple; +void output_init (); +void output_cleanup (); + bool output_open_audio (const String & filename, const Tuple & tuple, int format, int rate, int channels, int start_time); void output_set_tuple (const Tuple & tuple); diff --git a/src/libaudcore/playback.cc b/src/libaudcore/playback.cc index 1391990..b7e94a0 100644 --- a/src/libaudcore/playback.cc +++ b/src/libaudcore/playback.cc @@ -144,7 +144,7 @@ void playback_set_info (int entry, Tuple && tuple) if (! lock_if (in_sync)) return; - if (tuple && tuple != pb_info.tuple) + if (tuple.valid () && tuple != pb_info.tuple) { pb_info.tuple = std::move (tuple); @@ -294,10 +294,13 @@ static void run_playback () if (! lock_if (in_sync)) return; - pb_info.filename = std::move (dec.filename); + // for a cuesheet entry, determine the source filename + pb_info.filename = pb_info.tuple.get_str (Tuple::AudioFile); + if (! pb_info.filename) + pb_info.filename = std::move (dec.filename); // check that we have all the necessary data - if (! pb_info.filename || ! pb_info.tuple || ! dec.ip || + if (! pb_info.filename || ! pb_info.tuple.valid () || ! dec.ip || (! dec.ip->input_info.keys[InputKey::Scheme] && ! dec.file)) { pb_info.error = true; @@ -616,25 +619,6 @@ EXPORT int InputPlugin::check_seek () return seek; } -/* compatibility (non-virtual) implementation of InputPlugin::read_tag(). */ -EXPORT bool InputPlugin::default_read_tag (const char * filename, - VFSFile & file, Tuple * tuple, Index<char> * image) -{ - /* just call read_tuple() and read_image() */ - if (tuple) - { - if (! (* tuple = read_tuple (filename, file))) - return false; - if (image && file && file.fseek (0, VFS_SEEK_SET) != 0) - return true; /* true: tuple was read */ - } - - if (image) - * image = read_image (filename, file); - - return true; -} - // thread-safe EXPORT bool aud_drct_get_playing () { diff --git a/src/libaudcore/playlist-cache.cc b/src/libaudcore/playlist-cache.cc new file mode 100644 index 0000000..9711235 --- /dev/null +++ b/src/libaudcore/playlist-cache.cc @@ -0,0 +1,88 @@ +/* + * playlist-cache.h + * Copyright 2016 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +#include "playlist-internal.h" +#include "mainloop.h" +#include "multihash.h" + +#include <pthread.h> + +static SimpleHash<String, PlaylistAddItem> cache; +static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; +static QueuedFunc clear_timer; + +EXPORT void aud_playlist_cache_selected (int playlist) +{ + pthread_mutex_lock (& mutex); + + int entries = aud_playlist_entry_count (playlist); + + for (int i = 0; i < entries; i ++) + { + if (! aud_playlist_entry_get_selected (playlist, i)) + continue; + + String filename = aud_playlist_entry_get_filename (playlist, i); + Tuple tuple = aud_playlist_entry_get_tuple (playlist, i, Playlist::NoWait); + PluginHandle * decoder = aud_playlist_entry_get_decoder (playlist, i, Playlist::NoWait); + + if (tuple.valid () || decoder) + cache.add (filename, {filename, std::move (tuple), decoder}); + } + + clear_timer.queue (30000, (QueuedFunc::Func) playlist_cache_clear, nullptr); + + pthread_mutex_unlock (& mutex); +} + +void playlist_cache_load (Index<PlaylistAddItem> & items) +{ + pthread_mutex_lock (& mutex); + + if (! cache.n_items ()) + goto out; + + for (auto & item : items) + { + if (item.tuple.valid () && item.decoder) + continue; + + auto node = cache.lookup (item.filename); + if (! node) + continue; + + if (! item.tuple.valid () && node->tuple.valid ()) + item.tuple = node->tuple.ref (); + if (! item.decoder && node->decoder) + item.decoder = node->decoder; + } + +out: + pthread_mutex_unlock (& mutex); +} + +void playlist_cache_clear () +{ + pthread_mutex_lock (& mutex); + + cache.clear (); + clear_timer.stop (); + + pthread_mutex_unlock (& mutex); +} diff --git a/src/libaudcore/playlist-files.cc b/src/libaudcore/playlist-files.cc index 4fa9303..872e9ff 100644 --- a/src/libaudcore/playlist-files.cc +++ b/src/libaudcore/playlist-files.cc @@ -59,7 +59,7 @@ bool playlist_load (const char * filename, String & title, Index<PlaylistAddItem AUDINFO ("Trying playlist plugin %s.\n", aud_plugin_get_name (plugin)); plugin_found = true; - PlaylistPlugin * pp = (PlaylistPlugin *) aud_plugin_get_header (plugin); + auto pp = (PlaylistPlugin *) aud_plugin_get_header (plugin); if (! pp) continue; @@ -88,6 +88,11 @@ bool playlist_load (const char * filename, String & title, Index<PlaylistAddItem return false; } +// This procedure is only used when loading playlists from ~/.config/audacious; +// hence, it is drastically simpler than the full-featured routines in adder.cc. +// All support for adding folders, cuesheets, subtunes, etc. is omitted here. +// Additionally, in order to avoid heavy I/O at startup, failed entries are not +// rescanned; they can be rescanned later by refreshing the playlist. */ bool playlist_insert_playlist_raw (int list, int at, const char * filename) { String title; diff --git a/src/libaudcore/playlist-internal.h b/src/libaudcore/playlist-internal.h index f91b183..781cd4c 100644 --- a/src/libaudcore/playlist-internal.h +++ b/src/libaudcore/playlist-internal.h @@ -53,6 +53,10 @@ bool playlist_next_song (int playlist, bool repeat); DecodeInfo playback_entry_read (int serial); void playback_entry_set_tuple (int serial, Tuple && tuple); +/* playlist-cache.cc */ +void playlist_cache_load (Index<PlaylistAddItem> & items); +void playlist_cache_clear (); + /* playlist-files.cc */ bool playlist_load (const char * filename, String & title, Index<PlaylistAddItem> & items); bool playlist_insert_playlist_raw (int list, int at, const char * filename); diff --git a/src/libaudcore/playlist-utils.cc b/src/libaudcore/playlist-utils.cc index 8622c91..d1f8ffd 100644 --- a/src/libaudcore/playlist-utils.cc +++ b/src/libaudcore/playlist-utils.cc @@ -196,7 +196,7 @@ EXPORT void aud_playlist_remove_duplicates_by_scheme (int playlist, Playlist::So { Tuple current = aud_playlist_entry_get_tuple (playlist, count); - if (last && current && compare (last, current) == 0) + if (last.valid () && current.valid () && compare (last, current) == 0) aud_playlist_entry_set_selected (playlist, count, true); last = std::move (current); @@ -209,16 +209,16 @@ EXPORT void aud_playlist_remove_duplicates_by_scheme (int playlist, Playlist::So EXPORT void aud_playlist_remove_failed (int playlist) { int entries = aud_playlist_entry_count (playlist); - int count; aud_playlist_select_all (playlist, false); - for (count = 0; count < entries; count ++) + for (int count = 0; count < entries; count ++) { String filename = aud_playlist_entry_get_filename (playlist, count); - /* vfs_file_test() only works for file:// URIs currently */ - if (! strncmp (filename, "file://", 7) && ! VFSFile::test_file (filename, VFS_EXISTS)) + /* use VFS_NO_ACCESS since VFS_EXISTS doesn't distinguish between + * inaccessible files and URI schemes that don't support file_test() */ + if (VFSFile::test_file (filename, VFS_NO_ACCESS)) aud_playlist_entry_set_selected (playlist, count, true); } @@ -342,7 +342,7 @@ static void save_playlists_real () if (playlist_get_modified (i)) { StringBuf path = filename_build ({folder, name}); - aud_playlist_save (i, filename_to_uri (path), Playlist::Nothing); + aud_playlist_save (i, filename_to_uri (path), Playlist::NoWait); playlist_set_modified (i, false); } diff --git a/src/libaudcore/playlist.cc b/src/libaudcore/playlist.cc index a2b413c..9bfc718 100644 --- a/src/libaudcore/playlist.cc +++ b/src/libaudcore/playlist.cc @@ -112,7 +112,6 @@ struct Entry { void format (); void set_tuple (Tuple && new_tuple); - void set_failed (const String & new_error); String filename; PluginHandle * decoder; @@ -217,16 +216,17 @@ void Entry::format () void Entry::set_tuple (Tuple && new_tuple) { - /* Hack: We cannot refresh segmented entries (since their info is read from - * the cue sheet when it is first loaded), so leave them alone. -jlindgren */ - if (tuple.get_value_type (Tuple::StartTime) == Tuple::Int) + /* Since 3.8, cuesheet entries are handled differently. The entry filename + * points to the .cue file, and the path to the actual audio file is stored + * in the Tuple::AudioFile. If Tuple::AudioFile is not set, then assume + * that the playlist was created by an older version of Audacious, and + * revert to the former behavior (don't refresh this entry). */ + if (tuple.is_set (Tuple::StartTime) && ! tuple.is_set (Tuple::AudioFile)) return; - scanned = (bool) new_tuple; - failed = false; error = String (); - if (! new_tuple) + if (! new_tuple.valid ()) new_tuple.set_filename (filename); length = aud::max (0, new_tuple.get_int (Tuple::Length)); @@ -248,21 +248,12 @@ void PlaylistData::set_entry_tuple (Entry * entry, Tuple && tuple) selected_length += entry->length; } -void Entry::set_failed (const String & new_error) -{ - scanned = true; - failed = true; - error = new_error; -} - Entry::Entry (PlaylistAddItem && item) : filename (item.filename), decoder (item.decoder), number (-1), length (0), shuffle_num (0), - scanned (false), - failed (false), selected (false), queued (false) { @@ -351,14 +342,6 @@ static void update (void *) hook_call ("playlist update", aud::to_ptr (level)); } -static void send_playback_info (Entry * entry) -{ - // if the entry was not scanned or failed to scan, we must still call - // playback_set_info() in order to update the entry number - Tuple tuple = (entry->scanned && ! entry->failed) ? entry->tuple.ref () : Tuple (); - playback_set_info (entry->number, std::move (tuple)); -} - static void queue_update (UpdateLevel level, PlaylistData * p, int at, int count, int flags = 0) { if (p) @@ -369,7 +352,7 @@ static void queue_update (UpdateLevel level, PlaylistData * p, int at, int count if (level >= Metadata) { if (p == playing_playlist && p->position) - send_playback_info (p->position); + playback_set_info (p->position->number, p->position->tuple.ref ()); p->modified = true; } @@ -497,12 +480,15 @@ static ScanItem * scan_list_find_request (ScanRequest * request) static void scan_queue_entry (PlaylistData * playlist, Entry * entry, bool for_playback = false) { int flags = 0; - if (! entry->scanned || entry->failed) + if (! entry->tuple.valid ()) flags |= SCAN_TUPLE; if (for_playback) flags |= (SCAN_IMAGE | SCAN_FILE); - auto request = new ScanRequest (entry->filename, flags, scan_finish, entry->decoder); + /* scanner uses Tuple::AudioFile from existing tuple, if valid */ + auto request = new ScanRequest (entry->filename, flags, scan_finish, + entry->decoder, (flags & SCAN_TUPLE) ? Tuple () : entry->tuple.ref ()); + scan_list.append (new ScanItem (playlist, entry, request, for_playback)); /* playback entry will be scanned by the playback thread */ @@ -558,7 +544,8 @@ static bool scan_queue_next_entry () Entry * entry = playlist->entries[scan_row ++].get (); // blacklist stdin - if (! entry->scanned && ! scan_list_find_entry (entry) && + if (entry->tuple.state () == Tuple::Initial && + ! scan_list_find_entry (entry) && strncmp (entry->filename, "stdin://", 8)) { scan_queue_entry (playlist, entry); @@ -611,14 +598,20 @@ static void scan_finish (ScanRequest * request) if (! entry->decoder) entry->decoder = request->decoder; - if ((! entry->scanned || entry->failed) && request->tuple) + if (! entry->tuple.valid () && request->tuple.valid ()) { playlist->set_entry_tuple (entry, std::move (request->tuple)); queue_update (Metadata, playlist, entry->number, 1, DelayedUpdate); } - if (! entry->decoder || ! entry->scanned) - entry->set_failed (request->error); + if (! entry->decoder || ! entry->tuple.valid ()) + entry->error = request->error; + + if (entry->tuple.state () == Tuple::Initial) + { + entry->tuple.set_state (Tuple::Failed); + queue_update (Metadata, playlist, entry->number, 1, DelayedUpdate); + } delete item; @@ -690,8 +683,7 @@ static Entry * get_entry (int playlist_num, int entry_num, return entry; // check whether requested data (decoder and/or tuple) has been read - if ((! need_decoder || entry->decoder) && - (! need_tuple || (entry->scanned && ! entry->failed))) + if ((! need_decoder || entry->decoder) && (! need_tuple || entry->tuple.valid ())) return entry; // start scan if not already running ... @@ -717,6 +709,8 @@ static void start_playback (int seek_time, bool pause) playback_play (seek_time, pause); + // playback always begins with a rescan of the current entry in order to + // open the file, ensure a valid tuple, and read album art scan_cancel (playing_playlist->position); scan_queue_entry (playing_playlist, playing_playlist->position, true); } @@ -752,6 +746,7 @@ void playlist_init () hook_associate ("set metadata_on_play", (HookFunction) playlist_trigger_scan, nullptr); hook_associate ("set generic_title_format", (HookFunction) playlist_reformat_titles, nullptr); hook_associate ("set leading_zero", (HookFunction) playlist_reformat_titles, nullptr); + hook_associate ("set show_hours", (HookFunction) playlist_reformat_titles, nullptr); hook_associate ("set metadata_fallbacks", (HookFunction) playlist_reformat_titles, nullptr); hook_associate ("set show_numbers_in_pl", (HookFunction) playlist_reformat_titles, nullptr); } @@ -772,9 +767,12 @@ void playlist_end () hook_dissociate ("set metadata_on_play", (HookFunction) playlist_trigger_scan); hook_dissociate ("set generic_title_format", (HookFunction) playlist_reformat_titles); hook_dissociate ("set leading_zero", (HookFunction) playlist_reformat_titles); + hook_dissociate ("set show_hours", (HookFunction) playlist_reformat_titles); hook_dissociate ("set metadata_fallbacks", (HookFunction) playlist_reformat_titles); hook_dissociate ("set show_numbers_in_pl", (HookFunction) playlist_reformat_titles); + playlist_cache_clear (); + ENTER; /* playback should already be stopped */ @@ -1239,9 +1237,7 @@ EXPORT PluginHandle * aud_playlist_entry_get_decoder (int playlist_num, { ENTER; - const bool wait = (mode == Wait || mode == WaitGuess); - - Entry * entry = get_entry (playlist_num, entry_num, wait, false); + Entry * entry = get_entry (playlist_num, entry_num, (mode == Wait), false); PluginHandle * decoder = entry ? entry->decoder : nullptr; if (error) @@ -1255,14 +1251,8 @@ EXPORT Tuple aud_playlist_entry_get_tuple (int playlist_num, int entry_num, { ENTER; - const bool wait = (mode == Wait || mode == WaitGuess); - const bool guess = (mode == Guess || mode == WaitGuess); - - Entry * entry = get_entry (playlist_num, entry_num, false, wait); - - Tuple tuple; - if (entry && ((entry->scanned && ! entry->failed) || guess)) - tuple = entry->tuple.ref (); + Entry * entry = get_entry (playlist_num, entry_num, false, (mode == Wait)); + Tuple tuple = entry ? entry->tuple.ref () : Tuple (); if (error) * error = entry ? entry->error : String (); @@ -1660,27 +1650,19 @@ struct CompareData { PlaylistTupleCompareFunc tuple_compare; }; -static int compare_cb (const SmartPtr<Entry> & a, const SmartPtr<Entry> & b, void * _data) +static void sort_entries (Index<SmartPtr<Entry>> & entries, CompareData * data) { - CompareData * data = (CompareData *) _data; - - int diff = 0; - - if (data->filename_compare) - diff = data->filename_compare (a->filename, b->filename); - else if (data->tuple_compare) - diff = data->tuple_compare (a->tuple, b->tuple); - - if (diff) - return diff; - - /* preserve order of "equal" entries */ - return a->number - b->number; + entries.sort ([data] (const SmartPtr<Entry> & a, const SmartPtr<Entry> & b) { + if (data->filename_compare) + return data->filename_compare (a->filename, b->filename); + else + return data->tuple_compare (a->tuple, b->tuple); + }); } static void sort (PlaylistData * playlist, CompareData * data) { - playlist->entries.sort (compare_cb, data); + sort_entries (playlist->entries, data); number_entries (playlist, 0, playlist->entries.len ()); queue_update (Structure, playlist, 0, playlist->entries.len ()); @@ -1698,7 +1680,7 @@ static void sort_selected (PlaylistData * playlist, CompareData * data) selected.append (std::move (entry)); } - selected.sort (compare_cb, data); + sort_entries (selected, data); int i = 0; for (auto & entry : playlist->entries) @@ -1718,7 +1700,7 @@ static bool entries_are_scanned (PlaylistData * playlist, bool selected) if (selected && ! entry->selected) continue; - if (! entry->scanned) + if (entry->tuple.state () == Tuple::Initial) { aud_ui_show_error (_("The playlist cannot be sorted because " "metadata scanning is still in progress (or has been disabled).")); @@ -2231,12 +2213,12 @@ DecodeInfo playback_entry_read (int serial) item->handled_by_playback = true; LEAVE; - scanner_run (request); + request->run (); ENTER; if ((entry = get_playback_entry (serial))) { - send_playback_info (entry); + playback_set_info (entry->number, entry->tuple.ref ()); art_cache_current (entry->filename, std::move (request->image_data), std::move (request->image_file)); @@ -2258,7 +2240,8 @@ void playback_entry_set_tuple (int serial, Tuple && tuple) ENTER; Entry * entry = get_playback_entry (serial); - if (entry) + /* don't update cuesheet entries with stream metadata */ + if (entry && ! entry->tuple.is_set (Tuple::StartTime)) { playing_playlist->set_entry_tuple (entry, std::move (tuple)); queue_update (Metadata, playing_playlist, entry->number, 1); diff --git a/src/libaudcore/playlist.h b/src/libaudcore/playlist.h index 36e1263..ef63a28 100644 --- a/src/libaudcore/playlist.h +++ b/src/libaudcore/playlist.h @@ -70,10 +70,8 @@ enum SortType { /* Possible behaviors for playlist_entry_get_{decoder, tuple}. */ enum GetMode { - Nothing, // immediately return nullptr or Tuple() if not yet scanned - Guess, // immediately return a best guess if not yet scanned - Wait, // wait for the entry to be scanned; return nullptr or Tuple() on failure - WaitGuess // wait for the entry to be scanned; return a best guess on failure + NoWait, // non-blocking call; returned tuple will be in Initial state if not yet scanned + Wait // blocking call; returned tuple will be either Valid or Failed }; /* Format descriptor returned by playlist_save_formats() */ @@ -205,14 +203,13 @@ String aud_playlist_entry_get_filename (int playlist, int entry); * or if the entry has not yet been scanned, returns nullptr according to * <mode>. On error, an error message is optionally returned. */ PluginHandle * aud_playlist_entry_get_decoder (int playlist, int entry, - Playlist::GetMode mode = Playlist::WaitGuess, String * error = nullptr); + Playlist::GetMode mode = Playlist::Wait, String * error = nullptr); -/* Returns the tuple associated with an entry. On error, or if the entry has - * not yet been scanned, returns either a blank tuple or a tuple filled with - * "best guess" values, according to <mode>. On error, an error message is - * optionally returned. */ +/* Returns the tuple associated with an entry. The state of the returned tuple + * may indicate that the entry has not yet been scanned, or an error occurred, + * according to <mode>. On error, an error message is optionally returned. */ Tuple aud_playlist_entry_get_tuple (int playlist, int entry, - Playlist::GetMode mode = Playlist::WaitGuess, String * error = nullptr); + Playlist::GetMode mode = Playlist::Wait, String * error = nullptr); /* Moves the playback position to the beginning of the entry at <position>. If * <position> is -1, unsets the playback position. If <playlist> is the @@ -371,8 +368,7 @@ void aud_playlist_sort_selected_by_scheme (int playlist, Playlist::SortType sche void aud_playlist_remove_duplicates_by_scheme (int playlist, Playlist::SortType scheme); /* Removes all entries referring to unavailable files in a playlist. ("Remove - * failed" is something of a misnomer for the current behavior.) As currently - * implemented, only works for file:// URIs. */ + * failed" is something of a misnomer for the current behavior.) */ void aud_playlist_remove_failed (int playlist); /* Selects all the entries in a playlist that match regular expressions stored @@ -381,6 +377,10 @@ void aud_playlist_remove_failed (int playlist); * create a blank tuple and set its title field to "^A". */ void aud_playlist_select_by_patterns (int playlist, const Tuple & patterns); +/* Saves metadata for the selected entries in a playlist to an internal cache, + * which is used to speed up adding these entries to another playlist. */ +void aud_playlist_cache_selected (int playlist); + /* Returns true if <filename> refers to a playlist file. */ bool aud_filename_is_playlist (const char * filename); diff --git a/src/libaudcore/plugin-load.cc b/src/libaudcore/plugin-load.cc index 3117c6f..c249fea 100644 --- a/src/libaudcore/plugin-load.cc +++ b/src/libaudcore/plugin-load.cc @@ -50,15 +50,15 @@ struct LoadedModule { static Index<LoadedModule> loaded_modules; -bool plugin_check_flags (int version) +bool plugin_check_flags (int flags) { switch (aud_get_mainloop_type ()) { - case MainloopType::GLib: version &= ~_AUD_PLUGIN_GLIB_ONLY; break; - case MainloopType::Qt: version &= ~_AUD_PLUGIN_QT_ONLY; break; + case MainloopType::GLib: flags &= ~PluginGLibOnly; break; + case MainloopType::Qt: flags &= ~PluginQtOnly; break; } - return ! (version & 0xffff0000); + return ! flags; } Plugin * plugin_load (const char * filename) @@ -84,16 +84,15 @@ Plugin * plugin_load (const char * filename) return nullptr; } - /* flags are stored in high 16 bits of version field */ - if ((header->version & 0xffff) < _AUD_PLUGIN_VERSION_MIN || - (header->version & 0xffff) > _AUD_PLUGIN_VERSION) + if (header->version < _AUD_PLUGIN_VERSION_MIN || + header->version > _AUD_PLUGIN_VERSION) { AUDERR ("%s is not compatible with this version of Audacious.\n", filename); g_module_close (module); return nullptr; } - if (plugin_check_flags (header->version) && + if (plugin_check_flags (header->info.flags) && (header->type == PluginType::Transport || header->type == PluginType::Playlist || header->type == PluginType::Input || @@ -114,7 +113,7 @@ Plugin * plugin_load (const char * filename) static void plugin_unload (LoadedModule & loaded) { - if (plugin_check_flags (loaded.header->version) && + if (plugin_check_flags (loaded.header->info.flags) && (loaded.header->type == PluginType::Transport || loaded.header->type == PluginType::Playlist || loaded.header->type == PluginType::Input || diff --git a/src/libaudcore/plugin-registry.cc b/src/libaudcore/plugin-registry.cc index 88c6061..00114a9 100644 --- a/src/libaudcore/plugin-registry.cc +++ b/src/libaudcore/plugin-registry.cc @@ -35,7 +35,7 @@ /* Increment this when the format of the plugin-registry file changes. * Add 10 if the format changes in a way that will break parse_plugins_fallback(). */ -#define FORMAT 10 +#define FORMAT 11 /* Oldest file format supported by parse_plugins_fallback() */ #define MIN_FORMAT 2 // "enabled" flag was added in Audacious 2.4 @@ -50,7 +50,7 @@ class PluginHandle public: String basename, path; bool loaded; - int timestamp, version; + int timestamp, version, flags; PluginType type; Plugin * header; String name, domain; @@ -71,12 +71,13 @@ public: int has_subtunes, writes_tag; PluginHandle (const char * basename, const char * path, bool loaded, - int timestamp, int version, PluginType type, Plugin * header) : + int timestamp, int version, int flags, PluginType type, Plugin * header) : basename (basename), path (path), loaded (loaded), timestamp (timestamp), version (version), + flags (flags), type (type), header (header), priority (0), @@ -167,6 +168,7 @@ static void plugin_save (PluginHandle * plugin, FILE * handle) fprintf (handle, "%s %s\n", plugin_type_names[plugin->type], (const char *) plugin->path); fprintf (handle, "stamp %d\n", plugin->timestamp); fprintf (handle, "version %d\n", plugin->version); + fprintf (handle, "flags %d\n", plugin->flags); fprintf (handle, "name %s\n", (const char *) plugin->name); if (plugin->domain) @@ -319,11 +321,15 @@ static bool plugin_parse (FILE * handle) parse_next (handle); - int version = 0; + int version = 0, flags = 0; if (parse_integer ("version", & version)) parse_next (handle); + if (parse_integer ("flags", & flags)) + parse_next (handle); + + auto plugin = new PluginHandle (basename, String (), false, timestamp, + version, flags, type, nullptr); - auto plugin = new PluginHandle (basename, String (), false, timestamp, version, type, nullptr); plugins[type].append (plugin); plugin->name = parse_string ("name"); @@ -393,7 +399,7 @@ static void parse_plugins_fallback (FILE * handle) return; // setting timestamp to zero forces a rescan - auto plugin = new PluginHandle (basename, String (), false, 0, 0, type, nullptr); + auto plugin = new PluginHandle (basename, String (), false, 0, 0, 0, type, nullptr); plugins[type].append (plugin); plugin->enabled = (PluginEnabled) enabled; } @@ -425,7 +431,7 @@ ERR: fclose (handle); } -static int plugin_compare (PluginHandle * const & a, PluginHandle * const & b, void *) +static int plugin_compare (PluginHandle * const & a, PluginHandle * const & b) { if (a->type < b->type) return -1; @@ -457,7 +463,7 @@ void plugin_registry_prune () auto check_incompatible = [] (PluginHandle * plugin) { - if (plugin_check_flags (plugin->version)) + if (plugin_check_flags (plugin->flags)) return false; AUDINFO ("Incompatible plugin flags: %s\n", (const char *) plugin->basename); @@ -467,7 +473,7 @@ void plugin_registry_prune () for (auto type : aud::range<PluginType> ()) { plugins[type].remove_if (check_not_found); - plugins[type].sort (plugin_compare, nullptr); + plugins[type].sort (plugin_compare); compatible[type].insert (plugins[type].begin (), 0, plugins[type].len ()); compatible[type].remove_if (check_incompatible); } @@ -499,6 +505,7 @@ static void plugin_get_info (PluginHandle * plugin, bool is_new) Plugin * header = plugin->header; plugin->version = header->version; + plugin->flags = header->info.flags; plugin->name = String (header->info.name); plugin->domain = String (header->info.domain); plugin->has_about = (bool) header->info.about; @@ -590,7 +597,7 @@ void plugin_register (const char * path, int timestamp) return; plugin = new PluginHandle (basename, path, true, timestamp, - header->version, header->type, header); + header->version, header->info.flags, header->type, header); plugins[plugin->type].append (plugin); plugin_get_info (plugin, true); diff --git a/src/libaudcore/plugin.h b/src/libaudcore/plugin.h index 66077ad..65d0428 100644 --- a/src/libaudcore/plugin.h +++ b/src/libaudcore/plugin.h @@ -22,6 +22,7 @@ #define LIBAUDCORE_PLUGIN_H #include <libaudcore/audio.h> +#include <libaudcore/export.h> #include <libaudcore/plugins.h> #include <libaudcore/tuple.h> #include <libaudcore/visualizer.h> @@ -46,12 +47,8 @@ struct PluginPreferences; * the API tables), increment _AUD_PLUGIN_VERSION *and* set * _AUD_PLUGIN_VERSION_MIN to the same value. */ -#define _AUD_PLUGIN_VERSION_MIN 46 /* 3.6-devel */ -#define _AUD_PLUGIN_VERSION 47 /* 3.7-devel */ - -/* compatibility flags ORed into the version field */ -#define _AUD_PLUGIN_GLIB_ONLY 0x10000 /* plugin requires GLib mainloop */ -#define _AUD_PLUGIN_QT_ONLY 0x20000 /* plugin requires Qt mainloop */ +#define _AUD_PLUGIN_VERSION_MIN 48 /* 3.8-devel */ +#define _AUD_PLUGIN_VERSION 48 /* 3.8-devel */ /* A NOTE ON THREADS * @@ -99,14 +96,22 @@ struct PluginPreferences; * For the time being, aud_plugin_send_message() should only be called from the * program's main thread. */ -struct PluginInfo { +/* plugin flags */ +enum { + PluginGLibOnly = 0x1, // plugin requires GLib main loop + PluginQtOnly = 0x2 // plugin requires Qt main loop +}; + +struct PluginInfo +{ const char * name; const char * domain; // for gettext const char * about; const PluginPreferences * prefs; + int flags; }; -class Plugin +class LIBAUDCORE_PUBLIC Plugin { public: constexpr Plugin (PluginType type, PluginInfo info) : @@ -114,14 +119,7 @@ public: info (info) {} const int magic = _AUD_PLUGIN_MAGIC; - const int version = _AUD_PLUGIN_VERSION -#ifdef AUD_PLUGIN_GLIB_ONLY - | _AUD_PLUGIN_GLIB_ONLY -#endif -#ifdef AUD_PLUGIN_QT_ONLY - | _AUD_PLUGIN_QT_ONLY -#endif - ; + const int version = _AUD_PLUGIN_VERSION; const PluginType type; const PluginInfo info; @@ -132,7 +130,7 @@ public: virtual int take_message (const char * code, const void * data, int size) { return -1; } }; -class TransportPlugin : public Plugin +class LIBAUDCORE_PUBLIC TransportPlugin : public Plugin { public: constexpr TransportPlugin (const PluginInfo info, @@ -143,11 +141,16 @@ public: /* supported URI schemes (without "://") */ const ArrayRef<const char *> schemes; - /* fopen() implementation */ virtual VFSImpl * fopen (const char * filename, const char * mode, String & error) = 0; + + virtual VFSFileTest test_file (const char * filename, VFSFileTest test, String & error) + { return VFSFileTest (0); } + + virtual Index<String> read_folder (const char * filename, String & error) + { return Index<String> (); } }; -class PlaylistPlugin : public Plugin +class LIBAUDCORE_PUBLIC PlaylistPlugin : public Plugin { public: constexpr PlaylistPlugin (const PluginInfo info, @@ -177,7 +180,7 @@ public: const Index<PlaylistAddItem> & items) { return false; } }; -class OutputPlugin : public Plugin +class LIBAUDCORE_PUBLIC OutputPlugin : public Plugin { public: constexpr OutputPlugin (const PluginInfo info, int priority, bool force_reopen = false) : @@ -206,7 +209,7 @@ public: /* Begins playback of a PCM stream. <format> is one of the FMT_* * enumeration values defined in libaudcore/audio.h. Returns true on * success. */ - virtual bool open_audio (int format, int rate, int chans) = 0; + virtual bool open_audio (int format, int rate, int chans, String & error) = 0; /* Ends playback. Any buffered audio data is discarded. */ virtual void close_audio () = 0; @@ -237,7 +240,7 @@ public: virtual void flush () = 0; }; -class EffectPlugin : public Plugin +class LIBAUDCORE_PUBLIC EffectPlugin : public Plugin { public: constexpr EffectPlugin (const PluginInfo info, int order, bool preserves_format) : @@ -294,7 +297,7 @@ enum class InputKey { count }; -class InputPlugin : public Plugin +class LIBAUDCORE_PUBLIC InputPlugin : public Plugin { public: enum { @@ -368,9 +371,13 @@ public: /* Returns true if the plugin can handle the file. */ virtual bool is_our_file (const char * filename, VFSFile & file) = 0; - /* Reads metadata from the file. Optional if the plugin implements read_tag(). */ - virtual Tuple read_tuple (const char * filename, VFSFile & file) - { return Tuple(); } + /* Reads metadata and album art (if requested and available) from the file. + * The filename fields of the tuple are already set before the function is + * called. If album art is not needed, <image> will be nullptr. The return + * value should be true if <tuple> was successfully read, regardless of + * whether album art was read. */ + virtual bool read_tag (const char * filename, VFSFile & file, Tuple & tuple, + Index<char> * image) = 0; /* Plays the file. Returns false on error. Also see input-api.h. */ virtual bool play (const char * filename, VFSFile & file) = 0; @@ -379,29 +386,12 @@ public: virtual bool write_tuple (const char * filename, VFSFile & file, const Tuple & tuple) { return false; } - /* Optional. Reads an album art image (JPEG or PNG data) from the file. - * Returns an empty buffer on error. */ - virtual Index<char> read_image (const char * filename, VFSFile & file) - { return Index<char> (); } - /* Optional. Displays a window showing info about the file. In general, * this function should be avoided since Audacious already provides a file * info window. */ virtual bool file_info_box (const char * filename, VFSFile & file) { return false; } - /* Optional. Reads metadata and/or an album art from the file. - * Providing this function is encouraged over providing a separate - * read_tuple() and read_image(). The filename fields of the tuple - * (if not null) are already set before the function is called. */ - virtual bool read_tag (const char * filename, VFSFile & file, Tuple * tuple, - Index<char> * image) - { return default_read_tag (filename, file, tuple, image); } - - /* compatibility (non-virtual) implementation of read_tag(); do not use. */ - bool default_read_tag (const char * filename, VFSFile & file, Tuple * tuple, - Index<char> * image); - protected: /* Prepares the output system for playback in the specified format. Also * triggers the "playback ready" hook. Hence, if you call set_replay_gain, @@ -441,7 +431,7 @@ protected: static int check_seek (); }; -class DockablePlugin : public Plugin +class LIBAUDCORE_PUBLIC DockablePlugin : public Plugin { public: constexpr DockablePlugin (PluginType type, PluginInfo info) : @@ -454,7 +444,7 @@ public: virtual void * get_qt_widget () { return nullptr; } }; -class GeneralPlugin : public DockablePlugin +class LIBAUDCORE_PUBLIC GeneralPlugin : public DockablePlugin { public: constexpr GeneralPlugin (PluginInfo info, bool enabled_by_default) : @@ -464,7 +454,7 @@ public: const bool enabled_by_default; }; -class VisPlugin : public DockablePlugin, public Visualizer +class LIBAUDCORE_PUBLIC VisPlugin : public DockablePlugin, public Visualizer { public: constexpr VisPlugin (PluginInfo info, int type_mask) : @@ -472,7 +462,7 @@ public: Visualizer (type_mask) {} }; -class IfacePlugin : public Plugin +class LIBAUDCORE_PUBLIC IfacePlugin : public Plugin { public: constexpr IfacePlugin (PluginInfo info) : @@ -492,6 +482,8 @@ public: virtual void hide_prefs_window () = 0; virtual void plugin_menu_add (AudMenuID id, void func (), const char * name, const char * icon) = 0; virtual void plugin_menu_remove (AudMenuID id, void func ()) = 0; + + virtual void startup_notify (const char * id) {} }; #endif diff --git a/src/libaudcore/plugins-internal.h b/src/libaudcore/plugins-internal.h index c03e1e1..ef293d5 100644 --- a/src/libaudcore/plugins-internal.h +++ b/src/libaudcore/plugins-internal.h @@ -43,7 +43,7 @@ bool plugin_enable_secondary (PluginHandle * plugin, bool enable); /* plugin-load.c */ void plugin_system_init (); void plugin_system_cleanup (); -bool plugin_check_flags (int version); +bool plugin_check_flags (int flags); Plugin * plugin_load (const char * path); /* plugin-registry.c */ diff --git a/src/libaudcore/preferences.h b/src/libaudcore/preferences.h index 750679c..8946b01 100644 --- a/src/libaudcore/preferences.h +++ b/src/libaudcore/preferences.h @@ -24,6 +24,11 @@ struct PreferencesWidget; +enum class FileSelectMode { + File, + Folder +}; + struct ComboItem { const char * label; const char * str; @@ -66,6 +71,10 @@ struct WidgetVEntry { bool password; }; +struct WidgetVFileEntry { + FileSelectMode mode; +}; + struct WidgetVCombo { /* static init */ ArrayRef<ComboItem> elems; @@ -101,6 +110,7 @@ union WidgetVariant { WidgetVTable table; WidgetVFonts font_btn; WidgetVEntry entry; + WidgetVFileEntry file_entry; WidgetVCombo combo; WidgetVBox box; WidgetVNotebook notebook; @@ -117,6 +127,7 @@ union WidgetVariant { constexpr WidgetVariant (WidgetVTable table) : table (table) {} constexpr WidgetVariant (WidgetVFonts fonts) : font_btn (fonts) {} constexpr WidgetVariant (WidgetVEntry entry) : entry (entry) {} + constexpr WidgetVariant (WidgetVFileEntry file_entry) : file_entry (file_entry) {} constexpr WidgetVariant (WidgetVCombo combo) : combo (combo) {} constexpr WidgetVariant (WidgetVBox box) : box (box) {} constexpr WidgetVariant (WidgetVNotebook notebook) : notebook (notebook) {} @@ -209,6 +220,7 @@ struct PreferencesWidget RadioButton, SpinButton, Entry, + FileEntry, ComboBox, FontButton, Box, @@ -261,6 +273,12 @@ constexpr PreferencesWidget WidgetEntry (const char * label, { return {PreferencesWidget::Entry, label, (child == WIDGET_CHILD), cfg, entry}; } +constexpr PreferencesWidget WidgetFileEntry (const char * label, + WidgetConfig cfg, WidgetVFileEntry file_entry = WidgetVFileEntry(), + WidgetIsChild child = WIDGET_NOT_CHILD) + { return {PreferencesWidget::FileEntry, label, + (child == WIDGET_CHILD), cfg, file_entry}; } + constexpr PreferencesWidget WidgetCombo (const char * label, WidgetConfig cfg, WidgetVCombo combo, WidgetIsChild child = WIDGET_NOT_CHILD) { return {PreferencesWidget::ComboBox, label, @@ -284,11 +302,13 @@ constexpr PreferencesWidget WidgetNotebook (WidgetVNotebook notebook) constexpr PreferencesWidget WidgetSeparator (WidgetVSeparator separator = WidgetVSeparator ()) { return {PreferencesWidget::Separator, 0, 0, {}, separator}; } -constexpr PreferencesWidget WidgetCustomGTK (void * (* populate) ()) - { return {PreferencesWidget::CustomGTK, 0, 0, {}, populate}; } +constexpr PreferencesWidget WidgetCustomGTK (void * (* populate) (), + WidgetIsChild child = WIDGET_NOT_CHILD) + { return {PreferencesWidget::CustomGTK, 0, (child == WIDGET_CHILD), {}, populate}; } -constexpr PreferencesWidget WidgetCustomQt (void * (* populate) ()) - { return {PreferencesWidget::CustomQt, 0, 0, {}, populate}; } +constexpr PreferencesWidget WidgetCustomQt (void * (* populate) (), + WidgetIsChild child = WIDGET_NOT_CHILD) + { return {PreferencesWidget::CustomQt, 0, (child == WIDGET_CHILD), {}, populate}; } struct PluginPreferences { ArrayRef<PreferencesWidget> widgets; diff --git a/src/libaudcore/probe-buffer.cc b/src/libaudcore/probe-buffer.cc index 0ea6d6b..e23992c 100644 --- a/src/libaudcore/probe-buffer.cc +++ b/src/libaudcore/probe-buffer.cc @@ -24,9 +24,9 @@ static constexpr int MAXBUF = 256 * 1024; -ProbeBuffer::ProbeBuffer (const char * filename, SmartPtr<VFSImpl> && file) : +ProbeBuffer::ProbeBuffer (const char * filename, VFSImpl * file) : m_filename (filename), - m_file (std::move (file)) + m_file (file) { AUDINFO ("<%p> buffering enabled for %s\n", this, (const char *) m_filename); } diff --git a/src/libaudcore/probe-buffer.h b/src/libaudcore/probe-buffer.h index 8bec69f..5cdfc29 100644 --- a/src/libaudcore/probe-buffer.h +++ b/src/libaudcore/probe-buffer.h @@ -39,7 +39,7 @@ class ProbeBuffer : public VFSImpl { public: - ProbeBuffer (const char * filename, SmartPtr<VFSImpl> && file); + ProbeBuffer (const char * filename, VFSImpl * file); ~ProbeBuffer (); int64_t fread (void * ptr, int64_t size, int64_t nmemb); diff --git a/src/libaudcore/probe.cc b/src/libaudcore/probe.cc index 0c590a3..b2fff60 100644 --- a/src/libaudcore/probe.cc +++ b/src/libaudcore/probe.cc @@ -56,7 +56,8 @@ InputPlugin * load_input_plugin (PluginHandle * decoder, String * error) return ip; } -PluginHandle * file_find_decoder (const char * filename, bool fast, VFSFile & file, String * error) +EXPORT PluginHandle * aud_file_find_decoder (const char * filename, bool fast, + VFSFile & file, String * error) { AUDINFO ("%s %s.\n", fast ? "Fast-probing" : "Probing", filename); @@ -155,14 +156,8 @@ PluginHandle * file_find_decoder (const char * filename, bool fast, VFSFile & fi return nullptr; } -EXPORT PluginHandle * aud_file_find_decoder (const char * filename, bool fast, String * error) -{ - VFSFile file; - return file_find_decoder (filename, fast, file, error); -} - -bool file_read_tag (const char * filename, PluginHandle * decoder, - VFSFile & file, Tuple * tuple, Index<char> * image, String * error) +EXPORT bool aud_file_read_tag (const char * filename, PluginHandle * decoder, + VFSFile & file, Tuple & tuple, Index<char> * image, String * error) { auto ip = load_input_plugin (decoder, error); if (! ip) @@ -171,41 +166,21 @@ bool file_read_tag (const char * filename, PluginHandle * decoder, if (! open_input_file (filename, "r", ip, file, error)) return false; - if (tuple) - tuple->set_filename (filename); - - bool success; + Tuple new_tuple; + new_tuple.set_filename (filename); - /* read_tag() was added in 3.7 */ - if (ip->version >= 47) - success = ip->read_tag (filename, file, tuple, image); - else - success = ip->default_read_tag (filename, file, tuple, image); + if (ip->read_tag (filename, file, new_tuple, image)) + { + // cleanly replace existing tuple + new_tuple.set_state (Tuple::Valid); + tuple = std::move (new_tuple); + return true; + } - if (! success && error) + if (error) * error = String (_("Error reading metadata")); - if (! success && tuple) - * tuple = Tuple (); - - return success; -} - -EXPORT Tuple aud_file_read_tuple (const char * filename, PluginHandle * decoder, String * error) -{ - VFSFile file; - Tuple tuple; - - file_read_tag (filename, decoder, file, & tuple, nullptr, error); - return tuple; -} - -EXPORT Index<char> aud_file_read_image (const char * filename, PluginHandle * decoder) -{ - VFSFile file; - Index<char> image; - file_read_tag (filename, decoder, file, nullptr, & image, nullptr); - return image; + return false; } EXPORT bool aud_file_can_write_tuple (const char * filename, PluginHandle * decoder) diff --git a/src/libaudcore/probe.h b/src/libaudcore/probe.h index 641f415..8944289 100644 --- a/src/libaudcore/probe.h +++ b/src/libaudcore/probe.h @@ -33,9 +33,9 @@ class VFSFile; * ready" hook is called, with <file> as a parameter. If no album art could be * loaded, sets *queued to false and returns nullptr. * - * Since Audacious 3.7, album art for the currently playing song is always - * loaded before the "playback ready" hook is called. Hence the "current art - * ready" hook from previous versions is deprecated. */ + * If you only want to display album art for the currently playing song, you can + * call this function from the "playback ready" hook without any need for a + * separate "art ready" handler, since the album art is already at that point. */ const Index<char> * aud_art_request_data (const char * file, bool * queued = nullptr); /* Similar to art_request_data() but returns the URI of an image file. @@ -45,9 +45,16 @@ const char * aud_art_request_file (const char * file, bool * queued = nullptr); /* Releases album art returned by art_request_data() or art_request_file(). */ void aud_art_unref (const char * file); -PluginHandle * aud_file_find_decoder (const char * filename, bool fast, String * error = nullptr); -Tuple aud_file_read_tuple (const char * filename, PluginHandle * decoder, String * error = nullptr); -Index<char> aud_file_read_image (const char * filename, PluginHandle * decoder); +/* The following two functions take an additional VFSFile parameter to allow + * opening a file, probing for a decoder, and then reading the song metadata + * without opening the file a second time. If you don't already have a file + * handle open, then just pass in a null VFSFile and it will be opened for you. */ +PluginHandle * aud_file_find_decoder (const char * filename, bool fast, + VFSFile & file, String * error = nullptr); +bool aud_file_read_tag (const char * filename, PluginHandle * decoder, + VFSFile & file, Tuple & tuple, Index<char> * image = nullptr, + String * error = nullptr); + bool aud_file_can_write_tuple (const char * filename, PluginHandle * decoder); bool aud_file_write_tuple (const char * filename, PluginHandle * decoder, const Tuple & tuple); bool aud_custom_infowin (const char * filename, PluginHandle * decoder); diff --git a/src/libaudcore/runtime.cc b/src/libaudcore/runtime.cc index 9180deb..5c3660f 100644 --- a/src/libaudcore/runtime.cc +++ b/src/libaudcore/runtime.cc @@ -43,6 +43,7 @@ #include "hook.h" #include "internal.h" #include "mainloop.h" +#include "output.h" #include "playlist-internal.h" #include "plugins-internal.h" #include "scanner.h" @@ -64,6 +65,7 @@ size_t misc_bytes_allocated; static bool headless_mode; +static int instance_number = 1; #if defined(USE_QT) && ! defined(USE_GTK) static MainloopType mainloop_type = MainloopType::Qt; @@ -74,28 +76,23 @@ static MainloopType mainloop_type = MainloopType::GLib; static aud::array<AudPath, String> aud_paths; EXPORT void aud_set_headless_mode (bool headless) -{ - headless_mode = headless; -} - + { headless_mode = headless; } EXPORT bool aud_get_headless_mode () -{ - return headless_mode; -} + { return headless_mode; } -EXPORT void aud_set_mainloop_type (MainloopType type) -{ - mainloop_type = type; -} +EXPORT void aud_set_instance (int instance) + { instance_number = instance; } +EXPORT int aud_get_instance () + { return instance_number; } +EXPORT void aud_set_mainloop_type (MainloopType type) + { mainloop_type = type; } EXPORT MainloopType aud_get_mainloop_type () -{ - return mainloop_type; -} + { return mainloop_type; } static StringBuf get_path_to_self () { -#ifdef HAVE_PROC_SELF_EXE +#ifdef __linux__ StringBuf buf (-1); int len = readlink ("/proc/self/exe", buf, buf.len ()); @@ -180,7 +177,7 @@ static void set_default_paths () aud_paths[AudPath::IconFile] = String (HARDCODE_ICONFILE); } -static void relocate_all_paths () +static void set_install_paths () { StringBuf bindir = filename_normalize (str_copy (HARDCODE_BINDIR)); StringBuf datadir = filename_normalize (str_copy (HARDCODE_DATADIR)); @@ -236,17 +233,16 @@ static void relocate_all_paths () aud_paths[AudPath::IconFile] = relocate_path (iconfile, from, to); } -EXPORT void aud_init_paths () +static void set_config_paths () { - relocate_all_paths (); - const char * xdg_config_home = g_get_user_config_dir (); + StringBuf name = (instance_number == 1) ? str_copy ("audacious") : + str_printf ("audacious-%d", instance_number); - aud_paths[AudPath::UserDir] = String (filename_build ({xdg_config_home, "audacious"})); + aud_paths[AudPath::UserDir] = String (filename_build ({xdg_config_home, name})); aud_paths[AudPath::PlaylistDir] = String (filename_build ({aud_paths[AudPath::UserDir], "playlists"})); - /* create ~/.config/audacious/playlists */ if (g_mkdir_with_parents (aud_paths[AudPath::PlaylistDir], DIRMODE) < 0) AUDERR ("Failed to create %s: %s\n", (const char *) aud_paths[AudPath::PlaylistDir], strerror (errno)); @@ -260,14 +256,16 @@ EXPORT void aud_init_paths () #endif } -EXPORT void aud_cleanup_paths () -{ - for (String & path : aud_paths) - path = String (); -} - EXPORT const char * aud_get_path (AudPath id) { + if (! aud_paths[id]) + { + if (id <= AudPath::IconFile) + set_install_paths (); + else + set_config_paths (); + } + return aud_paths[id]; } @@ -291,6 +289,7 @@ EXPORT void aud_init () chardet_init (); eq_init (); + output_init (); playlist_init (); start_plugins_one (); @@ -343,6 +342,7 @@ EXPORT void aud_cleanup () art_cleanup (); chardet_cleanup (); eq_cleanup (); + output_cleanup (); playlist_end (); event_queue_cancel_all (); @@ -355,8 +355,11 @@ EXPORT void aud_cleanup () EXPORT void aud_leak_check () { + for (String & path : aud_paths) + path = String (); + string_leak_check (); if (misc_bytes_allocated) - AUDWARN ("Bytes allocated at exit: %zd\n", misc_bytes_allocated); + AUDWARN ("Bytes allocated at exit: %ld\n", (long) misc_bytes_allocated); } diff --git a/src/libaudcore/runtime.h b/src/libaudcore/runtime.h index c599e04..570f948 100644 --- a/src/libaudcore/runtime.h +++ b/src/libaudcore/runtime.h @@ -45,6 +45,13 @@ enum class OutputReset { ResetPlugin }; +enum class OutputStream { + AsDecoded, + AfterReplayGain, + AfterEffects, + AfterEqualizer +}; + namespace audlog { enum Level { @@ -62,7 +69,7 @@ namespace audlog void unsubscribe (Handler handler); void log (Level level, const char * file, int line, const char * func, - const char * format, ...); + const char * format, ...) __attribute__ ((__format__ (__printf__, 5, 6))); const char * get_level_name (Level level); } @@ -72,18 +79,20 @@ namespace audlog #define AUDINFO(...) do { audlog::log (audlog::Info, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__); } while (0) #define AUDDBG(...) do { audlog::log (audlog::Debug, __FILE__, __LINE__, __FUNCTION__, __VA_ARGS__); } while (0) -void aud_init_paths (); -void aud_cleanup_paths (); - const char * aud_get_path (AudPath id); void aud_set_headless_mode (bool headless); bool aud_get_headless_mode (); +// Note that the UserDir and PlaylistDir paths vary depending on the instance +// number. Therefore, calling aud_set_instance() after these paths have been +// referenced, or after aud_init(), is an error. +void aud_set_instance (int instance); +int aud_get_instance (); + void aud_set_mainloop_type (MainloopType type); MainloopType aud_get_mainloop_type (); -/* Requires: aud_init_paths() */ void aud_init_i18n (); void aud_config_set_defaults (const char * section, const char * const * entries); diff --git a/src/libaudcore/scanner.cc b/src/libaudcore/scanner.cc index 21df3c0..75cb04a 100644 --- a/src/libaudcore/scanner.cc +++ b/src/libaudcore/scanner.cc @@ -1,6 +1,6 @@ /* * scanner.c - * Copyright 2012 John Lindgren + * Copyright 2012-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,52 +21,96 @@ #include <glib.h> /* for GThreadPool */ +#include "audstrings.h" +#include "cue-cache.h" #include "i18n.h" #include "internal.h" #include "plugins.h" +#include "probe.h" #include "tuple.h" #include "vfs.h" static GThreadPool * pool; -void scanner_run (ScanRequest * r) +ScanRequest::ScanRequest (const String & filename, int flags, Callback callback, + PluginHandle * decoder, Tuple && tuple) : + filename (filename), + flags (flags), + callback (callback), + decoder (decoder), + tuple (std::move (tuple)), + ip (nullptr) { - if (! r->decoder) - r->decoder = file_find_decoder (r->filename, false, r->file, & r->error); - if (! r->decoder) + /* If this is a cuesheet entry (and it has not already been loaded), capture + * a reference to the cache immediately. During a playlist scan, requests + * have overlapping lifecycles--each new ScanRequest is created by the + * callback of the previous request--so the cached cuesheet persists as long + * as consecutive playlist entries reference it. */ + if (! this->tuple.valid () && is_cuesheet_entry (filename)) + cue_cache.capture (new CueCacheRef (strip_subtune (filename))); +} + +void ScanRequest::read_cuesheet_entry () +{ + for (auto & item : cue_cache->load ()) + { + if (item.filename == filename) + { + decoder = item.decoder; + tuple = item.tuple.ref (); + break; + } + } +} + +void ScanRequest::run () +{ + /* load cuesheet entry (possibly cached) */ + if (cue_cache) + read_cuesheet_entry (); + + /* for a cuesheet entry, determine the source filename */ + String audio_file = tuple.get_str (Tuple::AudioFile); + if (! audio_file) + audio_file = filename; + + bool need_tuple = (flags & SCAN_TUPLE) && ! tuple.valid (); + bool need_image = (flags & SCAN_IMAGE); + + if (! decoder) + decoder = aud_file_find_decoder (audio_file, false, file, & error); + if (! decoder) goto err; - if ((r->flags & (SCAN_TUPLE | SCAN_IMAGE))) + if (need_tuple || need_image) { - if (! (r->ip = load_input_plugin (r->decoder, & r->error))) + if (! (ip = load_input_plugin (decoder, & error))) goto err; - Tuple * ptuple = (r->flags & SCAN_TUPLE) ? & r->tuple : nullptr; - Index<char> * pimage = (r->flags & SCAN_IMAGE) ? & r->image_data : nullptr; - - if (! file_read_tag (r->filename, r->decoder, r->file, ptuple, pimage, & r->error)) + Index<char> * pimage = need_image ? & image_data : nullptr; + if (! aud_file_read_tag (audio_file, decoder, file, tuple, pimage, & error)) goto err; - if ((r->flags & SCAN_IMAGE) && ! r->image_data.len ()) - r->image_file = art_search (r->filename); + if ((flags & SCAN_IMAGE) && ! image_data.len ()) + image_file = art_search (audio_file); } /* rewind/reopen the input file */ - if ((r->flags & SCAN_FILE)) - open_input_file (r->filename, "r", r->ip, r->file, & r->error); + if ((flags & SCAN_FILE)) + open_input_file (audio_file, "r", ip, file, & error); else { err: /* close file if not needed or if an error occurred */ - r->file = VFSFile (); + file = VFSFile (); } - r->callback (r); + callback (this); } static void scan_worker (void * data, void *) { - scanner_run ((ScanRequest *) data); + ((ScanRequest *) data)->run (); delete (ScanRequest *) data; } diff --git a/src/libaudcore/scanner.h b/src/libaudcore/scanner.h index 18a6418..667afb3 100644 --- a/src/libaudcore/scanner.h +++ b/src/libaudcore/scanner.h @@ -1,6 +1,6 @@ /* * scanner.h - * Copyright 2012 John Lindgren + * Copyright 2012-2016 John Lindgren * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -20,7 +20,9 @@ #ifndef LIBAUDCORE_SCANNER_H #define LIBAUDCORE_SCANNER_H +#include "cue-cache.h" #include "index.h" +#include "objects.h" #include "tuple.h" #include "vfs.h" @@ -37,31 +39,33 @@ struct ScanRequest { typedef void (* Callback) (ScanRequest * request); - ScanRequest (const String & filename, int flags, Callback callback, - PluginHandle * decoder = nullptr) : - filename (filename), - flags (flags), - callback (callback), - decoder (decoder), - ip (nullptr) {} - const String filename; const int flags; const Callback callback; PluginHandle * decoder; + Tuple tuple; + InputPlugin * ip; VFSFile file; - Tuple tuple; Index<char> image_data; String image_file; String error; + + ScanRequest (const String & filename, int flags, Callback callback, + PluginHandle * decoder = nullptr, Tuple && tuple = Tuple ()); + + void run (); + +private: + SmartPtr<CueCacheRef> cue_cache; + + void read_cuesheet_entry (); }; void scanner_init (); void scanner_request (ScanRequest * request); -void scanner_run (ScanRequest * request); void scanner_cleanup (); #endif diff --git a/src/libaudcore/stringbuf.cc b/src/libaudcore/stringbuf.cc index 041b1e9..fc646f6 100644 --- a/src/libaudcore/stringbuf.cc +++ b/src/libaudcore/stringbuf.cc @@ -139,7 +139,7 @@ EXPORT void StringBuf::resize (int len) } } -EXPORT StringBuf::~StringBuf () +EXPORT StringBuf::~StringBuf () noexcept (false) { if (m_data) { diff --git a/src/libaudcore/templates.h b/src/libaudcore/templates.h index 1a78251..e78aea0 100644 --- a/src/libaudcore/templates.h +++ b/src/libaudcore/templates.h @@ -24,10 +24,10 @@ #include <type_traits> #include <utility> -#ifdef _WIN32 +// #undef POSIX and Windows macros to avoid name conflicts +#undef abs #undef min #undef max -#endif namespace aud { @@ -110,6 +110,13 @@ struct array constexpr array (Args && ... args) : vals { static_cast<Args &&> (args) ...} {} + // Due to GCC bug #63707, the forwarding constructor given above cannot be + // used to initialize the array when V is a class with no copy constructor. + // As a very limited workaround, provide a second constructor which can be + // used to initialize the array to default values in this case. + constexpr array () : + vals () {} + constexpr const V & operator[] (T t) const { return vals[(int) t]; } constexpr const V * begin () const diff --git a/src/libaudcore/tests/test.cc b/src/libaudcore/tests/test.cc index 9efe189..3d33332 100644 --- a/src/libaudcore/tests/test.cc +++ b/src/libaudcore/tests/test.cc @@ -49,6 +49,65 @@ static void test_audio_conversion () assert (in[i] == out[i]); } +static void test_numeric_conversion () +{ + static const char * in[] = { + "", + "x1234", + "+2147483647", + "-2147483648", + "999999999.999999", + "999999999.9999996", + "000000000000000000000000100000.000001000000000000000000000000", + "--5", + "3.+5", + "-6.7 dB" + }; + + static const char * out_double[] = { + "0", + "0", + "2147483647", + "-2147483648", + "999999999.999999", + "1000000000", + "100000.000001", + "0", + "3", + "-6.7" + }; + + static const char * out_int[] = { + "0", + "0", + "2147483647", + "-2147483648", + "999999999", + "999999999", + "100000", + "0", + "3", + "-6" + }; + + for (int i = 0; i < aud::n_elems (in); i ++) + { + double d_val = str_to_double (in[i]); + int i_val = str_to_int (in[i]); + StringBuf via_double = double_to_str (d_val); + StringBuf via_int = int_to_str (i_val); + + if (strcmp (via_double, out_double[i]) || strcmp (via_int, out_int[i])) + { + printf ("Converting [%s]\n", in[i]); + printf ("Expected [%s] and [%s]\n", out_double[i], out_int[i]); + printf ("Via [%g] and [%d]\n", d_val, i_val); + printf ("Got [%s] and [%s]\n", (const char *) via_double, (const char *) via_int); + exit (1); + } + } +} + static void test_filename_split () { /* expected results differ slightly from POSIX dirname/basename */ @@ -326,6 +385,7 @@ static void test_ringbuf () int main () { test_audio_conversion (); + test_numeric_conversion (); test_filename_split (); test_tuple_formats (); test_ringbuf (); diff --git a/src/libaudcore/timer.cc b/src/libaudcore/timer.cc index 173eb9a..4de6abf 100644 --- a/src/libaudcore/timer.cc +++ b/src/libaudcore/timer.cc @@ -35,13 +35,10 @@ struct TimerItem { struct TimerList { - QueuedFunc & source; + QueuedFunc source; Index<TimerItem> items; int use_count = 0; - TimerList (QueuedFunc & source) : - source (source) {} - bool contains (TimerFunc func, void * data) const { for (auto & item : items) @@ -69,10 +66,7 @@ struct TimerList }; static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; - -// QueuedFunc cannot be used in aud::array due to lack of a move constructor -static QueuedFunc qf_1Hz, qf_4Hz, qf_10Hz, qf_30Hz; -static aud::array<TimerRate, TimerList> lists {qf_1Hz, qf_4Hz, qf_10Hz, qf_30Hz}; +static aud::array<TimerRate, TimerList> lists; static void timer_run (void * list_) { diff --git a/src/libaudcore/tuple.cc b/src/libaudcore/tuple.cc index 52fd29b..4cd29b6 100644 --- a/src/libaudcore/tuple.cc +++ b/src/libaudcore/tuple.cc @@ -62,13 +62,14 @@ struct TupleData uint64_t setmask; // which fields are present Index<TupleVal> vals; // ordered list of field values - int *subtunes; /**< Array of int containing subtune index numbers. + short * subtunes; /**< Array of int containing subtune index numbers. Can be nullptr if indexing is linear or if there are no subtunes. */ - int nsubtunes; /**< Number of subtunes, if any. Values greater than 0 + short nsubtunes; /**< Number of subtunes, if any. Values greater than 0 mean that there are subtunes and #subtunes array may be set. */ + short state; int refcount; TupleData (); @@ -85,7 +86,7 @@ struct TupleData TupleVal * lookup (int field, bool add, bool remove); void set_int (int field, int x); void set_str (int field, const char * str); - void set_subtunes (int nsubs, const int * subs); + void set_subtunes (short nsubs, const short * subs); static TupleData * ref (TupleData * tuple); static void unref (TupleData * tuple); @@ -107,27 +108,28 @@ static const struct { {"title", Tuple::String, FallbackTitle}, {"artist", Tuple::String, FallbackArtist}, {"album", Tuple::String, FallbackAlbum}, + {"album-artist", Tuple::String, -1}, {"comment", Tuple::String, -1}, {"genre", Tuple::String, -1}, + {"year", Tuple::Int, -1}, + + {"composer", Tuple::String, -1}, + {"performer", Tuple::String, -1}, + {"copyright", Tuple::String, -1}, + {"date", Tuple::String, -1}, {"track-number", Tuple::Int, -1}, {"length", Tuple::Int, -1}, - {"year", Tuple::Int, -1}, - {"quality", Tuple::String, -1}, + + {"bitrate", Tuple::Int, -1}, {"codec", Tuple::String, -1}, + {"quality", Tuple::String, -1}, {"file-name", Tuple::String, -1}, {"file-path", Tuple::String, -1}, {"file-ext", Tuple::String, -1}, - {"album-artist", Tuple::String, -1}, - {"composer", Tuple::String, -1}, - {"performer", Tuple::String, -1}, - {"copyright", Tuple::String, -1}, - {"date", Tuple::String, -1}, - {"mbid", Tuple::String, -1}, - {"mime-type", Tuple::String, -1}, - {"bitrate", Tuple::Int, -1}, + {"audio-file", Tuple::String, -1}, {"subsong-id", Tuple::Int, -1}, {"subsong-num", Tuple::Int, -1}, @@ -162,6 +164,7 @@ static const FieldDictEntry field_dict[] = { {"album", Tuple::Album}, {"album-artist", Tuple::AlbumArtist}, {"artist", Tuple::Artist}, + {"audio-file", Tuple::AudioFile}, {"bitrate", Tuple::Bitrate}, {"codec", Tuple::Codec}, {"comment", Tuple::Comment}, @@ -180,8 +183,6 @@ static const FieldDictEntry field_dict[] = { {"gain-track-peak", Tuple::TrackPeak}, {"genre", Tuple::Genre}, {"length", Tuple::Length}, - {"mbid", Tuple::MusicBrainz}, - {"mime-type", Tuple::MIMEType}, {"performer", Tuple::Performer}, {"quality", Tuple::Quality}, {"segment-end", Tuple::EndTime}, @@ -277,17 +278,17 @@ void TupleData::set_str (int field, const char * str) new (& val->str) String (str); } -void TupleData::set_subtunes (int nsubs, const int * subs) +void TupleData::set_subtunes (short nsubs, const short * subs) { nsubtunes = nsubs; - delete subtunes; + delete[] subtunes; subtunes = nullptr; - if (subs) + if (nsubs && subs) { - subtunes = new int[nsubs]; - memcpy (subtunes, subs, sizeof (int) * nsubs); + subtunes = new short[nsubs]; + memcpy (subtunes, subs, sizeof subtunes[0] * nsubs); } } @@ -295,12 +296,14 @@ TupleData::TupleData () : setmask (0), subtunes (nullptr), nsubtunes (0), + state (Tuple::Initial), refcount (1) {} TupleData::TupleData (const TupleData & other) : setmask (other.setmask), subtunes (nullptr), nsubtunes (0), + state (other.state), refcount (1) { vals.insert (0, other.vals.len ()); @@ -345,8 +348,8 @@ TupleData::~TupleData () bool TupleData::is_same (const TupleData & other) { - if (setmask != other.setmask || nsubtunes != other.nsubtunes || - (! subtunes) != (! other.subtunes)) + if (state != other.state || setmask != other.setmask || + nsubtunes != other.nsubtunes || (! subtunes) != (! other.subtunes)) return false; auto a = vals.begin (); @@ -371,7 +374,7 @@ bool TupleData::is_same (const TupleData & other) } } - if (subtunes && memcmp (subtunes, other.subtunes, sizeof (int) * nsubtunes)) + if (subtunes && memcmp (subtunes, other.subtunes, sizeof subtunes[0] * nsubtunes)) return false; return true; @@ -427,6 +430,17 @@ EXPORT Tuple Tuple::ref () const return tuple; } +EXPORT Tuple::State Tuple::state () const +{ + return data ? (Tuple::State) data->state : Initial; +} + +EXPORT void Tuple::set_state (State st) +{ + data = TupleData::copy_on_write (data); + data->state = st; +} + EXPORT Tuple::ValueType Tuple::get_value_type (Field field) const { assert (is_valid_field (field)); @@ -553,18 +567,18 @@ EXPORT void Tuple::set_format (const char * format, int chans, int rate, int bra set_int (Bitrate, brate); } -EXPORT void Tuple::set_subtunes (int n_subtunes, const int * subtunes) +EXPORT void Tuple::set_subtunes (short n_subtunes, const short * subtunes) { data = TupleData::copy_on_write (data); data->set_subtunes (n_subtunes, subtunes); } -EXPORT int Tuple::get_n_subtunes () const +EXPORT short Tuple::get_n_subtunes() const { return data ? data->nsubtunes : 0; } -EXPORT int Tuple::get_nth_subtune (int n) const +EXPORT short Tuple::get_nth_subtune (short n) const { if (! data || n < 0 || n >= data->nsubtunes) return -1; @@ -644,10 +658,10 @@ EXPORT bool Tuple::fetch_stream_info (VFSFile & stream) * be modified, and the string returned will use the same memory. May return * nullptr. */ -static char * split_folder (char * path) +static char * split_folder (char * path, char sep) { char * c; - while ((c = strrchr (path, G_DIR_SEPARATOR))) + while ((c = strrchr (path, sep))) { * c = 0; if (c[1]) @@ -657,31 +671,33 @@ static char * split_folder (char * path) return path[0] ? path : nullptr; } -/* Separates the domain name from an internet URI. The string passed will be - * modified, and the string returned will share the same memory. May return - * nullptr. Examples: +/* These two functions separate the domain name from an internet URL. Examples: * "http://some.domain.org/folder/file.mp3" -> "some.domain.org" * "http://some.stream.fm:8000" -> "some.stream.fm" */ -static char * domain_name (char * name) +static const char * find_domain (const char * name) { if (! strncmp (name, "http://", 7)) - name += 7; - else if (! strncmp (name, "https://", 8)) - name += 8; - else if (! strncmp (name, "mms://", 6)) - name += 6; - else - return nullptr; + return name + 7; + if (! strncmp (name, "https://", 8)) + return name + 8; + if (! strncmp (name, "mms://", 6)) + return name + 6; + + return nullptr; +} +static StringBuf extract_domain (const char * start) +{ + StringBuf name = str_copy (start); char * c; if ((c = strchr (name, '/'))) - * c = 0; + name.resize (c - name); if ((c = strchr (name, ':'))) - * c = 0; + name.resize (c - name); if ((c = strchr (name, '?'))) - * c = 0; + name.resize (c - name); return name; } @@ -701,10 +717,22 @@ EXPORT void Tuple::generate_fallbacks () data = TupleData::copy_on_write (data); + // use album artist, if present + if (! artist && (artist = get_str (AlbumArtist))) + { + data->set_str (FallbackArtist, artist); + + if (album) + return; // nothing left to do + } + auto filepath = get_str (Path); if (! filepath) return; + const char * s; + char sep; + if (! strcmp (filepath, "cdda://")) { // audio CD: @@ -713,38 +741,40 @@ EXPORT void Tuple::generate_fallbacks () if (! album) data->set_str (FallbackAlbum, _("Audio CD")); } - else if (strstr (filepath, "://")) + else if ((s = find_domain (filepath))) { - // URL: + // internet URL: // use the domain name as the album - if (album) - return; - - StringBuf buf = str_copy (filepath); - const char * domain = domain_name (buf); - - if (domain) - data->set_str (FallbackAlbum, domain); + if (! album) + data->set_str (FallbackAlbum, extract_domain (s)); } else { - // local file: + // any other URI: // use the top two path elements as the artist and album - if (artist && album) - return; - - StringBuf buf; -#ifdef _WIN32 - if (filepath[0] && filepath[1] == ':' && filepath[2] == '\\') - buf.steal (str_copy (filepath + 3)); + if ((s = strstr (filepath, "://"))) + { + s += 3; + sep = '/'; + } else + { +#ifdef _WIN32 + if (g_ascii_isalpha (filepath[0]) && filepath[1] == ':') + s = filepath + 2; + else #endif - buf.steal (str_copy (filepath)); + s = filepath; + + sep = G_DIR_SEPARATOR; + } + + StringBuf buf = str_copy (s); - char * first = split_folder (buf); - char * second = (first && first > buf) ? split_folder (buf) : nullptr; + char * first = split_folder (buf, sep); + char * second = (first && first > buf) ? split_folder (buf, sep) : nullptr; // skip common strings and avoid duplicates for (auto skip : (const char *[]) {"~", "music", artist, album, get_str (Genre)}) diff --git a/src/libaudcore/tuple.h b/src/libaudcore/tuple.h index a70fcdc..6d45d73 100644 --- a/src/libaudcore/tuple.h +++ b/src/libaudcore/tuple.h @@ -30,6 +30,7 @@ struct ReplayGainInfo; struct TupleData; +class PluginHandle; class VFSFile; class Tuple @@ -38,39 +39,48 @@ public: /* Smart pointer to the actual TupleData struct. * Uses create-on-write and copy-on-write. */ + enum State { + Initial, /* Song info has not yet been read */ + Valid, /* Song info has been successfully read */ + Failed /* Song info could not be read */ + }; + enum Field { Invalid = -1, Title = 0, /* Song title */ Artist, /* Song artist */ Album, /* Album name */ + AlbumArtist, /* Artist for entire album, if different than song artist */ Comment, /* Freeform comment */ Genre, /* Song's genre */ + Year, /* Year of production, performance, etc. */ + + Composer, /* Composer, if different than artist */ + Performer, /* Performer, if different than artist */ + Copyright, /* Copyright declaration */ + Date, /* Date of production, performance, etc. */ Track, /* Track number */ Length, /* Track length in milliseconds */ - Year, /* Year of production, performance, etc. */ - Quality, /* String representing quality, such as "Stereo, 44 kHz" */ + + Bitrate, /* Bitrate in kilobits (1000 bits)/sec */ Codec, /* Codec name, such as "Ogg Vorbis" */ + Quality, /* String representing quality, such as "Stereo, 44 kHz" */ Basename, /* Base filename, not including the folder path */ Path, /* Folder path, including the trailing "/" */ Suffix, /* Filename extension, not including the "." */ - AlbumArtist, /* Artist for entire album, if different than song artist */ - Composer, /* Composer of song, if different than artist */ - Performer, - Copyright, - Date, - MusicBrainz, /* MusicBrainz identifer for the song */ - MIMEType, - Bitrate, /* Bitrate in kbits/sec */ + AudioFile, /* URI of audio file, if different from the nominal URI + * (e.g. for a cuesheet entry, where the nominal URI + * points to the .cue file) */ Subtune, /* Index number of subtune */ NumSubtunes, /* Total number of subtunes in the file */ - StartTime, - EndTime, + StartTime, /* Playback start point (used for cuesheets) */ + EndTime, /* Playback end point (used for cuesheets) */ /* Preserving replay gain information accurately is a challenge since there * are several differents formats around. We use an integer fraction, with @@ -91,7 +101,7 @@ public: n_fields }; - typedef aud::range<Field, Title, FormattedTitle> all_fields; + typedef aud::range<Field, Field (0), Field (n_fields - 1)> all_fields; enum ValueType { String, @@ -125,19 +135,23 @@ public: return * this; } - explicit operator bool () const - { return (bool) data; } - bool operator== (const Tuple & b) const; bool operator!= (const Tuple & b) const { return ! operator== (b); } Tuple ref () const; + /* Gets/sets the state of the song info. Before setting the state to Valid, + * you should ensure that, at a minimum, set_filename() has been called. */ + State state () const; + void set_state (State st); + /* Returns the value type of a field if set, otherwise Empty. */ ValueType get_value_type (Field field) const; - /* Convenience function to determine whether a field is set. */ + /* Convenience functions */ + bool valid () const + { return state () == Valid; } bool is_set (Field field) const { return get_value_type (field) != Empty; } @@ -174,16 +188,16 @@ public: /* In addition to the normal fields, tuples contain an integer array of * subtune ID numbers. This function sets that array. It also sets * NumSubtunes to the value <n_subtunes>. */ - void set_subtunes (int n_subtunes, const int * subtunes); + void set_subtunes (short n_subtunes, const short * subtunes); /* Returns the length of the subtune array. If the array has not been set, * returns zero. Note that if NumSubtunes is changed after * set_subtunes() is called, this function returns the value <n_subtunes> * passed to set_subtunes(), not the value of NumSubtunes. */ - int get_n_subtunes () const; + short get_n_subtunes () const; /* Returns the <n>th member of the subtune array. */ - int get_nth_subtune (int n) const; + short get_nth_subtune (short n) const; /* Sets a Replay Gain field pair from a decimal string. */ void set_gain (Field field, Field unit_field, const char * str); @@ -211,11 +225,14 @@ private: }; /* somewhat out of place here */ -class PluginHandle; -struct PlaylistAddItem { +struct PlaylistAddItem +{ String filename; Tuple tuple; PluginHandle * decoder; + + PlaylistAddItem copy () const + { return {filename, tuple.ref (), decoder}; } }; #endif /* LIBAUDCORE_TUPLE_H */ diff --git a/src/libaudcore/util.cc b/src/libaudcore/util.cc index c0cdc63..4d92ded 100644 --- a/src/libaudcore/util.cc +++ b/src/libaudcore/util.cc @@ -121,6 +121,27 @@ void cut_path_element (char * path, int pos) path[pos] = 0; /* leave [drive letter and] leading slash */ } +bool is_cuesheet_entry (const char * filename) +{ + const char * ext, * sub; + uri_parse (filename, nullptr, & ext, & sub, nullptr); + return sub[0] && sub - ext == 4 && ! strcmp_nocase (ext, ".cue", 4); +} + +bool is_subtune (const char * filename) +{ + const char * sub; + uri_parse (filename, nullptr, nullptr, & sub, nullptr); + return sub[0]; +} + +StringBuf strip_subtune (const char * filename) +{ + const char * sub; + uri_parse (filename, nullptr, nullptr, & sub, nullptr); + return str_copy (filename, sub - filename); +} + /* Thomas Wang's 32-bit mix function. See: * http://web.archive.org/web/20070307172248/http://www.concentric.net/~Ttwang/tech/inthash.htm */ diff --git a/src/libaudcore/vfs.cc b/src/libaudcore/vfs.cc index f4b9e48..8748fd4 100644 --- a/src/libaudcore/vfs.cc +++ b/src/libaudcore/vfs.cc @@ -23,31 +23,66 @@ #define __STDC_FORMAT_MACROS #include <inttypes.h> #include <string.h> -#include <sys/stat.h> -#include <sys/types.h> -#include <unistd.h> - -#include <glib/gstdio.h> #include "audstrings.h" #include "i18n.h" +#include "internal.h" #include "plugin.h" #include "plugins-internal.h" #include "probe-buffer.h" #include "runtime.h" #include "vfs_local.h" -static TransportPlugin * lookup_transport (const char * scheme) +/* embedded plugins */ +static LocalTransport local_transport; +static StdinTransport stdin_transport; + +static TransportPlugin * lookup_transport (const char * filename, + String & error, bool * custom_input = nullptr) { + StringBuf scheme = uri_get_scheme (filename); + if (! scheme) + { + AUDERR ("Invalid URI: %s\n", filename); + error = String (_("Invalid URI")); + return nullptr; + } + + if (! strcmp (scheme, "file")) + return & local_transport; + if (! strcmp (scheme, "stdin")) + return & stdin_transport; + for (PluginHandle * plugin : aud_plugin_list (PluginType::Transport)) { if (! aud_plugin_get_enabled (plugin)) continue; if (transport_plugin_has_scheme (plugin, scheme)) - return (TransportPlugin *) aud_plugin_get_header (plugin); + { + auto tp = (TransportPlugin *) aud_plugin_get_header (plugin); + if (tp) + return tp; + } } + if (custom_input) + { + for (PluginHandle * plugin : aud_plugin_list (PluginType::Input)) + { + if (! aud_plugin_get_enabled (plugin)) + continue; + + if (input_plugin_has_key (plugin, InputKey::Scheme, scheme)) + { + * custom_input = true; + return nullptr; + } + } + } + + AUDERR ("Unknown URI scheme: %s://\n", (const char *) scheme); + error = String (_("Unknown URI scheme")); return nullptr; } @@ -61,44 +96,28 @@ static TransportPlugin * lookup_transport (const char * scheme) */ EXPORT VFSFile::VFSFile (const char * filename, const char * mode) { - StringBuf scheme = uri_get_scheme (filename); - if (! scheme) - { - AUDERR ("Invalid URI: %s\n", filename); - m_error = String (_("Invalid URI")); + auto tp = lookup_transport (filename, m_error); + if (! tp) return; - } - const char * sub; - uri_parse (filename, nullptr, nullptr, & sub, nullptr); - StringBuf nosub = str_copy (filename, sub - filename); - - if (! strcmp (scheme, "file")) - m_impl.capture (vfs_local_fopen (nosub, mode, m_error)); - else if (! strcmp (scheme, "stdin")) - m_impl.capture (vfs_stdin_fopen (mode, m_error)); - else - { - TransportPlugin * tp = lookup_transport (scheme); - if (! tp) - { - AUDERR ("Unknown URI scheme: %s://", (const char *) scheme); - m_error = String (_("Unknown URI scheme")); - return; - } - - m_impl.capture (tp->fopen (nosub, mode, m_error)); - } - - if (! m_impl) + VFSImpl * impl = tp->fopen (strip_subtune (filename), mode, m_error); + if (! impl) return; /* enable buffering for read-only handles */ if (mode[0] == 'r' && ! strchr (mode, '+')) - m_impl.capture (new ProbeBuffer (filename, std::move (m_impl))); + impl = new ProbeBuffer (filename, impl); - AUDINFO ("<%p> open (mode %s) %s\n", m_impl.get (), mode, filename); + AUDINFO ("<%p> open (mode %s) %s\n", impl, mode, filename); m_filename = String (filename); + m_impl.capture (impl); +} + +EXPORT VFSFile VFSFile::tmpfile () +{ + VFSFile file; + file.m_impl.capture (vfs_tmpfile (file.m_error)); + return file; } /** @@ -155,8 +174,8 @@ EXPORT int64_t VFSFile::fwrite (const void * ptr, int64_t size, int64_t nmemb) EXPORT int VFSFile::fseek (int64_t offset, VFSSeekType whence) { AUDDBG ("<%p> seek to %" PRId64 " from %s\n", m_impl.get (), offset, - whence == SEEK_CUR ? "current" : whence == SEEK_SET ? "beginning" : - whence == SEEK_END ? "end" : "invalid"); + whence == VFS_SEEK_CUR ? "current" : whence == VFS_SEEK_SET ? "beginning" : + whence == VFS_SEEK_END ? "end" : "invalid"); if (! m_impl->fseek (offset, whence)) return 0; @@ -303,54 +322,72 @@ EXPORT Index<char> VFSFile::read_all () return buf; } -/** - * Wrapper for g_file_test(). - * - * @param path A path to test. - * @param test A GFileTest to run. - * @return The result of g_file_test(). - */ -EXPORT bool VFSFile::test_file (const char * path, VFSFileTest test) +EXPORT bool VFSFile::copy_from (VFSFile & source, int64_t size) { - if (strncmp (path, "file://", 7)) - return false; /* only local files are handled */ + constexpr int bufsize = 65536; - const char * sub; - uri_parse (path, nullptr, nullptr, & sub, nullptr); + Index<char> buf; + buf.resize (bufsize); - StringBuf no_sub = str_copy (path, sub - path); + while (size < 0 || size > 0) + { + int64_t to_read = (size > 0 && size < bufsize) ? size : bufsize; + int64_t readsize = source.fread (buf.begin (), 1, to_read); - StringBuf path2 = uri_to_filename (no_sub); - if (! path2) - return false; + if (size > 0) + size -= readsize; -#ifdef S_ISLNK - if (test & VFS_IS_SYMLINK) - { - GStatBuf st; - if (g_lstat (path2, & st) < 0) + if (fwrite (buf.begin (), 1, readsize) != readsize) return false; - if (S_ISLNK (st.st_mode)) - test = (VFSFileTest) (test & ~VFS_IS_SYMLINK); + if (readsize < to_read) + break; } -#endif - if (test & (VFS_IS_REGULAR | VFS_IS_DIR | VFS_IS_EXECUTABLE | VFS_EXISTS)) - { - GStatBuf st; - if (g_stat (path2, & st) < 0) - return false; + /* if a fixed size was requested, return true only if all the data was read. + * otherwise, return true only if the end of the source file was reached. */ + return size == 0 || (size < 0 && source.feof ()); +} + +EXPORT bool VFSFile::replace_with (VFSFile & source) +{ + if (source.fseek (0, VFS_SEEK_SET) < 0) + return false; - if (S_ISREG (st.st_mode)) - test = (VFSFileTest) (test & ~VFS_IS_REGULAR); - if (S_ISDIR (st.st_mode)) - test = (VFSFileTest) (test & ~VFS_IS_DIR); - if (st.st_mode & S_IXUSR) - test = (VFSFileTest) (test & ~VFS_IS_EXECUTABLE); + if (fseek (0, VFS_SEEK_SET) < 0) + return false; - test = (VFSFileTest) (test & ~VFS_EXISTS); - } + if (ftruncate (0) < 0) + return false; + + return copy_from (source, -1); +} + +EXPORT bool VFSFile::test_file (const char * filename, VFSFileTest test) +{ + String error; /* discarded */ + return test_file (filename, test, error) == test; +} + +EXPORT VFSFileTest VFSFile::test_file (const char * filename, VFSFileTest test, String & error) +{ + bool custom_input = false; + auto tp = lookup_transport (filename, error, & custom_input); + + /* for URI schemes handled by input plugins, return 0, indicating that we + * have no way of testing file attributes */ + if (custom_input) + return VFSFileTest (0); - return ! test; + /* for unsupported URI schemes, return VFS_NO_ACCESS */ + if (! tp) + return VFSFileTest (test & VFS_NO_ACCESS); + + return tp->test_file (strip_subtune (filename), test, error); +} + +EXPORT Index<String> VFSFile::read_folder (const char * filename, String & error) +{ + auto tp = lookup_transport (filename, error); + return tp ? tp->read_folder (filename, error) : Index<String> (); } diff --git a/src/libaudcore/vfs.h b/src/libaudcore/vfs.h index c6a0a16..e51cf84 100644 --- a/src/libaudcore/vfs.h +++ b/src/libaudcore/vfs.h @@ -29,6 +29,7 @@ #include <stdint.h> +#include <libaudcore/export.h> #include <libaudcore/index.h> #include <libaudcore/objects.h> @@ -37,7 +38,8 @@ enum VFSFileTest { VFS_IS_SYMLINK = (1 << 1), VFS_IS_DIR = (1 << 2), VFS_IS_EXECUTABLE = (1 << 3), - VFS_EXISTS = (1 << 4) + VFS_EXISTS = (1 << 4), + VFS_NO_ACCESS = (1 << 5) }; enum VFSSeekType { @@ -66,7 +68,17 @@ constexpr VFSSeekType to_vfs_seek_type (int whence) #endif // WANT_VFS_STDIO_COMPAT -class VFSImpl +// #undef POSIX functions/macros to avoid name conflicts +#undef fread +#undef fseek +#undef ftell +#undef fsize +#undef feof +#undef fwrite +#undef ftruncate +#undef fflush + +class LIBAUDCORE_PUBLIC VFSImpl { public: VFSImpl () {} @@ -100,6 +112,9 @@ public: VFSFile (const char * filename, const char * mode); + /* creates a temporary file (deleted when closed) */ + static VFSFile tmpfile (); + explicit operator bool () const { return (bool) m_impl; } const char * filename () const @@ -120,15 +135,33 @@ public: int ftruncate (int64_t length) __attribute__ ((warn_unused_result)); int fflush () __attribute__ ((warn_unused_result)); + /* used to read e.g. ICY metadata */ String get_metadata (const char * field); - void set_limit_to_buffer (bool limit); // added in 3.7 + /* the VFS layer buffers up to 256 KB of data at the beginning of files + * opened in read-only mode; this function disallows reading outside the + * buffered region (useful for probing the file type) */ + void set_limit_to_buffer (bool limit); /* utility functions */ + /* reads the entire file into memory (limited to 16 MB) */ Index<char> read_all (); - static bool test_file (const char * path, VFSFileTest test); + /* reads data from another open file and appends it to this one */ + bool copy_from (VFSFile & source, int64_t size = -1); + + /* overwrites the entire file with the contents of another */ + bool replace_with (VFSFile & source); + + /* tests certain attributes of a file without opening it. + * the 2-argument version returns true if all requested tests passed. + * the 3-argument version returns a bitmask indicating which tests passed. */ + static bool test_file (const char * filename, VFSFileTest test); + static VFSFileTest test_file (const char * filename, VFSFileTest test, String & error); + + /* returns a sorted list of folder entries (as full URIs) */ + static Index<String> read_folder (const char * filename, String & error); private: String m_filename, m_error; diff --git a/src/libaudcore/vfs_local.cc b/src/libaudcore/vfs_local.cc index c1b10e5..7af685f 100644 --- a/src/libaudcore/vfs_local.cc +++ b/src/libaudcore/vfs_local.cc @@ -17,15 +17,16 @@ * the use of this software. */ -#define WANT_VFS_STDIO_COMPAT -#include "vfs_local.h" - #include <errno.h> #include <string.h> #include <unistd.h> #include <glib/gstdio.h> +/* needs to be after system headers for #undef's to take effect */ +#define WANT_VFS_STDIO_COMPAT +#include "vfs_local.h" + #include "audstrings.h" #include "i18n.h" #include "runtime.h" @@ -75,7 +76,7 @@ private: LocalOp m_last_op; }; -VFSImpl * vfs_local_fopen (const char * uri, const char * mode, String & error) +VFSImpl * LocalTransport::fopen (const char * uri, const char * mode, String & error) { StringBuf path = uri_to_filename (uri); @@ -97,7 +98,7 @@ VFSImpl * vfs_local_fopen (const char * uri, const char * mode, String & error) StringBuf mode2 = str_concat ({mode, suffix}); - FILE * stream = g_fopen (path, mode2); + FILE * stream = ::g_fopen (path, mode2); if (! stream) { @@ -108,9 +109,9 @@ VFSImpl * vfs_local_fopen (const char * uri, const char * mode, String & error) * 2) UTF-8 filesystem mounted on legacy system */ if (errsave == ENOENT) { - StringBuf path2 = str_to_utf8 (uri_to_filename (uri, false)); + StringBuf path2 = uri_to_filename (uri, false); if (path2 && strcmp (path, path2)) - stream = g_fopen (path2, mode2); + stream = ::g_fopen (path2, mode2); } if (! stream) @@ -124,7 +125,7 @@ VFSImpl * vfs_local_fopen (const char * uri, const char * mode, String & error) return new LocalFile (path, stream); } -VFSImpl * vfs_stdin_fopen (const char * mode, String & error) +VFSImpl * StdinTransport::fopen (const char * uri, const char * mode, String & error) { if (mode[0] != 'r' || strchr (mode, '+')) { @@ -135,6 +136,21 @@ VFSImpl * vfs_stdin_fopen (const char * mode, String & error) return new LocalFile ("(stdin)", stdin); } +VFSImpl * vfs_tmpfile (String & error) +{ + FILE * stream = tmpfile (); + + if (! stream) + { + int errsave = errno; + perror ("(tmpfile)"); + error = String (strerror (errsave)); + return nullptr; + } + + return new LocalFile ("(tmpfile)", stream); +} + LocalFile::~LocalFile () { // do not close stdin @@ -305,3 +321,85 @@ ERR: perror (m_path); return -1; } + +VFSFileTest LocalTransport::test_file (const char * uri, VFSFileTest test, String & error) +{ + StringBuf path = uri_to_filename (uri); + if (! path) + { + error = String (_("Invalid file name")); + return VFSFileTest (test & VFS_NO_ACCESS); + } + + int passed = 0; + bool need_stat = true; + GStatBuf st; + +#ifdef S_ISLNK + if (test & VFS_IS_SYMLINK) + { + if (g_lstat (path, & st) < 0) + { + error = String (strerror (errno)); + passed |= VFS_NO_ACCESS; + goto out; + } + + if (S_ISLNK (st.st_mode)) + passed |= VFS_IS_SYMLINK; + else + need_stat = false; + } +#endif + + if (test & (VFS_IS_REGULAR | VFS_IS_DIR | VFS_IS_EXECUTABLE | VFS_EXISTS | VFS_NO_ACCESS)) + { + if (need_stat && g_stat (path, & st) < 0) + { + error = String (strerror (errno)); + passed |= VFS_NO_ACCESS; + goto out; + } + + if (S_ISREG (st.st_mode)) + passed |= VFS_IS_REGULAR; + if (S_ISDIR (st.st_mode)) + passed |= VFS_IS_DIR; + if (st.st_mode & S_IXUSR) + passed |= VFS_IS_EXECUTABLE; + + passed |= VFS_EXISTS; + } + +out: + return VFSFileTest (test & passed); +} + +Index<String> LocalTransport::read_folder (const char * uri, String & error) +{ + Index<String> entries; + + StringBuf path = uri_to_filename (uri); + if (! path) + { + error = String (_("Invalid file name")); + return entries; + } + + GError * gerr = nullptr; + GDir * folder = g_dir_open (path, 0, & gerr); + if (! folder) + { + error = String (gerr->message); + g_error_free (gerr); + return entries; + } + + const char * name; + while ((name = g_dir_read_name (folder))) + entries.append (String (filename_to_uri (filename_build ({path, name})))); + + g_dir_close (folder); + + return entries; +} diff --git a/src/libaudcore/vfs_local.h b/src/libaudcore/vfs_local.h index eed258b..071c0d9 100644 --- a/src/libaudcore/vfs_local.h +++ b/src/libaudcore/vfs_local.h @@ -20,9 +20,26 @@ #ifndef LIBAUDCORE_VFS_LOCAL_H #define LIBAUDCORE_VFS_LOCAL_H -#include "vfs.h" +#include "plugin.h" -VFSImpl * vfs_local_fopen (const char * uri, const char * mode, String & error); -VFSImpl * vfs_stdin_fopen (const char * mode, String & error); +class LocalTransport : public TransportPlugin +{ +public: + constexpr LocalTransport () : TransportPlugin (PluginInfo (), nullptr) {} + + VFSImpl * fopen (const char * filename, const char * mode, String & error); + VFSFileTest test_file (const char * filename, VFSFileTest test, String & error); + Index<String> read_folder (const char * filename, String & error); +}; + +class StdinTransport : public TransportPlugin +{ +public: + constexpr StdinTransport () : TransportPlugin (PluginInfo (), nullptr) {} + + VFSImpl * fopen (const char * filename, const char * mode, String & error); +}; + +VFSImpl * vfs_tmpfile (String & error); #endif /* LIBAUDCORE_VFS_LOCAL_H */ diff --git a/src/libaudcore/visualizer.h b/src/libaudcore/visualizer.h index d99eaff..5c825e6 100644 --- a/src/libaudcore/visualizer.h +++ b/src/libaudcore/visualizer.h @@ -20,7 +20,9 @@ #ifndef LIBAUDCORE_VISUALIZER_H #define LIBAUDCORE_VISUALIZER_H -class Visualizer +#include <libaudcore/export.h> + +class LIBAUDCORE_PUBLIC Visualizer { public: enum { |