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