aboutsummaryrefslogtreecommitdiffstats
path: root/src/command
diff options
context:
space:
mode:
authorMax Kellermann <max@duempel.org>2013-10-20 13:41:04 +0200
committerMax Kellermann <max@duempel.org>2013-10-20 13:41:04 +0200
commit5ec843dcc89737bb2ca9e428415d9a052c5a753d (patch)
tree17fea4f30c52810ece404200698f1975610f2c3b /src/command
parenta78b2d84ed7c2a82c69c56125036e70b009a87b0 (diff)
downloadmpd-5ec843dcc89737bb2ca9e428415d9a052c5a753d.tar.gz
mpd-5ec843dcc89737bb2ca9e428415d9a052c5a753d.tar.xz
mpd-5ec843dcc89737bb2ca9e428415d9a052c5a753d.zip
*Commands: move to src/command/
Diffstat (limited to 'src/command')
-rw-r--r--src/command/AllCommands.cxx375
-rw-r--r--src/command/AllCommands.hxx34
-rw-r--r--src/command/CommandError.cxx124
-rw-r--r--src/command/CommandError.hxx38
-rw-r--r--src/command/CommandListBuilder.cxx43
-rw-r--r--src/command/CommandListBuilder.hxx109
-rw-r--r--src/command/CommandResult.hxx61
-rw-r--r--src/command/DatabaseCommands.cxx221
-rw-r--r--src/command/DatabaseCommands.hxx57
-rw-r--r--src/command/MessageCommands.cxx137
-rw-r--r--src/command/MessageCommands.hxx42
-rw-r--r--src/command/OtherCommands.cxx315
-rw-r--r--src/command/OtherCommands.hxx69
-rw-r--r--src/command/OutputCommands.cxx90
-rw-r--r--src/command/OutputCommands.hxx39
-rw-r--r--src/command/PlayerCommands.cxx388
-rw-r--r--src/command/PlayerCommands.hxx90
-rw-r--r--src/command/PlaylistCommands.cxx222
-rw-r--r--src/command/PlaylistCommands.hxx60
-rw-r--r--src/command/QueueCommands.cxx391
-rw-r--r--src/command/QueueCommands.hxx84
-rw-r--r--src/command/StickerCommands.cxx178
-rw-r--r--src/command/StickerCommands.hxx30
23 files changed, 3197 insertions, 0 deletions
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 <assert.h>
+#include <string.h>
+
+/*
+ * 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 <glib.h>
+
+#include <assert.h>
+#include <errno.h>
+
+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 <string.h>
+
+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 <list>
+#include <string>
+
+#include <assert.h>
+
+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<std::string> 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<std::string> &&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 <windows.h>
+/* 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 <assert.h>
+#include <string.h>
+
+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 <set>
+#include <string>
+
+#include <assert.h>
+
+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<std::string> 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 <glib.h>
+
+#include <assert.h>
+#include <string.h>
+
+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 <string.h>
+
+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 <assert.h>
+#include <stdlib.h>
+
+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 <limits>
+
+#include <string.h>
+
+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<unsigned>::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<unsigned>::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 <glib.h>
+
+#include <string.h>
+
+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