API Subsonic: better support, added genre
This commit is contained in:
@@ -27,6 +27,7 @@
|
|||||||
#include "av/AvTranscoder.hpp"
|
#include "av/AvTranscoder.hpp"
|
||||||
#include "cover/CoverArtGrabber.hpp"
|
#include "cover/CoverArtGrabber.hpp"
|
||||||
#include "database/Artist.hpp"
|
#include "database/Artist.hpp"
|
||||||
|
#include "database/Cluster.hpp"
|
||||||
#include "database/Release.hpp"
|
#include "database/Release.hpp"
|
||||||
#include "database/Track.hpp"
|
#include "database/Track.hpp"
|
||||||
#include "main/Services.hpp"
|
#include "main/Services.hpp"
|
||||||
@@ -40,11 +41,16 @@
|
|||||||
#define GET_LICENSE_URL "/rest/getLicense.view"
|
#define GET_LICENSE_URL "/rest/getLicense.view"
|
||||||
#define GET_RANDOM_SONGS_URL "/rest/getRandomSongs.view"
|
#define GET_RANDOM_SONGS_URL "/rest/getRandomSongs.view"
|
||||||
#define GET_ALBUM_LIST_URL "/rest/getAlbumList.view"
|
#define GET_ALBUM_LIST_URL "/rest/getAlbumList.view"
|
||||||
|
#define GET_ALBUM_LIST2_URL "/rest/getAlbumList2.view"
|
||||||
#define GET_MUSIC_DIRECTORY_URL "/rest/getMusicDirectory.view"
|
#define GET_MUSIC_DIRECTORY_URL "/rest/getMusicDirectory.view"
|
||||||
#define GET_STARRED_URL "/rest/getStarred.view"
|
|
||||||
#define GET_MUSIC_FOLDERS_URL "/rest/getMusicFolders.view"
|
#define GET_MUSIC_FOLDERS_URL "/rest/getMusicFolders.view"
|
||||||
|
#define GET_GENRES_URL "/rest/getGenres.view"
|
||||||
#define GET_INDEXES_URL "/rest/getIndexes.view"
|
#define GET_INDEXES_URL "/rest/getIndexes.view"
|
||||||
#define GET_ARTISTS_URL "/rest/getArtists.view"
|
#define GET_ARTISTS_URL "/rest/getArtists.view"
|
||||||
|
#define GET_STARRED_URL "/rest/getStarred.view"
|
||||||
|
#define GET_STARRED2_URL "/rest/getStarred2.view"
|
||||||
|
#define GET_PLAYLISTS_URL "/rest/getPlaylists.view"
|
||||||
|
#define GET_SONGS_BY_GENRE_URL "/rest/getSongsByGenre.view"
|
||||||
|
|
||||||
// MediaRetrievals
|
// MediaRetrievals
|
||||||
#define STREAM_URL "/rest/stream.view"
|
#define STREAM_URL "/rest/stream.view"
|
||||||
@@ -59,10 +65,16 @@ static Response handlePingRequest(const Wt::Http::ParameterMap& request, Databas
|
|||||||
static Response handleGetLicenseRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
static Response handleGetLicenseRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
||||||
static Response handleGetRandomSongsRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
static Response handleGetRandomSongsRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
||||||
static Response handleGetAlbumListRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
static Response handleGetAlbumListRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
||||||
|
static Response handleGetAlbumList2Request(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
||||||
static Response handleGetMusicDirectoryRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
static Response handleGetMusicDirectoryRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
||||||
static Response handleGetMusicFoldersRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
static Response handleGetMusicFoldersRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
||||||
|
static Response handleGetGenresRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
||||||
static Response handleGetIndexesRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
static Response handleGetIndexesRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
||||||
static Response handleGetArtistsRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
static Response handleGetArtistsRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
||||||
|
static Response handleGetStarredRequest(const Wt::Http::ParameterMap& request, Database::Handler& db);
|
||||||
|
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);
|
||||||
|
|
||||||
// MediaRetrievals
|
// MediaRetrievals
|
||||||
using MediaRetrivalHandlerFunc = std::function<void(const Wt::Http::Request&, Database::Handler&, Wt::Http::Response& response)>;
|
using MediaRetrivalHandlerFunc = std::function<void(const Wt::Http::Request&, Database::Handler&, Wt::Http::Response& response)>;
|
||||||
@@ -75,11 +87,16 @@ static std::map<std::string, RequestHandlerFunc> requestHandlers
|
|||||||
{GET_LICENSE_URL, handleGetLicenseRequest},
|
{GET_LICENSE_URL, handleGetLicenseRequest},
|
||||||
{GET_RANDOM_SONGS_URL, handleGetRandomSongsRequest},
|
{GET_RANDOM_SONGS_URL, handleGetRandomSongsRequest},
|
||||||
{GET_ALBUM_LIST_URL, handleGetAlbumListRequest},
|
{GET_ALBUM_LIST_URL, handleGetAlbumListRequest},
|
||||||
|
{GET_ALBUM_LIST2_URL, handleGetAlbumList2Request},
|
||||||
{GET_MUSIC_DIRECTORY_URL, handleGetMusicDirectoryRequest},
|
{GET_MUSIC_DIRECTORY_URL, handleGetMusicDirectoryRequest},
|
||||||
{GET_STARRED_URL, handlePingRequest}, // TODO
|
|
||||||
{GET_MUSIC_FOLDERS_URL, handleGetMusicFoldersRequest},
|
{GET_MUSIC_FOLDERS_URL, handleGetMusicFoldersRequest},
|
||||||
|
{GET_GENRES_URL, handleGetGenresRequest},
|
||||||
{GET_INDEXES_URL, handleGetIndexesRequest},
|
{GET_INDEXES_URL, handleGetIndexesRequest},
|
||||||
{GET_ARTISTS_URL, handleGetArtistsRequest},
|
{GET_ARTISTS_URL, handleGetArtistsRequest},
|
||||||
|
{GET_STARRED_URL, handleGetStarredRequest},
|
||||||
|
{GET_STARRED2_URL, handleGetStarred2Request},
|
||||||
|
{GET_PLAYLISTS_URL, handleGetPlaylistsRequest},
|
||||||
|
{GET_SONGS_BY_GENRE_URL, handleGetSongsByGenreRequest},
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::map<std::string, MediaRetrivalHandlerFunc> mediaRetrievalHandlers
|
static std::map<std::string, MediaRetrivalHandlerFunc> mediaRetrievalHandlers
|
||||||
@@ -104,6 +121,21 @@ getParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& pa
|
|||||||
return readAs<T>(it->second.front());
|
return readAs<T>(it->second.front());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<>
|
||||||
|
boost::optional<std::string>
|
||||||
|
getParameterAs(const Wt::Http::ParameterMap& parameterMap, const std::string& param)
|
||||||
|
{
|
||||||
|
boost::optional<std::string> res;
|
||||||
|
|
||||||
|
auto it = parameterMap.find(param);
|
||||||
|
if (it == parameterMap.end())
|
||||||
|
return res;
|
||||||
|
|
||||||
|
if (it->second.size() != 1)
|
||||||
|
return res;
|
||||||
|
|
||||||
|
return it->second.front();
|
||||||
|
}
|
||||||
|
|
||||||
struct ClientInfo
|
struct ClientInfo
|
||||||
{
|
{
|
||||||
@@ -195,7 +227,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
|
|||||||
if (itHandler != requestHandlers.end())
|
if (itHandler != requestHandlers.end())
|
||||||
{
|
{
|
||||||
Response resp {(itHandler->second)(request.getParameterMap(), _db)};
|
Response resp {(itHandler->second)(request.getParameterMap(), _db)};
|
||||||
responseToStream(resp, clientInfo->format, response.out());
|
resp.write(response.out(), clientInfo->format);
|
||||||
response.setMimeType(ResponseFormatToMimeType(clientInfo->format));
|
response.setMimeType(ResponseFormatToMimeType(clientInfo->format));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -213,7 +245,7 @@ SubsonicResource::handleRequest(const Wt::Http::Request &request, Wt::Http::Resp
|
|||||||
catch (const Error& e)
|
catch (const Error& e)
|
||||||
{
|
{
|
||||||
Response resp {Response::createFailedResponse(e)};
|
Response resp {Response::createFailedResponse(e)};
|
||||||
responseToStream(resp, clientInfo->format, response.out());
|
resp.write(response.out(), clientInfo->format);
|
||||||
response.setMimeType(ResponseFormatToMimeType(clientInfo->format));
|
response.setMimeType(ResponseFormatToMimeType(clientInfo->format));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -301,15 +333,30 @@ static
|
|||||||
Response::Node
|
Response::Node
|
||||||
artistToResponseNode(const Database::Artist::pointer& artist)
|
artistToResponseNode(const Database::Artist::pointer& artist)
|
||||||
{
|
{
|
||||||
Response::Node artistResponse;
|
Response::Node artistNode;
|
||||||
|
|
||||||
artistResponse.setAttribute("id", IdToString({Id::Type::Artist, artist.id()}));
|
artistNode.setAttribute("id", IdToString({Id::Type::Artist, artist.id()}));
|
||||||
artistResponse.setAttribute("name", artist->getName());
|
artistNode.setAttribute("name", artist->getName());
|
||||||
artistResponse.setAttribute("albumCount", std::to_string(artist->getReleases().size()));
|
artistNode.setAttribute("albumCount", std::to_string(artist->getReleases().size()));
|
||||||
|
|
||||||
return artistResponse;
|
return artistNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
Response::Node
|
||||||
|
clusterToResponseNode(const Database::Cluster::pointer& cluster)
|
||||||
|
{
|
||||||
|
Response::Node clusterNode;
|
||||||
|
|
||||||
|
clusterNode.setValue(cluster->getName());
|
||||||
|
clusterNode.setAttribute("songCount", std::to_string(cluster->getTrackIds().size()));
|
||||||
|
{
|
||||||
|
auto releases {Database::Release::getByFilter(*cluster.session(), {cluster.id()})};
|
||||||
|
clusterNode.setAttribute("albumCount", std::to_string(releases.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return clusterNode;
|
||||||
|
}
|
||||||
|
|
||||||
// Handlers
|
// Handlers
|
||||||
Response
|
Response
|
||||||
@@ -339,6 +386,8 @@ handleGetRandomSongsRequest(const Wt::Http::ParameterMap& parameters, Database::
|
|||||||
if (!size)
|
if (!size)
|
||||||
size = 50;
|
size = 50;
|
||||||
|
|
||||||
|
*size = std::min(*size, std::size_t {500});
|
||||||
|
|
||||||
Wt::Dbo::Transaction transaction {db.getSession()};
|
Wt::Dbo::Transaction transaction {db.getSession()};
|
||||||
|
|
||||||
auto tracks {Database::Track::getAllRandom(db.getSession(), *size)};
|
auto tracks {Database::Track::getAllRandom(db.getSession(), *size)};
|
||||||
@@ -419,6 +468,10 @@ handleGetAlbumListRequest(const Wt::Http::ParameterMap& request, Database::Handl
|
|||||||
auto after {Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1)};
|
auto after {Wt::WLocalDateTime::currentServerDateTime().toUTC().addMonths(-1)};
|
||||||
releases = Database::Release::getLastAdded(db.getSession(), after, offset, size);
|
releases = Database::Release::getLastAdded(db.getSession(), after, offset, size);
|
||||||
}
|
}
|
||||||
|
else if (*type == "alphabeticalByName")
|
||||||
|
{
|
||||||
|
releases = Database::Release::getAll(db.getSession(), offset, size);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
throw Error {"Unsupported request"};
|
throw Error {"Unsupported request"};
|
||||||
|
|
||||||
@@ -433,6 +486,15 @@ handleGetAlbumListRequest(const Wt::Http::ParameterMap& request, Database::Handl
|
|||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Response
|
||||||
|
handleGetAlbumList2Request(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
||||||
|
{
|
||||||
|
Response response {Response::createOkResponse()};
|
||||||
|
response.createNode("albumList2");
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
Response
|
Response
|
||||||
handleGetMusicDirectoryRequest(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
handleGetMusicDirectoryRequest(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
||||||
{
|
{
|
||||||
@@ -445,8 +507,6 @@ handleGetMusicDirectoryRequest(const Wt::Http::ParameterMap& request, Database::
|
|||||||
if (!id)
|
if (!id)
|
||||||
throw Error {"Bad id"};
|
throw Error {"Bad id"};
|
||||||
|
|
||||||
Wt::Dbo::Transaction transaction {db.getSession()};
|
|
||||||
|
|
||||||
Response response {Response::createOkResponse()};
|
Response response {Response::createOkResponse()};
|
||||||
Response::Node& directoryNode {response.createNode("directory")};
|
Response::Node& directoryNode {response.createNode("directory")};
|
||||||
|
|
||||||
@@ -454,6 +514,8 @@ handleGetMusicDirectoryRequest(const Wt::Http::ParameterMap& request, Database::
|
|||||||
{
|
{
|
||||||
case Id::Type::Artist:
|
case Id::Type::Artist:
|
||||||
{
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {db.getSession()};
|
||||||
|
|
||||||
auto artist {Database::Artist::getById(db.getSession(), id->id)};
|
auto artist {Database::Artist::getById(db.getSession(), id->id)};
|
||||||
if (!artist)
|
if (!artist)
|
||||||
throw Error {Error::Code::RequestedDataNotFound};
|
throw Error {Error::Code::RequestedDataNotFound};
|
||||||
@@ -467,6 +529,8 @@ handleGetMusicDirectoryRequest(const Wt::Http::ParameterMap& request, Database::
|
|||||||
|
|
||||||
case Id::Type::Release:
|
case Id::Type::Release:
|
||||||
{
|
{
|
||||||
|
Wt::Dbo::Transaction transaction {db.getSession()};
|
||||||
|
|
||||||
auto release {Database::Release::getById(db.getSession(), id->id)};
|
auto release {Database::Release::getById(db.getSession(), id->id)};
|
||||||
if (!release)
|
if (!release)
|
||||||
throw Error {Error::Code::RequestedDataNotFound};
|
throw Error {Error::Code::RequestedDataNotFound};
|
||||||
@@ -499,14 +563,36 @@ handleGetMusicFoldersRequest(const Wt::Http::ParameterMap& request, Database::Ha
|
|||||||
}
|
}
|
||||||
|
|
||||||
Response
|
Response
|
||||||
handleGetIndexesRequest(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
handleGetGenresRequest(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
||||||
{
|
{
|
||||||
|
Response response {Response::createOkResponse()};
|
||||||
|
|
||||||
|
Response::Node& genresNode {response.createNode("genres")};
|
||||||
|
|
||||||
Wt::Dbo::Transaction transaction {db.getSession()};
|
Wt::Dbo::Transaction transaction {db.getSession()};
|
||||||
|
|
||||||
|
auto clusterType {Database::ClusterType::getByName(db.getSession(), "GENRE")};
|
||||||
|
if (clusterType)
|
||||||
|
{
|
||||||
|
auto clusters {clusterType->getClusters()};
|
||||||
|
|
||||||
|
for (const Database::Cluster::pointer& cluster : clusters)
|
||||||
|
genresNode.addArrayChild("genre", clusterToResponseNode(cluster));
|
||||||
|
}
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response
|
||||||
|
handleGetIndexesRequest(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
||||||
|
{
|
||||||
Response response {Response::createOkResponse()};
|
Response response {Response::createOkResponse()};
|
||||||
Response::Node& artistsNode {response.createNode("indexes")};
|
Response::Node& artistsNode {response.createNode("indexes")};
|
||||||
|
|
||||||
Response::Node& indexNode {artistsNode.createArrayChild("index")};
|
Response::Node& indexNode {artistsNode.createArrayChild("index")};
|
||||||
|
indexNode.setAttribute("name", "?");
|
||||||
|
|
||||||
|
Wt::Dbo::Transaction transaction {db.getSession()};
|
||||||
|
|
||||||
auto artists {Database::Artist::getAll(db.getSession())};
|
auto artists {Database::Artist::getAll(db.getSession())};
|
||||||
for (const Database::Artist::pointer& artist : artists)
|
for (const Database::Artist::pointer& artist : artists)
|
||||||
@@ -519,15 +605,86 @@ handleGetIndexesRequest(const Wt::Http::ParameterMap& request, Database::Handler
|
|||||||
Response
|
Response
|
||||||
handleGetArtistsRequest(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
handleGetArtistsRequest(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
||||||
{
|
{
|
||||||
Wt::Dbo::Transaction transaction {db.getSession()};
|
|
||||||
|
|
||||||
// TODO factorize with handleGetIndexesRequest
|
|
||||||
Response response {Response::createOkResponse()};
|
Response response {Response::createOkResponse()};
|
||||||
Response::Node& artistsNode {response.createNode("artists")};
|
Response::Node& artistsNode {response.createNode("artists")};
|
||||||
|
|
||||||
|
Response::Node& indexNode {artistsNode.createArrayChild("index")};
|
||||||
|
indexNode.setAttribute("name", "?");
|
||||||
|
|
||||||
|
Wt::Dbo::Transaction transaction {db.getSession()};
|
||||||
|
|
||||||
auto artists {Database::Artist::getAll(db.getSession())};
|
auto artists {Database::Artist::getAll(db.getSession())};
|
||||||
for (const Database::Artist::pointer& artist : artists)
|
for (const Database::Artist::pointer& artist : artists)
|
||||||
artistsNode.addArrayChild("artist", artistToResponseNode(artist));
|
indexNode.addArrayChild("artist", artistToResponseNode(artist));
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response
|
||||||
|
handleGetStarredRequest(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
||||||
|
{
|
||||||
|
Response response {Response::createOkResponse()};
|
||||||
|
response.createArrayNode("starred");
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response
|
||||||
|
handleGetStarred2Request(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
||||||
|
{
|
||||||
|
Response response {Response::createOkResponse()};
|
||||||
|
response.createArrayNode("starred2");
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response
|
||||||
|
handleGetPlaylistsRequest(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
||||||
|
{
|
||||||
|
Response response {Response::createOkResponse()};
|
||||||
|
response.createArrayNode("playlists");
|
||||||
|
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response
|
||||||
|
handleGetSongsByGenreRequest(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
||||||
|
{
|
||||||
|
// Mandatory params
|
||||||
|
auto genre {getParameterAs<std::string>(request, "genre")};
|
||||||
|
if (!genre)
|
||||||
|
throw Error {Error::Code::RequiredParameterMissing};
|
||||||
|
|
||||||
|
// Optional params
|
||||||
|
auto size {getParameterAs<std::size_t>(request, "count")};
|
||||||
|
if (!size)
|
||||||
|
size = 10;
|
||||||
|
|
||||||
|
*size = std::min(*size, std::size_t {500});
|
||||||
|
|
||||||
|
auto offset {getParameterAs<std::size_t>(request, "offset")};
|
||||||
|
if (!offset)
|
||||||
|
offset = 0;
|
||||||
|
|
||||||
|
LMS_LOG(API_SUBSONIC, DEBUG) << "genre ='" << *genre << "'";
|
||||||
|
|
||||||
|
Wt::Dbo::Transaction transaction {db.getSession()};
|
||||||
|
|
||||||
|
auto clusterType {Database::ClusterType::getByName(db.getSession(), "GENRE")};
|
||||||
|
if (!clusterType)
|
||||||
|
throw Error {Error::Code::RequestedDataNotFound};
|
||||||
|
|
||||||
|
auto cluster {clusterType->getCluster(*genre)};
|
||||||
|
if (!cluster)
|
||||||
|
throw Error {Error::Code::RequestedDataNotFound};
|
||||||
|
|
||||||
|
Response response {Response::createOkResponse()};
|
||||||
|
Response::Node& songsByGenreNode {response.createNode("songsByGenre")};
|
||||||
|
|
||||||
|
bool more;
|
||||||
|
auto tracks {Database::Track::getByFilter(db.getSession(), {cluster.id()}, {}, offset, size, more)};
|
||||||
|
for (const Database::Track::pointer& track : tracks)
|
||||||
|
songsByGenreNode.addArrayChild("song", trackToResponseNode(track));
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -545,6 +702,13 @@ createTranscoder(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
|||||||
if (!id || id->type != Id::Type::Track)
|
if (!id || id->type != Id::Type::Track)
|
||||||
throw Error {"bad id format"};
|
throw Error {"bad id format"};
|
||||||
|
|
||||||
|
// Optional params
|
||||||
|
auto maxBitRate {getParameterAs<std::size_t>(request, "maxBitRate")};
|
||||||
|
if (!maxBitRate)
|
||||||
|
maxBitRate = 128;
|
||||||
|
|
||||||
|
*maxBitRate = clamp(*maxBitRate, std::size_t {48}, std::size_t {320});
|
||||||
|
|
||||||
boost::filesystem::path trackPath;
|
boost::filesystem::path trackPath;
|
||||||
{
|
{
|
||||||
Wt::Dbo::Transaction transaction {db.getSession()};
|
Wt::Dbo::Transaction transaction {db.getSession()};
|
||||||
@@ -561,7 +725,7 @@ createTranscoder(const Wt::Http::ParameterMap& request, Database::Handler& db)
|
|||||||
|
|
||||||
Av::TranscodeParameters parameters {};
|
Av::TranscodeParameters parameters {};
|
||||||
|
|
||||||
parameters.bitrate = 128000;
|
parameters.bitrate = *maxBitRate * 1000;
|
||||||
parameters.encoding = Av::Encoding::MP3;
|
parameters.encoding = Av::Encoding::MP3;
|
||||||
|
|
||||||
return std::make_shared<Av::Transcoder>(trackPath, parameters);
|
return std::make_shared<Av::Transcoder>(trackPath, parameters);
|
||||||
@@ -572,6 +736,7 @@ handleStream(const Wt::Http::Request& request, Database::Handler& db, Wt::Http::
|
|||||||
{
|
{
|
||||||
LMS_LOG(API_SUBSONIC, DEBUG) << "STREAM";
|
LMS_LOG(API_SUBSONIC, DEBUG) << "STREAM";
|
||||||
|
|
||||||
|
// TODO store only weak ptrs and use a ring container to store shared_ptr?
|
||||||
std::shared_ptr<Av::Transcoder> transcoder;
|
std::shared_ptr<Av::Transcoder> transcoder;
|
||||||
|
|
||||||
Wt::Http::ResponseContinuation* continuation {request.continuation()};
|
Wt::Http::ResponseContinuation* continuation {request.continuation()};
|
||||||
@@ -583,7 +748,7 @@ handleStream(const Wt::Http::Request& request, Database::Handler& db, Wt::Http::
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
LMS_LOG(UI, DEBUG) << "Continuation! ";
|
LMS_LOG(UI, DEBUG) << "Continuation!";
|
||||||
transcoder = Wt::cpp17::any_cast<std::shared_ptr<Av::Transcoder>>(continuation->data());
|
transcoder = Wt::cpp17::any_cast<std::shared_ptr<Av::Transcoder>>(continuation->data());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -633,9 +798,11 @@ handleGetCoverArt(const Wt::Http::Request& request, Database::Handler& db, Wt::H
|
|||||||
throw Error {"bad id format"};
|
throw Error {"bad id format"};
|
||||||
|
|
||||||
auto size {getParameterAs<std::size_t>(request.getParameterMap(), "size")};
|
auto size {getParameterAs<std::size_t>(request.getParameterMap(), "size")};
|
||||||
if (!size || *size == 0)
|
if (!size)
|
||||||
size = 256;
|
size = 256;
|
||||||
|
|
||||||
|
*size = clamp(*size, std::size_t {32}, std::size_t {1024});
|
||||||
|
|
||||||
std::vector<uint8_t> cover;
|
std::vector<uint8_t> cover;
|
||||||
switch (id->type)
|
switch (id->type)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
#include <boost/property_tree/json_parser.hpp>
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
#include <boost/property_tree/xml_parser.hpp>
|
#include <boost/property_tree/xml_parser.hpp>
|
||||||
|
|
||||||
|
#include "utils/Exception.hpp"
|
||||||
|
|
||||||
#define API_VERSION "1.12.0"
|
#define API_VERSION "1.12.0"
|
||||||
|
|
||||||
namespace API::Subsonic
|
namespace API::Subsonic
|
||||||
@@ -74,37 +76,52 @@ _message {message}
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
Response::Node::setValue(const std::string& value)
|
||||||
|
{
|
||||||
|
if (!_children.empty() || !_childrenArrays.empty())
|
||||||
|
throw LmsException {"Node already has children"};
|
||||||
|
|
||||||
|
_value = value;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Response::Node::setAttribute(const std::string& key, const std::string& value)
|
Response::Node::setAttribute(const std::string& key, const std::string& value)
|
||||||
{
|
{
|
||||||
attributes[key] = value;
|
_attributes[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Response::Node::addChild(const std::string& key, Node node)
|
Response::Node::addChild(const std::string& key, Node node)
|
||||||
{
|
{
|
||||||
children[key].emplace_back(std::move(node));
|
if (!_value.empty())
|
||||||
|
throw LmsException {"Node already has a value"};
|
||||||
|
|
||||||
|
_children[key].emplace_back(std::move(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
Response::Node::addArrayChild(const std::string& key, Node node)
|
Response::Node::addArrayChild(const std::string& key, Node node)
|
||||||
{
|
{
|
||||||
childrenArrays[key].emplace_back(std::move(node));
|
if (!_value.empty())
|
||||||
|
throw LmsException {"Node already has a value"};
|
||||||
|
|
||||||
|
_childrenArrays[key].emplace_back(std::move(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Response::Node&
|
Response::Node&
|
||||||
Response::Node::createChild(const std::string& key)
|
Response::Node::createChild(const std::string& key)
|
||||||
{
|
{
|
||||||
children[key].emplace_back();
|
_children[key].emplace_back();
|
||||||
return children[key].back();
|
return _children[key].back();
|
||||||
}
|
}
|
||||||
|
|
||||||
Response::Node&
|
Response::Node&
|
||||||
Response::Node::createArrayChild(const std::string& key)
|
Response::Node::createArrayChild(const std::string& key)
|
||||||
{
|
{
|
||||||
childrenArrays[key].emplace_back();
|
_childrenArrays[key].emplace_back();
|
||||||
return childrenArrays[key].back();
|
return _childrenArrays[key].back();
|
||||||
}
|
}
|
||||||
|
|
||||||
Response
|
Response
|
||||||
@@ -138,55 +155,71 @@ Response::createFailedResponse(const Error& error)
|
|||||||
Response::Node&
|
Response::Node&
|
||||||
Response::createNode(const std::string& key)
|
Response::createNode(const std::string& key)
|
||||||
{
|
{
|
||||||
return _root.children["subsonic-response"].front().createChild(key);
|
return _root._children["subsonic-response"].front().createChild(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
boost::property_tree::ptree
|
Response::Node&
|
||||||
NodeToPropertyTree(const Response::Node& node, ResponseFormat format)
|
Response::createArrayNode(const std::string& key)
|
||||||
{
|
{
|
||||||
boost::property_tree::ptree res;
|
return _root._children["subsonic-response"].front().createArrayChild(key);
|
||||||
|
}
|
||||||
|
|
||||||
for (auto itChildNode : node.children)
|
void
|
||||||
|
Response::write(std::ostream& os, ResponseFormat format)
|
||||||
|
{
|
||||||
|
std::function<boost::property_tree::ptree(const Response::Node&, ResponseFormat)> nodeToPropertyTree = [&] (const Response::Node& node, ResponseFormat format)
|
||||||
{
|
{
|
||||||
for (const Response::Node& childNode : itChildNode.second)
|
boost::property_tree::ptree res;
|
||||||
res.add_child(itChildNode.first, NodeToPropertyTree(childNode, format));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto itChildArrayNode : node.childrenArrays)
|
for (auto itAttribute : node._attributes)
|
||||||
{
|
|
||||||
const std::vector<Response::Node>& childArrayNodes {itChildArrayNode .second};
|
|
||||||
|
|
||||||
if (format == ResponseFormat::json)
|
|
||||||
{
|
{
|
||||||
boost::property_tree::ptree array;
|
std::string key {format == ResponseFormat::xml ? "<xmlattr>." : ""};
|
||||||
|
|
||||||
for (const Response::Node& childNode : childArrayNodes )
|
key += itAttribute.first;
|
||||||
array.push_back(std::make_pair("", NodeToPropertyTree(childNode, format)));
|
|
||||||
|
|
||||||
res.add_child(itChildArrayNode.first, array);
|
res.put(key, itAttribute.second);
|
||||||
|
}
|
||||||
|
if (!node._value.empty())
|
||||||
|
{
|
||||||
|
if (format == ResponseFormat::json)
|
||||||
|
res.put("value", node._value);
|
||||||
|
else
|
||||||
|
res.put_value(node._value);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
for (const Response::Node& childNode : childArrayNodes )
|
for (auto itChildNode : node._children)
|
||||||
res.add_child(itChildArrayNode.first, NodeToPropertyTree(childNode, format));
|
{
|
||||||
|
for (const Response::Node& childNode : itChildNode.second)
|
||||||
|
res.add_child(itChildNode.first, nodeToPropertyTree(childNode, format));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto itChildArrayNode : node._childrenArrays)
|
||||||
|
{
|
||||||
|
const std::vector<Response::Node>& childArrayNodes {itChildArrayNode .second};
|
||||||
|
|
||||||
|
if (format == ResponseFormat::json)
|
||||||
|
{
|
||||||
|
boost::property_tree::ptree array;
|
||||||
|
|
||||||
|
for (const Response::Node& childNode : childArrayNodes )
|
||||||
|
array.push_back(std::make_pair("", nodeToPropertyTree(childNode, format)));
|
||||||
|
|
||||||
|
res.add_child(itChildArrayNode.first, array);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (const Response::Node& childNode : childArrayNodes )
|
||||||
|
res.add_child(itChildArrayNode.first, nodeToPropertyTree(childNode, format));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (auto itAttribute : node.attributes)
|
return res;
|
||||||
{
|
};
|
||||||
std::string key {format == ResponseFormat::xml ? "<xmlattr>." : ""};
|
|
||||||
|
|
||||||
key += itAttribute.first;
|
|
||||||
|
|
||||||
res.put(key, itAttribute.second);
|
boost::property_tree::ptree root {nodeToPropertyTree(_root, format)};
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
void responseToStream(const Response& response, ResponseFormat format, std::ostream& os)
|
|
||||||
{
|
|
||||||
boost::property_tree::ptree root {NodeToPropertyTree(response._root, format)};
|
|
||||||
|
|
||||||
switch (format)
|
switch (format)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,20 +60,25 @@ class Error
|
|||||||
class Response
|
class Response
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
struct Node
|
class Node
|
||||||
{
|
{
|
||||||
std::map<std::string, std::string> attributes;
|
public:
|
||||||
std::map<std::string, std::vector<Node>> children;
|
void setAttribute(const std::string& key, const std::string& value);
|
||||||
std::map<std::string, std::vector<Node>> childrenArrays;
|
|
||||||
|
|
||||||
// Helpers
|
// A Node has either a value or some children
|
||||||
void setAttribute(const std::string& key, const std::string& value);
|
void setValue(const std::string& value);
|
||||||
|
Node& createChild(const std::string& key);
|
||||||
|
Node& createArrayChild(const std::string& key);
|
||||||
|
|
||||||
Node& createChild(const std::string& key);
|
void addChild(const std::string& key, Node node);
|
||||||
Node& createArrayChild(const std::string& key);
|
void addArrayChild(const std::string& key, Node node);
|
||||||
|
|
||||||
void addChild(const std::string& key, Node node);
|
private:
|
||||||
void addArrayChild(const std::string& key, Node node);
|
friend class Response;
|
||||||
|
std::map<std::string, std::string> _attributes;
|
||||||
|
std::string _value;
|
||||||
|
std::map<std::string, std::vector<Node>> _children;
|
||||||
|
std::map<std::string, std::vector<Node>> _childrenArrays;
|
||||||
};
|
};
|
||||||
|
|
||||||
static Response createOkResponse();
|
static Response createOkResponse();
|
||||||
@@ -86,9 +91,10 @@ class Response
|
|||||||
Response& operator=(Response&&) = default;
|
Response& operator=(Response&&) = default;
|
||||||
|
|
||||||
Node& createNode(const std::string& key);
|
Node& createNode(const std::string& key);
|
||||||
|
Node& createArrayNode(const std::string& key);
|
||||||
|
|
||||||
|
void write(std::ostream& os, ResponseFormat format);
|
||||||
private:
|
private:
|
||||||
friend void responseToStream(const Response& response, ResponseFormat format, std::ostream& os);
|
|
||||||
|
|
||||||
Response() = default;
|
Response() = default;
|
||||||
Node _root;
|
Node _root;
|
||||||
|
|||||||
+15
-9
@@ -63,9 +63,13 @@ Artist::create(Wt::Dbo::Session& session, const std::string& name, const std::st
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Artist::pointer>
|
std::vector<Artist::pointer>
|
||||||
Artist::getAll(Wt::Dbo::Session& session, int offset, int size)
|
Artist::getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> offset, boost::optional<std::size_t> size)
|
||||||
{
|
{
|
||||||
Wt::Dbo::collection<pointer> res = session.find<Artist>().offset(offset).limit(size);
|
Wt::Dbo::collection<pointer> res = session.find<Artist>()
|
||||||
|
.offset(offset ? static_cast<int>(*offset) : -1)
|
||||||
|
.limit(size ? static_cast<int>(*size) : -1)
|
||||||
|
.orderBy("name COLLATE NOCASE");
|
||||||
|
|
||||||
return std::vector<pointer>(res.begin(), res.end());
|
return std::vector<pointer>(res.begin(), res.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,15 +127,17 @@ std::vector<Artist::pointer>
|
|||||||
Artist::getByFilter(Wt::Dbo::Session& session,
|
Artist::getByFilter(Wt::Dbo::Session& session,
|
||||||
const std::set<IdType>& clusters,
|
const std::set<IdType>& clusters,
|
||||||
const std::vector<std::string> keywords,
|
const std::vector<std::string> keywords,
|
||||||
int offset, int size, bool& moreResults)
|
boost::optional<std::size_t> offset,
|
||||||
|
boost::optional<std::size_t> size,
|
||||||
|
bool& moreResults)
|
||||||
{
|
{
|
||||||
Wt::Dbo::collection<Artist::pointer> collection = getQuery(session, clusters, keywords)
|
Wt::Dbo::collection<Artist::pointer> collection = getQuery(session, clusters, keywords)
|
||||||
.limit(size != -1 ? size + 1 : -1)
|
.limit(size ? static_cast<int>(*size) + 1 : -1)
|
||||||
.offset(offset);
|
.offset(offset ? static_cast<int>(*offset) : -1);
|
||||||
|
|
||||||
auto res = std::vector<pointer>(collection.begin(), collection.end());
|
auto res {std::vector<pointer>(collection.begin(), collection.end())};
|
||||||
|
|
||||||
if (size != -1 && res.size() == static_cast<std::size_t>(size) + 1)
|
if (size && res.size() == static_cast<std::size_t>(*size) + 1)
|
||||||
{
|
{
|
||||||
moreResults = true;
|
moreResults = true;
|
||||||
res.pop_back();
|
res.pop_back();
|
||||||
@@ -143,13 +149,13 @@ Artist::getByFilter(Wt::Dbo::Session& session,
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Artist::pointer>
|
std::vector<Artist::pointer>
|
||||||
Artist::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int limit)
|
Artist::getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional<std::size_t> limit)
|
||||||
{
|
{
|
||||||
Wt::Dbo::collection<Artist::pointer> res = session.query<Artist::pointer>("SELECT a from artist a INNER JOIN track_artist t_a ON t_a.artist_id = a.id INNER JOIN track t ON t.id = t_a.track_id")
|
Wt::Dbo::collection<Artist::pointer> res = session.query<Artist::pointer>("SELECT a from artist a INNER JOIN track_artist t_a ON t_a.artist_id = a.id INNER JOIN track t ON t.id = t_a.track_id")
|
||||||
.where("t.file_added > ?").bind(after)
|
.where("t.file_added > ?").bind(after)
|
||||||
.groupBy("a.id")
|
.groupBy("a.id")
|
||||||
.orderBy("t.file_added DESC")
|
.orderBy("t.file_added DESC")
|
||||||
.limit(limit);
|
.limit(limit ? static_cast<int>(*limit) : -1);
|
||||||
|
|
||||||
return std::vector<pointer>(res.begin(), res.end());
|
return std::vector<pointer>(res.begin(), res.end());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,8 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <boost/optional.hpp>
|
||||||
|
|
||||||
#include <Wt/WDateTime.h>
|
#include <Wt/WDateTime.h>
|
||||||
#include <Wt/Dbo/Dbo.h>
|
#include <Wt/Dbo/Dbo.h>
|
||||||
|
|
||||||
@@ -51,13 +53,13 @@ class Artist : public Wt::Dbo::Dbo<Artist>
|
|||||||
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session,
|
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session,
|
||||||
const std::set<IdType>& clusters, // at least one track that belongs to these clusters
|
const std::set<IdType>& clusters, // at least one track that belongs to these clusters
|
||||||
const std::vector<std::string> keywords, // name must match all of these keywords
|
const std::vector<std::string> keywords, // name must match all of these keywords
|
||||||
int offset,
|
boost::optional<std::size_t> offset,
|
||||||
int size,
|
boost::optional<std::size_t> size,
|
||||||
bool& moreExpected);
|
bool& moreExpected);
|
||||||
|
|
||||||
static std::vector<pointer> getAll(Wt::Dbo::Session& session, int offset = -1, int size = -1);
|
static std::vector<pointer> getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
|
||||||
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session); // No track related
|
static std::vector<pointer> getAllOrphans(Wt::Dbo::Session& session); // No track related
|
||||||
static std::vector<pointer> getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, int size = 1);
|
static std::vector<pointer> getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional<std::size_t> size = {});
|
||||||
|
|
||||||
// Accessors
|
// Accessors
|
||||||
const std::string& getName(void) const { return _name; }
|
const std::string& getName(void) const { return _name; }
|
||||||
|
|||||||
@@ -73,7 +73,8 @@ Release::getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> offset,
|
|||||||
{
|
{
|
||||||
Wt::Dbo::collection<pointer> res = session.find<Release>()
|
Wt::Dbo::collection<pointer> res = session.find<Release>()
|
||||||
.offset(offset ? static_cast<int>(*offset) : -1)
|
.offset(offset ? static_cast<int>(*offset) : -1)
|
||||||
.limit(size ? static_cast<int>(*size) : - 1);
|
.limit(size ? static_cast<int>(*size) : - 1)
|
||||||
|
.orderBy("name COLLATE NOCASE");
|
||||||
|
|
||||||
return std::vector<pointer>(res.begin(), res.end());
|
return std::vector<pointer>(res.begin(), res.end());
|
||||||
}
|
}
|
||||||
@@ -150,19 +151,28 @@ getQuery(Wt::Dbo::Session& session,
|
|||||||
return query;
|
return query;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<Release::pointer>
|
||||||
|
Release::getByFilter(Wt::Dbo::Session& session, const std::set<IdType>& clusterIds)
|
||||||
|
{
|
||||||
|
bool moreResults;
|
||||||
|
return getByFilter(session, clusterIds, {}, {}, {}, moreResults);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Release::pointer>
|
std::vector<Release::pointer>
|
||||||
Release::getByFilter(Wt::Dbo::Session& session,
|
Release::getByFilter(Wt::Dbo::Session& session,
|
||||||
const std::set<IdType>& clusterIds,
|
const std::set<IdType>& clusterIds,
|
||||||
const std::vector<std::string> keywords,
|
const std::vector<std::string> keywords,
|
||||||
int offset, int size, bool& moreResults)
|
boost::optional<std::size_t> offset,
|
||||||
|
boost::optional<std::size_t> size,
|
||||||
|
bool& moreResults)
|
||||||
{
|
{
|
||||||
Wt::Dbo::collection<pointer> collection = getQuery(session, clusterIds, keywords)
|
Wt::Dbo::collection<pointer> collection = getQuery(session, clusterIds, keywords)
|
||||||
.limit(size != -1 ? size + 1 : -1)
|
.limit(size ? static_cast<int>(*size) + 1 : -1)
|
||||||
.offset(offset);
|
.offset(offset ? static_cast<int>(*offset) : -1);
|
||||||
|
|
||||||
auto res = std::vector<pointer>(collection.begin(), collection.end());
|
auto res {std::vector<pointer>(collection.begin(), collection.end())};
|
||||||
|
|
||||||
if (size != -1 && res.size() == static_cast<std::size_t>(size) + 1)
|
if (size && res.size() == static_cast<std::size_t>(*size) + 1)
|
||||||
{
|
{
|
||||||
moreResults = true;
|
moreResults = true;
|
||||||
res.pop_back();
|
res.pop_back();
|
||||||
|
|||||||
@@ -53,11 +53,12 @@ class Release : public Wt::Dbo::Dbo<Release>
|
|||||||
static std::vector<pointer> getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> size = {});
|
static std::vector<pointer> getAllRandom(Wt::Dbo::Session& session, boost::optional<std::size_t> size = {});
|
||||||
static std::vector<pointer> getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
|
static std::vector<pointer> getLastAdded(Wt::Dbo::Session& session, Wt::WDateTime after, boost::optional<std::size_t> offset = {}, boost::optional<std::size_t> size = {});
|
||||||
|
|
||||||
|
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session, const std::set<IdType>& clusters);
|
||||||
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session,
|
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session,
|
||||||
const std::set<IdType>& clusters, // at least one track that belongs to these clusters
|
const std::set<IdType>& clusters, // at least one track that belongs to these clusters
|
||||||
const std::vector<std::string> keywords, // name must match all of these keywords
|
const std::vector<std::string> keywords, // name must match all of these keywords
|
||||||
int offset,
|
boost::optional<std::size_t> offset,
|
||||||
int size,
|
boost::optional<std::size_t> size,
|
||||||
bool& moreExpected);
|
bool& moreExpected);
|
||||||
|
|
||||||
std::vector<Wt::Dbo::ptr<Track>> getTracks(const std::set<IdType>& clusters = std::set<IdType>()) const;
|
std::vector<Wt::Dbo::ptr<Track>> getTracks(const std::set<IdType>& clusters = std::set<IdType>()) const;
|
||||||
|
|||||||
@@ -202,15 +202,17 @@ std::vector<Track::pointer>
|
|||||||
Track::getByFilter(Wt::Dbo::Session& session,
|
Track::getByFilter(Wt::Dbo::Session& session,
|
||||||
const std::set<IdType>& clusterIds,
|
const std::set<IdType>& clusterIds,
|
||||||
const std::vector<std::string> keywords,
|
const std::vector<std::string> keywords,
|
||||||
int offset, int size, bool& moreResults)
|
boost::optional<std::size_t> offset,
|
||||||
|
boost::optional<std::size_t> size,
|
||||||
|
bool& moreResults)
|
||||||
{
|
{
|
||||||
Wt::Dbo::collection<pointer> collection = getQuery(session, clusterIds, keywords)
|
Wt::Dbo::collection<pointer> collection = getQuery(session, clusterIds, keywords)
|
||||||
.limit(size != -1 ? size + 1 : -1)
|
.limit(size ? static_cast<int>(*size) + 1 : -1)
|
||||||
.offset(offset);
|
.offset(offset ? static_cast<int>(*offset) : -1);
|
||||||
|
|
||||||
auto res = std::vector<pointer>(collection.begin(), collection.end());
|
auto res = std::vector<pointer>(collection.begin(), collection.end());
|
||||||
|
|
||||||
if (size != -1 && res.size() == static_cast<std::size_t>(size) + 1)
|
if (size && res.size() == static_cast<std::size_t>(*size) + 1)
|
||||||
{
|
{
|
||||||
moreResults = true;
|
moreResults = true;
|
||||||
res.pop_back();
|
res.pop_back();
|
||||||
@@ -227,7 +229,7 @@ Track::getByFilter(Wt::Dbo::Session& session,
|
|||||||
{
|
{
|
||||||
bool moreResults;
|
bool moreResults;
|
||||||
|
|
||||||
return getByFilter(session, clusters, std::vector<std::string>(), -1, -1, moreResults);
|
return getByFilter(session, clusters, std::vector<std::string>{}, -1, -1, moreResults);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|||||||
@@ -59,8 +59,8 @@ class Track : public Wt::Dbo::Dbo<Track>
|
|||||||
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session,
|
static std::vector<pointer> getByFilter(Wt::Dbo::Session& session,
|
||||||
const std::set<IdType>& clusters, // tracks that belong to these clusters
|
const std::set<IdType>& clusters, // tracks that belong to these clusters
|
||||||
const std::vector<std::string> keywords, // name must match all of these keywords
|
const std::vector<std::string> keywords, // name must match all of these keywords
|
||||||
int offset,
|
boost::optional<std::size_t> offset,
|
||||||
int size,
|
boost::optional<std::size_t> size,
|
||||||
bool& moreExpected);
|
bool& moreExpected);
|
||||||
|
|
||||||
static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> limit = {});
|
static Wt::Dbo::collection< pointer > getAll(Wt::Dbo::Session& session, boost::optional<std::size_t> limit = {});
|
||||||
|
|||||||
@@ -94,4 +94,11 @@ void uniqueAndSortedByOccurence(In first, In last, Out out)
|
|||||||
*out++ = occurence.elem;
|
*out++ = occurence.elem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<class T, class Compare = std::less<>>
|
||||||
|
constexpr const T& clamp( T v, T lo, T hi, Compare comp = {})
|
||||||
|
{
|
||||||
|
assert(!comp(hi, lo));
|
||||||
|
return comp(v, lo) ? lo : comp(hi, v) ? hi : v;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user