summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audacious/Makefile5
-rw-r--r--src/audacious/dbus-server.cc119
-rw-r--r--src/audacious/main.cc37
-rw-r--r--src/audacious/main.h3
-rw-r--r--src/audtool/audtool.h4
-rw-r--r--src/audtool/handlers_general.c38
-rw-r--r--src/audtool/handlers_playback.c13
-rw-r--r--src/audtool/main.c32
-rw-r--r--src/audtool/wrappers.c23
-rw-r--r--src/dbus/aud-dbus.xml31
-rw-r--r--src/libaudcore/Makefile8
-rw-r--r--src/libaudcore/adder.cc260
-rw-r--r--src/libaudcore/art-search.cc5
-rw-r--r--src/libaudcore/art.cc5
-rw-r--r--src/libaudcore/audstrings.cc211
-rw-r--r--src/libaudcore/audstrings.h2
-rw-r--r--src/libaudcore/config.cc8
-rw-r--r--src/libaudcore/cue-cache.cc95
-rw-r--r--src/libaudcore/cue-cache.h (renamed from src/libaudqt/volumebutton.h)39
-rw-r--r--src/libaudcore/drct.cc14
-rw-r--r--src/libaudcore/export.h33
-rw-r--r--src/libaudcore/index.cc53
-rw-r--r--src/libaudcore/index.h37
-rw-r--r--src/libaudcore/inifile.h4
-rw-r--r--src/libaudcore/interface.cc6
-rw-r--r--src/libaudcore/interface.h1
-rw-r--r--src/libaudcore/internal.h11
-rw-r--r--src/libaudcore/mainloop.h5
-rw-r--r--src/libaudcore/objects.h2
-rw-r--r--src/libaudcore/output.cc92
-rw-r--r--src/libaudcore/output.h3
-rw-r--r--src/libaudcore/playback.cc28
-rw-r--r--src/libaudcore/playlist-cache.cc88
-rw-r--r--src/libaudcore/playlist-files.cc7
-rw-r--r--src/libaudcore/playlist-internal.h4
-rw-r--r--src/libaudcore/playlist-utils.cc12
-rw-r--r--src/libaudcore/playlist.cc113
-rw-r--r--src/libaudcore/playlist.h24
-rw-r--r--src/libaudcore/plugin-load.cc17
-rw-r--r--src/libaudcore/plugin-registry.cc27
-rw-r--r--src/libaudcore/plugin.h86
-rw-r--r--src/libaudcore/plugins-internal.h2
-rw-r--r--src/libaudcore/preferences.h28
-rw-r--r--src/libaudcore/probe-buffer.cc4
-rw-r--r--src/libaudcore/probe-buffer.h2
-rw-r--r--src/libaudcore/probe.cc55
-rw-r--r--src/libaudcore/probe.h19
-rw-r--r--src/libaudcore/runtime.cc59
-rw-r--r--src/libaudcore/runtime.h19
-rw-r--r--src/libaudcore/scanner.cc80
-rw-r--r--src/libaudcore/scanner.h26
-rw-r--r--src/libaudcore/stringbuf.cc2
-rw-r--r--src/libaudcore/templates.h11
-rw-r--r--src/libaudcore/tests/test.cc60
-rw-r--r--src/libaudcore/timer.cc10
-rw-r--r--src/libaudcore/tuple.cc156
-rw-r--r--src/libaudcore/tuple.h61
-rw-r--r--src/libaudcore/util.cc21
-rw-r--r--src/libaudcore/vfs.cc191
-rw-r--r--src/libaudcore/vfs.h41
-rw-r--r--src/libaudcore/vfs_local.cc114
-rw-r--r--src/libaudcore/vfs_local.h23
-rw-r--r--src/libaudcore/visualizer.h4
-rw-r--r--src/libaudgui/Makefile4
-rw-r--r--src/libaudgui/eq-preset.cc7
-rw-r--r--src/libaudgui/file-opener.cc1
-rw-r--r--src/libaudgui/infopopup.cc2
-rw-r--r--src/libaudgui/infowin.cc51
-rw-r--r--src/libaudgui/init.cc1
-rw-r--r--src/libaudgui/internal.h3
-rw-r--r--src/libaudgui/jump-to-track-cache.cc2
-rw-r--r--src/libaudgui/jump-to-track.cc2
-rw-r--r--src/libaudgui/libaudgui-gtk.h4
-rw-r--r--src/libaudgui/list.cc42
-rw-r--r--src/libaudgui/menu.cc2
-rw-r--r--src/libaudgui/playlists.cc3
-rw-r--r--src/libaudgui/prefs-widget.cc76
-rw-r--r--src/libaudgui/prefs-window.cc39
-rw-r--r--src/libaudgui/preset-browser.cc2
-rw-r--r--src/libaudgui/queue-manager.cc2
-rw-r--r--src/libaudgui/status.cc11
-rw-r--r--src/libaudgui/urilist.cc37
-rw-r--r--src/libaudgui/util.cc80
-rw-r--r--src/libaudqt/Makefile13
-rw-r--r--src/libaudqt/export.h33
-rw-r--r--src/libaudqt/iface.h19
-rw-r--r--src/libaudqt/info-widget.cc96
-rw-r--r--src/libaudqt/info-widget.h30
-rw-r--r--src/libaudqt/infowin.cc38
-rw-r--r--src/libaudqt/libaudqt.h4
-rw-r--r--src/libaudqt/prefs-builder.cc25
-rw-r--r--src/libaudqt/prefs-widget.cc13
-rw-r--r--src/libaudqt/prefs-widget.h22
-rw-r--r--src/libaudqt/prefs-window.cc22
-rw-r--r--src/libaudqt/queue-manager.cc2
-rw-r--r--src/libaudqt/util.cc8
-rw-r--r--src/libaudqt/volumebutton.cc26
-rw-r--r--src/libaudtag/Makefile4
-rw-r--r--src/libaudtag/ape/ape.cc6
-rw-r--r--src/libaudtag/audtag.cc16
-rw-r--r--src/libaudtag/audtag.h8
-rw-r--r--src/libaudtag/builtin.h32
-rw-r--r--src/libaudtag/id3/id3-common.cc12
-rw-r--r--src/libaudtag/id3/id3v1.cc7
-rw-r--r--src/libaudtag/id3/id3v22.cc6
-rw-r--r--src/libaudtag/id3/id3v24.cc39
-rw-r--r--src/libaudtag/tag_module.cc2
-rw-r--r--src/libaudtag/tag_module.h14
-rw-r--r--src/libaudtag/util.cc105
-rw-r--r--src/libaudtag/util.h19
110 files changed, 2336 insertions, 1249 deletions
diff --git a/src/audacious/Makefile b/src/audacious/Makefile
index 89a017e..e4678ba 100644
--- a/src/audacious/Makefile
+++ b/src/audacious/Makefile
@@ -28,10 +28,11 @@ CPPFLAGS := -I.. -I../.. \
${CPPFLAGS} \
${GLIB_CFLAGS}
-CPPFLAGS += -D_AUDACIOUS_CORE
-
LIBS := -L../libaudcore -laudcore \
${LIBS} -lm \
${LIBINTL} \
${GLIB_LIBS}
+ifeq ($(HAVE_MSWINDOWS),yes)
+LDFLAGS := ${LDFLAGS} -Wl,-subsystem,windows
+endif
diff --git a/src/audacious/dbus-server.cc b/src/audacious/dbus-server.cc
index c6cf76a..f208a4c 100644
--- a/src/audacious/dbus-server.cc
+++ b/src/audacious/dbus-server.cc
@@ -17,10 +17,12 @@
* the use of this software.
*/
+#include <libaudcore/audstrings.h>
#include <libaudcore/drct.h>
#include <libaudcore/equalizer.h>
#include <libaudcore/interface.h>
#include <libaudcore/playlist.h>
+#include <libaudcore/plugins.h>
#include <libaudcore/runtime.h>
#include <libaudcore/tuple.h>
@@ -347,6 +349,33 @@ static gboolean do_playqueue_remove (Obj * obj, Invoc * invoc, int pos)
return true;
}
+static gboolean do_plugin_enable (Obj * obj, Invoc * invoc, const char * name, gboolean enable)
+{
+ PluginHandle * plugin = aud_plugin_lookup_basename (name);
+ if (! plugin)
+ {
+ AUDERR ("No such plugin: %s\n", name);
+ return false;
+ }
+
+ aud_plugin_enable (plugin, enable);
+ FINISH (plugin_enable);
+ return true;
+}
+
+static gboolean do_plugin_is_enabled (Obj * obj, Invoc * invoc, const char * name)
+{
+ PluginHandle * plugin = aud_plugin_lookup_basename (name);
+ if (! plugin)
+ {
+ AUDERR ("No such plugin: %s\n", name);
+ return false;
+ }
+
+ FINISH2 (plugin_is_enabled, aud_plugin_get_enabled (plugin));
+ return true;
+}
+
static gboolean do_position (Obj * obj, Invoc * invoc)
{
FINISH2 (position, aud_playlist_get_position (CURRENT));
@@ -372,6 +401,19 @@ static gboolean do_quit (Obj * obj, Invoc * invoc)
return true;
}
+static gboolean do_record (Obj * obj, Invoc * invoc)
+{
+ aud_drct_enable_record (! aud_drct_get_record_enabled ());
+ FINISH (record);
+ return true;
+}
+
+static gboolean do_recording (Obj * obj, Invoc * invoc)
+{
+ FINISH2 (recording, aud_drct_get_record_enabled ());
+ return true;
+}
+
static gboolean do_repeat (Obj * obj, Invoc * invoc)
{
FINISH2 (repeat, aud_get_bool (nullptr, "repeat"));
@@ -554,35 +596,37 @@ static gboolean do_song_tuple (Obj * obj, Invoc * invoc, unsigned pos, const cha
{
Tuple::Field field = Tuple::field_by_name (key);
Tuple tuple;
- GVariant * var = nullptr;
+ GVariant * var;
if (field >= 0)
tuple = aud_playlist_entry_get_tuple (CURRENT, pos);
- if (tuple)
+ switch (tuple.get_value_type (field))
{
- switch (tuple.get_value_type (field))
- {
- case Tuple::String:
- var = g_variant_new_string (tuple.get_str (field));
- break;
-
- case Tuple::Int:
- var = g_variant_new_int32 (tuple.get_int (field));
- break;
-
- default:
- break;
- }
- }
+ case Tuple::String:
+ var = g_variant_new_string (tuple.get_str (field));
+ break;
+
+ case Tuple::Int:
+ var = g_variant_new_int32 (tuple.get_int (field));
+ break;
- if (! var)
+ default:
var = g_variant_new_string ("");
+ break;
+ }
FINISH2 (song_tuple, g_variant_new_variant (var));
return true;
}
+static gboolean do_startup_notify (Obj * obj, Invoc * invoc, const char * id)
+{
+ aud_ui_startup_notify (id);
+ FINISH (startup_notify);
+ return true;
+}
+
static gboolean do_status (Obj * obj, Invoc * invoc)
{
const char * status = "stopped";
@@ -706,10 +750,14 @@ handlers[] =
{"handle-playqueue-clear", (GCallback) do_playqueue_clear},
{"handle-playqueue-is-queued", (GCallback) do_playqueue_is_queued},
{"handle-playqueue-remove", (GCallback) do_playqueue_remove},
+ {"handle-plugin-enable", (GCallback) do_plugin_enable},
+ {"handle-plugin-is-enabled", (GCallback) do_plugin_is_enabled},
{"handle-position", (GCallback) do_position},
{"handle-queue-get-list-pos", (GCallback) do_queue_get_list_pos},
{"handle-queue-get-queue-pos", (GCallback) do_queue_get_queue_pos},
{"handle-quit", (GCallback) do_quit},
+ {"handle-recording", (GCallback) do_recording},
+ {"handle-record", (GCallback) do_record},
{"handle-repeat", (GCallback) do_repeat},
{"handle-reverse", (GCallback) do_reverse},
{"handle-seek", (GCallback) do_seek},
@@ -730,6 +778,7 @@ handlers[] =
{"handle-song-length", (GCallback) do_song_length},
{"handle-song-title", (GCallback) do_song_title},
{"handle-song-tuple", (GCallback) do_song_tuple},
+ {"handle-startup-notify", (GCallback) do_startup_notify},
{"handle-status", (GCallback) do_status},
{"handle-stop", (GCallback) do_stop},
{"handle-stop-after", (GCallback) do_stop_after},
@@ -748,16 +797,16 @@ static unsigned owner_id = 0;
static GDBusInterfaceSkeleton * skeleton = nullptr;
-static void name_acquired (GDBusConnection *, const char *, void *)
+static void name_acquired (GDBusConnection *, const char * name, void *)
{
- AUDINFO ("Owned D-Bus name (org.atheme.audacious) on session bus.\n");
+ AUDINFO ("Owned D-Bus name (%s) on session bus.\n", name);
g_main_loop_quit (mainloop);
}
-static void name_lost (GDBusConnection *, const char *, void *)
+static void name_lost (GDBusConnection *, const char * name, void *)
{
- AUDINFO ("Owning D-Bus name (org.atheme.audacious) failed, already taken?\n");
+ AUDINFO ("Owning D-Bus name (%s) failed, already taken?\n", name);
g_bus_unown_name (owner_id);
owner_id = 0;
@@ -765,8 +814,17 @@ static void name_lost (GDBusConnection *, const char *, void *)
g_main_loop_quit (mainloop);
}
+StringBuf dbus_server_name ()
+{
+ int instance = aud_get_instance ();
+ return (instance == 1) ? str_copy ("org.atheme.audacious") :
+ str_printf ("org.atheme.audacious-%d", instance);
+}
+
StartupType dbus_server_init ()
{
+ auto startup = StartupType::Unknown;
+
GError * error = nullptr;
GDBusConnection * bus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, & error);
GMainContext * context;
@@ -785,7 +843,7 @@ StartupType dbus_server_init ()
context = g_main_context_new ();
g_main_context_push_thread_default (context);
- owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, "org.atheme.audacious",
+ owner_id = g_bus_own_name (G_BUS_TYPE_SESSION, dbus_server_name (),
(GBusNameOwnerFlags) 0, nullptr, name_acquired, name_lost, nullptr, nullptr);
mainloop = g_main_loop_new (context, true);
@@ -793,14 +851,13 @@ StartupType dbus_server_init ()
g_main_loop_unref (mainloop);
mainloop = nullptr;
- g_main_context_pop_thread_default (context);
- g_main_context_unref (context);
-
if (owner_id)
- return StartupType::Server;
+ startup = StartupType::Server;
+ else
+ startup = StartupType::Client;
- dbus_server_cleanup ();
- return StartupType::Client;
+ g_main_context_pop_thread_default (context);
+ g_main_context_unref (context);
ERROR:
if (error)
@@ -809,8 +866,10 @@ ERROR:
g_error_free (error);
}
- dbus_server_cleanup ();
- return StartupType::Unknown;
+ if (startup != StartupType::Server)
+ dbus_server_cleanup ();
+
+ return startup;
}
void dbus_server_cleanup ()
diff --git a/src/audacious/main.cc b/src/audacious/main.cc
index 8aa7e02..f3c4a5f 100644
--- a/src/audacious/main.cc
+++ b/src/audacious/main.cc
@@ -107,13 +107,16 @@ static bool parse_options (int argc, char * * argv)
else
uri = String (filename_to_uri (filename_build ({cur, arg})));
- if (uri)
- filenames.append (uri);
+ filenames.append (uri);
}
else if (! arg[1]) /* "-" (standard input) */
{
filenames.append (String ("stdin://"));
}
+ else if (arg[1] >= '1' && arg[1] <= '9') /* instance number */
+ {
+ aud_set_instance (arg[1] - '0');
+ }
else if (arg[1] == '-') /* long option */
{
bool found = false;
@@ -181,6 +184,7 @@ static void print_help ()
static const char pad[21] = " ";
fprintf (stderr, _("Usage: audacious [OPTION] ... [FILE] ...\n\n"));
+ fprintf (stderr, " -1, -2, -3, etc. %s\n", _("Select instance to run/control"));
for (auto & arg_info : arg_map)
fprintf (stderr, " -%c, --%s%.*s%s\n", arg_info.short_arg,
@@ -201,16 +205,18 @@ static void do_remote ()
g_type_init ();
#endif
- /* check whether this is the first instance */
+ /* check whether the selected instance is running */
if (dbus_server_init () != StartupType::Client)
return;
- if (! (bus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, & error)))
- goto ERR;
-
- if (! (obj = obj_audacious_proxy_new_sync (bus, (GDBusProxyFlags) 0,
- "org.atheme.audacious", "/org/atheme/audacious", nullptr, & error)))
- goto ERR;
+ if (! (bus = g_bus_get_sync (G_BUS_TYPE_SESSION, nullptr, & error)) ||
+ ! (obj = obj_audacious_proxy_new_sync (bus, (GDBusProxyFlags) 0,
+ dbus_server_name (), "/org/atheme/audacious", nullptr, & error)))
+ {
+ AUDERR ("D-Bus error: %s\n", error->message);
+ g_error_free (error);
+ return;
+ }
AUDINFO ("Connected to remote session.\n");
@@ -254,16 +260,13 @@ static void do_remote ()
if (options.mainwin)
obj_audacious_call_show_main_win_sync (obj, true, nullptr, nullptr);
+ const char * startup_id = getenv ("DESKTOP_STARTUP_ID");
+ if (startup_id)
+ obj_audacious_call_startup_notify_sync (obj, startup_id, nullptr, nullptr);
+
g_object_unref (obj);
exit (EXIT_SUCCESS);
-
-ERR:
- if (error)
- {
- AUDERR ("D-Bus error: %s\n", error->message);
- g_error_free (error);
- }
}
#endif
@@ -321,7 +324,6 @@ static void main_cleanup ()
}
filenames.clear ();
- aud_cleanup_paths ();
aud_leak_check ();
}
@@ -345,7 +347,6 @@ int main (int argc, char * * argv)
signals_init_one ();
#endif
- aud_init_paths ();
aud_init_i18n ();
if (! parse_options (argc, argv))
diff --git a/src/audacious/main.h b/src/audacious/main.h
index 58243c8..26d3b89 100644
--- a/src/audacious/main.h
+++ b/src/audacious/main.h
@@ -20,6 +20,8 @@
#ifndef _AUDACIOUS_MAIN_H
#define _AUDACIOUS_MAIN_H
+#include <libaudcore/objects.h>
+
/* dbus-server.c */
#ifdef USE_DBUS
@@ -29,6 +31,7 @@ enum class StartupType {
Unknown
};
+StringBuf dbus_server_name ();
StartupType dbus_server_init ();
void dbus_server_cleanup ();
diff --git a/src/audtool/audtool.h b/src/audtool/audtool.h
index cb62488..c0bb88d 100644
--- a/src/audtool/audtool.h
+++ b/src/audtool/audtool.h
@@ -114,6 +114,8 @@ void playback_stopped (int, char * *);
void playback_status (int, char * *);
void playback_seek (int, char * *);
void playback_seek_relative (int, char * *);
+void playback_record (int, char * *);
+void playback_recording (int, char * *);
void mainwin_show (int, char * *);
void show_preferences_window (int, char * *);
@@ -123,6 +125,8 @@ void shutdown_audacious_server (int, char * *);
void show_about_window (int, char * *);
void get_version (int argc, char * * argv);
+void plugin_is_enabled (int argc, char * * argv);
+void plugin_enable (int argc, char * * argv);
void equalizer_get_eq (int argc, char * * argv);
void equalizer_get_eq_preamp (int argc, char * * argv);
diff --git a/src/audtool/handlers_general.c b/src/audtool/handlers_general.c
index 67e00f3..68c9da5 100644
--- a/src/audtool/handlers_general.c
+++ b/src/audtool/handlers_general.c
@@ -88,6 +88,10 @@ void shutdown_audacious_server (int argc, char * * argv)
void get_handlers_list (int argc, char * * argv)
{
+ audtool_report ("Usage: audtool [-#] COMMAND ...");
+ audtool_report (" where # (1-9) selects the instance of Audacious to control");
+ audtool_report ("");
+
for (int i = 0; handlers[i].name; i ++)
{
if (! g_ascii_strcasecmp ("<sep>", handlers[i].name))
@@ -98,6 +102,7 @@ void get_handlers_list (int argc, char * * argv)
audtool_report ("");
audtool_report ("Commands may be prefixed with '--' (GNU-style long options) or not, your choice.");
+ audtool_report ("Show/hide and enable/disable commands take an optional 'on' or 'off' argument.");
audtool_report ("Report bugs to http://redmine.audacious-media-player.org/projects/audacious");
}
@@ -112,3 +117,36 @@ void get_version (int argc, char * * argv)
audtool_report ("Audacious %s", version);
g_free (version);
}
+
+void plugin_is_enabled (int argc, char * * argv)
+{
+ if (argc != 2)
+ {
+ audtool_whine_args (argv[0], "<plugin>");
+ exit (1);
+ }
+
+ gboolean enabled = FALSE;
+ obj_audacious_call_plugin_is_enabled_sync (dbus_proxy, argv[1], & enabled, NULL, NULL);
+
+ exit (! enabled);
+}
+
+void plugin_enable (int argc, char * * argv)
+{
+ gboolean enable = TRUE;
+
+ if (argc == 2)
+ enable = TRUE;
+ else if (argc == 3 && ! g_ascii_strcasecmp (argv[2], "on"))
+ enable = TRUE;
+ else if (argc == 3 && ! g_ascii_strcasecmp (argv[2], "off"))
+ enable = FALSE;
+ else
+ {
+ audtool_whine_args (argv[0], "<plugin> <on/off>");
+ exit (1);
+ }
+
+ obj_audacious_call_plugin_enable_sync (dbus_proxy, argv[1], enable, NULL, NULL);
+}
diff --git a/src/audtool/handlers_playback.c b/src/audtool/handlers_playback.c
index 1b3a90a..8a812b7 100644
--- a/src/audtool/handlers_playback.c
+++ b/src/audtool/handlers_playback.c
@@ -101,3 +101,16 @@ void playback_seek_relative (int argc, char * * argv)
obj_audacious_call_time_sync (dbus_proxy, & oldtime, NULL, NULL);
obj_audacious_call_seek_sync (dbus_proxy, MAX (0, oldtime + atof (argv[1]) * 1000), NULL, NULL);
}
+
+void playback_record (int argc, char * * argv)
+{
+ obj_audacious_call_record_sync (dbus_proxy, NULL, NULL);
+}
+
+void playback_recording (int argc, char * * argv)
+{
+ gboolean recording = FALSE;
+ obj_audacious_call_recording_sync (dbus_proxy, & recording, NULL, NULL);
+
+ exit (! recording);
+}
diff --git a/src/audtool/main.c b/src/audtool/main.c
index 2ed0fa9..c49d62e 100644
--- a/src/audtool/main.c
+++ b/src/audtool/main.c
@@ -20,6 +20,7 @@
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <locale.h>
#include "audtool.h"
@@ -54,6 +55,8 @@ const struct commandhandler handlers[] =
{"playback-status", playback_status, "print status (playing/paused/stopped)", 0},
{"playback-seek", playback_seek, "seek to given time", 1},
{"playback-seek-relative", playback_seek_relative, "seek to relative time offset", 1},
+ {"playback-record", playback_record, "toggle stream recording", 0},
+ {"playback-recording", playback_recording, "exit code = 0 if recording", 0},
{"<sep>", NULL, "Playlist commands", 0},
{"playlist-advance", playlist_advance, "skip to next song", 0},
@@ -121,6 +124,8 @@ const struct commandhandler handlers[] =
{"about-show", show_about_window, "show/hide About window", 1},
{"version", get_version, "print Audacious version", 0},
+ {"plugin-is-enabled", plugin_is_enabled, "exit code = 0 if plugin is enabled", 1},
+ {"plugin-enable", plugin_enable, "enable/disable plugin", 2},
{"shutdown", shutdown_audacious_server, "shut down Audacious", 0},
{"help", get_handlers_list, "print this help", 0},
@@ -140,7 +145,7 @@ static void audtool_disconnect (void)
connection = NULL;
}
-static void audtool_connect (void)
+static void audtool_connect (int instance)
{
GError * error = NULL;
@@ -153,8 +158,14 @@ static void audtool_connect (void)
exit (EXIT_FAILURE);
}
- dbus_proxy = obj_audacious_proxy_new_sync (connection, 0,
- "org.atheme.audacious", "/org/atheme/audacious", NULL, & error);
+ char name[32];
+ if (instance == 1)
+ strcpy (name, "org.atheme.audacious");
+ else
+ sprintf (name, "org.atheme.audacious-%d", instance);
+
+ dbus_proxy = obj_audacious_proxy_new_sync (connection, 0, name,
+ "/org/atheme/audacious", NULL, & error);
if (! dbus_proxy)
{
@@ -169,7 +180,8 @@ static void audtool_connect (void)
int main (int argc, char * * argv)
{
- int i, j = 0, k = 0;
+ int instance = 1;
+ int i, j, k = 0;
setlocale (LC_CTYPE, "");
@@ -177,7 +189,15 @@ int main (int argc, char * * argv)
g_type_init();
#endif
- audtool_connect ();
+ // parse instance number (must come first)
+ if (argc >= 2 && argv[1][0] == '-' && argv[1][1] >= '1' && argv[1][1] <= '9' && ! argv[1][2])
+ {
+ instance = argv[1][1] - '0';
+ argc --;
+ argv ++;
+ }
+
+ audtool_connect (instance);
if (argc < 2)
{
@@ -193,7 +213,7 @@ int main (int argc, char * * argv)
! g_ascii_strcasecmp (g_strconcat ("--", handlers[i].name, NULL),
argv[j])) && g_ascii_strcasecmp ("<sep>", handlers[i].name))
{
- int numargs = handlers[i].args + 1 < argc - j ? handlers[i].args + 1 : argc - j;
+ int numargs = MIN (handlers[i].args + 1, argc - j);
handlers[i].handler (numargs, & argv[j]);
j += handlers[i].args;
k ++;
diff --git a/src/audtool/wrappers.c b/src/audtool/wrappers.c
index c255954..35afa35 100644
--- a/src/audtool/wrappers.c
+++ b/src/audtool/wrappers.c
@@ -24,19 +24,18 @@
void generic_on_off (int argc, char * * argv, OnOffFunc func)
{
- gboolean show = TRUE;
-
- if (argc >= 2)
+ gboolean show;
+
+ if (argc == 1)
+ show = TRUE;
+ else if (argc == 2 && ! g_ascii_strcasecmp (argv[1], "on"))
+ show = TRUE;
+ else if (argc == 2 && ! g_ascii_strcasecmp (argv[1], "off"))
+ show = FALSE;
+ else
{
- if (! g_ascii_strcasecmp (argv[1], "on"))
- show = TRUE;
- else if (! g_ascii_strcasecmp (argv[1], "off"))
- show = FALSE;
- else
- {
- audtool_whine_args (argv[0], "<on/off>");
- exit (1);
- }
+ audtool_whine_args (argv[0], "<on/off>");
+ exit (1);
}
func (dbus_proxy, show, NULL, NULL);
diff --git a/src/dbus/aud-dbus.xml b/src/dbus/aud-dbus.xml
index 36e2184..d028441 100644
--- a/src/dbus/aud-dbus.xml
+++ b/src/dbus/aud-dbus.xml
@@ -2,7 +2,7 @@
<!--
* aud-dbus.xml
- * Copyright 2007-2014 Ben Tucker, Yoshiki Yazawa, Matti Hämäläinen, and
+ * Copyright 2007-2016 Ben Tucker, Yoshiki Yazawa, Matti Hämäläinen, and
* John Lindgren
*
* Redistribution and use in source and binary forms, with or without
@@ -28,6 +28,16 @@
<arg type="s" direction="out" name="version"/>
</method>
+ <method name="PluginIsEnabled">
+ <arg type="s" direction="in" name="plugin" />
+ <arg type="b" direction="out" name="enabled" />
+ </method>
+
+ <method name="PluginEnable">
+ <arg type="s" direction="in" name="plugin" />
+ <arg type="b" direction="in" name="enable" />
+ </method>
+
<!-- Quit Audacious -->
<method name="Quit" />
@@ -44,6 +54,11 @@
<arg type="b" direction="in" name="show"/>
</method>
+ <!-- Send startup notification -->
+ <method name="StartupNotify">
+ <arg type="s" direction="in" name="id"/>
+ </method>
+
<!-- Get names of available 'standard' tuple fields -->
<method name="GetTupleFields">
<!-- Return array of tuple field names -->
@@ -57,6 +72,9 @@
<!-- Pause playback -->
<method name="Pause" />
+ <!-- Either play or pause -->
+ <method name="PlayPause" />
+
<!-- Stop playback -->
<method name="Stop" />
@@ -85,6 +103,14 @@
<arg type="s" direction="out" name="status"/>
</method>
+ <!-- Toggle recording of stream -->
+ <method name="Record" />
+
+ <!-- Is stream recording enabled? -->
+ <method name="Recording">
+ <arg type="b" direction="out" name="is_recording"/>
+ </method>
+
<!-- What is the bitrate, frequency, and number of channels of the -->
<!-- current audio format? -->
<method name="Info">
@@ -291,9 +317,6 @@
<arg type="b" direction="in" name="show"/>
</method>
- <!-- Either play or pause -->
- <method name="PlayPause" />
-
<!-- Playqueue get playlist pos -->
<method name="QueueGetListPos">
<arg type="u" direction="in" name="qpos"/>
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/libaudqt/volumebutton.h b/src/libaudcore/cue-cache.h
index d27281d..84ab516 100644
--- a/src/libaudqt/volumebutton.h
+++ b/src/libaudcore/cue-cache.h
@@ -1,6 +1,6 @@
/*
- * volumebutton.h
- * Copyright 2014 William Pitcock
+ * 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:
@@ -17,34 +17,25 @@
* the use of this software.
*/
-#ifndef LIBAUDQT_VOLUMEBUTTON_H
-#define LIBAUDQT_VOLUMEBUTTON_H
+#ifndef LIBAUDCORE_CUE_CACHE_H
+#define LIBAUDCORE_CUE_CACHE_H
-#include <QToolButton>
+#include "index.h"
+#include "tuple.h"
-class QFrame;
-class QSlider;
+struct CueCacheNode;
-namespace audqt {
-
-class VolumeButton : public QToolButton
+class CueCacheRef
{
public:
- VolumeButton (QWidget * parent = nullptr);
-
-private:
- void updateIcon (int val);
- void updateVolume ();
- void showSlider ();
- void setVolume (int val);
- QToolButton * newSliderButton (int delta);
+ CueCacheRef (const char * filename);
+ ~CueCacheRef ();
- void wheelEvent (QWheelEvent * e);
+ const Index<PlaylistAddItem> & load ();
- QSlider * m_slider;
- QFrame * m_container;
+private:
+ String m_filename;
+ CueCacheNode * m_node;
};
-} // namespace audqt
-
-#endif
+#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 {
diff --git a/src/libaudgui/Makefile b/src/libaudgui/Makefile
index 55ba07f..dde93e1 100644
--- a/src/libaudgui/Makefile
+++ b/src/libaudgui/Makefile
@@ -1,6 +1,6 @@
SHARED_LIB = ${LIB_PREFIX}audgui${LIB_SUFFIX}
-LIB_MAJOR = 3
-LIB_MINOR = 1
+LIB_MAJOR = 4
+LIB_MINOR = 0
SRCS = about.cc \
confirm.cc \
diff --git a/src/libaudgui/eq-preset.cc b/src/libaudgui/eq-preset.cc
index 2aa37b0..a2f1c9d 100644
--- a/src/libaudgui/eq-preset.cc
+++ b/src/libaudgui/eq-preset.cc
@@ -103,14 +103,13 @@ static void populate_list ()
static void save_list ()
{
- auto sort_cb = [] (const EqualizerPreset & a, const EqualizerPreset & b, void *)
- { return strcmp (a.name, b.name); };
-
Index<EqualizerPreset> presets;
for (const PresetItem & item : preset_list)
presets.append (item.preset);
- presets.sort (sort_cb, nullptr);
+ presets.sort ([] (const EqualizerPreset & a, const EqualizerPreset & b)
+ { return strcmp (a.name, b.name); });
+
aud_eq_write_presets (presets, "eq.preset");
}
diff --git a/src/libaudgui/file-opener.cc b/src/libaudgui/file-opener.cc
index a51abb8..efe339c 100644
--- a/src/libaudgui/file-opener.cc
+++ b/src/libaudgui/file-opener.cc
@@ -104,6 +104,7 @@ static GtkWidget * create_filebrowser (gboolean open)
gtk_container_add ((GtkContainer *) window, vbox);
GtkWidget * chooser = gtk_file_chooser_widget_new (GTK_FILE_CHOOSER_ACTION_OPEN);
+ gtk_file_chooser_set_local_only ((GtkFileChooser *) chooser, false);
gtk_file_chooser_set_select_multiple ((GtkFileChooser *) chooser, true);
String path = aud_get_str ("audgui", "filesel_path");
diff --git a/src/libaudgui/infopopup.cc b/src/libaudgui/infopopup.cc
index a117b2b..bc2edfd 100644
--- a/src/libaudgui/infopopup.cc
+++ b/src/libaudgui/infopopup.cc
@@ -318,7 +318,7 @@ EXPORT void audgui_infopopup_show (int playlist, int entry)
String filename = aud_playlist_entry_get_filename (playlist, entry);
Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry);
- if (filename && tuple)
+ if (filename && tuple.valid ())
infopopup_show (filename, tuple);
}
diff --git a/src/libaudgui/infowin.cc b/src/libaudgui/infowin.cc
index 353c9c3..5002dfc 100644
--- a/src/libaudgui/infowin.cc
+++ b/src/libaudgui/infowin.cc
@@ -72,6 +72,7 @@ static struct {
static GtkWidget * infowin;
static int current_playlist_id, current_entry;
static String current_file;
+static Tuple current_tuple;
static PluginHandle * current_decoder = nullptr;
static bool can_write = false;
static QueuedFunc ministatus_timer;
@@ -200,20 +201,17 @@ static void ministatus_display_message (const char * text)
static void infowin_update_tuple ()
{
- Tuple tuple;
- tuple.set_filename (current_file);
-
- set_field_str_from_entry (tuple, Tuple::Title, widgets.title);
- set_field_str_from_entry (tuple, Tuple::Artist, widgets.artist);
- set_field_str_from_entry (tuple, Tuple::Album, widgets.album);
- set_field_str_from_entry (tuple, Tuple::AlbumArtist, widgets.album_artist);
- set_field_str_from_entry (tuple, Tuple::Comment, widgets.comment);
- set_field_str_from_entry (tuple, Tuple::Genre, gtk_bin_get_child ((GtkBin *)
- widgets.genre));
- set_field_int_from_entry (tuple, Tuple::Year, widgets.year);
- set_field_int_from_entry (tuple, Tuple::Track, widgets.track);
-
- if (aud_file_write_tuple (current_file, current_decoder, tuple))
+ set_field_str_from_entry (current_tuple, Tuple::Title, widgets.title);
+ set_field_str_from_entry (current_tuple, Tuple::Artist, widgets.artist);
+ set_field_str_from_entry (current_tuple, Tuple::Album, widgets.album);
+ set_field_str_from_entry (current_tuple, Tuple::AlbumArtist, widgets.album_artist);
+ set_field_str_from_entry (current_tuple, Tuple::Comment, widgets.comment);
+ set_field_str_from_entry (current_tuple, Tuple::Genre,
+ gtk_bin_get_child ((GtkBin *) widgets.genre));
+ set_field_int_from_entry (current_tuple, Tuple::Year, widgets.year);
+ set_field_int_from_entry (current_tuple, Tuple::Track, widgets.track);
+
+ if (aud_file_write_tuple (current_file, current_decoder, current_tuple))
{
ministatus_display_message (_("Save successful"));
gtk_widget_set_sensitive (widgets.apply, false);
@@ -279,6 +277,7 @@ static void infowin_destroyed ()
infowin = nullptr;
current_file = String ();
+ current_tuple = Tuple ();
current_decoder = nullptr;
}
@@ -408,7 +407,7 @@ static void create_infowin ()
hook_associate ("art ready", (HookFunction) infowin_display_image, nullptr);
}
-static void infowin_show (int list, int entry, const char * filename,
+static void infowin_show (int list, int entry, const String & filename,
const Tuple & tuple, PluginHandle * decoder, bool writable)
{
if (! infowin)
@@ -416,7 +415,8 @@ static void infowin_show (int list, int entry, const char * filename,
current_playlist_id = aud_playlist_get_unique_id (list);
current_entry = entry;
- current_file = String (filename);
+ current_file = filename;
+ current_tuple = tuple.ref ();
current_decoder = decoder;
can_write = writable;
@@ -469,17 +469,20 @@ EXPORT void audgui_infowin_show (int playlist, int entry)
String error;
PluginHandle * decoder = aud_playlist_entry_get_decoder (playlist, entry,
Playlist::Wait, & error);
+ Tuple tuple = decoder ? aud_playlist_entry_get_tuple (playlist, entry,
+ Playlist::Wait, & error) : Tuple ();
- if (decoder && ! aud_custom_infowin (filename, decoder))
+ if (decoder && tuple.valid () && ! aud_custom_infowin (filename, decoder))
{
- Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry, Playlist::Wait, & error);
- if (tuple)
- {
- tuple.delete_fallbacks ();
- infowin_show (playlist, entry, filename, tuple, decoder,
- aud_file_can_write_tuple (filename, decoder));
- }
+ /* cuesheet entries cannot be updated */
+ bool can_write = aud_file_can_write_tuple (filename, decoder) &&
+ ! tuple.is_set (Tuple::StartTime);
+
+ tuple.delete_fallbacks ();
+ infowin_show (playlist, entry, filename, tuple, decoder, can_write);
}
+ else
+ audgui_infowin_hide ();
if (error)
aud_ui_show_error (str_printf (_("Error opening %s:\n%s"),
diff --git a/src/libaudgui/init.cc b/src/libaudgui/init.cc
index d560bc3..ede7414 100644
--- a/src/libaudgui/init.cc
+++ b/src/libaudgui/init.cc
@@ -170,5 +170,4 @@ EXPORT void audgui_cleanup ()
plugin_menu_cleanup ();
plugin_prefs_cleanup ();
- urilist_cleanup ();
}
diff --git a/src/libaudgui/internal.h b/src/libaudgui/internal.h
index d54204d..b43c97c 100644
--- a/src/libaudgui/internal.h
+++ b/src/libaudgui/internal.h
@@ -61,7 +61,4 @@ GtkWidget * plugin_view_new (PluginType type);
void status_init ();
void status_cleanup ();
-/* urilist.c */
-void urilist_cleanup ();
-
#endif /* AUDGUI_INTERNAL_H */
diff --git a/src/libaudgui/jump-to-track-cache.cc b/src/libaudgui/jump-to-track-cache.cc
index db07995..7dcf242 100644
--- a/src/libaudgui/jump-to-track-cache.cc
+++ b/src/libaudgui/jump-to-track-cache.cc
@@ -132,7 +132,7 @@ void JumpToTrackCache::init ()
item.entry = entry;
item.path = String (uri_to_display (aud_playlist_entry_get_filename (playlist, entry)));
- Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry, Playlist::Guess);
+ Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry, Playlist::NoWait);
item.title = tuple.get_str (Tuple::Title);
item.artist = tuple.get_str (Tuple::Artist);
item.album = tuple.get_str (Tuple::Album);
diff --git a/src/libaudgui/jump-to-track.cc b/src/libaudgui/jump-to-track.cc
index b903912..a681163 100644
--- a/src/libaudgui/jump-to-track.cc
+++ b/src/libaudgui/jump-to-track.cc
@@ -220,7 +220,7 @@ static void list_get_value (void * user, int row, int column, GValue * value)
g_value_set_int (value, 1 + entry);
break;
case 1:
- Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry, Playlist::Guess);
+ Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry, Playlist::NoWait);
g_value_set_string (value, tuple.get_str (Tuple::FormattedTitle));
break;
}
diff --git a/src/libaudgui/libaudgui-gtk.h b/src/libaudgui/libaudgui-gtk.h
index fc144a5..8bd8c08 100644
--- a/src/libaudgui/libaudgui-gtk.h
+++ b/src/libaudgui/libaudgui-gtk.h
@@ -63,6 +63,10 @@ void audgui_simple_message (GtkWidget * * widget, GtkMessageType type,
GtkWidget * audgui_button_new (const char * text, const char * icon,
AudguiCallback callback, void * data);
+GtkWidget * audgui_file_entry_new (GtkFileChooserAction action, const char * title);
+String audgui_file_entry_get_uri (GtkWidget * entry);
+void audgui_file_entry_set_uri (GtkWidget * entry, const char * uri);
+
GtkWidget * audgui_dialog_new (GtkMessageType type, const char * title,
const char * text, GtkWidget * button1, GtkWidget * button2);
void audgui_dialog_add_widget (GtkWidget * dialog, GtkWidget * widget);
diff --git a/src/libaudgui/list.cc b/src/libaudgui/list.cc
index a0b6ce6..906922b 100644
--- a/src/libaudgui/list.cc
+++ b/src/libaudgui/list.cc
@@ -343,6 +343,10 @@ static void drag_begin (GtkWidget * widget, GdkDragContext * context,
g_signal_stop_emission_by_name (widget, "drag-begin");
model->dragging = true;
+
+ /* If button_press_cb preserved a multiple selection, tell button_release_cb
+ * not to clear it. */
+ model->frozen = false;
}
static void drag_end (GtkWidget * widget, GdkDragContext * context,
@@ -426,14 +430,10 @@ static gboolean drag_motion (GtkWidget * widget, GdkDragContext * context,
{
g_signal_stop_emission_by_name (widget, "drag-motion");
- /* If button_press_cb preserved a multiple selection, tell button_release_cb
- * not to clear it. */
- model->frozen = false;
-
- if (model->dragging && MODEL_HAS_CB (model, shift_rows)) /* dragging within same list */
- gdk_drag_status (context, GDK_ACTION_MOVE, time);
- else if (MODEL_HAS_CB (model, data_type)) /* cross-widget dragging */
- gdk_drag_status (context, GDK_ACTION_COPY, time);
+ if (model->dragging && MODEL_HAS_CB (model, shift_rows))
+ gdk_drag_status (context, GDK_ACTION_MOVE, time); /* dragging within same list */
+ else if (MODEL_HAS_CB (model, data_type) && MODEL_HAS_CB (model, receive_data))
+ gdk_drag_status (context, GDK_ACTION_COPY, time); /* cross-widget dragging */
else
return false;
@@ -489,15 +489,17 @@ static gboolean drag_drop (GtkWidget * widget, GdkDragContext * context, int x,
gboolean success = true;
int row = audgui_list_row_at_point_rounded (widget, x, y);
- if (model->dragging && MODEL_HAS_CB (model, shift_rows)) /* dragging within same list */
+ if (model->dragging && MODEL_HAS_CB (model, shift_rows))
{
+ /* dragging within same list */
if (model->clicked_row >= 0 && model->clicked_row < model->rows)
model->cbs->shift_rows (model->user, model->clicked_row, row);
else
success = false;
}
- else if (MODEL_HAS_CB (model, data_type)) /* cross-widget dragging */
+ else if (MODEL_HAS_CB (model, data_type) && MODEL_HAS_CB (model, receive_data))
{
+ /* cross-widget dragging */
model->receive_row = row;
gtk_drag_get_data (widget, context, gdk_atom_intern
(model->cbs->data_type, false), time);
@@ -608,18 +610,22 @@ EXPORT GtkWidget * audgui_list_new_real (const AudguiListCallbacks * cbs, int cb
gboolean supports_drag = false;
- if (MODEL_HAS_CB (model, data_type) && MODEL_HAS_CB (model, get_data) &&
- MODEL_HAS_CB (model, receive_data))
+ if (MODEL_HAS_CB (model, data_type) &&
+ (MODEL_HAS_CB (model, get_data) || MODEL_HAS_CB (model, receive_data)))
{
const GtkTargetEntry target = {(char *) cbs->data_type, 0, 0};
- gtk_drag_source_set (list, GDK_BUTTON1_MASK, & target, 1,
- GDK_ACTION_COPY);
- gtk_drag_dest_set (list, (GtkDestDefaults) 0, & target, 1, GDK_ACTION_COPY);
+ if (MODEL_HAS_CB (model, get_data))
+ {
+ gtk_drag_source_set (list, GDK_BUTTON1_MASK, & target, 1, GDK_ACTION_COPY);
+ g_signal_connect (list, "drag-data-get", (GCallback) drag_data_get, model);
+ }
- g_signal_connect (list, "drag-data-get", (GCallback) drag_data_get, model);
- g_signal_connect (list, "drag-data-received", (GCallback)
- drag_data_received, model);
+ if (MODEL_HAS_CB (model, receive_data))
+ {
+ gtk_drag_dest_set (list, (GtkDestDefaults) 0, & target, 1, GDK_ACTION_COPY);
+ g_signal_connect (list, "drag-data-received", (GCallback) drag_data_received, model);
+ }
supports_drag = true;
}
diff --git a/src/libaudgui/menu.cc b/src/libaudgui/menu.cc
index 3069d8b..ef5ac34 100644
--- a/src/libaudgui/menu.cc
+++ b/src/libaudgui/menu.cc
@@ -65,7 +65,7 @@ static void unhook_cb (GtkCheckMenuItem * check, const AudguiMenuItem * item)
EXPORT GtkWidget * audgui_menu_item_new_with_domain
(const AudguiMenuItem * item, GtkAccelGroup * accel, const char * domain)
{
- const char * name = domain ? dgettext (domain, item->name) : item->name;
+ const char * name = (domain && item->name) ? dgettext (domain, item->name) : item->name;
GtkWidget * widget = nullptr;
if (name && item->func && ! item->cname) /* normal widget */
diff --git a/src/libaudgui/playlists.cc b/src/libaudgui/playlists.cc
index d106292..4922fba 100644
--- a/src/libaudgui/playlists.cc
+++ b/src/libaudgui/playlists.cc
@@ -68,7 +68,7 @@ static void finish_job (void * data)
Playlist::GetMode mode = Playlist::Wait;
if (aud_get_bool (nullptr, "metadata_on_play"))
- mode = Playlist::Nothing;
+ mode = Playlist::NoWait;
if (list >= 0)
{
@@ -184,6 +184,7 @@ static void create_selector (ImportExportJob * job, const char * filename, const
}
job->selector = gtk_file_chooser_dialog_new (title, nullptr, action, nullptr, nullptr);
+ gtk_file_chooser_set_local_only ((GtkFileChooser *) job->selector, false);
if (filename)
gtk_file_chooser_set_uri ((GtkFileChooser *) job->selector, filename);
diff --git a/src/libaudgui/prefs-widget.cc b/src/libaudgui/prefs-widget.cc
index df6b68b..c240ece 100644
--- a/src/libaudgui/prefs-widget.cc
+++ b/src/libaudgui/prefs-widget.cc
@@ -34,7 +34,7 @@ static void widget_changed (GtkWidget * widget, const PreferencesWidget * w)
{
case PreferencesWidget::CheckButton:
{
- gboolean set = gtk_toggle_button_get_active ((GtkToggleButton *) widget);
+ bool set = gtk_toggle_button_get_active ((GtkToggleButton *) widget);
w->cfg.set_bool (set);
auto child = (GtkWidget *) g_object_get_data ((GObject *) widget, "child");
@@ -45,10 +45,17 @@ static void widget_changed (GtkWidget * widget, const PreferencesWidget * w)
}
case PreferencesWidget::RadioButton:
- if (gtk_toggle_button_get_active ((GtkToggleButton *) widget))
+ {
+ bool set = gtk_toggle_button_get_active ((GtkToggleButton *) widget);
+ if (set)
w->cfg.set_int (w->data.radio_btn.value);
+ auto child = (GtkWidget *) g_object_get_data ((GObject *) widget, "child");
+ if (child)
+ gtk_widget_set_sensitive (child, set);
+
break;
+ }
case PreferencesWidget::SpinButton:
if (w->cfg.type == WidgetConfig::Int)
@@ -66,6 +73,13 @@ static void widget_changed (GtkWidget * widget, const PreferencesWidget * w)
w->cfg.set_string (gtk_entry_get_text ((GtkEntry *) widget));
break;
+ case PreferencesWidget::FileEntry:
+ {
+ String uri = audgui_file_entry_get_uri (widget);
+ w->cfg.set_string (uri ? uri : "");
+ break;
+ }
+
case PreferencesWidget::ComboBox:
{
auto items = (const ComboItem *) g_object_get_data ((GObject *) widget, "comboitems");
@@ -120,6 +134,10 @@ static void widget_update (void *, void * widget)
gtk_entry_set_text ((GtkEntry *) widget, w->cfg.get_string ());
break;
+ case PreferencesWidget::FileEntry:
+ audgui_file_entry_set_uri ((GtkWidget *) widget, w->cfg.get_string ());
+ break;
+
case PreferencesWidget::ComboBox:
combobox_update ((GtkWidget *) widget, w);
break;
@@ -158,6 +176,7 @@ static void widget_init (GtkWidget * widget, const PreferencesWidget * w)
break;
case PreferencesWidget::Entry:
+ case PreferencesWidget::FileEntry:
case PreferencesWidget::ComboBox:
g_signal_connect (widget, "changed", (GCallback) widget_changed, (void *) w);
break;
@@ -247,6 +266,31 @@ static void create_entry (const PreferencesWidget * widget, GtkWidget * * label,
widget_init (* entry, widget);
}
+/* WIDGET_FILE_ENTRY */
+
+static void create_file_entry (const PreferencesWidget * widget,
+ GtkWidget * * label, GtkWidget * * entry, const char * domain)
+{
+ switch (widget->data.file_entry.mode)
+ {
+ case FileSelectMode::File:
+ * entry = audgui_file_entry_new (GTK_FILE_CHOOSER_ACTION_OPEN, _("Choose File"));
+ break;
+
+ case FileSelectMode::Folder:
+ * entry = audgui_file_entry_new (GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER, _("Choose Folder"));
+ break;
+ }
+
+ if (widget->label)
+ {
+ * label = gtk_label_new (dgettext (domain, widget->label));
+ gtk_misc_set_alignment ((GtkMisc *) * label, 1, 0.5);
+ }
+
+ widget_init (* entry, widget);
+}
+
/* WIDGET_COMBO_BOX */
static void combobox_update (GtkWidget * combobox, const PreferencesWidget * widget)
@@ -339,6 +383,11 @@ static void fill_table (GtkWidget * table,
middle_policy = (GtkAttachOptions) (GTK_EXPAND | GTK_FILL);
break;
+ case PreferencesWidget::FileEntry:
+ create_file_entry (& w, & widget_left, & widget_middle, domain);
+ middle_policy = (GtkAttachOptions) (GTK_EXPAND | GTK_FILL);
+ break;
+
case PreferencesWidget::ComboBox:
create_cbox (& w, & widget_left, & widget_middle, domain);
break;
@@ -371,7 +420,7 @@ void audgui_create_widgets_with_domain (GtkWidget * box,
{
GtkWidget * widget = nullptr, * child_box = nullptr;
bool disable_child = false;
- GSList * radio_btn_group = nullptr;
+ GSList * radio_btn_group[2] = {};
int indent = 0;
int spacing = 0;
@@ -406,8 +455,11 @@ void audgui_create_widgets_with_domain (GtkWidget * box,
widget = nullptr;
disable_child = false;
- if (radio_btn_group && w.type != PreferencesWidget::RadioButton)
- radio_btn_group = nullptr;
+ if (w.type != PreferencesWidget::RadioButton)
+ radio_btn_group[w.child] = nullptr;
+
+ if (! w.child)
+ radio_btn_group[true] = nullptr;
switch (w.type)
{
@@ -449,9 +501,10 @@ void audgui_create_widgets_with_domain (GtkWidget * box,
}
case PreferencesWidget::RadioButton:
- widget = gtk_radio_button_new_with_mnemonic (radio_btn_group,
- dgettext (domain, w.label));
- radio_btn_group = gtk_radio_button_get_group ((GtkRadioButton *) widget);
+ widget = gtk_radio_button_new_with_mnemonic
+ (radio_btn_group[w.child], dgettext (domain, w.label));
+ radio_btn_group[w.child] = gtk_radio_button_get_group ((GtkRadioButton *) widget);
+ disable_child = (w.cfg.get_int () != w.data.radio_btn.value);
widget_init (widget, & w);
break;
@@ -503,11 +556,16 @@ void audgui_create_widgets_with_domain (GtkWidget * box,
break;
case PreferencesWidget::Entry:
+ case PreferencesWidget::FileEntry:
{
widget = gtk_hbox_new (false, 6);
GtkWidget * entry = nullptr;
- create_entry (& w, & label, & entry, domain);
+
+ if (w.type == PreferencesWidget::FileEntry)
+ create_file_entry (& w, & label, & entry, domain);
+ else
+ create_entry (& w, & label, & entry, domain);
if (label)
gtk_box_pack_start ((GtkBox *) widget, label, false, false, 0);
diff --git a/src/libaudgui/prefs-window.cc b/src/libaudgui/prefs-window.cc
index 948119c..5ba9a09 100644
--- a/src/libaudgui/prefs-window.cc
+++ b/src/libaudgui/prefs-window.cc
@@ -125,12 +125,20 @@ static const ComboItem chardet_detector_presets[] = {
};
static const ComboItem bitdepth_elements[] = {
+ ComboItem (N_("Automatic"), -1),
ComboItem ("16", 16),
ComboItem ("24", 24),
ComboItem ("32", 32),
ComboItem (N_("Floating point"), 0)
};
+static const ComboItem record_elements[] = {
+ ComboItem (N_("As decoded"), (int) OutputStream::AsDecoded),
+ ComboItem (N_("After applying ReplayGain"), (int) OutputStream::AfterReplayGain),
+ ComboItem (N_("After applying effects"), (int) OutputStream::AfterEffects),
+ ComboItem (N_("After applying equalization"), (int) OutputStream::AfterEqualizer)
+};
+
static Index<ComboItem> iface_combo_elements;
static int iface_combo_selected;
static GtkWidget * iface_prefs_box;
@@ -160,7 +168,7 @@ static void output_bit_depth_changed ();
static const PreferencesWidget output_combo_widgets[] = {
WidgetCombo (N_("Output plugin:"),
- WidgetInt (output_combo_selected, output_combo_changed),
+ WidgetInt (output_combo_selected, output_combo_changed, "audgui update output combo"),
{0, output_combo_fill}),
WidgetCustomGTK (output_create_config_button),
WidgetCustomGTK (output_create_about_button)
@@ -199,15 +207,19 @@ static const PreferencesWidget audio_page_widgets[] = {
WidgetSpin (N_("Buffer size:"),
WidgetInt (0, "output_buffer_size"),
{100, 10000, 1000, N_("ms")}),
- WidgetCustomGTK (record_create_checkbox),
- WidgetBox ({{record_buttons}, true},
- WIDGET_CHILD),
WidgetCheck (N_("Soft clipping"),
WidgetBool (0, "soft_clipping")),
WidgetCheck (N_("Use software volume control (not recommended)"),
WidgetBool (0, "software_volume_control")),
- WidgetLabel (N_("<b>Replay Gain</b>")),
- WidgetCheck (N_("Enable Replay Gain"),
+ WidgetLabel (N_("<b>Recording Settings</b>")),
+ WidgetCustomGTK (record_create_checkbox),
+ WidgetBox ({{record_buttons}, true},
+ WIDGET_CHILD),
+ WidgetCombo (N_("Record stream:"),
+ WidgetInt (0, "record_stream"),
+ {{record_elements}}),
+ WidgetLabel (N_("<b>ReplayGain</b>")),
+ WidgetCheck (N_("Enable ReplayGain"),
WidgetBool (0, "enable_replay_gain")),
WidgetCheck (N_("Album mode"),
WidgetBool (0, "replay_gain_album"),
@@ -215,8 +227,6 @@ static const PreferencesWidget audio_page_widgets[] = {
WidgetCheck (N_("Prevent clipping (recommended)"),
WidgetBool (0, "enable_clipping_prevention"),
WIDGET_CHILD),
- WidgetLabel (N_("<b>Adjust Levels</b>"),
- WIDGET_CHILD),
WidgetTable ({{gain_table}},
WIDGET_CHILD)
};
@@ -279,8 +289,10 @@ static const PreferencesWidget playlist_page_widgets[] = {
WidgetLabel (N_("<b>Song Display</b>")),
WidgetCheck (N_("Show song numbers"),
WidgetBool (0, "show_numbers_in_pl", send_title_change)),
- WidgetCheck (N_("Show leading zeroes (02:00 instead of 2:00)"),
+ WidgetCheck (N_("Show leading zeroes (02:00 vs. 2:00)"),
WidgetBool (0, "leading_zero", send_title_change)),
+ WidgetCheck (N_("Show hours separately (1:30:00 vs. 90:00)"),
+ WidgetBool (0, "show_hours", send_title_change)),
WidgetCustomGTK (create_titlestring_table),
WidgetLabel (N_("<b>Compatibility</b>")),
WidgetCheck (N_("Interpret \\ (backward slash) as a folder delimiter"),
@@ -617,13 +629,20 @@ static void create_appearance_category ()
static void output_combo_changed ()
{
- PluginHandle * plugin = aud_plugin_list (PluginType::Output)[output_combo_selected];
+ auto & list = aud_plugin_list (PluginType::Output);
+ PluginHandle * plugin = list[output_combo_selected];
if (aud_plugin_enable (plugin, true))
{
gtk_widget_set_sensitive (output_config_button, aud_plugin_has_configure (plugin));
gtk_widget_set_sensitive (output_about_button, aud_plugin_has_about (plugin));
}
+ else
+ {
+ /* set combo box back to current output */
+ output_combo_selected = list.find (aud_plugin_get_current (PluginType::Output));
+ hook_call ("audgui update output combo", nullptr);
+ }
}
static ArrayRef<ComboItem> output_combo_fill ()
diff --git a/src/libaudgui/preset-browser.cc b/src/libaudgui/preset-browser.cc
index fa7ccb5..9144086 100644
--- a/src/libaudgui/preset-browser.cc
+++ b/src/libaudgui/preset-browser.cc
@@ -51,6 +51,8 @@ static void show_preset_browser (const char * title, gboolean save,
GTK_RESPONSE_CANCEL, save ? _("Save") : _("Load"), GTK_RESPONSE_ACCEPT,
nullptr);
+ gtk_file_chooser_set_local_only ((GtkFileChooser *) browser, false);
+
if (default_filename)
gtk_file_chooser_set_current_name ((GtkFileChooser *) browser, default_filename);
diff --git a/src/libaudgui/queue-manager.cc b/src/libaudgui/queue-manager.cc
index 195a6ba..2b5e6d0 100644
--- a/src/libaudgui/queue-manager.cc
+++ b/src/libaudgui/queue-manager.cc
@@ -45,7 +45,7 @@ static void get_value (void * user, int row, int column, GValue * value)
g_value_set_int (value, 1 + entry);
break;
case COLUMN_TITLE:
- Tuple tuple = aud_playlist_entry_get_tuple (list, entry, Playlist::Guess);
+ Tuple tuple = aud_playlist_entry_get_tuple (list, entry, Playlist::NoWait);
g_value_set_string (value, tuple.get_str (Tuple::FormattedTitle));
break;
}
diff --git a/src/libaudgui/status.cc b/src/libaudgui/status.cc
index fa04fd3..6ba9b3c 100644
--- a/src/libaudgui/status.cc
+++ b/src/libaudgui/status.cc
@@ -27,7 +27,7 @@
static GtkWidget * progress_window;
static GtkWidget * progress_label, * progress_label_2;
-static GtkWidget * error_window;
+static GtkWidget * error_window, * info_window;
static void create_progress_window ()
{
@@ -85,12 +85,18 @@ static void show_error (void * data, void * user)
audgui_simple_message (& error_window, GTK_MESSAGE_ERROR, _("Error"), (const char *) data);
}
+static void show_info (void * data, void * user)
+{
+ audgui_simple_message (& info_window, GTK_MESSAGE_INFO, _("Information"), (const char *) data);
+}
+
void status_init ()
{
hook_associate ("ui show progress", show_progress, nullptr);
hook_associate ("ui show progress 2", show_progress_2, nullptr);
hook_associate ("ui hide progress", hide_progress, nullptr);
hook_associate ("ui show error", show_error, nullptr);
+ hook_associate ("ui show info", show_info, nullptr);
}
void status_cleanup ()
@@ -99,9 +105,12 @@ void status_cleanup ()
hook_dissociate ("ui show progress 2", show_progress_2);
hook_dissociate ("ui hide progress", hide_progress);
hook_dissociate ("ui show error", show_error);
+ hook_dissociate ("ui show info", show_info);
if (progress_window)
gtk_widget_destroy (progress_window);
if (error_window)
gtk_widget_destroy (error_window);
+ if (info_window)
+ gtk_widget_destroy (info_window);
}
diff --git a/src/libaudgui/urilist.cc b/src/libaudgui/urilist.cc
index 1dc04e4..a0809f4 100644
--- a/src/libaudgui/urilist.cc
+++ b/src/libaudgui/urilist.cc
@@ -21,33 +21,13 @@
#include <libaudcore/audstrings.h>
#include <libaudcore/drct.h>
-#include <libaudcore/mainloop.h>
-#include <libaudcore/multihash.h>
#include <libaudcore/playlist.h>
-#include <libaudcore/tuple.h>
-#include <libaudcore/vfs.h>
#include "libaudgui.h"
-static SimpleHash<String, Tuple> tuple_cache;
-static QueuedFunc cleanup_timer;
-
-void urilist_cleanup ()
-{
- tuple_cache.clear ();
- cleanup_timer.stop ();
-}
-
static String check_uri (const char * name)
{
- if (! strstr (name, "://"))
- {
- StringBuf uri = filename_to_uri (name);
- if (uri)
- return String (uri);
- }
-
- return String (name);
+ return strstr (name, "://") ? String (name) : String (filename_to_uri (name));
}
static Index<PlaylistAddItem> urilist_to_index (const char * list)
@@ -67,12 +47,7 @@ static Index<PlaylistAddItem> urilist_to_index (const char * list)
next = end = strchr (list, 0);
if (end > list)
- {
- String filename = check_uri (str_copy (list, end - list));
- const Tuple * tuple = tuple_cache.lookup (filename);
-
- index.append (filename, tuple ? tuple->ref () : Tuple ());
- }
+ index.append (check_uri (str_copy (list, end - list)));
list = next;
}
@@ -92,6 +67,8 @@ EXPORT void audgui_urilist_insert (int playlist, int at, const char * list)
EXPORT Index<char> audgui_urilist_create_from_selected (int playlist)
{
+ aud_playlist_cache_selected (playlist);
+
Index<char> buf;
int entries = aud_playlist_entry_count (playlist);
@@ -103,15 +80,9 @@ EXPORT Index<char> audgui_urilist_create_from_selected (int playlist)
buf.append ('\n');
String filename = aud_playlist_entry_get_filename (playlist, count);
- Tuple tuple = aud_playlist_entry_get_tuple (playlist, count, Playlist::Nothing);
-
buf.insert (filename, -1, strlen (filename));
- if (tuple)
- tuple_cache.add (filename, std::move (tuple));
}
}
- cleanup_timer.queue (30000, [] (void *) { urilist_cleanup (); }, nullptr);
-
return buf;
}
diff --git a/src/libaudgui/util.cc b/src/libaudgui/util.cc
index 54e1c74..98307e5 100644
--- a/src/libaudgui/util.cc
+++ b/src/libaudgui/util.cc
@@ -125,6 +125,86 @@ EXPORT GtkWidget * audgui_button_new (const char * text, const char * icon,
return button;
}
+struct FileEntryData {
+ GtkFileChooserAction action;
+ String title;
+};
+
+static void entry_response_cb (GtkWidget * dialog, int response, GtkWidget * entry)
+{
+ if (response == GTK_RESPONSE_ACCEPT)
+ {
+ char * uri = gtk_file_chooser_get_uri ((GtkFileChooser *) dialog);
+ if (uri)
+ {
+ audgui_file_entry_set_uri (entry, uri);
+ g_free (uri);
+ }
+ }
+
+ gtk_widget_destroy (dialog);
+}
+
+static void entry_browse_cb (GtkWidget * entry, GtkEntryIconPosition pos,
+ GdkEvent * event, const FileEntryData * data)
+{
+ GtkWidget * dialog = gtk_file_chooser_dialog_new (data->title, nullptr,
+ data->action, _("Open"), GTK_RESPONSE_ACCEPT, _("Cancel"),
+ GTK_RESPONSE_REJECT, nullptr);
+
+ gtk_file_chooser_set_local_only ((GtkFileChooser *) dialog, false);
+
+ String uri = audgui_file_entry_get_uri (entry);
+ if (uri)
+ gtk_file_chooser_set_uri ((GtkFileChooser *) dialog, uri);
+
+ g_signal_connect (dialog, "response", (GCallback) entry_response_cb, entry);
+ g_signal_connect_object (entry, "destroy", (GCallback) gtk_widget_destroy,
+ dialog, G_CONNECT_SWAPPED);
+
+ gtk_widget_show (dialog);
+}
+
+EXPORT GtkWidget * audgui_file_entry_new (GtkFileChooserAction action, const char * title)
+{
+ GtkWidget * entry = gtk_entry_new ();
+
+ auto data = new FileEntryData {action, String (title)};
+ auto destroy_cb = [] (void * data) { delete (FileEntryData *) data; };
+ g_object_set_data_full ((GObject *) entry, "file-entry-data", data, destroy_cb);
+
+ gtk_entry_set_icon_from_icon_name ((GtkEntry *) entry,
+ GTK_ENTRY_ICON_SECONDARY, "document-open");
+ g_signal_connect (entry, "icon-press", (GCallback) entry_browse_cb, data);
+
+ return entry;
+}
+
+EXPORT String audgui_file_entry_get_uri (GtkWidget * entry)
+{
+ const char * text = gtk_entry_get_text ((GtkEntry *) entry);
+
+ if (! text[0])
+ return String ();
+ else if (strstr (text, "://"))
+ return String (text);
+ else
+ return String (filename_to_uri (filename_normalize (filename_expand (str_copy (text)))));
+}
+
+EXPORT void audgui_file_entry_set_uri (GtkWidget * entry, const char * uri)
+{
+ if (! uri || ! uri[0])
+ {
+ gtk_entry_set_text ((GtkEntry *) entry, "");
+ return;
+ }
+
+ StringBuf path = uri_to_filename (uri, false);
+ gtk_entry_set_text ((GtkEntry *) entry, path ? filename_contract (std::move (path)) : uri);
+ gtk_editable_set_position ((GtkEditable *) entry, -1);
+}
+
EXPORT GtkWidget * audgui_dialog_new (GtkMessageType type, const char * title,
const char * text, GtkWidget * button1, GtkWidget * button2)
{
diff --git a/src/libaudqt/Makefile b/src/libaudqt/Makefile
index e8bbd2f..b3b6f9a 100644
--- a/src/libaudqt/Makefile
+++ b/src/libaudqt/Makefile
@@ -1,6 +1,6 @@
SHARED_LIB = ${LIB_PREFIX}audqt${LIB_SUFFIX}
-LIB_MAJOR = 0
-LIB_MINOR = 1
+LIB_MAJOR = 1
+LIB_MINOR = 0
SRCS = about.cc \
art.cc \
@@ -22,7 +22,11 @@ SRCS = about.cc \
util.cc \
volumebutton.cc
-INCLUDES = libaudqt.h iface.h volumebutton.h info-widget.h menu.h
+INCLUDES = export.h \
+ iface.h \
+ info-widget.h \
+ libaudqt.h \
+ menu.h
include ../../buildsys.mk
include ../../extra.mk
@@ -33,7 +37,8 @@ LD = ${CXX}
CPPFLAGS := -I.. -I../.. \
${CPPFLAGS} \
- ${QT_CFLAGS}
+ ${QT_CFLAGS} \
+ -DLIBAUDQT_BUILD
CFLAGS += ${LIB_CFLAGS}
diff --git a/src/libaudqt/export.h b/src/libaudqt/export.h
new file mode 100644
index 0000000..457e848
--- /dev/null
+++ b/src/libaudqt/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 LIBAUDQT_EXPORT_H
+#define LIBAUDQT_EXPORT_H
+
+#ifdef _WIN32
+ #ifdef LIBAUDQT_BUILD
+ #define LIBAUDQT_PUBLIC __declspec(dllexport)
+ #else
+ #define LIBAUDQT_PUBLIC __declspec(dllimport)
+ #endif
+#else
+ #define LIBAUDQT_PUBLIC __attribute__ ((visibility ("default")))
+#endif
+
+#endif // LIBAUDQT_EXPORT_H
diff --git a/src/libaudqt/iface.h b/src/libaudqt/iface.h
index e799dd1..fe36766 100644
--- a/src/libaudqt/iface.h
+++ b/src/libaudqt/iface.h
@@ -21,30 +21,33 @@
#define LIBAUDQT_IFACE_H
#include <libaudcore/plugin.h>
+#include <libaudqt/export.h>
#include <libaudqt/libaudqt.h>
#include <libaudqt/menu.h>
namespace audqt {
-class QtIfacePlugin : public IfacePlugin
+class LIBAUDQT_PUBLIC QtIfacePlugin : public IfacePlugin
{
public:
constexpr QtIfacePlugin (PluginInfo info) : IfacePlugin (info) {}
void show_about_window () { aboutwindow_show (); }
void hide_about_window () { aboutwindow_hide (); }
- void show_filebrowser (bool open) { fileopener_show (open ? FileMode::Open : FileMode::Add); }
+
+ void show_filebrowser (bool open)
+ { fileopener_show (open ? FileMode::Open : FileMode::Add); }
+
void hide_filebrowser () {}
void show_jump_to_song () {}
void hide_jump_to_song () {}
void show_prefs_window () { prefswin_show (); }
void hide_prefs_window () { prefswin_hide (); }
- void plugin_menu_add (AudMenuID id, void func (), const char * name, const char * icon) {
- menu_add (id, func, name, icon);
- }
- void plugin_menu_remove (AudMenuID id, void func ()) {
- menu_remove (id, func);
- }
+
+ void plugin_menu_add (AudMenuID id, void func (), const char * name, const char * icon)
+ { menu_add (id, func, name, icon); }
+ void plugin_menu_remove (AudMenuID id, void func ())
+ { menu_remove (id, func); }
};
} // namespace audqt
diff --git a/src/libaudqt/info-widget.cc b/src/libaudqt/info-widget.cc
index 85b0c0c..aa86e14 100644
--- a/src/libaudqt/info-widget.cc
+++ b/src/libaudqt/info-widget.cc
@@ -25,6 +25,7 @@
#include <libaudcore/i18n.h>
#include <libaudcore/probe.h>
+#include <libaudcore/tuple.h>
namespace audqt {
@@ -51,15 +52,48 @@ static const TupleFieldMap tuple_field_map[] = {
{nullptr, Tuple::Invalid, false},
{N_("Technical"), Tuple::Invalid, false},
{N_("Length"), Tuple::Length, false},
- {N_("MIME Type"), Tuple::MIMEType, false},
{N_("Codec"), Tuple::Codec, false},
{N_("Quality"), Tuple::Quality, false},
{N_("Bitrate"), Tuple::Bitrate, false},
};
-EXPORT InfoWidget::InfoWidget (QWidget * parent) : QTreeView (parent)
+class InfoModel : public QAbstractTableModel
{
- setModel (& m_model);
+public:
+ InfoModel (QObject * parent = nullptr) :
+ QAbstractTableModel (parent) {}
+
+ int rowCount (const QModelIndex & parent = QModelIndex ()) const
+ { return aud::n_elems (tuple_field_map); }
+ int columnCount (const QModelIndex & parent = QModelIndex ()) const
+ { return 2; }
+
+ QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const;
+ bool setData (const QModelIndex & index, const QVariant & value, int role = Qt::EditRole);
+ Qt::ItemFlags flags (const QModelIndex & index) const;
+
+ void setTupleData (const Tuple & tuple, String filename, PluginHandle * plugin)
+ {
+ m_tuple = tuple.ref ();
+ m_filename = filename;
+ m_plugin = plugin;
+ m_dirty = false;
+ }
+
+ bool updateFile () const;
+
+private:
+ Tuple m_tuple;
+ String m_filename;
+ PluginHandle * m_plugin = nullptr;
+ bool m_dirty = false;
+};
+
+EXPORT InfoWidget::InfoWidget (QWidget * parent) :
+ QTreeView (parent),
+ m_model (new InfoModel (this))
+{
+ setModel (m_model);
header ()->hide ();
setIndentation (0);
resizeColumnToContents (0);
@@ -72,29 +106,14 @@ EXPORT InfoWidget::~InfoWidget ()
EXPORT void InfoWidget::fillInfo (int playlist, int entry, const char * filename, const Tuple & tuple,
PluginHandle * decoder, bool updating_enabled)
{
- m_model.setTupleData (tuple, String (filename), decoder);
+ m_model->setTupleData (tuple, String (filename), decoder);
reset ();
setEditTriggers (updating_enabled ? QAbstractItemView::SelectedClicked : QAbstractItemView::NoEditTriggers);
}
EXPORT bool InfoWidget::updateFile ()
{
- return m_model.updateFile ();
-}
-
-InfoModel::InfoModel (QObject * parent) : QAbstractTableModel (parent)
-{
-}
-
-int InfoModel::rowCount (const QModelIndex & parent) const
-{
- auto r = ArrayRef<TupleFieldMap> (tuple_field_map);
- return r.len;
-}
-
-int InfoModel::columnCount (const QModelIndex & parent) const
-{
- return 2;
+ return m_model->updateFile ();
}
bool InfoModel::updateFile () const
@@ -102,10 +121,7 @@ bool InfoModel::updateFile () const
if (! m_dirty)
return true;
- Tuple t = m_tuple.ref ();
- t.set_filename (m_filename);
-
- return aud_file_write_tuple (m_filename, m_plugin, t);
+ return aud_file_write_tuple (m_filename, m_plugin, m_tuple);
}
bool InfoModel::setData (const QModelIndex & index, const QVariant & value, int role)
@@ -122,7 +138,7 @@ bool InfoModel::setData (const QModelIndex & index, const QVariant & value, int
auto t = Tuple::field_get_type (field_id);
if (t == Tuple::String)
{
- m_tuple.set_str (field_id, value.toString ().toLocal8Bit ());
+ m_tuple.set_str (field_id, value.toString ().toUtf8 ());
emit dataChanged (index, index, {role});
return true;
}
@@ -144,25 +160,19 @@ QVariant InfoModel::data (const QModelIndex & index, int role) const
{
if (index.column () == 0)
return translate_str (tuple_field_map [index.row ()].name);
- else if (index.column () == 1 && m_tuple)
+ else if (index.column () == 1)
{
if (field_id == Tuple::Invalid)
return QVariant ();
- auto t = Tuple::field_get_type (field_id);
-
- if (t == Tuple::String)
+ switch (m_tuple.get_value_type (field_id))
{
- const char * res = m_tuple.get_str (field_id);
- if (res)
- return QString (res);
- }
- else if (t == Tuple::Int)
- {
- int res = m_tuple.get_int (field_id);
- if (res == -1)
- return QVariant ();
- return res;
+ case Tuple::String:
+ return QString (m_tuple.get_str (field_id));
+ case Tuple::Int:
+ return m_tuple.get_int (field_id);
+ default:
+ return QVariant ();
}
}
}
@@ -197,12 +207,4 @@ Qt::ItemFlags InfoModel::flags (const QModelIndex & index) const
return Qt::ItemNeverHasChildren;
}
-void InfoModel::setTupleData (const Tuple & tuple, String filename, PluginHandle * plugin)
-{
- m_tuple = tuple.ref ();
- m_filename = filename;
- m_plugin = plugin;
- m_dirty = false;
-}
-
} // namespace audqt
diff --git a/src/libaudqt/info-widget.h b/src/libaudqt/info-widget.h
index edc43d1..86e3925 100644
--- a/src/libaudqt/info-widget.h
+++ b/src/libaudqt/info-widget.h
@@ -18,37 +18,17 @@
* the use of this software.
*/
-#include <QAbstractTableModel>
#include <QTreeView>
-
-#include <libaudcore/tuple.h>
+#include <libaudqt/export.h>
class PluginHandle;
+class Tuple;
namespace audqt {
-class InfoModel : public QAbstractTableModel
-{
-public:
- InfoModel (QObject * parent = nullptr);
-
- int rowCount (const QModelIndex & parent = QModelIndex ()) const;
- int columnCount (const QModelIndex & parent = QModelIndex ()) const;
- QVariant data (const QModelIndex & index, int role = Qt::DisplayRole) const;
- bool setData (const QModelIndex & index, const QVariant & value, int role = Qt::EditRole);
- Qt::ItemFlags flags (const QModelIndex & index) const;
-
- void setTupleData (const Tuple & tuple, String filename, PluginHandle * plugin);
- bool updateFile () const;
-
-private:
- Tuple m_tuple;
- String m_filename;
- PluginHandle * m_plugin = nullptr;
- bool m_dirty = false;
-};
+class InfoModel;
-class InfoWidget : public QTreeView
+class LIBAUDQT_PUBLIC InfoWidget : public QTreeView
{
public:
InfoWidget (QWidget * parent = nullptr);
@@ -59,7 +39,7 @@ public:
bool updateFile ();
private:
- InfoModel m_model;
+ InfoModel * m_model;
};
} // namespace audqt
diff --git a/src/libaudqt/infowin.cc b/src/libaudqt/infowin.cc
index 321e51d..a72e3c3 100644
--- a/src/libaudqt/infowin.cc
+++ b/src/libaudqt/infowin.cc
@@ -98,7 +98,8 @@ void InfoWindow::displayImage (const char * filename)
static InfoWindow * s_infowin = nullptr;
-EXPORT void infowin_show (int playlist, int entry)
+static void show_infowin (int playlist, int entry, const char * filename,
+ const Tuple & tuple, PluginHandle * decoder, bool can_write)
{
if (! s_infowin)
{
@@ -110,29 +111,38 @@ EXPORT void infowin_show (int playlist, int entry)
});
}
+ s_infowin->fillInfo (playlist, entry, filename, tuple, decoder, can_write);
+ s_infowin->resize (700, 300);
+ window_bring_to_front (s_infowin);
+}
+
+EXPORT void infowin_show (int playlist, int entry)
+{
String filename = aud_playlist_entry_get_filename (playlist, entry);
if (! filename)
return;
- PluginHandle * decoder = aud_playlist_entry_get_decoder (playlist, entry);
- if (! decoder)
- return;
-
- Tuple tuple = aud_playlist_entry_get_tuple (playlist, entry);
+ String error;
+ PluginHandle * decoder = aud_playlist_entry_get_decoder (playlist, entry,
+ Playlist::Wait, & error);
+ Tuple tuple = decoder ? aud_playlist_entry_get_tuple (playlist, entry,
+ Playlist::Wait, & error) : Tuple ();
- if (tuple)
+ if (decoder && tuple.valid () && ! aud_custom_infowin (filename, decoder))
{
+ /* cuesheet entries cannot be updated */
+ bool can_write = aud_file_can_write_tuple (filename, decoder) &&
+ ! tuple.is_set (Tuple::StartTime);
+
tuple.delete_fallbacks ();
- s_infowin->fillInfo (playlist, entry, filename, tuple, decoder,
- aud_file_can_write_tuple (filename, decoder));
+ show_infowin (playlist, entry, filename, tuple, decoder, can_write);
}
else
- aud_ui_show_error (str_printf (_("No info available for %s.\n"),
- (const char *) filename));
+ infowin_hide ();
- s_infowin->resize (700, 300);
-
- window_bring_to_front (s_infowin);
+ if (error)
+ aud_ui_show_error (str_printf (_("Error opening %s:\n%s"),
+ (const char *) filename, (const char *) error));
}
EXPORT void infowin_show_current ()
diff --git a/src/libaudqt/libaudqt.h b/src/libaudqt/libaudqt.h
index 54c68ce..ea21708 100644
--- a/src/libaudqt/libaudqt.h
+++ b/src/libaudqt/libaudqt.h
@@ -27,6 +27,7 @@
class QBoxLayout;
class QLayout;
class QPixmap;
+class QToolButton;
class QWidget;
enum class PluginType;
@@ -111,6 +112,9 @@ void infowin_hide ();
void queue_manager_show ();
void queue_manager_hide ();
+/* volumebutton.cc */
+QToolButton * volume_button_new (QWidget * parent = nullptr);
+
} // namespace audqt
#endif
diff --git a/src/libaudqt/prefs-builder.cc b/src/libaudqt/prefs-builder.cc
index 33cd0e9..dece721 100644
--- a/src/libaudqt/prefs-builder.cc
+++ b/src/libaudqt/prefs-builder.cc
@@ -39,8 +39,8 @@ void prefs_populate (QBoxLayout * layout, ArrayRef<PreferencesWidget> widgets, c
QBoxLayout * parent_layout = nullptr;
QBoxLayout * parent_orig_layout = nullptr;
- BooleanWidget * parent_widget = nullptr;
- QButtonGroup * radio_btn_group = nullptr;
+ ParentWidget * parent_widget = nullptr;
+ QButtonGroup * radio_btn_group[2] = {nullptr, nullptr};
for (const PreferencesWidget & w : widgets)
{
@@ -89,8 +89,10 @@ void prefs_populate (QBoxLayout * layout, ArrayRef<PreferencesWidget> widgets, c
parent_widget = nullptr;
}
- if (radio_btn_group && w.type != PreferencesWidget::RadioButton)
- radio_btn_group = nullptr;
+ if (w.type != PreferencesWidget::RadioButton)
+ radio_btn_group[w.child] = nullptr;
+ if (! w.child)
+ radio_btn_group[true] = nullptr;
switch (w.type)
{
@@ -149,15 +151,24 @@ void prefs_populate (QBoxLayout * layout, ArrayRef<PreferencesWidget> widgets, c
break;
case PreferencesWidget::Entry:
+ /* TODO: implement file chooser */
+ case PreferencesWidget::FileEntry:
layout->addWidget (new StringWidget (& w, domain));
break;
case PreferencesWidget::RadioButton:
- if (! radio_btn_group)
- radio_btn_group = new QButtonGroup;
+ {
+ if (! radio_btn_group[w.child])
+ radio_btn_group[w.child] = new QButtonGroup;
+
+ auto radio_btn = new RadioButtonWidget (& w, domain, radio_btn_group[w.child]);
+ layout->addWidget (radio_btn);
+
+ if (! w.child)
+ parent_widget = radio_btn;
- layout->addWidget (new RadioButtonWidget (& w, domain, radio_btn_group));
break;
+ }
case PreferencesWidget::FontButton:
/* XXX: unimplemented */
diff --git a/src/libaudqt/prefs-widget.cc b/src/libaudqt/prefs-widget.cc
index f4f2dfc..096809b 100644
--- a/src/libaudqt/prefs-widget.cc
+++ b/src/libaudqt/prefs-widget.cc
@@ -60,7 +60,7 @@ ButtonWidget::ButtonWidget (const PreferencesWidget * parent, const char * domai
/* boolean widget (checkbox) */
BooleanWidget::BooleanWidget (const PreferencesWidget * parent, const char * domain) :
QCheckBox (translate_str (parent->label, domain)),
- HookableWidget (parent, domain)
+ ParentWidget (parent, domain)
{
update ();
@@ -85,7 +85,7 @@ void BooleanWidget::update ()
RadioButtonWidget::RadioButtonWidget (const PreferencesWidget * parent,
const char * domain, QButtonGroup * btn_group) :
QRadioButton (translate_str (parent->label, domain)),
- HookableWidget (parent, domain)
+ ParentWidget (parent, domain)
{
if (btn_group)
btn_group->addButton (this, parent->data.radio_btn.value);
@@ -97,13 +97,18 @@ RadioButtonWidget::RadioButtonWidget (const PreferencesWidget * parent,
return;
if (checked)
m_parent->cfg.set_int (m_parent->data.radio_btn.value);
+ if (m_child_layout)
+ enable_layout (m_child_layout, checked);
});
}
void RadioButtonWidget::update ()
{
- if (m_parent->cfg.get_int () == m_parent->data.radio_btn.value)
+ bool checked = (m_parent->cfg.get_int () == m_parent->data.radio_btn.value);
+ if (checked)
setChecked (true);
+ if (m_child_layout)
+ enable_layout (m_child_layout, checked);
}
/* integer (spinbox) */
@@ -196,7 +201,7 @@ StringWidget::StringWidget (const PreferencesWidget * parent, const char * domai
if (parent->label)
layout->addWidget (new QLabel (translate_str (parent->label, domain)));
- if (parent->data.entry.password)
+ if (parent->type == PreferencesWidget::Entry && parent->data.entry.password)
m_lineedit->setEchoMode (QLineEdit::Password);
layout->addWidget (m_lineedit, 1);
diff --git a/src/libaudqt/prefs-widget.h b/src/libaudqt/prefs-widget.h
index f4deb4f..773945a 100644
--- a/src/libaudqt/prefs-widget.h
+++ b/src/libaudqt/prefs-widget.h
@@ -68,6 +68,19 @@ private:
SmartPtr<HookReceiver<HookableWidget>> hook;
};
+/* shared class which allows disabling child widgets */
+class ParentWidget : public HookableWidget {
+public:
+ void set_child_layout (QLayout * layout)
+ { m_child_layout = layout; }
+
+protected:
+ ParentWidget (const PreferencesWidget * parent, const char * domain) :
+ HookableWidget (parent, domain) {}
+
+ QLayout * m_child_layout = nullptr;
+};
+
/* button widget */
class ButtonWidget : public QPushButton {
public:
@@ -75,16 +88,11 @@ public:
};
/* boolean widget (checkbox) */
-class BooleanWidget : public QCheckBox, public HookableWidget {
+class BooleanWidget : public QCheckBox, public ParentWidget {
public:
BooleanWidget (const PreferencesWidget * parent, const char * domain);
-
- void set_child_layout (QLayout * layout)
- { m_child_layout = layout; }
-
private:
void update ();
- QLayout * m_child_layout = nullptr;
};
/* integer widget (spinner) */
@@ -97,7 +105,7 @@ private:
};
/* integer widget (radio button) */
-class RadioButtonWidget : public QRadioButton, HookableWidget {
+class RadioButtonWidget : public QRadioButton, public ParentWidget {
public:
RadioButtonWidget (const PreferencesWidget * parent, const char * domain,
QButtonGroup * btn_group);
diff --git a/src/libaudqt/prefs-window.cc b/src/libaudqt/prefs-window.cc
index e79b465..f9627e1 100644
--- a/src/libaudqt/prefs-window.cc
+++ b/src/libaudqt/prefs-window.cc
@@ -129,6 +129,7 @@ static const ComboItem chardet_detector_presets[] = {
};
static const ComboItem bitdepth_elements[] = {
+ ComboItem (N_("Automatic"), -1),
ComboItem ("16", 16),
ComboItem ("24", 24),
ComboItem ("32", 32),
@@ -164,7 +165,7 @@ static void output_bit_depth_changed ();
static const PreferencesWidget output_combo_widgets[] = {
WidgetCombo (N_("Output plugin:"),
- WidgetInt (output_combo_selected, output_combo_changed),
+ WidgetInt (output_combo_selected, output_combo_changed, "audqt update output combo"),
{0, output_combo_fill}),
WidgetCustomQt (output_create_config_button),
WidgetCustomQt (output_create_about_button)
@@ -194,8 +195,8 @@ static const PreferencesWidget audio_page_widgets[] = {
WidgetBool (0, "soft_clipping")),
WidgetCheck (N_("Use software volume control (not recommended)"),
WidgetBool (0, "software_volume_control")),
- WidgetLabel (N_("<b>Replay Gain</b>")),
- WidgetCheck (N_("Enable Replay Gain"),
+ WidgetLabel (N_("<b>ReplayGain</b>")),
+ WidgetCheck (N_("Enable ReplayGain"),
WidgetBool (0, "enable_replay_gain")),
WidgetCheck (N_("Album mode"),
WidgetBool (0, "replay_gain_album"),
@@ -270,8 +271,10 @@ static const PreferencesWidget playlist_page_widgets[] = {
WidgetLabel (N_("<b>Song Display</b>")),
WidgetCheck (N_("Show song numbers"),
WidgetBool (0, "show_numbers_in_pl", send_title_change)),
- WidgetCheck (N_("Show leading zeroes (02:00 instead of 2:00)"),
+ WidgetCheck (N_("Show leading zeroes (02:00 vs. 2:00)"),
WidgetBool (0, "leading_zero", send_title_change)),
+ WidgetCheck (N_("Show hours separately (1:30:00 vs. 90:00)"),
+ WidgetBool (0, "show_hours", send_title_change)),
WidgetCustomQt (create_titlestring_table),
WidgetLabel (N_("<b>Compatibility</b>")),
WidgetCheck (N_("Interpret \\ (backward slash) as a folder delimiter"),
@@ -367,7 +370,7 @@ static void * create_titlestring_table ()
}
QObject::connect (le, &QLineEdit::textChanged, [=] (const QString & text) {
- aud_set_str (nullptr, "generic_title_format", text.toLocal8Bit ().data ());
+ aud_set_str (nullptr, "generic_title_format", text.toUtf8 ().data ());
});
QObject::connect (cbox,
@@ -469,13 +472,20 @@ static void * iface_create_prefs_box ()
static void output_combo_changed ()
{
- PluginHandle * plugin = aud_plugin_list (PluginType::Output)[output_combo_selected];
+ auto & list = aud_plugin_list (PluginType::Output);
+ PluginHandle * plugin = list[output_combo_selected];
if (aud_plugin_enable (plugin, true))
{
output_config_button->setEnabled (aud_plugin_has_configure (plugin));
output_about_button->setEnabled (aud_plugin_has_about (plugin));
}
+ else
+ {
+ /* set combo box back to current output */
+ output_combo_selected = list.find (aud_plugin_get_current (PluginType::Output));
+ hook_call ("audqt update output combo", nullptr);
+ }
}
static void * output_create_config_button ()
diff --git a/src/libaudqt/queue-manager.cc b/src/libaudqt/queue-manager.cc
index 1f46f70..59dbc08 100644
--- a/src/libaudqt/queue-manager.cc
+++ b/src/libaudqt/queue-manager.cc
@@ -67,7 +67,7 @@ QVariant QueueManagerModel::data (const QModelIndex & index, int role) const
return entry + 1;
else
{
- Tuple tuple = aud_playlist_entry_get_tuple (list, entry, Playlist::Guess);
+ Tuple tuple = aud_playlist_entry_get_tuple (list, entry, Playlist::NoWait);
return QString ((const char *) tuple.get_str (Tuple::FormattedTitle));
}
}
diff --git a/src/libaudqt/util.cc b/src/libaudqt/util.cc
index 67224c4..9ca03b0 100644
--- a/src/libaudqt/util.cc
+++ b/src/libaudqt/util.cc
@@ -47,6 +47,10 @@ EXPORT void init ()
atexit ([] () { delete qapp; });
qapp->setAttribute (Qt::AA_UseHighDpiPixmaps);
+#if QT_VERSION >= QT_VERSION_CHECK(5, 3, 0)
+ qapp->setAttribute (Qt::AA_ForceRasterWidgets);
+#endif
+
qapp->setApplicationName (_("Audacious"));
qapp->setWindowIcon (QIcon::fromTheme (app_name));
}
@@ -131,6 +135,10 @@ EXPORT void simple_message (const char * title, const char * text, QMessageBox::
/* translate GTK+ accelerators and also handle dgettext() */
EXPORT QString translate_str (const char * str, const char * domain)
{
+ /* handle null and empty strings */
+ if (! str || ! str[0])
+ return QString (str);
+
/* translate the GTK+ accelerator (_) into a Qt accelerator (&) */
return QString (dgettext (domain, str)).replace ('_', '&');
}
diff --git a/src/libaudqt/volumebutton.cc b/src/libaudqt/volumebutton.cc
index ec8f889..ca25310 100644
--- a/src/libaudqt/volumebutton.cc
+++ b/src/libaudqt/volumebutton.cc
@@ -17,7 +17,6 @@
* the use of this software.
*/
-#include "volumebutton.h"
#include "libaudqt.h"
#include <QFrame>
@@ -32,7 +31,25 @@
namespace audqt {
-EXPORT VolumeButton::VolumeButton (QWidget * parent) :
+class VolumeButton : public QToolButton
+{
+public:
+ VolumeButton (QWidget * parent = nullptr);
+
+private:
+ void updateIcon (int val);
+ void updateVolume ();
+ void showSlider ();
+ void setVolume (int val);
+ QToolButton * newSliderButton (int delta);
+
+ void wheelEvent (QWheelEvent * e);
+
+ QSlider * m_slider;
+ QFrame * m_container;
+};
+
+VolumeButton::VolumeButton (QWidget * parent) :
QToolButton (parent)
{
setFocusPolicy (Qt::NoFocus);
@@ -142,4 +159,9 @@ void VolumeButton::wheelEvent (QWheelEvent * e)
m_slider->setValue (++ val);
}
+EXPORT QToolButton * volume_button_new (QWidget * parent)
+{
+ return new VolumeButton (parent);
+}
+
} // namespace audqt
diff --git a/src/libaudtag/Makefile b/src/libaudtag/Makefile
index 6d6d106..961b768 100644
--- a/src/libaudtag/Makefile
+++ b/src/libaudtag/Makefile
@@ -1,6 +1,6 @@
SHARED_LIB = ${LIB_PREFIX}audtag${LIB_SUFFIX}
-LIB_MAJOR = 2
-LIB_MINOR = 1
+LIB_MAJOR = 3
+LIB_MINOR = 0
SRCS = audtag.cc \
util.cc \
diff --git a/src/libaudtag/ape/ape.cc b/src/libaudtag/ape/ape.cc
index 165fed4..5188aa8 100644
--- a/src/libaudtag/ape/ape.cc
+++ b/src/libaudtag/ape/ape.cc
@@ -240,12 +240,8 @@ static Index<ValuePair> ape_read_items (VFSFile & handle)
return list;
}
-bool APETagModule::read_tag (VFSFile & handle, Tuple * ptuple, Index<char> * image)
+bool APETagModule::read_tag (VFSFile & handle, Tuple & tuple, Index<char> * image)
{
- if (! ptuple)
- return true; // nothing to do
-
- Tuple & tuple = * ptuple;
Index<ValuePair> list = ape_read_items (handle);
for (const ValuePair & pair : list)
diff --git a/src/libaudtag/audtag.cc b/src/libaudtag/audtag.cc
index bb50788..1eef8ca 100644
--- a/src/libaudtag/audtag.cc
+++ b/src/libaudtag/audtag.cc
@@ -26,7 +26,7 @@
namespace audtag {
-EXPORT bool read_tag (VFSFile & file, Tuple * tuple, Index<char> * image)
+EXPORT bool read_tag (VFSFile & file, Tuple & tuple, Index<char> * image)
{
TagModule * module = find_tag_module (file, TagType::None);
@@ -39,19 +39,7 @@ EXPORT bool read_tag (VFSFile & file, Tuple * tuple, Index<char> * image)
return module->read_tag (file, tuple, image);
}
-EXPORT bool tuple_read (Tuple & tuple, VFSFile & file)
-{
- return read_tag (file, & tuple, nullptr);
-}
-
-EXPORT Index<char> image_read (VFSFile & file)
-{
- Index<char> image;
- read_tag (file, nullptr, & image);
- return image;
-}
-
-EXPORT bool tuple_write (const Tuple & tuple, VFSFile & file, TagType new_type)
+EXPORT bool write_tuple (VFSFile & file, const Tuple & tuple, TagType new_type)
{
TagModule * module = find_tag_module (file, new_type);
diff --git a/src/libaudtag/audtag.h b/src/libaudtag/audtag.h
index e2467c3..70ec3b7 100644
--- a/src/libaudtag/audtag.h
+++ b/src/libaudtag/audtag.h
@@ -32,15 +32,11 @@ enum class TagType
ID3v2
};
-bool tuple_read (Tuple & tuple, VFSFile & file) __attribute__((deprecated));
-Index<char> image_read (VFSFile & file) __attribute__((deprecated));
+bool read_tag (VFSFile & file, Tuple & tuple, Index<char> * image);
/* new_type specifies the type of tag (see the TagType enum) that should be
* written if the file does not have any existing tag. */
-bool tuple_write (const Tuple & tuple, VFSFile & file, TagType new_type);
-
-/* since Audacious 3.7: reads tuple and image in one pass */
-bool read_tag (VFSFile & file, Tuple * tuple, Index<char> * image);
+bool write_tuple (VFSFile & file, const Tuple & tuple, TagType new_type);
}
diff --git a/src/libaudtag/builtin.h b/src/libaudtag/builtin.h
index 86a1f0c..1426def 100644
--- a/src/libaudtag/builtin.h
+++ b/src/libaudtag/builtin.h
@@ -17,42 +17,44 @@
* the use of this software.
*/
-#include "libaudtag/audtag.h"
-#include "libaudtag/tag_module.h"
-#include "libaudtag/util.h"
+#include "tag_module.h"
#ifndef __LIBAUDTAG_BUILTIN_H__
#define __LIBAUDTAG_BUILTIN_H__
namespace audtag {
-struct ID3v1TagModule : TagModule {
- ID3v1TagModule() : TagModule("ID3v1", TagType::None) { };
+struct ID3v1TagModule : TagModule
+{
+ constexpr ID3v1TagModule () : TagModule ("ID3v1", TagType::None) {}
bool can_handle_file (VFSFile & file);
- bool read_tag (VFSFile & file, Tuple * tuple, Index<char> * image);
+ bool read_tag (VFSFile & file, Tuple & tuple, Index<char> * image);
};
-struct ID3v22TagModule : TagModule {
- ID3v22TagModule() : TagModule("ID3v2.2", TagType::None) { };
+struct ID3v22TagModule : TagModule
+{
+ constexpr ID3v22TagModule () : TagModule ("ID3v2.2", TagType::None) {}
bool can_handle_file (VFSFile & file);
- bool read_tag (VFSFile & file, Tuple * tuple, Index<char> * image);
+ bool read_tag (VFSFile & file, Tuple & tuple, Index<char> * image);
};
-struct ID3v24TagModule : TagModule {
- ID3v24TagModule() : TagModule("ID3v2.3/v2.4", TagType::ID3v2) { };
+struct ID3v24TagModule : TagModule
+{
+ constexpr ID3v24TagModule () : TagModule ("ID3v2.3/v2.4", TagType::ID3v2) {}
bool can_handle_file (VFSFile & file);
- bool read_tag (VFSFile & file, Tuple * tuple, Index<char> * image);
+ bool read_tag (VFSFile & file, Tuple & tuple, Index<char> * image);
bool write_tag (VFSFile & file, const Tuple & tuple);
};
-struct APETagModule : TagModule {
- APETagModule() : TagModule("APE", TagType::APE) { };
+struct APETagModule : TagModule
+{
+ constexpr APETagModule () : TagModule ("APE", TagType::APE) {}
bool can_handle_file (VFSFile & file);
- bool read_tag (VFSFile & file, Tuple * tuple, Index<char> * image);
+ bool read_tag (VFSFile & file, Tuple & tuple, Index<char> * image);
bool write_tag (VFSFile & file, const Tuple & tuple);
};
diff --git a/src/libaudtag/id3/id3-common.cc b/src/libaudtag/id3/id3-common.cc
index 78e3d06..5d6f80a 100644
--- a/src/libaudtag/id3/id3-common.cc
+++ b/src/libaudtag/id3/id3-common.cc
@@ -34,12 +34,12 @@
#define ID3_ENCODING_UTF16_BE 2
#define ID3_ENCODING_UTF8 3
-static void * memchr16 (const void * mem, int16_t chr, int len)
+static const void * find_nul_utf16 (const void * mem, int len)
{
while (len >= 2)
{
- if (* (int16_t *) mem == chr)
- return (void *) mem;
+ if (! ((const char *) mem)[0] && ! ((const char *) mem)[1])
+ return mem;
mem = (char *) mem + 2;
len -= 2;
@@ -52,7 +52,7 @@ static void id3_strnlen (const char * data, int size, int encoding,
int * bytes_without_nul, int * bytes_with_nul)
{
bool is16 = (encoding == ID3_ENCODING_UTF16 || encoding == ID3_ENCODING_UTF16_BE);
- char * nul = is16 ? (char *) memchr16 (data, 0, size) : (char *) memchr (data, 0, size);
+ auto nul = (const char *) (is16 ? find_nul_utf16 (data, size) : memchr (data, 0, size));
if (nul)
{
@@ -181,8 +181,8 @@ static bool decode_rva_block (const char * * _data, int * _size,
return false;
* channel = (unsigned char) data[0];
- * adjustment = (char) data[1]; /* first byte is signed */
- * adjustment = (* adjustment << 8) | (unsigned char) data[2];
+ /* first byte is signed, but C/C++ allows shifting only unsigned values */
+ * adjustment = (int16_t) ((unsigned char) data[1] << 8) | (unsigned char) data[2];
* adjustment_unit = 512;
peak_bits = (unsigned char) data[3];
diff --git a/src/libaudtag/id3/id3v1.cc b/src/libaudtag/id3/id3v1.cc
index 96d7b5a..65ec9e5 100644
--- a/src/libaudtag/id3/id3v1.cc
+++ b/src/libaudtag/id3/id3v1.cc
@@ -24,6 +24,7 @@
#include <libaudcore/audstrings.h>
#include <libaudtag/builtin.h>
+#include <libaudtag/util.h>
#pragma pack(push)
#pragma pack(1)
@@ -95,12 +96,8 @@ static bool combine_string (Tuple & tuple, Tuple::Field field,
return true;
}
-bool ID3v1TagModule::read_tag (VFSFile & file, Tuple * ptuple, Index<char> * image)
+bool ID3v1TagModule::read_tag (VFSFile & file, Tuple & tuple, Index<char> * image)
{
- if (! ptuple)
- return true; // nothing to do
-
- Tuple & tuple = * ptuple;
ID3v1Tag tag;
ID3v1Ext ext;
diff --git a/src/libaudtag/id3/id3v22.cc b/src/libaudtag/id3/id3v22.cc
index 56235f9..b435faa 100644
--- a/src/libaudtag/id3/id3v22.cc
+++ b/src/libaudtag/id3/id3v22.cc
@@ -26,6 +26,7 @@
#include <libaudcore/audstrings.h>
#include <libaudcore/runtime.h>
#include <libaudtag/builtin.h>
+#include <libaudtag/util.h>
#include "id3-common.h"
@@ -209,7 +210,7 @@ bool ID3v22TagModule::can_handle_file (VFSFile & handle)
& data_size);
}
-bool ID3v22TagModule::read_tag (VFSFile & handle, Tuple * ptuple, Index<char> * image)
+bool ID3v22TagModule::read_tag (VFSFile & handle, Tuple & tuple, Index<char> * image)
{
int version, header_size, data_size;
bool syncsafe;
@@ -223,9 +224,6 @@ bool ID3v22TagModule::read_tag (VFSFile & handle, Tuple * ptuple, Index<char> *
AUDDBG ("Reading tags from %i bytes of ID3 data in %s\n", data_size,
handle.filename ());
- Tuple trash; // dump data here if caller does not want the tuple
- Tuple & tuple = ptuple ? * ptuple : trash;
-
for (pos = 0; pos < data_size; )
{
int frame_size;
diff --git a/src/libaudtag/id3/id3v24.cc b/src/libaudtag/id3/id3v24.cc
index f48cff2..c38f227 100644
--- a/src/libaudtag/id3/id3v24.cc
+++ b/src/libaudtag/id3/id3v24.cc
@@ -30,6 +30,7 @@
#include <libaudcore/multihash.h>
#include <libaudcore/runtime.h>
#include <libaudtag/builtin.h>
+#include <libaudtag/util.h>
#include "id3-common.h"
@@ -381,7 +382,7 @@ static void read_all_frames (const Index<char> & data, int version, FrameDict &
}
}
-static bool write_frame (int fd, const GenericFrame & frame, int version, int * frame_size)
+static bool write_frame (VFSFile & file, const GenericFrame & frame, int version, int * frame_size)
{
AUDDBG ("Writing frame %s, size %d\n", (const char *) frame.key, frame.len ());
@@ -396,10 +397,10 @@ static bool write_frame (int fd, const GenericFrame & frame, int version, int *
header.size = TO_BE32 (size);
header.flags = 0;
- if (write (fd, & header, sizeof (ID3v2FrameHeader)) != sizeof (ID3v2FrameHeader))
+ if (file.fwrite (& header, 1, sizeof (ID3v2FrameHeader)) != sizeof (ID3v2FrameHeader))
return false;
- if (write (fd, & frame[0], frame.len ()) != frame.len ())
+ if (file.fwrite (& frame[0], 1, frame.len ()) != frame.len ())
return false;
* frame_size = sizeof (ID3v2FrameHeader) + frame.len ();
@@ -407,7 +408,7 @@ static bool write_frame (int fd, const GenericFrame & frame, int version, int *
}
struct WriteState {
- int fd;
+ VFSFile & file;
int version;
int written_size;
};
@@ -419,21 +420,21 @@ static void write_frame_list (const String & key, FrameList & list, void * user)
for (const GenericFrame & frame : list)
{
int size;
- if (write_frame (state->fd, frame, state->version, & size))
+ if (write_frame (state->file, frame, state->version, & size))
state->written_size += size;
}
}
-static int write_all_frames (int fd, FrameDict & dict, int version)
+static int write_all_frames (VFSFile & file, FrameDict & dict, int version)
{
- WriteState state = {fd, version, 0};
+ WriteState state = {file, version, 0};
dict.iterate (write_frame_list, & state);
AUDDBG ("Total frame bytes written = %d.\n", state.written_size);
return state.written_size;
}
-static bool write_header (int fd, int version, int size)
+static bool write_header (VFSFile & file, int version, int size)
{
ID3v2Header header;
@@ -443,7 +444,7 @@ static bool write_header (int fd, int version, int size)
header.flags = 0;
header.size = TO_BE32 (syncsafe32 (size));
- return write (fd, & header, sizeof (ID3v2Header)) == sizeof (ID3v2Header);
+ return file.fwrite (& header, 1, sizeof (ID3v2Header)) == sizeof (ID3v2Header);
}
static int get_frame_id (const char * key)
@@ -550,7 +551,7 @@ bool ID3v24TagModule::can_handle_file (VFSFile & handle)
& data_size, & footer_size);
}
-bool ID3v24TagModule::read_tag (VFSFile & handle, Tuple * ptuple, Index<char> * image)
+bool ID3v24TagModule::read_tag (VFSFile & handle, Tuple & tuple, Index<char> * image)
{
int version, header_size, data_size, footer_size;
bool syncsafe;
@@ -561,10 +562,6 @@ bool ID3v24TagModule::read_tag (VFSFile & handle, Tuple * ptuple, Index<char> *
return false;
Index<char> data = read_tag_data (handle, data_size, syncsafe);
-
- Tuple trash; // dump data here if caller does not want the tuple
- Tuple & tuple = ptuple ? * ptuple : trash;
-
FrameList rva_frames;
for (const char * pos = data.begin (); pos < data.end (); )
@@ -671,26 +668,26 @@ bool ID3v24TagModule::write_tag (VFSFile & f, const Tuple & tuple)
int64_t mp3_offset = offset ? 0 : header_size + data_size + footer_size;
int64_t mp3_size = offset ? offset : -1;
- TempFile temp;
- if (! temp.create ())
+ auto temp = VFSFile::tmpfile ();
+ if (! temp)
return false;
/* write empty header (will be overwritten later) */
- if (! write_header (temp.fd (), version, 0))
+ if (! write_header (temp, version, 0))
return false;
/* write tag data */
- data_size = write_all_frames (temp.fd (), dict, version);
+ data_size = write_all_frames (temp, dict, version);
/* copy non-tag data */
- if (! temp.copy_from (f, mp3_offset, mp3_size))
+ if (f.fseek (mp3_offset, VFS_SEEK_SET) < 0 || ! temp.copy_from (f, mp3_size))
return false;
/* go back to beginning and write real header */
- if (lseek (temp.fd (), 0, SEEK_SET) < 0 || ! write_header (temp.fd (), version, data_size))
+ if (temp.fseek (0, VFS_SEEK_SET) < 0 || ! write_header (temp, version, data_size))
return false;
- if (! temp.replace (f))
+ if (! f.replace_with (temp))
return false;
return true;
diff --git a/src/libaudtag/tag_module.cc b/src/libaudtag/tag_module.cc
index ae24a49..15110d2 100644
--- a/src/libaudtag/tag_module.cc
+++ b/src/libaudtag/tag_module.cc
@@ -77,7 +77,7 @@ bool TagModule::can_handle_file (VFSFile & file)
return false;
}
-bool TagModule::read_tag (VFSFile & file, Tuple * tuple, Index<char> * image)
+bool TagModule::read_tag (VFSFile & file, Tuple & tuple, Index<char> * image)
{
AUDDBG ("%s: read_tag() not implemented.\n", m_name);
return false;
diff --git a/src/libaudtag/tag_module.h b/src/libaudtag/tag_module.h
index b9de501..a08effb 100644
--- a/src/libaudtag/tag_module.h
+++ b/src/libaudtag/tag_module.h
@@ -20,21 +20,23 @@
#ifndef TAG_MODULE_H
#define TAG_MODULE_H
-#include "libaudcore/tuple.h"
-#include "libaudcore/vfs.h"
+#include "audtag.h"
namespace audtag {
-struct TagModule {
- const char *m_name;
+struct TagModule
+{
+ const char * m_name;
TagType m_type; /* set to None if the module cannot create new tags */
virtual bool can_handle_file (VFSFile & file);
- virtual bool read_tag (VFSFile & file, Tuple * tuple, Index<char> * image);
+ virtual bool read_tag (VFSFile & file, Tuple & tuple, Index<char> * image);
virtual bool write_tag (VFSFile & file, const Tuple & tuple);
protected:
- TagModule(const char *name, TagType type) : m_name(name), m_type(type) { };
+ constexpr TagModule (const char * name, TagType type) :
+ m_name (name),
+ m_type (type) {}
};
TagModule * find_tag_module (VFSFile & handle, TagType new_type);
diff --git a/src/libaudtag/util.cc b/src/libaudtag/util.cc
index cc06f74..7b66728 100644
--- a/src/libaudtag/util.cc
+++ b/src/libaudtag/util.cc
@@ -17,13 +17,6 @@
* the use of this software.
*/
-#include <assert.h>
-#include <unistd.h>
-
-#include <glib/gstdio.h>
-
-#include <libaudcore/audstrings.h>
-
#include "util.h"
const char *convert_numericgenre_to_text(int numericgenre)
@@ -173,104 +166,10 @@ const char *convert_numericgenre_to_text(int numericgenre)
uint32_t unsyncsafe32 (uint32_t x)
{
- return (x & 0x7f) | ((x & 0x7f00) >> 1) | ((x & 0x7f0000) >> 2) | ((x &
- 0x7f000000) >> 3);
+ return (x & 0x7f) | ((x & 0x7f00) >> 1) | ((x & 0x7f0000) >> 2) | ((x & 0x7f000000) >> 3);
}
uint32_t syncsafe32 (uint32_t x)
{
- return (x & 0x7f) | ((x & 0x3f80) << 1) | ((x & 0x1fc000) << 2) | ((x &
- 0xfe00000) << 3);
-}
-
-bool TempFile::create ()
-{
- StringBuf tempname = filename_build ({g_get_tmp_dir (), "audacious-temp-XXXXXX"});
-
- assert (m_fd < 0);
- m_fd = g_mkstemp (tempname);
- if (m_fd < 0)
- return false;
-
- m_name = String (tempname);
-
- return true;
-}
-
-bool TempFile::copy_from (VFSFile & file, int64_t offset, int64_t size)
-{
- if (file.fseek (offset, VFS_SEEK_SET) < 0)
- return false;
-
- char buf[16384];
-
- while (size < 0 || size > 0)
- {
- int64_t readsize;
-
- if (size > 0)
- {
- readsize = aud::min (size, (int64_t) sizeof buf);
- if (file.fread (buf, 1, readsize) != readsize)
- return false;
-
- size -= readsize;
- }
- else
- {
- /* negative size means copy to EOF */
- readsize = file.fread (buf, 1, sizeof buf);
- if (! readsize)
- break;
- }
-
- int64_t written = 0;
- while (written < readsize)
- {
- int64_t writesize = write (m_fd, buf + written, readsize - written);
- if (writesize <= 0)
- return false;
-
- written += writesize;
- }
- }
-
- return true;
-}
-
-bool TempFile::replace (VFSFile & file)
-{
- if (lseek (m_fd, 0, SEEK_SET) < 0)
- return false;
-
- if (file.fseek (0, VFS_SEEK_SET) < 0)
- return false;
-
- if (file.ftruncate (0) < 0)
- return false;
-
- char buf[16384];
-
- while (1)
- {
- int64_t readsize = read (m_fd, buf, sizeof buf);
- if (readsize < 0)
- return false;
-
- if (readsize == 0)
- break;
-
- if (file.fwrite (buf, 1, readsize) != readsize)
- return false;
- }
-
- return true;
-}
-
-TempFile::~TempFile ()
-{
- if (m_fd >= 0)
- close (m_fd);
- if (m_name)
- g_unlink (m_name);
+ return (x & 0x7f) | ((x & 0x3f80) << 1) | ((x & 0x1fc000) << 2) | ((x & 0xfe00000) << 3);
}
diff --git a/src/libaudtag/util.h b/src/libaudtag/util.h
index c102c02..9d9f03e 100644
--- a/src/libaudtag/util.h
+++ b/src/libaudtag/util.h
@@ -22,8 +22,6 @@
#include <stdint.h>
-#include <libaudcore/vfs.h>
-
enum {
GENRE_BLUES = 0,
GENRE_CLASSIC_ROCK,
@@ -157,21 +155,4 @@ const char *convert_numericgenre_to_text(int numericgenre);
uint32_t unsyncsafe32 (uint32_t x);
uint32_t syncsafe32 (uint32_t x);
-class TempFile
-{
-public:
- bool create ();
- bool copy_from (VFSFile & file, int64_t offset, int64_t size);
- bool replace (VFSFile & file);
-
- int fd ()
- { return m_fd; }
-
- ~TempFile ();
-
-private:
- String m_name;
- int m_fd = -1;
-};
-
#endif /* TAGUTIL_H */