diff --git a/.clang-tidy b/.clang-tidy index e901f557..fd3c5114 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -12,7 +12,7 @@ CheckOptions: - key: cppcoreguidelines-avoid-do-while.IgnoreMacros value: '1' - key: performance-unnecessary-value-param.AllowedTypes - value: "shared_ptr;ObjectPtr" + value: "shared_ptr;ObjectPtr;.*::pointer" - key: cppcoreguidelines-special-member-functions.AllowMissingMoveFunctionsWhenCopyIsDeleted value: '1' - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor diff --git a/approot/messages.xml b/approot/messages.xml index ffee2275..9b6fe164 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -95,9 +95,10 @@ Cannot get track duration Cannot parse audio file +Cannot read file Cannot parse image file Cannot parse lyrics file -Cannot read file +Cannot parse playlist file Compact the database. Caution: this may take a while and will block the whole application during the compact step! {1} duplicate files: {1} errors: @@ -119,6 +120,7 @@ Scanning: step {1}/{2} Associating artist images: {1}%... Associating external lyrics: {1}%... +Associating playlist tracks: {1}%... Associating release images: {1}%... Checking for duplicate files... {1} files Checking for removed files... {1}% diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml index 4a7e576c..af414c08 100644 --- a/approot/messages_fr.xml +++ b/approot/messages_fr.xml @@ -95,9 +95,10 @@ Impossible de récupérer la durée de la piste Impossible d'analyser le fichier audio +Impossible de lire le fichier Impossible d'analyser le fichier image Impossible d'analyser le fichier de paroles -Impossible de lire le fichier +Impossible d'analyser le fichier de liste de lecture Compacter la base de données. Attention : cette opération peut prendre du temps et va vérouiller l'application pendant toute l'étape de compactage! {1} fichiers dupliqués : {1} erreurs : @@ -119,6 +120,7 @@ En cours de scan : étape {1}/{2} Association des images des artistes: {1}%... Association des paroles externes: {1}%... +Association des pistes des listes de lectures: {1}%... Association des images des albums: {1}%... Vérification des fichiers dupliqués... {1} fichiers Vérification des fichiers supprimés... {1}% diff --git a/approot/messages_it.xml b/approot/messages_it.xml index 478c0dc4..d4b8e40e 100644 --- a/approot/messages_it.xml +++ b/approot/messages_it.xml @@ -95,9 +95,10 @@ Non sono stato in grado di determinare la durata della traccia Impossibile analizzare il file audio +Non in grado di leggere il file Impossibile analizzare il file immagine Impossibile analizzare il file dei testi -Non in grado di leggere il file +Impossibile analizzare il file della playlist Compatta il database. Attenzione: ciò potrebbe richiedere del tempo e bloccherà l'intera applicazione durante il passaggio di compattazione! {1} file duplicati: {1} errori: @@ -119,6 +120,7 @@ Scansione: passo {1}/{2} Associando immagini degli artisti: {1}%... Associazione dei testi esterni: {1}%... +Associando brani della playlist: {1}%... Associando immagini degli album: {1}%... Controllo duplicati... {1} files Controllo file... {1}% diff --git a/approot/messages_pl.xml b/approot/messages_pl.xml index b0945b64..97841bc2 100644 --- a/approot/messages_pl.xml +++ b/approot/messages_pl.xml @@ -96,9 +96,10 @@ Nie udało się ustalić długości ścieżki Nie można przeanalizować pliku audio +Nie udało się odczytać pliku Nie można przeanalizować pliku obrazu Nie można przetworzyć pliku z tekstem -Nie udało się odczytać pliku +Nie można przeanalizować pliku playlisty Sprasuj bazę danych. Uwaga: może to trochę zająć, a cała aplikacja będzie w tym czasie zablokowana! {1} zduplikowany plik: @@ -128,6 +129,7 @@ Skanowanie: krok {1}/{2} Kojarzenie obrazów artystów: {1}%... Kojarzenie zewnętrznych tekstów: {1}%... +Kojarzenie utworów z playlisty: {1}%... Kojarzenie obrazów albumów: {1}%... Sprawdzanie duplikatów... {1} plik diff --git a/approot/messages_zh.xml b/approot/messages_zh.xml index 1a127517..f28c6fa9 100644 --- a/approot/messages_zh.xml +++ b/approot/messages_zh.xml @@ -99,6 +99,7 @@ 无法读取文件 + {1} 个重复文件: {1} 个错误: @@ -121,6 +122,7 @@ + 检查文件中... {1}% diff --git a/src/libs/core/impl/IOContextRunner.cpp b/src/libs/core/impl/IOContextRunner.cpp index 2094f691..465cf0fe 100644 --- a/src/libs/core/impl/IOContextRunner.cpp +++ b/src/libs/core/impl/IOContextRunner.cpp @@ -26,9 +26,9 @@ namespace lms::core { - IOContextRunner::IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount, std::string_view name) - : _ioService{ ioService } - , _work{ ioService } + IOContextRunner::IOContextRunner(boost::asio::io_context& ioContext, std::size_t threadCount, std::string_view name) + : _ioContext{ ioContext } + , _work{ ioContext } { LMS_LOG(UTILS, INFO, "Starting IO context with " << threadCount << " threads..."); @@ -50,7 +50,7 @@ namespace lms::core try { - _ioService.run(); + _ioContext.run(); } catch (const std::exception& e) { @@ -65,7 +65,7 @@ namespace lms::core { LMS_LOG(UTILS, DEBUG, "Stopping IO context..."); _work.reset(); - _ioService.stop(); + _ioContext.stop(); LMS_LOG(UTILS, DEBUG, "IO context stopped!"); } diff --git a/src/libs/core/include/core/IOContextRunner.hpp b/src/libs/core/include/core/IOContextRunner.hpp index a3fb30e0..56b1f881 100644 --- a/src/libs/core/include/core/IOContextRunner.hpp +++ b/src/libs/core/include/core/IOContextRunner.hpp @@ -22,25 +22,24 @@ #include #include -#include +#include namespace lms::core { class IOContextRunner { public: - IOContextRunner(boost::asio::io_service& ioService, std::size_t threadCount, std::string_view name); + IOContextRunner(boost::asio::io_context& ioContext, std::size_t threadCount, std::string_view name); ~IOContextRunner(); + IOContextRunner(const IOContextRunner&) = delete; + IOContextRunner& operator=(const IOContextRunner&) = delete; void stop(); std::size_t getThreadCount() const; private: - IOContextRunner(const IOContextRunner&) = delete; - IOContextRunner& operator=(const IOContextRunner&) = delete; - - boost::asio::io_service& _ioService; - std::optional _work; + boost::asio::io_context& _ioContext; + std::optional _work; std::vector _threads; }; } // namespace lms::core \ No newline at end of file diff --git a/src/libs/database/CMakeLists.txt b/src/libs/database/CMakeLists.txt index af4a6a1b..2fe78ac7 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/PlayListFile.cpp impl/PlayQueue.cpp impl/TrackArtistLink.cpp impl/TrackFeatures.cpp diff --git a/src/libs/database/impl/Directory.cpp b/src/libs/database/impl/Directory.cpp index 7bed3d4a..3824bb78 100644 --- a/src/libs/database/impl/Directory.cpp +++ b/src/libs/database/impl/Directory.cpp @@ -170,10 +170,12 @@ namespace lms::db query.leftJoin("track t ON d.id = t.directory_id"); query.leftJoin("image i ON d.id = i.directory_id"); query.leftJoin("track_lyrics l_lrc ON d.id = l_lrc.directory_id"); + query.leftJoin("playlist_file pl_f ON d.id = pl_f.directory_id"); query.where("d_child.id IS NULL"); query.where("t.directory_id IS NULL"); query.where("i.directory_id IS NULL"); query.where("l_lrc.directory_id IS NULL"); + query.where("pl_f.directory_id IS NULL"); return utils::execRangeQuery(query, range); } diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp index 44378136..4a83942d 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{ 76 }; + static constexpr Version LMS_DATABASE_VERSION{ 77 }; } VersionInfo::VersionInfo() @@ -127,7 +127,7 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" ( void migrateFromV36(Session& session) { // Increased precision for track durations (now in milliseconds instead of secodns) - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -136,7 +136,7 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" ( // Support Performer tags (via subtypes) utils::executeCommand(*session.getDboSession(), "ALTER TABLE track_artist_link ADD subtype TEXT"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -176,7 +176,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" ( utils::executeCommand(*session.getDboSession(), "DROP TABLE track"); utils::executeCommand(*session.getDboSession(), "ALTER TABLE track_backup RENAME TO track"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -186,7 +186,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" ( utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD primary_type INTEGER"); utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD secondary_types INTEGER"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -196,7 +196,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" ( utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD artist_display_name TEXT NOT NULL DEFAULT ''"); utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD artist_display_name TEXT NOT NULL DEFAULT ''"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -241,7 +241,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" ( utils::executeCommand(*session.getDboSession(), "ALTER TABLE cluster ADD track_count INTEGER"); utils::executeCommand(*session.getDboSession(), "ALTER TABLE cluster ADD release_count INTEGER"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -250,7 +250,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" ( // add bitrate utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD bitrate INTEGER NOT NULL DEFAULT 0"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -274,7 +274,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" ( utils::executeCommand(*session.getDboSession(), "ALTER TABLE scan_settings ADD COLUMN extra_tags_to_scan TEXT"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -299,14 +299,14 @@ CREATE TABLE IF NOT EXISTS "track_backup" ( utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "release_release_type_release_type" on "release_release_type" ("release_type_id"))"); utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "release_release_type_release" on "release_release_type" ("release_id"))"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } void migrateFromV48(Session& session) { // Regression for the extra tags not being parsed - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -316,7 +316,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" ( utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD year INTEGER"); utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD original_year INTEGER"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -423,7 +423,7 @@ SELECT // Add sort name for releases utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD sort_name TEXT NOT NULL DEFAULT ''"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -432,7 +432,7 @@ SELECT // Add release group mbid utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD group_mbid TEXT NOT NULL DEFAULT ''"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -443,7 +443,7 @@ SELECT utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD file_size BIGINT NOT NULL DEFAULT(0)"); utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD relative_file_path TEXT NOT NULL DEFAULT ''"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -454,7 +454,7 @@ SELECT utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD channel_count INTEGER NOT NULL DEFAULT(0)"); utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD sample_rate INTEGER NOT NULL DEFAULT(0)"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -494,7 +494,7 @@ SELECT constraint "fk_image_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred ))"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -625,7 +625,7 @@ SELECT utils::executeCommand(*session.getDboSession(), "DROP TABLE image"); utils::executeCommand(*session.getDboSession(), "ALTER TABLE image_backup RENAME TO image"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -660,7 +660,7 @@ SELECT utils::executeCommand(*session.getDboSession(), "DROP TABLE directory"); utils::executeCommand(*session.getDboSession(), "ALTER TABLE directory_backup RENAME TO directory"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -669,7 +669,7 @@ SELECT // Add a new column comment utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD comment TEXT NOT NULL DEFAULT ''"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -732,7 +732,7 @@ SELECT utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "release_label_label" on "release_label" ("label_id"))"); utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "release_label_release" on "release_label" ("release_id"))"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -740,7 +740,7 @@ SELECT { utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD is_compilation BOOLEAN NOT NULL DEFAULT(false)"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -805,7 +805,7 @@ SELECT for (const auto& indexName : indexeNames) utils::executeCommand(*session.getDboSession(), "DROP INDEX " + indexName); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -868,7 +868,7 @@ SELECT utils::executeCommand(*session.getDboSession(), "DROP TABLE artist"); utils::executeCommand(*session.getDboSession(), "ALTER TABLE artist_backup RENAME TO artist"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -903,7 +903,7 @@ SELECT constraint "fk_track_lyrics_directory" foreign key ("directory_id") references "directory" ("id") on delete cascade deferrable initially deferred ))"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -937,7 +937,7 @@ SELECT // Add catalog number utils::executeCommand(*session.getDboSession(), "ALTER TABLE release ADD barcode TEXT NOT NULL DEFAULT ''"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything + // Just increment the scan version of the settings to make the next scan rescan everything utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); } @@ -968,6 +968,67 @@ SELECT utils::executeCommand(*session.getDboSession(), "ALTER TABLE user ADD bcrypt_round_count INTEGER NOT NULL DEFAULT(7)"); } + void migrateFromV76(Session& session) + { + // Rename public -> visibility (false is Private(0) and true is Public(1)) + utils::executeCommand(*session.getDboSession(), "ALTER TABLE tracklist RENAME COLUMN public TO visibility"); + + // Supported extensions are now runtime + utils::executeCommand(*session.getDboSession(), "ALTER TABLE scan_settings DROP COLUMN audio_file_extensions"); + + // Add PlayListFile + utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "playlist_file" ( + "id" integer primary key autoincrement, + "version" integer not null, + "absolute_file_path" text not null, + "file_stem" text not null, + "file_size" bigint not null, + "file_last_write" text, + "name" text not null, + "entries" text not null, + "media_library_id" bigint, + "directory_id" bigint, + constraint "fk_playlist_file_media_library" foreign key ("media_library_id") references "media_library" ("id") on delete set null deferrable initially deferred, + constraint "fk_playlist_file_directory" foreign key ("directory_id") references "directory" ("id") on delete cascade deferrable initially deferred +))"); + + // Add a link into tracklist to ease cleanup + utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "tracklist_backup" ( + "id" integer primary key autoincrement, + "version" integer not null, + "name" text not null, + "type" integer not null, + "visibility" integer not null, + "creation_date_time" text, + "last_modified_date_time" text, + "user_id" bigint, + "playlist_file_id" bigint, + constraint "fk_tracklist_user" foreign key ("user_id") references "user" ("id") on delete cascade deferrable initially deferred, + constraint "fk_tracklist_playlist_file" foreign key ("playlist_file_id") references "playlist_file" ("id") on delete cascade deferrable initially deferred))"); + + utils::executeCommand(*session.getDboSession(), R"(INSERT INTO tracklist_backup +SELECT + id, + version, + name, + type, + visibility, + creation_date_time, + last_modified_date_time, + user_id, + NULL AS playlist_file_id +FROM tracklist)"); + + utils::executeCommand(*session.getDboSession(), R"(DROP TABLE tracklist)"); + utils::executeCommand(*session.getDboSession(), R"(ALTER TABLE tracklist_backup RENAME TO tracklist)"); + + // Add a file name in tracks + utils::executeCommand(*session.getDboSession(), "ALTER TABLE track ADD COLUMN file_name TEXT NOT NULL DEFAULT ''"); + + // Just increment the scan version of the settings to make the next scan rescan everything + utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET scan_version = scan_version + 1"); + } + bool doDbMigration(Session& session) { constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" }; @@ -1020,6 +1081,7 @@ SELECT { 73, migrateFromV73 }, { 74, migrateFromV74 }, { 75, migrateFromV75 }, + { 76, migrateFromV76 }, }; bool migrationPerformed{}; diff --git a/src/libs/database/impl/PlayListFile.cpp b/src/libs/database/impl/PlayListFile.cpp new file mode 100644 index 00000000..0bbc4db1 --- /dev/null +++ b/src/libs/database/impl/PlayListFile.cpp @@ -0,0 +1,142 @@ +/* + * 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/PlayListFile.hpp" + +#include + +#include "core/ILogger.hpp" +#include "database/Directory.hpp" +#include "database/MediaLibrary.hpp" +#include "database/Session.hpp" +#include "database/TrackList.hpp" +#include "database/User.hpp" + +#include "IdTypeTraits.hpp" +#include "PathTraits.hpp" +#include "StringViewTraits.hpp" +#include "Utils.hpp" + +namespace lms::db +{ + PlayListFile::PlayListFile(const std::filesystem::path& file) + { + setAbsoluteFilePath(file); + } + + PlayListFile::pointer PlayListFile::create(Session& session, const std::filesystem::path& file) + { + return session.getDboSession()->add(std::unique_ptr{ new PlayListFile{ file } }); + } + + std::size_t PlayListFile::getCount(Session& session) + { + session.checkReadTransaction(); + + return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(*) FROM playlist_file")); + } + + PlayListFile::pointer PlayListFile::find(Session& session, const std::filesystem::path& p) + { + session.checkReadTransaction(); + + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT pl_f from playlist_file pl_f").where("pl_f.absolute_file_path = ?").bind(p.string())); + } + + void PlayListFile::find(Session& session, PlayListFileId& lastRetrievedId, std::size_t count, const std::function& func) + { + session.checkReadTransaction(); + + auto query{ session.getDboSession()->query>("SELECT pl_f from playlist_file pl_f").orderBy("pl_f.id").where("pl_f.id > ?").bind(lastRetrievedId).limit(static_cast(count)) }; + + utils::forEachQueryResult(query, [&](const PlayListFile::pointer& playList) { + func(playList); + lastRetrievedId = playList->getId(); + }); + } + + PlayListFile::pointer PlayListFile::find(Session& session, PlayListFileId id) + { + session.checkReadTransaction(); + + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT pl_f from playlist_file pl_f").where("pl_f.id = ?").bind(id)); + } + + std::vector PlayListFile::getFiles() const + { + std::vector files; + { + Wt::Json::Object root; + Wt::Json::parse(_entries, root); + + assert(root.type("files") == Wt::Json::Type::Array); + const Wt::Json::Array& filesArray = root.get("files"); + for (const Wt::Json::Value& file : filesArray) + files.push_back(static_cast(file.toString())); + } + + return files; + } + + TrackList::pointer PlayListFile::getTrackList() const + { + return _trackList.lock(); + } + + Directory::pointer PlayListFile::getDirectory() const + { + return _directory; + } + + void PlayListFile::setAbsoluteFilePath(const std::filesystem::path& filePath) + { + assert(filePath.is_absolute()); + _absoluteFilePath = filePath; + _fileStem = filePath.stem(); + } + + void PlayListFile::setDirectory(ObjectPtr directory) + { + _directory = getDboPtr(directory); + } + + void PlayListFile::setTrackList(ObjectPtr trackList) + { + _trackList = getDboPtr(trackList); + } + + void PlayListFile::setName(std::string_view name) + { + _name = std::string{ name, 0, _maxNameLength }; + if (name.size() > _maxNameLength) + LMS_LOG(DB, WARNING, "PlaylistFile name too long, truncated to '" << _name << "'"); + } + + void PlayListFile::setFiles(std::span files) + { + Wt::Json::Object root; + + Wt::Json::Array fileArray; + for (const auto& file : files) + fileArray.push_back(Wt::Json::Value{ file.string() }); + + root["files"] = std::move(fileArray); + _entries = Wt::Json::serialize(root); + } +} // namespace lms::db diff --git a/src/libs/database/impl/ScanSettings.cpp b/src/libs/database/impl/ScanSettings.cpp index c031fb81..4168e402 100644 --- a/src/libs/database/impl/ScanSettings.cpp +++ b/src/libs/database/impl/ScanSettings.cpp @@ -46,17 +46,6 @@ namespace lms::db return utils::fetchQuerySingleResult(session.getDboSession()->find()); } - std::vector ScanSettings::getAudioFileExtensions() const - { - const auto extensions{ core::stringUtils::splitString(_audioFileExtensions, ' ') }; - - std::vector res(std::cbegin(extensions), std::cend(extensions)); - std::sort(std::begin(res), std::end(res)); - res.erase(std::unique(std::begin(res), std::end(res)), std::end(res)); - - return res; - } - std::vector ScanSettings::getExtraTagsToScan() const { std::vector tags{ core::stringUtils::splitString(_extraTagsToScan, ';') }; diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index d03389e2..2cb88d37 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/PlayListFile.hpp" #include "database/PlayQueue.hpp" #include "database/RatedArtist.hpp" #include "database/RatedRelease.hpp" @@ -105,6 +106,7 @@ namespace lms::db _session.mapClass