Subsonic API: added missing getAlbumList types to make Ultrasonic happy + added a protocol version check
This commit is contained in:
@@ -73,11 +73,54 @@ readAs(const std::string& str)
|
|||||||
|
|
||||||
namespace API::Subsonic
|
namespace API::Subsonic
|
||||||
{
|
{
|
||||||
|
struct ClientVersion
|
||||||
|
{
|
||||||
|
unsigned major;
|
||||||
|
unsigned minor;
|
||||||
|
unsigned patch;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
boost::optional<API::Subsonic::ClientVersion>
|
||||||
|
readAs(const std::string& str)
|
||||||
|
{
|
||||||
|
// Expects "X.Y.Z"
|
||||||
|
const auto numbers {splitString(str, ".")};
|
||||||
|
if (numbers.size() != 3)
|
||||||
|
return boost::none;
|
||||||
|
|
||||||
|
API::Subsonic::ClientVersion version;
|
||||||
|
|
||||||
|
auto number {readAs<unsigned>(numbers[0])};
|
||||||
|
if (!number)
|
||||||
|
return boost::none;
|
||||||
|
version.major = *number;
|
||||||
|
|
||||||
|
number = {readAs<unsigned>(numbers[1])};
|
||||||
|
if (!number)
|
||||||
|
return boost::none;
|
||||||
|
version.minor = *number;
|
||||||
|
|
||||||
|
number = {readAs<unsigned>(numbers[2])};
|
||||||
|
if (!number)
|
||||||
|
return boost::none;
|
||||||
|
version.patch = *number;
|
||||||
|
|
||||||
|
|
||||||
|
return version;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
namespace API::Subsonic
|
||||||
|
{
|
||||||
|
|
||||||
struct ClientInfo
|
struct ClientInfo
|
||||||
{
|
{
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string user;
|
std::string user;
|
||||||
std::string password;
|
std::string password;
|
||||||
|
ClientVersion version;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RequestContext
|
struct RequestContext
|
||||||
@@ -215,6 +258,7 @@ getClientInfo(const Wt::Http::ParameterMap& parameters)
|
|||||||
res.name = getMandatoryParameterAs<std::string>(parameters, "c");
|
res.name = getMandatoryParameterAs<std::string>(parameters, "c");
|
||||||
res.user = getMandatoryParameterAs<std::string>(parameters, "u");
|
res.user = getMandatoryParameterAs<std::string>(parameters, "u");
|
||||||
res.password = decodePasswordIfNeeded(getMandatoryParameterAs<std::string>(parameters, "p"));
|
res.password = decodePasswordIfNeeded(getMandatoryParameterAs<std::string>(parameters, "p"));
|
||||||
|
res.version = getMandatoryParameterAs<ClientVersion>(parameters, "v");
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
@@ -757,6 +801,10 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3)
|
|||||||
{
|
{
|
||||||
releases = Release::getAll(context.dbSession, offset, size);
|
releases = Release::getAll(context.dbSession, offset, size);
|
||||||
}
|
}
|
||||||
|
else if (type == "alphabeticalByArtist")
|
||||||
|
{
|
||||||
|
releases = Release::getAllOrderedByArtist(context.dbSession, offset, size);
|
||||||
|
}
|
||||||
else if (type == "byYear")
|
else if (type == "byYear")
|
||||||
{
|
{
|
||||||
int fromYear {getMandatoryParameterAs<int>(context.parameters, "fromYear")};
|
int fromYear {getMandatoryParameterAs<int>(context.parameters, "fromYear")};
|
||||||
@@ -764,6 +812,10 @@ handleGetAlbumListRequestCommon(const RequestContext& context, bool id3)
|
|||||||
|
|
||||||
releases = Release::getByYear(context.dbSession, fromYear, toYear, offset, size);
|
releases = Release::getByYear(context.dbSession, fromYear, toYear, offset, size);
|
||||||
}
|
}
|
||||||
|
else if (type == "starred")
|
||||||
|
{
|
||||||
|
releases = user->getStarredReleases(offset, size);
|
||||||
|
}
|
||||||
else if (type == "byGenre")
|
else if (type == "byGenre")
|
||||||
{
|
{
|
||||||
// Mandatory param
|
// Mandatory param
|
||||||
@@ -878,7 +930,7 @@ handleGetArtistInfoRequestCommon(RequestContext& context, bool id3)
|
|||||||
throw Error {Error::CustomType::BadIdFormat};
|
throw Error {Error::CustomType::BadIdFormat};
|
||||||
|
|
||||||
// Optional params
|
// Optional params
|
||||||
std::size_t count {getParameterAs<std::size_t>(context.parameters, "count").get_value_or(10)};
|
std::size_t count {getParameterAs<std::size_t>(context.parameters, "count").get_value_or(20)};
|
||||||
|
|
||||||
Response response {Response::createOkResponse()};
|
Response response {Response::createOkResponse()};
|
||||||
Response::Node& artistInfoNode {response.createNode(id3 ? "artistInfo2" : "artistInfo")};
|
Response::Node& artistInfoNode {response.createNode(id3 ? "artistInfo2" : "artistInfo")};
|
||||||
@@ -1814,9 +1866,16 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Session& dbSession {getOrCreateDbSession(_db)};
|
// Mandatory parameters
|
||||||
|
|
||||||
const ClientInfo clientInfo {getClientInfo(parameters)};
|
const ClientInfo clientInfo {getClientInfo(parameters)};
|
||||||
|
if (clientInfo.version.major > API_VERSION_MAJOR)
|
||||||
|
throw Error {Error::Code::ServerMustUpgrade};
|
||||||
|
if (clientInfo.version.major < API_VERSION_MAJOR)
|
||||||
|
throw Error {Error::Code::ClientMustUpgrade};
|
||||||
|
if (clientInfo.version.minor > API_VERSION_MINOR)
|
||||||
|
throw Error {Error::Code::ServerMustUpgrade};
|
||||||
|
|
||||||
|
Session& dbSession {getOrCreateDbSession(_db)};
|
||||||
|
|
||||||
switch (getService<Auth::PasswordService>()->checkUserPassword(dbSession,
|
switch (getService<Auth::PasswordService>()->checkUserPassword(dbSession,
|
||||||
boost::asio::ip::address::from_string(request.clientAddress()),
|
boost::asio::ip::address::from_string(request.clientAddress()),
|
||||||
|
|||||||
@@ -29,8 +29,6 @@
|
|||||||
|
|
||||||
#include "utils/Exception.hpp"
|
#include "utils/Exception.hpp"
|
||||||
|
|
||||||
#define API_VERSION "1.12.0"
|
|
||||||
|
|
||||||
namespace API::Subsonic
|
namespace API::Subsonic
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -163,7 +161,7 @@ Response::createOkResponse()
|
|||||||
Node& responseNode {response._root.createChild("subsonic-response")};
|
Node& responseNode {response._root.createChild("subsonic-response")};
|
||||||
|
|
||||||
responseNode.setAttribute("status", "ok");
|
responseNode.setAttribute("status", "ok");
|
||||||
responseNode.setAttribute("version", API_VERSION);
|
responseNode.setAttribute("version", API_VERSION_STR);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -175,7 +173,7 @@ Response::createFailedResponse(const Error& error)
|
|||||||
Node& responseNode {response._root.createChild("subsonic-response")};
|
Node& responseNode {response._root.createChild("subsonic-response")};
|
||||||
|
|
||||||
responseNode.setAttribute("status", "failed");
|
responseNode.setAttribute("status", "failed");
|
||||||
responseNode.setAttribute("version", API_VERSION);
|
responseNode.setAttribute("version", API_VERSION_STR);
|
||||||
|
|
||||||
Node& errorNode {responseNode.createChild("error")};
|
Node& errorNode {responseNode.createChild("error")};
|
||||||
errorNode.setAttribute("code", std::to_string(static_cast<int>(error.getCode())));
|
errorNode.setAttribute("code", std::to_string(static_cast<int>(error.getCode())));
|
||||||
|
|||||||
@@ -21,9 +21,15 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
namespace API::Subsonic
|
namespace API::Subsonic
|
||||||
{
|
{
|
||||||
|
|
||||||
|
#define API_VERSION_MAJOR 1
|
||||||
|
#define API_VERSION_MINOR 12
|
||||||
|
#define API_VERSION_PATCH 0
|
||||||
|
#define API_VERSION_STR "1.12.0"
|
||||||
|
|
||||||
enum class ResponseFormat
|
enum class ResponseFormat
|
||||||
{
|
{
|
||||||
xml,
|
xml,
|
||||||
|
|||||||
@@ -96,6 +96,23 @@ Release::getAll(Session& session, boost::optional<std::size_t> offset, boost::op
|
|||||||
return std::vector<pointer>(res.begin(), res.end());
|
return std::vector<pointer>(res.begin(), res.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Release::pointer>
|
||||||
|
Release::getAllOrderedByArtist(Session& session, boost::optional<std::size_t> offset, boost::optional<std::size_t> size)
|
||||||
|
{
|
||||||
|
session.checkSharedLocked();
|
||||||
|
|
||||||
|
Wt::Dbo::collection<pointer> res = session.getDboSession().query<Wt::Dbo::ptr<Release>>(
|
||||||
|
"SELECT DISTINCT r FROM release r"
|
||||||
|
" INNER JOIN track t ON r.id = t.release_id"
|
||||||
|
" INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id"
|
||||||
|
" INNER JOIN artist a ON t_a_l.artist_id = a.id")
|
||||||
|
.offset(offset ? static_cast<int>(*offset) : -1)
|
||||||
|
.limit(size ? static_cast<int>(*size) : -1)
|
||||||
|
.orderBy("a.name COLLATE NOCASE, r.name COLLATE NOCASE");
|
||||||
|
|
||||||
|
return std::vector<pointer>(res.begin(), res.end());
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Release::pointer>
|
std::vector<Release::pointer>
|
||||||
Release::getAllRandom(Session& session, boost::optional<std::size_t> size)
|
Release::getAllRandom(Session& session, boost::optional<std::size_t> size)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ class Release : public Wt::Dbo::Dbo<Release>
|
|||||||
static pointer getById(Session& session, IdType id);
|
static pointer getById(Session& session, IdType id);
|
||||||
static std::vector<pointer> getAllOrphans(Session& session); // no track related
|
static std::vector<pointer> getAllOrphans(Session& session); // no track related
|
||||||
static std::vector<pointer> getAll(Session& session, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
|
static std::vector<pointer> getAll(Session& session, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
|
||||||
|
static std::vector<pointer> getAllOrderedByArtist(Session& session, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
|
||||||
static std::vector<pointer> getAllRandom(Session& session, boost::optional<std::size_t> size = {});
|
static std::vector<pointer> getAllRandom(Session& session, boost::optional<std::size_t> size = {});
|
||||||
static std::vector<pointer> getLastAdded(Session& session, const Wt::WDateTime& after, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
|
static std::vector<pointer> getLastAdded(Session& session, const Wt::WDateTime& after, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
|
||||||
static std::vector<pointer> getByYear(Session& session, int yearFrom, int yearTo, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
|
static std::vector<pointer> getByYear(Session& session, int yearFrom, int yearTo, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
|
||||||
|
|||||||
@@ -247,9 +247,13 @@ User::hasStarredRelease(Wt::Dbo::ptr<Release> release) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Wt::Dbo::ptr<Release>>
|
std::vector<Wt::Dbo::ptr<Release>>
|
||||||
User::getStarredReleases() const
|
User::getStarredReleases(boost::optional<std::size_t> offset, boost::optional<std::size_t> limit) const
|
||||||
{
|
{
|
||||||
return std::vector<Wt::Dbo::ptr<Release>>(_starredReleases.begin(), _starredReleases.end());
|
Wt::Dbo::collection<Wt::Dbo::ptr<Release>> res = _starredReleases.find()
|
||||||
|
.offset(offset ? static_cast<int>(*offset) : -1)
|
||||||
|
.limit(limit ? static_cast<int>(*limit) : -1);
|
||||||
|
|
||||||
|
return std::vector<Wt::Dbo::ptr<Release>>(res.begin(), res.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,8 @@
|
|||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
#include <Wt/Dbo/Dbo.h>
|
#include <Wt/Dbo/Dbo.h>
|
||||||
#include <Wt/WDateTime.h>
|
#include <Wt/WDateTime.h>
|
||||||
|
|
||||||
@@ -160,7 +162,7 @@ class User : public Wt::Dbo::Dbo<User>
|
|||||||
void starRelease(Wt::Dbo::ptr<Release> release);
|
void starRelease(Wt::Dbo::ptr<Release> release);
|
||||||
void unstarRelease(Wt::Dbo::ptr<Release> release);
|
void unstarRelease(Wt::Dbo::ptr<Release> release);
|
||||||
bool hasStarredRelease(Wt::Dbo::ptr<Release> release) const;
|
bool hasStarredRelease(Wt::Dbo::ptr<Release> release) const;
|
||||||
std::vector<Wt::Dbo::ptr<Release>> getStarredReleases() const;
|
std::vector<Wt::Dbo::ptr<Release>> getStarredReleases(boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {}) const;
|
||||||
|
|
||||||
void starTrack(Wt::Dbo::ptr<Track> track);
|
void starTrack(Wt::Dbo::ptr<Track> track);
|
||||||
void unstarTrack(Wt::Dbo::ptr<Track> track);
|
void unstarTrack(Wt::Dbo::ptr<Track> track);
|
||||||
|
|||||||
Reference in New Issue
Block a user