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