diff --git a/conf/lms.conf b/conf/lms.conf index 56822c3f..d64477a7 100644 --- a/conf/lms.conf +++ b/conf/lms.conf @@ -79,9 +79,6 @@ api-subsonic-support-user-password-auth = true; # Main usage is to make auto detections for the 'p' (password) parameter work api-subsonic-old-server-protocol-clients = ("DSub"); -# List of clients for whom a default cover is served (as they do not have their own) -api-subsonic-default-cover-clients = ("DSub", "substreamer"); - # List of clients for whom open subsonic extensions and extra fields are disabled api-open-subsonic-disabled-clients = ("DSub"); diff --git a/src/libs/services/artwork/impl/ArtworkService.cpp b/src/libs/services/artwork/impl/ArtworkService.cpp index 2612be66..2aff44c1 100644 --- a/src/libs/services/artwork/impl/ArtworkService.cpp +++ b/src/libs/services/artwork/impl/ArtworkService.cpp @@ -192,85 +192,54 @@ namespace lms::cover return image; } - std::shared_ptr ArtworkService::getTrackImage(db::TrackId trackId, std::optional width) + std::shared_ptr ArtworkService::getImage(db::ImageId imageId, std::optional width) { - const ImageCache::EntryDesc cacheEntryDesc{ trackId, width }; + const ImageCache::EntryDesc cacheEntryDesc{ imageId, width }; std::shared_ptr cover{ _cache.getImage(cacheEntryDesc) }; if (cover) return cover; - std::filesystem::path trackFile; + std::filesystem::path imageFile; { db::Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - const db::Track::pointer track{ db::Track::find(session, trackId) }; - if (track && track->hasCover()) - trackFile = track->getAbsoluteFilePath(); + const db::Image::pointer image{ db::Image::find(session, imageId) }; + if (image) + imageFile = image->getAbsoluteFilePath(); } - cover = getTrackImage(trackFile, width); + cover = getFromImageFile(imageFile, width); if (cover) _cache.addImage(cacheEntryDesc, cover); return cover; } - std::shared_ptr ArtworkService::getReleaseCover(db::ReleaseId releaseId, std::optional width) - { - const ImageCache::EntryDesc cacheEntryDesc{ releaseId, width }; - - std::shared_ptr image{ _cache.getImage(cacheEntryDesc) }; - if (image) - return image; - - std::filesystem::path imagePath; - { - db::Session& session{ _db.getTLSSession() }; - auto transaction{ session.createReadTransaction() }; - - const db::Release::pointer release{ db::Release::find(session, releaseId) }; - if (release) - { - if (const db::Image::pointer dbImage{ release->getImage() }) - imagePath = dbImage->getAbsoluteFilePath(); - } - } - - image = getFromImageFile(imagePath, width); - if (image) - _cache.addImage(cacheEntryDesc, image); - - return image; - } - - std::shared_ptr ArtworkService::getArtistImage(db::ArtistId artistId, std::optional width) + std::shared_ptr ArtworkService::getTrackImage(db::TrackId trackId, std::optional width) { - const ImageCache::EntryDesc cacheEntryDesc{ artistId, width }; + const ImageCache::EntryDesc cacheEntryDesc{ trackId, width }; - std::shared_ptr artistImage{ _cache.getImage(cacheEntryDesc) }; - if (artistImage) - return artistImage; + std::shared_ptr cover{ _cache.getImage(cacheEntryDesc) }; + if (cover) + return cover; - std::filesystem::path imagePath; + std::filesystem::path trackFile; { db::Session& session{ _db.getTLSSession() }; - auto transaction{ session.createReadTransaction() }; - if (const db::Artist::pointer artist{ db::Artist::find(session, artistId) }) - { - if (const db::Image::pointer image{ artist->getImage() }) - imagePath = image->getAbsoluteFilePath(); - } + const db::Track::pointer track{ db::Track::find(session, trackId) }; + if (track && track->hasCover()) + trackFile = track->getAbsoluteFilePath(); } - artistImage = getFromImageFile(imagePath, width); - if (artistImage) - _cache.addImage(cacheEntryDesc, artistImage); + cover = getTrackImage(trackFile, width); + if (cover) + _cache.addImage(cacheEntryDesc, cover); - return artistImage; + return cover; } void ArtworkService::flushCache() diff --git a/src/libs/services/artwork/impl/ArtworkService.hpp b/src/libs/services/artwork/impl/ArtworkService.hpp index f08452a3..1dc1f5cd 100644 --- a/src/libs/services/artwork/impl/ArtworkService.hpp +++ b/src/libs/services/artwork/impl/ArtworkService.hpp @@ -48,9 +48,8 @@ namespace lms::cover ArtworkService& operator=(const ArtworkService&) = delete; private: + std::shared_ptr getImage(db::ImageId imageId, std::optional width) override; std::shared_ptr getTrackImage(db::TrackId trackId, std::optional width) override; - std::shared_ptr getReleaseCover(db::ReleaseId releaseId, std::optional width) override; - std::shared_ptr getArtistImage(db::ArtistId artistId, std::optional width) override; std::shared_ptr getDefaultReleaseCover() override; std::shared_ptr getDefaultArtistImage() override; diff --git a/src/libs/services/artwork/impl/ImageCache.hpp b/src/libs/services/artwork/impl/ImageCache.hpp index d5866930..6b60d577 100644 --- a/src/libs/services/artwork/impl/ImageCache.hpp +++ b/src/libs/services/artwork/impl/ImageCache.hpp @@ -25,8 +25,7 @@ #include #include -#include "database/ArtistId.hpp" -#include "database/ReleaseId.hpp" +#include "database/ImageId.hpp" #include "database/TrackId.hpp" #include "image/IEncodedImage.hpp" @@ -39,7 +38,7 @@ namespace lms::cover struct EntryDesc { - using VariantType = std::variant; + using VariantType = std::variant; VariantType id; std::optional size; diff --git a/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp b/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp index b0592015..66f986e6 100644 --- a/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp +++ b/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp @@ -23,8 +23,7 @@ #include #include -#include "database/ArtistId.hpp" -#include "database/ReleaseId.hpp" +#include "database/ImageId.hpp" #include "database/TrackId.hpp" #include "image/IEncodedImage.hpp" @@ -40,14 +39,11 @@ namespace lms::cover public: virtual ~IArtworkService() = default; - virtual std::shared_ptr getArtistImage(db::ArtistId artistId, std::optional width) = 0; + virtual std::shared_ptr getImage(db::ImageId imageId, std::optional width) = 0; // no logic to fallback to release here virtual std::shared_ptr getTrackImage(db::TrackId trackId, std::optional width) = 0; - // no logic to fallback to track here - virtual std::shared_ptr getReleaseCover(db::ReleaseId releaseId, std::optional width) = 0; - // Svg images dont have image "size" virtual std::shared_ptr getDefaultReleaseCover() = 0; virtual std::shared_ptr getDefaultArtistImage() = 0; diff --git a/src/libs/subsonic/CMakeLists.txt b/src/libs/subsonic/CMakeLists.txt index 76f2630c..875fce08 100644 --- a/src/libs/subsonic/CMakeLists.txt +++ b/src/libs/subsonic/CMakeLists.txt @@ -25,6 +25,7 @@ add_library(lmssubsonic SHARED impl/responses/ReplayGain.cpp impl/responses/Song.cpp impl/responses/User.cpp + impl/CoverArtId.cpp impl/ResponseFormat.cpp impl/ProtocolVersion.cpp impl/ParameterParsing.cpp diff --git a/src/libs/subsonic/impl/CoverArtId.cpp b/src/libs/subsonic/impl/CoverArtId.cpp new file mode 100644 index 00000000..7b64fd3b --- /dev/null +++ b/src/libs/subsonic/impl/CoverArtId.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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 3 of the License, or + * (at your option) any later version. + * + * LMS 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 LMS. If not, see . + */ + +#include "CoverArtId.hpp" + +#include "SubsonicId.hpp" +#include "core/String.hpp" + +namespace lms::api::subsonic +{ + namespace + { + constexpr char timestampSeparatorChar{ ':' }; + } + + std::string idToString(db::ImageId id) + { + return "im-" + id.toString(); + } + + std::string idToString(CoverArtId coverId) + { + // produce "id:timestamp" + std::string res{ std::visit([](auto&& id) { + return idToString(id); + }, + coverId.id) }; + + res += timestampSeparatorChar; + res += std::to_string(coverId.timestamp); + + return res; + } +} // namespace lms::api::subsonic + +// Used to parse parameters +namespace lms::core::stringUtils +{ + template<> + std::optional readAs(std::string_view str) + { + std::vector values{ core::stringUtils::splitString(str, '-') }; + if (values.size() != 2) + return std::nullopt; + + if (values[0] != "im") + return std::nullopt; + + if (const auto value{ core::stringUtils::readAs(values[1]) }) + return db::ImageId{ *value }; + + return std::nullopt; + } + + template<> + std::optional readAs(std::string_view str) + { + // expect "id:timestamp" + auto timeStampSeparator{ str.find_last_of(api::subsonic::timestampSeparatorChar) }; + if (timeStampSeparator == std::string_view::npos) + return std::nullopt; + + std::string_view strId{ str.substr(0, timeStampSeparator) }; + std::string_view strTimestamp{ str.substr(timeStampSeparator + 1) }; + + api::subsonic::CoverArtId cover; + if (const auto imagetId{ readAs(strId) }) + cover.id = *imagetId; + else if (const auto trackId{ readAs(strId) }) + cover.id = *trackId; + else + return std::nullopt; + + if (const auto timestamp{ readAs(strTimestamp) }) + cover.timestamp = *timestamp; + else + return std::nullopt; + + return cover; + } +} // namespace lms::core::stringUtils diff --git a/src/libs/subsonic/impl/CoverArtId.hpp b/src/libs/subsonic/impl/CoverArtId.hpp new file mode 100644 index 00000000..6753e9bc --- /dev/null +++ b/src/libs/subsonic/impl/CoverArtId.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS 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 3 of the License, or + * (at your option) any later version. + * + * LMS 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 LMS. If not, see . + */ + +#pragma once + +#include +#include + +#include "core/String.hpp" +#include "database/ImageId.hpp" +#include "database/TrackId.hpp" + +namespace lms::api::subsonic +{ + struct CoverArtId + { + std::variant id; + std::time_t timestamp; + }; + + std::string idToString(CoverArtId coverId); + std::string idToString(db::ImageId imageId); +} // namespace lms::api::subsonic + +// Used to parse parameters +namespace lms::core::stringUtils +{ + template<> + std::optional readAs(std::string_view str); + + template<> + std::optional readAs(std::string_view str); +} // namespace lms::core::stringUtils diff --git a/src/libs/subsonic/impl/RequestContext.hpp b/src/libs/subsonic/impl/RequestContext.hpp index f3ddda2f..1b80bf70 100644 --- a/src/libs/subsonic/impl/RequestContext.hpp +++ b/src/libs/subsonic/impl/RequestContext.hpp @@ -47,6 +47,5 @@ namespace lms::api::subsonic ProtocolVersion serverProtocolVersion; ResponseFormat responseFormat; bool enableOpenSubsonic{ true }; - bool enableDefaultCover{}; }; } // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index c21a54b0..6a38fae0 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -85,19 +85,6 @@ namespace lms::api::subsonic return res; } - std::unordered_set readDefaultCoverClients() - { - std::unordered_set res; - - core::Service::get()->visitStrings("api-subsonic-default-cover-clients", - [&](std::string_view client) { - res.emplace(std::string{ client }); - }, - { "DSub", "substreamer" }); - - return res; - } - std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap) { auto censorValue = [](const std::string& type, const std::string& value) -> std::string { @@ -307,7 +294,6 @@ namespace lms::api::subsonic SubsonicResource::SubsonicResource(db::Db& db) : _serverProtocolVersionsByClient{ readConfigProtocolVersions() } , _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() } - , _defaultReleaseCoverClients{ readDefaultCoverClients() } , _supportUserPasswordAuthentication{ core::Service::get()->getBool("api-subsonic-support-user-password-auth", true) } , _db{ db } { @@ -425,7 +411,6 @@ namespace lms::api::subsonic const Wt::Http::ParameterMap& parameters{ request.getParameterMap() }; const ClientInfo clientInfo{ getClientInfo(request) }; bool enableOpenSubsonic{ !_openSubsonicDisabledClients.contains(clientInfo.name) }; - bool enableDefaultCover{ _defaultReleaseCoverClients.contains(clientInfo.name) }; const ResponseFormat format{ getParameterAs(request.getParameterMap(), "f").value_or("xml") == "json" ? ResponseFormat::json : ResponseFormat::xml }; return RequestContext{ @@ -437,7 +422,6 @@ namespace lms::api::subsonic .serverProtocolVersion = getServerProtocolVersion(clientInfo.name), .responseFormat = format, .enableOpenSubsonic = enableOpenSubsonic, - .enableDefaultCover = enableDefaultCover }; } diff --git a/src/libs/subsonic/impl/SubsonicResource.hpp b/src/libs/subsonic/impl/SubsonicResource.hpp index 7c00f847..af8ace3d 100644 --- a/src/libs/subsonic/impl/SubsonicResource.hpp +++ b/src/libs/subsonic/impl/SubsonicResource.hpp @@ -51,7 +51,6 @@ namespace lms::api::subsonic const std::unordered_map _serverProtocolVersionsByClient; const std::unordered_set _openSubsonicDisabledClients; - const std::unordered_set _defaultReleaseCoverClients; const bool _supportUserPasswordAuthentication; db::Db& _db; diff --git a/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp b/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp index 78e09410..c5a738f6 100644 --- a/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp +++ b/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp @@ -24,7 +24,6 @@ #include "av/TranscodingParameters.hpp" #include "av/TranscodingResourceHandlerCreator.hpp" #include "av/Types.hpp" -#include "core/FileResourceHandlerCreator.hpp" #include "core/ILogger.hpp" #include "core/IResourceHandler.hpp" #include "core/String.hpp" @@ -35,6 +34,7 @@ #include "database/User.hpp" #include "services/artwork/IArtworkService.hpp" +#include "CoverArtId.hpp" #include "ParameterParsing.hpp" #include "RequestContext.hpp" #include "SubsonicId.hpp" @@ -326,36 +326,26 @@ namespace lms::api::subsonic void handleGetCoverArt(RequestContext& context, const Wt::Http::Request& /*request*/, Wt::Http::Response& response) { // Mandatory params - const auto trackId{ getParameterAs(context.parameters, "id") }; - const auto releaseId{ getParameterAs(context.parameters, "id") }; - const auto artistId{ getParameterAs(context.parameters, "id") }; - - if (!trackId && !releaseId && !artistId) - throw BadParameterGenericError{ "id" }; + const CoverArtId coverArtId{ getMandatoryParameterAs(context.parameters, "id") }; std::optional size{ getParameterAs(context.parameters, "size") }; if (size) *size = core::utils::clamp(*size, std::size_t{ 32 }, std::size_t{ 2048 }); - std::shared_ptr cover; - if (trackId) - cover = core::Service::get()->getTrackImage(*trackId, size); - else if (releaseId) - cover = core::Service::get()->getReleaseCover(*releaseId, size); - else if (artistId) - cover = core::Service::get()->getArtistImage(*artistId, size); - - if (!cover && context.enableDefaultCover && !artistId) - cover = core::Service::get()->getDefaultReleaseCover(); + std::shared_ptr image; + if (const db::TrackId * trackId{ std::get_if(&coverArtId.id) }) + image = core::Service::get()->getTrackImage(*trackId, size); + else if (const db::ImageId * imageId{ std::get_if(&coverArtId.id) }) + image = core::Service::get()->getImage(*imageId, size); - if (!cover) + if (!image) { response.setStatus(404); return; } - response.out().write(reinterpret_cast(cover->getData().data()), cover->getData().size()); - response.setMimeType(std::string{ cover->getMimeType() }); + response.out().write(reinterpret_cast(image->getData().data()), image->getData().size()); + response.setMimeType(std::string{ image->getMimeType() }); } } // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/responses/Album.cpp b/src/libs/subsonic/impl/responses/Album.cpp index 9d7666eb..5a33db2e 100644 --- a/src/libs/subsonic/impl/responses/Album.cpp +++ b/src/libs/subsonic/impl/responses/Album.cpp @@ -32,6 +32,7 @@ #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" +#include "CoverArtId.hpp" #include "RequestContext.hpp" #include "SubsonicId.hpp" #include "responses/Artist.hpp" @@ -84,9 +85,10 @@ namespace lms::api::subsonic } albumNode.setAttribute("created", core::stringUtils::toISO8601String(release->getLastWritten())); - if (release->getImage()) + if (const auto image{ release->getImage() }) { - albumNode.setAttribute("coverArt", idToString(release->getId())); + const CoverArtId coverArtId{ image->getId(), image->getLastWriteTime().toTime_t() }; + albumNode.setAttribute("coverArt", idToString(coverArtId)); } else { @@ -96,7 +98,8 @@ namespace lms::api::subsonic params.setRange(db::Range{ 0, 1 }); db::Track::find(context.dbSession, params, [&](const db::Track::pointer& track) { - albumNode.setAttribute("coverArt", idToString(track->getId())); + const CoverArtId coverArtId{ track->getId(), track->getLastWriteTime().toTime_t() }; + albumNode.setAttribute("coverArt", idToString(coverArtId)); }); } if (const auto year{ release->getYear() }) diff --git a/src/libs/subsonic/impl/responses/Artist.cpp b/src/libs/subsonic/impl/responses/Artist.cpp index 116d58e7..632e3548 100644 --- a/src/libs/subsonic/impl/responses/Artist.cpp +++ b/src/libs/subsonic/impl/responses/Artist.cpp @@ -29,6 +29,7 @@ #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" +#include "CoverArtId.hpp" #include "RequestContext.hpp" #include "SubsonicId.hpp" @@ -94,8 +95,11 @@ namespace lms::api::subsonic artistNode.setAttribute("id", idToString(artist->getId())); artistNode.setAttribute("name", artist->getName()); - if (artist->getImage()) - artistNode.setAttribute("coverArt", idToString(artist->getId())); + if (const auto image{ artist->getImage() }) + { + const CoverArtId coverArtId{ image->getId(), image->getLastWriteTime().toTime_t() }; + artistNode.setAttribute("coverArt", idToString(coverArtId)); + } const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) }; artistNode.setAttribute("albumCount", count); diff --git a/src/libs/subsonic/impl/responses/Playlist.cpp b/src/libs/subsonic/impl/responses/Playlist.cpp index 4aa0842b..7b12758f 100644 --- a/src/libs/subsonic/impl/responses/Playlist.cpp +++ b/src/libs/subsonic/impl/responses/Playlist.cpp @@ -23,6 +23,7 @@ #include "database/TrackList.hpp" #include "database/User.hpp" +#include "CoverArtId.hpp" #include "SubsonicId.hpp" namespace lms::api::subsonic @@ -50,7 +51,8 @@ namespace lms::api::subsonic params.setSortMethod(TrackSortMethod::TrackList); db::Track::find(session, params, [&](const db::Track::pointer& track) { - playlistNode.setAttribute("coverArt", idToString(track->getId())); + const CoverArtId coverArtId{ track->getId(), track->getLastWriteTime().toTime_t() }; + playlistNode.setAttribute("coverArt", idToString(coverArtId)); }); return playlistNode; diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index 93a60863..bc74729a 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -36,6 +36,7 @@ #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" +#include "CoverArtId.hpp" #include "RequestContext.hpp" #include "SubsonicId.hpp" #include "responses/Artist.hpp" @@ -109,9 +110,18 @@ namespace lms::api::subsonic const Release::pointer release{ track->getRelease() }; if (track->hasCover()) - trackResponse.setAttribute("coverArt", idToString(track->getId())); - else if (release && release->getImage()) - trackResponse.setAttribute("coverArt", idToString(release->getId())); + { + const CoverArtId coverArtId{ track->getId(), track->getLastWriteTime().toTime_t() }; + trackResponse.setAttribute("coverArt", idToString(coverArtId)); + } + else if (release) + { + if (const db::Image::pointer image{ release->getImage() }) + { + const CoverArtId coverArtId{ image->getId(), image->getLastWriteTime().toTime_t() }; + trackResponse.setAttribute("coverArt", idToString(coverArtId)); + } + } const std::vector& artists{ track->getArtists({ TrackArtistLinkType::Artist }) }; if (!artists.empty()) diff --git a/src/lms/ui/resource/ArtworkResource.cpp b/src/lms/ui/resource/ArtworkResource.cpp index 0bf8147b..70d4ea6d 100644 --- a/src/lms/ui/resource/ArtworkResource.cpp +++ b/src/lms/ui/resource/ArtworkResource.cpp @@ -60,8 +60,11 @@ namespace lms::ui auto transaction{ LmsApp->getDbSession().createReadTransaction() }; const db::Artist::pointer artist{ db::Artist::find(LmsApp->getDbSession(), artistId) }; - if (artist && artist->getImage()) - url = getArtistIdImageUrl(artistId, size); + if (artist) + { + if (const db::Image::pointer image{ artist->getImage() }) + url = getImageUrl(image->getId(), size, "artist"); + } } if (url.empty()) @@ -80,9 +83,9 @@ namespace lms::ui const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), releaseId) }; if (release) { - if (release->getImage()) + if (const db::Image::pointer image{ release->getImage() }) { - url = getReleaseIdCoverUrl(release->getId(), size); + url = getImageUrl(image->getId(), size, "release"); } else { @@ -92,7 +95,7 @@ namespace lms::ui params.setRange(db::Range{ 0, 1 }); db::Track::find(LmsApp->getDbSession(), params, [&](const db::Track::pointer& track) { - url = getTrackIdImageUrl(track->getId(), size); + url = getImageUrl(track->getId(), size, "release"); }); } } @@ -115,9 +118,14 @@ namespace lms::ui if (track) { if (track->hasCover()) - url = getTrackIdImageUrl(trackId, size); - else if (const db::Release::pointer release{ track->getRelease() }; release && release->getImage()) - url = getReleaseIdCoverUrl(release->getId(), size); + { + url = getImageUrl(trackId, size, "release"); + } + else if (const db::Release::pointer release{ track->getRelease() }) + { + if (const db::Image::pointer image{ release->getImage() }) + url = getImageUrl(image->getId(), size, "release"); + } } } @@ -127,29 +135,24 @@ namespace lms::ui return url; } - std::string ArtworkResource::getArtistIdImageUrl(db::ArtistId artistId, Size size) const + std::string ArtworkResource::getImageUrl(db::ImageId imageId, Size size, std::string_view type) const { - return url() + "&artistid=" + artistId.toString() + "&size=" + std::to_string(static_cast(size)); + return url() + "&imageid=" + imageId.toString() + "&size=" + std::to_string(static_cast(size)) + "&type=" + std::string{ type }; } - std::string ArtworkResource::getReleaseIdCoverUrl(db::ReleaseId releaseId, Size size) const + std::string ArtworkResource::getImageUrl(db::TrackId trackId, Size size, std::string_view type) const { - return url() + "&releaseid=" + releaseId.toString() + "&size=" + std::to_string(static_cast(size)); - } - - std::string ArtworkResource::getTrackIdImageUrl(db::TrackId trackId, Size size) const - { - return url() + "&trackid=" + trackId.toString() + "&size=" + std::to_string(static_cast(size)); + return url() + "&trackid=" + trackId.toString() + "&size=" + std::to_string(static_cast(size)) + "&type=" + std::string{ type }; } std::string ArtworkResource::getDefaultArtistImageUrl() const { - return url() + "&type=defaultartistimage"; + return url() + "&type=artist"; } std::string ArtworkResource::getDefaultReleaseCoverUrl() const { - return url() + "&type=defaultreleasecover"; + return url() + "&type=release"; } void ArtworkResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) @@ -157,58 +160,32 @@ namespace lms::ui LMS_SCOPED_TRACE_OVERVIEW("UI", "HandleCoverRequest"); // Retrieve parameters - const std::string* artistIdStr = request.getParameter("artistid"); + const std::string* imageIdStr = request.getParameter("imageid"); const std::string* trackIdStr = request.getParameter("trackid"); - const std::string* releaseIdStr = request.getParameter("releaseid"); const std::string* sizeStr = request.getParameter("size"); const std::string* typeStr = request.getParameter("type"); std::shared_ptr image; - // Mandatory parameter size - if ((artistIdStr || trackIdStr || releaseIdStr)) + if ((imageIdStr || trackIdStr)) { - if (!sizeStr) - { - LOG(DEBUG, "no size provided!"); - return; - } - - const auto size{ core::stringUtils::readAs(*sizeStr) }; - if (!size || *size > maxSize) + const auto size{ sizeStr ? core::stringUtils::readAs(*sizeStr) : std::nullopt }; + if (size && *size > maxSize) { LOG(DEBUG, "invalid size provided!"); return; } - if (artistIdStr) - { - LOG(DEBUG, "Requested cover for track " << *artistIdStr << ", size = " << *size); - - const std::optional artistId{ core::stringUtils::readAs(*artistIdStr) }; - if (!artistId) - return; - - image = core::Service::get()->getArtistImage(*artistId, *size); - if (!image) - image = core::Service::get()->getDefaultArtistImage(); - } - else if (releaseIdStr) + if (imageIdStr) { - LOG(DEBUG, "Requested cover for release " << *releaseIdStr << ", size = " << *size); - - const std::optional releaseId{ core::stringUtils::readAs(*releaseIdStr) }; - if (!releaseId) + const std::optional imageId{ core::stringUtils::readAs(*imageIdStr) }; + if (!imageId) return; - image = core::Service::get()->getReleaseCover(*releaseId, *size); - if (!image) - image = core::Service::get()->getDefaultReleaseCover(); + image = core::Service::get()->getImage(*imageId, size); } else if (trackIdStr) { - LOG(DEBUG, "Requested cover for track " << *trackIdStr << ", size = " << *size); - const std::optional trackId{ core::stringUtils::readAs(*trackIdStr) }; if (!trackId) { @@ -216,16 +193,15 @@ namespace lms::ui return; } - image = core::Service::get()->getTrackImage(*trackId, *size); - if (!image) - image = core::Service::get()->getDefaultReleaseCover(); + image = core::Service::get()->getTrackImage(*trackId, size); } } - else if (typeStr) + + if (!image) { - if (*typeStr == "defaultreleasecover") + if (*typeStr == "release") image = core::Service::get()->getDefaultReleaseCover(); - else if (*typeStr == "defaultartistimage") + else if (*typeStr == "artist") image = core::Service::get()->getDefaultArtistImage(); } diff --git a/src/lms/ui/resource/ArtworkResource.hpp b/src/lms/ui/resource/ArtworkResource.hpp index c3b40083..e3ba4a1c 100644 --- a/src/lms/ui/resource/ArtworkResource.hpp +++ b/src/lms/ui/resource/ArtworkResource.hpp @@ -22,6 +22,7 @@ #include #include "database/ArtistId.hpp" +#include "database/ImageId.hpp" #include "database/ReleaseId.hpp" #include "database/TrackId.hpp" @@ -46,9 +47,8 @@ namespace lms::ui std::string getTrackImageUrl(db::TrackId trackId, Size size) const; private: - std::string getArtistIdImageUrl(db::ArtistId artistId, Size size) const; - std::string getReleaseIdCoverUrl(db::ReleaseId releaseId, Size size) const; - std::string getTrackIdImageUrl(db::TrackId trackId, Size size) const; + std::string getImageUrl(db::ImageId imageId, Size size, std::string_view type) const; + std::string getImageUrl(db::TrackId trackId, Size size, std::string_view type) const; std::string getDefaultArtistImageUrl() const; std::string getDefaultReleaseCoverUrl() const;