diff options
Diffstat (limited to 'src/command')
32 files changed, 1241 insertions, 1117 deletions
diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx index 6a4b18198..8e8865ff3 100644 --- a/src/command/AllCommands.cxx +++ b/src/command/AllCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" #include "AllCommands.hxx" +#include "Request.hxx" #include "QueueCommands.hxx" #include "TagCommands.hxx" #include "PlayerCommands.hxx" @@ -32,11 +33,14 @@ #include "OtherCommands.hxx" #include "Permission.hxx" #include "tag/TagType.h" -#include "protocol/Result.hxx" #include "Partition.hxx" #include "client/Client.hxx" +#include "client/Response.hxx" +#include "util/Macros.hxx" #include "util/Tokenizer.hxx" #include "util/Error.hxx" +#include "util/ConstBuffer.hxx" +#include "util/StringAPI.hxx" #ifdef ENABLE_SQLITE #include "StickerCommands.hxx" @@ -60,22 +64,22 @@ struct command { unsigned permission; int min; int max; - CommandResult (*handler)(Client &client, unsigned argc, char **argv); + CommandResult (*handler)(Client &client, Request request, Response &response); }; /* don't be fooled, this is the command handler for "commands" command */ static CommandResult -handle_commands(Client &client, unsigned argc, char *argv[]); +handle_commands(Client &client, Request request, Response &response); static CommandResult -handle_not_commands(Client &client, unsigned argc, char *argv[]); +handle_not_commands(Client &client, Request request, Response &response); /** * The command registry. * * This array must be sorted! */ -static const struct command commands[] = { +static constexpr struct command commands[] = { { "add", PERMISSION_ADD, 1, 1, handle_add }, { "addid", PERMISSION_ADD, 1, 2, handle_addid }, { "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid }, @@ -194,62 +198,79 @@ static const struct command commands[] = { { "volume", PERMISSION_CONTROL, 1, 1, handle_volume }, }; -static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]); +static constexpr unsigned num_commands = ARRAY_SIZE(commands); static bool command_available(gcc_unused const Partition &partition, gcc_unused const struct command *cmd) { #ifdef ENABLE_SQLITE - if (strcmp(cmd->cmd, "sticker") == 0) + if (StringIsEqual(cmd->cmd, "sticker")) return sticker_enabled(); #endif #ifdef ENABLE_NEIGHBOR_PLUGINS - if (strcmp(cmd->cmd, "listneighbors") == 0) + if (StringIsEqual(cmd->cmd, "listneighbors")) return neighbor_commands_available(partition.instance); #endif + if (StringIsEqual(cmd->cmd, "save") || + StringIsEqual(cmd->cmd, "rm") || + StringIsEqual(cmd->cmd, "rename") || + StringIsEqual(cmd->cmd, "playlistdelete") || + StringIsEqual(cmd->cmd, "playlistmove") || + StringIsEqual(cmd->cmd, "playlistclear") || + StringIsEqual(cmd->cmd, "playlistadd") || + StringIsEqual(cmd->cmd, "listplaylists")) + return playlist_commands_available(); + return true; } -/* don't be fooled, this is the command handler for "commands" command */ static CommandResult -handle_commands(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +PrintAvailableCommands(Response &r, const Partition &partition, + unsigned permission) { - const unsigned permission = client.GetPermission(); - const struct command *cmd; - for (unsigned i = 0; i < num_commands; ++i) { - cmd = &commands[i]; + const struct command *cmd = &commands[i]; if (cmd->permission == (permission & cmd->permission) && - command_available(client.partition, cmd)) - client_printf(client, "command: %s\n", cmd->cmd); + command_available(partition, cmd)) + r.Format("command: %s\n", cmd->cmd); } return CommandResult::OK; } static CommandResult -handle_not_commands(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +PrintUnavailableCommands(Response &r, unsigned permission) { - const unsigned permission = client.GetPermission(); - const struct command *cmd; - for (unsigned i = 0; i < num_commands; ++i) { - cmd = &commands[i]; + const struct command *cmd = &commands[i]; if (cmd->permission != (permission & cmd->permission)) - client_printf(client, "command: %s\n", cmd->cmd); + r.Format("command: %s\n", cmd->cmd); } return CommandResult::OK; } -void command_init(void) +/* don't be fooled, this is the command handler for "commands" command */ +static CommandResult +handle_commands(Client &client, gcc_unused Request request, Response &r) +{ + return PrintAvailableCommands(r, client.partition, + client.GetPermission()); +} + +static CommandResult +handle_not_commands(Client &client, gcc_unused Request request, Response &r) +{ + return PrintUnavailableCommands(r, client.GetPermission()); +} + +void +command_init() { #ifndef NDEBUG /* ensure that the command list is sorted */ @@ -258,7 +279,8 @@ void command_init(void) #endif } -void command_finish(void) +void +command_finish() { } @@ -266,13 +288,12 @@ static const struct command * command_lookup(const char *name) { unsigned a = 0, b = num_commands, i; - int cmp; /* binary search */ do { i = (a + b) / 2; - cmp = strcmp(name, commands[i].cmd); + const auto cmp = strcmp(name, commands[i].cmd); if (cmp == 0) return &commands[i]; else if (cmp < 0) @@ -285,60 +306,53 @@ command_lookup(const char *name) } static bool -command_check_request(const struct command *cmd, Client &client, - unsigned permission, unsigned argc, char *argv[]) +command_check_request(const struct command *cmd, Response &r, + unsigned permission, Request args) { - const unsigned min = cmd->min + 1; - const unsigned max = cmd->max + 1; - if (cmd->permission != (permission & cmd->permission)) { - command_error(client, ACK_ERROR_PERMISSION, + r.FormatError(ACK_ERROR_PERMISSION, "you don't have permission for \"%s\"", cmd->cmd); return false; } - if (min == 0) + const int min = cmd->min; + const int max = cmd->max; + + if (min < 0) return true; - if (min == max && max != argc) { - command_error(client, ACK_ERROR_ARG, + if (min == max && unsigned(max) != args.size) { + r.FormatError(ACK_ERROR_ARG, "wrong number of arguments for \"%s\"", - argv[0]); + cmd->cmd); return false; - } else if (argc < min) { - command_error(client, ACK_ERROR_ARG, - "too few arguments for \"%s\"", argv[0]); + } else if (args.size < unsigned(min)) { + r.FormatError(ACK_ERROR_ARG, + "too few arguments for \"%s\"", cmd->cmd); return false; - } else if (argc > max && max /* != 0 */ ) { - command_error(client, ACK_ERROR_ARG, - "too many arguments for \"%s\"", argv[0]); + } else if (max >= 0 && args.size > unsigned(max)) { + r.FormatError(ACK_ERROR_ARG, + "too many arguments for \"%s\"", cmd->cmd); return false; } else return true; } static const struct command * -command_checked_lookup(Client &client, unsigned permission, - unsigned argc, char *argv[]) +command_checked_lookup(Response &r, unsigned permission, + const char *cmd_name, Request args) { - const struct command *cmd; - - current_command = ""; - - if (argc == 0) - return nullptr; - - cmd = command_lookup(argv[0]); + const struct command *cmd = command_lookup(cmd_name); if (cmd == nullptr) { - command_error(client, ACK_ERROR_UNKNOWN, - "unknown command \"%s\"", argv[0]); + r.FormatError(ACK_ERROR_UNKNOWN, + "unknown command \"%s\"", cmd_name); return nullptr; } - current_command = cmd->cmd; + r.SetCommand(cmd->cmd); - if (!command_check_request(cmd, client, permission, argc, argv)) + if (!command_check_request(cmd, r, permission, args)) return nullptr; return cmd; @@ -347,68 +361,59 @@ command_checked_lookup(Client &client, unsigned permission, CommandResult command_process(Client &client, unsigned num, char *line) { + Response r(client, num); Error error; - char *argv[COMMAND_ARGV_MAX] = { nullptr }; - const struct command *cmd; - CommandResult ret = CommandResult::ERROR; - - command_list_num = num; /* get the command name (first word on the line) */ + /* we have to set current_command because Response::Error() + expects it to be set */ Tokenizer tokenizer(line); - argv[0] = tokenizer.NextWord(error); - if (argv[0] == nullptr) { - current_command = ""; + + const char *const cmd_name = tokenizer.NextWord(error); + if (cmd_name == nullptr) { if (tokenizer.IsEnd()) - command_error(client, ACK_ERROR_UNKNOWN, - "No command given"); + r.FormatError(ACK_ERROR_UNKNOWN, "No command given"); else - command_error(client, ACK_ERROR_UNKNOWN, - "%s", error.GetMessage()); - - current_command = nullptr; + r.Error(ACK_ERROR_UNKNOWN, error.GetMessage()); /* this client does not speak the MPD protocol; kick the connection */ return CommandResult::FINISH; } - unsigned argc = 1; + char *argv[COMMAND_ARGV_MAX]; + Request args(argv, 0); /* now parse the arguments (quoted or unquoted) */ - while (argc < COMMAND_ARGV_MAX && - (argv[argc] = - tokenizer.NextParam(error)) != nullptr) - ++argc; - - /* some error checks; we have to set current_command because - command_error() expects it to be set */ + while (true) { + if (args.size == COMMAND_ARGV_MAX) { + r.Error(ACK_ERROR_ARG, "Too many arguments"); + return CommandResult::ERROR; + } - current_command = argv[0]; + char *a = tokenizer.NextParam(error); + if (a == nullptr) { + if (tokenizer.IsEnd()) + break; - if (argc >= COMMAND_ARGV_MAX) { - command_error(client, ACK_ERROR_ARG, "Too many arguments"); - current_command = nullptr; - return CommandResult::ERROR; - } + r.Error(ACK_ERROR_UNKNOWN, error.GetMessage()); + return CommandResult::ERROR; + } - if (!tokenizer.IsEnd()) { - command_error(client, ACK_ERROR_ARG, "%s", error.GetMessage()); - current_command = nullptr; - return CommandResult::ERROR; + argv[args.size++] = a; } /* look up and invoke the command handler */ - cmd = command_checked_lookup(client, client.GetPermission(), - argc, argv); - if (cmd) - ret = cmd->handler(client, argc, argv); + const struct command *cmd = + command_checked_lookup(r, client.GetPermission(), + cmd_name, args); - current_command = nullptr; - command_list_num = 0; + CommandResult ret = cmd + ? cmd->handler(client, args, r) + : CommandResult::ERROR; return ret; } diff --git a/src/command/AllCommands.hxx b/src/command/AllCommands.hxx index b7834a8de..e3405b034 100644 --- a/src/command/AllCommands.hxx +++ b/src/command/AllCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,9 +24,11 @@ class Client; -void command_init(void); +void +command_init(); -void command_finish(void); +void +command_finish(); CommandResult command_process(Client &client, unsigned num, char *line); diff --git a/src/command/CommandError.cxx b/src/command/CommandError.cxx index 89085fc68..9f06431b4 100644 --- a/src/command/CommandError.cxx +++ b/src/command/CommandError.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -20,7 +20,8 @@ #include "config.h" #include "CommandError.hxx" #include "db/DatabaseError.hxx" -#include "protocol/Result.hxx" +#include "LocateUri.hxx" +#include "client/Response.hxx" #include "util/Error.hxx" #include "Log.hxx" @@ -29,57 +30,55 @@ #include <errno.h> CommandResult -print_playlist_result(Client &client, PlaylistResult result) +print_playlist_result(Response &r, PlaylistResult result) { switch (result) { case PlaylistResult::SUCCESS: return CommandResult::OK; case PlaylistResult::ERRNO: - command_error(client, ACK_ERROR_SYSTEM, "%s", - strerror(errno)); + r.Error(ACK_ERROR_SYSTEM, strerror(errno)); return CommandResult::ERROR; case PlaylistResult::DENIED: - command_error(client, ACK_ERROR_PERMISSION, "Access denied"); + r.Error(ACK_ERROR_PERMISSION, "Access denied"); return CommandResult::ERROR; case PlaylistResult::NO_SUCH_SONG: - command_error(client, ACK_ERROR_NO_EXIST, "No such song"); + r.Error(ACK_ERROR_NO_EXIST, "No such song"); return CommandResult::ERROR; case PlaylistResult::NO_SUCH_LIST: - command_error(client, ACK_ERROR_NO_EXIST, "No such playlist"); + r.Error(ACK_ERROR_NO_EXIST, "No such playlist"); return CommandResult::ERROR; case PlaylistResult::LIST_EXISTS: - command_error(client, ACK_ERROR_EXIST, - "Playlist already exists"); + r.Error(ACK_ERROR_EXIST, "Playlist already exists"); return CommandResult::ERROR; case PlaylistResult::BAD_NAME: - command_error(client, ACK_ERROR_ARG, - "playlist name is invalid: " - "playlist names may not contain slashes," - " newlines or carriage returns"); + r.Error(ACK_ERROR_ARG, + "playlist name is invalid: " + "playlist names may not contain slashes," + " newlines or carriage returns"); return CommandResult::ERROR; case PlaylistResult::BAD_RANGE: - command_error(client, ACK_ERROR_ARG, "Bad song index"); + r.Error(ACK_ERROR_ARG, "Bad song index"); return CommandResult::ERROR; case PlaylistResult::NOT_PLAYING: - command_error(client, ACK_ERROR_PLAYER_SYNC, "Not playing"); + r.Error(ACK_ERROR_PLAYER_SYNC, "Not playing"); return CommandResult::ERROR; case PlaylistResult::TOO_LARGE: - command_error(client, ACK_ERROR_PLAYLIST_MAX, - "playlist is at the max size"); + r.Error(ACK_ERROR_PLAYLIST_MAX, + "playlist is at the max size"); return CommandResult::ERROR; case PlaylistResult::DISABLED: - command_error(client, ACK_ERROR_UNKNOWN, - "stored playlist support is disabled"); + r.Error(ACK_ERROR_UNKNOWN, + "stored playlist support is disabled"); return CommandResult::ERROR; } @@ -88,42 +87,42 @@ print_playlist_result(Client &client, PlaylistResult result) } CommandResult -print_error(Client &client, const Error &error) +print_error(Response &r, const Error &error) { assert(error.IsDefined()); LogError(error); if (error.IsDomain(playlist_domain)) { - return print_playlist_result(client, + return print_playlist_result(r, PlaylistResult(error.GetCode())); } else if (error.IsDomain(ack_domain)) { - command_error(client, (ack)error.GetCode(), - "%s", error.GetMessage()); + r.Error((ack)error.GetCode(), error.GetMessage()); return CommandResult::ERROR; #ifdef ENABLE_DATABASE } else if (error.IsDomain(db_domain)) { switch ((enum db_error)error.GetCode()) { case DB_DISABLED: - command_error(client, ACK_ERROR_NO_EXIST, "%s", - error.GetMessage()); + r.Error(ACK_ERROR_NO_EXIST, error.GetMessage()); return CommandResult::ERROR; case DB_NOT_FOUND: - command_error(client, ACK_ERROR_NO_EXIST, "Not found"); + r.Error(ACK_ERROR_NO_EXIST, "Not found"); return CommandResult::ERROR; case DB_CONFLICT: - command_error(client, ACK_ERROR_ARG, "Conflict"); + r.Error(ACK_ERROR_ARG, "Conflict"); return CommandResult::ERROR; } #endif + } else if (error.IsDomain(locate_uri_domain)) { + r.Error(ACK_ERROR_ARG, error.GetMessage()); + return CommandResult::ERROR; } else if (error.IsDomain(errno_domain)) { - command_error(client, ACK_ERROR_SYSTEM, "%s", - strerror(error.GetCode())); + r.Error(ACK_ERROR_SYSTEM, strerror(error.GetCode())); return CommandResult::ERROR; } - command_error(client, ACK_ERROR_UNKNOWN, "error"); + r.Error(ACK_ERROR_UNKNOWN, "error"); return CommandResult::ERROR; } diff --git a/src/command/CommandError.hxx b/src/command/CommandError.hxx index b48baa5bf..e33386078 100644 --- a/src/command/CommandError.hxx +++ b/src/command/CommandError.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,16 +23,16 @@ #include "CommandResult.hxx" #include "PlaylistError.hxx" -class Client; +class Response; class Error; CommandResult -print_playlist_result(Client &client, PlaylistResult result); +print_playlist_result(Response &r, PlaylistResult result); /** * Send the #Error to the client. */ CommandResult -print_error(Client &client, const Error &error); +print_error(Response &r, const Error &error); #endif diff --git a/src/command/CommandListBuilder.cxx b/src/command/CommandListBuilder.cxx index 477c246ff..4abb3ad16 100644 --- a/src/command/CommandListBuilder.cxx +++ b/src/command/CommandListBuilder.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -27,7 +27,6 @@ void CommandListBuilder::Reset() { list.clear(); - size = 0; mode = Mode::DISABLED; } diff --git a/src/command/CommandListBuilder.hxx b/src/command/CommandListBuilder.hxx index 0747c4697..9908121d6 100644 --- a/src/command/CommandListBuilder.hxx +++ b/src/command/CommandListBuilder.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -58,7 +58,7 @@ class CommandListBuilder { public: CommandListBuilder() - :mode(Mode::DISABLED), size(0) {} + :mode(Mode::DISABLED) {} /** * Is a command list currently being built? @@ -89,6 +89,7 @@ public: assert(mode == Mode::DISABLED); mode = (Mode)ok; + size = 0; } /** diff --git a/src/command/CommandResult.hxx b/src/command/CommandResult.hxx index a2e968fb6..13641ec39 100644 --- a/src/command/CommandResult.hxx +++ b/src/command/CommandResult.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 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 a3ea8d0ae..bfcf3aa54 100644 --- a/src/command/DatabaseCommands.cxx +++ b/src/command/DatabaseCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" #include "DatabaseCommands.hxx" +#include "Request.hxx" #include "db/DatabaseGlue.hxx" #include "db/DatabaseQueue.hxx" #include "db/DatabasePlaylist.hxx" @@ -27,83 +28,89 @@ #include "db/Selection.hxx" #include "CommandError.hxx" #include "client/Client.hxx" +#include "client/Response.hxx" #include "tag/Tag.hxx" #include "util/ConstBuffer.hxx" #include "util/Error.hxx" +#include "util/StringAPI.hxx" #include "SongFilter.hxx" -#include "protocol/Result.hxx" #include "BulkEdit.hxx" #include <string.h> CommandResult -handle_listfiles_db(Client &client, const char *uri) +handle_listfiles_db(Client &client, Response &r, const char *uri) { const DatabaseSelection selection(uri, false); Error error; - if (!db_selection_print(client, selection, false, true, error)) - return print_error(client, error); + if (!db_selection_print(r, client.partition, + selection, false, true, error)) + return print_error(r, error); return CommandResult::OK; } CommandResult -handle_lsinfo2(Client &client, unsigned argc, char *argv[]) +handle_lsinfo2(Client &client, const char *uri, Response &r) { - const char *const uri = argc == 2 - ? argv[1] - /* default is root directory */ - : ""; - const DatabaseSelection selection(uri, false); Error error; - if (!db_selection_print(client, selection, true, false, error)) - return print_error(client, error); + if (!db_selection_print(r, client.partition, + selection, true, false, error)) + return print_error(r, error); return CommandResult::OK; } static CommandResult -handle_match(Client &client, unsigned argc, char *argv[], bool fold_case) +handle_match(Client &client, Request args, Response &r, bool fold_case) { - ConstBuffer<const char *> args(argv + 1, argc - 1); + RangeArg window; + if (args.size >= 2 && StringIsEqual(args[args.size - 2], "window")) { + if (!args.Parse(args.size - 1, window, r)) + return CommandResult::ERROR; + + args.pop_back(); + args.pop_back(); + } else + window.SetAll(); SongFilter filter; if (!filter.Parse(args, fold_case)) { - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + r.Error(ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } const DatabaseSelection selection("", true, &filter); Error error; - return db_selection_print(client, selection, true, false, error) + return db_selection_print(r, client.partition, + selection, true, false, + window.start, window.end, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); } CommandResult -handle_find(Client &client, unsigned argc, char *argv[]) +handle_find(Client &client, Request args, Response &r) { - return handle_match(client, argc, argv, false); + return handle_match(client, args, r, false); } CommandResult -handle_search(Client &client, unsigned argc, char *argv[]) +handle_search(Client &client, Request args, Response &r) { - return handle_match(client, argc, argv, true); + return handle_match(client, args, r, true); } static CommandResult -handle_match_add(Client &client, unsigned argc, char *argv[], bool fold_case) +handle_match_add(Client &client, Request args, Response &r, bool fold_case) { - ConstBuffer<const char *> args(argv + 1, argc - 1); - SongFilter filter; if (!filter.Parse(args, fold_case)) { - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + r.Error(ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } @@ -113,55 +120,52 @@ handle_match_add(Client &client, unsigned argc, char *argv[], bool fold_case) Error error; return AddFromDatabase(client.partition, selection, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); } CommandResult -handle_findadd(Client &client, unsigned argc, char *argv[]) +handle_findadd(Client &client, Request args, Response &r) { - return handle_match_add(client, argc, argv, false); + return handle_match_add(client, args, r, false); } CommandResult -handle_searchadd(Client &client, unsigned argc, char *argv[]) +handle_searchadd(Client &client, Request args, Response &r) { - return handle_match_add(client, argc, argv, true); + return handle_match_add(client, args, r, true); } CommandResult -handle_searchaddpl(Client &client, unsigned argc, char *argv[]) +handle_searchaddpl(Client &client, Request args, Response &r) { - ConstBuffer<const char *> args(argv + 1, argc - 1); const char *playlist = args.shift(); SongFilter filter; if (!filter.Parse(args, true)) { - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + r.Error(ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } Error error; const Database *db = client.GetDatabase(error); if (db == nullptr) - return print_error(client, error); + return print_error(r, error); return search_add_to_playlist(*db, *client.GetStorage(), "", playlist, &filter, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); } CommandResult -handle_count(Client &client, unsigned argc, char *argv[]) +handle_count(Client &client, Request args, Response &r) { - 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) { + if (args.size >= 2 && StringIsEqual(args[args.size - 2], "group")) { 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, + r.FormatError(ACK_ERROR_ARG, "Unknown tag type: %s", s); return CommandResult::ERROR; } @@ -172,52 +176,50 @@ handle_count(Client &client, unsigned argc, char *argv[]) SongFilter filter; if (!args.IsEmpty() && !filter.Parse(args, false)) { - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + r.Error(ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } Error error; - return PrintSongCount(client, "", &filter, group, error) + return PrintSongCount(r, client.partition, "", &filter, group, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); } CommandResult -handle_listall(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_listall(Client &client, Request args, Response &r) { - const char *directory = ""; - - if (argc == 2) - directory = argv[1]; + /* default is root directory */ + const auto uri = args.GetOptional(0, ""); Error error; - return db_selection_print(client, DatabaseSelection(directory, true), + return db_selection_print(r, client.partition, + DatabaseSelection(uri, true), false, false, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); } CommandResult -handle_list(Client &client, unsigned argc, char *argv[]) +handle_list(Client &client, Request args, Response &r) { - ConstBuffer<const char *> args(argv + 1, argc - 1); const char *tag_name = args.shift(); unsigned tagType = locate_parse_type(tag_name); if (tagType >= TAG_NUM_OF_ITEM_TYPES && tagType != LOCATE_TAG_FILE_TYPE) { - command_error(client, ACK_ERROR_ARG, + r.FormatError(ACK_ERROR_ARG, "Unknown tag type: %s", tag_name); return CommandResult::ERROR; } SongFilter *filter = nullptr; - uint32_t group_mask = 0; + tag_mask_t group_mask = 0; if (args.size == 1) { /* for compatibility with < 0.12.0 */ if (tagType != TAG_ALBUM) { - command_error(client, ACK_ERROR_ARG, + r.FormatError(ACK_ERROR_ARG, "should be \"%s\" for 3 arguments", tag_item_names[TAG_ALBUM]); return CommandResult::ERROR; @@ -227,16 +229,16 @@ handle_list(Client &client, unsigned argc, char *argv[]) } while (args.size >= 2 && - strcmp(args[args.size - 2], "group") == 0) { + StringIsEqual(args[args.size - 2], "group")) { 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, + r.FormatError(ACK_ERROR_ARG, "Unknown tag type: %s", s); return CommandResult::ERROR; } - group_mask |= 1u << unsigned(gt); + group_mask |= tag_mask_t(1) << unsigned(gt); args.pop_back(); args.pop_back(); @@ -246,24 +248,24 @@ handle_list(Client &client, unsigned argc, char *argv[]) filter = new SongFilter(); if (!filter->Parse(args, false)) { delete filter; - command_error(client, ACK_ERROR_ARG, - "not able to parse args"); + r.Error(ACK_ERROR_ARG, "not able to parse args"); return CommandResult::ERROR; } } if (tagType < TAG_NUM_OF_ITEM_TYPES && - group_mask & (1u << tagType)) { + group_mask & (tag_mask_t(1) << tagType)) { delete filter; - command_error(client, ACK_ERROR_ARG, "Conflicting group"); + r.Error(ACK_ERROR_ARG, "Conflicting group"); return CommandResult::ERROR; } Error error; CommandResult ret = - PrintUniqueTags(client, tagType, group_mask, filter, error) + PrintUniqueTags(r, client.partition, + tagType, group_mask, filter, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); delete filter; @@ -271,16 +273,15 @@ handle_list(Client &client, unsigned argc, char *argv[]) } CommandResult -handle_listallinfo(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_listallinfo(Client &client, Request args, Response &r) { - const char *directory = ""; - - if (argc == 2) - directory = argv[1]; + /* default is root directory */ + const auto uri = args.GetOptional(0, ""); Error error; - return db_selection_print(client, DatabaseSelection(directory, true), + return db_selection_print(r, client.partition, + DatabaseSelection(uri, true), true, false, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); } diff --git a/src/command/DatabaseCommands.hxx b/src/command/DatabaseCommands.hxx index 7abf89e0c..660e147db 100644 --- a/src/command/DatabaseCommands.hxx +++ b/src/command/DatabaseCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,38 +23,40 @@ #include "CommandResult.hxx" class Client; +class Request; +class Response; CommandResult -handle_listfiles_db(Client &client, const char *uri); +handle_listfiles_db(Client &client, Response &r, const char *uri); CommandResult -handle_lsinfo2(Client &client, unsigned argc, char *argv[]); +handle_lsinfo2(Client &client, const char *uri, Response &response); CommandResult -handle_find(Client &client, unsigned argc, char *argv[]); +handle_find(Client &client, Request request, Response &response); CommandResult -handle_findadd(Client &client, unsigned argc, char *argv[]); +handle_findadd(Client &client, Request request, Response &response); CommandResult -handle_search(Client &client, unsigned argc, char *argv[]); +handle_search(Client &client, Request request, Response &response); CommandResult -handle_searchadd(Client &client, unsigned argc, char *argv[]); +handle_searchadd(Client &client, Request request, Response &response); CommandResult -handle_searchaddpl(Client &client, unsigned argc, char *argv[]); +handle_searchaddpl(Client &client, Request request, Response &response); CommandResult -handle_count(Client &client, unsigned argc, char *argv[]); +handle_count(Client &client, Request request, Response &response); CommandResult -handle_listall(Client &client, unsigned argc, char *argv[]); +handle_listall(Client &client, Request request, Response &response); CommandResult -handle_list(Client &client, unsigned argc, char *argv[]); +handle_list(Client &client, Request request, Response &response); CommandResult -handle_listallinfo(Client &client, unsigned argc, char *argv[]); +handle_listallinfo(Client &client, Request request, Response &response); #endif diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx index 1b6a11cf5..486c00d89 100644 --- a/src/command/FileCommands.cxx +++ b/src/command/FileCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,10 +21,12 @@ #include "config.h" #include "FileCommands.hxx" +#include "Request.hxx" #include "CommandError.hxx" #include "protocol/Ack.hxx" -#include "protocol/Result.hxx" #include "client/Client.hxx" +#include "client/Response.hxx" +#include "util/ConstBuffer.hxx" #include "util/CharUtil.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" @@ -35,10 +37,10 @@ #include "TagFile.hxx" #include "storage/StorageInterface.hxx" #include "fs/AllocatedPath.hxx" -#include "fs/FileSystem.hxx" +#include "fs/FileInfo.hxx" #include "fs/DirectoryReader.hxx" +#include "LocateUri.hxx" #include "TimePrint.hxx" -#include "ls.hxx" #include <assert.h> #include <sys/stat.h> @@ -46,7 +48,7 @@ gcc_pure static bool -SkipNameFS(const char *name_fs) +SkipNameFS(PathTraitsFS::const_pointer name_fs) { return name_fs[0] == '.' && (name_fs[1] == 0 || @@ -55,9 +57,9 @@ SkipNameFS(const char *name_fs) gcc_pure static bool -skip_path(const char *name_fs) +skip_path(Path name_fs) { - return strchr(name_fs, '\n') != nullptr; + return name_fs.HasNewline(); } #if defined(WIN32) && GCC_CHECK_VERSION(4,6) @@ -68,28 +70,19 @@ skip_path(const char *name_fs) #endif CommandResult -handle_listfiles_local(Client &client, const char *path_utf8) +handle_listfiles_local(Response &r, + const char *path_utf8, Path path_fs) { - 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 error; error.FormatErrno("Failed to open '%s'", path_utf8); - return print_error(client, error); + return print_error(r, error); } while (reader.ReadEntry()) { const Path name_fs = reader.GetEntry(); - if (SkipNameFS(name_fs.c_str()) || skip_path(name_fs.c_str())) + if (SkipNameFS(name_fs.c_str()) || skip_path(name_fs)) continue; std::string name_utf8 = name_fs.ToUTF8(); @@ -98,20 +91,21 @@ handle_listfiles_local(Client &client, const char *path_utf8) const AllocatedPath full_fs = AllocatedPath::Build(path_fs, name_fs); - struct stat st; - if (!StatFile(full_fs, st, false)) + FileInfo fi; + if (!GetFileInfo(full_fs, fi, 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()); + if (fi.IsRegular()) + r.Format("file: %s\n" + "size: %" PRIu64 "\n", + name_utf8.c_str(), + fi.GetSize()); + else if (fi.IsDirectory()) + r.Format("directory: %s\n", name_utf8.c_str()); + else + continue; - time_print(client, "Last-Modified", st.st_mtime); + time_print(r, "Last-Modified", fi.GetModificationTime()); } return CommandResult::OK; @@ -154,10 +148,10 @@ IsValidValue(const char *p) static void print_pair(const char *key, const char *value, void *ctx) { - Client &client = *(Client *)ctx; + auto &r = *(Response *)ctx; if (IsValidName(key) && IsValidValue(value)) - client_printf(client, "%s: %s\n", key, value); + r.Format("%s: %s\n", key, value); } static constexpr tag_handler print_comment_handler = { @@ -167,93 +161,90 @@ static constexpr tag_handler print_comment_handler = { }; static CommandResult -read_stream_comments(Client &client, const char *uri) +read_stream_comments(Response &r, const char *uri) { - if (!uri_supported_scheme(uri)) { - command_error(client, ACK_ERROR_NO_EXIST, - "unsupported URI scheme"); + if (!tag_stream_scan(uri, print_comment_handler, &r)) { + r.Error(ACK_ERROR_NO_EXIST, "Failed to load file"); 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::OK; + +} + +static CommandResult +read_file_comments(Response &r, const Path path_fs) +{ + if (!tag_file_scan(path_fs, print_comment_handler, &r)) { + r.Error(ACK_ERROR_NO_EXIST, "Failed to load file"); return CommandResult::ERROR; } + tag_ape_scan2(path_fs, &print_comment_handler, &r); + tag_id3_scan(path_fs, &print_comment_handler, &r); + return CommandResult::OK; } static CommandResult -read_file_comments(Client &client, const Path path_fs) +read_db_comments(Client &client, Response &r, const char *uri) { - if (!tag_file_scan(path_fs, print_comment_handler, &client)) { - command_error(client, ACK_ERROR_NO_EXIST, - "Failed to load file"); +#ifdef ENABLE_DATABASE + const Storage *storage = client.GetStorage(); + if (storage == nullptr) { +#else + (void)client; + (void)uri; +#endif + r.Error(ACK_ERROR_NO_EXIST, "No database"); return CommandResult::ERROR; +#ifdef ENABLE_DATABASE } - tag_ape_scan2(path_fs, &print_comment_handler, &client); - tag_id3_scan(path_fs, &print_comment_handler, &client); + { + AllocatedPath path_fs = storage->MapFS(uri); + if (!path_fs.IsNull()) + return read_file_comments(r, path_fs); + } - return CommandResult::OK; + { + const std::string uri2 = storage->MapUTF8(uri); + if (uri_has_scheme(uri2.c_str())) + return read_stream_comments(r, uri2.c_str()); + } + r.Error(ACK_ERROR_NO_EXIST, "No such file"); + return CommandResult::ERROR; +#endif } CommandResult -handle_read_comments(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_read_comments(Client &client, Request args, Response &r) { - assert(argc == 2); + assert(args.size == 1); - const char *const uri = argv[1]; + const char *const uri = args.front(); - if (memcmp(uri, "file:///", 8) == 0) { - /* read comments from arbitrary local file */ - const char *path_utf8 = uri + 7; - AllocatedPath 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); - - 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 database"); - return CommandResult::ERROR; + Error error; + const auto located_uri = LocateUri(uri, &client, #ifdef ENABLE_DATABASE - } - - { - 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; + nullptr, #endif - } else { - command_error(client, ACK_ERROR_NO_EXIST, "No such file"); - return CommandResult::ERROR; + error); + switch (located_uri.type) { + case LocatedUri::Type::UNKNOWN: + return print_error(r, error); + + case LocatedUri::Type::ABSOLUTE: + return read_stream_comments(r, located_uri.canonical_uri); + + case LocatedUri::Type::RELATIVE: + return read_db_comments(client, r, located_uri.canonical_uri); + + case LocatedUri::Type::PATH: + return read_file_comments(r, located_uri.path); } + + gcc_unreachable(); } diff --git a/src/command/FileCommands.hxx b/src/command/FileCommands.hxx index 62835a82c..9c6631df5 100644 --- a/src/command/FileCommands.hxx +++ b/src/command/FileCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,11 +23,15 @@ #include "CommandResult.hxx" class Client; +class Request; +class Response; +class Path; CommandResult -handle_listfiles_local(Client &client, const char *path_utf8); +handle_listfiles_local(Response &response, + const char *path_utf8, Path path_fs); CommandResult -handle_read_comments(Client &client, unsigned argc, char *argv[]); +handle_read_comments(Client &client, Request request, Response &response); #endif diff --git a/src/command/MessageCommands.cxx b/src/command/MessageCommands.cxx index a86bdf30c..28f97a52f 100644 --- a/src/command/MessageCommands.cxx +++ b/src/command/MessageCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,11 +19,13 @@ #include "config.h" #include "MessageCommands.hxx" +#include "Request.hxx" #include "client/Client.hxx" #include "client/ClientList.hxx" +#include "client/Response.hxx" #include "Instance.hxx" #include "Partition.hxx" -#include "protocol/Result.hxx" +#include "util/ConstBuffer.hxx" #include <set> #include <string> @@ -31,27 +33,25 @@ #include <assert.h> CommandResult -handle_subscribe(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_subscribe(Client &client, Request args, Response &r) { - assert(argc == 2); + assert(args.size == 1); + const char *const channel_name = args[0]; - switch (client.Subscribe(argv[1])) { + switch (client.Subscribe(channel_name)) { case Client::SubscribeResult::OK: return CommandResult::OK; case Client::SubscribeResult::INVALID: - command_error(client, ACK_ERROR_ARG, - "invalid channel name"); + r.Error(ACK_ERROR_ARG, "invalid channel name"); return CommandResult::ERROR; case Client::SubscribeResult::ALREADY: - command_error(client, ACK_ERROR_EXIST, - "already subscribed to this channel"); + r.Error(ACK_ERROR_EXIST, "already subscribed to this channel"); return CommandResult::ERROR; case Client::SubscribeResult::FULL: - command_error(client, ACK_ERROR_EXIST, - "subscription list is full"); + r.Error(ACK_ERROR_EXIST, "subscription list is full"); return CommandResult::ERROR; } @@ -61,24 +61,23 @@ handle_subscribe(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_unsubscribe(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_unsubscribe(Client &client, Request args, Response &r) { - assert(argc == 2); + assert(args.size == 1); + const char *const channel_name = args[0]; - if (client.Unsubscribe(argv[1])) + if (client.Unsubscribe(channel_name)) return CommandResult::OK; else { - command_error(client, ACK_ERROR_NO_EXIST, - "not subscribed to this channel"); + r.Error(ACK_ERROR_NO_EXIST, "not subscribed to this channel"); return CommandResult::ERROR; } } CommandResult -handle_channels(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_channels(Client &client, gcc_unused Request args, Response &r) { - assert(argc == 1); + assert(args.IsEmpty()); std::set<std::string> channels; for (const auto &c : *client.partition.instance.client_list) @@ -86,22 +85,22 @@ handle_channels(Client &client, c.subscriptions.end()); for (const auto &channel : channels) - client_printf(client, "channel: %s\n", channel.c_str()); + r.Format("channel: %s\n", channel.c_str()); return CommandResult::OK; } CommandResult handle_read_messages(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) + gcc_unused Request args, Response &r) { - assert(argc == 1); + assert(args.IsEmpty()); while (!client.messages.empty()) { const ClientMessage &msg = client.messages.front(); - client_printf(client, "channel: %s\nmessage: %s\n", - msg.GetChannel(), msg.GetMessage()); + r.Format("channel: %s\nmessage: %s\n", + msg.GetChannel(), msg.GetMessage()); client.messages.pop_front(); } @@ -109,19 +108,20 @@ handle_read_messages(Client &client, } CommandResult -handle_send_message(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_send_message(Client &client, Request args, Response &r) { - assert(argc == 3); + assert(args.size == 2); - if (!client_message_valid_channel_name(argv[1])) { - command_error(client, ACK_ERROR_ARG, - "invalid channel name"); + const char *const channel_name = args[0]; + const char *const message_text = args[1]; + + if (!client_message_valid_channel_name(channel_name)) { + r.Error(ACK_ERROR_ARG, "invalid channel name"); return CommandResult::ERROR; } bool sent = false; - const ClientMessage msg(argv[1], argv[2]); + const ClientMessage msg(channel_name, message_text); for (auto &c : *client.partition.instance.client_list) if (c.PushMessage(msg)) sent = true; @@ -129,8 +129,8 @@ handle_send_message(Client &client, if (sent) return CommandResult::OK; else { - command_error(client, ACK_ERROR_NO_EXIST, - "nobody is subscribed to this channel"); + r.Error(ACK_ERROR_NO_EXIST, + "nobody is subscribed to this channel"); return CommandResult::ERROR; } } diff --git a/src/command/MessageCommands.hxx b/src/command/MessageCommands.hxx index ac8afe2fb..986c7cf6a 100644 --- a/src/command/MessageCommands.hxx +++ b/src/command/MessageCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,20 +23,22 @@ #include "CommandResult.hxx" class Client; +class Request; +class Response; CommandResult -handle_subscribe(Client &client, unsigned argc, char *argv[]); +handle_subscribe(Client &client, Request request, Response &response); CommandResult -handle_unsubscribe(Client &client, unsigned argc, char *argv[]); +handle_unsubscribe(Client &client, Request request, Response &response); CommandResult -handle_channels(Client &client, unsigned argc, char *argv[]); +handle_channels(Client &client, Request request, Response &response); CommandResult -handle_read_messages(Client &client, unsigned argc, char *argv[]); +handle_read_messages(Client &client, Request request, Response &response); CommandResult -handle_send_message(Client &client, unsigned argc, char *argv[]); +handle_send_message(Client &client, Request request, Response &response); #endif diff --git a/src/command/NeighborCommands.cxx b/src/command/NeighborCommands.cxx index 22e8adf9e..254f7a346 100644 --- a/src/command/NeighborCommands.cxx +++ b/src/command/NeighborCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,12 +19,14 @@ #include "config.h" #include "NeighborCommands.hxx" +#include "Request.hxx" #include "client/Client.hxx" +#include "client/Response.hxx" #include "Instance.hxx" #include "Partition.hxx" -#include "protocol/Result.hxx" #include "neighbor/Glue.hxx" #include "neighbor/Info.hxx" +#include "util/ConstBuffer.hxx" #include <set> #include <string> @@ -38,22 +40,19 @@ neighbor_commands_available(const Instance &instance) } CommandResult -handle_listneighbors(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_listneighbors(Client &client, gcc_unused Request args, Response &r) { const NeighborGlue *const neighbors = client.partition.instance.neighbors; if (neighbors == nullptr) { - command_error(client, ACK_ERROR_UNKNOWN, - "No neighbor plugin configured"); + r.Error(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()); + r.Format("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 index 7fb309aeb..efe05c5ae 100644 --- a/src/command/NeighborCommands.hxx +++ b/src/command/NeighborCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -25,12 +25,14 @@ struct Instance; class Client; +class Request; +class Response; gcc_pure bool neighbor_commands_available(const Instance &instance); CommandResult -handle_listneighbors(Client &client, unsigned argc, char *argv[]); +handle_listneighbors(Client &client, Request request, Response &response); #endif diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx index a924f77b5..b4a23fe4b 100644 --- a/src/command/OtherCommands.cxx +++ b/src/command/OtherCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,11 +19,13 @@ #include "config.h" #include "OtherCommands.hxx" +#include "Request.hxx" #include "FileCommands.hxx" #include "StorageCommands.hxx" #include "CommandError.hxx" #include "db/Uri.hxx" #include "storage/StorageInterface.hxx" +#include "LocateUri.hxx" #include "DetachedSong.hxx" #include "SongPrint.hxx" #include "TagPrint.hxx" @@ -31,18 +33,19 @@ #include "tag/TagHandler.hxx" #include "TimePrint.hxx" #include "decoder/DecoderPrint.hxx" -#include "protocol/ArgParser.hxx" -#include "protocol/Result.hxx" #include "ls.hxx" #include "mixer/Volume.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" +#include "util/ConstBuffer.hxx" +#include "util/StringAPI.hxx" #include "fs/AllocatedPath.hxx" #include "Stats.hxx" #include "Permission.hxx" #include "PlaylistFile.hxx" #include "db/PlaylistVector.hxx" #include "client/Client.hxx" +#include "client/Response.hxx" #include "Partition.hxx" #include "Instance.hxx" #include "Idle.hxx" @@ -57,52 +60,51 @@ #include <string.h> static void -print_spl_list(Client &client, const PlaylistVector &list) +print_spl_list(Response &r, const PlaylistVector &list) { for (const auto &i : list) { - client_printf(client, "playlist: %s\n", i.name.c_str()); + r.Format("playlist: %s\n", i.name.c_str()); if (i.mtime > 0) - time_print(client, "Last-Modified", i.mtime); + time_print(r, "Last-Modified", i.mtime); } } CommandResult -handle_urlhandlers(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_urlhandlers(Client &client, gcc_unused Request args, Response &r) { if (client.IsLocal()) - client_puts(client, "handler: file://\n"); - print_supported_uri_schemes(client); + r.Format("handler: file://\n"); + print_supported_uri_schemes(r); return CommandResult::OK; } CommandResult -handle_decoders(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_decoders(gcc_unused Client &client, gcc_unused Request args, + Response &r) { - decoder_list_print(client); + decoder_list_print(r); return CommandResult::OK; } CommandResult -handle_tagtypes(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_tagtypes(gcc_unused Client &client, gcc_unused Request request, + Response &r) { - tag_print_types(client); + tag_print_types(r); return CommandResult::OK; } CommandResult -handle_kill(gcc_unused Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_kill(gcc_unused Client &client, gcc_unused Request request, + gcc_unused Response &r) { return CommandResult::KILL; } CommandResult -handle_close(gcc_unused Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_close(gcc_unused Client &client, gcc_unused Request args, + gcc_unused Response &r) { return CommandResult::FINISH; } @@ -110,44 +112,60 @@ handle_close(gcc_unused Client &client, static void print_tag(TagType type, const char *value, void *ctx) { - Client &client = *(Client *)ctx; + auto &r = *(Response *)ctx; - tag_print(client, type, value); + tag_print(r, type, value); } CommandResult -handle_listfiles(Client &client, unsigned argc, char *argv[]) +handle_listfiles(Client &client, Request args, Response &r) { - 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); + /* default is root directory */ + const auto uri = args.GetOptional(0, ""); + Error error; + const auto located_uri = LocateUri(uri, &client, #ifdef ENABLE_DATABASE - if (uri_has_scheme(uri)) - /* use storage plugin to list remote directory */ - return handle_listfiles_storage(client, uri); + nullptr, +#endif + error); - /* must be a path relative to the configured - music_directory */ + switch (located_uri.type) { + case LocatedUri::Type::UNKNOWN: + return print_error(r, error); - 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); + case LocatedUri::Type::ABSOLUTE: +#ifdef ENABLE_DATABASE + /* use storage plugin to list remote directory */ + return handle_listfiles_storage(r, located_uri.canonical_uri); +#else + r.Error(ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +#endif - /* fall back to entries from database if we have no storage */ - return handle_listfiles_db(client, uri); + case LocatedUri::Type::RELATIVE: +#ifdef ENABLE_DATABASE + if (client.partition.instance.storage != nullptr) + /* if we have a storage instance, obtain a list of + files from it */ + return handle_listfiles_storage(r, + *client.partition.instance.storage, + uri); + + /* fall back to entries from database if we have no storage */ + return handle_listfiles_db(client, r, uri); #else - command_error(client, ACK_ERROR_NO_EXIST, "No database"); - return CommandResult::ERROR; + r.Error(ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; #endif + + case LocatedUri::Type::PATH: + /* list local directory */ + return handle_listfiles_local(r, located_uri.canonical_uri, + located_uri.path); + } + + gcc_unreachable(); } static constexpr tag_handler print_tag_handler = { @@ -156,69 +174,35 @@ static constexpr tag_handler print_tag_handler = { nullptr, }; -CommandResult -handle_lsinfo(Client &client, unsigned argc, char *argv[]) +static CommandResult +handle_lsinfo_absolute(Response &r, const char *uri) { - const char *const uri = argc == 2 - ? argv[1] - /* default is root directory */ - : ""; - - if (memcmp(uri, "file:///", 8) == 0) { - /* print information about an arbitrary local file */ - 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.AllowFile(path_fs, error)) - return print_error(client, error); - - 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); - return CommandResult::OK; + if (!tag_stream_scan(uri, print_tag_handler, &r)) { + r.Error(ACK_ERROR_NO_EXIST, "No such file"); + return CommandResult::ERROR; } - 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; - } + return CommandResult::OK; +} +static CommandResult +handle_lsinfo_relative(Client &client, Response &r, const char *uri) +{ #ifdef ENABLE_DATABASE - CommandResult result = handle_lsinfo2(client, argc, argv); + CommandResult result = handle_lsinfo2(client, uri, r); if (result != CommandResult::OK) return result; +#else + (void)client; #endif if (isRootDirectory(uri)) { Error error; const auto &list = ListPlaylistFiles(error); - print_spl_list(client, list); + print_spl_list(r, list); } else { #ifndef ENABLE_DATABASE - command_error(client, ACK_ERROR_NO_EXIST, "No database"); + r.Error(ACK_ERROR_NO_EXIST, "No database"); return CommandResult::ERROR; #endif } @@ -226,38 +210,84 @@ handle_lsinfo(Client &client, unsigned argc, char *argv[]) return CommandResult::OK; } +static CommandResult +handle_lsinfo_path(Client &client, Response &r, + const char *path_utf8, Path path_fs) +{ + DetachedSong song(path_utf8); + if (!song.LoadFile(path_fs)) { + r.Error(ACK_ERROR_NO_EXIST, "No such file"); + return CommandResult::ERROR; + } + + song_print_info(r, client.partition, song); + return CommandResult::OK; +} + +CommandResult +handle_lsinfo(Client &client, Request args, Response &r) +{ + /* default is root directory */ + const auto uri = args.GetOptional(0, ""); + + Error error; + const auto located_uri = LocateUri(uri, &client, +#ifdef ENABLE_DATABASE + nullptr, +#endif + error); + + switch (located_uri.type) { + case LocatedUri::Type::UNKNOWN: + return print_error(r, error); + + case LocatedUri::Type::ABSOLUTE: + return handle_lsinfo_absolute(r, located_uri.canonical_uri); + + case LocatedUri::Type::RELATIVE: + return handle_lsinfo_relative(client, r, + located_uri.canonical_uri); + + case LocatedUri::Type::PATH: + /* print information about an arbitrary local file */ + return handle_lsinfo_path(client, r, located_uri.canonical_uri, + located_uri.path); + } + + gcc_unreachable(); +} + #ifdef ENABLE_DATABASE static CommandResult -handle_update(Client &client, UpdateService &update, +handle_update(Response &r, 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); + r.Format("updating_db: %i\n", ret); return CommandResult::OK; } else { - command_error(client, ACK_ERROR_UPDATE_ALREADY, - "already updating"); + r.Error(ACK_ERROR_UPDATE_ALREADY, "already updating"); return CommandResult::ERROR; } } static CommandResult -handle_update(Client &client, Database &db, +handle_update(Response &r, 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); + r.Format("updating_db: %i\n", id); return CommandResult::OK; } else if (error.IsDefined()) { - return print_error(client, error); + return print_error(r, 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"); + r.Error(ACK_ERROR_NO_EXIST, "Not implemented"); return CommandResult::ERROR; } } @@ -265,72 +295,62 @@ handle_update(Client &client, Database &db, #endif static CommandResult -handle_update(Client &client, unsigned argc, char *argv[], bool discard) +handle_update(Client &client, Request args, Response &r, bool discard) { #ifdef ENABLE_DATABASE const char *path = ""; - assert(argc <= 2); - if (argc == 2) { - path = argv[1]; + assert(args.size <= 1); + if (!args.IsEmpty()) { + path = args.front(); - if (*path == 0 || strcmp(path, "/") == 0) + if (*path == 0 || StringIsEqual(path, "/")) /* backwards compatibility with MPD 0.15 */ path = ""; else if (!uri_safe_local(path)) { - command_error(client, ACK_ERROR_ARG, - "Malformed path"); + r.Error(ACK_ERROR_ARG, "Malformed path"); return CommandResult::ERROR; } } UpdateService *update = client.partition.instance.update; if (update != nullptr) - return handle_update(client, *update, path, discard); + return handle_update(r, *update, path, discard); Database *db = client.partition.instance.database; if (db != nullptr) - return handle_update(client, *db, path, discard); + return handle_update(r, *db, path, discard); #else - (void)argc; - (void)argv; + (void)client; + (void)args; (void)discard; #endif - command_error(client, ACK_ERROR_NO_EXIST, "No database"); + r.Error(ACK_ERROR_NO_EXIST, "No database"); return CommandResult::ERROR; } CommandResult -handle_update(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_update(Client &client, Request args, gcc_unused Response &r) { - return handle_update(client, argc, argv, false); + return handle_update(client, args, r, false); } CommandResult -handle_rescan(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_rescan(Client &client, Request args, Response &r) { - return handle_update(client, argc, argv, true); + return handle_update(client, args, r, true); } CommandResult -handle_setvol(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_setvol(Client &client, Request args, Response &r) { unsigned level; - bool success; - - if (!check_unsigned(client, &level, argv[1])) + if (!args.Parse(0, level, r, 100)) return CommandResult::ERROR; - if (level > 100) { - command_error(client, ACK_ERROR_ARG, "Invalid volume value"); - return CommandResult::ERROR; - } - - success = volume_level_change(client.partition.outputs, level); - if (!success) { - command_error(client, ACK_ERROR_SYSTEM, - "problems setting volume"); + if (!volume_level_change(client.partition.outputs, level)) { + r.Error(ACK_ERROR_SYSTEM, "problems setting volume"); return CommandResult::ERROR; } @@ -338,20 +358,15 @@ handle_setvol(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_volume(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_volume(Client &client, Request args, Response &r) { int relative; - if (!check_int(client, &relative, argv[1])) + if (!args.Parse(0, relative, r, -100, 100)) return CommandResult::ERROR; - if (relative < -100 || relative > 100) { - command_error(client, ACK_ERROR_ARG, "Invalid volume value"); - return CommandResult::ERROR; - } - const int old_volume = volume_level_get(client.partition.outputs); if (old_volume < 0) { - command_error(client, ACK_ERROR_SYSTEM, "No mixer"); + r.Error(ACK_ERROR_SYSTEM, "No mixer"); return CommandResult::ERROR; } @@ -363,8 +378,7 @@ handle_volume(Client &client, gcc_unused unsigned argc, char *argv[]) if (new_volume != old_volume && !volume_level_change(client.partition.outputs, new_volume)) { - command_error(client, ACK_ERROR_SYSTEM, - "problems setting volume"); + r.Error(ACK_ERROR_SYSTEM, "problems setting volume"); return CommandResult::ERROR; } @@ -372,27 +386,25 @@ handle_volume(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_stats(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_stats(Client &client, gcc_unused Request args, Response &r) { - stats_print(client); + stats_print(r, client.partition); return CommandResult::OK; } CommandResult -handle_ping(gcc_unused Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_ping(gcc_unused Client &client, gcc_unused Request args, + gcc_unused Response &r) { return CommandResult::OK; } CommandResult -handle_password(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_password(Client &client, Request args, Response &r) { unsigned permission = 0; - - if (getPermissionFromPassword(argv[1], &permission) < 0) { - command_error(client, ACK_ERROR_PASSWORD, "incorrect password"); + if (getPermissionFromPassword(args.front(), &permission) < 0) { + r.Error(ACK_ERROR_PASSWORD, "incorrect password"); return CommandResult::ERROR; } @@ -402,12 +414,11 @@ handle_password(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_config(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_config(Client &client, gcc_unused Request args, Response &r) { if (!client.IsLocal()) { - command_error(client, ACK_ERROR_PERMISSION, - "Command only permitted to local clients"); + r.Error(ACK_ERROR_PERMISSION, + "Command only permitted to local clients"); return CommandResult::ERROR; } @@ -415,7 +426,7 @@ handle_config(Client &client, const Storage *storage = client.GetStorage(); if (storage != nullptr) { const auto path = storage->MapUTF8(""); - client_printf(client, "music_directory: %s\n", path.c_str()); + r.Format("music_directory: %s\n", path.c_str()); } #endif @@ -423,17 +434,14 @@ handle_config(Client &client, } CommandResult -handle_idle(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_idle(Client &client, Request args, Response &r) { unsigned flags = 0; - - for (unsigned i = 1; i < argc; ++i) { - unsigned event = idle_parse_name(argv[i]); + for (const char *i : args) { + unsigned event = idle_parse_name(i); if (event == 0) { - command_error(client, ACK_ERROR_ARG, - "Unrecognized idle event: %s", - argv[i]); + r.FormatError(ACK_ERROR_ARG, + "Unrecognized idle event: %s", i); return CommandResult::ERROR; } diff --git a/src/command/OtherCommands.hxx b/src/command/OtherCommands.hxx index 7cfa35dfb..2a918df97 100644 --- a/src/command/OtherCommands.hxx +++ b/src/command/OtherCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,53 +23,55 @@ #include "CommandResult.hxx" class Client; +class Request; +class Response; CommandResult -handle_urlhandlers(Client &client, unsigned argc, char *argv[]); +handle_urlhandlers(Client &client, Request request, Response &response); CommandResult -handle_decoders(Client &client, unsigned argc, char *argv[]); +handle_decoders(Client &client, Request request, Response &response); CommandResult -handle_tagtypes(Client &client, unsigned argc, char *argv[]); +handle_tagtypes(Client &client, Request request, Response &response); CommandResult -handle_kill(Client &client, unsigned argc, char *argv[]); +handle_kill(Client &client, Request request, Response &response); CommandResult -handle_close(Client &client, unsigned argc, char *argv[]); +handle_close(Client &client, Request request, Response &response); CommandResult -handle_listfiles(Client &client, unsigned argc, char *argv[]); +handle_listfiles(Client &client, Request request, Response &response); CommandResult -handle_lsinfo(Client &client, unsigned argc, char *argv[]); +handle_lsinfo(Client &client, Request request, Response &response); CommandResult -handle_update(Client &client, unsigned argc, char *argv[]); +handle_update(Client &client, Request request, Response &response); CommandResult -handle_rescan(Client &client, unsigned argc, char *argv[]); +handle_rescan(Client &client, Request request, Response &response); CommandResult -handle_setvol(Client &client, unsigned argc, char *argv[]); +handle_setvol(Client &client, Request request, Response &response); CommandResult -handle_volume(Client &client, unsigned argc, char *argv[]); +handle_volume(Client &client, Request request, Response &response); CommandResult -handle_stats(Client &client, unsigned argc, char *argv[]); +handle_stats(Client &client, Request request, Response &response); CommandResult -handle_ping(Client &client, unsigned argc, char *argv[]); +handle_ping(Client &client, Request request, Response &response); CommandResult -handle_password(Client &client, unsigned argc, char *argv[]); +handle_password(Client &client, Request request, Response &response); CommandResult -handle_config(Client &client, unsigned argc, char *argv[]); +handle_config(Client &client, Request request, Response &response); CommandResult -handle_idle(Client &client, unsigned argc, char *argv[]); +handle_idle(Client &client, Request request, Response &response); #endif diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx index c69a0dd65..7bbe5f905 100644 --- a/src/command/OutputCommands.cxx +++ b/src/command/OutputCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,23 +19,24 @@ #include "config.h" #include "OutputCommands.hxx" +#include "Request.hxx" #include "output/OutputPrint.hxx" #include "output/OutputCommand.hxx" -#include "protocol/Result.hxx" -#include "protocol/ArgParser.hxx" #include "client/Client.hxx" +#include "client/Response.hxx" #include "Partition.hxx" +#include "util/ConstBuffer.hxx" CommandResult -handle_enableoutput(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_enableoutput(Client &client, Request args, Response &r) { + assert(args.size == 1); unsigned device; - if (!check_unsigned(client, &device, argv[1])) + if (!args.Parse(0, device, r)) return CommandResult::ERROR; if (!audio_output_enable_index(client.partition.outputs, device)) { - command_error(client, ACK_ERROR_NO_EXIST, - "No such audio output"); + r.Error(ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; } @@ -43,15 +44,15 @@ handle_enableoutput(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_disableoutput(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_disableoutput(Client &client, Request args, Response &r) { + assert(args.size == 1); unsigned device; - if (!check_unsigned(client, &device, argv[1])) + if (!args.Parse(0, device, r)) return CommandResult::ERROR; if (!audio_output_disable_index(client.partition.outputs, device)) { - command_error(client, ACK_ERROR_NO_EXIST, - "No such audio output"); + r.Error(ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; } @@ -59,15 +60,15 @@ handle_disableoutput(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_toggleoutput(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_toggleoutput(Client &client, Request args, Response &r) { + assert(args.size == 1); unsigned device; - if (!check_unsigned(client, &device, argv[1])) + if (!args.Parse(0, device, r)) return CommandResult::ERROR; if (!audio_output_toggle_index(client.partition.outputs, device)) { - command_error(client, ACK_ERROR_NO_EXIST, - "No such audio output"); + r.Error(ACK_ERROR_NO_EXIST, "No such audio output"); return CommandResult::ERROR; } @@ -75,10 +76,10 @@ handle_toggleoutput(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_devices(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_devices(Client &client, gcc_unused Request args, Response &r) { - printAudioDevices(client, client.partition.outputs); + assert(args.IsEmpty()); + printAudioDevices(r, client.partition.outputs); return CommandResult::OK; } diff --git a/src/command/OutputCommands.hxx b/src/command/OutputCommands.hxx index 8d6be0511..3dd81bc23 100644 --- a/src/command/OutputCommands.hxx +++ b/src/command/OutputCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,17 +23,19 @@ #include "CommandResult.hxx" class Client; +class Request; +class Response; CommandResult -handle_enableoutput(Client &client, unsigned argc, char *argv[]); +handle_enableoutput(Client &client, Request request, Response &response); CommandResult -handle_disableoutput(Client &client, unsigned argc, char *argv[]); +handle_disableoutput(Client &client, Request request, Response &response); CommandResult -handle_toggleoutput(Client &client, unsigned argc, char *argv[]); +handle_toggleoutput(Client &client, Request request, Response &response); CommandResult -handle_devices(Client &client, unsigned argc, char *argv[]); +handle_devices(Client &client, Request request, Response &response); #endif diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx index cd7f42289..11cde2e98 100644 --- a/src/command/PlayerCommands.cxx +++ b/src/command/PlayerCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,17 +19,18 @@ #include "config.h" #include "PlayerCommands.hxx" +#include "Request.hxx" #include "CommandError.hxx" #include "queue/Playlist.hxx" #include "PlaylistPrint.hxx" #include "client/Client.hxx" +#include "client/Response.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" +#include "util/ConstBuffer.hxx" #ifdef ENABLE_DATABASE #include "db/update/Service.hxx" @@ -56,51 +57,47 @@ #define COMMAND_STATUS_UPDATING_DB "updating_db" CommandResult -handle_play(Client &client, unsigned argc, char *argv[]) +handle_play(Client &client, Request args, Response &r) { int song = -1; - - if (argc == 2 && !check_int(client, &song, argv[1])) + if (!args.ParseOptional(0, song, r)) return CommandResult::ERROR; + PlaylistResult result = client.partition.PlayPosition(song); - return print_playlist_result(client, result); + return print_playlist_result(r, result); } CommandResult -handle_playid(Client &client, unsigned argc, char *argv[]) +handle_playid(Client &client, Request args, Response &r) { int id = -1; - - if (argc == 2 && !check_int(client, &id, argv[1])) + if (!args.ParseOptional(0, id, r)) return CommandResult::ERROR; PlaylistResult result = client.partition.PlayId(id); - return print_playlist_result(client, result); + return print_playlist_result(r, result); } CommandResult -handle_stop(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_stop(Client &client, gcc_unused Request args, gcc_unused Response &r) { client.partition.Stop(); return CommandResult::OK; } CommandResult -handle_currentsong(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_currentsong(Client &client, gcc_unused Request args, Response &r) { - playlist_print_current(client, client.playlist); + playlist_print_current(r, client.partition, client.playlist); return CommandResult::OK; } CommandResult -handle_pause(Client &client, - unsigned argc, char *argv[]) +handle_pause(Client &client, Request args, Response &r) { - if (argc == 2) { + if (!args.IsEmpty()) { bool pause_flag; - if (!check_bool(client, &pause_flag, argv[1])) + if (!args.Parse(0, pause_flag, r)) return CommandResult::ERROR; client.player_control.SetPause(pause_flag); @@ -111,8 +108,7 @@ handle_pause(Client &client, } CommandResult -handle_status(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_status(Client &client, gcc_unused Request args, Response &r) { const char *state = nullptr; int song; @@ -132,63 +128,61 @@ handle_status(Client &client, } const playlist &playlist = client.playlist; - client_printf(client, - "volume: %i\n" - COMMAND_STATUS_REPEAT ": %i\n" - COMMAND_STATUS_RANDOM ": %i\n" - COMMAND_STATUS_SINGLE ": %i\n" - COMMAND_STATUS_CONSUME ": %i\n" - COMMAND_STATUS_PLAYLIST ": %li\n" - COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" - COMMAND_STATUS_MIXRAMPDB ": %f\n" - COMMAND_STATUS_STATE ": %s\n", - volume_level_get(client.partition.outputs), - playlist.GetRepeat(), - playlist.GetRandom(), - playlist.GetSingle(), - playlist.GetConsume(), - (unsigned long)playlist.GetVersion(), - playlist.GetLength(), - client.player_control.GetMixRampDb(), - state); + r.Format("volume: %i\n" + COMMAND_STATUS_REPEAT ": %i\n" + COMMAND_STATUS_RANDOM ": %i\n" + COMMAND_STATUS_SINGLE ": %i\n" + COMMAND_STATUS_CONSUME ": %i\n" + COMMAND_STATUS_PLAYLIST ": %li\n" + COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n" + COMMAND_STATUS_MIXRAMPDB ": %f\n" + COMMAND_STATUS_STATE ": %s\n", + volume_level_get(client.partition.outputs), + playlist.GetRepeat(), + playlist.GetRandom(), + playlist.GetSingle(), + playlist.GetConsume(), + (unsigned long)playlist.GetVersion(), + playlist.GetLength(), + client.player_control.GetMixRampDb(), + state); if (client.player_control.GetCrossFade() > 0) - client_printf(client, - COMMAND_STATUS_CROSSFADE ": %i\n", - int(client.player_control.GetCrossFade() + 0.5)); + r.Format(COMMAND_STATUS_CROSSFADE ": %i\n", + int(client.player_control.GetCrossFade() + 0.5)); if (client.player_control.GetMixRampDelay() > 0) - client_printf(client, - COMMAND_STATUS_MIXRAMPDELAY ": %f\n", - client.player_control.GetMixRampDelay()); + r.Format(COMMAND_STATUS_MIXRAMPDELAY ": %f\n", + client.player_control.GetMixRampDelay()); song = playlist.GetCurrentPosition(); if (song >= 0) { - client_printf(client, - COMMAND_STATUS_SONG ": %i\n" - COMMAND_STATUS_SONGID ": %u\n", - song, playlist.PositionToId(song)); + r.Format(COMMAND_STATUS_SONG ": %i\n" + COMMAND_STATUS_SONGID ": %u\n", + song, playlist.PositionToId(song)); } if (player_status.state != PlayerState::STOP) { - client_printf(client, - COMMAND_STATUS_TIME ": %i:%i\n" - "elapsed: %1.3f\n" - COMMAND_STATUS_BITRATE ": %u\n", - 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); + r.Format(COMMAND_STATUS_TIME ": %i:%i\n" + "elapsed: %1.3f\n" + COMMAND_STATUS_BITRATE ": %u\n", + 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.total_time.IsNegative()) + r.Format("duration: %1.3f\n", + player_status.total_time.ToDoubleS()); if (player_status.audio_format.IsDefined()) { struct audio_format_string af_string; - client_printf(client, - COMMAND_STATUS_AUDIO ": %s\n", - audio_format_to_string(player_status.audio_format, - &af_string)); + r.Format(COMMAND_STATUS_AUDIO ": %s\n", + audio_format_to_string(player_status.audio_format, + &af_string)); } } @@ -198,32 +192,27 @@ handle_status(Client &client, ? update_service->GetId() : 0; if (updateJobId != 0) { - client_printf(client, - COMMAND_STATUS_UPDATING_DB ": %i\n", - updateJobId); + r.Format(COMMAND_STATUS_UPDATING_DB ": %i\n", + updateJobId); } #endif Error error = client.player_control.LockGetError(); if (error.IsDefined()) - client_printf(client, - COMMAND_STATUS_ERROR ": %s\n", - error.GetMessage()); + r.Format(COMMAND_STATUS_ERROR ": %s\n", + error.GetMessage()); song = playlist.GetNextPosition(); - if (song >= 0) { - client_printf(client, - COMMAND_STATUS_NEXTSONG ": %i\n" - COMMAND_STATUS_NEXTSONGID ": %u\n", - song, playlist.PositionToId(song)); - } + if (song >= 0) + r.Format(COMMAND_STATUS_NEXTSONG ": %i\n" + COMMAND_STATUS_NEXTSONGID ": %u\n", + song, playlist.PositionToId(song)); return CommandResult::OK; } CommandResult -handle_next(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_next(Client &client, gcc_unused Request args, gcc_unused Response &r) { playlist &playlist = client.playlist; @@ -239,18 +228,18 @@ handle_next(Client &client, } CommandResult -handle_previous(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_previous(Client &client, gcc_unused Request args, + gcc_unused Response &r) { client.partition.PlayPrevious(); return CommandResult::OK; } CommandResult -handle_repeat(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_repeat(Client &client, Request args, Response &r) { bool status; - if (!check_bool(client, &status, argv[1])) + if (!args.Parse(0, status, r)) return CommandResult::ERROR; client.partition.SetRepeat(status); @@ -258,10 +247,10 @@ handle_repeat(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_single(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_single(Client &client, Request args, Response &r) { bool status; - if (!check_bool(client, &status, argv[1])) + if (!args.Parse(0, status, r)) return CommandResult::ERROR; client.partition.SetSingle(status); @@ -269,10 +258,10 @@ handle_single(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_consume(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_consume(Client &client, Request args, Response &r) { bool status; - if (!check_bool(client, &status, argv[1])) + if (!args.Parse(0, status, r)) return CommandResult::ERROR; client.partition.SetConsume(status); @@ -280,10 +269,10 @@ handle_consume(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_random(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_random(Client &client, Request args, Response &r) { bool status; - if (!check_bool(client, &status, argv[1])) + if (!args.Parse(0, status, r)) return CommandResult::ERROR; client.partition.SetRandom(status); @@ -292,102 +281,94 @@ handle_random(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_clearerror(gcc_unused Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_clearerror(Client &client, gcc_unused Request args, + gcc_unused Response &r) { client.player_control.ClearError(); return CommandResult::OK; } CommandResult -handle_seek(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_seek(Client &client, Request args, Response &r) { unsigned song; SongTime seek_time; - - if (!check_unsigned(client, &song, argv[1])) - return CommandResult::ERROR; - if (!ParseCommandArg(client, seek_time, argv[2])) + if (!args.Parse(0, song, r) || !args.Parse(1, seek_time, r)) return CommandResult::ERROR; PlaylistResult result = client.partition.SeekSongPosition(song, seek_time); - return print_playlist_result(client, result); + return print_playlist_result(r, result); } CommandResult -handle_seekid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_seekid(Client &client, Request args, Response &r) { unsigned id; SongTime seek_time; - - if (!check_unsigned(client, &id, argv[1])) + if (!args.Parse(0, id, r)) return CommandResult::ERROR; - if (!ParseCommandArg(client, seek_time, argv[2])) + if (!args.Parse(1, seek_time, r)) return CommandResult::ERROR; PlaylistResult result = client.partition.SeekSongId(id, seek_time); - return print_playlist_result(client, result); + return print_playlist_result(r, result); } CommandResult -handle_seekcur(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_seekcur(Client &client, Request args, Response &r) { - const char *p = argv[1]; + const char *p = args.front(); bool relative = *p == '+' || *p == '-'; SignedSongTime seek_time; - if (!ParseCommandArg(client, seek_time, p)) + if (!ParseCommandArg(r, seek_time, p)) return CommandResult::ERROR; PlaylistResult result = client.partition.SeekCurrent(seek_time, relative); - return print_playlist_result(client, result); + return print_playlist_result(r, result); } CommandResult -handle_crossfade(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_crossfade(Client &client, Request args, Response &r) { unsigned xfade_time; - - if (!check_unsigned(client, &xfade_time, argv[1])) + if (!args.Parse(0, xfade_time, r)) return CommandResult::ERROR; - client.player_control.SetCrossFade(xfade_time); + client.player_control.SetCrossFade(xfade_time); return CommandResult::OK; } CommandResult -handle_mixrampdb(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_mixrampdb(Client &client, Request args, Response &r) { float db; - - if (!check_float(client, &db, argv[1])) + if (!args.Parse(0, db, r)) return CommandResult::ERROR; - client.player_control.SetMixRampDb(db); + client.player_control.SetMixRampDb(db); return CommandResult::OK; } CommandResult -handle_mixrampdelay(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_mixrampdelay(Client &client, Request args, Response &r) { float delay_secs; - - if (!check_float(client, &delay_secs, argv[1])) + if (!args.Parse(0, delay_secs, r)) return CommandResult::ERROR; + client.player_control.SetMixRampDelay(delay_secs); return CommandResult::OK; } CommandResult -handle_replay_gain_mode(Client &client, - gcc_unused unsigned argc, char *argv[]) +handle_replay_gain_mode(Client &client, Request args, Response &r) { - if (!replay_gain_set_mode_string(argv[1])) { - command_error(client, ACK_ERROR_ARG, - "Unrecognized replay gain mode"); + if (!replay_gain_set_mode_string(args.front())) { + r.Error(ACK_ERROR_ARG, "Unrecognized replay gain mode"); return CommandResult::ERROR; } @@ -396,10 +377,9 @@ handle_replay_gain_mode(Client &client, } CommandResult -handle_replay_gain_status(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_replay_gain_status(gcc_unused Client &client, gcc_unused Request args, + Response &r) { - client_printf(client, "replay_gain_mode: %s\n", - replay_gain_get_mode_string()); + r.Format("replay_gain_mode: %s\n", replay_gain_get_mode_string()); return CommandResult::OK; } diff --git a/src/command/PlayerCommands.hxx b/src/command/PlayerCommands.hxx index da7083f1e..76ce51ef5 100644 --- a/src/command/PlayerCommands.hxx +++ b/src/command/PlayerCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,68 +23,70 @@ #include "CommandResult.hxx" class Client; +class Request; +class Response; CommandResult -handle_play(Client &client, unsigned argc, char *argv[]); +handle_play(Client &client, Request request, Response &response); CommandResult -handle_playid(Client &client, unsigned argc, char *argv[]); +handle_playid(Client &client, Request request, Response &response); CommandResult -handle_stop(Client &client, unsigned argc, char *argv[]); +handle_stop(Client &client, Request request, Response &response); CommandResult -handle_currentsong(Client &client, unsigned argc, char *argv[]); +handle_currentsong(Client &client, Request request, Response &response); CommandResult -handle_pause(Client &client, unsigned argc, char *argv[]); +handle_pause(Client &client, Request request, Response &response); CommandResult -handle_status(Client &client, unsigned argc, char *argv[]); +handle_status(Client &client, Request request, Response &response); CommandResult -handle_next(Client &client, unsigned argc, char *argv[]); +handle_next(Client &client, Request request, Response &response); CommandResult -handle_previous(Client &client, unsigned argc, char *avg[]); +handle_previous(Client &client, Request request, Response &response); CommandResult -handle_repeat(Client &client, unsigned argc, char *argv[]); +handle_repeat(Client &client, Request request, Response &response); CommandResult -handle_single(Client &client, unsigned argc, char *argv[]); +handle_single(Client &client, Request request, Response &response); CommandResult -handle_consume(Client &client, unsigned argc, char *argv[]); +handle_consume(Client &client, Request request, Response &response); CommandResult -handle_random(Client &client, unsigned argc, char *argv[]); +handle_random(Client &client, Request request, Response &response); CommandResult -handle_clearerror(Client &client, unsigned argc, char *argv[]); +handle_clearerror(Client &client, Request request, Response &response); CommandResult -handle_seek(Client &client, unsigned argc, char *argv[]); +handle_seek(Client &client, Request request, Response &response); CommandResult -handle_seekid(Client &client, unsigned argc, char *argv[]); +handle_seekid(Client &client, Request request, Response &response); CommandResult -handle_seekcur(Client &client, unsigned argc, char *argv[]); +handle_seekcur(Client &client, Request request, Response &response); CommandResult -handle_crossfade(Client &client, unsigned argc, char *argv[]); +handle_crossfade(Client &client, Request request, Response &response); CommandResult -handle_mixrampdb(Client &client, unsigned argc, char *argv[]); +handle_mixrampdb(Client &client, Request request, Response &response); CommandResult -handle_mixrampdelay(Client &client, unsigned argc, char *argv[]); +handle_mixrampdelay(Client &client, Request request, Response &response); CommandResult -handle_replay_gain_mode(Client &client, unsigned argc, char *argv[]); +handle_replay_gain_mode(Client &client, Request request, Response &response); CommandResult -handle_replay_gain_status(Client &client, unsigned argc, char *argv[]); +handle_replay_gain_status(Client &client, Request request, Response &response); #endif diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx index c2b18064c..625e82055 100644 --- a/src/command/PlaylistCommands.cxx +++ b/src/command/PlaylistCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" #include "PlaylistCommands.hxx" +#include "Request.hxx" #include "db/DatabasePlaylist.hxx" #include "CommandError.hxx" #include "PlaylistPrint.hxx" @@ -32,143 +33,157 @@ #include "queue/Playlist.hxx" #include "TimePrint.hxx" #include "client/Client.hxx" -#include "protocol/ArgParser.hxx" -#include "protocol/Result.hxx" +#include "client/Response.hxx" #include "ls.hxx" +#include "Mapper.hxx" +#include "fs/AllocatedPath.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" +#include "util/ConstBuffer.hxx" + +bool +playlist_commands_available() +{ + return !map_spl_path().IsNull(); +} static void -print_spl_list(Client &client, const PlaylistVector &list) +print_spl_list(Response &r, const PlaylistVector &list) { for (const auto &i : list) { - client_printf(client, "playlist: %s\n", i.name.c_str()); + r.Format("playlist: %s\n", i.name.c_str()); if (i.mtime > 0) - time_print(client, "Last-Modified", i.mtime); + time_print(r, "Last-Modified", i.mtime); } } CommandResult -handle_save(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_save(Client &client, Request args, Response &r) { - PlaylistResult result = spl_save_playlist(argv[1], client.playlist); - return print_playlist_result(client, result); + Error error; + return spl_save_playlist(args.front(), client.playlist, error) + ? CommandResult::OK + : print_error(r, error); } CommandResult -handle_load(Client &client, unsigned argc, char *argv[]) +handle_load(Client &client, Request args, Response &r) { - unsigned start_index, end_index; - - if (argc < 3) { - start_index = 0; - end_index = unsigned(-1); - } else if (!check_range(client, &start_index, &end_index, argv[2])) + RangeArg range = RangeArg::All(); + if (!args.ParseOptional(1, range, r)) return CommandResult::ERROR; const ScopeBulkEdit bulk_edit(client.partition); Error error; const SongLoader loader(client); - if (!playlist_open_into_queue(argv[1], - start_index, end_index, + if (!playlist_open_into_queue(args.front(), + range.start, range.end, client.playlist, client.player_control, loader, error)) - return print_error(client, error); + return print_error(r, error); return CommandResult::OK; } CommandResult -handle_listplaylist(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_listplaylist(Client &client, Request args, Response &r) { - if (playlist_file_print(client, argv[1], false)) + const char *const name = args.front(); + + if (playlist_file_print(r, client.partition, SongLoader(client), + name, false)) return CommandResult::OK; Error error; - return spl_print(client, argv[1], false, error) + return spl_print(r, client.partition, name, false, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); } CommandResult -handle_listplaylistinfo(Client &client, - gcc_unused unsigned argc, char *argv[]) +handle_listplaylistinfo(Client &client, Request args, Response &r) { - if (playlist_file_print(client, argv[1], true)) + const char *const name = args.front(); + + if (playlist_file_print(r, client.partition, SongLoader(client), + name, true)) return CommandResult::OK; Error error; - return spl_print(client, argv[1], true, error) + return spl_print(r, client.partition, name, true, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); } CommandResult -handle_rm(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_rm(gcc_unused Client &client, Request args, Response &r) { + const char *const name = args.front(); + Error error; - return spl_delete(argv[1], error) + return spl_delete(name, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); } CommandResult -handle_rename(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_rename(gcc_unused Client &client, Request args, Response &r) { + const char *const old_name = args[0]; + const char *const new_name = args[1]; + Error error; - return spl_rename(argv[1], argv[2], error) + return spl_rename(old_name, new_name, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); } CommandResult -handle_playlistdelete(Client &client, - gcc_unused unsigned argc, char *argv[]) { - char *playlist = argv[1]; +handle_playlistdelete(gcc_unused Client &client, Request args, Response &r) +{ + const char *const name = args[0]; unsigned from; - - if (!check_unsigned(client, &from, argv[2])) + if (!args.Parse(1, from, r)) return CommandResult::ERROR; Error error; - return spl_remove_index(playlist, from, error) + return spl_remove_index(name, from, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); } CommandResult -handle_playlistmove(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_playlistmove(gcc_unused Client &client, Request args, Response &r) { - char *playlist = argv[1]; + const char *const name = args.front(); unsigned from, to; - - if (!check_unsigned(client, &from, argv[2])) - return CommandResult::ERROR; - if (!check_unsigned(client, &to, argv[3])) + if (!args.Parse(1, from, r) || !args.Parse(2, to, r)) return CommandResult::ERROR; Error error; - return spl_move_index(playlist, from, to, error) + return spl_move_index(name, from, to, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); } CommandResult -handle_playlistclear(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_playlistclear(gcc_unused Client &client, Request args, Response &r) { + const char *const name = args.front(); + Error error; - return spl_clear(argv[1], error) + return spl_clear(name, error) ? CommandResult::OK - : print_error(client, error); + : print_error(r, error); } CommandResult -handle_playlistadd(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_playlistadd(Client &client, Request args, Response &r) { - char *playlist = argv[1]; - char *uri = argv[2]; + const char *const playlist = args[0]; + const char *const uri = args[1]; bool success; Error error; @@ -179,7 +194,7 @@ handle_playlistadd(Client &client, gcc_unused unsigned argc, char *argv[]) #ifdef ENABLE_DATABASE const Database *db = client.GetDatabase(error); if (db == nullptr) - return print_error(client, error); + return print_error(r, error); success = search_add_to_playlist(*db, *client.GetStorage(), uri, playlist, nullptr, @@ -190,23 +205,22 @@ handle_playlistadd(Client &client, gcc_unused unsigned argc, char *argv[]) } if (!success && !error.IsDefined()) { - command_error(client, ACK_ERROR_NO_EXIST, - "directory or file not found"); + r.Error(ACK_ERROR_NO_EXIST, "directory or file not found"); return CommandResult::ERROR; } - return success ? CommandResult::OK : print_error(client, error); + return success ? CommandResult::OK : print_error(r, error); } CommandResult -handle_listplaylists(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_listplaylists(gcc_unused Client &client, gcc_unused Request args, + Response &r) { Error error; const auto list = ListPlaylistFiles(error); if (list.empty() && error.IsDefined()) - return print_error(client, error); + return print_error(r, error); - print_spl_list(client, list); + print_spl_list(r, list); return CommandResult::OK; } diff --git a/src/command/PlaylistCommands.hxx b/src/command/PlaylistCommands.hxx index fba4e1318..9f263df62 100644 --- a/src/command/PlaylistCommands.hxx +++ b/src/command/PlaylistCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,40 +21,47 @@ #define MPD_PLAYLIST_COMMANDS_HXX #include "CommandResult.hxx" +#include "Compiler.h" class Client; +class Request; +class Response; + +gcc_const +bool +playlist_commands_available(); CommandResult -handle_save(Client &client, unsigned argc, char *argv[]); +handle_save(Client &client, Request request, Response &response); CommandResult -handle_load(Client &client, unsigned argc, char *argv[]); +handle_load(Client &client, Request request, Response &response); CommandResult -handle_listplaylist(Client &client, unsigned argc, char *argv[]); +handle_listplaylist(Client &client, Request request, Response &response); CommandResult -handle_listplaylistinfo(Client &client, unsigned argc, char *argv[]); +handle_listplaylistinfo(Client &client, Request request, Response &response); CommandResult -handle_rm(Client &client, unsigned argc, char *argv[]); +handle_rm(Client &client, Request request, Response &response); CommandResult -handle_rename(Client &client, unsigned argc, char *argv[]); +handle_rename(Client &client, Request request, Response &response); CommandResult -handle_playlistdelete(Client &client, unsigned argc, char *argv[]); +handle_playlistdelete(Client &client, Request request, Response &response); CommandResult -handle_playlistmove(Client &client, unsigned argc, char *argv[]); +handle_playlistmove(Client &client, Request request, Response &response); CommandResult -handle_playlistclear(Client &client, unsigned argc, char *argv[]); +handle_playlistclear(Client &client, Request request, Response &response); CommandResult -handle_playlistadd(Client &client, unsigned argc, char *argv[]); +handle_playlistadd(Client &client, Request request, Response &response); CommandResult -handle_listplaylists(Client &client, unsigned argc, char *argv[]); +handle_listplaylists(Client &client, Request request, Response &response); #endif diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx index d0b789eb1..7751aa26d 100644 --- a/src/command/QueueCommands.cxx +++ b/src/command/QueueCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,19 +19,20 @@ #include "config.h" #include "QueueCommands.hxx" +#include "Request.hxx" #include "CommandError.hxx" #include "db/DatabaseQueue.hxx" #include "db/Selection.hxx" #include "SongFilter.hxx" #include "SongLoader.hxx" +#include "DetachedSong.hxx" +#include "LocateUri.hxx" #include "queue/Playlist.hxx" #include "PlaylistPrint.hxx" #include "client/Client.hxx" +#include "client/Response.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" @@ -42,26 +43,48 @@ #include <string.h> -static const char * -translate_uri(Client &client, const char *uri) +static CommandResult +AddUri(Client &client, const LocatedUri &uri, Response &r) { - 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; - } + Error error; + DetachedSong *song = SongLoader(client).LoadSong(uri, error); + if (song == nullptr) + return print_error(r, error); + + auto &partition = client.partition; + unsigned id = partition.playlist.AppendSong(partition.pc, + std::move(*song), error); + delete song; + if (id == 0) + return print_error(r, error); + + return CommandResult::OK; +} + +static CommandResult +AddDatabaseSelection(Client &client, const char *uri, Response &r) +{ +#ifdef ENABLE_DATABASE + const ScopeBulkEdit bulk_edit(client.partition); + + const DatabaseSelection selection(uri, true); + Error error; + return AddFromDatabase(client.partition, selection, error) + ? CommandResult::OK + : print_error(r, error); +#else + (void)client; + (void)uri; - return uri; + r.Error(ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +#endif } CommandResult -handle_add(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_add(Client &client, Request args, Response &r) { - const char *uri = argv[1]; + const char *uri = args.front(); if (memcmp(uri, "/", 2) == 0) /* this URI is malformed, but some clients are buggy and use "add /" to add the whole database, which @@ -70,61 +93,54 @@ handle_add(Client &client, gcc_unused unsigned argc, char *argv[]) here */ uri = ""; - uri = translate_uri(client, uri); - if (uri == nullptr) - return CommandResult::ERROR; - - if (uri_has_scheme(uri) || PathTraitsUTF8::IsAbsolute(uri)) { - const SongLoader loader(client); - Error error; - unsigned id = client.partition.AppendURI(loader, uri, error); - if (id == 0) - return print_error(client, error); - - return CommandResult::OK; - } - -#ifdef ENABLE_DATABASE - const ScopeBulkEdit bulk_edit(client.partition); - - const DatabaseSelection selection(uri, true); Error error; - 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; + const auto located_uri = LocateUri(uri, &client, +#ifdef ENABLE_DATABASE + nullptr, #endif + error); + switch (located_uri.type) { + case LocatedUri::Type::UNKNOWN: + return print_error(r, error); + + case LocatedUri::Type::ABSOLUTE: + case LocatedUri::Type::PATH: + return AddUri(client, located_uri, r); + + case LocatedUri::Type::RELATIVE: + return AddDatabaseSelection(client, located_uri.canonical_uri, + r); + } + + gcc_unreachable(); } CommandResult -handle_addid(Client &client, unsigned argc, char *argv[]) +handle_addid(Client &client, Request args, Response &r) { - const char *const uri = translate_uri(client, argv[1]); - if (uri == nullptr) - return CommandResult::ERROR; + const char *const uri = args.front(); const SongLoader loader(client); Error error; unsigned added_id = client.partition.AppendURI(loader, uri, error); if (added_id == 0) - return print_error(client, error); + return print_error(r, error); - if (argc == 3) { + if (args.size == 2) { unsigned to; - if (!check_unsigned(client, &to, argv[2])) + if (!args.Parse(1, to, r)) return CommandResult::ERROR; + PlaylistResult result = client.partition.MoveId(added_id, to); if (result != PlaylistResult::SUCCESS) { CommandResult ret = - print_playlist_result(client, result); + print_playlist_result(r, result); client.partition.DeleteId(added_id); return ret; } } - client_printf(client, "Id: %u\n", added_id); + r.Format("Id: %u\n", added_id); return CommandResult::OK; } @@ -160,15 +176,15 @@ parse_time_range(const char *p, SongTime &start_r, SongTime &end_r) } CommandResult -handle_rangeid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_rangeid(Client &client, Request args, Response &r) { unsigned id; - if (!check_unsigned(client, &id, argv[1])) + if (!args.Parse(0, id, r)) return CommandResult::ERROR; SongTime start, end; - if (!parse_time_range(argv[2], start, end)) { - command_error(client, ACK_ERROR_ARG, "Bad range"); + if (!parse_time_range(args[1], start, end)) { + r.Error(ACK_ERROR_ARG, "Bad range"); return CommandResult::ERROR; } @@ -176,118 +192,110 @@ handle_rangeid(Client &client, gcc_unused unsigned argc, char *argv[]) if (!client.partition.playlist.SetSongIdRange(client.partition.pc, id, start, end, error)) - return print_error(client, error); + return print_error(r, error); return CommandResult::OK; } CommandResult -handle_delete(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_delete(Client &client, Request args, Response &r) { - unsigned start, end; - - if (!check_range(client, &start, &end, argv[1])) + RangeArg range; + if (!args.Parse(0, range, r)) return CommandResult::ERROR; - PlaylistResult result = client.partition.DeleteRange(start, end); - return print_playlist_result(client, result); + auto result = client.partition.DeleteRange(range.start, range.end); + return print_playlist_result(r, result); } CommandResult -handle_deleteid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_deleteid(Client &client, Request args, Response &r) { unsigned id; - - if (!check_unsigned(client, &id, argv[1])) + if (!args.Parse(0, id, r)) return CommandResult::ERROR; PlaylistResult result = client.partition.DeleteId(id); - return print_playlist_result(client, result); + return print_playlist_result(r, result); } CommandResult -handle_playlist(Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_playlist(Client &client, gcc_unused Request args, Response &r) { - playlist_print_uris(client, client.playlist); + playlist_print_uris(r, client.partition, client.playlist); return CommandResult::OK; } CommandResult -handle_shuffle(gcc_unused Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_shuffle(gcc_unused Client &client, Request args, Response &r) { - unsigned start = 0, end = client.playlist.queue.GetLength(); - if (argc == 2 && !check_range(client, &start, &end, argv[1])) + RangeArg range = RangeArg::All(); + if (!args.ParseOptional(0, range, r)) return CommandResult::ERROR; - client.partition.Shuffle(start, end); + client.partition.Shuffle(range.start, range.end); return CommandResult::OK; } CommandResult -handle_clear(gcc_unused Client &client, - gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_clear(Client &client, gcc_unused Request args, gcc_unused Response &r) { client.partition.ClearQueue(); return CommandResult::OK; } CommandResult -handle_plchanges(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_plchanges(Client &client, Request args, Response &r) { uint32_t version; - - if (!check_uint32(client, &version, argv[1])) + if (!ParseCommandArg32(r, version, args.front())) return CommandResult::ERROR; - playlist_print_changes_info(client, client.playlist, version); + playlist_print_changes_info(r, client.partition, + client.playlist, version); return CommandResult::OK; } CommandResult -handle_plchangesposid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_plchangesposid(Client &client, Request args, Response &r) { uint32_t version; - - if (!check_uint32(client, &version, argv[1])) + if (!ParseCommandArg32(r, version, args.front())) return CommandResult::ERROR; - playlist_print_changes_position(client, client.playlist, version); + playlist_print_changes_position(r, client.playlist, version); return CommandResult::OK; } CommandResult -handle_playlistinfo(Client &client, unsigned argc, char *argv[]) +handle_playlistinfo(Client &client, Request args, Response &r) { - unsigned start = 0, end = std::numeric_limits<unsigned>::max(); - bool ret; - - if (argc == 2 && !check_range(client, &start, &end, argv[1])) + RangeArg range = RangeArg::All(); + if (!args.ParseOptional(0, range, r)) return CommandResult::ERROR; - ret = playlist_print_info(client, client.playlist, start, end); - if (!ret) - return print_playlist_result(client, + if (!playlist_print_info(r, client.partition, client.playlist, + range.start, range.end)) + return print_playlist_result(r, PlaylistResult::BAD_RANGE); return CommandResult::OK; } CommandResult -handle_playlistid(Client &client, unsigned argc, char *argv[]) +handle_playlistid(Client &client, Request args, Response &r) { - if (argc >= 2) { + if (!args.IsEmpty()) { unsigned id; - if (!check_unsigned(client, &id, argv[1])) + if (!args.Parse(0, id, r)) return CommandResult::ERROR; - bool ret = playlist_print_id(client, client.playlist, id); + bool ret = playlist_print_id(r, client.partition, + client.playlist, id); if (!ret) - return print_playlist_result(client, - PlaylistResult::NO_SUCH_SONG); + return print_playlist_result(r, PlaylistResult::NO_SUCH_SONG); } else { - playlist_print_info(client, client.playlist, + playlist_print_info(r, client.partition, client.playlist, 0, std::numeric_limits<unsigned>::max()); } @@ -295,147 +303,120 @@ handle_playlistid(Client &client, unsigned argc, char *argv[]) } static CommandResult -handle_playlist_match(Client &client, unsigned argc, char *argv[], +handle_playlist_match(Client &client, Request args, Response &r, bool fold_case) { - ConstBuffer<const char *> args(argv + 1, argc - 1); - SongFilter filter; if (!filter.Parse(args, fold_case)) { - command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + r.Error(ACK_ERROR_ARG, "incorrect arguments"); return CommandResult::ERROR; } - playlist_print_find(client, client.playlist, filter); + playlist_print_find(r, client.partition, client.playlist, filter); return CommandResult::OK; } CommandResult -handle_playlistfind(Client &client, unsigned argc, char *argv[]) +handle_playlistfind(Client &client, Request args, Response &r) { - return handle_playlist_match(client, argc, argv, false); + return handle_playlist_match(client, args, r, false); } CommandResult -handle_playlistsearch(Client &client, unsigned argc, char *argv[]) +handle_playlistsearch(Client &client, Request args, Response &r) { - return handle_playlist_match(client, argc, argv, true); + return handle_playlist_match(client, args, r, true); } CommandResult -handle_prio(Client &client, unsigned argc, char *argv[]) +handle_prio(Client &client, Request args, Response &r) { unsigned priority; - - if (!check_unsigned(client, &priority, argv[1])) + if (!args.ParseShift(0, priority, r, 0xff)) return CommandResult::ERROR; - if (priority > 0xff) { - command_error(client, ACK_ERROR_ARG, - "Priority out of range: %s", argv[1]); - return CommandResult::ERROR; - } - - for (unsigned i = 2; i < argc; ++i) { - unsigned start_position, end_position; - if (!check_range(client, &start_position, &end_position, - argv[i])) + for (const char *i : args) { + RangeArg range; + if (!ParseCommandArg(r, range, i)) return CommandResult::ERROR; PlaylistResult result = - client.partition.SetPriorityRange(start_position, - end_position, - priority); + client.partition.SetPriorityRange(range.start, + range.end, + priority); if (result != PlaylistResult::SUCCESS) - return print_playlist_result(client, result); + return print_playlist_result(r, result); } return CommandResult::OK; } CommandResult -handle_prioid(Client &client, unsigned argc, char *argv[]) +handle_prioid(Client &client, Request args, Response &r) { unsigned priority; - - if (!check_unsigned(client, &priority, argv[1])) + if (!args.ParseShift(0, priority, r, 0xff)) return CommandResult::ERROR; - if (priority > 0xff) { - command_error(client, ACK_ERROR_ARG, - "Priority out of range: %s", argv[1]); - return CommandResult::ERROR; - } - - for (unsigned i = 2; i < argc; ++i) { + for (const char *i : args) { unsigned song_id; - if (!check_unsigned(client, &song_id, argv[i])) + if (!ParseCommandArg(r, song_id, i)) return CommandResult::ERROR; PlaylistResult result = client.partition.SetPriorityId(song_id, priority); if (result != PlaylistResult::SUCCESS) - return print_playlist_result(client, result); + return print_playlist_result(r, result); } return CommandResult::OK; } CommandResult -handle_move(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_move(Client &client, Request args, Response &r) { - unsigned start, end; + RangeArg range; int to; - if (!check_range(client, &start, &end, argv[1])) - return CommandResult::ERROR; - if (!check_int(client, &to, argv[2])) + if (!args.Parse(0, range, r) || !args.Parse(1, to, r)) return CommandResult::ERROR; PlaylistResult result = - client.partition.MoveRange(start, end, to); - return print_playlist_result(client, result); + client.partition.MoveRange(range.start, range.end, to); + return print_playlist_result(r, result); } CommandResult -handle_moveid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_moveid(Client &client, Request args, Response &r) { unsigned id; int to; - - if (!check_unsigned(client, &id, argv[1])) - return CommandResult::ERROR; - if (!check_int(client, &to, argv[2])) + if (!args.Parse(0, id, r) || !args.Parse(1, to, r)) return CommandResult::ERROR; + PlaylistResult result = client.partition.MoveId(id, to); - return print_playlist_result(client, result); + return print_playlist_result(r, result); } CommandResult -handle_swap(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_swap(Client &client, Request args, Response &r) { unsigned song1, song2; - - if (!check_unsigned(client, &song1, argv[1])) - return CommandResult::ERROR; - if (!check_unsigned(client, &song2, argv[2])) + if (!args.Parse(0, song1, r) || !args.Parse(1, song2, r)) return CommandResult::ERROR; PlaylistResult result = client.partition.SwapPositions(song1, song2); - return print_playlist_result(client, result); + return print_playlist_result(r, result); } CommandResult -handle_swapid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_swapid(Client &client, Request args, Response &r) { unsigned id1, id2; - - if (!check_unsigned(client, &id1, argv[1])) - return CommandResult::ERROR; - if (!check_unsigned(client, &id2, argv[2])) + if (!args.Parse(0, id1, r) || !args.Parse(1, id2, r)) return CommandResult::ERROR; PlaylistResult result = client.partition.SwapIds(id1, id2); - return print_playlist_result(client, result); + return print_playlist_result(r, result); } diff --git a/src/command/QueueCommands.hxx b/src/command/QueueCommands.hxx index f98f7bad2..49499d8ea 100644 --- a/src/command/QueueCommands.hxx +++ b/src/command/QueueCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,65 +23,67 @@ #include "CommandResult.hxx" class Client; +class Request; +class Response; CommandResult -handle_add(Client &client, unsigned argc, char *argv[]); +handle_add(Client &client, Request request, Response &response); CommandResult -handle_addid(Client &client, unsigned argc, char *argv[]); +handle_addid(Client &client, Request request, Response &response); CommandResult -handle_rangeid(Client &client, unsigned argc, char *argv[]); +handle_rangeid(Client &client, Request request, Response &response); CommandResult -handle_delete(Client &client, unsigned argc, char *argv[]); +handle_delete(Client &client, Request request, Response &response); CommandResult -handle_deleteid(Client &client, unsigned argc, char *argv[]); +handle_deleteid(Client &client, Request request, Response &response); CommandResult -handle_playlist(Client &client, unsigned argc, char *argv[]); +handle_playlist(Client &client, Request request, Response &response); CommandResult -handle_shuffle(Client &client, unsigned argc, char *argv[]); +handle_shuffle(Client &client, Request request, Response &response); CommandResult -handle_clear(Client &client, unsigned argc, char *argv[]); +handle_clear(Client &client, Request request, Response &response); CommandResult -handle_plchanges(Client &client, unsigned argc, char *argv[]); +handle_plchanges(Client &client, Request request, Response &response); CommandResult -handle_plchangesposid(Client &client, unsigned argc, char *argv[]); +handle_plchangesposid(Client &client, Request request, Response &response); CommandResult -handle_playlistinfo(Client &client, unsigned argc, char *argv[]); +handle_playlistinfo(Client &client, Request request, Response &response); CommandResult -handle_playlistid(Client &client, unsigned argc, char *argv[]); +handle_playlistid(Client &client, Request request, Response &response); CommandResult -handle_playlistfind(Client &client, unsigned argc, char *argv[]); +handle_playlistfind(Client &client, Request request, Response &response); CommandResult -handle_playlistsearch(Client &client, unsigned argc, char *argv[]); +handle_playlistsearch(Client &client, Request request, Response &response); CommandResult -handle_prio(Client &client, unsigned argc, char *argv[]); +handle_prio(Client &client, Request request, Response &response); CommandResult -handle_prioid(Client &client, unsigned argc, char *argv[]); +handle_prioid(Client &client, Request request, Response &response); CommandResult -handle_move(Client &client, unsigned argc, char *argv[]); +handle_move(Client &client, Request request, Response &response); CommandResult -handle_moveid(Client &client, unsigned argc, char *argv[]); +handle_moveid(Client &client, Request request, Response &response); CommandResult -handle_swap(Client &client, unsigned argc, char *argv[]); +handle_swap(Client &client, Request request, Response &response); CommandResult -handle_swapid(Client &client, unsigned argc, char *argv[]); +handle_swapid(Client &client, Request request, Response &response); #endif diff --git a/src/command/Request.hxx b/src/command/Request.hxx new file mode 100644 index 000000000..1616b7045 --- /dev/null +++ b/src/command/Request.hxx @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2003-2015 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_REQUEST_HXX +#define MPD_REQUEST_HXX + +#include "check.h" +#include "protocol/ArgParser.hxx" +#include "util/ConstBuffer.hxx" + +#include <utility> + +#include <assert.h> + +class Response; + +class Request : public ConstBuffer<const char *> { + typedef ConstBuffer<const char *> Base; + +public: + constexpr Request(const char *const*argv, size_type n) + :Base(argv, n) {} + + constexpr const char *GetOptional(unsigned idx, + const char *default_value=nullptr) const { + return idx < size + ? data[idx] + : default_value; + } + + template<typename T, typename... Args> + bool Parse(unsigned idx, T &value_r, Response &r, + Args&&... args) { + assert(idx < size); + + return ParseCommandArg(r, value_r, data[idx], + std::forward<Args>(args)...); + } + + template<typename T, typename... Args> + bool ParseOptional(unsigned idx, T &value_r, Response &r, + Args&&... args) { + return idx >= size || + Parse(idx, value_r, r, + std::forward<Args>(args)...); + } + + template<typename T, typename... Args> + bool ParseShift(unsigned idx, T &value_r, Response &r, + Args&&... args) { + bool success = Parse(idx, value_r, r, + std::forward<Args>(args)...); + shift(); + return success; + } +}; + +#endif diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx index 37506d51b..d5d7ab1f8 100644 --- a/src/command/StickerCommands.cxx +++ b/src/command/StickerCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,6 +19,7 @@ #include "config.h" #include "StickerCommands.hxx" +#include "Request.hxx" #include "SongPrint.hxx" #include "db/Interface.hxx" #include "db/DatabaseGlue.hxx" @@ -26,16 +27,17 @@ #include "sticker/StickerPrint.hxx" #include "sticker/StickerDatabase.hxx" #include "CommandError.hxx" -#include "protocol/Result.hxx" #include "client/Client.hxx" +#include "client/Response.hxx" #include "Partition.hxx" #include "Instance.hxx" #include "util/Error.hxx" - -#include <string.h> +#include "util/ConstBuffer.hxx" +#include "util/StringAPI.hxx" struct sticker_song_find_data { - Client &client; + Response &r; + Partition &partition; const char *name; }; @@ -46,125 +48,161 @@ sticker_song_find_print_cb(const LightSong &song, const char *value, struct sticker_song_find_data *data = (struct sticker_song_find_data *)user_data; - song_print_uri(data->client, song); - sticker_print_value(data->client, data->name, value); + song_print_uri(data->r, data->partition, song); + sticker_print_value(data->r, data->name, value); } static CommandResult -handle_sticker_song(Client &client, unsigned argc, char *argv[]) +handle_sticker_song(Response &r, Partition &partition, Request args) { Error error; - const Database *db = client.GetDatabase(error); + const Database *db = partition.GetDatabase(error); if (db == nullptr) - return print_error(client, error); + return print_error(r, error); + + const char *const cmd = args.front(); /* get song song_id key */ - if (argc == 5 && strcmp(argv[1], "get") == 0) { - const LightSong *song = db->GetSong(argv[3], error); + if (args.size == 4 && StringIsEqual(cmd, "get")) { + const LightSong *song = db->GetSong(args[2], error); if (song == nullptr) - return print_error(client, error); + return print_error(r, error); - const auto value = sticker_song_get_value(*song, argv[4]); + const auto value = sticker_song_get_value(*song, args[3], + error); db->ReturnSong(song); if (value.empty()) { - command_error(client, ACK_ERROR_NO_EXIST, - "no such sticker"); + if (error.IsDefined()) + return print_error(r, error); + + r.Error(ACK_ERROR_NO_EXIST, "no such sticker"); return CommandResult::ERROR; } - sticker_print_value(client, argv[4], value.c_str()); + sticker_print_value(r, args[3], value.c_str()); return CommandResult::OK; /* list song song_id */ - } else if (argc == 4 && strcmp(argv[1], "list") == 0) { - const LightSong *song = db->GetSong(argv[3], error); + } else if (args.size == 3 && StringIsEqual(cmd, "list")) { + const LightSong *song = db->GetSong(args[2], error); if (song == nullptr) - return print_error(client, error); + return print_error(r, error); - sticker *sticker = sticker_song_get(*song); + Sticker *sticker = sticker_song_get(*song, error); db->ReturnSong(song); if (sticker) { - sticker_print(client, *sticker); + sticker_print(r, *sticker); sticker_free(sticker); - } + } else if (error.IsDefined()) + return print_error(r, error); return CommandResult::OK; /* set song song_id id key */ - } else if (argc == 6 && strcmp(argv[1], "set") == 0) { - const LightSong *song = db->GetSong(argv[3], error); + } else if (args.size == 5 && StringIsEqual(cmd, "set")) { + const LightSong *song = db->GetSong(args[2], error); if (song == nullptr) - return print_error(client, error); + return print_error(r, error); - bool ret = sticker_song_set_value(*song, argv[4], argv[5]); + bool ret = sticker_song_set_value(*song, args[3], args[4], + error); db->ReturnSong(song); if (!ret) { - command_error(client, ACK_ERROR_SYSTEM, - "failed to set sticker value"); + if (error.IsDefined()) + return print_error(r, error); + + r.Error(ACK_ERROR_SYSTEM, + "failed to set sticker value"); return CommandResult::ERROR; } return CommandResult::OK; /* delete song song_id [key] */ - } else if ((argc == 4 || argc == 5) && - strcmp(argv[1], "delete") == 0) { - const LightSong *song = db->GetSong(argv[3], error); + } else if ((args.size == 3 || args.size == 4) && + StringIsEqual(cmd, "delete")) { + const LightSong *song = db->GetSong(args[2], error); if (song == nullptr) - return print_error(client, error); + return print_error(r, error); - bool ret = argc == 4 - ? sticker_song_delete(*song) - : sticker_song_delete_value(*song, argv[4]); + bool ret = args.size == 3 + ? sticker_song_delete(*song, error) + : sticker_song_delete_value(*song, args[3], error); db->ReturnSong(song); if (!ret) { - command_error(client, ACK_ERROR_SYSTEM, - "no such sticker"); + if (error.IsDefined()) + return print_error(r, error); + + r.Error(ACK_ERROR_SYSTEM, "no such sticker"); return CommandResult::ERROR; } return CommandResult::OK; /* find song dir key */ - } else if (argc == 5 && strcmp(argv[1], "find") == 0) { + } else if ((args.size == 4 || args.size == 6) && + StringIsEqual(cmd, "find")) { /* "sticker find song a/directory name" */ - const char *const base_uri = argv[3]; + const char *const base_uri = args[2]; + + StickerOperator op = StickerOperator::EXISTS; + const char *value = nullptr; + + if (args.size == 6) { + /* match the value */ + + const char *op_s = args[4]; + value = args[5]; + + if (StringIsEqual(op_s, "=")) + op = StickerOperator::EQUALS; + else if (StringIsEqual(op_s, "<")) + op = StickerOperator::LESS_THAN; + else if (StringIsEqual(op_s, ">")) + op = StickerOperator::GREATER_THAN; + else { + r.Error(ACK_ERROR_ARG, "bad operator"); + return CommandResult::ERROR; + } + } - bool success; struct sticker_song_find_data data = { - client, - argv[4], + r, + partition, + args[3], }; - success = sticker_song_find(*db, base_uri, data.name, - sticker_song_find_print_cb, &data); - if (!success) { - command_error(client, ACK_ERROR_SYSTEM, - "failed to set search sticker database"); + if (!sticker_song_find(*db, base_uri, data.name, + op, value, + sticker_song_find_print_cb, &data, + error)) { + if (error.IsDefined()) + return print_error(r, error); + + r.Error(ACK_ERROR_SYSTEM, + "failed to set search sticker database"); return CommandResult::ERROR; } return CommandResult::OK; } else { - command_error(client, ACK_ERROR_ARG, "bad request"); + r.Error(ACK_ERROR_ARG, "bad request"); return CommandResult::ERROR; } } CommandResult -handle_sticker(Client &client, unsigned argc, char *argv[]) +handle_sticker(Client &client, Request args, Response &r) { - assert(argc >= 4); + assert(args.size >= 3); if (!sticker_enabled()) { - command_error(client, ACK_ERROR_UNKNOWN, - "sticker database is disabled"); + r.Error(ACK_ERROR_UNKNOWN, "sticker database is disabled"); return CommandResult::ERROR; } - if (strcmp(argv[2], "song") == 0) - return handle_sticker_song(client, argc, argv); + if (StringIsEqual(args[1], "song")) + return handle_sticker_song(r, client.partition, args); else { - command_error(client, ACK_ERROR_ARG, - "unknown sticker domain"); + r.Error(ACK_ERROR_ARG, "unknown sticker domain"); return CommandResult::ERROR; } } diff --git a/src/command/StickerCommands.hxx b/src/command/StickerCommands.hxx index cf46cd034..5bb9cc426 100644 --- a/src/command/StickerCommands.hxx +++ b/src/command/StickerCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,8 +23,10 @@ #include "CommandResult.hxx" class Client; +class Request; +class Response; CommandResult -handle_sticker(Client &client, unsigned argc, char *argv[]); +handle_sticker(Client &client, Request request, Response &response); #endif diff --git a/src/command/StorageCommands.cxx b/src/command/StorageCommands.cxx index ee51c573e..3c11eb0d7 100644 --- a/src/command/StorageCommands.cxx +++ b/src/command/StorageCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -21,12 +21,14 @@ #include "config.h" #include "StorageCommands.hxx" +#include "Request.hxx" #include "CommandError.hxx" -#include "protocol/Result.hxx" #include "util/UriUtil.hxx" #include "util/Error.hxx" +#include "util/ConstBuffer.hxx" #include "fs/Traits.hxx" #include "client/Client.hxx" +#include "client/Response.hxx" #include "Partition.hxx" #include "Instance.hxx" #include "storage/Registry.hxx" @@ -55,7 +57,7 @@ skip_path(const char *name_utf8) #endif static bool -handle_listfiles_storage(Client &client, StorageDirectoryReader &reader, +handle_listfiles_storage(Response &r, StorageDirectoryReader &reader, Error &error) { const char *name_utf8; @@ -63,29 +65,29 @@ handle_listfiles_storage(Client &client, StorageDirectoryReader &reader, if (skip_path(name_utf8)) continue; - FileInfo info; + StorageFileInfo info; if (!reader.GetInfo(false, info, error)) continue; switch (info.type) { - case FileInfo::Type::OTHER: + case StorageFileInfo::Type::OTHER: /* ignore */ continue; - case FileInfo::Type::REGULAR: - client_printf(client, "file: %s\n" - "size: %" PRIu64 "\n", - name_utf8, - info.size); + case StorageFileInfo::Type::REGULAR: + r.Format("file: %s\n" + "size: %" PRIu64 "\n", + name_utf8, + info.size); break; - case FileInfo::Type::DIRECTORY: - client_printf(client, "directory: %s\n", name_utf8); + case StorageFileInfo::Type::DIRECTORY: + r.Format("directory: %s\n", name_utf8); break; } if (info.mtime != 0) - time_print(client, "Last-Modified", info.mtime); + time_print(r, "Last-Modified", info.mtime); } return true; @@ -96,58 +98,57 @@ handle_listfiles_storage(Client &client, StorageDirectoryReader &reader, #endif static bool -handle_listfiles_storage(Client &client, Storage &storage, const char *uri, +handle_listfiles_storage(Response &r, 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); + bool success = handle_listfiles_storage(r, *reader, error); delete reader; return success; } CommandResult -handle_listfiles_storage(Client &client, Storage &storage, const char *uri) +handle_listfiles_storage(Response &r, Storage &storage, const char *uri) { Error error; - if (!handle_listfiles_storage(client, storage, uri, error)) - return print_error(client, error); + if (!handle_listfiles_storage(r, storage, uri, error)) + return print_error(r, error); return CommandResult::OK; } CommandResult -handle_listfiles_storage(Client &client, const char *uri) +handle_listfiles_storage(Response &r, const char *uri) { Error error; Storage *storage = CreateStorageURI(io_thread_get(), uri, error); if (storage == nullptr) { if (error.IsDefined()) - return print_error(client, error); + return print_error(r, error); - command_error(client, ACK_ERROR_ARG, - "Unrecognized storage URI"); + r.Error(ACK_ERROR_ARG, "Unrecognized storage URI"); return CommandResult::ERROR; } - bool success = handle_listfiles_storage(client, *storage, "", error); + bool success = handle_listfiles_storage(r, *storage, "", error); delete storage; if (!success) - return print_error(client, error); + return print_error(r, error); return CommandResult::OK; } static void -print_storage_uri(Client &client, const Storage &storage) +print_storage_uri(Client &client, Response &r, const Storage &storage) { std::string uri = storage.MapUTF8(""); if (uri.empty()) return; - if (PathTraitsFS::IsAbsolute(uri.c_str())) { + if (PathTraitsUTF8::IsAbsolute(uri.c_str())) { /* storage points to local directory */ if (!client.IsLocal()) @@ -163,24 +164,24 @@ print_storage_uri(Client &client, const Storage &storage) uri = std::move(allocated); } - client_printf(client, "storage: %s\n", uri.c_str()); + r.Format("storage: %s\n", uri.c_str()); } CommandResult -handle_listmounts(Client &client, gcc_unused unsigned argc, gcc_unused char *argv[]) +handle_listmounts(Client &client, gcc_unused Request args, Response &r) { Storage *_composite = client.partition.instance.storage; if (_composite == nullptr) { - command_error(client, ACK_ERROR_NO_EXIST, "No database"); + r.Error(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); + const auto visitor = [&client, &r](const char *mount_uri, + const Storage &storage){ + r.Format("mount: %s\n", mount_uri); + print_storage_uri(client, r, storage); }; composite.VisitMounts(visitor); @@ -189,21 +190,21 @@ handle_listmounts(Client &client, gcc_unused unsigned argc, gcc_unused char *arg } CommandResult -handle_mount(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_mount(Client &client, Request args, Response &r) { Storage *_composite = client.partition.instance.storage; if (_composite == nullptr) { - command_error(client, ACK_ERROR_NO_EXIST, "No database"); + r.Error(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]; + const char *const local_uri = args[0]; + const char *const remote_uri = args[1]; if (*local_uri == 0) { - command_error(client, ACK_ERROR_ARG, "Bad mount point"); + r.Error(ACK_ERROR_ARG, "Bad mount point"); return CommandResult::ERROR; } @@ -213,7 +214,7 @@ handle_mount(Client &client, gcc_unused unsigned argc, char *argv[]) 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"); + r.Error(ACK_ERROR_ARG, "Bad mount point"); return CommandResult::ERROR; } @@ -222,10 +223,9 @@ handle_mount(Client &client, gcc_unused unsigned argc, char *argv[]) error); if (storage == nullptr) { if (error.IsDefined()) - return print_error(client, error); + return print_error(r, error); - command_error(client, ACK_ERROR_ARG, - "Unrecognized storage URI"); + r.Error(ACK_ERROR_ARG, "Unrecognized storage URI"); return CommandResult::ERROR; } @@ -239,7 +239,7 @@ handle_mount(Client &client, gcc_unused unsigned argc, char *argv[]) if (!db.Mount(local_uri, remote_uri, error)) { composite.Unmount(local_uri); - return print_error(client, error); + return print_error(r, error); } // TODO: call Instance::OnDatabaseModified()? @@ -252,20 +252,20 @@ handle_mount(Client &client, gcc_unused unsigned argc, char *argv[]) } CommandResult -handle_unmount(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_unmount(Client &client, Request args, Response &r) { Storage *_composite = client.partition.instance.storage; if (_composite == nullptr) { - command_error(client, ACK_ERROR_NO_EXIST, "No database"); + r.Error(ACK_ERROR_NO_EXIST, "No database"); return CommandResult::ERROR; } CompositeStorage &composite = *(CompositeStorage *)_composite; - const char *const local_uri = argv[1]; + const char *const local_uri = args.front(); if (*local_uri == 0) { - command_error(client, ACK_ERROR_ARG, "Bad mount point"); + r.Error(ACK_ERROR_ARG, "Bad mount point"); return CommandResult::ERROR; } @@ -287,7 +287,7 @@ handle_unmount(Client &client, gcc_unused unsigned argc, char *argv[]) #endif if (!composite.Unmount(local_uri)) { - command_error(client, ACK_ERROR_ARG, "Not a mount point"); + r.Error(ACK_ERROR_ARG, "Not a mount point"); return CommandResult::ERROR; } diff --git a/src/command/StorageCommands.hxx b/src/command/StorageCommands.hxx index a3636d54a..7d3c552f6 100644 --- a/src/command/StorageCommands.hxx +++ b/src/command/StorageCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -24,20 +24,22 @@ class Client; class Storage; +class Request; +class Response; CommandResult -handle_listfiles_storage(Client &client, Storage &storage, const char *uri); +handle_listfiles_storage(Response &r, Storage &storage, const char *uri); CommandResult -handle_listfiles_storage(Client &client, const char *uri); +handle_listfiles_storage(Response &r, const char *uri); CommandResult -handle_listmounts(Client &client, unsigned argc, char *argv[]); +handle_listmounts(Client &client, Request request, Response &response); CommandResult -handle_mount(Client &client, unsigned argc, char *argv[]); +handle_mount(Client &client, Request request, Response &response); CommandResult -handle_unmount(Client &client, unsigned argc, char *argv[]); +handle_unmount(Client &client, Request request, Response &response); #endif diff --git a/src/command/TagCommands.cxx b/src/command/TagCommands.cxx index 2d537671c..2a7076bdc 100644 --- a/src/command/TagCommands.cxx +++ b/src/command/TagCommands.cxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -19,51 +19,51 @@ #include "config.h" #include "TagCommands.hxx" +#include "Request.hxx" #include "CommandError.hxx" #include "client/Client.hxx" -#include "protocol/ArgParser.hxx" -#include "protocol/Result.hxx" +#include "client/Response.hxx" #include "tag/Tag.hxx" #include "Partition.hxx" +#include "util/ConstBuffer.hxx" CommandResult -handle_addtagid(Client &client, gcc_unused unsigned argc, char *argv[]) +handle_addtagid(Client &client, Request args, Response &r) { unsigned song_id; - if (!check_unsigned(client, &song_id, argv[1])) + if (!args.Parse(0, song_id, r)) return CommandResult::ERROR; - const char *const tag_name = argv[2]; + const char *const tag_name = args[1]; const TagType tag_type = tag_name_parse_i(tag_name); if (tag_type == TAG_NUM_OF_ITEM_TYPES) { - command_error(client, ACK_ERROR_ARG, - "Unknown tag type: %s", tag_name); + r.FormatError(ACK_ERROR_ARG, "Unknown tag type: %s", tag_name); return CommandResult::ERROR; } - const char *const value = argv[3]; + const char *const value = args[2]; Error error; if (!client.partition.playlist.AddSongIdTag(song_id, tag_type, value, error)) - return print_error(client, error); + return print_error(r, error); return CommandResult::OK; } CommandResult -handle_cleartagid(Client &client, unsigned argc, char *argv[]) +handle_cleartagid(Client &client, Request args, Response &r) { unsigned song_id; - if (!check_unsigned(client, &song_id, argv[1])) + if (!args.Parse(0, song_id, r)) return CommandResult::ERROR; TagType tag_type = TAG_NUM_OF_ITEM_TYPES; - if (argc >= 3) { - const char *const tag_name = argv[2]; + if (args.size >= 2) { + const char *const tag_name = args[1]; tag_type = tag_name_parse_i(tag_name); if (tag_type == TAG_NUM_OF_ITEM_TYPES) { - command_error(client, ACK_ERROR_ARG, + r.FormatError(ACK_ERROR_ARG, "Unknown tag type: %s", tag_name); return CommandResult::ERROR; } @@ -72,7 +72,7 @@ handle_cleartagid(Client &client, unsigned argc, char *argv[]) Error error; if (!client.partition.playlist.ClearSongIdTag(song_id, tag_type, error)) - return print_error(client, error); + return print_error(r, error); return CommandResult::OK; } diff --git a/src/command/TagCommands.hxx b/src/command/TagCommands.hxx index 748838e68..868d6d783 100644 --- a/src/command/TagCommands.hxx +++ b/src/command/TagCommands.hxx @@ -1,5 +1,5 @@ /* - * Copyright (C) 2003-2014 The Music Player Daemon Project + * Copyright (C) 2003-2015 The Music Player Daemon Project * http://www.musicpd.org * * This program is free software; you can redistribute it and/or modify @@ -23,11 +23,13 @@ #include "CommandResult.hxx" class Client; +class Request; +class Response; CommandResult -handle_addtagid(Client &client, unsigned argc, char *argv[]); +handle_addtagid(Client &client, Request request, Response &response); CommandResult -handle_cleartagid(Client &client, unsigned argc, char *argv[]); +handle_cleartagid(Client &client, Request request, Response &response); #endif |