Extracted playlists entry points
This commit is contained in:
@@ -5,11 +5,13 @@ add_library(lmssubsonic SHARED
|
||||
impl/entrypoints/MediaAnnotation.cpp
|
||||
impl/entrypoints/MediaLibraryScanning.cpp
|
||||
impl/entrypoints/MediaRetrieval.cpp
|
||||
impl/entrypoints/Playlists.cpp
|
||||
impl/entrypoints/Searching.cpp
|
||||
impl/entrypoints/UserManagement.cpp
|
||||
impl/responses/Album.cpp
|
||||
impl/responses/Artist.cpp
|
||||
impl/responses/Bookmark.cpp
|
||||
impl/responses/Playlist.cpp
|
||||
impl/responses/Song.cpp
|
||||
impl/responses/User.cpp
|
||||
impl/ProtocolVersion.cpp
|
||||
|
||||
@@ -51,6 +51,7 @@
|
||||
#include "entrypoints/MediaAnnotation.hpp"
|
||||
#include "entrypoints/MediaLibraryScanning.hpp"
|
||||
#include "entrypoints/MediaRetrieval.hpp"
|
||||
#include "entrypoints/Playlists.hpp"
|
||||
#include "entrypoints/Searching.hpp"
|
||||
#include "entrypoints/UserManagement.hpp"
|
||||
#include "responses/Artist.hpp"
|
||||
@@ -176,80 +177,7 @@ handlePingRequest(RequestContext& context)
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
}
|
||||
|
||||
static
|
||||
Response
|
||||
handleCreatePlaylistRequest(RequestContext& context)
|
||||
{
|
||||
// Optional params
|
||||
const auto id {getParameterAs<TrackListId>(context.parameters, "playlistId")};
|
||||
auto name {getParameterAs<std::string>(context.parameters, "name")};
|
||||
|
||||
std::vector<TrackId> trackIds {getMultiParametersAs<TrackId>(context.parameters, "songId")};
|
||||
|
||||
if (!name && !id)
|
||||
throw RequiredParameterMissingError {"name or id"};
|
||||
|
||||
auto transaction {context.dbSession.createUniqueTransaction()};
|
||||
|
||||
User::pointer user {User::find(context.dbSession, context.userId)};
|
||||
if (!user)
|
||||
throw UserNotAuthorizedError {};
|
||||
|
||||
TrackList::pointer tracklist;
|
||||
if (id)
|
||||
{
|
||||
tracklist = TrackList::find(context.dbSession, *id);
|
||||
if (!tracklist
|
||||
|| tracklist->getUser() != user
|
||||
|| tracklist->getType() != TrackListType::Playlist)
|
||||
{
|
||||
throw RequestedDataNotFoundError {};
|
||||
}
|
||||
|
||||
if (name)
|
||||
tracklist.modify()->setName(*name);
|
||||
}
|
||||
else
|
||||
{
|
||||
tracklist = context.dbSession.create<TrackList>(*name, TrackListType::Playlist, false, user);
|
||||
}
|
||||
|
||||
for (const TrackId trackId : trackIds)
|
||||
{
|
||||
Track::pointer track {Track::find(context.dbSession, trackId)};
|
||||
if (!track)
|
||||
continue;
|
||||
|
||||
context.dbSession.create<TrackListEntry>(track, tracklist);
|
||||
}
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
}
|
||||
|
||||
static
|
||||
Response
|
||||
handleDeletePlaylistRequest(RequestContext& context)
|
||||
{
|
||||
TrackListId id {getMandatoryParameterAs<TrackListId>(context.parameters, "id")};
|
||||
|
||||
auto transaction {context.dbSession.createUniqueTransaction()};
|
||||
|
||||
User::pointer user {User::find(context.dbSession, context.userId)};
|
||||
if (!user)
|
||||
throw UserNotAuthorizedError {};
|
||||
|
||||
TrackList::pointer tracklist {TrackList::find(context.dbSession, id)};
|
||||
if (!tracklist
|
||||
|| tracklist->getUser() != user
|
||||
|| tracklist->getType() != TrackListType::Playlist)
|
||||
{
|
||||
throw RequestedDataNotFoundError {};
|
||||
}
|
||||
|
||||
tracklist.remove();
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
}
|
||||
|
||||
static
|
||||
Response
|
||||
@@ -717,134 +645,6 @@ handleGetSimilarSongs2Request(RequestContext& context)
|
||||
return handleGetSimilarSongsRequestCommon(context, true /* id3 */);
|
||||
}
|
||||
|
||||
static
|
||||
Response::Node
|
||||
tracklistToResponseNode(const TrackList::pointer& tracklist, Session&)
|
||||
{
|
||||
Response::Node playlistNode;
|
||||
|
||||
playlistNode.setAttribute("id", idToString(tracklist->getId()));
|
||||
playlistNode.setAttribute("name", tracklist->getName());
|
||||
playlistNode.setAttribute("songCount", tracklist->getCount());
|
||||
playlistNode.setAttribute("duration", std::chrono::duration_cast<std::chrono::seconds>(tracklist->getDuration()).count());
|
||||
playlistNode.setAttribute("public", tracklist->isPublic());
|
||||
playlistNode.setAttribute("created", reportedDummyDate);
|
||||
playlistNode.setAttribute("owner", tracklist->getUser()->getLoginName());
|
||||
|
||||
return playlistNode;
|
||||
}
|
||||
|
||||
static
|
||||
Response
|
||||
handleGetPlaylistRequest(RequestContext& context)
|
||||
{
|
||||
// Mandatory params
|
||||
TrackListId trackListId {getMandatoryParameterAs<TrackListId>(context.parameters, "id")};
|
||||
|
||||
auto transaction {context.dbSession.createSharedTransaction()};
|
||||
|
||||
User::pointer user {User::find(context.dbSession, context.userId)};
|
||||
if (!user)
|
||||
throw UserNotAuthorizedError {};
|
||||
|
||||
TrackList::pointer tracklist {TrackList::find(context.dbSession, trackListId)};
|
||||
if (!tracklist)
|
||||
throw RequestedDataNotFoundError {};
|
||||
|
||||
Response response {Response::createOkResponse(context.serverProtocolVersion)};
|
||||
Response::Node playlistNode {tracklistToResponseNode(tracklist, context.dbSession)};
|
||||
|
||||
auto entries {tracklist->getEntries()};
|
||||
for (const TrackListEntry::pointer& entry : entries)
|
||||
playlistNode.addArrayChild("entry", createSongNode(entry->getTrack(), context.dbSession, user));
|
||||
|
||||
response.addNode("playlist", playlistNode );
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
static
|
||||
Response
|
||||
handleGetPlaylistsRequest(RequestContext& context)
|
||||
{
|
||||
auto transaction {context.dbSession.createSharedTransaction()};
|
||||
|
||||
Response response {Response::createOkResponse(context.serverProtocolVersion)};
|
||||
Response::Node& playlistsNode {response.createNode("playlists")};
|
||||
|
||||
TrackList::FindParameters params;
|
||||
params.setUser(context.userId);
|
||||
params.setType(TrackListType::Playlist);
|
||||
|
||||
auto tracklistIds {TrackList::find(context.dbSession, params)};
|
||||
for (const TrackListId trackListId : tracklistIds.results)
|
||||
{
|
||||
const TrackList::pointer trackList {TrackList::find(context.dbSession, trackListId)};
|
||||
playlistsNode.addArrayChild("playlist", tracklistToResponseNode(trackList, context.dbSession));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
static
|
||||
Response
|
||||
handleUpdatePlaylistRequest(RequestContext& context)
|
||||
{
|
||||
// Mandatory params
|
||||
TrackListId id {getMandatoryParameterAs<TrackListId>(context.parameters, "playlistId")};
|
||||
|
||||
// Optional parameters
|
||||
auto name {getParameterAs<std::string>(context.parameters, "name")};
|
||||
auto isPublic {getParameterAs<bool>(context.parameters, "public")};
|
||||
|
||||
std::vector<TrackId> trackIdsToAdd {getMultiParametersAs<TrackId>(context.parameters, "songIdToAdd")};
|
||||
std::vector<std::size_t> trackPositionsToRemove {getMultiParametersAs<std::size_t>(context.parameters, "songIndexToRemove")};
|
||||
|
||||
auto transaction {context.dbSession.createUniqueTransaction()};
|
||||
|
||||
User::pointer user {User::find(context.dbSession, context.userId)};
|
||||
if (!user)
|
||||
throw UserNotAuthorizedError {};
|
||||
|
||||
TrackList::pointer tracklist {TrackList::find(context.dbSession, id)};
|
||||
if (!tracklist
|
||||
|| tracklist->getUser() != user
|
||||
|| tracklist->getType() != TrackListType::Playlist)
|
||||
{
|
||||
throw RequestedDataNotFoundError {};
|
||||
}
|
||||
|
||||
if (name)
|
||||
tracklist.modify()->setName(*name);
|
||||
|
||||
if (isPublic)
|
||||
tracklist.modify()->setIsPublic(*isPublic);
|
||||
|
||||
{
|
||||
// Remove from end to make indexes stable
|
||||
std::sort(std::begin(trackPositionsToRemove), std::end(trackPositionsToRemove), std::greater<std::size_t>());
|
||||
|
||||
for (std::size_t trackPositionToRemove : trackPositionsToRemove)
|
||||
{
|
||||
auto entry {tracklist->getEntry(trackPositionToRemove)};
|
||||
if (entry)
|
||||
entry.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Add tracks
|
||||
for (const TrackId trackIdToAdd : trackIdsToAdd)
|
||||
{
|
||||
Track::pointer track {Track::find(context.dbSession, trackIdToAdd)};
|
||||
if (!track)
|
||||
continue;
|
||||
|
||||
context.dbSession.create<TrackListEntry>(track, tracklist);
|
||||
}
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
}
|
||||
|
||||
static
|
||||
Response
|
||||
handleNotImplemented(RequestContext&)
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Playlists.hpp"
|
||||
|
||||
#include "services/database/Session.hpp"
|
||||
#include "services/database/Track.hpp"
|
||||
#include "services/database/TrackList.hpp"
|
||||
#include "services/database/User.hpp"
|
||||
#include "responses/Playlist.hpp"
|
||||
#include "responses/Song.hpp"
|
||||
#include "ParameterParsing.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
|
||||
namespace API::Subsonic
|
||||
{
|
||||
using namespace Database;
|
||||
|
||||
Response handleGetPlaylistsRequest(RequestContext& context)
|
||||
{
|
||||
auto transaction{ context.dbSession.createSharedTransaction() };
|
||||
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response::Node& playlistsNode{ response.createNode("playlists") };
|
||||
|
||||
TrackList::FindParameters params;
|
||||
params.setUser(context.userId);
|
||||
params.setType(TrackListType::Playlist);
|
||||
|
||||
auto tracklistIds{ TrackList::find(context.dbSession, params) };
|
||||
for (const TrackListId trackListId : tracklistIds.results)
|
||||
{
|
||||
const TrackList::pointer trackList{ TrackList::find(context.dbSession, trackListId) };
|
||||
playlistsNode.addArrayChild("playlist", createPlaylistNode(trackList, context.dbSession));
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Response handleGetPlaylistRequest(RequestContext& context)
|
||||
{
|
||||
// Mandatory params
|
||||
TrackListId trackListId{ getMandatoryParameterAs<TrackListId>(context.parameters, "id") };
|
||||
|
||||
auto transaction{ context.dbSession.createSharedTransaction() };
|
||||
|
||||
User::pointer user{ User::find(context.dbSession, context.userId) };
|
||||
if (!user)
|
||||
throw UserNotAuthorizedError{};
|
||||
|
||||
TrackList::pointer tracklist{ TrackList::find(context.dbSession, trackListId) };
|
||||
if (!tracklist)
|
||||
throw RequestedDataNotFoundError{};
|
||||
|
||||
Response response{ Response::createOkResponse(context.serverProtocolVersion) };
|
||||
Response::Node playlistNode{ createPlaylistNode(tracklist, context.dbSession) };
|
||||
|
||||
auto entries{ tracklist->getEntries() };
|
||||
for (const TrackListEntry::pointer& entry : entries)
|
||||
playlistNode.addArrayChild("entry", createSongNode(entry->getTrack(), context.dbSession, user));
|
||||
|
||||
response.addNode("playlist", playlistNode);
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
Response handleCreatePlaylistRequest(RequestContext& context)
|
||||
{
|
||||
// Optional params
|
||||
const auto id{ getParameterAs<TrackListId>(context.parameters, "playlistId") };
|
||||
auto name{ getParameterAs<std::string>(context.parameters, "name") };
|
||||
|
||||
std::vector<TrackId> trackIds{ getMultiParametersAs<TrackId>(context.parameters, "songId") };
|
||||
|
||||
if (!name && !id)
|
||||
throw RequiredParameterMissingError{ "name or id" };
|
||||
|
||||
auto transaction{ context.dbSession.createUniqueTransaction() };
|
||||
|
||||
User::pointer user{ User::find(context.dbSession, context.userId) };
|
||||
if (!user)
|
||||
throw UserNotAuthorizedError{};
|
||||
|
||||
TrackList::pointer tracklist;
|
||||
if (id)
|
||||
{
|
||||
tracklist = TrackList::find(context.dbSession, *id);
|
||||
if (!tracklist
|
||||
|| tracklist->getUser() != user
|
||||
|| tracklist->getType() != TrackListType::Playlist)
|
||||
{
|
||||
throw RequestedDataNotFoundError{};
|
||||
}
|
||||
|
||||
if (name)
|
||||
tracklist.modify()->setName(*name);
|
||||
}
|
||||
else
|
||||
{
|
||||
tracklist = context.dbSession.create<TrackList>(*name, TrackListType::Playlist, false, user);
|
||||
}
|
||||
|
||||
for (const TrackId trackId : trackIds)
|
||||
{
|
||||
Track::pointer track{ Track::find(context.dbSession, trackId) };
|
||||
if (!track)
|
||||
continue;
|
||||
|
||||
context.dbSession.create<TrackListEntry>(track, tracklist);
|
||||
}
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
}
|
||||
|
||||
Response handleUpdatePlaylistRequest(RequestContext& context)
|
||||
{
|
||||
// Mandatory params
|
||||
TrackListId id{ getMandatoryParameterAs<TrackListId>(context.parameters, "playlistId") };
|
||||
|
||||
// Optional parameters
|
||||
auto name{ getParameterAs<std::string>(context.parameters, "name") };
|
||||
auto isPublic{ getParameterAs<bool>(context.parameters, "public") };
|
||||
|
||||
std::vector<TrackId> trackIdsToAdd{ getMultiParametersAs<TrackId>(context.parameters, "songIdToAdd") };
|
||||
std::vector<std::size_t> trackPositionsToRemove{ getMultiParametersAs<std::size_t>(context.parameters, "songIndexToRemove") };
|
||||
|
||||
auto transaction{ context.dbSession.createUniqueTransaction() };
|
||||
|
||||
User::pointer user{ User::find(context.dbSession, context.userId) };
|
||||
if (!user)
|
||||
throw UserNotAuthorizedError{};
|
||||
|
||||
TrackList::pointer tracklist{ TrackList::find(context.dbSession, id) };
|
||||
if (!tracklist
|
||||
|| tracklist->getUser() != user
|
||||
|| tracklist->getType() != TrackListType::Playlist)
|
||||
{
|
||||
throw RequestedDataNotFoundError{};
|
||||
}
|
||||
|
||||
if (name)
|
||||
tracklist.modify()->setName(*name);
|
||||
|
||||
if (isPublic)
|
||||
tracklist.modify()->setIsPublic(*isPublic);
|
||||
|
||||
{
|
||||
// Remove from end to make indexes stable
|
||||
std::sort(std::begin(trackPositionsToRemove), std::end(trackPositionsToRemove), std::greater<std::size_t>());
|
||||
|
||||
for (std::size_t trackPositionToRemove : trackPositionsToRemove)
|
||||
{
|
||||
auto entry{ tracklist->getEntry(trackPositionToRemove) };
|
||||
if (entry)
|
||||
entry.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// Add tracks
|
||||
for (const TrackId trackIdToAdd : trackIdsToAdd)
|
||||
{
|
||||
Track::pointer track{ Track::find(context.dbSession, trackIdToAdd) };
|
||||
if (!track)
|
||||
continue;
|
||||
|
||||
context.dbSession.create<TrackListEntry>(track, tracklist);
|
||||
}
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
}
|
||||
|
||||
Response handleDeletePlaylistRequest(RequestContext& context)
|
||||
{
|
||||
TrackListId id{ getMandatoryParameterAs<TrackListId>(context.parameters, "id") };
|
||||
|
||||
auto transaction{ context.dbSession.createUniqueTransaction() };
|
||||
|
||||
User::pointer user{ User::find(context.dbSession, context.userId) };
|
||||
if (!user)
|
||||
throw UserNotAuthorizedError{};
|
||||
|
||||
TrackList::pointer tracklist{ TrackList::find(context.dbSession, id) };
|
||||
if (!tracklist
|
||||
|| tracklist->getUser() != user
|
||||
|| tracklist->getType() != TrackListType::Playlist)
|
||||
{
|
||||
throw RequestedDataNotFoundError{};
|
||||
}
|
||||
|
||||
tracklist.remove();
|
||||
|
||||
return Response::createOkResponse(context.serverProtocolVersion);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "RequestContext.hpp"
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace API::Subsonic
|
||||
{
|
||||
Response handleGetPlaylistsRequest(RequestContext& context);
|
||||
Response handleGetPlaylistRequest(RequestContext& context);
|
||||
Response handleCreatePlaylistRequest(RequestContext& context);
|
||||
Response handleUpdatePlaylistRequest(RequestContext& context);
|
||||
Response handleDeletePlaylistRequest(RequestContext& context);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Playlist.hpp"
|
||||
|
||||
#include "services/database/TrackList.hpp"
|
||||
#include "services/database/User.hpp"
|
||||
#include "SubsonicId.hpp"
|
||||
|
||||
namespace API::Subsonic
|
||||
{
|
||||
using namespace Database;
|
||||
|
||||
static const std::string_view reportedDummyDate{ "2000-01-01T00:00:00" };
|
||||
|
||||
Response::Node createPlaylistNode(const TrackList::pointer& tracklist, Session&)
|
||||
{
|
||||
Response::Node playlistNode;
|
||||
|
||||
playlistNode.setAttribute("id", idToString(tracklist->getId()));
|
||||
playlistNode.setAttribute("name", tracklist->getName());
|
||||
playlistNode.setAttribute("songCount", tracklist->getCount());
|
||||
playlistNode.setAttribute("duration", std::chrono::duration_cast<std::chrono::seconds>(tracklist->getDuration()).count());
|
||||
playlistNode.setAttribute("public", tracklist->isPublic());
|
||||
playlistNode.setAttribute("created", reportedDummyDate);
|
||||
playlistNode.setAttribute("owner", tracklist->getUser()->getLoginName());
|
||||
|
||||
return playlistNode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2023 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "services/database/Object.hpp"
|
||||
#include "SubsonicResponse.hpp"
|
||||
|
||||
namespace Database
|
||||
{
|
||||
class TrackList;
|
||||
class Session;
|
||||
}
|
||||
|
||||
namespace API::Subsonic
|
||||
{
|
||||
Response::Node createPlaylistNode(const Database::ObjectPtr<Database::TrackList>& tracklist, Database::Session& session);
|
||||
}
|
||||
Reference in New Issue
Block a user