diff --git a/src/db/plugins/upnp/UpnpDatabasePlugin.cxx b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx index 40b4d2872..236f4f53c 100644 --- a/src/db/plugins/upnp/UpnpDatabasePlugin.cxx +++ b/src/db/plugins/upnp/UpnpDatabasePlugin.cxx @@ -21,7 +21,7 @@ #include "tag/Table.hxx" #include "fs/Traits.hxx" #include "util/RecursiveMap.hxx" -#include "util/SplitString.hxx" +#include "util/StringSplit.hxx" #include "config/Block.hxx" #include @@ -35,6 +35,17 @@ static constexpr const char *rootid = "0"; static constexpr std::string_view rootid_sv{rootid}; +static std::string_view +AfterRootIdSegment(std::string_view &uri) noexcept +{ + if (uri.size() >= rootid_sv.size() + 2 && + uri.starts_with(rootid_sv) && + uri[rootid_sv.size()] == '/') + return uri.substr(rootid_sv.size() + 1); + else + return {}; +} + class UpnpSongData { protected: std::string uri; @@ -97,7 +108,7 @@ public: private: void VisitServer(const ContentDirectoryService &server, - std::forward_list &&vpath, + std::string_view uri, const DatabaseSelection &selection, const VisitDirectory& visit_directory, const VisitSong& visit_song, @@ -117,7 +128,7 @@ private: const DatabaseSelection &selection) const; UPnPDirObject Namei(const ContentDirectoryService &server, - std::forward_list &&vpath) const; + std::string_view uri) const; /** * Take server and objid, return metadata. @@ -178,28 +189,19 @@ UpnpDatabase::ReturnSong(const LightSong *_song) const noexcept const LightSong * UpnpDatabase::GetSong(std::string_view uri) const { - auto vpath = SplitString(uri, '/'); - if (vpath.empty()) + auto [server_name, uri_in_server] = Split(uri, '/'); + if (server_name.empty() || uri_in_server.empty()) throw DatabaseError(DatabaseErrorCode::NOT_FOUND, "No such song"); - auto server = discovery->GetServer(vpath.front()); - vpath.pop_front(); - - if (vpath.empty()) - throw DatabaseError(DatabaseErrorCode::NOT_FOUND, - "No such song"); + auto server = discovery->GetServer(server_name); UPnPDirObject dirent; - if (vpath.front() != rootid_sv) { - dirent = Namei(server, std::move(vpath)); + if (const auto id = AfterRootIdSegment(uri_in_server); + id.data() == nullptr) { + dirent = Namei(server, uri_in_server); } else { - vpath.pop_front(); - if (vpath.empty()) - throw DatabaseError(DatabaseErrorCode::NOT_FOUND, - "No such song"); - - dirent = ReadNode(server, std::string(vpath.front()).c_str()); + dirent = ReadNode(server, std::string{id}.c_str()); } return new UpnpSong(std::move(dirent), uri); @@ -398,9 +400,9 @@ UpnpDatabase::BuildPath(const ContentDirectoryService &server, // Take server and internal title pathname and return objid and metadata. UPnPDirObject UpnpDatabase::Namei(const ContentDirectoryService &server, - std::forward_list &&vpath) const + std::string_view uri) const { - if (vpath.empty()) + if (uri.empty()) // looking for root info return ReadNode(server, rootid); @@ -410,14 +412,16 @@ UpnpDatabase::Namei(const ContentDirectoryService &server, while (true) { auto dirbuf = server.readDir(handle, objid.c_str()); + const auto [name, rest] = Split(uri, '/'); + // Look for the name in the sub-container list - UPnPDirObject *child = dirbuf.FindObject(vpath.front()); + UPnPDirObject *child = dirbuf.FindObject(name); if (child == nullptr) throw DatabaseError(DatabaseErrorCode::NOT_FOUND, "No such object"); - vpath.pop_front(); - if (vpath.empty()) + uri = rest; + if (uri.empty()) return std::move(*child); if (child->type != UPnPDirObject::Type::CONTAINER) @@ -486,7 +490,7 @@ VisitObject(const UPnPDirObject &object, const char *uri, // really just one path parameter. void UpnpDatabase::VisitServer(const ContentDirectoryService &server, - std::forward_list &&vpath, + std::string_view uri, const DatabaseSelection &selection, const VisitDirectory& visit_directory, const VisitSong& visit_song, @@ -500,19 +504,12 @@ UpnpDatabase::VisitServer(const ContentDirectoryService &server, /* !Note: this *can't* be handled by Namei further down, because the path is not valid for traversal. Besides, it's just faster to access the target node directly */ - if (!vpath.empty() && vpath.front() == rootid_sv) { - vpath.pop_front(); - if (vpath.empty()) - return; - - const std::string objid(vpath.front()); - vpath.pop_front(); - if (!vpath.empty()) - throw DatabaseError(DatabaseErrorCode::NOT_FOUND, - "Not found"); + if (uri == rootid_sv) + return; + if (const auto id = AfterRootIdSegment(uri); id.data() != nullptr) { if (visit_song) { - auto dirent = ReadNode(server, objid.c_str()); + auto dirent = ReadNode(server, std::string{id}.c_str()); if (dirent.type != UPnPDirObject::Type::ITEM || dirent.item_class != UPnPDirObject::ItemClass::MUSIC) @@ -529,7 +526,7 @@ UpnpDatabase::VisitServer(const ContentDirectoryService &server, } // Translate the target path into an object id and the associated metadata. - const auto tdirent = Namei(server, std::move(vpath)); + const auto tdirent = Namei(server, uri); /* If recursive is set, this is a search... No use sending it if the filter is empty. In this case, we implement limited @@ -556,9 +553,9 @@ UpnpDatabase::VisitServer(const ContentDirectoryService &server, data to the client when we're done anyway. */ const auto contents = server.readDir(handle, tdirent.id.c_str()); for (const auto &dirent : contents.objects) { - const std::string uri = PathTraitsUTF8::Build(base_uri, - dirent.name.c_str()); - VisitObject(dirent, uri.c_str(), + const std::string child_uri = PathTraitsUTF8::Build(base_uri, + dirent.name.c_str()); + VisitObject(dirent, child_uri.c_str(), selection, visit_directory, visit_song, visit_playlist); @@ -583,8 +580,7 @@ UpnpDatabase::Visit(const DatabaseSelection &selection, { DatabaseVisitorHelper helper(CheckSelection(selection), visit_song); - auto vpath = SplitString(selection.uri, '/'); - if (vpath.empty()) { + if (selection.uri.empty()) { for (const auto &server : discovery->GetDirectories()) { if (visit_directory) { const LightDirectory d(server.GetFriendlyName().c_str(), @@ -593,7 +589,7 @@ UpnpDatabase::Visit(const DatabaseSelection &selection, } if (selection.recursive) - VisitServer(server, std::move(vpath), selection, + VisitServer(server, {}, selection, visit_directory, visit_song, visit_playlist); } @@ -603,10 +599,10 @@ UpnpDatabase::Visit(const DatabaseSelection &selection, } // We do have a path: the first element selects the server - auto server = discovery->GetServer(vpath.front()); - vpath.pop_front(); + const auto [server_name, uri_in_server] = Split(std::string_view{selection.uri}, '/'); + auto server = discovery->GetServer(server_name); - VisitServer(server, std::move(vpath), selection, + VisitServer(server, uri_in_server, selection, visit_directory, visit_song, visit_playlist); helper.Commit(); }