From 1f344aaaae631ac4b502577b31554597e3ceb7b4 Mon Sep 17 00:00:00 2001 From: emeric Date: Sat, 6 Apr 2019 15:02:59 +0200 Subject: [PATCH] API Subsonic: added search methods --- src/api/subsonic/SubsonicResource.cpp | 133 +++++++++++++++----------- src/api/subsonic/SubsonicResponse.cpp | 2 +- src/database/Artist.cpp | 2 +- src/database/Artist.hpp | 2 +- 4 files changed, 82 insertions(+), 57 deletions(-) diff --git a/src/api/subsonic/SubsonicResource.cpp b/src/api/subsonic/SubsonicResource.cpp index df9e31a2..4576bb51 100644 --- a/src/api/subsonic/SubsonicResource.cpp +++ b/src/api/subsonic/SubsonicResource.cpp @@ -54,6 +54,8 @@ #define GET_STARRED2_URL "/rest/getStarred2.view" #define GET_PLAYLISTS_URL "/rest/getPlaylists.view" #define GET_SONGS_BY_GENRE_URL "/rest/getSongsByGenre.view" +#define SEARCH2_URL "/rest/search2.view" +#define SEARCH3_URL "/rest/search3.view" // MediaRetrievals #define STREAM_URL "/rest/stream.view" @@ -80,6 +82,8 @@ static Response handleGetStarredRequest(const Wt::Http::ParameterMap& request, D static Response handleGetStarred2Request(const Wt::Http::ParameterMap& request, Database::Handler& db); static Response handleGetPlaylistsRequest(const Wt::Http::ParameterMap& request, Database::Handler& db); static Response handleGetSongsByGenreRequest(const Wt::Http::ParameterMap& request, Database::Handler& db); +static Response handleSearch2Request(const Wt::Http::ParameterMap& request, Database::Handler& db); +static Response handleSearch3Request(const Wt::Http::ParameterMap& request, Database::Handler& db); // MediaRetrievals using MediaRetrivalHandlerFunc = std::function; @@ -104,6 +108,8 @@ static std::map requestHandlers {GET_STARRED2_URL, handleGetStarred2Request}, {GET_PLAYLISTS_URL, handleGetPlaylistsRequest}, {GET_SONGS_BY_GENRE_URL, handleGetSongsByGenreRequest}, + {SEARCH2_URL, handleSearch2Request}, + {SEARCH3_URL, handleSearch3Request}, }; static std::map mediaRetrievalHandlers @@ -238,17 +244,10 @@ SubsonicResource::getPaths() void SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) { - LMS_LOG(API_SUBSONIC, DEBUG) << "REQUEST. Path = '" << request.path() << "', pathInfo = '" << request.pathInfo() << "', queryString = '" << request.queryString() << "'"; + LMS_LOG(API_SUBSONIC, DEBUG) << "REQUEST " << request.path(); const Wt::Http::ParameterMap parameters {request.getParameterMap()}; - for (const auto it : parameters) - { - LMS_LOG(API_SUBSONIC, DEBUG) << "Found param '" << it.first << "'"; - for (const std::string& value : it.second) - LMS_LOG(API_SUBSONIC, DEBUG) << "\t'" << value << "'"; - } - auto clientInfo {getClientInfo(parameters)}; if (!clientInfo) { @@ -286,6 +285,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp } catch (const Error& e) { + LMS_LOG(API_SUBSONIC, ERROR) << "Error while processing command. code = " << static_cast(e.getCode()) << ", msg = '" << e.getMessage() << "'"; Response resp {Response::createFailedResponse(e)}; resp.write(response.out(), clientInfo->format); response.setMimeType(ResponseFormatToMimeType(clientInfo->format)); @@ -463,17 +463,12 @@ Response handleGetRandomSongsRequest(const Wt::Http::ParameterMap& parameters, Database::Handler& db) { // Optional params - auto size {getParameterAs(parameters, "size")}; - if (!size) - size = 50; - - *size = std::min(*size, std::size_t {500}); + std::size_t size {getParameterAs(parameters, "size").get_value_or(50)}; + size = std::min(size, std::size_t {500}); Wt::Dbo::Transaction transaction {db.getSession()}; - auto tracks {Database::Track::getAllRandom(db.getSession(), *size)}; - - LMS_LOG(API_SUBSONIC, DEBUG) << "Got " << tracks.size() << " tracks"; + auto tracks {Database::Track::getAllRandom(db.getSession(), size)}; Response response {Response::createOkResponse()}; @@ -527,13 +522,8 @@ handleGetAlbumListRequestCommon(const Wt::Http::ParameterMap& request, Database: throw Error {Error::Code::RequiredParameterMissing}; // Optional params - auto size {getParameterAs(request, "size")}; - if (!size) - size = 10; - - auto offset {getParameterAs(request, "offset")}; - if (!offset) - offset = 0; + std::size_t size {getParameterAs(request, "size").get_value_or(10)}; + std::size_t offset {getParameterAs(request, "offset").get_value_or(0)}; std::vector releases; @@ -541,11 +531,11 @@ handleGetAlbumListRequestCommon(const Wt::Http::ParameterMap& request, Database: if (*type == "random") { - releases = getRandomAlbums(db.getSession(), *offset, *size); + releases = getRandomAlbums(db.getSession(), offset, size); } else if (*type == "newest") { - auto after {Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1)}; + auto after {Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-6)}; releases = Database::Release::getLastAdded(db.getSession(), after, offset, size); } else if (*type == "alphabeticalByName") @@ -555,8 +545,6 @@ handleGetAlbumListRequestCommon(const Wt::Http::ParameterMap& request, Database: else throw Error {"Unsupported request"}; - LMS_LOG(API_SUBSONIC, DEBUG) << "Got " << releases.size() << " albums"; - Response response {Response::createOkResponse()}; Response::Node& albumListNode {response.createNode(id3 ? "albumList2" : "albumList")}; @@ -805,17 +793,10 @@ handleGetSongsByGenreRequest(const Wt::Http::ParameterMap& request, Database::Ha throw Error {Error::Code::RequiredParameterMissing}; // Optional params - auto size {getParameterAs(request, "count")}; - if (!size) - size = 10; + std::size_t size {getParameterAs(request, "count").get_value_or(10)}; + size = std::min(size, std::size_t {500}); - *size = std::min(*size, std::size_t {500}); - - auto offset {getParameterAs(request, "offset")}; - if (!offset) - offset = 0; - - LMS_LOG(API_SUBSONIC, DEBUG) << "genre ='" << *genre << "'"; + std::size_t offset {getParameterAs(request, "offset").get_value_or(0)}; Wt::Dbo::Transaction transaction {db.getSession()}; @@ -838,6 +819,64 @@ handleGetSongsByGenreRequest(const Wt::Http::ParameterMap& request, Database::Ha return response; } +Response +handleSearchRequestCommon(const Wt::Http::ParameterMap& request, Database::Handler& db, bool id3) +{ + // Mandatory params + auto query {getParameterAs(request, "query")}; + if (!query) + throw Error {Error::Code::RequiredParameterMissing}; + + std::vector keywords {splitString(*query, " ")}; + + // Optional params + std::size_t artistCount {getParameterAs(request, "artistCount").get_value_or(20)}; + std::size_t artistOffset {getParameterAs(request, "artistOffset").get_value_or(0)}; + std::size_t albumCount {getParameterAs(request, "albumCount").get_value_or(20)}; + std::size_t albumOffset {getParameterAs(request, "albumOffset").get_value_or(0)}; + std::size_t songCount {getParameterAs(request, "songCount").get_value_or(20)}; + std::size_t songOffset {getParameterAs(request, "songOffset").get_value_or(0)}; + + Wt::Dbo::Transaction transaction {db.getSession()}; + + Response response {Response::createOkResponse()}; + Response::Node& searchResult2Node {response.createNode(id3 ? "searchResult3" : "searchResult2")}; + + bool more; + { + auto artists {Database::Artist::getByFilter(db.getSession(), {}, keywords, artistOffset, artistCount, more)}; + for (const Database::Artist::pointer& artist : artists) + searchResult2Node.addArrayChild("artist", artistToResponseNode(artist, id3)); + } + + { + auto releases {Database::Release::getByFilter(db.getSession(), {}, keywords, albumOffset, albumCount, more)}; + for (const Database::Release::pointer& release : releases) + searchResult2Node.addArrayChild("album", releaseToResponseNode(release, id3)); + } + + { + auto tracks {Database::Track::getByFilter(db.getSession(), {}, keywords, songOffset, songCount, more)}; + for (const Database::Track::pointer& track : tracks) + searchResult2Node.addArrayChild("song", trackToResponseNode(track, id3)); + } + + return response; +} + +Response +handleSearch2Request(const Wt::Http::ParameterMap& request, Database::Handler& db) +{ + return handleSearchRequestCommon(request, db, false /* no id3 */); +} + +Response +handleSearch3Request(const Wt::Http::ParameterMap& request, Database::Handler& db) +{ + return handleSearchRequestCommon(request, db, true /* id3 */); +} + + static std::shared_ptr createTranscoder(const Wt::Http::ParameterMap& request, Database::Handler& db) @@ -846,11 +885,8 @@ createTranscoder(const Wt::Http::ParameterMap& request, Database::Handler& db) Id id {getParameterAsId(request, "id")}; // Optional params - auto maxBitRate {getParameterAs(request, "maxBitRate")}; - if (!maxBitRate) - maxBitRate = 128; - - *maxBitRate = clamp(*maxBitRate, std::size_t {48}, std::size_t {320}); + std::size_t maxBitRate {getParameterAs(request, "maxBitRate").get_value_or(128)}; + maxBitRate = clamp(maxBitRate, std::size_t {48}, std::size_t {320}); boost::filesystem::path trackPath; { @@ -858,17 +894,14 @@ createTranscoder(const Wt::Http::ParameterMap& request, Database::Handler& db) auto track {Database::Track::getById(db.getSession(), id.id)}; if (!track) - { - LMS_LOG(API_SUBSONIC, ERROR) << "Bad track id"; throw Error {Error::Code::RequestedDataNotFound}; - } trackPath = track->getPath(); } Av::TranscodeParameters parameters {}; - parameters.bitrate = *maxBitRate * 1000; + parameters.bitrate = maxBitRate * 1000; parameters.encoding = Av::Encoding::MP3; return std::make_shared(trackPath, parameters); @@ -877,8 +910,6 @@ createTranscoder(const Wt::Http::ParameterMap& request, Database::Handler& db) void handleStream(const Wt::Http::Request& request, Database::Handler& db, Wt::Http::Response& response) { - LMS_LOG(API_SUBSONIC, DEBUG) << "STREAM"; - // TODO store only weak ptrs and use a ring container to store shared_ptr? std::shared_ptr transcoder; @@ -891,7 +922,6 @@ handleStream(const Wt::Http::Request& request, Database::Handler& db, Wt::Http:: } else { - LMS_LOG(UI, DEBUG) << "Continuation!"; transcoder = Wt::cpp17::any_cast>(continuation->data()); } @@ -907,7 +937,6 @@ handleStream(const Wt::Http::Request& request, Database::Handler& db, Wt::Http:: transcoder->process(data, chunkSize); - LMS_LOG(API_SUBSONIC, DEBUG) << "Writing " << data.size() << " bytes..."; response.out().write(reinterpret_cast(&data[0]), data.size()); if (!response.out()) @@ -922,15 +951,11 @@ handleStream(const Wt::Http::Request& request, Database::Handler& db, Wt::Http:: continuation = response.createContinuation(); continuation->setData(transcoder); } - else - LMS_LOG(API_SUBSONIC, DEBUG) << "No more data!"; } void handleGetCoverArt(const Wt::Http::Request& request, Database::Handler& db, Wt::Http::Response& response) { - LMS_LOG(API_SUBSONIC, DEBUG) << "STREAM"; - // Mandatory params Id id {getParameterAsId(request.getParameterMap(), "id")}; diff --git a/src/api/subsonic/SubsonicResponse.cpp b/src/api/subsonic/SubsonicResponse.cpp index 0c744ab8..d6a6b6eb 100644 --- a/src/api/subsonic/SubsonicResponse.cpp +++ b/src/api/subsonic/SubsonicResponse.cpp @@ -239,7 +239,7 @@ Response::write(std::ostream& os, ResponseFormat format) // property_tree does not support empty json array std::ostringstream oss; boost::property_tree::write_json(oss, root); - os << std::regex_replace(oss.str(), std::regex {R"(\[[\r\n]*\s*\"\"[\r\n]*\s*\])"}, R"(\{\})"); + os << std::regex_replace(oss.str(), std::regex {R"(\[[\r\n]*\s*\"\"[\r\n]*\s*\])"}, R"({})"); break; } } diff --git a/src/database/Artist.cpp b/src/database/Artist.cpp index 81834fa1..bd022bf8 100644 --- a/src/database/Artist.cpp +++ b/src/database/Artist.cpp @@ -126,7 +126,7 @@ getQuery(Wt::Dbo::Session& session, std::vector Artist::getByFilter(Wt::Dbo::Session& session, const std::set& clusters, - const std::vector keywords, + const std::vector& keywords, boost::optional offset, boost::optional size, bool& moreResults) diff --git a/src/database/Artist.hpp b/src/database/Artist.hpp index c415e15a..bb376461 100644 --- a/src/database/Artist.hpp +++ b/src/database/Artist.hpp @@ -52,7 +52,7 @@ class Artist : public Wt::Dbo::Dbo static std::vector getByName(Wt::Dbo::Session& session, const std::string& name); static std::vector getByFilter(Wt::Dbo::Session& session, const std::set& clusters, // at least one track that belongs to these clusters - const std::vector keywords, // name must match all of these keywords + const std::vector& keywords, // name must match all of these keywords boost::optional offset, boost::optional size, bool& moreExpected);