aboutsummaryrefslogtreecommitdiffstats
path: root/src/command
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/command/AllCommands.cxx43
-rw-r--r--src/command/AllCommands.hxx2
-rw-r--r--src/command/CommandError.cxx17
-rw-r--r--src/command/CommandError.hxx2
-rw-r--r--src/command/CommandListBuilder.cxx4
-rw-r--r--src/command/CommandListBuilder.hxx2
-rw-r--r--src/command/CommandResult.hxx2
-rw-r--r--src/command/DatabaseCommands.cxx47
-rw-r--r--src/command/DatabaseCommands.hxx5
-rw-r--r--src/command/FileCommands.cxx177
-rw-r--r--src/command/FileCommands.hxx5
-rw-r--r--src/command/MessageCommands.cxx13
-rw-r--r--src/command/MessageCommands.hxx2
-rw-r--r--src/command/NeighborCommands.cxx59
-rw-r--r--src/command/NeighborCommands.hxx36
-rw-r--r--src/command/OtherCommands.cxx209
-rw-r--r--src/command/OtherCommands.hxx5
-rw-r--r--src/command/OutputCommands.cxx24
-rw-r--r--src/command/OutputCommands.hxx2
-rw-r--r--src/command/PlayerCommands.cxx31
-rw-r--r--src/command/PlayerCommands.hxx2
-rw-r--r--src/command/PlaylistCommands.cxx55
-rw-r--r--src/command/PlaylistCommands.hxx2
-rw-r--r--src/command/QueueCommands.cxx107
-rw-r--r--src/command/QueueCommands.hxx2
-rw-r--r--src/command/StickerCommands.cxx56
-rw-r--r--src/command/StickerCommands.hxx2
-rw-r--r--src/command/StorageCommands.cxx295
-rw-r--r--src/command/StorageCommands.hxx43
-rw-r--r--src/command/TagCommands.cxx78
-rw-r--r--src/command/TagCommands.hxx33
31 files changed, 1073 insertions, 289 deletions
diff --git a/src/command/AllCommands.cxx b/src/command/AllCommands.cxx
index 36f6fb97c..e28a7a135 100644
--- a/src/command/AllCommands.cxx
+++ b/src/command/AllCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,23 +20,27 @@
#include "config.h"
#include "AllCommands.hxx"
#include "QueueCommands.hxx"
+#include "TagCommands.hxx"
#include "PlayerCommands.hxx"
#include "PlaylistCommands.hxx"
+#include "StorageCommands.hxx"
#include "DatabaseCommands.hxx"
#include "FileCommands.hxx"
#include "OutputCommands.hxx"
#include "MessageCommands.hxx"
+#include "NeighborCommands.hxx"
#include "OtherCommands.hxx"
#include "Permission.hxx"
#include "tag/TagType.h"
#include "protocol/Result.hxx"
-#include "Client.hxx"
+#include "Partition.hxx"
+#include "client/Client.hxx"
#include "util/Tokenizer.hxx"
#include "util/Error.hxx"
#ifdef ENABLE_SQLITE
#include "StickerCommands.hxx"
-#include "StickerDatabase.hxx"
+#include "sticker/StickerDatabase.hxx"
#endif
#include <assert.h>
@@ -74,14 +78,18 @@ handle_not_commands(Client &client, int argc, char *argv[]);
static const struct command commands[] = {
{ "add", PERMISSION_ADD, 1, 1, handle_add },
{ "addid", PERMISSION_ADD, 1, 2, handle_addid },
+ { "addtagid", PERMISSION_ADD, 3, 3, handle_addtagid },
{ "channels", PERMISSION_READ, 0, 0, handle_channels },
{ "clear", PERMISSION_CONTROL, 0, 0, handle_clear },
{ "clearerror", PERMISSION_CONTROL, 0, 0, handle_clearerror },
+ { "cleartagid", PERMISSION_ADD, 1, 2, handle_cleartagid },
{ "close", PERMISSION_NONE, -1, -1, handle_close },
{ "commands", PERMISSION_NONE, 0, 0, handle_commands },
{ "config", PERMISSION_ADMIN, 0, 0, handle_config },
{ "consume", PERMISSION_CONTROL, 1, 1, handle_consume },
+#ifdef ENABLE_DATABASE
{ "count", PERMISSION_READ, 2, -1, handle_count },
+#endif
{ "crossfade", PERMISSION_CONTROL, 1, 1, handle_crossfade },
{ "currentsong", PERMISSION_READ, 0, 0, handle_currentsong },
{ "decoders", PERMISSION_READ, 0, 0, handle_decoders },
@@ -89,13 +97,24 @@ static const struct command commands[] = {
{ "deleteid", PERMISSION_CONTROL, 1, 1, handle_deleteid },
{ "disableoutput", PERMISSION_ADMIN, 1, 1, handle_disableoutput },
{ "enableoutput", PERMISSION_ADMIN, 1, 1, handle_enableoutput },
+#ifdef ENABLE_DATABASE
{ "find", PERMISSION_READ, 2, -1, handle_find },
{ "findadd", PERMISSION_ADD, 2, -1, handle_findadd},
+#endif
{ "idle", PERMISSION_READ, 0, -1, handle_idle },
{ "kill", PERMISSION_ADMIN, -1, -1, handle_kill },
+#ifdef ENABLE_DATABASE
{ "list", PERMISSION_READ, 1, -1, handle_list },
{ "listall", PERMISSION_READ, 0, 1, handle_listall },
{ "listallinfo", PERMISSION_READ, 0, 1, handle_listallinfo },
+#endif
+ { "listfiles", PERMISSION_READ, 0, 1, handle_listfiles },
+#ifdef ENABLE_DATABASE
+ { "listmounts", PERMISSION_READ, 0, 0, handle_listmounts },
+#endif
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ { "listneighbors", PERMISSION_READ, 0, 0, handle_listneighbors },
+#endif
{ "listplaylist", PERMISSION_READ, 1, 1, handle_listplaylist },
{ "listplaylistinfo", PERMISSION_READ, 1, 1, handle_listplaylistinfo },
{ "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists },
@@ -103,6 +122,9 @@ static const struct command commands[] = {
{ "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo },
{ "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb },
{ "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay },
+#ifdef ENABLE_DATABASE
+ { "mount", PERMISSION_ADMIN, 2, 2, handle_mount },
+#endif
{ "move", PERMISSION_CONTROL, 2, 2, handle_move },
{ "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid },
{ "next", PERMISSION_CONTROL, 0, 0, handle_next },
@@ -139,9 +161,11 @@ static const struct command commands[] = {
{ "rescan", PERMISSION_CONTROL, 0, 1, handle_rescan },
{ "rm", PERMISSION_CONTROL, 1, 1, handle_rm },
{ "save", PERMISSION_CONTROL, 1, 1, handle_save },
+#ifdef ENABLE_DATABASE
{ "search", PERMISSION_READ, 2, -1, handle_search },
{ "searchadd", PERMISSION_ADD, 2, -1, handle_searchadd },
{ "searchaddpl", PERMISSION_CONTROL, 3, -1, handle_searchaddpl },
+#endif
{ "seek", PERMISSION_CONTROL, 2, 2, handle_seek },
{ "seekcur", PERMISSION_CONTROL, 1, 1, handle_seekcur },
{ "seekid", PERMISSION_CONTROL, 2, 2, handle_seekid },
@@ -160,6 +184,9 @@ static const struct command commands[] = {
{ "swapid", PERMISSION_CONTROL, 2, 2, handle_swapid },
{ "tagtypes", PERMISSION_READ, 0, 0, handle_tagtypes },
{ "toggleoutput", PERMISSION_ADMIN, 1, 1, handle_toggleoutput },
+#ifdef ENABLE_DATABASE
+ { "unmount", PERMISSION_ADMIN, 1, 1, handle_unmount },
+#endif
{ "unsubscribe", PERMISSION_READ, 1, 1, handle_unsubscribe },
{ "update", PERMISSION_CONTROL, 0, 1, handle_update },
{ "urlhandlers", PERMISSION_READ, 0, 0, handle_urlhandlers },
@@ -169,13 +196,19 @@ static const struct command commands[] = {
static const unsigned num_commands = sizeof(commands) / sizeof(commands[0]);
static bool
-command_available(gcc_unused const struct command *cmd)
+command_available(gcc_unused const Partition &partition,
+ gcc_unused const struct command *cmd)
{
#ifdef ENABLE_SQLITE
if (strcmp(cmd->cmd, "sticker") == 0)
return sticker_enabled();
#endif
+#ifdef ENABLE_NEIGHBOR_PLUGINS
+ if (strcmp(cmd->cmd, "listneighbors") == 0)
+ return neighbor_commands_available(partition.instance);
+#endif
+
return true;
}
@@ -191,7 +224,7 @@ handle_commands(Client &client,
cmd = &commands[i];
if (cmd->permission == (permission & cmd->permission) &&
- command_available(cmd))
+ command_available(client.partition, cmd))
client_printf(client, "command: %s\n", cmd->cmd);
}
diff --git a/src/command/AllCommands.hxx b/src/command/AllCommands.hxx
index 2be94c136..b7834a8de 100644
--- a/src/command/AllCommands.hxx
+++ b/src/command/AllCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/CommandError.cxx b/src/command/CommandError.cxx
index fc14d4a5d..89085fc68 100644
--- a/src/command/CommandError.cxx
+++ b/src/command/CommandError.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,14 +19,13 @@
#include "config.h"
#include "CommandError.hxx"
-#include "DatabaseError.hxx"
+#include "db/DatabaseError.hxx"
#include "protocol/Result.hxx"
#include "util/Error.hxx"
#include "Log.hxx"
-#include <glib.h>
-
#include <assert.h>
+#include <string.h>
#include <errno.h>
CommandResult
@@ -38,7 +37,7 @@ print_playlist_result(Client &client, PlaylistResult result)
case PlaylistResult::ERRNO:
command_error(client, ACK_ERROR_SYSTEM, "%s",
- g_strerror(errno));
+ strerror(errno));
return CommandResult::ERROR;
case PlaylistResult::DENIED:
@@ -102,6 +101,7 @@ print_error(Client &client, const Error &error)
command_error(client, (ack)error.GetCode(),
"%s", error.GetMessage());
return CommandResult::ERROR;
+#ifdef ENABLE_DATABASE
} else if (error.IsDomain(db_domain)) {
switch ((enum db_error)error.GetCode()) {
case DB_DISABLED:
@@ -112,10 +112,15 @@ print_error(Client &client, const Error &error)
case DB_NOT_FOUND:
command_error(client, ACK_ERROR_NO_EXIST, "Not found");
return CommandResult::ERROR;
+
+ case DB_CONFLICT:
+ command_error(client, ACK_ERROR_ARG, "Conflict");
+ return CommandResult::ERROR;
}
+#endif
} else if (error.IsDomain(errno_domain)) {
command_error(client, ACK_ERROR_SYSTEM, "%s",
- g_strerror(error.GetCode()));
+ strerror(error.GetCode()));
return CommandResult::ERROR;
}
diff --git a/src/command/CommandError.hxx b/src/command/CommandError.hxx
index c7d3fff76..b48baa5bf 100644
--- a/src/command/CommandError.hxx
+++ b/src/command/CommandError.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/CommandListBuilder.cxx b/src/command/CommandListBuilder.cxx
index cc10f7205..1dcbf2946 100644
--- a/src/command/CommandListBuilder.cxx
+++ b/src/command/CommandListBuilder.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,7 +19,7 @@
#include "config.h"
#include "CommandListBuilder.hxx"
-#include "ClientInternal.hxx"
+#include "client/ClientInternal.hxx"
#include <string.h>
diff --git a/src/command/CommandListBuilder.hxx b/src/command/CommandListBuilder.hxx
index a112ac33b..0747c4697 100644
--- a/src/command/CommandListBuilder.hxx
+++ b/src/command/CommandListBuilder.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/CommandResult.hxx b/src/command/CommandResult.hxx
index 9916b70cb..a2e968fb6 100644
--- a/src/command/CommandResult.hxx
+++ b/src/command/CommandResult.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/DatabaseCommands.cxx b/src/command/DatabaseCommands.cxx
index b86cbdae7..96ea357bc 100644
--- a/src/command/DatabaseCommands.cxx
+++ b/src/command/DatabaseCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,36 +19,42 @@
#include "config.h"
#include "DatabaseCommands.hxx"
-#include "DatabaseQueue.hxx"
-#include "DatabasePlaylist.hxx"
-#include "DatabasePrint.hxx"
-#include "DatabaseSelection.hxx"
+#include "db/DatabaseGlue.hxx"
+#include "db/DatabaseQueue.hxx"
+#include "db/DatabasePlaylist.hxx"
+#include "db/DatabasePrint.hxx"
+#include "db/Selection.hxx"
#include "CommandError.hxx"
-#include "Client.hxx"
+#include "client/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_listfiles_db(Client &client, const char *uri)
+{
+ const DatabaseSelection selection(uri, false);
+
+ Error error;
+ if (!db_selection_print(client, selection, false, true, error))
+ return print_error(client, error);
+
+ return CommandResult::OK;
+}
CommandResult
handle_lsinfo2(Client &client, int argc, char *argv[])
{
- const char *uri;
-
- if (argc == 2)
- uri = argv[1];
- else
+ const char *const uri = argc == 2
+ ? argv[1]
/* default is root directory */
- uri = "";
+ : "";
const DatabaseSelection selection(uri, false);
Error error;
- if (!db_selection_print(client, selection, true, error))
+ if (!db_selection_print(client, selection, true, false, error))
return print_error(client, error);
return CommandResult::OK;
@@ -66,7 +72,7 @@ handle_match(Client &client, int argc, char *argv[], bool fold_case)
const DatabaseSelection selection("", true, &filter);
Error error;
- return db_selection_print(client, selection, true, error)
+ return db_selection_print(client, selection, true, false, error)
? CommandResult::OK
: print_error(client, error);
}
@@ -123,7 +129,12 @@ handle_searchaddpl(Client &client, int argc, char *argv[])
}
Error error;
- return search_add_to_playlist("", playlist, &filter, error)
+ const Database *db = client.GetDatabase(error);
+ if (db == nullptr)
+ return print_error(client, error);
+
+ return search_add_to_playlist(*db, *client.GetStorage(),
+ "", playlist, &filter, error)
? CommandResult::OK
: print_error(client, error);
}
diff --git a/src/command/DatabaseCommands.hxx b/src/command/DatabaseCommands.hxx
index 76b2ba817..7a9c68ffe 100644
--- a/src/command/DatabaseCommands.hxx
+++ b/src/command/DatabaseCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,9 @@
class Client;
CommandResult
+handle_listfiles_db(Client &client, const char *uri);
+
+CommandResult
handle_lsinfo2(Client &client, int argc, char *argv[]);
CommandResult
diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx
index eecc3102f..7b69b0bd9 100644
--- a/src/command/FileCommands.cxx
+++ b/src/command/FileCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,23 +17,108 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#define __STDC_FORMAT_MACROS /* for PRIu64 */
+
#include "config.h"
#include "FileCommands.hxx"
#include "CommandError.hxx"
#include "protocol/Ack.hxx"
#include "protocol/Result.hxx"
-#include "ClientFile.hxx"
-#include "Client.hxx"
+#include "client/Client.hxx"
#include "util/CharUtil.hxx"
+#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "tag/TagHandler.hxx"
#include "tag/ApeTag.hxx"
#include "tag/TagId3.hxx"
+#include "TagStream.hxx"
#include "TagFile.hxx"
-#include "Mapper.hxx"
+#include "storage/StorageInterface.hxx"
#include "fs/AllocatedPath.hxx"
+#include "fs/FileSystem.hxx"
+#include "TimePrint.hxx"
+#include "ls.hxx"
#include <assert.h>
+#include <sys/stat.h>
+#include <inttypes.h> /* for PRIu64 */
+
+gcc_pure
+static bool
+SkipNameFS(const char *name_fs)
+{
+ return name_fs[0] == '.' &&
+ (name_fs[1] == 0 ||
+ (name_fs[1] == '.' && name_fs[2] == 0));
+}
+
+gcc_pure
+static bool
+skip_path(const char *name_fs)
+{
+ return strchr(name_fs, '\n') != nullptr;
+}
+
+#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
+/* PRIu64 causes bogus compiler warning */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+#pragma GCC diagnostic ignored "-Wformat-extra-args"
+#endif
+
+CommandResult
+handle_listfiles_local(Client &client, const char *path_utf8)
+{
+ const auto path_fs = AllocatedPath::FromUTF8(path_utf8);
+ if (path_fs.IsNull()) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported file name");
+ return CommandResult::ERROR;
+ }
+
+ Error error;
+ if (!client.AllowFile(path_fs, error))
+ return print_error(client, error);
+
+ DirectoryReader reader(path_fs);
+ if (reader.HasFailed()) {
+ error.FormatErrno("Failed to open '%s'", path_utf8);
+ return print_error(client, error);
+ }
+
+ while (reader.ReadEntry()) {
+ const Path name_fs = reader.GetEntry();
+ if (SkipNameFS(name_fs.c_str()) || skip_path(name_fs.c_str()))
+ continue;
+
+ std::string name_utf8 = name_fs.ToUTF8();
+ if (name_utf8.empty())
+ continue;
+
+ const AllocatedPath full_fs =
+ AllocatedPath::Build(path_fs, name_fs);
+ struct stat st;
+ if (!StatFile(full_fs, st, false))
+ continue;
+
+ if (S_ISREG(st.st_mode)) {
+ client_printf(client, "file: %s\n"
+ "size: %" PRIu64 "\n",
+ name_utf8.c_str(),
+ uint64_t(st.st_size));
+ } else if (S_ISDIR(st.st_mode))
+ client_printf(client, "directory: %s\n",
+ name_utf8.c_str());
+
+ time_print(client, "Last-Modified", st.st_mtime);
+ }
+
+ return CommandResult::OK;
+}
+
+#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
+#pragma GCC diagnostic pop
+#endif
gcc_pure
static bool
@@ -80,6 +165,41 @@ static constexpr tag_handler print_comment_handler = {
print_pair,
};
+static CommandResult
+read_stream_comments(Client &client, const char *uri)
+{
+ if (!uri_supported_scheme(uri)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported URI scheme");
+ return CommandResult::ERROR;
+ }
+
+ if (!tag_stream_scan(uri, print_comment_handler, &client)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "Failed to load file");
+ return CommandResult::ERROR;
+ }
+
+ return CommandResult::OK;
+
+}
+
+static CommandResult
+read_file_comments(Client &client, const Path path_fs)
+{
+ if (!tag_file_scan(path_fs, print_comment_handler, &client)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "Failed to load file");
+ return CommandResult::ERROR;
+ }
+
+ tag_ape_scan2(path_fs, &print_comment_handler, &client);
+ tag_id3_scan(path_fs, &print_comment_handler, &client);
+
+ return CommandResult::OK;
+
+}
+
CommandResult
handle_read_comments(Client &client, gcc_unused int argc, char *argv[])
{
@@ -87,12 +207,10 @@ handle_read_comments(Client &client, gcc_unused int argc, char *argv[])
const char *const uri = argv[1];
- AllocatedPath path_fs = AllocatedPath::Null();
-
if (memcmp(uri, "file:///", 8) == 0) {
/* read comments from arbitrary local file */
const char *path_utf8 = uri + 7;
- path_fs = AllocatedPath::FromUTF8(path_utf8);
+ AllocatedPath path_fs = AllocatedPath::FromUTF8(path_utf8);
if (path_fs.IsNull()) {
command_error(client, ACK_ERROR_NO_EXIST,
"unsupported file name");
@@ -100,28 +218,41 @@ handle_read_comments(Client &client, gcc_unused int argc, char *argv[])
}
Error error;
- if (!client_allow_file(client, path_fs, error))
+ if (!client.AllowFile(path_fs, error))
return print_error(client, error);
- } else if (*uri != '/') {
- path_fs = map_uri_fs(uri);
- if (path_fs.IsNull()) {
+
+ return read_file_comments(client, path_fs);
+ } else if (uri_has_scheme(uri)) {
+ return read_stream_comments(client, uri);
+ } else if (!PathTraitsUTF8::IsAbsolute(uri)) {
+#ifdef ENABLE_DATABASE
+ const Storage *storage = client.GetStorage();
+ if (storage == nullptr) {
+#endif
command_error(client, ACK_ERROR_NO_EXIST,
- "No such file");
+ "No database");
return CommandResult::ERROR;
+#ifdef ENABLE_DATABASE
}
- } else {
+
+ {
+ AllocatedPath path_fs = storage->MapFS(uri);
+ if (!path_fs.IsNull())
+ return read_file_comments(client, path_fs);
+ }
+
+ {
+ const std::string uri2 = storage->MapUTF8(uri);
+ if (uri_has_scheme(uri2.c_str()))
+ return read_stream_comments(client,
+ uri2.c_str());
+ }
+
command_error(client, ACK_ERROR_NO_EXIST, "No such file");
return CommandResult::ERROR;
- }
-
- if (!tag_file_scan(path_fs, &print_comment_handler, &client)) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "Failed to load file");
+#endif
+ } else {
+ command_error(client, ACK_ERROR_NO_EXIST, "No such file");
return CommandResult::ERROR;
}
-
- tag_ape_scan2(path_fs, &print_comment_handler, &client);
- tag_id3_scan(path_fs, &print_comment_handler, &client);
-
- return CommandResult::OK;
}
diff --git a/src/command/FileCommands.hxx b/src/command/FileCommands.hxx
index e184d6e30..51467a009 100644
--- a/src/command/FileCommands.hxx
+++ b/src/command/FileCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -25,6 +25,9 @@
class Client;
CommandResult
+handle_listfiles_local(Client &client, const char *path_utf8);
+
+CommandResult
handle_read_comments(Client &client, int argc, char *argv[]);
#endif
diff --git a/src/command/MessageCommands.cxx b/src/command/MessageCommands.cxx
index 7d9893e70..fe7500aaf 100644
--- a/src/command/MessageCommands.cxx
+++ b/src/command/MessageCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,12 +19,11 @@
#include "config.h"
#include "MessageCommands.hxx"
-#include "Client.hxx"
-#include "ClientList.hxx"
+#include "client/Client.hxx"
+#include "client/ClientList.hxx"
#include "Instance.hxx"
-#include "Main.hxx"
+#include "Partition.hxx"
#include "protocol/Result.hxx"
-#include "protocol/ArgParser.hxx"
#include <set>
#include <string>
@@ -82,7 +81,7 @@ handle_channels(Client &client,
assert(argc == 1);
std::set<std::string> channels;
- for (const auto &c : *instance->client_list)
+ for (const auto &c : *client.partition.instance.client_list)
channels.insert(c->subscriptions.begin(),
c->subscriptions.end());
@@ -123,7 +122,7 @@ handle_send_message(Client &client,
bool sent = false;
const ClientMessage msg(argv[1], argv[2]);
- for (const auto &c : *instance->client_list)
+ for (const auto &c : *client.partition.instance.client_list)
if (c->PushMessage(msg))
sent = true;
diff --git a/src/command/MessageCommands.hxx b/src/command/MessageCommands.hxx
index 6a107f69d..2edc9e816 100644
--- a/src/command/MessageCommands.hxx
+++ b/src/command/MessageCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/NeighborCommands.cxx b/src/command/NeighborCommands.cxx
new file mode 100644
index 000000000..1171f6424
--- /dev/null
+++ b/src/command/NeighborCommands.cxx
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "NeighborCommands.hxx"
+#include "client/Client.hxx"
+#include "Instance.hxx"
+#include "Partition.hxx"
+#include "protocol/Result.hxx"
+#include "neighbor/Glue.hxx"
+#include "neighbor/Info.hxx"
+
+#include <set>
+#include <string>
+
+#include <assert.h>
+
+bool
+neighbor_commands_available(const Instance &instance)
+{
+ return instance.neighbors != nullptr;
+}
+
+CommandResult
+handle_listneighbors(Client &client,
+ gcc_unused int argc, gcc_unused char *argv[])
+{
+ const NeighborGlue *const neighbors =
+ client.partition.instance.neighbors;
+ if (neighbors == nullptr) {
+ command_error(client, ACK_ERROR_UNKNOWN,
+ "No neighbor plugin configured");
+ return CommandResult::ERROR;
+ }
+
+ for (const auto &i : neighbors->GetList())
+ client_printf(client,
+ "neighbor: %s\n"
+ "name: %s\n",
+ i.uri.c_str(),
+ i.display_name.c_str());
+ return CommandResult::OK;
+}
diff --git a/src/command/NeighborCommands.hxx b/src/command/NeighborCommands.hxx
new file mode 100644
index 000000000..64a7fe120
--- /dev/null
+++ b/src/command/NeighborCommands.hxx
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_NEIGHBOR_COMMANDS_HXX
+#define MPD_NEIGHBOR_COMMANDS_HXX
+
+#include "CommandResult.hxx"
+#include "Compiler.h"
+
+struct Instance;
+class Client;
+
+gcc_pure
+bool
+neighbor_commands_available(const Instance &instance);
+
+CommandResult
+handle_listneighbors(Client &client, int argc, char *argv[]);
+
+#endif
diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx
index 7b2cb1331..eac26735b 100644
--- a/src/command/OtherCommands.cxx
+++ b/src/command/OtherCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,33 +19,37 @@
#include "config.h"
#include "OtherCommands.hxx"
-#include "DatabaseCommands.hxx"
+#include "FileCommands.hxx"
+#include "StorageCommands.hxx"
#include "CommandError.hxx"
-#include "UpdateGlue.hxx"
-#include "Directory.hxx"
-#include "Song.hxx"
+#include "db/Uri.hxx"
+#include "storage/StorageInterface.hxx"
+#include "DetachedSong.hxx"
#include "SongPrint.hxx"
#include "TagPrint.hxx"
+#include "TagStream.hxx"
+#include "tag/TagHandler.hxx"
#include "TimePrint.hxx"
-#include "Mapper.hxx"
-#include "DecoderPrint.hxx"
+#include "decoder/DecoderPrint.hxx"
#include "protocol/ArgParser.hxx"
#include "protocol/Result.hxx"
#include "ls.hxx"
-#include "Volume.hxx"
-#include "util/ASCII.hxx"
+#include "mixer/Volume.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
#include "fs/AllocatedPath.hxx"
#include "Stats.hxx"
#include "Permission.hxx"
#include "PlaylistFile.hxx"
-#include "ClientFile.hxx"
-#include "Client.hxx"
+#include "db/PlaylistVector.hxx"
+#include "client/Client.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
#include "Idle.hxx"
-#ifdef ENABLE_SQLITE
-#include "StickerDatabase.hxx"
+#ifdef ENABLE_DATABASE
+#include "DatabaseCommands.hxx"
+#include "db/update/Service.hxx"
#endif
#include <assert.h>
@@ -102,16 +106,62 @@ handle_close(gcc_unused Client &client,
return CommandResult::FINISH;
}
+static void
+print_tag(TagType type, const char *value, void *ctx)
+{
+ Client &client = *(Client *)ctx;
+
+ tag_print(client, type, value);
+}
+
CommandResult
-handle_lsinfo(Client &client, int argc, char *argv[])
+handle_listfiles(Client &client, int argc, char *argv[])
{
- const char *uri;
+ const char *const uri = argc == 2
+ ? argv[1]
+ /* default is root directory */
+ : "";
+
+ if (memcmp(uri, "file:///", 8) == 0)
+ /* list local directory */
+ return handle_listfiles_local(client, uri + 7);
+
+#ifdef ENABLE_DATABASE
+ if (uri_has_scheme(uri))
+ /* use storage plugin to list remote directory */
+ return handle_listfiles_storage(client, uri);
+
+ /* must be a path relative to the configured
+ music_directory */
+
+ if (client.partition.instance.storage != nullptr)
+ /* if we have a storage instance, obtain a list of
+ files from it */
+ return handle_listfiles_storage(client,
+ *client.partition.instance.storage,
+ uri);
+
+ /* fall back to entries from database if we have no storage */
+ return handle_listfiles_db(client, uri);
+#else
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+#endif
+}
- if (argc == 2)
- uri = argv[1];
- else
+static constexpr tag_handler print_tag_handler = {
+ nullptr,
+ print_tag,
+ nullptr,
+};
+
+CommandResult
+handle_lsinfo(Client &client, int argc, char *argv[])
+{
+ const char *const uri = argc == 2
+ ? argv[1]
/* default is root directory */
- uri = "";
+ : "";
if (memcmp(uri, "file:///", 8) == 0) {
/* print information about an arbitrary local file */
@@ -125,39 +175,61 @@ handle_lsinfo(Client &client, int argc, char *argv[])
}
Error error;
- if (!client_allow_file(client, path_fs, error))
+ if (!client.AllowFile(path_fs, error))
return print_error(client, error);
- Song *song = Song::LoadFile(path_utf8, nullptr);
- if (song == nullptr) {
+ DetachedSong song(path_utf8);
+ if (!song.Update()) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "No such file");
+ return CommandResult::ERROR;
+ }
+
+ song_print_info(client, song);
+ return CommandResult::OK;
+ }
+
+ if (uri_has_scheme(uri)) {
+ if (!uri_supported_scheme(uri)) {
+ command_error(client, ACK_ERROR_NO_EXIST,
+ "unsupported URI scheme");
+ return CommandResult::ERROR;
+ }
+
+ if (!tag_stream_scan(uri, print_tag_handler, &client)) {
command_error(client, ACK_ERROR_NO_EXIST,
"No such file");
return CommandResult::ERROR;
}
- song_print_info(client, *song);
- song->Free();
return CommandResult::OK;
}
+#ifdef ENABLE_DATABASE
CommandResult result = handle_lsinfo2(client, argc, argv);
if (result != CommandResult::OK)
return result;
+#endif
if (isRootDirectory(uri)) {
Error error;
const auto &list = ListPlaylistFiles(error);
print_spl_list(client, list);
+ } else {
+#ifndef ENABLE_DATABASE
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+#endif
}
return CommandResult::OK;
}
-CommandResult
-handle_update(Client &client, gcc_unused int argc, char *argv[])
+static CommandResult
+handle_update(Client &client, int argc, char *argv[], bool discard)
{
+#ifdef ENABLE_DATABASE
const char *path = "";
- unsigned ret;
assert(argc <= 2);
if (argc == 2) {
@@ -173,7 +245,13 @@ handle_update(Client &client, gcc_unused int argc, char *argv[])
}
}
- ret = update_enqueue(path, false);
+ UpdateService *update = client.partition.instance.update;
+ if (update == nullptr) {
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+ }
+
+ unsigned ret = update->Enqueue(path, discard);
if (ret > 0) {
client_printf(client, "updating_db: %i\n", ret);
return CommandResult::OK;
@@ -182,34 +260,27 @@ handle_update(Client &client, gcc_unused int argc, char *argv[])
"already updating");
return CommandResult::ERROR;
}
+#else
+ (void)client;
+ (void)argc;
+ (void)argv;
+ (void)discard;
+
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+#endif
}
CommandResult
-handle_rescan(Client &client, gcc_unused int argc, char *argv[])
+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 (!uri_safe_local(path)) {
- command_error(client, ACK_ERROR_ARG,
- "Malformed path");
- return CommandResult::ERROR;
- }
- }
+ return handle_update(client, argc, argv, false);
+}
- 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_rescan(Client &client, gcc_unused int argc, char *argv[])
+{
+ return handle_update(client, argc, argv, true);
}
CommandResult
@@ -226,7 +297,7 @@ handle_setvol(Client &client, gcc_unused int argc, char *argv[])
return CommandResult::ERROR;
}
- success = volume_level_change(level);
+ success = volume_level_change(client.partition.outputs, level);
if (!success) {
command_error(client, ACK_ERROR_SYSTEM,
"problems setting volume");
@@ -248,7 +319,7 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[])
return CommandResult::ERROR;
}
- const int old_volume = volume_level_get();
+ const int old_volume = volume_level_get(client.partition.outputs);
if (old_volume < 0) {
command_error(client, ACK_ERROR_SYSTEM, "No mixer");
return CommandResult::ERROR;
@@ -260,7 +331,8 @@ handle_volume(Client &client, gcc_unused int argc, char *argv[])
else if (new_volume > 100)
new_volume = 100;
- if (new_volume != old_volume && !volume_level_change(new_volume)) {
+ if (new_volume != old_volume &&
+ !volume_level_change(client.partition.outputs, new_volume)) {
command_error(client, ACK_ERROR_SYSTEM,
"problems setting volume");
return CommandResult::ERROR;
@@ -309,9 +381,13 @@ handle_config(Client &client,
return CommandResult::ERROR;
}
- const char *path = mapper_get_music_directory_utf8();
- if (path != nullptr)
- client_printf(client, "music_directory: %s\n", path);
+#ifdef ENABLE_DATABASE
+ const Storage *storage = client.GetStorage();
+ if (storage != nullptr) {
+ const auto path = storage->MapUTF8("");
+ client_printf(client, "music_directory: %s\n", path.c_str());
+ }
+#endif
return CommandResult::OK;
}
@@ -320,20 +396,19 @@ CommandResult
handle_idle(Client &client,
gcc_unused int argc, gcc_unused char *argv[])
{
- unsigned flags = 0, j;
+ unsigned flags = 0;
int i;
- const char *const* idle_names;
- idle_names = idle_get_names();
for (i = 1; i < argc; ++i) {
- if (!argv[i])
- continue;
-
- for (j = 0; idle_names[j]; ++j) {
- if (StringEqualsCaseASCII(argv[i], idle_names[j])) {
- flags |= (1 << j);
- }
+ unsigned event = idle_parse_name(argv[i]);
+ if (event == 0) {
+ command_error(client, ACK_ERROR_ARG,
+ "Unrecognized idle event: %s",
+ argv[i]);
+ return CommandResult::ERROR;
}
+
+ flags |= event;
}
/* No argument means that the client wants to receive everything */
diff --git a/src/command/OtherCommands.hxx b/src/command/OtherCommands.hxx
index 1a0b16ed1..f487e9605 100644
--- a/src/command/OtherCommands.hxx
+++ b/src/command/OtherCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -40,6 +40,9 @@ CommandResult
handle_close(Client &client, int argc, char *argv[]);
CommandResult
+handle_listfiles(Client &client, int argc, char *argv[]);
+
+CommandResult
handle_lsinfo(Client &client, int argc, char *argv[]);
CommandResult
diff --git a/src/command/OutputCommands.cxx b/src/command/OutputCommands.cxx
index e949448af..03058d7ed 100644
--- a/src/command/OutputCommands.cxx
+++ b/src/command/OutputCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,24 +19,21 @@
#include "config.h"
#include "OutputCommands.hxx"
-#include "OutputPrint.hxx"
-#include "OutputCommand.hxx"
+#include "output/OutputPrint.hxx"
+#include "output/OutputCommand.hxx"
#include "protocol/Result.hxx"
#include "protocol/ArgParser.hxx"
-
-#include <string.h>
+#include "client/Client.hxx"
+#include "Partition.hxx"
CommandResult
handle_enableoutput(Client &client, gcc_unused int argc, char *argv[])
{
unsigned device;
- bool ret;
-
if (!check_unsigned(client, &device, argv[1]))
return CommandResult::ERROR;
- ret = audio_output_enable_index(device);
- if (!ret) {
+ if (!audio_output_enable_index(client.partition.outputs, device)) {
command_error(client, ACK_ERROR_NO_EXIST,
"No such audio output");
return CommandResult::ERROR;
@@ -49,13 +46,10 @@ 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) {
+ if (!audio_output_disable_index(client.partition.outputs, device)) {
command_error(client, ACK_ERROR_NO_EXIST,
"No such audio output");
return CommandResult::ERROR;
@@ -71,7 +65,7 @@ handle_toggleoutput(Client &client, gcc_unused int argc, char *argv[])
if (!check_unsigned(client, &device, argv[1]))
return CommandResult::ERROR;
- if (!audio_output_toggle_index(device)) {
+ if (!audio_output_toggle_index(client.partition.outputs, device)) {
command_error(client, ACK_ERROR_NO_EXIST,
"No such audio output");
return CommandResult::ERROR;
@@ -84,7 +78,7 @@ CommandResult
handle_devices(Client &client,
gcc_unused int argc, gcc_unused char *argv[])
{
- printAudioDevices(client);
+ printAudioDevices(client, client.partition.outputs);
return CommandResult::OK;
}
diff --git a/src/command/OutputCommands.hxx b/src/command/OutputCommands.hxx
index a5edc22e2..c12c14049 100644
--- a/src/command/OutputCommands.hxx
+++ b/src/command/OutputCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/PlayerCommands.cxx b/src/command/PlayerCommands.cxx
index 12c71dfd4..748b5b894 100644
--- a/src/command/PlayerCommands.cxx
+++ b/src/command/PlayerCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,18 +20,21 @@
#include "config.h"
#include "PlayerCommands.hxx"
#include "CommandError.hxx"
-#include "Playlist.hxx"
+#include "queue/Playlist.hxx"
#include "PlaylistPrint.hxx"
-#include "UpdateGlue.hxx"
-#include "Client.hxx"
-#include "Volume.hxx"
-#include "OutputAll.hxx"
+#include "client/Client.hxx"
+#include "mixer/Volume.hxx"
#include "Partition.hxx"
+#include "Instance.hxx"
#include "protocol/Result.hxx"
#include "protocol/ArgParser.hxx"
#include "AudioFormat.hxx"
#include "ReplayGainConfig.hxx"
+#ifdef ENABLE_DATABASE
+#include "db/update/Service.hxx"
+#endif
+
#define COMMAND_STATUS_STATE "state"
#define COMMAND_STATUS_REPEAT "repeat"
#define COMMAND_STATUS_SINGLE "single"
@@ -112,7 +115,6 @@ handle_status(Client &client,
gcc_unused int argc, gcc_unused char *argv[])
{
const char *state = nullptr;
- int updateJobId;
int song;
const auto player_status = client.player_control.GetStatus();
@@ -140,7 +142,7 @@ handle_status(Client &client,
COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n"
COMMAND_STATUS_MIXRAMPDB ": %f\n"
COMMAND_STATUS_STATE ": %s\n",
- volume_level_get(),
+ volume_level_get(client.partition.outputs),
playlist.GetRepeat(),
playlist.GetRandom(),
playlist.GetSingle(),
@@ -188,11 +190,17 @@ handle_status(Client &client,
}
}
- if ((updateJobId = isUpdatingDB())) {
+#ifdef ENABLE_DATABASE
+ const UpdateService *update_service = client.partition.instance.update;
+ unsigned updateJobId = update_service != nullptr
+ ? update_service->GetId()
+ : 0;
+ if (updateJobId != 0) {
client_printf(client,
COMMAND_STATUS_UPDATING_DB ": %i\n",
updateJobId);
}
+#endif
Error error = client.player_control.LockGetError();
if (error.IsDefined())
@@ -277,7 +285,7 @@ handle_random(Client &client, gcc_unused int argc, char *argv[])
return CommandResult::ERROR;
client.partition.SetRandom(status);
- audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client.partition.GetRandom()));
+ client.partition.outputs.SetReplayGainMode(replay_gain_get_real_mode(client.partition.GetRandom()));
return CommandResult::OK;
}
@@ -379,8 +387,7 @@ handle_replay_gain_mode(Client &client,
return CommandResult::ERROR;
}
- audio_output_all_set_replay_gain_mode(replay_gain_get_real_mode(client.playlist.queue.random));
-
+ client.partition.outputs.SetReplayGainMode(replay_gain_get_real_mode(client.playlist.queue.random));
return CommandResult::OK;
}
diff --git a/src/command/PlayerCommands.hxx b/src/command/PlayerCommands.hxx
index 8dad0855b..c90fa4548 100644
--- a/src/command/PlayerCommands.hxx
+++ b/src/command/PlayerCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/PlaylistCommands.cxx b/src/command/PlaylistCommands.cxx
index d178fa097..bc426db4e 100644
--- a/src/command/PlaylistCommands.cxx
+++ b/src/command/PlaylistCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -19,25 +19,24 @@
#include "config.h"
#include "PlaylistCommands.hxx"
-#include "DatabasePlaylist.hxx"
+#include "db/DatabasePlaylist.hxx"
#include "CommandError.hxx"
#include "PlaylistPrint.hxx"
#include "PlaylistSave.hxx"
#include "PlaylistFile.hxx"
-#include "PlaylistVector.hxx"
-#include "PlaylistQueue.hxx"
+#include "db/PlaylistVector.hxx"
+#include "SongLoader.hxx"
+#include "playlist/PlaylistQueue.hxx"
+#include "playlist/Print.hxx"
+#include "queue/Playlist.hxx"
#include "TimePrint.hxx"
-#include "Client.hxx"
+#include "client/Client.hxx"
#include "protocol/ArgParser.hxx"
#include "protocol/Result.hxx"
#include "ls.hxx"
-#include "Playlist.hxx"
#include "util/UriUtil.hxx"
#include "util/Error.hxx"
-#include <assert.h>
-#include <stdlib.h>
-
static void
print_spl_list(Client &client, const PlaylistVector &list)
{
@@ -67,15 +66,14 @@ handle_load(Client &client, int argc, char *argv[])
} 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;
+ const SongLoader loader(client);
+ if (!playlist_open_into_queue(argv[1],
+ start_index, end_index,
+ client.playlist,
+ client.player_control, loader, error))
+ return print_error(client, error);
+
if (playlist_load_spl(client.playlist, client.player_control,
argv[1], start_index, end_index,
error))
@@ -188,16 +186,21 @@ handle_playlistadd(Client &client, gcc_unused int argc, char *argv[])
bool success;
Error error;
if (uri_has_scheme(uri)) {
- if (!uri_supported_scheme(uri)) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "unsupported URI scheme");
- return CommandResult::ERROR;
- }
-
- success = spl_append_uri(uri, playlist, error);
- } else
- success = search_add_to_playlist(uri, playlist, nullptr,
+ const SongLoader loader(client);
+ success = spl_append_uri(playlist, loader, uri, error);
+ } else {
+#ifdef ENABLE_DATABASE
+ const Database *db = client.GetDatabase(error);
+ if (db == nullptr)
+ return print_error(client, error);
+
+ success = search_add_to_playlist(*db, *client.GetStorage(),
+ uri, playlist, nullptr,
error);
+#else
+ success = false;
+#endif
+ }
if (!success && !error.IsDefined()) {
command_error(client, ACK_ERROR_NO_EXIST,
diff --git a/src/command/PlaylistCommands.hxx b/src/command/PlaylistCommands.hxx
index 802d6ff2f..c737c54eb 100644
--- a/src/command/PlaylistCommands.hxx
+++ b/src/command/PlaylistCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx
index a21eb75f0..81e5098bb 100644
--- a/src/command/QueueCommands.cxx
+++ b/src/command/QueueCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,13 +20,13 @@
#include "config.h"
#include "QueueCommands.hxx"
#include "CommandError.hxx"
-#include "DatabaseQueue.hxx"
+#include "db/DatabaseQueue.hxx"
+#include "db/Selection.hxx"
#include "SongFilter.hxx"
-#include "DatabaseSelection.hxx"
-#include "Playlist.hxx"
+#include "SongLoader.hxx"
+#include "queue/Playlist.hxx"
#include "PlaylistPrint.hxx"
-#include "ClientFile.hxx"
-#include "Client.hxx"
+#include "client/Client.hxx"
#include "Partition.hxx"
#include "protocol/ArgParser.hxx"
#include "protocol/Result.hxx"
@@ -39,88 +39,69 @@
#include <string.h>
-CommandResult
-handle_add(Client &client, gcc_unused int argc, char *argv[])
+static const char *
+translate_uri(Client &client, const char *uri)
{
- char *uri = argv[1];
- PlaylistResult result;
+ if (memcmp(uri, "file:///", 8) == 0)
+ /* drop the "file://", leave only an absolute path
+ (starting with a slash) */
+ return uri + 7;
+
+ if (PathTraitsUTF8::IsAbsolute(uri)) {
+ command_error(client, ACK_ERROR_NO_EXIST, "Malformed URI");
+ return nullptr;
+ }
- if (memcmp(uri, "file:///", 8) == 0) {
- const char *path_utf8 = uri + 7;
- const auto path_fs = AllocatedPath::FromUTF8(path_utf8);
+ return uri;
+}
- if (path_fs.IsNull()) {
- command_error(client, ACK_ERROR_NO_EXIST,
- "unsupported file name");
- return CommandResult::ERROR;
- }
+CommandResult
+handle_add(Client &client, gcc_unused int argc, char *argv[])
+{
+ const char *const uri = translate_uri(client, argv[1]);
+ if (uri == nullptr)
+ return CommandResult::ERROR;
+ if (uri_has_scheme(uri) || PathTraitsUTF8::IsAbsolute(uri)) {
+ const SongLoader loader(client);
Error error;
- if (!client_allow_file(client, path_fs, error))
+ unsigned id = client.partition.AppendURI(loader, uri, error);
+ if (id == 0)
return print_error(client, error);
- 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);
+ return CommandResult::OK;
}
+#ifdef ENABLE_DATABASE
const DatabaseSelection selection(uri, true);
Error error;
return AddFromDatabase(client.partition, selection, error)
? CommandResult::OK
: print_error(client, error);
+#else
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+#endif
}
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);
- }
+ const char *const uri = translate_uri(client, argv[1]);
+ if (uri == nullptr)
+ return CommandResult::ERROR;
- if (result != PlaylistResult::SUCCESS)
- return print_playlist_result(client, result);
+ const SongLoader loader(client);
+ Error error;
+ unsigned added_id = client.partition.AppendURI(loader, uri, error);
+ if (added_id == 0)
+ return print_error(client, error);
if (argc == 3) {
unsigned to;
if (!check_unsigned(client, &to, argv[2]))
return CommandResult::ERROR;
- result = client.partition.MoveId(added_id, to);
+ PlaylistResult result = client.partition.MoveId(added_id, to);
if (result != PlaylistResult::SUCCESS) {
CommandResult ret =
print_playlist_result(client, result);
diff --git a/src/command/QueueCommands.hxx b/src/command/QueueCommands.hxx
index 90d744447..af5413d8c 100644
--- a/src/command/QueueCommands.hxx
+++ b/src/command/QueueCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/StickerCommands.cxx b/src/command/StickerCommands.cxx
index b65e6f607..caae870e9 100644
--- a/src/command/StickerCommands.cxx
+++ b/src/command/StickerCommands.cxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -20,19 +20,18 @@
#include "config.h"
#include "StickerCommands.hxx"
#include "SongPrint.hxx"
-#include "DatabaseLock.hxx"
-#include "DatabasePlugin.hxx"
-#include "DatabaseGlue.hxx"
-#include "DatabaseSimple.hxx"
-#include "SongSticker.hxx"
-#include "StickerPrint.hxx"
-#include "StickerDatabase.hxx"
+#include "db/Interface.hxx"
+#include "db/DatabaseGlue.hxx"
+#include "sticker/SongSticker.hxx"
+#include "sticker/StickerPrint.hxx"
+#include "sticker/StickerDatabase.hxx"
#include "CommandError.hxx"
#include "protocol/Result.hxx"
+#include "client/Client.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
#include "util/Error.hxx"
-#include <glib.h>
-
#include <string.h>
struct sticker_song_find_data {
@@ -41,7 +40,7 @@ struct sticker_song_find_data {
};
static void
-sticker_song_find_print_cb(Song &song, const char *value,
+sticker_song_find_print_cb(const LightSong &song, const char *value,
void *user_data)
{
struct sticker_song_find_data *data =
@@ -55,17 +54,17 @@ static CommandResult
handle_sticker_song(Client &client, int argc, char *argv[])
{
Error error;
- const Database *db = GetDatabase(error);
+ const Database *db = client.GetDatabase(error);
if (db == nullptr)
return print_error(client, error);
/* get song song_id key */
if (argc == 5 && strcmp(argv[1], "get") == 0) {
- Song *song = db->GetSong(argv[3], error);
+ const LightSong *song = db->GetSong(argv[3], error);
if (song == nullptr)
return print_error(client, error);
- const auto value = sticker_song_get_value(song, argv[4]);
+ const auto value = sticker_song_get_value(*song, argv[4]);
db->ReturnSong(song);
if (value.empty()) {
command_error(client, ACK_ERROR_NO_EXIST,
@@ -78,11 +77,11 @@ handle_sticker_song(Client &client, int argc, char *argv[])
return CommandResult::OK;
/* list song song_id */
} else if (argc == 4 && strcmp(argv[1], "list") == 0) {
- Song *song = db->GetSong(argv[3], error);
+ const LightSong *song = db->GetSong(argv[3], error);
if (song == nullptr)
return print_error(client, error);
- sticker *sticker = sticker_song_get(song);
+ sticker *sticker = sticker_song_get(*song);
db->ReturnSong(song);
if (sticker) {
sticker_print(client, *sticker);
@@ -92,11 +91,11 @@ handle_sticker_song(Client &client, int argc, char *argv[])
return CommandResult::OK;
/* set song song_id id key */
} else if (argc == 6 && strcmp(argv[1], "set") == 0) {
- Song *song = db->GetSong(argv[3], error);
+ const LightSong *song = db->GetSong(argv[3], error);
if (song == nullptr)
return print_error(client, error);
- bool ret = sticker_song_set_value(song, argv[4], argv[5]);
+ bool ret = sticker_song_set_value(*song, argv[4], argv[5]);
db->ReturnSong(song);
if (!ret) {
command_error(client, ACK_ERROR_SYSTEM,
@@ -108,13 +107,13 @@ handle_sticker_song(Client &client, int argc, char *argv[])
/* delete song song_id [key] */
} else if ((argc == 4 || argc == 5) &&
strcmp(argv[1], "delete") == 0) {
- Song *song = db->GetSong(argv[3], error);
+ const LightSong *song = db->GetSong(argv[3], error);
if (song == nullptr)
return print_error(client, error);
bool ret = argc == 4
- ? sticker_song_delete(song)
- : sticker_song_delete_value(song, argv[4]);
+ ? sticker_song_delete(*song)
+ : sticker_song_delete_value(*song, argv[4]);
db->ReturnSong(song);
if (!ret) {
command_error(client, ACK_ERROR_SYSTEM,
@@ -126,24 +125,17 @@ handle_sticker_song(Client &client, int argc, char *argv[])
/* find song dir key */
} else if (argc == 5 && strcmp(argv[1], "find") == 0) {
/* "sticker find song a/directory name" */
+
+ const char *const base_uri = argv[3];
+
bool success;
struct sticker_song_find_data data = {
client,
argv[4],
};
- db_lock();
- Directory *directory = db_get_directory(argv[3]);
- if (directory == nullptr) {
- db_unlock();
- command_error(client, ACK_ERROR_NO_EXIST,
- "no such directory");
- return CommandResult::ERROR;
- }
-
- success = sticker_song_find(*directory, data.name,
+ success = sticker_song_find(*db, base_uri, data.name,
sticker_song_find_print_cb, &data);
- db_unlock();
if (!success) {
command_error(client, ACK_ERROR_SYSTEM,
"failed to set search sticker database");
diff --git a/src/command/StickerCommands.hxx b/src/command/StickerCommands.hxx
index 9e4380f37..ab8ac0db1 100644
--- a/src/command/StickerCommands.hxx
+++ b/src/command/StickerCommands.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2013 The Music Player Daemon Project
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
diff --git a/src/command/StorageCommands.cxx b/src/command/StorageCommands.cxx
new file mode 100644
index 000000000..7a20fcc01
--- /dev/null
+++ b/src/command/StorageCommands.cxx
@@ -0,0 +1,295 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define __STDC_FORMAT_MACROS /* for PRIu64 */
+
+#include "config.h"
+#include "StorageCommands.hxx"
+#include "CommandError.hxx"
+#include "protocol/Result.hxx"
+#include "util/UriUtil.hxx"
+#include "util/Error.hxx"
+#include "fs/Traits.hxx"
+#include "client/Client.hxx"
+#include "Partition.hxx"
+#include "Instance.hxx"
+#include "storage/Registry.hxx"
+#include "storage/CompositeStorage.hxx"
+#include "storage/FileInfo.hxx"
+#include "db/plugins/simple/SimpleDatabasePlugin.hxx"
+#include "db/update/Service.hxx"
+#include "TimePrint.hxx"
+#include "Idle.hxx"
+
+#include <inttypes.h> /* for PRIu64 */
+
+gcc_pure
+static bool
+skip_path(const char *name_utf8)
+{
+ return strchr(name_utf8, '\n') != nullptr;
+}
+
+#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
+/* PRIu64 causes bogus compiler warning */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat"
+#pragma GCC diagnostic ignored "-Wformat-extra-args"
+#endif
+
+static bool
+handle_listfiles_storage(Client &client, StorageDirectoryReader &reader,
+ Error &error)
+{
+ const char *name_utf8;
+ while ((name_utf8 = reader.Read()) != nullptr) {
+ if (skip_path(name_utf8))
+ continue;
+
+ FileInfo info;
+ if (!reader.GetInfo(false, info, error))
+ continue;
+
+ switch (info.type) {
+ case FileInfo::Type::OTHER:
+ /* ignore */
+ continue;
+
+ case FileInfo::Type::REGULAR:
+ client_printf(client, "file: %s\n"
+ "size: %" PRIu64 "\n",
+ name_utf8,
+ info.size);
+ break;
+
+ case FileInfo::Type::DIRECTORY:
+ client_printf(client, "directory: %s\n", name_utf8);
+ break;
+ }
+
+ if (info.mtime != 0)
+ time_print(client, "Last-Modified", info.mtime);
+ }
+
+ return true;
+}
+
+#if defined(WIN32) && GCC_CHECK_VERSION(4,6)
+#pragma GCC diagnostic pop
+#endif
+
+static bool
+handle_listfiles_storage(Client &client, Storage &storage, const char *uri,
+ Error &error)
+{
+ auto reader = storage.OpenDirectory(uri, error);
+ if (reader == nullptr)
+ return false;
+
+ bool success = handle_listfiles_storage(client, *reader, error);
+ delete reader;
+ return success;
+}
+
+CommandResult
+handle_listfiles_storage(Client &client, Storage &storage, const char *uri)
+{
+ Error error;
+ if (!handle_listfiles_storage(client, storage, uri, error))
+ return print_error(client, error);
+
+ return CommandResult::OK;
+}
+
+CommandResult
+handle_listfiles_storage(Client &client, const char *uri)
+{
+ Error error;
+ Storage *storage = CreateStorageURI(uri, error);
+ if (storage == nullptr) {
+ if (error.IsDefined())
+ return print_error(client, error);
+
+ command_error(client, ACK_ERROR_ARG,
+ "Unrecognized storage URI");
+ return CommandResult::ERROR;
+ }
+
+ bool success = handle_listfiles_storage(client, *storage, "", error);
+ delete storage;
+ if (!success)
+ return print_error(client, error);
+
+ return CommandResult::OK;
+}
+
+static void
+print_storage_uri(Client &client, const Storage &storage)
+{
+ std::string uri = storage.MapUTF8("");
+ if (uri.empty())
+ return;
+
+ if (PathTraitsFS::IsAbsolute(uri.c_str())) {
+ /* storage points to local directory */
+
+ if (!client.IsLocal())
+ /* only "local" clients may see local paths
+ (same policy as with the "config"
+ command) */
+ return;
+ } else {
+ /* hide username/passwords from client */
+
+ std::string allocated = uri_remove_auth(uri.c_str());
+ if (!allocated.empty())
+ uri = std::move(allocated);
+ }
+
+ client_printf(client, "storage: %s\n", uri.c_str());
+}
+
+CommandResult
+handle_listmounts(Client &client, gcc_unused int argc, gcc_unused char *argv[])
+{
+ Storage *_composite = client.partition.instance.storage;
+ if (_composite == nullptr) {
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+ }
+
+ CompositeStorage &composite = *(CompositeStorage *)_composite;
+
+ const auto visitor = [&client](const char *mount_uri,
+ const Storage &storage){
+ client_printf(client, "mount: %s\n", mount_uri);
+ print_storage_uri(client, storage);
+ };
+
+ composite.VisitMounts(visitor);
+
+ return CommandResult::OK;
+}
+
+CommandResult
+handle_mount(Client &client, gcc_unused int argc, char *argv[])
+{
+ Storage *_composite = client.partition.instance.storage;
+ if (_composite == nullptr) {
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+ }
+
+ CompositeStorage &composite = *(CompositeStorage *)_composite;
+
+ const char *const local_uri = argv[1];
+ const char *const remote_uri = argv[2];
+
+ if (*local_uri == 0) {
+ command_error(client, ACK_ERROR_ARG, "Bad mount point");
+ return CommandResult::ERROR;
+ }
+
+ if (strchr(local_uri, '/') != nullptr) {
+ /* allow only top-level mounts for now */
+ /* TODO: eliminate this limitation after ensuring that
+ UpdateQueue::Erase() really gets called for every
+ unmount, and no Directory disappears recursively
+ during database update */
+ command_error(client, ACK_ERROR_ARG, "Bad mount point");
+ return CommandResult::ERROR;
+ }
+
+ Error error;
+ Storage *storage = CreateStorageURI(remote_uri, error);
+ if (storage == nullptr) {
+ if (error.IsDefined())
+ return print_error(client, error);
+
+ command_error(client, ACK_ERROR_ARG,
+ "Unrecognized storage URI");
+ return CommandResult::ERROR;
+ }
+
+ composite.Mount(local_uri, storage);
+ idle_add(IDLE_MOUNT);
+
+#ifdef ENABLE_DATABASE
+ Database *_db = client.partition.instance.database;
+ if (_db != nullptr && _db->IsPlugin(simple_db_plugin)) {
+ SimpleDatabase &db = *(SimpleDatabase *)_db;
+
+ if (!db.Mount(local_uri, remote_uri, error)) {
+ composite.Unmount(local_uri);
+ return print_error(client, error);
+ }
+
+ // TODO: call Instance::OnDatabaseModified()?
+ // TODO: trigger database update?
+ idle_add(IDLE_DATABASE);
+ }
+#endif
+
+ return CommandResult::OK;
+}
+
+CommandResult
+handle_unmount(Client &client, gcc_unused int argc, char *argv[])
+{
+ Storage *_composite = client.partition.instance.storage;
+ if (_composite == nullptr) {
+ command_error(client, ACK_ERROR_NO_EXIST, "No database");
+ return CommandResult::ERROR;
+ }
+
+ CompositeStorage &composite = *(CompositeStorage *)_composite;
+
+ const char *const local_uri = argv[1];
+
+ if (*local_uri == 0) {
+ command_error(client, ACK_ERROR_ARG, "Bad mount point");
+ return CommandResult::ERROR;
+ }
+
+#ifdef ENABLE_DATABASE
+ if (client.partition.instance.update != nullptr)
+ /* ensure that no database update will attempt to work
+ with the database/storage instances we're about to
+ destroy here */
+ client.partition.instance.update->CancelMount(local_uri);
+
+ Database *_db = client.partition.instance.database;
+ if (_db != nullptr && _db->IsPlugin(simple_db_plugin)) {
+ SimpleDatabase &db = *(SimpleDatabase *)_db;
+
+ if (db.Unmount(local_uri))
+ // TODO: call Instance::OnDatabaseModified()?
+ idle_add(IDLE_DATABASE);
+ }
+#endif
+
+ if (!composite.Unmount(local_uri)) {
+ command_error(client, ACK_ERROR_ARG, "Not a mount point");
+ return CommandResult::ERROR;
+ }
+
+ idle_add(IDLE_MOUNT);
+
+ return CommandResult::OK;
+}
diff --git a/src/command/StorageCommands.hxx b/src/command/StorageCommands.hxx
new file mode 100644
index 000000000..905cd636e
--- /dev/null
+++ b/src/command/StorageCommands.hxx
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_STORAGE_COMMANDS_HXX
+#define MPD_STORAGE_COMMANDS_HXX
+
+#include "CommandResult.hxx"
+
+class Client;
+class Storage;
+
+CommandResult
+handle_listfiles_storage(Client &client, Storage &storage, const char *uri);
+
+CommandResult
+handle_listfiles_storage(Client &client, const char *uri);
+
+CommandResult
+handle_listmounts(Client &client, int argc, char *argv[]);
+
+CommandResult
+handle_mount(Client &client, int argc, char *argv[]);
+
+CommandResult
+handle_unmount(Client &client, int argc, char *argv[]);
+
+#endif
diff --git a/src/command/TagCommands.cxx b/src/command/TagCommands.cxx
new file mode 100644
index 000000000..02e95af71
--- /dev/null
+++ b/src/command/TagCommands.cxx
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+#include "TagCommands.hxx"
+#include "CommandError.hxx"
+#include "client/Client.hxx"
+#include "protocol/ArgParser.hxx"
+#include "protocol/Result.hxx"
+#include "tag/Tag.hxx"
+#include "Partition.hxx"
+
+CommandResult
+handle_addtagid(Client &client, gcc_unused int argc, char *argv[])
+{
+ unsigned song_id;
+ if (!check_unsigned(client, &song_id, argv[1]))
+ return CommandResult::ERROR;
+
+ const char *const tag_name = argv[2];
+ const TagType tag_type = tag_name_parse_i(tag_name);
+ if (tag_type == TAG_NUM_OF_ITEM_TYPES) {
+ command_error(client, ACK_ERROR_ARG,
+ "Unknown tag type: %s", tag_name);
+ return CommandResult::ERROR;
+ }
+
+ const char *const value = argv[3];
+
+ Error error;
+ if (!client.partition.playlist.AddSongIdTag(song_id, tag_type, value,
+ error))
+ return print_error(client, error);
+
+ return CommandResult::OK;
+}
+
+CommandResult
+handle_cleartagid(Client &client, int argc, char *argv[])
+{
+ unsigned song_id;
+ if (!check_unsigned(client, &song_id, argv[1]))
+ return CommandResult::ERROR;
+
+ TagType tag_type = TAG_NUM_OF_ITEM_TYPES;
+ if (argc >= 3) {
+ const char *const tag_name = argv[2];
+ tag_type = tag_name_parse_i(tag_name);
+ if (tag_type == TAG_NUM_OF_ITEM_TYPES) {
+ command_error(client, ACK_ERROR_ARG,
+ "Unknown tag type: %s", tag_name);
+ return CommandResult::ERROR;
+ }
+ }
+
+ Error error;
+ if (!client.partition.playlist.ClearSongIdTag(song_id, tag_type,
+ error))
+ return print_error(client, error);
+
+ return CommandResult::OK;
+}
diff --git a/src/command/TagCommands.hxx b/src/command/TagCommands.hxx
new file mode 100644
index 000000000..b54ddb178
--- /dev/null
+++ b/src/command/TagCommands.hxx
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2003-2014 The Music Player Daemon Project
+ * http://www.musicpd.org
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef MPD_TAG_COMMANDS_HXX
+#define MPD_TAG_COMMANDS_HXX
+
+#include "CommandResult.hxx"
+
+class Client;
+
+CommandResult
+handle_addtagid(Client &client, int argc, char *argv[]);
+
+CommandResult
+handle_cleartagid(Client &client, int argc, char *argv[]);
+
+#endif