diff options
Diffstat (limited to '')
160 files changed, 4098 insertions, 2589 deletions
diff --git a/src/Chrono.hxx b/src/Chrono.hxx index cc87c5ba1..960a6364c 100644 --- a/src/Chrono.hxx +++ b/src/Chrono.hxx @@ -26,7 +26,7 @@ #include <utility> #include <cstdint> -#if defined(__GNUC__) && !GCC_CHECK_VERSION(4,7) && !defined(__clang__) +#if GCC_OLDER_THAN(4,7) /* std::chrono::duration operators are "constexpr" since gcc 4.7 */ #define chrono_constexpr gcc_pure #else diff --git a/src/CommandLine.cxx b/src/CommandLine.cxx index c6e9c69c5..385729cf6 100644 --- a/src/CommandLine.cxx +++ b/src/CommandLine.cxx @@ -98,40 +98,44 @@ static constexpr Domain cmdline_domain("cmdline"); gcc_noreturn static void version(void) { - puts("Music Player Daemon " VERSION + printf("Music Player Daemon " VERSION #ifdef GIT_COMMIT - " (" GIT_COMMIT ")" + " (" GIT_COMMIT ")" #endif - "\n" - "\n" - "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n" - "Copyright (C) 2008-2014 Max Kellermann <max@duempel.org>\n" - "This is free software; see the source for copying conditions. There is NO\n" - "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n"); + "\n" + "\n" + "Copyright (C) 2003-2007 Warren Dukes <warren.dukes@gmail.com>\n" + "Copyright (C) 2008-2014 Max Kellermann <max@duempel.org>\n" + "This is free software; see the source for copying conditions. There is NO\n" + "warranty; not even MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n" #ifdef ENABLE_DATABASE - puts("\n" - "Database plugins:"); + "\n" + "Database plugins:\n"); for (auto i = database_plugins; *i != nullptr; ++i) printf(" %s", (*i)->name); - puts("\n\n" - "Storage plugins:"); + printf("\n\n" + "Storage plugins:\n"); for (auto i = storage_plugins; *i != nullptr; ++i) printf(" %s", (*i)->name); + + printf("\n" #endif #ifdef ENABLE_NEIGHBOR_PLUGINS - puts("\n\n" - "Neighbor plugins:"); + "\n" + "Neighbor plugins:\n"); for (auto i = neighbor_plugins; *i != nullptr; ++i) printf(" %s", (*i)->name); + + printf("\n" #endif - puts("\n\n" - "Decoders plugins:"); + "\n" + "Decoders plugins:\n"); decoder_plugins_for_each([](const DecoderPlugin &plugin){ printf(" [%s]", plugin.name); @@ -141,26 +145,31 @@ static void version(void) for (; *suffixes != nullptr; ++suffixes) printf(" %s", *suffixes); - puts(""); + printf("\n"); }); - puts("\n" - "Output plugins:"); + printf("\n" + "Tag plugins:\n" +#ifdef ENABLE_ID3TAG + " id3tag" +#endif + "\n\n" + "Output plugins:\n"); audio_output_plugins_for_each(plugin) printf(" %s", plugin->name); - puts(""); + printf("\n" #ifdef ENABLE_ENCODER - puts("\n" - "Encoder plugins:"); + "\n" + "Encoder plugins:\n"); encoder_plugins_for_each(plugin) printf(" %s", plugin->name); - puts(""); + printf("\n" #endif #ifdef ENABLE_ARCHIVE - puts("\n" - "Archive plugins:"); + "\n" + "Archive plugins:\n"); archive_plugins_for_each(plugin) { printf(" [%s]", plugin->name); @@ -169,22 +178,24 @@ static void version(void) for (; *suffixes != nullptr; ++suffixes) printf(" %s", *suffixes); - puts(""); + printf("\n"); } + + printf("" #endif - puts("\n" - "Input plugins:"); + "\n" + "Input plugins:\n"); input_plugins_for_each(plugin) printf(" %s", plugin->name); - puts("\n\n" - "Playlist plugins:"); + printf("\n\n" + "Playlist plugins:\n"); playlist_plugins_for_each(plugin) printf(" %s", plugin->name); - puts("\n\n" - "Protocols:"); + printf("\n\n" + "Protocols:\n"); print_supported_uri_schemes_to_fp(stdout); exit(EXIT_SUCCESS); @@ -206,12 +217,12 @@ static void PrintOption(const OptionDef &opt) gcc_noreturn static void help(void) { - puts("Usage:\n" - " mpd [OPTION...] [path/to/mpd.conf]\n" - "\n" - "Music Player Daemon - a daemon for playing music.\n" - "\n" - "Options:"); + printf("Usage:\n" + " mpd [OPTION...] [path/to/mpd.conf]\n" + "\n" + "Music Player Daemon - a daemon for playing music.\n" + "\n" + "Options:\n"); PrintOption(opt_help); PrintOption(opt_kill); diff --git a/src/Compiler.h b/src/Compiler.h index fea971526..dc3de5af9 100644 --- a/src/Compiler.h +++ b/src/Compiler.h @@ -28,8 +28,20 @@ #define GCC_VERSION 0 #endif +/** + * Are we building with the specified version of gcc (not clang or any + * other compiler) or newer? + */ #define GCC_CHECK_VERSION(major, minor) \ - (defined(__GNUC__) && GCC_VERSION >= GCC_MAKE_VERSION(major, minor, 0)) + (defined(__GNUC__) && !defined(__clang__) && \ + GCC_VERSION >= GCC_MAKE_VERSION(major, minor, 0)) + +/** + * Are we building with clang (any version) or at least the specified + * gcc version? + */ +#define CLANG_OR_GCC_VERSION(major, minor) \ + (defined(__clang__) || GCC_CHECK_VERSION(major, minor)) /** * Are we building with gcc (not clang or any other compiler) and a @@ -59,7 +71,7 @@ (defined(__clang__) && \ CLANG_VERSION >= GCC_MAKE_VERSION(major, minor, 0)) -#if GCC_CHECK_VERSION(4,0) +#if CLANG_OR_GCC_VERSION(4,0) /* GCC 4.x */ @@ -119,7 +131,7 @@ #endif -#if GCC_CHECK_VERSION(4,3) +#if CLANG_OR_GCC_VERSION(4,3) #define gcc_hot __attribute__((hot)) #define gcc_cold __attribute__((cold)) @@ -131,7 +143,7 @@ #endif /* ! GCC_UNUSED >= 40300 */ -#if GCC_CHECK_VERSION(4,6) && !defined(__clang__) +#if GCC_CHECK_VERSION(4,6) #define gcc_flatten __attribute__((flatten)) #else #define gcc_flatten @@ -140,7 +152,7 @@ #ifndef __cplusplus /* plain C99 has "restrict" */ #define gcc_restrict restrict -#elif GCC_CHECK_VERSION(4,0) +#elif CLANG_OR_GCC_VERSION(4,0) /* "__restrict__" is a GCC extension for C++ */ #define gcc_restrict __restrict__ #else @@ -158,7 +170,7 @@ #define final #endif -#if defined(__clang__) || GCC_CHECK_VERSION(4,8) +#if CLANG_OR_GCC_VERSION(4,8) #define gcc_alignas(T, fallback) alignas(T) #else #define gcc_alignas(T, fallback) gcc_aligned(fallback) diff --git a/src/Instance.cxx b/src/Instance.cxx index 232cd21df..0f7f52e6e 100644 --- a/src/Instance.cxx +++ b/src/Instance.cxx @@ -22,6 +22,7 @@ #include "Partition.hxx" #include "Idle.hxx" #include "Stats.hxx" +#include "util/Error.hxx" #ifdef ENABLE_DATABASE #include "db/DatabaseError.hxx" @@ -76,7 +77,7 @@ Instance::OnDatabaseSongRemoved(const LightSong &song) #ifdef ENABLE_SQLITE /* if the song has a sticker, remove it */ if (sticker_enabled()) - sticker_song_delete(song); + sticker_song_delete(song, IgnoreError()); #endif const auto uri = song.GetURI(); diff --git a/src/Main.cxx b/src/Main.cxx index 26d4e7ae4..9972b5cdd 100644 --- a/src/Main.cxx +++ b/src/Main.cxx @@ -50,7 +50,6 @@ #include "AudioConfig.hxx" #include "pcm/PcmConvert.hxx" #include "unix/SignalHandlers.hxx" -#include "unix/Daemon.hxx" #include "system/FatalError.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" @@ -65,6 +64,10 @@ #include "config/ConfigError.hxx" #include "Stats.hxx" +#ifdef ENABLE_DAEMON +#include "unix/Daemon.hxx" +#endif + #ifdef ENABLE_DATABASE #include "db/update/Service.hxx" #include "db/Configured.hxx" @@ -133,7 +136,7 @@ Instance *instance; static StateFile *state_file; -#ifndef ANDROID +#ifdef ENABLE_DAEMON static bool glue_daemonize_init(const struct options *options, Error &error) @@ -422,9 +425,11 @@ int mpd_main(int argc, char *argv[]) struct options options; Error error; -#ifndef ANDROID +#ifdef ENABLE_DAEMON daemonize_close_stdin(); +#endif +#ifndef ANDROID #ifdef HAVE_LOCALE_H /* initialize locale */ setlocale(LC_CTYPE,""); @@ -432,11 +437,6 @@ int mpd_main(int argc, char *argv[]) #ifdef HAVE_GLIB g_set_application_name("Music Player Daemon"); - -#if !GLIB_CHECK_VERSION(2,32,0) - /* enable GLib's thread safety code */ - g_thread_init(nullptr); -#endif #endif #endif @@ -470,7 +470,9 @@ int mpd_main(int argc, char *argv[]) LogError(error); return EXIT_FAILURE; } +#endif +#ifdef ENABLE_DAEMON if (!glue_daemonize_init(&options, error)) { LogError(error); return EXIT_FAILURE; @@ -512,7 +514,7 @@ int mpd_main(int argc, char *argv[]) return EXIT_FAILURE; } -#ifndef ANDROID +#ifdef ENABLE_DAEMON daemonize_set_user(); daemonize_begin(options.daemon); #endif @@ -544,7 +546,10 @@ static int mpd_main_after_fork(struct options options) GlobalEvents::Register(GlobalEvents::SHUTDOWN, shutdown_event_emitted); #endif - ConfigureFS(); + if (!ConfigureFS(error)) { + LogError(error); + return EXIT_FAILURE; + } if (!glue_mapper_init(error)) { LogError(error); @@ -585,9 +590,11 @@ static int mpd_main_after_fork(struct options options) playlist_list_global_init(); -#ifndef ANDROID +#ifdef ENABLE_DAEMON daemonize_commit(); +#endif +#ifndef ANDROID setup_log_output(options.log_stderr); SignalHandlersInit(*instance->event_loop); @@ -710,6 +717,8 @@ static int mpd_main_after_fork(struct options options) mapper_finish(); #endif + DeinitFS(); + delete instance->partition; command_finish(); decoder_plugin_deinit_all(); @@ -724,9 +733,11 @@ static int mpd_main_after_fork(struct options options) delete instance->event_loop; delete instance; instance = nullptr; -#ifndef ANDROID + +#ifdef ENABLE_DAEMON daemonize_finish(); #endif + #ifdef WIN32 WSACleanup(); #endif diff --git a/src/ReplayGainInfo.hxx b/src/ReplayGainInfo.hxx index 37815c933..9b702c701 100644 --- a/src/ReplayGainInfo.hxx +++ b/src/ReplayGainInfo.hxx @@ -39,8 +39,7 @@ struct ReplayGainTuple { peak = 0.0; } - gcc_pure - bool IsDefined() const { + constexpr bool IsDefined() const { return gain > -100; } @@ -52,6 +51,11 @@ struct ReplayGainTuple { struct ReplayGainInfo { ReplayGainTuple tuples[2]; + constexpr bool IsDefined() const { + return tuples[REPLAY_GAIN_ALBUM].IsDefined() || + tuples[REPLAY_GAIN_TRACK].IsDefined(); + } + void Clear() { tuples[REPLAY_GAIN_ALBUM].Clear(); tuples[REPLAY_GAIN_TRACK].Clear(); diff --git a/src/SongPrint.cxx b/src/SongPrint.cxx index 05d462b6d..07b9458ca 100644 --- a/src/SongPrint.cxx +++ b/src/SongPrint.cxx @@ -122,5 +122,8 @@ song_print_info(Client &client, const DetachedSong &song, bool base) const auto duration = song.GetDuration(); if (!duration.IsNegative()) - client_printf(client, "Time: %u\n", duration.RoundS()); + client_printf(client, "Time: %i\n" + "duration: %1.3f\n", + duration.RoundS(), + duration.ToDoubleS()); } diff --git a/src/TagPrint.cxx b/src/TagPrint.cxx index 4937fa622..51ee80d83 100644 --- a/src/TagPrint.cxx +++ b/src/TagPrint.cxx @@ -23,8 +23,6 @@ #include "tag/TagSettings.h" #include "client/Client.hxx" -#define SONG_TIME "Time: " - void tag_print_types(Client &client) { int i; @@ -53,7 +51,10 @@ tag_print_values(Client &client, const Tag &tag) void tag_print(Client &client, const Tag &tag) { if (!tag.duration.IsNegative()) - client_printf(client, SONG_TIME "%i\n", tag.duration.RoundS()); + client_printf(client, "Time: %i\n" + "duration: %1.3f\n", + tag.duration.RoundS(), + tag.duration.ToDoubleS()); tag_print_values(client, tag); } diff --git a/src/archive/ArchiveList.cxx b/src/archive/ArchiveList.cxx index 79c3a16fe..904f640c3 100644 --- a/src/archive/ArchiveList.cxx +++ b/src/archive/ArchiveList.cxx @@ -29,13 +29,13 @@ #include <string.h> const ArchivePlugin *const archive_plugins[] = { -#ifdef HAVE_BZ2 +#ifdef ENABLE_BZ2 &bz2_archive_plugin, #endif -#ifdef HAVE_ZZIP +#ifdef ENABLE_ZZIP &zzip_archive_plugin, #endif -#ifdef HAVE_ISO9660 +#ifdef ENABLE_ISO9660 &iso9660_archive_plugin, #endif nullptr diff --git a/src/archive/plugins/Bzip2ArchivePlugin.cxx b/src/archive/plugins/Bzip2ArchivePlugin.cxx index 2b92049dd..8548cb1e8 100644 --- a/src/archive/plugins/Bzip2ArchivePlugin.cxx +++ b/src/archive/plugins/Bzip2ArchivePlugin.cxx @@ -53,7 +53,7 @@ public: Bzip2ArchiveFile(Path path, InputStream *_is) :ArchiveFile(bz2_archive_plugin), - name(PathTraitsFS::GetBase(path.c_str())), + name(path.GetBase().c_str()), istream(_is) { // remove .bz2 suffix const size_t len = name.length(); diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx index 6a4b18198..e0b16d77d 100644 --- a/src/command/AllCommands.cxx +++ b/src/command/AllCommands.cxx @@ -35,8 +35,10 @@ #include "protocol/Result.hxx" #include "Partition.hxx" #include "client/Client.hxx" +#include "util/Macros.hxx" #include "util/Tokenizer.hxx" #include "util/Error.hxx" +#include "util/ConstBuffer.hxx" #ifdef ENABLE_SQLITE #include "StickerCommands.hxx" @@ -60,22 +62,22 @@ struct command { unsigned permission; int min; int max; - CommandResult (*handler)(Client &client, unsigned argc, char **argv); + CommandResult (*handler)(Client &client, ConstBuffer<const char *> args); }; /* don't be fooled, this is the command handler for "commands" command */ static CommandResult -handle_commands(Client &client, unsigned argc, char *argv[]); +handle_commands(Client &client, ConstBuffer<const char *> args); static CommandResult -handle_not_commands(Client &client, unsigned argc, char *argv[]); +handle_not_commands(Client &client, ConstBuffer<const char *> args); /** * The command registry. * * This array must be sorted! */ -static const struct command commands[] = { +static constexpr struct command commands[] = { { "add", PERMISSION_ADD, 1, 1, handle_add }, { "addid", PERMISSION_ADD, 1, 2, handle_addid }, { "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid }, @@ -194,7 +196,7 @@ static const struct command commands[] = { { "volume", PERMISSION_CONTROL, 1, 1, handle_volume }, }; -static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]); +static constexpr unsigned num_commands = ARRAY_SIZE(commands); static bool command_available(gcc_unused const Partition &partition, @@ -210,19 +212,27 @@ command_available(gcc_unused const Partition &partition, return neighbor_commands_available(partition.instance); #endif + if (strcmp(cmd->cmd, "save") == 0 || + strcmp(cmd->cmd, "rm") == 0 || + strcmp(cmd->cmd, "rename") == 0 || + strcmp(cmd->cmd, "playlistdelete") == 0 || + strcmp(cmd->cmd, "playlistmove") == 0 || + strcmp(cmd->cmd, "playlistclear") == 0 || + strcmp(cmd->cmd, "playlistadd") == 0 || + strcmp(cmd->cmd, "listplaylists") == 0) + return playlist_commands_available(); + return true; } /* don't be fooled, this is the command handler for "commands" command */ static CommandResult -handle_commands(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_commands(Client &client, gcc_unused ConstBuffer<const char *> args) { const unsigned permission = client.GetPermission(); - const struct command *cmd; for (unsigned i = 0; i < num_commands; ++i) { - cmd = &commands[i]; + const struct command *cmd = &commands[i]; if (cmd->permission == (permission & cmd->permission) && command_available(client.partition, cmd)) @@ -233,14 +243,12 @@ handle_commands(Client &client, } static CommandResult -handle_not_commands(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_not_commands(Client &client, gcc_unused ConstBuffer<const char *> args) { const unsigned permission = client.GetPermission(); - const struct command *cmd; for (unsigned i = 0; i < num_commands; ++i) { - cmd = &commands[i]; + const struct command *cmd = &commands[i]; if (cmd->permission != (permission & cmd->permission)) client_printf(client, "command: %s\n", cmd->cmd); @@ -266,13 +274,12 @@ static const struct command * command_lookup(const char *name) { unsigned a = 0, b = num_commands, i; - int cmp; /* binary search */ do { i = (a + b) / 2; - cmp = strcmp(name, commands[i].cmd); + const auto cmp = strcmp(name, commands[i].cmd); if (cmp == 0) return &commands[i]; else if (cmp < 0) @@ -286,11 +293,8 @@ command_lookup(const char *name) static bool command_check_request(const struct command *cmd, Client &client, - unsigned permission, unsigned argc, char *argv[]) + unsigned permission, ConstBuffer<const char *> args) { - const unsigned min = cmd->min + 1; - const unsigned max = cmd->max + 1; - if (cmd->permission != (permission & cmd->permission)) { command_error(client, ACK_ERROR_PERMISSION, "you don't have permission for \"%s\"", @@ -298,21 +302,24 @@ command_check_request(const struct command *cmd, Client &client, return false; } - if (min == 0) + const int min = cmd->min; + const int max = cmd->max; + + if (min < 0) return true; - if (min == max && max != argc) { + if (min == max && unsigned(max) != args.size) { command_error(client, ACK_ERROR_ARG, "wrong number of arguments for \"%s\"", - argv[0]); + cmd->cmd); return false; - } else if (argc < min) { + } else if (args.size < unsigned(min)) { command_error(client, ACK_ERROR_ARG, - "too few arguments for \"%s\"", argv[0]); + "too few arguments for \"%s\"", cmd->cmd); return false; - } else if (argc > max && max /* != 0 */ ) { + } else if (max >= 0 && args.size > unsigned(max)) { command_error(client, ACK_ERROR_ARG, - "too many arguments for \"%s\"", argv[0]); + "too many arguments for \"%s\"", cmd->cmd); return false; } else return true; @@ -320,25 +327,20 @@ command_check_request(const struct command *cmd, Client &client, static const struct command * command_checked_lookup(Client &client, unsigned permission, - unsigned argc, char *argv[]) + const char *cmd_name, ConstBuffer<const char *> args) { - const struct command *cmd; - current_command = ""; - if (argc == 0) - return nullptr; - - cmd = command_lookup(argv[0]); + const struct command *cmd = command_lookup(cmd_name); if (cmd == nullptr) { command_error(client, ACK_ERROR_UNKNOWN, - "unknown command \"%s\"", argv[0]); + "unknown command \"%s\"", cmd_name); return nullptr; } current_command = cmd->cmd; - if (!command_check_request(cmd, client, permission, argc, argv)) + if (!command_check_request(cmd, client, permission, args)) return nullptr; return cmd; @@ -348,17 +350,18 @@ CommandResult command_process(Client &client, unsigned num, char *line) { Error error; - char *argv[COMMAND_ARGV_MAX] = { nullptr }; - const struct command *cmd; - CommandResult ret = CommandResult::ERROR; command_list_num = num; /* get the command name (first word on the line) */ + /* we have to set current_command because command_error() + expects it to be set */ Tokenizer tokenizer(line); - argv[0] = tokenizer.NextWord(error); - if (argv[0] == nullptr) { + + const char *const cmd_name = current_command = + tokenizer.NextWord(error); + if (cmd_name == nullptr) { current_command = ""; if (tokenizer.IsEnd()) command_error(client, ACK_ERROR_UNKNOWN, @@ -374,38 +377,41 @@ command_process(Client &client, unsigned num, char *line) return CommandResult::FINISH; } - unsigned argc = 1; + char *argv[COMMAND_ARGV_MAX]; + ConstBuffer<const char *> args(argv, 0); /* now parse the arguments (quoted or unquoted) */ - while (argc < COMMAND_ARGV_MAX && - (argv[argc] = - tokenizer.NextParam(error)) != nullptr) - ++argc; - - /* some error checks; we have to set current_command because - command_error() expects it to be set */ - - current_command = argv[0]; - - if (argc >= COMMAND_ARGV_MAX) { - command_error(client, ACK_ERROR_ARG, "Too many arguments"); - current_command = nullptr; - return CommandResult::ERROR; - } - - if (!tokenizer.IsEnd()) { - command_error(client, ACK_ERROR_ARG, "%s", error.GetMessage()); - current_command = nullptr; - return CommandResult::ERROR; + while (true) { + if (args.size == COMMAND_ARGV_MAX) { + command_error(client, ACK_ERROR_ARG, + "Too many arguments"); + current_command = nullptr; + return CommandResult::ERROR; + } + + char *a = tokenizer.NextParam(error); + if (a == nullptr) { + if (tokenizer.IsEnd()) + break; + + command_error(client, ACK_ERROR_ARG, "%s", error.GetMessage()); + current_command = nullptr; + return CommandResult::ERROR; + } + + argv[args.size++] = a; } /* look up and invoke the command handler */ - cmd = command_checked_lookup(client, client.GetPermission(), - argc, argv); - if (cmd) - ret = cmd->handler(client, argc, argv); + const struct command *cmd = + command_checked_lookup(client, client.GetPermission(), + cmd_name, args); + + CommandResult ret = cmd + ? cmd->handler(client, args) + : CommandResult::ERROR; current_command = nullptr; command_list_num = 0; diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx index a3ea8d0ae..2d1e1f69b 100644 --- a/src/command/DatabaseCommands.cxx +++ b/src/command/DatabaseCommands.cxx @@ -32,6 +32,7 @@ #include "util/Error.hxx" #include "SongFilter.hxx" #include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" #include "BulkEdit.hxx" #include <string.h> @@ -49,12 +50,10 @@ handle_listfiles_db(Client &client, const char *uri) } CommandResult -handle_lsinfo2(Client &client, unsigned argc, char *argv[]) +handle_lsinfo2(Client &client, ConstBuffer<const char *> args) { - const char *const uri = argc == 2 - ? argv[1] - /* default is root directory */ - : ""; + /* default is root directory */ + const char *const uri = args.IsEmpty() ? "" : args.front(); const DatabaseSelection selection(uri, false); @@ -66,9 +65,17 @@ handle_lsinfo2(Client &client, unsigned argc, char *argv[]) } static CommandResult -handle_match(Client &client, unsigned argc, char *argv[], bool fold_case) +handle_match(Client &client, ConstBuffer<const char *> args, bool fold_case) { - ConstBuffer<const char *> args(argv + 1, argc - 1); + unsigned window_start = 0, window_end = std::numeric_limits<int>::max(); + if (args.size >= 2 && strcmp(args[args.size - 2], "window") == 0) { + if (!check_range(client, &window_start, &window_end, + args.back())) + return CommandResult::ERROR; + + args.pop_back(); + args.pop_back(); + } SongFilter filter; if (!filter.Parse(args, fold_case)) { @@ -79,28 +86,27 @@ handle_match(Client &client, unsigned argc, char *argv[], bool fold_case) const DatabaseSelection selection("", true, &filter); Error error; - return db_selection_print(client, selection, true, false, error) + return db_selection_print(client, selection, true, false, + window_start, window_end, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_find(Client &client, unsigned argc, char *argv[]) +handle_find(Client &client, ConstBuffer<const char *> args) { - return handle_match(client, argc, argv, false); + return handle_match(client, args, false); } CommandResult -handle_search(Client &client, unsigned argc, char *argv[]) +handle_search(Client &client, ConstBuffer<const char *> args) { - return handle_match(client, argc, argv, true); + return handle_match(client, args, true); } static CommandResult -handle_match_add(Client &client, unsigned argc, char *argv[], bool fold_case) +handle_match_add(Client &client, ConstBuffer<const char *> args, bool fold_case) { - ConstBuffer<const char *> args(argv + 1, argc - 1); - SongFilter filter; if (!filter.Parse(args, fold_case)) { command_error(client, ACK_ERROR_ARG, "incorrect arguments"); @@ -117,21 +123,20 @@ handle_match_add(Client &client, unsigned argc, char *argv[], bool fold_case) } CommandResult -handle_findadd(Client &client, unsigned argc, char *argv[]) +handle_findadd(Client &client, ConstBuffer<const char *> args) { - return handle_match_add(client, argc, argv, false); + return handle_match_add(client, args, false); } CommandResult -handle_searchadd(Client &client, unsigned argc, char *argv[]) +handle_searchadd(Client &client, ConstBuffer<const char *> args) { - return handle_match_add(client, argc, argv, true); + return handle_match_add(client, args, true); } CommandResult -handle_searchaddpl(Client &client, unsigned argc, char *argv[]) +handle_searchaddpl(Client &client, ConstBuffer<const char *> args) { - ConstBuffer<const char *> args(argv + 1, argc - 1); const char *playlist = args.shift(); SongFilter filter; @@ -152,10 +157,8 @@ handle_searchaddpl(Client &client, unsigned argc, char *argv[]) } CommandResult -handle_count(Client &client, unsigned argc, char *argv[]) +handle_count(Client &client, ConstBuffer<const char *> args) { - ConstBuffer<const char *> args(argv + 1, argc - 1); - TagType group = TAG_NUM_OF_ITEM_TYPES; if (args.size >= 2 && strcmp(args[args.size - 2], "group") == 0) { const char *s = args[args.size - 1]; @@ -183,24 +186,21 @@ handle_count(Client &client, unsigned argc, char *argv[]) } CommandResult -handle_listall(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_listall(Client &client, ConstBuffer<const char *> args) { - const char *directory = ""; - - if (argc == 2) - directory = argv[1]; + /* default is root directory */ + const char *const uri = args.IsEmpty() ? "" : args.front(); Error error; - return db_selection_print(client, DatabaseSelection(directory, true), + return db_selection_print(client, DatabaseSelection(uri, true), false, false, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_list(Client &client, unsigned argc, char *argv[]) +handle_list(Client &client, ConstBuffer<const char *> args) { - ConstBuffer<const char *> args(argv + 1, argc - 1); const char *tag_name = args.shift(); unsigned tagType = locate_parse_type(tag_name); @@ -271,15 +271,13 @@ handle_list(Client &client, unsigned argc, char *argv[]) } CommandResult -handle_listallinfo(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_listallinfo(Client &client, ConstBuffer<const char *> args) { - const char *directory = ""; - - if (argc == 2) - directory = argv[1]; + /* default is root directory */ + const char *const uri = args.IsEmpty() ? "" : args.front(); Error error; - return db_selection_print(client, DatabaseSelection(directory, true), + return db_selection_print(client, DatabaseSelection(uri, true), true, false, error) ? CommandResult::OK : print_error(client, error); diff --git a/src/command/DatabaseCommands.hxx b/src/command/DatabaseCommands.hxx index 7abf89e0c..0f6e2700a 100644 --- a/src/command/DatabaseCommands.hxx +++ b/src/command/DatabaseCommands.hxx @@ -23,38 +23,39 @@ #include "CommandResult.hxx" class Client; +template<typename T> struct ConstBuffer; CommandResult handle_listfiles_db(Client &client, const char *uri); CommandResult -handle_lsinfo2(Client &client, unsigned argc, char *argv[]); +handle_lsinfo2(Client &client, ConstBuffer<const char *> args); CommandResult -handle_find(Client &client, unsigned argc, char *argv[]); +handle_find(Client &client, ConstBuffer<const char *> args); CommandResult -handle_findadd(Client &client, unsigned argc, char *argv[]); +handle_findadd(Client &client, ConstBuffer<const char *> args); CommandResult -handle_search(Client &client, unsigned argc, char *argv[]); +handle_search(Client &client, ConstBuffer<const char *> args); CommandResult -handle_searchadd(Client &client, unsigned argc, char *argv[]); +handle_searchadd(Client &client, ConstBuffer<const char *> args); CommandResult -handle_searchaddpl(Client &client, unsigned argc, char *argv[]); +handle_searchaddpl(Client &client, ConstBuffer<const char *> args); CommandResult -handle_count(Client &client, unsigned argc, char *argv[]); +handle_count(Client &client, ConstBuffer<const char *> args); CommandResult -handle_listall(Client &client, unsigned argc, char *argv[]); +handle_listall(Client &client, ConstBuffer<const char *> args); CommandResult -handle_list(Client &client, unsigned argc, char *argv[]); +handle_list(Client &client, ConstBuffer<const char *> args); CommandResult -handle_listallinfo(Client &client, unsigned argc, char *argv[]); +handle_listallinfo(Client &client, ConstBuffer<const char *> args); #endif diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx index 1b6a11cf5..acf71eca4 100644 --- a/src/command/FileCommands.cxx +++ b/src/command/FileCommands.cxx @@ -25,6 +25,7 @@ #include "protocol/Ack.hxx" #include "protocol/Result.hxx" #include "client/Client.hxx" +#include "util/ConstBuffer.hxx" #include "util/CharUtil.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" @@ -202,11 +203,10 @@ read_file_comments(Client &client, const Path path_fs) } CommandResult -handle_read_comments(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_read_comments(Client &client, ConstBuffer<const char *> args) { - assert(argc == 2); - - const char *const uri = argv[1]; + assert(args.size == 1); + const char *const uri = args.front(); if (memcmp(uri, "file:///", 8) == 0) { /* read comments from arbitrary local file */ diff --git a/src/command/FileCommands.hxx b/src/command/FileCommands.hxx index 62835a82c..b77157e3f 100644 --- a/src/command/FileCommands.hxx +++ b/src/command/FileCommands.hxx @@ -23,11 +23,12 @@ #include "CommandResult.hxx" class Client; +template<typename T> struct ConstBuffer; CommandResult handle_listfiles_local(Client &client, const char *path_utf8); CommandResult -handle_read_comments(Client &client, unsigned argc, char *argv[]); +handle_read_comments(Client &client, ConstBuffer<const char *> args); #endif diff --git a/src/command/MessageCommands.cxx b/src/command/MessageCommands.cxx index a86bdf30c..4bf22abcc 100644 --- a/src/command/MessageCommands.cxx +++ b/src/command/MessageCommands.cxx @@ -24,6 +24,7 @@ #include "Instance.hxx" #include "Partition.hxx" #include "protocol/Result.hxx" +#include "util/ConstBuffer.hxx" #include <set> #include <string> @@ -31,11 +32,12 @@ #include <assert.h> CommandResult -handle_subscribe(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_subscribe(Client &client, ConstBuffer<const char *> args) { - assert(argc == 2); + assert(args.size == 1); + const char *const channel_name = args[0]; - switch (client.Subscribe(argv[1])) { + switch (client.Subscribe(channel_name)) { case Client::SubscribeResult::OK: return CommandResult::OK; @@ -61,11 +63,12 @@ handle_subscribe(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_unsubscribe(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_unsubscribe(Client &client, ConstBuffer<const char *> args) { - assert(argc == 2); + assert(args.size == 1); + const char *const channel_name = args[0]; - if (client.Unsubscribe(argv[1])) + if (client.Unsubscribe(channel_name)) return CommandResult::OK; else { command_error(client, ACK_ERROR_NO_EXIST, @@ -75,10 +78,9 @@ handle_unsubscribe(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_channels(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_channels(Client &client, gcc_unused ConstBuffer<const char *> args) { - assert(argc == 1); + assert(args.IsEmpty()); std::set<std::string> channels; for (const auto &c : *client.partition.instance.client_list) @@ -93,9 +95,9 @@ handle_channels(Client &client, CommandResult handle_read_messages(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) + gcc_unused ConstBuffer<const char *> args) { - assert(argc == 1); + assert(args.IsEmpty()); while (!client.messages.empty()) { const ClientMessage &msg = client.messages.front(); @@ -109,19 +111,21 @@ handle_read_messages(Client &client, } CommandResult -handle_send_message(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_send_message(Client &client, ConstBuffer<const char *> args) { - assert(argc == 3); + assert(args.size == 2); - if (!client_message_valid_channel_name(argv[1])) { + const char *const channel_name = args[0]; + const char *const message_text = args[1]; + + if (!client_message_valid_channel_name(channel_name)) { command_error(client, ACK_ERROR_ARG, "invalid channel name"); return CommandResult::ERROR; } bool sent = false; - const ClientMessage msg(argv[1], argv[2]); + const ClientMessage msg(channel_name, message_text); for (auto &c : *client.partition.instance.client_list) if (c.PushMessage(msg)) sent = true; diff --git a/src/command/MessageCommands.hxx b/src/command/MessageCommands.hxx index ac8afe2fb..b10863277 100644 --- a/src/command/MessageCommands.hxx +++ b/src/command/MessageCommands.hxx @@ -23,20 +23,21 @@ #include "CommandResult.hxx" class Client; +template<typename T> struct ConstBuffer; CommandResult -handle_subscribe(Client &client, unsigned argc, char *argv[]); +handle_subscribe(Client &client, ConstBuffer<const char *> args); CommandResult -handle_unsubscribe(Client &client, unsigned argc, char *argv[]); +handle_unsubscribe(Client &client, ConstBuffer<const char *> args); CommandResult -handle_channels(Client &client, unsigned argc, char *argv[]); +handle_channels(Client &client, ConstBuffer<const char *> args); CommandResult -handle_read_messages(Client &client, unsigned argc, char *argv[]); +handle_read_messages(Client &client, ConstBuffer<const char *> args); CommandResult -handle_send_message(Client &client, unsigned argc, char *argv[]); +handle_send_message(Client &client, ConstBuffer<const char *> args); #endif diff --git a/src/command/NeighborCommands.cxx b/src/command/NeighborCommands.cxx index 22e8adf9e..3efae7883 100644 --- a/src/command/NeighborCommands.cxx +++ b/src/command/NeighborCommands.cxx @@ -25,6 +25,7 @@ #include "protocol/Result.hxx" #include "neighbor/Glue.hxx" #include "neighbor/Info.hxx" +#include "util/ConstBuffer.hxx" #include <set> #include <string> @@ -38,8 +39,7 @@ neighbor_commands_available(const Instance &instance) } CommandResult -handle_listneighbors(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_listneighbors(Client &client, gcc_unused ConstBuffer<const char *> args) { const NeighborGlue *const neighbors = client.partition.instance.neighbors; diff --git a/src/command/NeighborCommands.hxx b/src/command/NeighborCommands.hxx index 7fb309aeb..e07f26925 100644 --- a/src/command/NeighborCommands.hxx +++ b/src/command/NeighborCommands.hxx @@ -25,12 +25,13 @@ struct Instance; class Client; +template<typename T> struct ConstBuffer; gcc_pure bool neighbor_commands_available(const Instance &instance); CommandResult -handle_listneighbors(Client &client, unsigned argc, char *argv[]); +handle_listneighbors(Client &client, ConstBuffer<const char *> args); #endif diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx index a924f77b5..6328acc4c 100644 --- a/src/command/OtherCommands.cxx +++ b/src/command/OtherCommands.cxx @@ -37,6 +37,7 @@ #include "mixer/Volume.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" +#include "util/ConstBuffer.hxx" #include "fs/AllocatedPath.hxx" #include "Stats.hxx" #include "Permission.hxx" @@ -68,8 +69,7 @@ print_spl_list(Client &client, const PlaylistVector &list) } CommandResult -handle_urlhandlers(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_urlhandlers(Client &client, gcc_unused ConstBuffer<const char *> args) { if (client.IsLocal()) client_puts(client, "handler: file://\n"); @@ -78,31 +78,27 @@ handle_urlhandlers(Client &client, } CommandResult -handle_decoders(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_decoders(Client &client, gcc_unused ConstBuffer<const char *> args) { decoder_list_print(client); return CommandResult::OK; } CommandResult -handle_tagtypes(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_tagtypes(Client &client, gcc_unused ConstBuffer<const char *> args) { tag_print_types(client); return CommandResult::OK; } CommandResult -handle_kill(gcc_unused Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_kill(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args) { return CommandResult::KILL; } CommandResult -handle_close(gcc_unused Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_close(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args) { return CommandResult::FINISH; } @@ -116,12 +112,10 @@ print_tag(TagType type, const char *value, void *ctx) } CommandResult -handle_listfiles(Client &client, unsigned argc, char *argv[]) +handle_listfiles(Client &client, ConstBuffer<const char *> args) { - const char *const uri = argc == 2 - ? argv[1] - /* default is root directory */ - : ""; + /* default is root directory */ + const char *const uri = args.IsEmpty() ? "" : args.front(); if (memcmp(uri, "file:///", 8) == 0) /* list local directory */ @@ -157,12 +151,10 @@ static constexpr tag_handler print_tag_handler = { }; CommandResult -handle_lsinfo(Client &client, unsigned argc, char *argv[]) +handle_lsinfo(Client &client, ConstBuffer<const char *> args) { - const char *const uri = argc == 2 - ? argv[1] - /* default is root directory */ - : ""; + /* default is root directory */ + const char *const uri = args.IsEmpty() ? "" : args.front(); if (memcmp(uri, "file:///", 8) == 0) { /* print information about an arbitrary local file */ @@ -207,7 +199,7 @@ handle_lsinfo(Client &client, unsigned argc, char *argv[]) } #ifdef ENABLE_DATABASE - CommandResult result = handle_lsinfo2(client, argc, argv); + CommandResult result = handle_lsinfo2(client, args); if (result != CommandResult::OK) return result; #endif @@ -265,14 +257,14 @@ handle_update(Client &client, Database &db, #endif static CommandResult -handle_update(Client &client, unsigned argc, char *argv[], bool discard) +handle_update(Client &client, ConstBuffer<const char *> args, bool discard) { #ifdef ENABLE_DATABASE const char *path = ""; - assert(argc <= 2); - if (argc == 2) { - path = argv[1]; + assert(args.size <= 1); + if (!args.IsEmpty()) { + path = args.front(); if (*path == 0 || strcmp(path, "/") == 0) /* backwards compatibility with MPD 0.15 */ @@ -292,8 +284,7 @@ handle_update(Client &client, unsigned argc, char *argv[], bool discard) if (db != nullptr) return handle_update(client, *db, path, discard); #else - (void)argc; - (void)argv; + (void)args; (void)discard; #endif @@ -302,24 +293,24 @@ handle_update(Client &client, unsigned argc, char *argv[], bool discard) } CommandResult -handle_update(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_update(Client &client, gcc_unused ConstBuffer<const char *> args) { - return handle_update(client, argc, argv, false); + return handle_update(client, args, false); } CommandResult -handle_rescan(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_rescan(Client &client, gcc_unused ConstBuffer<const char *> args) { - return handle_update(client, argc, argv, true); + return handle_update(client, args, true); } CommandResult -handle_setvol(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_setvol(Client &client, ConstBuffer<const char *> args) { unsigned level; bool success; - if (!check_unsigned(client, &level, argv[1])) + if (!check_unsigned(client, &level, args.front())) return CommandResult::ERROR; if (level > 100) { @@ -338,10 +329,10 @@ handle_setvol(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_volume(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_volume(Client &client, ConstBuffer<const char *> args) { int relative; - if (!check_int(client, &relative, argv[1])) + if (!check_int(client, &relative, args.front())) return CommandResult::ERROR; if (relative < -100 || relative > 100) { @@ -372,26 +363,24 @@ handle_volume(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_stats(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_stats(Client &client, gcc_unused ConstBuffer<const char *> args) { stats_print(client); return CommandResult::OK; } CommandResult -handle_ping(gcc_unused Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_ping(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args) { return CommandResult::OK; } CommandResult -handle_password(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_password(Client &client, ConstBuffer<const char *> args) { unsigned permission = 0; - if (getPermissionFromPassword(argv[1], &permission) < 0) { + if (getPermissionFromPassword(args.front(), &permission) < 0) { command_error(client, ACK_ERROR_PASSWORD, "incorrect password"); return CommandResult::ERROR; } @@ -402,8 +391,7 @@ handle_password(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_config(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_config(Client &client, gcc_unused ConstBuffer<const char *> args) { if (!client.IsLocal()) { command_error(client, ACK_ERROR_PERMISSION, @@ -423,17 +411,16 @@ handle_config(Client &client, } CommandResult -handle_idle(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_idle(Client &client, ConstBuffer<const char *> args) { unsigned flags = 0; - for (unsigned i = 1; i < argc; ++i) { - unsigned event = idle_parse_name(argv[i]); + for (const char *i : args) { + unsigned event = idle_parse_name(i); if (event == 0) { command_error(client, ACK_ERROR_ARG, "Unrecognized idle event: %s", - argv[i]); + i); return CommandResult::ERROR; } diff --git a/src/command/OtherCommands.hxx b/src/command/OtherCommands.hxx index 7cfa35dfb..a0076954e 100644 --- a/src/command/OtherCommands.hxx +++ b/src/command/OtherCommands.hxx @@ -23,53 +23,54 @@ #include "CommandResult.hxx" class Client; +template<typename T> struct ConstBuffer; CommandResult -handle_urlhandlers(Client &client, unsigned argc, char *argv[]); +handle_urlhandlers(Client &client, ConstBuffer<const char *> args); CommandResult -handle_decoders(Client &client, unsigned argc, char *argv[]); +handle_decoders(Client &client, ConstBuffer<const char *> args); CommandResult -handle_tagtypes(Client &client, unsigned argc, char *argv[]); +handle_tagtypes(Client &client, ConstBuffer<const char *> args); CommandResult -handle_kill(Client &client, unsigned argc, char *argv[]); +handle_kill(Client &client, ConstBuffer<const char *> args); CommandResult -handle_close(Client &client, unsigned argc, char *argv[]); +handle_close(Client &client, ConstBuffer<const char *> args); CommandResult -handle_listfiles(Client &client, unsigned argc, char *argv[]); +handle_listfiles(Client &client, ConstBuffer<const char *> args); CommandResult -handle_lsinfo(Client &client, unsigned argc, char *argv[]); +handle_lsinfo(Client &client, ConstBuffer<const char *> args); CommandResult -handle_update(Client &client, unsigned argc, char *argv[]); +handle_update(Client &client, ConstBuffer<const char *> args); CommandResult -handle_rescan(Client &client, unsigned argc, char *argv[]); +handle_rescan(Client &client, ConstBuffer<const char *> args); CommandResult -handle_setvol(Client &client, unsigned argc, char *argv[]); +handle_setvol(Client &client, ConstBuffer<const char *> args); CommandResult -handle_volume(Client &client, unsigned argc, char *argv[]); +handle_volume(Client &client, ConstBuffer<const char *> args); CommandResult -handle_stats(Client &client, unsigned argc, char *argv[]); +handle_stats(Client &client, ConstBuffer<const char *> args); CommandResult -handle_ping(Client &client, unsigned argc, char *argv[]); +handle_ping(Client &client, ConstBuffer<const char *> args); CommandResult -handle_password(Client &client, unsigned argc, char *argv[]); +handle_password(Client &client, ConstBuffer<const char *> args); CommandResult -handle_config(Client &client, unsigned argc, char *argv[]); +handle_config(Client &client, ConstBuffer<const char *> args); CommandResult -handle_idle(Client &client, unsigned argc, char *argv[]); +handle_idle(Client &client, ConstBuffer<const char *> args); #endif diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx index c69a0dd65..5b0894310 100644 --- a/src/command/OutputCommands.cxx +++ b/src/command/OutputCommands.cxx @@ -25,12 +25,15 @@ #include "protocol/ArgParser.hxx" #include "client/Client.hxx" #include "Partition.hxx" +#include "util/ConstBuffer.hxx" CommandResult -handle_enableoutput(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_enableoutput(Client &client, ConstBuffer<const char *> args) { + assert(args.size == 1); + unsigned device; - if (!check_unsigned(client, &device, argv[1])) + if (!check_unsigned(client, &device, args.front())) return CommandResult::ERROR; if (!audio_output_enable_index(client.partition.outputs, device)) { @@ -43,10 +46,12 @@ handle_enableoutput(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_disableoutput(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_disableoutput(Client &client, ConstBuffer<const char *> args) { + assert(args.size == 1); + unsigned device; - if (!check_unsigned(client, &device, argv[1])) + if (!check_unsigned(client, &device, args.front())) return CommandResult::ERROR; if (!audio_output_disable_index(client.partition.outputs, device)) { @@ -59,10 +64,12 @@ handle_disableoutput(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_toggleoutput(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_toggleoutput(Client &client, ConstBuffer<const char *> args) { + assert(args.size == 1); + unsigned device; - if (!check_unsigned(client, &device, argv[1])) + if (!check_unsigned(client, &device, args.front())) return CommandResult::ERROR; if (!audio_output_toggle_index(client.partition.outputs, device)) { @@ -75,9 +82,10 @@ handle_toggleoutput(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_devices(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_devices(Client &client, gcc_unused ConstBuffer<const char *> args) { + assert(args.IsEmpty()); + printAudioDevices(client, client.partition.outputs); return CommandResult::OK; diff --git a/src/command/OutputCommands.hxx b/src/command/OutputCommands.hxx index 8d6be0511..d550791ac 100644 --- a/src/command/OutputCommands.hxx +++ b/src/command/OutputCommands.hxx @@ -23,17 +23,18 @@ #include "CommandResult.hxx" class Client; +template<typename T> struct ConstBuffer; CommandResult -handle_enableoutput(Client &client, unsigned argc, char *argv[]); +handle_enableoutput(Client &client, ConstBuffer<const char *> args); CommandResult -handle_disableoutput(Client &client, unsigned argc, char *argv[]); +handle_disableoutput(Client &client, ConstBuffer<const char *> args); CommandResult -handle_toggleoutput(Client &client, unsigned argc, char *argv[]); +handle_toggleoutput(Client &client, ConstBuffer<const char *> args); CommandResult -handle_devices(Client &client, unsigned argc, char *argv[]); +handle_devices(Client &client, ConstBuffer<const char *> args); #endif diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx index cd7f42289..c24088f9a 100644 --- a/src/command/PlayerCommands.cxx +++ b/src/command/PlayerCommands.cxx @@ -30,6 +30,7 @@ #include "protocol/ArgParser.hxx" #include "AudioFormat.hxx" #include "ReplayGainConfig.hxx" +#include "util/ConstBuffer.hxx" #ifdef ENABLE_DATABASE #include "db/update/Service.hxx" @@ -56,22 +57,22 @@ #define COMMAND_STATUS_UPDATING_DB "updating_db" CommandResult -handle_play(Client &client, unsigned argc, char *argv[]) +handle_play(Client &client, ConstBuffer<const char *> args) { int song = -1; - if (argc == 2 && !check_int(client, &song, argv[1])) + if (!args.IsEmpty() && !check_int(client, &song, args.front())) return CommandResult::ERROR; PlaylistResult result = client.partition.PlayPosition(song); return print_playlist_result(client, result); } CommandResult -handle_playid(Client &client, unsigned argc, char *argv[]) +handle_playid(Client &client, ConstBuffer<const char *> args) { int id = -1; - if (argc == 2 && !check_int(client, &id, argv[1])) + if (!args.IsEmpty() && !check_int(client, &id, args.front())) return CommandResult::ERROR; PlaylistResult result = client.partition.PlayId(id); @@ -79,28 +80,25 @@ handle_playid(Client &client, unsigned argc, char *argv[]) } CommandResult -handle_stop(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_stop(Client &client, gcc_unused ConstBuffer<const char *> args) { client.partition.Stop(); return CommandResult::OK; } CommandResult -handle_currentsong(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_currentsong(Client &client, gcc_unused ConstBuffer<const char *> args) { playlist_print_current(client, client.playlist); return CommandResult::OK; } CommandResult -handle_pause(Client &client, - unsigned argc, char *argv[]) +handle_pause(Client &client, ConstBuffer<const char *> args) { - if (argc == 2) { + if (!args.IsEmpty()) { bool pause_flag; - if (!check_bool(client, &pause_flag, argv[1])) + if (!check_bool(client, &pause_flag, args.front())) return CommandResult::ERROR; client.player_control.SetPause(pause_flag); @@ -111,8 +109,7 @@ handle_pause(Client &client, } CommandResult -handle_status(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_status(Client &client, gcc_unused ConstBuffer<const char *> args) { const char *state = nullptr; int song; @@ -182,6 +179,10 @@ handle_status(Client &client, player_status.elapsed_time.ToDoubleS(), player_status.bit_rate); + if (!player_status.total_time.IsNegative()) + client_printf(client, "duration: %1.3f\n", + player_status.total_time.ToDoubleS()); + if (player_status.audio_format.IsDefined()) { struct audio_format_string af_string; @@ -222,8 +223,7 @@ handle_status(Client &client, } CommandResult -handle_next(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_next(Client &client, gcc_unused ConstBuffer<const char *> args) { playlist &playlist = client.playlist; @@ -239,18 +239,17 @@ handle_next(Client &client, } CommandResult -handle_previous(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_previous(Client &client, gcc_unused ConstBuffer<const char *> args) { client.partition.PlayPrevious(); return CommandResult::OK; } CommandResult -handle_repeat(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_repeat(Client &client, ConstBuffer<const char *> args) { bool status; - if (!check_bool(client, &status, argv[1])) + if (!check_bool(client, &status, args.front())) return CommandResult::ERROR; client.partition.SetRepeat(status); @@ -258,10 +257,10 @@ handle_repeat(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_single(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_single(Client &client, ConstBuffer<const char *> args) { bool status; - if (!check_bool(client, &status, argv[1])) + if (!check_bool(client, &status, args.front())) return CommandResult::ERROR; client.partition.SetSingle(status); @@ -269,10 +268,10 @@ handle_single(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_consume(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_consume(Client &client, ConstBuffer<const char *> args) { bool status; - if (!check_bool(client, &status, argv[1])) + if (!check_bool(client, &status, args.front())) return CommandResult::ERROR; client.partition.SetConsume(status); @@ -280,10 +279,10 @@ handle_consume(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_random(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_random(Client &client, ConstBuffer<const char *> args) { bool status; - if (!check_bool(client, &status, argv[1])) + if (!check_bool(client, &status, args.front())) return CommandResult::ERROR; client.partition.SetRandom(status); @@ -292,22 +291,21 @@ handle_random(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_clearerror(gcc_unused Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_clearerror(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args) { client.player_control.ClearError(); return CommandResult::OK; } CommandResult -handle_seek(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_seek(Client &client, ConstBuffer<const char *> args) { unsigned song; SongTime seek_time; - if (!check_unsigned(client, &song, argv[1])) + if (!check_unsigned(client, &song, args[0])) return CommandResult::ERROR; - if (!ParseCommandArg(client, seek_time, argv[2])) + if (!ParseCommandArg(client, seek_time, args[1])) return CommandResult::ERROR; PlaylistResult result = @@ -316,14 +314,14 @@ handle_seek(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_seekid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_seekid(Client &client, ConstBuffer<const char *> args) { unsigned id; SongTime seek_time; - if (!check_unsigned(client, &id, argv[1])) + if (!check_unsigned(client, &id, args[0])) return CommandResult::ERROR; - if (!ParseCommandArg(client, seek_time, argv[2])) + if (!ParseCommandArg(client, seek_time, args[1])) return CommandResult::ERROR; PlaylistResult result = @@ -332,9 +330,9 @@ handle_seekid(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_seekcur(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_seekcur(Client &client, ConstBuffer<const char *> args) { - const char *p = argv[1]; + const char *p = args.front(); bool relative = *p == '+' || *p == '-'; SignedSongTime seek_time; if (!ParseCommandArg(client, seek_time, p)) @@ -346,11 +344,11 @@ handle_seekcur(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_crossfade(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_crossfade(Client &client, ConstBuffer<const char *> args) { unsigned xfade_time; - if (!check_unsigned(client, &xfade_time, argv[1])) + if (!check_unsigned(client, &xfade_time, args.front())) return CommandResult::ERROR; client.player_control.SetCrossFade(xfade_time); @@ -358,11 +356,11 @@ handle_crossfade(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_mixrampdb(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_mixrampdb(Client &client, ConstBuffer<const char *> args) { float db; - if (!check_float(client, &db, argv[1])) + if (!check_float(client, &db, args.front())) return CommandResult::ERROR; client.player_control.SetMixRampDb(db); @@ -370,11 +368,11 @@ handle_mixrampdb(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_mixrampdelay(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_mixrampdelay(Client &client, ConstBuffer<const char *> args) { float delay_secs; - if (!check_float(client, &delay_secs, argv[1])) + if (!check_float(client, &delay_secs, args.front())) return CommandResult::ERROR; client.player_control.SetMixRampDelay(delay_secs); @@ -382,10 +380,9 @@ handle_mixrampdelay(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_replay_gain_mode(Client &client, - gcc_unused unsigned argc, char *argv[]) +handle_replay_gain_mode(Client &client, ConstBuffer<const char *> args) { - if (!replay_gain_set_mode_string(argv[1])) { + if (!replay_gain_set_mode_string(args.front())) { command_error(client, ACK_ERROR_ARG, "Unrecognized replay gain mode"); return CommandResult::ERROR; @@ -396,8 +393,7 @@ handle_replay_gain_mode(Client &client, } CommandResult -handle_replay_gain_status(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_replay_gain_status(Client &client, gcc_unused ConstBuffer<const char *> args) { client_printf(client, "replay_gain_mode: %s\n", replay_gain_get_mode_string()); diff --git a/src/command/PlayerCommands.hxx b/src/command/PlayerCommands.hxx index da7083f1e..492a4aced 100644 --- a/src/command/PlayerCommands.hxx +++ b/src/command/PlayerCommands.hxx @@ -23,68 +23,69 @@ #include "CommandResult.hxx" class Client; +template<typename T> struct ConstBuffer; CommandResult -handle_play(Client &client, unsigned argc, char *argv[]); +handle_play(Client &client, ConstBuffer<const char *> args); CommandResult -handle_playid(Client &client, unsigned argc, char *argv[]); +handle_playid(Client &client, ConstBuffer<const char *> args); CommandResult -handle_stop(Client &client, unsigned argc, char *argv[]); +handle_stop(Client &client, ConstBuffer<const char *> args); CommandResult -handle_currentsong(Client &client, unsigned argc, char *argv[]); +handle_currentsong(Client &client, ConstBuffer<const char *> args); CommandResult -handle_pause(Client &client, unsigned argc, char *argv[]); +handle_pause(Client &client, ConstBuffer<const char *> args); CommandResult -handle_status(Client &client, unsigned argc, char *argv[]); +handle_status(Client &client, ConstBuffer<const char *> args); CommandResult -handle_next(Client &client, unsigned argc, char *argv[]); +handle_next(Client &client, ConstBuffer<const char *> args); CommandResult -handle_previous(Client &client, unsigned argc, char *avg[]); +handle_previous(Client &client, ConstBuffer<const char *> args); CommandResult -handle_repeat(Client &client, unsigned argc, char *argv[]); +handle_repeat(Client &client, ConstBuffer<const char *> args); CommandResult -handle_single(Client &client, unsigned argc, char *argv[]); +handle_single(Client &client, ConstBuffer<const char *> args); CommandResult -handle_consume(Client &client, unsigned argc, char *argv[]); +handle_consume(Client &client, ConstBuffer<const char *> args); CommandResult -handle_random(Client &client, unsigned argc, char *argv[]); +handle_random(Client &client, ConstBuffer<const char *> args); CommandResult -handle_clearerror(Client &client, unsigned argc, char *argv[]); +handle_clearerror(Client &client, ConstBuffer<const char *> args); CommandResult -handle_seek(Client &client, unsigned argc, char *argv[]); +handle_seek(Client &client, ConstBuffer<const char *> args); CommandResult -handle_seekid(Client &client, unsigned argc, char *argv[]); +handle_seekid(Client &client, ConstBuffer<const char *> args); CommandResult -handle_seekcur(Client &client, unsigned argc, char *argv[]); +handle_seekcur(Client &client, ConstBuffer<const char *> args); CommandResult -handle_crossfade(Client &client, unsigned argc, char *argv[]); +handle_crossfade(Client &client, ConstBuffer<const char *> args); CommandResult -handle_mixrampdb(Client &client, unsigned argc, char *argv[]); +handle_mixrampdb(Client &client, ConstBuffer<const char *> args); CommandResult -handle_mixrampdelay(Client &client, unsigned argc, char *argv[]); +handle_mixrampdelay(Client &client, ConstBuffer<const char *> args); CommandResult -handle_replay_gain_mode(Client &client, unsigned argc, char *argv[]); +handle_replay_gain_mode(Client &client, ConstBuffer<const char *> args); CommandResult -handle_replay_gain_status(Client &client, unsigned argc, char *argv[]); +handle_replay_gain_status(Client &client, ConstBuffer<const char *> args); #endif diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx index c2b18064c..4abc88031 100644 --- a/src/command/PlaylistCommands.cxx +++ b/src/command/PlaylistCommands.cxx @@ -35,8 +35,17 @@ #include "protocol/ArgParser.hxx" #include "protocol/Result.hxx" #include "ls.hxx" +#include "Mapper.hxx" +#include "fs/AllocatedPath.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" +#include "util/ConstBuffer.hxx" + +bool +playlist_commands_available() +{ + return !map_spl_path().IsNull(); +} static void print_spl_list(Client &client, const PlaylistVector &list) @@ -50,28 +59,28 @@ print_spl_list(Client &client, const PlaylistVector &list) } CommandResult -handle_save(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_save(Client &client, ConstBuffer<const char *> args) { - PlaylistResult result = spl_save_playlist(argv[1], client.playlist); + PlaylistResult result = spl_save_playlist(args.front(), client.playlist); return print_playlist_result(client, result); } CommandResult -handle_load(Client &client, unsigned argc, char *argv[]) +handle_load(Client &client, ConstBuffer<const char *> args) { unsigned start_index, end_index; - if (argc < 3) { + if (args.size < 2) { start_index = 0; end_index = unsigned(-1); - } else if (!check_range(client, &start_index, &end_index, argv[2])) + } else if (!check_range(client, &start_index, &end_index, args[1])) return CommandResult::ERROR; const ScopeBulkEdit bulk_edit(client.partition); Error error; const SongLoader loader(client); - if (!playlist_open_into_queue(argv[1], + if (!playlist_open_into_queue(args.front(), start_index, end_index, client.playlist, client.player_control, loader, error)) @@ -81,94 +90,104 @@ handle_load(Client &client, unsigned argc, char *argv[]) } CommandResult -handle_listplaylist(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_listplaylist(Client &client, ConstBuffer<const char *> args) { - if (playlist_file_print(client, argv[1], false)) + const char *const name = args.front(); + + if (playlist_file_print(client, name, false)) return CommandResult::OK; Error error; - return spl_print(client, argv[1], false, error) + return spl_print(client, name, false, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_listplaylistinfo(Client &client, - gcc_unused unsigned argc, char *argv[]) +handle_listplaylistinfo(Client &client, ConstBuffer<const char *> args) { - if (playlist_file_print(client, argv[1], true)) + const char *const name = args.front(); + + if (playlist_file_print(client, name, true)) return CommandResult::OK; Error error; - return spl_print(client, argv[1], true, error) + return spl_print(client, name, true, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_rm(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_rm(Client &client, ConstBuffer<const char *> args) { + const char *const name = args.front(); + Error error; - return spl_delete(argv[1], error) + return spl_delete(name, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_rename(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_rename(Client &client, ConstBuffer<const char *> args) { + const char *const old_name = args[0]; + const char *const new_name = args[1]; + Error error; - return spl_rename(argv[1], argv[2], error) + return spl_rename(old_name, new_name, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_playlistdelete(Client &client, - gcc_unused unsigned argc, char *argv[]) { - char *playlist = argv[1]; +handle_playlistdelete(Client &client, ConstBuffer<const char *> args) +{ + const char *const name = args[0]; unsigned from; - if (!check_unsigned(client, &from, argv[2])) + if (!check_unsigned(client, &from, args[1])) return CommandResult::ERROR; Error error; - return spl_remove_index(playlist, from, error) + return spl_remove_index(name, from, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_playlistmove(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_playlistmove(Client &client, ConstBuffer<const char *> args) { - char *playlist = argv[1]; + const char *const name = args.front(); unsigned from, to; - if (!check_unsigned(client, &from, argv[2])) + if (!check_unsigned(client, &from, args[1])) return CommandResult::ERROR; - if (!check_unsigned(client, &to, argv[3])) + if (!check_unsigned(client, &to, args[2])) return CommandResult::ERROR; Error error; - return spl_move_index(playlist, from, to, error) + return spl_move_index(name, from, to, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_playlistclear(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_playlistclear(Client &client, ConstBuffer<const char *> args) { + const char *const name = args.front(); + Error error; - return spl_clear(argv[1], error) + return spl_clear(name, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_playlistadd(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_playlistadd(Client &client, ConstBuffer<const char *> args) { - char *playlist = argv[1]; - char *uri = argv[2]; + const char *const playlist = args[0]; + const char *const uri = args[1]; bool success; Error error; @@ -199,8 +218,7 @@ handle_playlistadd(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_listplaylists(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_listplaylists(Client &client, gcc_unused ConstBuffer<const char *> args) { Error error; const auto list = ListPlaylistFiles(error); diff --git a/src/command/PlaylistCommands.hxx b/src/command/PlaylistCommands.hxx index fba4e1318..91721899c 100644 --- a/src/command/PlaylistCommands.hxx +++ b/src/command/PlaylistCommands.hxx @@ -21,40 +21,46 @@ #define MPD_PLAYLIST_COMMANDS_HXX #include "CommandResult.hxx" +#include "Compiler.h" class Client; +template<typename T> struct ConstBuffer; + +gcc_const +bool +playlist_commands_available(); CommandResult -handle_save(Client &client, unsigned argc, char *argv[]); +handle_save(Client &client, ConstBuffer<const char *> args); CommandResult -handle_load(Client &client, unsigned argc, char *argv[]); +handle_load(Client &client, ConstBuffer<const char *> args); CommandResult -handle_listplaylist(Client &client, unsigned argc, char *argv[]); +handle_listplaylist(Client &client, ConstBuffer<const char *> args); CommandResult -handle_listplaylistinfo(Client &client, unsigned argc, char *argv[]); +handle_listplaylistinfo(Client &client, ConstBuffer<const char *> args); CommandResult -handle_rm(Client &client, unsigned argc, char *argv[]); +handle_rm(Client &client, ConstBuffer<const char *> args); CommandResult -handle_rename(Client &client, unsigned argc, char *argv[]); +handle_rename(Client &client, ConstBuffer<const char *> args); CommandResult -handle_playlistdelete(Client &client, unsigned argc, char *argv[]); +handle_playlistdelete(Client &client, ConstBuffer<const char *> args); CommandResult -handle_playlistmove(Client &client, unsigned argc, char *argv[]); +handle_playlistmove(Client &client, ConstBuffer<const char *> args); CommandResult -handle_playlistclear(Client &client, unsigned argc, char *argv[]); +handle_playlistclear(Client &client, ConstBuffer<const char *> args); CommandResult -handle_playlistadd(Client &client, unsigned argc, char *argv[]); +handle_playlistadd(Client &client, ConstBuffer<const char *> args); CommandResult -handle_listplaylists(Client &client, unsigned argc, char *argv[]); +handle_listplaylists(Client &client, ConstBuffer<const char *> args); #endif diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx index d0b789eb1..e94c7bc8b 100644 --- a/src/command/QueueCommands.cxx +++ b/src/command/QueueCommands.cxx @@ -59,9 +59,9 @@ translate_uri(Client &client, const char *uri) } CommandResult -handle_add(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_add(Client &client, ConstBuffer<const char *> args) { - const char *uri = argv[1]; + const char *uri = args.front(); if (memcmp(uri, "/", 2) == 0) /* this URI is malformed, but some clients are buggy and use "add /" to add the whole database, which @@ -99,9 +99,9 @@ handle_add(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_addid(Client &client, unsigned argc, char *argv[]) +handle_addid(Client &client, ConstBuffer<const char *> args) { - const char *const uri = translate_uri(client, argv[1]); + const char *const uri = translate_uri(client, args.front()); if (uri == nullptr) return CommandResult::ERROR; @@ -111,9 +111,9 @@ handle_addid(Client &client, unsigned argc, char *argv[]) if (added_id == 0) return print_error(client, error); - if (argc == 3) { + if (args.size == 2) { unsigned to; - if (!check_unsigned(client, &to, argv[2])) + if (!check_unsigned(client, &to, args[1])) return CommandResult::ERROR; PlaylistResult result = client.partition.MoveId(added_id, to); if (result != PlaylistResult::SUCCESS) { @@ -160,14 +160,14 @@ parse_time_range(const char *p, SongTime &start_r, SongTime &end_r) } CommandResult -handle_rangeid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_rangeid(Client &client, ConstBuffer<const char *> args) { unsigned id; - if (!check_unsigned(client, &id, argv[1])) + if (!check_unsigned(client, &id, args.front())) return CommandResult::ERROR; SongTime start, end; - if (!parse_time_range(argv[2], start, end)) { + if (!parse_time_range(args[1], start, end)) { command_error(client, ACK_ERROR_ARG, "Bad range"); return CommandResult::ERROR; } @@ -182,11 +182,11 @@ handle_rangeid(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_delete(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_delete(Client &client, ConstBuffer<const char *> args) { unsigned start, end; - if (!check_range(client, &start, &end, argv[1])) + if (!check_range(client, &start, &end, args.front())) return CommandResult::ERROR; PlaylistResult result = client.partition.DeleteRange(start, end); @@ -194,11 +194,11 @@ handle_delete(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_deleteid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_deleteid(Client &client, ConstBuffer<const char *> args) { unsigned id; - if (!check_unsigned(client, &id, argv[1])) + if (!check_unsigned(client, &id, args.front())) return CommandResult::ERROR; PlaylistResult result = client.partition.DeleteId(id); @@ -206,19 +206,17 @@ handle_deleteid(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_playlist(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_playlist(Client &client, gcc_unused ConstBuffer<const char *> args) { playlist_print_uris(client, client.playlist); return CommandResult::OK; } CommandResult -handle_shuffle(gcc_unused Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_shuffle(gcc_unused Client &client, ConstBuffer<const char *> args) { unsigned start = 0, end = client.playlist.queue.GetLength(); - if (argc == 2 && !check_range(client, &start, &end, argv[1])) + if (args.size == 1 && !check_range(client, &start, &end, args.front())) return CommandResult::ERROR; client.partition.Shuffle(start, end); @@ -226,19 +224,18 @@ handle_shuffle(gcc_unused Client &client, } CommandResult -handle_clear(gcc_unused Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_clear(gcc_unused Client &client, gcc_unused ConstBuffer<const char *> args) { client.partition.ClearQueue(); return CommandResult::OK; } CommandResult -handle_plchanges(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_plchanges(Client &client, ConstBuffer<const char *> args) { uint32_t version; - if (!check_uint32(client, &version, argv[1])) + if (!check_uint32(client, &version, args.front())) return CommandResult::ERROR; playlist_print_changes_info(client, client.playlist, version); @@ -246,11 +243,11 @@ handle_plchanges(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_plchangesposid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_plchangesposid(Client &client, ConstBuffer<const char *> args) { uint32_t version; - if (!check_uint32(client, &version, argv[1])) + if (!check_uint32(client, &version, args.front())) return CommandResult::ERROR; playlist_print_changes_position(client, client.playlist, version); @@ -258,12 +255,12 @@ handle_plchangesposid(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_playlistinfo(Client &client, unsigned argc, char *argv[]) +handle_playlistinfo(Client &client, ConstBuffer<const char *> args) { unsigned start = 0, end = std::numeric_limits<unsigned>::max(); bool ret; - if (argc == 2 && !check_range(client, &start, &end, argv[1])) + if (args.size == 1 && !check_range(client, &start, &end, args.front())) return CommandResult::ERROR; ret = playlist_print_info(client, client.playlist, start, end); @@ -275,11 +272,11 @@ handle_playlistinfo(Client &client, unsigned argc, char *argv[]) } CommandResult -handle_playlistid(Client &client, unsigned argc, char *argv[]) +handle_playlistid(Client &client, ConstBuffer<const char *> args) { - if (argc >= 2) { + if (!args.IsEmpty()) { unsigned id; - if (!check_unsigned(client, &id, argv[1])) + if (!check_unsigned(client, &id, args.front())) return CommandResult::ERROR; bool ret = playlist_print_id(client, client.playlist, id); @@ -295,11 +292,9 @@ handle_playlistid(Client &client, unsigned argc, char *argv[]) } static CommandResult -handle_playlist_match(Client &client, unsigned argc, char *argv[], +handle_playlist_match(Client &client, ConstBuffer<const char *> args, bool fold_case) { - ConstBuffer<const char *> args(argv + 1, argc - 1); - SongFilter filter; if (!filter.Parse(args, fold_case)) { command_error(client, ACK_ERROR_ARG, "incorrect arguments"); @@ -311,35 +306,35 @@ handle_playlist_match(Client &client, unsigned argc, char *argv[], } CommandResult -handle_playlistfind(Client &client, unsigned argc, char *argv[]) +handle_playlistfind(Client &client, ConstBuffer<const char *> args) { - return handle_playlist_match(client, argc, argv, false); + return handle_playlist_match(client, args, false); } CommandResult -handle_playlistsearch(Client &client, unsigned argc, char *argv[]) +handle_playlistsearch(Client &client, ConstBuffer<const char *> args) { - return handle_playlist_match(client, argc, argv, true); + return handle_playlist_match(client, args, true); } CommandResult -handle_prio(Client &client, unsigned argc, char *argv[]) +handle_prio(Client &client, ConstBuffer<const char *> args) { + const char *const priority_string = args.shift(); unsigned priority; - if (!check_unsigned(client, &priority, argv[1])) + if (!check_unsigned(client, &priority, priority_string)) return CommandResult::ERROR; if (priority > 0xff) { command_error(client, ACK_ERROR_ARG, - "Priority out of range: %s", argv[1]); + "Priority out of range: %s", priority_string); return CommandResult::ERROR; } - for (unsigned i = 2; i < argc; ++i) { + for (const char *i : args) { unsigned start_position, end_position; - if (!check_range(client, &start_position, &end_position, - argv[i])) + if (!check_range(client, &start_position, &end_position, i)) return CommandResult::ERROR; PlaylistResult result = @@ -354,22 +349,23 @@ handle_prio(Client &client, unsigned argc, char *argv[]) } CommandResult -handle_prioid(Client &client, unsigned argc, char *argv[]) +handle_prioid(Client &client, ConstBuffer<const char *> args) { + const char *const priority_string = args.shift(); unsigned priority; - if (!check_unsigned(client, &priority, argv[1])) + if (!check_unsigned(client, &priority, priority_string)) return CommandResult::ERROR; if (priority > 0xff) { command_error(client, ACK_ERROR_ARG, - "Priority out of range: %s", argv[1]); + "Priority out of range: %s", priority_string); return CommandResult::ERROR; } - for (unsigned i = 2; i < argc; ++i) { + for (const char *i : args) { unsigned song_id; - if (!check_unsigned(client, &song_id, argv[i])) + if (!check_unsigned(client, &song_id, i)) return CommandResult::ERROR; PlaylistResult result = @@ -382,14 +378,14 @@ handle_prioid(Client &client, unsigned argc, char *argv[]) } CommandResult -handle_move(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_move(Client &client, ConstBuffer<const char *> args) { unsigned start, end; int to; - if (!check_range(client, &start, &end, argv[1])) + if (!check_range(client, &start, &end, args[0])) return CommandResult::ERROR; - if (!check_int(client, &to, argv[2])) + if (!check_int(client, &to, args[1])) return CommandResult::ERROR; PlaylistResult result = @@ -398,27 +394,27 @@ handle_move(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_moveid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_moveid(Client &client, ConstBuffer<const char *> args) { unsigned id; int to; - if (!check_unsigned(client, &id, argv[1])) + if (!check_unsigned(client, &id, args[0])) return CommandResult::ERROR; - if (!check_int(client, &to, argv[2])) + if (!check_int(client, &to, args[1])) return CommandResult::ERROR; PlaylistResult result = client.partition.MoveId(id, to); return print_playlist_result(client, result); } CommandResult -handle_swap(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_swap(Client &client, ConstBuffer<const char *> args) { unsigned song1, song2; - if (!check_unsigned(client, &song1, argv[1])) + if (!check_unsigned(client, &song1, args[0])) return CommandResult::ERROR; - if (!check_unsigned(client, &song2, argv[2])) + if (!check_unsigned(client, &song2, args[1])) return CommandResult::ERROR; PlaylistResult result = @@ -427,13 +423,13 @@ handle_swap(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_swapid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_swapid(Client &client, ConstBuffer<const char *> args) { unsigned id1, id2; - if (!check_unsigned(client, &id1, argv[1])) + if (!check_unsigned(client, &id1, args[0])) return CommandResult::ERROR; - if (!check_unsigned(client, &id2, argv[2])) + if (!check_unsigned(client, &id2, args[1])) return CommandResult::ERROR; PlaylistResult result = client.partition.SwapIds(id1, id2); diff --git a/src/command/QueueCommands.hxx b/src/command/QueueCommands.hxx index f98f7bad2..5193e8b65 100644 --- a/src/command/QueueCommands.hxx +++ b/src/command/QueueCommands.hxx @@ -23,65 +23,66 @@ #include "CommandResult.hxx" class Client; +template<typename T> struct ConstBuffer; CommandResult -handle_add(Client &client, unsigned argc, char *argv[]); +handle_add(Client &client, ConstBuffer<const char *> args); CommandResult -handle_addid(Client &client, unsigned argc, char *argv[]); +handle_addid(Client &client, ConstBuffer<const char *> args); CommandResult -handle_rangeid(Client &client, unsigned argc, char *argv[]); +handle_rangeid(Client &client, ConstBuffer<const char *> args); CommandResult -handle_delete(Client &client, unsigned argc, char *argv[]); +handle_delete(Client &client, ConstBuffer<const char *> args); CommandResult -handle_deleteid(Client &client, unsigned argc, char *argv[]); +handle_deleteid(Client &client, ConstBuffer<const char *> args); CommandResult -handle_playlist(Client &client, unsigned argc, char *argv[]); +handle_playlist(Client &client, ConstBuffer<const char *> args); CommandResult -handle_shuffle(Client &client, unsigned argc, char *argv[]); +handle_shuffle(Client &client, ConstBuffer<const char *> args); CommandResult -handle_clear(Client &client, unsigned argc, char *argv[]); +handle_clear(Client &client, ConstBuffer<const char *> args); CommandResult -handle_plchanges(Client &client, unsigned argc, char *argv[]); +handle_plchanges(Client &client, ConstBuffer<const char *> args); CommandResult -handle_plchangesposid(Client &client, unsigned argc, char *argv[]); +handle_plchangesposid(Client &client, ConstBuffer<const char *> args); CommandResult -handle_playlistinfo(Client &client, unsigned argc, char *argv[]); +handle_playlistinfo(Client &client, ConstBuffer<const char *> args); CommandResult -handle_playlistid(Client &client, unsigned argc, char *argv[]); +handle_playlistid(Client &client, ConstBuffer<const char *> args); CommandResult -handle_playlistfind(Client &client, unsigned argc, char *argv[]); +handle_playlistfind(Client &client, ConstBuffer<const char *> args); CommandResult -handle_playlistsearch(Client &client, unsigned argc, char *argv[]); +handle_playlistsearch(Client &client, ConstBuffer<const char *> args); CommandResult -handle_prio(Client &client, unsigned argc, char *argv[]); +handle_prio(Client &client, ConstBuffer<const char *> args); CommandResult -handle_prioid(Client &client, unsigned argc, char *argv[]); +handle_prioid(Client &client, ConstBuffer<const char *> args); CommandResult -handle_move(Client &client, unsigned argc, char *argv[]); +handle_move(Client &client, ConstBuffer<const char *> args); CommandResult -handle_moveid(Client &client, unsigned argc, char *argv[]); +handle_moveid(Client &client, ConstBuffer<const char *> args); CommandResult -handle_swap(Client &client, unsigned argc, char *argv[]); +handle_swap(Client &client, ConstBuffer<const char *> args); CommandResult -handle_swapid(Client &client, unsigned argc, char *argv[]); +handle_swapid(Client &client, ConstBuffer<const char *> args); #endif diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx index 37506d51b..fce53e162 100644 --- a/src/command/StickerCommands.cxx +++ b/src/command/StickerCommands.cxx @@ -31,6 +31,7 @@ #include "Partition.hxx" #include "Instance.hxx" #include "util/Error.hxx" +#include "util/ConstBuffer.hxx" #include <string.h> @@ -51,53 +52,64 @@ sticker_song_find_print_cb(const LightSong &song, const char *value, } static CommandResult -handle_sticker_song(Client &client, unsigned argc, char *argv[]) +handle_sticker_song(Client &client, ConstBuffer<const char *> args) { Error error; const Database *db = client.GetDatabase(error); if (db == nullptr) return print_error(client, error); + const char *const cmd = args.front(); + /* get song song_id key */ - if (argc == 5 && strcmp(argv[1], "get") == 0) { - const LightSong *song = db->GetSong(argv[3], error); + if (args.size == 4 && strcmp(cmd, "get") == 0) { + const LightSong *song = db->GetSong(args[2], error); if (song == nullptr) return print_error(client, error); - const auto value = sticker_song_get_value(*song, argv[4]); + const auto value = sticker_song_get_value(*song, args[3], + error); db->ReturnSong(song); if (value.empty()) { + if (error.IsDefined()) + return print_error(client, error); + command_error(client, ACK_ERROR_NO_EXIST, "no such sticker"); return CommandResult::ERROR; } - sticker_print_value(client, argv[4], value.c_str()); + sticker_print_value(client, args[3], value.c_str()); return CommandResult::OK; /* list song song_id */ - } else if (argc == 4 && strcmp(argv[1], "list") == 0) { - const LightSong *song = db->GetSong(argv[3], error); + } else if (args.size == 3 && strcmp(cmd, "list") == 0) { + const LightSong *song = db->GetSong(args[2], error); if (song == nullptr) return print_error(client, error); - sticker *sticker = sticker_song_get(*song); + sticker *sticker = sticker_song_get(*song, error); db->ReturnSong(song); if (sticker) { sticker_print(client, *sticker); sticker_free(sticker); - } + } else if (error.IsDefined()) + return print_error(client, error); return CommandResult::OK; /* set song song_id id key */ - } else if (argc == 6 && strcmp(argv[1], "set") == 0) { - const LightSong *song = db->GetSong(argv[3], error); + } else if (args.size == 5 && strcmp(cmd, "set") == 0) { + const LightSong *song = db->GetSong(args[2], error); if (song == nullptr) return print_error(client, error); - bool ret = sticker_song_set_value(*song, argv[4], argv[5]); + bool ret = sticker_song_set_value(*song, args[3], args[4], + error); db->ReturnSong(song); if (!ret) { + if (error.IsDefined()) + return print_error(client, error); + command_error(client, ACK_ERROR_SYSTEM, "failed to set sticker value"); return CommandResult::ERROR; @@ -105,17 +117,20 @@ handle_sticker_song(Client &client, unsigned argc, char *argv[]) return CommandResult::OK; /* delete song song_id [key] */ - } else if ((argc == 4 || argc == 5) && - strcmp(argv[1], "delete") == 0) { - const LightSong *song = db->GetSong(argv[3], error); + } else if ((args.size == 3 || args.size == 4) && + strcmp(cmd, "delete") == 0) { + const LightSong *song = db->GetSong(args[2], error); if (song == nullptr) return print_error(client, error); - bool ret = argc == 4 - ? sticker_song_delete(*song) - : sticker_song_delete_value(*song, argv[4]); + bool ret = args.size == 3 + ? sticker_song_delete(*song, error) + : sticker_song_delete_value(*song, args[3], error); db->ReturnSong(song); if (!ret) { + if (error.IsDefined()) + return print_error(client, error); + command_error(client, ACK_ERROR_SYSTEM, "no such sticker"); return CommandResult::ERROR; @@ -123,20 +138,48 @@ handle_sticker_song(Client &client, unsigned argc, char *argv[]) return CommandResult::OK; /* find song dir key */ - } else if (argc == 5 && strcmp(argv[1], "find") == 0) { + } else if ((args.size == 4 || args.size == 6) && + strcmp(cmd, "find") == 0) { /* "sticker find song a/directory name" */ - const char *const base_uri = argv[3]; + const char *const base_uri = args[2]; + + StickerOperator op = StickerOperator::EXISTS; + const char *value = nullptr; + + if (args.size == 6) { + /* match the value */ + + const char *op_s = args[4]; + value = args[5]; + + if (strcmp(op_s, "=") == 0) + op = StickerOperator::EQUALS; + else if (strcmp(op_s, "<") == 0) + op = StickerOperator::LESS_THAN; + else if (strcmp(op_s, ">") == 0) + op = StickerOperator::GREATER_THAN; + else { + command_error(client, ACK_ERROR_ARG, + "bad operator"); + return CommandResult::ERROR; + } + } bool success; struct sticker_song_find_data data = { client, - argv[4], + args[3], }; success = sticker_song_find(*db, base_uri, data.name, - sticker_song_find_print_cb, &data); + op, value, + sticker_song_find_print_cb, &data, + error); if (!success) { + if (error.IsDefined()) + return print_error(client, error); + command_error(client, ACK_ERROR_SYSTEM, "failed to set search sticker database"); return CommandResult::ERROR; @@ -150,9 +193,9 @@ handle_sticker_song(Client &client, unsigned argc, char *argv[]) } CommandResult -handle_sticker(Client &client, unsigned argc, char *argv[]) +handle_sticker(Client &client, ConstBuffer<const char *> args) { - assert(argc >= 4); + assert(args.size >= 3); if (!sticker_enabled()) { command_error(client, ACK_ERROR_UNKNOWN, @@ -160,8 +203,8 @@ handle_sticker(Client &client, unsigned argc, char *argv[]) return CommandResult::ERROR; } - if (strcmp(argv[2], "song") == 0) - return handle_sticker_song(client, argc, argv); + if (strcmp(args[1], "song") == 0) + return handle_sticker_song(client, args); else { command_error(client, ACK_ERROR_ARG, "unknown sticker domain"); diff --git a/src/command/StickerCommands.hxx b/src/command/StickerCommands.hxx index cf46cd034..0e8d765fa 100644 --- a/src/command/StickerCommands.hxx +++ b/src/command/StickerCommands.hxx @@ -23,8 +23,9 @@ #include "CommandResult.hxx" class Client; +template<typename T> struct ConstBuffer; CommandResult -handle_sticker(Client &client, unsigned argc, char *argv[]); +handle_sticker(Client &client, ConstBuffer<const char *> args); #endif diff --git a/src/command/StorageCommands.cxx b/src/command/StorageCommands.cxx index ee51c573e..510e9f1b1 100644 --- a/src/command/StorageCommands.cxx +++ b/src/command/StorageCommands.cxx @@ -25,6 +25,7 @@ #include "protocol/Result.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" +#include "util/ConstBuffer.hxx" #include "fs/Traits.hxx" #include "client/Client.hxx" #include "Partition.hxx" @@ -167,7 +168,7 @@ print_storage_uri(Client &client, const Storage &storage) } CommandResult -handle_listmounts(Client &client, gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_listmounts(Client &client, gcc_unused ConstBuffer<const char *> args) { Storage *_composite = client.partition.instance.storage; if (_composite == nullptr) { @@ -189,7 +190,7 @@ handle_listmounts(Client &client, gcc_unused unsigned argc, gcc_unused char *arg } CommandResult -handle_mount(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_mount(Client &client, ConstBuffer<const char *> args) { Storage *_composite = client.partition.instance.storage; if (_composite == nullptr) { @@ -199,8 +200,8 @@ handle_mount(Client &client, gcc_unused unsigned argc, char *argv[]) CompositeStorage &composite = *(CompositeStorage *)_composite; - const char *const local_uri = argv[1]; - const char *const remote_uri = argv[2]; + const char *const local_uri = args[0]; + const char *const remote_uri = args[1]; if (*local_uri == 0) { command_error(client, ACK_ERROR_ARG, "Bad mount point"); @@ -252,7 +253,7 @@ handle_mount(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_unmount(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_unmount(Client &client, ConstBuffer<const char *> args) { Storage *_composite = client.partition.instance.storage; if (_composite == nullptr) { @@ -262,7 +263,7 @@ handle_unmount(Client &client, gcc_unused unsigned argc, char *argv[]) CompositeStorage &composite = *(CompositeStorage *)_composite; - const char *const local_uri = argv[1]; + const char *const local_uri = args.front(); if (*local_uri == 0) { command_error(client, ACK_ERROR_ARG, "Bad mount point"); diff --git a/src/command/StorageCommands.hxx b/src/command/StorageCommands.hxx index a3636d54a..ce3622c4b 100644 --- a/src/command/StorageCommands.hxx +++ b/src/command/StorageCommands.hxx @@ -24,6 +24,7 @@ class Client; class Storage; +template<typename T> struct ConstBuffer; CommandResult handle_listfiles_storage(Client &client, Storage &storage, const char *uri); @@ -32,12 +33,12 @@ CommandResult handle_listfiles_storage(Client &client, const char *uri); CommandResult -handle_listmounts(Client &client, unsigned argc, char *argv[]); +handle_listmounts(Client &client, ConstBuffer<const char *> args); CommandResult -handle_mount(Client &client, unsigned argc, char *argv[]); +handle_mount(Client &client, ConstBuffer<const char *> args); CommandResult -handle_unmount(Client &client, unsigned argc, char *argv[]); +handle_unmount(Client &client, ConstBuffer<const char *> args); #endif diff --git a/src/command/TagCommands.cxx b/src/command/TagCommands.cxx index 2d537671c..6f821f451 100644 --- a/src/command/TagCommands.cxx +++ b/src/command/TagCommands.cxx @@ -25,15 +25,16 @@ #include "protocol/Result.hxx" #include "tag/Tag.hxx" #include "Partition.hxx" +#include "util/ConstBuffer.hxx" CommandResult -handle_addtagid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_addtagid(Client &client, ConstBuffer<const char *> args) { unsigned song_id; - if (!check_unsigned(client, &song_id, argv[1])) + if (!check_unsigned(client, &song_id, args.front())) return CommandResult::ERROR; - const char *const tag_name = argv[2]; + const char *const tag_name = args[1]; const TagType tag_type = tag_name_parse_i(tag_name); if (tag_type == TAG_NUM_OF_ITEM_TYPES) { command_error(client, ACK_ERROR_ARG, @@ -41,7 +42,7 @@ handle_addtagid(Client &client, gcc_unused unsigned argc, char *argv[]) return CommandResult::ERROR; } - const char *const value = argv[3]; + const char *const value = args[2]; Error error; if (!client.partition.playlist.AddSongIdTag(song_id, tag_type, value, @@ -52,15 +53,15 @@ handle_addtagid(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_cleartagid(Client &client, unsigned argc, char *argv[]) +handle_cleartagid(Client &client, ConstBuffer<const char *> args) { unsigned song_id; - if (!check_unsigned(client, &song_id, argv[1])) + if (!check_unsigned(client, &song_id, args.front())) return CommandResult::ERROR; TagType tag_type = TAG_NUM_OF_ITEM_TYPES; - if (argc >= 3) { - const char *const tag_name = argv[2]; + if (args.size >= 2) { + const char *const tag_name = args[1]; tag_type = tag_name_parse_i(tag_name); if (tag_type == TAG_NUM_OF_ITEM_TYPES) { command_error(client, ACK_ERROR_ARG, diff --git a/src/command/TagCommands.hxx b/src/command/TagCommands.hxx index 748838e68..bc813b151 100644 --- a/src/command/TagCommands.hxx +++ b/src/command/TagCommands.hxx @@ -23,11 +23,12 @@ #include "CommandResult.hxx" class Client; +template<typename T> struct ConstBuffer; CommandResult -handle_addtagid(Client &client, unsigned argc, char *argv[]); +handle_addtagid(Client &client, ConstBuffer<const char *> args); CommandResult -handle_cleartagid(Client &client, unsigned argc, char *argv[]); +handle_cleartagid(Client &client, ConstBuffer<const char *> args); #endif diff --git a/src/config/ConfigData.hxx b/src/config/ConfigData.hxx index e42d674ba..1a16fae7f 100644 --- a/src/config/ConfigData.hxx +++ b/src/config/ConfigData.hxx @@ -109,7 +109,7 @@ struct config_param { const char *default_value=nullptr) const; /** - * Same as config_dup_path(), but looks up the setting in the + * Same as config_get_path(), but looks up the setting in the * specified block. */ AllocatedPath GetBlockPath(const char *name, const char *default_value, diff --git a/src/config/ConfigGlobal.cxx b/src/config/ConfigGlobal.cxx index 9bc83398c..06b41de80 100644 --- a/src/config/ConfigGlobal.cxx +++ b/src/config/ConfigGlobal.cxx @@ -38,6 +38,7 @@ void config_global_finish(void) { for (auto i : config_data.params) delete i; + config_data.params.fill(0); } void config_global_init(void) diff --git a/src/config/ConfigParser.cxx b/src/config/ConfigParser.cxx index 3535c9a13..da7d58e49 100644 --- a/src/config/ConfigParser.cxx +++ b/src/config/ConfigParser.cxx @@ -23,8 +23,8 @@ bool get_bool(const char *value, bool *value_r) { - static const char *t[] = { "yes", "true", "1", nullptr }; - static const char *f[] = { "no", "false", "0", nullptr }; + static const char *const t[] = { "yes", "true", "1", nullptr }; + static const char *const f[] = { "no", "false", "0", nullptr }; if (string_array_contains(t, value)) { *value_r = true; diff --git a/src/config/ConfigTemplates.cxx b/src/config/ConfigTemplates.cxx index 58ee56425..801ee8b20 100644 --- a/src/config/ConfigTemplates.cxx +++ b/src/config/ConfigTemplates.cxx @@ -19,6 +19,7 @@ #include "ConfigTemplates.hxx" #include "ConfigOption.hxx" +#include "util/Macros.hxx" #include <string.h> @@ -81,8 +82,7 @@ const ConfigTemplate config_templates[] = { { "neighbors", true, true }, }; -static constexpr unsigned n_config_templates = - sizeof(config_templates) / sizeof(config_templates[0]); +static constexpr unsigned n_config_templates = ARRAY_SIZE(config_templates); static_assert(n_config_templates == unsigned(CONF_MAX), "Wrong number of config_templates"); diff --git a/src/db/DatabasePrint.cxx b/src/db/DatabasePrint.cxx index 498aedf97..c38f1c277 100644 --- a/src/db/DatabasePrint.cxx +++ b/src/db/DatabasePrint.cxx @@ -147,27 +147,50 @@ PrintPlaylistFull(Client &client, bool base, bool db_selection_print(Client &client, const DatabaseSelection &selection, - bool full, bool base, Error &error) + bool full, bool base, + unsigned window_start, unsigned window_end, + Error &error) { const Database *db = client.GetDatabase(error); if (db == nullptr) return false; + unsigned i = 0; + using namespace std::placeholders; const auto d = selection.filter == nullptr ? std::bind(full ? PrintDirectoryFull : PrintDirectoryBrief, std::ref(client), base, _1) : VisitDirectory(); - const auto s = std::bind(full ? PrintSongFull : PrintSongBrief, - std::ref(client), base, _1); + VisitSong s = std::bind(full ? PrintSongFull : PrintSongBrief, + std::ref(client), base, _1); const auto p = selection.filter == nullptr ? std::bind(full ? PrintPlaylistFull : PrintPlaylistBrief, std::ref(client), base, _1, _2) : VisitPlaylist(); + if (window_start > 0 || + window_end < (unsigned)std::numeric_limits<int>::max()) + s = [s, window_start, window_end, &i](const LightSong &song, + Error &error2){ + const bool in_window = i >= window_start && i < window_end; + ++i; + return !in_window || s(song, error2); + }; + return db->Visit(selection, d, s, p, error); } +bool +db_selection_print(Client &client, const DatabaseSelection &selection, + bool full, bool base, + Error &error) +{ + return db_selection_print(client, selection, full, base, + 0, std::numeric_limits<int>::max(), + error); +} + static bool PrintSongURIVisitor(Client &client, const LightSong &song) { diff --git a/src/db/DatabasePrint.hxx b/src/db/DatabasePrint.hxx index 2ab5e703d..7e4dd8572 100644 --- a/src/db/DatabasePrint.hxx +++ b/src/db/DatabasePrint.hxx @@ -38,6 +38,12 @@ db_selection_print(Client &client, const DatabaseSelection &selection, bool full, bool base, Error &error); bool +db_selection_print(Client &client, const DatabaseSelection &selection, + bool full, bool base, + unsigned window_start, unsigned window_end, + Error &error); + +bool PrintUniqueTags(Client &client, unsigned type, uint32_t group_mask, const SongFilter *filter, Error &error); diff --git a/src/db/Helpers.cxx b/src/db/Helpers.cxx index add4bb98e..27bfb7c01 100644 --- a/src/db/Helpers.cxx +++ b/src/db/Helpers.cxx @@ -46,7 +46,7 @@ StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums, for (const auto &item : tag) { switch (item.type) { case TAG_ARTIST: -#if defined(__clang__) || GCC_CHECK_VERSION(4,8) +#if CLANG_OR_GCC_VERSION(4,8) artists.emplace(item.value); #else artists.insert(item.value); @@ -54,7 +54,7 @@ StatsVisitTag(DatabaseStats &stats, StringSet &artists, StringSet &albums, break; case TAG_ALBUM: -#if defined(__clang__) || GCC_CHECK_VERSION(4,8) +#if CLANG_OR_GCC_VERSION(4,8) albums.emplace(item.value); #else albums.insert(item.value); diff --git a/src/db/Registry.cxx b/src/db/Registry.cxx index 5681a9b82..a7d6dc05e 100644 --- a/src/db/Registry.cxx +++ b/src/db/Registry.cxx @@ -28,10 +28,10 @@ const DatabasePlugin *const database_plugins[] = { &simple_db_plugin, -#ifdef HAVE_LIBMPDCLIENT +#ifdef ENABLE_LIBMPDCLIENT &proxy_db_plugin, #endif -#ifdef HAVE_LIBUPNP +#ifdef ENABLE_UPNP &upnp_db_plugin, #endif nullptr diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.cxx b/src/db/plugins/simple/SimpleDatabasePlugin.cxx index d6ad5e91f..00eb078bc 100644 --- a/src/db/plugins/simple/SimpleDatabasePlugin.cxx +++ b/src/db/plugins/simple/SimpleDatabasePlugin.cxx @@ -41,7 +41,7 @@ #include "util/Domain.hxx" #include "Log.hxx" -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB #include "fs/io/GzipOutputStream.hxx" #endif @@ -52,21 +52,21 @@ static constexpr Domain simple_db_domain("simple_db"); inline SimpleDatabase::SimpleDatabase() :Database(simple_db_plugin), path(AllocatedPath::Null()), -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB compress(true), #endif cache_path(AllocatedPath::Null()), prefixed_light_song(nullptr) {} inline SimpleDatabase::SimpleDatabase(AllocatedPath &&_path, -#ifndef HAVE_ZLIB +#ifndef ENABLE_ZLIB gcc_unused #endif bool _compress) :Database(simple_db_plugin), path(std::move(_path)), path_utf8(path.ToUTF8()), -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB compress(_compress), #endif cache_path(AllocatedPath::Null()), @@ -104,7 +104,7 @@ SimpleDatabase::Configure(const config_param ¶m, Error &error) if (path.IsNull() && error.IsDefined()) return false; -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB compress = param.GetBlockValue("compress", compress); #endif @@ -389,7 +389,7 @@ SimpleDatabase::Save(Error &error) OutputStream *os = &fos; -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB GzipOutputStream *gzip = nullptr; if (compress) { gzip = new GzipOutputStream(*os, error); @@ -407,13 +407,13 @@ SimpleDatabase::Save(Error &error) db_save_internal(bos, *root); if (!bos.Flush(error)) { -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB delete gzip; #endif return false; } -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB if (gzip != nullptr) { bool success = gzip->Flush(error); delete gzip; @@ -487,7 +487,7 @@ SimpleDatabase::Mount(const char *local_uri, const char *storage_uri, std::string name(storage_uri); std::replace_if(name.begin(), name.end(), IsUnsafeChar, '_'); -#ifndef HAVE_ZLIB +#ifndef ENABLE_ZLIB constexpr bool compress = false; #endif auto db = new SimpleDatabase(AllocatedPath::Build(cache_path, diff --git a/src/db/plugins/simple/SimpleDatabasePlugin.hxx b/src/db/plugins/simple/SimpleDatabasePlugin.hxx index d82225f8c..dec2a3a7c 100644 --- a/src/db/plugins/simple/SimpleDatabasePlugin.hxx +++ b/src/db/plugins/simple/SimpleDatabasePlugin.hxx @@ -39,7 +39,7 @@ class SimpleDatabase : public Database { AllocatedPath path; std::string path_utf8; -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB bool compress; #endif diff --git a/src/db/plugins/upnp/Directory.cxx b/src/db/plugins/upnp/Directory.cxx index e94a1a997..55f2693ae 100644 --- a/src/db/plugins/upnp/Directory.cxx +++ b/src/db/plugins/upnp/Directory.cxx @@ -89,18 +89,18 @@ ParseDuration(const char *duration) * this. Twonky returns directory names (titles) like 'Artist/Album'. */ gcc_pure -static std::string -titleToPathElt(std::string &&s) +static std::string && +TitleToPathSegment(std::string &&s) { std::replace(s.begin(), s.end(), '/', '_'); - return s; + return std::move(s); } /** * An XML parser which builds directory contents from DIDL lite input. */ class UPnPDirParser final : public CommonExpatParser { - UPnPDirContent &m_dir; + UPnPDirContent &directory; enum { NONE, @@ -120,22 +120,22 @@ class UPnPDirParser final : public CommonExpatParser { */ std::string value; - UPnPDirObject m_tobj; + UPnPDirObject object; TagBuilder tag; public: - UPnPDirParser(UPnPDirContent& dir) - :m_dir(dir), + UPnPDirParser(UPnPDirContent &_directory) + :directory(_directory), state(NONE), tag_type(TAG_NUM_OF_ITEM_TYPES) { - m_tobj.clear(); + object.Clear(); } protected: virtual void StartElement(const XML_Char *name, const XML_Char **attrs) { - if (m_tobj.type != UPnPDirObject::Type::UNKNOWN && + if (object.type != UPnPDirObject::Type::UNKNOWN && tag_type == TAG_NUM_OF_ITEM_TYPES) { tag_type = tag_table_lookup(upnp_tags, name); if (tag_type != TAG_NUM_OF_ITEM_TYPES) @@ -147,31 +147,31 @@ protected: switch (name[0]) { case 'c': if (!strcmp(name, "container")) { - m_tobj.clear(); - m_tobj.type = UPnPDirObject::Type::CONTAINER; + object.Clear(); + object.type = UPnPDirObject::Type::CONTAINER; const char *id = GetAttribute(attrs, "id"); if (id != nullptr) - m_tobj.m_id = id; + object.id = id; const char *pid = GetAttribute(attrs, "parentID"); if (pid != nullptr) - m_tobj.m_pid = pid; + object.parent_id = pid; } break; case 'i': if (!strcmp(name, "item")) { - m_tobj.clear(); - m_tobj.type = UPnPDirObject::Type::ITEM; + object.Clear(); + object.type = UPnPDirObject::Type::ITEM; const char *id = GetAttribute(attrs, "id"); if (id != nullptr) - m_tobj.m_id = id; + object.id = id; const char *pid = GetAttribute(attrs, "parentID"); if (pid != nullptr) - m_tobj.m_pid = pid; + object.parent_id = pid; } break; @@ -197,25 +197,15 @@ protected: } } - bool checkobjok() { - if (m_tobj.m_id.empty() || m_tobj.m_pid.empty() || - m_tobj.name.empty() || - (m_tobj.type == UPnPDirObject::Type::ITEM && - m_tobj.item_class == UPnPDirObject::ItemClass::UNKNOWN)) - return false; - - return true; - } - virtual void EndElement(const XML_Char *name) { if (tag_type != TAG_NUM_OF_ITEM_TYPES) { - assert(m_tobj.type != UPnPDirObject::Type::UNKNOWN); + assert(object.type != UPnPDirObject::Type::UNKNOWN); tag.AddItem(tag_type, value.c_str()); if (tag_type == TAG_TITLE) - m_tobj.name = titleToPathElt(std::move(value)); + object.name = TitleToPathSegment(std::move(value)); value.clear(); tag_type = TAG_NUM_OF_ITEM_TYPES; @@ -223,9 +213,9 @@ protected: } if ((!strcmp(name, "container") || !strcmp(name, "item")) && - checkobjok()) { - tag.Commit(m_tobj.tag); - m_dir.objects.emplace_back(std::move(m_tobj)); + object.Check()) { + tag.Commit(object.tag); + directory.objects.emplace_back(std::move(object)); } state = NONE; @@ -234,7 +224,7 @@ protected: virtual void CharacterData(const XML_Char *s, int len) { if (tag_type != TAG_NUM_OF_ITEM_TYPES) { - assert(m_tobj.type != UPnPDirObject::Type::UNKNOWN); + assert(object.type != UPnPDirObject::Type::UNKNOWN); value.append(s, len); return; @@ -245,11 +235,11 @@ protected: break; case RES: - m_tobj.url.assign(s, len); + object.url.assign(s, len); break; case CLASS: - m_tobj.item_class = ParseItemClass(s, len); + object.item_class = ParseItemClass(s, len); break; } } diff --git a/src/db/plugins/upnp/Object.hxx b/src/db/plugins/upnp/Object.hxx index 16a66c774..6d71c158b 100644 --- a/src/db/plugins/upnp/Object.hxx +++ b/src/db/plugins/upnp/Object.hxx @@ -21,6 +21,7 @@ #define MPD_UPNP_OBJECT_HXX #include "tag/Tag.hxx" +#include "Compiler.h" #include <string> @@ -50,8 +51,16 @@ public: PLAYLIST, }; - std::string m_id; // ObjectId - std::string m_pid; // Parent ObjectId + /** + * ObjectId + */ + std::string id; + + /** + * Parent's ObjectId + */ + std::string parent_id; + std::string url; /** @@ -71,15 +80,21 @@ public: UPnPDirObject &operator=(UPnPDirObject &&) = default; - void clear() - { - m_id.clear(); - m_pid.clear(); + void Clear() { + id.clear(); + parent_id.clear(); url.clear(); type = Type::UNKNOWN; item_class = ItemClass::UNKNOWN; tag.Clear(); } + + gcc_pure + bool Check() const { + return !id.empty() && !parent_id.empty() && !name.empty() && + (type != UPnPDirObject::Type::ITEM || + item_class != UPnPDirObject::ItemClass::UNKNOWN); + } }; #endif /* _UPNPDIRCONTENT_H_X_INCLUDED_ */ diff --git a/src/db/plugins/upnp/UpnpDatabasePlugin.cxx b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx index 9970cdcf3..aea97aa8e 100644 --- a/src/db/plugins/upnp/UpnpDatabasePlugin.cxx +++ b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx @@ -414,7 +414,7 @@ UpnpDatabase::SearchSongs(const ContentDirectoryService &server, // So we return synthetic and ugly paths based on the object id, // which we later have to detect. const std::string path = songPath(server.getFriendlyName(), - dirent.m_id); + dirent.id); if (!visitSong(std::move(dirent), path.c_str(), selection, visit_song, error)) @@ -449,13 +449,13 @@ UpnpDatabase::BuildPath(const ContentDirectoryService &server, std::string &path, Error &error) const { - const char *pid = idirent.m_id.c_str(); + const char *pid = idirent.id.c_str(); path.clear(); UPnPDirObject dirent; while (strcmp(pid, rootid) != 0) { if (!ReadNode(server, pid, dirent, error)) return false; - pid = dirent.m_pid.c_str(); + pid = dirent.parent_id.c_str(); if (path.empty()) path = dirent.name; @@ -511,7 +511,7 @@ UpnpDatabase::Namei(const ContentDirectoryService &server, return false; } - objid = std::move(child->m_id); + objid = std::move(child->id); } } @@ -623,7 +623,7 @@ UpnpDatabase::VisitServer(const ContentDirectoryService &server, } std::string path = songPath(server.getFriendlyName(), - dirent.m_id); + dirent.id); if (!visitSong(std::move(dirent), path.c_str(), selection, visit_song, error)) @@ -642,7 +642,7 @@ UpnpDatabase::VisitServer(const ContentDirectoryService &server, recursion (1-deep) here, which will handle the "add dir" case. */ if (selection.recursive && selection.filter) - return SearchSongs(server, tdirent.m_id.c_str(), selection, + return SearchSongs(server, tdirent.id.c_str(), selection, visit_song, error); const char *const base_uri = selection.uri.empty() @@ -660,7 +660,7 @@ UpnpDatabase::VisitServer(const ContentDirectoryService &server, and loop here, but it's not useful as mpd will only return data to the client when we're done anyway. */ UPnPDirContent dirbuf; - if (!server.readDir(handle, tdirent.m_id.c_str(), dirbuf, + if (!server.readDir(handle, tdirent.id.c_str(), dirbuf, error)) return false; @@ -749,7 +749,7 @@ UpnpDatabase::VisitUniqueTags(const DatabaseSelection &selection, const char *value = dirent.tag.GetValue(tag); if (value != nullptr) { -#if defined(__clang__) || GCC_CHECK_VERSION(4,8) +#if CLANG_OR_GCC_VERSION(4,8) values.emplace(value); #else values.insert(value); diff --git a/src/db/update/InotifySource.hxx b/src/db/update/InotifySource.hxx index 2557680a0..22b05bf4e 100644 --- a/src/db/update/InotifySource.hxx +++ b/src/db/update/InotifySource.hxx @@ -41,8 +41,8 @@ public: } /** - * Creates a new inotify source and registers it in the GLib main - * loop. + * Creates a new inotify source and registers it in the + * #EventLoop. * * @param a callback invoked for events received from the kernel */ diff --git a/src/decoder/DecoderList.cxx b/src/decoder/DecoderList.cxx index cd6881ce2..cd1665e58 100644 --- a/src/decoder/DecoderList.cxx +++ b/src/decoder/DecoderList.cxx @@ -48,44 +48,42 @@ #include <string.h> const struct DecoderPlugin *const decoder_plugins[] = { -#ifdef HAVE_MAD +#ifdef ENABLE_MAD &mad_decoder_plugin, #endif -#ifdef HAVE_MPG123 +#ifdef ENABLE_MPG123 &mpg123_decoder_plugin, #endif #ifdef ENABLE_VORBIS_DECODER &vorbis_decoder_plugin, #endif -#if defined(HAVE_FLAC) +#ifdef ENABLE_FLAC &oggflac_decoder_plugin, -#endif -#ifdef HAVE_FLAC &flac_decoder_plugin, #endif -#ifdef HAVE_OPUS +#ifdef ENABLE_OPUS &opus_decoder_plugin, #endif #ifdef ENABLE_SNDFILE &sndfile_decoder_plugin, #endif -#ifdef HAVE_AUDIOFILE +#ifdef ENABLE_AUDIOFILE &audiofile_decoder_plugin, #endif #ifdef ENABLE_DSD &dsdiff_decoder_plugin, &dsf_decoder_plugin, #endif -#ifdef HAVE_FAAD +#ifdef ENABLE_FAAD &faad_decoder_plugin, #endif -#ifdef HAVE_MPCDEC +#ifdef ENABLE_MPCDEC &mpcdec_decoder_plugin, #endif -#ifdef HAVE_WAVPACK +#ifdef ENABLE_WAVPACK &wavpack_decoder_plugin, #endif -#ifdef HAVE_MODPLUG +#ifdef ENABLE_MODPLUG &modplug_decoder_plugin, #endif #ifdef ENABLE_MIKMOD_DECODER @@ -100,13 +98,13 @@ const struct DecoderPlugin *const decoder_plugins[] = { #ifdef ENABLE_FLUIDSYNTH &fluidsynth_decoder_plugin, #endif -#ifdef HAVE_ADPLUG +#ifdef ENABLE_ADPLUG &adplug_decoder_plugin, #endif -#ifdef HAVE_FFMPEG +#ifdef ENABLE_FFMPEG &ffmpeg_decoder_plugin, #endif -#ifdef HAVE_GME +#ifdef ENABLE_GME &gme_decoder_plugin, #endif &pcm_decoder_plugin, diff --git a/src/decoder/plugins/DsdLib.cxx b/src/decoder/plugins/DsdLib.cxx index 7321261f6..9da08f3eb 100644 --- a/src/decoder/plugins/DsdLib.cxx +++ b/src/decoder/plugins/DsdLib.cxx @@ -34,7 +34,7 @@ #include <string.h> #include <stdlib.h> -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG #include <id3tag.h> #endif @@ -103,7 +103,7 @@ dsdlib_valid_freq(uint32_t samplefreq) } } -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG void dsdlib_tag_id3(InputStream &is, const struct tag_handler *handler, diff --git a/src/decoder/plugins/DsdiffDecoderPlugin.cxx b/src/decoder/plugins/DsdiffDecoderPlugin.cxx index b6c79e11e..33f433330 100644 --- a/src/decoder/plugins/DsdiffDecoderPlugin.cxx +++ b/src/decoder/plugins/DsdiffDecoderPlugin.cxx @@ -244,7 +244,7 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is, /** offset for title tag */ offset_type title_offset = 0; -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG offset_type id3_offset = 0; #endif @@ -269,7 +269,7 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is, chunk_size = chunk_header->GetSize(); title_offset = is.GetOffset(); } -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG /* 'ID3 ' chunk, offspec. Used by sacdextract */ if (chunk_header->id.Equals("ID3 ")) { chunk_size = chunk_header->GetSize(); @@ -283,7 +283,7 @@ dsdiff_read_metadata_extra(Decoder *decoder, InputStream &is, /* done processing chunk headers, process tags if any */ -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG if (id3_offset != 0) { /* a ID3 tag has preference over the other tags, do not process other tags if we have one */ diff --git a/src/decoder/plugins/DsfDecoderPlugin.cxx b/src/decoder/plugins/DsfDecoderPlugin.cxx index 690616d15..b8ae837f7 100644 --- a/src/decoder/plugins/DsfDecoderPlugin.cxx +++ b/src/decoder/plugins/DsfDecoderPlugin.cxx @@ -47,7 +47,7 @@ struct DsfMetaData { unsigned sample_rate, channels; bool bitreverse; offset_type n_blocks; -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG offset_type id3_offset; #endif }; @@ -111,7 +111,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is, if (sizeof(dsf_header) != chunk_size) return false; -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG const offset_type metadata_offset = dsf_header.pmeta.Read(); #endif @@ -174,7 +174,7 @@ dsf_read_metadata(Decoder *decoder, InputStream &is, metadata->n_blocks = data_size / block_size; metadata->channels = channels; metadata->sample_rate = samplefreq; -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG metadata->id3_offset = metadata_offset; #endif /* check bits per sample format, determine if bitreverse is needed */ @@ -352,7 +352,7 @@ dsf_scan_stream(InputStream &is, audio_format.sample_rate); tag_handler_invoke_duration(handler, handler_ctx, songtime); -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG /* Add available tags from the ID3 tag */ dsdlib_tag_id3(is, handler, handler_ctx, metadata.id3_offset); #endif diff --git a/src/decoder/plugins/FfmpegDecoderPlugin.cxx b/src/decoder/plugins/FfmpegDecoderPlugin.cxx index 722f954e2..85e852fb4 100644 --- a/src/decoder/plugins/FfmpegDecoderPlugin.cxx +++ b/src/decoder/plugins/FfmpegDecoderPlugin.cxx @@ -20,14 +20,24 @@ /* necessary because libavutil/common.h uses UINT64_C */ #define __STDC_CONSTANT_MACROS +#include "lib/ffmpeg/Time.hxx" #include "config.h" #include "FfmpegDecoderPlugin.hxx" #include "lib/ffmpeg/Domain.hxx" +#include "lib/ffmpeg/Error.hxx" +#include "lib/ffmpeg/LogError.hxx" +#include "lib/ffmpeg/Init.hxx" +#include "lib/ffmpeg/Buffer.hxx" #include "../DecoderAPI.hxx" #include "FfmpegMetaData.hxx" +#include "FfmpegIo.hxx" +#include "tag/TagBuilder.hxx" #include "tag/TagHandler.hxx" +#include "tag/ReplayGain.hxx" +#include "tag/MixRamp.hxx" #include "input/InputStream.hxx" #include "CheckAudioFormat.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "LogV.hxx" @@ -38,7 +48,6 @@ extern "C" { #include <libavformat/avio.h> #include <libavutil/avutil.h> #include <libavutil/log.h> -#include <libavutil/mathematics.h> #if LIBAVUTIL_VERSION_MAJOR >= 53 #include <libavutil/frame.h> @@ -48,205 +57,50 @@ extern "C" { #include <assert.h> #include <string.h> -/* suppress the ffmpeg compatibility macro */ -#ifdef SampleFormat -#undef SampleFormat -#endif - -static LogLevel -import_ffmpeg_level(int level) -{ - if (level <= AV_LOG_FATAL) - return LogLevel::ERROR; - - if (level <= AV_LOG_WARNING) - return LogLevel::WARNING; - - if (level <= AV_LOG_INFO) - return LogLevel::INFO; - - return LogLevel::DEBUG; -} - -static void -mpd_ffmpeg_log_callback(gcc_unused void *ptr, int level, - const char *fmt, va_list vl) -{ - const AVClass * cls = nullptr; - - if (ptr != nullptr) - cls = *(const AVClass *const*)ptr; - - if (cls != nullptr) { - char domain[64]; - snprintf(domain, sizeof(domain), "%s/%s", - ffmpeg_domain.GetName(), cls->item_name(ptr)); - const Domain d(domain); - LogFormatV(d, import_ffmpeg_level(level), fmt, vl); - } -} - -struct AvioStream { - Decoder *const decoder; - InputStream &input; - - AVIOContext *io; - - unsigned char buffer[8192]; - - AvioStream(Decoder *_decoder, InputStream &_input) - :decoder(_decoder), input(_input), io(nullptr) {} - - ~AvioStream() { - if (io != nullptr) - av_free(io); - } - - bool Open(); -}; - -static int -mpd_ffmpeg_stream_read(void *opaque, uint8_t *buf, int size) -{ - AvioStream *stream = (AvioStream *)opaque; - - return decoder_read(stream->decoder, stream->input, - (void *)buf, size); -} - -static int64_t -mpd_ffmpeg_stream_seek(void *opaque, int64_t pos, int whence) -{ - AvioStream *stream = (AvioStream *)opaque; - - switch (whence) { - case SEEK_SET: - break; - - case SEEK_CUR: - pos += stream->input.GetOffset(); - break; - - case SEEK_END: - if (!stream->input.KnownSize()) - return -1; - - pos += stream->input.GetSize(); - break; - - case AVSEEK_SIZE: - if (!stream->input.KnownSize()) - return -1; - - return stream->input.GetSize(); - - default: - return -1; - } - - if (!stream->input.LockSeek(pos, IgnoreError())) - return -1; - - return stream->input.GetOffset(); -} - -bool -AvioStream::Open() -{ - io = avio_alloc_context(buffer, sizeof(buffer), - false, this, - mpd_ffmpeg_stream_read, nullptr, - input.IsSeekable() - ? mpd_ffmpeg_stream_seek : nullptr); - return io != nullptr; -} - -/** - * API compatibility wrapper for av_open_input_stream() and - * avformat_open_input(). - */ -static int -mpd_ffmpeg_open_input(AVFormatContext **ic_ptr, - AVIOContext *pb, - const char *filename, - AVInputFormat *fmt) +static AVFormatContext * +FfmpegOpenInput(AVIOContext *pb, + const char *filename, + AVInputFormat *fmt) { AVFormatContext *context = avformat_alloc_context(); if (context == nullptr) - return AVERROR(ENOMEM); + return nullptr; context->pb = pb; - *ic_ptr = context; - return avformat_open_input(ic_ptr, filename, fmt, nullptr); + + avformat_open_input(&context, filename, fmt, nullptr); + return context; } static bool ffmpeg_init(gcc_unused const config_param ¶m) { - av_log_set_callback(mpd_ffmpeg_log_callback); - - av_register_all(); + FfmpegInit(); return true; } +gcc_pure static int -ffmpeg_find_audio_stream(const AVFormatContext *format_context) +ffmpeg_find_audio_stream(const AVFormatContext &format_context) { - for (unsigned i = 0; i < format_context->nb_streams; ++i) - if (format_context->streams[i]->codec->codec_type == + for (unsigned i = 0; i < format_context.nb_streams; ++i) + if (format_context.streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) return i; return -1; } -gcc_const -static double -time_from_ffmpeg(int64_t t, const AVRational time_base) -{ - assert(t != (int64_t)AV_NOPTS_VALUE); - - return (double)av_rescale_q(t, time_base, (AVRational){1, 1024}) - / (double)1024; -} - -template<typename Ratio> -static constexpr AVRational -RatioToAVRational() -{ - return { Ratio::num, Ratio::den }; -} - -gcc_const -static int64_t -time_to_ffmpeg(SongTime t, const AVRational time_base) -{ - return av_rescale_q(t.count(), - RatioToAVRational<SongTime::period>(), - time_base); -} - -/** - * Replace #AV_NOPTS_VALUE with the given fallback. - */ -static constexpr int64_t -timestamp_fallback(int64_t t, int64_t fallback) -{ - return gcc_likely(t != int64_t(AV_NOPTS_VALUE)) - ? t - : fallback; -} - /** * Accessor for AVStream::start_time that replaces AV_NOPTS_VALUE with * zero. We can't use AV_NOPTS_VALUE in calculations, and we simply * assume that the stream's start time is zero, which appears to be * the best way out of that situation. */ -static int64_t +static constexpr int64_t start_time_fallback(const AVStream &stream) { - return timestamp_fallback(stream.start_time, 0); + return FfmpegTimestampFallback(stream.start_time, 0); } static void @@ -264,99 +118,103 @@ copy_interleave_frame2(uint8_t *dest, uint8_t **src, } /** - * Copy PCM data from a AVFrame to an interleaved buffer. + * Copy PCM data from a non-empty AVFrame to an interleaved buffer. */ -static int -copy_interleave_frame(const AVCodecContext *codec_context, - const AVFrame *frame, - uint8_t **output_buffer, - uint8_t **global_buffer, int *global_buffer_size) +static ConstBuffer<void> +copy_interleave_frame(const AVCodecContext &codec_context, + const AVFrame &frame, + FfmpegBuffer &global_buffer, + Error &error) { + assert(frame.nb_samples > 0); + int plane_size; const int data_size = av_samples_get_buffer_size(&plane_size, - codec_context->channels, - frame->nb_samples, - codec_context->sample_fmt, 1); - if (data_size <= 0) - return data_size; - - if (av_sample_fmt_is_planar(codec_context->sample_fmt) && - codec_context->channels > 1) { - if(*global_buffer_size < data_size) { - av_freep(global_buffer); - - *global_buffer = (uint8_t*)av_malloc(data_size); - - if (!*global_buffer) - /* Not enough memory - shouldn't happen */ - return AVERROR(ENOMEM); - *global_buffer_size = data_size; + codec_context.channels, + frame.nb_samples, + codec_context.sample_fmt, 1); + assert(data_size != 0); + if (data_size < 0) { + SetFfmpegError(error, data_size); + return 0; + } + + void *output_buffer; + if (av_sample_fmt_is_planar(codec_context.sample_fmt) && + codec_context.channels > 1) { + output_buffer = global_buffer.GetT<uint8_t>(data_size); + if (output_buffer == nullptr) { + /* Not enough memory - shouldn't happen */ + error.SetErrno(ENOMEM); + return 0; } - *output_buffer = *global_buffer; - copy_interleave_frame2(*output_buffer, frame->extended_data, - frame->nb_samples, - codec_context->channels, - av_get_bytes_per_sample(codec_context->sample_fmt)); + + copy_interleave_frame2((uint8_t *)output_buffer, + frame.extended_data, + frame.nb_samples, + codec_context.channels, + av_get_bytes_per_sample(codec_context.sample_fmt)); } else { - *output_buffer = frame->extended_data[0]; + output_buffer = frame.extended_data[0]; } - return data_size; + return { output_buffer, (size_t)data_size }; } +/** + * Decode an #AVPacket and send the resulting PCM data to the decoder + * API. + */ static DecoderCommand ffmpeg_send_packet(Decoder &decoder, InputStream &is, - const AVPacket *packet, - AVCodecContext *codec_context, - const AVStream *stream, - AVFrame *frame, - uint8_t **buffer, int *buffer_size) + AVPacket packet, + AVCodecContext &codec_context, + const AVStream &stream, + AVFrame &frame, + FfmpegBuffer &buffer) { - if (packet->pts >= 0 && packet->pts != (int64_t)AV_NOPTS_VALUE) { - auto start = start_time_fallback(*stream); - if (packet->pts >= start) + if (packet.pts >= 0 && packet.pts != (int64_t)AV_NOPTS_VALUE) { + auto start = start_time_fallback(stream); + if (packet.pts >= start) decoder_timestamp(decoder, - time_from_ffmpeg(packet->pts - start, - stream->time_base)); + FfmpegTimeToDouble(packet.pts - start, + stream.time_base)); } - AVPacket packet2 = *packet; - - uint8_t *output_buffer; + Error error; DecoderCommand cmd = DecoderCommand::NONE; - while (packet2.size > 0 && cmd == DecoderCommand::NONE) { - int audio_size = 0; + while (packet.size > 0 && cmd == DecoderCommand::NONE) { int got_frame = 0; - int len = avcodec_decode_audio4(codec_context, - frame, &got_frame, - &packet2); - if (len >= 0 && got_frame) { - audio_size = copy_interleave_frame(codec_context, - frame, - &output_buffer, - buffer, buffer_size); - if (audio_size < 0) - len = audio_size; - } - + int len = avcodec_decode_audio4(&codec_context, + &frame, &got_frame, + &packet); if (len < 0) { /* if error, we skip the frame */ - LogDefault(ffmpeg_domain, - "decoding failed, frame skipped"); + LogFfmpegError(len, "decoding failed, frame skipped"); break; } - packet2.data += len; - packet2.size -= len; + packet.data += len; + packet.size -= len; - if (audio_size <= 0) + if (!got_frame || frame.nb_samples <= 0) continue; + auto output_buffer = + copy_interleave_frame(codec_context, frame, + buffer, error); + if (output_buffer.IsNull()) { + /* this must be a serious error, + e.g. OOM */ + LogError(error); + return DecoderCommand::STOP; + } + cmd = decoder_data(decoder, is, - output_buffer, audio_size, - codec_context->bit_rate / 1000); + output_buffer.data, output_buffer.size, + codec_context.bit_rate / 1000); } return cmd; } @@ -399,10 +257,8 @@ ffmpeg_sample_format(enum AVSampleFormat sample_fmt) static AVInputFormat * ffmpeg_probe(Decoder *decoder, InputStream &is) { - enum { - BUFFER_SIZE = 16384, - PADDING = 16, - }; + constexpr size_t BUFFER_SIZE = 16384; + constexpr size_t PADDING = 16; unsigned char buffer[BUFFER_SIZE]; size_t nbytes = decoder_read(decoder, is, buffer, BUFFER_SIZE); @@ -442,85 +298,163 @@ ffmpeg_probe(Decoder *decoder, InputStream &is) } static void -ffmpeg_decode(Decoder &decoder, InputStream &input) +FfmpegParseMetaData(AVDictionary &dict, ReplayGainInfo &rg, MixRampInfo &mr) { - AVInputFormat *input_format = ffmpeg_probe(&decoder, input); - if (input_format == nullptr) - return; + AVDictionaryEntry *i = nullptr; - FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)", - input_format->name, input_format->long_name); + while ((i = av_dict_get(&dict, "", i, + AV_DICT_IGNORE_SUFFIX)) != nullptr) { + const char *name = i->key; + const char *value = i->value; - AvioStream stream(&decoder, input); - if (!stream.Open()) { - LogError(ffmpeg_domain, "Failed to open stream"); - return; + if (!ParseReplayGainTag(rg, name, value)) + ParseMixRampTag(mr, name, value); } +} - //ffmpeg works with ours "fileops" helper - AVFormatContext *format_context = nullptr; - if (mpd_ffmpeg_open_input(&format_context, stream.io, - input.GetURI(), - input_format) != 0) { - LogError(ffmpeg_domain, "Open failed"); +static void +FfmpegParseMetaData(const AVStream &stream, + ReplayGainInfo &rg, MixRampInfo &mr) +{ + FfmpegParseMetaData(*stream.metadata, rg, mr); +} + +static void +FfmpegParseMetaData(const AVFormatContext &format_context, int audio_stream, + ReplayGainInfo &rg, MixRampInfo &mr) +{ + assert(audio_stream >= 0); + + FfmpegParseMetaData(*format_context.metadata, rg, mr); + FfmpegParseMetaData(*format_context.streams[audio_stream], + rg, mr); +} + +static void +FfmpegParseMetaData(Decoder &decoder, + const AVFormatContext &format_context, int audio_stream) +{ + ReplayGainInfo rg; + rg.Clear(); + + MixRampInfo mr; + mr.Clear(); + + FfmpegParseMetaData(format_context, audio_stream, rg, mr); + + if (rg.IsDefined()) + decoder_replay_gain(decoder, &rg); + + if (mr.IsDefined()) + decoder_mixramp(decoder, std::move(mr)); +} + +static void +FfmpegScanMetadata(const AVStream &stream, + const tag_handler &handler, void *handler_ctx) +{ + FfmpegScanDictionary(stream.metadata, &handler, handler_ctx); +} + +static void +FfmpegScanMetadata(const AVFormatContext &format_context, int audio_stream, + const tag_handler &handler, void *handler_ctx) +{ + assert(audio_stream >= 0); + + FfmpegScanDictionary(format_context.metadata, &handler, handler_ctx); + FfmpegScanMetadata(*format_context.streams[audio_stream], + handler, handler_ctx); +} + +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(56, 1, 0) + +static void +FfmpegScanTag(const AVFormatContext &format_context, int audio_stream, + TagBuilder &tag) +{ + FfmpegScanMetadata(format_context, audio_stream, + full_tag_handler, &tag); +} + +/** + * Check if a new stream tag was received and pass it to + * decoder_tag(). + */ +static void +FfmpegCheckTag(Decoder &decoder, InputStream &is, + AVFormatContext &format_context, int audio_stream) +{ + AVStream &stream = *format_context.streams[audio_stream]; + if ((stream.event_flags & AVSTREAM_EVENT_FLAG_METADATA_UPDATED) == 0) + /* no new metadata */ return; - } + /* clear the flag */ + stream.event_flags &= ~AVSTREAM_EVENT_FLAG_METADATA_UPDATED; + + TagBuilder tag; + FfmpegScanTag(format_context, audio_stream, tag); + if (!tag.IsEmpty()) + decoder_tag(decoder, is, tag.Commit()); +} + +#endif + +static void +FfmpegDecode(Decoder &decoder, InputStream &input, + AVFormatContext &format_context) +{ const int find_result = - avformat_find_stream_info(format_context, nullptr); + avformat_find_stream_info(&format_context, nullptr); if (find_result < 0) { LogError(ffmpeg_domain, "Couldn't find stream info"); - avformat_close_input(&format_context); return; } int audio_stream = ffmpeg_find_audio_stream(format_context); if (audio_stream == -1) { LogError(ffmpeg_domain, "No audio stream inside"); - avformat_close_input(&format_context); return; } - AVStream *av_stream = format_context->streams[audio_stream]; + AVStream &av_stream = *format_context.streams[audio_stream]; - AVCodecContext *codec_context = av_stream->codec; + AVCodecContext &codec_context = *av_stream.codec; #if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 25, 0) const AVCodecDescriptor *codec_descriptor = - avcodec_descriptor_get(codec_context->codec_id); + avcodec_descriptor_get(codec_context.codec_id); if (codec_descriptor != nullptr) FormatDebug(ffmpeg_domain, "codec '%s'", codec_descriptor->name); #else - if (codec_context->codec_name[0] != 0) + if (codec_context.codec_name[0] != 0) FormatDebug(ffmpeg_domain, "codec '%s'", - codec_context->codec_name); + codec_context.codec_name); #endif - AVCodec *codec = avcodec_find_decoder(codec_context->codec_id); + AVCodec *codec = avcodec_find_decoder(codec_context.codec_id); if (!codec) { LogError(ffmpeg_domain, "Unsupported audio codec"); - avformat_close_input(&format_context); return; } const SampleFormat sample_format = - ffmpeg_sample_format(codec_context->sample_fmt); + ffmpeg_sample_format(codec_context.sample_fmt); if (sample_format == SampleFormat::UNDEFINED) { // (error message already done by ffmpeg_sample_format()) - avformat_close_input(&format_context); return; } Error error; AudioFormat audio_format; if (!audio_format_init_checked(audio_format, - codec_context->sample_rate, + codec_context.sample_rate, sample_format, - codec_context->channels, error)) { + codec_context.channels, error)) { LogError(error); - avformat_close_input(&format_context); return; } @@ -529,22 +463,20 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) values into AVCodecContext.channels - a change that will be reverted later by avcodec_decode_audio3() */ - const int open_result = avcodec_open2(codec_context, codec, nullptr); + const int open_result = avcodec_open2(&codec_context, codec, nullptr); if (open_result < 0) { LogError(ffmpeg_domain, "Could not open codec"); - avformat_close_input(&format_context); return; } const SignedSongTime total_time = - format_context->duration != (int64_t)AV_NOPTS_VALUE - ? SignedSongTime::FromScale<uint64_t>(format_context->duration, - AV_TIME_BASE) - : SignedSongTime::Negative(); + FromFfmpegTimeChecked(av_stream.duration, av_stream.time_base); decoder_initialized(decoder, audio_format, input.IsSeekable(), total_time); + FfmpegParseMetaData(decoder, format_context, audio_stream); + #if LIBAVUTIL_VERSION_MAJOR >= 53 AVFrame *frame = av_frame_alloc(); #else @@ -552,26 +484,28 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) #endif if (!frame) { LogError(ffmpeg_domain, "Could not allocate frame"); - avformat_close_input(&format_context); return; } - uint8_t *interleaved_buffer = nullptr; - int interleaved_buffer_size = 0; + FfmpegBuffer interleaved_buffer; DecoderCommand cmd; do { AVPacket packet; - if (av_read_frame(format_context, &packet) < 0) + if (av_read_frame(&format_context, &packet) < 0) /* end of file */ break; +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(56, 1, 0) + FfmpegCheckTag(decoder, input, format_context, audio_stream); +#endif + if (packet.stream_index == audio_stream) cmd = ffmpeg_send_packet(decoder, input, - &packet, codec_context, + packet, codec_context, av_stream, - frame, - &interleaved_buffer, &interleaved_buffer_size); + *frame, + interleaved_buffer); else cmd = decoder_get_command(decoder); @@ -579,15 +513,15 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) if (cmd == DecoderCommand::SEEK) { int64_t where = - time_to_ffmpeg(decoder_seek_time(decoder), - av_stream->time_base) + - start_time_fallback(*av_stream); + ToFfmpegTime(decoder_seek_time(decoder), + av_stream.time_base) + + start_time_fallback(av_stream); - if (av_seek_frame(format_context, audio_stream, where, + if (av_seek_frame(&format_context, audio_stream, where, AVSEEK_FLAG_ANY) < 0) decoder_seek_error(decoder); else { - avcodec_flush_buffers(codec_context); + avcodec_flush_buffers(&codec_context); decoder_command_finished(decoder); } } @@ -598,15 +532,63 @@ ffmpeg_decode(Decoder &decoder, InputStream &input) #elif LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(54, 28, 0) avcodec_free_frame(&frame); #else - av_freep(&frame); + av_free(frame); #endif - av_freep(&interleaved_buffer); - avcodec_close(codec_context); + avcodec_close(&codec_context); +} + +static void +ffmpeg_decode(Decoder &decoder, InputStream &input) +{ + AVInputFormat *input_format = ffmpeg_probe(&decoder, input); + if (input_format == nullptr) + return; + + FormatDebug(ffmpeg_domain, "detected input format '%s' (%s)", + input_format->name, input_format->long_name); + + AvioStream stream(&decoder, input); + if (!stream.Open()) { + LogError(ffmpeg_domain, "Failed to open stream"); + return; + } + + AVFormatContext *format_context = + FfmpegOpenInput(stream.io, input.GetURI(), input_format); + if (format_context == nullptr) { + LogError(ffmpeg_domain, "Open failed"); + return; + } + + FfmpegDecode(decoder, input, *format_context); avformat_close_input(&format_context); } -//no tag reading in ffmpeg, check if playable +static bool +FfmpegScanStream(AVFormatContext &format_context, + const struct tag_handler &handler, void *handler_ctx) +{ + const int find_result = + avformat_find_stream_info(&format_context, nullptr); + if (find_result < 0) + return false; + + const int audio_stream = ffmpeg_find_audio_stream(format_context); + if (audio_stream < 0) + return false; + + const AVStream &stream = *format_context.streams[audio_stream]; + if (stream.duration != (int64_t)AV_NOPTS_VALUE) + tag_handler_invoke_duration(&handler, handler_ctx, + FromFfmpegTime(stream.duration, + stream.time_base)); + + FfmpegScanMetadata(format_context, audio_stream, handler, handler_ctx); + + return true; +} + static bool ffmpeg_scan_stream(InputStream &is, const struct tag_handler *handler, void *handler_ctx) @@ -619,33 +601,14 @@ ffmpeg_scan_stream(InputStream &is, if (!stream.Open()) return false; - AVFormatContext *f = nullptr; - if (mpd_ffmpeg_open_input(&f, stream.io, is.GetURI(), - input_format) != 0) + AVFormatContext *f = + FfmpegOpenInput(stream.io, is.GetURI(), input_format); + if (f == nullptr) return false; - const int find_result = - avformat_find_stream_info(f, nullptr); - if (find_result < 0) { - avformat_close_input(&f); - return false; - } - - if (f->duration != (int64_t)AV_NOPTS_VALUE) { - const auto duration = - SongTime::FromScale<uint64_t>(f->duration, - AV_TIME_BASE); - tag_handler_invoke_duration(handler, handler_ctx, duration); - } - - ffmpeg_scan_dictionary(f->metadata, handler, handler_ctx); - int idx = ffmpeg_find_audio_stream(f); - if (idx >= 0) - ffmpeg_scan_dictionary(f->streams[idx]->metadata, - handler, handler_ctx); - + bool result = FfmpegScanStream(*f, *handler, handler_ctx); avformat_close_input(&f); - return true; + return result; } /** diff --git a/src/decoder/plugins/FfmpegIo.cxx b/src/decoder/plugins/FfmpegIo.cxx new file mode 100644 index 000000000..d47e575bc --- /dev/null +++ b/src/decoder/plugins/FfmpegIo.cxx @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "FfmpegIo.hxx" +#include "../DecoderAPI.hxx" +#include "input/InputStream.hxx" +#include "util/Error.hxx" + +AvioStream::~AvioStream() +{ + av_free(io); +} + +inline int +AvioStream::Read(void *dest, int size) +{ + return decoder_read(decoder, input, dest, size); +} + +inline int64_t +AvioStream::Seek(int64_t pos, int whence) +{ + switch (whence) { + case SEEK_SET: + break; + + case SEEK_CUR: + pos += input.GetOffset(); + break; + + case SEEK_END: + if (!input.KnownSize()) + return -1; + + pos += input.GetSize(); + break; + + case AVSEEK_SIZE: + if (!input.KnownSize()) + return -1; + + return input.GetSize(); + + default: + return -1; + } + + if (!input.LockSeek(pos, IgnoreError())) + return -1; + + return input.GetOffset(); +} + +int +AvioStream::_Read(void *opaque, uint8_t *buf, int size) +{ + AvioStream &stream = *(AvioStream *)opaque; + + return stream.Read(buf, size); +} + +int64_t +AvioStream::_Seek(void *opaque, int64_t pos, int whence) +{ + AvioStream &stream = *(AvioStream *)opaque; + + return stream.Seek(pos, whence); +} + +bool +AvioStream::Open() +{ + io = avio_alloc_context(buffer, sizeof(buffer), + false, this, + _Read, nullptr, + input.IsSeekable() ? _Seek : nullptr); + return io != nullptr; +} diff --git a/src/decoder/plugins/FfmpegIo.hxx b/src/decoder/plugins/FfmpegIo.hxx new file mode 100644 index 000000000..bbd3a5b62 --- /dev/null +++ b/src/decoder/plugins/FfmpegIo.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_IO_HXX +#define MPD_FFMPEG_IO_HXX + +#include "check.h" + +extern "C" { +#include "libavformat/avio.h" +} + +#include <stdint.h> + +class InputStream; +struct Decoder; + +struct AvioStream { + Decoder *const decoder; + InputStream &input; + + AVIOContext *io; + + uint8_t buffer[8192]; + + AvioStream(Decoder *_decoder, InputStream &_input) + :decoder(_decoder), input(_input), io(nullptr) {} + + ~AvioStream(); + + bool Open(); + +private: + int Read(void *buffer, int size); + int64_t Seek(int64_t pos, int whence); + + static int _Read(void *opaque, uint8_t *buf, int size); + static int64_t _Seek(void *opaque, int64_t pos, int whence); +}; + +#endif diff --git a/src/decoder/plugins/FfmpegMetaData.cxx b/src/decoder/plugins/FfmpegMetaData.cxx index a39466945..b8f7a12a3 100644 --- a/src/decoder/plugins/FfmpegMetaData.cxx +++ b/src/decoder/plugins/FfmpegMetaData.cxx @@ -25,7 +25,11 @@ #include "tag/TagTable.hxx" #include "tag/TagHandler.hxx" -static const struct tag_table ffmpeg_tags[] = { +extern "C" { +#include <libavutil/dict.h> +} + +static constexpr struct tag_table ffmpeg_tags[] = { { "year", TAG_DATE }, { "author-sort", TAG_ARTIST_SORT }, { "album_artist", TAG_ALBUM_ARTIST }, @@ -36,9 +40,9 @@ static const struct tag_table ffmpeg_tags[] = { }; static void -ffmpeg_copy_metadata(TagType type, - AVDictionary *m, const char *name, - const struct tag_handler *handler, void *handler_ctx) +FfmpegScanTag(TagType type, + AVDictionary *m, const char *name, + const struct tag_handler *handler, void *handler_ctx) { AVDictionaryEntry *mt = nullptr; @@ -48,8 +52,8 @@ ffmpeg_copy_metadata(TagType type, } static void -ffmpeg_scan_pairs(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx) +FfmpegScanPairs(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) { AVDictionaryEntry *i = nullptr; @@ -59,18 +63,20 @@ ffmpeg_scan_pairs(AVDictionary *dict, } void -ffmpeg_scan_dictionary(AVDictionary *dict, - const struct tag_handler *handler, void *handler_ctx) +FfmpegScanDictionary(AVDictionary *dict, + const struct tag_handler *handler, void *handler_ctx) { - for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) - ffmpeg_copy_metadata(TagType(i), dict, tag_item_names[i], - handler, handler_ctx); + if (handler->tag != nullptr) { + for (unsigned i = 0; i < TAG_NUM_OF_ITEM_TYPES; ++i) + FfmpegScanTag(TagType(i), dict, tag_item_names[i], + handler, handler_ctx); - for (const struct tag_table *i = ffmpeg_tags; - i->name != nullptr; ++i) - ffmpeg_copy_metadata(i->type, dict, i->name, - handler, handler_ctx); + for (const struct tag_table *i = ffmpeg_tags; + i->name != nullptr; ++i) + FfmpegScanTag(i->type, dict, i->name, + handler, handler_ctx); + } if (handler->pair != nullptr) - ffmpeg_scan_pairs(dict, handler, handler_ctx); + FfmpegScanPairs(dict, handler, handler_ctx); } diff --git a/src/decoder/plugins/FfmpegMetaData.hxx b/src/decoder/plugins/FfmpegMetaData.hxx index 5eb41db68..4b77adf5d 100644 --- a/src/decoder/plugins/FfmpegMetaData.hxx +++ b/src/decoder/plugins/FfmpegMetaData.hxx @@ -20,19 +20,11 @@ #ifndef MPD_FFMPEG_METADATA_HXX #define MPD_FFMPEG_METADATA_HXX -extern "C" { -#include <libavutil/dict.h> -} - -/* suppress the ffmpeg compatibility macro */ -#ifdef SampleFormat -#undef SampleFormat -#endif - +struct AVDictionary; struct tag_handler; void -ffmpeg_scan_dictionary(AVDictionary *dict, - const tag_handler *handler, void *handler_ctx); +FfmpegScanDictionary(AVDictionary *dict, + const tag_handler *handler, void *handler_ctx); #endif diff --git a/src/decoder/plugins/FlacMetadata.cxx b/src/decoder/plugins/FlacMetadata.cxx index 03e276dce..86c6da04b 100644 --- a/src/decoder/plugins/FlacMetadata.cxx +++ b/src/decoder/plugins/FlacMetadata.cxx @@ -30,7 +30,7 @@ #include "tag/MixRamp.hxx" #include "ReplayGainInfo.hxx" #include "util/ASCII.hxx" -#include "util/SplitString.hxx" +#include "util/DivideString.hxx" bool flac_parse_replay_gain(ReplayGainInfo &rgi, @@ -97,7 +97,7 @@ flac_scan_comment(const FLAC__StreamMetadata_VorbisComment_Entry *entry, { if (handler->pair != nullptr) { const char *comment = (const char *)entry->entry; - const SplitString split(comment, '='); + const DivideString split(comment, '='); if (split.IsDefined() && !split.IsEmpty()) tag_handler_invoke_pair(handler, handler_ctx, split.GetFirst(), diff --git a/src/decoder/plugins/FluidsynthDecoderPlugin.cxx b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx index f19ac5bf4..55247549b 100644 --- a/src/decoder/plugins/FluidsynthDecoderPlugin.cxx +++ b/src/decoder/plugins/FluidsynthDecoderPlugin.cxx @@ -35,7 +35,7 @@ static unsigned sample_rate; static const char *soundfont_path; /** - * Convert a fluidsynth log level to a GLib log level. + * Convert a fluidsynth log level to a MPD log level. */ static LogLevel fluidsynth_level_to_mpd(enum fluid_log_level level) @@ -61,7 +61,7 @@ fluidsynth_level_to_mpd(enum fluid_log_level level) } /** - * The fluidsynth logging callback. It forwards messages to the GLib + * The fluidsynth logging callback. It forwards messages to the MPD * logging library. */ static void diff --git a/src/decoder/plugins/GmeDecoderPlugin.cxx b/src/decoder/plugins/GmeDecoderPlugin.cxx index cc6ce5e5d..8d3116ae1 100644 --- a/src/decoder/plugins/GmeDecoderPlugin.cxx +++ b/src/decoder/plugins/GmeDecoderPlugin.cxx @@ -23,6 +23,7 @@ #include "CheckAudioFormat.hxx" #include "tag/TagHandler.hxx" #include "fs/Path.hxx" +#include "fs/AllocatedPath.hxx" #include "util/Alloc.hxx" #include "util/FormatString.hxx" #include "util/UriUtil.hxx" @@ -30,7 +31,6 @@ #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> #include <assert.h> #include <stdlib.h> #include <string.h> @@ -47,65 +47,42 @@ static constexpr unsigned GME_BUFFER_FRAMES = 2048; static constexpr unsigned GME_BUFFER_SAMPLES = GME_BUFFER_FRAMES * GME_CHANNELS; -/** - * returns the file path stripped of any /tune_xxx.* subtune - * suffix - */ -static char * -get_container_name(Path path_fs) +struct GmeContainerPath { + AllocatedPath path; + unsigned track; +}; + +gcc_pure +static unsigned +ParseSubtuneName(const char *base) { - const char *subtune_suffix = uri_get_suffix(path_fs.c_str()); - char *path_container = xstrdup(path_fs.c_str()); - - char pat[64]; - snprintf(pat, sizeof(pat), "%s%s", - "*/" SUBTUNE_PREFIX "???.", - subtune_suffix); - GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); - if (!g_pattern_match(path_with_subtune, - strlen(path_container), path_container, nullptr)) { - g_pattern_spec_free(path_with_subtune); - return path_container; - } + if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0) + return 0; - char *ptr = g_strrstr(path_container, "/" SUBTUNE_PREFIX); - if (ptr != nullptr) - *ptr='\0'; + base += sizeof(SUBTUNE_PREFIX) - 1; - g_pattern_spec_free(path_with_subtune); - return path_container; + char *endptr; + auto track = strtoul(base, &endptr, 10); + if (endptr == base || *endptr != '.') + return 0; + + return track; } /** - * returns tune number from file.nsf/tune_xxx.* style path or 0 if no subtune - * is appended. + * returns the file path stripped of any /tune_xxx.* subtune suffix + * and the track number (or 0 if no "tune_xxx" suffix is present). */ -static int -get_song_num(Path path_fs) +static GmeContainerPath +ParseContainerPath(Path path_fs) { - const char *subtune_suffix = uri_get_suffix(path_fs.c_str()); + const Path base = path_fs.GetBase(); + unsigned track; + if (base.IsNull() || + (track = ParseSubtuneName(base.c_str())) < 1) + return { AllocatedPath(path_fs), 0 }; - char pat[64]; - snprintf(pat, sizeof(pat), "%s%s", - "*/" SUBTUNE_PREFIX "???.", - subtune_suffix); - GPatternSpec *path_with_subtune = g_pattern_spec_new(pat); - - if (g_pattern_match(path_with_subtune, - path_fs.length(), path_fs.data(), nullptr)) { - char *sub = g_strrstr(path_fs.c_str(), "/" SUBTUNE_PREFIX); - g_pattern_spec_free(path_with_subtune); - if (!sub) - return 0; - - sub += strlen("/" SUBTUNE_PREFIX); - int song_num = strtol(sub, nullptr, 10); - - return song_num - 1; - } else { - g_pattern_spec_free(path_with_subtune); - return 0; - } + return { path_fs.GetDirectoryName(), track - 1 }; } static char * @@ -136,20 +113,18 @@ gme_container_scan(Path path_fs, const unsigned int tnum) static void gme_file_decode(Decoder &decoder, Path path_fs) { - char *path_container = get_container_name(path_fs); + const auto container = ParseContainerPath(path_fs); Music_Emu *emu; const char *gme_err = - gme_open_file(path_container, &emu, GME_SAMPLE_RATE); - free(path_container); + gme_open_file(container.path.c_str(), &emu, GME_SAMPLE_RATE); if (gme_err != nullptr) { LogWarning(gme_domain, gme_err); return; } gme_info_t *ti; - const int song_num = get_song_num(path_fs); - gme_err = gme_track_info(emu, &ti, song_num); + gme_err = gme_track_info(emu, &ti, container.track); if (gme_err != nullptr) { LogWarning(gme_domain, gme_err); gme_delete(emu); @@ -175,7 +150,7 @@ gme_file_decode(Decoder &decoder, Path path_fs) decoder_initialized(decoder, audio_format, true, song_len); - gme_err = gme_start_track(emu, song_num); + gme_err = gme_start_track(emu, container.track); if (gme_err != nullptr) LogWarning(gme_domain, gme_err); @@ -209,72 +184,85 @@ gme_file_decode(Decoder &decoder, Path path_fs) gme_delete(emu); } -static bool -gme_scan_file(Path path_fs, - const struct tag_handler *handler, void *handler_ctx) +static void +ScanGmeInfo(const gme_info_t &info, unsigned song_num, int track_count, + const struct tag_handler *handler, void *handler_ctx) { - char *path_container = get_container_name(path_fs); - - Music_Emu *emu; - const char *gme_err = - gme_open_file(path_container, &emu, GME_SAMPLE_RATE); - free(path_container); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - return false; - } - - const int song_num = get_song_num(path_fs); - - gme_info_t *ti; - gme_err = gme_track_info(emu, &ti, song_num); - if (gme_err != nullptr) { - LogWarning(gme_domain, gme_err); - gme_delete(emu); - return false; - } - - assert(ti != nullptr); - - if (ti->length > 0) + if (info.length > 0) tag_handler_invoke_duration(handler, handler_ctx, - SongTime::FromMS(ti->length)); + SongTime::FromMS(info.length)); - if (ti->song != nullptr) { - if (gme_track_count(emu) > 1) { + if (info.song != nullptr) { + if (track_count > 1) { /* start numbering subtunes from 1 */ char tag_title[1024]; snprintf(tag_title, sizeof(tag_title), - "%s (%d/%d)", - ti->song, song_num + 1, - gme_track_count(emu)); + "%s (%u/%d)", + info.song, song_num + 1, + track_count); tag_handler_invoke_tag(handler, handler_ctx, TAG_TITLE, tag_title); } else tag_handler_invoke_tag(handler, handler_ctx, - TAG_TITLE, ti->song); + TAG_TITLE, info.song); } - if (ti->author != nullptr) + if (info.author != nullptr) tag_handler_invoke_tag(handler, handler_ctx, - TAG_ARTIST, ti->author); + TAG_ARTIST, info.author); - if (ti->game != nullptr) + if (info.game != nullptr) tag_handler_invoke_tag(handler, handler_ctx, - TAG_ALBUM, ti->game); + TAG_ALBUM, info.game); - if (ti->comment != nullptr) + if (info.comment != nullptr) tag_handler_invoke_tag(handler, handler_ctx, - TAG_COMMENT, ti->comment); + TAG_COMMENT, info.comment); - if (ti->copyright != nullptr) + if (info.copyright != nullptr) tag_handler_invoke_tag(handler, handler_ctx, - TAG_DATE, ti->copyright); + TAG_DATE, info.copyright); +} + +static bool +ScanMusicEmu(Music_Emu *emu, unsigned song_num, + const struct tag_handler *handler, void *handler_ctx) +{ + gme_info_t *ti; + const char *gme_err = gme_track_info(emu, &ti, song_num); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return false; + } + + assert(ti != nullptr); + + ScanGmeInfo(*ti, song_num, gme_track_count(emu), + handler, handler_ctx); gme_free_info(ti); + return true; +} + +static bool +gme_scan_file(Path path_fs, + const struct tag_handler *handler, void *handler_ctx) +{ + const auto container = ParseContainerPath(path_fs); + + Music_Emu *emu; + const char *gme_err = + gme_open_file(container.path.c_str(), &emu, GME_SAMPLE_RATE); + if (gme_err != nullptr) { + LogWarning(gme_domain, gme_err); + return false; + } + + const bool result = ScanMusicEmu(emu, container.track, handler, handler_ctx); + gme_delete(emu); - return true; + return result; } static const char *const gme_suffixes[] = { diff --git a/src/decoder/plugins/MadDecoderPlugin.cxx b/src/decoder/plugins/MadDecoderPlugin.cxx index de6c9b127..669b57817 100644 --- a/src/decoder/plugins/MadDecoderPlugin.cxx +++ b/src/decoder/plugins/MadDecoderPlugin.cxx @@ -36,7 +36,7 @@ #include <mad.h> -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG #include <id3tag.h> #endif @@ -251,7 +251,7 @@ MadDecoder::FillBuffer() return true; } -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG static bool parse_id3_replay_gain_info(ReplayGainInfo &rgi, struct id3_tag *tag) @@ -285,7 +285,7 @@ parse_id3_replay_gain_info(ReplayGainInfo &rgi, } #endif -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG gcc_pure static MixRampInfo parse_id3_mixramp(struct id3_tag *tag) @@ -317,7 +317,7 @@ parse_id3_mixramp(struct id3_tag *tag) inline void MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) { -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG id3_byte_t *allocated = nullptr; const id3_length_t count = stream.bufend - stream.this_frame; @@ -369,7 +369,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) id3_tag_delete(id3_tag); delete[] allocated; -#else /* !HAVE_ID3TAG */ +#else /* !ENABLE_ID3TAG */ (void)mpd_tag; /* This code is enabled when libid3tag is disabled. Instead @@ -386,7 +386,7 @@ MadDecoder::ParseId3(size_t tagsize, Tag **mpd_tag) #endif } -#ifndef HAVE_ID3TAG +#ifndef ENABLE_ID3TAG /** * This function emulates libid3tag when it is disabled. Instead of * doing a real analyzation of the frame, it just checks whether the @@ -402,7 +402,7 @@ id3_tag_query(const void *p0, size_t length) ? (p[8] << 7) + p[9] + 10 : 0; } -#endif /* !HAVE_ID3TAG */ +#endif /* !ENABLE_ID3TAG */ static enum mp3_action RecoverFrameError(struct mad_stream &stream) @@ -504,10 +504,10 @@ struct xing { enum xing_magic magic; /* header magic */ }; -static const unsigned XING_FRAMES = 1; -static const unsigned XING_BYTES = 2; -static const unsigned XING_TOC = 4; -static const unsigned XING_SCALE = 8; +static constexpr unsigned XING_FRAMES = 1; +static constexpr unsigned XING_BYTES = 2; +static constexpr unsigned XING_TOC = 4; +static constexpr unsigned XING_SCALE = 8; struct lame_version { unsigned major; diff --git a/src/decoder/plugins/SidplayDecoderPlugin.cxx b/src/decoder/plugins/SidplayDecoderPlugin.cxx index 8435f095f..395945157 100644 --- a/src/decoder/plugins/SidplayDecoderPlugin.cxx +++ b/src/decoder/plugins/SidplayDecoderPlugin.cxx @@ -22,67 +22,44 @@ #include "../DecoderAPI.hxx" #include "tag/TagHandler.hxx" #include "fs/Path.hxx" +#include "fs/AllocatedPath.hxx" #include "util/FormatString.hxx" #include "util/Domain.hxx" +#include "util/Error.hxx" #include "system/ByteOrder.hxx" +#include "system/FatalError.hxx" #include "Log.hxx" -#include <errno.h> -#include <stdlib.h> #include <string.h> -#include <glib.h> #include <sidplay/sidplay2.h> #include <sidplay/builders/resid.h> #include <sidplay/utils/SidTuneMod.h> +#include <sidplay/utils/SidDatabase.h> #define SUBTUNE_PREFIX "tune_" static constexpr Domain sidplay_domain("sidplay"); -static GPatternSpec *path_with_subtune; -static const char *songlength_file; -static GKeyFile *songlength_database; +static SidDatabase *songlength_database; static bool all_files_are_containers; static unsigned default_songlength; static bool filter_setting; -static GKeyFile * -sidplay_load_songlength_db(const char *path) +static SidDatabase * +sidplay_load_songlength_db(const Path path) { - GError *error = nullptr; - gchar *data; - gsize size; - - if (!g_file_get_contents(path, &data, &size, &error)) { + SidDatabase *db = new SidDatabase(); + if (db->open(path.c_str()) < 0) { FormatError(sidplay_domain, "unable to read songlengths file %s: %s", - path, error->message); - g_error_free(error); - return nullptr; - } - - /* replace any ; comment characters with # */ - for (gsize i = 0; i < size; i++) - if (data[i] == ';') - data[i] = '#'; - - GKeyFile *db = g_key_file_new(); - bool success = g_key_file_load_from_data(db, data, size, - G_KEY_FILE_NONE, &error); - g_free(data); - if (!success) { - FormatError(sidplay_domain, - "unable to parse songlengths file %s: %s", - path, error->message); - g_error_free(error); - g_key_file_free(db); + path.c_str(), db->error()); + delete db; return nullptr; } - g_key_file_set_list_separator(db, ' '); return db; } @@ -90,18 +67,18 @@ static bool sidplay_init(const config_param ¶m) { /* read the songlengths database file */ - songlength_file = param.GetBlockValue("songlength_database"); - if (songlength_file != nullptr) - songlength_database = sidplay_load_songlength_db(songlength_file); + Error error; + const auto database_path = param.GetBlockPath("songlength_database", error); + if (!database_path.IsNull()) + songlength_database = sidplay_load_songlength_db(database_path); + else if (error.IsDefined()) + FatalError(error); default_songlength = param.GetBlockValue("default_songlength", 0u); all_files_are_containers = param.GetBlockValue("all_files_are_containers", true); - path_with_subtune=g_pattern_spec_new( - "*/" SUBTUNE_PREFIX "???.sid"); - filter_setting = param.GetBlockValue("filter", true); return true; @@ -110,96 +87,61 @@ sidplay_init(const config_param ¶m) static void sidplay_finish() { - g_pattern_spec_free(path_with_subtune); - - if(songlength_database) - g_key_file_free(songlength_database); + delete songlength_database; } -/** - * returns the file path stripped of any /tune_xxx.sid subtune - * suffix - */ -static char * -get_container_name(Path path_fs) +struct SidplayContainerPath { + AllocatedPath path; + unsigned track; +}; + +gcc_pure +static unsigned +ParseSubtuneName(const char *base) { - char *path_container = strdup(path_fs.c_str()); + if (memcmp(base, SUBTUNE_PREFIX, sizeof(SUBTUNE_PREFIX) - 1) != 0) + return 0; - if(!g_pattern_match(path_with_subtune, - strlen(path_container), path_container, nullptr)) - return path_container; + base += sizeof(SUBTUNE_PREFIX) - 1; - char *ptr=g_strrstr(path_container, "/" SUBTUNE_PREFIX); - if(ptr) *ptr='\0'; + char *endptr; + auto track = strtoul(base, &endptr, 10); + if (endptr == base || *endptr != '.') + return 0; - return path_container; + return track; } /** - * returns tune number from file.sid/tune_xxx.sid style path or 1 if - * no subtune is appended + * returns the file path stripped of any /tune_xxx.* subtune suffix + * and the track number (or 1 if no "tune_xxx" suffix is present). */ -static unsigned -get_song_num(const char *path_fs) +static SidplayContainerPath +ParseContainerPath(Path path_fs) { - if(g_pattern_match(path_with_subtune, - strlen(path_fs), path_fs, nullptr)) { - char *sub=g_strrstr(path_fs, "/" SUBTUNE_PREFIX); - if(!sub) return 1; - - sub+=strlen("/" SUBTUNE_PREFIX); - int song_num=strtol(sub, nullptr, 10); - - if (errno == EINVAL) - return 1; - else - return song_num; - } else - return 1; + const Path base = path_fs.GetBase(); + unsigned track; + if (base.IsNull() || + (track = ParseSubtuneName(base.c_str())) < 1) + return { AllocatedPath(path_fs), 1 }; + + return { path_fs.GetDirectoryName(), track }; } /* get the song length in seconds */ static SignedSongTime -get_song_length(Path path_fs) +get_song_length(SidTuneMod &tune) { + assert(tune); + if (songlength_database == nullptr) return SignedSongTime::Negative(); - char *sid_file = get_container_name(path_fs); - SidTuneMod tune(sid_file); - free(sid_file); - if(!tune) { - LogWarning(sidplay_domain, - "failed to load file for calculating md5 sum"); + const auto length = songlength_database->length(tune); + if (length < 0) return SignedSongTime::Negative(); - } - char md5sum[SIDTUNE_MD5_LENGTH+1]; - tune.createMD5(md5sum); - - const unsigned song_num = get_song_num(path_fs.c_str()); - gsize num_items; - gchar **values=g_key_file_get_string_list(songlength_database, - "Database", md5sum, &num_items, nullptr); - if(!values || song_num>num_items) { - g_strfreev(values); - return SignedSongTime::Negative(); - } - - int minutes=strtol(values[song_num-1], nullptr, 10); - if(errno==EINVAL) minutes=0; - - int seconds; - char *ptr=strchr(values[song_num-1], ':'); - if(ptr) { - seconds=strtol(ptr+1, nullptr, 10); - if(errno==EINVAL) seconds=0; - } else - seconds=0; - - g_strfreev(values); - - return SignedSongTime::FromS((minutes * 60) + seconds); + return SignedSongTime::FromS(length); } static void @@ -209,18 +151,17 @@ sidplay_file_decode(Decoder &decoder, Path path_fs) /* load the tune */ - char *path_container=get_container_name(path_fs); - SidTune tune(path_container, nullptr, true); - free(path_container); + const auto container = ParseContainerPath(path_fs); + SidTuneMod tune(container.path.c_str()); if (!tune) { LogWarning(sidplay_domain, "failed to load file"); return; } - const int song_num = get_song_num(path_fs.c_str()); + const int song_num = container.track; tune.selectSong(song_num); - auto duration = get_song_length(path_fs); + auto duration = get_song_length(tune); if (duration.IsNegative() && default_songlength > 0) duration = SongTime::FromS(default_songlength); @@ -347,14 +288,15 @@ static bool sidplay_scan_file(Path path_fs, const struct tag_handler *handler, void *handler_ctx) { - const int song_num = get_song_num(path_fs.c_str()); - char *path_container=get_container_name(path_fs); + const auto container = ParseContainerPath(path_fs); + const unsigned song_num = container.track; - SidTune tune(path_container, nullptr, true); - free(path_container); + SidTuneMod tune(container.path.c_str()); if (!tune) return false; + tune.selectSong(song_num); + const SidTuneInfo &info = tune.getInfo(); /* title */ @@ -385,7 +327,7 @@ sidplay_scan_file(Path path_fs, tag_handler_invoke_tag(handler, handler_ctx, TAG_TRACK, track); /* time */ - const auto duration = get_song_length(path_fs); + const auto duration = get_song_length(tune); if (!duration.IsNegative()) tag_handler_invoke_duration(handler, handler_ctx, SongTime(duration)); diff --git a/src/decoder/plugins/VorbisComments.cxx b/src/decoder/plugins/VorbisComments.cxx index 062f46acf..10652f129 100644 --- a/src/decoder/plugins/VorbisComments.cxx +++ b/src/decoder/plugins/VorbisComments.cxx @@ -27,7 +27,7 @@ #include "tag/ReplayGain.hxx" #include "ReplayGainInfo.hxx" #include "util/ASCII.hxx" -#include "util/SplitString.hxx" +#include "util/DivideString.hxx" #include <stddef.h> #include <stdlib.h> @@ -74,7 +74,7 @@ vorbis_scan_comment(const char *comment, const struct tag_handler *handler, void *handler_ctx) { if (handler->pair != nullptr) { - const SplitString split(comment, '='); + const DivideString split(comment, '='); if (split.IsDefined() && !split.IsEmpty()) tag_handler_invoke_pair(handler, handler_ctx, split.GetFirst(), diff --git a/src/decoder/plugins/WavpackDecoderPlugin.cxx b/src/decoder/plugins/WavpackDecoderPlugin.cxx index 67859bbd2..0b41d052e 100644 --- a/src/decoder/plugins/WavpackDecoderPlugin.cxx +++ b/src/decoder/plugins/WavpackDecoderPlugin.cxx @@ -28,10 +28,10 @@ #include "util/Error.hxx" #include "util/Domain.hxx" #include "util/Macros.hxx" +#include "util/Alloc.hxx" #include "Log.hxx" #include <wavpack/wavpack.h> -#include <glib.h> #include <assert.h> #include <stdio.h> @@ -484,10 +484,10 @@ wavpack_open_wvc(Decoder &decoder, const char *uri) if (uri == nullptr) return nullptr; - char *wvc_url = g_strconcat(uri, "c", nullptr); + char *wvc_url = xstrcatdup(uri, "c"); InputStream *is_wvc = decoder_open_uri(decoder, uri, IgnoreError()); - g_free(wvc_url); + free(wvc_url); if (is_wvc == nullptr) return nullptr; diff --git a/src/decoder/plugins/XiphTags.cxx b/src/decoder/plugins/XiphTags.cxx index 11a0bcd42..db5f6c5e1 100644 --- a/src/decoder/plugins/XiphTags.cxx +++ b/src/decoder/plugins/XiphTags.cxx @@ -27,7 +27,6 @@ const struct tag_table xiph_tags[] = { { "tracknumber", TAG_TRACK }, { "discnumber", TAG_DISC }, - { "album artist", TAG_ALBUM_ARTIST }, { "description", TAG_COMMENT }, { nullptr, TAG_NUM_OF_ITEM_TYPES } }; diff --git a/src/encoder/EncoderList.cxx b/src/encoder/EncoderList.cxx index 4bca5a4fe..79c045328 100644 --- a/src/encoder/EncoderList.cxx +++ b/src/encoder/EncoderList.cxx @@ -33,16 +33,16 @@ const EncoderPlugin *const encoder_plugins[] = { &null_encoder_plugin, -#ifdef ENABLE_VORBIS_ENCODER +#ifdef ENABLE_VORBISENC &vorbis_encoder_plugin, #endif -#ifdef HAVE_OPUS +#ifdef ENABLE_OPUS &opus_encoder_plugin, #endif -#ifdef ENABLE_LAME_ENCODER +#ifdef ENABLE_LAME &lame_encoder_plugin, #endif -#ifdef ENABLE_TWOLAME_ENCODER +#ifdef ENABLE_TWOLAME &twolame_encoder_plugin, #endif #ifdef ENABLE_WAVE_ENCODER @@ -51,7 +51,7 @@ const EncoderPlugin *const encoder_plugins[] = { #ifdef ENABLE_FLAC_ENCODER &flac_encoder_plugin, #endif -#ifdef ENABLE_SHINE_ENCODER +#ifdef ENABLE_SHINE &shine_encoder_plugin, #endif nullptr diff --git a/src/encoder/plugins/VorbisEncoderPlugin.cxx b/src/encoder/plugins/VorbisEncoderPlugin.cxx index ecc784a47..01c9910a0 100644 --- a/src/encoder/plugins/VorbisEncoderPlugin.cxx +++ b/src/encoder/plugins/VorbisEncoderPlugin.cxx @@ -25,14 +25,13 @@ #include "tag/Tag.hxx" #include "AudioFormat.hxx" #include "config/ConfigError.hxx" +#include "util/StringUtil.hxx" #include "util/NumberParser.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include <vorbis/vorbisenc.h> -#include <glib.h> - struct vorbis_encoder { /** the base class */ Encoder encoder; @@ -58,7 +57,7 @@ struct vorbis_encoder { static constexpr Domain vorbis_encoder_domain("vorbis_encoder"); static bool -vorbis_encoder_configure(struct vorbis_encoder *encoder, +vorbis_encoder_configure(struct vorbis_encoder &encoder, const config_param ¶m, Error &error) { const char *value = param.GetBlockValue("quality"); @@ -66,10 +65,10 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder, /* a quality was configured (VBR) */ char *endptr; - encoder->quality = ParseDouble(value, &endptr); + encoder.quality = ParseDouble(value, &endptr); - if (*endptr != '\0' || encoder->quality < -1.0 || - encoder->quality > 10.0) { + if (*endptr != '\0' || encoder.quality < -1.0 || + encoder.quality > 10.0) { error.Format(config_domain, "quality \"%s\" is not a number in the " "range -1 to 10", @@ -92,11 +91,11 @@ vorbis_encoder_configure(struct vorbis_encoder *encoder, return false; } - encoder->quality = -2.0; + encoder.quality = -2.0; char *endptr; - encoder->bitrate = ParseInt(value, &endptr); - if (*endptr != '\0' || encoder->bitrate <= 0) { + encoder.bitrate = ParseInt(value, &endptr); + if (*endptr != '\0' || encoder.bitrate <= 0) { error.Set(config_domain, "bitrate should be a positive integer"); return false; @@ -112,7 +111,7 @@ vorbis_encoder_init(const config_param ¶m, Error &error) vorbis_encoder *encoder = new vorbis_encoder(); /* load configuration from "param" */ - if (!vorbis_encoder_configure(encoder, param, error)) { + if (!vorbis_encoder_configure(*encoder, param, error)) { /* configuration has failed, roll back and return error */ delete encoder; return nullptr; @@ -132,63 +131,63 @@ vorbis_encoder_finish(Encoder *_encoder) } static bool -vorbis_encoder_reinit(struct vorbis_encoder *encoder, Error &error) +vorbis_encoder_reinit(struct vorbis_encoder &encoder, Error &error) { - vorbis_info_init(&encoder->vi); + vorbis_info_init(&encoder.vi); - if (encoder->quality >= -1.0) { + if (encoder.quality >= -1.0) { /* a quality was configured (VBR) */ - if (0 != vorbis_encode_init_vbr(&encoder->vi, - encoder->audio_format.channels, - encoder->audio_format.sample_rate, - encoder->quality * 0.1)) { + if (0 != vorbis_encode_init_vbr(&encoder.vi, + encoder.audio_format.channels, + encoder.audio_format.sample_rate, + encoder.quality * 0.1)) { error.Set(vorbis_encoder_domain, "error initializing vorbis vbr"); - vorbis_info_clear(&encoder->vi); + vorbis_info_clear(&encoder.vi); return false; } } else { /* a bit rate was configured */ - if (0 != vorbis_encode_init(&encoder->vi, - encoder->audio_format.channels, - encoder->audio_format.sample_rate, -1.0, - encoder->bitrate * 1000, -1.0)) { + if (0 != vorbis_encode_init(&encoder.vi, + encoder.audio_format.channels, + encoder.audio_format.sample_rate, -1.0, + encoder.bitrate * 1000, -1.0)) { error.Set(vorbis_encoder_domain, "error initializing vorbis encoder"); - vorbis_info_clear(&encoder->vi); + vorbis_info_clear(&encoder.vi); return false; } } - vorbis_analysis_init(&encoder->vd, &encoder->vi); - vorbis_block_init(&encoder->vd, &encoder->vb); - encoder->stream.Initialize(GenerateOggSerial()); + vorbis_analysis_init(&encoder.vd, &encoder.vi); + vorbis_block_init(&encoder.vd, &encoder.vb); + encoder.stream.Initialize(GenerateOggSerial()); return true; } static void -vorbis_encoder_headerout(struct vorbis_encoder *encoder, vorbis_comment *vc) +vorbis_encoder_headerout(struct vorbis_encoder &encoder, vorbis_comment &vc) { ogg_packet packet, comments, codebooks; - vorbis_analysis_headerout(&encoder->vd, vc, + vorbis_analysis_headerout(&encoder.vd, &vc, &packet, &comments, &codebooks); - encoder->stream.PacketIn(packet); - encoder->stream.PacketIn(comments); - encoder->stream.PacketIn(codebooks); + encoder.stream.PacketIn(packet); + encoder.stream.PacketIn(comments); + encoder.stream.PacketIn(codebooks); } static void -vorbis_encoder_send_header(struct vorbis_encoder *encoder) +vorbis_encoder_send_header(struct vorbis_encoder &encoder) { vorbis_comment vc; vorbis_comment_init(&vc); - vorbis_encoder_headerout(encoder, &vc); + vorbis_encoder_headerout(encoder, vc); vorbis_comment_clear(&vc); } @@ -197,11 +196,11 @@ vorbis_encoder_open(Encoder *_encoder, AudioFormat &audio_format, Error &error) { - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder; audio_format.format = SampleFormat::FLOAT; - encoder->audio_format = audio_format; + encoder.audio_format = audio_format; if (!vorbis_encoder_reinit(encoder, error)) return false; @@ -212,70 +211,70 @@ vorbis_encoder_open(Encoder *_encoder, } static void -vorbis_encoder_clear(struct vorbis_encoder *encoder) +vorbis_encoder_clear(struct vorbis_encoder &encoder) { - encoder->stream.Deinitialize(); - vorbis_block_clear(&encoder->vb); - vorbis_dsp_clear(&encoder->vd); - vorbis_info_clear(&encoder->vi); + encoder.stream.Deinitialize(); + vorbis_block_clear(&encoder.vb); + vorbis_dsp_clear(&encoder.vd); + vorbis_info_clear(&encoder.vi); } static void vorbis_encoder_close(Encoder *_encoder) { - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder; vorbis_encoder_clear(encoder); } static void -vorbis_encoder_blockout(struct vorbis_encoder *encoder) +vorbis_encoder_blockout(struct vorbis_encoder &encoder) { - while (vorbis_analysis_blockout(&encoder->vd, &encoder->vb) == 1) { - vorbis_analysis(&encoder->vb, nullptr); - vorbis_bitrate_addblock(&encoder->vb); + while (vorbis_analysis_blockout(&encoder.vd, &encoder.vb) == 1) { + vorbis_analysis(&encoder.vb, nullptr); + vorbis_bitrate_addblock(&encoder.vb); ogg_packet packet; - while (vorbis_bitrate_flushpacket(&encoder->vd, &packet)) - encoder->stream.PacketIn(packet); + while (vorbis_bitrate_flushpacket(&encoder.vd, &packet)) + encoder.stream.PacketIn(packet); } } static bool vorbis_encoder_flush(Encoder *_encoder, gcc_unused Error &error) { - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder; - encoder->stream.Flush(); + encoder.stream.Flush(); return true; } static bool vorbis_encoder_pre_tag(Encoder *_encoder, gcc_unused Error &error) { - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder; - vorbis_analysis_wrote(&encoder->vd, 0); + vorbis_analysis_wrote(&encoder.vd, 0); vorbis_encoder_blockout(encoder); /* reinitialize vorbis_dsp_state and vorbis_block to reset the end-of-stream marker */ - vorbis_block_clear(&encoder->vb); - vorbis_dsp_clear(&encoder->vd); - vorbis_analysis_init(&encoder->vd, &encoder->vi); - vorbis_block_init(&encoder->vd, &encoder->vb); + vorbis_block_clear(&encoder.vb); + vorbis_dsp_clear(&encoder.vd); + vorbis_analysis_init(&encoder.vd, &encoder.vi); + vorbis_block_init(&encoder.vd, &encoder.vb); - encoder->stream.Flush(); + encoder.stream.Flush(); return true; } static void -copy_tag_to_vorbis_comment(vorbis_comment *vc, const Tag *tag) +copy_tag_to_vorbis_comment(vorbis_comment *vc, const Tag &tag) { - for (const auto &item : *tag) { - char *name = g_ascii_strup(tag_item_names[item.type], -1); + for (const auto &item : tag) { + char name[64]; + ToUpperASCII(name, tag_item_names[item.type], sizeof(name)); vorbis_comment_add_tag(vc, name, item.value); - g_free(name); } } @@ -283,21 +282,21 @@ static bool vorbis_encoder_tag(Encoder *_encoder, const Tag *tag, gcc_unused Error &error) { - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder; vorbis_comment comment; /* write the vorbis_comment object */ vorbis_comment_init(&comment); - copy_tag_to_vorbis_comment(&comment, tag); + copy_tag_to_vorbis_comment(&comment, *tag); /* reset ogg_stream_state and begin a new stream */ - encoder->stream.Reinitialize(GenerateOggSerial()); + encoder.stream.Reinitialize(GenerateOggSerial()); /* send that vorbis_comment to the ogg_stream_state */ - vorbis_encoder_headerout(encoder, &comment); + vorbis_encoder_headerout(encoder, comment); vorbis_comment_clear(&comment); return true; @@ -317,19 +316,19 @@ vorbis_encoder_write(Encoder *_encoder, const void *data, size_t length, gcc_unused Error &error) { - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder; - unsigned num_frames = length / encoder->audio_format.GetFrameSize(); + unsigned num_frames = length / encoder.audio_format.GetFrameSize(); /* this is for only 16-bit audio */ - interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&encoder->vd, + interleaved_to_vorbis_buffer(vorbis_analysis_buffer(&encoder.vd, num_frames), (const float *)data, num_frames, - encoder->audio_format.channels); + encoder.audio_format.channels); - vorbis_analysis_wrote(&encoder->vd, num_frames); + vorbis_analysis_wrote(&encoder.vd, num_frames); vorbis_encoder_blockout(encoder); return true; } @@ -337,9 +336,9 @@ vorbis_encoder_write(Encoder *_encoder, static size_t vorbis_encoder_read(Encoder *_encoder, void *dest, size_t length) { - struct vorbis_encoder *encoder = (struct vorbis_encoder *)_encoder; + struct vorbis_encoder &encoder = *(struct vorbis_encoder *)_encoder; - return encoder->stream.PageOut(dest, length); + return encoder.stream.PageOut(dest, length); } static const char * diff --git a/src/event/ServerSocket.cxx b/src/event/ServerSocket.cxx index 313f0a6cf..0ea1d7179 100644 --- a/src/event/ServerSocket.cxx +++ b/src/event/ServerSocket.cxx @@ -198,7 +198,7 @@ OneServerSocket::Open(Error &error) if (!path.IsNull()) chmod(path.c_str(), 0666); - /* register in the GLib main loop */ + /* register in the EventLoop */ SetFD(_fd); diff --git a/src/fs/AllocatedPath.cxx b/src/fs/AllocatedPath.cxx index ceaad73ea..bd026db74 100644 --- a/src/fs/AllocatedPath.cxx +++ b/src/fs/AllocatedPath.cxx @@ -24,33 +24,14 @@ #include "util/Error.hxx" #include "Compiler.h" -#ifdef HAVE_GLIB -#include <glib.h> -#endif - -#include <string.h> - -#ifdef HAVE_GLIB - -inline AllocatedPath::AllocatedPath(Donate, pointer _value) - :value(_value) { - g_free(_value); -} - -#endif - /* no inlining, please */ AllocatedPath::~AllocatedPath() {} AllocatedPath AllocatedPath::FromUTF8(const char *path_utf8) { -#ifdef HAVE_GLIB - char *path = ::PathFromUTF8(path_utf8); - if (path == nullptr) - return AllocatedPath::Null(); - - return AllocatedPath(Donate(), path); +#ifdef HAVE_FS_CHARSET + return AllocatedPath(::PathFromUTF8(path_utf8)); #else return FromFS(path_utf8); #endif @@ -111,7 +92,7 @@ AllocatedPath::ChopSeparators() while (l >= 2 && PathTraitsFS::IsSeparator(p[l - 1])) { --l; -#if GCC_CHECK_VERSION(4,7) && !defined(__clang__) +#if GCC_CHECK_VERSION(4,7) value.pop_back(); #else value.erase(value.end() - 1, value.end()); diff --git a/src/fs/AllocatedPath.hxx b/src/fs/AllocatedPath.hxx index c345470c8..73b6891ad 100644 --- a/src/fs/AllocatedPath.hxx +++ b/src/fs/AllocatedPath.hxx @@ -44,13 +44,6 @@ class AllocatedPath { string value; - struct Donate {}; - - /** - * Donate the allocated pointer to a new #AllocatedPath object. - */ - AllocatedPath(Donate, pointer _value); - AllocatedPath(const_pointer _value):value(_value) {} AllocatedPath(string &&_value):value(std::move(_value)) {} diff --git a/src/fs/Charset.cxx b/src/fs/Charset.cxx index c634c9340..4d562b59f 100644 --- a/src/fs/Charset.cxx +++ b/src/fs/Charset.cxx @@ -21,66 +21,52 @@ #include "Charset.hxx" #include "Domain.hxx" #include "Limits.hxx" -#include "system/FatalError.hxx" #include "Log.hxx" #include "Traits.hxx" - -#ifdef HAVE_GLIB -#include <glib.h> -#endif +#include "lib/icu/Converter.hxx" +#include "util/Error.hxx" #include <algorithm> #include <assert.h> #include <string.h> -#ifdef HAVE_GLIB - -/** - * Maximal number of bytes required to represent path name in UTF-8 - * (including nul-terminator). - * This value is a rought estimate of upper bound. - * It's based on path name limit in bytes (MPD_PATH_MAX) - * and assumption that some weird encoding could represent some UTF-8 4 byte - * sequences with single byte. - */ -static constexpr size_t MPD_PATH_MAX_UTF8 = (MPD_PATH_MAX - 1) * 4 + 1; +#ifdef HAVE_FS_CHARSET static std::string fs_charset; -gcc_pure -static bool -IsSupportedCharset(const char *charset) -{ - /* convert a space to check if the charset is valid */ - char *test = g_convert(" ", 1, charset, "UTF-8", nullptr, nullptr, nullptr); - if (test == nullptr) - return false; - - g_free(test); - return true; -} +static IcuConverter *fs_converter; -void -SetFSCharset(const char *charset) +bool +SetFSCharset(const char *charset, Error &error) { assert(charset != nullptr); + assert(fs_converter == nullptr); - if (!IsSupportedCharset(charset)) - FormatFatalError("invalid filesystem charset: %s", charset); - - fs_charset = charset; + fs_converter = IcuConverter::Create(charset, error); + if (fs_converter == nullptr) + return false; FormatDebug(path_domain, "SetFSCharset: fs charset is: %s", fs_charset.c_str()); + return true; } #endif +void +DeinitFSCharset() +{ +#ifdef HAVE_ICU_CONVERTER + delete fs_converter; + fs_converter = nullptr; +#endif +} + const char * GetFSCharset() { -#ifdef HAVE_GLIB +#ifdef HAVE_FS_CHARSET return fs_charset.empty() ? "UTF-8" : fs_charset.c_str(); #else return "UTF-8"; @@ -108,43 +94,24 @@ PathToUTF8(const char *path_fs) assert(path_fs != nullptr); #endif -#ifdef HAVE_GLIB - if (fs_charset.empty()) { +#ifdef HAVE_FS_CHARSET + if (fs_converter == nullptr) { #endif auto result = std::string(path_fs); FixSeparators(result); return result; -#ifdef HAVE_GLIB +#ifdef HAVE_FS_CHARSET } - GIConv conv = g_iconv_open("utf-8", fs_charset.c_str()); - if (conv == reinterpret_cast<GIConv>(-1)) - return std::string(); - - // g_iconv() does not need nul-terminator, - // std::string could be created without it too. - char path_utf8[MPD_PATH_MAX_UTF8 - 1]; - char *in = const_cast<char *>(path_fs); - char *out = path_utf8; - size_t in_left = strlen(path_fs); - size_t out_left = sizeof(path_utf8); - - size_t ret = g_iconv(conv, &in, &in_left, &out, &out_left); - - g_iconv_close(conv); - - if (ret == static_cast<size_t>(-1) || in_left > 0) - return std::string(); - - auto result_path = std::string(path_utf8, sizeof(path_utf8) - out_left); + auto result_path = fs_converter->ToUTF8(path_fs); FixSeparators(result_path); return result_path; #endif } -#ifdef HAVE_GLIB +#ifdef HAVE_FS_CHARSET -char * +std::string PathFromUTF8(const char *path_utf8) { #if !CLANG_CHECK_VERSION(3,6) @@ -152,12 +119,10 @@ PathFromUTF8(const char *path_utf8) assert(path_utf8 != nullptr); #endif - if (fs_charset.empty()) - return g_strdup(path_utf8); + if (fs_converter == nullptr) + return path_utf8; - return g_convert(path_utf8, -1, - fs_charset.c_str(), "utf-8", - nullptr, nullptr, nullptr); + return fs_converter->FromUTF8(path_utf8); } #endif diff --git a/src/fs/Charset.hxx b/src/fs/Charset.hxx index 0a71d7c58..f1d5f3bbf 100644 --- a/src/fs/Charset.hxx +++ b/src/fs/Charset.hxx @@ -25,6 +25,12 @@ #include <string> +#if defined(HAVE_ICU) || defined(HAVE_GLIB) +#define HAVE_FS_CHARSET +#endif + +class Error; + /** * Gets file system character set name. */ @@ -32,8 +38,11 @@ gcc_const const char * GetFSCharset(); +bool +SetFSCharset(const char *charset, Error &error); + void -SetFSCharset(const char *charset); +DeinitFSCharset(); /** * Convert the path to UTF-8. @@ -43,8 +52,12 @@ gcc_pure gcc_nonnull_all std::string PathToUTF8(const char *path_fs); -gcc_malloc gcc_nonnull_all -char * +/** + * Convert the path from UTF-8. + * Returns empty string on error. + */ +gcc_pure gcc_nonnull_all +std::string PathFromUTF8(const char *path_utf8); #endif diff --git a/src/fs/Config.cxx b/src/fs/Config.cxx index 6aa23005c..7b0da061a 100644 --- a/src/fs/Config.cxx +++ b/src/fs/Config.cxx @@ -29,21 +29,15 @@ #include <glib.h> #endif -void -ConfigureFS() +bool +ConfigureFS(Error &error) { -#if defined(HAVE_GLIB) || defined(WIN32) +#ifdef HAVE_FS_CHARSET const char *charset = nullptr; charset = config_get_string(CONF_FS_CHARSET, nullptr); if (charset == nullptr) { -#ifndef WIN32 - const gchar **encodings; - g_get_filename_charsets(&encodings); - - if (encodings[0] != nullptr && *encodings[0] != '\0') - charset = encodings[0]; -#else +#ifdef WIN32 /* Glib claims that file system encoding is always utf-8 * on native Win32 (i.e. not Cygwin). * However this is true only if <gstdio.h> helpers are used. @@ -52,10 +46,26 @@ ConfigureFS() static char win_charset[13]; sprintf(win_charset, "cp%u", GetACP()); charset = win_charset; +#elif defined(HAVE_GLIB) + const gchar **encodings; + g_get_filename_charsets(&encodings); + + if (encodings[0] != nullptr && *encodings[0] != '\0') + charset = encodings[0]; #endif } - if (charset != nullptr) - SetFSCharset(charset); + return charset == nullptr || SetFSCharset(charset, error); +#else + (void)error; + return true; +#endif +} + +void +DeinitFS() +{ +#ifdef HAVE_FS_CHARSET + DeinitFSCharset(); #endif } diff --git a/src/fs/Config.hxx b/src/fs/Config.hxx index d4f1709f5..403c07685 100644 --- a/src/fs/Config.hxx +++ b/src/fs/Config.hxx @@ -22,10 +22,15 @@ #include "check.h" +class Error; + /** * Performs global one-time initialization of this class. */ +bool +ConfigureFS(Error &error); + void -ConfigureFS(); +DeinitFS(); #endif diff --git a/src/fs/Path.hxx b/src/fs/Path.hxx index 9e0fa5aeb..4a3ae815f 100644 --- a/src/fs/Path.hxx +++ b/src/fs/Path.hxx @@ -29,6 +29,8 @@ #include <assert.h> #include <string.h> +class AllocatedPath; + /** * A path name in the native file system character set. * @@ -129,6 +131,22 @@ public: std::string ToUTF8() const; /** + * Determine the "base" file name. + * The return value points inside this object. + */ + gcc_pure + Path GetBase() const { + return FromFS(PathTraitsFS::GetBase(value)); + } + + /** + * Gets directory name of this path. + * Returns a "nulled" instance on error. + */ + gcc_pure + AllocatedPath GetDirectoryName() const; + + /** * Determine the relative part of the given path to this * object, not including the directory separator. Returns an * empty string if the given path equals this object or diff --git a/src/fs/Path2.cxx b/src/fs/Path2.cxx new file mode 100644 index 000000000..966e34dad --- /dev/null +++ b/src/fs/Path2.cxx @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Path.hxx" +#include "AllocatedPath.hxx" + +AllocatedPath +Path::GetDirectoryName() const +{ + return AllocatedPath::FromFS(PathTraitsFS::GetParent(c_str())); +} diff --git a/src/fs/io/TextFile.cxx b/src/fs/io/TextFile.cxx index 28d6dabcb..1710e0e89 100644 --- a/src/fs/io/TextFile.cxx +++ b/src/fs/io/TextFile.cxx @@ -28,14 +28,14 @@ TextFile::TextFile(Path path_fs, Error &error) :file_reader(new FileReader(path_fs, error)), -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB gunzip_reader(file_reader->IsDefined() ? new AutoGunzipReader(*file_reader) : nullptr), #endif buffered_reader(file_reader->IsDefined() ? new BufferedReader(* -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB gunzip_reader #else file_reader @@ -48,7 +48,7 @@ TextFile::TextFile(Path path_fs, Error &error) TextFile::~TextFile() { delete buffered_reader; -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB delete gunzip_reader; #endif delete file_reader; diff --git a/src/fs/io/TextFile.hxx b/src/fs/io/TextFile.hxx index 5577363e7..425797ce7 100644 --- a/src/fs/io/TextFile.hxx +++ b/src/fs/io/TextFile.hxx @@ -34,7 +34,7 @@ class BufferedReader; class TextFile { FileReader *const file_reader; -#ifdef HAVE_ZLIB +#ifdef ENABLE_ZLIB AutoGunzipReader *const gunzip_reader; #endif diff --git a/src/input/AsyncInputStream.hxx b/src/input/AsyncInputStream.hxx index d1f0c3b9d..b717087e7 100644 --- a/src/input/AsyncInputStream.hxx +++ b/src/input/AsyncInputStream.hxx @@ -62,6 +62,10 @@ protected: Error postponed_error; public: + /** + * @param _buffer a buffer allocated with HugeAllocate(); the + * destructor will free it using HugeFree() + */ AsyncInputStream(const char *_url, Mutex &_mutex, Cond &_cond, void *_buffer, size_t _buffer_size, diff --git a/src/input/Registry.cxx b/src/input/Registry.cxx index 2b981df1c..6be3233e4 100644 --- a/src/input/Registry.cxx +++ b/src/input/Registry.cxx @@ -22,7 +22,7 @@ #include "util/Macros.hxx" #include "plugins/FileInputPlugin.hxx" -#ifdef HAVE_ALSA +#ifdef ENABLE_ALSA #include "plugins/AlsaInputPlugin.hxx" #endif @@ -34,7 +34,7 @@ #include "plugins/CurlInputPlugin.hxx" #endif -#ifdef HAVE_FFMPEG +#ifdef ENABLE_FFMPEG #include "plugins/FfmpegInputPlugin.hxx" #endif @@ -60,7 +60,7 @@ const InputPlugin *const input_plugins[] = { &input_plugin_file, -#ifdef HAVE_ALSA +#ifdef ENABLE_ALSA &input_plugin_alsa, #endif #ifdef ENABLE_ARCHIVE @@ -69,7 +69,7 @@ const InputPlugin *const input_plugins[] = { #ifdef ENABLE_CURL &input_plugin_curl, #endif -#ifdef HAVE_FFMPEG +#ifdef ENABLE_FFMPEG &input_plugin_ffmpeg, #endif #ifdef ENABLE_SMBCLIENT diff --git a/src/input/plugins/CdioParanoiaInputPlugin.cxx b/src/input/plugins/CdioParanoiaInputPlugin.cxx index f847b35c1..2b45a34aa 100644 --- a/src/input/plugins/CdioParanoiaInputPlugin.cxx +++ b/src/input/plugins/CdioParanoiaInputPlugin.cxx @@ -39,7 +39,6 @@ #include <stddef.h> #include <string.h> #include <stdlib.h> -#include <glib.h> #include <assert.h> #ifdef HAVE_CDIO_PARANOIA_PARANOIA_H @@ -149,7 +148,7 @@ parse_cdio_uri(struct cdio_uri *dest, const char *src, Error &error) const char *slash = strrchr(src, '/'); if (slash == nullptr) { /* play the whole CD in the specified drive */ - g_strlcpy(dest->device, src, sizeof(dest->device)); + CopyString(dest->device, src, sizeof(dest->device)); dest->track = -1; return true; } diff --git a/src/input/plugins/FfmpegInputPlugin.cxx b/src/input/plugins/FfmpegInputPlugin.cxx index 669f8d403..701beb313 100644 --- a/src/input/plugins/FfmpegInputPlugin.cxx +++ b/src/input/plugins/FfmpegInputPlugin.cxx @@ -22,6 +22,7 @@ #include "config.h" #include "FfmpegInputPlugin.hxx" +#include "lib/ffmpeg/Init.hxx" #include "lib/ffmpeg/Domain.hxx" #include "lib/ffmpeg/Error.hxx" #include "../InputStream.hxx" @@ -31,7 +32,6 @@ extern "C" { #include <libavformat/avio.h> -#include <libavformat/avformat.h> } struct FfmpegInputStream final : public InputStream { @@ -75,7 +75,7 @@ static InputPlugin::InitResult input_ffmpeg_init(gcc_unused const config_param ¶m, Error &error) { - av_register_all(); + FfmpegInit(); /* disable this plugin if there's no registered protocol */ if (!input_ffmpeg_supported()) { diff --git a/src/input/plugins/NfsInputPlugin.cxx b/src/input/plugins/NfsInputPlugin.cxx index c6c0970b9..c1f7a48ce 100644 --- a/src/input/plugins/NfsInputPlugin.cxx +++ b/src/input/plugins/NfsInputPlugin.cxx @@ -28,10 +28,6 @@ #include "util/StringUtil.hxx" #include "util/Error.hxx" -extern "C" { -#include <nfsc/libnfs.h> -} - #include <string.h> #include <sys/stat.h> #include <fcntl.h> diff --git a/src/lib/despotify/DespotifyUtils.cxx b/src/lib/despotify/DespotifyUtils.cxx index f67679c50..aae16000c 100644 --- a/src/lib/despotify/DespotifyUtils.cxx +++ b/src/lib/despotify/DespotifyUtils.cxx @@ -23,6 +23,7 @@ #include "config/ConfigGlobal.hxx" #include "config/ConfigOption.hxx" #include "util/Domain.hxx" +#include "util/Macros.hxx" #include "Log.hxx" extern "C" { @@ -42,24 +43,21 @@ static void callback(struct despotify_session* ds, int sig, void *data, gcc_unused void *callback_data) { - size_t i; - - for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { + for (size_t i = 0; i < ARRAY_SIZE(registered_callbacks); ++i) { void (*cb)(struct despotify_session *, int, void *, void *) = registered_callbacks[i]; void *cb_data = registered_callback_data[i]; - if (cb) + if (cb != nullptr) cb(ds, sig, data, cb_data); } } -bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), - void *cb_data) +bool +mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, + void *, void *), + void *cb_data) { - size_t i; - - for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { - + for (size_t i = 0; i < ARRAY_SIZE(registered_callbacks); ++i) { if (!registered_callbacks[i]) { registered_callbacks[i] = cb; registered_callback_data[i] = cb_data; @@ -71,12 +69,11 @@ bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, return false; } -void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)) +void +mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, + void *, void *)) { - size_t i; - - for (i = 0; i < sizeof(registered_callbacks) / sizeof(registered_callbacks[0]); i++) { - + for (size_t i = 0; i < ARRAY_SIZE(registered_callbacks); ++i) { if (registered_callbacks[i] == cb) { registered_callbacks[i] = nullptr; } @@ -86,42 +83,50 @@ void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, in Tag mpd_despotify_tag_from_track(const ds_track &track) { - char tracknum[20]; - char comment[80]; - char date[20]; - if (!track.has_meta_data) return Tag(); TagBuilder tag; - snprintf(tracknum, sizeof(tracknum), "%d", track.tracknumber); - snprintf(date, sizeof(date), "%d", track.year); - snprintf(comment, sizeof(comment), "Bitrate %d Kbps, %sgeo restricted", - track.file_bitrate / 1000, - track.geo_restricted ? "" : "not "); + + { + char tracknum[20]; + snprintf(tracknum, sizeof(tracknum), "%d", track.tracknumber); + tag.AddItem(TAG_TRACK, tracknum); + } + + { + char date[20]; + snprintf(date, sizeof(date), "%d", track.year); + tag.AddItem(TAG_DATE, date); + } + + { + char comment[80]; + snprintf(comment, sizeof(comment), + "Bitrate %d Kbps, %sgeo restricted", + track.file_bitrate / 1000, + track.geo_restricted ? "" : "not "); + tag.AddItem(TAG_COMMENT, comment); + } + tag.AddItem(TAG_TITLE, track.title); tag.AddItem(TAG_ARTIST, track.artist->name); - tag.AddItem(TAG_TRACK, tracknum); tag.AddItem(TAG_ALBUM, track.album); - tag.AddItem(TAG_DATE, date); - tag.AddItem(TAG_COMMENT, comment); tag.SetDuration(SignedSongTime::FromMS(track.length)); return tag.Commit(); } -struct despotify_session *mpd_despotify_get_session(void) +struct despotify_session * +mpd_despotify_get_session() { - const char *user; - const char *passwd; - bool high_bitrate; - if (g_session) return g_session; - user = config_get_string(CONF_DESPOTIFY_USER, nullptr); - passwd = config_get_string(CONF_DESPOTIFY_PASSWORD, nullptr); - high_bitrate = config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true); + const char *const user = + config_get_string(CONF_DESPOTIFY_USER, nullptr); + const char *const passwd = + config_get_string(CONF_DESPOTIFY_PASSWORD, nullptr); if (user == nullptr || passwd == nullptr) { LogDebug(despotify_domain, @@ -134,6 +139,8 @@ struct despotify_session *mpd_despotify_get_session(void) return nullptr; } + const bool high_bitrate = + config_get_bool(CONF_DESPOTIFY_HIGH_BITRATE, true); g_session = despotify_init_client(callback, nullptr, high_bitrate, true); if (!g_session) { diff --git a/src/lib/despotify/DespotifyUtils.hxx b/src/lib/despotify/DespotifyUtils.hxx index 835b901a2..a21632bb8 100644 --- a/src/lib/despotify/DespotifyUtils.hxx +++ b/src/lib/despotify/DespotifyUtils.hxx @@ -17,8 +17,8 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef MPD_DESPOTIFY_H -#define MPD_DESPOTIFY_H +#ifndef MPD_DESPOTIFY_UTILS_HXX +#define MPD_DESPOTIFY_UTILS_HXX struct Tag; struct despotify_session; @@ -35,7 +35,8 @@ extern const class Domain despotify_domain; * @return a pointer to the despotify session, or nullptr if it can't * be initialized (e.g., if the configuration isn't supplied) */ -struct despotify_session *mpd_despotify_get_session(void); +struct despotify_session * +mpd_despotify_get_session(); /** * Create a MPD tags structure from a spotify track @@ -57,15 +58,19 @@ mpd_despotify_tag_from_track(const ds_track &track); * * @return true if the callback could be registered */ -bool mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, void *, void *), - void *cb_data); +bool +mpd_despotify_register_callback(void (*cb)(struct despotify_session *, int, + void *, void *), + void *cb_data); /** * Unregister a despotify callback. * * @param cb the callback to unregister. */ -void mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, void *, void *)); +void +mpd_despotify_unregister_callback(void (*cb)(struct despotify_session *, int, + void *, void *)); #endif diff --git a/src/lib/ffmpeg/Buffer.hxx b/src/lib/ffmpeg/Buffer.hxx new file mode 100644 index 000000000..50a702f59 --- /dev/null +++ b/src/lib/ffmpeg/Buffer.hxx @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_BUFFER_HXX +#define MPD_FFMPEG_BUFFER_HXX + +extern "C" { +#include <libavutil/mem.h> + +#if LIBAVUTIL_VERSION_INT >= AV_VERSION_INT(52, 18, 0) +#define HAVE_AV_FAST_MALLOC +#else +#include <libavcodec/avcodec.h> +#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(52, 25, 0) +#define HAVE_AV_FAST_MALLOC +#endif +#endif +} + +#include <stddef.h> + +/* suppress the ffmpeg compatibility macro */ +#ifdef SampleFormat +#undef SampleFormat +#endif + +class FfmpegBuffer { + void *data; + unsigned size; + +public: + FfmpegBuffer():data(nullptr), size(0) {} + + ~FfmpegBuffer() { + av_free(data); + } + + void *Get(size_t min_size) { +#ifdef HAVE_AV_FAST_MALLOC + av_fast_malloc(&data, &size, min_size); +#else + void *new_data = av_fast_realloc(data, &size, min_size); + if (new_data == nullptr) + return AVERROR(ENOMEM); + data = new_data; +#endif + return data; + } + + template<typename T> + T *GetT(size_t n) { + return (T *)Get(n * sizeof(T)); + } +}; + +#endif diff --git a/src/lib/ffmpeg/Init.cxx b/src/lib/ffmpeg/Init.cxx new file mode 100644 index 000000000..24f4ab238 --- /dev/null +++ b/src/lib/ffmpeg/Init.cxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "Init.hxx" +#include "LogCallback.hxx" + +extern "C" { +#include <libavformat/avformat.h> +} + +void +FfmpegInit() +{ + av_log_set_callback(FfmpegLogCallback); + + av_register_all(); +} + diff --git a/src/lib/ffmpeg/Init.hxx b/src/lib/ffmpeg/Init.hxx new file mode 100644 index 000000000..bcc4805a9 --- /dev/null +++ b/src/lib/ffmpeg/Init.hxx @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_INIT_HXX +#define MPD_FFMPEG_INIT_HXX + +void +FfmpegInit(); + +#endif diff --git a/src/lib/ffmpeg/LogCallback.cxx b/src/lib/ffmpeg/LogCallback.cxx new file mode 100644 index 000000000..799ba2f34 --- /dev/null +++ b/src/lib/ffmpeg/LogCallback.cxx @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* necessary because libavutil/common.h uses UINT64_C */ +#define __STDC_CONSTANT_MACROS + +#include "config.h" +#include "LogCallback.hxx" +#include "Domain.hxx" +#include "LogV.hxx" +#include "util/Domain.hxx" + +extern "C" { +#include <libavutil/log.h> +} + +#include <stdio.h> + +gcc_const +static LogLevel +FfmpegImportLogLevel(int level) +{ + if (level <= AV_LOG_FATAL) + return LogLevel::ERROR; + + if (level <= AV_LOG_WARNING) + return LogLevel::WARNING; + + if (level <= AV_LOG_INFO) + return LogLevel::INFO; + + return LogLevel::DEBUG; +} + +void +FfmpegLogCallback(gcc_unused void *ptr, int level, const char *fmt, va_list vl) +{ + const AVClass * cls = nullptr; + + if (ptr != nullptr) + cls = *(const AVClass *const*)ptr; + + if (cls != nullptr) { + char domain[64]; + snprintf(domain, sizeof(domain), "%s/%s", + ffmpeg_domain.GetName(), cls->item_name(ptr)); + const Domain d(domain); + LogFormatV(d, FfmpegImportLogLevel(level), fmt, vl); + } +} diff --git a/src/lib/ffmpeg/LogCallback.hxx b/src/lib/ffmpeg/LogCallback.hxx new file mode 100644 index 000000000..cb4b2ccf5 --- /dev/null +++ b/src/lib/ffmpeg/LogCallback.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_LOG_CALLBACK_HXX +#define MPD_FFMPEG_LOG_CALLBACK_HXX + +#include "check.h" + +#include <stdarg.h> + +void +FfmpegLogCallback(void *ptr, int level, const char *fmt, va_list vl); + +#endif diff --git a/src/lib/ffmpeg/LogError.cxx b/src/lib/ffmpeg/LogError.cxx new file mode 100644 index 000000000..da761f35a --- /dev/null +++ b/src/lib/ffmpeg/LogError.cxx @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "LogError.hxx" +#include "Domain.hxx" +#include "Log.hxx" + +#include <cstdint> /* needed due to libavutil bug */ + +extern "C" { +#include <libavutil/error.h> +} + +void +LogFfmpegError(int errnum) +{ + char msg[256]; + av_strerror(errnum, msg, sizeof(msg)); + LogError(ffmpeg_domain, msg); +} + +void +LogFfmpegError(int errnum, const char *prefix) +{ + char msg[256]; + av_strerror(errnum, msg, sizeof(msg)); + FormatError(ffmpeg_domain, "%s: %s", prefix, msg); +} diff --git a/src/lib/ffmpeg/LogError.hxx b/src/lib/ffmpeg/LogError.hxx new file mode 100644 index 000000000..ccafc6f94 --- /dev/null +++ b/src/lib/ffmpeg/LogError.hxx @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_LOG_ERROR_HXX +#define MPD_FFMPEG_LOG_ERROR_HXX + +void +LogFfmpegError(int errnum); + +void +LogFfmpegError(int errnum, const char *prefix); + +#endif diff --git a/src/lib/ffmpeg/Time.hxx b/src/lib/ffmpeg/Time.hxx new file mode 100644 index 000000000..7f2146016 --- /dev/null +++ b/src/lib/ffmpeg/Time.hxx @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_FFMPEG_TIME_HXX +#define MPD_FFMPEG_TIME_HXX + +#include "Chrono.hxx" +#include "Compiler.h" + +extern "C" { +#include <libavutil/avutil.h> +#include <libavutil/mathematics.h> +} + +#include <assert.h> +#include <stdint.h> + +/* suppress the ffmpeg compatibility macro */ +#ifdef SampleFormat +#undef SampleFormat +#endif + +/** + * Convert a FFmpeg time stamp to a floating point value (in seconds). + */ +gcc_const +static inline double +FfmpegTimeToDouble(int64_t t, const AVRational time_base) +{ + assert(t != (int64_t)AV_NOPTS_VALUE); + + return (double)av_rescale_q(t, time_base, (AVRational){1, 1024}) + / (double)1024; +} + +/** + * Convert a std::ratio to a #AVRational. + */ +template<typename Ratio> +static inline constexpr AVRational +RatioToAVRational() +{ + return { Ratio::num, Ratio::den }; +} + +/** + * Convert a FFmpeg time stamp to a #SongTime. + */ +gcc_const +static inline SongTime +FromFfmpegTime(int64_t t, const AVRational time_base) +{ + assert(t != (int64_t)AV_NOPTS_VALUE); + + return SongTime::FromMS(av_rescale_q(t, time_base, + (AVRational){1, 1000})); +} + +/** + * Convert a FFmpeg time stamp to a #SignedSongTime. + */ +gcc_const +static inline SignedSongTime +FromFfmpegTimeChecked(int64_t t, const AVRational time_base) +{ + return t != (int64_t)AV_NOPTS_VALUE + ? SignedSongTime(FromFfmpegTime(t, time_base)) + : SignedSongTime::Negative(); +} + +/** + * Convert a #SongTime to a FFmpeg time stamp with the given base. + */ +gcc_const +static inline int64_t +ToFfmpegTime(SongTime t, const AVRational time_base) +{ + return av_rescale_q(t.count(), + RatioToAVRational<SongTime::period>(), + time_base); +} + +/** + * Replace #AV_NOPTS_VALUE with the given fallback. + */ +static constexpr int64_t +FfmpegTimestampFallback(int64_t t, int64_t fallback) +{ + return gcc_likely(t != int64_t(AV_NOPTS_VALUE)) + ? t + : fallback; +} + +#endif diff --git a/src/lib/icu/Collate.cxx b/src/lib/icu/Collate.cxx index 17b536b37..f2ffb7b74 100644 --- a/src/lib/icu/Collate.cxx +++ b/src/lib/icu/Collate.cxx @@ -21,6 +21,7 @@ #include "Collate.hxx" #ifdef HAVE_ICU +#include "Util.hxx" #include "Error.hxx" #include "util/WritableBuffer.hxx" #include "util/ConstBuffer.hxx" @@ -71,50 +72,6 @@ IcuCollateFinish() ucol_close(collator); } -static WritableBuffer<UChar> -UCharFromUTF8(const char *src) -{ - assert(src != nullptr); - - const size_t src_length = strlen(src); - const size_t dest_capacity = src_length; - UChar *dest = new UChar[dest_capacity]; - - UErrorCode error_code = U_ZERO_ERROR; - int32_t dest_length; - u_strFromUTF8(dest, dest_capacity, &dest_length, - src, src_length, - &error_code); - if (U_FAILURE(error_code)) { - delete[] dest; - return nullptr; - } - - return { dest, size_t(dest_length) }; -} - -static WritableBuffer<char> -UCharToUTF8(ConstBuffer<UChar> src) -{ - assert(!src.IsNull()); - - /* worst-case estimate */ - size_t dest_capacity = 4 * src.size; - - char *dest = new char[dest_capacity]; - - UErrorCode error_code = U_ZERO_ERROR; - int32_t dest_length; - u_strToUTF8(dest, dest_capacity, &dest_length, src.data, src.size, - &error_code); - if (U_FAILURE(error_code)) { - delete[] dest; - return nullptr; - } - - return { dest, size_t(dest_length) }; -} - #endif gcc_pure diff --git a/src/lib/icu/Converter.cxx b/src/lib/icu/Converter.cxx new file mode 100644 index 000000000..bb170a071 --- /dev/null +++ b/src/lib/icu/Converter.cxx @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Converter.hxx" +#include "Error.hxx" +#include "util/Error.hxx" +#include "util/Macros.hxx" +#include "util/WritableBuffer.hxx" +#include "util/ConstBuffer.hxx" + +#include <string.h> + +#ifdef HAVE_ICU +#include "Util.hxx" +#include <unicode/ucnv.h> +#elif defined(HAVE_GLIB) +#include "util/Domain.hxx" +static constexpr Domain g_iconv_domain("g_iconv"); +#endif + +#ifdef HAVE_ICU + +IcuConverter::~IcuConverter() +{ + ucnv_close(converter); +} + +#endif + +#ifdef HAVE_ICU_CONVERTER + +IcuConverter * +IcuConverter::Create(const char *charset, Error &error) +{ +#ifdef HAVE_ICU + UErrorCode code = U_ZERO_ERROR; + UConverter *converter = ucnv_open(charset, &code); + if (converter == nullptr) { + error.Format(icu_domain, int(code), + "Failed to initialize charset '%s': %s", + charset, u_errorName(code)); + return nullptr; + } + + return new IcuConverter(converter); +#elif defined(HAVE_GLIB) + GIConv to = g_iconv_open("utf-8", charset); + GIConv from = g_iconv_open(charset, "utf-8"); + if (to == (GIConv)-1 || from == (GIConv)-1) { + if (to != (GIConv)-1) + g_iconv_close(to); + if (from != (GIConv)-1) + g_iconv_close(from); + error.Format(g_iconv_domain, + "Failed to initialize charset '%s'", charset); + return nullptr; + } + + return new IcuConverter(to, from); +#endif +} + +#ifdef HAVE_ICU +#elif defined(HAVE_GLIB) + +static std::string +DoConvert(GIConv conv, const char *src) +{ + // TODO: dynamic buffer? + char buffer[4096]; + char *in = const_cast<char *>(src); + char *out = buffer; + size_t in_left = strlen(src); + size_t out_left = sizeof(buffer); + + size_t n = g_iconv(conv, &in, &in_left, &out, &out_left); + + if (n == static_cast<size_t>(-1) || in_left > 0) + return std::string(); + + return std::string(buffer, sizeof(buffer) - out_left); +} + +#endif + +std::string +IcuConverter::ToUTF8(const char *s) const +{ +#ifdef HAVE_ICU + const ScopeLock protect(mutex); + + ucnv_resetToUnicode(converter); + + // TODO: dynamic buffer? + UChar buffer[4096], *target = buffer; + const char *source = s; + + UErrorCode code = U_ZERO_ERROR; + + ucnv_toUnicode(converter, &target, buffer + ARRAY_SIZE(buffer), + &source, source + strlen(source), + nullptr, true, &code); + if (code != U_ZERO_ERROR) + return std::string(); + + const size_t target_length = target - buffer; + const auto u = UCharToUTF8({buffer, target_length}); + if (u.IsNull()) + return std::string(); + + std::string result(u.data, u.size); + delete[] u.data; + return result; + +#elif defined(HAVE_GLIB) + return DoConvert(to_utf8, s); +#endif +} + +std::string +IcuConverter::FromUTF8(const char *s) const +{ +#ifdef HAVE_ICU + const ScopeLock protect(mutex); + + const auto u = UCharFromUTF8(s); + if (u.IsNull()) + return std::string(); + + ucnv_resetFromUnicode(converter); + + // TODO: dynamic buffer? + char buffer[4096], *target = buffer; + const UChar *source = u.data; + UErrorCode code = U_ZERO_ERROR; + + ucnv_fromUnicode(converter, &target, buffer + ARRAY_SIZE(buffer), + &source, u.end(), + nullptr, true, &code); + delete[] u.data; + + if (code != U_ZERO_ERROR) + return std::string(); + + return std::string(buffer, target); + +#elif defined(HAVE_GLIB) + return DoConvert(from_utf8, s); +#endif +} + +#endif diff --git a/src/lib/icu/Converter.hxx b/src/lib/icu/Converter.hxx new file mode 100644 index 000000000..26eccfe94 --- /dev/null +++ b/src/lib/icu/Converter.hxx @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ICU_CONVERTER_HXX +#define MPD_ICU_CONVERTER_HXX + +#include "check.h" +#include "Compiler.h" + +#ifdef HAVE_ICU +#include "thread/Mutex.hxx" +#define HAVE_ICU_CONVERTER +#elif defined(HAVE_GLIB) +#include <glib.h> +#define HAVE_ICU_CONVERTER +#endif + +#ifdef HAVE_ICU_CONVERTER + +#include <string> + +class Error; + +#ifdef HAVE_ICU +struct UConverter; +#endif + +/** + * This class can convert strings with a certain character set to and + * from UTF-8. + */ +class IcuConverter { +#ifdef HAVE_ICU + /** + * ICU's UConverter class is not thread-safe. This mutex + * serializes simultaneous calls. + */ + mutable Mutex mutex; + + UConverter *const converter; + + IcuConverter(UConverter *_converter):converter(_converter) {} +#elif defined(HAVE_GLIB) + const GIConv to_utf8, from_utf8; + + IcuConverter(GIConv _to, GIConv _from) + :to_utf8(_to), from_utf8(_from) {} +#endif + +public: +#ifdef HAVE_ICU + ~IcuConverter(); +#elif defined(HAVE_GLIB) + ~IcuConverter() { + g_iconv_close(to_utf8); + g_iconv_close(from_utf8); + } +#endif + + static IcuConverter *Create(const char *charset, Error &error); + + /** + * Convert the string to UTF-8. + * Returns empty string on error. + */ + gcc_pure gcc_nonnull_all + std::string ToUTF8(const char *s) const; + + /** + * Convert the string from UTF-8. + * Returns empty string on error. + */ + gcc_pure gcc_nonnull_all + std::string FromUTF8(const char *s) const; +}; + +#endif + +#endif diff --git a/src/lib/icu/Util.cxx b/src/lib/icu/Util.cxx new file mode 100644 index 000000000..a18043c03 --- /dev/null +++ b/src/lib/icu/Util.cxx @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Util.hxx" +#include "util/WritableBuffer.hxx" +#include "util/ConstBuffer.hxx" + +#include <unicode/ustring.h> + +#include <assert.h> +#include <string.h> + +WritableBuffer<UChar> +UCharFromUTF8(const char *src) +{ + assert(src != nullptr); + + const size_t src_length = strlen(src); + const size_t dest_capacity = src_length; + UChar *dest = new UChar[dest_capacity]; + + UErrorCode error_code = U_ZERO_ERROR; + int32_t dest_length; + u_strFromUTF8(dest, dest_capacity, &dest_length, + src, src_length, + &error_code); + if (U_FAILURE(error_code)) { + delete[] dest; + return nullptr; + } + + return { dest, size_t(dest_length) }; +} + +WritableBuffer<char> +UCharToUTF8(ConstBuffer<UChar> src) +{ + assert(!src.IsNull()); + + /* worst-case estimate */ + size_t dest_capacity = 4 * src.size; + + char *dest = new char[dest_capacity]; + + UErrorCode error_code = U_ZERO_ERROR; + int32_t dest_length; + u_strToUTF8(dest, dest_capacity, &dest_length, src.data, src.size, + &error_code); + if (U_FAILURE(error_code)) { + delete[] dest; + return nullptr; + } + + return { dest, size_t(dest_length) }; +} diff --git a/src/lib/icu/Util.hxx b/src/lib/icu/Util.hxx new file mode 100644 index 000000000..ce80bb3fd --- /dev/null +++ b/src/lib/icu/Util.hxx @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_ICU_UTIL_HXX +#define MPD_ICU_UTIL_HXX + +#include "check.h" + +#include <unicode/utypes.h> + +template<typename T> struct WritableBuffer; +template<typename T> struct ConstBuffer; + +/** + * Wrapper for u_strFromUTF8(). The returned pointer must be freed + * with delete[]. + */ +WritableBuffer<UChar> +UCharFromUTF8(const char *src); + +/** + * Wrapper for u_strToUTF8(). The returned pointer must be freed with + * delete[]. + */ +WritableBuffer<char> +UCharToUTF8(ConstBuffer<UChar> src); + +#endif diff --git a/src/lib/sqlite/Domain.cxx b/src/lib/sqlite/Domain.cxx new file mode 100644 index 000000000..82c99d718 --- /dev/null +++ b/src/lib/sqlite/Domain.cxx @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "Domain.hxx" +#include "util/Domain.hxx" + +const Domain sqlite_domain("sqlite"); diff --git a/src/lib/sqlite/Domain.hxx b/src/lib/sqlite/Domain.hxx new file mode 100644 index 000000000..12d8ccf72 --- /dev/null +++ b/src/lib/sqlite/Domain.hxx @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SQLITE_DOMAIN_HXX +#define MPD_SQLITE_DOMAIN_HXX + +class Domain; + +extern const Domain sqlite_domain; + +#endif diff --git a/src/lib/sqlite/Util.hxx b/src/lib/sqlite/Util.hxx new file mode 100644 index 000000000..da74d1c3c --- /dev/null +++ b/src/lib/sqlite/Util.hxx @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_SQLITE_UTIL_HXX +#define MPD_SQLITE_UTIL_HXX + +#include "Domain.hxx" +#include "util/Error.hxx" + +#include <sqlite3.h> + +#include <assert.h> + +static void +SetError(Error &error, sqlite3 *db, int code, const char *msg) +{ + error.Format(sqlite_domain, code, "%s: %s", + msg, sqlite3_errmsg(db)); +} + +static void +SetError(Error &error, sqlite3_stmt *stmt, int code, const char *msg) +{ + SetError(error, sqlite3_db_handle(stmt), code, msg); +} + +static bool +Bind(sqlite3_stmt *stmt, unsigned i, const char *value, Error &error) +{ + int result = sqlite3_bind_text(stmt, i, value, -1, nullptr); + if (result != SQLITE_OK) { + SetError(error, stmt, result, "sqlite3_bind_text() failed"); + return false; + } + + return true; +} + +template<typename... Args> +static bool +BindAll2(gcc_unused Error &error, gcc_unused sqlite3_stmt *stmt, + gcc_unused unsigned i) +{ + assert(int(i - 1) == sqlite3_bind_parameter_count(stmt)); + + return true; +} + +template<typename... Args> +static bool +BindAll2(Error &error, sqlite3_stmt *stmt, unsigned i, + const char *value, Args&&... args) +{ + return Bind(stmt, i, value, error) && + BindAll2(error, stmt, i + 1, std::forward<Args>(args)...); +} + +template<typename... Args> +static bool +BindAll(Error &error, sqlite3_stmt *stmt, Args&&... args) +{ + assert(int(sizeof...(args)) == sqlite3_bind_parameter_count(stmt)); + + return BindAll2(error, stmt, 1, std::forward<Args>(args)...); +} + +/** + * Wrapper for BindAll() that returns the specified sqlite3_stmt* on + * success and nullptr on error. + */ +template<typename... Args> +static sqlite3_stmt * +BindAllOrNull(Error &error, sqlite3_stmt *stmt, Args&&... args) +{ + return BindAll(error, stmt, std::forward<Args>(args)...) + ? stmt + : nullptr; +} + +/** + * Call sqlite3_stmt() repepatedly until something other than + * SQLITE_BUSY is returned. + */ +static int +ExecuteBusy(sqlite3_stmt *stmt) +{ + int result; + do { + result = sqlite3_step(stmt); + } while (result == SQLITE_BUSY); + + return result; +} + +/** + * Wrapper for ExecuteBusy() that returns true on SQLITE_ROW. + */ +static bool +ExecuteRow(sqlite3_stmt *stmt, Error &error) +{ + int result = ExecuteBusy(stmt); + if (result == SQLITE_ROW) + return true; + + if (result != SQLITE_DONE) + SetError(error, stmt, result, "sqlite3_step() failed"); + + return false; +} + +/** + * Wrapper for ExecuteBusy() that interprets everything other than + * SQLITE_DONE as error. + */ +static bool +ExecuteCommand(sqlite3_stmt *stmt, Error &error) +{ + int result = ExecuteBusy(stmt); + if (result != SQLITE_DONE) { + SetError(error, stmt, result, "sqlite3_step() failed"); + return false; + } + + return true; +} + +/** + * Wrapper for ExecuteCommand() that returns the number of rows + * modified via sqlite3_changes(). Returns -1 on error. + */ +static inline int +ExecuteChanges(sqlite3_stmt *stmt, Error &error) +{ + if (!ExecuteCommand(stmt, error)) + return -1; + + return sqlite3_changes(sqlite3_db_handle(stmt)); +} + +/** + * Wrapper for ExecuteChanges() that returns true if at least one row + * was modified. Returns false if nothing was modified or if an error + * occurred. + */ +static inline bool +ExecuteModified(sqlite3_stmt *stmt, Error &error) +{ + return ExecuteChanges(stmt, error) > 0; +} + +template<typename F> +static inline bool +ExecuteForEach(sqlite3_stmt *stmt, Error &error, F &&f) +{ + while (true) { + int result = ExecuteBusy(stmt); + switch (result) { + case SQLITE_ROW: + f(); + break; + + case SQLITE_DONE: + return true; + + default: + SetError(error, stmt, result, "sqlite3_step() failed"); + return false; + } + } +} + +#endif diff --git a/src/ls.cxx b/src/ls.cxx index 96c9f60e5..fe70cdc11 100644 --- a/src/ls.cxx +++ b/src/ls.cxx @@ -30,7 +30,7 @@ * is detected at runtime and displayed as a urlhandler if the client is * connected by IPC socket. */ -static const char *remoteUrlPrefixes[] = { +static const char *const remoteUrlPrefixes[] = { #if defined(ENABLE_CURL) "http://", "https://", @@ -41,7 +41,7 @@ static const char *remoteUrlPrefixes[] = { "mmst://", "mmsu://", #endif -#ifdef HAVE_FFMPEG +#ifdef ENABLE_FFMPEG "gopher://", "rtp://", "rtsp://", @@ -61,7 +61,7 @@ static const char *remoteUrlPrefixes[] = { #ifdef ENABLE_DESPOTIFY "spt://", #endif -#ifdef HAVE_ALSA +#ifdef ENABLE_ALSA "alsa://", #endif NULL @@ -69,7 +69,7 @@ static const char *remoteUrlPrefixes[] = { void print_supported_uri_schemes_to_fp(FILE *fp) { - const char **prefixes = remoteUrlPrefixes; + const char *const*prefixes = remoteUrlPrefixes; #ifdef HAVE_UN fprintf(fp, " file://"); @@ -83,7 +83,7 @@ void print_supported_uri_schemes_to_fp(FILE *fp) void print_supported_uri_schemes(Client &client) { - const char **prefixes = remoteUrlPrefixes; + const char *const *prefixes = remoteUrlPrefixes; while (*prefixes) { client_printf(client, "handler: %s\n", *prefixes); @@ -93,7 +93,7 @@ void print_supported_uri_schemes(Client &client) bool uri_supported_scheme(const char *uri) { - const char **urlPrefixes = remoteUrlPrefixes; + const char *const*urlPrefixes = remoteUrlPrefixes; assert(uri_has_scheme(uri)); diff --git a/src/mixer/MixerList.hxx b/src/mixer/MixerList.hxx index e75b2e6ff..8be74adf2 100644 --- a/src/mixer/MixerList.hxx +++ b/src/mixer/MixerList.hxx @@ -27,6 +27,7 @@ struct MixerPlugin; +extern const MixerPlugin null_mixer_plugin; extern const MixerPlugin software_mixer_plugin; extern const MixerPlugin alsa_mixer_plugin; extern const MixerPlugin oss_mixer_plugin; diff --git a/src/mixer/MixerType.cxx b/src/mixer/MixerType.cxx index cd45db0d9..8972118e5 100644 --- a/src/mixer/MixerType.cxx +++ b/src/mixer/MixerType.cxx @@ -23,17 +23,19 @@ #include <assert.h> #include <string.h> -enum mixer_type +MixerType mixer_type_parse(const char *input) { assert(input != NULL); if (strcmp(input, "none") == 0 || strcmp(input, "disabled") == 0) - return MIXER_TYPE_NONE; + return MixerType::NONE; else if (strcmp(input, "hardware") == 0) - return MIXER_TYPE_HARDWARE; + return MixerType::HARDWARE; else if (strcmp(input, "software") == 0) - return MIXER_TYPE_SOFTWARE; + return MixerType::SOFTWARE; + else if (strcmp(input, "null") == 0) + return MixerType::NULL_; else - return MIXER_TYPE_UNKNOWN; + return MixerType::UNKNOWN; } diff --git a/src/mixer/MixerType.hxx b/src/mixer/MixerType.hxx index bfa2637b7..40f60203d 100644 --- a/src/mixer/MixerType.hxx +++ b/src/mixer/MixerType.hxx @@ -20,28 +20,31 @@ #ifndef MPD_MIXER_TYPE_HXX #define MPD_MIXER_TYPE_HXX -enum mixer_type { +enum class MixerType { /** parser error */ - MIXER_TYPE_UNKNOWN, + UNKNOWN, /** mixer disabled */ - MIXER_TYPE_NONE, + NONE, + + /** "null" mixer (virtual fake) */ + NULL_, /** software mixer with pcm_volume() */ - MIXER_TYPE_SOFTWARE, + SOFTWARE, /** hardware mixer (output's plugin) */ - MIXER_TYPE_HARDWARE, + HARDWARE, }; /** - * Parses a "mixer_type" setting from the configuration file. + * Parses a #MixerType setting from the configuration file. * - * @param input the configured string value; must not be NULL - * @return a #mixer_type value; MIXER_TYPE_UNKNOWN means #input could - * not be parsed + * @param input the configured string value; must not be NULL @return + * a #MixerType value; #MixerType::UNKNOWN means #input could not be + * parsed */ -enum mixer_type +MixerType mixer_type_parse(const char *input); #endif diff --git a/src/mixer/plugins/NullMixerPlugin.cxx b/src/mixer/plugins/NullMixerPlugin.cxx new file mode 100644 index 000000000..b8894d443 --- /dev/null +++ b/src/mixer/plugins/NullMixerPlugin.cxx @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "mixer/MixerInternal.hxx" + +class NullMixer final : public Mixer { + /** + * The current volume in percent (0..100). + */ + unsigned volume; + +public: + NullMixer(MixerListener &_listener) + :Mixer(null_mixer_plugin, _listener), + volume(100) + { + } + + /* virtual methods from class Mixer */ + bool Open(gcc_unused Error &error) override { + return true; + } + + void Close() override { + } + + int GetVolume(gcc_unused Error &error) override { + return volume; + } + + bool SetVolume(unsigned _volume, gcc_unused Error &error) override { + volume = _volume; + return true; + } +}; + +static Mixer * +null_mixer_init(gcc_unused EventLoop &event_loop, + gcc_unused AudioOutput &ao, + MixerListener &listener, + gcc_unused const config_param ¶m, + gcc_unused Error &error) +{ + return new NullMixer(listener); +} + +const MixerPlugin null_mixer_plugin = { + null_mixer_init, + true, +}; diff --git a/src/neighbor/Registry.cxx b/src/neighbor/Registry.cxx index f6d1f97b3..6f0651423 100644 --- a/src/neighbor/Registry.cxx +++ b/src/neighbor/Registry.cxx @@ -29,7 +29,7 @@ const NeighborPlugin *const neighbor_plugins[] = { #ifdef ENABLE_SMBCLIENT &smbclient_neighbor_plugin, #endif -#ifdef HAVE_LIBUPNP +#ifdef ENABLE_UPNP &upnp_neighbor_plugin, #endif nullptr diff --git a/src/neighbor/plugins/SmbclientNeighborPlugin.cxx b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx index 2701b0ccd..0e31c6e52 100644 --- a/src/neighbor/plugins/SmbclientNeighborPlugin.cxx +++ b/src/neighbor/plugins/SmbclientNeighborPlugin.cxx @@ -216,7 +216,7 @@ SmbclientNeighborExplorer::Run() prev = i; } else { /* can't see it anymore: move to "lost" */ -#if defined(__clang__) || GCC_CHECK_VERSION(4,7) +#if CLANG_OR_GCC_VERSION(4,7) lost.splice_after(lost.before_begin(), list, prev); #else /* the forward_list::splice_after() lvalue diff --git a/src/output/Init.cxx b/src/output/Init.cxx index 79ef4f998..f9648e0d4 100644 --- a/src/output/Init.cxx +++ b/src/output/Init.cxx @@ -58,7 +58,7 @@ AudioOutput::AudioOutput(const AudioOutputPlugin &_plugin) filter(nullptr), replay_gain_filter(nullptr), other_replay_gain_filter(nullptr), - command(AO_COMMAND_NONE) + command(Command::NONE) { assert(plugin.finish != nullptr); assert(plugin.open != nullptr); @@ -94,7 +94,7 @@ audio_output_detect(Error &error) * mixer_enabled, if the mixer_type setting is not configured. */ gcc_pure -static enum mixer_type +static MixerType audio_output_mixer_type(const config_param ¶m) { /* read the local "mixer_type" setting */ @@ -104,7 +104,7 @@ audio_output_mixer_type(const config_param ¶m) /* try the local "mixer_enabled" setting next (deprecated) */ if (!param.GetBlockValue("mixer_enabled", true)) - return MIXER_TYPE_NONE; + return MixerType::NONE; /* fall back to the global "mixer_type" setting (also deprecated) */ @@ -123,18 +123,22 @@ audio_output_load_mixer(EventLoop &event_loop, AudioOutput &ao, Mixer *mixer; switch (audio_output_mixer_type(param)) { - case MIXER_TYPE_NONE: - case MIXER_TYPE_UNKNOWN: + case MixerType::NONE: + case MixerType::UNKNOWN: return nullptr; - case MIXER_TYPE_HARDWARE: + case MixerType::NULL_: + return mixer_new(event_loop, null_mixer_plugin, ao, listener, + param, error); + + case MixerType::HARDWARE: if (plugin == nullptr) return nullptr; return mixer_new(event_loop, *plugin, ao, listener, param, error); - case MIXER_TYPE_SOFTWARE: + case MixerType::SOFTWARE: mixer = mixer_new(event_loop, software_mixer_plugin, ao, listener, config_param(), diff --git a/src/output/Internal.hxx b/src/output/Internal.hxx index 6e6ffb442..d2ca39585 100644 --- a/src/output/Internal.hxx +++ b/src/output/Internal.hxx @@ -40,32 +40,32 @@ struct config_param; struct PlayerControl; struct AudioOutputPlugin; -enum audio_output_command { - AO_COMMAND_NONE = 0, - AO_COMMAND_ENABLE, - AO_COMMAND_DISABLE, - AO_COMMAND_OPEN, +struct AudioOutput { + enum class Command { + NONE, + ENABLE, + DISABLE, + OPEN, - /** - * This command is invoked when the input audio format - * changes. - */ - AO_COMMAND_REOPEN, + /** + * This command is invoked when the input audio format + * changes. + */ + REOPEN, - AO_COMMAND_CLOSE, - AO_COMMAND_PAUSE, + CLOSE, + PAUSE, - /** - * Drains the internal (hardware) buffers of the device. This - * operation may take a while to complete. - */ - AO_COMMAND_DRAIN, + /** + * Drains the internal (hardware) buffers of the device. This + * operation may take a while to complete. + */ + DRAIN, - AO_COMMAND_CANCEL, - AO_COMMAND_KILL -}; + CANCEL, + KILL + }; -struct AudioOutput { /** * The device's configured display name. */ @@ -231,7 +231,7 @@ struct AudioOutput { /** * The next command to be performed by the output thread. */ - enum audio_output_command command; + Command command; /** * The music pipe which provides music chunks to be played. @@ -284,7 +284,7 @@ struct AudioOutput { } bool IsCommandFinished() const { - return command == AO_COMMAND_NONE; + return command == Command::NONE; } /** @@ -299,7 +299,7 @@ struct AudioOutput { * * Caller must lock the mutex. */ - void CommandAsync(audio_output_command cmd); + void CommandAsync(Command cmd); /** * Sends a command to the #AudioOutput object and waits for @@ -307,13 +307,13 @@ struct AudioOutput { * * Caller must lock the mutex. */ - void CommandWait(audio_output_command cmd); + void CommandWait(Command cmd); /** * Lock the #AudioOutput object and execute the command * synchronously. */ - void LockCommandWait(audio_output_command cmd); + void LockCommandWait(Command cmd); /** * Enables the device. diff --git a/src/output/OutputControl.cxx b/src/output/OutputControl.cxx index 89428fa87..7581d4d58 100644 --- a/src/output/OutputControl.cxx +++ b/src/output/OutputControl.cxx @@ -46,7 +46,7 @@ AudioOutput::WaitForCommand() } void -AudioOutput::CommandAsync(audio_output_command cmd) +AudioOutput::CommandAsync(Command cmd) { assert(IsCommandFinished()); @@ -55,14 +55,14 @@ AudioOutput::CommandAsync(audio_output_command cmd) } void -AudioOutput::CommandWait(audio_output_command cmd) +AudioOutput::CommandWait(Command cmd) { CommandAsync(cmd); WaitForCommand(); } void -AudioOutput::LockCommandWait(audio_output_command cmd) +AudioOutput::LockCommandWait(Command cmd) { const ScopeLock protect(mutex); CommandWait(cmd); @@ -92,7 +92,7 @@ AudioOutput::LockEnableWait() StartThread(); } - LockCommandWait(AO_COMMAND_ENABLE); + LockCommandWait(Command::ENABLE); } void @@ -109,7 +109,7 @@ AudioOutput::LockDisableWait() return; } - LockCommandWait(AO_COMMAND_DISABLE); + LockCommandWait(Command::DISABLE); } inline bool @@ -134,7 +134,7 @@ AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp) /* we're not using audio_output_cancel() here, because that function is asynchronous */ - CommandWait(AO_COMMAND_CANCEL); + CommandWait(Command::CANCEL); } return true; @@ -148,7 +148,9 @@ AudioOutput::Open(const AudioFormat audio_format, const MusicPipe &mp) if (!thread.IsDefined()) StartThread(); - CommandWait(open ? AO_COMMAND_REOPEN : AO_COMMAND_OPEN); + CommandWait(open + ? Command::REOPEN + : Command::OPEN); const bool open2 = open; if (open2 && mixer != nullptr) { @@ -172,7 +174,7 @@ AudioOutput::CloseWait() assert(!open || !fail_timer.IsDefined()); if (open) - CommandWait(AO_COMMAND_CLOSE); + CommandWait(Command::CLOSE); else fail_timer.Reset(); } @@ -219,7 +221,7 @@ AudioOutput::LockPauseAsync() assert(allow_play); if (IsOpen()) - CommandAsync(AO_COMMAND_PAUSE); + CommandAsync(Command::PAUSE); } void @@ -229,7 +231,7 @@ AudioOutput::LockDrainAsync() assert(allow_play); if (IsOpen()) - CommandAsync(AO_COMMAND_DRAIN); + CommandAsync(Command::DRAIN); } void @@ -239,7 +241,7 @@ AudioOutput::LockCancelAsync() if (IsOpen()) { allow_play = false; - CommandAsync(AO_COMMAND_CANCEL); + CommandAsync(Command::CANCEL); } } @@ -277,7 +279,7 @@ AudioOutput::StopThread() assert(thread.IsDefined()); assert(allow_play); - LockCommandWait(AO_COMMAND_KILL); + LockCommandWait(Command::KILL); thread.Join(); } diff --git a/src/output/OutputThread.cxx b/src/output/OutputThread.cxx index 2ec0670c1..eb9277d04 100644 --- a/src/output/OutputThread.cxx +++ b/src/output/OutputThread.cxx @@ -45,8 +45,8 @@ void AudioOutput::CommandFinished() { - assert(command != AO_COMMAND_NONE); - command = AO_COMMAND_NONE; + assert(command != Command::NONE); + command = Command::NONE; mutex.unlock(); audio_output_client_notify.Signal(); @@ -342,7 +342,7 @@ AudioOutput::WaitForDelay() (void)cond.timed_wait(mutex, delay); - if (command != AO_COMMAND_NONE) + if (command != Command::NONE) return false; } } @@ -471,7 +471,7 @@ AudioOutput::PlayChunk(const MusicChunk *chunk) Error error; - while (!data.IsEmpty() && command == AO_COMMAND_NONE) { + while (!data.IsEmpty() && command == Command::NONE) { if (!WaitForDelay()) break; @@ -529,7 +529,7 @@ AudioOutput::Play() assert(!in_playback_loop); in_playback_loop = true; - while (chunk != nullptr && command == AO_COMMAND_NONE) { + while (chunk != nullptr && command == Command::NONE) { assert(!current_chunk_finished); current_chunk = chunk; @@ -577,7 +577,7 @@ AudioOutput::Pause() Close(false); break; } - } while (command == AO_COMMAND_NONE); + } while (command == Command::NONE); pause = false; } @@ -594,30 +594,30 @@ AudioOutput::Task() while (1) { switch (command) { - case AO_COMMAND_NONE: + case Command::NONE: break; - case AO_COMMAND_ENABLE: + case Command::ENABLE: Enable(); CommandFinished(); break; - case AO_COMMAND_DISABLE: + case Command::DISABLE: Disable(); CommandFinished(); break; - case AO_COMMAND_OPEN: + case Command::OPEN: Open(); CommandFinished(); break; - case AO_COMMAND_REOPEN: + case Command::REOPEN: Reopen(); CommandFinished(); break; - case AO_COMMAND_CLOSE: + case Command::CLOSE: assert(open); assert(pipe != nullptr); @@ -625,7 +625,7 @@ AudioOutput::Task() CommandFinished(); break; - case AO_COMMAND_PAUSE: + case Command::PAUSE: if (!open) { /* the output has failed after audio_output_all_pause() has @@ -642,7 +642,7 @@ AudioOutput::Task() the new command first */ continue; - case AO_COMMAND_DRAIN: + case Command::DRAIN: if (open) { assert(current_chunk == nullptr); assert(pipe->Peek() == nullptr); @@ -655,7 +655,7 @@ AudioOutput::Task() CommandFinished(); continue; - case AO_COMMAND_CANCEL: + case Command::CANCEL: current_chunk = nullptr; if (open) { @@ -667,7 +667,7 @@ AudioOutput::Task() CommandFinished(); continue; - case AO_COMMAND_KILL: + case Command::KILL: current_chunk = nullptr; CommandFinished(); mutex.unlock(); @@ -679,7 +679,7 @@ AudioOutput::Task() chunks in the pipe */ continue; - if (command == AO_COMMAND_NONE) { + if (command == Command::NONE) { woken_for_play = false; cond.wait(mutex); } @@ -696,7 +696,7 @@ AudioOutput::Task(void *arg) void AudioOutput::StartThread() { - assert(command == AO_COMMAND_NONE); + assert(command == Command::NONE); Error error; if (!thread.Start(Task, this, error)) diff --git a/src/output/Registry.cxx b/src/output/Registry.cxx index 566f6b6a8..2ce844179 100644 --- a/src/output/Registry.cxx +++ b/src/output/Registry.cxx @@ -54,13 +54,13 @@ const AudioOutputPlugin *const audio_output_plugins[] = { #ifdef ENABLE_PIPE_OUTPUT &pipe_output_plugin, #endif -#ifdef HAVE_ALSA +#ifdef ENABLE_ALSA &alsa_output_plugin, #endif -#ifdef HAVE_ROAR +#ifdef ENABLE_ROAR &roar_output_plugin, #endif -#ifdef HAVE_AO +#ifdef ENABLE_AO &ao_output_plugin, #endif #ifdef HAVE_OSS @@ -75,10 +75,10 @@ const AudioOutputPlugin *const audio_output_plugins[] = { #ifdef ENABLE_SOLARIS_OUTPUT &solaris_output_plugin, #endif -#ifdef HAVE_PULSE +#ifdef ENABLE_PULSE &pulse_output_plugin, #endif -#ifdef HAVE_JACK +#ifdef ENABLE_JACK &jack_output_plugin, #endif #ifdef ENABLE_HTTPD_OUTPUT diff --git a/src/output/plugins/AoOutputPlugin.cxx b/src/output/plugins/AoOutputPlugin.cxx index af8c88fa1..4b98f4a77 100644 --- a/src/output/plugins/AoOutputPlugin.cxx +++ b/src/output/plugins/AoOutputPlugin.cxx @@ -20,12 +20,13 @@ #include "config.h" #include "AoOutputPlugin.hxx" #include "../OutputAPI.hxx" +#include "util/DivideString.hxx" +#include "util/SplitString.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" #include <ao/ao.h> -#include <glib.h> #include <string.h> @@ -126,25 +127,18 @@ AoOutput::Configure(const config_param ¶m, Error &error) value = param.GetBlockValue("options", nullptr); if (value != nullptr) { - gchar **_options = g_strsplit(value, ";", 0); + for (const auto &i : SplitString(value, ';')) { + const DivideString ss(i.c_str(), '=', true); - for (unsigned i = 0; _options[i] != nullptr; ++i) { - gchar **key_value = g_strsplit(_options[i], "=", 2); - - if (key_value[0] == nullptr || key_value[1] == nullptr) { + if (!ss.IsDefined()) { error.Format(ao_output_domain, "problems parsing options \"%s\"", - _options[i]); + i.c_str()); return false; } - ao_append_option(&options, key_value[0], - key_value[1]); - - g_strfreev(key_value); + ao_append_option(&options, ss.GetFirst(), ss.GetSecond()); } - - g_strfreev(_options); } return true; diff --git a/src/output/plugins/JackOutputPlugin.cxx b/src/output/plugins/JackOutputPlugin.cxx index e1dad7893..ba05e6c5e 100644 --- a/src/output/plugins/JackOutputPlugin.cxx +++ b/src/output/plugins/JackOutputPlugin.cxx @@ -21,25 +21,25 @@ #include "JackOutputPlugin.hxx" #include "../OutputAPI.hxx" #include "config/ConfigError.hxx" +#include "util/ConstBuffer.hxx" +#include "util/SplitString.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" #include <assert.h> -#include <glib.h> #include <jack/jack.h> #include <jack/types.h> #include <jack/ringbuffer.h> +#include <unistd.h> /* for usleep() */ #include <stdlib.h> #include <string.h> -enum { - MAX_PORTS = 16, -}; +static constexpr unsigned MAX_PORTS = 16; -static const size_t jack_sample_size = sizeof(jack_default_audio_sample_t); +static constexpr size_t jack_sample_size = sizeof(jack_default_audio_sample_t); struct JackOutput { AudioOutput base; @@ -55,10 +55,10 @@ struct JackOutput { /* configuration */ - char *source_ports[MAX_PORTS]; + std::string source_ports[MAX_PORTS]; unsigned num_source_ports; - char *destination_ports[MAX_PORTS]; + std::string destination_ports[MAX_PORTS]; unsigned num_destination_ports; size_t ringbuffer_size; @@ -82,24 +82,53 @@ struct JackOutput { JackOutput() :base(jack_output_plugin) {} - bool Initialize(const config_param ¶m, Error &error_r) { - return base.Configure(param, error_r); + bool Configure(const config_param ¶m, Error &error); + + bool Connect(Error &error); + + /** + * Disconnect the JACK client. + */ + void Disconnect(); + + void Shutdown() { + shutdown = true; } + + bool Enable(Error &error); + void Disable(); + + bool Open(AudioFormat &new_audio_format, Error &error); + + bool Start(Error &error); + void Stop(); + + /** + * Determine the number of frames guaranteed to be available + * on all channels. + */ + gcc_pure + jack_nframes_t GetAvailable() const; + + void Process(jack_nframes_t nframes); + + /** + * @return the number of frames that were written + */ + size_t WriteSamples(const float *src, size_t n_frames); + + size_t Play(const void *chunk, size_t size, Error &error); }; static constexpr Domain jack_output_domain("jack_output"); -/** - * Determine the number of frames guaranteed to be available on all - * channels. - */ -static jack_nframes_t -mpd_jack_available(const JackOutput *jd) +inline jack_nframes_t +JackOutput::GetAvailable() const { - size_t min = jack_ringbuffer_read_space(jd->ringbuffer[0]); + size_t min = jack_ringbuffer_read_space(ringbuffer[0]); - for (unsigned i = 1; i < jd->audio_format.channels; ++i) { - size_t current = jack_ringbuffer_read_space(jd->ringbuffer[i]); + for (unsigned i = 1; i < audio_format.channels; ++i) { + size_t current = jack_ringbuffer_read_space(ringbuffer[i]); if (current < min) min = current; } @@ -109,85 +138,121 @@ mpd_jack_available(const JackOutput *jd) return min / jack_sample_size; } -static int -mpd_jack_process(jack_nframes_t nframes, void *arg) +/** + * Call jack_ringbuffer_read_advance() on all buffers in the list. + */ +static void +MultiReadAdvance(ConstBuffer<jack_ringbuffer_t *> buffers, + size_t size) { - JackOutput *jd = (JackOutput *) arg; + for (auto *i : buffers) + jack_ringbuffer_read_advance(i, size); +} + +/** + * Write a specific amount of "silence" to the given port. + */ +static void +WriteSilence(jack_port_t &port, jack_nframes_t nframes) +{ + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(&port, nframes); + if (out == nullptr) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is invoked + anyway, but unable to get a buffer */ + return; + + std::fill_n(out, nframes, 0.0); +} + +/** + * Write a specific amount of "silence" to all ports in the list. + */ +static void +MultiWriteSilence(ConstBuffer<jack_port_t *> ports, jack_nframes_t nframes) +{ + for (auto *i : ports) + WriteSilence(*i, nframes); +} + +/** + * Copy data from the buffer to the port. If the buffer underruns, + * fill with silence. + */ +static void +Copy(jack_port_t &dest, jack_nframes_t nframes, + jack_ringbuffer_t &src, jack_nframes_t available) +{ + jack_default_audio_sample_t *out = + (jack_default_audio_sample_t *) + jack_port_get_buffer(&dest, nframes); + if (out == nullptr) + /* workaround for libjack1 bug: if the server + connection fails, the process callback is + invoked anyway, but unable to get a + buffer */ + return; + /* copy from buffer to port */ + jack_ringbuffer_read(&src, (char *)out, + available * jack_sample_size); + + /* ringbuffer underrun, fill with silence */ + std::fill(out + available, out + nframes, 0.0); +} + +inline void +JackOutput::Process(jack_nframes_t nframes) +{ if (nframes <= 0) - return 0; + return; - if (jd->pause) { + jack_nframes_t available = GetAvailable(); + + const unsigned n_channels = audio_format.channels; + + if (pause) { /* empty the ring buffers */ - const jack_nframes_t available = mpd_jack_available(jd); - for (unsigned i = 0; i < jd->audio_format.channels; ++i) - jack_ringbuffer_read_advance(jd->ringbuffer[i], - available * jack_sample_size); + MultiReadAdvance({ringbuffer, n_channels}, + available * jack_sample_size); /* generate silence while MPD is paused */ - for (unsigned i = 0; i < jd->audio_format.channels; ++i) { - jack_default_audio_sample_t *out = - (jack_default_audio_sample_t *) - jack_port_get_buffer(jd->ports[i], nframes); + MultiWriteSilence({ports, n_channels}, nframes); - for (jack_nframes_t f = 0; f < nframes; ++f) - out[f] = 0.0; - } - - return 0; + return; } - jack_nframes_t available = mpd_jack_available(jd); if (available > nframes) available = nframes; - for (unsigned i = 0; i < jd->audio_format.channels; ++i) { - jack_default_audio_sample_t *out = - (jack_default_audio_sample_t *) - jack_port_get_buffer(jd->ports[i], nframes); - if (out == nullptr) - /* workaround for libjack1 bug: if the server - connection fails, the process callback is - invoked anyway, but unable to get a - buffer */ - continue; - - jack_ringbuffer_read(jd->ringbuffer[i], - (char *)out, available * jack_sample_size); - - for (jack_nframes_t f = available; f < nframes; ++f) - /* ringbuffer underrun, fill with silence */ - out[f] = 0.0; - } + for (unsigned i = 0; i < n_channels; ++i) + Copy(*ports[i], nframes, *ringbuffer[i], available); /* generate silence for the unused source ports */ - for (unsigned i = jd->audio_format.channels; - i < jd->num_source_ports; ++i) { - jack_default_audio_sample_t *out = - (jack_default_audio_sample_t *) - jack_port_get_buffer(jd->ports[i], nframes); - if (out == nullptr) - /* workaround for libjack1 bug: if the server - connection fails, the process callback is - invoked anyway, but unable to get a - buffer */ - continue; - - for (jack_nframes_t f = 0; f < nframes; ++f) - out[f] = 0.0; - } + MultiWriteSilence({ports + n_channels, num_source_ports - n_channels}, + nframes); +} +static int +mpd_jack_process(jack_nframes_t nframes, void *arg) +{ + JackOutput &jo = *(JackOutput *) arg; + + jo.Process(nframes); return 0; } static void mpd_jack_shutdown(void *arg) { - JackOutput *jd = (JackOutput *) arg; - jd->shutdown = true; + JackOutput &jo = *(JackOutput *) arg; + + jo.Shutdown(); } static void @@ -200,9 +265,10 @@ set_audioformat(JackOutput *jd, AudioFormat &audio_format) else if (audio_format.channels > jd->num_source_ports) audio_format.channels = 2; - if (audio_format.format != SampleFormat::S16 && - audio_format.format != SampleFormat::S24_P32) - audio_format.format = SampleFormat::S24_P32; + /* JACK uses 32 bit float in the range [-1 .. 1] - just like + MPD's SampleFormat::FLOAT*/ + static_assert(jack_sample_size == sizeof(float), "Expected float32"); + audio_format.format = SampleFormat::FLOAT; } static void @@ -219,55 +285,47 @@ mpd_jack_info(const char *msg) } #endif -/** - * Disconnect the JACK client. - */ -static void -mpd_jack_disconnect(JackOutput *jd) +void +JackOutput::Disconnect() { - assert(jd != nullptr); - assert(jd->client != nullptr); + assert(client != nullptr); - jack_deactivate(jd->client); - jack_client_close(jd->client); - jd->client = nullptr; + jack_deactivate(client); + jack_client_close(client); + client = nullptr; } /** * Connect the JACK client and performs some basic setup * (e.g. register callbacks). */ -static bool -mpd_jack_connect(JackOutput *jd, Error &error) +bool +JackOutput::Connect(Error &error) { - jack_status_t status; + shutdown = false; - assert(jd != nullptr); - - jd->shutdown = false; - - jd->client = jack_client_open(jd->name, jd->options, &status, - jd->server_name); - if (jd->client == nullptr) { + jack_status_t status; + client = jack_client_open(name, options, &status, server_name); + if (client == nullptr) { error.Format(jack_output_domain, status, "Failed to connect to JACK server, status=%d", status); return false; } - jack_set_process_callback(jd->client, mpd_jack_process, jd); - jack_on_shutdown(jd->client, mpd_jack_shutdown, jd); + jack_set_process_callback(client, mpd_jack_process, this); + jack_on_shutdown(client, mpd_jack_shutdown, this); - for (unsigned i = 0; i < jd->num_source_ports; ++i) { - jd->ports[i] = jack_port_register(jd->client, - jd->source_ports[i], - JACK_DEFAULT_AUDIO_TYPE, - JackPortIsOutput, 0); - if (jd->ports[i] == nullptr) { + for (unsigned i = 0; i < num_source_ports; ++i) { + ports[i] = jack_port_register(client, + source_ports[i].c_str(), + JACK_DEFAULT_AUDIO_TYPE, + JackPortIsOutput, 0); + if (ports[i] == nullptr) { error.Format(jack_output_domain, "Cannot register output port \"%s\"", - jd->source_ports[i]); - mpd_jack_disconnect(jd); + source_ports[i].c_str()); + Disconnect(); return false; } } @@ -282,23 +340,19 @@ mpd_jack_test_default_device(void) } static unsigned -parse_port_list(const char *source, char **dest, Error &error) +parse_port_list(const char *source, std::string dest[], Error &error) { - char **list = g_strsplit(source, ",", 0); unsigned n = 0; - - for (n = 0; list[n] != nullptr; ++n) { + for (auto &&i : SplitString(source, ',')) { if (n >= MAX_PORTS) { error.Set(config_domain, "too many port names"); return 0; } - dest[n] = list[n]; + dest[n++] = std::move(i); } - g_free(list); - if (n == 0) { error.Format(config_domain, "at least one port name expected"); @@ -308,41 +362,34 @@ parse_port_list(const char *source, char **dest, Error &error) return n; } -static AudioOutput * -mpd_jack_init(const config_param ¶m, Error &error) +bool +JackOutput::Configure(const config_param ¶m, Error &error) { - JackOutput *jd = new JackOutput(); - - if (!jd->Initialize(param, error)) { - delete jd; - return nullptr; - } - - const char *value; + if (!base.Configure(param, error)) + return false; - jd->options = JackNullOption; + options = JackNullOption; - jd->name = param.GetBlockValue("client_name", nullptr); - if (jd->name != nullptr) - jd->options = jack_options_t(jd->options | JackUseExactName); + name = param.GetBlockValue("client_name", nullptr); + if (name != nullptr) + options = jack_options_t(options | JackUseExactName); else /* if there's a no configured client name, we don't care about the JackUseExactName option */ - jd->name = "Music Player Daemon"; + name = "Music Player Daemon"; - jd->server_name = param.GetBlockValue("server_name", nullptr); - if (jd->server_name != nullptr) - jd->options = jack_options_t(jd->options | JackServerName); + server_name = param.GetBlockValue("server_name", nullptr); + if (server_name != nullptr) + options = jack_options_t(options | JackServerName); if (!param.GetBlockValue("autostart", false)) - jd->options = jack_options_t(jd->options | JackNoStartServer); + options = jack_options_t(options | JackNoStartServer); /* configure the source ports */ - value = param.GetBlockValue("source_ports", "left,right"); - jd->num_source_ports = parse_port_list(value, - jd->source_ports, error); - if (jd->num_source_ports == 0) + const char *value = param.GetBlockValue("source_ports", "left,right"); + num_source_ports = parse_port_list(value, source_ports, error); + if (num_source_ports == 0) return nullptr; /* configure the destination ports */ @@ -358,24 +405,59 @@ mpd_jack_init(const config_param ¶m, Error &error) } if (value != nullptr) { - jd->num_destination_ports = - parse_port_list(value, - jd->destination_ports, error); - if (jd->num_destination_ports == 0) + num_destination_ports = + parse_port_list(value, destination_ports, error); + if (num_destination_ports == 0) return nullptr; } else { - jd->num_destination_ports = 0; + num_destination_ports = 0; } - if (jd->num_destination_ports > 0 && - jd->num_destination_ports != jd->num_source_ports) + if (num_destination_ports > 0 && + num_destination_ports != num_source_ports) FormatWarning(jack_output_domain, "number of source ports (%u) mismatches the " "number of destination ports (%u) in line %d", - jd->num_source_ports, jd->num_destination_ports, + num_source_ports, num_destination_ports, param.line); - jd->ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u); + ringbuffer_size = param.GetBlockValue("ringbuffer_size", 32768u); + + return true; +} + +inline bool +JackOutput::Enable(Error &error) +{ + for (unsigned i = 0; i < num_source_ports; ++i) + ringbuffer[i] = nullptr; + + return Connect(error); +} + +inline void +JackOutput::Disable() +{ + if (client != nullptr) + Disconnect(); + + for (unsigned i = 0; i < num_source_ports; ++i) { + if (ringbuffer[i] != nullptr) { + jack_ringbuffer_free(ringbuffer[i]); + ringbuffer[i] = nullptr; + } + } +} + +static AudioOutput * +mpd_jack_init(const config_param ¶m, Error &error) +{ + JackOutput *jd = new JackOutput(); + + if (!jd->Configure(param, error)) { + delete jd; + return nullptr; + } jack_set_error_function(mpd_jack_error); @@ -391,160 +473,134 @@ mpd_jack_finish(AudioOutput *ao) { JackOutput *jd = (JackOutput *)ao; - for (unsigned i = 0; i < jd->num_source_ports; ++i) - g_free(jd->source_ports[i]); - - for (unsigned i = 0; i < jd->num_destination_ports; ++i) - g_free(jd->destination_ports[i]); - delete jd; } static bool mpd_jack_enable(AudioOutput *ao, Error &error) { - JackOutput *jd = (JackOutput *)ao; - - for (unsigned i = 0; i < jd->num_source_ports; ++i) - jd->ringbuffer[i] = nullptr; + JackOutput &jo = *(JackOutput *)ao; - return mpd_jack_connect(jd, error); + return jo.Enable(error); } static void mpd_jack_disable(AudioOutput *ao) { - JackOutput *jd = (JackOutput *)ao; - - if (jd->client != nullptr) - mpd_jack_disconnect(jd); + JackOutput &jo = *(JackOutput *)ao; - for (unsigned i = 0; i < jd->num_source_ports; ++i) { - if (jd->ringbuffer[i] != nullptr) { - jack_ringbuffer_free(jd->ringbuffer[i]); - jd->ringbuffer[i] = nullptr; - } - } + jo.Disable(); } /** * Stops the playback on the JACK connection. */ -static void -mpd_jack_stop(JackOutput *jd) +void +JackOutput::Stop() { - assert(jd != nullptr); - - if (jd->client == nullptr) + if (client == nullptr) return; - if (jd->shutdown) + if (shutdown) /* the connection has failed; close it */ - mpd_jack_disconnect(jd); + Disconnect(); else /* the connection is alive: just stop playback */ - jack_deactivate(jd->client); + jack_deactivate(client); } -static bool -mpd_jack_start(JackOutput *jd, Error &error) +inline bool +JackOutput::Start(Error &error) { - const char *destination_ports[MAX_PORTS], **jports; - const char *duplicate_port = nullptr; - unsigned num_destination_ports; - - assert(jd->client != nullptr); - assert(jd->audio_format.channels <= jd->num_source_ports); + assert(client != nullptr); + assert(audio_format.channels <= num_source_ports); /* allocate the ring buffers on the first open(); these persist until MPD exits. It's too unsafe to delete them because we can never know when mpd_jack_process() gets called */ - for (unsigned i = 0; i < jd->num_source_ports; ++i) { - if (jd->ringbuffer[i] == nullptr) - jd->ringbuffer[i] = - jack_ringbuffer_create(jd->ringbuffer_size); + for (unsigned i = 0; i < num_source_ports; ++i) { + if (ringbuffer[i] == nullptr) + ringbuffer[i] = + jack_ringbuffer_create(ringbuffer_size); /* clear the ring buffer to be sure that data from previous playbacks are gone */ - jack_ringbuffer_reset(jd->ringbuffer[i]); + jack_ringbuffer_reset(ringbuffer[i]); } - if ( jack_activate(jd->client) ) { + if ( jack_activate(client) ) { error.Set(jack_output_domain, "cannot activate client"); - mpd_jack_stop(jd); + Stop(); return false; } - if (jd->num_destination_ports == 0) { + const char *dports[MAX_PORTS], **jports; + unsigned num_dports; + if (num_destination_ports == 0) { /* no output ports were configured - ask libjack for defaults */ - jports = jack_get_ports(jd->client, nullptr, nullptr, + jports = jack_get_ports(client, nullptr, nullptr, JackPortIsPhysical | JackPortIsInput); if (jports == nullptr) { error.Set(jack_output_domain, "no ports found"); - mpd_jack_stop(jd); + Stop(); return false; } assert(*jports != nullptr); - for (num_destination_ports = 0; - num_destination_ports < MAX_PORTS && - jports[num_destination_ports] != nullptr; - ++num_destination_ports) { + for (num_dports = 0; num_dports < MAX_PORTS && + jports[num_dports] != nullptr; + ++num_dports) { FormatDebug(jack_output_domain, "destination_port[%u] = '%s'\n", - num_destination_ports, - jports[num_destination_ports]); - destination_ports[num_destination_ports] = - jports[num_destination_ports]; + num_dports, + jports[num_dports]); + dports[num_dports] = jports[num_dports]; } } else { /* use the configured output ports */ - num_destination_ports = jd->num_destination_ports; - memcpy(destination_ports, jd->destination_ports, - num_destination_ports * sizeof(*destination_ports)); + num_dports = num_destination_ports; + for (unsigned i = 0; i < num_dports; ++i) + dports[i] = destination_ports[i].c_str(); jports = nullptr; } - assert(num_destination_ports > 0); + assert(num_dports > 0); - if (jd->audio_format.channels >= 2 && num_destination_ports == 1) { + const char *duplicate_port = nullptr; + if (audio_format.channels >= 2 && num_dports == 1) { /* mix stereo signal on one speaker */ - while (num_destination_ports < jd->audio_format.channels) - destination_ports[num_destination_ports++] = - destination_ports[0]; - } else if (num_destination_ports > jd->audio_format.channels) { - if (jd->audio_format.channels == 1 && num_destination_ports > 2) { + std::fill(dports + num_dports, dports + audio_format.channels, + dports[0]); + } else if (num_dports > audio_format.channels) { + if (audio_format.channels == 1 && num_dports > 2) { /* mono input file: connect the one source channel to the both destination channels */ - duplicate_port = destination_ports[1]; - num_destination_ports = 1; + duplicate_port = dports[1]; + num_dports = 1; } else /* connect only as many ports as we need */ - num_destination_ports = jd->audio_format.channels; + num_dports = audio_format.channels; } - assert(num_destination_ports <= jd->num_source_ports); - - for (unsigned i = 0; i < num_destination_ports; ++i) { - int ret; + assert(num_dports <= num_source_ports); - ret = jack_connect(jd->client, jack_port_name(jd->ports[i]), - destination_ports[i]); + for (unsigned i = 0; i < num_dports; ++i) { + int ret = jack_connect(client, jack_port_name(ports[i]), + dports[i]); if (ret != 0) { error.Format(jack_output_domain, - "Not a valid JACK port: %s", - destination_ports[i]); + "Not a valid JACK port: %s", dports[i]); if (jports != nullptr) free(jports); - mpd_jack_stop(jd); + Stop(); return false; } } @@ -554,7 +610,7 @@ mpd_jack_start(JackOutput *jd, Error &error) the both destination channels */ int ret; - ret = jack_connect(jd->client, jack_port_name(jd->ports[0]), + ret = jack_connect(client, jack_port_name(ports[0]), duplicate_port); if (ret != 0) { error.Format(jack_output_domain, @@ -564,7 +620,7 @@ mpd_jack_start(JackOutput *jd, Error &error) if (jports != nullptr) free(jports); - mpd_jack_stop(jd); + Stop(); return false; } } @@ -575,37 +631,38 @@ mpd_jack_start(JackOutput *jd, Error &error) return true; } -static bool -mpd_jack_open(AudioOutput *ao, AudioFormat &audio_format, - Error &error) +inline bool +JackOutput::Open(AudioFormat &new_audio_format, Error &error) { - JackOutput *jd = (JackOutput *)ao; - - assert(jd != nullptr); - - jd->pause = false; + pause = false; - if (jd->client != nullptr && jd->shutdown) - mpd_jack_disconnect(jd); + if (client != nullptr && shutdown) + Disconnect(); - if (jd->client == nullptr && !mpd_jack_connect(jd, error)) + if (client == nullptr && !Connect(error)) return false; - set_audioformat(jd, audio_format); - jd->audio_format = audio_format; + set_audioformat(this, new_audio_format); + audio_format = new_audio_format; - if (!mpd_jack_start(jd, error)) - return false; + return Start(error); +} - return true; +static bool +mpd_jack_open(AudioOutput *ao, AudioFormat &audio_format, + Error &error) +{ + JackOutput &jo = *(JackOutput *)ao; + + return jo.Open(audio_format, error); } static void -mpd_jack_close(gcc_unused AudioOutput *ao) +mpd_jack_close(AudioOutput *ao) { - JackOutput *jd = (JackOutput *)ao; + JackOutput &jo = *(JackOutput *)ao; - mpd_jack_stop(jd); + jo.Stop(); } static unsigned @@ -618,116 +675,82 @@ mpd_jack_delay(AudioOutput *ao) : 0; } -static inline jack_default_audio_sample_t -sample_16_to_jack(int16_t sample) +inline size_t +JackOutput::WriteSamples(const float *src, size_t n_frames) { - return sample / (jack_default_audio_sample_t)(1 << (16 - 1)); -} + assert(n_frames > 0); -static void -mpd_jack_write_samples_16(JackOutput *jd, const int16_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - unsigned i; - - while (num_samples-- > 0) { - for (i = 0; i < jd->audio_format.channels; ++i) { - sample = sample_16_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[i], - (const char *)&sample, - sizeof(sample)); - } - } -} + const unsigned n_channels = audio_format.channels; -static inline jack_default_audio_sample_t -sample_24_to_jack(int32_t sample) -{ - return sample / (jack_default_audio_sample_t)(1 << (24 - 1)); -} + float *dest[MAX_CHANNELS]; + size_t space = -1; + for (unsigned i = 0; i < n_channels; ++i) { + jack_ringbuffer_data_t d[2]; + jack_ringbuffer_get_write_vector(ringbuffer[i], d); -static void -mpd_jack_write_samples_24(JackOutput *jd, const int32_t *src, - unsigned num_samples) -{ - jack_default_audio_sample_t sample; - unsigned i; - - while (num_samples-- > 0) { - for (i = 0; i < jd->audio_format.channels; ++i) { - sample = sample_24_to_jack(*src++); - jack_ringbuffer_write(jd->ringbuffer[i], - (const char *)&sample, - sizeof(sample)); - } - } -} + /* choose the first non-empty writable area */ + const jack_ringbuffer_data_t &e = d[d[0].len == 0]; -static void -mpd_jack_write_samples(JackOutput *jd, const void *src, - unsigned num_samples) -{ - switch (jd->audio_format.format) { - case SampleFormat::S16: - mpd_jack_write_samples_16(jd, (const int16_t*)src, - num_samples); - break; - - case SampleFormat::S24_P32: - mpd_jack_write_samples_24(jd, (const int32_t*)src, - num_samples); - break; - - default: - assert(false); - gcc_unreachable(); + if (e.len < space) + /* send data symmetrically */ + space = e.len; + + dest[i] = (float *)e.buf; } + + space /= jack_sample_size; + if (space == 0) + return 0; + + const size_t result = n_frames = std::min(space, n_frames); + + while (n_frames-- > 0) + for (unsigned i = 0; i < n_channels; ++i) + *dest[i]++ = *src++; + + const size_t per_channel_advance = result * jack_sample_size; + for (unsigned i = 0; i < n_channels; ++i) + jack_ringbuffer_write_advance(ringbuffer[i], + per_channel_advance); + + return result; } -static size_t -mpd_jack_play(AudioOutput *ao, const void *chunk, size_t size, - Error &error) +inline size_t +JackOutput::Play(const void *chunk, size_t size, Error &error) { - JackOutput *jd = (JackOutput *)ao; - const size_t frame_size = jd->audio_format.GetFrameSize(); - size_t space = 0, space1; - - jd->pause = false; + pause = false; + const size_t frame_size = audio_format.GetFrameSize(); assert(size % frame_size == 0); size /= frame_size; while (true) { - if (jd->shutdown) { + if (shutdown) { error.Set(jack_output_domain, "Refusing to play, because " "there is no client thread"); return 0; } - space = jack_ringbuffer_write_space(jd->ringbuffer[0]); - for (unsigned i = 1; i < jd->audio_format.channels; ++i) { - space1 = jack_ringbuffer_write_space(jd->ringbuffer[i]); - if (space > space1) - /* send data symmetrically */ - space = space1; - } - - if (space >= jack_sample_size) - break; + size_t frames_written = + WriteSamples((const float *)chunk, size); + if (frames_written > 0) + return frames_written * frame_size; /* XXX do something more intelligent to synchronize */ - g_usleep(1000); + usleep(1000); } +} - space /= jack_sample_size; - if (space < size) - size = space; +static size_t +mpd_jack_play(AudioOutput *ao, const void *chunk, size_t size, + Error &error) +{ + JackOutput &jo = *(JackOutput *)ao; - mpd_jack_write_samples(jd, chunk, size); - return size * frame_size; + return jo.Play(chunk, size, error); } static bool diff --git a/src/output/plugins/OssOutputPlugin.cxx b/src/output/plugins/OssOutputPlugin.cxx index 39d87fc35..fc31a1511 100644 --- a/src/output/plugins/OssOutputPlugin.cxx +++ b/src/output/plugins/OssOutputPlugin.cxx @@ -124,7 +124,7 @@ oss_stat_device(const char *device, int *errno_r) return OSS_STAT_NO_ERROR; } -static const char *default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; +static const char *const default_devices[] = { "/dev/sound/dsp", "/dev/dsp" }; static bool oss_output_test_default_device(void) @@ -380,7 +380,7 @@ oss_setup_sample_rate(int fd, AudioFormat &audio_format, break; } - static const int sample_rates[] = { 48000, 44100, 0 }; + static constexpr int sample_rates[] = { 48000, 44100, 0 }; for (unsigned i = 0; sample_rates[i] != 0; ++i) { sample_rate = sample_rates[i]; if (sample_rate == (int)audio_format.sample_rate) @@ -572,7 +572,7 @@ oss_setup_sample_format(int fd, AudioFormat &audio_format, /* the requested sample format is not available - probe for other formats supported by MPD */ - static const SampleFormat sample_formats[] = { + static constexpr SampleFormat sample_formats[] = { SampleFormat::S24_P32, SampleFormat::S32, SampleFormat::S16, diff --git a/src/output/plugins/PulseOutputPlugin.cxx b/src/output/plugins/PulseOutputPlugin.cxx index 120bad090..5dc733383 100644 --- a/src/output/plugins/PulseOutputPlugin.cxx +++ b/src/output/plugins/PulseOutputPlugin.cxx @@ -523,7 +523,11 @@ pulse_output_setup_stream(PulseOutput *po, const pa_sample_spec *ss, assert(po != nullptr); assert(po->context != nullptr); - po->stream = pa_stream_new(po->context, po->name, ss, nullptr); + /* WAVE-EX is been adopted as the speaker map for most media files */ + pa_channel_map chan_map; + pa_channel_map_init_auto(&chan_map, ss->channels, + PA_CHANNEL_MAP_WAVEEX); + po->stream = pa_stream_new(po->context, po->name, ss, &chan_map); if (po->stream == nullptr) { SetError(error, po->context, "pa_stream_new() has failed"); return false; diff --git a/src/output/plugins/WinmmOutputPlugin.cxx b/src/output/plugins/WinmmOutputPlugin.cxx index e5c5a6f0c..b7af0b0f8 100644 --- a/src/output/plugins/WinmmOutputPlugin.cxx +++ b/src/output/plugins/WinmmOutputPlugin.cxx @@ -56,6 +56,18 @@ struct WinmmOutput { static constexpr Domain winmm_output_domain("winmm_output"); +static void +SetWaveOutError(Error &error, MMRESULT result, const char *prefix) +{ + char buffer[256]; + if (waveOutGetErrorTextA(result, buffer, + ARRAY_SIZE(buffer)) == MMSYSERR_NOERROR) + error.Format(winmm_output_domain, int(result), + "%s: %s", prefix, buffer); + else + error.Set(winmm_output_domain, int(result), prefix); +} + HWAVEOUT winmm_output_get_handle(WinmmOutput &output) { @@ -179,7 +191,7 @@ winmm_output_open(AudioOutput *ao, AudioFormat &audio_format, (DWORD_PTR)wo->event, 0, CALLBACK_EVENT); if (result != MMSYSERR_NOERROR) { CloseHandle(wo->event); - error.Set(winmm_output_domain, "waveOutOpen() failed"); + SetWaveOutError(error, result, "waveOutOpen() failed"); return false; } @@ -225,8 +237,8 @@ winmm_set_buffer(WinmmOutput *wo, WinmmBuffer *buffer, MMRESULT result = waveOutPrepareHeader(wo->handle, &buffer->hdr, sizeof(buffer->hdr)); if (result != MMSYSERR_NOERROR) { - error.Set(winmm_output_domain, result, - "waveOutPrepareHeader() failed"); + SetWaveOutError(error, result, + "waveOutPrepareHeader() failed"); return false; } @@ -251,8 +263,8 @@ winmm_drain_buffer(WinmmOutput *wo, WinmmBuffer *buffer, if (result == MMSYSERR_NOERROR) return true; else if (result != WAVERR_STILLPLAYING) { - error.Set(winmm_output_domain, result, - "waveOutUnprepareHeader() failed"); + SetWaveOutError(error, result, + "waveOutUnprepareHeader() failed"); return false; } @@ -278,8 +290,7 @@ winmm_output_play(AudioOutput *ao, const void *chunk, size_t size, Error &error) if (result != MMSYSERR_NOERROR) { waveOutUnprepareHeader(wo->handle, &buffer->hdr, sizeof(buffer->hdr)); - error.Set(winmm_output_domain, result, - "waveOutWrite() failed"); + SetWaveOutError(error, result, "waveOutWrite() failed"); return 0; } diff --git a/src/output/plugins/httpd/HttpdInternal.hxx b/src/output/plugins/httpd/HttpdInternal.hxx index 20ff15e42..303170268 100644 --- a/src/output/plugins/httpd/HttpdInternal.hxx +++ b/src/output/plugins/httpd/HttpdInternal.hxx @@ -153,7 +153,7 @@ public: HttpdOutput(EventLoop &_loop); ~HttpdOutput(); -#if defined(__clang__) || GCC_CHECK_VERSION(4,7) +#if CLANG_OR_GCC_VERSION(4,7) constexpr #endif static HttpdOutput *Cast(AudioOutput *ao) { diff --git a/src/output/plugins/httpd/IcyMetaDataServer.cxx b/src/output/plugins/httpd/IcyMetaDataServer.cxx index 146df23d1..108d4e7ec 100644 --- a/src/output/plugins/httpd/IcyMetaDataServer.cxx +++ b/src/output/plugins/httpd/IcyMetaDataServer.cxx @@ -22,8 +22,8 @@ #include "Page.hxx" #include "tag/Tag.hxx" #include "util/FormatString.hxx" - -#include <glib.h> +#include "util/StringUtil.hxx" +#include "util/Macros.hxx" #include <string.h> @@ -57,16 +57,13 @@ icy_server_metadata_header(const char *name, static char * icy_server_metadata_string(const char *stream_title, const char* stream_url) { - gchar *icy_metadata; - guint meta_length; - // The leading n is a placeholder for the length information - icy_metadata = FormatNew("nStreamTitle='%s';" - "StreamUrl='%s';", - stream_title, - stream_url); + char *icy_metadata = FormatNew("nStreamTitle='%s';" + "StreamUrl='%s';", + stream_title, + stream_url); - meta_length = strlen(icy_metadata); + size_t meta_length = strlen(icy_metadata); meta_length--; // subtract placeholder @@ -85,43 +82,30 @@ icy_server_metadata_string(const char *stream_title, const char* stream_url) Page * icy_server_metadata_page(const Tag &tag, const TagType *types) { - const gchar *tag_items[TAG_NUM_OF_ITEM_TYPES]; - gint last_item, item; - guint position; - gchar *icy_string; - gchar stream_title[(1 + 255 - 28) * 16]; // Length + Metadata - - // "StreamTitle='';StreamUrl='';" - // = 4081 - 28 - stream_title[0] = '\0'; - - last_item = -1; + const char *tag_items[TAG_NUM_OF_ITEM_TYPES]; + int last_item = -1; while (*types != TAG_NUM_OF_ITEM_TYPES) { - const gchar *tag_item = tag.GetValue(*types++); + const char *tag_item = tag.GetValue(*types++); if (tag_item) tag_items[++last_item] = tag_item; } - position = item = 0; - while (position < sizeof(stream_title) && item <= last_item) { - gint length = 0; - - length = g_strlcpy(stream_title + position, - tag_items[item++], - sizeof(stream_title) - position); + int item = 0; - position += length; + // Length + Metadata - "StreamTitle='';StreamUrl='';" = 4081 - 28 + char stream_title[(1 + 255 - 28) * 16]; + char *p = stream_title, *const end = stream_title + ARRAY_SIZE(stream_title); + stream_title[0] = '\0'; - if (item <= last_item) { - length = g_strlcpy(stream_title + position, - " - ", - sizeof(stream_title) - position); + while (p < end && item <= last_item) { + p = CopyString(p, tag_items[item++], end - p); - position += length; - } + if (item <= last_item) + p = CopyString(p, " - ", end - p); } - icy_string = icy_server_metadata_string(stream_title, ""); + char *icy_string = icy_server_metadata_string(stream_title, ""); if (icy_string == nullptr) return nullptr; diff --git a/src/pcm/ConfiguredResampler.cxx b/src/pcm/ConfiguredResampler.cxx index f6aec3f95..a65ec8702 100644 --- a/src/pcm/ConfiguredResampler.cxx +++ b/src/pcm/ConfiguredResampler.cxx @@ -25,11 +25,11 @@ #include "config/ConfigError.hxx" #include "util/Error.hxx" -#ifdef HAVE_LIBSAMPLERATE +#ifdef ENABLE_LIBSAMPLERATE #include "LibsamplerateResampler.hxx" #endif -#ifdef HAVE_SOXR +#ifdef ENABLE_SOXR #include "SoxrResampler.hxx" #endif @@ -38,11 +38,11 @@ enum class SelectedResampler { FALLBACK, -#ifdef HAVE_LIBSAMPLERATE +#ifdef ENABLE_LIBSAMPLERATE LIBSAMPLERATE, #endif -#ifdef HAVE_SOXR +#ifdef ENABLE_SOXR SOXR, #endif }; @@ -58,14 +58,14 @@ pcm_resampler_global_init(Error &error) if (strcmp(converter, "internal") == 0) return true; -#ifdef HAVE_SOXR +#ifdef ENABLE_SOXR if (memcmp(converter, "soxr", 4) == 0) { selected_resampler = SelectedResampler::SOXR; return pcm_resample_soxr_global_init(converter, error); } #endif -#ifdef HAVE_LIBSAMPLERATE +#ifdef ENABLE_LIBSAMPLERATE selected_resampler = SelectedResampler::LIBSAMPLERATE; return pcm_resample_lsr_global_init(converter, error); #endif @@ -86,12 +86,12 @@ pcm_resampler_create() case SelectedResampler::FALLBACK: return new FallbackPcmResampler(); -#ifdef HAVE_LIBSAMPLERATE +#ifdef ENABLE_LIBSAMPLERATE case SelectedResampler::LIBSAMPLERATE: return new LibsampleratePcmResampler(); #endif -#ifdef HAVE_SOXR +#ifdef ENABLE_SOXR case SelectedResampler::SOXR: return new SoxrPcmResampler(); #endif diff --git a/src/playlist/PlaylistRegistry.cxx b/src/playlist/PlaylistRegistry.cxx index 4e9ef890e..ad0afa628 100644 --- a/src/playlist/PlaylistRegistry.cxx +++ b/src/playlist/PlaylistRegistry.cxx @@ -45,11 +45,8 @@ const struct playlist_plugin *const playlist_plugins[] = { &extm3u_playlist_plugin, &m3u_playlist_plugin, -#ifdef HAVE_GLIB - // TODO: enable without GLib &pls_playlist_plugin, -#endif -#ifdef HAVE_EXPAT +#ifdef ENABLE_EXPAT &xspf_playlist_plugin, &asx_playlist_plugin, &rss_playlist_plugin, @@ -60,8 +57,10 @@ const struct playlist_plugin *const playlist_plugins[] = { #ifdef ENABLE_SOUNDCLOUD &soundcloud_playlist_plugin, #endif +#ifdef ENABLE_CUE &cue_playlist_plugin, &embcue_playlist_plugin, +#endif nullptr }; diff --git a/src/playlist/plugins/AsxPlaylistPlugin.cxx b/src/playlist/plugins/AsxPlaylistPlugin.cxx index 3185a8144..59943681a 100644 --- a/src/playlist/plugins/AsxPlaylistPlugin.cxx +++ b/src/playlist/plugins/AsxPlaylistPlugin.cxx @@ -28,7 +28,7 @@ #include "Log.hxx" /** - * This is the state object for the GLib XML parser. + * This is the state object for our XML parser. */ struct AsxParser { /** diff --git a/src/playlist/plugins/PlsPlaylistPlugin.cxx b/src/playlist/plugins/PlsPlaylistPlugin.cxx index f7724f522..4f7c5a1ff 100644 --- a/src/playlist/plugins/PlsPlaylistPlugin.cxx +++ b/src/playlist/plugins/PlsPlaylistPlugin.cxx @@ -21,121 +21,142 @@ #include "PlsPlaylistPlugin.hxx" #include "../PlaylistPlugin.hxx" #include "../MemorySongEnumerator.hxx" -#include "input/InputStream.hxx" +#include "input/TextInputStream.hxx" #include "DetachedSong.hxx" #include "tag/TagBuilder.hxx" +#include "util/ASCII.hxx" +#include "util/StringUtil.hxx" +#include "util/DivideString.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" -#include "Log.hxx" - -#include <glib.h> #include <string> -#include <stdio.h> -#include <stdio.h> +#include <stdlib.h> static constexpr Domain pls_domain("pls"); -static void -pls_parser(GKeyFile *keyfile, std::forward_list<DetachedSong> &songs) +static bool +FindPlaylistSection(TextInputStream &is) { - gchar *value; - GError *error = nullptr; - int num_entries = g_key_file_get_integer(keyfile, "playlist", - "NumberOfEntries", &error); - if (error) { - FormatError(pls_domain, - "Invalid PLS file: '%s'", error->message); - g_error_free(error); - error = nullptr; - - /* Hack to work around shoutcast failure to comform to spec */ - num_entries = g_key_file_get_integer(keyfile, "playlist", - "numberofentries", &error); - if (error) { - g_error_free(error); - error = nullptr; - } + char *line; + while ((line = is.ReadLine()) != nullptr) { + line = Strip(line); + if (StringEqualsCaseASCII(line, "[playlist]")) + return true; } - for (; num_entries > 0; --num_entries) { - char key[64]; - sprintf(key, "File%u", num_entries); - char *uri = g_key_file_get_string(keyfile, "playlist", key, - &error); - if(error) { - FormatError(pls_domain, "Invalid PLS entry %s: '%s'", - key, error->message); - g_error_free(error); - return; - } + return false; +} - TagBuilder tag; +static bool +ParsePls(TextInputStream &is, std::forward_list<DetachedSong> &songs) +{ + assert(songs.empty()); - sprintf(key, "Title%u", num_entries); - value = g_key_file_get_string(keyfile, "playlist", key, - nullptr); - if (value != nullptr) - tag.AddItem(TAG_TITLE, value); + if (!FindPlaylistSection(is)) + return false; - g_free(value); + unsigned n_entries = 0; - sprintf(key, "Length%u", num_entries); - int length = g_key_file_get_integer(keyfile, "playlist", key, - nullptr); - if (length > 0) - tag.SetDuration(SignedSongTime::FromS(length)); + struct Entry { + std::string file, title; + int length; - songs.emplace_front(uri, tag.Commit()); - g_free(uri); - } + Entry():length(-1) {} + }; -} + static constexpr unsigned MAX_ENTRIES = 65536; -static SongEnumerator * -pls_open_stream(InputStream &is) -{ - GError *error = nullptr; - Error error2; - - std::string kf_data; - - do { - char buffer[1024]; - size_t nbytes = is.LockRead(buffer, sizeof(buffer), error2); - if (nbytes == 0) { - if (error2.IsDefined()) { - LogError(error2); - return nullptr; - } + std::vector<Entry> entries; + + char *line; + while ((line = is.ReadLine()) != nullptr) { + line = Strip(line); + if (*line == 0 || *line == ';') + continue; + + if (*line == '[') + /* another section starts; we only want + [Playlist], so stop here */ break; + + const DivideString ds(line, '=', true); + if (!ds.IsDefined()) + continue; + + const char *const name = ds.GetFirst(); + const char *const value = ds.GetSecond(); + + if (StringEqualsCaseASCII(name, "NumberOfEntries")) { + n_entries = strtoul(value, nullptr, 10); + if (n_entries == 0) + /* empty file - nothing remains to be + done */ + return true; + + if (n_entries > MAX_ENTRIES) + n_entries = MAX_ENTRIES; + entries.resize(n_entries); + } else if (StringEqualsCaseASCII(name, "File", 4)) { + unsigned i = strtoul(name + 4, nullptr, 10); + if (i >= 1 && i <= (n_entries > 0 ? n_entries : MAX_ENTRIES)) { + if (entries.size() < i) + entries.resize(i); + entries[i - 1].file = value; + } + } else if (StringEqualsCaseASCII(name, "Title", 5)) { + unsigned i = strtoul(name + 5, nullptr, 10); + if (i >= 1 && i <= (n_entries > 0 ? n_entries : MAX_ENTRIES)) { + if (entries.size() < i) + entries.resize(i); + entries[i - 1].title = value; + } + } else if (StringEqualsCaseASCII(name, "Length", 6)) { + unsigned i = strtoul(name + 6, nullptr, 10); + if (i >= 1 && i <= (n_entries > 0 ? n_entries : MAX_ENTRIES)) { + if (entries.size() < i) + entries.resize(i); + entries[i - 1].length = atoi(value); + } } + } - kf_data.append(buffer, nbytes); - /* Limit to 64k */ - } while (kf_data.length() < 65536); + if (n_entries == 0) + /* no "NumberOfEntries" found */ + return false; - if (kf_data.empty()) { - LogWarning(pls_domain, "KeyFile parser failed: No Data"); - return nullptr; - } + auto i = songs.before_begin(); + for (const auto &entry : entries) { + const char *uri = entry.file.c_str(); - GKeyFile *keyfile = g_key_file_new(); - if (!g_key_file_load_from_data(keyfile, - kf_data.data(), kf_data.length(), - G_KEY_FILE_NONE, &error)) { - FormatError(pls_domain, - "KeyFile parser failed: %s", error->message); - g_error_free(error); - g_key_file_free(keyfile); - return nullptr; + TagBuilder tag; + if (!entry.title.empty()) + tag.AddItem(TAG_TITLE, entry.title.c_str()); + + if (entry.length > 0) + tag.SetDuration(SignedSongTime::FromS(entry.length)); + + i = songs.emplace_after(i, uri, tag.Commit()); } + return true; +} + +static bool +ParsePls(InputStream &is, std::forward_list<DetachedSong> &songs) +{ + TextInputStream tis(is); + return ParsePls(tis, songs); +} + +static SongEnumerator * +pls_open_stream(InputStream &is) +{ std::forward_list<DetachedSong> songs; - pls_parser(keyfile, songs); - g_key_file_free(keyfile); + if (!ParsePls(is, songs)) + return nullptr; return new MemorySongEnumerator(std::move(songs)); } diff --git a/src/playlist/plugins/RssPlaylistPlugin.cxx b/src/playlist/plugins/RssPlaylistPlugin.cxx index 6f9aad54b..3d32b29ca 100644 --- a/src/playlist/plugins/RssPlaylistPlugin.cxx +++ b/src/playlist/plugins/RssPlaylistPlugin.cxx @@ -28,7 +28,7 @@ #include "Log.hxx" /** - * This is the state object for the GLib XML parser. + * This is the state object for the our XML parser. */ struct RssParser { /** diff --git a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx index ec4d240a5..6be9c65ab 100644 --- a/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx +++ b/src/playlist/plugins/SoundCloudPlaylistPlugin.cxx @@ -25,16 +25,17 @@ #include "input/InputStream.hxx" #include "tag/TagBuilder.hxx" #include "util/StringUtil.hxx" +#include "util/Alloc.hxx" #include "util/Error.hxx" #include "util/Domain.hxx" #include "Log.hxx" -#include <glib.h> #include <yajl/yajl_parse.h> #include <string> #include <string.h> +#include <stdlib.h> static struct { std::string apikey; @@ -60,7 +61,7 @@ soundcloud_init(const config_param ¶m) /** * Construct a full soundcloud resolver URL from the given fragment. * @param uri uri of a soundcloud page (or just the path) - * @return Constructed URL. Must be freed with g_free. + * @return Constructed URL. Must be freed with free(). */ static char * soundcloud_resolve(const char* uri) @@ -68,18 +69,18 @@ soundcloud_resolve(const char* uri) char *u, *ru; if (StringStartsWith(uri, "https://")) { - u = g_strdup(uri); + u = xstrdup(uri); } else if (StringStartsWith(uri, "soundcloud.com")) { - u = g_strconcat("https://", uri, nullptr); + u = xstrcatdup("https://", uri); } else { /* assume it's just a path on soundcloud.com */ - u = g_strconcat("https://soundcloud.com/", uri, nullptr); + u = xstrcatdup("https://soundcloud.com/", uri); } - ru = g_strconcat("https://api.soundcloud.com/resolve.json?url=", - u, "&client_id=", - soundcloud_config.apikey.c_str(), nullptr); - g_free(u); + ru = xstrcatdup("https://api.soundcloud.com/resolve.json?url=", + u, "&client_id=", + soundcloud_config.apikey.c_str()); + free(u); return ru; } @@ -111,12 +112,7 @@ struct parse_data { }; static int -handle_integer(void *ctx, - long -#ifndef HAVE_YAJL1 - long -#endif - intval) +handle_integer(void *ctx, long long intval) { struct parse_data *data = (struct parse_data *) ctx; @@ -132,25 +128,19 @@ handle_integer(void *ctx, } static int -handle_string(void *ctx, const unsigned char* stringval, -#ifdef HAVE_YAJL1 - unsigned int -#else - size_t -#endif - stringlen) +handle_string(void *ctx, const unsigned char *stringval, size_t stringlen) { struct parse_data *data = (struct parse_data *) ctx; const char *s = (const char *) stringval; switch (data->key) { case Title: - g_free(data->title); - data->title = g_strndup(s, stringlen); + free(data->title); + data->title = xstrndup(s, stringlen); break; case Stream_URL: - g_free(data->stream_url); - data->stream_url = g_strndup(s, stringlen); + free(data->stream_url); + data->stream_url = xstrndup(s, stringlen); data->got_url = 1; break; default: @@ -161,13 +151,7 @@ handle_string(void *ctx, const unsigned char* stringval, } static int -handle_mapkey(void *ctx, const unsigned char* stringval, -#ifdef HAVE_YAJL1 - unsigned int -#else - size_t -#endif - stringlen) +handle_mapkey(void *ctx, const unsigned char *stringval, size_t stringlen) { struct parse_data *data = (struct parse_data *) ctx; @@ -211,8 +195,8 @@ handle_end_map(void *ctx) /* got_url == 1, track finished, make it into a song */ data->got_url = 0; - char *u = g_strconcat(data->stream_url, "?client_id=", - soundcloud_config.apikey.c_str(), nullptr); + char *u = xstrcatdup(data->stream_url, "?client_id=", + soundcloud_config.apikey.c_str()); TagBuilder tag; tag.SetDuration(SignedSongTime::FromMS(data->duration)); @@ -220,7 +204,7 @@ handle_end_map(void *ctx) tag.AddItem(TAG_NAME, data->title); data->songs.emplace_front(u, tag.Commit()); - g_free(u); + free(u); return 1; } @@ -282,20 +266,11 @@ soundcloud_parse_json(const char *url, yajl_handle hand, } if (done) { -#ifdef HAVE_YAJL1 - stat = yajl_parse_complete(hand); -#else stat = yajl_complete_parse(hand); -#endif } else stat = yajl_parse(hand, ubuffer, nbytes); - if (stat != yajl_status_ok -#ifdef HAVE_YAJL1 - && stat != yajl_status_insufficient_data -#endif - ) - { + if (stat != yajl_status_ok) { unsigned char *str = yajl_get_error(hand, 1, ubuffer, nbytes); LogError(soundcloud_domain, (const char *)str); yajl_free_error(hand, str); @@ -325,24 +300,24 @@ soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond) char *u = nullptr; if (memcmp(uri, "track/", 6) == 0) { const char *rest = uri + 6; - u = g_strconcat("https://api.soundcloud.com/tracks/", - rest, ".json?client_id=", - soundcloud_config.apikey.c_str(), nullptr); + u = xstrcatdup("https://api.soundcloud.com/tracks/", + rest, ".json?client_id=", + soundcloud_config.apikey.c_str()); } else if (memcmp(uri, "playlist/", 9) == 0) { const char *rest = uri + 9; - u = g_strconcat("https://api.soundcloud.com/playlists/", - rest, ".json?client_id=", - soundcloud_config.apikey.c_str(), nullptr); + u = xstrcatdup("https://api.soundcloud.com/playlists/", + rest, ".json?client_id=", + soundcloud_config.apikey.c_str()); } else if (memcmp(uri, "user/", 5) == 0) { const char *rest = uri + 5; - u = g_strconcat("https://api.soundcloud.com/users/", - rest, "/tracks.json?client_id=", - soundcloud_config.apikey.c_str(), nullptr); + u = xstrcatdup("https://api.soundcloud.com/users/", + rest, "/tracks.json?client_id=", + soundcloud_config.apikey.c_str()); } else if (memcmp(uri, "search/", 7) == 0) { const char *rest = uri + 7; - u = g_strconcat("https://api.soundcloud.com/tracks.json?q=", - rest, "&client_id=", - soundcloud_config.apikey.c_str(), nullptr); + u = xstrcatdup("https://api.soundcloud.com/tracks.json?q=", + rest, "&client_id=", + soundcloud_config.apikey.c_str()); } else if (memcmp(uri, "url/", 4) == 0) { const char *rest = uri + 4; /* Translate to soundcloud resolver call. libcurl will automatically @@ -359,19 +334,14 @@ soundcloud_open_uri(const char *uri, Mutex &mutex, Cond &cond) data.got_url = 0; data.title = nullptr; data.stream_url = nullptr; -#ifdef HAVE_YAJL1 - yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, nullptr, - &data); -#else yajl_handle hand = yajl_alloc(&parse_callbacks, nullptr, &data); -#endif int ret = soundcloud_parse_json(u, hand, mutex, cond); - g_free(u); + free(u); yajl_free(hand); - g_free(data.title); - g_free(data.stream_url); + free(data.title); + free(data.stream_url); if (ret == -1) return nullptr; diff --git a/src/playlist/plugins/XspfPlaylistPlugin.cxx b/src/playlist/plugins/XspfPlaylistPlugin.cxx index 5b6010b53..01a157ba8 100644 --- a/src/playlist/plugins/XspfPlaylistPlugin.cxx +++ b/src/playlist/plugins/XspfPlaylistPlugin.cxx @@ -34,7 +34,7 @@ static constexpr Domain xspf_domain("xspf"); /** - * This is the state object for the GLib XML parser. + * This is the state object for our XML parser. */ struct XspfParser { /** diff --git a/src/queue/Playlist.cxx b/src/queue/Playlist.cxx index b2fd673b4..60588ab90 100644 --- a/src/queue/Playlist.cxx +++ b/src/queue/Playlist.cxx @@ -44,45 +44,52 @@ playlist::TagModified(DetachedSong &&song) idle_add(IDLE_PLAYLIST); } -/** - * Queue a song, addressed by its order number. - */ -static void -playlist_queue_song_order(playlist &playlist, PlayerControl &pc, - unsigned order) +inline void +playlist::QueueSongOrder(PlayerControl &pc, unsigned order) + { - assert(playlist.queue.IsValidOrder(order)); + assert(queue.IsValidOrder(order)); - playlist.queued = order; + queued = order; - const DetachedSong &song = playlist.queue.GetOrder(order); + const DetachedSong &song = queue.GetOrder(order); FormatDebug(playlist_domain, "queue song %i:\"%s\"", - playlist.queued, song.GetURI()); + queued, song.GetURI()); pc.EnqueueSong(new DetachedSong(song)); } -/** - * Called if the player thread has started playing the "queued" song. - */ -static void -playlist_song_started(playlist &playlist, PlayerControl &pc) +void +playlist::SongStarted() +{ + assert(current >= 0); + + /* reset a song's "priority" when playback starts */ + if (queue.SetPriority(queue.OrderToPosition(current), 0, -1, false)) + OnModified(); +} + +inline void +playlist::QueuedSongStarted(PlayerControl &pc) { assert(pc.next_song == nullptr); - assert(playlist.queued >= -1); + assert(queued >= -1); + assert(current >= 0); /* queued song has started: copy queued to current, and notify the clients */ - int current = playlist.current; - playlist.current = playlist.queued; - playlist.queued = -1; + const int old_current = current; + current = queued; + queued = -1; - if(playlist.queue.consume) - playlist.DeleteOrder(pc, current); + if (queue.consume) + DeleteOrder(pc, old_current); idle_add(IDLE_PLAYER); + + SongStarted(); } const DetachedSong * @@ -139,7 +146,7 @@ playlist::UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev) if (next_order >= 0) { if (next_song != prev) - playlist_queue_song_order(*this, pc, next_order); + QueueSongOrder(pc, next_order); else queued = next_order; } @@ -157,10 +164,9 @@ playlist::PlayOrder(PlayerControl &pc, int order) pc.Play(new DetachedSong(song)); current = order; -} -static void -playlist_resume_playback(playlist &playlist, PlayerControl &pc); + SongStarted(); +} void playlist::SyncWithPlayer(PlayerControl &pc) @@ -180,12 +186,12 @@ playlist::SyncWithPlayer(PlayerControl &pc) should be restarted with the next song. That can happen if the playlist isn't filling the queue fast enough */ - playlist_resume_playback(*this, pc); + ResumePlayback(pc); else { /* check if the player thread has already started playing the queued song */ if (pc_next_song == nullptr && queued != -1) - playlist_song_started(*this, pc); + QueuedSongStarted(pc); pc.Lock(); pc_next_song = pc.next_song; @@ -198,31 +204,27 @@ playlist::SyncWithPlayer(PlayerControl &pc) } } -/** - * The player has stopped for some reason. Check the error, and - * decide whether to re-start playback - */ -static void -playlist_resume_playback(playlist &playlist, PlayerControl &pc) +inline void +playlist::ResumePlayback(PlayerControl &pc) { - assert(playlist.playing); + assert(playing); assert(pc.GetState() == PlayerState::STOP); const auto error = pc.GetErrorType(); if (error == PlayerError::NONE) - playlist.error_count = 0; + error_count = 0; else - ++playlist.error_count; + ++error_count; - if ((playlist.stop_on_error && error != PlayerError::NONE) || + if ((stop_on_error && error != PlayerError::NONE) || error == PlayerError::OUTPUT || - playlist.error_count >= playlist.queue.GetLength()) + error_count >= queue.GetLength()) /* too many errors, or critical error: stop playback */ - playlist.Stop(pc); + Stop(pc); else /* continue playback at the next song */ - playlist.PlayNext(pc); + PlayNext(pc); } void diff --git a/src/queue/Playlist.hxx b/src/queue/Playlist.hxx index ea19d9bba..ab8fbe2d4 100644 --- a/src/queue/Playlist.hxx +++ b/src/queue/Playlist.hxx @@ -135,6 +135,17 @@ protected: void OnModified(); /** + * Called when playback of a new song starts. Unlike + * QueuedSongStarted(), this also gets called when the user + * manually switches to another song. It may be used for + * playlist fixups. + * + * The song being started is specified by the #current + * attribute. + */ + void SongStarted(); + + /** * Updates the "queued song". Calculates the next song * according to the current one (if MPD isn't playing, it * takes the first song), and queues this song. Clears the @@ -145,6 +156,24 @@ protected: */ void UpdateQueuedSong(PlayerControl &pc, const DetachedSong *prev); + /** + * Queue a song, addressed by its order number. + */ + void QueueSongOrder(PlayerControl &pc, unsigned order); + + /** + * Called when the player thread has started playing the + * "queued" song, i.e. it has switched from one song to the + * next automatically. + */ + void QueuedSongStarted(PlayerControl &pc); + + /** + * The player has stopped for some reason. Check the error, + * and decide whether to re-start playback. + */ + void ResumePlayback(PlayerControl &pc); + public: void BeginBulk(); void CommitBulk(PlayerControl &pc); diff --git a/src/queue/Queue.cxx b/src/queue/Queue.cxx index 99b545ab1..039af096e 100644 --- a/src/queue/Queue.cxx +++ b/src/queue/Queue.cxx @@ -402,7 +402,8 @@ Queue::CountSamePriority(unsigned start_order, uint8_t priority) const } bool -Queue::SetPriority(unsigned position, uint8_t priority, int after_order) +Queue::SetPriority(unsigned position, uint8_t priority, int after_order, + bool reorder) { assert(position < length); @@ -414,7 +415,7 @@ Queue::SetPriority(unsigned position, uint8_t priority, int after_order) item->version = version; item->priority = priority; - if (!random) + if (!random || !reorder) /* don't reorder if not in random mode */ return true; diff --git a/src/queue/Queue.hxx b/src/queue/Queue.hxx index 016619e65..32acac689 100644 --- a/src/queue/Queue.hxx +++ b/src/queue/Queue.hxx @@ -340,10 +340,13 @@ struct Queue { /** * Shuffles a (position) range in the queue. The songs are physically * shuffled, not by using the "order" mapping. + * + * @param reorder false to suppress updating the order list */ void ShuffleRange(unsigned start, unsigned end); - bool SetPriority(unsigned position, uint8_t priority, int after_order); + bool SetPriority(unsigned position, uint8_t priority, int after_order, + bool reorder=true); bool SetPriorityRange(unsigned start_position, unsigned end_position, uint8_t priority, int after_order); diff --git a/src/sticker/Match.hxx b/src/sticker/Match.hxx new file mode 100644 index 000000000..a10a3a805 --- /dev/null +++ b/src/sticker/Match.hxx @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STICKER_MATCH_HXX +#define MPD_STICKER_MATCH_HXX + +enum class StickerOperator { + /** + * Matches if a sticker with the specified name exists. The + * "value" parameter is ignored (must be nullptr). + */ + EXISTS, + + /** + * Matches if a sticker with the specified name and value + * exists. + */ + EQUALS, + + /** + * Matches if a sticker with the specified name exists with a + * value smaller than the specified one. + */ + LESS_THAN, + + /** + * Matches if a sticker with the specified name exists with a + * value bigger than the specified one. + */ + GREATER_THAN, +}; + +#endif diff --git a/src/sticker/SongSticker.cxx b/src/sticker/SongSticker.cxx index b6f46f167..5e5793b70 100644 --- a/src/sticker/SongSticker.cxx +++ b/src/sticker/SongSticker.cxx @@ -23,46 +23,48 @@ #include "db/LightSong.hxx" #include "db/Interface.hxx" #include "util/Error.hxx" - -#include <glib.h> +#include "util/Alloc.hxx" #include <assert.h> #include <string.h> +#include <stdlib.h> std::string -sticker_song_get_value(const LightSong &song, const char *name) +sticker_song_get_value(const LightSong &song, const char *name, Error &error) { const auto uri = song.GetURI(); - return sticker_load_value("song", uri.c_str(), name); + return sticker_load_value("song", uri.c_str(), name, error); } bool sticker_song_set_value(const LightSong &song, - const char *name, const char *value) + const char *name, const char *value, + Error &error) { const auto uri = song.GetURI(); - return sticker_store_value("song", uri.c_str(), name, value); + return sticker_store_value("song", uri.c_str(), name, value, error); } bool -sticker_song_delete(const LightSong &song) +sticker_song_delete(const LightSong &song, Error &error) { const auto uri = song.GetURI(); - return sticker_delete("song", uri.c_str()); + return sticker_delete("song", uri.c_str(), error); } bool -sticker_song_delete_value(const LightSong &song, const char *name) +sticker_song_delete_value(const LightSong &song, const char *name, + Error &error) { const auto uri = song.GetURI(); - return sticker_delete_value("song", uri.c_str(), name); + return sticker_delete_value("song", uri.c_str(), name, error); } struct sticker * -sticker_song_get(const LightSong &song) +sticker_song_get(const LightSong &song, Error &error) { const auto uri = song.GetURI(); - return sticker_load("song", uri.c_str()); + return sticker_load("song", uri.c_str(), error); } struct sticker_song_find_data { @@ -95,9 +97,11 @@ sticker_song_find_cb(const char *uri, const char *value, void *user_data) bool sticker_song_find(const Database &db, const char *base_uri, const char *name, + StickerOperator op, const char *value, void (*func)(const LightSong &song, const char *value, void *user_data), - void *user_data) + void *user_data, + Error &error) { struct sticker_song_find_data data; data.db = &db; @@ -109,16 +113,17 @@ sticker_song_find(const Database &db, const char *base_uri, const char *name, if (*data.base_uri != 0) /* append slash to base_uri */ data.base_uri = allocated = - g_strconcat(data.base_uri, "/", nullptr); + xstrcatdup(data.base_uri, "/"); else /* searching in root directory - no trailing slash */ allocated = nullptr; data.base_uri_length = strlen(data.base_uri); - bool success = sticker_find("song", data.base_uri, name, - sticker_song_find_cb, &data); - g_free(allocated); + bool success = sticker_find("song", data.base_uri, name, op, value, + sticker_song_find_cb, &data, + error); + free(allocated); return success; } diff --git a/src/sticker/SongSticker.hxx b/src/sticker/SongSticker.hxx index 5956cd6f9..ec4f1b344 100644 --- a/src/sticker/SongSticker.hxx +++ b/src/sticker/SongSticker.hxx @@ -20,6 +20,7 @@ #ifndef MPD_SONG_STICKER_HXX #define MPD_SONG_STICKER_HXX +#include "Match.hxx" #include "Compiler.h" #include <string> @@ -27,6 +28,7 @@ struct LightSong; struct sticker; class Database; +class Error; /** * Returns one value from a song's sticker record. The caller must @@ -34,7 +36,7 @@ class Database; */ gcc_pure std::string -sticker_song_get_value(const LightSong &song, const char *name); +sticker_song_get_value(const LightSong &song, const char *name, Error &error); /** * Sets a sticker value in the specified song. Overwrites existing @@ -42,20 +44,22 @@ sticker_song_get_value(const LightSong &song, const char *name); */ bool sticker_song_set_value(const LightSong &song, - const char *name, const char *value); + const char *name, const char *value, + Error &error); /** * Deletes a sticker from the database. All values are deleted. */ bool -sticker_song_delete(const LightSong &song); +sticker_song_delete(const LightSong &song, Error &error); /** * Deletes a sticker value. Does nothing if the sticker did not * exist. */ bool -sticker_song_delete_value(const LightSong &song, const char *name); +sticker_song_delete_value(const LightSong &song, const char *name, + Error &error); /** * Loads the sticker for the specified song. @@ -64,7 +68,7 @@ sticker_song_delete_value(const LightSong &song, const char *name); * @return a sticker object, or NULL on error or if there is no sticker */ sticker * -sticker_song_get(const LightSong &song); +sticker_song_get(const LightSong &song, Error &error); /** * Finds stickers with the specified name below the specified @@ -79,8 +83,10 @@ sticker_song_get(const LightSong &song); */ bool sticker_song_find(const Database &db, const char *base_uri, const char *name, + StickerOperator op, const char *value, void (*func)(const LightSong &song, const char *value, void *user_data), - void *user_data); + void *user_data, + Error &error); #endif diff --git a/src/sticker/StickerDatabase.cxx b/src/sticker/StickerDatabase.cxx index 93eaa900d..df6dc22dd 100644 --- a/src/sticker/StickerDatabase.cxx +++ b/src/sticker/StickerDatabase.cxx @@ -19,23 +19,18 @@ #include "config.h" #include "StickerDatabase.hxx" +#include "lib/sqlite/Domain.hxx" +#include "lib/sqlite/Util.hxx" #include "fs/Path.hxx" #include "Idle.hxx" #include "util/Error.hxx" -#include "util/Domain.hxx" #include "util/Macros.hxx" -#include "Log.hxx" #include <string> #include <map> -#include <sqlite3.h> #include <assert.h> -#if SQLITE_VERSION_NUMBER < 3003009 -#define sqlite3_prepare_v2 sqlite3_prepare -#endif - struct sticker { std::map<std::string, std::string> table; }; @@ -48,6 +43,9 @@ enum sticker_sql { STICKER_SQL_DELETE, STICKER_SQL_DELETE_VALUE, STICKER_SQL_FIND, + STICKER_SQL_FIND_VALUE, + STICKER_SQL_FIND_LT, + STICKER_SQL_FIND_GT, }; static const char *const sticker_sql[] = { @@ -65,6 +63,15 @@ static const char *const sticker_sql[] = { "DELETE FROM sticker WHERE type=? AND uri=? AND name=?", //[STICKER_SQL_FIND] = "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=?", + + //[STICKER_SQL_FIND_VALUE] = + "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value=?", + + //[STICKER_SQL_FIND_LT] = + "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value<?", + + //[STICKER_SQL_FIND_GT] = + "SELECT uri,value FROM sticker WHERE type=? AND uri LIKE (? || '%') AND name=? AND value>?", }; static const char sticker_sql_create[] = @@ -81,23 +88,13 @@ static const char sticker_sql_create[] = static sqlite3 *sticker_db; static sqlite3_stmt *sticker_stmt[ARRAY_SIZE(sticker_sql)]; -static constexpr Domain sticker_domain("sticker"); - -static void -LogError(sqlite3 *db, const char *msg) -{ - FormatError(sticker_domain, "%s: %s", msg, sqlite3_errmsg(db)); -} - static sqlite3_stmt * sticker_prepare(const char *sql, Error &error) { - int ret; sqlite3_stmt *stmt; - - ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, nullptr); + int ret = sqlite3_prepare_v2(sticker_db, sql, -1, &stmt, nullptr); if (ret != SQLITE_OK) { - error.Format(sticker_domain, ret, + error.Format(sqlite_domain, ret, "sqlite3_prepare_v2() failed: %s", sqlite3_errmsg(sticker_db)); return nullptr; @@ -118,9 +115,9 @@ sticker_global_init(Path path, Error &error) ret = sqlite3_open(path.c_str(), &sticker_db); if (ret != SQLITE_OK) { const std::string utf8 = path.ToUTF8(); - error.Format(sticker_domain, ret, - "Failed to open sqlite database '%s': %s", - utf8.c_str(), sqlite3_errmsg(sticker_db)); + error.Format(sqlite_domain, ret, + "Failed to open sqlite database '%s': %s", + utf8.c_str(), sqlite3_errmsg(sticker_db)); return false; } @@ -129,7 +126,7 @@ sticker_global_init(Path path, Error &error) ret = sqlite3_exec(sticker_db, sticker_sql_create, nullptr, nullptr, nullptr); if (ret != SQLITE_OK) { - error.Format(sticker_domain, ret, + error.Format(sqlite_domain, ret, "Failed to create sticker table: %s", sqlite3_errmsg(sticker_db)); return false; @@ -149,7 +146,7 @@ sticker_global_init(Path path, Error &error) } void -sticker_global_finish(void) +sticker_global_finish() { if (sticker_db == nullptr) /* not configured */ @@ -165,16 +162,16 @@ sticker_global_finish(void) } bool -sticker_enabled(void) +sticker_enabled() { return sticker_db != nullptr; } std::string -sticker_load_value(const char *type, const char *uri, const char *name) +sticker_load_value(const char *type, const char *uri, const char *name, + Error &error) { sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_GET]; - int ret; assert(sticker_enabled()); assert(type != nullptr); @@ -184,40 +181,12 @@ sticker_load_value(const char *type, const char *uri, const char *name) if (*name == 0) return std::string(); - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return std::string(); - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return std::string(); - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); + if (!BindAll(error, stmt, type, uri, name)) return std::string(); - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); std::string value; - if (ret == SQLITE_ROW) { - /* record found */ + if (ExecuteRow(stmt, error)) value = (const char*)sqlite3_column_text(stmt, 0); - } else if (ret == SQLITE_DONE) { - /* no record found */ - } else { - /* error */ - LogError(sticker_db, "sqlite3_step() failed"); - } sqlite3_reset(stmt); sqlite3_clear_bindings(stmt); @@ -227,63 +196,36 @@ sticker_load_value(const char *type, const char *uri, const char *name) static bool sticker_list_values(std::map<std::string, std::string> &table, - const char *type, const char *uri) + const char *type, const char *uri, + Error &error) { sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_LIST]; - int ret; assert(type != nullptr); assert(uri != nullptr); assert(sticker_enabled()); - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); + if (!BindAll(error, stmt, type, uri)) return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - do { - ret = sqlite3_step(stmt); - switch (ret) { - const char *name, *value; - - case SQLITE_ROW: - name = (const char*)sqlite3_column_text(stmt, 0); - value = (const char*)sqlite3_column_text(stmt, 1); + const bool success = ExecuteForEach(stmt, error, [stmt, &table](){ + const char *name = (const char *)sqlite3_column_text(stmt, 0); + const char *value = (const char *)sqlite3_column_text(stmt, 1); table.insert(std::make_pair(name, value)); - break; - case SQLITE_DONE: - break; - case SQLITE_BUSY: - /* no op */ - break; - default: - LogError(sticker_db, "sqlite3_step() failed"); - return false; - } - } while (ret != SQLITE_DONE); + }); sqlite3_reset(stmt); sqlite3_clear_bindings(stmt); - return true; + return success; } static bool sticker_update_value(const char *type, const char *uri, - const char *name, const char *value) + const char *name, const char *value, + Error &error) { sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_UPDATE]; - int ret; assert(type != nullptr); assert(uri != nullptr); @@ -293,56 +235,25 @@ sticker_update_value(const char *type, const char *uri, assert(sticker_enabled()); - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, value, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); + if (!BindAll(error, stmt, value, type, uri, name)) return false; - } - ret = sqlite3_bind_text(stmt, 3, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 4, name, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - LogError(sticker_db, "sqlite3_step() failed"); - return false; - } - - ret = sqlite3_changes(sticker_db); + bool modified = ExecuteModified(stmt, error); sqlite3_reset(stmt); sqlite3_clear_bindings(stmt); - idle_add(IDLE_STICKER); - return ret > 0; + if (modified) + idle_add(IDLE_STICKER); + return modified; } static bool sticker_insert_value(const char *type, const char *uri, - const char *name, const char *value) + const char *name, const char *value, + Error &error) { sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_INSERT]; - int ret; assert(type != nullptr); assert(uri != nullptr); @@ -352,52 +263,23 @@ sticker_insert_value(const char *type, const char *uri, assert(sticker_enabled()); - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 4, value, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); + if (!BindAll(error, stmt, type, uri, name, value)) return false; - } - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - LogError(sticker_db, "sqlite3_step() failed"); - return false; - } + bool success = ExecuteCommand(stmt, error); sqlite3_reset(stmt); sqlite3_clear_bindings(stmt); - - idle_add(IDLE_STICKER); - return true; + if (success) + idle_add(IDLE_STICKER); + return success; } bool sticker_store_value(const char *type, const char *uri, - const char *name, const char *value) + const char *name, const char *value, + Error &error) { assert(sticker_enabled()); assert(type != nullptr); @@ -408,96 +290,53 @@ sticker_store_value(const char *type, const char *uri, if (*name == 0) return false; - return sticker_update_value(type, uri, name, value) || - sticker_insert_value(type, uri, name, value); + return sticker_update_value(type, uri, name, value, error) || + sticker_insert_value(type, uri, name, value, error); } bool -sticker_delete(const char *type, const char *uri) +sticker_delete(const char *type, const char *uri, Error &error) { sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE]; - int ret; assert(sticker_enabled()); assert(type != nullptr); assert(uri != nullptr); - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); + if (!BindAll(error, stmt, type, uri)) return false; - } - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - LogError(sticker_db, "sqlite3_step() failed"); - return false; - } + bool modified = ExecuteModified(stmt, error); sqlite3_reset(stmt); sqlite3_clear_bindings(stmt); - idle_add(IDLE_STICKER); - return true; + if (modified) + idle_add(IDLE_STICKER); + return modified; } bool -sticker_delete_value(const char *type, const char *uri, const char *name) +sticker_delete_value(const char *type, const char *uri, const char *name, + Error &error) { sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_DELETE_VALUE]; - int ret; assert(sticker_enabled()); assert(type != nullptr); assert(uri != nullptr); - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - - ret = sqlite3_bind_text(stmt, 2, uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); + if (!BindAll(error, stmt, type, uri, name)) return false; - } - - ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } - do { - ret = sqlite3_step(stmt); - } while (ret == SQLITE_BUSY); - - if (ret != SQLITE_DONE) { - LogError(sticker_db, "sqlite3_step() failed"); - return false; - } - - ret = sqlite3_changes(sticker_db); + bool modified = ExecuteModified(stmt, error); sqlite3_reset(stmt); sqlite3_clear_bindings(stmt); - idle_add(IDLE_STICKER); - return ret > 0; + if (modified) + idle_add(IDLE_STICKER); + return modified; } void @@ -527,11 +366,11 @@ sticker_foreach(const sticker &sticker, } struct sticker * -sticker_load(const char *type, const char *uri) +sticker_load(const char *type, const char *uri, Error &error) { sticker s; - if (!sticker_list_values(s.table, type, uri)) + if (!sticker_list_values(s.table, type, uri, error)) return nullptr; if (s.table.empty()) @@ -541,64 +380,67 @@ sticker_load(const char *type, const char *uri) return new sticker(std::move(s)); } -bool -sticker_find(const char *type, const char *base_uri, const char *name, - void (*func)(const char *uri, const char *value, - void *user_data), - void *user_data) +static sqlite3_stmt * +BindFind(const char *type, const char *base_uri, const char *name, + StickerOperator op, const char *value, + Error &error) { - sqlite3_stmt *const stmt = sticker_stmt[STICKER_SQL_FIND]; - int ret; - assert(type != nullptr); assert(name != nullptr); - assert(func != nullptr); - assert(sticker_enabled()); - - sqlite3_reset(stmt); - - ret = sqlite3_bind_text(stmt, 1, type, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; - } if (base_uri == nullptr) base_uri = ""; - ret = sqlite3_bind_text(stmt, 2, base_uri, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); - return false; + switch (op) { + case StickerOperator::EXISTS: + return BindAllOrNull(error, sticker_stmt[STICKER_SQL_FIND], + type, base_uri, name); + + case StickerOperator::EQUALS: + return BindAllOrNull(error, + sticker_stmt[STICKER_SQL_FIND_VALUE], + type, base_uri, name, value); + + case StickerOperator::LESS_THAN: + return BindAllOrNull(error, + sticker_stmt[STICKER_SQL_FIND_LT], + type, base_uri, name, value); + + case StickerOperator::GREATER_THAN: + return BindAllOrNull(error, + sticker_stmt[STICKER_SQL_FIND_GT], + type, base_uri, name, value); } - ret = sqlite3_bind_text(stmt, 3, name, -1, nullptr); - if (ret != SQLITE_OK) { - LogError(sticker_db, "sqlite3_bind_text() failed"); + assert(false); + gcc_unreachable(); +} + +bool +sticker_find(const char *type, const char *base_uri, const char *name, + StickerOperator op, const char *value, + void (*func)(const char *uri, const char *value, + void *user_data), + void *user_data, + Error &error) +{ + assert(func != nullptr); + assert(sticker_enabled()); + + sqlite3_stmt *const stmt = BindFind(type, base_uri, name, op, value, + error); + if (stmt == nullptr) return false; - } - do { - ret = sqlite3_step(stmt); - switch (ret) { - case SQLITE_ROW: + const bool success = ExecuteForEach(stmt, error, + [stmt, func, user_data](){ func((const char*)sqlite3_column_text(stmt, 0), (const char*)sqlite3_column_text(stmt, 1), user_data); - break; - case SQLITE_DONE: - break; - case SQLITE_BUSY: - /* no op */ - break; - default: - LogError(sticker_db, "sqlite3_step() failed"); - return false; - } - } while (ret != SQLITE_DONE); + }); sqlite3_reset(stmt); sqlite3_clear_bindings(stmt); - return true; + return success; } diff --git a/src/sticker/StickerDatabase.hxx b/src/sticker/StickerDatabase.hxx index 8993489c4..d3cfe885e 100644 --- a/src/sticker/StickerDatabase.hxx +++ b/src/sticker/StickerDatabase.hxx @@ -42,6 +42,7 @@ #ifndef MPD_STICKER_DATABASE_HXX #define MPD_STICKER_DATABASE_HXX +#include "Match.hxx" #include "Compiler.h" #include <string> @@ -62,21 +63,22 @@ sticker_global_init(Path path, Error &error); * Close the sticker database. */ void -sticker_global_finish(void); +sticker_global_finish(); /** * Returns true if the sticker database is configured and available. */ gcc_const bool -sticker_enabled(void); +sticker_enabled(); /** * Returns one value from an object's sticker record. Returns an * empty string if the value doesn't exist. */ std::string -sticker_load_value(const char *type, const char *uri, const char *name); +sticker_load_value(const char *type, const char *uri, const char *name, + Error &error); /** * Sets a sticker value in the specified object. Overwrites existing @@ -84,21 +86,24 @@ sticker_load_value(const char *type, const char *uri, const char *name); */ bool sticker_store_value(const char *type, const char *uri, - const char *name, const char *value); + const char *name, const char *value, + Error &error); /** * Deletes a sticker from the database. All sticker values of the * specified object are deleted. */ bool -sticker_delete(const char *type, const char *uri); +sticker_delete(const char *type, const char *uri, + Error &error); /** * Deletes a sticker value. Fails if no sticker with this name * exists. */ bool -sticker_delete_value(const char *type, const char *uri, const char *name); +sticker_delete_value(const char *type, const char *uri, const char *name, + Error &error); /** * Frees resources held by the sticker object. @@ -140,7 +145,8 @@ sticker_foreach(const sticker &sticker, * @return a sticker object, or nullptr on error or if there is no sticker */ sticker * -sticker_load(const char *type, const char *uri); +sticker_load(const char *type, const char *uri, + Error &error); /** * Finds stickers with the specified name below the specified URI. @@ -149,13 +155,17 @@ sticker_load(const char *type, const char *uri); * @param base_uri the URI prefix of the resources, or nullptr if all * resources should be searched * @param name the name of the sticker + * @param op the comparison operator + * @param value the operand * @return true on success (even if no sticker was found), false on * failure */ bool sticker_find(const char *type, const char *base_uri, const char *name, + StickerOperator op, const char *value, void (*func)(const char *uri, const char *value, void *user_data), - void *user_data); + void *user_data, + Error &error); #endif diff --git a/src/storage/CompositeStorage.cxx b/src/storage/CompositeStorage.cxx index 89a2fc756..ce9c1e8b1 100644 --- a/src/storage/CompositeStorage.cxx +++ b/src/storage/CompositeStorage.cxx @@ -137,7 +137,7 @@ CompositeStorage::Directory::Make(const char *uri) Directory *directory = this; while (*uri != 0) { const std::string name = NextSegment(uri); -#if defined(__clang__) || GCC_CHECK_VERSION(4,8) +#if CLANG_OR_GCC_VERSION(4,8) auto i = directory->children.emplace(std::move(name), Directory()); #else diff --git a/src/system/FatalError.cxx b/src/system/FatalError.cxx index 35e94f169..cb5d7160a 100644 --- a/src/system/FatalError.cxx +++ b/src/system/FatalError.cxx @@ -23,10 +23,6 @@ #include "util/Domain.hxx" #include "LogV.hxx" -#ifdef HAVE_GLIB -#include <glib.h> -#endif - #include <unistd.h> #include <stdarg.h> #include <stdio.h> @@ -78,18 +74,31 @@ FatalError(const char *msg, const Error &error) FormatFatalError("%s: %s", msg, error.GetMessage()); } +#ifdef WIN32 + +void +FatalSystemError(const char *msg, DWORD code) +{ + char buffer[256]; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, code, 0, + buffer, sizeof(buffer), nullptr); + FormatFatalError("%s: %s", msg, buffer); +} + +#endif + void FatalSystemError(const char *msg) { - const char *system_error; #ifdef WIN32 - system_error = g_win32_error_message(GetLastError()); + FatalSystemError(msg, GetLastError()); #else - system_error = strerror(errno); -#endif - + const char *system_error = strerror(errno); FormatError(fatal_error_domain, "%s: %s", msg, system_error); Abort(); +#endif } void diff --git a/src/system/FatalError.hxx b/src/system/FatalError.hxx index d4698b3d9..2cf693bc3 100644 --- a/src/system/FatalError.hxx +++ b/src/system/FatalError.hxx @@ -23,6 +23,10 @@ #include "check.h" #include "Compiler.h" +#ifdef WIN32 +#include <windef.h> +#endif + class Error; /** @@ -53,6 +57,14 @@ gcc_noreturn void FatalSystemError(const char *msg); +#ifdef WIN32 + +gcc_noreturn +void +FatalSystemError(const char *msg, DWORD code); + +#endif + gcc_noreturn void FormatFatalSystemError(const char *fmt, ...); diff --git a/src/system/fd_util.h b/src/system/fd_util.h index f4a940e91..6d6fe1e69 100644 --- a/src/system/fd_util.h +++ b/src/system/fd_util.h @@ -103,7 +103,7 @@ socketpair_cloexec_nonblock(int domain, int type, int protocol, int sv[2]); #endif -#ifdef HAVE_LIBMPDCLIENT +#ifdef ENABLE_LIBMPDCLIENT /* Avoid symbol conflict with statically linked libmpdclient */ #define socket_cloexec_nonblock socket_cloexec_nonblock_noconflict #endif diff --git a/src/tag/ApeTag.cxx b/src/tag/ApeTag.cxx index f714a1624..4e3a8b187 100644 --- a/src/tag/ApeTag.cxx +++ b/src/tag/ApeTag.cxx @@ -30,7 +30,6 @@ #include <string.h> const struct tag_table ape_tags[] = { - { "album artist", TAG_ALBUM_ARTIST }, { "year", TAG_DATE }, { nullptr, TAG_NUM_OF_ITEM_TYPES } }; diff --git a/src/tag/Set.cxx b/src/tag/Set.cxx index 6a55a450f..157060d0c 100644 --- a/src/tag/Set.cxx +++ b/src/tag/Set.cxx @@ -75,7 +75,7 @@ TagSet::InsertUnique(const Tag &src, TagType type, const char *value, else builder.AddItem(type, value); CopyTagMask(builder, src, group_mask); -#if defined(__clang__) || GCC_CHECK_VERSION(4,8) +#if CLANG_OR_GCC_VERSION(4,8) emplace(builder.Commit()); #else insert(builder.Commit()); diff --git a/src/tag/TagId3.cxx b/src/tag/TagId3.cxx index 02dc58364..2f10dfb53 100644 --- a/src/tag/TagId3.cxx +++ b/src/tag/TagId3.cxx @@ -66,12 +66,14 @@ static constexpr Domain id3_domain("id3"); +gcc_pure static inline bool tag_is_id3v1(struct id3_tag *tag) { return (id3_tag_options(tag, 0, 0) & ID3_TAG_OPTION_ID3V1) != 0; } +gcc_pure static id3_utf8_t * tag_id3_getstring(const struct id3_frame *frame, unsigned i) { @@ -249,10 +251,11 @@ tag_id3_import_comment(struct id3_tag *tag, const char *id, TagType type, * Parse a TXXX name, and convert it to a TagType enum value. * Returns TAG_NUM_OF_ITEM_TYPES if the TXXX name is not understood. */ +gcc_pure static TagType tag_id3_parse_txxx_name(const char *name) { - static const struct tag_table txxx_tags[] = { + static constexpr struct tag_table txxx_tags[] = { { "ALBUMARTISTSORT", TAG_ALBUM_ARTIST_SORT }, { "MusicBrainz Artist Id", TAG_MUSICBRAINZ_ARTISTID }, { "MusicBrainz Album Id", TAG_MUSICBRAINZ_ALBUMID }, diff --git a/src/tag/TagId3.hxx b/src/tag/TagId3.hxx index 1928d539d..ece7a599b 100644 --- a/src/tag/TagId3.hxx +++ b/src/tag/TagId3.hxx @@ -29,7 +29,7 @@ struct Tag; struct id3_tag; class Error; -#ifdef HAVE_ID3TAG +#ifdef ENABLE_ID3TAG bool tag_id3_scan(Path path_fs, diff --git a/src/tag/TagPool.cxx b/src/tag/TagPool.cxx index 29f605337..88211be1b 100644 --- a/src/tag/TagPool.cxx +++ b/src/tag/TagPool.cxx @@ -87,7 +87,7 @@ calc_hash(TagType type, const char *p) return hash ^ type; } -#if defined(__clang__) || GCC_CHECK_VERSION(4,7) +#if CLANG_OR_GCC_VERSION(4,7) constexpr #endif static inline TagPoolSlot * diff --git a/src/util/Alloc.cxx b/src/util/Alloc.cxx index ec3579470..0533a58f8 100644 --- a/src/util/Alloc.cxx +++ b/src/util/Alloc.cxx @@ -74,3 +74,87 @@ xstrndup(const char *s, size_t n) return p; } + +#if CLANG_OR_GCC_VERSION(4,7) + +template<typename... Args> +static inline size_t +FillLengths(size_t *lengths, const char *a, Args&&... args) +{ + return FillLengths(lengths, a) + FillLengths(lengths + 1, args...); +} + +template<> +inline size_t +FillLengths(size_t *lengths, const char *a) +{ + return *lengths = strlen(a); +} + +template<typename... Args> +static inline void +StringCat(char *p, const size_t *lengths, const char *a, Args&&... args) +{ + StringCat(p, lengths, a); + StringCat(p + *lengths, lengths + 1, args...); +} + +template<> +inline void +StringCat(char *p, const size_t *lengths, const char *a) +{ + memcpy(p, a, *lengths); +} + +#endif + +template<typename... Args> +gcc_malloc gcc_nonnull_all +static inline char * +t_xstrcatdup(Args&&... args) +{ +#if CLANG_OR_GCC_VERSION(4,7) + constexpr size_t n = sizeof...(args); + + size_t lengths[n]; + const size_t total = FillLengths(lengths, args...); + + char *p = (char *)xalloc(total + 1); + StringCat(p, lengths, args...); + p[total] = 0; + return p; +#else + /* fallback implementation for gcc 4.6, because that old + compiler is too buggy to compile the above template + functions */ + const char *const argv[] = { args... }; + + size_t total = 0; + for (auto i : argv) + total += strlen(i); + + char *p = (char *)xalloc(total + 1), *q = p; + for (auto i : argv) + q = stpcpy(q, i); + + return p; +#endif +} + +char * +xstrcatdup(const char *a, const char *b) +{ + return t_xstrcatdup(a, b); +} + +char * +xstrcatdup(const char *a, const char *b, const char *c) +{ + return t_xstrcatdup(a, b, c); +} + +char * +xstrcatdup(const char *a, const char *b, const char *c, const char *d) +{ + return t_xstrcatdup(a, b, c, d); +} diff --git a/src/util/Alloc.hxx b/src/util/Alloc.hxx index 15c123b7a..654b7d0fe 100644 --- a/src/util/Alloc.hxx +++ b/src/util/Alloc.hxx @@ -64,4 +64,23 @@ gcc_malloc gcc_nonnull_all char * xstrndup(const char *s, size_t n); +/** + * Concatenate two strings, returning a new allocation. Use free() to + * free it. + * + * This function never fails; in out-of-memory situations, it aborts + * the process. + */ +gcc_malloc gcc_nonnull_all +char * +xstrcatdup(const char *a, const char *b); + +gcc_malloc gcc_nonnull_all +char * +xstrcatdup(const char *a, const char *b, const char *c); + +gcc_malloc gcc_nonnull_all +char * +xstrcatdup(const char *a, const char *b, const char *c, const char *d); + #endif diff --git a/src/util/Cast.hxx b/src/util/Cast.hxx index 887137da4..647171970 100644 --- a/src/util/Cast.hxx +++ b/src/util/Cast.hxx @@ -84,7 +84,7 @@ ContainerAttributeOffset(const A C::*p) * Cast the given pointer to a struct member to its parent structure. */ template<class C, class A> -#if defined(__clang__) || GCC_CHECK_VERSION(4,7) +#if CLANG_OR_GCC_VERSION(4,7) constexpr #endif static inline C & @@ -97,7 +97,7 @@ ContainerCast(A &a, A C::*member) * Cast the given pointer to a struct member to its parent structure. */ template<class C, class A> -#if defined(__clang__) || GCC_CHECK_VERSION(4,7) +#if CLANG_OR_GCC_VERSION(4,7) constexpr #endif static inline const C & diff --git a/src/util/DivideString.cxx b/src/util/DivideString.cxx new file mode 100644 index 000000000..f781d141f --- /dev/null +++ b/src/util/DivideString.cxx @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "DivideString.hxx" +#include "StringUtil.hxx" + +#include <string.h> + +DivideString::DivideString(const char *s, char separator, bool strip) + :first(nullptr) +{ + const char *x = strchr(s, separator); + if (x == nullptr) + return; + + size_t length = x - s; + second = x + 1; + + if (strip) + second = StripLeft(second); + + if (strip) { + const char *end = s + length; + s = StripLeft(s); + end = StripRight(s, end); + length = end - s; + } + + first = new char[length + 1]; + memcpy(first, s, length); + first[length] = 0; +} diff --git a/src/util/DivideString.hxx b/src/util/DivideString.hxx new file mode 100644 index 000000000..126aa45d1 --- /dev/null +++ b/src/util/DivideString.hxx @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2003-2014 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_DIVIDE_STRING_HXX +#define MPD_DIVIDE_STRING_HXX + +#include "Compiler.h" + +#include <assert.h> + +/** + * Split a given constant string at a separator character. Duplicates + * the first part to be able to null-terminate it. + */ +class DivideString { + char *first; + const char *second; + +public: + /** + * @param strip strip the first part and left-strip the second + * part? + */ + DivideString(const char *s, char separator, bool strip=false); + + ~DivideString() { + delete[] first; + } + + /** + * Was the separator found? + */ + bool IsDefined() const { + return first != nullptr; + } + + /** + * Is the first part empty? + */ + bool IsEmpty() const { + assert(IsDefined()); + + return *first == 0; + } + + const char *GetFirst() const { + assert(IsDefined()); + + return first; + } + + const char *GetSecond() const { + assert(IsDefined()); + + return second; + } +}; + +#endif diff --git a/src/util/Error.cxx b/src/util/Error.cxx index 92b2cc5d0..67a1b03fd 100644 --- a/src/util/Error.cxx +++ b/src/util/Error.cxx @@ -32,7 +32,7 @@ #include "Domain.hxx" #ifdef WIN32 -#include <glib.h> +#include <windows.h> #endif #include <errno.h> @@ -135,7 +135,11 @@ Error::FormatErrno(const char *fmt, ...) void Error::SetLastError(DWORD _code, const char *prefix) { - const char *msg = g_win32_error_message(_code); + char msg[256]; + FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, _code, 0, msg, sizeof(msg), nullptr); + Format(win32_domain, int(_code), "%s: %s", prefix, msg); } diff --git a/src/util/Manual.hxx b/src/util/Manual.hxx index 75cffac06..6ba932bdd 100644 --- a/src/util/Manual.hxx +++ b/src/util/Manual.hxx @@ -41,7 +41,7 @@ #include <assert.h> -#if defined(__clang__) || GCC_CHECK_VERSION(4,7) +#if CLANG_OR_GCC_VERSION(4,7) #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstrict-aliasing" #endif @@ -54,12 +54,7 @@ */ template<class T> class Manual { -#if GCC_OLDER_THAN(4,8) - /* no alignas() on gcc < 4.8: apply worst-case fallback */ - __attribute__((aligned(8))) -#else - alignas(T) -#endif + gcc_alignas(T, 8) char data[sizeof(T)]; #ifndef NDEBUG @@ -89,32 +84,46 @@ public: void Destruct() { assert(initialized); - T *t = (T *)data; - t->T::~T(); + T &t = Get(); + t.T::~T(); #ifndef NDEBUG initialized = false; #endif } + T &Get() { + assert(initialized); + + void *p = static_cast<void *>(data); + return *static_cast<T *>(p); + } + + const T &Get() const { + assert(initialized); + + const void *p = static_cast<const void *>(data); + return *static_cast<const T *>(p); + } + operator T &() { - return *(T *)data; + return Get(); } operator const T &() const { - return *(const T *)data; + return Get(); } T *operator->() { - return (T *)data; + return &Get(); } const T *operator->() const { - return (T *)data; + return &Get(); } }; -#if defined(__clang__) || GCC_VERSION >= 40700 +#if CLANG_OR_GCC_VERSION(4,7) #pragma GCC diagnostic pop #endif diff --git a/src/util/SplitString.cxx b/src/util/SplitString.cxx index 75e799279..0bb1e5165 100644 --- a/src/util/SplitString.cxx +++ b/src/util/SplitString.cxx @@ -18,20 +18,42 @@ */ #include "SplitString.hxx" +#include "StringUtil.hxx" #include <string.h> -SplitString::SplitString(const char *s, char separator) - :first(nullptr) +std::forward_list<std::string> +SplitString(const char *s, char separator, bool strip) { - const char *x = strchr(s, separator); - if (x == nullptr) - return; + if (strip) + s = StripLeft(s); - size_t length = x - s; - second = x + 1; + std::forward_list<std::string> list; + if (*s == 0) + return list; - first = new char[length + 1]; - memcpy(first, s, length); - first[length] = 0; + auto i = list.before_begin(); + + while (true) { + const char *next = strchr(s, separator); + if (next == nullptr) + break; + + const char *end = next++; + if (strip) + end = StripRight(s, end); + + i = list.emplace_after(i, s, end); + + s = next; + if (strip) + s = StripLeft(s); + } + + const char *end = s + strlen(s); + if (strip) + end = StripRight(s, end); + + list.emplace_after(i, s, end); + return list; } diff --git a/src/util/SplitString.hxx b/src/util/SplitString.hxx index 96ffb21ec..bc95cff81 100644 --- a/src/util/SplitString.hxx +++ b/src/util/SplitString.hxx @@ -20,52 +20,20 @@ #ifndef MPD_SPLIT_STRING_HXX #define MPD_SPLIT_STRING_HXX -#include "Compiler.h" - -#include <assert.h> +#include <forward_list> +#include <string> /** - * Split a given constant string at a separator character. Duplicates - * the first part to be able to null-terminate it. + * Split a string at a certain separator character into sub strings + * and returns a list of these. + * + * Two consecutive separator characters result in an empty string in + * the list. + * + * An empty input string, as a special case, results in an empty list + * (and not a list with an empty string). */ -class SplitString { - char *first; - const char *second; - -public: - SplitString(const char *s, char separator); - - ~SplitString() { - delete[] first; - } - - /** - * Was the separator found? - */ - bool IsDefined() const { - return first != nullptr; - } - - /** - * Is the first part empty? - */ - bool IsEmpty() const { - assert(IsDefined()); - - return *first == 0; - } - - const char *GetFirst() const { - assert(IsDefined()); - - return first; - } - - const char *GetSecond() const { - assert(IsDefined()); - - return second; - } -}; +std::forward_list<std::string> +SplitString(const char *s, char separator, bool strip=true); #endif diff --git a/src/util/StringUtil.cxx b/src/util/StringUtil.cxx index bcade2b3b..f5b4b7a8b 100644 --- a/src/util/StringUtil.cxx +++ b/src/util/StringUtil.cxx @@ -120,3 +120,23 @@ string_array_contains(const char *const* haystack, const char *needle) return false; } + +void +ToUpperASCII(char *dest, const char *src, size_t size) +{ + assert(dest != nullptr); + assert(src != nullptr); + assert(size > 1); + + char *const end = dest + size - 1; + + do { + char ch = *src++; + if (ch == 0) + break; + + *dest++ = ToUpperASCII(ch); + } while (dest < end); + + *dest = 0; +} diff --git a/src/util/StringUtil.hxx b/src/util/StringUtil.hxx index 9beda5441..77fe1be18 100644 --- a/src/util/StringUtil.hxx +++ b/src/util/StringUtil.hxx @@ -114,4 +114,12 @@ gcc_pure bool string_array_contains(const char *const* haystack, const char *needle); +/** + * Convert the specified ASCII string (0x00..0x7f) to upper case. + * + * @param size the destination buffer size + */ +void +ToUpperASCII(char *dest, const char *src, size_t size); + #endif diff --git a/src/win32/Win32Main.cxx b/src/win32/Win32Main.cxx index 75a1e9a23..30d94bef5 100644 --- a/src/win32/Win32Main.cxx +++ b/src/win32/Win32Main.cxx @@ -29,8 +29,6 @@ #include <cstdlib> #include <atomic> -#include <glib.h> - #include <windows.h> static int service_argc; @@ -42,7 +40,7 @@ static SERVICE_STATUS_HANDLE service_handle; static void WINAPI service_main(DWORD argc, CHAR *argv[]); -static SERVICE_TABLE_ENTRY service_registry[] = { +static constexpr SERVICE_TABLE_ENTRY service_registry[] = { {service_name, service_main}, {nullptr, nullptr} }; @@ -82,19 +80,12 @@ service_dispatcher(gcc_unused DWORD control, gcc_unused DWORD event_type, static void WINAPI service_main(gcc_unused DWORD argc, gcc_unused CHAR *argv[]) { - DWORD error_code; - gchar* error_message; - service_handle = RegisterServiceCtrlHandlerEx(service_name, service_dispatcher, nullptr); - if (service_handle == 0) { - error_code = GetLastError(); - error_message = g_win32_error_message(error_code); - FormatFatalError("RegisterServiceCtrlHandlerEx() failed: %s", - error_message); - } + if (service_handle == 0) + FatalSystemError("RegisterServiceCtrlHandlerEx() failed"); service_notify_status(SERVICE_START_PENDING); mpd_main(service_argc, service_argv); @@ -131,16 +122,13 @@ console_handler(DWORD event) int win32_main(int argc, char *argv[]) { - DWORD error_code; - gchar* error_message; - service_argc = argc; service_argv = argv; if (StartServiceCtrlDispatcher(service_registry)) return 0; /* run as service successefully */ - error_code = GetLastError(); + const DWORD error_code = GetLastError(); if (error_code == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) { /* running as console app */ running.store(false); @@ -149,9 +137,7 @@ int win32_main(int argc, char *argv[]) return mpd_main(argc, argv); } - error_message = g_win32_error_message(error_code); - FormatFatalError("StartServiceCtrlDispatcher() failed: %s", - error_message); + FatalSystemError("StartServiceCtrlDispatcher() failed", error_code); } void win32_app_started() diff --git a/src/win32/mpd.ico b/win32/res/mpd.ico Binary files differindex 86fd9fe43..86fd9fe43 100644 --- a/src/win32/mpd.ico +++ b/win32/res/mpd.ico diff --git a/src/win32/mpd_win32_rc.rc.in b/win32/res/mpd.rc.in index e5312dc78..3a33e9981 100644 --- a/src/win32/mpd_win32_rc.rc.in +++ b/win32/res/mpd.rc.in @@ -3,7 +3,7 @@ #define VERSION_NUMBER @VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@ #define VERSION_NUMBER_STR "@VERSION_MAJOR@,@VERSION_MINOR@,@VERSION_REVISION@,@VERSION_EXTRA@" -MPD_ICON ICON "@top_srcdir@/src/win32/mpd.ico" +MPD_ICON ICON "@top_srcdir@/win32/res/mpd.ico" 1 VERSIONINFO FILETYPE VFT_APP |