From 0ca2d8b34ca7cd2e9412ca48d7587e0284877f61 Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 31 Oct 2024 15:50:17 +0100 Subject: [PATCH 01/28] Subsonci API: added getAlbmInfo support --- src/libs/subsonic/CMakeLists.txt | 1 + src/libs/subsonic/impl/SubsonicResource.cpp | 4 +- .../subsonic/impl/entrypoints/Browsing.cpp | 32 +++++++++++++ .../subsonic/impl/entrypoints/Browsing.hpp | 2 + .../subsonic/impl/responses/AlbumInfo.cpp | 47 +++++++++++++++++++ .../subsonic/impl/responses/AlbumInfo.hpp | 36 ++++++++++++++ 6 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 src/libs/subsonic/impl/responses/AlbumInfo.cpp create mode 100644 src/libs/subsonic/impl/responses/AlbumInfo.hpp diff --git a/src/libs/subsonic/CMakeLists.txt b/src/libs/subsonic/CMakeLists.txt index 7d9f81e62..fdc51c3bd 100644 --- a/src/libs/subsonic/CMakeLists.txt +++ b/src/libs/subsonic/CMakeLists.txt @@ -11,6 +11,7 @@ add_library(lmssubsonic SHARED impl/entrypoints/System.cpp impl/entrypoints/UserManagement.cpp impl/responses/Album.cpp + impl/responses/AlbumInfo.cpp impl/responses/Artist.cpp impl/responses/Bookmark.cpp impl/responses/Contributor.cpp diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index bf034115f..5e2d10cce 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -170,8 +170,8 @@ namespace lms::api::subsonic { "/getVideos", { handleNotImplemented } }, { "/getArtistInfo", { handleNotImplemented } }, { "/getArtistInfo2", { handleGetArtistInfo2Request } }, - { "/getAlbumInfo", { handleNotImplemented } }, - { "/getAlbumInfo2", { handleNotImplemented } }, + { "/getAlbumInfo", { handleGetAlbumInfo } }, + { "/getAlbumInfo2", { handleGetAlbumInfo2 } }, { "/getSimilarSongs", { handleGetSimilarSongsRequest } }, { "/getSimilarSongs2", { handleGetSimilarSongs2Request } }, { "/getTopSongs", { handleGetTopSongs } }, diff --git a/src/libs/subsonic/impl/entrypoints/Browsing.cpp b/src/libs/subsonic/impl/entrypoints/Browsing.cpp index 135f5765a..4607c712a 100644 --- a/src/libs/subsonic/impl/entrypoints/Browsing.cpp +++ b/src/libs/subsonic/impl/entrypoints/Browsing.cpp @@ -38,6 +38,7 @@ #include "SubsonicId.hpp" #include "Utils.hpp" #include "responses/Album.hpp" +#include "responses/AlbumInfo.hpp" #include "responses/Artist.hpp" #include "responses/Genre.hpp" #include "responses/Song.hpp" @@ -560,6 +561,37 @@ namespace lms::api::subsonic return response; } + Response handleGetAlbumInfo(RequestContext& context) + { + const db::DirectoryId directoryId{ getMandatoryParameterAs(context.parameters, "id") }; + + Response response{ Response::createOkResponse(context.serverProtocolVersion) }; + + { + auto transaction{ context.dbSession.createReadTransaction() }; + + if (db::Release::pointer release{ getReleaseFromDirectory(context.dbSession, directoryId) }) + response.addNode("albumInfo", createAlbumInfoNode(context, release)); + } + return response; + } + + Response handleGetAlbumInfo2(RequestContext& context) + { + const db::ReleaseId releaseId{ getMandatoryParameterAs(context.parameters, "id") }; + + Response response{ Response::createOkResponse(context.serverProtocolVersion) }; + + { + auto transaction{ context.dbSession.createReadTransaction() }; + + if (db::Release::pointer release{ db::Release::find(context.dbSession, releaseId) }) + response.addNode("albumInfo", createAlbumInfoNode(context, release)); + } + + return response; + } + Response handleGetSimilarSongsRequest(RequestContext& context) { return handleGetSimilarSongsRequestCommon(context, false /* no id3 */); diff --git a/src/libs/subsonic/impl/entrypoints/Browsing.hpp b/src/libs/subsonic/impl/entrypoints/Browsing.hpp index 48a0f09b3..f0db9e588 100644 --- a/src/libs/subsonic/impl/entrypoints/Browsing.hpp +++ b/src/libs/subsonic/impl/entrypoints/Browsing.hpp @@ -34,6 +34,8 @@ namespace lms::api::subsonic Response handleGetSongRequest(RequestContext& context); Response handleGetArtistInfoRequest(RequestContext& context); Response handleGetArtistInfo2Request(RequestContext& context); + Response handleGetAlbumInfo(RequestContext& context); + Response handleGetAlbumInfo2(RequestContext& context); Response handleGetSimilarSongsRequest(RequestContext& context); Response handleGetSimilarSongs2Request(RequestContext& context); Response handleGetTopSongs(RequestContext& context); diff --git a/src/libs/subsonic/impl/responses/AlbumInfo.cpp b/src/libs/subsonic/impl/responses/AlbumInfo.cpp new file mode 100644 index 000000000..4d3a4e5a1 --- /dev/null +++ b/src/libs/subsonic/impl/responses/AlbumInfo.cpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2024 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 "AlbumInfo.hpp" + +#include "database/Release.hpp" + +#include "RequestContext.hpp" + +namespace lms::api::subsonic +{ + Response::Node createAlbumInfoNode(RequestContext& context, const db::ObjectPtr& release) + { + Response::Node albumInfo; + + if (const auto releaseMBID{ release->getMBID() }) + { + switch (context.responseFormat) + { + case ResponseFormat::json: + albumInfo.setAttribute("musicBrainzId", releaseMBID->getAsString()); + break; + case ResponseFormat::xml: + albumInfo.createChild("musicBrainzId").setValue(releaseMBID->getAsString()); + break; + } + } + + return albumInfo; + } +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/AlbumInfo.hpp b/src/libs/subsonic/impl/responses/AlbumInfo.hpp new file mode 100644 index 000000000..fac1c7591 --- /dev/null +++ b/src/libs/subsonic/impl/responses/AlbumInfo.hpp @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2024 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 . + */ + +#pragma once + +#include "database/Object.hpp" + +#include "SubsonicResponse.hpp" + +namespace lms::db +{ + class Release; +} // namespace lms::db + +namespace lms::api::subsonic +{ + struct RequestContext; + + Response::Node createAlbumInfoNode(RequestContext& context, const db::ObjectPtr& release); +} // namespace lms::api::subsonic \ No newline at end of file From 472a5907adca35d5ce0c912cf503d57cdddad05a Mon Sep 17 00:00:00 2001 From: emeric Date: Thu, 31 Oct 2024 18:49:57 +0100 Subject: [PATCH 02/28] Subsonci API: added savePlayQueue / getPlayQueue support --- src/libs/database/CMakeLists.txt | 1 + src/libs/database/impl/Migration.cpp | 28 +++++- src/libs/database/impl/PlayQueue.cpp | 97 +++++++++++++++++++ src/libs/database/impl/Session.cpp | 2 + .../database/include/database/PlayQueue.hpp | 97 +++++++++++++++++++ src/libs/database/test/Migration.cpp | 2 + src/libs/subsonic/impl/SubsonicResource.cpp | 4 +- .../subsonic/impl/entrypoints/Bookmarks.cpp | 80 +++++++++++++++ .../subsonic/impl/entrypoints/Bookmarks.hpp | 2 + .../subsonic/impl/entrypoints/Browsing.cpp | 11 +-- 10 files changed, 315 insertions(+), 9 deletions(-) create mode 100644 src/libs/database/impl/PlayQueue.cpp create mode 100644 src/libs/database/include/database/PlayQueue.hpp diff --git a/src/libs/database/CMakeLists.txt b/src/libs/database/CMakeLists.txt index 3b2bf1d2a..af4a6a1b2 100644 --- a/src/libs/database/CMakeLists.txt +++ b/src/libs/database/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(lmsdatabase SHARED impl/Listen.cpp impl/MediaLibrary.cpp impl/Migration.cpp + impl/PlayQueue.cpp impl/TrackArtistLink.cpp impl/TrackFeatures.cpp impl/TrackList.cpp diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp index d1a0a1cee..179ca4385 100644 --- a/src/libs/database/impl/Migration.cpp +++ b/src/libs/database/impl/Migration.cpp @@ -35,7 +35,7 @@ namespace lms::db { namespace { - static constexpr Version LMS_DATABASE_VERSION{ 71 }; + static constexpr Version LMS_DATABASE_VERSION{ 72 }; } VersionInfo::VersionInfo() @@ -907,6 +907,31 @@ SELECT utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } + void migrateFromV71(Session& session) + { + // Add a file name/stem in tracks + utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "playqueue" ( + "id" integer primary key autoincrement, + "version" integer not null, + "name" text not null, + "current_index" integer not null, + "current_position_in_track" integer, + "last_modified_date_time" text, + "user_id" bigint, + constraint "fk_playqueue_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred +))"); + + utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "playqueue_track" ( + "playqueue_id" bigint, + "track_id" bigint not null, + primary key ("playqueue_id", "track_id"), + constraint "fk_playqueue_track_key1" foreign key ("playqueue_id") references "playqueue" ("id") on delete cascade deferrable initially deferred, + constraint "fk_playqueue_track_key2" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred +))"); + utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "playqueue_track_playqueue" on "playqueue_track" ("playqueue_id"))"); + utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "playqueue_track_track" on "playqueue_track" ("track_id"))"); + } + bool doDbMigration(Session& session) { constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" }; @@ -954,6 +979,7 @@ SELECT { 68, migrateFromV68 }, { 69, migrateFromV69 }, { 70, migrateFromV70 }, + { 71, migrateFromV71 }, }; bool migrationPerformed{}; diff --git a/src/libs/database/impl/PlayQueue.cpp b/src/libs/database/impl/PlayQueue.cpp new file mode 100644 index 000000000..e08efbab4 --- /dev/null +++ b/src/libs/database/impl/PlayQueue.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 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 "database/PlayQueue.hpp" + +#include + +#include "database/Directory.hpp" +#include "database/MediaLibrary.hpp" +#include "database/Release.hpp" +#include "database/Session.hpp" +#include "database/Track.hpp" +#include "database/User.hpp" + +#include "IdTypeTraits.hpp" +#include "StringViewTraits.hpp" +#include "Utils.hpp" + +namespace lms::db +{ + PlayQueue::PlayQueue(const ObjectPtr& user, std::string_view name) + : _name{ name } + , _user{ getDboPtr(user) } + { + } + + PlayQueue::pointer PlayQueue::create(Session& session, const ObjectPtr& user, std::string_view name) + { + return session.getDboSession()->add(std::unique_ptr{ new PlayQueue{ user, name } }); + } + + std::size_t PlayQueue::getCount(Session& session) + { + session.checkReadTransaction(); + + return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(*) FROM playqueue")); + } + + PlayQueue::pointer PlayQueue::find(Session& session, PlayQueueId id) + { + session.checkReadTransaction(); + + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT p from playqueue p").where("p.id = ?").bind(id)); + } + + PlayQueue::pointer PlayQueue::find(Session& session, UserId userId, std::string_view name) + { + session.checkReadTransaction(); + + auto query{ session.getDboSession()->query>("SELECT p from playqueue p") }; + query.where("p.user_id = ?").bind(userId); + query.where("p.name = ?").bind(name); + + return utils::fetchQuerySingleResult(query); + } + + void PlayQueue::clear() + { + _tracks.clear(); + } + + void PlayQueue::addTrack(const ObjectPtr& track) + { + _tracks.insert(getDboPtr(track)); + } + + Track::pointer PlayQueue::getTrackAtCurrentIndex() const + { + auto query{ _tracks.find() }; + query.offset(_currentIndex); + query.limit(1); + + return utils::fetchQuerySingleResult(query); + } + + void PlayQueue::visitTracks(const std::function& track)>& visitor) const + { + utils::forEachQueryResult(_tracks.find(), visitor); + } + +} // namespace lms::db diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index 23f6d6370..afd1b89ad 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -30,6 +30,7 @@ #include "database/Image.hpp" #include "database/Listen.hpp" #include "database/MediaLibrary.hpp" +#include "database/PlayQueue.hpp" #include "database/RatedArtist.hpp" #include "database/RatedRelease.hpp" #include "database/RatedTrack.hpp" @@ -104,6 +105,7 @@ namespace lms::db _session.mapClass