From 5ec843dcc89737bb2ca9e428415d9a052c5a753d Mon Sep 17 00:00:00 2001 From: Max Kellermann Date: Sun, 20 Oct 2013 13:41:04 +0200 Subject: *Commands: move to src/command/ --- src/command/AllCommands.cxx | 375 +++++++++++++++++++++++++++++++++++ src/command/AllCommands.hxx | 34 ++++ src/command/CommandError.cxx | 124 ++++++++++++ src/command/CommandError.hxx | 38 ++++ src/command/CommandListBuilder.cxx | 43 ++++ src/command/CommandListBuilder.hxx | 109 +++++++++++ src/command/CommandResult.hxx | 61 ++++++ src/command/DatabaseCommands.cxx | 221 +++++++++++++++++++++ src/command/DatabaseCommands.hxx | 57 ++++++ src/command/MessageCommands.cxx | 137 +++++++++++++ src/command/MessageCommands.hxx | 42 ++++ src/command/OtherCommands.cxx | 315 ++++++++++++++++++++++++++++++ src/command/OtherCommands.hxx | 69 +++++++ src/command/OutputCommands.cxx | 90 +++++++++ src/command/OutputCommands.hxx | 39 ++++ src/command/PlayerCommands.cxx | 388 ++++++++++++++++++++++++++++++++++++ src/command/PlayerCommands.hxx | 90 +++++++++ src/command/PlaylistCommands.cxx | 222 +++++++++++++++++++++ src/command/PlaylistCommands.hxx | 60 ++++++ src/command/QueueCommands.cxx | 391 +++++++++++++++++++++++++++++++++++++ src/command/QueueCommands.hxx | 84 ++++++++ src/command/StickerCommands.cxx | 178 +++++++++++++++++ src/command/StickerCommands.hxx | 30 +++ 23 files changed, 3197 insertions(+) create mode 100644 src/command/AllCommands.cxx create mode 100644 src/command/AllCommands.hxx create mode 100644 src/command/CommandError.cxx create mode 100644 src/command/CommandError.hxx create mode 100644 src/command/CommandListBuilder.cxx create mode 100644 src/command/CommandListBuilder.hxx create mode 100644 src/command/CommandResult.hxx create mode 100644 src/command/DatabaseCommands.cxx create mode 100644 src/command/DatabaseCommands.hxx create mode 100644 src/command/MessageCommands.cxx create mode 100644 src/command/MessageCommands.hxx create mode 100644 src/command/OtherCommands.cxx create mode 100644 src/command/OtherCommands.hxx create mode 100644 src/command/OutputCommands.cxx create mode 100644 src/command/OutputCommands.hxx create mode 100644 src/command/PlayerCommands.cxx create mode 100644 src/command/PlayerCommands.hxx create mode 100644 src/command/PlaylistCommands.cxx create mode 100644 src/command/PlaylistCommands.hxx create mode 100644 src/command/QueueCommands.cxx create mode 100644 src/command/QueueCommands.hxx create mode 100644 src/command/StickerCommands.cxx create mode 100644 src/command/StickerCommands.hxx (limited to 'src/command') diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx new file mode 100644 index 000000000..2271aeff2 --- /dev/null +++ b/src/command/AllCommands.cxx @@ -0,0 +1,375 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "AllCommands.hxx" +#include "QueueCommands.hxx" +#include "PlayerCommands.hxx" +#include "PlaylistCommands.hxx" +#include "DatabaseCommands.hxx" +#include "OutputCommands.hxx" +#include "MessageCommands.hxx" +#include "OtherCommands.hxx" +#include "Permission.hxx" +#include "tag/TagType.h" +#include "protocol/Result.hxx" +#include "Client.hxx" +#include "util/Tokenizer.hxx" +#include "util/Error.hxx" + +#ifdef ENABLE_SQLITE +#include "StickerCommands.hxx" +#include "StickerDatabase.hxx" +#endif + +#include +#include + +/* + * The most we ever use is for search/find, and that limits it to the + * number of tags we can have. Add one for the command, and one extra + * to catch errors clients may send us + */ +#define COMMAND_ARGV_MAX (2+(TAG_NUM_OF_ITEM_TYPES*2)) + +/* if min: -1 don't check args * + * if max: -1 no max args */ +struct command { + const char *cmd; + unsigned permission; + int min; + int max; + CommandResult (*handler)(Client &client, int argc, char **argv); +}; + +/* don't be fooled, this is the command handler for "commands" command */ +static CommandResult +handle_commands(Client &client, int argc, char *argv[]); + +static CommandResult +handle_not_commands(Client &client, int argc, char *argv[]); + +/** + * The command registry. + * + * This array must be sorted! + */ +static const struct command commands[] = { + { "add", PERMISSION_ADD, 1, 1, handle_add }, + { "addid", PERMISSION_ADD, 1, 2, handle_addid }, + { "channels", PERMISSION_READ, 0, 0, handle_channels }, + { "clear", PERMISSION_CONTROL, 0, 0, handle_clear }, + { "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror }, + { "close", PERMISSION_NONE, -1, -1, handle_close }, + { "commands", PERMISSION_NONE, 0, 0, handle_commands }, + { "config", PERMISSION_ADMIN, 0, 0, handle_config }, + { "consume", PERMISSION_CONTROL, 1, 1, handle_consume }, + { "count", PERMISSION_READ, 2, -1, handle_count }, + { "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade }, + { "currentsong", PERMISSION_READ, 0, 0, handle_currentsong }, + { "decoders", PERMISSION_READ, 0, 0, handle_decoders }, + { "delete", PERMISSION_CONTROL, 1, 1, handle_delete }, + { "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid }, + { "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput }, + { "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput }, + { "find", PERMISSION_READ, 2, -1, handle_find }, + { "findadd", PERMISSION_READ, 2, -1, handle_findadd}, + { "idle", PERMISSION_READ, 0, -1, handle_idle }, + { "kill", PERMISSION_ADMIN, -1, -1, handle_kill }, + { "list", PERMISSION_READ, 1, -1, handle_list }, + { "listall", PERMISSION_READ, 0, 1, handle_listall }, + { "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo }, + { "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist }, + { "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo }, + { "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists }, + { "load", PERMISSION_ADD, 1, 2, handle_load }, + { "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo }, + { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb }, + { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay }, + { "move", PERMISSION_CONTROL, 2, 2, handle_move }, + { "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid }, + { "next", PERMISSION_CONTROL, 0, 0, handle_next }, + { "notcommands", PERMISSION_NONE, 0, 0, handle_not_commands }, + { "outputs", PERMISSION_READ, 0, 0, handle_devices }, + { "password", PERMISSION_NONE, 1, 1, handle_password }, + { "pause", PERMISSION_CONTROL, 0, 1, handle_pause }, + { "ping", PERMISSION_NONE, 0, 0, handle_ping }, + { "play", PERMISSION_CONTROL, 0, 1, handle_play }, + { "playid", PERMISSION_CONTROL, 0, 1, handle_playid }, + { "playlist", PERMISSION_READ, 0, 0, handle_playlist }, + { "playlistadd", PERMISSION_CONTROL, 2, 2, handle_playlistadd }, + { "playlistclear", PERMISSION_CONTROL, 1, 1, handle_playlistclear }, + { "playlistdelete", PERMISSION_CONTROL, 2, 2, handle_playlistdelete }, + { "playlistfind", PERMISSION_READ, 2, -1, handle_playlistfind }, + { "playlistid", PERMISSION_READ, 0, 1, handle_playlistid }, + { "playlistinfo", PERMISSION_READ, 0, 1, handle_playlistinfo }, + { "playlistmove", PERMISSION_CONTROL, 3, 3, handle_playlistmove }, + { "playlistsearch", PERMISSION_READ, 2, -1, handle_playlistsearch }, + { "plchanges", PERMISSION_READ, 1, 1, handle_plchanges }, + { "plchangesposid", PERMISSION_READ, 1, 1, handle_plchangesposid }, + { "previous", PERMISSION_CONTROL, 0, 0, handle_previous }, + { "prio", PERMISSION_CONTROL, 2, -1, handle_prio }, + { "prioid", PERMISSION_CONTROL, 2, -1, handle_prioid }, + { "random", PERMISSION_CONTROL, 1, 1, handle_random }, + { "readmessages", PERMISSION_READ, 0, 0, handle_read_messages }, + { "rename", PERMISSION_CONTROL, 2, 2, handle_rename }, + { "repeat", PERMISSION_CONTROL, 1, 1, handle_repeat }, + { "replay_gain_mode", PERMISSION_CONTROL, 1, 1, + handle_replay_gain_mode }, + { "replay_gain_status", PERMISSION_READ, 0, 0, + handle_replay_gain_status }, + { "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan }, + { "rm", PERMISSION_CONTROL, 1, 1, handle_rm }, + { "save", PERMISSION_CONTROL, 1, 1, handle_save }, + { "search", PERMISSION_READ, 2, -1, handle_search }, + { "searchadd", PERMISSION_ADD, 2, -1, handle_searchadd }, + { "searchaddpl", PERMISSION_CONTROL, 3, -1, handle_searchaddpl }, + { "seek", PERMISSION_CONTROL, 2, 2, handle_seek }, + { "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur }, + { "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid }, + { "sendmessage", PERMISSION_CONTROL, 2, 2, handle_send_message }, + { "setvol", PERMISSION_CONTROL, 1, 1, handle_setvol }, + { "shuffle", PERMISSION_CONTROL, 0, 1, handle_shuffle }, + { "single", PERMISSION_CONTROL, 1, 1, handle_single }, + { "stats", PERMISSION_READ, 0, 0, handle_stats }, + { "status", PERMISSION_READ, 0, 0, handle_status }, +#ifdef ENABLE_SQLITE + { "sticker", PERMISSION_ADMIN, 3, -1, handle_sticker }, +#endif + { "stop", PERMISSION_CONTROL, 0, 0, handle_stop }, + { "subscribe", PERMISSION_READ, 1, 1, handle_subscribe }, + { "swap", PERMISSION_CONTROL, 2, 2, handle_swap }, + { "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid }, + { "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes }, + { "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput }, + { "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe }, + { "update", PERMISSION_CONTROL, 0, 1, handle_update }, + { "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers }, +}; + +static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]); + +static bool +command_available(gcc_unused const struct command *cmd) +{ +#ifdef ENABLE_SQLITE + if (strcmp(cmd->cmd, "sticker") == 0) + return sticker_enabled(); +#endif + + return true; +} + +/* don't be fooled, this is the command handler for "commands" command */ +static CommandResult +handle_commands(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + const unsigned permission = client.GetPermission(); + const struct command *cmd; + + for (unsigned i = 0; i < num_commands; ++i) { + cmd = &commands[i]; + + if (cmd->permission == (permission & cmd->permission) && + command_available(cmd)) + client_printf(client, "command: %s\n", cmd->cmd); + } + + return CommandResult::OK; +} + +static CommandResult +handle_not_commands(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + const unsigned permission = client.GetPermission(); + const struct command *cmd; + + for (unsigned i = 0; i < num_commands; ++i) { + cmd = &commands[i]; + + if (cmd->permission != (permission & cmd->permission)) + client_printf(client, "command: %s\n", cmd->cmd); + } + + return CommandResult::OK; +} + +void command_init(void) +{ +#ifndef NDEBUG + /* ensure that the command list is sorted */ + for (unsigned i = 0; i < num_commands - 1; ++i) + assert(strcmp(commands[i].cmd, commands[i + 1].cmd) < 0); +#endif +} + +void command_finish(void) +{ +} + +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); + if (cmp == 0) + return &commands[i]; + else if (cmp < 0) + b = i; + else if (cmp > 0) + a = i + 1; + } while (a < b); + + return nullptr; +} + +static bool +command_check_request(const struct command *cmd, Client &client, + unsigned permission, int argc, char *argv[]) +{ + int min = cmd->min + 1; + int max = cmd->max + 1; + + if (cmd->permission != (permission & cmd->permission)) { + command_error(client, ACK_ERROR_PERMISSION, + "you don't have permission for \"%s\"", + cmd->cmd); + return false; + } + + if (min == 0) + return true; + + if (min == max && max != argc) { + command_error(client, ACK_ERROR_ARG, + "wrong number of arguments for \"%s\"", + argv[0]); + return false; + } else if (argc < min) { + command_error(client, ACK_ERROR_ARG, + "too few arguments for \"%s\"", argv[0]); + return false; + } else if (argc > max && max /* != 0 */ ) { + command_error(client, ACK_ERROR_ARG, + "too many arguments for \"%s\"", argv[0]); + return false; + } else + return true; +} + +static const struct command * +command_checked_lookup(Client &client, unsigned permission, + int argc, char *argv[]) +{ + const struct command *cmd; + + current_command = ""; + + if (argc == 0) + return nullptr; + + cmd = command_lookup(argv[0]); + if (cmd == nullptr) { + command_error(client, ACK_ERROR_UNKNOWN, + "unknown command \"%s\"", argv[0]); + return nullptr; + } + + current_command = cmd->cmd; + + if (!command_check_request(cmd, client, permission, argc, argv)) + return nullptr; + + return cmd; +} + +CommandResult +command_process(Client &client, unsigned num, char *line) +{ + Error error; + char *argv[COMMAND_ARGV_MAX] = { nullptr }; + const struct command *cmd; + CommandResult ret = CommandResult::ERROR; + + command_list_num = num; + + /* get the command name (first word on the line) */ + + Tokenizer tokenizer(line); + argv[0] = tokenizer.NextWord(error); + if (argv[0] == nullptr) { + current_command = ""; + if (tokenizer.IsEnd()) + command_error(client, ACK_ERROR_UNKNOWN, + "No command given"); + else + command_error(client, ACK_ERROR_UNKNOWN, + "%s", error.GetMessage()); + + current_command = nullptr; + + return CommandResult::ERROR; + } + + unsigned argc = 1; + + /* now parse the arguments (quoted or unquoted) */ + + while (argc < COMMAND_ARGV_MAX && + (argv[argc] = + tokenizer.NextParam(error)) != nullptr) + ++argc; + + /* some error checks; we have to set current_command because + command_error() expects it to be set */ + + current_command = argv[0]; + + if (argc >= COMMAND_ARGV_MAX) { + command_error(client, ACK_ERROR_ARG, "Too many arguments"); + current_command = nullptr; + return CommandResult::ERROR; + } + + if (!tokenizer.IsEnd()) { + command_error(client, ACK_ERROR_ARG, "%s", error.GetMessage()); + current_command = nullptr; + return CommandResult::ERROR; + } + + /* look up and invoke the command handler */ + + cmd = command_checked_lookup(client, client.GetPermission(), + argc, argv); + if (cmd) + ret = cmd->handler(client, argc, argv); + + current_command = nullptr; + command_list_num = 0; + + return ret; +} diff --git a/src/command/AllCommands.hxx b/src/command/AllCommands.hxx new file mode 100644 index 000000000..2be94c136 --- /dev/null +++ b/src/command/AllCommands.hxx @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2003-2013 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_ALL_COMMANDS_HXX +#define MPD_ALL_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; + +void command_init(void); + +void command_finish(void); + +CommandResult +command_process(Client &client, unsigned num, char *line); + +#endif diff --git a/src/command/CommandError.cxx b/src/command/CommandError.cxx new file mode 100644 index 000000000..fe9d9481a --- /dev/null +++ b/src/command/CommandError.cxx @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CommandError.hxx" +#include "DatabaseError.hxx" +#include "protocol/Result.hxx" +#include "util/Error.hxx" +#include "Log.hxx" + +#include + +#include +#include + +CommandResult +print_playlist_result(Client &client, PlaylistResult result) +{ + switch (result) { + case PlaylistResult::SUCCESS: + return CommandResult::OK; + + case PlaylistResult::ERRNO: + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(errno)); + return CommandResult::ERROR; + + case PlaylistResult::DENIED: + command_error(client, ACK_ERROR_PERMISSION, "Access denied"); + return CommandResult::ERROR; + + case PlaylistResult::NO_SUCH_SONG: + command_error(client, 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"); + return CommandResult::ERROR; + + case PlaylistResult::LIST_EXISTS: + command_error(client, 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"); + return CommandResult::ERROR; + + case PlaylistResult::BAD_RANGE: + command_error(client, ACK_ERROR_ARG, "Bad song index"); + return CommandResult::ERROR; + + case PlaylistResult::NOT_PLAYING: + command_error(client, 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"); + return CommandResult::ERROR; + + case PlaylistResult::DISABLED: + command_error(client, ACK_ERROR_UNKNOWN, + "stored playlist support is disabled"); + return CommandResult::ERROR; + } + + assert(0); + return CommandResult::ERROR; +} + +CommandResult +print_error(Client &client, const Error &error) +{ + assert(error.IsDefined()); + + LogError(error); + + if (error.IsDomain(playlist_domain)) { + return print_playlist_result(client, + PlaylistResult(error.GetCode())); + } else if (error.IsDomain(ack_domain)) { + command_error(client, (ack)error.GetCode(), + "%s", error.GetMessage()); + return CommandResult::ERROR; + } 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()); + return CommandResult::ERROR; + + case DB_NOT_FOUND: + command_error(client, ACK_ERROR_NO_EXIST, "Not found"); + return CommandResult::ERROR; + } + } else if (error.IsDomain(errno_domain)) { + command_error(client, ACK_ERROR_SYSTEM, "%s", + g_strerror(error.GetCode())); + return CommandResult::ERROR; + } + + command_error(client, ACK_ERROR_UNKNOWN, "error"); + return CommandResult::ERROR; +} diff --git a/src/command/CommandError.hxx b/src/command/CommandError.hxx new file mode 100644 index 000000000..183797213 --- /dev/null +++ b/src/command/CommandError.hxx @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2003-2012 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_COMMAND_ERROR_HXX +#define MPD_COMMAND_ERROR_HXX + +#include "CommandResult.hxx" +#include "PlaylistError.hxx" + +class Client; +class Error; + +CommandResult +print_playlist_result(Client &client, PlaylistResult result); + +/** + * Send the #Error to the client. + */ +CommandResult +print_error(Client &client, const Error &error); + +#endif diff --git a/src/command/CommandListBuilder.cxx b/src/command/CommandListBuilder.cxx new file mode 100644 index 000000000..cc10f7205 --- /dev/null +++ b/src/command/CommandListBuilder.cxx @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "CommandListBuilder.hxx" +#include "ClientInternal.hxx" + +#include + +void +CommandListBuilder::Reset() +{ + list.clear(); + mode = Mode::DISABLED; +} + +bool +CommandListBuilder::Add(const char *cmd) +{ + size_t len = strlen(cmd) + 1; + size += len; + if (size > client_max_command_list_size) + return false; + + list.emplace_back(cmd); + return true; +} diff --git a/src/command/CommandListBuilder.hxx b/src/command/CommandListBuilder.hxx new file mode 100644 index 000000000..a112ac33b --- /dev/null +++ b/src/command/CommandListBuilder.hxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2013 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_COMMAND_LIST_BUILDER_HXX +#define MPD_COMMAND_LIST_BUILDER_HXX + +#include +#include + +#include + +class CommandListBuilder { + /** + * print OK after each command execution + */ + enum class Mode { + /** + * Not active. + */ + DISABLED = -1, + + /** + * Enabled in normal list mode. + */ + ENABLED = false, + + /** + * Enabled in "list_OK" mode. + */ + OK = true, + } mode; + + /** + * for when in list mode + */ + std::list list; + + /** + * Memory consumed by the list. + */ + size_t size; + +public: + CommandListBuilder() + :mode(Mode::DISABLED), size(0) {} + + /** + * Is a command list currently being built? + */ + bool IsActive() const { + return mode != Mode::DISABLED; + } + + /** + * Is the object in "list_OK" mode? + */ + bool IsOKMode() const { + assert(IsActive()); + + return (bool)mode; + } + + /** + * Reset the object: delete the list and clear the mode. + */ + void Reset(); + + /** + * Begin building a command list. + */ + void Begin(bool ok) { + assert(list.empty()); + assert(mode == Mode::DISABLED); + + mode = (Mode)ok; + } + + /** + * @return false if the list is full + */ + bool Add(const char *cmd); + + /** + * Finishes the list and returns it. + */ + std::list &&Commit() { + assert(IsActive()); + + return std::move(list); + } +}; + +#endif diff --git a/src/command/CommandResult.hxx b/src/command/CommandResult.hxx new file mode 100644 index 000000000..4132dacb7 --- /dev/null +++ b/src/command/CommandResult.hxx @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2003-2012 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_COMMAND_RESULT_HXX +#define MPD_COMMAND_RESULT_HXX + +#ifdef WIN32 +#include +/* damn you, windows.h! */ +#ifdef ERROR +#undef ERROR +#endif +#endif + +enum class CommandResult { + /** + * The command has succeeded, but the "OK" response was not + * yet sent to the client. + */ + OK, + + /** + * The connection is now in "idle" mode, and no response shall + * be generated. + */ + IDLE, + + /** + * There was an error. The "ACK" response was sent to the + * client. + */ + ERROR, + + /** + * The connection to this client shall be closed. + */ + CLOSE, + + /** + * The MPD process shall be shut down. + */ + KILL, +}; + +#endif diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx new file mode 100644 index 000000000..b86cbdae7 --- /dev/null +++ b/src/command/DatabaseCommands.cxx @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "DatabaseCommands.hxx" +#include "DatabaseQueue.hxx" +#include "DatabasePlaylist.hxx" +#include "DatabasePrint.hxx" +#include "DatabaseSelection.hxx" +#include "CommandError.hxx" +#include "Client.hxx" +#include "tag/Tag.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "SongFilter.hxx" +#include "protocol/Result.hxx" + +#include +#include + +CommandResult +handle_lsinfo2(Client &client, int argc, char *argv[]) +{ + const char *uri; + + if (argc == 2) + uri = argv[1]; + else + /* default is root directory */ + uri = ""; + + const DatabaseSelection selection(uri, false); + + Error error; + if (!db_selection_print(client, selection, true, error)) + return print_error(client, error); + + return CommandResult::OK; +} + +static CommandResult +handle_match(Client &client, int argc, char *argv[], bool fold_case) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return CommandResult::ERROR; + } + + const DatabaseSelection selection("", true, &filter); + + Error error; + return db_selection_print(client, selection, true, error) + ? CommandResult::OK + : print_error(client, error); +} + +CommandResult +handle_find(Client &client, int argc, char *argv[]) +{ + return handle_match(client, argc, argv, false); +} + +CommandResult +handle_search(Client &client, int argc, char *argv[]) +{ + return handle_match(client, argc, argv, true); +} + +static CommandResult +handle_match_add(Client &client, int argc, char *argv[], bool fold_case) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return CommandResult::ERROR; + } + + const DatabaseSelection selection("", true, &filter); + Error error; + return AddFromDatabase(client.partition, selection, error) + ? CommandResult::OK + : print_error(client, error); +} + +CommandResult +handle_findadd(Client &client, int argc, char *argv[]) +{ + return handle_match_add(client, argc, argv, false); +} + +CommandResult +handle_searchadd(Client &client, int argc, char *argv[]) +{ + return handle_match_add(client, argc, argv, true); +} + +CommandResult +handle_searchaddpl(Client &client, int argc, char *argv[]) +{ + const char *playlist = argv[1]; + + SongFilter filter; + if (!filter.Parse(argc - 2, argv + 2, true)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return CommandResult::ERROR; + } + + Error error; + return search_add_to_playlist("", playlist, &filter, error) + ? CommandResult::OK + : print_error(client, error); +} + +CommandResult +handle_count(Client &client, int argc, char *argv[]) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, false)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return CommandResult::ERROR; + } + + Error error; + return searchStatsForSongsIn(client, "", &filter, error) + ? CommandResult::OK + : print_error(client, error); +} + +CommandResult +handle_listall(Client &client, gcc_unused int argc, char *argv[]) +{ + const char *directory = ""; + + if (argc == 2) + directory = argv[1]; + + Error error; + return printAllIn(client, directory, error) + ? CommandResult::OK + : print_error(client, error); +} + +CommandResult +handle_list(Client &client, int argc, char *argv[]) +{ + unsigned tagType = locate_parse_type(argv[1]); + + if (tagType == TAG_NUM_OF_ITEM_TYPES) { + command_error(client, ACK_ERROR_ARG, "\"%s\" is not known", argv[1]); + return CommandResult::ERROR; + } + + if (tagType == LOCATE_TAG_ANY_TYPE) { + command_error(client, ACK_ERROR_ARG, + "\"any\" is not a valid return tag type"); + return CommandResult::ERROR; + } + + /* for compatibility with < 0.12.0 */ + SongFilter *filter; + if (argc == 3) { + if (tagType != TAG_ALBUM) { + command_error(client, ACK_ERROR_ARG, + "should be \"%s\" for 3 arguments", + tag_item_names[TAG_ALBUM]); + return CommandResult::ERROR; + } + + filter = new SongFilter((unsigned)TAG_ARTIST, argv[2]); + } else if (argc > 2) { + filter = new SongFilter(); + if (!filter->Parse(argc - 2, argv + 2, false)) { + delete filter; + command_error(client, ACK_ERROR_ARG, + "not able to parse args"); + return CommandResult::ERROR; + } + } else + filter = nullptr; + + Error error; + CommandResult ret = + listAllUniqueTags(client, tagType, filter, error) + ? CommandResult::OK + : print_error(client, error); + + delete filter; + + return ret; +} + +CommandResult +handle_listallinfo(Client &client, gcc_unused int argc, char *argv[]) +{ + const char *directory = ""; + + if (argc == 2) + directory = argv[1]; + + Error error; + return printInfoForAllIn(client, directory, error) + ? CommandResult::OK + : print_error(client, error); +} diff --git a/src/command/DatabaseCommands.hxx b/src/command/DatabaseCommands.hxx new file mode 100644 index 000000000..c60caf246 --- /dev/null +++ b/src/command/DatabaseCommands.hxx @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2003-2012 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_DATABASE_COMMANDS_HXX +#define MPD_DATABASE_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; + +CommandResult +handle_lsinfo2(Client &client, int argc, char *argv[]); + +CommandResult +handle_find(Client &client, int argc, char *argv[]); + +CommandResult +handle_findadd(Client &client, int argc, char *argv[]); + +CommandResult +handle_search(Client &client, int argc, char *argv[]); + +CommandResult +handle_searchadd(Client &client, int argc, char *argv[]); + +CommandResult +handle_searchaddpl(Client &client, int argc, char *argv[]); + +CommandResult +handle_count(Client &client, int argc, char *argv[]); + +CommandResult +handle_listall(Client &client, int argc, char *argv[]); + +CommandResult +handle_list(Client &client, int argc, char *argv[]); + +CommandResult +handle_listallinfo(Client &client, int argc, char *argv[]); + +#endif diff --git a/src/command/MessageCommands.cxx b/src/command/MessageCommands.cxx new file mode 100644 index 000000000..7d9893e70 --- /dev/null +++ b/src/command/MessageCommands.cxx @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "MessageCommands.hxx" +#include "Client.hxx" +#include "ClientList.hxx" +#include "Instance.hxx" +#include "Main.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" + +#include +#include + +#include + +CommandResult +handle_subscribe(Client &client, gcc_unused int argc, char *argv[]) +{ + assert(argc == 2); + + switch (client.Subscribe(argv[1])) { + case Client::SubscribeResult::OK: + return CommandResult::OK; + + case Client::SubscribeResult::INVALID: + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return CommandResult::ERROR; + + case Client::SubscribeResult::ALREADY: + command_error(client, 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"); + return CommandResult::ERROR; + } + + /* unreachable */ + assert(false); + gcc_unreachable(); +} + +CommandResult +handle_unsubscribe(Client &client, gcc_unused int argc, char *argv[]) +{ + assert(argc == 2); + + if (client.Unsubscribe(argv[1])) + return CommandResult::OK; + else { + command_error(client, ACK_ERROR_NO_EXIST, + "not subscribed to this channel"); + return CommandResult::ERROR; + } +} + +CommandResult +handle_channels(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + assert(argc == 1); + + std::set channels; + for (const auto &c : *instance->client_list) + channels.insert(c->subscriptions.begin(), + c->subscriptions.end()); + + for (const auto &channel : channels) + client_printf(client, "channel: %s\n", channel.c_str()); + + return CommandResult::OK; +} + +CommandResult +handle_read_messages(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + assert(argc == 1); + + while (!client.messages.empty()) { + const ClientMessage &msg = client.messages.front(); + + client_printf(client, "channel: %s\nmessage: %s\n", + msg.GetChannel(), msg.GetMessage()); + client.messages.pop_front(); + } + + return CommandResult::OK; +} + +CommandResult +handle_send_message(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + assert(argc == 3); + + if (!client_message_valid_channel_name(argv[1])) { + command_error(client, ACK_ERROR_ARG, + "invalid channel name"); + return CommandResult::ERROR; + } + + bool sent = false; + const ClientMessage msg(argv[1], argv[2]); + for (const auto &c : *instance->client_list) + if (c->PushMessage(msg)) + sent = true; + + if (sent) + return CommandResult::OK; + else { + command_error(client, 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 new file mode 100644 index 000000000..921b20c94 --- /dev/null +++ b/src/command/MessageCommands.hxx @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2003-2012 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_MESSAGE_COMMANDS_HXX +#define MPD_MESSAGE_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; + +CommandResult +handle_subscribe(Client &client, int argc, char *argv[]); + +CommandResult +handle_unsubscribe(Client &client, int argc, char *argv[]); + +CommandResult +handle_channels(Client &client, int argc, char *argv[]); + +CommandResult +handle_read_messages(Client &client, int argc, char *argv[]); + +CommandResult +handle_send_message(Client &client, int argc, char *argv[]); + +#endif diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx new file mode 100644 index 000000000..1291caf4e --- /dev/null +++ b/src/command/OtherCommands.cxx @@ -0,0 +1,315 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OtherCommands.hxx" +#include "DatabaseCommands.hxx" +#include "CommandError.hxx" +#include "UpdateGlue.hxx" +#include "Directory.hxx" +#include "Song.hxx" +#include "SongPrint.hxx" +#include "TagPrint.hxx" +#include "TimePrint.hxx" +#include "Mapper.hxx" +#include "DecoderPrint.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" +#include "Volume.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "fs/AllocatedPath.hxx" +#include "Stats.hxx" +#include "Permission.hxx" +#include "PlaylistFile.hxx" +#include "ClientFile.hxx" +#include "Client.hxx" +#include "Idle.hxx" + +#ifdef ENABLE_SQLITE +#include "StickerDatabase.hxx" +#endif + +#include + +#include +#include + +static void +print_spl_list(Client &client, const PlaylistVector &list) +{ + for (const auto &i : list) { + client_printf(client, "playlist: %s\n", i.name.c_str()); + + if (i.mtime > 0) + time_print(client, "Last-Modified", i.mtime); + } +} + +CommandResult +handle_urlhandlers(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + if (client.IsLocal()) + client_puts(client, "handler: file://\n"); + print_supported_uri_schemes(client); + return CommandResult::OK; +} + +CommandResult +handle_decoders(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + decoder_list_print(client); + return CommandResult::OK; +} + +CommandResult +handle_tagtypes(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + tag_print_types(client); + return CommandResult::OK; +} + +CommandResult +handle_kill(gcc_unused Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + return CommandResult::KILL; +} + +CommandResult +handle_close(gcc_unused Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + return CommandResult::CLOSE; +} + +CommandResult +handle_lsinfo(Client &client, int argc, char *argv[]) +{ + const char *uri; + + if (argc == 2) + uri = argv[1]; + else + /* default is root directory */ + uri = ""; + + 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_allow_file(client, path_fs, error)) + return print_error(client, error); + + Song *song = Song::LoadFile(path_utf8, nullptr); + if (song == NULL) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such file"); + return CommandResult::ERROR; + } + + song_print_info(client, *song); + song->Free(); + return CommandResult::OK; + } + + CommandResult result = handle_lsinfo2(client, argc, argv); + if (result != CommandResult::OK) + return result; + + if (isRootDirectory(uri)) { + Error error; + const auto &list = ListPlaylistFiles(error); + print_spl_list(client, list); + } + + return CommandResult::OK; +} + +CommandResult +handle_update(Client &client, gcc_unused int argc, char *argv[]) +{ + const char *path = ""; + unsigned ret; + + assert(argc <= 2); + if (argc == 2) { + path = argv[1]; + + if (*path == 0 || strcmp(path, "/") == 0) + /* backwards compatibility with MPD 0.15 */ + path = ""; + else if (!uri_safe_local(path)) { + command_error(client, ACK_ERROR_ARG, + "Malformed path"); + return CommandResult::ERROR; + } + } + + ret = update_enqueue(path, false); + if (ret > 0) { + client_printf(client, "updating_db: %i\n", ret); + return CommandResult::OK; + } else { + command_error(client, ACK_ERROR_UPDATE_ALREADY, + "already updating"); + return CommandResult::ERROR; + } +} + +CommandResult +handle_rescan(Client &client, gcc_unused int argc, char *argv[]) +{ + const char *path = ""; + unsigned ret; + + assert(argc <= 2); + if (argc == 2) { + path = argv[1]; + + if (!uri_safe_local(path)) { + command_error(client, ACK_ERROR_ARG, + "Malformed path"); + return CommandResult::ERROR; + } + } + + ret = update_enqueue(path, true); + if (ret > 0) { + client_printf(client, "updating_db: %i\n", ret); + return CommandResult::OK; + } else { + command_error(client, ACK_ERROR_UPDATE_ALREADY, + "already updating"); + return CommandResult::ERROR; + } +} + +CommandResult +handle_setvol(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned level; + bool success; + + if (!check_unsigned(client, &level, argv[1])) + return CommandResult::ERROR; + + if (level > 100) { + command_error(client, ACK_ERROR_ARG, "Invalid volume value"); + return CommandResult::ERROR; + } + + success = volume_level_change(level); + if (!success) { + command_error(client, ACK_ERROR_SYSTEM, + "problems setting volume"); + return CommandResult::ERROR; + } + + return CommandResult::OK; +} + +CommandResult +handle_stats(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + stats_print(client); + return CommandResult::OK; +} + +CommandResult +handle_ping(gcc_unused Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + return CommandResult::OK; +} + +CommandResult +handle_password(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned permission = 0; + + if (getPermissionFromPassword(argv[1], &permission) < 0) { + command_error(client, ACK_ERROR_PASSWORD, "incorrect password"); + return CommandResult::ERROR; + } + + client.SetPermission(permission); + + return CommandResult::OK; +} + +CommandResult +handle_config(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + if (!client.IsLocal()) { + command_error(client, ACK_ERROR_PERMISSION, + "Command only permitted to local clients"); + return CommandResult::ERROR; + } + + const char *path = mapper_get_music_directory_utf8(); + if (path != NULL) + client_printf(client, "music_directory: %s\n", path); + + return CommandResult::OK; +} + +CommandResult +handle_idle(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + unsigned flags = 0, j; + int i; + const char *const* idle_names; + + idle_names = idle_get_names(); + for (i = 1; i < argc; ++i) { + if (!argv[i]) + continue; + + for (j = 0; idle_names[j]; ++j) { + if (!g_ascii_strcasecmp(argv[i], idle_names[j])) { + flags |= (1 << j); + } + } + } + + /* No argument means that the client wants to receive everything */ + if (flags == 0) + flags = ~0; + + /* enable "idle" mode on this client */ + client.IdleWait(flags); + + return CommandResult::IDLE; +} diff --git a/src/command/OtherCommands.hxx b/src/command/OtherCommands.hxx new file mode 100644 index 000000000..fe3e145c4 --- /dev/null +++ b/src/command/OtherCommands.hxx @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2003-2012 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_OTHER_COMMANDS_HXX +#define MPD_OTHER_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; + +CommandResult +handle_urlhandlers(Client &client, int argc, char *argv[]); + +CommandResult +handle_decoders(Client &client, int argc, char *argv[]); + +CommandResult +handle_tagtypes(Client &client, int argc, char *argv[]); + +CommandResult +handle_kill(Client &client, int argc, char *argv[]); + +CommandResult +handle_close(Client &client, int argc, char *argv[]); + +CommandResult +handle_lsinfo(Client &client, int argc, char *argv[]); + +CommandResult +handle_update(Client &client, int argc, char *argv[]); + +CommandResult +handle_rescan(Client &client, int argc, char *argv[]); + +CommandResult +handle_setvol(Client &client, int argc, char *argv[]); + +CommandResult +handle_stats(Client &client, int argc, char *argv[]); + +CommandResult +handle_ping(Client &client, int argc, char *argv[]); + +CommandResult +handle_password(Client &client, int argc, char *argv[]); + +CommandResult +handle_config(Client &client, int argc, char *argv[]); + +CommandResult +handle_idle(Client &client, int argc, char *argv[]); + +#endif diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx new file mode 100644 index 000000000..8d6ea8ed7 --- /dev/null +++ b/src/command/OutputCommands.cxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "OutputCommands.hxx" +#include "OutputPrint.hxx" +#include "OutputCommand.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" + +#include + +CommandResult +handle_enableoutput(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned device; + bool ret; + + if (!check_unsigned(client, &device, argv[1])) + return CommandResult::ERROR; + + ret = audio_output_enable_index(device); + if (!ret) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return CommandResult::ERROR; + } + + return CommandResult::OK; +} + +CommandResult +handle_disableoutput(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned device; + bool ret; + + if (!check_unsigned(client, &device, argv[1])) + return CommandResult::ERROR; + + ret = audio_output_disable_index(device); + if (!ret) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return CommandResult::ERROR; + } + + return CommandResult::OK; +} + +CommandResult +handle_toggleoutput(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned device; + if (!check_unsigned(client, &device, argv[1])) + return CommandResult::ERROR; + + if (!audio_output_toggle_index(device)) { + command_error(client, ACK_ERROR_NO_EXIST, + "No such audio output"); + return CommandResult::ERROR; + } + + return CommandResult::OK; +} + +CommandResult +handle_devices(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + printAudioDevices(client); + + return CommandResult::OK; +} diff --git a/src/command/OutputCommands.hxx b/src/command/OutputCommands.hxx new file mode 100644 index 000000000..ac3c0211a --- /dev/null +++ b/src/command/OutputCommands.hxx @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2003-2012 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_OUTPUT_COMMANDS_HXX +#define MPD_OUTPUT_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; + +CommandResult +handle_enableoutput(Client &client, int argc, char *argv[]); + +CommandResult +handle_disableoutput(Client &client, int argc, char *argv[]); + +CommandResult +handle_toggleoutput(Client &client, int argc, char *argv[]); + +CommandResult +handle_devices(Client &client, int argc, char *argv[]); + +#endif diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx new file mode 100644 index 000000000..d30bde273 --- /dev/null +++ b/src/command/PlayerCommands.cxx @@ -0,0 +1,388 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlayerCommands.hxx" +#include "CommandError.hxx" +#include "Playlist.hxx" +#include "PlaylistPrint.hxx" +#include "UpdateGlue.hxx" +#include "Client.hxx" +#include "Volume.hxx" +#include "OutputAll.hxx" +#include "Partition.hxx" +#include "protocol/Result.hxx" +#include "protocol/ArgParser.hxx" +#include "AudioFormat.hxx" +#include "ReplayGainConfig.hxx" + +#define COMMAND_STATUS_STATE "state" +#define COMMAND_STATUS_REPEAT "repeat" +#define COMMAND_STATUS_SINGLE "single" +#define COMMAND_STATUS_CONSUME "consume" +#define COMMAND_STATUS_RANDOM "random" +#define COMMAND_STATUS_PLAYLIST "playlist" +#define COMMAND_STATUS_PLAYLIST_LENGTH "playlistlength" +#define COMMAND_STATUS_SONG "song" +#define COMMAND_STATUS_SONGID "songid" +#define COMMAND_STATUS_NEXTSONG "nextsong" +#define COMMAND_STATUS_NEXTSONGID "nextsongid" +#define COMMAND_STATUS_TIME "time" +#define COMMAND_STATUS_BITRATE "bitrate" +#define COMMAND_STATUS_ERROR "error" +#define COMMAND_STATUS_CROSSFADE "xfade" +#define COMMAND_STATUS_MIXRAMPDB "mixrampdb" +#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay" +#define COMMAND_STATUS_AUDIO "audio" +#define COMMAND_STATUS_UPDATING_DB "updating_db" + +CommandResult +handle_play(Client &client, int argc, char *argv[]) +{ + int song = -1; + + if (argc == 2 && !check_int(client, &song, argv[1])) + return CommandResult::ERROR; + PlaylistResult result = client.partition.PlayPosition(song); + return print_playlist_result(client, result); +} + +CommandResult +handle_playid(Client &client, int argc, char *argv[]) +{ + int id = -1; + + if (argc == 2 && !check_int(client, &id, argv[1])) + return CommandResult::ERROR; + + PlaylistResult result = client.partition.PlayId(id); + return print_playlist_result(client, result); +} + +CommandResult +handle_stop(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + client.partition.Stop(); + return CommandResult::OK; +} + +CommandResult +handle_currentsong(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + playlist_print_current(client, client.playlist); + return CommandResult::OK; +} + +CommandResult +handle_pause(Client &client, + int argc, char *argv[]) +{ + if (argc == 2) { + bool pause_flag; + if (!check_bool(client, &pause_flag, argv[1])) + return CommandResult::ERROR; + + client.player_control.SetPause(pause_flag); + } else + client.player_control.Pause(); + + return CommandResult::OK; +} + +CommandResult +handle_status(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + const char *state = NULL; + int updateJobId; + int song; + + const auto player_status = client.player_control.GetStatus(); + + switch (player_status.state) { + case PlayerState::STOP: + state = "stop"; + break; + case PlayerState::PAUSE: + state = "pause"; + break; + case PlayerState::PLAY: + state = "play"; + break; + } + + 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_CROSSFADE ": %i\n" + COMMAND_STATUS_MIXRAMPDB ": %f\n" + COMMAND_STATUS_MIXRAMPDELAY ": %f\n" + COMMAND_STATUS_STATE ": %s\n", + volume_level_get(), + playlist.GetRepeat(), + playlist.GetRandom(), + playlist.GetSingle(), + playlist.GetConsume(), + (unsigned long)playlist.GetVersion(), + playlist.GetLength(), + (int)(client.player_control.GetCrossFade() + 0.5), + client.player_control.GetMixRampDb(), + client.player_control.GetMixRampDelay(), + state); + + song = playlist.GetCurrentPosition(); + if (song >= 0) { + client_printf(client, + 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", + (int)(player_status.elapsed_time + 0.5), + (int)(player_status.total_time + 0.5), + player_status.elapsed_time, + player_status.bit_rate); + + 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)); + } + } + + if ((updateJobId = isUpdatingDB())) { + client_printf(client, + COMMAND_STATUS_UPDATING_DB ": %i\n", + updateJobId); + } + + Error error = client.player_control.LockGetError(); + if (error.IsDefined()) + client_printf(client, + 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)); + } + + return CommandResult::OK; +} + +CommandResult +handle_next(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + playlist &playlist = client.playlist; + + /* single mode is not considered when this is user who + * wants to change song. */ + const bool single = playlist.queue.single; + playlist.queue.single = false; + + client.partition.PlayNext(); + + playlist.queue.single = single; + return CommandResult::OK; +} + +CommandResult +handle_previous(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + client.partition.PlayPrevious(); + return CommandResult::OK; +} + +CommandResult +handle_repeat(Client &client, gcc_unused int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return CommandResult::ERROR; + + client.partition.SetRepeat(status); + return CommandResult::OK; +} + +CommandResult +handle_single(Client &client, gcc_unused int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return CommandResult::ERROR; + + client.partition.SetSingle(status); + return CommandResult::OK; +} + +CommandResult +handle_consume(Client &client, gcc_unused int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return CommandResult::ERROR; + + client.partition.SetConsume(status); + return CommandResult::OK; +} + +CommandResult +handle_random(Client &client, gcc_unused int argc, char *argv[]) +{ + bool status; + if (!check_bool(client, &status, argv[1])) + return CommandResult::ERROR; + + client.partition.SetRandom(status); + audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client.partition.GetRandom())); + return CommandResult::OK; +} + +CommandResult +handle_clearerror(gcc_unused Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + client.player_control.ClearError(); + return CommandResult::OK; +} + +CommandResult +handle_seek(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned song, seek_time; + + if (!check_unsigned(client, &song, argv[1])) + return CommandResult::ERROR; + if (!check_unsigned(client, &seek_time, argv[2])) + return CommandResult::ERROR; + + PlaylistResult result = + client.partition.SeekSongPosition(song, seek_time); + return print_playlist_result(client, result); +} + +CommandResult +handle_seekid(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned id, seek_time; + + if (!check_unsigned(client, &id, argv[1])) + return CommandResult::ERROR; + if (!check_unsigned(client, &seek_time, argv[2])) + return CommandResult::ERROR; + + PlaylistResult result = + client.partition.SeekSongId(id, seek_time); + return print_playlist_result(client, result); +} + +CommandResult +handle_seekcur(Client &client, gcc_unused int argc, char *argv[]) +{ + const char *p = argv[1]; + bool relative = *p == '+' || *p == '-'; + int seek_time; + if (!check_int(client, &seek_time, p)) + return CommandResult::ERROR; + + PlaylistResult result = + client.partition.SeekCurrent(seek_time, relative); + return print_playlist_result(client, result); +} + +CommandResult +handle_crossfade(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned xfade_time; + + if (!check_unsigned(client, &xfade_time, argv[1])) + return CommandResult::ERROR; + client.player_control.SetCrossFade(xfade_time); + + return CommandResult::OK; +} + +CommandResult +handle_mixrampdb(Client &client, gcc_unused int argc, char *argv[]) +{ + float db; + + if (!check_float(client, &db, argv[1])) + return CommandResult::ERROR; + client.player_control.SetMixRampDb(db); + + return CommandResult::OK; +} + +CommandResult +handle_mixrampdelay(Client &client, gcc_unused int argc, char *argv[]) +{ + float delay_secs; + + if (!check_float(client, &delay_secs, argv[1])) + return CommandResult::ERROR; + client.player_control.SetMixRampDelay(delay_secs); + + return CommandResult::OK; +} + +CommandResult +handle_replay_gain_mode(Client &client, + gcc_unused int argc, char *argv[]) +{ + if (!replay_gain_set_mode_string(argv[1])) { + command_error(client, ACK_ERROR_ARG, + "Unrecognized replay gain mode"); + return CommandResult::ERROR; + } + + audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client.playlist.queue.random)); + + return CommandResult::OK; +} + +CommandResult +handle_replay_gain_status(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + client_printf(client, "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 new file mode 100644 index 000000000..c201c7a57 --- /dev/null +++ b/src/command/PlayerCommands.hxx @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2003-2012 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_PLAYER_COMMANDS_HXX +#define MPD_PLAYER_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; + +CommandResult +handle_play(Client &client, int argc, char *argv[]); + +CommandResult +handle_playid(Client &client, int argc, char *argv[]); + +CommandResult +handle_stop(Client &client, int argc, char *argv[]); + +CommandResult +handle_currentsong(Client &client, int argc, char *argv[]); + +CommandResult +handle_pause(Client &client, int argc, char *argv[]); + +CommandResult +handle_status(Client &client, int argc, char *argv[]); + +CommandResult +handle_next(Client &client, int argc, char *argv[]); + +CommandResult +handle_previous(Client &client, int argc, char *avg[]); + +CommandResult +handle_repeat(Client &client, int argc, char *argv[]); + +CommandResult +handle_single(Client &client, int argc, char *argv[]); + +CommandResult +handle_consume(Client &client, int argc, char *argv[]); + +CommandResult +handle_random(Client &client, int argc, char *argv[]); + +CommandResult +handle_clearerror(Client &client, int argc, char *argv[]); + +CommandResult +handle_seek(Client &client, int argc, char *argv[]); + +CommandResult +handle_seekid(Client &client, int argc, char *argv[]); + +CommandResult +handle_seekcur(Client &client, int argc, char *argv[]); + +CommandResult +handle_crossfade(Client &client, int argc, char *argv[]); + +CommandResult +handle_mixrampdb(Client &client, int argc, char *argv[]); + +CommandResult +handle_mixrampdelay(Client &client, int argc, char *argv[]); + +CommandResult +handle_replay_gain_mode(Client &client, int argc, char *argv[]); + +CommandResult +handle_replay_gain_status(Client &client, int argc, char *argv[]); + +#endif diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx new file mode 100644 index 000000000..d178fa097 --- /dev/null +++ b/src/command/PlaylistCommands.cxx @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "PlaylistCommands.hxx" +#include "DatabasePlaylist.hxx" +#include "CommandError.hxx" +#include "PlaylistPrint.hxx" +#include "PlaylistSave.hxx" +#include "PlaylistFile.hxx" +#include "PlaylistVector.hxx" +#include "PlaylistQueue.hxx" +#include "TimePrint.hxx" +#include "Client.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" +#include "Playlist.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" + +#include +#include + +static void +print_spl_list(Client &client, const PlaylistVector &list) +{ + for (const auto &i : list) { + client_printf(client, "playlist: %s\n", i.name.c_str()); + + if (i.mtime > 0) + time_print(client, "Last-Modified", i.mtime); + } +} + +CommandResult +handle_save(Client &client, gcc_unused int argc, char *argv[]) +{ + PlaylistResult result = spl_save_playlist(argv[1], client.playlist); + return print_playlist_result(client, result); +} + +CommandResult +handle_load(Client &client, int argc, char *argv[]) +{ + 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])) + return CommandResult::ERROR; + + const PlaylistResult result = + playlist_open_into_queue(argv[1], + start_index, end_index, + client.playlist, + client.player_control, true); + if (result != PlaylistResult::NO_SUCH_LIST) + return print_playlist_result(client, result); + + Error error; + if (playlist_load_spl(client.playlist, client.player_control, + argv[1], start_index, end_index, + error)) + return CommandResult::OK; + + if (error.IsDomain(playlist_domain) && + PlaylistResult(error.GetCode()) == PlaylistResult::BAD_NAME) { + /* the message for BAD_NAME is confusing when the + client wants to load a playlist file from the music + directory; patch the Error object to show "no such + playlist" instead */ + Error error2(playlist_domain, int(PlaylistResult::NO_SUCH_LIST), + error.GetMessage()); + error = std::move(error2); + } + + return print_error(client, error); +} + +CommandResult +handle_listplaylist(Client &client, gcc_unused int argc, char *argv[]) +{ + if (playlist_file_print(client, argv[1], false)) + return CommandResult::OK; + + Error error; + return spl_print(client, argv[1], false, error) + ? CommandResult::OK + : print_error(client, error); +} + +CommandResult +handle_listplaylistinfo(Client &client, + gcc_unused int argc, char *argv[]) +{ + if (playlist_file_print(client, argv[1], true)) + return CommandResult::OK; + + Error error; + return spl_print(client, argv[1], true, error) + ? CommandResult::OK + : print_error(client, error); +} + +CommandResult +handle_rm(Client &client, gcc_unused int argc, char *argv[]) +{ + Error error; + return spl_delete(argv[1], error) + ? CommandResult::OK + : print_error(client, error); +} + +CommandResult +handle_rename(Client &client, gcc_unused int argc, char *argv[]) +{ + Error error; + return spl_rename(argv[1], argv[2], error) + ? CommandResult::OK + : print_error(client, error); +} + +CommandResult +handle_playlistdelete(Client &client, + gcc_unused int argc, char *argv[]) { + char *playlist = argv[1]; + unsigned from; + + if (!check_unsigned(client, &from, argv[2])) + return CommandResult::ERROR; + + Error error; + return spl_remove_index(playlist, from, error) + ? CommandResult::OK + : print_error(client, error); +} + +CommandResult +handle_playlistmove(Client &client, gcc_unused int argc, char *argv[]) +{ + char *playlist = argv[1]; + unsigned from, to; + + if (!check_unsigned(client, &from, argv[2])) + return CommandResult::ERROR; + if (!check_unsigned(client, &to, argv[3])) + return CommandResult::ERROR; + + Error error; + return spl_move_index(playlist, from, to, error) + ? CommandResult::OK + : print_error(client, error); +} + +CommandResult +handle_playlistclear(Client &client, gcc_unused int argc, char *argv[]) +{ + Error error; + return spl_clear(argv[1], error) + ? CommandResult::OK + : print_error(client, error); +} + +CommandResult +handle_playlistadd(Client &client, gcc_unused int argc, char *argv[]) +{ + char *playlist = argv[1]; + char *uri = argv[2]; + + bool success; + Error error; + if (uri_has_scheme(uri)) { + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return CommandResult::ERROR; + } + + success = spl_append_uri(uri, playlist, error); + } else + success = search_add_to_playlist(uri, playlist, nullptr, + error); + + if (!success && !error.IsDefined()) { + command_error(client, ACK_ERROR_NO_EXIST, + "directory or file not found"); + return CommandResult::ERROR; + } + + return success ? CommandResult::OK : print_error(client, error); +} + +CommandResult +handle_listplaylists(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + Error error; + const auto list = ListPlaylistFiles(error); + if (list.empty() && error.IsDefined()) + return print_error(client, error); + + print_spl_list(client, list); + return CommandResult::OK; +} diff --git a/src/command/PlaylistCommands.hxx b/src/command/PlaylistCommands.hxx new file mode 100644 index 000000000..786f8b89f --- /dev/null +++ b/src/command/PlaylistCommands.hxx @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2003-2012 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_PLAYLIST_COMMANDS_HXX +#define MPD_PLAYLIST_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; + +CommandResult +handle_save(Client &client, int argc, char *argv[]); + +CommandResult +handle_load(Client &client, int argc, char *argv[]); + +CommandResult +handle_listplaylist(Client &client, int argc, char *argv[]); + +CommandResult +handle_listplaylistinfo(Client &client, int argc, char *argv[]); + +CommandResult +handle_rm(Client &client, int argc, char *argv[]); + +CommandResult +handle_rename(Client &client, int argc, char *argv[]); + +CommandResult +handle_playlistdelete(Client &client, int argc, char *argv[]); + +CommandResult +handle_playlistmove(Client &client, int argc, char *argv[]); + +CommandResult +handle_playlistclear(Client &client, int argc, char *argv[]); + +CommandResult +handle_playlistadd(Client &client, int argc, char *argv[]); + +CommandResult +handle_listplaylists(Client &client, int argc, char *argv[]); + +#endif diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx new file mode 100644 index 000000000..a21eb75f0 --- /dev/null +++ b/src/command/QueueCommands.cxx @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2003-2013 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "QueueCommands.hxx" +#include "CommandError.hxx" +#include "DatabaseQueue.hxx" +#include "SongFilter.hxx" +#include "DatabaseSelection.hxx" +#include "Playlist.hxx" +#include "PlaylistPrint.hxx" +#include "ClientFile.hxx" +#include "Client.hxx" +#include "Partition.hxx" +#include "protocol/ArgParser.hxx" +#include "protocol/Result.hxx" +#include "ls.hxx" +#include "util/UriUtil.hxx" +#include "util/Error.hxx" +#include "fs/AllocatedPath.hxx" + +#include + +#include + +CommandResult +handle_add(Client &client, gcc_unused int argc, char *argv[]) +{ + char *uri = argv[1]; + PlaylistResult result; + + if (memcmp(uri, "file:///", 8) == 0) { + const char *path_utf8 = uri + 7; + const auto path_fs = AllocatedPath::FromUTF8(path_utf8); + + if (path_fs.IsNull()) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported file name"); + return CommandResult::ERROR; + } + + Error error; + if (!client_allow_file(client, path_fs, error)) + return print_error(client, error); + + result = client.partition.AppendFile(path_utf8); + return print_playlist_result(client, result); + } + + if (uri_has_scheme(uri)) { + if (!uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return CommandResult::ERROR; + } + + result = client.partition.AppendURI(uri); + return print_playlist_result(client, result); + } + + const DatabaseSelection selection(uri, true); + Error error; + return AddFromDatabase(client.partition, selection, error) + ? CommandResult::OK + : print_error(client, error); +} + +CommandResult +handle_addid(Client &client, int argc, char *argv[]) +{ + char *uri = argv[1]; + unsigned added_id; + PlaylistResult result; + + if (memcmp(uri, "file:///", 8) == 0) { + const char *path_utf8 = uri + 7; + const auto path_fs = AllocatedPath::FromUTF8(path_utf8); + + if (path_fs.IsNull()) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported file name"); + return CommandResult::ERROR; + } + + Error error; + if (!client_allow_file(client, path_fs, error)) + return print_error(client, error); + + result = client.partition.AppendFile(path_utf8, &added_id); + } else { + if (uri_has_scheme(uri) && !uri_supported_scheme(uri)) { + command_error(client, ACK_ERROR_NO_EXIST, + "unsupported URI scheme"); + return CommandResult::ERROR; + } + + result = client.partition.AppendURI(uri, &added_id); + } + + if (result != PlaylistResult::SUCCESS) + return print_playlist_result(client, result); + + if (argc == 3) { + unsigned to; + if (!check_unsigned(client, &to, argv[2])) + return CommandResult::ERROR; + result = client.partition.MoveId(added_id, to); + if (result != PlaylistResult::SUCCESS) { + CommandResult ret = + print_playlist_result(client, result); + client.partition.DeleteId(added_id); + return ret; + } + } + + client_printf(client, "Id: %u\n", added_id); + return CommandResult::OK; +} + +CommandResult +handle_delete(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned start, end; + + if (!check_range(client, &start, &end, argv[1])) + return CommandResult::ERROR; + + PlaylistResult result = client.partition.DeleteRange(start, end); + return print_playlist_result(client, result); +} + +CommandResult +handle_deleteid(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned id; + + if (!check_unsigned(client, &id, argv[1])) + return CommandResult::ERROR; + + PlaylistResult result = client.partition.DeleteId(id); + return print_playlist_result(client, result); +} + +CommandResult +handle_playlist(Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + playlist_print_uris(client, client.playlist); + return CommandResult::OK; +} + +CommandResult +handle_shuffle(gcc_unused Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + unsigned start = 0, end = client.playlist.queue.GetLength(); + if (argc == 2 && !check_range(client, &start, &end, argv[1])) + return CommandResult::ERROR; + + client.partition.Shuffle(start, end); + return CommandResult::OK; +} + +CommandResult +handle_clear(gcc_unused Client &client, + gcc_unused int argc, gcc_unused char *argv[]) +{ + client.partition.ClearQueue(); + return CommandResult::OK; +} + +CommandResult +handle_plchanges(Client &client, gcc_unused int argc, char *argv[]) +{ + uint32_t version; + + if (!check_uint32(client, &version, argv[1])) + return CommandResult::ERROR; + + playlist_print_changes_info(client, client.playlist, version); + return CommandResult::OK; +} + +CommandResult +handle_plchangesposid(Client &client, gcc_unused int argc, char *argv[]) +{ + uint32_t version; + + if (!check_uint32(client, &version, argv[1])) + return CommandResult::ERROR; + + playlist_print_changes_position(client, client.playlist, version); + return CommandResult::OK; +} + +CommandResult +handle_playlistinfo(Client &client, int argc, char *argv[]) +{ + unsigned start = 0, end = std::numeric_limits::max(); + bool ret; + + if (argc == 2 && !check_range(client, &start, &end, argv[1])) + return CommandResult::ERROR; + + ret = playlist_print_info(client, client.playlist, start, end); + if (!ret) + return print_playlist_result(client, + PlaylistResult::BAD_RANGE); + + return CommandResult::OK; +} + +CommandResult +handle_playlistid(Client &client, int argc, char *argv[]) +{ + if (argc >= 2) { + unsigned id; + if (!check_unsigned(client, &id, argv[1])) + return CommandResult::ERROR; + + bool ret = playlist_print_id(client, client.playlist, id); + if (!ret) + return print_playlist_result(client, + PlaylistResult::NO_SUCH_SONG); + } else { + playlist_print_info(client, client.playlist, + 0, std::numeric_limits::max()); + } + + return CommandResult::OK; +} + +static CommandResult +handle_playlist_match(Client &client, int argc, char *argv[], + bool fold_case) +{ + SongFilter filter; + if (!filter.Parse(argc - 1, argv + 1, fold_case)) { + command_error(client, ACK_ERROR_ARG, "incorrect arguments"); + return CommandResult::ERROR; + } + + playlist_print_find(client, client.playlist, filter); + return CommandResult::OK; +} + +CommandResult +handle_playlistfind(Client &client, int argc, char *argv[]) +{ + return handle_playlist_match(client, argc, argv, false); +} + +CommandResult +handle_playlistsearch(Client &client, int argc, char *argv[]) +{ + return handle_playlist_match(client, argc, argv, true); +} + +CommandResult +handle_prio(Client &client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return CommandResult::ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return CommandResult::ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned start_position, end_position; + if (!check_range(client, &start_position, &end_position, + argv[i])) + return CommandResult::ERROR; + + PlaylistResult result = + client.partition.SetPriorityRange(start_position, + end_position, + priority); + if (result != PlaylistResult::SUCCESS) + return print_playlist_result(client, result); + } + + return CommandResult::OK; +} + +CommandResult +handle_prioid(Client &client, int argc, char *argv[]) +{ + unsigned priority; + + if (!check_unsigned(client, &priority, argv[1])) + return CommandResult::ERROR; + + if (priority > 0xff) { + command_error(client, ACK_ERROR_ARG, + "Priority out of range: %s", argv[1]); + return CommandResult::ERROR; + } + + for (int i = 2; i < argc; ++i) { + unsigned song_id; + if (!check_unsigned(client, &song_id, argv[i])) + return CommandResult::ERROR; + + PlaylistResult result = + client.partition.SetPriorityId(song_id, priority); + if (result != PlaylistResult::SUCCESS) + return print_playlist_result(client, result); + } + + return CommandResult::OK; +} + +CommandResult +handle_move(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned start, end; + int to; + + if (!check_range(client, &start, &end, argv[1])) + return CommandResult::ERROR; + if (!check_int(client, &to, argv[2])) + return CommandResult::ERROR; + + PlaylistResult result = + client.partition.MoveRange(start, end, to); + return print_playlist_result(client, result); +} + +CommandResult +handle_moveid(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned id; + int to; + + if (!check_unsigned(client, &id, argv[1])) + return CommandResult::ERROR; + if (!check_int(client, &to, argv[2])) + return CommandResult::ERROR; + PlaylistResult result = client.partition.MoveId(id, to); + return print_playlist_result(client, result); +} + +CommandResult +handle_swap(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned song1, song2; + + if (!check_unsigned(client, &song1, argv[1])) + return CommandResult::ERROR; + if (!check_unsigned(client, &song2, argv[2])) + return CommandResult::ERROR; + + PlaylistResult result = + client.partition.SwapPositions(song1, song2); + return print_playlist_result(client, result); +} + +CommandResult +handle_swapid(Client &client, gcc_unused int argc, char *argv[]) +{ + unsigned id1, id2; + + if (!check_unsigned(client, &id1, argv[1])) + return CommandResult::ERROR; + if (!check_unsigned(client, &id2, argv[2])) + return CommandResult::ERROR; + + PlaylistResult result = client.partition.SwapIds(id1, id2); + return print_playlist_result(client, result); +} diff --git a/src/command/QueueCommands.hxx b/src/command/QueueCommands.hxx new file mode 100644 index 000000000..3beb28e52 --- /dev/null +++ b/src/command/QueueCommands.hxx @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2003-2012 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_QUEUE_COMMANDS_HXX +#define MPD_QUEUE_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; + +CommandResult +handle_add(Client &client, int argc, char *argv[]); + +CommandResult +handle_addid(Client &client, int argc, char *argv[]); + +CommandResult +handle_delete(Client &client, int argc, char *argv[]); + +CommandResult +handle_deleteid(Client &client, int argc, char *argv[]); + +CommandResult +handle_playlist(Client &client, int argc, char *argv[]); + +CommandResult +handle_shuffle(Client &client, int argc, char *argv[]); + +CommandResult +handle_clear(Client &client, int argc, char *argv[]); + +CommandResult +handle_plchanges(Client &client, int argc, char *argv[]); + +CommandResult +handle_plchangesposid(Client &client, int argc, char *argv[]); + +CommandResult +handle_playlistinfo(Client &client, int argc, char *argv[]); + +CommandResult +handle_playlistid(Client &client, int argc, char *argv[]); + +CommandResult +handle_playlistfind(Client &client, int argc, char *argv[]); + +CommandResult +handle_playlistsearch(Client &client, int argc, char *argv[]); + +CommandResult +handle_prio(Client &client, int argc, char *argv[]); + +CommandResult +handle_prioid(Client &client, int argc, char *argv[]); + +CommandResult +handle_move(Client &client, int argc, char *argv[]); + +CommandResult +handle_moveid(Client &client, int argc, char *argv[]); + +CommandResult +handle_swap(Client &client, int argc, char *argv[]); + +CommandResult +handle_swapid(Client &client, int argc, char *argv[]); + +#endif diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx new file mode 100644 index 000000000..cbd371c8f --- /dev/null +++ b/src/command/StickerCommands.cxx @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" +#include "StickerCommands.hxx" +#include "SongPrint.hxx" +#include "DatabaseLock.hxx" +#include "DatabasePlugin.hxx" +#include "DatabaseGlue.hxx" +#include "DatabaseSimple.hxx" +#include "SongSticker.hxx" +#include "StickerPrint.hxx" +#include "StickerDatabase.hxx" +#include "CommandError.hxx" +#include "protocol/Result.hxx" +#include "util/Error.hxx" + +#include + +#include + +struct sticker_song_find_data { + Client &client; + const char *name; +}; + +static void +sticker_song_find_print_cb(Song &song, const char *value, + void *user_data) +{ + 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); +} + +static CommandResult +handle_sticker_song(Client &client, int argc, char *argv[]) +{ + Error error; + const Database *db = GetDatabase(error); + if (db == nullptr) + return print_error(client, error); + + /* get song song_id key */ + if (argc == 5 && strcmp(argv[1], "get") == 0) { + Song *song = db->GetSong(argv[3], error); + if (song == nullptr) + return print_error(client, error); + + const auto value = sticker_song_get_value(song, argv[4]); + db->ReturnSong(song); + if (value.empty()) { + command_error(client, ACK_ERROR_NO_EXIST, + "no such sticker"); + return CommandResult::ERROR; + } + + sticker_print_value(client, argv[4], value.c_str()); + + return CommandResult::OK; + /* list song song_id */ + } else if (argc == 4 && strcmp(argv[1], "list") == 0) { + Song *song = db->GetSong(argv[3], error); + if (song == nullptr) + return print_error(client, error); + + sticker *sticker = sticker_song_get(song); + db->ReturnSong(song); + if (sticker) { + sticker_print(client, *sticker); + sticker_free(sticker); + } + + return CommandResult::OK; + /* set song song_id id key */ + } else if (argc == 6 && strcmp(argv[1], "set") == 0) { + Song *song = db->GetSong(argv[3], error); + if (song == nullptr) + return print_error(client, error); + + bool ret = sticker_song_set_value(song, argv[4], argv[5]); + db->ReturnSong(song); + if (!ret) { + command_error(client, 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) { + Song *song = db->GetSong(argv[3], error); + if (song == nullptr) + return print_error(client, error); + + bool ret = argc == 4 + ? sticker_song_delete(song) + : sticker_song_delete_value(song, argv[4]); + db->ReturnSong(song); + if (!ret) { + command_error(client, 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) { + /* "sticker find song a/directory name" */ + bool success; + struct sticker_song_find_data data = { + client, + argv[4], + }; + + db_lock(); + Directory *directory = db_get_directory(argv[3]); + if (directory == nullptr) { + db_unlock(); + command_error(client, ACK_ERROR_NO_EXIST, + "no such directory"); + return CommandResult::ERROR; + } + + success = sticker_song_find(*directory, data.name, + sticker_song_find_print_cb, &data); + db_unlock(); + if (!success) { + command_error(client, ACK_ERROR_SYSTEM, + "failed to set search sticker database"); + return CommandResult::ERROR; + } + + return CommandResult::OK; + } else { + command_error(client, ACK_ERROR_ARG, "bad request"); + return CommandResult::ERROR; + } +} + +CommandResult +handle_sticker(Client &client, int argc, char *argv[]) +{ + assert(argc >= 4); + + if (!sticker_enabled()) { + command_error(client, ACK_ERROR_UNKNOWN, + "sticker database is disabled"); + return CommandResult::ERROR; + } + + if (strcmp(argv[2], "song") == 0) + return handle_sticker_song(client, argc, argv); + else { + command_error(client, ACK_ERROR_ARG, + "unknown sticker domain"); + return CommandResult::ERROR; + } +} diff --git a/src/command/StickerCommands.hxx b/src/command/StickerCommands.hxx new file mode 100644 index 000000000..fb91a8ad3 --- /dev/null +++ b/src/command/StickerCommands.hxx @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2003-2012 The Music Player Daemon Project + * http://www.musicpd.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPD_STICKER_COMMANDS_HXX +#define MPD_STICKER_COMMANDS_HXX + +#include "CommandResult.hxx" + +class Client; + +CommandResult +handle_sticker(Client &client, int argc, char *argv[]); + +#endif -- cgit v1.2.3