Subsonic API: now returns 404 when the requested cover art is not found (but a client list has been added to serve them a default cover art)

This commit is contained in:
emeric
2023-11-08 23:43:50 +01:00
parent 475519f19e
commit bd61093898
9 changed files with 188 additions and 162 deletions
+4 -1
View File
@@ -61,7 +61,10 @@ api-subsonic = true;
# Use this list to make the reported server version to 1.12.0 depending on the client's name # Use this list to make the reported server version to 1.12.0 depending on the client's name
# Main usage is to make auto detections for the 'p' (password) parameter work # Main usage is to make auto detections for the 'p' (password) parameter work
api-subsonic-report-old-server-protocol = ("DSub"); 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 # List of clients for whom open subsonic extensions and extra fields are disabled
api-open-subsonic-disabled-clients = ("DSub"); api-open-subsonic-disabled-clients = ("DSub");
@@ -345,9 +345,6 @@ namespace Cover
} }
} }
if (!cover)
cover = getDefault(width);
if (cover) if (cover)
saveToCache(cacheEntryDesc, cover); saveToCache(cacheEntryDesc, cover);
@@ -397,9 +394,6 @@ namespace Cover
cover = getFromTrack(session, releaseInfo->firstTrackId, width, false /* no release fallback */); cover = getFromTrack(session, releaseInfo->firstTrackId, width, false /* no release fallback */);
} }
if (!cover)
cover = getDefault(width);
if (cover) if (cover)
saveToCache(cacheEntryDesc, cover); saveToCache(cacheEntryDesc, cover);
@@ -56,12 +56,10 @@ namespace Cover
&& size == other.size; && size == other.size;
} }
}; };
} // ns Cover } // ns Cover
namespace std namespace std
{ {
template<> template<>
class hash<Cover::CacheEntryDesc> class hash<Cover::CacheEntryDesc>
{ {
@@ -86,18 +84,15 @@ namespace Cover
class CoverService : public ICoverService class CoverService : public ICoverService
{ {
public: public:
CoverService(Database::Db& db, CoverService(Database::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath);
const std::filesystem::path& execPath,
const std::filesystem::path& defaultCoverPath);
CoverService(const CoverService&) = delete; CoverService(const CoverService&) = delete;
CoverService& operator=(const CoverService&) = delete; CoverService& operator=(const CoverService&) = delete;
CoverService(CoverService&&) = delete;
CoverService& operator=(CoverService&&) = delete;
private: private:
std::shared_ptr<Image::IEncodedImage> getFromTrack(Database::TrackId trackId, Image::ImageSize width) override; std::shared_ptr<Image::IEncodedImage> getFromTrack(Database::TrackId trackId, Image::ImageSize width) override;
std::shared_ptr<Image::IEncodedImage> getFromRelease(Database::ReleaseId releaseId, Image::ImageSize width) override; std::shared_ptr<Image::IEncodedImage> getFromRelease(Database::ReleaseId releaseId, Image::ImageSize width) override;
std::shared_ptr<Image::IEncodedImage> getDefault(Image::ImageSize width) override;
void flushCache() override; void flushCache() override;
void setJpegQuality(unsigned quality) override; void setJpegQuality(unsigned quality) override;
@@ -109,7 +104,6 @@ namespace Cover
std::multimap<std::string, std::filesystem::path> getCoverPaths(const std::filesystem::path& directoryPath) const; std::multimap<std::string, std::filesystem::path> getCoverPaths(const std::filesystem::path& directoryPath) const;
std::unique_ptr<Image::IEncodedImage> getFromDirectory(const std::filesystem::path& directory, Image::ImageSize width) const; std::unique_ptr<Image::IEncodedImage> getFromDirectory(const std::filesystem::path& directory, Image::ImageSize width) const;
std::unique_ptr<Image::IEncodedImage> getFromSameNamedFile(const std::filesystem::path& filePath, Image::ImageSize width) const; std::unique_ptr<Image::IEncodedImage> getFromSameNamedFile(const std::filesystem::path& filePath, Image::ImageSize width) const;
std::shared_ptr<Image::IEncodedImage> getDefault(Image::ImageSize width);
bool checkCoverFile(const std::filesystem::path& directoryPath) const; bool checkCoverFile(const std::filesystem::path& directoryPath) const;
@@ -41,14 +41,14 @@ namespace Cover
virtual std::shared_ptr<Image::IEncodedImage> getFromTrack(Database::TrackId trackId, Image::ImageSize width) = 0; virtual std::shared_ptr<Image::IEncodedImage> getFromTrack(Database::TrackId trackId, Image::ImageSize width) = 0;
virtual std::shared_ptr<Image::IEncodedImage> getFromRelease(Database::ReleaseId releaseId, Image::ImageSize width) = 0; virtual std::shared_ptr<Image::IEncodedImage> getFromRelease(Database::ReleaseId releaseId, Image::ImageSize width) = 0;
virtual std::shared_ptr<Image::IEncodedImage> getDefault(Image::ImageSize width) = 0;
virtual void flushCache() = 0; virtual void flushCache() = 0;
virtual void setJpegQuality(unsigned quality) = 0; // from 1 to 100 virtual void setJpegQuality(unsigned quality) = 0; // from 1 to 100
}; };
std::unique_ptr<ICoverService> createCoverService(Database::Db& db, std::unique_ptr<ICoverService> createCoverService(Database::Db& db, const std::filesystem::path& execPath, const std::filesystem::path& defaultCoverPath);
const std::filesystem::path& execPath,
const std::filesystem::path& defaultCoverPath);
} // namespace CoverArt } // namespace CoverArt
@@ -42,6 +42,7 @@ namespace API::Subsonic
ClientInfo clientInfo; ClientInfo clientInfo;
ProtocolVersion serverProtocolVersion; ProtocolVersion serverProtocolVersion;
bool enableOpenSubsonic{ true }; bool enableOpenSubsonic{ true };
bool enableDefaultCover{ };
}; };
} }
+17 -2
View File
@@ -66,7 +66,7 @@ namespace API::Subsonic
{ {
std::unordered_map<std::string, ProtocolVersion> res; std::unordered_map<std::string, ProtocolVersion> res;
Service<IConfig>::get()->visitStrings("api-subsonic-report-old-server-protocol", Service<IConfig>::get()->visitStrings("api-subsonic-old-server-protocol-clients",
[&](std::string_view client) [&](std::string_view client)
{ {
res.emplace(std::string{ client }, ProtocolVersion{ 1, 12, 0 }); res.emplace(std::string{ client }, ProtocolVersion{ 1, 12, 0 });
@@ -88,6 +88,19 @@ namespace API::Subsonic
return res; return res;
} }
std::unordered_set<std::string> readDefaultCoverClients()
{
std::unordered_set<std::string> res;
Service<IConfig>::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) std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap)
{ {
auto censorValue = [](const std::string& type, const std::string& value) -> std::string auto censorValue = [](const std::string& type, const std::string& value) -> std::string
@@ -266,6 +279,7 @@ namespace API::Subsonic
SubsonicResource::SubsonicResource(Db& db) SubsonicResource::SubsonicResource(Db& db)
: _serverProtocolVersionsByClient{ readConfigProtocolVersions() } : _serverProtocolVersionsByClient{ readConfigProtocolVersions() }
, _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() } , _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() }
, _defaultCoverClients{ readDefaultCoverClients() }
, _db{ db } , _db{ db }
{ {
} }
@@ -378,8 +392,9 @@ namespace API::Subsonic
const ClientInfo clientInfo{ getClientInfo(parameters) }; const ClientInfo clientInfo{ getClientInfo(parameters) };
const Database::UserId userId{ authenticateUser(request, clientInfo) }; const Database::UserId userId{ authenticateUser(request, clientInfo) };
bool enableOpenSubsonic{ _openSubsonicDisabledClients.find(clientInfo.name) == std::cend(_openSubsonicDisabledClients) }; bool enableOpenSubsonic{ _openSubsonicDisabledClients.find(clientInfo.name) == std::cend(_openSubsonicDisabledClients) };
bool enableDefaultCover{ _defaultCoverClients.find(clientInfo.name) != std::cend(_openSubsonicDisabledClients) };
return { parameters, _db.getTLSSession(), userId, clientInfo, getServerProtocolVersion(clientInfo.name), enableOpenSubsonic }; return { parameters, _db.getTLSSession(), userId, clientInfo, getServerProtocolVersion(clientInfo.name), enableOpenSubsonic, enableDefaultCover };
} }
Database::UserId SubsonicResource::authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo) Database::UserId SubsonicResource::authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo)
@@ -53,6 +53,8 @@ namespace API::Subsonic
const std::unordered_map<std::string, ProtocolVersion> _serverProtocolVersionsByClient; const std::unordered_map<std::string, ProtocolVersion> _serverProtocolVersionsByClient;
const std::unordered_set<std::string> _openSubsonicDisabledClients; const std::unordered_set<std::string> _openSubsonicDisabledClients;
const std::unordered_set<std::string> _defaultCoverClients;
Database::Db& _db; Database::Db& _db;
}; };
@@ -207,8 +207,9 @@ namespace API::Subsonic
// Mandatory params // Mandatory params
const auto trackId{ getParameterAs<TrackId>(context.parameters, "id") }; const auto trackId{ getParameterAs<TrackId>(context.parameters, "id") };
const auto releaseId{ getParameterAs<ReleaseId>(context.parameters, "id") }; const auto releaseId{ getParameterAs<ReleaseId>(context.parameters, "id") };
const auto artistId{ getParameterAs<ArtistId>(context.parameters, "id") };
if (!trackId && !releaseId) if (!trackId && !releaseId && !artistId)
throw BadParameterGenericError{ "id" }; throw BadParameterGenericError{ "id" };
std::size_t size{ getParameterAs<std::size_t>(context.parameters, "size").value_or(1024) }; std::size_t size{ getParameterAs<std::size_t>(context.parameters, "size").value_or(1024) };
@@ -219,6 +220,21 @@ namespace API::Subsonic
cover = Service<Cover::ICoverService>::get()->getFromTrack(*trackId, size); cover = Service<Cover::ICoverService>::get()->getFromTrack(*trackId, size);
else if (releaseId) else if (releaseId)
cover = Service<Cover::ICoverService>::get()->getFromRelease(*releaseId, size); cover = Service<Cover::ICoverService>::get()->getFromRelease(*releaseId, size);
else if (artistId)
{
// TODO handle a placeholder for artists
response.setStatus(404);
return;
}
if (!cover && context.enableDefaultCover)
cover = Service<Cover::ICoverService>::get()->getDefault(size);
if (!cover)
{
response.setStatus(404);
return;
}
response.out().write(reinterpret_cast<const char*>(cover->getData()), cover->getDataSize()); response.out().write(reinterpret_cast<const char*>(cover->getData()), cover->getDataSize());
response.setMimeType(std::string{ cover->getMimeType() }); response.setMimeType(std::string{ cover->getMimeType() });
+7 -6
View File
@@ -49,20 +49,17 @@ CoverResource::~CoverResource()
beingDeleted(); beingDeleted();
} }
std::string std::string CoverResource::getReleaseUrl(Database::ReleaseId releaseId, Size size) const
CoverResource::getReleaseUrl(Database::ReleaseId releaseId, Size size) const
{ {
return url() + "&releaseid=" + releaseId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size)); return url() + "&releaseid=" + releaseId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size));
} }
std::string std::string CoverResource::getTrackUrl(Database::TrackId trackId, Size size) const
CoverResource::getTrackUrl(Database::TrackId trackId, Size size) const
{ {
return url() + "&trackid=" + trackId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size)); return url() + "&trackid=" + trackId.toString() + "&size=" + std::to_string(static_cast<std::size_t>(size));
} }
void void CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response)
{ {
// Retrieve parameters // Retrieve parameters
const std::string* trackIdStr = request.getParameter("trackid"); const std::string* trackIdStr = request.getParameter("trackid");
@@ -97,6 +94,8 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
} }
cover = Service<Cover::ICoverService>::get()->getFromTrack(*trackId, *size); cover = Service<Cover::ICoverService>::get()->getFromTrack(*trackId, *size);
if (!cover)
cover = Service<Cover::ICoverService>::get()->getDefault(*size);
} }
else if (releaseIdStr) else if (releaseIdStr)
{ {
@@ -107,6 +106,8 @@ CoverResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Respons
return; return;
cover = Service<Cover::ICoverService>::get()->getFromRelease(*releaseId, *size); cover = Service<Cover::ICoverService>::get()->getFromRelease(*releaseId, *size);
if (!cover)
cover = Service<Cover::ICoverService>::get()->getDefault(*size);
} }
else else
{ {