diff --git a/Makefile.am b/Makefile.am index 28707446b..8a82d708b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -168,6 +168,7 @@ libmpd_a_SOURCES = \ src/ReplayGainConfig.cxx src/ReplayGainConfig.hxx \ src/ReplayGainInfo.cxx src/ReplayGainInfo.hxx \ src/DetachedSong.cxx src/DetachedSong.hxx \ + src/LocateUri.cxx src/LocateUri.hxx \ src/SongUpdate.cxx \ src/SongLoader.cxx src/SongLoader.hxx \ src/SongPrint.cxx src/SongPrint.hxx \ @@ -2141,6 +2142,7 @@ test_test_translate_song_SOURCES = \ src/PlaylistError.cxx \ src/DetachedSong.cxx \ src/SongLoader.cxx \ + src/LocateUri.cxx \ src/Log.cxx \ test/test_translate_song.cxx test_test_translate_song_CPPFLAGS = $(AM_CPPFLAGS) $(CPPUNIT_CFLAGS) -DCPPUNIT_HAVE_RTTI=0 diff --git a/src/LocateUri.cxx b/src/LocateUri.cxx new file mode 100644 index 000000000..71c8d9093 --- /dev/null +++ b/src/LocateUri.cxx @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2003-2015 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 "LocateUri.hxx" +#include "client/Client.hxx" +#include "fs/AllocatedPath.hxx" +#include "ls.hxx" +#include "util/UriUtil.hxx" +#include "util/StringUtil.hxx" +#include "util/Error.hxx" +#include "util/Domain.hxx" + +#ifdef ENABLE_DATABASE +#include "storage/StorageInterface.hxx" +#endif + +const Domain locate_uri_domain("locate_uri"); + +static LocatedUri +LocateFileUri(const char *uri, const Client *client, +#ifdef ENABLE_DATABASE + const Storage *storage, +#endif + Error &error) +{ + auto path = AllocatedPath::FromUTF8(uri, error); + if (path.IsNull()) + return LocatedUri::Unknown(); + +#ifdef ENABLE_DATABASE + if (storage != nullptr) { + const char *suffix = storage->MapToRelativeUTF8(uri); + if (suffix != nullptr) + /* this path was relative to the music + directory */ + return LocatedUri(LocatedUri::Type::RELATIVE, suffix); + } +#endif + + if (client != nullptr && !client->AllowFile(path, error)) + return LocatedUri::Unknown(); + + return LocatedUri(LocatedUri::Type::PATH, uri, std::move(path)); +} + +static LocatedUri +LocateAbsoluteUri(const char *uri, +#ifdef ENABLE_DATABASE + const Storage *storage, +#endif + Error &error) +{ + if (!uri_supported_scheme(uri)) { + error.Set(locate_uri_domain, "Unsupported URI scheme"); + return LocatedUri::Unknown(); + } + +#ifdef ENABLE_DATABASE + if (storage != nullptr) { + const char *suffix = storage->MapToRelativeUTF8(uri); + if (suffix != nullptr) + return LocatedUri(LocatedUri::Type::RELATIVE, suffix); + } +#endif + + return LocatedUri(LocatedUri::Type::ABSOLUTE, uri); +} + +LocatedUri +LocateUri(const char *uri, const Client *client, +#ifdef ENABLE_DATABASE + const Storage *storage, +#endif + Error &error) +{ + /* skip the obsolete "file://" prefix */ + const char *path_utf8 = StringAfterPrefix(uri, "file://"); + if (path_utf8 != nullptr) { + if (!PathTraitsUTF8::IsAbsolute(path_utf8)) { + error.Set(locate_uri_domain, "Malformed file:// URI"); + return LocatedUri::Unknown(); + } + + return LocateFileUri(path_utf8, client, +#ifdef ENABLE_DATABASE + storage, +#endif + error); + } else if (PathTraitsUTF8::IsAbsolute(uri)) + return LocateFileUri(uri, client, +#ifdef ENABLE_DATABASE + storage, +#endif + error); + else if (uri_has_scheme(uri)) + return LocateAbsoluteUri(uri, +#ifdef ENABLE_DATABASE + storage, +#endif + error); + else + return LocatedUri(LocatedUri::Type::RELATIVE, uri); +} diff --git a/src/LocateUri.hxx b/src/LocateUri.hxx new file mode 100644 index 000000000..1cb57ace4 --- /dev/null +++ b/src/LocateUri.hxx @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2003-2015 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_LOCATE_URI_HXX +#define MPD_LOCATE_URI_HXX + +#include "check.h" +#include "Compiler.h" +#include "fs/AllocatedPath.hxx" + +#ifdef WIN32 +#include +/* damn you, windows.h! */ +#ifdef ABSOLUTE +#undef ABSOLUTE +#endif +#ifdef RELATIVE +#undef RELATIVE +#endif +#endif + +class Domain; +class Error; +class Client; + +#ifdef ENABLE_DATABASE +class Storage; +#endif + +struct LocatedUri { + enum class Type { + /** + * Failed to parse the URI. + */ + UNKNOWN, + + /** + * An absolute URI with a supported scheme. + */ + ABSOLUTE, + + /** + * A relative URI path. + */ + RELATIVE, + + /** + * A local file. The #path attribute is valid. + */ + PATH, + } type; + + const char *canonical_uri; + + /** + * Contains the local file path if type==FILE. + */ + AllocatedPath path; + + LocatedUri(Type _type, const char *_uri, + AllocatedPath &&_path=AllocatedPath::Null()) + :type(_type), canonical_uri(_uri), path(std::move(_path)) {} + + gcc_const + static LocatedUri Unknown() { + return LocatedUri(Type::UNKNOWN, nullptr); + } + + bool IsUnknown() const { + return type == Type::UNKNOWN; + } +}; + +extern const Domain locate_uri_domain; + +/** + * Classify a URI. + * + * @param client the #Client that is used to determine whether a local + * file is allowed; nullptr disables the check and allows all local + * files + * @param storage a #Storage instance which may be used to convert + * absolute URIs to relative ones, using Storage::MapToRelativeUTF8(); + * that feature is disabled if this parameter is nullptr + */ +LocatedUri +LocateUri(const char *uri, const Client *client, +#ifdef ENABLE_DATABASE + const Storage *storage, +#endif + Error &error); + +#endif diff --git a/src/SongLoader.cxx b/src/SongLoader.cxx index 9f7359a31..14f9f4dd6 100644 --- a/src/SongLoader.cxx +++ b/src/SongLoader.cxx @@ -19,19 +19,15 @@ #include "config.h" #include "SongLoader.hxx" +#include "LocateUri.hxx" #include "client/Client.hxx" #include "db/DatabaseSong.hxx" #include "storage/StorageInterface.hxx" -#include "ls.hxx" -#include "fs/AllocatedPath.hxx" -#include "fs/Traits.hxx" -#include "util/UriUtil.hxx" #include "util/Error.hxx" #include "DetachedSong.hxx" #include "PlaylistError.hxx" #include -#include #ifdef ENABLE_DATABASE @@ -57,7 +53,7 @@ SongLoader::LoadFromDatabase(const char *uri, Error &error) const } DetachedSong * -SongLoader::LoadFile(const char *path_utf8, Error &error) const +SongLoader::LoadFile(const char *path_utf8, Path path_fs, Error &error) const { #ifdef ENABLE_DATABASE if (storage != nullptr) { @@ -69,13 +65,6 @@ SongLoader::LoadFile(const char *path_utf8, Error &error) const } #endif - const auto path_fs = AllocatedPath::FromUTF8(path_utf8, error); - if (path_fs.IsNull()) - return nullptr; - - if (client != nullptr && !client->AllowFile(path_fs, error)) - return nullptr; - DetachedSong *song = new DetachedSong(path_utf8); if (!song->LoadFile(path_fs)) { error.Set(playlist_domain, int(PlaylistResult::NO_SUCH_SONG), @@ -87,6 +76,27 @@ SongLoader::LoadFile(const char *path_utf8, Error &error) const return song; } +DetachedSong * +SongLoader::LoadSong(const LocatedUri &located_uri, Error &error) const +{ + switch (located_uri.type) { + case LocatedUri::Type::UNKNOWN: + gcc_unreachable(); + + case LocatedUri::Type::ABSOLUTE: + return new DetachedSong(located_uri.canonical_uri); + + case LocatedUri::Type::RELATIVE: + return LoadFromDatabase(located_uri.canonical_uri, error); + + case LocatedUri::Type::PATH: + return LoadFile(located_uri.canonical_uri, located_uri.path, + error); + } + + gcc_unreachable(); +} + DetachedSong * SongLoader::LoadSong(const char *uri_utf8, Error &error) const { @@ -95,25 +105,13 @@ SongLoader::LoadSong(const char *uri_utf8, Error &error) const assert(uri_utf8 != nullptr); #endif - if (memcmp(uri_utf8, "file:///", 8) == 0) - /* absolute path */ - return LoadFile(uri_utf8 + 7, error); - else if (PathTraitsUTF8::IsAbsolute(uri_utf8)) - /* absolute path */ - return LoadFile(uri_utf8, error); - else if (uri_has_scheme(uri_utf8)) { - /* remove URI */ - if (!uri_supported_scheme(uri_utf8)) { - error.Set(playlist_domain, - int(PlaylistResult::NO_SUCH_SONG), - "Unsupported URI scheme"); - return nullptr; - } + const auto located_uri = LocateUri(uri_utf8, client, +#ifdef ENABLE_DATABASE + storage, +#endif + error); + if (located_uri.IsUnknown()) + return nullptr; - return new DetachedSong(uri_utf8); - } else { - /* URI relative to the music directory */ - - return LoadFromDatabase(uri_utf8, error); - } + return LoadSong(located_uri, error); } diff --git a/src/SongLoader.hxx b/src/SongLoader.hxx index 33baa6953..1c1300f11 100644 --- a/src/SongLoader.hxx +++ b/src/SongLoader.hxx @@ -29,7 +29,9 @@ class Client; class Database; class Storage; class DetachedSong; +class Path; class Error; +struct LocatedUri; /** * A utility class that loads a #DetachedSong object by its URI. If @@ -66,6 +68,8 @@ public: } #endif + DetachedSong *LoadSong(const LocatedUri &uri, Error &error) const; + gcc_nonnull_all DetachedSong *LoadSong(const char *uri_utf8, Error &error) const; @@ -74,7 +78,8 @@ private: DetachedSong *LoadFromDatabase(const char *uri, Error &error) const; gcc_nonnull_all - DetachedSong *LoadFile(const char *path_utf8, Error &error) const; + DetachedSong *LoadFile(const char *path_utf8, Path path_fs, + Error &error) const; }; #endif diff --git a/src/command/CommandError.cxx b/src/command/CommandError.cxx index d95722c3b..9f06431b4 100644 --- a/src/command/CommandError.cxx +++ b/src/command/CommandError.cxx @@ -20,6 +20,7 @@ #include "config.h" #include "CommandError.hxx" #include "db/DatabaseError.hxx" +#include "LocateUri.hxx" #include "client/Response.hxx" #include "util/Error.hxx" #include "Log.hxx" @@ -114,6 +115,9 @@ print_error(Response &r, const Error &error) return CommandResult::ERROR; } #endif + } else if (error.IsDomain(locate_uri_domain)) { + r.Error(ACK_ERROR_ARG, error.GetMessage()); + return CommandResult::ERROR; } else if (error.IsDomain(errno_domain)) { r.Error(ACK_ERROR_SYSTEM, strerror(error.GetCode())); return CommandResult::ERROR; diff --git a/src/command/FileCommands.cxx b/src/command/FileCommands.cxx index f7e16a0d2..486c00d89 100644 --- a/src/command/FileCommands.cxx +++ b/src/command/FileCommands.cxx @@ -39,8 +39,8 @@ #include "fs/AllocatedPath.hxx" #include "fs/FileInfo.hxx" #include "fs/DirectoryReader.hxx" +#include "LocateUri.hxx" #include "TimePrint.hxx" -#include "ls.hxx" #include #include @@ -70,21 +70,12 @@ skip_path(Path name_fs) #endif CommandResult -handle_listfiles_local(Client &client, Response &r, - const char *path_utf8) +handle_listfiles_local(Response &r, + const char *path_utf8, Path path_fs) { - const auto path_fs = AllocatedPath::FromUTF8(path_utf8); - if (path_fs.IsNull()) { - r.Error(ACK_ERROR_NO_EXIST, "unsupported file name"); - return CommandResult::ERROR; - } - - Error error; - if (!client.AllowFile(path_fs, error)) - return print_error(r, error); - DirectoryReader reader(path_fs); if (reader.HasFailed()) { + Error error; error.FormatErrno("Failed to open '%s'", path_utf8); return print_error(r, error); } @@ -172,11 +163,6 @@ static constexpr tag_handler print_comment_handler = { static CommandResult read_stream_comments(Response &r, const char *uri) { - if (!uri_supported_scheme(uri)) { - r.Error(ACK_ERROR_NO_EXIST, "unsupported URI scheme"); - return CommandResult::ERROR; - } - if (!tag_stream_scan(uri, print_comment_handler, &r)) { r.Error(ACK_ERROR_NO_EXIST, "Failed to load file"); return CommandResult::ERROR; @@ -201,63 +187,64 @@ read_file_comments(Response &r, const Path path_fs) } -static const char * -translate_uri(const char *uri) +static CommandResult +read_db_comments(Client &client, Response &r, const char *uri) { - if (memcmp(uri, "file:///", 8) == 0) - /* drop the "file://", leave only an absolute path - (starting with a slash) */ - return uri + 7; +#ifdef ENABLE_DATABASE + const Storage *storage = client.GetStorage(); + if (storage == nullptr) { +#else + (void)client; + (void)uri; +#endif + r.Error(ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +#ifdef ENABLE_DATABASE + } - return uri; + { + AllocatedPath path_fs = storage->MapFS(uri); + if (!path_fs.IsNull()) + return read_file_comments(r, path_fs); + } + + { + const std::string uri2 = storage->MapUTF8(uri); + if (uri_has_scheme(uri2.c_str())) + return read_stream_comments(r, uri2.c_str()); + } + + r.Error(ACK_ERROR_NO_EXIST, "No such file"); + return CommandResult::ERROR; +#endif } CommandResult handle_read_comments(Client &client, Request args, Response &r) { assert(args.size == 1); - const char *const uri = translate_uri(args.front()); - if (PathTraitsUTF8::IsAbsolute(uri)) { - /* read comments from arbitrary local file */ - const char *path_utf8 = uri; - AllocatedPath path_fs = AllocatedPath::FromUTF8(path_utf8); - if (path_fs.IsNull()) { - r.Error(ACK_ERROR_NO_EXIST, "unsupported file name"); - return CommandResult::ERROR; - } + const char *const uri = args.front(); - Error error; - if (!client.AllowFile(path_fs, error)) - return print_error(r, error); - - return read_file_comments(r, path_fs); - } else if (uri_has_scheme(uri)) { - return read_stream_comments(r, uri); - } else { + Error error; + const auto located_uri = LocateUri(uri, &client, #ifdef ENABLE_DATABASE - const Storage *storage = client.GetStorage(); - if (storage == nullptr) { + nullptr, #endif - r.Error(ACK_ERROR_NO_EXIST, "No database"); - return CommandResult::ERROR; -#ifdef ENABLE_DATABASE - } + error); + switch (located_uri.type) { + case LocatedUri::Type::UNKNOWN: + return print_error(r, error); - { - AllocatedPath path_fs = storage->MapFS(uri); - if (!path_fs.IsNull()) - return read_file_comments(r, path_fs); - } + case LocatedUri::Type::ABSOLUTE: + return read_stream_comments(r, located_uri.canonical_uri); - { - const std::string uri2 = storage->MapUTF8(uri); - if (uri_has_scheme(uri2.c_str())) - return read_stream_comments(r, uri2.c_str()); - } + case LocatedUri::Type::RELATIVE: + return read_db_comments(client, r, located_uri.canonical_uri); - r.Error(ACK_ERROR_NO_EXIST, "No such file"); - return CommandResult::ERROR; -#endif + case LocatedUri::Type::PATH: + return read_file_comments(r, located_uri.path); } + + gcc_unreachable(); } diff --git a/src/command/FileCommands.hxx b/src/command/FileCommands.hxx index c072d2028..9c6631df5 100644 --- a/src/command/FileCommands.hxx +++ b/src/command/FileCommands.hxx @@ -25,10 +25,11 @@ class Client; class Request; class Response; +class Path; CommandResult -handle_listfiles_local(Client &client, Response &response, - const char *path_utf8); +handle_listfiles_local(Response &response, + const char *path_utf8, Path path_fs); CommandResult handle_read_comments(Client &client, Request request, Response &response); diff --git a/src/command/OtherCommands.cxx b/src/command/OtherCommands.cxx index 09b951b03..b4a23fe4b 100644 --- a/src/command/OtherCommands.cxx +++ b/src/command/OtherCommands.cxx @@ -25,6 +25,7 @@ #include "CommandError.hxx" #include "db/Uri.hxx" #include "storage/StorageInterface.hxx" +#include "LocateUri.hxx" #include "DetachedSong.hxx" #include "SongPrint.hxx" #include "TagPrint.hxx" @@ -122,31 +123,49 @@ handle_listfiles(Client &client, Request args, Response &r) /* default is root directory */ const auto uri = args.GetOptional(0, ""); - if (memcmp(uri, "file:///", 8) == 0) - /* list local directory */ - return handle_listfiles_local(client, r, uri + 7); - + Error error; + const auto located_uri = LocateUri(uri, &client, #ifdef ENABLE_DATABASE - if (uri_has_scheme(uri)) - /* use storage plugin to list remote directory */ - return handle_listfiles_storage(r, 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(r, - *client.partition.instance.storage, - uri); - - /* fall back to entries from database if we have no storage */ - return handle_listfiles_db(client, r, uri); -#else - r.Error(ACK_ERROR_NO_EXIST, "No database"); - return CommandResult::ERROR; + nullptr, #endif + error); + + switch (located_uri.type) { + case LocatedUri::Type::UNKNOWN: + return print_error(r, error); + + case LocatedUri::Type::ABSOLUTE: +#ifdef ENABLE_DATABASE + /* use storage plugin to list remote directory */ + return handle_listfiles_storage(r, located_uri.canonical_uri); +#else + r.Error(ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +#endif + + case LocatedUri::Type::RELATIVE: +#ifdef ENABLE_DATABASE + if (client.partition.instance.storage != nullptr) + /* if we have a storage instance, obtain a list of + files from it */ + return handle_listfiles_storage(r, + *client.partition.instance.storage, + uri); + + /* fall back to entries from database if we have no storage */ + return handle_listfiles_db(client, r, uri); +#else + r.Error(ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +#endif + + case LocatedUri::Type::PATH: + /* list local directory */ + return handle_listfiles_local(r, located_uri.canonical_uri, + located_uri.path); + } + + gcc_unreachable(); } static constexpr tag_handler print_tag_handler = { @@ -155,54 +174,26 @@ static constexpr tag_handler print_tag_handler = { nullptr, }; -CommandResult -handle_lsinfo(Client &client, Request args, Response &r) +static CommandResult +handle_lsinfo_absolute(Response &r, const char *uri) { - /* default is root directory */ - const auto uri = args.GetOptional(0, ""); - - if (memcmp(uri, "file:///", 8) == 0) { - /* print information about an arbitrary local file */ - const char *path_utf8 = uri + 7; - const auto path_fs = AllocatedPath::FromUTF8(path_utf8); - - if (path_fs.IsNull()) { - r.Error(ACK_ERROR_NO_EXIST, "unsupported file name"); - return CommandResult::ERROR; - } - - Error error; - if (!client.AllowFile(path_fs, error)) - return print_error(r, error); - - DetachedSong song(path_utf8); - if (!song.LoadFile(path_fs)) { - r.Error(ACK_ERROR_NO_EXIST, "No such file"); - return CommandResult::ERROR; - } - - song_print_info(r, client.partition, song); - return CommandResult::OK; + if (!tag_stream_scan(uri, print_tag_handler, &r)) { + r.Error(ACK_ERROR_NO_EXIST, "No such file"); + return CommandResult::ERROR; } - if (uri_has_scheme(uri)) { - if (!uri_supported_scheme(uri)) { - r.Error(ACK_ERROR_NO_EXIST, "unsupported URI scheme"); - return CommandResult::ERROR; - } - - if (!tag_stream_scan(uri, print_tag_handler, &r)) { - r.Error(ACK_ERROR_NO_EXIST, "No such file"); - return CommandResult::ERROR; - } - - return CommandResult::OK; - } + return CommandResult::OK; +} +static CommandResult +handle_lsinfo_relative(Client &client, Response &r, const char *uri) +{ #ifdef ENABLE_DATABASE CommandResult result = handle_lsinfo2(client, uri, r); if (result != CommandResult::OK) return result; +#else + (void)client; #endif if (isRootDirectory(uri)) { @@ -219,6 +210,53 @@ handle_lsinfo(Client &client, Request args, Response &r) return CommandResult::OK; } +static CommandResult +handle_lsinfo_path(Client &client, Response &r, + const char *path_utf8, Path path_fs) +{ + DetachedSong song(path_utf8); + if (!song.LoadFile(path_fs)) { + r.Error(ACK_ERROR_NO_EXIST, "No such file"); + return CommandResult::ERROR; + } + + song_print_info(r, client.partition, song); + return CommandResult::OK; +} + +CommandResult +handle_lsinfo(Client &client, Request args, Response &r) +{ + /* default is root directory */ + const auto uri = args.GetOptional(0, ""); + + Error error; + const auto located_uri = LocateUri(uri, &client, +#ifdef ENABLE_DATABASE + nullptr, +#endif + error); + + switch (located_uri.type) { + case LocatedUri::Type::UNKNOWN: + return print_error(r, error); + + case LocatedUri::Type::ABSOLUTE: + return handle_lsinfo_absolute(r, located_uri.canonical_uri); + + case LocatedUri::Type::RELATIVE: + return handle_lsinfo_relative(client, r, + located_uri.canonical_uri); + + case LocatedUri::Type::PATH: + /* print information about an arbitrary local file */ + return handle_lsinfo_path(client, r, located_uri.canonical_uri, + located_uri.path); + } + + gcc_unreachable(); +} + #ifdef ENABLE_DATABASE static CommandResult diff --git a/src/command/QueueCommands.cxx b/src/command/QueueCommands.cxx index 1698e88df..7751aa26d 100644 --- a/src/command/QueueCommands.cxx +++ b/src/command/QueueCommands.cxx @@ -25,13 +25,14 @@ #include "db/Selection.hxx" #include "SongFilter.hxx" #include "SongLoader.hxx" +#include "DetachedSong.hxx" +#include "LocateUri.hxx" #include "queue/Playlist.hxx" #include "PlaylistPrint.hxx" #include "client/Client.hxx" #include "client/Response.hxx" #include "Partition.hxx" #include "BulkEdit.hxx" -#include "ls.hxx" #include "util/ConstBuffer.hxx" #include "util/UriUtil.hxx" #include "util/NumberParser.hxx" @@ -42,15 +43,42 @@ #include -static const char * -translate_uri(const char *uri) +static CommandResult +AddUri(Client &client, const LocatedUri &uri, Response &r) { - if (memcmp(uri, "file:///", 8) == 0) - /* drop the "file://", leave only an absolute path - (starting with a slash) */ - return uri + 7; + Error error; + DetachedSong *song = SongLoader(client).LoadSong(uri, error); + if (song == nullptr) + return print_error(r, error); - return uri; + auto &partition = client.partition; + unsigned id = partition.playlist.AppendSong(partition.pc, + std::move(*song), error); + delete song; + if (id == 0) + return print_error(r, error); + + return CommandResult::OK; +} + +static CommandResult +AddDatabaseSelection(Client &client, const char *uri, Response &r) +{ +#ifdef ENABLE_DATABASE + const ScopeBulkEdit bulk_edit(client.partition); + + const DatabaseSelection selection(uri, true); + Error error; + return AddFromDatabase(client.partition, selection, error) + ? CommandResult::OK + : print_error(r, error); +#else + (void)client; + (void)uri; + + r.Error(ACK_ERROR_NO_EXIST, "No database"); + return CommandResult::ERROR; +#endif } CommandResult @@ -65,36 +93,32 @@ handle_add(Client &client, Request args, Response &r) here */ uri = ""; - uri = translate_uri(uri); + Error error; + const auto located_uri = LocateUri(uri, &client, +#ifdef ENABLE_DATABASE + nullptr, +#endif + error); + switch (located_uri.type) { + case LocatedUri::Type::UNKNOWN: + return print_error(r, error); - if (uri_has_scheme(uri) || PathTraitsUTF8::IsAbsolute(uri)) { - const SongLoader loader(client); - Error error; - unsigned id = client.partition.AppendURI(loader, uri, error); - if (id == 0) - return print_error(r, error); + case LocatedUri::Type::ABSOLUTE: + case LocatedUri::Type::PATH: + return AddUri(client, located_uri, r); - return CommandResult::OK; + case LocatedUri::Type::RELATIVE: + return AddDatabaseSelection(client, located_uri.canonical_uri, + r); } -#ifdef ENABLE_DATABASE - const ScopeBulkEdit bulk_edit(client.partition); - - const DatabaseSelection selection(uri, true); - Error error; - return AddFromDatabase(client.partition, selection, error) - ? CommandResult::OK - : print_error(r, error); -#else - r.Error(ACK_ERROR_NO_EXIST, "No database"); - return CommandResult::ERROR; -#endif + gcc_unreachable(); } CommandResult handle_addid(Client &client, Request args, Response &r) { - const char *const uri = translate_uri(args.front()); + const char *const uri = args.front(); const SongLoader loader(client); Error error;