/* * 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 . */ #include "Playlists.hpp" #include "database/Session.hpp" #include "database/Track.hpp" #include "database/TrackList.hpp" #include "database/User.hpp" #include "responses/Playlist.hpp" #include "responses/Song.hpp" #include "ParameterParsing.hpp" #include "SubsonicId.hpp" namespace lms::api::subsonic { using namespace db; Response handleGetPlaylistsRequest(RequestContext& context) { auto transaction{ context.dbSession.createReadTransaction() }; Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response::Node& playlistsNode{ response.createNode("playlists") }; TrackList::FindParameters params; params.setUser(context.user->getId()); 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(context.parameters, "id") }; auto transaction{ context.dbSession.createReadTransaction() }; 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.results) playlistNode.addArrayChild("entry", createSongNode(context, entry->getTrack(), context.user)); response.addNode("playlist", std::move(playlistNode)); return response; } Response handleCreatePlaylistRequest(RequestContext& context) { // Optional params const auto id{ getParameterAs(context.parameters, "playlistId") }; auto name{ getParameterAs(context.parameters, "name") }; std::vector trackIds{ getMultiParametersAs(context.parameters, "songId") }; if (!name && !id) throw RequiredParameterMissingError{ "name or playlistId" }; auto transaction{ context.dbSession.createWriteTransaction() }; TrackList::pointer tracklist; if (id) { tracklist = TrackList::find(context.dbSession, *id); if (!tracklist || tracklist->getUser() != context.user || tracklist->getType() != TrackListType::Playlist) { throw RequestedDataNotFoundError{}; } if (name) tracklist.modify()->setName(*name); } else { tracklist = context.dbSession.create(*name, TrackListType::Playlist, false, context.user); } for (const TrackId trackId : trackIds) { Track::pointer track{ Track::find(context.dbSession, trackId) }; if (!track) continue; context.dbSession.create(track, tracklist); } Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response::Node playlistNode{ createPlaylistNode(tracklist, context.dbSession) }; auto entries{ tracklist->getEntries() }; for (const TrackListEntry::pointer& entry : entries.results) playlistNode.addArrayChild("entry", createSongNode(context, entry->getTrack(), context.user)); response.addNode("playlist", std::move(playlistNode)); return response; } Response handleUpdatePlaylistRequest(RequestContext& context) { // Mandatory params TrackListId id{ getMandatoryParameterAs(context.parameters, "playlistId") }; // Optional parameters auto name{ getParameterAs(context.parameters, "name") }; auto isPublic{ getParameterAs(context.parameters, "public") }; std::vector trackIdsToAdd{ getMultiParametersAs(context.parameters, "songIdToAdd") }; std::vector trackPositionsToRemove{ getMultiParametersAs(context.parameters, "songIndexToRemove") }; auto transaction{ context.dbSession.createWriteTransaction() }; TrackList::pointer tracklist{ TrackList::find(context.dbSession, id) }; if (!tracklist || tracklist->getUser() != context.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()); 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(track, tracklist); } return Response::createOkResponse(context.serverProtocolVersion); } Response handleDeletePlaylistRequest(RequestContext& context) { TrackListId id{ getMandatoryParameterAs(context.parameters, "id") }; auto transaction{ context.dbSession.createWriteTransaction() }; TrackList::pointer tracklist{ TrackList::find(context.dbSession, id) }; if (!tracklist || tracklist->getUser() != context.user || tracklist->getType() != TrackListType::Playlist) { throw RequestedDataNotFoundError{}; } tracklist.remove(); return Response::createOkResponse(context.serverProtocolVersion); } }