diff options
Diffstat (limited to 'src/command')
31 files changed, 1451 insertions, 535 deletions
diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx index 36f6fb97c..6a4b18198 100644 --- a/src/command/AllCommands.cxx +++ b/src/command/AllCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,23 +20,27 @@ #include "config.h" #include "AllCommands.hxx" #include "QueueCommands.hxx" +#include "TagCommands.hxx" #include "PlayerCommands.hxx" #include "PlaylistCommands.hxx" +#include "StorageCommands.hxx" #include "DatabaseCommands.hxx" #include "FileCommands.hxx" #include "OutputCommands.hxx" #include "MessageCommands.hxx" +#include "NeighborCommands.hxx" #include "OtherCommands.hxx" #include "Permission.hxx" #include "tag/TagType.h" #include "protocol/Result.hxx" -#include "Client.hxx" +#include "Partition.hxx" +#include "client/Client.hxx" #include "util/Tokenizer.hxx" #include "util/Error.hxx" #ifdef ENABLE_SQLITE #include "StickerCommands.hxx" -#include "StickerDatabase.hxx" +#include "sticker/StickerDatabase.hxx" #endif #include <assert.h> @@ -56,15 +60,15 @@ struct command { unsigned permission; int min; int max; - CommandResult (*handler)(Client &client, int argc, char **argv); + CommandResult (*handler)(Client &client, unsigned argc, char **argv); }; /* don't be fooled, this is the command handler for "commands" command */ static CommandResult -handle_commands(Client &client, int argc, char *argv[]); +handle_commands(Client &client, unsigned argc, char *argv[]); static CommandResult -handle_not_commands(Client &client, int argc, char *argv[]); +handle_not_commands(Client &client, unsigned argc, char *argv[]); /** * The command registry. @@ -74,14 +78,18 @@ handle_not_commands(Client &client, int argc, char *argv[]); static const struct command commands[] = { { "add", PERMISSION_ADD, 1, 1, handle_add }, { "addid", PERMISSION_ADD, 1, 2, handle_addid }, + { "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid }, { "channels", PERMISSION_READ, 0, 0, handle_channels }, { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, + { "cleartagid", PERMISSION_ADD, 1, 2, handle_cleartagid }, { "close", PERMISSION_NONE, -1, -1, handle_close }, { "commands", PERMISSION_NONE, 0, 0, handle_commands }, { "config", PERMISSION_ADMIN, 0, 0, handle_config }, { "consume", PERMISSION_CONTROL, 1, 1, handle_consume }, +#ifdef ENABLE_DATABASE { "count", PERMISSION_READ, 2, -1, handle_count }, +#endif { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade }, { "currentsong", PERMISSION_READ, 0, 0, handle_currentsong }, { "decoders", PERMISSION_READ, 0, 0, handle_decoders }, @@ -89,13 +97,24 @@ static const struct command commands[] = { { "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid }, { "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput }, { "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput }, +#ifdef ENABLE_DATABASE { "find", PERMISSION_READ, 2, -1, handle_find }, { "findadd", PERMISSION_ADD, 2, -1, handle_findadd}, +#endif { "idle", PERMISSION_READ, 0, -1, handle_idle }, { "kill", PERMISSION_ADMIN, -1, -1, handle_kill }, +#ifdef ENABLE_DATABASE { "list", PERMISSION_READ, 1, -1, handle_list }, { "listall", PERMISSION_READ, 0, 1, handle_listall }, { "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo }, +#endif + { "listfiles", PERMISSION_READ, 0, 1, handle_listfiles }, +#ifdef ENABLE_DATABASE + { "listmounts", PERMISSION_READ, 0, 0, handle_listmounts }, +#endif +#ifdef ENABLE_NEIGHBOR_PLUGINS + { "listneighbors", PERMISSION_READ, 0, 0, handle_listneighbors }, +#endif { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist }, { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo }, { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, @@ -103,6 +122,9 @@ static const struct command commands[] = { { "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo }, { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb }, { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay }, +#ifdef ENABLE_DATABASE + { "mount", PERMISSION_ADMIN, 2, 2, handle_mount }, +#endif { "move", PERMISSION_CONTROL, 2, 2, handle_move }, { "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid }, { "next", PERMISSION_CONTROL, 0, 0, handle_next }, @@ -128,6 +150,7 @@ static const struct command commands[] = { { "prio", PERMISSION_CONTROL, 2, -1, handle_prio }, { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid }, { "random", PERMISSION_CONTROL, 1, 1, handle_random }, + { "rangeid", PERMISSION_ADD, 2, 2, handle_rangeid }, { "readcomments", PERMISSION_READ, 1, 1, handle_read_comments }, { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, @@ -139,9 +162,11 @@ static const struct command commands[] = { { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan }, { "rm", PERMISSION_CONTROL, 1, 1, handle_rm }, { "save", PERMISSION_CONTROL, 1, 1, handle_save }, +#ifdef ENABLE_DATABASE { "search", PERMISSION_READ, 2, -1, handle_search }, { "searchadd", PERMISSION_ADD, 2, -1, handle_searchadd }, { "searchaddpl", PERMISSION_CONTROL, 3, -1, handle_searchaddpl }, +#endif { "seek", PERMISSION_CONTROL, 2, 2, handle_seek }, { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur }, { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid }, @@ -160,6 +185,9 @@ static const struct command commands[] = { { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, { "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput }, +#ifdef ENABLE_DATABASE + { "unmount", PERMISSION_ADMIN, 1, 1, handle_unmount }, +#endif { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe }, { "update", PERMISSION_CONTROL, 0, 1, handle_update }, { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, @@ -169,20 +197,26 @@ static const struct command commands[] = { static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]); static bool -command_available(gcc_unused const struct command *cmd) +command_available(gcc_unused const Partition &partition, + gcc_unused const struct command *cmd) { #ifdef ENABLE_SQLITE if (strcmp(cmd->cmd, "sticker") == 0) return sticker_enabled(); #endif +#ifdef ENABLE_NEIGHBOR_PLUGINS + if (strcmp(cmd->cmd, "listneighbors") == 0) + return neighbor_commands_available(partition.instance); +#endif + return true; } /* don't be fooled, this is the command handler for "commands" command */ static CommandResult handle_commands(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { const unsigned permission = client.GetPermission(); const struct command *cmd; @@ -191,7 +225,7 @@ handle_commands(Client &client, cmd = &commands[i]; if (cmd->permission == (permission & cmd->permission) && - command_available(cmd)) + command_available(client.partition, cmd)) client_printf(client, "command: %s\n", cmd->cmd); } @@ -200,7 +234,7 @@ handle_commands(Client &client, static CommandResult handle_not_commands(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { const unsigned permission = client.GetPermission(); const struct command *cmd; @@ -252,10 +286,10 @@ command_lookup(const char *name) static bool command_check_request(const struct command *cmd, Client &client, - unsigned permission, int argc, char *argv[]) + unsigned permission, unsigned argc, char *argv[]) { - int min = cmd->min + 1; - int max = cmd->max + 1; + const unsigned min = cmd->min + 1; + const unsigned max = cmd->max + 1; if (cmd->permission != (permission & cmd->permission)) { command_error(client, ACK_ERROR_PERMISSION, @@ -286,7 +320,7 @@ command_check_request(const struct command *cmd, Client &client, static const struct command * command_checked_lookup(Client &client, unsigned permission, - int argc, char *argv[]) + unsigned argc, char *argv[]) { const struct command *cmd; @@ -335,7 +369,9 @@ command_process(Client &client, unsigned num, char *line) current_command = nullptr; - return CommandResult::ERROR; + /* this client does not speak the MPD protocol; kick + the connection */ + return CommandResult::FINISH; } unsigned argc = 1; diff --git a/src/command/AllCommands.hxx b/src/command/AllCommands.hxx index 2be94c136..b7834a8de 100644 --- a/src/command/AllCommands.hxx +++ b/src/command/AllCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 diff --git a/src/command/CommandError.cxx b/src/command/CommandError.cxx index fc14d4a5d..89085fc68 100644 --- a/src/command/CommandError.cxx +++ b/src/command/CommandError.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,14 +19,13 @@ #include "config.h" #include "CommandError.hxx" -#include "DatabaseError.hxx" +#include "db/DatabaseError.hxx" #include "protocol/Result.hxx" #include "util/Error.hxx" #include "Log.hxx" -#include <glib.h> - #include <assert.h> +#include <string.h> #include <errno.h> CommandResult @@ -38,7 +37,7 @@ print_playlist_result(Client &client, PlaylistResult result) case PlaylistResult::ERRNO: command_error(client, ACK_ERROR_SYSTEM, "%s", - g_strerror(errno)); + strerror(errno)); return CommandResult::ERROR; case PlaylistResult::DENIED: @@ -102,6 +101,7 @@ print_error(Client &client, const Error &error) command_error(client, (ack)error.GetCode(), "%s", error.GetMessage()); return CommandResult::ERROR; +#ifdef ENABLE_DATABASE } else if (error.IsDomain(db_domain)) { switch ((enum db_error)error.GetCode()) { case DB_DISABLED: @@ -112,10 +112,15 @@ print_error(Client &client, const Error &error) case DB_NOT_FOUND: command_error(client, ACK_ERROR_NO_EXIST, "Not found"); return CommandResult::ERROR; + + case DB_CONFLICT: + command_error(client, ACK_ERROR_ARG, "Conflict"); + return CommandResult::ERROR; } +#endif } else if (error.IsDomain(errno_domain)) { command_error(client, ACK_ERROR_SYSTEM, "%s", - g_strerror(error.GetCode())); + strerror(error.GetCode())); return CommandResult::ERROR; } diff --git a/src/command/CommandError.hxx b/src/command/CommandError.hxx index c7d3fff76..b48baa5bf 100644 --- a/src/command/CommandError.hxx +++ b/src/command/CommandError.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 diff --git a/src/command/CommandListBuilder.cxx b/src/command/CommandListBuilder.cxx index 4e0a8bd2a..477c246ff 100644 --- a/src/command/CommandListBuilder.cxx +++ b/src/command/CommandListBuilder.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,7 +19,7 @@ #include "config.h" #include "CommandListBuilder.hxx" -#include "ClientInternal.hxx" +#include "client/ClientInternal.hxx" #include <string.h> diff --git a/src/command/CommandListBuilder.hxx b/src/command/CommandListBuilder.hxx index a112ac33b..0747c4697 100644 --- a/src/command/CommandListBuilder.hxx +++ b/src/command/CommandListBuilder.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 diff --git a/src/command/CommandResult.hxx b/src/command/CommandResult.hxx index 9916b70cb..a2e968fb6 100644 --- a/src/command/CommandResult.hxx +++ b/src/command/CommandResult.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx index a7d2467b8..a3ea8d0ae 100644 --- a/src/command/DatabaseCommands.cxx +++ b/src/command/DatabaseCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,47 +19,59 @@ #include "config.h" #include "DatabaseCommands.hxx" -#include "DatabaseQueue.hxx" -#include "DatabasePlaylist.hxx" -#include "DatabasePrint.hxx" -#include "DatabaseSelection.hxx" +#include "db/DatabaseGlue.hxx" +#include "db/DatabaseQueue.hxx" +#include "db/DatabasePlaylist.hxx" +#include "db/DatabasePrint.hxx" +#include "db/Count.hxx" +#include "db/Selection.hxx" #include "CommandError.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include "tag/Tag.hxx" -#include "util/UriUtil.hxx" +#include "util/ConstBuffer.hxx" #include "util/Error.hxx" #include "SongFilter.hxx" #include "protocol/Result.hxx" #include "BulkEdit.hxx" -#include <assert.h> #include <string.h> CommandResult -handle_lsinfo2(Client &client, int argc, char *argv[]) +handle_listfiles_db(Client &client, const char *uri) { - const char *uri; + const DatabaseSelection selection(uri, false); - if (argc == 2) - uri = argv[1]; - else + Error error; + if (!db_selection_print(client, selection, false, true, error)) + return print_error(client, error); + + return CommandResult::OK; +} + +CommandResult +handle_lsinfo2(Client &client, unsigned argc, char *argv[]) +{ + const char *const uri = argc == 2 + ? argv[1] /* default is root directory */ - uri = ""; + : ""; const DatabaseSelection selection(uri, false); Error error; - if (!db_selection_print(client, selection, true, error)) + if (!db_selection_print(client, selection, true, false, error)) return print_error(client, error); return CommandResult::OK; } static CommandResult -handle_match(Client &client, int argc, char *argv[], bool fold_case) +handle_match(Client &client, unsigned argc, char *argv[], bool fold_case) { + ConstBuffer<const char *> args(argv + 1, argc - 1); + SongFilter filter; - if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + if (!filter.Parse(args, fold_case)) { command_error(client, ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } @@ -67,28 +79,30 @@ handle_match(Client &client, int argc, char *argv[], bool fold_case) const DatabaseSelection selection("", true, &filter); Error error; - return db_selection_print(client, selection, true, error) + return db_selection_print(client, selection, true, false, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_find(Client &client, int argc, char *argv[]) +handle_find(Client &client, unsigned argc, char *argv[]) { return handle_match(client, argc, argv, false); } CommandResult -handle_search(Client &client, int argc, char *argv[]) +handle_search(Client &client, unsigned argc, char *argv[]) { return handle_match(client, argc, argv, true); } static CommandResult -handle_match_add(Client &client, int argc, char *argv[], bool fold_case) +handle_match_add(Client &client, unsigned argc, char *argv[], bool fold_case) { + ConstBuffer<const char *> args(argv + 1, argc - 1); + SongFilter filter; - if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + if (!filter.Parse(args, fold_case)) { command_error(client, ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } @@ -103,51 +117,73 @@ handle_match_add(Client &client, int argc, char *argv[], bool fold_case) } CommandResult -handle_findadd(Client &client, int argc, char *argv[]) +handle_findadd(Client &client, unsigned argc, char *argv[]) { return handle_match_add(client, argc, argv, false); } CommandResult -handle_searchadd(Client &client, int argc, char *argv[]) +handle_searchadd(Client &client, unsigned argc, char *argv[]) { return handle_match_add(client, argc, argv, true); } CommandResult -handle_searchaddpl(Client &client, int argc, char *argv[]) +handle_searchaddpl(Client &client, unsigned argc, char *argv[]) { - const char *playlist = argv[1]; + ConstBuffer<const char *> args(argv + 1, argc - 1); + const char *playlist = args.shift(); SongFilter filter; - if (!filter.Parse(argc - 2, argv + 2, true)) { + if (!filter.Parse(args, true)) { command_error(client, ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } Error error; - return search_add_to_playlist("", playlist, &filter, error) + const Database *db = client.GetDatabase(error); + if (db == nullptr) + return print_error(client, error); + + return search_add_to_playlist(*db, *client.GetStorage(), + "", playlist, &filter, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_count(Client &client, int argc, char *argv[]) +handle_count(Client &client, unsigned argc, char *argv[]) { + 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]; + group = tag_name_parse_i(s); + if (group == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, + "Unknown tag type: %s", s); + return CommandResult::ERROR; + } + + args.pop_back(); + args.pop_back(); + } + SongFilter filter; - if (!filter.Parse(argc - 1, argv + 1, false)) { + if (!args.IsEmpty() && !filter.Parse(args, false)) { command_error(client, ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } Error error; - return searchStatsForSongsIn(client, "", &filter, error) + return PrintSongCount(client, "", &filter, group, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_listall(Client &client, gcc_unused int argc, char *argv[]) +handle_listall(Client &client, gcc_unused unsigned argc, char *argv[]) { const char *directory = ""; @@ -155,30 +191,31 @@ handle_listall(Client &client, gcc_unused int argc, char *argv[]) directory = argv[1]; Error error; - return printAllIn(client, directory, error) + return db_selection_print(client, DatabaseSelection(directory, true), + false, false, error) ? CommandResult::OK : print_error(client, error); } CommandResult -handle_list(Client &client, int argc, char *argv[]) +handle_list(Client &client, unsigned argc, char *argv[]) { - unsigned tagType = locate_parse_type(argv[1]); - - if (tagType == TAG_NUM_OF_ITEM_TYPES) { - command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); - return CommandResult::ERROR; - } + ConstBuffer<const char *> args(argv + 1, argc - 1); + const char *tag_name = args.shift(); + unsigned tagType = locate_parse_type(tag_name); - if (tagType == LOCATE_TAG_ANY_TYPE) { + if (tagType >= TAG_NUM_OF_ITEM_TYPES && + tagType != LOCATE_TAG_FILE_TYPE) { command_error(client, ACK_ERROR_ARG, - "\"any\" is not a valid return tag type"); + "Unknown tag type: %s", tag_name); return CommandResult::ERROR; } - /* for compatibility with < 0.12.0 */ - SongFilter *filter; - if (argc == 3) { + SongFilter *filter = nullptr; + uint32_t group_mask = 0; + + if (args.size == 1) { + /* for compatibility with < 0.12.0 */ if (tagType != TAG_ALBUM) { command_error(client, ACK_ERROR_ARG, "should be \"%s\" for 3 arguments", @@ -186,21 +223,45 @@ handle_list(Client &client, int argc, char *argv[]) return CommandResult::ERROR; } - filter = new SongFilter((unsigned)TAG_ARTIST, argv[2]); - } else if (argc > 2) { + filter = new SongFilter((unsigned)TAG_ARTIST, args.shift()); + } + + while (args.size >= 2 && + strcmp(args[args.size - 2], "group") == 0) { + const char *s = args[args.size - 1]; + TagType gt = tag_name_parse_i(s); + if (gt == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, + "Unknown tag type: %s", s); + return CommandResult::ERROR; + } + + group_mask |= 1u << unsigned(gt); + + args.pop_back(); + args.pop_back(); + } + + if (!args.IsEmpty()) { filter = new SongFilter(); - if (!filter->Parse(argc - 2, argv + 2, false)) { + if (!filter->Parse(args, false)) { delete filter; command_error(client, ACK_ERROR_ARG, "not able to parse args"); return CommandResult::ERROR; } - } else - filter = nullptr; + } + + if (tagType < TAG_NUM_OF_ITEM_TYPES && + group_mask & (1u << tagType)) { + delete filter; + command_error(client, ACK_ERROR_ARG, "Conflicting group"); + return CommandResult::ERROR; + } Error error; CommandResult ret = - listAllUniqueTags(client, tagType, filter, error) + PrintUniqueTags(client, tagType, group_mask, filter, error) ? CommandResult::OK : print_error(client, error); @@ -210,7 +271,7 @@ handle_list(Client &client, int argc, char *argv[]) } CommandResult -handle_listallinfo(Client &client, gcc_unused int argc, char *argv[]) +handle_listallinfo(Client &client, gcc_unused unsigned argc, char *argv[]) { const char *directory = ""; @@ -218,7 +279,8 @@ handle_listallinfo(Client &client, gcc_unused int argc, char *argv[]) directory = argv[1]; Error error; - return printInfoForAllIn(client, directory, error) + return db_selection_print(client, DatabaseSelection(directory, true), + true, false, error) ? CommandResult::OK : print_error(client, error); } diff --git a/src/command/DatabaseCommands.hxx b/src/command/DatabaseCommands.hxx index 76b2ba817..7abf89e0c 100644 --- a/src/command/DatabaseCommands.hxx +++ b/src/command/DatabaseCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -25,33 +25,36 @@ class Client; CommandResult -handle_lsinfo2(Client &client, int argc, char *argv[]); +handle_listfiles_db(Client &client, const char *uri); CommandResult -handle_find(Client &client, int argc, char *argv[]); +handle_lsinfo2(Client &client, unsigned argc, char *argv[]); CommandResult -handle_findadd(Client &client, int argc, char *argv[]); +handle_find(Client &client, unsigned argc, char *argv[]); CommandResult -handle_search(Client &client, int argc, char *argv[]); +handle_findadd(Client &client, unsigned argc, char *argv[]); CommandResult -handle_searchadd(Client &client, int argc, char *argv[]); +handle_search(Client &client, unsigned argc, char *argv[]); CommandResult -handle_searchaddpl(Client &client, int argc, char *argv[]); +handle_searchadd(Client &client, unsigned argc, char *argv[]); CommandResult -handle_count(Client &client, int argc, char *argv[]); +handle_searchaddpl(Client &client, unsigned argc, char *argv[]); CommandResult -handle_listall(Client &client, int argc, char *argv[]); +handle_count(Client &client, unsigned argc, char *argv[]); CommandResult -handle_list(Client &client, int argc, char *argv[]); +handle_listall(Client &client, unsigned argc, char *argv[]); CommandResult -handle_listallinfo(Client &client, int argc, char *argv[]); +handle_list(Client &client, unsigned argc, char *argv[]); + +CommandResult +handle_listallinfo(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx index eecc3102f..0a4f9592a 100644 --- a/src/command/FileCommands.cxx +++ b/src/command/FileCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -17,23 +17,108 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define __STDC_FORMAT_MACROS /* for PRIu64 */ + #include "config.h" #include "FileCommands.hxx" #include "CommandError.hxx" #include "protocol/Ack.hxx" #include "protocol/Result.hxx" -#include "ClientFile.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include "util/CharUtil.hxx" +#include "util/UriUtil.hxx" #include "util/Error.hxx" #include "tag/TagHandler.hxx" #include "tag/ApeTag.hxx" #include "tag/TagId3.hxx" +#include "TagStream.hxx" #include "TagFile.hxx" -#include "Mapper.hxx" +#include "storage/StorageInterface.hxx" #include "fs/AllocatedPath.hxx" +#include "fs/FileSystem.hxx" +#include "TimePrint.hxx" +#include "ls.hxx" #include <assert.h> +#include <sys/stat.h> +#include <inttypes.h> /* for PRIu64 */ + +gcc_pure +static bool +SkipNameFS(const char *name_fs) +{ + return name_fs[0] == '.' && + (name_fs[1] == 0 || + (name_fs[1] == '.' && name_fs[2] == 0)); +} + +gcc_pure +static bool +skip_path(const char *name_fs) +{ + return strchr(name_fs, '\n') != nullptr; +} + +#if defined(WIN32) && GCC_CHECK_VERSION(4,6) +/* PRIu64 causes bogus compiler warning */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" +#endif + +CommandResult +handle_listfiles_local(Client &client, const char *path_utf8) +{ + const auto path_fs = AllocatedPath::FromUTF8(path_utf8); + if (path_fs.IsNull()) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported file name"); + return CommandResult::ERROR; + } + + Error error; + if (!client.AllowFile(path_fs, error)) + return print_error(client, error); + + DirectoryReader reader(path_fs); + if (reader.HasFailed()) { + error.FormatErrno("Failed to open '%s'", path_utf8); + return print_error(client, error); + } + + while (reader.ReadEntry()) { + const Path name_fs = reader.GetEntry(); + if (SkipNameFS(name_fs.c_str()) || skip_path(name_fs.c_str())) + continue; + + std::string name_utf8 = name_fs.ToUTF8(); + if (name_utf8.empty()) + continue; + + const AllocatedPath full_fs = + AllocatedPath::Build(path_fs, name_fs); + struct stat st; + if (!StatFile(full_fs, st, false)) + continue; + + if (S_ISREG(st.st_mode)) { + client_printf(client, "file: %s\n" + "size: %" PRIu64 "\n", + name_utf8.c_str(), + uint64_t(st.st_size)); + } else if (S_ISDIR(st.st_mode)) + client_printf(client, "directory: %s\n", + name_utf8.c_str()); + + time_print(client, "Last-Modified", st.st_mtime); + } + + return CommandResult::OK; +} + +#if defined(WIN32) && GCC_CHECK_VERSION(4,6) +#pragma GCC diagnostic pop +#endif gcc_pure static bool @@ -80,19 +165,52 @@ static constexpr tag_handler print_comment_handler = { print_pair, }; +static CommandResult +read_stream_comments(Client &client, const char *uri) +{ + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return CommandResult::ERROR; + } + + if (!tag_stream_scan(uri, print_comment_handler, &client)) { + command_error(client, ACK_ERROR_NO_EXIST, + "Failed to load file"); + return CommandResult::ERROR; + } + + return CommandResult::OK; + +} + +static CommandResult +read_file_comments(Client &client, const Path path_fs) +{ + if (!tag_file_scan(path_fs, print_comment_handler, &client)) { + command_error(client, ACK_ERROR_NO_EXIST, + "Failed to load file"); + return CommandResult::ERROR; + } + + tag_ape_scan2(path_fs, &print_comment_handler, &client); + tag_id3_scan(path_fs, &print_comment_handler, &client); + + return CommandResult::OK; + +} + CommandResult -handle_read_comments(Client &client, gcc_unused int argc, char *argv[]) +handle_read_comments(Client &client, gcc_unused unsigned argc, char *argv[]) { assert(argc == 2); const char *const uri = argv[1]; - AllocatedPath path_fs = AllocatedPath::Null(); - if (memcmp(uri, "file:///", 8) == 0) { /* read comments from arbitrary local file */ const char *path_utf8 = uri + 7; - path_fs = AllocatedPath::FromUTF8(path_utf8); + AllocatedPath path_fs = AllocatedPath::FromUTF8(path_utf8); if (path_fs.IsNull()) { command_error(client, ACK_ERROR_NO_EXIST, "unsupported file name"); @@ -100,28 +218,41 @@ handle_read_comments(Client &client, gcc_unused int argc, char *argv[]) } Error error; - if (!client_allow_file(client, path_fs, error)) + if (!client.AllowFile(path_fs, error)) return print_error(client, error); - } else if (*uri != '/') { - path_fs = map_uri_fs(uri); - if (path_fs.IsNull()) { + + return read_file_comments(client, path_fs); + } else if (uri_has_scheme(uri)) { + return read_stream_comments(client, uri); + } else if (!PathTraitsUTF8::IsAbsolute(uri)) { +#ifdef ENABLE_DATABASE + const Storage *storage = client.GetStorage(); + if (storage == nullptr) { +#endif command_error(client, ACK_ERROR_NO_EXIST, - "No such file"); + "No database"); return CommandResult::ERROR; +#ifdef ENABLE_DATABASE } - } else { + + { + AllocatedPath path_fs = storage->MapFS(uri); + if (!path_fs.IsNull()) + return read_file_comments(client, path_fs); + } + + { + const std::string uri2 = storage->MapUTF8(uri); + if (uri_has_scheme(uri2.c_str())) + return read_stream_comments(client, + uri2.c_str()); + } + command_error(client, ACK_ERROR_NO_EXIST, "No such file"); return CommandResult::ERROR; - } - - if (!tag_file_scan(path_fs, &print_comment_handler, &client)) { - command_error(client, ACK_ERROR_NO_EXIST, - "Failed to load file"); +#endif + } else { + command_error(client, ACK_ERROR_NO_EXIST, "No such file"); return CommandResult::ERROR; } - - tag_ape_scan2(path_fs, &print_comment_handler, &client); - tag_id3_scan(path_fs, &print_comment_handler, &client); - - return CommandResult::OK; } diff --git a/src/command/FileCommands.hxx b/src/command/FileCommands.hxx index e184d6e30..62835a82c 100644 --- a/src/command/FileCommands.hxx +++ b/src/command/FileCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -25,6 +25,9 @@ class Client; CommandResult -handle_read_comments(Client &client, int argc, char *argv[]); +handle_listfiles_local(Client &client, const char *path_utf8); + +CommandResult +handle_read_comments(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/MessageCommands.cxx b/src/command/MessageCommands.cxx index 7d9893e70..a86bdf30c 100644 --- a/src/command/MessageCommands.cxx +++ b/src/command/MessageCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,12 +19,11 @@ #include "config.h" #include "MessageCommands.hxx" -#include "Client.hxx" -#include "ClientList.hxx" +#include "client/Client.hxx" +#include "client/ClientList.hxx" #include "Instance.hxx" -#include "Main.hxx" +#include "Partition.hxx" #include "protocol/Result.hxx" -#include "protocol/ArgParser.hxx" #include <set> #include <string> @@ -32,7 +31,7 @@ #include <assert.h> CommandResult -handle_subscribe(Client &client, gcc_unused int argc, char *argv[]) +handle_subscribe(Client &client, gcc_unused unsigned argc, char *argv[]) { assert(argc == 2); @@ -62,7 +61,7 @@ handle_subscribe(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_unsubscribe(Client &client, gcc_unused int argc, char *argv[]) +handle_unsubscribe(Client &client, gcc_unused unsigned argc, char *argv[]) { assert(argc == 2); @@ -77,14 +76,14 @@ handle_unsubscribe(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_channels(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { assert(argc == 1); std::set<std::string> channels; - for (const auto &c : *instance->client_list) - channels.insert(c->subscriptions.begin(), - c->subscriptions.end()); + for (const auto &c : *client.partition.instance.client_list) + channels.insert(c.subscriptions.begin(), + c.subscriptions.end()); for (const auto &channel : channels) client_printf(client, "channel: %s\n", channel.c_str()); @@ -94,7 +93,7 @@ handle_channels(Client &client, CommandResult handle_read_messages(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { assert(argc == 1); @@ -111,7 +110,7 @@ handle_read_messages(Client &client, CommandResult handle_send_message(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { assert(argc == 3); @@ -123,8 +122,8 @@ handle_send_message(Client &client, bool sent = false; const ClientMessage msg(argv[1], argv[2]); - for (const auto &c : *instance->client_list) - if (c->PushMessage(msg)) + for (auto &c : *client.partition.instance.client_list) + if (c.PushMessage(msg)) sent = true; if (sent) diff --git a/src/command/MessageCommands.hxx b/src/command/MessageCommands.hxx index 6a107f69d..ac8afe2fb 100644 --- a/src/command/MessageCommands.hxx +++ b/src/command/MessageCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -25,18 +25,18 @@ class Client; CommandResult -handle_subscribe(Client &client, int argc, char *argv[]); +handle_subscribe(Client &client, unsigned argc, char *argv[]); CommandResult -handle_unsubscribe(Client &client, int argc, char *argv[]); +handle_unsubscribe(Client &client, unsigned argc, char *argv[]); CommandResult -handle_channels(Client &client, int argc, char *argv[]); +handle_channels(Client &client, unsigned argc, char *argv[]); CommandResult -handle_read_messages(Client &client, int argc, char *argv[]); +handle_read_messages(Client &client, unsigned argc, char *argv[]); CommandResult -handle_send_message(Client &client, int argc, char *argv[]); +handle_send_message(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/NeighborCommands.cxx b/src/command/NeighborCommands.cxx new file mode 100644 index 000000000..22e8adf9e --- /dev/null +++ b/src/command/NeighborCommands.cxx @@ -0,0 +1,59 @@ +/* + * 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 "NeighborCommands.hxx" +#include "client/Client.hxx" +#include "Instance.hxx" +#include "Partition.hxx" +#include "protocol/Result.hxx" +#include "neighbor/Glue.hxx" +#include "neighbor/Info.hxx" + +#include <set> +#include <string> + +#include <assert.h> + +bool +neighbor_commands_available(const Instance &instance) +{ + return instance.neighbors != nullptr; +} + +CommandResult +handle_listneighbors(Client &client, + gcc_unused unsigned argc, gcc_unused char *argv[]) +{ + const NeighborGlue *const neighbors = + client.partition.instance.neighbors; + if (neighbors == nullptr) { + command_error(client, ACK_ERROR_UNKNOWN, + "No neighbor plugin configured"); + return CommandResult::ERROR; + } + + for (const auto &i : neighbors->GetList()) + client_printf(client, + "neighbor: %s\n" + "name: %s\n", + i.uri.c_str(), + i.display_name.c_str()); + return CommandResult::OK; +} diff --git a/src/command/NeighborCommands.hxx b/src/command/NeighborCommands.hxx new file mode 100644 index 000000000..7fb309aeb --- /dev/null +++ b/src/command/NeighborCommands.hxx @@ -0,0 +1,36 @@ +/* + * 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_NEIGHBOR_COMMANDS_HXX +#define MPD_NEIGHBOR_COMMANDS_HXX + +#include "CommandResult.hxx" +#include "Compiler.h" + +struct Instance; +class Client; + +gcc_pure +bool +neighbor_commands_available(const Instance &instance); + +CommandResult +handle_listneighbors(Client &client, unsigned argc, char *argv[]); + +#endif diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx index 7b2cb1331..a924f77b5 100644 --- a/src/command/OtherCommands.cxx +++ b/src/command/OtherCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,33 +19,38 @@ #include "config.h" #include "OtherCommands.hxx" -#include "DatabaseCommands.hxx" +#include "FileCommands.hxx" +#include "StorageCommands.hxx" #include "CommandError.hxx" -#include "UpdateGlue.hxx" -#include "Directory.hxx" -#include "Song.hxx" +#include "db/Uri.hxx" +#include "storage/StorageInterface.hxx" +#include "DetachedSong.hxx" #include "SongPrint.hxx" #include "TagPrint.hxx" +#include "TagStream.hxx" +#include "tag/TagHandler.hxx" #include "TimePrint.hxx" -#include "Mapper.hxx" -#include "DecoderPrint.hxx" +#include "decoder/DecoderPrint.hxx" #include "protocol/ArgParser.hxx" #include "protocol/Result.hxx" #include "ls.hxx" -#include "Volume.hxx" -#include "util/ASCII.hxx" +#include "mixer/Volume.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" #include "fs/AllocatedPath.hxx" #include "Stats.hxx" #include "Permission.hxx" #include "PlaylistFile.hxx" -#include "ClientFile.hxx" -#include "Client.hxx" +#include "db/PlaylistVector.hxx" +#include "client/Client.hxx" +#include "Partition.hxx" +#include "Instance.hxx" #include "Idle.hxx" -#ifdef ENABLE_SQLITE -#include "StickerDatabase.hxx" +#ifdef ENABLE_DATABASE +#include "DatabaseCommands.hxx" +#include "db/Interface.hxx" +#include "db/update/Service.hxx" #endif #include <assert.h> @@ -64,7 +69,7 @@ print_spl_list(Client &client, const PlaylistVector &list) CommandResult handle_urlhandlers(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { if (client.IsLocal()) client_puts(client, "handler: file://\n"); @@ -74,7 +79,7 @@ handle_urlhandlers(Client &client, CommandResult handle_decoders(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { decoder_list_print(client); return CommandResult::OK; @@ -82,7 +87,7 @@ handle_decoders(Client &client, CommandResult handle_tagtypes(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { tag_print_types(client); return CommandResult::OK; @@ -90,28 +95,74 @@ handle_tagtypes(Client &client, CommandResult handle_kill(gcc_unused Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { return CommandResult::KILL; } CommandResult handle_close(gcc_unused Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { return CommandResult::FINISH; } +static void +print_tag(TagType type, const char *value, void *ctx) +{ + Client &client = *(Client *)ctx; + + tag_print(client, type, value); +} + CommandResult -handle_lsinfo(Client &client, int argc, char *argv[]) +handle_listfiles(Client &client, unsigned argc, char *argv[]) { - const char *uri; + const char *const uri = argc == 2 + ? argv[1] + /* default is root directory */ + : ""; + + if (memcmp(uri, "file:///", 8) == 0) + /* list local directory */ + return handle_listfiles_local(client, uri + 7); + +#ifdef ENABLE_DATABASE + if (uri_has_scheme(uri)) + /* use storage plugin to list remote directory */ + return handle_listfiles_storage(client, uri); + + /* must be a path relative to the configured + music_directory */ + + if (client.partition.instance.storage != nullptr) + /* if we have a storage instance, obtain a list of + files from it */ + return handle_listfiles_storage(client, + *client.partition.instance.storage, + uri); + + /* fall back to entries from database if we have no storage */ + return handle_listfiles_db(client, uri); +#else + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +#endif +} + +static constexpr tag_handler print_tag_handler = { + nullptr, + print_tag, + nullptr, +}; - if (argc == 2) - uri = argv[1]; - else +CommandResult +handle_lsinfo(Client &client, unsigned argc, char *argv[]) +{ + const char *const uri = argc == 2 + ? argv[1] /* default is root directory */ - uri = ""; + : ""; if (memcmp(uri, "file:///", 8) == 0) { /* print information about an arbitrary local file */ @@ -125,55 +176,63 @@ handle_lsinfo(Client &client, int argc, char *argv[]) } Error error; - if (!client_allow_file(client, path_fs, error)) + if (!client.AllowFile(path_fs, error)) return print_error(client, error); - Song *song = Song::LoadFile(path_utf8, nullptr); - if (song == nullptr) { + DetachedSong song(path_utf8); + if (!song.Update()) { command_error(client, ACK_ERROR_NO_EXIST, "No such file"); return CommandResult::ERROR; } - song_print_info(client, *song); - song->Free(); + song_print_info(client, song); return CommandResult::OK; } + if (uri_has_scheme(uri)) { + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return CommandResult::ERROR; + } + + if (!tag_stream_scan(uri, print_tag_handler, &client)) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such file"); + return CommandResult::ERROR; + } + + return CommandResult::OK; + } + +#ifdef ENABLE_DATABASE CommandResult result = handle_lsinfo2(client, argc, argv); if (result != CommandResult::OK) return result; +#endif if (isRootDirectory(uri)) { Error error; const auto &list = ListPlaylistFiles(error); print_spl_list(client, list); + } else { +#ifndef ENABLE_DATABASE + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +#endif } return CommandResult::OK; } -CommandResult -handle_update(Client &client, gcc_unused int argc, char *argv[]) -{ - const char *path = ""; - unsigned ret; +#ifdef ENABLE_DATABASE - assert(argc <= 2); - if (argc == 2) { - path = argv[1]; - - if (*path == 0 || strcmp(path, "/") == 0) - /* backwards compatibility with MPD 0.15 */ - path = ""; - else if (!uri_safe_local(path)) { - command_error(client, ACK_ERROR_ARG, - "Malformed path"); - return CommandResult::ERROR; - } - } - - ret = update_enqueue(path, false); +static CommandResult +handle_update(Client &client, UpdateService &update, + const char *uri_utf8, bool discard) +{ + unsigned ret = update.Enqueue(uri_utf8, discard); if (ret > 0) { client_printf(client, "updating_db: %i\n", ret); return CommandResult::OK; @@ -184,36 +243,78 @@ handle_update(Client &client, gcc_unused int argc, char *argv[]) } } -CommandResult -handle_rescan(Client &client, gcc_unused int argc, char *argv[]) +static CommandResult +handle_update(Client &client, Database &db, + const char *uri_utf8, bool discard) { + Error error; + unsigned id = db.Update(uri_utf8, discard, error); + if (id > 0) { + client_printf(client, "updating_db: %i\n", id); + return CommandResult::OK; + } else if (error.IsDefined()) { + return print_error(client, error); + } else { + /* Database::Update() has returned 0 without setting + the Error: the method is not implemented */ + command_error(client, ACK_ERROR_NO_EXIST, "Not implemented"); + return CommandResult::ERROR; + } +} + +#endif + +static CommandResult +handle_update(Client &client, unsigned argc, char *argv[], bool discard) +{ +#ifdef ENABLE_DATABASE const char *path = ""; - unsigned ret; assert(argc <= 2); if (argc == 2) { path = argv[1]; - if (!uri_safe_local(path)) { + if (*path == 0 || strcmp(path, "/") == 0) + /* backwards compatibility with MPD 0.15 */ + path = ""; + else if (!uri_safe_local(path)) { command_error(client, ACK_ERROR_ARG, "Malformed path"); return CommandResult::ERROR; } } - ret = update_enqueue(path, true); - if (ret > 0) { - client_printf(client, "updating_db: %i\n", ret); - return CommandResult::OK; - } else { - command_error(client, ACK_ERROR_UPDATE_ALREADY, - "already updating"); - return CommandResult::ERROR; - } + UpdateService *update = client.partition.instance.update; + if (update != nullptr) + return handle_update(client, *update, path, discard); + + Database *db = client.partition.instance.database; + if (db != nullptr) + return handle_update(client, *db, path, discard); +#else + (void)argc; + (void)argv; + (void)discard; +#endif + + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +} + +CommandResult +handle_update(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + return handle_update(client, argc, argv, false); } CommandResult -handle_setvol(Client &client, gcc_unused int argc, char *argv[]) +handle_rescan(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + return handle_update(client, argc, argv, true); +} + +CommandResult +handle_setvol(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned level; bool success; @@ -226,7 +327,7 @@ handle_setvol(Client &client, gcc_unused int argc, char *argv[]) return CommandResult::ERROR; } - success = volume_level_change(level); + success = volume_level_change(client.partition.outputs, level); if (!success) { command_error(client, ACK_ERROR_SYSTEM, "problems setting volume"); @@ -237,7 +338,7 @@ handle_setvol(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_volume(Client &client, gcc_unused int argc, char *argv[]) +handle_volume(Client &client, gcc_unused unsigned argc, char *argv[]) { int relative; if (!check_int(client, &relative, argv[1])) @@ -248,7 +349,7 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[]) return CommandResult::ERROR; } - const int old_volume = volume_level_get(); + const int old_volume = volume_level_get(client.partition.outputs); if (old_volume < 0) { command_error(client, ACK_ERROR_SYSTEM, "No mixer"); return CommandResult::ERROR; @@ -260,7 +361,8 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[]) else if (new_volume > 100) new_volume = 100; - if (new_volume != old_volume && !volume_level_change(new_volume)) { + if (new_volume != old_volume && + !volume_level_change(client.partition.outputs, new_volume)) { command_error(client, ACK_ERROR_SYSTEM, "problems setting volume"); return CommandResult::ERROR; @@ -271,7 +373,7 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_stats(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { stats_print(client); return CommandResult::OK; @@ -279,13 +381,13 @@ handle_stats(Client &client, CommandResult handle_ping(gcc_unused Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { return CommandResult::OK; } CommandResult -handle_password(Client &client, gcc_unused int argc, char *argv[]) +handle_password(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned permission = 0; @@ -301,7 +403,7 @@ handle_password(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_config(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { if (!client.IsLocal()) { command_error(client, ACK_ERROR_PERMISSION, @@ -309,31 +411,33 @@ handle_config(Client &client, return CommandResult::ERROR; } - const char *path = mapper_get_music_directory_utf8(); - if (path != nullptr) - client_printf(client, "music_directory: %s\n", path); +#ifdef ENABLE_DATABASE + const Storage *storage = client.GetStorage(); + if (storage != nullptr) { + const auto path = storage->MapUTF8(""); + client_printf(client, "music_directory: %s\n", path.c_str()); + } +#endif return CommandResult::OK; } CommandResult handle_idle(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { - unsigned flags = 0, j; - int i; - const char *const* idle_names; - - idle_names = idle_get_names(); - for (i = 1; i < argc; ++i) { - if (!argv[i]) - continue; - - for (j = 0; idle_names[j]; ++j) { - if (StringEqualsCaseASCII(argv[i], idle_names[j])) { - flags |= (1 << j); - } + unsigned flags = 0; + + for (unsigned i = 1; i < argc; ++i) { + unsigned event = idle_parse_name(argv[i]); + if (event == 0) { + command_error(client, ACK_ERROR_ARG, + "Unrecognized idle event: %s", + argv[i]); + return CommandResult::ERROR; } + + flags |= event; } /* No argument means that the client wants to receive everything */ diff --git a/src/command/OtherCommands.hxx b/src/command/OtherCommands.hxx index 1a0b16ed1..7cfa35dfb 100644 --- a/src/command/OtherCommands.hxx +++ b/src/command/OtherCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -25,48 +25,51 @@ class Client; CommandResult -handle_urlhandlers(Client &client, int argc, char *argv[]); +handle_urlhandlers(Client &client, unsigned argc, char *argv[]); CommandResult -handle_decoders(Client &client, int argc, char *argv[]); +handle_decoders(Client &client, unsigned argc, char *argv[]); CommandResult -handle_tagtypes(Client &client, int argc, char *argv[]); +handle_tagtypes(Client &client, unsigned argc, char *argv[]); CommandResult -handle_kill(Client &client, int argc, char *argv[]); +handle_kill(Client &client, unsigned argc, char *argv[]); CommandResult -handle_close(Client &client, int argc, char *argv[]); +handle_close(Client &client, unsigned argc, char *argv[]); CommandResult -handle_lsinfo(Client &client, int argc, char *argv[]); +handle_listfiles(Client &client, unsigned argc, char *argv[]); CommandResult -handle_update(Client &client, int argc, char *argv[]); +handle_lsinfo(Client &client, unsigned argc, char *argv[]); CommandResult -handle_rescan(Client &client, int argc, char *argv[]); +handle_update(Client &client, unsigned argc, char *argv[]); CommandResult -handle_setvol(Client &client, int argc, char *argv[]); +handle_rescan(Client &client, unsigned argc, char *argv[]); CommandResult -handle_volume(Client &client, int argc, char *argv[]); +handle_setvol(Client &client, unsigned argc, char *argv[]); CommandResult -handle_stats(Client &client, int argc, char *argv[]); +handle_volume(Client &client, unsigned argc, char *argv[]); CommandResult -handle_ping(Client &client, int argc, char *argv[]); +handle_stats(Client &client, unsigned argc, char *argv[]); CommandResult -handle_password(Client &client, int argc, char *argv[]); +handle_ping(Client &client, unsigned argc, char *argv[]); CommandResult -handle_config(Client &client, int argc, char *argv[]); +handle_password(Client &client, unsigned argc, char *argv[]); CommandResult -handle_idle(Client &client, int argc, char *argv[]); +handle_config(Client &client, unsigned argc, char *argv[]); + +CommandResult +handle_idle(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx index e949448af..c69a0dd65 100644 --- a/src/command/OutputCommands.cxx +++ b/src/command/OutputCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,24 +19,21 @@ #include "config.h" #include "OutputCommands.hxx" -#include "OutputPrint.hxx" -#include "OutputCommand.hxx" +#include "output/OutputPrint.hxx" +#include "output/OutputCommand.hxx" #include "protocol/Result.hxx" #include "protocol/ArgParser.hxx" - -#include <string.h> +#include "client/Client.hxx" +#include "Partition.hxx" CommandResult -handle_enableoutput(Client &client, gcc_unused int argc, char *argv[]) +handle_enableoutput(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned device; - bool ret; - if (!check_unsigned(client, &device, argv[1])) return CommandResult::ERROR; - ret = audio_output_enable_index(device); - if (!ret) { + if (!audio_output_enable_index(client.partition.outputs, device)) { command_error(client, ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; @@ -46,16 +43,13 @@ handle_enableoutput(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_disableoutput(Client &client, gcc_unused int argc, char *argv[]) +handle_disableoutput(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned device; - bool ret; - if (!check_unsigned(client, &device, argv[1])) return CommandResult::ERROR; - ret = audio_output_disable_index(device); - if (!ret) { + if (!audio_output_disable_index(client.partition.outputs, device)) { command_error(client, ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; @@ -65,13 +59,13 @@ handle_disableoutput(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_toggleoutput(Client &client, gcc_unused int argc, char *argv[]) +handle_toggleoutput(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned device; if (!check_unsigned(client, &device, argv[1])) return CommandResult::ERROR; - if (!audio_output_toggle_index(device)) { + if (!audio_output_toggle_index(client.partition.outputs, device)) { command_error(client, ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; @@ -82,9 +76,9 @@ handle_toggleoutput(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_devices(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { - printAudioDevices(client); + printAudioDevices(client, client.partition.outputs); return CommandResult::OK; } diff --git a/src/command/OutputCommands.hxx b/src/command/OutputCommands.hxx index a5edc22e2..8d6be0511 100644 --- a/src/command/OutputCommands.hxx +++ b/src/command/OutputCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -25,15 +25,15 @@ class Client; CommandResult -handle_enableoutput(Client &client, int argc, char *argv[]); +handle_enableoutput(Client &client, unsigned argc, char *argv[]); CommandResult -handle_disableoutput(Client &client, int argc, char *argv[]); +handle_disableoutput(Client &client, unsigned argc, char *argv[]); CommandResult -handle_toggleoutput(Client &client, int argc, char *argv[]); +handle_toggleoutput(Client &client, unsigned argc, char *argv[]); CommandResult -handle_devices(Client &client, int argc, char *argv[]); +handle_devices(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx index 12c71dfd4..cd7f42289 100644 --- a/src/command/PlayerCommands.cxx +++ b/src/command/PlayerCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,18 +20,21 @@ #include "config.h" #include "PlayerCommands.hxx" #include "CommandError.hxx" -#include "Playlist.hxx" +#include "queue/Playlist.hxx" #include "PlaylistPrint.hxx" -#include "UpdateGlue.hxx" -#include "Client.hxx" -#include "Volume.hxx" -#include "OutputAll.hxx" +#include "client/Client.hxx" +#include "mixer/Volume.hxx" #include "Partition.hxx" +#include "Instance.hxx" #include "protocol/Result.hxx" #include "protocol/ArgParser.hxx" #include "AudioFormat.hxx" #include "ReplayGainConfig.hxx" +#ifdef ENABLE_DATABASE +#include "db/update/Service.hxx" +#endif + #define COMMAND_STATUS_STATE "state" #define COMMAND_STATUS_REPEAT "repeat" #define COMMAND_STATUS_SINGLE "single" @@ -53,7 +56,7 @@ #define COMMAND_STATUS_UPDATING_DB "updating_db" CommandResult -handle_play(Client &client, int argc, char *argv[]) +handle_play(Client &client, unsigned argc, char *argv[]) { int song = -1; @@ -64,7 +67,7 @@ handle_play(Client &client, int argc, char *argv[]) } CommandResult -handle_playid(Client &client, int argc, char *argv[]) +handle_playid(Client &client, unsigned argc, char *argv[]) { int id = -1; @@ -77,7 +80,7 @@ handle_playid(Client &client, int argc, char *argv[]) CommandResult handle_stop(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { client.partition.Stop(); return CommandResult::OK; @@ -85,7 +88,7 @@ handle_stop(Client &client, CommandResult handle_currentsong(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { playlist_print_current(client, client.playlist); return CommandResult::OK; @@ -93,7 +96,7 @@ handle_currentsong(Client &client, CommandResult handle_pause(Client &client, - int argc, char *argv[]) + unsigned argc, char *argv[]) { if (argc == 2) { bool pause_flag; @@ -109,10 +112,9 @@ handle_pause(Client &client, CommandResult handle_status(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { const char *state = nullptr; - int updateJobId; int song; const auto player_status = client.player_control.GetStatus(); @@ -140,7 +142,7 @@ handle_status(Client &client, COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" COMMAND_STATUS_MIXRAMPDB ": %f\n" COMMAND_STATUS_STATE ": %s\n", - volume_level_get(), + volume_level_get(client.partition.outputs), playlist.GetRepeat(), playlist.GetRandom(), playlist.GetSingle(), @@ -173,9 +175,11 @@ handle_status(Client &client, COMMAND_STATUS_TIME ": %i:%i\n" "elapsed: %1.3f\n" COMMAND_STATUS_BITRATE ": %u\n", - (int)(player_status.elapsed_time + 0.5), - (int)(player_status.total_time + 0.5), - player_status.elapsed_time, + player_status.elapsed_time.RoundS(), + player_status.total_time.IsNegative() + ? 0u + : unsigned(player_status.total_time.RoundS()), + player_status.elapsed_time.ToDoubleS(), player_status.bit_rate); if (player_status.audio_format.IsDefined()) { @@ -188,11 +192,17 @@ handle_status(Client &client, } } - if ((updateJobId = isUpdatingDB())) { +#ifdef ENABLE_DATABASE + const UpdateService *update_service = client.partition.instance.update; + unsigned updateJobId = update_service != nullptr + ? update_service->GetId() + : 0; + if (updateJobId != 0) { client_printf(client, COMMAND_STATUS_UPDATING_DB ": %i\n", updateJobId); } +#endif Error error = client.player_control.LockGetError(); if (error.IsDefined()) @@ -213,7 +223,7 @@ handle_status(Client &client, CommandResult handle_next(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { playlist &playlist = client.playlist; @@ -230,14 +240,14 @@ handle_next(Client &client, CommandResult handle_previous(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { client.partition.PlayPrevious(); return CommandResult::OK; } CommandResult -handle_repeat(Client &client, gcc_unused int argc, char *argv[]) +handle_repeat(Client &client, gcc_unused unsigned argc, char *argv[]) { bool status; if (!check_bool(client, &status, argv[1])) @@ -248,7 +258,7 @@ handle_repeat(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_single(Client &client, gcc_unused int argc, char *argv[]) +handle_single(Client &client, gcc_unused unsigned argc, char *argv[]) { bool status; if (!check_bool(client, &status, argv[1])) @@ -259,7 +269,7 @@ handle_single(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_consume(Client &client, gcc_unused int argc, char *argv[]) +handle_consume(Client &client, gcc_unused unsigned argc, char *argv[]) { bool status; if (!check_bool(client, &status, argv[1])) @@ -270,33 +280,34 @@ handle_consume(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_random(Client &client, gcc_unused int argc, char *argv[]) +handle_random(Client &client, gcc_unused unsigned argc, char *argv[]) { bool status; if (!check_bool(client, &status, argv[1])) return CommandResult::ERROR; client.partition.SetRandom(status); - audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client.partition.GetRandom())); + client.partition.outputs.SetReplayGainMode(replay_gain_get_real_mode(client.partition.GetRandom())); return CommandResult::OK; } CommandResult handle_clearerror(gcc_unused Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { client.player_control.ClearError(); return CommandResult::OK; } CommandResult -handle_seek(Client &client, gcc_unused int argc, char *argv[]) +handle_seek(Client &client, gcc_unused unsigned argc, char *argv[]) { - unsigned song, seek_time; + unsigned song; + SongTime seek_time; if (!check_unsigned(client, &song, argv[1])) return CommandResult::ERROR; - if (!check_unsigned(client, &seek_time, argv[2])) + if (!ParseCommandArg(client, seek_time, argv[2])) return CommandResult::ERROR; PlaylistResult result = @@ -305,13 +316,14 @@ handle_seek(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_seekid(Client &client, gcc_unused int argc, char *argv[]) +handle_seekid(Client &client, gcc_unused unsigned argc, char *argv[]) { - unsigned id, seek_time; + unsigned id; + SongTime seek_time; if (!check_unsigned(client, &id, argv[1])) return CommandResult::ERROR; - if (!check_unsigned(client, &seek_time, argv[2])) + if (!ParseCommandArg(client, seek_time, argv[2])) return CommandResult::ERROR; PlaylistResult result = @@ -320,12 +332,12 @@ handle_seekid(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_seekcur(Client &client, gcc_unused int argc, char *argv[]) +handle_seekcur(Client &client, gcc_unused unsigned argc, char *argv[]) { const char *p = argv[1]; bool relative = *p == '+' || *p == '-'; - int seek_time; - if (!check_int(client, &seek_time, p)) + SignedSongTime seek_time; + if (!ParseCommandArg(client, seek_time, p)) return CommandResult::ERROR; PlaylistResult result = @@ -334,7 +346,7 @@ handle_seekcur(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_crossfade(Client &client, gcc_unused int argc, char *argv[]) +handle_crossfade(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned xfade_time; @@ -346,7 +358,7 @@ handle_crossfade(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_mixrampdb(Client &client, gcc_unused int argc, char *argv[]) +handle_mixrampdb(Client &client, gcc_unused unsigned argc, char *argv[]) { float db; @@ -358,7 +370,7 @@ handle_mixrampdb(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_mixrampdelay(Client &client, gcc_unused int argc, char *argv[]) +handle_mixrampdelay(Client &client, gcc_unused unsigned argc, char *argv[]) { float delay_secs; @@ -371,7 +383,7 @@ handle_mixrampdelay(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_replay_gain_mode(Client &client, - gcc_unused int argc, char *argv[]) + gcc_unused unsigned argc, char *argv[]) { if (!replay_gain_set_mode_string(argv[1])) { command_error(client, ACK_ERROR_ARG, @@ -379,14 +391,13 @@ handle_replay_gain_mode(Client &client, return CommandResult::ERROR; } - audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client.playlist.queue.random)); - + client.partition.outputs.SetReplayGainMode(replay_gain_get_real_mode(client.playlist.queue.random)); return CommandResult::OK; } CommandResult handle_replay_gain_status(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { 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 8dad0855b..da7083f1e 100644 --- a/src/command/PlayerCommands.hxx +++ b/src/command/PlayerCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -25,66 +25,66 @@ class Client; CommandResult -handle_play(Client &client, int argc, char *argv[]); +handle_play(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playid(Client &client, int argc, char *argv[]); +handle_playid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_stop(Client &client, int argc, char *argv[]); +handle_stop(Client &client, unsigned argc, char *argv[]); CommandResult -handle_currentsong(Client &client, int argc, char *argv[]); +handle_currentsong(Client &client, unsigned argc, char *argv[]); CommandResult -handle_pause(Client &client, int argc, char *argv[]); +handle_pause(Client &client, unsigned argc, char *argv[]); CommandResult -handle_status(Client &client, int argc, char *argv[]); +handle_status(Client &client, unsigned argc, char *argv[]); CommandResult -handle_next(Client &client, int argc, char *argv[]); +handle_next(Client &client, unsigned argc, char *argv[]); CommandResult -handle_previous(Client &client, int argc, char *avg[]); +handle_previous(Client &client, unsigned argc, char *avg[]); CommandResult -handle_repeat(Client &client, int argc, char *argv[]); +handle_repeat(Client &client, unsigned argc, char *argv[]); CommandResult -handle_single(Client &client, int argc, char *argv[]); +handle_single(Client &client, unsigned argc, char *argv[]); CommandResult -handle_consume(Client &client, int argc, char *argv[]); +handle_consume(Client &client, unsigned argc, char *argv[]); CommandResult -handle_random(Client &client, int argc, char *argv[]); +handle_random(Client &client, unsigned argc, char *argv[]); CommandResult -handle_clearerror(Client &client, int argc, char *argv[]); +handle_clearerror(Client &client, unsigned argc, char *argv[]); CommandResult -handle_seek(Client &client, int argc, char *argv[]); +handle_seek(Client &client, unsigned argc, char *argv[]); CommandResult -handle_seekid(Client &client, int argc, char *argv[]); +handle_seekid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_seekcur(Client &client, int argc, char *argv[]); +handle_seekcur(Client &client, unsigned argc, char *argv[]); CommandResult -handle_crossfade(Client &client, int argc, char *argv[]); +handle_crossfade(Client &client, unsigned argc, char *argv[]); CommandResult -handle_mixrampdb(Client &client, int argc, char *argv[]); +handle_mixrampdb(Client &client, unsigned argc, char *argv[]); CommandResult -handle_mixrampdelay(Client &client, int argc, char *argv[]); +handle_mixrampdelay(Client &client, unsigned argc, char *argv[]); CommandResult -handle_replay_gain_mode(Client &client, int argc, char *argv[]); +handle_replay_gain_mode(Client &client, unsigned argc, char *argv[]); CommandResult -handle_replay_gain_status(Client &client, int argc, char *argv[]); +handle_replay_gain_status(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx index c4441293e..c2b18064c 100644 --- a/src/command/PlaylistCommands.cxx +++ b/src/command/PlaylistCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -19,26 +19,25 @@ #include "config.h" #include "PlaylistCommands.hxx" -#include "DatabasePlaylist.hxx" +#include "db/DatabasePlaylist.hxx" #include "CommandError.hxx" #include "PlaylistPrint.hxx" #include "PlaylistSave.hxx" #include "PlaylistFile.hxx" -#include "PlaylistVector.hxx" -#include "PlaylistQueue.hxx" +#include "db/PlaylistVector.hxx" +#include "SongLoader.hxx" #include "BulkEdit.hxx" +#include "playlist/PlaylistQueue.hxx" +#include "playlist/Print.hxx" +#include "queue/Playlist.hxx" #include "TimePrint.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include "protocol/ArgParser.hxx" #include "protocol/Result.hxx" #include "ls.hxx" -#include "Playlist.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" -#include <assert.h> -#include <stdlib.h> - static void print_spl_list(Client &client, const PlaylistVector &list) { @@ -51,14 +50,14 @@ print_spl_list(Client &client, const PlaylistVector &list) } CommandResult -handle_save(Client &client, gcc_unused int argc, char *argv[]) +handle_save(Client &client, gcc_unused unsigned argc, char *argv[]) { PlaylistResult result = spl_save_playlist(argv[1], client.playlist); return print_playlist_result(client, result); } CommandResult -handle_load(Client &client, int argc, char *argv[]) +handle_load(Client &client, unsigned argc, char *argv[]) { unsigned start_index, end_index; @@ -70,36 +69,19 @@ handle_load(Client &client, int argc, char *argv[]) const ScopeBulkEdit bulk_edit(client.partition); - const PlaylistResult result = - playlist_open_into_queue(argv[1], - start_index, end_index, - client.playlist, - client.player_control, true); - if (result != PlaylistResult::NO_SUCH_LIST) - return print_playlist_result(client, result); - Error error; - if (playlist_load_spl(client.playlist, client.player_control, - argv[1], start_index, end_index, - error)) - return CommandResult::OK; - - if (error.IsDomain(playlist_domain) && - PlaylistResult(error.GetCode()) == PlaylistResult::BAD_NAME) { - /* the message for BAD_NAME is confusing when the - client wants to load a playlist file from the music - directory; patch the Error object to show "no such - playlist" instead */ - Error error2(playlist_domain, int(PlaylistResult::NO_SUCH_LIST), - error.GetMessage()); - error = std::move(error2); - } + const SongLoader loader(client); + if (!playlist_open_into_queue(argv[1], + start_index, end_index, + client.playlist, + client.player_control, loader, error)) + return print_error(client, error); - return print_error(client, error); + return CommandResult::OK; } CommandResult -handle_listplaylist(Client &client, gcc_unused int argc, char *argv[]) +handle_listplaylist(Client &client, gcc_unused unsigned argc, char *argv[]) { if (playlist_file_print(client, argv[1], false)) return CommandResult::OK; @@ -112,7 +94,7 @@ handle_listplaylist(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_listplaylistinfo(Client &client, - gcc_unused int argc, char *argv[]) + gcc_unused unsigned argc, char *argv[]) { if (playlist_file_print(client, argv[1], true)) return CommandResult::OK; @@ -124,7 +106,7 @@ handle_listplaylistinfo(Client &client, } CommandResult -handle_rm(Client &client, gcc_unused int argc, char *argv[]) +handle_rm(Client &client, gcc_unused unsigned argc, char *argv[]) { Error error; return spl_delete(argv[1], error) @@ -133,7 +115,7 @@ handle_rm(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_rename(Client &client, gcc_unused int argc, char *argv[]) +handle_rename(Client &client, gcc_unused unsigned argc, char *argv[]) { Error error; return spl_rename(argv[1], argv[2], error) @@ -143,7 +125,7 @@ handle_rename(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_playlistdelete(Client &client, - gcc_unused int argc, char *argv[]) { + gcc_unused unsigned argc, char *argv[]) { char *playlist = argv[1]; unsigned from; @@ -157,7 +139,7 @@ handle_playlistdelete(Client &client, } CommandResult -handle_playlistmove(Client &client, gcc_unused int argc, char *argv[]) +handle_playlistmove(Client &client, gcc_unused unsigned argc, char *argv[]) { char *playlist = argv[1]; unsigned from, to; @@ -174,7 +156,7 @@ handle_playlistmove(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_playlistclear(Client &client, gcc_unused int argc, char *argv[]) +handle_playlistclear(Client &client, gcc_unused unsigned argc, char *argv[]) { Error error; return spl_clear(argv[1], error) @@ -183,7 +165,7 @@ handle_playlistclear(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_playlistadd(Client &client, gcc_unused int argc, char *argv[]) +handle_playlistadd(Client &client, gcc_unused unsigned argc, char *argv[]) { char *playlist = argv[1]; char *uri = argv[2]; @@ -191,16 +173,21 @@ handle_playlistadd(Client &client, gcc_unused int argc, char *argv[]) bool success; Error error; if (uri_has_scheme(uri)) { - if (!uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return CommandResult::ERROR; - } - - success = spl_append_uri(uri, playlist, error); - } else - success = search_add_to_playlist(uri, playlist, nullptr, + const SongLoader loader(client); + success = spl_append_uri(playlist, loader, uri, error); + } else { +#ifdef ENABLE_DATABASE + const Database *db = client.GetDatabase(error); + if (db == nullptr) + return print_error(client, error); + + success = search_add_to_playlist(*db, *client.GetStorage(), + uri, playlist, nullptr, error); +#else + success = false; +#endif + } if (!success && !error.IsDefined()) { command_error(client, ACK_ERROR_NO_EXIST, @@ -213,7 +200,7 @@ handle_playlistadd(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_listplaylists(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { Error error; const auto list = ListPlaylistFiles(error); diff --git a/src/command/PlaylistCommands.hxx b/src/command/PlaylistCommands.hxx index 802d6ff2f..fba4e1318 100644 --- a/src/command/PlaylistCommands.hxx +++ b/src/command/PlaylistCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -25,36 +25,36 @@ class Client; CommandResult -handle_save(Client &client, int argc, char *argv[]); +handle_save(Client &client, unsigned argc, char *argv[]); CommandResult -handle_load(Client &client, int argc, char *argv[]); +handle_load(Client &client, unsigned argc, char *argv[]); CommandResult -handle_listplaylist(Client &client, int argc, char *argv[]); +handle_listplaylist(Client &client, unsigned argc, char *argv[]); CommandResult -handle_listplaylistinfo(Client &client, int argc, char *argv[]); +handle_listplaylistinfo(Client &client, unsigned argc, char *argv[]); CommandResult -handle_rm(Client &client, int argc, char *argv[]); +handle_rm(Client &client, unsigned argc, char *argv[]); CommandResult -handle_rename(Client &client, int argc, char *argv[]); +handle_rename(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistdelete(Client &client, int argc, char *argv[]); +handle_playlistdelete(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistmove(Client &client, int argc, char *argv[]); +handle_playlistmove(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistclear(Client &client, int argc, char *argv[]); +handle_playlistclear(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistadd(Client &client, int argc, char *argv[]); +handle_playlistadd(Client &client, unsigned argc, char *argv[]); CommandResult -handle_listplaylists(Client &client, int argc, char *argv[]); +handle_listplaylists(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx index a987e1bc9..36c8ac84f 100644 --- a/src/command/QueueCommands.cxx +++ b/src/command/QueueCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,19 +20,21 @@ #include "config.h" #include "QueueCommands.hxx" #include "CommandError.hxx" -#include "DatabaseQueue.hxx" +#include "db/DatabaseQueue.hxx" +#include "db/Selection.hxx" #include "SongFilter.hxx" -#include "DatabaseSelection.hxx" -#include "Playlist.hxx" +#include "SongLoader.hxx" +#include "queue/Playlist.hxx" #include "PlaylistPrint.hxx" -#include "ClientFile.hxx" -#include "Client.hxx" +#include "client/Client.hxx" #include "Partition.hxx" #include "BulkEdit.hxx" #include "protocol/ArgParser.hxx" #include "protocol/Result.hxx" #include "ls.hxx" +#include "util/ConstBuffer.hxx" #include "util/UriUtil.hxx" +#include "util/NumberParser.hxx" #include "util/Error.hxx" #include "fs/AllocatedPath.hxx" @@ -40,40 +42,40 @@ #include <string.h> -CommandResult -handle_add(Client &client, gcc_unused int argc, char *argv[]) +static const char * +translate_uri(Client &client, const char *uri) { - char *uri = argv[1]; + if (memcmp(uri, "file:///", 8) == 0) + /* drop the "file://", leave only an absolute path + (starting with a slash) */ + return uri + 7; + + if (PathTraitsUTF8::IsAbsolute(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, "Malformed URI"); + return nullptr; + } - if (memcmp(uri, "file:///", 8) == 0) { - const char *path_utf8 = uri + 7; - const auto path_fs = AllocatedPath::FromUTF8(path_utf8); + return uri; +} - if (path_fs.IsNull()) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported file name"); - return CommandResult::ERROR; - } +CommandResult +handle_add(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + const char *const uri = translate_uri(client, argv[1]); + if (uri == nullptr) + return CommandResult::ERROR; + if (uri_has_scheme(uri) || PathTraitsUTF8::IsAbsolute(uri)) { + const SongLoader loader(client); Error error; - if (!client_allow_file(client, path_fs, error)) + unsigned id = client.partition.AppendURI(loader, uri, error); + if (id == 0) return print_error(client, error); - auto result = client.partition.AppendFile(path_utf8); - return print_playlist_result(client, result); - } - - if (uri_has_scheme(uri)) { - if (!uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return CommandResult::ERROR; - } - - auto result = client.partition.AppendURI(uri); - return print_playlist_result(client, result); + return CommandResult::OK; } +#ifdef ENABLE_DATABASE const ScopeBulkEdit bulk_edit(client.partition); const DatabaseSelection selection(uri, true); @@ -81,48 +83,30 @@ handle_add(Client &client, gcc_unused int argc, char *argv[]) return AddFromDatabase(client.partition, selection, error) ? CommandResult::OK : print_error(client, error); +#else + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +#endif } CommandResult -handle_addid(Client &client, int argc, char *argv[]) +handle_addid(Client &client, unsigned argc, char *argv[]) { - char *uri = argv[1]; - unsigned added_id; - PlaylistResult result; - - if (memcmp(uri, "file:///", 8) == 0) { - const char *path_utf8 = uri + 7; - const auto path_fs = AllocatedPath::FromUTF8(path_utf8); - - if (path_fs.IsNull()) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported file name"); - return CommandResult::ERROR; - } - - Error error; - if (!client_allow_file(client, path_fs, error)) - return print_error(client, error); - - result = client.partition.AppendFile(path_utf8, &added_id); - } else { - if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); - return CommandResult::ERROR; - } - - result = client.partition.AppendURI(uri, &added_id); - } + const char *const uri = translate_uri(client, argv[1]); + if (uri == nullptr) + return CommandResult::ERROR; - if (result != PlaylistResult::SUCCESS) - return print_playlist_result(client, result); + const SongLoader loader(client); + Error error; + unsigned added_id = client.partition.AppendURI(loader, uri, error); + if (added_id == 0) + return print_error(client, error); if (argc == 3) { unsigned to; if (!check_unsigned(client, &to, argv[2])) return CommandResult::ERROR; - result = client.partition.MoveId(added_id, to); + PlaylistResult result = client.partition.MoveId(added_id, to); if (result != PlaylistResult::SUCCESS) { CommandResult ret = print_playlist_result(client, result); @@ -135,8 +119,61 @@ handle_addid(Client &client, int argc, char *argv[]) return CommandResult::OK; } +/** + * Parse a string in the form "START:END", both being (optional) + * fractional non-negative time offsets in seconds. Returns both in + * integer milliseconds. Omitted values are zero. + */ +static bool +parse_time_range(const char *p, SongTime &start_r, SongTime &end_r) +{ + char *endptr; + + const float start = ParseFloat(p, &endptr); + if (*endptr != ':' || start < 0) + return false; + + start_r = endptr > p + ? SongTime::FromS(start) + : SongTime::zero(); + + p = endptr + 1; + + const float end = ParseFloat(p, &endptr); + if (*endptr != 0 || end < 0) + return false; + + end_r = endptr > p + ? SongTime::FromS(end) + : SongTime::zero(); + + return end_r.IsZero() || end_r > start_r; +} + +CommandResult +handle_rangeid(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + unsigned id; + if (!check_unsigned(client, &id, argv[1])) + return CommandResult::ERROR; + + SongTime start, end; + if (!parse_time_range(argv[2], start, end)) { + command_error(client, ACK_ERROR_ARG, "Bad range"); + return CommandResult::ERROR; + } + + Error error; + if (!client.partition.playlist.SetSongIdRange(client.partition.pc, + id, start, end, + error)) + return print_error(client, error); + + return CommandResult::OK; +} + CommandResult -handle_delete(Client &client, gcc_unused int argc, char *argv[]) +handle_delete(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned start, end; @@ -148,7 +185,7 @@ handle_delete(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_deleteid(Client &client, gcc_unused int argc, char *argv[]) +handle_deleteid(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned id; @@ -161,7 +198,7 @@ handle_deleteid(Client &client, gcc_unused int argc, char *argv[]) CommandResult handle_playlist(Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { playlist_print_uris(client, client.playlist); return CommandResult::OK; @@ -169,7 +206,7 @@ handle_playlist(Client &client, CommandResult handle_shuffle(gcc_unused Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { unsigned start = 0, end = client.playlist.queue.GetLength(); if (argc == 2 && !check_range(client, &start, &end, argv[1])) @@ -181,14 +218,14 @@ handle_shuffle(gcc_unused Client &client, CommandResult handle_clear(gcc_unused Client &client, - gcc_unused int argc, gcc_unused char *argv[]) + gcc_unused unsigned argc, gcc_unused char *argv[]) { client.partition.ClearQueue(); return CommandResult::OK; } CommandResult -handle_plchanges(Client &client, gcc_unused int argc, char *argv[]) +handle_plchanges(Client &client, gcc_unused unsigned argc, char *argv[]) { uint32_t version; @@ -200,7 +237,7 @@ handle_plchanges(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_plchangesposid(Client &client, gcc_unused int argc, char *argv[]) +handle_plchangesposid(Client &client, gcc_unused unsigned argc, char *argv[]) { uint32_t version; @@ -212,7 +249,7 @@ handle_plchangesposid(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_playlistinfo(Client &client, int argc, char *argv[]) +handle_playlistinfo(Client &client, unsigned argc, char *argv[]) { unsigned start = 0, end = std::numeric_limits<unsigned>::max(); bool ret; @@ -229,7 +266,7 @@ handle_playlistinfo(Client &client, int argc, char *argv[]) } CommandResult -handle_playlistid(Client &client, int argc, char *argv[]) +handle_playlistid(Client &client, unsigned argc, char *argv[]) { if (argc >= 2) { unsigned id; @@ -249,11 +286,13 @@ handle_playlistid(Client &client, int argc, char *argv[]) } static CommandResult -handle_playlist_match(Client &client, int argc, char *argv[], +handle_playlist_match(Client &client, unsigned argc, char *argv[], bool fold_case) { + ConstBuffer<const char *> args(argv + 1, argc - 1); + SongFilter filter; - if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + if (!filter.Parse(args, fold_case)) { command_error(client, ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } @@ -263,19 +302,19 @@ handle_playlist_match(Client &client, int argc, char *argv[], } CommandResult -handle_playlistfind(Client &client, int argc, char *argv[]) +handle_playlistfind(Client &client, unsigned argc, char *argv[]) { return handle_playlist_match(client, argc, argv, false); } CommandResult -handle_playlistsearch(Client &client, int argc, char *argv[]) +handle_playlistsearch(Client &client, unsigned argc, char *argv[]) { return handle_playlist_match(client, argc, argv, true); } CommandResult -handle_prio(Client &client, int argc, char *argv[]) +handle_prio(Client &client, unsigned argc, char *argv[]) { unsigned priority; @@ -288,7 +327,7 @@ handle_prio(Client &client, int argc, char *argv[]) return CommandResult::ERROR; } - for (int i = 2; i < argc; ++i) { + for (unsigned i = 2; i < argc; ++i) { unsigned start_position, end_position; if (!check_range(client, &start_position, &end_position, argv[i])) @@ -306,7 +345,7 @@ handle_prio(Client &client, int argc, char *argv[]) } CommandResult -handle_prioid(Client &client, int argc, char *argv[]) +handle_prioid(Client &client, unsigned argc, char *argv[]) { unsigned priority; @@ -319,7 +358,7 @@ handle_prioid(Client &client, int argc, char *argv[]) return CommandResult::ERROR; } - for (int i = 2; i < argc; ++i) { + for (unsigned i = 2; i < argc; ++i) { unsigned song_id; if (!check_unsigned(client, &song_id, argv[i])) return CommandResult::ERROR; @@ -334,7 +373,7 @@ handle_prioid(Client &client, int argc, char *argv[]) } CommandResult -handle_move(Client &client, gcc_unused int argc, char *argv[]) +handle_move(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned start, end; int to; @@ -350,7 +389,7 @@ handle_move(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_moveid(Client &client, gcc_unused int argc, char *argv[]) +handle_moveid(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned id; int to; @@ -364,7 +403,7 @@ handle_moveid(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_swap(Client &client, gcc_unused int argc, char *argv[]) +handle_swap(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned song1, song2; @@ -379,7 +418,7 @@ handle_swap(Client &client, gcc_unused int argc, char *argv[]) } CommandResult -handle_swapid(Client &client, gcc_unused int argc, char *argv[]) +handle_swapid(Client &client, gcc_unused unsigned argc, char *argv[]) { unsigned id1, id2; diff --git a/src/command/QueueCommands.hxx b/src/command/QueueCommands.hxx index 90d744447..f98f7bad2 100644 --- a/src/command/QueueCommands.hxx +++ b/src/command/QueueCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -25,60 +25,63 @@ class Client; CommandResult -handle_add(Client &client, int argc, char *argv[]); +handle_add(Client &client, unsigned argc, char *argv[]); CommandResult -handle_addid(Client &client, int argc, char *argv[]); +handle_addid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_delete(Client &client, int argc, char *argv[]); +handle_rangeid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_deleteid(Client &client, int argc, char *argv[]); +handle_delete(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlist(Client &client, int argc, char *argv[]); +handle_deleteid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_shuffle(Client &client, int argc, char *argv[]); +handle_playlist(Client &client, unsigned argc, char *argv[]); CommandResult -handle_clear(Client &client, int argc, char *argv[]); +handle_shuffle(Client &client, unsigned argc, char *argv[]); CommandResult -handle_plchanges(Client &client, int argc, char *argv[]); +handle_clear(Client &client, unsigned argc, char *argv[]); CommandResult -handle_plchangesposid(Client &client, int argc, char *argv[]); +handle_plchanges(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistinfo(Client &client, int argc, char *argv[]); +handle_plchangesposid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistid(Client &client, int argc, char *argv[]); +handle_playlistinfo(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistfind(Client &client, int argc, char *argv[]); +handle_playlistid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_playlistsearch(Client &client, int argc, char *argv[]); +handle_playlistfind(Client &client, unsigned argc, char *argv[]); CommandResult -handle_prio(Client &client, int argc, char *argv[]); +handle_playlistsearch(Client &client, unsigned argc, char *argv[]); CommandResult -handle_prioid(Client &client, int argc, char *argv[]); +handle_prio(Client &client, unsigned argc, char *argv[]); CommandResult -handle_move(Client &client, int argc, char *argv[]); +handle_prioid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_moveid(Client &client, int argc, char *argv[]); +handle_move(Client &client, unsigned argc, char *argv[]); CommandResult -handle_swap(Client &client, int argc, char *argv[]); +handle_moveid(Client &client, unsigned argc, char *argv[]); CommandResult -handle_swapid(Client &client, int argc, char *argv[]); +handle_swap(Client &client, unsigned argc, char *argv[]); + +CommandResult +handle_swapid(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx index b65e6f607..37506d51b 100644 --- a/src/command/StickerCommands.cxx +++ b/src/command/StickerCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -20,19 +20,18 @@ #include "config.h" #include "StickerCommands.hxx" #include "SongPrint.hxx" -#include "DatabaseLock.hxx" -#include "DatabasePlugin.hxx" -#include "DatabaseGlue.hxx" -#include "DatabaseSimple.hxx" -#include "SongSticker.hxx" -#include "StickerPrint.hxx" -#include "StickerDatabase.hxx" +#include "db/Interface.hxx" +#include "db/DatabaseGlue.hxx" +#include "sticker/SongSticker.hxx" +#include "sticker/StickerPrint.hxx" +#include "sticker/StickerDatabase.hxx" #include "CommandError.hxx" #include "protocol/Result.hxx" +#include "client/Client.hxx" +#include "Partition.hxx" +#include "Instance.hxx" #include "util/Error.hxx" -#include <glib.h> - #include <string.h> struct sticker_song_find_data { @@ -41,7 +40,7 @@ struct sticker_song_find_data { }; static void -sticker_song_find_print_cb(Song &song, const char *value, +sticker_song_find_print_cb(const LightSong &song, const char *value, void *user_data) { struct sticker_song_find_data *data = @@ -52,20 +51,20 @@ sticker_song_find_print_cb(Song &song, const char *value, } static CommandResult -handle_sticker_song(Client &client, int argc, char *argv[]) +handle_sticker_song(Client &client, unsigned argc, char *argv[]) { Error error; - const Database *db = GetDatabase(error); + const Database *db = client.GetDatabase(error); if (db == nullptr) return print_error(client, error); /* get song song_id key */ if (argc == 5 && strcmp(argv[1], "get") == 0) { - Song *song = db->GetSong(argv[3], error); + const LightSong *song = db->GetSong(argv[3], 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, argv[4]); db->ReturnSong(song); if (value.empty()) { command_error(client, ACK_ERROR_NO_EXIST, @@ -78,11 +77,11 @@ handle_sticker_song(Client &client, int argc, char *argv[]) return CommandResult::OK; /* list song song_id */ } else if (argc == 4 && strcmp(argv[1], "list") == 0) { - Song *song = db->GetSong(argv[3], error); + const LightSong *song = db->GetSong(argv[3], error); if (song == nullptr) return print_error(client, error); - sticker *sticker = sticker_song_get(song); + sticker *sticker = sticker_song_get(*song); db->ReturnSong(song); if (sticker) { sticker_print(client, *sticker); @@ -92,11 +91,11 @@ handle_sticker_song(Client &client, int argc, char *argv[]) return CommandResult::OK; /* set song song_id id key */ } else if (argc == 6 && strcmp(argv[1], "set") == 0) { - Song *song = db->GetSong(argv[3], error); + const LightSong *song = db->GetSong(argv[3], 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, argv[4], argv[5]); db->ReturnSong(song); if (!ret) { command_error(client, ACK_ERROR_SYSTEM, @@ -108,13 +107,13 @@ handle_sticker_song(Client &client, int argc, char *argv[]) /* delete song song_id [key] */ } else if ((argc == 4 || argc == 5) && strcmp(argv[1], "delete") == 0) { - Song *song = db->GetSong(argv[3], error); + const LightSong *song = db->GetSong(argv[3], error); if (song == nullptr) return print_error(client, error); bool ret = argc == 4 - ? sticker_song_delete(song) - : sticker_song_delete_value(song, argv[4]); + ? sticker_song_delete(*song) + : sticker_song_delete_value(*song, argv[4]); db->ReturnSong(song); if (!ret) { command_error(client, ACK_ERROR_SYSTEM, @@ -126,24 +125,17 @@ handle_sticker_song(Client &client, int argc, char *argv[]) /* find song dir key */ } else if (argc == 5 && strcmp(argv[1], "find") == 0) { /* "sticker find song a/directory name" */ + + const char *const base_uri = argv[3]; + bool success; struct sticker_song_find_data data = { client, argv[4], }; - db_lock(); - Directory *directory = db_get_directory(argv[3]); - if (directory == nullptr) { - db_unlock(); - command_error(client, ACK_ERROR_NO_EXIST, - "no such directory"); - return CommandResult::ERROR; - } - - success = sticker_song_find(*directory, data.name, + success = sticker_song_find(*db, base_uri, data.name, sticker_song_find_print_cb, &data); - db_unlock(); if (!success) { command_error(client, ACK_ERROR_SYSTEM, "failed to set search sticker database"); @@ -158,7 +150,7 @@ handle_sticker_song(Client &client, int argc, char *argv[]) } CommandResult -handle_sticker(Client &client, int argc, char *argv[]) +handle_sticker(Client &client, unsigned argc, char *argv[]) { assert(argc >= 4); diff --git a/src/command/StickerCommands.hxx b/src/command/StickerCommands.hxx index 9e4380f37..cf46cd034 100644 --- a/src/command/StickerCommands.hxx +++ b/src/command/StickerCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2013 The Music Player Daemon Project + * 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 @@ -25,6 +25,6 @@ class Client; CommandResult -handle_sticker(Client &client, int argc, char *argv[]); +handle_sticker(Client &client, unsigned argc, char *argv[]); #endif diff --git a/src/command/StorageCommands.cxx b/src/command/StorageCommands.cxx new file mode 100644 index 000000000..aeec73e1c --- /dev/null +++ b/src/command/StorageCommands.cxx @@ -0,0 +1,295 @@ +/* + * 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. + */ + +#define __STDC_FORMAT_MACROS /* for PRIu64 */ + +#include "config.h" +#include "StorageCommands.hxx" +#include "CommandError.hxx" +#include "protocol/Result.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "fs/Traits.hxx" +#include "client/Client.hxx" +#include "Partition.hxx" +#include "Instance.hxx" +#include "storage/Registry.hxx" +#include "storage/CompositeStorage.hxx" +#include "storage/FileInfo.hxx" +#include "db/plugins/simple/SimpleDatabasePlugin.hxx" +#include "db/update/Service.hxx" +#include "TimePrint.hxx" +#include "Idle.hxx" + +#include <inttypes.h> /* for PRIu64 */ + +gcc_pure +static bool +skip_path(const char *name_utf8) +{ + return strchr(name_utf8, '\n') != nullptr; +} + +#if defined(WIN32) && GCC_CHECK_VERSION(4,6) +/* PRIu64 causes bogus compiler warning */ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wformat" +#pragma GCC diagnostic ignored "-Wformat-extra-args" +#endif + +static bool +handle_listfiles_storage(Client &client, StorageDirectoryReader &reader, + Error &error) +{ + const char *name_utf8; + while ((name_utf8 = reader.Read()) != nullptr) { + if (skip_path(name_utf8)) + continue; + + FileInfo info; + if (!reader.GetInfo(false, info, error)) + continue; + + switch (info.type) { + case FileInfo::Type::OTHER: + /* ignore */ + continue; + + case FileInfo::Type::REGULAR: + client_printf(client, "file: %s\n" + "size: %" PRIu64 "\n", + name_utf8, + info.size); + break; + + case FileInfo::Type::DIRECTORY: + client_printf(client, "directory: %s\n", name_utf8); + break; + } + + if (info.mtime != 0) + time_print(client, "Last-Modified", info.mtime); + } + + return true; +} + +#if defined(WIN32) && GCC_CHECK_VERSION(4,6) +#pragma GCC diagnostic pop +#endif + +static bool +handle_listfiles_storage(Client &client, Storage &storage, const char *uri, + Error &error) +{ + auto reader = storage.OpenDirectory(uri, error); + if (reader == nullptr) + return false; + + bool success = handle_listfiles_storage(client, *reader, error); + delete reader; + return success; +} + +CommandResult +handle_listfiles_storage(Client &client, Storage &storage, const char *uri) +{ + Error error; + if (!handle_listfiles_storage(client, storage, uri, error)) + return print_error(client, error); + + return CommandResult::OK; +} + +CommandResult +handle_listfiles_storage(Client &client, const char *uri) +{ + Error error; + Storage *storage = CreateStorageURI(uri, error); + if (storage == nullptr) { + if (error.IsDefined()) + return print_error(client, error); + + command_error(client, ACK_ERROR_ARG, + "Unrecognized storage URI"); + return CommandResult::ERROR; + } + + bool success = handle_listfiles_storage(client, *storage, "", error); + delete storage; + if (!success) + return print_error(client, error); + + return CommandResult::OK; +} + +static void +print_storage_uri(Client &client, const Storage &storage) +{ + std::string uri = storage.MapUTF8(""); + if (uri.empty()) + return; + + if (PathTraitsFS::IsAbsolute(uri.c_str())) { + /* storage points to local directory */ + + if (!client.IsLocal()) + /* only "local" clients may see local paths + (same policy as with the "config" + command) */ + return; + } else { + /* hide username/passwords from client */ + + std::string allocated = uri_remove_auth(uri.c_str()); + if (!allocated.empty()) + uri = std::move(allocated); + } + + client_printf(client, "storage: %s\n", uri.c_str()); +} + +CommandResult +handle_listmounts(Client &client, gcc_unused unsigned argc, gcc_unused char *argv[]) +{ + Storage *_composite = client.partition.instance.storage; + if (_composite == nullptr) { + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; + } + + CompositeStorage &composite = *(CompositeStorage *)_composite; + + const auto visitor = [&client](const char *mount_uri, + const Storage &storage){ + client_printf(client, "mount: %s\n", mount_uri); + print_storage_uri(client, storage); + }; + + composite.VisitMounts(visitor); + + return CommandResult::OK; +} + +CommandResult +handle_mount(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + Storage *_composite = client.partition.instance.storage; + if (_composite == nullptr) { + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; + } + + CompositeStorage &composite = *(CompositeStorage *)_composite; + + const char *const local_uri = argv[1]; + const char *const remote_uri = argv[2]; + + if (*local_uri == 0) { + command_error(client, ACK_ERROR_ARG, "Bad mount point"); + return CommandResult::ERROR; + } + + if (strchr(local_uri, '/') != nullptr) { + /* allow only top-level mounts for now */ + /* TODO: eliminate this limitation after ensuring that + UpdateQueue::Erase() really gets called for every + unmount, and no Directory disappears recursively + during database update */ + command_error(client, ACK_ERROR_ARG, "Bad mount point"); + return CommandResult::ERROR; + } + + Error error; + Storage *storage = CreateStorageURI(remote_uri, error); + if (storage == nullptr) { + if (error.IsDefined()) + return print_error(client, error); + + command_error(client, ACK_ERROR_ARG, + "Unrecognized storage URI"); + return CommandResult::ERROR; + } + + composite.Mount(local_uri, storage); + idle_add(IDLE_MOUNT); + +#ifdef ENABLE_DATABASE + Database *_db = client.partition.instance.database; + if (_db != nullptr && _db->IsPlugin(simple_db_plugin)) { + SimpleDatabase &db = *(SimpleDatabase *)_db; + + if (!db.Mount(local_uri, remote_uri, error)) { + composite.Unmount(local_uri); + return print_error(client, error); + } + + // TODO: call Instance::OnDatabaseModified()? + // TODO: trigger database update? + idle_add(IDLE_DATABASE); + } +#endif + + return CommandResult::OK; +} + +CommandResult +handle_unmount(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + Storage *_composite = client.partition.instance.storage; + if (_composite == nullptr) { + command_error(client, ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; + } + + CompositeStorage &composite = *(CompositeStorage *)_composite; + + const char *const local_uri = argv[1]; + + if (*local_uri == 0) { + command_error(client, ACK_ERROR_ARG, "Bad mount point"); + return CommandResult::ERROR; + } + +#ifdef ENABLE_DATABASE + if (client.partition.instance.update != nullptr) + /* ensure that no database update will attempt to work + with the database/storage instances we're about to + destroy here */ + client.partition.instance.update->CancelMount(local_uri); + + Database *_db = client.partition.instance.database; + if (_db != nullptr && _db->IsPlugin(simple_db_plugin)) { + SimpleDatabase &db = *(SimpleDatabase *)_db; + + if (db.Unmount(local_uri)) + // TODO: call Instance::OnDatabaseModified()? + idle_add(IDLE_DATABASE); + } +#endif + + if (!composite.Unmount(local_uri)) { + command_error(client, ACK_ERROR_ARG, "Not a mount point"); + return CommandResult::ERROR; + } + + idle_add(IDLE_MOUNT); + + return CommandResult::OK; +} diff --git a/src/command/StorageCommands.hxx b/src/command/StorageCommands.hxx new file mode 100644 index 000000000..a3636d54a --- /dev/null +++ b/src/command/StorageCommands.hxx @@ -0,0 +1,43 @@ +/* + * 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_STORAGE_COMMANDS_HXX +#define MPD_STORAGE_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; +class Storage; + +CommandResult +handle_listfiles_storage(Client &client, Storage &storage, const char *uri); + +CommandResult +handle_listfiles_storage(Client &client, const char *uri); + +CommandResult +handle_listmounts(Client &client, unsigned argc, char *argv[]); + +CommandResult +handle_mount(Client &client, unsigned argc, char *argv[]); + +CommandResult +handle_unmount(Client &client, unsigned argc, char *argv[]); + +#endif diff --git a/src/command/TagCommands.cxx b/src/command/TagCommands.cxx new file mode 100644 index 000000000..2d537671c --- /dev/null +++ b/src/command/TagCommands.cxx @@ -0,0 +1,78 @@ +/* + * 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 "TagCommands.hxx" +#include "CommandError.hxx" +#include "client/Client.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "tag/Tag.hxx" +#include "Partition.hxx" + +CommandResult +handle_addtagid(Client &client, gcc_unused unsigned argc, char *argv[]) +{ + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[1])) + return CommandResult::ERROR; + + const char *const tag_name = argv[2]; + const TagType tag_type = tag_name_parse_i(tag_name); + if (tag_type == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, + "Unknown tag type: %s", tag_name); + return CommandResult::ERROR; + } + + const char *const value = argv[3]; + + Error error; + if (!client.partition.playlist.AddSongIdTag(song_id, tag_type, value, + error)) + return print_error(client, error); + + return CommandResult::OK; +} + +CommandResult +handle_cleartagid(Client &client, unsigned argc, char *argv[]) +{ + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[1])) + return CommandResult::ERROR; + + TagType tag_type = TAG_NUM_OF_ITEM_TYPES; + if (argc >= 3) { + const char *const tag_name = argv[2]; + tag_type = tag_name_parse_i(tag_name); + if (tag_type == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, + "Unknown tag type: %s", tag_name); + return CommandResult::ERROR; + } + } + + Error error; + if (!client.partition.playlist.ClearSongIdTag(song_id, tag_type, + error)) + return print_error(client, error); + + return CommandResult::OK; +} diff --git a/src/command/TagCommands.hxx b/src/command/TagCommands.hxx new file mode 100644 index 000000000..748838e68 --- /dev/null +++ b/src/command/TagCommands.hxx @@ -0,0 +1,33 @@ +/* + * 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_TAG_COMMANDS_HXX +#define MPD_TAG_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; + +CommandResult +handle_addtagid(Client &client, unsigned argc, char *argv[]); + +CommandResult +handle_cleartagid(Client &client, unsigned argc, char *argv[]); + +#endif |