diff --git a/.clang-tidy b/.clang-tidy index fd3c5114..2e08e112 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -17,4 +17,6 @@ CheckOptions: value: '1' - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor value: '1' + - key: readability-function-cognitive-complexity.IgnoreMacros + value: '1' ... diff --git a/Dockerfile-release b/Dockerfile-release index 69ae0be3..2521139e 100644 --- a/Dockerfile-release +++ b/Dockerfile-release @@ -1,4 +1,4 @@ -FROM alpine:3.21.3 AS build +FROM alpine:3.22 AS build WORKDIR /tmp/workdir @@ -79,7 +79,7 @@ RUN \ make -j$(nproc) install # WT -ARG WT_VERSION=4.11.3 +ARG WT_VERSION=4.11.4 ARG WT_DEBUG=OFF RUN \ DIR=/tmp/wt && mkdir -p ${DIR} && cd ${DIR} && \ @@ -101,7 +101,7 @@ RUN \ cp ./*.h ${PREFIX}/include/stb # TAGLIB -ARG TAGLIB_VERSION=v2.0.2 +ARG TAGLIB_VERSION=v2.1.1 RUN \ DIR=/tmp/taglib && mkdir -p ${DIR} && cd ${DIR} && \ curl -sLO https://github.com/taglib/taglib/archive/${TAGLIB_VERSION}.tar.gz && \ @@ -145,7 +145,7 @@ RUN \ rm -rf /tmp/fakeroot/share/Wt/resources/themes ## Release Stage -FROM alpine:3.21.3 AS release +FROM alpine:3.22 AS release LABEL maintainer="Emeric Poupon " ARG RUNTIME_PACKAGES=" \ diff --git a/approot/admin-db.xml b/approot/admin-db.xml new file mode 100644 index 00000000..ca7246ac --- /dev/null +++ b/approot/admin-db.xml @@ -0,0 +1,14 @@ + + + + +
+
+
+ ${export-query-plans-btn class="btn btn-primary"} +
+
+
+
+ +
diff --git a/approot/admin-debugtools.xml b/approot/admin-debugtools.xml new file mode 100644 index 00000000..83025d9f --- /dev/null +++ b/approot/admin-debugtools.xml @@ -0,0 +1,15 @@ + + + + +
+ ${tr:Lms.Admin.DebugTools.Tracing.tracing} + ${tracing} +
+ ${tr:Lms.Admin.DebugTools.Db.db} + ${db} +
+ +
+ +
diff --git a/approot/admin-tracing.xml b/approot/admin-tracing.xml index eeb1ad43..5f5376a2 100644 --- a/approot/admin-tracing.xml +++ b/approot/admin-tracing.xml @@ -1,9 +1,8 @@ - +
- ${tr:Lms.Admin.Tracing.tracing}
${export-btn class="btn btn-primary"} diff --git a/approot/main.xml b/approot/main.xml index 0dea95e8..d9c0fcd4 100644 --- a/approot/main.xml +++ b/approot/main.xml @@ -44,7 +44,10 @@
  • ${scan-settings class="dropdown-item"}
  • ${scanner class="dropdown-item"}
  • ${users class="dropdown-item"}
  • -
  • ${tracing class="dropdown-item"}
  • + ${} + +
  • ${debug-tools class="dropdown-item"}
  • + ${
    } ${} diff --git a/approot/messages.xml b/approot/messages.xml index d3df21c2..070c40f2 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -52,10 +52,10 @@ User not found + Debug tools Libraries Scan settings Scanner - Tracing Users @@ -145,13 +145,20 @@ Reconciliating artists: {1} entries... Reloading similarity engine: {1}%... Removing orphaned entries: {1} entries... -Scanning files: {1}/{2} ({3}%)... +Library analysis: {1} files... Step status Updating library fields: {1} entries + +Debug tools + + +Export query plans +Database + -Export traces -Tracing +Export traces +Tracing New user diff --git a/approot/messages_es.xml b/approot/messages_es.xml index 2811c7bd..0bce3785 100644 --- a/approot/messages_es.xml +++ b/approot/messages_es.xml @@ -52,10 +52,10 @@ No existe el usuario + Herramientas de depuración Bibliotecas Opciones de escaneo Escanear - Traces Usuarios @@ -139,19 +139,25 @@ Comprobando ficheros eliminados... {1}% Compactando la base de datos... Calculando estadísticas... {1}% -Descubriendo ficheros: {1} ficheros Accediendo a las características de la pista desde AcousticBrainz : {1}/{2} ficheros ({3}%)... Optimizando la base de datos... {1}%... Reconciliando artistas: {1} entradas... Recargando el motor de similitud: {1}%... Borrando entradas huérfanas: {1} entradas... -Escaneando ficheros: {1} / {2} ({3}%)... +Análisis de bibliotecas: {1} ficheros... Estado de las etapas Actualizando campos de la biblioteca: {1} entradas + +Herramientas de depuración + + +Exportar planes de consulta +Base de datos + -Exportar las trazas -Trazas +Exportar las trazas +Trazas Añadir diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml index 54a216c9..c8c654b0 100644 --- a/approot/messages_fr.xml +++ b/approot/messages_fr.xml @@ -52,10 +52,10 @@ L'utilisateur n'existe pas + Outils de débogage Bibliothèques Paramètres du scan Scanner - Traces Utilisateurs @@ -74,7 +74,7 @@ Permettre la fusion des artistes sans MBID avec ceux qui en ont un -Utilizza un'immagine dell'album se non è disponibile un'immagine dell'artista +Utiliser une image d'album si aucune image de l'artiste n'est disponible Délimiteurs à utiliser pour séparer les tags d'artistes Artistes à ne pas séparer en utilisant les délimiteurs (un artiste par ligne) Tous les jours @@ -139,19 +139,25 @@ Vérification des fichiers supprimés... {1}% Compactage de la base de données... Calcul des statistiques... {1}% -Découverte des fichiers : {1} fichiers Récupération des métadonnées AcousticBrainz : {1}/{2} fichiers ({3}%)... Optimisation de la base de données... {1}%... Reconciliation des artistes: {1} entrées... Rechargement du moteur de recommandation : {1}%... Retrait des entrées orphelines: {1} entrées... -Scan des fichiers : {1}/{2} ({3}%)... +Analyse des bibliothèques : {1} fichiers... Statut de l'étape Mise à jour des champs des bibliothèques: {1} entrées + +Outils de débogage + + +Exporter les plans de requête +Base de données + -Exporter les traces -Traces +Exporter les traces +Traces Ajouter diff --git a/approot/messages_it.xml b/approot/messages_it.xml index 95638714..42a157a5 100644 --- a/approot/messages_it.xml +++ b/approot/messages_it.xml @@ -52,10 +52,10 @@ Utente non trovato + Strumenti di debug Librerie Impostazioni di scansione Scanner - Tracing Utenti @@ -74,7 +74,7 @@ Consentire la fusione degli artisti senza MBID con quelli che ne hanno uno -Usa l'immagine dell'album se non è disponibile un'immagine dell'artista +Utilizza un'immagine dell'album se non è disponibile un'immagine dell'artista Delimitatori da utilizzare per separare i tag degli artisti Artisti da non suddividere utilizzando i delimitatori (un artista per riga) Giornaliera @@ -139,19 +139,25 @@ Controllo file... {1}% Compattazione del database... Calcolo statistiche... {1}% -File trovati: {1} files Recupero metadati da AcousticBrainz: {1}/{2} tracce ({3}%)... Ottimizzazione del database... {1}%... Riconciliazione artisti: {1} voci... Ricarica motore di tracce simili: {1}%... Rimozione voci orfane: {1} voci... -Scansione dei file: {1}/{2} ({3}%)... +Analisi delle librerie: {1} file... Stato passo Aggiornamento dei campi della libreria: {1} voci + +Strumenti di debug + + +Esporta i piani di query +Database + -Esporta tracce -Tracing +Esporta tracce +Tracing Nuovo utente diff --git a/approot/messages_pl.xml b/approot/messages_pl.xml index 8a117a43..5adcdb87 100644 --- a/approot/messages_pl.xml +++ b/approot/messages_pl.xml @@ -53,10 +53,10 @@ Użytkownik nie odnaleziony + Narzędzia debugowania Biblioteki Ustawienia skanowania Skaner - Śledzenie Użytkownicy @@ -152,23 +152,29 @@ Sprawdzanie plików... {1}% Prasowanie bazy danych... Obliczanie statystyk... {1}% - - Odkrywanie plików: {1} plik - Odkrywanie plików: {1} pliki - Odkrywanie plików: {1} plików - Pobieranie danych o ścieżce z AcousticBrainz: {1}/{2} ścieżek ({3}%)... Optymalizowanie bazy danych... {1}%... Uzgodnianie artystów: {1} wpisów... Przeładowywanie silnika podobieństw: {1}%... Usuwanie osieroconych wpisów: {1} wpisów... -Skanowanie plików: {1}/{2} ({3}%)... + + Analiza bibliotek: {1} plik + Analiza bibliotek: {1} pliki + Analiza bibliotek: {1} plików + Obecny krok Aktualizowanie pól biblioteki: {1} wpisów + +Narzędzia debugowania + + +Eksportuj plany zapytań +Baza danych + -Eksportuj ślady -Śledzenie +Eksportuj ślady +Śledzenie Nowy użytkownik diff --git a/approot/messages_zh.xml b/approot/messages_zh.xml index 4825aea7..6ca9201f 100644 --- a/approot/messages_zh.xml +++ b/approot/messages_zh.xml @@ -137,15 +137,20 @@ 检查文件中... {1}% -检索文件中: {1} 文件 从 AcousticBrainz 获取音轨特征: {1}/{2} 音轨 ({3}%)... 重载相似引擎中 {1}%... -扫描文件中: {1}/{2} 个文件 ({3}%)... + 当前步骤状态 + + + + + + diff --git a/conf/lms.conf b/conf/lms.conf index 1c2dfa12..4636f82e 100644 --- a/conf/lms.conf +++ b/conf/lms.conf @@ -17,7 +17,11 @@ log-min-severity = "info"; # Can be "none", "quick", or "full" db-integrity-check = "quick"; # Output db queries on stdout +# Use this only for debugging purpose, as this may impact performance db-show-queries = false; +# Record query plans for database queries. +# Use this only for debugging purposes, as this may impact performance +db-record-query-plans = false; # Listen port/addr of the web server listen-port = 5082; @@ -118,5 +122,5 @@ scanner-skip-duplicate-mbid = false; # Scanner read style for metadata, may be 'fast', 'average' or 'accurate' scanner-parser-read-style = "average"; -# Number of threads to use for scanning file metadata (0 means number of logical CPUs / 2) -scanner-metadata-thread-count = 0; \ No newline at end of file +# Number of threads to use for parallelized tasks (e.g., scanning file metadata). 0 means half the number of logical CPUs. +scanner-thread-count = 0; \ No newline at end of file diff --git a/src/libs/av/impl/AudioFile.cpp b/src/libs/av/impl/AudioFile.cpp index 614c6cde..6c91c368 100644 --- a/src/libs/av/impl/AudioFile.cpp +++ b/src/libs/av/impl/AudioFile.cpp @@ -28,7 +28,6 @@ extern "C" } #include -#include #include #include "core/ILogger.hpp" diff --git a/src/libs/core/CMakeLists.txt b/src/libs/core/CMakeLists.txt index 8ce177b5..0eb0675e 100644 --- a/src/libs/core/CMakeLists.txt +++ b/src/libs/core/CMakeLists.txt @@ -10,6 +10,7 @@ add_library(lmscore STATIC impl/ChildProcessManager.cpp impl/Config.cpp impl/FileResourceHandler.cpp + impl/JobScheduler.cpp impl/IOContextRunner.cpp impl/Logger.cpp impl/MimeTypes.cpp diff --git a/src/libs/core/impl/JobScheduler.cpp b/src/libs/core/impl/JobScheduler.cpp new file mode 100644 index 00000000..49e97b46 --- /dev/null +++ b/src/libs/core/impl/JobScheduler.cpp @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2025 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 "JobScheduler.hpp" + +#include + +#include "core/ITraceLogger.hpp" + +#include "core/IJob.hpp" + +namespace lms::core +{ + std::unique_ptr createJobScheduler(core::LiteralString name, std::size_t threadCount) + { + return std::make_unique(name, threadCount); + } + + JobScheduler::JobScheduler(core::LiteralString name, std::size_t threadCount) + : _name{ name } + , _ioContextRunner{ _ioContext, threadCount, name.str() } + { + } + + JobScheduler::~JobScheduler() = default; + + void JobScheduler::setShouldAbortCallback(ShouldAbortCallback callback) + { + _abortCallback = callback; + } + + std::size_t JobScheduler::getThreadCount() const + { + return _ioContextRunner.getThreadCount(); + } + + void JobScheduler::scheduleJob(std::unique_ptr job) + { + { + std::scoped_lock lock{ _mutex }; + _ongoingJobCount += 1; + } + + auto jobHandler{ [job = std::move(job), this]() mutable { + if (_abortCallback && _abortCallback()) + { + std::scoped_lock lock{ _mutex }; + _ongoingJobCount -= 1; + } + else + { + { + LMS_SCOPED_TRACE_OVERVIEW(_name, job->getName()); + job->run(); + } + + { + std::scoped_lock lock{ _mutex }; + + _doneJobs.emplace_back(std::move(job)); + _ongoingJobCount -= 1; + } + } + + _condVar.notify_all(); + } }; + + boost::asio::post(_ioContext, std::move(jobHandler)); + } + + std::size_t JobScheduler::getJobsDoneCount() const + { + std::scoped_lock lock{ _mutex }; + return _doneJobs.size(); + } + + size_t JobScheduler::popJobsDone(std::vector>& doneJobs, std::size_t maxCount) + { + doneJobs.clear(); + doneJobs.reserve(maxCount); + + { + std::scoped_lock lock{ _mutex }; + + while (doneJobs.size() < maxCount && !_doneJobs.empty()) + { + doneJobs.push_back(std::move(_doneJobs.front())); + _doneJobs.pop_front(); + } + } + + return doneJobs.size(); + } + + void JobScheduler::waitUntilJobCountAtMost(std::size_t maxOngoingJobs) + { + if (_ongoingJobCount <= maxOngoingJobs) + return; + + { + LMS_SCOPED_TRACE_OVERVIEW(_name, "WaitJobs"); + + std::unique_lock lock{ _mutex }; + _condVar.wait(lock, [=, this] { return _ongoingJobCount <= maxOngoingJobs; }); + } + } + + void JobScheduler::wait() + { + waitUntilJobCountAtMost(0); + } + +} // namespace lms::core diff --git a/src/libs/core/impl/JobScheduler.hpp b/src/libs/core/impl/JobScheduler.hpp new file mode 100644 index 00000000..2511fef6 --- /dev/null +++ b/src/libs/core/impl/JobScheduler.hpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2025 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 +#include +#include +#include + +#include "core/IJobScheduler.hpp" +#include "core/IOContextRunner.hpp" + +namespace lms::core +{ + class JobScheduler : public IJobScheduler + { + public: + JobScheduler(core::LiteralString name, std::size_t threadCount); + ~JobScheduler() override; + JobScheduler(const JobScheduler&) = delete; + JobScheduler& operator=(const JobScheduler&) = delete; + + private: + void setShouldAbortCallback(ShouldAbortCallback callback) override; + + std::size_t getThreadCount() const override; + void scheduleJob(std::unique_ptr job) override; + + std::size_t getJobsDoneCount() const override; + size_t popJobsDone(std::vector>& jobs, std::size_t maxCount) override; + + void waitUntilJobCountAtMost(std::size_t maxOngoingJobs) override; + void wait() override; + + core::LiteralString _name; + boost::asio::io_context _ioContext; + core::IOContextRunner _ioContextRunner; + + ShouldAbortCallback _abortCallback; + + mutable std::mutex _mutex; + std::atomic _ongoingJobCount; + std::deque> _doneJobs; + std::condition_variable _condVar; + }; +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/impl/Path.cpp b/src/libs/core/impl/Path.cpp index 248042c7..ff7436a4 100644 --- a/src/libs/core/impl/Path.cpp +++ b/src/libs/core/impl/Path.cpp @@ -20,45 +20,13 @@ #include "core/Path.hpp" #include -#include -#include -#include -#include #include -#include - -#include "core/Crc32Calculator.hpp" -#include "core/Exception.hpp" #include "core/ILogger.hpp" #include "core/String.hpp" namespace lms::core::pathUtils { - std::uint32_t computeCrc32(const std::filesystem::path& p) - { - core::Crc32Calculator crc32; - - std::ifstream ifs{ p, std::ios_base::binary }; - if (ifs) - { - do - { - std::array buffer; - - ifs.read(buffer.data(), buffer.size()); - crc32.processBytes(reinterpret_cast(buffer.data()), ifs.gcount()); - } while (ifs); - } - else - { - LMS_LOG(DBUPDATER, ERROR, "Failed to open file " << p); - throw LmsException("Failed to open file '" + p.string() + "'"); - } - - return crc32.getResult(); - } - bool ensureDirectory(const std::filesystem::path& dir) { if (std::filesystem::exists(dir)) @@ -67,81 +35,6 @@ namespace lms::core::pathUtils return std::filesystem::create_directory(dir); } - Wt::WDateTime getLastWriteTime(const std::filesystem::path& file, std::error_code& ec) - { - Wt::WDateTime res; - - struct stat sb - { - }; - if (stat(file.c_str(), &sb) == -1) - { - ec = std::error_code{ errno, std::generic_category() }; - } - else - { - ec = std::error_code{}; - res = Wt::WDateTime::fromTime_t(sb.st_mtime); - } - - return res; - } - - bool exploreFilesRecursive(const std::filesystem::path& directory, std::function cb, const std::filesystem::path* excludeDirFileName) - { - std::error_code ec; - std::filesystem::directory_iterator itPath{ directory, std::filesystem::directory_options::follow_directory_symlink, ec }; - - if (ec) - { - cb(ec, directory); - return true; // try to continue exploring anyway - } - - if (excludeDirFileName && !excludeDirFileName->empty()) - { - const std::filesystem::path excludePath{ directory / *excludeDirFileName }; - - if (std::filesystem::exists(excludePath, ec)) - { - LMS_LOG(DBUPDATER, DEBUG, "Found " << excludePath << ": skipping directory"); - return true; - } - } - - std::filesystem::directory_iterator itEnd; - while (itPath != itEnd) - { - bool continueExploring{ true }; - - if (ec) - { - continueExploring = cb(ec, *itPath); - } - else - { - if (std::filesystem::is_regular_file(*itPath, ec)) - { - continueExploring = cb(ec, *itPath); - } - else if (std::filesystem::is_directory(*itPath, ec)) - { - if (!ec) - continueExploring = exploreFilesRecursive(*itPath, cb, excludeDirFileName); - else - continueExploring = cb(ec, *itPath); - } - } - - if (!continueExploring) - return false; - - itPath.increment(ec); - } - - return true; - } - bool hasFileAnyExtension(const std::filesystem::path& file, std::span supportedExtensions) { const std::filesystem::path extension{ stringUtils::stringToLower(file.extension().c_str()) }; diff --git a/src/libs/services/scanner/impl/steps/ScanStepDiscoverFiles.hpp b/src/libs/core/include/core/IJob.hpp similarity index 58% rename from src/libs/services/scanner/impl/steps/ScanStepDiscoverFiles.hpp rename to src/libs/core/include/core/IJob.hpp index ab8baeff..4cf73268 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepDiscoverFiles.hpp +++ b/src/libs/core/include/core/IJob.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2023 Emeric Poupon + * Copyright (C) 2025 Emeric Poupon * * This file is part of LMS. * @@ -19,19 +19,16 @@ #pragma once -#include "ScanStepBase.hpp" +#include "core/LiteralString.hpp" -namespace lms::scanner +namespace lms::core { - class ScanStepDiscoverFiles : public ScanStepBase + class IJob { public: - using ScanStepBase::ScanStepBase; + virtual ~IJob() = default; - private: - ScanStep getStep() const override { return ScanStep::DiscoverFiles; } - core::LiteralString getStepName() const override { return "Discover files"; } - bool needProcess(const ScanContext& context) const override; - void process(ScanContext& context) override; + virtual LiteralString getName() const = 0; + virtual void run() = 0; }; -} // namespace lms::scanner +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/include/core/IJobScheduler.hpp b/src/libs/core/include/core/IJobScheduler.hpp new file mode 100644 index 00000000..40644c17 --- /dev/null +++ b/src/libs/core/include/core/IJobScheduler.hpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 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 +#include +#include + +#include "core/LiteralString.hpp" + +namespace lms::core +{ + class IJob; + class IJobScheduler + { + public: + virtual ~IJobScheduler() = default; + + using ShouldAbortCallback = std::function; + virtual void setShouldAbortCallback(ShouldAbortCallback callback) = 0; + + virtual std::size_t getThreadCount() const = 0; + virtual void scheduleJob(std::unique_ptr job) = 0; + + virtual std::size_t getJobsDoneCount() const = 0; + virtual size_t popJobsDone(std::vector>& jobs, std::size_t maxCount) = 0; + + virtual void waitUntilJobCountAtMost(std::size_t maxOngoingJobs) = 0; + virtual void wait() = 0; + }; + + std::unique_ptr createJobScheduler(core::LiteralString name, std::size_t threadCount); +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/include/core/ILogger.hpp b/src/libs/core/include/core/ILogger.hpp index 8c7ee446..336898e9 100644 --- a/src/libs/core/include/core/ILogger.hpp +++ b/src/libs/core/include/core/ILogger.hpp @@ -101,3 +101,10 @@ namespace lms::core::logging if (auto* logger_{ ::lms::core::Service<::lms::core::logging::ILogger>::get() }; logger_ && logger_->isSeverityActive(::lms::core::logging::Severity::severity)) \ ::lms::core::logging::Log{ *logger_, ::lms::core::logging::Module::module, ::lms::core::logging::Severity::severity }.getOstream() << message; \ } while (0) + +#define LMS_LOG_IF(module, severity, cond, message) \ + do \ + { \ + if (auto* logger_{ ::lms::core::Service<::lms::core::logging::ILogger>::get() }; logger_ && logger_->isSeverityActive(::lms::core::logging::Severity::severity) && cond) \ + ::lms::core::logging::Log{ *logger_, ::lms::core::logging::Module::module, ::lms::core::logging::Severity::severity }.getOstream() << message; \ + } while (0) diff --git a/src/libs/core/include/core/Path.hpp b/src/libs/core/include/core/Path.hpp index 1d9688bd..2d8fece0 100644 --- a/src/libs/core/include/core/Path.hpp +++ b/src/libs/core/include/core/Path.hpp @@ -20,26 +20,16 @@ #pragma once #include -#include #include -#include #include namespace lms::core::pathUtils { - std::uint32_t computeCrc32(const std::filesystem::path& p); - // Make sure the given path is a directory // Create it if needed bool ensureDirectory(const std::filesystem::path& dir); - // Get the last write time since Epoch - Wt::WDateTime getLastWriteTime(const std::filesystem::path& file, std::error_code& ec); - - // returns false if aborted by user - bool exploreFilesRecursive(const std::filesystem::path& directory, std::function cb, const std::filesystem::path* excludeDirFileName = {}); - // Check if file's extension is one of provided extensions bool hasFileAnyExtension(const std::filesystem::path& file, std::span extensions); diff --git a/src/libs/core/test/CMakeLists.txt b/src/libs/core/test/CMakeLists.txt index 3e89617a..82ee61a3 100644 --- a/src/libs/core/test/CMakeLists.txt +++ b/src/libs/core/test/CMakeLists.txt @@ -2,6 +2,7 @@ include(GoogleTest) add_executable(test-core EnumSet.cpp + JobScheduler.cpp LiteralString.cpp PartialDateTime.cpp Path.cpp diff --git a/src/libs/core/test/JobScheduler.cpp b/src/libs/core/test/JobScheduler.cpp new file mode 100644 index 00000000..d1cedaa5 --- /dev/null +++ b/src/libs/core/test/JobScheduler.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2025 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 + +#include + +#include "core/IJob.hpp" +#include "core/IJobScheduler.hpp" + +namespace lms::core +{ + namespace + { + class TestJob : public IJob + { + public: + TestJob(std::atomic& count) + : workCount(count) {} + + private: + LiteralString getName() const override { return "test"; }; + void run() override + { + // Simulate some work + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + ++workCount; + } + + std::atomic& workCount; + }; + + } // namespace + TEST(JobScheduler, basic) + { + std::atomic workCount{ 0 }; + + auto scheduler{ createJobScheduler("TestScheduler", 2) }; + ASSERT_NE(scheduler, nullptr); + + for (int i = 0; i < 10; ++i) + scheduler->scheduleJob(std::make_unique(workCount)); + + // Wait for all jobs to complete + scheduler->wait(); + EXPECT_EQ(workCount.load(), 10); + + std::vector> doneJobs; + scheduler->popJobsDone(doneJobs, 10); + EXPECT_EQ(doneJobs.size(), 10); + } + + TEST(JobScheduler, abort) + { + std::atomic workCount{ 0 }; + + auto scheduler{ createJobScheduler("TestScheduler", 2) }; + ASSERT_NE(scheduler, nullptr); + + scheduler->setShouldAbortCallback([] { return true; }); + + for (int i = 0; i < 10; ++i) + scheduler->scheduleJob(std::make_unique(workCount)); + + // Wait for all jobs to complete + scheduler->wait(); + EXPECT_EQ(workCount.load(), 0); // nothing was done + + std::vector> doneJobs; + scheduler->popJobsDone(doneJobs, 10); + EXPECT_EQ(doneJobs.size(), 0); + } + +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/database/CMakeLists.txt b/src/libs/database/CMakeLists.txt index 29b07d8b..96af316e 100644 --- a/src/libs/database/CMakeLists.txt +++ b/src/libs/database/CMakeLists.txt @@ -1,39 +1,42 @@ add_library(lmsdatabase STATIC - impl/Artist.cpp - impl/ArtistInfo.cpp - impl/Artwork.cpp - impl/AuthToken.cpp - impl/Cluster.cpp + impl/objects/Artist.cpp + impl/objects/ArtistInfo.cpp + impl/objects/Artwork.cpp + impl/objects/AuthToken.cpp + impl/objects/Cluster.cpp + impl/objects/Directory.cpp + impl/objects/Image.cpp + impl/objects/Listen.cpp + impl/objects/MediaLibrary.cpp + impl/objects/PlayListFile.cpp + impl/objects/PlayQueue.cpp + impl/objects/TrackArtistLink.cpp + impl/objects/TrackFeatures.cpp + impl/objects/TrackList.cpp + impl/objects/RatedArtist.cpp + impl/objects/RatedRelease.cpp + impl/objects/RatedTrack.cpp + impl/objects/Release.cpp + impl/objects/ScanSettings.cpp + impl/objects/StarredArtist.cpp + impl/objects/StarredRelease.cpp + impl/objects/StarredTrack.cpp + impl/objects/Track.cpp + impl/objects/TrackBookmark.cpp + impl/objects/TrackEmbeddedImage.cpp + impl/objects/TrackEmbeddedImageLink.cpp + impl/objects/TrackLyrics.cpp + impl/objects/UIState.cpp + impl/objects/User.cpp impl/Db.cpp - impl/Directory.cpp impl/IdType.cpp - impl/Image.cpp - impl/Listen.cpp - impl/MediaLibrary.cpp impl/Migration.cpp - impl/PlayListFile.cpp - impl/PlayQueue.cpp - impl/TrackArtistLink.cpp - impl/TrackFeatures.cpp - impl/TrackList.cpp - impl/RatedArtist.cpp - impl/RatedRelease.cpp - impl/RatedTrack.cpp - impl/Release.cpp - impl/ScanSettings.cpp + impl/Object.cpp + impl/QueryPlanRecorder.cpp impl/Session.cpp - impl/StarredArtist.cpp - impl/StarredRelease.cpp - impl/StarredTrack.cpp impl/SqlQuery.cpp - impl/Track.cpp - impl/TrackBookmark.cpp - impl/TrackEmbeddedImage.cpp - impl/TrackEmbeddedImageLink.cpp - impl/TrackLyrics.cpp + impl/Transaction.cpp impl/Types.cpp - impl/UIState.cpp - impl/User.cpp impl/Utils.cpp ) @@ -46,6 +49,7 @@ target_include_directories(lmsdatabase INTERFACE ) target_include_directories(lmsdatabase PRIVATE + impl include ) diff --git a/src/libs/database/impl/Db.cpp b/src/libs/database/impl/Db.cpp index 0bb7e46a..057974ab 100644 --- a/src/libs/database/impl/Db.cpp +++ b/src/libs/database/impl/Db.cpp @@ -17,8 +17,6 @@ * along with LMS. If not, see . */ -#include "database/Db.hpp" - #include #include @@ -29,7 +27,9 @@ #include "core/ILogger.hpp" #include "core/Service.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" + +#include "Db.hpp" namespace lms::db { @@ -114,7 +114,7 @@ namespace lms::db std::string table; std::string foreignTable; - // see https://www.sqlite.org/pragma.html#pragma_foreign_key_check for exepcted result + // see https://www.sqlite.org/pragma.html#pragma_foreign_key_check for expected result while (statement->nextRow()) { foreignKeyConstraintsPassed = false; @@ -132,8 +132,64 @@ namespace lms::db return foreignKeyConstraintsPassed; } + + std::optional getPageSize(Wt::Dbo::SqlConnection& connection) + { + auto statement = connection.prepareStatement("PRAGMA page_size"); + statement->execute(); + + std::optional res; + while (statement->nextRow()) + { + assert(!res); + int value{}; + if (statement->getResult(0, &value)) + res = value; + break; + } + + return res; + } + + std::optional getCacheSize(Wt::Dbo::SqlConnection& connection) + { + auto statement = connection.prepareStatement("PRAGMA cache_size"); + statement->execute(); + + std::optional res; + while (statement->nextRow()) + { + assert(!res); + int value{}; + if (statement->getResult(0, &value)) + res = value; + break; + } + + return res; + } + + void getCompileOptions(Wt::Dbo::SqlConnection& connection, std::function callback) + { + auto statement = connection.prepareStatement("PRAGMA compile_options"); + statement->execute(); + + std::string res; + while (statement->nextRow()) + { + res.clear(); + + if (statement->getResult(0, &res, static_cast(res.capacity()))) + callback(res); + } + } } // namespace + std::unique_ptr createDb(const std::filesystem::path& dbPath, std::size_t connectionCount) + { + return std::make_unique(dbPath, connectionCount); + } + // Session living class handling the database and the login Db::Db(const std::filesystem::path& dbPath, std::size_t connectionCount) { @@ -152,6 +208,13 @@ namespace lms::db _connectionPool = std::move(connectionPool); + executeSql("PRAGMA temp_store=MEMORY"); + executeSql("PRAGMA cache_size=-8000"); + executeSql("PRAGMA automatic_index=0"); + + logPageSize(); + logCacheSize(); + logCompileOptions(); if (checkType == "quick") { performQuickCheck(); @@ -194,6 +257,34 @@ namespace lms::db return *tlsSession; } + void Db::logPageSize() + { + ScopedConnection connection{ *_connectionPool }; + const std::optional pageSize{ getPageSize(*connection) }; + + if (pageSize) + LMS_LOG(DB, INFO, "Page size set to " << *pageSize); + } + + void Db::logCacheSize() + { + ScopedConnection connection{ *_connectionPool }; + const std::optional cacheSize{ getCacheSize(*connection) }; + + if (cacheSize) + LMS_LOG(DB, INFO, "Cache size set to " << *cacheSize); + } + + void Db::logCompileOptions() + { + ScopedConnection connection{ *_connectionPool }; + + LMS_LOG(DB, INFO, "Sqlite3 compile options:"); + getCompileOptions(*connection, [](std::string_view compileOption) { + LMS_LOG(DB, INFO, compileOption); + }); + } + void Db::performQuickCheck() { ScopedConnection connection{ *_connectionPool }; @@ -201,7 +292,7 @@ namespace lms::db LMS_LOG(DB, INFO, "Performing quick database check..."); // Quick check is just a simple integrity check - bool quickCheckPassed{ checkDbIntegrity(*connection, IntegrityCheckType::Quick, [&](std::string_view error) { + const bool quickCheckPassed{ checkDbIntegrity(*connection, IntegrityCheckType::Quick, [&](std::string_view error) { LMS_LOG(DB, ERROR, "Quick check error: " << error); }) }; @@ -217,7 +308,7 @@ namespace lms::db LMS_LOG(DB, INFO, "Checking database integrity..."); - bool integrityCheckPassed{ checkDbIntegrity(*connection, IntegrityCheckType::Full, [&](std::string_view error) { + const bool integrityCheckPassed{ checkDbIntegrity(*connection, IntegrityCheckType::Full, [&](std::string_view error) { LMS_LOG(DB, ERROR, "Integrity check error: " << error); }) }; @@ -233,7 +324,7 @@ namespace lms::db LMS_LOG(DB, INFO, "Checking foreign key constraints..."); - bool foreignKeyConstraintsPassed{ checkDbForeignKeyConstraints(*connection, [&](std::string_view table, long long rowId, std::string_view referredTable) { + const bool foreignKeyConstraintsPassed{ checkDbForeignKeyConstraints(*connection, [&](std::string_view table, long long rowId, std::string_view referredTable) { LMS_LOG(DB, ERROR, "Foreign key constraint failed in table '" << table << "', rowid = " << rowId << ", referred table = '" << referredTable << "'"); }) }; diff --git a/src/libs/database/include/database/Db.hpp b/src/libs/database/impl/Db.hpp similarity index 89% rename from src/libs/database/include/database/Db.hpp rename to src/libs/database/impl/Db.hpp index 352bdf03..63890021 100644 --- a/src/libs/database/include/database/Db.hpp +++ b/src/libs/database/impl/Db.hpp @@ -19,21 +19,22 @@ #pragma once -#include +#include +#include +#include #include #include "core/RecursiveSharedMutex.hpp" +#include "database/IDb.hpp" + namespace lms::db { - class Session; - class Db + class Db : public IDb { public: - Db(const std::filesystem::path& dbPath, std::size_t connectionCount = 10); - - Session& getTLSSession(); + Db(const std::filesystem::path& dbPath, std::size_t connectionCount); void executeSql(const std::string& sql); @@ -43,9 +44,14 @@ namespace lms::db friend class Session; + Session& getTLSSession() override; + core::RecursiveSharedMutex& getMutex() { return _sharedMutex; } Wt::Dbo::SqlConnectionPool& getConnectionPool() { return *_connectionPool; } + void logPageSize(); + void logCacheSize(); + void logCompileOptions(); void performQuickCheck(); void performIntegrityCheck(); void performForeignKeyConstraintsCheck(); diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp index 31532b5f..c6e1e1b6 100644 --- a/src/libs/database/impl/Migration.cpp +++ b/src/libs/database/impl/Migration.cpp @@ -24,18 +24,17 @@ #include "core/Exception.hpp" #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" -#include "database/Db.hpp" -#include "database/ScanSettings.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/ScanSettings.hpp" +#include "Db.hpp" #include "Utils.hpp" namespace lms::db { namespace { - static constexpr Version LMS_DATABASE_VERSION{ 94 }; + static constexpr Version LMS_DATABASE_VERSION{ 98 }; } VersionInfo::VersionInfo() @@ -1386,11 +1385,49 @@ FROM artist)"); utils::executeCommand(*session.getDboSession(), "ALTER TABLE scan_settings ADD COLUMN artist_image_fallback_to_release BOOLEAN NOT NULL DEFAULT(false)"); } + void migrateFromV94(Session& session) + { + // Removed not that useful columns in track + dropIndexes(session); + + utils::executeCommand(*session.getDboSession(), "ALTER TABLE track DROP COLUMN relative_file_path"); + utils::executeCommand(*session.getDboSession(), "ALTER TABLE track DROP COLUMN file_stem"); + utils::executeCommand(*session.getDboSession(), "ALTER TABLE track DROP COLUMN file_name"); + } + + void migrateFromV95(Session& session) + { + // Make sure each image and each embessed image has an artwork object + utils::executeCommand(*session.getDboSession(), R"(INSERT INTO artwork (version, track_embedded_image_id) +SELECT 1 AS version, tei.id +FROM track_embedded_image tei +LEFT JOIN artwork art ON tei.id = art.track_embedded_image_id +WHERE art.track_embedded_image_id IS NULL)"); + + utils::executeCommand(*session.getDboSession(), R"(INSERT INTO artwork (version, image_id) +SELECT 1 AS version, img.id +FROM image img +LEFT JOIN artwork art ON img.id = art.image_id +WHERE art.image_id IS NULL)"); + } + + void migrateFromV96(Session& session) + { + // Removed not that useful indexes + dropIndexes(session); + } + + void migrateFromV97(Session& session) + { + // Removed not that useful indexes + dropIndexes(session); + } + bool doDbMigration(Session& session) { constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" }; - ScopedNoForeignKeys noPragmaKeys{ session.getDb() }; + ScopedNoForeignKeys noPragmaKeys{ static_cast(session.getDb()) }; using MigrationFunction = std::function; @@ -1456,6 +1493,10 @@ FROM artist)"); { 91, migrateFromV91 }, { 92, migrateFromV92 }, { 93, migrateFromV93 }, + { 94, migrateFromV94 }, + { 95, migrateFromV95 }, + { 96, migrateFromV96 }, + { 97, migrateFromV97 }, }; bool migrationPerformed{}; diff --git a/src/libs/database/impl/Object.cpp b/src/libs/database/impl/Object.cpp new file mode 100644 index 00000000..18e56f77 --- /dev/null +++ b/src/libs/database/impl/Object.cpp @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2025 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/Object.hpp" + +#include "TransactionChecker.hpp" + +namespace lms::db +{ + void ObjectPtrBase::checkWriteTransaction(Wt::Dbo::Session& session) + { +#if LMS_CHECK_TRANSACTION_ACCESSES + TransactionChecker::checkWriteTransaction(session); +#endif + } +} // namespace lms::db diff --git a/src/libs/database/impl/QueryPlanRecorder.cpp b/src/libs/database/impl/QueryPlanRecorder.cpp new file mode 100644 index 00000000..fef01d1c --- /dev/null +++ b/src/libs/database/impl/QueryPlanRecorder.cpp @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2025 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 "QueryPlanRecorder.hpp" + +#include +#include + +#include +#include + +#include "core/ILogger.hpp" + +namespace lms::db +{ + std::unique_ptr createQueryPlanRecorder() + { + return std::make_unique(); + } + + QueryPlanRecorder::QueryPlanRecorder() + { + LMS_LOG(DB, INFO, "Recording database query plans"); + } + + QueryPlanRecorder::~QueryPlanRecorder() = default; + + void QueryPlanRecorder::visitQueryPlans(const QueryPlanVisitor& visitor) const + { + const std::shared_lock lock{ _mutex }; + + for (const auto& [query, plan] : _queryPlans) + visitor(query, plan); + } + + void QueryPlanRecorder::recordQueryPlanIfNeeded(Wt::Dbo::Session& session, const std::string& query) + { + { + const std::shared_lock lock{ _mutex }; + + if (_queryPlans.contains(query)) + return; + } + + Wt::Dbo::Transaction transaction{ session }; + + Wt::Dbo::SqlConnection* connection{ transaction.connection() }; + auto statement{ connection->prepareStatement("EXPLAIN QUERY PLAN " + query) }; + statement->execute(); + + std::map entries{ { 0, "" } }; + std::map> relationships; + + std::string detail; + while (statement->nextRow()) + { + detail.clear(); + + int id{}; + int parent{}; + int unused{}; + + if (statement->getResult(0, &id) + && statement->getResult(1, &parent) + && statement->getResult(2, &unused) + && statement->getResult(3, &detail, static_cast(detail.capacity()))) + { + entries.emplace(id, detail); + relationships[parent].push_back(id); + } + } + + // format + std::string result; + std::function formatQuery = [&](int id, unsigned level) -> void { + for (std::size_t i{}; i < level; ++i) + result += '\t'; + + result += entries.at(id); + result += '\n'; + auto itChildren = relationships.find(id); + if (itChildren == relationships.end()) + return; + + for (int child : itChildren->second) + formatQuery(child, level + 1); + }; + + formatQuery(0, 0); + + { + const std::unique_lock lock{ _mutex }; + _queryPlans.try_emplace(query, std::move(result)); + } + } +} // namespace lms::db diff --git a/src/libs/database/impl/QueryPlanRecorder.hpp b/src/libs/database/impl/QueryPlanRecorder.hpp new file mode 100644 index 00000000..6025b7a1 --- /dev/null +++ b/src/libs/database/impl/QueryPlanRecorder.hpp @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2025 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 +#include +#include + +#include + +#include "database/IQueryPlanRecorder.hpp" + +namespace lms::db +{ + class QueryPlanRecorder : public IQueryPlanRecorder + { + public: + QueryPlanRecorder(); + ~QueryPlanRecorder() override; + QueryPlanRecorder(const QueryPlanRecorder&) = delete; + QueryPlanRecorder& operator=(const QueryPlanRecorder&) = delete; + + void visitQueryPlans(const QueryPlanVisitor& visitor) const override; + + void recordQueryPlanIfNeeded(Wt::Dbo::Session& session, const std::string& query); + + private: + mutable std::shared_mutex _mutex; + std::map _queryPlans; + }; +} // namespace lms::db diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index deac39c9..b01e7268 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -19,41 +19,44 @@ #include "database/Session.hpp" +#include + #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" -#include "database/Artist.hpp" -#include "database/ArtistInfo.hpp" -#include "database/Artwork.hpp" -#include "database/AuthToken.hpp" -#include "database/Cluster.hpp" -#include "database/Db.hpp" -#include "database/Directory.hpp" -#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" -#include "database/RatedTrack.hpp" -#include "database/Release.hpp" -#include "database/ScanSettings.hpp" -#include "database/StarredArtist.hpp" -#include "database/StarredRelease.hpp" -#include "database/StarredTrack.hpp" -#include "database/Track.hpp" -#include "database/TrackArtistLink.hpp" -#include "database/TrackBookmark.hpp" -#include "database/TrackEmbeddedImage.hpp" -#include "database/TrackEmbeddedImageLink.hpp" -#include "database/TrackFeatures.hpp" -#include "database/TrackList.hpp" -#include "database/TrackLyrics.hpp" -#include "database/TransactionChecker.hpp" -#include "database/UIState.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/ArtistInfo.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/AuthToken.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/Image.hpp" +#include "database/objects/Listen.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/PlayListFile.hpp" +#include "database/objects/PlayQueue.hpp" +#include "database/objects/RatedArtist.hpp" +#include "database/objects/RatedRelease.hpp" +#include "database/objects/RatedTrack.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/ScanSettings.hpp" +#include "database/objects/StarredArtist.hpp" +#include "database/objects/StarredRelease.hpp" +#include "database/objects/StarredTrack.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/TrackBookmark.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" +#include "database/objects/TrackEmbeddedImageLink.hpp" +#include "database/objects/TrackFeatures.hpp" +#include "database/objects/TrackList.hpp" +#include "database/objects/TrackLyrics.hpp" +#include "database/objects/UIState.hpp" +#include "database/objects/User.hpp" + +#include "Db.hpp" #include "Migration.hpp" +#include "TransactionChecker.hpp" #include "Utils.hpp" #include "traits/EnumSetTraits.hpp" #include "traits/ImageHashTypeTraits.hpp" @@ -62,44 +65,10 @@ namespace lms::db { - WriteTransaction::WriteTransaction(core::RecursiveSharedMutex& mutex, Wt::Dbo::Session& session) - : _lock{ mutex } - , _transaction{ session } - { -#if LMS_CHECK_TRANSACTION_ACCESSES - TransactionChecker::pushWriteTransaction(_transaction.session()); -#endif - } - - WriteTransaction::~WriteTransaction() - { -#if LMS_CHECK_TRANSACTION_ACCESSES - TransactionChecker::popWriteTransaction(_transaction.session()); -#endif - - core::tracing::ScopedTrace _trace{ "Database", core::tracing::Level::Detailed, "Commit" }; - _transaction.commit(); - } - - ReadTransaction::ReadTransaction(Wt::Dbo::Session& session) - : _transaction{ session } - { -#if LMS_CHECK_TRANSACTION_ACCESSES - TransactionChecker::pushReadTransaction(_transaction.session()); -#endif - } - - ReadTransaction::~ReadTransaction() - { -#if LMS_CHECK_TRANSACTION_ACCESSES - TransactionChecker::popReadTransaction(_transaction.session()); -#endif - } - - Session::Session(Db& db) + Session::Session(IDb& db) : _db{ db } { - _session.setConnectionPool(_db.getConnectionPool()); + _session.setConnectionPool(static_cast(_db).getConnectionPool()); _session.mapClass("artist"); _session.mapClass("artist_info"); @@ -140,7 +109,7 @@ namespace lms::db WriteTransaction Session::createWriteTransaction() { - return WriteTransaction{ _db.getMutex(), _session }; + return WriteTransaction{ static_cast(_db).getMutex(), _session }; } ReadTransaction Session::createReadTransaction() @@ -148,6 +117,19 @@ namespace lms::db return ReadTransaction{ _session }; } + void Session::checkWriteTransaction() const + { +#if LMS_CHECK_TRANSACTION_ACCESSES + TransactionChecker::checkWriteTransaction(_session); +#endif + } + void Session::checkReadTransaction() const + { +#if LMS_CHECK_TRANSACTION_ACCESSES + TransactionChecker::checkReadTransaction(_session); +#endif + } + void Session::execute(std::string_view statement) { utils::executeCommand(_session, std::string{ statement }); @@ -234,7 +216,7 @@ namespace lms::db utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_id_idx ON track_embedded_image_link(id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_track_id_idx ON track_embedded_image_link(track_id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_track_embedded_image_id_track_id_idx ON track_embedded_image_link(track_embedded_image_id, track_id)"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_type_track_embedded_image_id_track_id_idx ON track_embedded_image_link(type,track_embedded_image_id,track_id)"); + utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_embedded_image_link_track_track_embedded_image_id_idx ON track_embedded_image_link(track_id, track_embedded_image_id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_directory_stem_idx ON image(directory_id, stem COLLATE NOCASE)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS image_id_idx ON image(id)"); @@ -254,6 +236,7 @@ namespace lms::db utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS media_library_id_idx ON media_library(id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS playlist_file_id_idx ON playlist_file(id)"); + utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS playlist_file_directory_idx ON playlist_file(directory_id);"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS playlist_file_absolute_file_path_idx ON playlist_file(absolute_file_path)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS rated_artist_user_artist_idx ON rated_artist(user_id,artist_id)"); @@ -275,27 +258,19 @@ namespace lms::db utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_absolute_path_idx ON track(absolute_file_path)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_date_idx ON track(date)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_directory_release_idx ON track(directory_id, release_id);"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_directory_file_stem_idx ON track(directory_id, file_stem);"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_file_added_idx ON track(file_added)"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_file_added_desc_idx ON track(file_added DESC)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_file_last_write_idx ON track(file_last_write)"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_file_last_write_desc_idx ON track(file_last_write DESC)"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_file_name_idx ON track(file_name COLLATE NOCASE)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_media_library_idx ON track(media_library_id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_media_library_release_idx ON track(media_library_id, release_id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_mbid_idx ON track(mbid)"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_name_idx ON track(name)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_name_file_size_idx ON track(name, file_size)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_name_nocase_idx ON track(name COLLATE NOCASE)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_original_date_idx ON track(original_date)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_recording_mbid_idx ON track(recording_mbid)"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_idx ON track(release_id)"); + utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_disc_idx ON track(release_id, disc_number)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_date_idx ON track(release_id, date)"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_date_desc_idx ON track(release_id, date DESC)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_file_last_write_idx ON track(release_id, file_last_write)"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_file_last_write_desc_idx ON track(release_id, file_last_write DESC)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_file_added_idx ON track(release_id, file_added)"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_release_file_added_desc_idx ON track(release_id, file_added DESC)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS tracklist_id_idx ON tracklist(id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS tracklist_name_idx ON tracklist(name)"); @@ -309,15 +284,15 @@ namespace lms::db utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_idx ON track_artist_link(artist_id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_mbid_matched_artist_idx ON track_artist_link(artist_mbid_matched, artist_id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_track_idx ON track_artist_link(artist_id, track_id)"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_type_idx ON track_artist_link(artist_id, type)"); + utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_artist_type_track_idx ON track_artist_link(artist_id, type, track_id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_track_artist_idx ON track_artist_link(track_id, artist_id)"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_track_type_idx ON track_artist_link(track_id,type)"); - utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_type_track_artist_idx ON track_artist_link(type, track_id, artist_id)"); + utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_artist_link_track_type_idx ON track_artist_link(track_id, type)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_features_track_idx ON track_features(track_id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_lyrics_id_idx ON track_lyrics(id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_lyrics_absolute_file_path_idx ON track_lyrics(absolute_file_path)"); + utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_lyrics_directory_idx ON track_lyrics(directory_id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_lyrics_track_idx ON track_lyrics(track_id)"); utils::executeCommand(_session, "CREATE INDEX IF NOT EXISTS track_bookmark_user_idx ON track_bookmark(user_id)"); @@ -359,33 +334,13 @@ namespace lms::db // We manually take a lock here since vacuum cannot be inside a transaction { - std::unique_lock lock{ _db.getMutex() }; - _db.executeSql("VACUUM"); + std::unique_lock lock{ static_cast(_db).getMutex() }; + static_cast(_db).executeSql("VACUUM"); } LMS_LOG(DB, INFO, "Vacuum complete!"); } - void Session::refreshTracingLoggerStats() - { - auto* traceLogger{ core::Service::get() }; - if (!traceLogger) - return; - - auto transaction{ createReadTransaction() }; - - traceLogger->setMetadata("db_artist_count", std::to_string(db::Artist::getCount(*this))); - traceLogger->setMetadata("db_cluster_count", std::to_string(db::Cluster::getCount(*this))); - traceLogger->setMetadata("db_cluster_type_count", std::to_string(db::ClusterType::getCount(*this))); - traceLogger->setMetadata("db_starred_artist_count", std::to_string(db::StarredArtist::getCount(*this))); - traceLogger->setMetadata("db_starred_release_count", std::to_string(db::StarredRelease::getCount(*this))); - traceLogger->setMetadata("db_starred_track_count", std::to_string(db::StarredTrack::getCount(*this))); - traceLogger->setMetadata("db_track_bookmark_count", std::to_string(db::TrackBookmark::getCount(*this))); - traceLogger->setMetadata("db_listen_count", std::to_string(db::Listen::getCount(*this))); - traceLogger->setMetadata("db_release_count", std::to_string(db::Release::getCount(*this))); - traceLogger->setMetadata("db_track_count", std::to_string(db::Track::getCount(*this))); - } - void Session::fullAnalyze() { LMS_SCOPED_TRACE_OVERVIEW("Database", "Analyze"); @@ -403,6 +358,8 @@ namespace lms::db bool Session::areAllTablesEmpty() { + checkReadTransaction(); + const std::vector entryList{ utils::fetchQueryResults(_session.query("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")) }; return std::all_of(entryList.cbegin(), entryList.cend(), [this](const std::string& entry) { @@ -411,6 +368,19 @@ namespace lms::db }); } + FileStats Session::getFileStats() + { + FileStats stats{}; + + stats.trackCount = db::Track::getCount(*this); + stats.artistInfoCount = db::ArtistInfo::getCount(*this); + stats.imageCount = db::Image::getCount(*this); + stats.trackLyricsCount = db::TrackLyrics::getExternalLyricsCount(*this); + stats.playListCount = db::PlayListFile::getCount(*this); + + return stats; + } + void Session::retrieveEntriesToAnalyze(std::vector& entryList) { auto transaction{ createReadTransaction() }; @@ -426,4 +396,10 @@ namespace lms::db } LMS_LOG(DB, DEBUG, "Analyzing " << entry << ": done!"); } + + void Session::execute(std::string_view query, long long id) + { + utils::executeCommand(_session, query, id); + } + } // namespace lms::db diff --git a/src/libs/database/impl/Transaction.cpp b/src/libs/database/impl/Transaction.cpp new file mode 100644 index 00000000..af6b5373 --- /dev/null +++ b/src/libs/database/impl/Transaction.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2025 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/Transaction.hpp" + +#include "core/RecursiveSharedMutex.hpp" + +#include "TransactionChecker.hpp" + +namespace lms::db +{ + WriteTransaction::WriteTransaction(core::RecursiveSharedMutex& mutex, Wt::Dbo::Session& session) + : _lock{ mutex } + , _trace{ "Database", core::tracing::Level::Detailed, "WriteTransaction" } + , _transaction{ session } + { +#if LMS_CHECK_TRANSACTION_ACCESSES + TransactionChecker::pushWriteTransaction(_transaction.session()); +#endif + } + + WriteTransaction::~WriteTransaction() + { +#if LMS_CHECK_TRANSACTION_ACCESSES + TransactionChecker::popWriteTransaction(_transaction.session()); +#endif + + core::tracing::ScopedTrace _trace{ "Database", core::tracing::Level::Detailed, "Commit" }; + _transaction.commit(); + } + + ReadTransaction::ReadTransaction(Wt::Dbo::Session& session) + : _trace{ "Database", core::tracing::Level::Detailed, "ReadTransaction" } + , _transaction{ session } + { +#if LMS_CHECK_TRANSACTION_ACCESSES + TransactionChecker::pushReadTransaction(_transaction.session()); +#endif + } + + ReadTransaction::~ReadTransaction() + { +#if LMS_CHECK_TRANSACTION_ACCESSES + TransactionChecker::popReadTransaction(_transaction.session()); +#endif + } +} // namespace lms::db diff --git a/src/libs/database/impl/TransactionChecker.cpp b/src/libs/database/impl/TransactionChecker.cpp index 003e84bf..89d1f45c 100644 --- a/src/libs/database/impl/TransactionChecker.cpp +++ b/src/libs/database/impl/TransactionChecker.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "database/TransactionChecker.hpp" +#include "TransactionChecker.hpp" static_assert(LMS_CHECK_TRANSACTION_ACCESSES, "File should be excluded from build"); @@ -32,39 +32,39 @@ namespace lms::db struct StackEntry { TransactionChecker::TransactionType type; - Wt::Dbo::Session* session{}; + const Wt::Dbo::Session* session{}; }; static thread_local std::vector transactionStack; } // namespace - void TransactionChecker::pushWriteTransaction(Wt::Dbo::Session& session) + void TransactionChecker::pushWriteTransaction(const Wt::Dbo::Session& session) { pushTransaction(TransactionType::Write, session); } - void TransactionChecker::pushReadTransaction(Wt::Dbo::Session& session) + void TransactionChecker::pushReadTransaction(const Wt::Dbo::Session& session) { pushTransaction(TransactionType::Read, session); } - void TransactionChecker::popWriteTransaction(Wt::Dbo::Session& session) + void TransactionChecker::popWriteTransaction(const Wt::Dbo::Session& session) { popTransaction(TransactionType::Write, session); } - void TransactionChecker::popReadTransaction(Wt::Dbo::Session& session) + void TransactionChecker::popReadTransaction(const Wt::Dbo::Session& session) { popTransaction(TransactionType::Read, session); } - void TransactionChecker::pushTransaction(TransactionType type, Wt::Dbo::Session& session) + void TransactionChecker::pushTransaction(TransactionType type, const Wt::Dbo::Session& session) { assert(transactionStack.empty() || transactionStack.back().session == &session); transactionStack.push_back(StackEntry{ type, &session }); } - void TransactionChecker::popTransaction(TransactionType type, Wt::Dbo::Session& session) + void TransactionChecker::popTransaction(TransactionType type, const Wt::Dbo::Session& session) { assert(!transactionStack.empty()); assert(transactionStack.back().type == type); @@ -72,25 +72,25 @@ namespace lms::db transactionStack.pop_back(); } - void TransactionChecker::checkWriteTransaction(Wt::Dbo::Session& session) + void TransactionChecker::checkWriteTransaction(const Wt::Dbo::Session& session) { assert(!transactionStack.empty()); assert(transactionStack.back().type == TransactionType::Write); assert(transactionStack.back().session == &session); } - void TransactionChecker::checkWriteTransaction(Session& session) + void TransactionChecker::checkWriteTransaction(const Session& session) { checkWriteTransaction(*session.getDboSession()); } - void TransactionChecker::checkReadTransaction(Wt::Dbo::Session& session) + void TransactionChecker::checkReadTransaction(const Wt::Dbo::Session& session) { assert(!transactionStack.empty()); assert(transactionStack.back().session == &session); } - void TransactionChecker::checkReadTransaction(Session& session) + void TransactionChecker::checkReadTransaction(const Session& session) { checkReadTransaction(*session.getDboSession()); } diff --git a/src/libs/database/include/database/TransactionChecker.hpp b/src/libs/database/impl/TransactionChecker.hpp similarity index 58% rename from src/libs/database/include/database/TransactionChecker.hpp rename to src/libs/database/impl/TransactionChecker.hpp index fffeedb7..1076f33d 100644 --- a/src/libs/database/include/database/TransactionChecker.hpp +++ b/src/libs/database/impl/TransactionChecker.hpp @@ -26,8 +26,11 @@ #endif #if LMS_CHECK_TRANSACTION_ACCESSES - #include - #include + +namespace Wt::Dbo +{ + class Session; +} namespace lms::db { @@ -42,20 +45,20 @@ namespace lms::db Write, }; - static void pushWriteTransaction(Wt::Dbo::Session& session); - static void pushReadTransaction(Wt::Dbo::Session& session); + static void pushWriteTransaction(const Wt::Dbo::Session& session); + static void pushReadTransaction(const Wt::Dbo::Session& session); - static void popWriteTransaction(Wt::Dbo::Session& session); - static void popReadTransaction(Wt::Dbo::Session& session); + static void popWriteTransaction(const Wt::Dbo::Session& session); + static void popReadTransaction(const Wt::Dbo::Session& session); - static void checkWriteTransaction(Wt::Dbo::Session& session); - static void checkWriteTransaction(Session& session); - static void checkReadTransaction(Wt::Dbo::Session& session); - static void checkReadTransaction(Session& session); + static void checkWriteTransaction(const Wt::Dbo::Session& session); + static void checkWriteTransaction(const Session& session); + static void checkReadTransaction(const Wt::Dbo::Session& session); + static void checkReadTransaction(const Session& session); private: - static void pushTransaction(TransactionType type, Wt::Dbo::Session& session); - static void popTransaction(TransactionType type, Wt::Dbo::Session& session); + static void pushTransaction(TransactionType type, const Wt::Dbo::Session& session); + static void popTransaction(TransactionType type, const Wt::Dbo::Session& session); }; } // namespace lms::db diff --git a/src/libs/database/impl/Utils.hpp b/src/libs/database/impl/Utils.hpp index 59e83a81..bb377756 100644 --- a/src/libs/database/impl/Utils.hpp +++ b/src/libs/database/impl/Utils.hpp @@ -19,29 +19,47 @@ #pragma once -#include #include #include -#include +#include +#include +#include +#include #include #include "core/ITraceLogger.hpp" +#include "core/Service.hpp" #include "database/Types.hpp" +#include "QueryPlanRecorder.hpp" + namespace lms::db::utils { #define ESCAPE_CHAR_STR "\\" static inline constexpr char escapeChar{ '\\' }; std::string escapeLikeKeyword(std::string_view keywords); + Wt::WDateTime normalizeDateTime(const Wt::WDateTime& dateTime); + + namespace details + { + template + void recordQueryPlanIfNeeded(const Query& query) + { + if (IQueryPlanRecorder * recorder{ core::Service::get() }) + static_cast(recorder)->recordQueryPlanIfNeeded(query.session(), query.asString()); + } + } // namespace details + template void applyRange(Query& query, std::optional range) { if (range) { query.limit(static_cast(range->size)); - query.offset(static_cast(range->offset)); + if (range->offset != 0) + query.offset(static_cast(range->offset)); } } @@ -82,13 +100,18 @@ namespace lms::db::utils template void forEachQueryResult(const Query& query, UnaryFunc&& func) { + details::recordQueryPlanIfNeeded(query); + LMS_SCOPED_TRACE_DETAILED_WITH_ARG("Database", "ForEachQueryResult", "Query", query.asString()); + forEachResult(query.resultList(), std::forward(func)); } template std::vector fetchQueryResults(const Query& query) { + details::recordQueryPlanIfNeeded(query); + LMS_SCOPED_TRACE_DETAILED_WITH_ARG("Database", "FetchQueryResults", "Query", query.asString()); auto collection{ query.resultList() }; @@ -98,6 +121,8 @@ namespace lms::db::utils template std::vector::type> fetchQueryResults(const Query& query) { + details::recordQueryPlanIfNeeded(query); + LMS_SCOPED_TRACE_DETAILED_WITH_ARG("Database", "FetchQueryResults", "Query", query.asString()); auto collection{ query.resultList() }; @@ -107,6 +132,8 @@ namespace lms::db::utils template auto fetchQuerySingleResult(const Query& query) { + details::recordQueryPlanIfNeeded(query); + LMS_SCOPED_TRACE_DETAILED_WITH_ARG("Database", "FetchQuerySingleResult", "Query", query.asString()); return query.resultValue(); } @@ -175,11 +202,12 @@ namespace lms::db::utils template void executeCommand(Wt::Dbo::Session& session, std::string_view command, const Args&... args) { - LMS_SCOPED_TRACE_DETAILED_WITH_ARG("Database", "ExecuteCommand", "Command", command); - Wt::Dbo::Call call{ session.execute(std::string{ command }) }; (call.bind(args), ...); - } - Wt::WDateTime normalizeDateTime(const Wt::WDateTime& dateTime); + { + LMS_SCOPED_TRACE_DETAILED_WITH_ARG("Database", "ExecuteCommand", "Command", command); + call.run(); + } + } } // namespace lms::db::utils \ No newline at end of file diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/objects/Artist.cpp similarity index 86% rename from src/libs/database/impl/Artist.cpp rename to src/libs/database/impl/objects/Artist.cpp index 57ff3c50..436f6735 100644 --- a/src/libs/database/impl/Artist.cpp +++ b/src/libs/database/impl/objects/Artist.cpp @@ -16,22 +16,28 @@ * You should have received a copy of the GNU General Public License * along with LMS. If not, see . */ -#include "database/Artist.hpp" +#include "database/objects/Artist.hpp" +#include #include #include "core/ILogger.hpp" -#include "database/Artwork.hpp" -#include "database/Cluster.hpp" -#include "database/Directory.hpp" -#include "database/Release.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/StarredArtist.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/User.hpp" #include "SqlQuery.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" +#include "traits/StringViewTraits.hpp" + +DBO_INSTANTIATE_TEMPLATES(lms::db::Artist) namespace lms::db { @@ -237,11 +243,25 @@ namespace lms::db }); } + void Artist::find(Session& session, const IdRange& idRange, const std::function& func) + { + assert(idRange.isValid()); + + auto query{ session.getDboSession()->query>("SELECT a from artist a").orderBy("a.id").where("a.id BETWEEN ? AND ?").bind(idRange.first).bind(idRange.last) }; + + utils::forEachQueryResult(query, [&](const Artist::pointer& artist) { + func(artist); + }); + } + std::vector Artist::find(Session& session, std::string_view name) { session.checkReadTransaction(); - return utils::fetchQueryResults(session.getDboSession()->query>("SELECT a FROM artist a").where("a.name = ?").bind(std::string{ name, 0, _maxNameLength }).orderBy("LENGTH(a.mbid) DESC")); // put mbid entries first + if (name.size() > maxNameLength) + name = name.substr(0, maxNameLength); + + return utils::fetchQueryResults(session.getDboSession()->query>("SELECT a FROM artist a").where("a.name = ?").bind(name).orderBy("LENGTH(a.mbid) DESC")); // put mbid entries first } Artist::pointer Artist::find(Session& session, const core::UUID& mbid) @@ -280,6 +300,16 @@ namespace lms::db utils::forEachQueryRangeResult(query, params.range, func); } + IdRange Artist::findNextIdRange(Session& session, ArtistId lastRetrievedId, std::size_t count) + { + auto query{ session.getDboSession()->query>("SELECT MIN(sub.id) AS first_id, MAX(sub.id) AS last_id FROM (SELECT a.id FROM artist a WHERE a.id > ? ORDER BY a.id LIMIT ?) sub") }; + query.bind(lastRetrievedId); + query.bind(static_cast(count)); + + auto res{ utils::fetchQuerySingleResult(query) }; + return IdRange{ .first = std::get<0>(res), .last = std::get<1>(res) }; + } + RangeResults Artist::findOrphanIds(Session& session, std::optional range) { session.checkReadTransaction(); @@ -304,6 +334,16 @@ AND NOT EXISTS ( return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT 1 FROM artist").where("id = ?").bind(id)) == 1; } + void Artist::updatePreferredArtwork(Session& session, ArtistId artistId, ArtworkId artworkId) + { + session.checkWriteTransaction(); + + if (artworkId.isValid()) + utils::executeCommand(*session.getDboSession(), "UPDATE artist SET preferred_artwork_id = ? WHERE id = ?", artworkId, artistId); + else + utils::executeCommand(*session.getDboSession(), "UPDATE artist SET preferred_artwork_id = NULL WHERE id = ?", artistId); + } + std::optional Artist::getMBID() const { return core::UUID::fromString(_mbid); @@ -408,17 +448,14 @@ AND NOT EXISTS ( void Artist::setName(std::string_view name) { - _name.assign(name, 0, _maxNameLength); - if (name.size() > _maxNameLength) - LMS_LOG(DB, WARNING, "Artist name too long, truncated to '" << _name << "'"); + _name.assign(name, 0, maxNameLength); + LMS_LOG_IF(DB, WARNING, name.size() > maxNameLength, "Artist name too long, truncated to '" << _name << "'"); } void Artist::setSortName(std::string_view sortName) { - _sortName.assign(sortName, 0, _maxNameLength); - - if (sortName.size() > _maxNameLength) - LMS_LOG(DB, WARNING, "Artist sort name too long, truncated to '" << _sortName << "'"); + _sortName.assign(sortName, 0, maxNameLength); + LMS_LOG_IF(DB, WARNING, sortName.size() > maxNameLength, "Artist sort name too long, truncated to '" << _sortName << "'"); } void Artist::setPreferredArtwork(ObjectPtr artwork) diff --git a/src/libs/database/impl/ArtistInfo.cpp b/src/libs/database/impl/objects/ArtistInfo.cpp similarity index 84% rename from src/libs/database/impl/ArtistInfo.cpp rename to src/libs/database/impl/objects/ArtistInfo.cpp index 9ae1a6cd..b7acd5d4 100644 --- a/src/libs/database/impl/ArtistInfo.cpp +++ b/src/libs/database/impl/objects/ArtistInfo.cpp @@ -17,19 +17,22 @@ * along with LMS. If not, see . */ -#include "database/ArtistInfo.hpp" +#include "database/objects/ArtistInfo.hpp" +#include #include -#include "database/Artist.hpp" -#include "database/ArtistInfoId.hpp" -#include "database/Directory.hpp" #include "database/Session.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/ArtistInfoId.hpp" +#include "database/objects/Directory.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/PathTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::ArtistInfo) + namespace lms::db { ArtistInfo::pointer ArtistInfo::create(Session& session) @@ -124,6 +127,18 @@ namespace lms::db }); } + void ArtistInfo::findAbsoluteFilePath(Session& session, ArtistInfoId& lastRetrievedId, std::size_t count, const std::function& func) + { + session.checkReadTransaction(); + + auto query{ session.getDboSession()->query>("SELECT a_i.id, a_i.absolute_file_path FROM artist_info a_i").orderBy("a_i.id").where("a_i.id > ?").bind(lastRetrievedId).limit(static_cast(count)) }; + + utils::forEachQueryResult(query, [&](const auto& res) { + func(std::get<0>(res), std::get<1>(res)); + lastRetrievedId = std::get<0>(res); + }); + } + Artist::pointer ArtistInfo::getArtist() const { return _artist; diff --git a/src/libs/database/impl/Artwork.cpp b/src/libs/database/impl/objects/Artwork.cpp similarity index 76% rename from src/libs/database/impl/Artwork.cpp rename to src/libs/database/impl/objects/Artwork.cpp index e9264f06..e91b7d84 100644 --- a/src/libs/database/impl/Artwork.cpp +++ b/src/libs/database/impl/objects/Artwork.cpp @@ -17,16 +17,20 @@ * along with LMS. If not, see . */ -#include "database/Artwork.hpp" +#include "database/objects/Artwork.hpp" +#include #include -#include "database/Image.hpp" #include "database/Session.hpp" -#include "database/TrackEmbeddedImage.hpp" +#include "database/objects/Image.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" +#include "traits/PathTraits.hpp" + +DBO_INSTANTIATE_TEMPLATES(lms::db::Artwork) namespace lms::db { @@ -87,4 +91,17 @@ namespace lms::db return utils::fetchQuerySingleResult(query); } + + std::filesystem::path Artwork::getAbsoluteFilePath() const + { + auto query{ session()->query("SELECT COALESCE(image.absolute_file_path, track.absolute_file_path) AS absolute_file_path FROM artwork") }; + query.leftJoin("image ON artwork.image_id = image.id"); + query.leftJoin("track_embedded_image ON artwork.track_embedded_image_id = track_embedded_image.id"); + query.leftJoin("track_embedded_image_link ON track_embedded_image.id = track_embedded_image_link.track_embedded_image_id"); + query.leftJoin("track ON track.id = track_embedded_image_link.track_id"); + query.limit(1); // there may be several tracks matching this artwork... + query.where("artwork.id = ?").bind(getId()); + + return utils::fetchQuerySingleResult(query); + } } // namespace lms::db diff --git a/src/libs/database/impl/AuthToken.cpp b/src/libs/database/impl/objects/AuthToken.cpp similarity index 95% rename from src/libs/database/impl/AuthToken.cpp rename to src/libs/database/impl/objects/AuthToken.cpp index 8622975f..4f32c34b 100644 --- a/src/libs/database/impl/AuthToken.cpp +++ b/src/libs/database/impl/objects/AuthToken.cpp @@ -17,17 +17,20 @@ * along with LMS. If not, see . */ -#include "database/AuthToken.hpp" +#include "database/objects/AuthToken.hpp" +#include #include #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/StringViewTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::AuthToken) + namespace lms::db { AuthToken::AuthToken(std::string_view domain, std::string_view value, const Wt::WDateTime& expiry, std::optional maxUseCount, ObjectPtr user) diff --git a/src/libs/database/impl/Cluster.cpp b/src/libs/database/impl/objects/Cluster.cpp similarity index 93% rename from src/libs/database/impl/Cluster.cpp rename to src/libs/database/impl/objects/Cluster.cpp index 720e040e..1ce9a7ee 100644 --- a/src/libs/database/impl/Cluster.cpp +++ b/src/libs/database/impl/objects/Cluster.cpp @@ -17,22 +17,29 @@ * along with LMS. If not, see . */ -#include "database/Cluster.hpp" +#include "database/objects/Cluster.hpp" + +#include -#include "database/Artist.hpp" -#include "database/Artwork.hpp" -#include "database/Directory.hpp" -#include "database/MediaLibrary.hpp" -#include "database/Release.hpp" -#include "database/ScanSettings.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" +#include "database/objects/TrackEmbeddedImageLink.hpp" +#include "database/objects/TrackLyrics.hpp" -#include "SqlQuery.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/StringViewTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::Cluster) +DBO_INSTANTIATE_TEMPLATES(lms::db::ClusterType) + namespace lms::db { namespace @@ -167,7 +174,7 @@ namespace lms::db { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(DISTINCT r.id) FROM release r INNER JOIN track t on t.release_id = r.id INNER JOIN track_cluster t_c ON t_c.track_id = t.id").where("t_c.cluster_id = ?").bind(id)); + return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(DISTINCT t.release_id) FROM track t INNER JOIN track_cluster t_c ON t_c.track_id = t.id").where("t_c.cluster_id = ?").bind(id)); } void Cluster::addTrack(ObjectPtr track) diff --git a/src/libs/database/impl/Directory.cpp b/src/libs/database/impl/objects/Directory.cpp similarity index 98% rename from src/libs/database/impl/Directory.cpp rename to src/libs/database/impl/objects/Directory.cpp index da595498..9e0a69f8 100644 --- a/src/libs/database/impl/Directory.cpp +++ b/src/libs/database/impl/objects/Directory.cpp @@ -17,16 +17,20 @@ * along with LMS. If not, see . */ -#include "database/Directory.hpp" +#include "database/objects/Directory.hpp" + +#include -#include "database/MediaLibrary.hpp" #include "database/Session.hpp" #include "database/Types.hpp" +#include "database/objects/MediaLibrary.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/PathTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::Directory) + namespace lms::db { namespace diff --git a/src/libs/database/impl/Image.cpp b/src/libs/database/impl/objects/Image.cpp similarity index 76% rename from src/libs/database/impl/Image.cpp rename to src/libs/database/impl/objects/Image.cpp index e0d43c4d..ec63a4fd 100644 --- a/src/libs/database/impl/Image.cpp +++ b/src/libs/database/impl/objects/Image.cpp @@ -17,18 +17,21 @@ * along with LMS. If not, see . */ -#include "database/Image.hpp" +#include "database/objects/Image.hpp" +#include #include -#include "database/Artist.hpp" -#include "database/Directory.hpp" #include "database/Session.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Directory.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/PathTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::Image) + namespace lms::db { namespace @@ -77,15 +80,15 @@ namespace lms::db return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT i from image i").where("i.absolute_file_path = ?").bind(file)); } - void Image::find(Session& session, ImageId& lastRetrievedImage, std::size_t count, const std::function& func) + void Image::find(Session& session, ImageId& lastRetrievedId, std::size_t count, const std::function& func) { session.checkReadTransaction(); - auto query{ session.getDboSession()->query>("SELECT i from image i").orderBy("i.id").where("i.id > ?").bind(lastRetrievedImage).limit(static_cast(count)) }; + auto query{ session.getDboSession()->query>("SELECT i from image i").orderBy("i.id").where("i.id > ?").bind(lastRetrievedId).limit(static_cast(count)) }; utils::forEachQueryResult(query, [&](const Image::pointer& image) { func(image); - lastRetrievedImage = image->getId(); + lastRetrievedId = image->getId(); }); } @@ -105,6 +108,18 @@ namespace lms::db }); } + void Image::findAbsoluteFilePath(Session& session, ImageId& lastRetrievedId, std::size_t count, const std::function& func) + { + session.checkReadTransaction(); + + auto query{ session.getDboSession()->query>("SELECT i.id,i.absolute_file_path from image i").orderBy("i.id").where("i.id > ?").bind(lastRetrievedId).limit(static_cast(count)) }; + + utils::forEachQueryResult(query, [&](const auto& res) { + func(std::get<0>(res), std::get<1>(res)); + lastRetrievedId = std::get<0>(res); + }); + } + void Image::setAbsoluteFilePath(const std::filesystem::path& p) { assert(p.is_absolute()); diff --git a/src/libs/database/impl/Listen.cpp b/src/libs/database/impl/objects/Listen.cpp similarity index 98% rename from src/libs/database/impl/Listen.cpp rename to src/libs/database/impl/objects/Listen.cpp index 73e58490..08874598 100644 --- a/src/libs/database/impl/Listen.cpp +++ b/src/libs/database/impl/objects/Listen.cpp @@ -17,15 +17,21 @@ * along with LMS. If not, see . */ -#include "database/Listen.hpp" +#include "database/objects/Listen.hpp" + +#include +#include + #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "SqlQuery.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::Listen) + namespace lms::db { namespace diff --git a/src/libs/database/impl/MediaLibrary.cpp b/src/libs/database/impl/objects/MediaLibrary.cpp similarity index 95% rename from src/libs/database/impl/MediaLibrary.cpp rename to src/libs/database/impl/objects/MediaLibrary.cpp index cbbadaae..73c0a60d 100644 --- a/src/libs/database/impl/MediaLibrary.cpp +++ b/src/libs/database/impl/objects/MediaLibrary.cpp @@ -17,17 +17,20 @@ * along with LMS. If not, see . */ -#include "database/MediaLibrary.hpp" +#include "database/objects/MediaLibrary.hpp" + +#include -#include "database/ScanSettings.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Track.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/PathTraits.hpp" #include "traits/StringViewTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::MediaLibrary) + namespace lms::db { MediaLibrary::MediaLibrary(std::string_view name, const std::filesystem::path& p) diff --git a/src/libs/database/impl/PlayListFile.cpp b/src/libs/database/impl/objects/PlayListFile.cpp similarity index 66% rename from src/libs/database/impl/PlayListFile.cpp rename to src/libs/database/impl/objects/PlayListFile.cpp index 8d60ed03..e7b408d1 100644 --- a/src/libs/database/impl/PlayListFile.cpp +++ b/src/libs/database/impl/objects/PlayListFile.cpp @@ -17,22 +17,26 @@ * along with LMS. If not, see . */ -#include "database/PlayListFile.hpp" +#include "database/objects/PlayListFile.hpp" +#include #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 "database/objects/Directory.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackList.hpp" +#include "database/objects/User.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/PathTraits.hpp" #include "traits/StringViewTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::PlayListFile) + namespace lms::db { PlayListFile::PlayListFile(const std::filesystem::path& file) @@ -71,6 +75,39 @@ namespace lms::db }); } + void PlayListFile::findAbsoluteFilePath(Session& session, PlayListFileId& lastRetrievedId, std::size_t count, const std::function& func) + { + session.checkReadTransaction(); + + auto query{ session.getDboSession()->query>("SELECT pl_f.id, pl_f.absolute_file_path FROM playlist_file pl_f").orderBy("pl_f.id").where("pl_f.id > ?").bind(lastRetrievedId).limit(static_cast(count)) }; + + utils::forEachQueryResult(query, [&](const auto& res) { + func(std::get<0>(res), std::get<1>(res)); + lastRetrievedId = std::get<0>(res); + }); + } + + void PlayListFile::find(Session& session, const IdRange& idRange, const std::function& func) + { + assert(idRange.isValid()); + + auto query{ session.getDboSession()->query>("SELECT pl_f from playlist_file pl_f").orderBy("pl_f.id").where("pl_f.id BETWEEN ? AND ?").bind(idRange.first).bind(idRange.last) }; + + utils::forEachQueryResult(query, [&](const PlayListFile::pointer& release) { + func(release); + }); + } + + IdRange PlayListFile::findNextIdRange(Session& session, PlayListFileId lastRetrievedId, std::size_t count) + { + auto query{ session.getDboSession()->query>("SELECT MIN(sub.id) AS first_id, MAX(sub.id) AS last_id FROM (SELECT pl_f.id FROM playlist_file pl_f WHERE pl_f.id > ? ORDER BY pl_f.id LIMIT ?) sub") }; + query.bind(lastRetrievedId); + query.bind(static_cast(count)); + + auto res{ utils::fetchQuerySingleResult(query) }; + return IdRange{ .first = std::get<0>(res), .last = std::get<1>(res) }; + } + PlayListFile::pointer PlayListFile::find(Session& session, PlayListFileId id) { session.checkReadTransaction(); @@ -130,8 +167,7 @@ namespace lms::db 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 << "'"); + LMS_LOG_IF(DB, WARNING, name.size() > _maxNameLength, "PlaylistFile name too long, truncated to '" << _name << "'"); } void PlayListFile::setFiles(std::span files) diff --git a/src/libs/database/impl/PlayQueue.cpp b/src/libs/database/impl/objects/PlayQueue.cpp similarity index 80% rename from src/libs/database/impl/PlayQueue.cpp rename to src/libs/database/impl/objects/PlayQueue.cpp index d5941f20..ddbe913c 100644 --- a/src/libs/database/impl/PlayQueue.cpp +++ b/src/libs/database/impl/objects/PlayQueue.cpp @@ -17,22 +17,31 @@ * along with LMS. If not, see . */ -#include "database/PlayQueue.hpp" +#include "database/objects/PlayQueue.hpp" -#include +#include +#include -#include "database/Artwork.hpp" -#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 "database/objects/Artist.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" +#include "database/objects/TrackEmbeddedImageLink.hpp" +#include "database/objects/TrackLyrics.hpp" +#include "database/objects/User.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/StringViewTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::PlayQueue) + namespace lms::db { PlayQueue::PlayQueue(const ObjectPtr& user, std::string_view name) diff --git a/src/libs/database/impl/RatedArtist.cpp b/src/libs/database/impl/objects/RatedArtist.cpp similarity index 93% rename from src/libs/database/impl/RatedArtist.cpp rename to src/libs/database/impl/objects/RatedArtist.cpp index 3e480f1f..45bafda5 100644 --- a/src/libs/database/impl/RatedArtist.cpp +++ b/src/libs/database/impl/objects/RatedArtist.cpp @@ -17,17 +17,20 @@ * along with LMS. If not, see . */ -#include "database/RatedArtist.hpp" +#include "database/objects/RatedArtist.hpp" +#include #include -#include "database/Artist.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/User.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::RatedArtist) + namespace lms::db { RatedArtist::RatedArtist(ObjectPtr artist, ObjectPtr user) diff --git a/src/libs/database/impl/RatedRelease.cpp b/src/libs/database/impl/objects/RatedRelease.cpp similarity index 93% rename from src/libs/database/impl/RatedRelease.cpp rename to src/libs/database/impl/objects/RatedRelease.cpp index b7de0d78..f0686052 100644 --- a/src/libs/database/impl/RatedRelease.cpp +++ b/src/libs/database/impl/objects/RatedRelease.cpp @@ -17,17 +17,20 @@ * along with LMS. If not, see . */ -#include "database/RatedRelease.hpp" +#include "database/objects/RatedRelease.hpp" +#include #include -#include "database/Release.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/User.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::RatedRelease) + namespace lms::db { RatedRelease::RatedRelease(ObjectPtr release, ObjectPtr user) diff --git a/src/libs/database/impl/RatedTrack.cpp b/src/libs/database/impl/objects/RatedTrack.cpp similarity index 93% rename from src/libs/database/impl/RatedTrack.cpp rename to src/libs/database/impl/objects/RatedTrack.cpp index 0c0ec110..8a8a1d7d 100644 --- a/src/libs/database/impl/RatedTrack.cpp +++ b/src/libs/database/impl/objects/RatedTrack.cpp @@ -17,17 +17,20 @@ * along with LMS. If not, see . */ -#include "database/RatedTrack.hpp" +#include "database/objects/RatedTrack.hpp" +#include #include #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::RatedTrack) + namespace lms::db { RatedTrack::RatedTrack(ObjectPtr track, ObjectPtr user) diff --git a/src/libs/database/impl/Release.cpp b/src/libs/database/impl/objects/Release.cpp similarity index 93% rename from src/libs/database/impl/Release.cpp rename to src/libs/database/impl/objects/Release.cpp index 12c85d6b..2c56eb8f 100644 --- a/src/libs/database/impl/Release.cpp +++ b/src/libs/database/impl/objects/Release.cpp @@ -17,19 +17,25 @@ * along with LMS. If not, see . */ -#include "database/Release.hpp" +#include "database/objects/Release.hpp" +#include #include #include "core/PartialDateTime.hpp" -#include "database/Artist.hpp" -#include "database/Artwork.hpp" -#include "database/Cluster.hpp" -#include "database/Directory.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" #include "database/Types.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" +#include "database/objects/TrackEmbeddedImageLink.hpp" +#include "database/objects/TrackLyrics.hpp" +#include "database/objects/User.hpp" #include "SqlQuery.hpp" #include "Utils.hpp" @@ -38,6 +44,11 @@ #include "traits/PartialDateTimeTraits.hpp" #include "traits/StringViewTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::Country) +DBO_INSTANTIATE_TEMPLATES(lms::db::Label) +DBO_INSTANTIATE_TEMPLATES(lms::db::Release) +DBO_INSTANTIATE_TEMPLATES(lms::db::ReleaseType) + namespace lms::db { namespace @@ -504,6 +515,27 @@ namespace lms::db }); } + void Release::find(Session& session, const IdRange& idRange, const std::function& func) + { + assert(idRange.isValid()); + + auto query{ session.getDboSession()->query>("SELECT r from release r").orderBy("r.id").where("r.id BETWEEN ? AND ?").bind(idRange.first).bind(idRange.last) }; + + utils::forEachQueryResult(query, [&](const Release::pointer& release) { + func(release); + }); + } + + IdRange Release::findNextIdRange(Session& session, ReleaseId lastRetrievedId, std::size_t count) + { + auto query{ session.getDboSession()->query>("SELECT MIN(sub.id) AS first_id, MAX(sub.id) AS last_id FROM (SELECT r.id FROM release r WHERE r.id > ? ORDER BY r.id LIMIT ?) sub") }; + query.bind(lastRetrievedId); + query.bind(static_cast(count)); + + auto res{ utils::fetchQuerySingleResult(query) }; + return IdRange{ .first = std::get<0>(res), .last = std::get<1>(res) }; + } + RangeResults Release::find(Session& session, const FindParameters& params) { session.checkReadTransaction(); @@ -528,6 +560,16 @@ namespace lms::db return utils::execRangeQuery(query, params.range); } + void Release::updatePreferredArtwork(Session& session, ReleaseId releaseId, ArtworkId artworkId) + { + session.checkWriteTransaction(); + + if (artworkId.isValid()) + utils::executeCommand(*session.getDboSession(), "UPDATE release SET preferred_artwork_id = ? WHERE id = ?", artworkId, releaseId); + else + utils::executeCommand(*session.getDboSession(), "UPDATE release SET preferred_artwork_id = NULL WHERE id = ?", releaseId); + } + std::size_t Release::getCount(Session& session, const FindParameters& params) { session.checkReadTransaction(); diff --git a/src/libs/database/impl/ScanSettings.cpp b/src/libs/database/impl/objects/ScanSettings.cpp similarity index 96% rename from src/libs/database/impl/ScanSettings.cpp rename to src/libs/database/impl/objects/ScanSettings.cpp index f38ee204..23907e24 100644 --- a/src/libs/database/impl/ScanSettings.cpp +++ b/src/libs/database/impl/objects/ScanSettings.cpp @@ -17,18 +17,21 @@ * along with LMS. If not, see . */ -#include "database/ScanSettings.hpp" +#include "database/objects/ScanSettings.hpp" +#include #include #include "core/String.hpp" -#include "database/MediaLibrary.hpp" #include "database/Session.hpp" +#include "database/objects/MediaLibrary.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/StringViewTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::ScanSettings) + namespace lms::db { ScanSettings::ScanSettings(std::string_view name) diff --git a/src/libs/database/impl/StarredArtist.cpp b/src/libs/database/impl/objects/StarredArtist.cpp similarity index 93% rename from src/libs/database/impl/StarredArtist.cpp rename to src/libs/database/impl/objects/StarredArtist.cpp index e19071c1..94474f86 100644 --- a/src/libs/database/impl/StarredArtist.cpp +++ b/src/libs/database/impl/objects/StarredArtist.cpp @@ -17,17 +17,20 @@ * along with LMS. If not, see . */ -#include "database/StarredArtist.hpp" +#include "database/objects/StarredArtist.hpp" +#include #include -#include "database/Artist.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/User.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::StarredArtist) + namespace lms::db { StarredArtist::StarredArtist(ObjectPtr artist, ObjectPtr user, FeedbackBackend backend) diff --git a/src/libs/database/impl/StarredRelease.cpp b/src/libs/database/impl/objects/StarredRelease.cpp similarity index 93% rename from src/libs/database/impl/StarredRelease.cpp rename to src/libs/database/impl/objects/StarredRelease.cpp index 1b5fded8..54b3eadb 100644 --- a/src/libs/database/impl/StarredRelease.cpp +++ b/src/libs/database/impl/objects/StarredRelease.cpp @@ -17,17 +17,20 @@ * along with LMS. If not, see . */ -#include "database/StarredRelease.hpp" +#include "database/objects/StarredRelease.hpp" +#include #include -#include "database/Release.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/User.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::StarredRelease) + namespace lms::db { StarredRelease::StarredRelease(ObjectPtr release, ObjectPtr user, FeedbackBackend backend) diff --git a/src/libs/database/impl/StarredTrack.cpp b/src/libs/database/impl/objects/StarredTrack.cpp similarity index 95% rename from src/libs/database/impl/StarredTrack.cpp rename to src/libs/database/impl/objects/StarredTrack.cpp index 842b276d..74230219 100644 --- a/src/libs/database/impl/StarredTrack.cpp +++ b/src/libs/database/impl/objects/StarredTrack.cpp @@ -17,17 +17,20 @@ * along with LMS. If not, see . */ -#include "database/StarredTrack.hpp" +#include "database/objects/StarredTrack.hpp" +#include #include #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::StarredTrack) + namespace lms::db { StarredTrack::StarredTrack(ObjectPtr track, ObjectPtr user, FeedbackBackend backend) diff --git a/src/libs/database/impl/Track.cpp b/src/libs/database/impl/objects/Track.cpp similarity index 86% rename from src/libs/database/impl/Track.cpp rename to src/libs/database/impl/objects/Track.cpp index ae308b53..4925b3e3 100644 --- a/src/libs/database/impl/Track.cpp +++ b/src/libs/database/impl/objects/Track.cpp @@ -17,24 +17,25 @@ * along with LMS. If not, see . */ -#include "database/Track.hpp" +#include "database/objects/Track.hpp" +#include #include #include "core/ILogger.hpp" -#include "database/Artist.hpp" -#include "database/Artwork.hpp" -#include "database/Cluster.hpp" -#include "database/Directory.hpp" -#include "database/MediaLibrary.hpp" -#include "database/Release.hpp" #include "database/Session.hpp" -#include "database/TrackArtistLink.hpp" -#include "database/TrackEmbeddedImage.hpp" -#include "database/TrackEmbeddedImageLink.hpp" -#include "database/TrackFeatures.hpp" -#include "database/TrackLyrics.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" +#include "database/objects/TrackEmbeddedImageLink.hpp" +#include "database/objects/TrackFeatures.hpp" +#include "database/objects/TrackLyrics.hpp" +#include "database/objects/User.hpp" #include "SqlQuery.hpp" #include "Utils.hpp" @@ -43,6 +44,8 @@ #include "traits/PathTraits.hpp" #include "traits/StringViewTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::Track) + namespace lms::db { namespace @@ -58,12 +61,6 @@ namespace lms::db for (std::string_view keyword : params.keywords) query.where("t.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeLikeKeyword(keyword) + "%"); - if (!params.fileStem.empty()) - query.where("t.file_stem = ?").bind(params.fileStem); - - if (!params.fileName.empty()) - query.where("t.file_name = ?").bind(params.fileName); - if (!params.name.empty()) query.where("t.name = ?").bind(params.name); @@ -212,8 +209,8 @@ namespace lms::db case TrackSortMethod::Name: query.orderBy("t.name COLLATE NOCASE"); break; - case TrackSortMethod::FileName: - query.orderBy("t.file_name COLLATE NOCASE"); + case TrackSortMethod::AbsoluteFilePath: + query.orderBy("t.absolute_file_path COLLATE NOCASE"); break; case TrackSortMethod::DateDescAndRelease: query.orderBy("t.date DESC,t.release_id,t.disc_number,t.track_number"); @@ -264,6 +261,25 @@ namespace lms::db return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT t from track t").where("t.absolute_file_path = ?").bind(p)); } + std::optional Track::findFileInfo(Session& session, const std::filesystem::path& p) + { + session.checkReadTransaction(); + + auto query{ session.getDboSession()->query>("SELECT t.scan_version, t.file_last_write FROM track t WHERE t.absolute_file_path = ?") }; + query.bind(p); + + std::optional result; + + utils::forEachQueryResult(query, [&](const auto& row) { + FileInfo info; + info.scanVersion = std::get<0>(row); + info.lastWrittenTime = std::get<1>(row); + result = info; + }); + + return result; + } + Track::pointer Track::find(Session& session, TrackId id) { session.checkReadTransaction(); @@ -271,21 +287,54 @@ namespace lms::db return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT t from track t").where("t.id = ?").bind(id)); } - void Track::find(Session& session, TrackId& lastRetrievedTrack, std::size_t count, const std::function& func, MediaLibraryId library) + void Track::find(Session& session, TrackId& lastRetrievedId, std::size_t count, const std::function& func, MediaLibraryId library) { session.checkReadTransaction(); - auto query{ session.getDboSession()->query>("SELECT t from track t").orderBy("t.id").where("t.id > ?").bind(lastRetrievedTrack).limit(static_cast(count)) }; + auto query{ session.getDboSession()->query>("SELECT t from track t").orderBy("t.id").where("t.id > ?").bind(lastRetrievedId).limit(static_cast(count)) }; if (library.isValid()) query.where("media_library_id = ?").bind(library); utils::forEachQueryResult(query, [&](const Track::pointer& track) { func(track); - lastRetrievedTrack = track->getId(); + lastRetrievedId = track->getId(); }); } + void Track::findAbsoluteFilePath(Session& session, TrackId& lastRetrievedId, std::size_t count, const std::function& func) + { + session.checkReadTransaction(); + + auto query{ session.getDboSession()->query>("SELECT t.id,t.absolute_file_path from track t").orderBy("t.id").where("t.id > ?").bind(lastRetrievedId).limit(static_cast(count)) }; + + utils::forEachQueryResult(query, [&](const auto& res) { + func(std::get<0>(res), std::get<1>(res)); + lastRetrievedId = std::get<0>(res); + }); + } + + void Track::find(Session& session, const IdRange& idRange, const std::function& func) + { + assert(idRange.isValid()); + + auto query{ session.getDboSession()->query>("SELECT t from track t").orderBy("t.id").where("t.id BETWEEN ? AND ?").bind(idRange.first).bind(idRange.last) }; + + utils::forEachQueryResult(query, [&](const Track::pointer& track) { + func(track); + }); + } + + IdRange Track::findNextIdRange(Session& session, TrackId lastRetrievedId, std::size_t count) + { + auto query{ session.getDboSession()->query>("SELECT MIN(sub.id) AS first_id, MAX(sub.id) AS last_id FROM (SELECT t.id FROM track t WHERE t.id > ? ORDER BY t.id LIMIT ?) sub") }; + query.bind(lastRetrievedId); + query.bind(static_cast(count)); + + auto res{ utils::fetchQuerySingleResult(query) }; + return IdRange{ .first = std::get<0>(res), .last = std::get<1>(res) }; + } + bool Track::exists(Session& session, TrackId id) { session.checkReadTransaction(); @@ -456,41 +505,25 @@ namespace lms::db void Track::setAbsoluteFilePath(const std::filesystem::path& filePath) { assert(filePath.is_absolute()); - _absoluteFilePath = filePath; - _fileStem = filePath.stem(); - _fileName = filePath.filename(); - } - - void Track::setRelativeFilePath(const std::filesystem::path& filePath) - { - assert(filePath.is_relative()); - - assert(_absoluteFilePath.filename() == filePath.filename()); // must be compatible with previous setAbsoluteFilePath call - _fileStem = filePath.stem(); // lazy migration (_fileStem added later, could be set only with setAbsoluteFilePath) - _fileName = filePath.filename(); // lazy migration (_fileName added later, could be set only with setAbsoluteFilePath) - _relativeFilePath = filePath; } void Track::setName(std::string_view name) { _name = std::string{ name, 0, _maxNameLength }; - if (name.size() > _maxNameLength) - LMS_LOG(DB, WARNING, "Track name too long, truncated to '" << _name << "'"); + LMS_LOG_IF(DB, WARNING, name.size() > _maxNameLength, "Track name too long, truncated to '" << _name << "'"); } void Track::setCopyright(std::string_view copyright) { _copyright = std::string{ copyright, 0, _maxCopyrightLength }; - if (copyright.size() > _maxCopyrightLength) - LMS_LOG(DB, WARNING, "Track copyright too long, truncated to '" << _copyright << "'"); + LMS_LOG_IF(DB, WARNING, copyright.size() > _maxCopyrightLength, "Track copyright too long, truncated to '" << _copyright << "'"); } void Track::setCopyrightURL(std::string_view copyrightURL) { _copyrightURL = std::string{ copyrightURL, 0, _maxCopyrightURLLength }; - if (copyrightURL.size() > _maxCopyrightURLLength) - LMS_LOG(DB, WARNING, "Track copyright URL too long, truncated to '" << _copyrightURL << "'"); + LMS_LOG_IF(DB, WARNING, copyrightURL.size() > _maxCopyrightURLLength, "Track copyright URL too long, truncated to '" << _copyrightURL << "'"); } void Track::clearArtistLinks() diff --git a/src/libs/database/impl/TrackArtistLink.cpp b/src/libs/database/impl/objects/TrackArtistLink.cpp similarity index 91% rename from src/libs/database/impl/TrackArtistLink.cpp rename to src/libs/database/impl/objects/TrackArtistLink.cpp index ea92ef36..90a373ff 100644 --- a/src/libs/database/impl/TrackArtistLink.cpp +++ b/src/libs/database/impl/objects/TrackArtistLink.cpp @@ -17,15 +17,20 @@ * along with LMS. If not, see . */ -#include "database/TrackArtistLink.hpp" +#include "database/objects/TrackArtistLink.hpp" -#include "database/Artist.hpp" +#include + +#include "core/ILogger.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Track.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::TrackArtistLink) + namespace lms::db { namespace @@ -173,11 +178,13 @@ namespace lms::db void TrackArtistLink::setArtistName(std::string_view artistName) { - _artistName = artistName; + _artistName.assign(artistName, 0, Artist::maxNameLength); + LMS_LOG_IF(DB, WARNING, artistName.size() > Artist::maxNameLength, "Artist link name too long, truncated to '" << _artistName << "'"); } void TrackArtistLink::setArtistSortName(std::string_view artistSortName) { - _artistSortName = artistSortName; + _artistSortName.assign(artistSortName, 0, Artist::maxNameLength); + LMS_LOG_IF(DB, WARNING, artistSortName.size() > Artist::maxNameLength, "Artist link sort name too long, truncated to '" << _artistSortName << "'"); } } // namespace lms::db diff --git a/src/libs/database/impl/TrackBookmark.cpp b/src/libs/database/impl/objects/TrackBookmark.cpp similarity index 92% rename from src/libs/database/impl/TrackBookmark.cpp rename to src/libs/database/impl/objects/TrackBookmark.cpp index f0d0bc22..aa358bd7 100644 --- a/src/libs/database/impl/TrackBookmark.cpp +++ b/src/libs/database/impl/objects/TrackBookmark.cpp @@ -17,15 +17,19 @@ * along with LMS. If not, see . */ -#include "database/TrackBookmark.hpp" +#include "database/objects/TrackBookmark.hpp" + +#include #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::TrackBookmark) + namespace lms::db { TrackBookmark::TrackBookmark(ObjectPtr user, ObjectPtr track) diff --git a/src/libs/database/impl/TrackEmbeddedImage.cpp b/src/libs/database/impl/objects/TrackEmbeddedImage.cpp similarity index 90% rename from src/libs/database/impl/TrackEmbeddedImage.cpp rename to src/libs/database/impl/objects/TrackEmbeddedImage.cpp index 9b844b8d..52e174f0 100644 --- a/src/libs/database/impl/TrackEmbeddedImage.cpp +++ b/src/libs/database/impl/objects/TrackEmbeddedImage.cpp @@ -17,17 +17,22 @@ * along with LMS. If not, see . */ -#include "database/Directory.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" + +#include #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackEmbeddedImage.hpp" #include "database/Types.hpp" +#include "database/objects/Track.hpp" + +#include "database/objects/TrackEmbeddedImageLink.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/ImageHashTypeTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::TrackEmbeddedImage) + namespace lms::db { namespace @@ -43,7 +48,7 @@ namespace lms::db || params.track.isValid() || params.release.isValid() || params.trackList.isValid() - || !params.imageTypes.empty() + || params.imageType.has_value() || params.sortMethod == TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc || params.sortMethod == TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc) { @@ -90,19 +95,8 @@ namespace lms::db query.where("t_l_e.tracklist_id = ?").bind(params.trackList); } - if (!params.imageTypes.empty()) - { - std::string clause{ "t_e_i_l.type IN (" }; - for (const auto& type : params.imageTypes) - { - if (clause.back() != '(') - clause += ","; - clause += "?"; - query.bind(type); - } - clause += ")"; - query.where(clause); - } + if (params.imageType.has_value()) + query.where("t_e_i_l.type = ?").bind(params.imageType.value()); } switch (params.sortMethod) diff --git a/src/libs/database/impl/TrackEmbeddedImageLink.cpp b/src/libs/database/impl/objects/TrackEmbeddedImageLink.cpp similarity index 89% rename from src/libs/database/impl/TrackEmbeddedImageLink.cpp rename to src/libs/database/impl/objects/TrackEmbeddedImageLink.cpp index 300d2ed3..e39b2965 100644 --- a/src/libs/database/impl/TrackEmbeddedImageLink.cpp +++ b/src/libs/database/impl/objects/TrackEmbeddedImageLink.cpp @@ -17,15 +17,21 @@ * along with LMS. If not, see . */ -#include "database/TrackEmbeddedImageLink.hpp" +#include "database/objects/TrackEmbeddedImageLink.hpp" + +#include +#include #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackEmbeddedImage.hpp" #include "database/Types.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" +#include "traits/ImageHashTypeTraits.hpp" + +DBO_INSTANTIATE_TEMPLATES(lms::db::TrackEmbeddedImageLink) namespace lms::db { diff --git a/src/libs/database/impl/TrackFeatures.cpp b/src/libs/database/impl/objects/TrackFeatures.cpp similarity index 95% rename from src/libs/database/impl/TrackFeatures.cpp rename to src/libs/database/impl/objects/TrackFeatures.cpp index cfb088ea..46c9607a 100644 --- a/src/libs/database/impl/TrackFeatures.cpp +++ b/src/libs/database/impl/objects/TrackFeatures.cpp @@ -17,19 +17,22 @@ * along with LMS. If not, see . */ -#include "database/TrackFeatures.hpp" +#include "database/objects/TrackFeatures.hpp" +#include #include #include #include "core/ILogger.hpp" -#include "database/Directory.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/Track.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::TrackFeatures) + namespace lms::db { diff --git a/src/libs/database/impl/TrackList.cpp b/src/libs/database/impl/objects/TrackList.cpp similarity index 97% rename from src/libs/database/impl/TrackList.cpp rename to src/libs/database/impl/objects/TrackList.cpp index 1b32030f..de77c518 100644 --- a/src/libs/database/impl/TrackList.cpp +++ b/src/libs/database/impl/objects/TrackList.cpp @@ -16,21 +16,27 @@ * You should have received a copy of the GNU General Public License * along with LMS. If not, see . */ -#include "database/TrackList.hpp" +#include "database/objects/TrackList.hpp" #include -#include "database/Cluster.hpp" -#include "database/PlayListFile.hpp" +#include +#include + #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/PlayListFile.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "SqlQuery.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/StringViewTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::TrackList) +DBO_INSTANTIATE_TEMPLATES(lms::db::TrackListEntry) + namespace lms::db { namespace diff --git a/src/libs/database/impl/TrackLyrics.cpp b/src/libs/database/impl/objects/TrackLyrics.cpp similarity index 87% rename from src/libs/database/impl/TrackLyrics.cpp rename to src/libs/database/impl/objects/TrackLyrics.cpp index 18779f55..e580efbe 100644 --- a/src/libs/database/impl/TrackLyrics.cpp +++ b/src/libs/database/impl/objects/TrackLyrics.cpp @@ -17,21 +17,23 @@ * along with LMS. If not, see . */ -#include "database/TrackLyrics.hpp" +#include "database/objects/TrackLyrics.hpp" -#include +#include #include -#include "database/Artist.hpp" -#include "database/Directory.hpp" -#include "database/Release.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/PathTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::TrackLyrics) + namespace lms::db { namespace @@ -126,6 +128,18 @@ namespace lms::db return utils::execRangeQuery(query, range); } + void TrackLyrics::findAbsoluteFilePath(Session& session, TrackLyricsId& lastRetrievedId, std::size_t count, const std::function& func) + { + session.checkReadTransaction(); + + auto query{ session.getDboSession()->query>("SELECT t_lrc.id,t_lrc.absolute_file_path from track_lyrics t_lrc").orderBy("t_lrc.id").where("t_lrc.id > ?").bind(lastRetrievedId).limit(static_cast(count)) }; + + utils::forEachQueryResult(query, [&](const auto& res) { + func(std::get<0>(res), std::get<1>(res)); + lastRetrievedId = std::get<0>(res); + }); + } + TrackLyrics::SynchronizedLines TrackLyrics::getSynchronizedLines() const { SynchronizedLines synchronizedLines; diff --git a/src/libs/database/impl/UIState.cpp b/src/libs/database/impl/objects/UIState.cpp similarity index 93% rename from src/libs/database/impl/UIState.cpp rename to src/libs/database/impl/objects/UIState.cpp index 22a11d75..8b4b07cc 100644 --- a/src/libs/database/impl/UIState.cpp +++ b/src/libs/database/impl/objects/UIState.cpp @@ -17,15 +17,19 @@ * along with LMS. If not, see . */ -#include "database/UIState.hpp" +#include "database/objects/UIState.hpp" + +#include #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" #include "Utils.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/StringViewTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::UIState) + namespace lms::db { UIState::UIState(std::string_view item, ObjectPtr user) diff --git a/src/libs/database/impl/User.cpp b/src/libs/database/impl/objects/User.cpp similarity index 90% rename from src/libs/database/impl/User.cpp rename to src/libs/database/impl/objects/User.cpp index fcbb6e71..1bc03388 100644 --- a/src/libs/database/impl/User.cpp +++ b/src/libs/database/impl/objects/User.cpp @@ -17,21 +17,27 @@ * along with LMS. If not, see . */ -#include "database/User.hpp" +#include "database/objects/User.hpp" #include -#include "database/Artist.hpp" -#include "database/Release.hpp" +#include +#include + #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/UIState.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/AuthToken.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/UIState.hpp" #include "Utils.hpp" #include "traits/EnumSetTraits.hpp" #include "traits/IdTypeTraits.hpp" #include "traits/StringViewTraits.hpp" +DBO_INSTANTIATE_TEMPLATES(lms::db::User) + namespace lms::db { User::User(std::string_view loginName) diff --git a/src/libs/services/scanner/impl/steps/ArtworkUtils.hpp b/src/libs/database/include/database/IDb.hpp similarity index 56% rename from src/libs/services/scanner/impl/steps/ArtworkUtils.hpp rename to src/libs/database/include/database/IDb.hpp index 8d18b009..aa332f34 100644 --- a/src/libs/services/scanner/impl/steps/ArtworkUtils.hpp +++ b/src/libs/database/include/database/IDb.hpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Emeric Poupon + * Copyright (C) 2019 Emeric Poupon * * This file is part of LMS. * @@ -20,22 +20,18 @@ #pragma once #include - -#include "database/ArtworkId.hpp" -#include "database/ImageId.hpp" -#include "database/Object.hpp" -#include "database/TrackEmbeddedImageId.hpp" +#include namespace lms::db { - class Artwork; class Session; + class IDb + { + public: + virtual ~IDb() = default; + + virtual Session& getTLSSession() = 0; + }; + + std::unique_ptr createDb(const std::filesystem::path& dbPath, std::size_t connectionCount = 10); } // namespace lms::db - -namespace lms::scanner::utils -{ - db::ObjectPtr getOrCreateArtworkFromTrackEmbeddedImage(db::Session& session, db::TrackEmbeddedImageId trackEmbeddedImageId); - db::ObjectPtr getOrCreateArtworkFromImage(db::Session& session, db::ImageId imageId); - - std::filesystem::path toPath(db::Session& session, db::ArtworkId artworkId); -} // namespace lms::scanner::utils \ No newline at end of file diff --git a/src/libs/database/include/database/IQueryPlanRecorder.hpp b/src/libs/database/include/database/IQueryPlanRecorder.hpp new file mode 100644 index 00000000..298634c8 --- /dev/null +++ b/src/libs/database/include/database/IQueryPlanRecorder.hpp @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2025 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 +#include + +namespace lms::db +{ + // Due to technical limitations, query plans are recorded globally across all databases. + // As a result, this class is implemented as a singleton rather than being owned per DB instance. + class IQueryPlanRecorder + { + public: + virtual ~IQueryPlanRecorder() = default; + + using QueryPlanVisitor = std::function; + virtual void visitQueryPlans(const QueryPlanVisitor& visitor) const = 0; + }; + + std::unique_ptr createQueryPlanRecorder(); +} // namespace lms::db diff --git a/src/libs/database/include/database/IdRange.hpp b/src/libs/database/include/database/IdRange.hpp new file mode 100644 index 00000000..cc7f7434 --- /dev/null +++ b/src/libs/database/include/database/IdRange.hpp @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2025 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 + +namespace lms::db +{ + template + struct IdRange + { + public: + IdType first; + IdType last; + + bool isValid() const { return first.isValid() && last.isValid(); } + }; +} // namespace lms::db diff --git a/src/libs/database/include/database/Object.hpp b/src/libs/database/include/database/Object.hpp index 6ea23173..90456b0b 100644 --- a/src/libs/database/include/database/Object.hpp +++ b/src/libs/database/include/database/Object.hpp @@ -20,15 +20,19 @@ #pragma once #include -#include #include "database/IdType.hpp" -#include "database/TransactionChecker.hpp" namespace lms::db { + class ObjectPtrBase + { + protected: + static void checkWriteTransaction(Wt::Dbo::Session& session); + }; + template - class ObjectPtr + class ObjectPtr : public ObjectPtrBase { public: ObjectPtr() = default; @@ -45,18 +49,13 @@ namespace lms::db auto modify() { -#if LMS_CHECK_TRANSACTION_ACCESSES - TransactionChecker::checkWriteTransaction(*_obj.session()); -#endif + checkWriteTransaction(*_obj.session()); return _obj.modify(); } void remove() { -#if LMS_CHECK_TRANSACTION_ACCESSES - TransactionChecker::checkWriteTransaction(*_obj.session()); -#endif - + checkWriteTransaction(*_obj.session()); _obj.remove(); } diff --git a/src/libs/database/include/database/Session.hpp b/src/libs/database/include/database/Session.hpp index e1b4cd66..90c0448c 100644 --- a/src/libs/database/include/database/Session.hpp +++ b/src/libs/database/include/database/Session.hpp @@ -19,56 +19,23 @@ #pragma once -#include -#include +#include +#include #include +#include #include -#include "core/ITraceLogger.hpp" -#include "core/RecursiveSharedMutex.hpp" -#include "database/TransactionChecker.hpp" +#include "database/Transaction.hpp" +#include "database/Types.hpp" namespace lms::db { - class WriteTransaction - { - public: - ~WriteTransaction(); - - private: - friend class Session; - WriteTransaction(core::RecursiveSharedMutex& mutex, Wt::Dbo::Session& session); - - WriteTransaction(const WriteTransaction&) = delete; - WriteTransaction& operator=(const WriteTransaction&) = delete; - - const std::unique_lock _lock; - const core::tracing::ScopedTrace _trace{ "Database", core::tracing::Level::Detailed, "WriteTransaction" }; // before actual transaction - Wt::Dbo::Transaction _transaction; - }; - - class ReadTransaction - { - public: - ~ReadTransaction(); - - private: - friend class Session; - ReadTransaction(Wt::Dbo::Session& session); - - ReadTransaction(const ReadTransaction&) = delete; - ReadTransaction& operator=(const ReadTransaction&) = delete; - - const core::tracing::ScopedTrace _trace{ "Database", core::tracing::Level::Detailed, "ReadTransaction" }; // before actual transaction - Wt::Dbo::Transaction _transaction; - }; - - class Db; + class IDb; class Session { public: - Session(Db& db); + Session(IDb& db); ~Session() = default; Session(const Session&) = delete; Session& operator=(const Session&) = delete; @@ -76,18 +43,8 @@ namespace lms::db [[nodiscard]] WriteTransaction createWriteTransaction(); [[nodiscard]] ReadTransaction createReadTransaction(); - void checkWriteTransaction() - { -#if LMS_CHECK_TRANSACTION_ACCESSES - TransactionChecker::checkWriteTransaction(_session); -#endif - } - void checkReadTransaction() - { -#if LMS_CHECK_TRANSACTION_ACCESSES - TransactionChecker::checkReadTransaction(_session); -#endif - } + void checkWriteTransaction() const; + void checkReadTransaction() const; void execute(std::string_view statement); @@ -97,23 +54,19 @@ namespace lms::db void analyzeEntry(const std::string& entry); bool areAllTablesEmpty(); // need to acquire a read transaction + FileStats getFileStats(); // need to acquire a read transaction void prepareTablesIfNeeded(); // need to run only once at startup bool migrateSchemaIfNeeded(); // returns true if migration was performed void createIndexesIfNeeded(); void vacuumIfNeeded(); void vacuum(); - void refreshTracingLoggerStats(); // returning a ptr here to ease further wrapping using operator-> - Wt::Dbo::Session* getDboSession() - { - return &_session; - } - Db& getDb() - { - return _db; - } + Wt::Dbo::Session* getDboSession() { return &_session; } + const Wt::Dbo::Session* getDboSession() const { return &_session; } + + IDb& getDb() { return _db; } template typename Object::pointer create(Args&&... args) @@ -126,8 +79,26 @@ namespace lms::db return res; } + template + void destroy(typename Object::IdType id) + { + destroy(std::span{ &id, 1 }); + } + + template + void destroy(std::span ids) + { + checkWriteTransaction(); + + const std::string query{ std::string{ "DELETE FROM " } + _session.tableName() + " WHERE id = ?" }; + for (typename Object::IdType id : ids) + execute(query, id.getValue()); + } + private: - Db& _db; + void execute(std::string_view query, long long id); + + IDb& _db; Wt::Dbo::Session _session; }; } // namespace lms::db diff --git a/src/libs/database/include/database/Transaction.hpp b/src/libs/database/include/database/Transaction.hpp new file mode 100644 index 00000000..095e628d --- /dev/null +++ b/src/libs/database/include/database/Transaction.hpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2025 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 + +#include + +#include "core/ITraceLogger.hpp" + +namespace lms::core +{ + class RecursiveSharedMutex; +} + +namespace lms::db +{ + class WriteTransaction + { + public: + ~WriteTransaction(); + + private: + friend class Session; + WriteTransaction(core::RecursiveSharedMutex& mutex, Wt::Dbo::Session& session); + + WriteTransaction(const WriteTransaction&) = delete; + WriteTransaction& operator=(const WriteTransaction&) = delete; + + const std::unique_lock _lock; + const core::tracing::ScopedTrace _trace; // before actual transaction + Wt::Dbo::Transaction _transaction; + }; + + class ReadTransaction + { + public: + ~ReadTransaction(); + + private: + friend class Session; + ReadTransaction(Wt::Dbo::Session& session); + + ReadTransaction(const ReadTransaction&) = delete; + ReadTransaction& operator=(const ReadTransaction&) = delete; + + const core::tracing::ScopedTrace _trace; // before actual transaction + Wt::Dbo::Transaction _transaction; + }; +} // namespace lms::db \ No newline at end of file diff --git a/src/libs/database/include/database/Types.hpp b/src/libs/database/include/database/Types.hpp index f7fb08dd..9d4e4fc4 100644 --- a/src/libs/database/include/database/Types.hpp +++ b/src/libs/database/include/database/Types.hpp @@ -102,6 +102,17 @@ namespace lms::db } }; + struct FileStats + { + std::size_t trackCount; + std::size_t imageCount; + std::size_t trackLyricsCount; + std::size_t playListCount; + std::size_t artistInfoCount; + + std::size_t getTotalFileCount() const { return trackCount + imageCount + trackLyricsCount + playListCount + artistInfoCount; } + }; + struct YearRange { int begin{}; @@ -114,6 +125,12 @@ namespace lms::db std::string name; }; + struct FileInfo + { + Wt::WDateTime lastWrittenTime; + std::size_t scanVersion{}; + }; + enum class ArtistSortMethod { None, @@ -193,7 +210,7 @@ namespace lms::db LastWrittenDesc, AddedDesc, StarredDateDesc, - FileName, + AbsoluteFilePath, Name, DateDescAndRelease, Release, // order by disc/track number diff --git a/src/libs/database/include/database/Artist.hpp b/src/libs/database/include/database/objects/Artist.hpp similarity index 89% rename from src/libs/database/include/database/Artist.hpp rename to src/libs/database/include/database/objects/Artist.hpp index 86972a75..6310e283 100644 --- a/src/libs/database/include/database/Artist.hpp +++ b/src/libs/database/include/database/objects/Artist.hpp @@ -25,21 +25,23 @@ #include #include -#include +#include +#include #include #include "core/EnumSet.hpp" #include "core/UUID.hpp" -#include "database/ArtistId.hpp" -#include "database/ArtworkId.hpp" -#include "database/ClusterId.hpp" -#include "database/Filters.hpp" -#include "database/MediaLibraryId.hpp" +#include "database/IdRange.hpp" #include "database/Object.hpp" -#include "database/ReleaseId.hpp" -#include "database/TrackId.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/ArtworkId.hpp" +#include "database/objects/ClusterId.hpp" +#include "database/objects/Filters.hpp" +#include "database/objects/MediaLibraryId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { @@ -56,6 +58,8 @@ namespace lms::db class Artist final : public Object { public: + static constexpr std::size_t maxNameLength{ 512 }; + struct FindParameters { Filters filters; @@ -125,12 +129,17 @@ namespace lms::db static pointer find(Session& session, ArtistId id); static std::vector find(Session& session, std::string_view name); // exact match on name field static void find(Session& session, ArtistId& lastRetrievedArtist, std::size_t count, const std::function& func, MediaLibraryId library = {}); + static void find(Session& session, const IdRange& idRange, const std::function& func); static RangeResults find(Session& session, const FindParameters& params); static void find(Session& session, const FindParameters& params, std::function func); + static IdRange findNextIdRange(Session& session, ArtistId lastRetrievedId, std::size_t count); static RangeResults findIds(Session& session, const FindParameters& params); static RangeResults findOrphanIds(Session& session, std::optional range = std::nullopt); // No track related static bool exists(Session& session, ArtistId id); + // Updates + static void updatePreferredArtwork(Session& session, ArtistId artistId, ArtworkId artworkId); + // Accessors const std::string& getName() const { return _name; } const std::string& getSortName() const { return _sortName; } @@ -166,8 +175,6 @@ namespace lms::db } private: - static constexpr std::size_t _maxNameLength{ 512 }; - friend class Session; // Create Artist(const std::string& name, const std::optional& MBID = {}); diff --git a/src/libs/database/include/database/ArtistId.hpp b/src/libs/database/include/database/objects/ArtistId.hpp similarity index 100% rename from src/libs/database/include/database/ArtistId.hpp rename to src/libs/database/include/database/objects/ArtistId.hpp diff --git a/src/libs/database/include/database/ArtistInfo.hpp b/src/libs/database/include/database/objects/ArtistInfo.hpp similarity index 93% rename from src/libs/database/include/database/ArtistInfo.hpp rename to src/libs/database/include/database/objects/ArtistInfo.hpp index 381fbca2..28d5e4b8 100644 --- a/src/libs/database/include/database/ArtistInfo.hpp +++ b/src/libs/database/include/database/objects/ArtistInfo.hpp @@ -24,14 +24,14 @@ #include #include -#include +#include #include -#include "database/ArtistId.hpp" -#include "database/ArtistInfoId.hpp" -#include "database/DirectoryId.hpp" #include "database/Object.hpp" #include "database/Types.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/ArtistInfoId.hpp" +#include "database/objects/DirectoryId.hpp" namespace lms::db { @@ -53,6 +53,7 @@ namespace lms::db static void find(Session& session, ArtistInfoId& lastRetrievedId, std::size_t count, const std::function& func); static void findArtistNameNoLongerMatch(Session& session, std::optional range, const std::function& func); static void findWithArtistNameAmbiguity(Session& session, std::optional range, bool allowArtistMBIDFallback, const std::function& func); + static void findAbsoluteFilePath(Session& session, ArtistInfoId& lastRetrievedId, std::size_t count, const std::function& func); // getters std::size_t getScanVersion() const { return _scanVersion; } diff --git a/src/libs/database/include/database/ArtistInfoId.hpp b/src/libs/database/include/database/objects/ArtistInfoId.hpp similarity index 100% rename from src/libs/database/include/database/ArtistInfoId.hpp rename to src/libs/database/include/database/objects/ArtistInfoId.hpp diff --git a/src/libs/database/include/database/Artwork.hpp b/src/libs/database/include/database/objects/Artwork.hpp similarity index 89% rename from src/libs/database/include/database/Artwork.hpp rename to src/libs/database/include/database/objects/Artwork.hpp index 581dcc00..50b7626a 100644 --- a/src/libs/database/include/database/Artwork.hpp +++ b/src/libs/database/include/database/objects/Artwork.hpp @@ -19,13 +19,15 @@ #pragma once -#include +#include + +#include #include -#include "database/ArtworkId.hpp" -#include "database/ImageId.hpp" #include "database/Object.hpp" -#include "database/TrackEmbeddedImageId.hpp" +#include "database/objects/ArtworkId.hpp" +#include "database/objects/ImageId.hpp" +#include "database/objects/TrackEmbeddedImageId.hpp" namespace lms::db { @@ -48,6 +50,7 @@ namespace lms::db TrackEmbeddedImageId getTrackEmbeddedImageId() const { return _trackEmbeddedImage.id(); } ImageId getImageId() const { return _image.id(); } Wt::WDateTime getLastWrittenTime() const; + std::filesystem::path getAbsoluteFilePath() const; template void persist(Action& a) diff --git a/src/libs/database/include/database/ArtworkId.hpp b/src/libs/database/include/database/objects/ArtworkId.hpp similarity index 100% rename from src/libs/database/include/database/ArtworkId.hpp rename to src/libs/database/include/database/objects/ArtworkId.hpp diff --git a/src/libs/database/include/database/AuthToken.hpp b/src/libs/database/include/database/objects/AuthToken.hpp similarity index 96% rename from src/libs/database/include/database/AuthToken.hpp rename to src/libs/database/include/database/objects/AuthToken.hpp index 720716aa..755d468d 100644 --- a/src/libs/database/include/database/AuthToken.hpp +++ b/src/libs/database/include/database/objects/AuthToken.hpp @@ -22,13 +22,12 @@ #include #include -#include +#include #include -#include "database/AuthTokenId.hpp" #include "database/Object.hpp" -#include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/AuthTokenId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/AuthTokenId.hpp b/src/libs/database/include/database/objects/AuthTokenId.hpp similarity index 100% rename from src/libs/database/include/database/AuthTokenId.hpp rename to src/libs/database/include/database/objects/AuthTokenId.hpp diff --git a/src/libs/database/include/database/Cluster.hpp b/src/libs/database/include/database/objects/Cluster.hpp similarity index 97% rename from src/libs/database/include/database/Cluster.hpp rename to src/libs/database/include/database/objects/Cluster.hpp index 471e1c8e..f30e343d 100644 --- a/src/libs/database/include/database/Cluster.hpp +++ b/src/libs/database/include/database/objects/Cluster.hpp @@ -22,17 +22,17 @@ #include #include #include -#include #include -#include +#include +#include #include -#include "database/ClusterId.hpp" #include "database/Object.hpp" -#include "database/Release.hpp" -#include "database/TrackId.hpp" #include "database/Types.hpp" +#include "database/objects/ClusterId.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/TrackId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/ClusterId.hpp b/src/libs/database/include/database/objects/ClusterId.hpp similarity index 100% rename from src/libs/database/include/database/ClusterId.hpp rename to src/libs/database/include/database/objects/ClusterId.hpp diff --git a/src/libs/database/include/database/CountryId.hpp b/src/libs/database/include/database/objects/CountryId.hpp similarity index 100% rename from src/libs/database/include/database/CountryId.hpp rename to src/libs/database/include/database/objects/CountryId.hpp diff --git a/src/libs/database/include/database/Directory.hpp b/src/libs/database/include/database/objects/Directory.hpp similarity index 96% rename from src/libs/database/include/database/Directory.hpp rename to src/libs/database/include/database/objects/Directory.hpp index 45f17743..58390b03 100644 --- a/src/libs/database/include/database/Directory.hpp +++ b/src/libs/database/include/database/objects/Directory.hpp @@ -21,19 +21,20 @@ #include #include +#include #include #include #include -#include +#include #include "core/EnumSet.hpp" -#include "database/ArtistId.hpp" -#include "database/DirectoryId.hpp" -#include "database/MediaLibraryId.hpp" #include "database/Object.hpp" -#include "database/ReleaseId.hpp" #include "database/Types.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/DirectoryId.hpp" +#include "database/objects/MediaLibraryId.hpp" +#include "database/objects/ReleaseId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/DirectoryId.hpp b/src/libs/database/include/database/objects/DirectoryId.hpp similarity index 100% rename from src/libs/database/include/database/DirectoryId.hpp rename to src/libs/database/include/database/objects/DirectoryId.hpp diff --git a/src/libs/database/include/database/Filters.hpp b/src/libs/database/include/database/objects/Filters.hpp similarity index 90% rename from src/libs/database/include/database/Filters.hpp rename to src/libs/database/include/database/objects/Filters.hpp index d287021c..1d95a09f 100644 --- a/src/libs/database/include/database/Filters.hpp +++ b/src/libs/database/include/database/objects/Filters.hpp @@ -22,10 +22,10 @@ #include #include -#include "database/ClusterId.hpp" -#include "database/LabelId.hpp" -#include "database/MediaLibraryId.hpp" -#include "database/ReleaseTypeId.hpp" +#include "database/objects/ClusterId.hpp" +#include "database/objects/LabelId.hpp" +#include "database/objects/MediaLibraryId.hpp" +#include "database/objects/ReleaseTypeId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/Image.hpp b/src/libs/database/include/database/objects/Image.hpp similarity index 90% rename from src/libs/database/include/database/Image.hpp rename to src/libs/database/include/database/objects/Image.hpp index 5361fabc..da4c48bf 100644 --- a/src/libs/database/include/database/Image.hpp +++ b/src/libs/database/include/database/objects/Image.hpp @@ -21,14 +21,15 @@ #include #include +#include -#include +#include #include -#include "database/DirectoryId.hpp" -#include "database/ImageId.hpp" #include "database/Object.hpp" #include "database/Types.hpp" +#include "database/objects/DirectoryId.hpp" +#include "database/objects/ImageId.hpp" namespace lms::db { @@ -69,7 +70,8 @@ namespace lms::db static pointer find(Session& session, const std::filesystem::path& file); static RangeResults find(Session& session, const FindParameters& params); static void find(Session& session, const FindParameters& parameters, const std::function& func); - static void find(Session& session, ImageId& lastRetrievedImage, std::size_t count, const std::function& func); + static void find(Session& session, ImageId& lastRetrievedId, std::size_t count, const std::function& func); + static void findAbsoluteFilePath(Session& session, ImageId& lastRetrievedId, std::size_t count, const std::function& func); // getters const std::filesystem::path& getAbsoluteFilePath() const { return _fileAbsolutePath; } diff --git a/src/libs/database/include/database/ImageId.hpp b/src/libs/database/include/database/objects/ImageId.hpp similarity index 100% rename from src/libs/database/include/database/ImageId.hpp rename to src/libs/database/include/database/objects/ImageId.hpp diff --git a/src/libs/database/include/database/LabelId.hpp b/src/libs/database/include/database/objects/LabelId.hpp similarity index 100% rename from src/libs/database/include/database/LabelId.hpp rename to src/libs/database/include/database/objects/LabelId.hpp diff --git a/src/libs/database/include/database/Listen.hpp b/src/libs/database/include/database/objects/Listen.hpp similarity index 96% rename from src/libs/database/include/database/Listen.hpp rename to src/libs/database/include/database/objects/Listen.hpp index dcbb32c7..b6882fa2 100644 --- a/src/libs/database/include/database/Listen.hpp +++ b/src/libs/database/include/database/objects/Listen.hpp @@ -21,17 +21,17 @@ #include -#include +#include #include -#include "database/ArtistId.hpp" -#include "database/Filters.hpp" -#include "database/ListenId.hpp" #include "database/Object.hpp" -#include "database/ReleaseId.hpp" -#include "database/TrackId.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/Filters.hpp" +#include "database/objects/ListenId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/ListenId.hpp b/src/libs/database/include/database/objects/ListenId.hpp similarity index 100% rename from src/libs/database/include/database/ListenId.hpp rename to src/libs/database/include/database/objects/ListenId.hpp diff --git a/src/libs/database/include/database/MediaLibrary.hpp b/src/libs/database/include/database/objects/MediaLibrary.hpp similarity index 96% rename from src/libs/database/include/database/MediaLibrary.hpp rename to src/libs/database/include/database/objects/MediaLibrary.hpp index 0649674e..484f4e5a 100644 --- a/src/libs/database/include/database/MediaLibrary.hpp +++ b/src/libs/database/include/database/objects/MediaLibrary.hpp @@ -20,13 +20,14 @@ #pragma once #include +#include #include #include -#include +#include -#include "database/MediaLibraryId.hpp" #include "database/Object.hpp" +#include "database/objects/MediaLibraryId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/MediaLibraryId.hpp b/src/libs/database/include/database/objects/MediaLibraryId.hpp similarity index 100% rename from src/libs/database/include/database/MediaLibraryId.hpp rename to src/libs/database/include/database/objects/MediaLibraryId.hpp diff --git a/src/libs/database/include/database/PlayListFile.hpp b/src/libs/database/include/database/objects/PlayListFile.hpp similarity index 86% rename from src/libs/database/include/database/PlayListFile.hpp rename to src/libs/database/include/database/objects/PlayListFile.hpp index 0eaccae3..c6443d0e 100644 --- a/src/libs/database/include/database/PlayListFile.hpp +++ b/src/libs/database/include/database/objects/PlayListFile.hpp @@ -24,11 +24,14 @@ #include #include -#include +#include +#include +#include #include -#include "database/DirectoryId.hpp" +#include "database/IdRange.hpp" #include "database/Object.hpp" +#include "database/objects/DirectoryId.hpp" LMS_DECLARE_IDTYPE(PlayListFileId) @@ -49,6 +52,9 @@ namespace lms::db static pointer find(Session& session, PlayListFileId id); static pointer find(Session& session, const std::filesystem::path& path); static void find(Session& session, PlayListFileId& lastRetrievedId, std::size_t count, const std::function& func); + static void findAbsoluteFilePath(Session& session, PlayListFileId& lastRetrievedId, std::size_t count, const std::function& func); + static void find(Session& session, const IdRange& idRange, const std::function& func); + static IdRange findNextIdRange(Session& session, PlayListFileId lastRetrievedId, std::size_t count); // getters const std::filesystem::path& getAbsoluteFilePath() const { return _absoluteFilePath; } diff --git a/src/libs/database/include/database/PlayQueue.hpp b/src/libs/database/include/database/objects/PlayQueue.hpp similarity index 96% rename from src/libs/database/include/database/PlayQueue.hpp rename to src/libs/database/include/database/objects/PlayQueue.hpp index a3dcbaee..845db082 100644 --- a/src/libs/database/include/database/PlayQueue.hpp +++ b/src/libs/database/include/database/objects/PlayQueue.hpp @@ -20,17 +20,16 @@ #pragma once #include -#include #include #include -#include +#include +#include #include #include "database/Object.hpp" -#include "database/TrackId.hpp" -#include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/UserId.hpp" LMS_DECLARE_IDTYPE(PlayQueueId) diff --git a/src/libs/database/include/database/RatedArtist.hpp b/src/libs/database/include/database/objects/RatedArtist.hpp similarity index 94% rename from src/libs/database/include/database/RatedArtist.hpp rename to src/libs/database/include/database/objects/RatedArtist.hpp index 7e846b67..0a336d08 100644 --- a/src/libs/database/include/database/RatedArtist.hpp +++ b/src/libs/database/include/database/objects/RatedArtist.hpp @@ -19,14 +19,16 @@ #pragma once -#include +#include + +#include #include -#include "database/ArtistId.hpp" #include "database/Object.hpp" -#include "database/RatedArtistId.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/RatedArtistId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/RatedArtistId.hpp b/src/libs/database/include/database/objects/RatedArtistId.hpp similarity index 100% rename from src/libs/database/include/database/RatedArtistId.hpp rename to src/libs/database/include/database/objects/RatedArtistId.hpp diff --git a/src/libs/database/include/database/RatedRelease.hpp b/src/libs/database/include/database/objects/RatedRelease.hpp similarity index 94% rename from src/libs/database/include/database/RatedRelease.hpp rename to src/libs/database/include/database/objects/RatedRelease.hpp index 54a12e1c..4096f380 100644 --- a/src/libs/database/include/database/RatedRelease.hpp +++ b/src/libs/database/include/database/objects/RatedRelease.hpp @@ -19,14 +19,16 @@ #pragma once -#include +#include + +#include #include #include "database/Object.hpp" -#include "database/RatedReleaseId.hpp" -#include "database/ReleaseId.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/RatedReleaseId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/RatedReleaseId.hpp b/src/libs/database/include/database/objects/RatedReleaseId.hpp similarity index 100% rename from src/libs/database/include/database/RatedReleaseId.hpp rename to src/libs/database/include/database/objects/RatedReleaseId.hpp diff --git a/src/libs/database/include/database/RatedTrack.hpp b/src/libs/database/include/database/objects/RatedTrack.hpp similarity index 94% rename from src/libs/database/include/database/RatedTrack.hpp rename to src/libs/database/include/database/objects/RatedTrack.hpp index bdc2a6c6..de944e5b 100644 --- a/src/libs/database/include/database/RatedTrack.hpp +++ b/src/libs/database/include/database/objects/RatedTrack.hpp @@ -19,14 +19,16 @@ #pragma once -#include +#include + +#include #include #include "database/Object.hpp" -#include "database/RatedTrackId.hpp" -#include "database/TrackId.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/RatedTrackId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/RatedTrackId.hpp b/src/libs/database/include/database/objects/RatedTrackId.hpp similarity index 100% rename from src/libs/database/include/database/RatedTrackId.hpp rename to src/libs/database/include/database/objects/RatedTrackId.hpp diff --git a/src/libs/database/include/database/Release.hpp b/src/libs/database/include/database/objects/Release.hpp similarity index 94% rename from src/libs/database/include/database/Release.hpp rename to src/libs/database/include/database/objects/Release.hpp index 1f55173a..1f8085c0 100644 --- a/src/libs/database/include/database/Release.hpp +++ b/src/libs/database/include/database/objects/Release.hpp @@ -24,24 +24,26 @@ #include #include -#include +#include +#include #include #include "core/EnumSet.hpp" #include "core/PartialDateTime.hpp" #include "core/UUID.hpp" -#include "database/ArtistId.hpp" -#include "database/ArtworkId.hpp" -#include "database/CountryId.hpp" -#include "database/DirectoryId.hpp" -#include "database/Filters.hpp" -#include "database/LabelId.hpp" -#include "database/MediaLibraryId.hpp" +#include "database/IdRange.hpp" #include "database/Object.hpp" -#include "database/ReleaseId.hpp" -#include "database/ReleaseTypeId.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/ArtworkId.hpp" +#include "database/objects/CountryId.hpp" +#include "database/objects/DirectoryId.hpp" +#include "database/objects/Filters.hpp" +#include "database/objects/LabelId.hpp" +#include "database/objects/MediaLibraryId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/ReleaseTypeId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { @@ -249,12 +251,17 @@ namespace lms::db static pointer find(Session& session, const core::UUID& MBID); static pointer find(Session& session, ReleaseId id); static void find(Session& session, ReleaseId& lastRetrievedRelease, std::size_t count, const std::function& func, MediaLibraryId library = {}); + static void find(Session& session, const IdRange& idRange, const std::function& func); + static IdRange findNextIdRange(Session& session, ReleaseId lastRetrievedId, std::size_t count); static RangeResults find(Session& session, const FindParameters& parameters); static void find(Session& session, const FindParameters& parameters, const std::function& func); static RangeResults findIds(Session& session, const FindParameters& parameters); static std::size_t getCount(Session& session, const FindParameters& parameters); static RangeResults findOrphanIds(Session& session, std::optional range = std::nullopt); // not track related + // Updates + static void updatePreferredArtwork(Session& session, ReleaseId id, ArtworkId artworkId); + // Get the cluster of the tracks that belong to this release // Each clusters are grouped by cluster type, sorted by the number of occurence (max to min) // size is the max number of cluster per cluster type diff --git a/src/libs/database/include/database/ReleaseId.hpp b/src/libs/database/include/database/objects/ReleaseId.hpp similarity index 100% rename from src/libs/database/include/database/ReleaseId.hpp rename to src/libs/database/include/database/objects/ReleaseId.hpp diff --git a/src/libs/database/include/database/ReleaseTypeId.hpp b/src/libs/database/include/database/objects/ReleaseTypeId.hpp similarity index 100% rename from src/libs/database/include/database/ReleaseTypeId.hpp rename to src/libs/database/include/database/objects/ReleaseTypeId.hpp diff --git a/src/libs/database/include/database/ScanSettings.hpp b/src/libs/database/include/database/objects/ScanSettings.hpp similarity index 99% rename from src/libs/database/include/database/ScanSettings.hpp rename to src/libs/database/include/database/objects/ScanSettings.hpp index 4da2ef94..83f3e7ac 100644 --- a/src/libs/database/include/database/ScanSettings.hpp +++ b/src/libs/database/include/database/objects/ScanSettings.hpp @@ -24,7 +24,7 @@ #include #include -#include +#include #include #include "database/IdType.hpp" diff --git a/src/libs/database/include/database/StarredArtist.hpp b/src/libs/database/include/database/objects/StarredArtist.hpp similarity index 94% rename from src/libs/database/include/database/StarredArtist.hpp rename to src/libs/database/include/database/objects/StarredArtist.hpp index 0e05137e..3bca8939 100644 --- a/src/libs/database/include/database/StarredArtist.hpp +++ b/src/libs/database/include/database/objects/StarredArtist.hpp @@ -19,14 +19,14 @@ #pragma once -#include +#include #include -#include "database/ArtistId.hpp" #include "database/Object.hpp" -#include "database/StarredArtistId.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/StarredArtistId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/StarredArtistId.hpp b/src/libs/database/include/database/objects/StarredArtistId.hpp similarity index 100% rename from src/libs/database/include/database/StarredArtistId.hpp rename to src/libs/database/include/database/objects/StarredArtistId.hpp diff --git a/src/libs/database/include/database/StarredRelease.hpp b/src/libs/database/include/database/objects/StarredRelease.hpp similarity index 94% rename from src/libs/database/include/database/StarredRelease.hpp rename to src/libs/database/include/database/objects/StarredRelease.hpp index 7894f990..6f172d38 100644 --- a/src/libs/database/include/database/StarredRelease.hpp +++ b/src/libs/database/include/database/objects/StarredRelease.hpp @@ -19,14 +19,14 @@ #pragma once -#include +#include #include #include "database/Object.hpp" -#include "database/ReleaseId.hpp" -#include "database/StarredReleaseId.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/StarredReleaseId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/StarredReleaseId.hpp b/src/libs/database/include/database/objects/StarredReleaseId.hpp similarity index 100% rename from src/libs/database/include/database/StarredReleaseId.hpp rename to src/libs/database/include/database/objects/StarredReleaseId.hpp diff --git a/src/libs/database/include/database/StarredTrack.hpp b/src/libs/database/include/database/objects/StarredTrack.hpp similarity index 95% rename from src/libs/database/include/database/StarredTrack.hpp rename to src/libs/database/include/database/objects/StarredTrack.hpp index 05d3490d..352b1ba7 100644 --- a/src/libs/database/include/database/StarredTrack.hpp +++ b/src/libs/database/include/database/objects/StarredTrack.hpp @@ -19,14 +19,16 @@ #pragma once -#include +#include + +#include #include #include "database/Object.hpp" -#include "database/StarredTrackId.hpp" -#include "database/TrackId.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/StarredTrackId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/StarredTrackId.hpp b/src/libs/database/include/database/objects/StarredTrackId.hpp similarity index 100% rename from src/libs/database/include/database/StarredTrackId.hpp rename to src/libs/database/include/database/objects/StarredTrackId.hpp diff --git a/src/libs/database/include/database/Track.hpp b/src/libs/database/include/database/objects/Track.hpp similarity index 92% rename from src/libs/database/include/database/Track.hpp rename to src/libs/database/include/database/objects/Track.hpp index f7b4cf80..39c5b624 100644 --- a/src/libs/database/include/database/Track.hpp +++ b/src/libs/database/include/database/objects/Track.hpp @@ -27,26 +27,27 @@ #include #include -#include -#include +#include +#include #include #include "core/EnumSet.hpp" #include "core/PartialDateTime.hpp" #include "core/UUID.hpp" -#include "database/ArtistId.hpp" -#include "database/ArtworkId.hpp" -#include "database/ClusterId.hpp" -#include "database/DirectoryId.hpp" -#include "database/Filters.hpp" -#include "database/MediaLibraryId.hpp" +#include "database/IdRange.hpp" #include "database/Object.hpp" -#include "database/ReleaseId.hpp" -#include "database/TrackEmbeddedImageId.hpp" -#include "database/TrackId.hpp" -#include "database/TrackListId.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/ArtworkId.hpp" +#include "database/objects/ClusterId.hpp" +#include "database/objects/DirectoryId.hpp" +#include "database/objects/Filters.hpp" +#include "database/objects/MediaLibraryId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackEmbeddedImageId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/TrackListId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { @@ -72,8 +73,6 @@ namespace lms::db Filters filters; std::vector keywords; // if non empty, name must match all of these keywords std::string name; // if non empty, must match this name (title) - std::string fileStem; // if non empty, must match this file stem - std::string fileName; // if non empty, must match this file name TrackSortMethod sortMethod{ TrackSortMethod::None }; std::optional range; Wt::WDateTime writtenAfter; @@ -107,17 +106,6 @@ namespace lms::db name = _name; return *this; } - FindParameters& setFileStem(std::string_view _fileStem) - { - fileStem = _fileStem; - return *this; - } - FindParameters& setFileName(std::string_view _fileName) - { - fileName = _fileName; - return *this; - } - FindParameters& setSortMethod(TrackSortMethod _method) { sortMethod = _method; @@ -203,8 +191,13 @@ namespace lms::db // Find utility functions static std::size_t getCount(Session& session); static pointer findByPath(Session& session, const std::filesystem::path& p); + static std::optional findFileInfo(Session& session, const std::filesystem::path& p); static pointer find(Session& session, TrackId id); - static void find(Session& session, TrackId& lastRetrievedTrack, std::size_t count, const std::function& func, MediaLibraryId library = {}); + static void find(Session& session, TrackId& lastRetrievedId, std::size_t count, const std::function& func, MediaLibraryId library = {}); + static void find(Session& session, const IdRange& idRange, const std::function& func); + static IdRange findNextIdRange(Session& session, TrackId lastRetrievedId, std::size_t count); + static void findAbsoluteFilePath(Session& session, TrackId& lastRetrievedId, std::size_t count, const std::function& func); + static bool exists(Session& session, TrackId id); static std::vector findByRecordingMBID(Session& session, const core::UUID& MBID); static std::vector findByMBID(Session& session, const core::UUID& MBID); @@ -229,7 +222,6 @@ namespace lms::db void setDiscSubtitle(std::string_view name) { _discSubtitle = name; } void setName(std::string_view name); void setAbsoluteFilePath(const std::filesystem::path& filePath); - void setRelativeFilePath(const std::filesystem::path& filePath); void setFileSize(std::size_t fileSize) { _fileSize = fileSize; } void setLastWriteTime(const Wt::WDateTime& time) { _fileLastWrite = time; } void setAddedTime(const Wt::WDateTime& time) { _fileAdded = time; } @@ -270,7 +262,6 @@ namespace lms::db const std::string& getDiscSubtitle() const { return _discSubtitle; } std::string getName() const { return _name; } const std::filesystem::path& getAbsoluteFilePath() const { return _absoluteFilePath; } - const std::filesystem::path& getRelativeFilePath() const { return _relativeFilePath; } long long getFileSize() const { return _fileSize; } std::size_t getBitrate() const { return _bitrate; } std::size_t getBitsPerSample() const { return _bitsPerSample; } @@ -329,9 +320,6 @@ namespace lms::db Wt::Dbo::field(a, _date, "date"); Wt::Dbo::field(a, _originalDate, "original_date"); Wt::Dbo::field(a, _absoluteFilePath, "absolute_file_path"); - Wt::Dbo::field(a, _relativeFilePath, "relative_file_path"); - Wt::Dbo::field(a, _fileStem, "file_stem"); - Wt::Dbo::field(a, _fileName, "file_name"); Wt::Dbo::field(a, _fileSize, "file_size"); Wt::Dbo::field(a, _fileLastWrite, "file_last_write"); Wt::Dbo::field(a, _fileAdded, "file_added"); @@ -378,9 +366,6 @@ namespace lms::db core::PartialDateTime _date; core::PartialDateTime _originalDate; std::filesystem::path _absoluteFilePath; // full path - std::filesystem::path _relativeFilePath; // relative to root (that may be deleted) - std::filesystem::path _fileStem; - std::filesystem::path _fileName; long long _fileSize{}; Wt::WDateTime _fileLastWrite; Wt::WDateTime _fileAdded; diff --git a/src/libs/database/include/database/TrackArtistLink.hpp b/src/libs/database/include/database/objects/TrackArtistLink.hpp similarity index 97% rename from src/libs/database/include/database/TrackArtistLink.hpp rename to src/libs/database/include/database/objects/TrackArtistLink.hpp index 47547f3b..1f9e6117 100644 --- a/src/libs/database/include/database/TrackArtistLink.hpp +++ b/src/libs/database/include/database/objects/TrackArtistLink.hpp @@ -23,15 +23,15 @@ #include #include -#include +#include #include "core/EnumSet.hpp" -#include "database/ArtistId.hpp" #include "database/IdType.hpp" #include "database/Object.hpp" -#include "database/ReleaseId.hpp" -#include "database/TrackId.hpp" #include "database/Types.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackId.hpp" LMS_DECLARE_IDTYPE(TrackArtistLinkId) diff --git a/src/libs/database/include/database/TrackBookmark.hpp b/src/libs/database/include/database/objects/TrackBookmark.hpp similarity index 96% rename from src/libs/database/include/database/TrackBookmark.hpp rename to src/libs/database/include/database/objects/TrackBookmark.hpp index 61a606a2..833747e8 100644 --- a/src/libs/database/include/database/TrackBookmark.hpp +++ b/src/libs/database/include/database/objects/TrackBookmark.hpp @@ -22,13 +22,13 @@ #include #include -#include +#include #include "database/IdType.hpp" #include "database/Object.hpp" -#include "database/TrackId.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/UserId.hpp" LMS_DECLARE_IDTYPE(TrackBookmarkId) diff --git a/src/libs/database/include/database/TrackEmbeddedImage.hpp b/src/libs/database/include/database/objects/TrackEmbeddedImage.hpp similarity index 91% rename from src/libs/database/include/database/TrackEmbeddedImage.hpp rename to src/libs/database/include/database/objects/TrackEmbeddedImage.hpp index 47e8fae8..c9ea94a3 100644 --- a/src/libs/database/include/database/TrackEmbeddedImage.hpp +++ b/src/libs/database/include/database/objects/TrackEmbeddedImage.hpp @@ -23,16 +23,17 @@ #include #include -#include +#include +#include #include "core/EnumSet.hpp" -#include "database/ArtistId.hpp" #include "database/Object.hpp" -#include "database/ReleaseId.hpp" -#include "database/TrackEmbeddedImageId.hpp" -#include "database/TrackId.hpp" -#include "database/TrackListId.hpp" #include "database/Types.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackEmbeddedImageId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/TrackListId.hpp" namespace lms::db { @@ -54,7 +55,7 @@ namespace lms::db ReleaseId release; std::optional discNumber; TrackListId trackList; - core::EnumSet imageTypes; + std::optional imageType; TrackEmbeddedImageSortMethod sortMethod{ TrackEmbeddedImageSortMethod::None }; FindParameters& setRange(std::optional _range) @@ -89,9 +90,9 @@ namespace lms::db trackList = _trackList; return *this; } - FindParameters& setImageTypes(core::EnumSet _imageTypes) + FindParameters& setImageType(std::optional _imageType) { - imageTypes = _imageTypes; + imageType = _imageType; return *this; } FindParameters& setSortMethod(TrackEmbeddedImageSortMethod _sortMethod) diff --git a/src/libs/database/include/database/TrackEmbeddedImageId.hpp b/src/libs/database/include/database/objects/TrackEmbeddedImageId.hpp similarity index 100% rename from src/libs/database/include/database/TrackEmbeddedImageId.hpp rename to src/libs/database/include/database/objects/TrackEmbeddedImageId.hpp diff --git a/src/libs/database/include/database/TrackEmbeddedImageLink.hpp b/src/libs/database/include/database/objects/TrackEmbeddedImageLink.hpp similarity index 94% rename from src/libs/database/include/database/TrackEmbeddedImageLink.hpp rename to src/libs/database/include/database/objects/TrackEmbeddedImageLink.hpp index 77dd0d2e..ea670888 100644 --- a/src/libs/database/include/database/TrackEmbeddedImageLink.hpp +++ b/src/libs/database/include/database/objects/TrackEmbeddedImageLink.hpp @@ -22,12 +22,12 @@ #include #include -#include +#include #include "database/Object.hpp" -#include "database/TrackEmbeddedImageId.hpp" -#include "database/TrackEmbeddedImageLinkId.hpp" #include "database/Types.hpp" +#include "database/objects/TrackEmbeddedImageId.hpp" +#include "database/objects/TrackEmbeddedImageLinkId.hpp" namespace lms::db { @@ -40,8 +40,6 @@ namespace lms::db public: TrackEmbeddedImageLink() = default; - using HashType = std::uint64_t; - // find static std::size_t getCount(Session& session); static pointer find(Session& session, TrackEmbeddedImageLinkId id); diff --git a/src/libs/database/include/database/TrackEmbeddedImageLinkId.hpp b/src/libs/database/include/database/objects/TrackEmbeddedImageLinkId.hpp similarity index 100% rename from src/libs/database/include/database/TrackEmbeddedImageLinkId.hpp rename to src/libs/database/include/database/objects/TrackEmbeddedImageLinkId.hpp diff --git a/src/libs/database/include/database/TrackFeatures.hpp b/src/libs/database/include/database/objects/TrackFeatures.hpp similarity index 97% rename from src/libs/database/include/database/TrackFeatures.hpp rename to src/libs/database/include/database/objects/TrackFeatures.hpp index 4ff8e1e9..bea09c34 100644 --- a/src/libs/database/include/database/TrackFeatures.hpp +++ b/src/libs/database/include/database/objects/TrackFeatures.hpp @@ -25,12 +25,12 @@ #include #include -#include +#include #include "database/IdType.hpp" #include "database/Object.hpp" -#include "database/TrackId.hpp" #include "database/Types.hpp" +#include "database/objects/TrackId.hpp" LMS_DECLARE_IDTYPE(TrackFeaturesId) diff --git a/src/libs/database/include/database/TrackId.hpp b/src/libs/database/include/database/objects/TrackId.hpp similarity index 100% rename from src/libs/database/include/database/TrackId.hpp rename to src/libs/database/include/database/objects/TrackId.hpp diff --git a/src/libs/database/include/database/TrackList.hpp b/src/libs/database/include/database/objects/TrackList.hpp similarity index 97% rename from src/libs/database/include/database/TrackList.hpp rename to src/libs/database/include/database/objects/TrackList.hpp index 57c10b7c..37647203 100644 --- a/src/libs/database/include/database/TrackList.hpp +++ b/src/libs/database/include/database/objects/TrackList.hpp @@ -24,15 +24,16 @@ #include #include -#include +#include +#include #include -#include "database/Filters.hpp" #include "database/Object.hpp" -#include "database/TrackId.hpp" -#include "database/TrackListId.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/Filters.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/TrackListId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/TrackListId.hpp b/src/libs/database/include/database/objects/TrackListId.hpp similarity index 100% rename from src/libs/database/include/database/TrackListId.hpp rename to src/libs/database/include/database/objects/TrackListId.hpp diff --git a/src/libs/database/include/database/TrackLyrics.hpp b/src/libs/database/include/database/objects/TrackLyrics.hpp similarity index 95% rename from src/libs/database/include/database/TrackLyrics.hpp rename to src/libs/database/include/database/objects/TrackLyrics.hpp index fbb49629..dd7c5c9c 100644 --- a/src/libs/database/include/database/TrackLyrics.hpp +++ b/src/libs/database/include/database/objects/TrackLyrics.hpp @@ -22,15 +22,16 @@ #include #include #include +#include #include #include -#include +#include #include "database/IdType.hpp" #include "database/Object.hpp" -#include "database/TrackId.hpp" #include "database/Types.hpp" +#include "database/objects/TrackId.hpp" LMS_DECLARE_IDTYPE(TrackLyricsId) @@ -82,6 +83,7 @@ namespace lms::db static void find(Session& session, const FindParameters& params, const std::function& func); static void find(Session& session, TrackLyricsId& lastRetrievedId, std::size_t count, const std::function& func); static RangeResults findOrphanIds(Session& session, std::optional range); + static void findAbsoluteFilePath(Session& session, TrackLyricsId& lastRetrievedId, std::size_t count, const std::function& func); using SynchronizedLines = std::map; diff --git a/src/libs/database/include/database/UIState.hpp b/src/libs/database/include/database/objects/UIState.hpp similarity index 92% rename from src/libs/database/include/database/UIState.hpp rename to src/libs/database/include/database/objects/UIState.hpp index 06455269..458350e4 100644 --- a/src/libs/database/include/database/UIState.hpp +++ b/src/libs/database/include/database/objects/UIState.hpp @@ -19,13 +19,14 @@ #pragma once -#include +#include +#include + +#include -#include "core/String.hpp" #include "database/Object.hpp" -#include "database/Types.hpp" -#include "database/UIStateId.hpp" -#include "database/UserId.hpp" +#include "database/objects/UIStateId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/UIStateId.hpp b/src/libs/database/include/database/objects/UIStateId.hpp similarity index 100% rename from src/libs/database/include/database/UIStateId.hpp rename to src/libs/database/include/database/objects/UIStateId.hpp diff --git a/src/libs/database/include/database/User.hpp b/src/libs/database/include/database/objects/User.hpp similarity index 99% rename from src/libs/database/include/database/User.hpp rename to src/libs/database/include/database/objects/User.hpp index 4bf2c4fc..ba731886 100644 --- a/src/libs/database/include/database/User.hpp +++ b/src/libs/database/include/database/objects/User.hpp @@ -22,14 +22,15 @@ #include #include -#include +#include +#include #include #include "core/EnumSet.hpp" #include "core/UUID.hpp" #include "database/Object.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { diff --git a/src/libs/database/include/database/UserId.hpp b/src/libs/database/include/database/objects/UserId.hpp similarity index 100% rename from src/libs/database/include/database/UserId.hpp rename to src/libs/database/include/database/objects/UserId.hpp diff --git a/src/libs/database/test/Artist.cpp b/src/libs/database/test/Artist.cpp index 1b82997b..69abbfd0 100644 --- a/src/libs/database/test/Artist.cpp +++ b/src/libs/database/test/Artist.cpp @@ -19,8 +19,8 @@ #include "Common.hpp" -#include "database/Artwork.hpp" -#include "database/Image.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Image.hpp" namespace lms::db::tests { @@ -608,6 +608,93 @@ namespace lms::db::tests } } + TEST_F(DatabaseFixture, Artist_findNextIdRange) + { + { + auto transaction{ session.createReadTransaction() }; + + auto range{ Artist::findNextIdRange(session, ArtistId{}, 0) }; + EXPECT_FALSE(range.isValid()); + EXPECT_EQ(range.first, ArtistId{}); + EXPECT_EQ(range.last, ArtistId{}); + + range = Artist::findNextIdRange(session, ArtistId{}, 100); + EXPECT_FALSE(range.isValid()); + EXPECT_EQ(range.first, ArtistId{}); + EXPECT_EQ(range.last, ArtistId{}); + } + + ScopedArtist artist1{ session, "Artist1" }; + { + auto transaction{ session.createReadTransaction() }; + + auto range{ Artist::findNextIdRange(session, ArtistId{}, 0) }; + EXPECT_FALSE(range.isValid()); + EXPECT_EQ(range.first, ArtistId{}); + EXPECT_EQ(range.last, ArtistId{}); + + range = Artist::findNextIdRange(session, ArtistId{}, 1); + EXPECT_TRUE(range.isValid()); + EXPECT_EQ(range.first, artist1.getId()); + EXPECT_EQ(range.last, artist1.getId()); + + range = Artist::findNextIdRange(session, range.last, 1); + EXPECT_FALSE(range.isValid()); + EXPECT_EQ(range.first, ArtistId{}); + EXPECT_EQ(range.last, ArtistId{}); + + range = Artist::findNextIdRange(session, ArtistId{}, 100); + EXPECT_TRUE(range.isValid()); + EXPECT_EQ(range.first, artist1.getId()); + EXPECT_EQ(range.last, artist1.getId()); + } + + ScopedArtist artist2{ session, "Artist2" }; + ScopedArtist artist3{ session, "Artist3" }; + + { + auto transaction{ session.createReadTransaction() }; + + auto range{ Artist::findNextIdRange(session, ArtistId{}, 2) }; + EXPECT_TRUE(range.isValid()); + EXPECT_EQ(range.first, artist1.getId()); + EXPECT_EQ(range.last, artist2.getId()); + + range = Artist::findNextIdRange(session, artist2.getId(), 2); + EXPECT_TRUE(range.isValid()); + EXPECT_EQ(range.first, artist3.getId()); + EXPECT_EQ(range.last, artist3.getId()); + } + } + + TEST_F(DatabaseFixture, Artist_findByRange) + { + ScopedArtist artist1{ session, "Artist1" }; + ScopedArtist artist2{ session, "Artist2" }; + ScopedArtist artist3{ session, "Artist3" }; + + { + auto transaction{ session.createReadTransaction() }; + + std::size_t count{}; + Artist::find(session, IdRange{ .first = artist1.getId(), .last = artist1.getId() }, [&](const db::Artist::pointer& artist) { + count++; + EXPECT_EQ(artist->getId(), artist1.getId()); + }); + EXPECT_EQ(count, 1); + } + + { + auto transaction{ session.createReadTransaction() }; + + std::size_t count{}; + Artist::find(session, IdRange{ .first = artist1.getId(), .last = artist3.getId() }, [&](const db::Artist::pointer&) { + count++; + }); + EXPECT_EQ(count, 3); + } + } + TEST_F(DatabaseFixture, Artist_sortMethod) { ScopedArtist artistA{ session, "artistA" }; @@ -817,4 +904,34 @@ namespace lms::db::tests EXPECT_EQ(artists.results[3], artistC.getId()); } } + + TEST_F(DatabaseFixture, Artist_updateArtwork) + { + ScopedArtist artist{ session, "MyArtist" }; + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(artist->getPreferredArtwork(), Artwork::pointer{}); + } + + ScopedImage image{ session, "/image1.jpg" }; + ScopedArtwork artwork{ session, image.lockAndGet() }; + + { + auto transaction{ session.createWriteTransaction() }; + Artist::updatePreferredArtwork(session, artist->getId(), artwork.getId()); + } + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(artist->getPreferredArtwork()->getId(), artwork.getId()); + } + + { + auto transaction{ session.createWriteTransaction() }; + Artist::updatePreferredArtwork(session, artist.getId(), ArtworkId{}); + } + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(artist->getPreferredArtwork(), Artwork::pointer{}); + } + } } // namespace lms::db::tests diff --git a/src/libs/database/test/ArtistInfo.cpp b/src/libs/database/test/ArtistInfo.cpp index ced776ed..706195ba 100644 --- a/src/libs/database/test/ArtistInfo.cpp +++ b/src/libs/database/test/ArtistInfo.cpp @@ -19,9 +19,9 @@ #include "Common.hpp" -#include "database/Artist.hpp" -#include "database/ArtistInfo.hpp" -#include "database/Directory.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/ArtistInfo.hpp" +#include "database/objects/Directory.hpp" namespace lms::db::tests { @@ -208,4 +208,29 @@ namespace lms::db::tests ASSERT_TRUE(visited); } } + + TEST_F(DatabaseFixture, ArtistInfo_findAbsoluteFilePath) + { + ScopedArtistInfo artistInfo{ session }; + + const std::filesystem::path absoluteFilePath{ "/path/to/artist.nfo" }; + { + auto transaction{ session.createWriteTransaction() }; + artistInfo.get().modify()->setAbsoluteFilePath(absoluteFilePath); + } + + { + auto transaction{ session.createReadTransaction() }; + + ArtistInfoId lastRetrievedId; + + std::filesystem::path retrievedFilePath; + ArtistInfo::findAbsoluteFilePath(session, lastRetrievedId, 1, [&](ArtistInfoId artistInfoId, const std::filesystem::path& filePath) { + EXPECT_EQ(artistInfoId, artistInfo.getId()); + retrievedFilePath = filePath; + }); + + EXPECT_EQ(retrievedFilePath, absoluteFilePath); + } + } } // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/Artwork.cpp b/src/libs/database/test/Artwork.cpp index 832aba0d..88c020cd 100644 --- a/src/libs/database/test/Artwork.cpp +++ b/src/libs/database/test/Artwork.cpp @@ -17,10 +17,10 @@ #include "Common.hpp" -#include "database/Artwork.hpp" -#include "database/Image.hpp" -#include "database/TrackEmbeddedImage.hpp" -#include "database/TrackEmbeddedImageLink.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Image.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" +#include "database/objects/TrackEmbeddedImageLink.hpp" namespace lms::db::tests { @@ -48,11 +48,13 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; image.get().modify()->setLastWriteTime(dateTime); + image.get().modify()->setAbsoluteFilePath("/tmp/foo"); } { auto transaction{ session.createReadTransaction() }; EXPECT_EQ(artwork.get()->getLastWrittenTime(), dateTime); + EXPECT_EQ(artwork.get()->getAbsoluteFilePath(), "/tmp/foo"); } } @@ -73,11 +75,13 @@ namespace lms::db::tests auto transaction{ session.createWriteTransaction() }; session.create(track.get(), image.get()); track.get().modify()->setLastWriteTime(dateTime); + track.get().modify()->setAbsoluteFilePath("/tmp/foo"); } { auto transaction{ session.createReadTransaction() }; EXPECT_EQ(artwork.get()->getLastWrittenTime(), dateTime); + EXPECT_EQ(artwork.get()->getAbsoluteFilePath(), "/tmp/foo"); } } } // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/AuthToken.cpp b/src/libs/database/test/AuthToken.cpp index 3cf6fc91..071fd358 100644 --- a/src/libs/database/test/AuthToken.cpp +++ b/src/libs/database/test/AuthToken.cpp @@ -19,7 +19,7 @@ #include "Common.hpp" -#include "database/AuthToken.hpp" +#include "database/objects/AuthToken.hpp" namespace lms::db::tests { diff --git a/src/libs/database/test/Common.cpp b/src/libs/database/test/Common.cpp index 2a59b829..b9377ff4 100644 --- a/src/libs/database/test/Common.cpp +++ b/src/libs/database/test/Common.cpp @@ -19,23 +19,22 @@ #include "Common.hpp" -#include "database/Db.hpp" -#include "database/Release.hpp" #include "database/Session.hpp" #include "database/Types.hpp" +#include "database/objects/Release.hpp" namespace lms::db::tests { TmpDatabase::TmpDatabase() : _tmpFile{ std::tmpnam(nullptr) } , _fileDeleter{ _tmpFile } - , _db{ _tmpFile } + , _db{ createDb(_tmpFile) } { } - db::Db& TmpDatabase::getDb() + db::IDb& TmpDatabase::getDb() { - return _db; + return *_db; } DatabaseFixture::~DatabaseFixture() diff --git a/src/libs/database/test/Common.hpp b/src/libs/database/test/Common.hpp index 45f53db5..7a025976 100644 --- a/src/libs/database/test/Common.hpp +++ b/src/libs/database/test/Common.hpp @@ -24,21 +24,21 @@ #include -#include "database/Artist.hpp" -#include "database/Cluster.hpp" -#include "database/Db.hpp" -#include "database/Listen.hpp" -#include "database/MediaLibrary.hpp" -#include "database/Release.hpp" -#include "database/ScanSettings.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackArtistLink.hpp" -#include "database/TrackBookmark.hpp" -#include "database/TrackFeatures.hpp" -#include "database/TrackList.hpp" #include "database/Types.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Listen.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/ScanSettings.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/TrackBookmark.hpp" +#include "database/objects/TrackFeatures.hpp" +#include "database/objects/TrackList.hpp" +#include "database/objects/User.hpp" namespace lms::db::tests { @@ -131,12 +131,12 @@ namespace lms::db::tests public: TmpDatabase(); - db::Db& getDb(); + IDb& getDb(); private: const std::filesystem::path _tmpFile; ScopedFileDeleter _fileDeleter; - db::Db _db; + std::unique_ptr _db; }; class DatabaseFixture : public ::testing::Test diff --git a/src/libs/database/test/Directory.cpp b/src/libs/database/test/Directory.cpp index 546a0de0..5f365d81 100644 --- a/src/libs/database/test/Directory.cpp +++ b/src/libs/database/test/Directory.cpp @@ -19,7 +19,7 @@ #include "Common.hpp" -#include "database/Directory.hpp" +#include "database/objects/Directory.hpp" namespace lms::db::tests { diff --git a/src/libs/database/test/Image.cpp b/src/libs/database/test/Image.cpp index b32cdd5f..a26340ad 100644 --- a/src/libs/database/test/Image.cpp +++ b/src/libs/database/test/Image.cpp @@ -19,8 +19,8 @@ #include "Common.hpp" -#include "database/Directory.hpp" -#include "database/Image.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/Image.hpp" namespace lms::db::tests { @@ -98,4 +98,28 @@ namespace lms::db::tests EXPECT_EQ(results.front()->getId(), image.getId()); } } + + TEST_F(DatabaseFixture, Image_findAbsoluteFilePath) + { + ScopedImage image{ session, "/path/to/image" }; + + const std::filesystem::path absoluteFilePath{ "/path/to/image" }; + { + auto transaction{ session.createWriteTransaction() }; + image.get().modify()->setAbsoluteFilePath(absoluteFilePath); + } + + { + auto transaction{ session.createReadTransaction() }; + ImageId lastRetrievedImageId; + std::filesystem::path retrievedPath; + Image::findAbsoluteFilePath(session, lastRetrievedImageId, 1, [&](ImageId id, const std::filesystem::path& path) { + EXPECT_EQ(id, image.getId()); + retrievedPath = path; + }); + + EXPECT_EQ(retrievedPath, absoluteFilePath); + } + } + } // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/Listen.cpp b/src/libs/database/test/Listen.cpp index 1e6d8adc..4a79b015 100644 --- a/src/libs/database/test/Listen.cpp +++ b/src/libs/database/test/Listen.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "database/Listen.hpp" +#include "database/objects/Listen.hpp" #include "Common.hpp" diff --git a/src/libs/database/test/Migration.cpp b/src/libs/database/test/Migration.cpp index da3b66d4..828889ab 100644 --- a/src/libs/database/test/Migration.cpp +++ b/src/libs/database/test/Migration.cpp @@ -20,27 +20,26 @@ #include "Common.hpp" #include "core/String.hpp" -#include "database/Artist.hpp" -#include "database/ArtistInfo.hpp" -#include "database/AuthToken.hpp" -#include "database/Db.hpp" -#include "database/Directory.hpp" -#include "database/Image.hpp" -#include "database/PlayListFile.hpp" -#include "database/PlayQueue.hpp" -#include "database/RatedArtist.hpp" -#include "database/RatedRelease.hpp" -#include "database/RatedTrack.hpp" -#include "database/ScanSettings.hpp" -#include "database/StarredArtist.hpp" -#include "database/StarredRelease.hpp" -#include "database/StarredTrack.hpp" -#include "database/TrackArtistLink.hpp" -#include "database/TrackEmbeddedImage.hpp" -#include "database/TrackEmbeddedImageLink.hpp" -#include "database/TrackLyrics.hpp" -#include "database/UIState.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/ArtistInfo.hpp" +#include "database/objects/AuthToken.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/Image.hpp" +#include "database/objects/PlayListFile.hpp" +#include "database/objects/PlayQueue.hpp" +#include "database/objects/RatedArtist.hpp" +#include "database/objects/RatedRelease.hpp" +#include "database/objects/RatedTrack.hpp" +#include "database/objects/ScanSettings.hpp" +#include "database/objects/StarredArtist.hpp" +#include "database/objects/StarredRelease.hpp" +#include "database/objects/StarredTrack.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" +#include "database/objects/TrackEmbeddedImageLink.hpp" +#include "database/objects/TrackLyrics.hpp" +#include "database/objects/UIState.hpp" +#include "database/objects/User.hpp" namespace lms::db::tests { diff --git a/src/libs/database/test/PlayListFile.cpp b/src/libs/database/test/PlayListFile.cpp index e0011eeb..304c0523 100644 --- a/src/libs/database/test/PlayListFile.cpp +++ b/src/libs/database/test/PlayListFile.cpp @@ -19,8 +19,8 @@ #include "Common.hpp" -#include "database/PlayListFile.hpp" -#include "database/TrackList.hpp" +#include "database/objects/PlayListFile.hpp" +#include "database/objects/TrackList.hpp" namespace lms::db::tests { @@ -85,6 +85,24 @@ namespace lms::db::tests } } + TEST_F(DatabaseFixture, PlayListFile_findAbsoluteFilePath) + { + ScopedPlayListFile playlist{ session, "/tmp/foo.m3u" }; + + { + auto transaction{ session.createReadTransaction() }; + + PlayListFileId lastRetrievedId; + std::filesystem::path retrievedFilePath; + PlayListFile::findAbsoluteFilePath(session, lastRetrievedId, 1, [&](PlayListFileId playListFileId, const std::filesystem::path& filePath) { + EXPECT_EQ(playListFileId, playlist.getId()); + retrievedFilePath = filePath; + }); + + EXPECT_EQ(retrievedFilePath, "/tmp/foo.m3u"); + } + } + TEST_F(DatabaseFixture, PlayListFile_deleteTrackList) { { diff --git a/src/libs/database/test/RatedArtist.cpp b/src/libs/database/test/RatedArtist.cpp index 3cb44501..38ddacdc 100644 --- a/src/libs/database/test/RatedArtist.cpp +++ b/src/libs/database/test/RatedArtist.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "database/RatedArtist.hpp" +#include "database/objects/RatedArtist.hpp" #include "Common.hpp" diff --git a/src/libs/database/test/RatedRelease.cpp b/src/libs/database/test/RatedRelease.cpp index 7a309e92..ee4c66e0 100644 --- a/src/libs/database/test/RatedRelease.cpp +++ b/src/libs/database/test/RatedRelease.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "database/RatedRelease.hpp" +#include "database/objects/RatedRelease.hpp" #include "Common.hpp" diff --git a/src/libs/database/test/RatedTrack.cpp b/src/libs/database/test/RatedTrack.cpp index d939a6af..13ed38b5 100644 --- a/src/libs/database/test/RatedTrack.cpp +++ b/src/libs/database/test/RatedTrack.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "database/RatedTrack.hpp" +#include "database/objects/RatedTrack.hpp" #include "Common.hpp" diff --git a/src/libs/database/test/Release.cpp b/src/libs/database/test/Release.cpp index 3d3b3a11..f232c6e7 100644 --- a/src/libs/database/test/Release.cpp +++ b/src/libs/database/test/Release.cpp @@ -20,8 +20,8 @@ #include "Common.hpp" #include "core/PartialDateTime.hpp" -#include "database/Artwork.hpp" -#include "database/Image.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Image.hpp" namespace lms::db::tests { @@ -173,6 +173,93 @@ namespace lms::db::tests } } + TEST_F(DatabaseFixture, Release_findNextIdRange) + { + { + auto transaction{ session.createReadTransaction() }; + + auto range{ Release::findNextIdRange(session, ReleaseId{}, 0) }; + EXPECT_FALSE(range.isValid()); + EXPECT_EQ(range.first, ReleaseId{}); + EXPECT_EQ(range.last, ReleaseId{}); + + range = Release::findNextIdRange(session, ReleaseId{}, 100); + EXPECT_FALSE(range.isValid()); + EXPECT_EQ(range.first, ReleaseId{}); + EXPECT_EQ(range.last, ReleaseId{}); + } + + ScopedRelease release1{ session, "Artist1" }; + { + auto transaction{ session.createReadTransaction() }; + + auto range{ Release::findNextIdRange(session, ReleaseId{}, 0) }; + EXPECT_FALSE(range.isValid()); + EXPECT_EQ(range.first, ReleaseId{}); + EXPECT_EQ(range.last, ReleaseId{}); + + range = Release::findNextIdRange(session, ReleaseId{}, 1); + EXPECT_TRUE(range.isValid()); + EXPECT_EQ(range.first, release1.getId()); + EXPECT_EQ(range.last, release1.getId()); + + range = Release::findNextIdRange(session, range.last, 1); + EXPECT_FALSE(range.isValid()); + EXPECT_EQ(range.first, ReleaseId{}); + EXPECT_EQ(range.last, ReleaseId{}); + + range = Release::findNextIdRange(session, ReleaseId{}, 100); + EXPECT_TRUE(range.isValid()); + EXPECT_EQ(range.first, release1.getId()); + EXPECT_EQ(range.last, release1.getId()); + } + + ScopedRelease release2{ session, "Artist2" }; + ScopedRelease release3{ session, "Artist3" }; + + { + auto transaction{ session.createReadTransaction() }; + + auto range{ Release::findNextIdRange(session, ReleaseId{}, 2) }; + EXPECT_TRUE(range.isValid()); + EXPECT_EQ(range.first, release1.getId()); + EXPECT_EQ(range.last, release2.getId()); + + range = Release::findNextIdRange(session, release2.getId(), 2); + EXPECT_TRUE(range.isValid()); + EXPECT_EQ(range.first, release3.getId()); + EXPECT_EQ(range.last, release3.getId()); + } + } + + TEST_F(DatabaseFixture, Release_findByRange) + { + ScopedRelease release1{ session, "Artist1" }; + ScopedRelease release2{ session, "Artist2" }; + ScopedRelease release3{ session, "Artist3" }; + + { + auto transaction{ session.createReadTransaction() }; + + std::size_t count{}; + Release::find(session, IdRange{ .first = release1.getId(), .last = release1.getId() }, [&](const db::Release::pointer& release) { + count++; + EXPECT_EQ(release->getId(), release1.getId()); + }); + EXPECT_EQ(count, 1); + } + + { + auto transaction{ session.createReadTransaction() }; + + std::size_t count{}; + Release::find(session, IdRange{ .first = release1.getId(), .last = release3.getId() }, [&](const db::Release::pointer&) { + count++; + }); + EXPECT_EQ(count, 3); + } + } + TEST_F(DatabaseFixture, Release_singleTrack) { ScopedRelease release{ session, "MyRelease" }; @@ -1431,4 +1518,35 @@ namespace lms::db::tests EXPECT_EQ(releases.results[1]->getId(), release1->getId()); } } + + TEST_F(DatabaseFixture, Release_updateArtwork) + { + ScopedRelease release{ session, "MyRelease" }; + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(release->getPreferredArtwork(), Artwork::pointer{}); + } + + ScopedImage image{ session, "/image1.jpg" }; + ScopedArtwork artwork{ session, image.lockAndGet() }; + + { + auto transaction{ session.createWriteTransaction() }; + Release::updatePreferredArtwork(session, release->getId(), artwork.getId()); + } + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(release->getPreferredArtwork()->getId(), artwork.getId()); + } + + { + auto transaction{ session.createWriteTransaction() }; + Release::updatePreferredArtwork(session, release.getId(), ArtworkId{}); + } + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(release->getPreferredArtwork(), Artwork::pointer{}); + } + } + } // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/ScanSettings.cpp b/src/libs/database/test/ScanSettings.cpp index a6b28010..c619e4e6 100644 --- a/src/libs/database/test/ScanSettings.cpp +++ b/src/libs/database/test/ScanSettings.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "database/ScanSettings.hpp" +#include "database/objects/ScanSettings.hpp" #include diff --git a/src/libs/database/test/StarredArtist.cpp b/src/libs/database/test/StarredArtist.cpp index dac89056..f85af565 100644 --- a/src/libs/database/test/StarredArtist.cpp +++ b/src/libs/database/test/StarredArtist.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "database/StarredArtist.hpp" +#include "database/objects/StarredArtist.hpp" #include "Common.hpp" diff --git a/src/libs/database/test/StarredRelease.cpp b/src/libs/database/test/StarredRelease.cpp index 6fc161a4..d8e66a35 100644 --- a/src/libs/database/test/StarredRelease.cpp +++ b/src/libs/database/test/StarredRelease.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "database/StarredRelease.hpp" +#include "database/objects/StarredRelease.hpp" #include "Common.hpp" diff --git a/src/libs/database/test/StarredTrack.cpp b/src/libs/database/test/StarredTrack.cpp index 03e560c3..0c0b4632 100644 --- a/src/libs/database/test/StarredTrack.cpp +++ b/src/libs/database/test/StarredTrack.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "database/StarredTrack.hpp" +#include "database/objects/StarredTrack.hpp" #include "Common.hpp" diff --git a/src/libs/database/test/Track.cpp b/src/libs/database/test/Track.cpp index 3f1744e0..9b83cf63 100644 --- a/src/libs/database/test/Track.cpp +++ b/src/libs/database/test/Track.cpp @@ -21,8 +21,8 @@ #include -#include "database/Artwork.hpp" -#include "database/Image.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Image.hpp" namespace lms::db::tests { @@ -151,6 +151,117 @@ namespace lms::db::tests } } + TEST_F(DatabaseFixture, Track_findNextIdRange) + { + { + auto transaction{ session.createReadTransaction() }; + + auto range{ Track::findNextIdRange(session, TrackId{}, 0) }; + EXPECT_FALSE(range.isValid()); + EXPECT_EQ(range.first, TrackId{}); + EXPECT_EQ(range.last, TrackId{}); + + range = Track::findNextIdRange(session, TrackId{}, 100); + EXPECT_FALSE(range.isValid()); + EXPECT_EQ(range.first, TrackId{}); + EXPECT_EQ(range.last, TrackId{}); + } + + ScopedTrack track1{ session }; + { + auto transaction{ session.createReadTransaction() }; + + auto range{ Track::findNextIdRange(session, TrackId{}, 0) }; + EXPECT_FALSE(range.isValid()); + EXPECT_EQ(range.first, TrackId{}); + EXPECT_EQ(range.last, TrackId{}); + + range = Track::findNextIdRange(session, TrackId{}, 1); + EXPECT_TRUE(range.isValid()); + EXPECT_EQ(range.first, track1.getId()); + EXPECT_EQ(range.last, track1.getId()); + + range = Track::findNextIdRange(session, range.last, 1); + EXPECT_FALSE(range.isValid()); + EXPECT_EQ(range.first, TrackId{}); + EXPECT_EQ(range.last, TrackId{}); + + range = Track::findNextIdRange(session, TrackId{}, 100); + EXPECT_TRUE(range.isValid()); + EXPECT_EQ(range.first, track1.getId()); + EXPECT_EQ(range.last, track1.getId()); + } + + ScopedTrack track2{ session }; + ScopedTrack track3{ session }; + + { + auto transaction{ session.createReadTransaction() }; + + auto range{ Track::findNextIdRange(session, TrackId{}, 2) }; + EXPECT_TRUE(range.isValid()); + EXPECT_EQ(range.first, track1.getId()); + EXPECT_EQ(range.last, track2.getId()); + + range = Track::findNextIdRange(session, track2.getId(), 2); + EXPECT_TRUE(range.isValid()); + EXPECT_EQ(range.first, track3.getId()); + EXPECT_EQ(range.last, track3.getId()); + } + } + + TEST_F(DatabaseFixture, Track_findByRange) + { + ScopedTrack track1{ session }; + ScopedTrack track2{ session }; + ScopedTrack track3{ session }; + + { + auto transaction{ session.createReadTransaction() }; + + std::size_t count{}; + Track::find(session, IdRange{ .first = track1.getId(), .last = track1.getId() }, [&](const db::Track::pointer& track) { + count++; + EXPECT_EQ(track->getId(), track1.getId()); + }); + EXPECT_EQ(count, 1); + } + + { + auto transaction{ session.createReadTransaction() }; + + std::size_t count{}; + Track::find(session, IdRange{ .first = track1.getId(), .last = track3.getId() }, [&](const db::Track::pointer&) { + count++; + }); + EXPECT_EQ(count, 3); + } + } + + TEST_F(DatabaseFixture, Track_findAbsoluteFilePath) + { + ScopedTrack track{ session }; + const std::filesystem::path absoluteFilePath{ "/path/to/track.mp3" }; + { + auto transaction{ session.createWriteTransaction() }; + track.get().modify()->setAbsoluteFilePath(absoluteFilePath); + } + + { + auto transaction{ session.createReadTransaction() }; + + TrackId lastRetrievedTrackId; + std::vector> visitedTracks; + Track::findAbsoluteFilePath(session, lastRetrievedTrackId, 10, [&](TrackId trackId, const std::filesystem::path& filePath) { + visitedTracks.emplace_back(trackId, filePath); + }); + ASSERT_EQ(visitedTracks.size(), 1); + EXPECT_EQ(visitedTracks[0].first, track.getId()); + EXPECT_EQ(visitedTracks[0].second, absoluteFilePath); + EXPECT_EQ(lastRetrievedTrackId, track.getId()); + } + } + TEST_F(DatabaseFixture, Track_MediaLibrary) { ScopedTrack track{ session }; @@ -343,13 +454,11 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; track.get().modify()->setAbsoluteFilePath("/root/foo/file.path"); - track.get().modify()->setRelativeFilePath("foo/file.path"); } { auto transaction{ session.createReadTransaction() }; EXPECT_EQ(track->getAbsoluteFilePath(), "/root/foo/file.path"); - EXPECT_EQ(track->getRelativeFilePath(), "foo/file.path"); } } diff --git a/src/libs/database/test/TrackArtistLink.cpp b/src/libs/database/test/TrackArtistLink.cpp index 51bcfba3..d8e512a2 100644 --- a/src/libs/database/test/TrackArtistLink.cpp +++ b/src/libs/database/test/TrackArtistLink.cpp @@ -20,7 +20,7 @@ #include "Common.hpp" #include "database/Types.hpp" -#include "database/TrackArtistLink.hpp" +#include "database/objects/TrackArtistLink.hpp" namespace lms::db::tests { diff --git a/src/libs/database/test/TrackBookmark.cpp b/src/libs/database/test/TrackBookmark.cpp index 2b30eb07..5e4d8208 100644 --- a/src/libs/database/test/TrackBookmark.cpp +++ b/src/libs/database/test/TrackBookmark.cpp @@ -19,7 +19,7 @@ #include "Common.hpp" -#include "database/TrackBookmark.hpp" +#include "database/objects/TrackBookmark.hpp" namespace lms::db::tests { diff --git a/src/libs/database/test/TrackEmbeddedImage.cpp b/src/libs/database/test/TrackEmbeddedImage.cpp index 16169592..5b8123e9 100644 --- a/src/libs/database/test/TrackEmbeddedImage.cpp +++ b/src/libs/database/test/TrackEmbeddedImage.cpp @@ -19,10 +19,10 @@ #include -#include "database/TrackEmbeddedImage.hpp" -#include "database/TrackEmbeddedImageLink.hpp" -#include "database/TrackList.hpp" #include "database/Types.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" +#include "database/objects/TrackEmbeddedImageLink.hpp" +#include "database/objects/TrackList.hpp" #include "Common.hpp" @@ -198,7 +198,7 @@ namespace lms::db::tests TrackEmbeddedImage::FindParameters params; params.setRelease(release.getId()); - params.setImageTypes({ ImageType::Media }); + params.setImageType(ImageType::Media); params.setSortMethod(TrackEmbeddedImageSortMethod::SizeDesc); std::vector visitedIds; @@ -214,7 +214,7 @@ namespace lms::db::tests TrackEmbeddedImage::FindParameters params; params.setRelease(release.getId()); - params.setImageTypes({ ImageType::Media }); + params.setImageType(ImageType::Media); params.setSortMethod(TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc); std::vector visitedIds; @@ -230,7 +230,7 @@ namespace lms::db::tests TrackEmbeddedImage::FindParameters params; params.setRelease(release.getId()); - params.setImageTypes({ ImageType::Media }); + params.setImageType(ImageType::Media); params.setSortMethod(TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc); std::vector visitedIds; @@ -240,6 +240,19 @@ namespace lms::db::tests EXPECT_EQ(visitedIds[1], image3.getId()); EXPECT_EQ(visitedIds[2], image2.getId()); } + + { + auto transaction{ session.createReadTransaction() }; + + TrackEmbeddedImage::FindParameters params; + params.setRelease(release.getId()); + params.setImageType(ImageType::BackCover); + params.setSortMethod(TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc); + + std::vector visitedIds; + TrackEmbeddedImage::find(session, params, [&](const TrackEmbeddedImage::pointer& image) { visitedIds.push_back(image->getId()); }); + ASSERT_EQ(visitedIds.size(), 0); + } } TEST_F(DatabaseFixture, TrackEmbeddedImage_findByParams_artist) diff --git a/src/libs/database/test/TrackFeatures.cpp b/src/libs/database/test/TrackFeatures.cpp index cb799684..edf56025 100644 --- a/src/libs/database/test/TrackFeatures.cpp +++ b/src/libs/database/test/TrackFeatures.cpp @@ -19,7 +19,7 @@ #include "Common.hpp" -#include "database/TrackFeatures.hpp" +#include "database/objects/TrackFeatures.hpp" namespace lms::db::tests { diff --git a/src/libs/database/test/TrackLyrics.cpp b/src/libs/database/test/TrackLyrics.cpp index 323c69cc..3ab59f3d 100644 --- a/src/libs/database/test/TrackLyrics.cpp +++ b/src/libs/database/test/TrackLyrics.cpp @@ -19,13 +19,39 @@ #include "Common.hpp" -#include "database/Track.hpp" -#include "database/TrackLyrics.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackLyrics.hpp" namespace lms::db::tests { using ScopedTrackLyrics = ScopedEntity; + TEST_F(DatabaseFixture, TrackLyrics_findAbsoluteFilePath) + { + ScopedTrack track{ session }; + ScopedTrackLyrics lyrics{ session }; + + { + auto transaction{ session.createWriteTransaction() }; + + TrackLyrics::pointer dbLyrics{ lyrics.get() }; + + dbLyrics.modify()->setAbsoluteFilePath("/tmp/test.lrc"); + dbLyrics.modify()->setTrack(track.get()); + } + { + auto transaction{ session.createReadTransaction() }; + + TrackLyricsId lastRetrievedId; + std::filesystem::path retrievedFilePath; + TrackLyrics::findAbsoluteFilePath(session, lastRetrievedId, 1, [&](TrackLyricsId trackLyricsId, const std::filesystem::path& absoluteFilePath) { + EXPECT_EQ(trackLyricsId, lyrics.getId()); + retrievedFilePath = absoluteFilePath; + }); + EXPECT_EQ(retrievedFilePath, "/tmp/test.lrc"); + } + } + TEST_F(DatabaseFixture, TrackLyrics_synchronized) { using namespace std::chrono_literals; diff --git a/src/libs/metadata/impl/AudioFileParser.cpp b/src/libs/metadata/impl/AudioFileParser.cpp index 29cfa356..60f36eda 100644 --- a/src/libs/metadata/impl/AudioFileParser.cpp +++ b/src/libs/metadata/impl/AudioFileParser.cpp @@ -456,12 +456,12 @@ namespace lms::metadata track.artists = getArtists(tagReader, { TagType::Artists, TagType::Artist }, { TagType::ArtistSortOrder }, { TagType::MusicBrainzArtistID }, _params); track.artistDisplayName = computeArtistDisplayName(track.artists, getTagValueAs(tagReader, TagType::Artist), _params.artistTagDelimiters); - track.conductorArtists = getArtists(tagReader, { TagType::Conductors, TagType::Conductor }, { TagType::ConductorsSortOrder, TagType::ConductorSortOrder }, {}, _params); - track.composerArtists = getArtists(tagReader, { TagType::Composers, TagType::Composer }, { TagType::ComposersSortOrder, TagType::ComposerSortOrder }, {}, _params); - track.lyricistArtists = getArtists(tagReader, { TagType::Lyricists, TagType::Lyricist }, { TagType::LyricistsSortOrder, TagType::LyricistSortOrder }, {}, _params); - track.mixerArtists = getArtists(tagReader, { TagType::Mixers, TagType::Mixer }, { TagType::MixersSortOrder, TagType::MixerSortOrder }, {}, _params); - track.producerArtists = getArtists(tagReader, { TagType::Producers, TagType::Producer }, { TagType::ProducersSortOrder, TagType::ProducerSortOrder }, {}, _params); - track.remixerArtists = getArtists(tagReader, { TagType::Remixers, TagType::Remixer }, { TagType::RemixersSortOrder, TagType::RemixerSortOrder }, {}, _params); + track.conductorArtists = getArtists(tagReader, { TagType::Conductors, TagType::Conductor }, { TagType::ConductorsSortOrder, TagType::ConductorSortOrder }, { TagType::MusicBrainzConductorID }, _params); + track.composerArtists = getArtists(tagReader, { TagType::Composers, TagType::Composer }, { TagType::ComposersSortOrder, TagType::ComposerSortOrder }, { TagType::MusicBrainzComposerID }, _params); + track.lyricistArtists = getArtists(tagReader, { TagType::Lyricists, TagType::Lyricist }, { TagType::LyricistsSortOrder, TagType::LyricistSortOrder }, { TagType::MusicBrainzLyricistID }, _params); + track.mixerArtists = getArtists(tagReader, { TagType::Mixers, TagType::Mixer }, { TagType::MixersSortOrder, TagType::MixerSortOrder }, { TagType::MusicBrainzMixerID }, _params); + track.producerArtists = getArtists(tagReader, { TagType::Producers, TagType::Producer }, { TagType::ProducersSortOrder, TagType::ProducerSortOrder }, { TagType::MusicBrainzProducerID }, _params); + track.remixerArtists = getArtists(tagReader, { TagType::Remixers, TagType::Remixer }, { TagType::RemixersSortOrder, TagType::RemixerSortOrder }, { TagType::MusicBrainzRemixerID }, _params); track.performerArtists = getPerformerArtists(tagReader); // artistDelimiters not supported // If a file has originalDate but no originalYear, set it diff --git a/src/libs/metadata/impl/ITagReader.hpp b/src/libs/metadata/impl/ITagReader.hpp index 0b4bc8db..0cf72856 100644 --- a/src/libs/metadata/impl/ITagReader.hpp +++ b/src/libs/metadata/impl/ITagReader.hpp @@ -89,14 +89,22 @@ namespace lms::metadata Movement, MovementCount, MovementNumber, + MusicBrainzArrangerID, // non standard MusicBrainzArtistID, + MusicBrainzComposerID, // non standard + MusicBrainzConductorID, // non standard + MusicBrainzDirectorID, // non standard MusicBrainzDiscID, + MusicBrainzLyricistID, // non standard MusicBrainzOriginalArtistID, MusicBrainzOriginalReleaseID, + MusicBrainzMixerID, // non standard + MusicBrainzProducerID, // non standard MusicBrainzRecordingID, MusicBrainzReleaseArtistID, MusicBrainzReleaseGroupID, MusicBrainzReleaseID, + MusicBrainzRemixerID, // non standard MusicBrainzTrackID, MusicBrainzWorkID, MusicIPFingerprint, diff --git a/src/libs/metadata/impl/Lyrics.cpp b/src/libs/metadata/impl/Lyrics.cpp index 028d7cbf..45b66cc8 100644 --- a/src/libs/metadata/impl/Lyrics.cpp +++ b/src/libs/metadata/impl/Lyrics.cpp @@ -155,15 +155,20 @@ namespace lms::metadata accumulatedLyrics.clear(); }; + bool firstLine{ true }; std::string line; while (std::getline(is, line)) { - std::string_view trimmedLine{ core::stringUtils::stringTrimEnd(line) }; - // Remove potential UTF8 BOM - constexpr std::string_view utf8BOM{ "\xEF\xBB\xBF" }; - if (trimmedLine.starts_with(utf8BOM)) - trimmedLine = trimmedLine.substr(utf8BOM.size()); + if (firstLine) + { + firstLine = false; + constexpr std::string_view utf8BOM{ "\xEF\xBB\xBF" }; + if (line.starts_with(utf8BOM)) + line.erase(0, utf8BOM.size()); + } + + std::string_view trimmedLine{ core::stringUtils::stringTrimEnd(line) }; // Skip comments if (!trimmedLine.empty() && trimmedLine.front() == '#') diff --git a/src/libs/metadata/impl/PlayList.cpp b/src/libs/metadata/impl/PlayList.cpp index 4d15f9eb..ca0fadb5 100644 --- a/src/libs/metadata/impl/PlayList.cpp +++ b/src/libs/metadata/impl/PlayList.cpp @@ -63,11 +63,21 @@ namespace lms::metadata PlayList parsePlayList(std::istream& is) { + bool firstLine{ true }; PlayList playlist; std::string line; while (std::getline(is, line)) { + // Remove potential UTF8 BOM + if (firstLine) + { + firstLine = false; + constexpr std::string_view utf8BOM{ "\xEF\xBB\xBF" }; + if (line.starts_with(utf8BOM)) + line.erase(0, utf8BOM.size()); + } + const std::string_view trimmedLine{ core::stringUtils::stringTrim(line) }; if (trimmedLine.empty()) continue; diff --git a/src/libs/metadata/impl/avformat/AvFormatTagReader.cpp b/src/libs/metadata/impl/avformat/AvFormatTagReader.cpp index 1ac145a0..30134f3e 100644 --- a/src/libs/metadata/impl/avformat/AvFormatTagReader.cpp +++ b/src/libs/metadata/impl/avformat/AvFormatTagReader.cpp @@ -89,13 +89,21 @@ namespace lms::metadata::avformat { TagType::MovementCount, { "MOVEMENTCOUNT" } }, { TagType::MovementNumber, { "MOVEMENTNUMBER" } }, { TagType::MusicBrainzArtistID, { "MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ/ARTIST ID" } }, + { TagType::MusicBrainzArrangerID, { "MUSICBRAINZ_ARRANGERID", "MUSICBRAINZ ARRANGER ID", "MUSICBRAINZ/ARRANGER ID" } }, + { TagType::MusicBrainzComposerID, { "MUSICBRAINZ_COMPOSERID", "MUSICBRAINZ COMPOSER ID", "MUSICBRAINZ/COMPOSER ID" } }, + { TagType::MusicBrainzConductorID, { "MUSICBRAINZ_CONDUCTORID", "MUSICBRAINZ CONDUCTOR ID", "MUSICBRAINZ/CONDUCTOR ID" } }, + { TagType::MusicBrainzDirectorID, { "MUSICBRAINZ_DIRECTORID", "MUSICBRAINZ DIRECTOR ID", "MUSICBRAINZ/DIRECTOR ID" } }, { TagType::MusicBrainzDiscID, { "MUSICBRAINZ_DISCID", "MUSICBRAINZ DISC ID", "MUSICBRAINZ/DISC ID" } }, + { TagType::MusicBrainzLyricistID, { "MUSICBRAINZ_LYRICISTID", "MUSICBRAINZ LYRICIST ID", "MUSICBRAINZ/LYRICIST ID" } }, { TagType::MusicBrainzOriginalArtistID, { "MUSICBRAINZ_ORIGINALARTISTID", "MUSICBRAINZ ORIGINAL ARTIST ID", "MUSICBRAINZ/ORIGINAL ARTIST ID" } }, { TagType::MusicBrainzOriginalReleaseID, { "MUSICBRAINZ_ORIGINALRELEASEID", "MUSICBRAINZ ORIGINAL RELEASE ID", "MUSICBRAINZ/ORIGINAL RELEASE ID" } }, + { TagType::MusicBrainzMixerID, { "MUSICBRAINZ_MIXERID", "MUSICBRAINZ MIXER ID", "MUSICBRAINZ/MIXER ID" } }, + { TagType::MusicBrainzProducerID, { "MUSICBRAINZ_PRODUCERID", "MUSICBRAINZ PRODUCER ID", "MUSICBRAINZ/PRODUCER ID" } }, { TagType::MusicBrainzRecordingID, { "MUSICBRAINZ_TRACKID", "MUSICBRAINZ TRACK ID", "MUSICBRAINZ/TRACK ID" } }, { TagType::MusicBrainzReleaseArtistID, { "MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID" } }, { TagType::MusicBrainzReleaseGroupID, { "MUSICBRAINZ_RELEASEGROUPID", "MUSICBRAINZ RELEASE GROUP ID", "MUSICBRAINZ/RELEASE GROUP ID" } }, { TagType::MusicBrainzReleaseID, { "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID" } }, + { TagType::MusicBrainzRemixerID, { "MUSICBRAINZ_REMIXERID", "MUSICBRAINZ REMIXER ID", "MUSICBRAINZ/REMIXER ID" } }, { TagType::MusicBrainzTrackID, { "MUSICBRAINZ_RELEASETRACKID", "MUSICBRAINZ RELEASE TRACK ID", "MUSICBRAINZ/RELEASE TRACK ID" } }, { TagType::MusicBrainzWorkID, { "MUSICBRAINZ_WORKID", "MUSICBRAINZ WORK ID", "MUSICBRAINZ/WORK ID" } }, { TagType::OriginalArtist, { "ORIGINALARTIST" } }, diff --git a/src/libs/metadata/impl/taglib/TagLibTagReader.cpp b/src/libs/metadata/impl/taglib/TagLibTagReader.cpp index a7ae70e0..44a0d7ab 100644 --- a/src/libs/metadata/impl/taglib/TagLibTagReader.cpp +++ b/src/libs/metadata/impl/taglib/TagLibTagReader.cpp @@ -124,13 +124,21 @@ namespace lms::metadata::taglib { TagType::MovementCount, { "MOVEMENTCOUNT" } }, { TagType::MovementNumber, { "MOVEMENTNUMBER" } }, { TagType::MusicBrainzArtistID, { "MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ/ARTIST ID" } }, + { TagType::MusicBrainzArrangerID, { "MUSICBRAINZ_ARRANGERID", "MUSICBRAINZ ARRANGER ID", "MUSICBRAINZ/ARRANGER ID" } }, + { TagType::MusicBrainzComposerID, { "MUSICBRAINZ_COMPOSERID", "MUSICBRAINZ COMPOSER ID", "MUSICBRAINZ/COMPOSER ID" } }, + { TagType::MusicBrainzConductorID, { "MUSICBRAINZ_CONDUCTORID", "MUSICBRAINZ CONDUCTOR ID", "MUSICBRAINZ/CONDUCTOR ID" } }, + { TagType::MusicBrainzDirectorID, { "MUSICBRAINZ_DIRECTORID", "MUSICBRAINZ DIRECTOR ID", "MUSICBRAINZ/DIRECTOR ID" } }, { TagType::MusicBrainzDiscID, { "MUSICBRAINZ_DISCID", "MUSICBRAINZ DISC ID", "MUSICBRAINZ/DISC ID" } }, + { TagType::MusicBrainzLyricistID, { "MUSICBRAINZ_LYRICISTID", "MUSICBRAINZ LYRICIST ID", "MUSICBRAINZ/LYRICIST ID" } }, { TagType::MusicBrainzOriginalArtistID, { "MUSICBRAINZ_ORIGINALARTISTID", "MUSICBRAINZ ORIGINAL ARTIST ID", "MUSICBRAINZ/ORIGINAL ARTIST ID" } }, { TagType::MusicBrainzOriginalReleaseID, { "MUSICBRAINZ_ORIGINALRELEASEID", "MUSICBRAINZ ORIGINAL RELEASE ID", "MUSICBRAINZ/ORIGINAL RELEASE ID" } }, + { TagType::MusicBrainzMixerID, { "MUSICBRAINZ_MIXERID", "MUSICBRAINZ MIXER ID", "MUSICBRAINZ/MIXER ID" } }, + { TagType::MusicBrainzProducerID, { "MUSICBRAINZ_PRODUCERID", "MUSICBRAINZ PRODUCER ID", "MUSICBRAINZ/PRODUCER ID" } }, { TagType::MusicBrainzRecordingID, { "MUSICBRAINZ_TRACKID", "MUSICBRAINZ TRACK ID", "MUSICBRAINZ/TRACK ID" } }, { TagType::MusicBrainzReleaseArtistID, { "MUSICBRAINZ_ALBUMARTISTID", "MUSICBRAINZ ALBUM ARTIST ID", "MUSICBRAINZ/ALBUM ARTIST ID" } }, { TagType::MusicBrainzReleaseGroupID, { "MUSICBRAINZ_RELEASEGROUPID", "MUSICBRAINZ RELEASE GROUP ID", "MUSICBRAINZ/RELEASE GROUP ID" } }, { TagType::MusicBrainzReleaseID, { "MUSICBRAINZ_ALBUMID", "MUSICBRAINZ ALBUM ID", "MUSICBRAINZ/ALBUM ID" } }, + { TagType::MusicBrainzRemixerID, { "MUSICBRAINZ_REMIXERID", "MUSICBRAINZ REMIXER ID", "MUSICBRAINZ/REMIXER ID" } }, { TagType::MusicBrainzTrackID, { "MUSICBRAINZ_RELEASETRACKID", "MUSICBRAINZ RELEASE TRACK ID", "MUSICBRAINZ/RELEASE TRACK ID" } }, { TagType::MusicBrainzWorkID, { "MUSICBRAINZ_WORKID", "MUSICBRAINZ WORK ID", "MUSICBRAINZ/WORK ID" } }, { TagType::OriginalArtist, { "ORIGINALARTIST" } }, diff --git a/src/libs/metadata/impl/taglib/Utils.cpp b/src/libs/metadata/impl/taglib/Utils.cpp index 17e0f93a..94dcc168 100644 --- a/src/libs/metadata/impl/taglib/Utils.cpp +++ b/src/libs/metadata/impl/taglib/Utils.cpp @@ -111,7 +111,7 @@ namespace lms::metadata::taglib::utils file = std::make_unique(stream, TagLib::ID3v2::FrameFactory::instance(), readAudioProperties, audioPropertiesStyle); // VORBIS else if (ext == "OGG") - return std::make_unique(stream, readAudioProperties, audioPropertiesStyle); + file = std::make_unique(stream, readAudioProperties, audioPropertiesStyle); else if (ext == "OGA") { /* .oga can be any audio in the Ogg container. First try FLAC, then Vorbis. */ @@ -154,10 +154,7 @@ namespace lms::metadata::taglib::utils #endif if (file && !file->isValid()) - { - LMS_LOG(METADATA, DEBUG, "File " << file << ": failed to parse by extension"); file.reset(); - } return file; } @@ -208,10 +205,7 @@ namespace lms::metadata::taglib::utils #endif if (file && !file->isValid()) - { - LMS_LOG(METADATA, DEBUG, "File " << file << ": failed to parse by content"); file.reset(); - } return file; } @@ -223,7 +217,12 @@ namespace lms::metadata::taglib::utils TagLib::FileStream fileStream{ createFileStream(p) }; std::unique_ptr file{ parseFileByExtension(&fileStream, p.extension(), readStyle, readAudioProperties.value()) }; if (!file) + { + LMS_LOG(METADATA, DEBUG, "File " << p << ": failed to parse by extension"); file = parseFileByContent(&fileStream, readStyle, readAudioProperties.value()); + if (!file) + LMS_LOG(METADATA, DEBUG, "File " << p << ": failed to parse by content"); + } return file; } diff --git a/src/libs/metadata/test/PlayList.cpp b/src/libs/metadata/test/PlayList.cpp index 165963a5..03be747c 100644 --- a/src/libs/metadata/test/PlayList.cpp +++ b/src/libs/metadata/test/PlayList.cpp @@ -19,6 +19,7 @@ */ #include +#include #include "metadata/PlayList.hpp" @@ -51,4 +52,16 @@ one to be/../one to be/normalized/foo.mp3)" }; EXPECT_EQ(playlist.files[4], "and another one/with relative path/foo.mp3"); EXPECT_EQ(playlist.files[5], "one to be/normalized/foo.mp3"); } + + TEST(PlayList, UTF8_bom) + { + const unsigned char content[] = { 0xEF, 0xBB, 0xBF, '#', 'E', 'X', 'T', 'M', '3', 'U', '\r', '\n', '\r', '\n', '.', '.', '/', 't', 'e', 's', 't', '.', 'm', 'p', '3', '\r', '\n' }; + std::istringstream is{ std::string(reinterpret_cast(content), sizeof(content)) }; + + const PlayList playlist{ parsePlayList(is) }; + EXPECT_EQ(playlist.name, ""); + ASSERT_EQ(playlist.files.size(), 1); + EXPECT_EQ(playlist.files[0], "../test.mp3"); + } + } // namespace lms::metadata::tests \ No newline at end of file diff --git a/src/libs/services/artwork/impl/ArtworkService.cpp b/src/libs/services/artwork/impl/ArtworkService.cpp index fa3a7467..0480b756 100644 --- a/src/libs/services/artwork/impl/ArtworkService.cpp +++ b/src/libs/services/artwork/impl/ArtworkService.cpp @@ -22,17 +22,17 @@ #include "core/IConfig.hpp" #include "core/ILogger.hpp" #include "core/Utils.hpp" -#include "database/Artist.hpp" -#include "database/Artwork.hpp" -#include "database/Db.hpp" -#include "database/Image.hpp" -#include "database/ImageId.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackEmbeddedImage.hpp" -#include "database/TrackEmbeddedImageLink.hpp" -#include "database/TrackList.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Image.hpp" +#include "database/objects/ImageId.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" +#include "database/objects/TrackEmbeddedImageLink.hpp" +#include "database/objects/TrackList.hpp" #include "image/Exception.hpp" #include "image/IEncodedImage.hpp" #include "image/Image.hpp" @@ -40,12 +40,12 @@ namespace lms::artwork { - std::unique_ptr createArtworkService(db::Db& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath) + std::unique_ptr createArtworkService(db::IDb& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath) { return std::make_unique(db, defaultReleaseCoverSvgPath, defaultArtistImageSvgPath); } - ArtworkService::ArtworkService(db::Db& db, + ArtworkService::ArtworkService(db::IDb& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath) : _db{ db } diff --git a/src/libs/services/artwork/impl/ArtworkService.hpp b/src/libs/services/artwork/impl/ArtworkService.hpp index a0aa24d5..6eb0a691 100644 --- a/src/libs/services/artwork/impl/ArtworkService.hpp +++ b/src/libs/services/artwork/impl/ArtworkService.hpp @@ -22,8 +22,8 @@ #include #include -#include "database/ImageId.hpp" -#include "database/TrackEmbeddedImageId.hpp" +#include "database/objects/ImageId.hpp" +#include "database/objects/TrackEmbeddedImageId.hpp" #include "services/artwork/IArtworkService.hpp" #include "ImageCache.hpp" @@ -43,7 +43,7 @@ namespace lms::artwork class ArtworkService : public IArtworkService { public: - ArtworkService(db::Db& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath); + ArtworkService(db::IDb& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath); ~ArtworkService() override; ArtworkService(const ArtworkService&) = delete; ArtworkService& operator=(const ArtworkService&) = delete; @@ -65,7 +65,7 @@ namespace lms::artwork std::unique_ptr getFromImageFile(const std::filesystem::path& p, std::optional width) const; std::unique_ptr getTrackImage(const std::filesystem::path& path, std::size_t index, std::optional width) const; - db::Db& _db; + db::IDb& _db; std::unique_ptr _audioFileParser; ImageCache _cache; diff --git a/src/libs/services/artwork/impl/ImageCache.hpp b/src/libs/services/artwork/impl/ImageCache.hpp index 995e67f7..e4921362 100644 --- a/src/libs/services/artwork/impl/ImageCache.hpp +++ b/src/libs/services/artwork/impl/ImageCache.hpp @@ -26,7 +26,7 @@ #include #include -#include "database/ArtworkId.hpp" +#include "database/objects/ArtworkId.hpp" #include "image/IEncodedImage.hpp" namespace lms::artwork diff --git a/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp b/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp index 4d8c1605..d8fcc71f 100644 --- a/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp +++ b/src/libs/services/artwork/include/services/artwork/IArtworkService.hpp @@ -23,13 +23,13 @@ #include #include -#include "database/ArtworkId.hpp" -#include "database/TrackListId.hpp" +#include "database/objects/ArtworkId.hpp" +#include "database/objects/TrackListId.hpp" #include "image/IEncodedImage.hpp" namespace lms::db { - class Db; + class IDb; } namespace lms::artwork @@ -54,6 +54,6 @@ namespace lms::artwork virtual void setJpegQuality(unsigned quality) = 0; // from 1 to 100 }; - std::unique_ptr createArtworkService(db::Db& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath); + std::unique_ptr createArtworkService(db::IDb& db, const std::filesystem::path& defaultReleaseCoverSvgPath, const std::filesystem::path& defaultArtistImageSvgPath); } // namespace lms::artwork diff --git a/src/libs/services/auth/impl/AuthServiceBase.cpp b/src/libs/services/auth/impl/AuthServiceBase.cpp index 3dfed8d2..cb3b9e5b 100644 --- a/src/libs/services/auth/impl/AuthServiceBase.cpp +++ b/src/libs/services/auth/impl/AuthServiceBase.cpp @@ -20,15 +20,15 @@ #include "AuthServiceBase.hpp" #include "core/ILogger.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" namespace lms::auth { using namespace db; - AuthServiceBase::AuthServiceBase(Db& db) + AuthServiceBase::AuthServiceBase(db::IDb& db) : _db{ db } { } diff --git a/src/libs/services/auth/impl/AuthServiceBase.hpp b/src/libs/services/auth/impl/AuthServiceBase.hpp index ec066fb1..c25aa28d 100644 --- a/src/libs/services/auth/impl/AuthServiceBase.hpp +++ b/src/libs/services/auth/impl/AuthServiceBase.hpp @@ -21,11 +21,11 @@ #include -#include "database/UserId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { - class Db; + class IDb; class Session; } // namespace lms::db @@ -34,7 +34,7 @@ namespace lms::auth class AuthServiceBase { protected: - AuthServiceBase(db::Db& db); + AuthServiceBase(db::IDb& db); ~AuthServiceBase() = default; AuthServiceBase(const AuthServiceBase&) = delete; AuthServiceBase& operator=(const AuthServiceBase&) = delete; @@ -45,6 +45,6 @@ namespace lms::auth db::Session& getDbSession(); private: - db::Db& _db; + db::IDb& _db; }; } // namespace lms::auth diff --git a/src/libs/services/auth/impl/AuthTokenService.cpp b/src/libs/services/auth/impl/AuthTokenService.cpp index da31cbe2..5fef93db 100644 --- a/src/libs/services/auth/impl/AuthTokenService.cpp +++ b/src/libs/services/auth/impl/AuthTokenService.cpp @@ -23,9 +23,9 @@ #include #include "core/ILogger.hpp" -#include "database/AuthToken.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/AuthToken.hpp" +#include "database/objects/User.hpp" #include "services/auth/Types.hpp" namespace lms::auth @@ -44,12 +44,12 @@ namespace lms::auth } } // namespace - std::unique_ptr createAuthTokenService(db::Db& db, std::size_t maxThrottlerEntryCount) + std::unique_ptr createAuthTokenService(db::IDb& db, std::size_t maxThrottlerEntryCount) { return std::make_unique(db, maxThrottlerEntryCount); } - AuthTokenService::AuthTokenService(db::Db& db, std::size_t maxThrottlerEntryCount) + AuthTokenService::AuthTokenService(db::IDb& db, std::size_t maxThrottlerEntryCount) : AuthServiceBase{ db } , _loginThrottler{ maxThrottlerEntryCount } { diff --git a/src/libs/services/auth/impl/AuthTokenService.hpp b/src/libs/services/auth/impl/AuthTokenService.hpp index c7f3e5c0..c0206774 100644 --- a/src/libs/services/auth/impl/AuthTokenService.hpp +++ b/src/libs/services/auth/impl/AuthTokenService.hpp @@ -37,7 +37,7 @@ namespace lms::auth class AuthTokenService : public IAuthTokenService, public AuthServiceBase { public: - AuthTokenService(db::Db& db, std::size_t maxThrottlerEntryCount); + AuthTokenService(db::IDb& db, std::size_t maxThrottlerEntryCount); ~AuthTokenService() override = default; AuthTokenService(const AuthTokenService&) = delete; diff --git a/src/libs/services/auth/impl/EnvService.cpp b/src/libs/services/auth/impl/EnvService.cpp index 486616ad..74d0283d 100644 --- a/src/libs/services/auth/impl/EnvService.cpp +++ b/src/libs/services/auth/impl/EnvService.cpp @@ -25,7 +25,7 @@ namespace lms::auth { - std::unique_ptr createEnvService(std::string_view backendName, db::Db& db) + std::unique_ptr createEnvService(std::string_view backendName, db::IDb& db) { if (backendName == "http-headers") return std::make_unique(db); diff --git a/src/libs/services/auth/impl/PasswordServiceBase.cpp b/src/libs/services/auth/impl/PasswordServiceBase.cpp index 6972a954..c9f0da03 100644 --- a/src/libs/services/auth/impl/PasswordServiceBase.cpp +++ b/src/libs/services/auth/impl/PasswordServiceBase.cpp @@ -35,7 +35,7 @@ namespace lms::auth { static const Wt::Auth::SHA1HashFunction sha1Function; - std::unique_ptr createPasswordService(std::string_view backend, db::Db& db, std::size_t maxThrottlerEntryCount) + std::unique_ptr createPasswordService(std::string_view backend, db::IDb& db, std::size_t maxThrottlerEntryCount) { if (backend == "internal") return std::make_unique(db, maxThrottlerEntryCount); @@ -46,7 +46,7 @@ namespace lms::auth throw Exception{ "Authentication backend '" + std::string{ backend } + "' not supported!" }; } - PasswordServiceBase::PasswordServiceBase(db::Db& db, std::size_t maxThrottlerEntries) + PasswordServiceBase::PasswordServiceBase(db::IDb& db, std::size_t maxThrottlerEntries) : AuthServiceBase{ db } , _loginThrottler{ maxThrottlerEntries } { diff --git a/src/libs/services/auth/impl/PasswordServiceBase.hpp b/src/libs/services/auth/impl/PasswordServiceBase.hpp index 8743e707..3c5be452 100644 --- a/src/libs/services/auth/impl/PasswordServiceBase.hpp +++ b/src/libs/services/auth/impl/PasswordServiceBase.hpp @@ -27,7 +27,7 @@ namespace lms::db { - class Db; + class IDb; class Session; } // namespace lms::db @@ -36,7 +36,7 @@ namespace lms::auth class PasswordServiceBase : public IPasswordService, public AuthServiceBase { public: - PasswordServiceBase(db::Db& db, std::size_t maxThrottlerEntries); + PasswordServiceBase(db::IDb& db, std::size_t maxThrottlerEntries); ~PasswordServiceBase() override = default; PasswordServiceBase(const PasswordServiceBase&) = delete; diff --git a/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.cpp b/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.cpp index 9c065e7f..7bc60b04 100644 --- a/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.cpp +++ b/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.cpp @@ -27,7 +27,7 @@ namespace lms::auth { - HttpHeadersEnvService::HttpHeadersEnvService(db::Db& db) + HttpHeadersEnvService::HttpHeadersEnvService(db::IDb& db) : AuthServiceBase{ db } , _fieldName{ core::Service::get()->getString("http-headers-login-field", "X-Forwarded-User") } { diff --git a/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.hpp b/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.hpp index 0e3f9ffe..e8334130 100644 --- a/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.hpp +++ b/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.hpp @@ -28,7 +28,7 @@ namespace lms::auth class HttpHeadersEnvService : public IEnvService, public AuthServiceBase { public: - HttpHeadersEnvService(db::Db& db); + HttpHeadersEnvService(db::IDb& db); private: CheckResult processEnv(const Wt::WEnvironment& env) override; diff --git a/src/libs/services/auth/impl/internal/InternalPasswordService.cpp b/src/libs/services/auth/impl/internal/InternalPasswordService.cpp index 013923e0..8764b875 100644 --- a/src/libs/services/auth/impl/internal/InternalPasswordService.cpp +++ b/src/libs/services/auth/impl/internal/InternalPasswordService.cpp @@ -24,12 +24,12 @@ #include "core/IConfig.hpp" #include "core/ILogger.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" #include "services/auth/Types.hpp" namespace lms::auth { - InternalPasswordService::InternalPasswordService(db::Db& db, std::size_t maxThrottlerEntries) + InternalPasswordService::InternalPasswordService(db::IDb& db, std::size_t maxThrottlerEntries) : PasswordServiceBase{ db, maxThrottlerEntries } , _bcryptRoundCount{ static_cast(core::Service::get()->getULong("internal-password-bcrypt-round", 12)) } { diff --git a/src/libs/services/auth/impl/internal/InternalPasswordService.hpp b/src/libs/services/auth/impl/internal/InternalPasswordService.hpp index 105fd746..92fbfc53 100644 --- a/src/libs/services/auth/impl/internal/InternalPasswordService.hpp +++ b/src/libs/services/auth/impl/internal/InternalPasswordService.hpp @@ -22,7 +22,7 @@ #include #include -#include "database/User.hpp" +#include "database/objects/User.hpp" #include "PasswordServiceBase.hpp" #include "services/auth/IPasswordService.hpp" @@ -32,7 +32,7 @@ namespace lms::auth class InternalPasswordService : public PasswordServiceBase { public: - InternalPasswordService(db::Db& db, std::size_t maxThrottlerEntries); + InternalPasswordService(db::IDb& db, std::size_t maxThrottlerEntries); private: bool checkUserPassword(std::string_view loginName, std::string_view password) override; diff --git a/src/libs/services/auth/include/services/auth/IAuthTokenService.hpp b/src/libs/services/auth/include/services/auth/IAuthTokenService.hpp index 12763ac5..300d4b32 100644 --- a/src/libs/services/auth/include/services/auth/IAuthTokenService.hpp +++ b/src/libs/services/auth/include/services/auth/IAuthTokenService.hpp @@ -28,11 +28,11 @@ #include #include "core/LiteralString.hpp" -#include "database/UserId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { - class Db; + class IDb; } // namespace lms::db namespace lms::auth @@ -81,5 +81,5 @@ namespace lms::auth virtual void clearAuthTokens(core::LiteralString domain, db::UserId userid) = 0; }; - std::unique_ptr createAuthTokenService(db::Db& db, std::size_t maxThrottlerEntryCount); + std::unique_ptr createAuthTokenService(db::IDb& db, std::size_t maxThrottlerEntryCount); } // namespace lms::auth diff --git a/src/libs/services/auth/include/services/auth/IEnvService.hpp b/src/libs/services/auth/include/services/auth/IEnvService.hpp index 4c84dc4f..4a743bf4 100644 --- a/src/libs/services/auth/include/services/auth/IEnvService.hpp +++ b/src/libs/services/auth/include/services/auth/IEnvService.hpp @@ -22,11 +22,11 @@ #include #include -#include "database/UserId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { - class Db; + class IDb; class Session; } // namespace lms::db @@ -65,5 +65,5 @@ namespace lms::auth virtual CheckResult processRequest(const Wt::Http::Request& request) = 0; }; - std::unique_ptr createEnvService(std::string_view backend, db::Db& db); + std::unique_ptr createEnvService(std::string_view backend, db::IDb& db); } // namespace lms::auth diff --git a/src/libs/services/auth/include/services/auth/IPasswordService.hpp b/src/libs/services/auth/include/services/auth/IPasswordService.hpp index a1a90d68..83260ca5 100644 --- a/src/libs/services/auth/include/services/auth/IPasswordService.hpp +++ b/src/libs/services/auth/include/services/auth/IPasswordService.hpp @@ -25,12 +25,12 @@ #include #include -#include "database/UserId.hpp" +#include "database/objects/UserId.hpp" #include "services/auth/Types.hpp" namespace lms::db { - class Db; + class IDb; class User; } // namespace lms::db @@ -69,5 +69,5 @@ namespace lms::auth virtual void setPassword(db::UserId userId, std::string_view newPassword) = 0; }; - std::unique_ptr createPasswordService(std::string_view backend, db::Db& db, std::size_t maxThrottlerEntryCount); + std::unique_ptr createPasswordService(std::string_view backend, db::IDb& db, std::size_t maxThrottlerEntryCount); } // namespace lms::auth diff --git a/src/libs/services/feedback/impl/FeedbackService.cpp b/src/libs/services/feedback/impl/FeedbackService.cpp index 448234d8..c7e53313 100644 --- a/src/libs/services/feedback/impl/FeedbackService.cpp +++ b/src/libs/services/feedback/impl/FeedbackService.cpp @@ -21,30 +21,30 @@ #include "FeedbackService.impl.hpp" #include "core/ILogger.hpp" -#include "database/Artist.hpp" -#include "database/Db.hpp" -#include "database/RatedArtist.hpp" -#include "database/RatedRelease.hpp" -#include "database/RatedTrack.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/StarredArtist.hpp" -#include "database/StarredRelease.hpp" -#include "database/StarredTrack.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/RatedArtist.hpp" +#include "database/objects/RatedRelease.hpp" +#include "database/objects/RatedTrack.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/StarredArtist.hpp" +#include "database/objects/StarredRelease.hpp" +#include "database/objects/StarredTrack.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "internal/InternalBackend.hpp" #include "listenbrainz/ListenBrainzBackend.hpp" namespace lms::feedback { - std::unique_ptr createFeedbackService(boost::asio::io_context& ioContext, Db& db) + std::unique_ptr createFeedbackService(boost::asio::io_context& ioContext, db::IDb& db) { return std::make_unique(ioContext, db); } - FeedbackService::FeedbackService(boost::asio::io_context& ioContext, Db& db) + FeedbackService::FeedbackService(boost::asio::io_context& ioContext, db::IDb& db) : _db{ db } { LMS_LOG(SCROBBLING, INFO, "Starting service..."); @@ -58,7 +58,7 @@ namespace lms::feedback LMS_LOG(SCROBBLING, INFO, "Service stopped!"); } - std::optional FeedbackService::getUserFeedbackBackend(UserId userId) + std::optional FeedbackService::getUserFeedbackBackend(db::UserId userId) { std::optional feedbackBackend; diff --git a/src/libs/services/feedback/impl/FeedbackService.hpp b/src/libs/services/feedback/impl/FeedbackService.hpp index 6c5add69..e8407c7f 100644 --- a/src/libs/services/feedback/impl/FeedbackService.hpp +++ b/src/libs/services/feedback/impl/FeedbackService.hpp @@ -28,7 +28,7 @@ namespace lms::db { - class Db; + class IDb; } namespace lms::feedback @@ -36,7 +36,7 @@ namespace lms::feedback class FeedbackService : public IFeedbackService { public: - FeedbackService(boost::asio::io_context& ioContext, db::Db& db); + FeedbackService(boost::asio::io_context& ioContext, db::IDb& db); ~FeedbackService() override; FeedbackService(const FeedbackService&) = delete; FeedbackService& operator=(const FeedbackService&) = delete; @@ -86,7 +86,7 @@ namespace lms::feedback template std::optional getRating(db::UserId userId, ObjIdType objectId); - db::Db& _db; + db::IDb& _db; std::unordered_map> _backends; }; diff --git a/src/libs/services/feedback/impl/FeedbackService.impl.hpp b/src/libs/services/feedback/impl/FeedbackService.impl.hpp index 29307983..6f549648 100644 --- a/src/libs/services/feedback/impl/FeedbackService.impl.hpp +++ b/src/libs/services/feedback/impl/FeedbackService.impl.hpp @@ -19,9 +19,9 @@ #pragma once -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" namespace lms::feedback { diff --git a/src/libs/services/feedback/impl/IFeedbackBackend.hpp b/src/libs/services/feedback/impl/IFeedbackBackend.hpp index cb377f1a..dea1b433 100644 --- a/src/libs/services/feedback/impl/IFeedbackBackend.hpp +++ b/src/libs/services/feedback/impl/IFeedbackBackend.hpp @@ -21,9 +21,9 @@ #include -#include "database/StarredArtistId.hpp" -#include "database/StarredReleaseId.hpp" -#include "database/StarredTrackId.hpp" +#include "database/objects/StarredArtistId.hpp" +#include "database/objects/StarredReleaseId.hpp" +#include "database/objects/StarredTrackId.hpp" namespace lms::feedback { diff --git a/src/libs/services/feedback/impl/internal/InternalBackend.cpp b/src/libs/services/feedback/impl/internal/InternalBackend.cpp index 7d3f1b9a..0c90b540 100644 --- a/src/libs/services/feedback/impl/internal/InternalBackend.cpp +++ b/src/libs/services/feedback/impl/internal/InternalBackend.cpp @@ -19,11 +19,11 @@ #include "InternalBackend.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/StarredArtist.hpp" -#include "database/StarredRelease.hpp" -#include "database/StarredTrack.hpp" +#include "database/objects/StarredArtist.hpp" +#include "database/objects/StarredRelease.hpp" +#include "database/objects/StarredTrack.hpp" namespace lms::feedback { @@ -48,7 +48,7 @@ namespace lms::feedback } } // namespace details - InternalBackend::InternalBackend(db::Db& db) + InternalBackend::InternalBackend(db::IDb& db) : _db{ db } { } diff --git a/src/libs/services/feedback/impl/internal/InternalBackend.hpp b/src/libs/services/feedback/impl/internal/InternalBackend.hpp index 0d41be4e..1839b498 100644 --- a/src/libs/services/feedback/impl/internal/InternalBackend.hpp +++ b/src/libs/services/feedback/impl/internal/InternalBackend.hpp @@ -23,7 +23,7 @@ namespace lms::db { - class Db; + class IDb; } namespace lms::feedback @@ -31,7 +31,7 @@ namespace lms::feedback class InternalBackend final : public IFeedbackBackend { public: - InternalBackend(db::Db& db); + InternalBackend(db::IDb& db); ~InternalBackend() override = default; InternalBackend(const InternalBackend&) = delete; InternalBackend& operator=(const InternalBackend&) = delete; @@ -44,6 +44,6 @@ namespace lms::feedback void onStarred(db::StarredTrackId trackId) override; void onUnstarred(db::StarredTrackId trackId) override; - db::Db& _db; + db::IDb& _db; }; } // namespace lms::feedback diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp index a0a80c66..1a07b794 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -29,11 +30,11 @@ #include "core/IConfig.hpp" #include "core/Service.hpp" #include "core/http/IClient.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/StarredTrack.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/StarredTrack.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "Exception.hpp" #include "FeedbacksParser.hpp" @@ -60,7 +61,7 @@ namespace lms::feedback::listenBrainz } } // namespace - FeedbacksSynchronizer::FeedbacksSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client) + FeedbacksSynchronizer::FeedbacksSynchronizer(boost::asio::io_context& ioContext, db::IDb& db, core::http::IClient& client) : _ioContext{ ioContext } , _db{ db } , _client{ client } diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.hpp b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.hpp index 0371de51..a1a39078 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.hpp @@ -26,8 +26,8 @@ #include #include -#include "database/StarredTrackId.hpp" -#include "database/UserId.hpp" +#include "database/objects/StarredTrackId.hpp" +#include "database/objects/UserId.hpp" #include "FeedbackTypes.hpp" @@ -39,7 +39,7 @@ namespace lms } namespace db { - class Db; + class IDb; } } // namespace lms @@ -48,7 +48,7 @@ namespace lms::feedback::listenBrainz class FeedbacksSynchronizer { public: - FeedbacksSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client); + FeedbacksSynchronizer(boost::asio::io_context& ioContext, db::IDb& db, core::http::IClient& client); ~FeedbacksSynchronizer() = default; FeedbacksSynchronizer(const FeedbacksSynchronizer&) = delete; FeedbacksSynchronizer& operator=(const FeedbacksSynchronizer&) = delete; @@ -95,7 +95,7 @@ namespace lms::feedback::listenBrainz boost::asio::io_context& _ioContext; boost::asio::io_context::strand _strand{ _ioContext }; - db::Db& _db; + db::IDb& _db; boost::asio::steady_timer _syncTimer{ _ioContext }; core::http::IClient& _client; diff --git a/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.cpp b/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.cpp index bb7347ad..59a7fc71 100644 --- a/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.cpp @@ -22,11 +22,11 @@ #include "core/IConfig.hpp" #include "core/Service.hpp" #include "core/http/IClient.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/StarredArtist.hpp" -#include "database/StarredRelease.hpp" -#include "database/Track.hpp" +#include "database/objects/StarredArtist.hpp" +#include "database/objects/StarredRelease.hpp" +#include "database/objects/Track.hpp" #include "Utils.hpp" @@ -56,7 +56,7 @@ namespace lms::feedback::listenBrainz } } // namespace details - ListenBrainzBackend::ListenBrainzBackend(boost::asio::io_context& ioContext, db::Db& db) + ListenBrainzBackend::ListenBrainzBackend(boost::asio::io_context& ioContext, db::IDb& db) : _ioContext{ ioContext } , _db{ db } , _baseAPIUrl{ core::Service::get()->getString("listenbrainz-api-base-url", "https://api.listenbrainz.org") } diff --git a/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.hpp b/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.hpp index f7359095..88ff7419 100644 --- a/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.hpp @@ -28,7 +28,7 @@ namespace lms::db { - class Db; + class IDb; } namespace lms::feedback::listenBrainz @@ -36,7 +36,7 @@ namespace lms::feedback::listenBrainz class ListenBrainzBackend final : public IFeedbackBackend { public: - ListenBrainzBackend(boost::asio::io_context& ioContext, db::Db& db); + ListenBrainzBackend(boost::asio::io_context& ioContext, db::IDb& db); ~ListenBrainzBackend() override; private: @@ -51,7 +51,7 @@ namespace lms::feedback::listenBrainz void onUnstarred(db::StarredTrackId starredTrackId) override; boost::asio::io_context& _ioContext; - db::Db& _db; + db::IDb& _db; std::string _baseAPIUrl; std::unique_ptr _client; FeedbacksSynchronizer _feedbacksSynchronizer; diff --git a/src/libs/services/feedback/impl/listenbrainz/Utils.cpp b/src/libs/services/feedback/impl/listenbrainz/Utils.cpp index e240c8c6..6f9932d4 100644 --- a/src/libs/services/feedback/impl/listenbrainz/Utils.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/Utils.cpp @@ -23,7 +23,7 @@ #include #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" namespace lms::feedback::listenBrainz::utils { diff --git a/src/libs/services/feedback/impl/listenbrainz/Utils.hpp b/src/libs/services/feedback/impl/listenbrainz/Utils.hpp index a55641ee..d2790cef 100644 --- a/src/libs/services/feedback/impl/listenbrainz/Utils.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/Utils.hpp @@ -21,7 +21,7 @@ #include "core/ILogger.hpp" #include "core/UUID.hpp" -#include "database/UserId.hpp" +#include "database/objects/UserId.hpp" #define LOG(sev, message) LMS_LOG(FEEDBACK, sev, "[listenbrainz] " << message) diff --git a/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp b/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp index b88c8102..e1353b4a 100644 --- a/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp +++ b/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp @@ -25,16 +25,16 @@ #include #include -#include "database/ArtistId.hpp" -#include "database/Filters.hpp" -#include "database/ReleaseId.hpp" -#include "database/TrackId.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/Filters.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/UserId.hpp" namespace lms::db { - class Db; + class IDb; } namespace lms::feedback @@ -125,6 +125,6 @@ namespace lms::feedback virtual std::optional getRating(db::UserId userId, db::TrackId trackId) = 0; }; - std::unique_ptr createFeedbackService(boost::asio::io_context& ioContext, db::Db& db); + std::unique_ptr createFeedbackService(boost::asio::io_context& ioContext, db::IDb& db); } // namespace lms::feedback diff --git a/src/libs/services/recommendation/impl/ClustersEngineCreator.hpp b/src/libs/services/recommendation/impl/ClustersEngineCreator.hpp index d83328c8..7d157526 100644 --- a/src/libs/services/recommendation/impl/ClustersEngineCreator.hpp +++ b/src/libs/services/recommendation/impl/ClustersEngineCreator.hpp @@ -23,11 +23,11 @@ namespace lms::db { - class Db; + class IDb; } namespace lms::recommendation { class IEngine; - std::unique_ptr createClustersEngine(db::Db& db); + std::unique_ptr createClustersEngine(db::IDb& db); } // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/FeaturesEngineCreator.hpp b/src/libs/services/recommendation/impl/FeaturesEngineCreator.hpp index 107e250a..3d9bcb2e 100644 --- a/src/libs/services/recommendation/impl/FeaturesEngineCreator.hpp +++ b/src/libs/services/recommendation/impl/FeaturesEngineCreator.hpp @@ -25,10 +25,10 @@ namespace lms::db { - class Db; + class IDb; } namespace lms::recommendation { - std::unique_ptr createFeaturesEngine(db::Db& db); + std::unique_ptr createFeaturesEngine(db::IDb& db); } diff --git a/src/libs/services/recommendation/impl/IEngine.hpp b/src/libs/services/recommendation/impl/IEngine.hpp index 890631dc..a072fba4 100644 --- a/src/libs/services/recommendation/impl/IEngine.hpp +++ b/src/libs/services/recommendation/impl/IEngine.hpp @@ -22,13 +22,13 @@ #include #include "core/EnumSet.hpp" -#include "database/TrackListId.hpp" #include "database/Types.hpp" +#include "database/objects/TrackListId.hpp" #include "services/recommendation/Types.hpp" namespace lms::db { - class Db; + class IDb; } namespace lms::recommendation @@ -47,6 +47,6 @@ namespace lms::recommendation virtual ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const = 0; }; - std::unique_ptr createEngine(db::Db& db); + std::unique_ptr createEngine(db::IDb& db); } // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp b/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp index 5256e2d7..05a0f892 100644 --- a/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp +++ b/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp @@ -22,9 +22,9 @@ #include #include "core/ILogger.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Track.hpp" #include "services/recommendation/IRecommendationService.hpp" #include "playlist-constraints/ConsecutiveArtists.hpp" @@ -35,12 +35,12 @@ namespace lms::recommendation { using namespace db; - std::unique_ptr createPlaylistGeneratorService(Db& db, IRecommendationService& recommendationService) + std::unique_ptr createPlaylistGeneratorService(db::IDb& db, IRecommendationService& recommendationService) { return std::make_unique(db, recommendationService); } - PlaylistGeneratorService::PlaylistGeneratorService(Db& db, IRecommendationService& recommendationService) + PlaylistGeneratorService::PlaylistGeneratorService(db::IDb& db, IRecommendationService& recommendationService) : _db{ db } , _recommendationService{ recommendationService } { diff --git a/src/libs/services/recommendation/impl/PlaylistGeneratorService.hpp b/src/libs/services/recommendation/impl/PlaylistGeneratorService.hpp index 6287d778..0e6f88f0 100644 --- a/src/libs/services/recommendation/impl/PlaylistGeneratorService.hpp +++ b/src/libs/services/recommendation/impl/PlaylistGeneratorService.hpp @@ -29,14 +29,14 @@ namespace lms::recommendation class PlaylistGeneratorService : public IPlaylistGeneratorService { public: - PlaylistGeneratorService(db::Db& db, IRecommendationService& recommendationService); + PlaylistGeneratorService(db::IDb& db, IRecommendationService& recommendationService); private: TrackContainer extendPlaylist(db::TrackListId tracklistId, std::size_t maxCount) const override; TrackContainer getTracksFromTrackList(db::TrackListId tracklistId) const; - db::Db& _db; + db::IDb& _db; IRecommendationService& _recommendationService; std::vector> _constraints; }; diff --git a/src/libs/services/recommendation/impl/RecommendationService.cpp b/src/libs/services/recommendation/impl/RecommendationService.cpp index 51a7e365..e51ba11b 100644 --- a/src/libs/services/recommendation/impl/RecommendationService.cpp +++ b/src/libs/services/recommendation/impl/RecommendationService.cpp @@ -21,9 +21,9 @@ #include -#include "database/Db.hpp" -#include "database/ScanSettings.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" +#include "database/objects/ScanSettings.hpp" #include "ClustersEngineCreator.hpp" #include "FeaturesEngineCreator.hpp" @@ -40,12 +40,12 @@ namespace lms::recommendation } } // namespace - std::unique_ptr createRecommendationService(db::Db& db) + std::unique_ptr createRecommendationService(db::IDb& db) { return std::make_unique(db); } - RecommendationService::RecommendationService(db::Db& db) + RecommendationService::RecommendationService(db::IDb& db) : _db{ db } { load(); diff --git a/src/libs/services/recommendation/impl/RecommendationService.hpp b/src/libs/services/recommendation/impl/RecommendationService.hpp index d5cde868..8424e528 100644 --- a/src/libs/services/recommendation/impl/RecommendationService.hpp +++ b/src/libs/services/recommendation/impl/RecommendationService.hpp @@ -27,7 +27,7 @@ namespace lms::db { - class Db; + class IDb; } namespace lms::recommendation @@ -41,7 +41,7 @@ namespace lms::recommendation class RecommendationService : public IRecommendationService { public: - RecommendationService(db::Db& db); + RecommendationService(db::IDb& db); ~RecommendationService() override = default; RecommendationService(const RecommendationService&) = delete; RecommendationService& operator=(const RecommendationService&) = delete; @@ -58,7 +58,7 @@ namespace lms::recommendation void clearEngines(); void loadPendingEngine(EngineType engineType, std::unique_ptr engine, bool forceReload, const ProgressCallback& progressCallback); - db::Db& _db; + db::IDb& _db; std::optional _engineType; std::unique_ptr _engine; }; diff --git a/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp b/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp index 662d9da6..e1100d0f 100644 --- a/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp +++ b/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp @@ -19,20 +19,19 @@ #include "ClustersEngine.hpp" -#include "database/Artist.hpp" -#include "database/Cluster.hpp" -#include "database/Db.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackList.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackList.hpp" namespace lms::recommendation { - using namespace db; - std::unique_ptr createClustersEngine(Db& db) + std::unique_ptr createClustersEngine(db::IDb& db) { return std::make_unique(db); } diff --git a/src/libs/services/recommendation/impl/clusters/ClustersEngine.hpp b/src/libs/services/recommendation/impl/clusters/ClustersEngine.hpp index 7cf50846..4ae10610 100644 --- a/src/libs/services/recommendation/impl/clusters/ClustersEngine.hpp +++ b/src/libs/services/recommendation/impl/clusters/ClustersEngine.hpp @@ -27,7 +27,7 @@ namespace lms::recommendation class ClusterEngine : public IEngine { public: - ClusterEngine(db::Db& db) + ClusterEngine(db::IDb& db) : _db{ db } {} ~ClusterEngine() override = default; @@ -45,7 +45,7 @@ namespace lms::recommendation ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const override; ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const override; - db::Db& _db; + db::IDb& _db; }; } // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp b/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp index cca183fc..cb3bf190 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp +++ b/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp @@ -23,21 +23,21 @@ #include "core/ILogger.hpp" #include "core/Random.hpp" -#include "database/Artist.hpp" -#include "database/Db.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackArtistLink.hpp" -#include "database/TrackFeatures.hpp" -#include "database/TrackList.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/TrackFeatures.hpp" +#include "database/objects/TrackList.hpp" #include "som/DataNormalizer.hpp" namespace lms::recommendation { using namespace db; - std::unique_ptr createFeaturesEngine(Db& db) + std::unique_ptr createFeaturesEngine(db::IDb& db) { return std::make_unique(db); } diff --git a/src/libs/services/recommendation/impl/features/FeaturesEngine.hpp b/src/libs/services/recommendation/impl/features/FeaturesEngine.hpp index 1e0c91fa..3633a9f2 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesEngine.hpp +++ b/src/libs/services/recommendation/impl/features/FeaturesEngine.hpp @@ -46,7 +46,7 @@ namespace lms::recommendation class FeaturesEngine : public IEngine { public: - FeaturesEngine(db::Db& db) + FeaturesEngine(db::IDb& db) : _db{ db } {} FeaturesEngine(const FeaturesEngine&) = delete; @@ -105,7 +105,7 @@ namespace lms::recommendation const ObjectPositions& objectPositions, std::size_t maxCount) const; - db::Db& _db; + db::IDb& _db; bool _loadCancelled{}; std::unique_ptr _network; double _networkRefVectorsDistanceMedian{}; diff --git a/src/libs/services/recommendation/impl/features/FeaturesEngineCache.hpp b/src/libs/services/recommendation/impl/features/FeaturesEngineCache.hpp index 84ca8697..1b4794ae 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesEngineCache.hpp +++ b/src/libs/services/recommendation/impl/features/FeaturesEngineCache.hpp @@ -22,7 +22,7 @@ #include #include -#include "database/TrackId.hpp" +#include "database/objects/TrackId.hpp" #include "som/Network.hpp" namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.cpp b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.cpp index 731cddd8..f5a27025 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.cpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.cpp @@ -21,10 +21,10 @@ #include -#include "database/Db.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" namespace lms::recommendation::PlaylistGeneratorConstraint { @@ -42,7 +42,7 @@ namespace lms::recommendation::PlaylistGeneratorConstraint } } // namespace - ConsecutiveArtists::ConsecutiveArtists(db::Db& db) + ConsecutiveArtists::ConsecutiveArtists(db::IDb& db) : _db{ db } { } diff --git a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.hpp b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.hpp index 15ce02f4..4082b927 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.hpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.hpp @@ -23,7 +23,7 @@ namespace lms::db { - class Db; + class IDb; } namespace lms::recommendation::PlaylistGeneratorConstraint @@ -31,7 +31,7 @@ namespace lms::recommendation::PlaylistGeneratorConstraint class ConsecutiveArtists : public IConstraint { public: - ConsecutiveArtists(db::Db& db); + ConsecutiveArtists(db::IDb& db); ~ConsecutiveArtists() override = default; ConsecutiveArtists(const ConsecutiveArtists&) = delete; ConsecutiveArtists& operator=(const ConsecutiveArtists&) = delete; @@ -40,6 +40,6 @@ namespace lms::recommendation::PlaylistGeneratorConstraint float computeScore(const TrackContainer& trackIds, std::size_t trackIndex) override; ArtistContainer getArtists(db::TrackId trackId); - db::Db& _db; + db::IDb& _db; }; } // namespace lms::recommendation::PlaylistGeneratorConstraint diff --git a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.cpp b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.cpp index 9e072f07..9b0f2802 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.cpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.cpp @@ -19,14 +19,14 @@ #include "ConsecutiveReleases.hpp" -#include "database/Db.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" namespace lms::recommendation::PlaylistGeneratorConstraint { - ConsecutiveReleases::ConsecutiveReleases(db::Db& db) + ConsecutiveReleases::ConsecutiveReleases(db::IDb& db) : _db{ db } { } diff --git a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.hpp b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.hpp index 11480ae7..819d4ac1 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.hpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.hpp @@ -21,11 +21,11 @@ #include "IConstraint.hpp" -#include "database/ReleaseId.hpp" +#include "database/objects/ReleaseId.hpp" namespace lms::db { - class Db; + class IDb; } namespace lms::recommendation::PlaylistGeneratorConstraint @@ -33,7 +33,7 @@ namespace lms::recommendation::PlaylistGeneratorConstraint class ConsecutiveReleases : public IConstraint { public: - ConsecutiveReleases(db::Db& db); + ConsecutiveReleases(db::IDb& db); ~ConsecutiveReleases() override = default; ConsecutiveReleases(const ConsecutiveReleases&) = delete; ConsecutiveReleases& operator=(const ConsecutiveReleases&) = delete; @@ -43,6 +43,6 @@ namespace lms::recommendation::PlaylistGeneratorConstraint db::ReleaseId getReleaseId(db::TrackId trackId); - db::Db& _db; + db::IDb& _db; }; } // namespace lms::recommendation::PlaylistGeneratorConstraint diff --git a/src/libs/services/recommendation/include/services/recommendation/IPlaylistGeneratorService.hpp b/src/libs/services/recommendation/include/services/recommendation/IPlaylistGeneratorService.hpp index 58bda161..b36dd023 100644 --- a/src/libs/services/recommendation/include/services/recommendation/IPlaylistGeneratorService.hpp +++ b/src/libs/services/recommendation/include/services/recommendation/IPlaylistGeneratorService.hpp @@ -21,12 +21,12 @@ #include -#include "database/TrackListId.hpp" +#include "database/objects/TrackListId.hpp" #include "services/recommendation/Types.hpp" namespace lms::db { - class Db; + class IDb; } namespace lms::recommendation @@ -41,5 +41,5 @@ namespace lms::recommendation virtual TrackContainer extendPlaylist(db::TrackListId tracklistId, std::size_t maxCount) const = 0; }; - std::unique_ptr createPlaylistGeneratorService(db::Db& db, IRecommendationService& recommendationService); + std::unique_ptr createPlaylistGeneratorService(db::IDb& db, IRecommendationService& recommendationService); } // namespace lms::recommendation diff --git a/src/libs/services/recommendation/include/services/recommendation/IRecommendationService.hpp b/src/libs/services/recommendation/include/services/recommendation/IRecommendationService.hpp index 0872cc69..fabb9a0d 100644 --- a/src/libs/services/recommendation/include/services/recommendation/IRecommendationService.hpp +++ b/src/libs/services/recommendation/include/services/recommendation/IRecommendationService.hpp @@ -23,15 +23,15 @@ #include #include "core/EnumSet.hpp" -#include "database/ArtistId.hpp" -#include "database/ReleaseId.hpp" -#include "database/TrackListId.hpp" #include "database/Types.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackListId.hpp" #include "services/recommendation/Types.hpp" namespace lms::db { - class Db; + class IDb; } namespace lms::recommendation @@ -49,5 +49,5 @@ namespace lms::recommendation virtual ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const = 0; }; - std::unique_ptr createRecommendationService(db::Db& db); + std::unique_ptr createRecommendationService(db::IDb& db); } // namespace lms::recommendation diff --git a/src/libs/services/recommendation/include/services/recommendation/Types.hpp b/src/libs/services/recommendation/include/services/recommendation/Types.hpp index 53a7c9b8..5389640e 100644 --- a/src/libs/services/recommendation/include/services/recommendation/Types.hpp +++ b/src/libs/services/recommendation/include/services/recommendation/Types.hpp @@ -2,9 +2,9 @@ #include -#include "database/ArtistId.hpp" -#include "database/ReleaseId.hpp" -#include "database/TrackId.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackId.hpp" namespace lms::recommendation { diff --git a/src/libs/services/scanner/CMakeLists.txt b/src/libs/services/scanner/CMakeLists.txt index 62d6f0f2..86cec564 100644 --- a/src/libs/services/scanner/CMakeLists.txt +++ b/src/libs/services/scanner/CMakeLists.txt @@ -8,8 +8,7 @@ add_library(lmsscanner STATIC impl/scanners/LyricsFileScanner.cpp impl/scanners/PlayListFileScanner.cpp impl/scanners/Utils.cpp - impl/steps/ArtworkUtils.cpp - impl/steps/FileScanQueue.cpp + impl/steps/JobQueue.cpp impl/steps/ScanErrorLogger.cpp impl/steps/ScanStepArtistReconciliation.cpp impl/steps/ScanStepAssociateArtistImages.cpp @@ -22,12 +21,13 @@ add_library(lmsscanner STATIC impl/steps/ScanStepCheckForRemovedFiles.cpp impl/steps/ScanStepCompact.cpp impl/steps/ScanStepComputeClusterStats.cpp - impl/steps/ScanStepDiscoverFiles.cpp impl/steps/ScanStepOptimize.cpp impl/steps/ScanStepRemoveOrphanedDbEntries.cpp impl/steps/ScanStepScanFiles.cpp impl/steps/ScanStepUpdateLibraryFields.cpp + impl/FileScanners.cpp impl/ScannerService.cpp + impl/ScannerServiceTraceLogger.cpp impl/ScannerStats.cpp ) diff --git a/src/libs/services/scanner/impl/FileScanners.cpp b/src/libs/services/scanner/impl/FileScanners.cpp new file mode 100644 index 00000000..3a43e656 --- /dev/null +++ b/src/libs/services/scanner/impl/FileScanners.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2025 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 "FileScanners.hpp" + +#include + +#include "core/String.hpp" + +#include "scanners/IFileScanner.hpp" + +namespace lms::scanner +{ + void FileScanners::add(std::unique_ptr scanner) + { + for (const std::filesystem::path& file : scanner->getSupportedFiles()) + { + [[maybe_unused]] auto [it, inserted]{ _scannerByFile.emplace(file, scanner.get()) }; + assert(inserted); + } + + for (const std::filesystem::path& extension : scanner->getSupportedExtensions()) + { + [[maybe_unused]] auto [it, inserted]{ _scannerByExtension.emplace(extension, scanner.get()) }; + assert(inserted); + } + + _fileScanners.emplace_back(std::move(scanner)); + } + + void FileScanners::clear() + { + _fileScanners.clear(); + _scannerByFile.clear(); + _scannerByExtension.clear(); + } + + IFileScanner* FileScanners::select(const std::filesystem::path& filePath) const + { + { + const std::string fileName{ core::stringUtils::stringToLower(filePath.filename().string()) }; + + auto itScanner{ _scannerByFile.find(fileName) }; + if (itScanner != std::cend(_scannerByFile)) + return itScanner->second; + } + + { + const std::string extension{ core::stringUtils::stringToLower(filePath.extension().string()) }; + + auto itScanner{ _scannerByExtension.find(extension) }; + if (itScanner != std::cend(_scannerByExtension)) + return itScanner->second; + } + + return nullptr; + } + + void FileScanners::visit(const std::function& visitor) const + { + for (const auto& scanner : _fileScanners) + visitor(*scanner); + } + +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/FileScanners.hpp b/src/libs/services/scanner/impl/FileScanners.hpp new file mode 100644 index 00000000..8c7e6af2 --- /dev/null +++ b/src/libs/services/scanner/impl/FileScanners.hpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2025 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 +#include +#include +#include + +namespace lms::scanner +{ + class IFileScanner; + + class FileScanners + { + public: + void add(std::unique_ptr scanner); + void clear(); + + IFileScanner* select(const std::filesystem::path& filePath) const; + void visit(const std::function& visitor) const; + + private: + std::unordered_map _scannerByFile; + std::unordered_map _scannerByExtension; + std::vector> _fileScanners; + }; +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/MediaLibraryInfo.hpp b/src/libs/services/scanner/impl/MediaLibraryInfo.hpp index 5b62eb83..116aa748 100644 --- a/src/libs/services/scanner/impl/MediaLibraryInfo.hpp +++ b/src/libs/services/scanner/impl/MediaLibraryInfo.hpp @@ -21,7 +21,7 @@ #include -#include "database/MediaLibraryId.hpp" +#include "database/objects/MediaLibraryId.hpp" namespace lms::scanner { diff --git a/src/libs/services/scanner/impl/ScannerService.cpp b/src/libs/services/scanner/impl/ScannerService.cpp index f5bc18b8..b5b8a84b 100644 --- a/src/libs/services/scanner/impl/ScannerService.cpp +++ b/src/libs/services/scanner/impl/ScannerService.cpp @@ -24,11 +24,12 @@ #include #include "core/IConfig.hpp" +#include "core/IJobScheduler.hpp" #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" -#include "database/MediaLibrary.hpp" -#include "database/ScanSettings.hpp" #include "database/Session.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/ScanSettings.hpp" #include "scanners/ArtistInfoFileScanner.hpp" #include "scanners/AudioFileScanner.hpp" @@ -46,7 +47,6 @@ #include "steps/ScanStepCheckForRemovedFiles.hpp" #include "steps/ScanStepCompact.hpp" #include "steps/ScanStepComputeClusterStats.hpp" -#include "steps/ScanStepDiscoverFiles.hpp" #include "steps/ScanStepOptimize.hpp" #include "steps/ScanStepRemoveOrphanedDbEntries.hpp" #include "steps/ScanStepScanFiles.hpp" @@ -139,18 +139,47 @@ namespace lms::scanner scanSettings.modify()->setSkipSingleReleasePlayLists(settings.skipSingleReleasePlayLists); // TODO add more fields } + + std::size_t getScannerThreadCount() + { + std::size_t threadCount{ core::Service::get()->getULong("scanner-thread-count", 0) }; + + if (threadCount == 0) + threadCount = std::max(std::thread::hardware_concurrency() / 2, 1); + + return threadCount; + } + } // namespace - std::unique_ptr createScannerService(Db& db) + std::unique_ptr createScannerService(db::IDb& db) { return std::make_unique(db); } - ScannerService::ScannerService(Db& db) + ScannerService::ScannerService(db::IDb& db) : _db{ db } + , _jobScheduler{ core::createJobScheduler("Scanner", getScannerThreadCount()) } { _ioService.setThreadCount(1); + LMS_LOG(DBUPDATER, INFO, "Using " << _jobScheduler->getThreadCount() << " thread(s) for jobs"); + _jobScheduler->setShouldAbortCallback([this]() { return _abortScan; }); + + std::size_t totalFileCount{}; + { + auto& session{ _db.getTLSSession() }; + auto transaction{ session.createReadTransaction() }; + totalFileCount = session.getFileStats().getTotalFileCount(); + } + + // Force optimize in case scanner aborted during a large import, but do this only if there are enough elements in the database + // Otherwise, indexes may be not used and queries may be slower and slower while adding more and more elements in the db + LMS_LOG(DBUPDATER, INFO, "Scanned file count = " << totalFileCount); + if (totalFileCount >= 1'000) + _db.getTLSSession().fullAnalyze(); + + refreshTracingLoggerStats(); refreshScanSettings(); start(); @@ -356,7 +385,14 @@ namespace lms::scanner _currentScanStepStats.reset(); // must be sync with _curState } - LMS_LOG(DBUPDATER, INFO, "Scan " << (_abortScan ? "aborted" : "complete") << ". Changes = " << stats.nbChanges() << " (added = " << stats.additions << ", removed = " << stats.deletions << ", updated = " << stats.updates << ", failures = " << stats.failures << "), Not changed = " << stats.skips << ", Scanned = " << stats.scans << " (errors = " << stats.errorsCount << "), features fetched = " << stats.featuresFetched << ", duplicates = " << stats.duplicates.size()); + refreshTracingLoggerStats(); + LMS_LOG(DBUPDATER, INFO, "Scan " << (_abortScan ? "aborted" : "complete") << ". Changes = " << stats.getChangesCount() << " (added = " << stats.additions << ", removed = " << stats.deletions << ", updated = " << stats.updates << ", failures = " << stats.failures << "), Not changed = " << stats.skips << ", Scanned = " << stats.scans << " (errors = " << stats.errorsCount << "), features fetched = " << stats.featuresFetched << ", duplicates = " << stats.duplicates.size()); + + { + auto transaction{ _db.getTLSSession().createReadTransaction() }; + const db::FileStats stats{ _db.getTLSSession().getFileStats() }; + LMS_LOG(DBUPDATER, INFO, stats.getTotalFileCount() << " total files: " << stats.artistInfoCount << " artist info, " << stats.imageCount << " images, " << stats.playListCount << " playlists, " << stats.trackCount << " tracks, " << stats.trackLyricsCount << " lyrics"); + } if (!_abortScan) { @@ -432,32 +468,37 @@ namespace lms::scanner if (!_lastScanSettings) _lastScanSettings = readScannerSettings(_db.getTLSSession(), lastScanSettingsName); - auto cbFunc{ [this](const ScanStepStats& stats) { + auto progressFunc{ [this](const ScanStepStats& stats) { notifyInProgressIfNeeded(stats); } }; _fileScanners.clear(); - _fileScanners.emplace_back(std::make_unique(_db, _settings)); - _fileScanners.emplace_back(std::make_unique(_db, _settings)); - _fileScanners.emplace_back(std::make_unique(_db, _settings)); - _fileScanners.emplace_back(std::make_unique(_db, _settings)); - _fileScanners.emplace_back(std::make_unique(_db, _settings)); + _fileScanners.add(std::make_unique(_db, _settings)); + _fileScanners.add(std::make_unique(_db, _settings)); + _fileScanners.add(std::make_unique(_db, _settings)); + _fileScanners.add(std::make_unique(_db, _settings)); + _fileScanners.add(std::make_unique(_db, _settings)); - std::vector fileScanners; - std::transform(std::cbegin(_fileScanners), std::cend(_fileScanners), std::back_inserter(fileScanners), [](const std::unique_ptr& scanner) { return scanner.get(); }); + _fileScanners.visit([](const IFileScanner& scanner) { + for (const std::filesystem::path& file : scanner.getSupportedFiles()) + LMS_LOG(DBUPDATER, INFO, scanner.getName() << ": supporting file " << file); + + for (const std::filesystem::path& extension : scanner.getSupportedExtensions()) + LMS_LOG(DBUPDATER, INFO, scanner.getName() << ": supporting file extension " << extension); + }); ScanStepBase::InitParams params{ + .jobScheduler = *_jobScheduler, .settings = _settings, .lastScanSettings = _lastScanSettings.has_value() ? &(_lastScanSettings.value()) : nullptr, - .progressCallback = cbFunc, + .progressCallback = progressFunc, .abortScan = _abortScan, .db = _db, - .fileScanners = fileScanners, + .fileScanners = _fileScanners, }; // Order is important: steps are sequential _scanSteps.clear(); - _scanSteps.emplace_back(std::make_unique(params)); _scanSteps.emplace_back(std::make_unique(params)); _scanSteps.emplace_back(std::make_unique(params)); _scanSteps.emplace_back(std::make_unique(params)); @@ -483,14 +524,14 @@ namespace lms::scanner _currentScanStepStats = stepStats; } - const std::chrono::system_clock::time_point now{ std::chrono::system_clock::now() }; + const auto now{ std::chrono::steady_clock::now() }; _events.scanInProgress.emit(stepStats); _lastScanInProgressEmit = now; } void ScannerService::notifyInProgressIfNeeded(const ScanStepStats& stepStats) { - std::chrono::system_clock::time_point now{ std::chrono::system_clock::now() }; + const auto now{ std::chrono::steady_clock::now() }; if (now - _lastScanInProgressEmit >= std::chrono::seconds{ 1 }) notifyInProgress(stepStats); diff --git a/src/libs/services/scanner/impl/ScannerService.hpp b/src/libs/services/scanner/impl/ScannerService.hpp index fd615dc5..88779ae9 100644 --- a/src/libs/services/scanner/impl/ScannerService.hpp +++ b/src/libs/services/scanner/impl/ScannerService.hpp @@ -20,6 +20,7 @@ #pragma once #include +#include #include #include #include @@ -30,12 +31,18 @@ #include #include +#include "FileScanners.hpp" #include "ScannerSettings.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" #include "services/scanner/IScannerService.hpp" #include "steps/IScanStep.hpp" +namespace lms::core +{ + class IJobScheduler; +} + namespace lms::scanner { class IFileScanner; @@ -46,7 +53,7 @@ namespace lms::scanner class ScannerService : public IScannerService { public: - ScannerService(db::Db& db); + ScannerService(db::IDb& db); ~ScannerService() override; ScannerService(const ScannerService&) = delete; ScannerService& operator=(const ScannerService&) = delete; @@ -75,11 +82,15 @@ namespace lms::scanner // Helpers void refreshScanSettings(); + void refreshTracingLoggerStats(); void notifyInProgressIfNeeded(const ScanStepStats& stats); void notifyInProgress(const ScanStepStats& stats); - std::vector> _fileScanners; + db::IDb& _db; + std::unique_ptr _jobScheduler; + + FileScanners _fileScanners; std::vector> _scanSteps; std::mutex _controlMutex; @@ -87,8 +98,7 @@ namespace lms::scanner Wt::WIOService _ioService; boost::asio::system_timer _scheduleTimer{ _ioService }; Events _events; - std::chrono::system_clock::time_point _lastScanInProgressEmit; - db::Db& _db; + std::chrono::steady_clock::time_point _lastScanInProgressEmit; mutable std::shared_mutex _statusMutex; State _curState{ State::NotScheduled }; diff --git a/src/libs/services/scanner/impl/ScannerServiceTraceLogger.cpp b/src/libs/services/scanner/impl/ScannerServiceTraceLogger.cpp new file mode 100644 index 00000000..1c25f484 --- /dev/null +++ b/src/libs/services/scanner/impl/ScannerServiceTraceLogger.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013 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 "ScannerService.hpp" + +#include "database/objects/Artist.hpp" +#include "database/objects/ArtistInfo.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Image.hpp" +#include "database/objects/Listen.hpp" +#include "database/objects/StarredArtist.hpp" +#include "database/objects/StarredRelease.hpp" +#include "database/objects/StarredTrack.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/TrackBookmark.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" +#include "database/objects/TrackEmbeddedImageLink.hpp" + +namespace lms::scanner +{ + void ScannerService::refreshTracingLoggerStats() + { + auto* traceLogger{ core::Service::get() }; + if (!traceLogger) + return; + + auto& session{ _db.getTLSSession() }; + auto transaction{ session.createReadTransaction() }; + + traceLogger->setMetadata("db_artist_count", std::to_string(db::Artist::getCount(session))); + traceLogger->setMetadata("db_artist_info_count", std::to_string(db::ArtistInfo::getCount(session))); + traceLogger->setMetadata("db_cluster_count", std::to_string(db::Cluster::getCount(session))); + traceLogger->setMetadata("db_cluster_type_count", std::to_string(db::ClusterType::getCount(session))); + traceLogger->setMetadata("db_image_count", std::to_string(db::Image::getCount(session))); + traceLogger->setMetadata("db_listen_count", std::to_string(db::Listen::getCount(session))); + traceLogger->setMetadata("db_release_count", std::to_string(db::Release::getCount(session))); + traceLogger->setMetadata("db_starred_artist_count", std::to_string(db::StarredArtist::getCount(session))); + traceLogger->setMetadata("db_starred_release_count", std::to_string(db::StarredRelease::getCount(session))); + traceLogger->setMetadata("db_starred_track_count", std::to_string(db::StarredTrack::getCount(session))); + traceLogger->setMetadata("db_track_bookmark_count", std::to_string(db::TrackBookmark::getCount(session))); + traceLogger->setMetadata("db_track_count", std::to_string(db::Track::getCount(session))); + traceLogger->setMetadata("db_track_artist_link_count", std::to_string(db::TrackArtistLink::getCount(session))); + traceLogger->setMetadata("db_track_embedded_image_count", std::to_string(db::TrackEmbeddedImage::getCount(session))); + traceLogger->setMetadata("db_track_embedded_image_link_count", std::to_string(db::TrackEmbeddedImageLink::getCount(session))); + } +} // namespace lms::scanner \ No newline at end of file diff --git a/src/libs/services/scanner/impl/ScannerSettings.hpp b/src/libs/services/scanner/impl/ScannerSettings.hpp index d915404b..0a450b3b 100644 --- a/src/libs/services/scanner/impl/ScannerSettings.hpp +++ b/src/libs/services/scanner/impl/ScannerSettings.hpp @@ -25,7 +25,7 @@ #include -#include "database/ScanSettings.hpp" +#include "database/objects/ScanSettings.hpp" #include "MediaLibraryInfo.hpp" diff --git a/src/libs/services/scanner/impl/ScannerStats.cpp b/src/libs/services/scanner/impl/ScannerStats.cpp index 37887424..74f15f14 100644 --- a/src/libs/services/scanner/impl/ScannerStats.cpp +++ b/src/libs/services/scanner/impl/ScannerStats.cpp @@ -21,12 +21,12 @@ namespace lms::scanner { - std::size_t ScanStats::nbFiles() const + std::size_t ScanStats::getTotalFileCount() const { return skips + additions + updates + failures; } - std::size_t ScanStats::nbChanges() const + std::size_t ScanStats::getChangesCount() const { return additions + deletions + updates; } diff --git a/src/libs/services/scanner/impl/helpers/ArtistHelpers.hpp b/src/libs/services/scanner/impl/helpers/ArtistHelpers.hpp index 83af1527..1c81320a 100644 --- a/src/libs/services/scanner/impl/helpers/ArtistHelpers.hpp +++ b/src/libs/services/scanner/impl/helpers/ArtistHelpers.hpp @@ -20,7 +20,7 @@ #pragma once #include "core/TaggedType.hpp" -#include "database/Artist.hpp" +#include "database/objects/Artist.hpp" namespace lms::metadata { diff --git a/src/libs/services/scanner/impl/scanners/ArtistInfoFileScanner.cpp b/src/libs/services/scanner/impl/scanners/ArtistInfoFileScanner.cpp index 21469e80..c6a984be 100644 --- a/src/libs/services/scanner/impl/scanners/ArtistInfoFileScanner.cpp +++ b/src/libs/services/scanner/impl/scanners/ArtistInfoFileScanner.cpp @@ -24,11 +24,11 @@ #include "core/ILogger.hpp" #include "core/String.hpp" -#include "database/Artist.hpp" -#include "database/ArtistInfo.hpp" -#include "database/Db.hpp" -#include "database/MediaLibrary.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/ArtistInfo.hpp" +#include "database/objects/MediaLibrary.hpp" #include "metadata/ArtistInfo.hpp" #include "metadata/Types.hpp" #include "services/scanner/ScanErrors.hpp" @@ -137,7 +137,7 @@ namespace lms::scanner } } // namespace - ArtistInfoFileScanner::ArtistInfoFileScanner(db::Db& db, const ScannerSettings& settings) + ArtistInfoFileScanner::ArtistInfoFileScanner(db::IDb& db, const ScannerSettings& settings) : _db{ db } , _settings{ settings } { diff --git a/src/libs/services/scanner/impl/scanners/ArtistInfoFileScanner.hpp b/src/libs/services/scanner/impl/scanners/ArtistInfoFileScanner.hpp index 4d0af151..8036d4e9 100644 --- a/src/libs/services/scanner/impl/scanners/ArtistInfoFileScanner.hpp +++ b/src/libs/services/scanner/impl/scanners/ArtistInfoFileScanner.hpp @@ -21,13 +21,10 @@ #include "IFileScanner.hpp" -namespace lms +namespace lms::db { - namespace db - { - class Db; - } -} // namespace lms + class IDb; +} namespace lms::scanner { @@ -36,7 +33,7 @@ namespace lms::scanner class ArtistInfoFileScanner : public IFileScanner { public: - ArtistInfoFileScanner(db::Db& db, const ScannerSettings& _settings); + ArtistInfoFileScanner(db::IDb& db, const ScannerSettings& _settings); ~ArtistInfoFileScanner() override = default; ArtistInfoFileScanner(const ArtistInfoFileScanner&) = delete; ArtistInfoFileScanner& operator=(const ArtistInfoFileScanner&) = delete; @@ -48,7 +45,7 @@ namespace lms::scanner bool needsScan(const FileToScan& file) const override; std::unique_ptr createScanOperation(FileToScan&& fileToScan) const override; - db::Db& _db; + db::IDb& _db; const ScannerSettings& _settings; }; } // namespace lms::scanner \ No newline at end of file diff --git a/src/libs/services/scanner/impl/scanners/AudioFileScanOperation.cpp b/src/libs/services/scanner/impl/scanners/AudioFileScanOperation.cpp index e5fcf43b..d0d30e44 100644 --- a/src/libs/services/scanner/impl/scanners/AudioFileScanOperation.cpp +++ b/src/libs/services/scanner/impl/scanners/AudioFileScanOperation.cpp @@ -24,20 +24,21 @@ #include "core/PartialDateTime.hpp" #include "core/Path.hpp" #include "core/XxHash3.hpp" -#include "database/Artist.hpp" -#include "database/Cluster.hpp" -#include "database/Db.hpp" -#include "database/Directory.hpp" -#include "database/MediaLibrary.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackArtistLink.hpp" -#include "database/TrackEmbeddedImage.hpp" -#include "database/TrackEmbeddedImageLink.hpp" -#include "database/TrackFeatures.hpp" -#include "database/TrackLyrics.hpp" #include "database/Types.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" +#include "database/objects/TrackEmbeddedImageLink.hpp" +#include "database/objects/TrackFeatures.hpp" +#include "database/objects/TrackLyrics.hpp" #include "image/Exception.hpp" #include "image/Image.hpp" #include "metadata/Exception.hpp" @@ -332,6 +333,8 @@ namespace lms::scanner image.modify()->setWidth(imageInfo.properties.width); image.modify()->setHeight(imageInfo.properties.height); image.modify()->setMimeType(imageInfo.mimeType); + + session.create(image); } return image; @@ -376,11 +379,12 @@ namespace lms::scanner return db::Advisory::UnSet; } - db::Track::pointer findMovedTrackBySizeAndMetaData(db::Session& session, const metadata::Track& parsedTrack, size_t fileSize, const std::filesystem::path& relativePath) + db::Track::pointer findMovedTrackBySizeAndMetaData(db::Session& session, const metadata::Track& parsedTrack, const std::filesystem::path& trackPath, size_t fileSize) { db::Track::FindParameters params; // Add as many fields as possible to limit errors params.setName(parsedTrack.title); + params.setFileSize(fileSize); if (parsedTrack.medium) { if (parsedTrack.medium->position) @@ -390,7 +394,6 @@ namespace lms::scanner } if (parsedTrack.position) params.setTrackNumber(*parsedTrack.position); - params.setFileSize(fileSize); bool error{}; db::Track::pointer res; @@ -402,7 +405,7 @@ namespace lms::scanner if (res) { - LMS_LOG(DBUPDATER, DEBUG, "Found too many candidates for file move. New file = " << relativePath << ", candidate = " << track->getAbsoluteFilePath() << ", previous candidate = " << res->getAbsoluteFilePath()); + LMS_LOG(DBUPDATER, DEBUG, "Found too many candidates for file move. New file = " << trackPath << ", candidate = " << track->getAbsoluteFilePath() << ", previous candidate = " << res->getAbsoluteFilePath()); error = true; } res = track; @@ -463,7 +466,7 @@ namespace lms::scanner } } // namespace - AudioFileScanOperation::AudioFileScanOperation(FileToScan&& fileToScan, db::Db& db, const ScannerSettings& settings, metadata::IAudioFileParser& parser) + AudioFileScanOperation::AudioFileScanOperation(FileToScan&& fileToScan, db::IDb& db, const ScannerSettings& settings, metadata::IAudioFileParser& parser) : FileScanOperationBase{ std::move(fileToScan), db, settings } , _parser{ parser } { @@ -473,7 +476,6 @@ namespace lms::scanner void AudioFileScanOperation::scan() { - LMS_SCOPED_TRACE_OVERVIEW("Scanner", "ScanAudioFile"); std::unique_ptr track; try @@ -595,7 +597,7 @@ namespace lms::scanner if (!track) { // maybe the file just moved? - track = findMovedTrackBySizeAndMetaData(dbSession, *_parsedTrack, getFileSize(), getRelativeFilePath()); + track = findMovedTrackBySizeAndMetaData(dbSession, *_parsedTrack, getFilePath(), getFileSize()); if (track) { LMS_LOG(DBUPDATER, DEBUG, "Considering track " << getFilePath() << " moved from " << track->getAbsoluteFilePath()); @@ -650,7 +652,6 @@ namespace lms::scanner track.modify()->setDuration(_parsedTrack->audioProperties.duration); track.modify()->setSampleRate(_parsedTrack->audioProperties.sampleRate); - track.modify()->setRelativeFilePath(getRelativeFilePath()); track.modify()->setFileSize(getFileSize()); track.modify()->setLastWriteTime(getLastWriteTime()); @@ -684,6 +685,7 @@ namespace lms::scanner createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Composer, _parsedTrack->composerArtists, allowFallback); createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Lyricist, _parsedTrack->lyricistArtists, allowFallback); createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Mixer, _parsedTrack->mixerArtists, allowFallback); + createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Producer, _parsedTrack->producerArtists, allowFallback); createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Remixer, _parsedTrack->remixerArtists, allowFallback); for (const auto& [role, performers] : _parsedTrack->performerArtists) diff --git a/src/libs/services/scanner/impl/scanners/AudioFileScanOperation.hpp b/src/libs/services/scanner/impl/scanners/AudioFileScanOperation.hpp index a6bf3c54..20344ee9 100644 --- a/src/libs/services/scanner/impl/scanners/AudioFileScanOperation.hpp +++ b/src/libs/services/scanner/impl/scanners/AudioFileScanOperation.hpp @@ -32,7 +32,7 @@ namespace lms::db { - class Db; + class IDb; } // namespace lms::db namespace lms::metadata @@ -56,7 +56,7 @@ namespace lms::scanner class AudioFileScanOperation : public FileScanOperationBase { public: - AudioFileScanOperation(FileToScan&& fileToScan, db::Db& db, const ScannerSettings& settings, metadata::IAudioFileParser& parser); + AudioFileScanOperation(FileToScan&& fileToScan, db::IDb& db, const ScannerSettings& settings, metadata::IAudioFileParser& parser); ~AudioFileScanOperation() override; AudioFileScanOperation(const AudioFileScanOperation&) = delete; AudioFileScanOperation& operator=(const AudioFileScanOperation&) = delete; diff --git a/src/libs/services/scanner/impl/scanners/AudioFileScanner.cpp b/src/libs/services/scanner/impl/scanners/AudioFileScanner.cpp index 476bfea9..5b8d7a0c 100644 --- a/src/libs/services/scanner/impl/scanners/AudioFileScanner.cpp +++ b/src/libs/services/scanner/impl/scanners/AudioFileScanner.cpp @@ -21,10 +21,10 @@ #include "core/IConfig.hpp" #include "core/Service.hpp" -#include "database/Db.hpp" -#include "database/MediaLibrary.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/Track.hpp" #include "metadata/IAudioFileParser.hpp" #include "AudioFileScanOperation.hpp" @@ -63,7 +63,8 @@ namespace lms::scanner } } // namespace - AudioFileScanner::AudioFileScanner(db::Db& db, const ScannerSettings& settings) + + AudioFileScanner::AudioFileScanner(db::IDb& db, const ScannerSettings& settings) : _db{ db } , _settings{ settings } , _metadataParser{ metadata::createAudioFileParser(createAudioFileParserParameters(settings)) } // For now, always use TagLib @@ -92,10 +93,10 @@ namespace lms::scanner db::Session& dbSession{ _db.getTLSSession() }; auto transaction{ dbSession.createReadTransaction() }; - const db::Track::pointer track{ db::Track::findByPath(dbSession, file.filePath) }; - return !track - || track->getLastWriteTime() != file.lastWriteTime - || track->getScanVersion() != _settings.audioScanVersion; + std::optional fileInfo{ db::Track::findFileInfo(dbSession, file.filePath) }; + return !fileInfo + || fileInfo->lastWrittenTime != file.lastWriteTime + || fileInfo->scanVersion != _settings.audioScanVersion; } std::unique_ptr AudioFileScanner::createScanOperation(FileToScan&& fileToScan) const diff --git a/src/libs/services/scanner/impl/scanners/AudioFileScanner.hpp b/src/libs/services/scanner/impl/scanners/AudioFileScanner.hpp index 3fa46cbf..924f4baf 100644 --- a/src/libs/services/scanner/impl/scanners/AudioFileScanner.hpp +++ b/src/libs/services/scanner/impl/scanners/AudioFileScanner.hpp @@ -25,7 +25,7 @@ namespace lms { namespace db { - class Db; + class IDb; } namespace metadata @@ -41,7 +41,7 @@ namespace lms::scanner class AudioFileScanner : public IFileScanner { public: - AudioFileScanner(db::Db& db, const ScannerSettings& settings); + AudioFileScanner(db::IDb& db, const ScannerSettings& settings); ~AudioFileScanner() override; AudioFileScanner(const AudioFileScanner&) = delete; AudioFileScanner& operator=(const AudioFileScanner&) = delete; @@ -53,7 +53,7 @@ namespace lms::scanner bool needsScan(const FileToScan& file) const override; std::unique_ptr createScanOperation(FileToScan&& fileToScan) const override; - db::Db& _db; + db::IDb& _db; const ScannerSettings& _settings; std::unique_ptr _metadataParser; }; diff --git a/src/libs/services/scanner/impl/scanners/FileScanOperationBase.cpp b/src/libs/services/scanner/impl/scanners/FileScanOperationBase.cpp index ca3ca660..5275be53 100644 --- a/src/libs/services/scanner/impl/scanners/FileScanOperationBase.cpp +++ b/src/libs/services/scanner/impl/scanners/FileScanOperationBase.cpp @@ -23,7 +23,7 @@ namespace lms::scanner { - FileScanOperationBase::FileScanOperationBase(FileToScan&& fileToScan, db::Db& db, const ScannerSettings& settings) + FileScanOperationBase::FileScanOperationBase(FileToScan&& fileToScan, db::IDb& db, const ScannerSettings& settings) : _file{ std::move(fileToScan) } , _db{ db } , _settings{ settings } diff --git a/src/libs/services/scanner/impl/scanners/FileScanOperationBase.hpp b/src/libs/services/scanner/impl/scanners/FileScanOperationBase.hpp index 6d3b717d..f8318a52 100644 --- a/src/libs/services/scanner/impl/scanners/FileScanOperationBase.hpp +++ b/src/libs/services/scanner/impl/scanners/FileScanOperationBase.hpp @@ -27,7 +27,7 @@ namespace lms::db { - class Db; + class IDb; } // namespace lms::db namespace lms::scanner @@ -38,7 +38,7 @@ namespace lms::scanner class FileScanOperationBase : public IFileScanOperation { public: - FileScanOperationBase(FileToScan&& fileToScan, db::Db& db, const ScannerSettings& settings); + FileScanOperationBase(FileToScan&& fileToScan, db::IDb& db, const ScannerSettings& settings); ~FileScanOperationBase() override; FileScanOperationBase(const FileScanOperationBase&) = delete; FileScanOperationBase& operator=(const FileScanOperationBase&) = delete; @@ -46,11 +46,10 @@ namespace lms::scanner protected: const std::filesystem::path& getFilePath() const override { return _file.filePath; } const MediaLibraryInfo& getMediaLibrary() const { return _file.mediaLibrary; } - db::Db& getDb() { return _db; } + db::IDb& getDb() { return _db; } const ScannerSettings& getScannerSettings() const { return _settings; } Wt::WDateTime getLastWriteTime() const { return _file.lastWriteTime; } std::size_t getFileSize() const { return _file.fileSize; } - const std::filesystem::path& getRelativeFilePath() const { return _file.relativePath; } template void addError(CtrArgs&&... args) @@ -62,7 +61,7 @@ namespace lms::scanner private: const FileToScan _file; - db::Db& _db; + db::IDb& _db; const ScannerSettings& _settings; ScanErrorVector _errors; }; diff --git a/src/libs/services/scanner/impl/scanners/FileToScan.hpp b/src/libs/services/scanner/impl/scanners/FileToScan.hpp index 8f44015d..4996b26e 100644 --- a/src/libs/services/scanner/impl/scanners/FileToScan.hpp +++ b/src/libs/services/scanner/impl/scanners/FileToScan.hpp @@ -30,7 +30,6 @@ namespace lms::scanner struct FileToScan { std::filesystem::path filePath; - std::filesystem::path relativePath; MediaLibraryInfo mediaLibrary; Wt::WDateTime lastWriteTime; std::size_t fileSize{}; diff --git a/src/libs/services/scanner/impl/scanners/ImageFileScanner.cpp b/src/libs/services/scanner/impl/scanners/ImageFileScanner.cpp index 7869e938..b84e4672 100644 --- a/src/libs/services/scanner/impl/scanners/ImageFileScanner.cpp +++ b/src/libs/services/scanner/impl/scanners/ImageFileScanner.cpp @@ -22,10 +22,12 @@ #include #include "core/ILogger.hpp" -#include "database/Db.hpp" -#include "database/Image.hpp" -#include "database/MediaLibrary.hpp" + +#include "database/IDb.hpp" #include "database/Session.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Image.hpp" +#include "database/objects/MediaLibrary.hpp" #include "image/Exception.hpp" #include "image/Image.hpp" #include "services/scanner/ScanErrors.hpp" @@ -84,7 +86,10 @@ namespace lms::scanner const bool added{ !image }; if (!image) + { image = dbSession.create(getFilePath()); + dbSession.create(image); + } image.modify()->setLastWriteTime(getLastWriteTime()); image.modify()->setFileSize(getFileSize()); @@ -104,7 +109,7 @@ namespace lms::scanner } } // namespace - ImageFileScanner::ImageFileScanner(db::Db& db, ScannerSettings& settings) + ImageFileScanner::ImageFileScanner(db::IDb& db, ScannerSettings& settings) : _db{ db } , _settings{ settings } { diff --git a/src/libs/services/scanner/impl/scanners/ImageFileScanner.hpp b/src/libs/services/scanner/impl/scanners/ImageFileScanner.hpp index 4e34b0cb..7c4f8974 100644 --- a/src/libs/services/scanner/impl/scanners/ImageFileScanner.hpp +++ b/src/libs/services/scanner/impl/scanners/ImageFileScanner.hpp @@ -21,13 +21,10 @@ #include "IFileScanner.hpp" -namespace lms +namespace lms::db { - namespace db - { - class Db; - } -} // namespace lms + class IDb; +} namespace lms::scanner { @@ -36,7 +33,7 @@ namespace lms::scanner class ImageFileScanner : public IFileScanner { public: - ImageFileScanner(db::Db& db, ScannerSettings& settings); + ImageFileScanner(db::IDb& db, ScannerSettings& settings); ~ImageFileScanner() override = default; ImageFileScanner(const ImageFileScanner&) = delete; ImageFileScanner& operator=(const ImageFileScanner&) = delete; @@ -48,7 +45,7 @@ namespace lms::scanner bool needsScan(const FileToScan& file) const override; std::unique_ptr createScanOperation(FileToScan&& fileToScan) const override; - db::Db& _db; + db::IDb& _db; ScannerSettings& _settings; }; } // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/scanners/LyricsFileScanner.cpp b/src/libs/services/scanner/impl/scanners/LyricsFileScanner.cpp index f88e3563..de85094a 100644 --- a/src/libs/services/scanner/impl/scanners/LyricsFileScanner.cpp +++ b/src/libs/services/scanner/impl/scanners/LyricsFileScanner.cpp @@ -25,10 +25,10 @@ #include "FileScanOperationBase.hpp" #include "ScannerSettings.hpp" #include "core/ILogger.hpp" -#include "database/Db.hpp" -#include "database/MediaLibrary.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/TrackLyrics.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/TrackLyrics.hpp" #include "metadata/Lyrics.hpp" #include "services/scanner/ScanErrors.hpp" @@ -122,7 +122,7 @@ namespace lms::scanner } } // namespace - LyricsFileScanner::LyricsFileScanner(db::Db& db, ScannerSettings& _settings) + LyricsFileScanner::LyricsFileScanner(db::IDb& db, ScannerSettings& _settings) : _db{ db } , _settings{ _settings } { diff --git a/src/libs/services/scanner/impl/scanners/LyricsFileScanner.hpp b/src/libs/services/scanner/impl/scanners/LyricsFileScanner.hpp index 3bcb4f0d..0b265b77 100644 --- a/src/libs/services/scanner/impl/scanners/LyricsFileScanner.hpp +++ b/src/libs/services/scanner/impl/scanners/LyricsFileScanner.hpp @@ -21,13 +21,10 @@ #include "IFileScanner.hpp" -namespace lms +namespace lms::db { - namespace db - { - class Db; - } -} // namespace lms + class IDb; +} namespace lms::scanner { @@ -36,7 +33,7 @@ namespace lms::scanner class LyricsFileScanner : public IFileScanner { public: - LyricsFileScanner(db::Db& db, ScannerSettings& settings); + LyricsFileScanner(db::IDb& db, ScannerSettings& settings); ~LyricsFileScanner() override = default; LyricsFileScanner(const LyricsFileScanner&) = delete; LyricsFileScanner& operator=(const LyricsFileScanner&) = delete; @@ -48,7 +45,7 @@ namespace lms::scanner bool needsScan(const FileToScan& file) const override; std::unique_ptr createScanOperation(FileToScan&& fileToScan) const override; - db::Db& _db; + db::IDb& _db; ScannerSettings& _settings; }; } // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/scanners/PlayListFileScanner.cpp b/src/libs/services/scanner/impl/scanners/PlayListFileScanner.cpp index ca168dab..809ed1fa 100644 --- a/src/libs/services/scanner/impl/scanners/PlayListFileScanner.cpp +++ b/src/libs/services/scanner/impl/scanners/PlayListFileScanner.cpp @@ -23,10 +23,10 @@ #include #include "core/ILogger.hpp" -#include "database/Db.hpp" -#include "database/MediaLibrary.hpp" -#include "database/PlayListFile.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/PlayListFile.hpp" #include "metadata/Exception.hpp" #include "metadata/PlayList.hpp" @@ -121,7 +121,7 @@ namespace lms::scanner } } // namespace - PlayListFileScanner::PlayListFileScanner(db::Db& db, ScannerSettings& settings) + PlayListFileScanner::PlayListFileScanner(db::IDb& db, ScannerSettings& settings) : _db{ db } , _settings{ settings } { diff --git a/src/libs/services/scanner/impl/scanners/PlayListFileScanner.hpp b/src/libs/services/scanner/impl/scanners/PlayListFileScanner.hpp index db0f2333..0bbfb8a4 100644 --- a/src/libs/services/scanner/impl/scanners/PlayListFileScanner.hpp +++ b/src/libs/services/scanner/impl/scanners/PlayListFileScanner.hpp @@ -21,13 +21,10 @@ #include "IFileScanner.hpp" -namespace lms +namespace lms::db { - namespace db - { - class Db; - } -} // namespace lms + class IDb; +} namespace lms::scanner { @@ -36,7 +33,7 @@ namespace lms::scanner class PlayListFileScanner : public IFileScanner { public: - PlayListFileScanner(db::Db& db, ScannerSettings& settings); + PlayListFileScanner(db::IDb& db, ScannerSettings& settings); ~PlayListFileScanner() override = default; PlayListFileScanner(const PlayListFileScanner&) = delete; PlayListFileScanner& operator=(const PlayListFileScanner&) = delete; @@ -48,7 +45,7 @@ namespace lms::scanner bool needsScan(const FileToScan& file) const override; std::unique_ptr createScanOperation(FileToScan&& fileToScan) const override; - db::Db& _db; + db::IDb& _db; ScannerSettings& _settings; }; } // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/scanners/Utils.cpp b/src/libs/services/scanner/impl/scanners/Utils.cpp index 4469a3a1..fc187ce8 100644 --- a/src/libs/services/scanner/impl/scanners/Utils.cpp +++ b/src/libs/services/scanner/impl/scanners/Utils.cpp @@ -22,9 +22,9 @@ #include #include "core/Path.hpp" -#include "database/Directory.hpp" -#include "database/MediaLibrary.hpp" #include "database/Session.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/MediaLibrary.hpp" namespace lms::scanner::utils { diff --git a/src/libs/services/scanner/impl/steps/ArtworkUtils.cpp b/src/libs/services/scanner/impl/steps/ArtworkUtils.cpp deleted file mode 100644 index 153aca0d..00000000 --- a/src/libs/services/scanner/impl/steps/ArtworkUtils.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * 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 "ArtworkUtils.hpp" - -#include "database/Artwork.hpp" -#include "database/Image.hpp" -#include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackEmbeddedImage.hpp" - -namespace lms::scanner::utils -{ - namespace - { - std::filesystem::path toPath(db::Session& session, const db::TrackEmbeddedImageId trackEmbeddedImageId) - { - session.checkReadTransaction(); - - std::filesystem::path res; - - db::Track::FindParameters params; - params.setEmbeddedImage(trackEmbeddedImageId); - params.setRange(db::Range{ .offset = 0, .size = 1 }); - db::Track::find(session, params, [&](const db::Track::pointer& track) { - res = track->getAbsoluteFilePath(); - }); - - return res; - } - - std::filesystem::path toPath(db::Session& session, db::ImageId imageId) - { - session.checkReadTransaction(); - - db::Image::pointer image{ db::Image::find(session, imageId) }; - return image ? image->getAbsoluteFilePath() : std::filesystem::path{}; - } - - } // namespace - - db::ObjectPtr getOrCreateArtworkFromTrackEmbeddedImage(db::Session& session, db::TrackEmbeddedImageId trackEmbeddedImageId) - { - assert(trackEmbeddedImageId.isValid()); - session.checkWriteTransaction(); - - db::ObjectPtr artwork{ db::Artwork::find(session, trackEmbeddedImageId) }; - if (!artwork) - { - db::TrackEmbeddedImage::pointer trackEmbeddedImage{ db::TrackEmbeddedImage::find(session, trackEmbeddedImageId) }; - assert(trackEmbeddedImage); - artwork = session.create(trackEmbeddedImage); - } - return artwork; - } - - db::ObjectPtr getOrCreateArtworkFromImage(db::Session& session, db::ImageId imageId) - { - assert(imageId.isValid()); - session.checkWriteTransaction(); - - db::ObjectPtr artwork{ db::Artwork::find(session, imageId) }; - if (!artwork) - { - db::Image::pointer image{ db::Image::find(session, imageId) }; - assert(image); - artwork = session.create(image); - } - return artwork; - } - - std::filesystem::path toPath(db::Session& session, db::ArtworkId artworkId) - { - session.checkReadTransaction(); - - db::Artwork::pointer artwork{ db::Artwork::find(session, artworkId) }; - if (!artwork) - return std::filesystem::path{}; - - if (artwork->getTrackEmbeddedImageId().isValid()) - return toPath(session, artwork->getTrackEmbeddedImageId()); - if (artwork->getImageId().isValid()) - return toPath(session, artwork->getImageId()); - - return std::filesystem::path{}; - } - -} // namespace lms::scanner::utils \ No newline at end of file diff --git a/src/libs/services/scanner/impl/steps/FileScanQueue.cpp b/src/libs/services/scanner/impl/steps/FileScanQueue.cpp deleted file mode 100644 index 9b61a808..00000000 --- a/src/libs/services/scanner/impl/steps/FileScanQueue.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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 "FileScanQueue.hpp" - -#include - -#include "core/ILogger.hpp" -#include "core/ITraceLogger.hpp" - -#include "scanners/IFileScanOperation.hpp" - -namespace lms::scanner -{ - FileScanQueue::FileScanQueue(std::size_t threadCount, bool& abort) - : _scanContextRunner{ _scanIoContext, threadCount, "FileScan" } - , _abort{ abort } - { - } - - FileScanQueue::~FileScanQueue() = default; - - void FileScanQueue::pushScanRequest(std::unique_ptr operation) - { - { - std::scoped_lock lock{ _mutex }; - _ongoingScanCount += 1; - } - - auto operationHandler{ [operation = std::move(operation), this]() mutable { - if (_abort) - { - std::scoped_lock lock{ _mutex }; - _ongoingScanCount -= 1; - } - else - { - { - LMS_SCOPED_TRACE_OVERVIEW("Scanner", operation->getName()); - LMS_LOG(DBUPDATER, DEBUG, operation->getName() << ": scanning file " << operation->getFilePath()); - operation->scan(); - } - - { - std::scoped_lock lock{ _mutex }; - - _scanResults.emplace_back(std::move(operation)); - _ongoingScanCount -= 1; - } - } - - _condVar.notify_all(); - } }; - - boost::asio::post(_scanIoContext, std::move(operationHandler)); - } - - std::size_t FileScanQueue::getResultsCount() const - { - std::scoped_lock lock{ _mutex }; - return _scanResults.size(); - } - - size_t FileScanQueue::popResults(std::vector>& results, std::size_t maxCount) - { - results.clear(); - results.reserve(maxCount); - - { - std::scoped_lock lock{ _mutex }; - - while (results.size() < maxCount && !_scanResults.empty()) - { - results.push_back(std::move(_scanResults.front())); - _scanResults.pop_front(); - } - } - - return results.size(); - } - - void FileScanQueue::wait(std::size_t maxScanRequestCount) - { - if (_ongoingScanCount <= maxScanRequestCount) - return; - - { - LMS_SCOPED_TRACE_OVERVIEW("Scanner", "WaitParseResults"); - - std::unique_lock lock{ _mutex }; - _condVar.wait(lock, [=, this] { return _ongoingScanCount <= maxScanRequestCount; }); - } - } - -} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/steps/FileScanQueue.hpp b/src/libs/services/scanner/impl/steps/FileScanQueue.hpp deleted file mode 100644 index 0cf4259e..00000000 --- a/src/libs/services/scanner/impl/steps/FileScanQueue.hpp +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include - -#include "core/IOContextRunner.hpp" - -namespace lms::scanner -{ - class IFileScanOperation; - - class FileScanQueue - { - public: - FileScanQueue(std::size_t threadCount, bool& abort); - ~FileScanQueue(); - FileScanQueue(const FileScanQueue&) = delete; - FileScanQueue& operator=(const FileScanQueue&) = delete; - - std::size_t getThreadCount() const { return _scanContextRunner.getThreadCount(); } - - void pushScanRequest(std::unique_ptr operation); - - std::size_t getResultsCount() const; - size_t popResults(std::vector>& results, std::size_t maxCount); - - void wait(std::size_t maxScanRequestCount = 0); // wait until ongoing scan request count <= maxScanRequestCount - - private: - boost::asio::io_context _scanIoContext; - core::IOContextRunner _scanContextRunner; - - mutable std::mutex _mutex; - std::atomic _ongoingScanCount{}; - std::deque> _scanResults; - std::condition_variable _condVar; - bool& _abort; - }; -} // namespace lms::scanner \ No newline at end of file diff --git a/src/libs/services/scanner/impl/steps/JobQueue.cpp b/src/libs/services/scanner/impl/steps/JobQueue.cpp new file mode 100644 index 00000000..aa5db125 --- /dev/null +++ b/src/libs/services/scanner/impl/steps/JobQueue.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2025 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 "JobQueue.hpp" + +#include + +#include "core/IJob.hpp" +#include "core/IJobScheduler.hpp" + +namespace lms::scanner +{ + JobQueue::JobQueue(core::IJobScheduler& scheduler, std::size_t maxQueueSize, ProcessFunction processJobsDoneFunc, std::size_t batchSize, float _drainThreshold) + : _scheduler{ scheduler } + , _maxQueueSize{ maxQueueSize } + , _processJobsDoneFunc{ std::move(processJobsDoneFunc) } + , _batchSize{ batchSize } + , _drainThreshold{ _drainThreshold } + { + assert(_scheduler.getJobsDoneCount() == 0); + } + + JobQueue::~JobQueue() + { + finish(); + } + + void JobQueue::push(std::unique_ptr job) + { + _scheduler.scheduleJob(std::move(job)); + drainIfNeeded(); + } + + void JobQueue::finish() + { + _scheduler.wait(); + while (_scheduler.popJobsDone(_jobsDone, _batchSize) > 0) + { + _processJobsDoneFunc(std::span{ _jobsDone }); + _jobsDone.clear(); + } + } + + void JobQueue::drainIfNeeded() + { + while (_scheduler.getJobsDoneCount() > static_cast(_maxQueueSize * _drainThreshold)) + { + _scheduler.popJobsDone(_jobsDone, _batchSize); + _processJobsDoneFunc(std::span{ _jobsDone }); + _jobsDone.clear(); + } + _scheduler.waitUntilJobCountAtMost(_maxQueueSize); + } +} // namespace lms::scanner \ No newline at end of file diff --git a/src/libs/services/scanner/impl/steps/JobQueue.hpp b/src/libs/services/scanner/impl/steps/JobQueue.hpp new file mode 100644 index 00000000..db68727f --- /dev/null +++ b/src/libs/services/scanner/impl/steps/JobQueue.hpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2025 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 +#include +#include +#include + +namespace lms::core +{ + class IJob; + class IJobScheduler; +} // namespace lms::core + +namespace lms::scanner +{ + class JobQueue + { + public: + using ProcessFunction = std::function>)>; + + // processBatchSize -> how many jobs done to notify at once using processJobsDoneFunc + // drainThreshold: fraction of maxQueueSize at which completed jobs are processed + JobQueue(core::IJobScheduler& scheduler, std::size_t maxQueueSize, ProcessFunction processJobsDoneFunc, std::size_t processBatchSize, float drainThreshold); + ~JobQueue(); + JobQueue(const JobQueue&) = delete; + JobQueue& operator=(const JobQueue&) = delete; + + // push can wait and invoke the supplied ProcessFunction + void push(std::unique_ptr job); + void finish(); + + private: + void drainIfNeeded(); + + core::IJobScheduler& _scheduler; + const std::size_t _maxQueueSize; + ProcessFunction _processJobsDoneFunc; + const std::size_t _batchSize; + const float _drainThreshold; + std::vector> _jobsDone; + }; + +} // namespace lms::scanner \ No newline at end of file diff --git a/src/libs/services/scanner/impl/steps/ScanStepArtistReconciliation.cpp b/src/libs/services/scanner/impl/steps/ScanStepArtistReconciliation.cpp index c39bc2ba..77d2bbdd 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepArtistReconciliation.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepArtistReconciliation.cpp @@ -23,13 +23,13 @@ #include #include "core/ILogger.hpp" -#include "database/Artist.hpp" -#include "database/ArtistInfo.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackArtistLink.hpp" -#include "database/TrackList.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/ArtistInfo.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/TrackList.hpp" #include "metadata/Types.hpp" #include "ScanContext.hpp" diff --git a/src/libs/services/scanner/impl/steps/ScanStepAssociateArtistImages.cpp b/src/libs/services/scanner/impl/steps/ScanStepAssociateArtistImages.cpp index 5eff245d..c8209f9a 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepAssociateArtistImages.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepAssociateArtistImages.cpp @@ -24,24 +24,24 @@ #include #include #include -#include #include "core/IConfig.hpp" +#include "core/IJob.hpp" #include "core/ILogger.hpp" #include "core/Path.hpp" #include "core/String.hpp" -#include "database/Artist.hpp" -#include "database/ArtistInfo.hpp" -#include "database/Artwork.hpp" -#include "database/ArtworkId.hpp" -#include "database/Db.hpp" -#include "database/Directory.hpp" -#include "database/Image.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/ArtistInfo.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/ArtworkId.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/Image.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" -#include "ArtworkUtils.hpp" +#include "JobQueue.hpp" #include "ScanContext.hpp" #include "ScannerSettings.hpp" @@ -49,42 +49,24 @@ namespace lms::scanner { namespace { - using ArtistArtwork = std::variant; // TODO handle embedded images in tracks? - bool isSameArtwork(ArtistArtwork preferredArtwork, const db::ObjectPtr& artwork) - { - if (std::holds_alternative(preferredArtwork)) - return !artwork; - - if (const db::ImageId* imageId = std::get_if(&preferredArtwork)) - return artwork && *imageId == artwork->getImageId(); - - if (const db::ArtworkId* artworkId = std::get_if(&preferredArtwork)) - return artwork && *artworkId == artwork->getId(); - - return false; - } - struct ArtistArtworkAssociation { - db::Artist::pointer artist; - ArtistArtwork preferredArtwork; + db::ArtistId artistId; + db::ArtworkId preferredArtworkId; }; using ArtistArtworkAssociationContainer = std::deque; - struct SearchArtistImageContext + struct SearchArtistArtworkParams { - db::Session& session; - db::ArtistId lastRetrievedArtistId; - std::size_t processedArtistCount{}; std::span artistFileNames; const ScannerSettings& settings; }; - db::Image::pointer findImageInDirectory(SearchArtistImageContext& searchContext, const std::filesystem::path& directoryPath, std::span fileStemsToSearch) + db::Image::pointer findImageInDirectory(db::Session& session, const std::filesystem::path& directoryPath, std::span fileStemsToSearch) { db::Image::pointer image; - const db::Directory::pointer directory{ db::Directory::find(searchContext.session, directoryPath) }; + const db::Directory::pointer directory{ db::Directory::find(session, directoryPath) }; if (directory) // may not exist for artists that are split on different media libraries { for (std::string_view fileStem : fileStemsToSearch) @@ -93,7 +75,7 @@ namespace lms::scanner params.setDirectory(directory->getId()); params.setFileStem(fileStem); - db::Image::find(searchContext.session, params, [&](const db::Image::pointer foundImg) { + db::Image::find(session, params, [&](const db::Image::pointer foundImg) { if (!image) image = foundImg; }); @@ -106,38 +88,38 @@ namespace lms::scanner return image; } - db::ImageId getImageFromMbid(SearchArtistImageContext& searchContext, const core::UUID& mbid) + db::Image::pointer getImageFromMbid(db::Session& session, const core::UUID& mbid) { db::Image::pointer image; // Find anywhere, since it is supposed to be unique! - db::Image::find(searchContext.session, db::Image::FindParameters{}.setFileStem(mbid.getAsString()), [&](const db::Image::pointer foundImg) { + db::Image::find(session, db::Image::FindParameters{}.setFileStem(mbid.getAsString()), [&](const db::Image::pointer foundImg) { if (!image) image = foundImg; }); - return image ? image->getId() : db::ImageId{}; + return image; } - db::ImageId searchImageInArtistInfoDirectory(SearchArtistImageContext& searchContext, db::ArtistId artistId) + db::Image::pointer searchImageInArtistInfoDirectory(db::Session& session, db::ArtistId artistId) { db::Image::pointer image; std::vector fileInfoPaths; - db::ArtistInfo::find(searchContext.session, artistId, [&](const db::ArtistInfo::pointer& artistInfo) { + db::ArtistInfo::find(session, artistId, [&](const db::ArtistInfo::pointer& artistInfo) { fileInfoPaths.push_back(artistInfo->getAbsoluteFilePath()); if (!image) - image = findImageInDirectory(searchContext, artistInfo->getDirectory()->getAbsolutePath(), std::array{ "thumb", "folder" }); + image = findImageInDirectory(session, artistInfo->getDirectory()->getAbsolutePath(), std::array{ "thumb", "folder" }); }); if (fileInfoPaths.size() > 1) LMS_LOG(DBUPDATER, DEBUG, "Found " << fileInfoPaths.size() << " artist info files for same artist: " << core::stringUtils::joinStrings(fileInfoPaths, ", ")); - return image ? image->getId() : db::ImageId{}; + return image; } - db::ImageId searchImageInDirectories(SearchArtistImageContext& searchContext, db::ArtistId artistId) + db::Image::pointer searchImageInDirectories(db::Session& session, const SearchArtistArtworkParams& searchParams, db::ArtistId artistId) { db::Image::pointer image; @@ -145,7 +127,7 @@ namespace lms::scanner db::Directory::FindParameters params; params.setArtist(artistId, { db::TrackArtistLinkType::ReleaseArtist }); - db::Directory::find(searchContext.session, params, [&](const db::Directory::pointer& directory) { + db::Directory::find(session, params, [&](const db::Directory::pointer& directory) { releasePaths.insert(directory->getAbsolutePath()); }); @@ -164,9 +146,9 @@ namespace lms::scanner std::filesystem::path directoryToInspect{ core::pathUtils::getLongestCommonPath(std::cbegin(releasePaths), std::cend(releasePaths)) }; while (true) { - image = findImageInDirectory(searchContext, directoryToInspect, searchContext.artistFileNames); + image = findImageInDirectory(session, directoryToInspect, searchParams.artistFileNames); if (image) - return image->getId(); + return image; std::filesystem::path parentPath{ directoryToInspect.parent_path() }; if (parentPath == directoryToInspect) @@ -181,106 +163,74 @@ namespace lms::scanner // /someOtherUserConfiguredArtistFile.jpg for (const std::filesystem::path& releasePath : releasePaths) { - image = findImageInDirectory(searchContext, releasePath, searchContext.artistFileNames); + image = findImageInDirectory(session, releasePath, searchParams.artistFileNames); if (image) - return image->getId(); + return image; } } - return image ? image->getId() : db::ImageId{}; + return image; } - db::ArtworkId getFirstReleaseArtwork(db::Session& session, const db::Artist::pointer& artist) + db::Artwork::pointer getFirstReleaseArtwork(db::Session& session, const db::Artist::pointer& artist) { - db::ArtworkId artworkId; + db::Artwork::pointer artwork; db::Release::FindParameters params; params.setArtist(artist->getId(), { db::TrackArtistLinkType::ReleaseArtist }); params.setSortMethod(db::ReleaseSortMethod::OriginalDate); db::Release::find(session, params, [&](const db::Release::pointer& release) { - if (artworkId.isValid()) + if (artwork) return; - artworkId = release->getPreferredArtworkId(); + artwork = release->getPreferredArtwork(); }); - return artworkId; + return artwork; } - ArtistArtwork computePreferredArtwork(SearchArtistImageContext& searchContext, const db::Artist::pointer& artist) + db::Artwork::pointer computePreferredArtistArtwork(db::Session& session, const SearchArtistArtworkParams& searchParams, const db::Artist::pointer& artist) { - db::ImageId imageId; - if (const auto mbid{ artist->getMBID() }) { - imageId = getImageFromMbid(searchContext, *mbid); - if (imageId.isValid()) - return ArtistArtwork{ imageId }; + const db::Image::pointer image{ getImageFromMbid(session, *mbid) }; + if (image) + return db::Artwork::find(session, image->getId()); } - imageId = searchImageInArtistInfoDirectory(searchContext, artist->getId()); - if (imageId.isValid()) - return ArtistArtwork{ imageId }; - - imageId = searchImageInDirectories(searchContext, artist->getId()); - if (imageId.isValid()) - return ArtistArtwork{ imageId }; - - if (searchContext.settings.artistImageFallbackToRelease) - { - db::ArtworkId artworkId{ getFirstReleaseArtwork(searchContext.session, artist) }; - if (artworkId.isValid()) - return ArtistArtwork{ artworkId }; - } - - return ArtistArtwork{}; - } - - bool fetchNextArtistArtworksToUpdate(SearchArtistImageContext& searchContext, ArtistArtworkAssociationContainer& ArtistArtworkAssociations) - { - const db::ArtistId artistId{ searchContext.lastRetrievedArtistId }; - { - constexpr std::size_t readBatchSize{ 100 }; - - auto transaction{ searchContext.session.createReadTransaction() }; - - db::Artist::find(searchContext.session, searchContext.lastRetrievedArtistId, readBatchSize, [&](const db::Artist::pointer& artist) { - ArtistArtwork preferredArtwork{ computePreferredArtwork(searchContext, artist) }; - - if (!isSameArtwork(preferredArtwork, artist->getPreferredArtwork())) - ArtistArtworkAssociations.push_back(ArtistArtworkAssociation{ artist, preferredArtwork }); - - searchContext.processedArtistCount++; - }); + const db::Image::pointer image{ searchImageInArtistInfoDirectory(session, artist->getId()) }; + if (image) + return db::Artwork::find(session, image->getId()); } - return artistId != searchContext.lastRetrievedArtistId; + { + const db::Image::pointer image{ searchImageInDirectories(session, searchParams, artist->getId()) }; + if (image) + return db::Artwork::find(session, image->getId()); + } + + if (searchParams.settings.artistImageFallbackToRelease) + { + const db::Artwork::pointer artwork{ getFirstReleaseArtwork(session, artist) }; + if (artwork) + return artwork; + } + + return db::Artwork::pointer{}; } - void updateArtistPreferredArtwork(db::Session& session, const ArtistArtworkAssociation& ArtistArtworkAssociation) + void updateArtistPreferredArtwork(db::Session& session, const ArtistArtworkAssociation& artistArtworkAssociation) { - db::Artist::pointer artist{ ArtistArtworkAssociation.artist }; - - db::Artwork::pointer artwork; - if (const db::ImageId * imageId{ std::get_if(&ArtistArtworkAssociation.preferredArtwork) }) - artwork = utils::getOrCreateArtworkFromImage(session, *imageId); - else if (const db::ArtworkId * artworkId{ std::get_if(&ArtistArtworkAssociation.preferredArtwork) }) - artwork = db::Artwork::find(session, *artworkId); - - artist.modify()->setPreferredArtwork(artwork); - if (artwork) - LMS_LOG(DBUPDATER, DEBUG, "Updated preferred artwork for artist '" << artist->getName() << "' with image in " << utils::toPath(session, artwork->getId())); - else - LMS_LOG(DBUPDATER, DEBUG, "Removed preferred artwork from artist '" << artist->getName() << "'"); + db::Artist::updatePreferredArtwork(session, artistArtworkAssociation.artistId, artistArtworkAssociation.preferredArtworkId); } - void updateArtistArtworks(db::Session& session, ArtistArtworkAssociationContainer& imageAssociations) + void updateArtistPreferredArtworks(db::Session& session, ArtistArtworkAssociationContainer& imageAssociations, bool forceFullBatch) { constexpr std::size_t writeBatchSize{ 50 }; - while (!imageAssociations.empty()) + while ((forceFullBatch && imageAssociations.size() >= writeBatchSize) || !imageAssociations.empty()) { auto transaction{ session.createWriteTransaction() }; @@ -305,6 +255,62 @@ namespace lms::scanner return res; } + bool fetchNextArtistIdRange(db::Session& session, db::ArtistId& lastRetrievedId, db::IdRange& idRange) + { + constexpr std::size_t readBatchSize{ 100 }; + + auto transaction{ session.createReadTransaction() }; + + idRange = db::Artist::findNextIdRange(session, lastRetrievedId, readBatchSize); + lastRetrievedId = idRange.last; + + return idRange.isValid(); + } + + class ComputeArtistArtworkAssociationsJob : public core::IJob + { + public: + ComputeArtistArtworkAssociationsJob(db::IDb& db, const SearchArtistArtworkParams& searchParams, db::IdRange artistIdRange) + : _db{ db } + , _searchParams{ searchParams } + , _artistIdRange{ artistIdRange } + { + } + + std::span getAssociations() const { return _associations; } + std::size_t getProcessedArtistCount() const { return _processedArtistCount; } + + private: + core::LiteralString getName() const override { return "Associate Artist Artworks"; } + void run() override + { + auto& session{ _db.getTLSSession() }; + auto transaction{ session.createReadTransaction() }; + + db::Artist::find(session, _artistIdRange, [this, &session](const db::Artist::pointer& artist) { + const db::Artwork::pointer preferredArtwork{ computePreferredArtistArtwork(session, _searchParams, artist) }; + + if (artist->getPreferredArtwork() != preferredArtwork) + { + _associations.push_back(ArtistArtworkAssociation{ artist->getId(), preferredArtwork ? preferredArtwork->getId() : db::ArtworkId{} }); + + if (preferredArtwork) + LMS_LOG(DBUPDATER, DEBUG, "Updating preferred artwork for artist '" << artist->getName() << "' with image in " << preferredArtwork->getAbsoluteFilePath()); + else + LMS_LOG(DBUPDATER, DEBUG, "Removing preferred artwork from artist '" << artist->getName() << "'"); + } + + _processedArtistCount++; + }); + } + + db::IDb& _db; + const SearchArtistArtworkParams& _searchParams; + db::IdRange _artistIdRange; + std::vector _associations; + std::size_t _processedArtistCount{}; + }; + } // namespace ScanStepAssociateArtistImages::ScanStepAssociateArtistImages(InitParams& initParams) @@ -315,7 +321,7 @@ namespace lms::scanner bool ScanStepAssociateArtistImages::needProcess(const ScanContext& context) const { - if (context.stats.nbChanges() > 0) + if (context.stats.getChangesCount() > 0) return true; if (getLastScanSettings() && getLastScanSettings()->artistImageFallbackToRelease != _settings.artistImageFallbackToRelease) @@ -333,22 +339,40 @@ namespace lms::scanner context.currentStepStats.totalElems = db::Artist::getCount(session); } - SearchArtistImageContext searchContext{ - .session = session, - .lastRetrievedArtistId = {}, + const SearchArtistArtworkParams searchParams{ .artistFileNames = _artistFileNames, .settings = _settings, }; - ArtistArtworkAssociationContainer ArtistArtworkAssociations; - while (fetchNextArtistArtworksToUpdate(searchContext, ArtistArtworkAssociations)) - { + ArtistArtworkAssociationContainer artistArtworkAssociations; + auto processJobsDone = [&](std::span> jobs) { if (_abortScan) return; - updateArtistArtworks(session, ArtistArtworkAssociations); - context.currentStepStats.processedElems = searchContext.processedArtistCount; + for (const auto& job : jobs) + { + const auto& associationJob{ static_cast(*job) }; + const auto& artistAssociations{ associationJob.getAssociations() }; + + artistArtworkAssociations.insert(std::end(artistArtworkAssociations), std::cbegin(artistAssociations), std::cend(artistAssociations)); + + context.currentStepStats.processedElems += associationJob.getProcessedArtistCount(); + } + + updateArtistPreferredArtworks(session, artistArtworkAssociations, true); _progressCallback(context.currentStepStats); + }; + + { + JobQueue queue{ getJobScheduler(), 20, processJobsDone, 1, 0.85F }; + + db::ArtistId lastRetrievedArtistId{}; + db::IdRange artistIdRange; + while (fetchNextArtistIdRange(session, lastRetrievedArtistId, artistIdRange)) + queue.push(std::make_unique(_db, searchParams, artistIdRange)); } + + // process all remaining associations + updateArtistPreferredArtworks(session, artistArtworkAssociations, false); } } // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/steps/ScanStepAssociateExternalLyrics.cpp b/src/libs/services/scanner/impl/steps/ScanStepAssociateExternalLyrics.cpp index 7765eb58..e5038c31 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepAssociateExternalLyrics.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepAssociateExternalLyrics.cpp @@ -22,11 +22,11 @@ #include #include "core/ILogger.hpp" -#include "database/Db.hpp" -#include "database/Directory.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackLyrics.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackLyrics.hpp" #include "ScanContext.hpp" @@ -58,9 +58,11 @@ namespace lms::scanner assert(!lyrics->getFileStem().empty()); params.setDirectory(lyrics->getDirectory()->getId()); - params.setFileStem(stem); db::Track::find(session, params, [&](const db::Track::pointer& track) { + if (track->getAbsoluteFilePath().filename().stem() != stem) + return; + if (matchingTrack) LMS_LOG(DBUPDATER, DEBUG, "External lyrics '" << lyrics->getAbsoluteFilePath() << "' already matched with '" << matchingTrack->getAbsoluteFilePath() << "', replaced by '" << track->getAbsoluteFilePath() << "'"); @@ -68,7 +70,7 @@ namespace lms::scanner }); }; - // First try with the stem. If it does not match, try again with the parent steam, if it exists, to handle the file.laguagecode.lrc case + // First try with the stem. If it does not match, try again with the parent steam, if it exists, to handle the file.languagecode.lrc case tryMatch(lyrics->getFileStem()); if (!matchingTrack) { @@ -143,7 +145,7 @@ namespace lms::scanner bool ScanStepAssociateExternalLyrics::needProcess(const ScanContext& context) const { - if (context.stats.nbChanges() > 0) + if (context.stats.getChangesCount() > 0) return true; return false; diff --git a/src/libs/services/scanner/impl/steps/ScanStepAssociatePlayListTracks.cpp b/src/libs/services/scanner/impl/steps/ScanStepAssociatePlayListTracks.cpp index 7e058cb5..2e1a3cad 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepAssociatePlayListTracks.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepAssociatePlayListTracks.cpp @@ -21,17 +21,20 @@ #include #include +#include +#include "core/IJob.hpp" #include "core/ILogger.hpp" -#include "database/Db.hpp" -#include "database/Directory.hpp" -#include "database/PlayListFile.hpp" -#include "database/ReleaseId.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackList.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/PlayListFile.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackList.hpp" #include "services/scanner/ScanErrors.hpp" +#include "JobQueue.hpp" #include "ScanContext.hpp" #include "ScannerSettings.hpp" @@ -53,15 +56,6 @@ namespace lms::scanner }; using PlayListFileAssociationContainer = std::deque; - struct SearchPlayListFileContext - { - db::Session& session; - db::PlayListFileId lastRetrievedPlayListFileId; - std::size_t processedPlayListFileCount{}; - const ScannerSettings& settings; - std::vector> errors; - }; - db::Track::pointer getMatchingTrack(db::Session& session, const std::filesystem::path& filePath, const db::Directory::pointer& playListDirectory) { db::Track::pointer matchingTrack; @@ -110,66 +104,7 @@ namespace lms::scanner return needUpdate; } - bool fetchNextPlayListFilesToUpdate(SearchPlayListFileContext& searchContext, PlayListFileAssociationContainer& playListFileAssociations) - { - const db::PlayListFileId playListFileIdId{ searchContext.lastRetrievedPlayListFileId }; - - { - constexpr std::size_t readBatchSize{ 20 }; - - auto transaction{ searchContext.session.createReadTransaction() }; - - db::PlayListFile::find(searchContext.session, searchContext.lastRetrievedPlayListFileId, readBatchSize, [&](const db::PlayListFile::pointer& playListFile) { - PlayListFileAssociation playListAssociation; - - playListAssociation.playListFileIdId = playListFile->getId(); - - std::vector> pendingErrors; - - const auto files{ playListFile->getFiles() }; - for (const std::filesystem::path& file : files) - { - // TODO optim: no need to fetch the whole track - const db::Track::pointer track{ getMatchingTrack(searchContext.session, file, playListFile->getDirectory()) }; - if (track) - playListAssociation.tracks.push_back(TrackInfo{ .trackId = track->getId(), .releaseId = track->getReleaseId() }); - else - { - pendingErrors.emplace_back(std::make_shared(playListFile->getAbsoluteFilePath(), file)); - } - } - - if (pendingErrors.size() == files.size()) - { - pendingErrors.clear(); - pendingErrors.emplace_back(std::make_shared(playListFile->getAbsoluteFilePath())); - } - searchContext.errors.insert(std::end(searchContext.errors), std::begin(pendingErrors), std::end(pendingErrors)); - - if (playListAssociation.tracks.empty() - || (searchContext.settings.skipSingleReleasePlayLists && isSingleReleasePlayList(playListAssociation.tracks))) - { - playListAssociation.tracks.clear(); - } - - bool needUpdate{ true }; - if (const db::TrackList::pointer trackList{ playListFile->getTrackList() }) - { - if (!playListAssociation.tracks.empty()) - needUpdate = trackListNeedsUpdate(searchContext.session, playListFile->getName(), playListAssociation.tracks, trackList); - } - - if (needUpdate) - playListFileAssociations.emplace_back(std::move(playListAssociation)); - - searchContext.processedPlayListFileCount++; - }); - } - - return playListFileIdId != searchContext.lastRetrievedPlayListFileId; - } - - void updatePlayListFile(db::Session& session, const PlayListFileAssociation& playListFileAssociation) + void updatePlayList(db::Session& session, const PlayListFileAssociation& playListFileAssociation) { db::PlayListFile::pointer playListFile{ db::PlayListFile::find(session, playListFileAssociation.playListFileIdId) }; assert(playListFile); @@ -200,6 +135,7 @@ namespace lms::scanner trackList.modify()->clear(); for (const TrackInfo trackInfo : playListFileAssociation.tracks) { + // TODO no need to fetch the whole track for that if (db::Track::pointer track{ db::Track::find(session, trackInfo.trackId) }) session.create(track, trackList, playListFile->getLastWriteTime()); } @@ -207,26 +143,115 @@ namespace lms::scanner LMS_LOG(DBUPDATER, DEBUG, std::string_view{ createTrackList ? "Created" : "Updated" } << " associated tracklist for " << playListFile->getAbsoluteFilePath() << " (" << playListFileAssociation.tracks.size() << " tracks)"); } - void updatePlayListFiles(db::Session& session, PlayListFileAssociationContainer& playListFileAssociations) + void updatePlayLists(db::Session& session, PlayListFileAssociationContainer& playListFileAssociations, bool forceFullBatch) { constexpr std::size_t writeBatchSize{ 5 }; - while (!playListFileAssociations.empty()) + while ((forceFullBatch && playListFileAssociations.size() >= writeBatchSize) || !playListFileAssociations.empty()) { auto transaction{ session.createWriteTransaction() }; for (std::size_t i{}; !playListFileAssociations.empty() && i < writeBatchSize; ++i) { - updatePlayListFile(session, playListFileAssociations.front()); + updatePlayList(session, playListFileAssociations.front()); playListFileAssociations.pop_front(); } } } + + bool fetchNextPlayListFileIdRange(db::Session& session, db::PlayListFileId& lastPlayListFileId, db::IdRange& idRange) + { + constexpr std::size_t readBatchSize{ 100 }; + + auto transaction{ session.createReadTransaction() }; + + idRange = db::PlayListFile::findNextIdRange(session, lastPlayListFileId, readBatchSize); + lastPlayListFileId = idRange.last; + + return idRange.isValid(); + } + + class ComputePlayListFileAssociationsJob : public core::IJob + { + public: + ComputePlayListFileAssociationsJob(db::IDb& db, const ScannerSettings& settings, db::IdRange playListFileIdRange) + : _db{ db } + , _settings{ settings } + , _playListFileIdRange{ playListFileIdRange } + { + } + + std::span getAssociations() const { return _associations; } + std::size_t getProcessedCount() const { return _processedCount; } + std::span> getErrors() const { return _errors; } + + private: + core::LiteralString getName() const override { return "Associate PlayList Tracks"; } + void run() override + { + auto& session{ _db.getTLSSession() }; + auto transaction{ session.createReadTransaction() }; + + db::PlayListFile::find(session, _playListFileIdRange, [&](const db::PlayListFile::pointer& playListFile) { + PlayListFileAssociation playListAssociation; + + playListAssociation.playListFileIdId = playListFile->getId(); + + std::vector> pendingErrors; + + const auto& files{ playListFile->getFiles() }; + for (const std::filesystem::path& file : files) + { + // TODO optim: no need to fetch the whole track + const db::Track::pointer track{ getMatchingTrack(session, file, playListFile->getDirectory()) }; + if (track) + playListAssociation.tracks.push_back(TrackInfo{ .trackId = track->getId(), .releaseId = track->getReleaseId() }); + else + pendingErrors.emplace_back(std::make_shared(playListFile->getAbsoluteFilePath(), file)); + } + + if (pendingErrors.size() == files.size()) + { + pendingErrors.clear(); + pendingErrors.emplace_back(std::make_shared(playListFile->getAbsoluteFilePath())); + } + _errors.insert(std::end(_errors), std::begin(pendingErrors), std::end(pendingErrors)); + + if (playListAssociation.tracks.empty() + || (_settings.skipSingleReleasePlayLists && isSingleReleasePlayList(playListAssociation.tracks))) + { + playListAssociation.tracks.clear(); + } + + bool needUpdate{ true }; + if (const db::TrackList::pointer trackList{ playListFile->getTrackList() }) + { + if (!playListAssociation.tracks.empty()) + needUpdate = trackListNeedsUpdate(session, playListFile->getName(), playListAssociation.tracks, trackList); + } + else if (playListAssociation.tracks.empty()) + needUpdate = false; + + if (needUpdate) + _associations.emplace_back(std::move(playListAssociation)); + + _processedCount++; + }); + } + + db::IDb& _db; + const ScannerSettings& _settings; + db::IdRange _playListFileIdRange; + std::vector _associations; + std::vector> _errors; + std::size_t _processedCount{}; + }; + } // namespace bool ScanStepAssociatePlayListTracks::needProcess(const ScanContext& context) const { - if (context.stats.nbChanges() > 0) + if (context.stats.getChangesCount() > 0) return true; if (getLastScanSettings() && getLastScanSettings()->skipSingleReleasePlayLists != _settings.skipSingleReleasePlayLists) @@ -244,27 +269,37 @@ namespace lms::scanner context.currentStepStats.totalElems = db::PlayListFile::getCount(session); } - SearchPlayListFileContext searchContext{ - .session = session, - .lastRetrievedPlayListFileId = {}, - .settings = _settings, - .errors = context.stats.errors - }; - - PlayListFileAssociationContainer playListFileAssociations; - while (fetchNextPlayListFilesToUpdate(searchContext, playListFileAssociations)) - { + PlayListFileAssociationContainer playListTrackAssociations; + auto processJobsDone = [&](std::span> jobs) { if (_abortScan) return; - updatePlayListFiles(session, playListFileAssociations); + for (const auto& job : jobs) + { + const auto& associationJob{ static_cast(*job) }; + const auto& associations{ associationJob.getAssociations() }; - context.currentStepStats.processedElems = searchContext.processedPlayListFileCount; - for (const std::shared_ptr& error : searchContext.errors) - addError(context, error); - searchContext.errors.clear(); + playListTrackAssociations.insert(std::end(playListTrackAssociations), std::cbegin(associations), std::cend(associations)); + for (const std::shared_ptr& error : associationJob.getErrors()) + addError(context, error); + context.currentStepStats.processedElems += associationJob.getProcessedCount(); + } + + updatePlayLists(session, playListTrackAssociations, true); _progressCallback(context.currentStepStats); + }; + + { + JobQueue queue{ getJobScheduler(), 20, processJobsDone, 1, 0.85F }; + + db::PlayListFileId lastPlayListFileId; + db::IdRange playListFileIdRange; + while (fetchNextPlayListFileIdRange(session, lastPlayListFileId, playListFileIdRange)) + queue.push(std::make_unique(_db, _settings, playListFileIdRange)); } + + // process all remaining associations + updatePlayLists(session, playListTrackAssociations, false); } } // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/steps/ScanStepAssociateReleaseImages.cpp b/src/libs/services/scanner/impl/steps/ScanStepAssociateReleaseImages.cpp index b6ce83d2..2488a139 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepAssociateReleaseImages.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepAssociateReleaseImages.cpp @@ -23,105 +23,86 @@ #include #include #include -#include #include "core/IConfig.hpp" +#include "core/IJob.hpp" #include "core/ILogger.hpp" #include "core/Path.hpp" -#include "database/Artwork.hpp" -#include "database/Db.hpp" -#include "database/Directory.hpp" -#include "database/Image.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackEmbeddedImage.hpp" -#include "database/TrackEmbeddedImageId.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/Image.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" -#include "ArtworkUtils.hpp" +#include "JobQueue.hpp" #include "ScanContext.hpp" namespace lms::scanner { namespace { - using ReleaseArtwork = std::variant; - bool isSameArtwork(ReleaseArtwork preferredArtwork, const db::ObjectPtr& artwork) - { - if (std::holds_alternative(preferredArtwork)) - return !artwork; - - if (const db::TrackEmbeddedImageId* trackEmbeddedImageId = std::get_if(&preferredArtwork)) - return artwork && *trackEmbeddedImageId == artwork->getTrackEmbeddedImageId(); - - if (const db::ImageId* imageId = std::get_if(&preferredArtwork)) - return artwork && *imageId == artwork->getImageId(); - - return false; - } - struct ReleaseArtworkAssociation { - db::Release::pointer release; - ReleaseArtwork preferredArtwork; + db::ReleaseId releaseId; + db::ArtworkId preferredArtworkId; }; using ReleaseArtworkAssociationContainer = std::deque; - struct SearchReleaseArtworkContext + struct SearchReleaseArtworkParams { - db::Session& session; - db::ReleaseId lastRetrievedReleaseId; - std::size_t processedReleaseCount{}; const std::vector& releaseImageFileNames; }; - db::Image::pointer findImageInDirectory(SearchReleaseArtworkContext& searchContext, const std::filesystem::path& directoryPath) + db::Artwork::pointer findImageInDirectory(db::Session& session, const SearchReleaseArtworkParams& searchParams, const std::filesystem::path& directoryPath) { - db::Image::pointer image; + db::Artwork::pointer artwork; - const db::Directory::pointer directory{ db::Directory::find(searchContext.session, directoryPath) }; + const db::Directory::pointer directory{ db::Directory::find(session, directoryPath) }; if (directory) // may not exist for releases that are split on different media libraries { - for (std::string_view fileStem : searchContext.releaseImageFileNames) + for (std::string_view fileStem : searchParams.releaseImageFileNames) { db::Image::FindParameters params; params.setDirectory(directory->getId()); params.setFileStem(fileStem); - db::Image::find(searchContext.session, params, [&](const db::Image::pointer foundImg) { - if (!image) - image = foundImg; + db::Image::find(session, params, [&](const db::Image::pointer& image) { + if (!artwork) + artwork = db::Artwork::find(session, image->getId()); }); - if (image) + if (artwork) break; } } - return image; + return artwork; } - db::Image::pointer computePreferredReleaseImage(SearchReleaseArtworkContext& searchContext, const db::Release::pointer& release) + db::Artwork::pointer computePreferredReleaseImage(db::Session& session, const SearchReleaseArtworkParams& searchParams, const db::Release::pointer& release) { - db::Image::pointer image; + db::Artwork::pointer artwork; const auto mbid{ release->getMBID() }; if (mbid) { // Find anywhere, since it is suppoed to be unique! - db::Image::find(searchContext.session, db::Image::FindParameters{}.setFileStem(mbid->getAsString()), [&](const db::Image::pointer foundImg) { - if (!image) - image = foundImg; + db::Image::find(session, db::Image::FindParameters{}.setFileStem(mbid->getAsString()), [&](const db::Image::pointer& image) { + if (!artwork) + artwork = db::Artwork::find(session, image->getId()); }); } - if (!image) + if (!artwork) { std::set releasePaths; db::Directory::FindParameters params; params.setRelease(release->getId()); - db::Directory::find(searchContext.session, params, [&](const db::Directory::pointer& directory) { + db::Directory::find(session, params, [&](const db::Directory::pointer& directory) { releasePaths.insert(directory->getAbsolutePath()); }); @@ -132,112 +113,75 @@ namespace lms::scanner if (releasePaths.size() > 1) { const std::filesystem::path releasePath{ core::pathUtils::getLongestCommonPath(std::cbegin(releasePaths), std::cend(releasePaths)) }; - image = findImageInDirectory(searchContext, releasePath); + artwork = findImageInDirectory(session, searchParams, releasePath); } - if (!image) + if (!artwork) { for (const std::filesystem::path& releasePath : releasePaths) { - image = findImageInDirectory(searchContext, releasePath); - if (image) + artwork = findImageInDirectory(session, searchParams, releasePath); + if (artwork) break; } } } - return image; + return artwork; } - ReleaseArtwork computePreferredReleaseArtwork(SearchReleaseArtworkContext& searchContext, const db::Release::pointer& release) + db::Artwork::pointer computePreferredReleaseArtwork(db::Session& session, const SearchReleaseArtworkParams& searchParams, const db::Release::pointer& release) { - const db::Image::pointer image{ computePreferredReleaseImage(searchContext, release) }; - if (image) - return ReleaseArtwork{ image->getId() }; + db::Artwork::pointer artwork{ computePreferredReleaseImage(session, searchParams, release) }; + if (artwork) + return artwork; // Fallback on embedded Front image - db::TrackEmbeddedImageId trackEmbeddedImageId; { db::TrackEmbeddedImage::FindParameters params; params.setRelease(release->getId()); - params.setImageTypes({ db::ImageType::FrontCover }); + params.setImageType(db::ImageType::FrontCover); params.setSortMethod(db::TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc); - params.setRange(db::Range{ .offset = 0, .size = 1 }); - - db::TrackEmbeddedImage::find(searchContext.session, params, [&](const db::TrackEmbeddedImage::pointer& image) { trackEmbeddedImageId = image->getId(); }); + db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { + if (!artwork) + artwork = db::Artwork::find(session, image->getId()); + }); } - if (trackEmbeddedImageId.isValid()) - return ReleaseArtwork{ trackEmbeddedImageId }; + if (artwork) + return artwork; // Fallback on embedded media image { db::TrackEmbeddedImage::FindParameters params; params.setRelease(release->getId()); - params.setImageTypes({ db::ImageType::Media }); + params.setImageType(db::ImageType::Media); params.setSortMethod(db::TrackEmbeddedImageSortMethod::DiscNumberThenTrackNumberThenSizeDesc); - params.setRange(db::Range{ .offset = 0, .size = 1 }); - - db::TrackEmbeddedImage::find(searchContext.session, params, [&](const db::TrackEmbeddedImage::pointer& image) { trackEmbeddedImageId = image->getId(); }); - } - - if (trackEmbeddedImageId.isValid()) - return ReleaseArtwork{ trackEmbeddedImageId }; - - return ReleaseArtwork{}; - } - - bool fetchNextReleaseArtworksToUpdate(SearchReleaseArtworkContext& searchContext, ReleaseArtworkAssociationContainer& ReleaseArtworkAssociations) - { - const db::ReleaseId releaseId{ searchContext.lastRetrievedReleaseId }; - - { - constexpr std::size_t readBatchSize{ 100 }; - - auto transaction{ searchContext.session.createReadTransaction() }; - - db::Release::find(searchContext.session, searchContext.lastRetrievedReleaseId, readBatchSize, [&](const db::Release::pointer& release) { - const ReleaseArtwork preferredArtwork{ computePreferredReleaseArtwork(searchContext, release) }; - const db::Artwork::pointer currentPreferredArtwork{ release->getPreferredArtwork() }; - - if (!isSameArtwork(preferredArtwork, currentPreferredArtwork)) - ReleaseArtworkAssociations.push_back(ReleaseArtworkAssociation{ release, preferredArtwork }); - searchContext.processedReleaseCount++; + db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { + if (!artwork) + artwork = db::Artwork::find(session, image->getId()); }); } - return releaseId != searchContext.lastRetrievedReleaseId; + return artwork; } - void updateReleaseArtwork(db::Session& session, const ReleaseArtworkAssociation& ReleaseArtworkAssociation) + void updateReleasePreferredArtwork(db::Session& session, const ReleaseArtworkAssociation& releaseArtworkAssociation) { - db::Release::pointer release{ ReleaseArtworkAssociation.release }; - - db::Artwork::pointer artwork; - if (const db::TrackEmbeddedImageId * trackEmbeddedImageId{ std::get_if(&ReleaseArtworkAssociation.preferredArtwork) }) - artwork = utils::getOrCreateArtworkFromTrackEmbeddedImage(session, *trackEmbeddedImageId); - else if (const db::ImageId * imageId{ std::get_if(&ReleaseArtworkAssociation.preferredArtwork) }) - artwork = utils::getOrCreateArtworkFromImage(session, *imageId); - - release.modify()->setPreferredArtwork(artwork); - - if (artwork) - LMS_LOG(DBUPDATER, DEBUG, "Updated preferred artwork in release '" << release->getName() << "' with image in " << utils::toPath(session, artwork->getId())); - else - LMS_LOG(DBUPDATER, DEBUG, "Removed preferred artwork from release '" << release->getName() << "'"); + db::Release::updatePreferredArtwork(session, releaseArtworkAssociation.releaseId, releaseArtworkAssociation.preferredArtworkId); } - void updateReleaseArtworks(db::Session& session, ReleaseArtworkAssociationContainer& imageAssociations) + void updateReleasePreferredArtworks(db::Session& session, ReleaseArtworkAssociationContainer& imageAssociations, bool forceFullBatch) { constexpr std::size_t writeBatchSize{ 50 }; - while (!imageAssociations.empty()) + while ((forceFullBatch && imageAssociations.size() >= writeBatchSize) || !imageAssociations.empty()) { auto transaction{ session.createWriteTransaction() }; for (std::size_t i{}; !imageAssociations.empty() && i < writeBatchSize; ++i) { - updateReleaseArtwork(session, imageAssociations.front()); + updateReleasePreferredArtwork(session, imageAssociations.front()); imageAssociations.pop_front(); } } @@ -256,6 +200,62 @@ namespace lms::scanner return res; } + bool fetchNextReleaseIdRange(db::Session& session, db::ReleaseId& lastRetrievedId, db::IdRange& idRange) + { + constexpr std::size_t readBatchSize{ 100 }; + + auto transaction{ session.createReadTransaction() }; + + idRange = db::Release::findNextIdRange(session, lastRetrievedId, readBatchSize); + lastRetrievedId = idRange.last; + + return idRange.isValid(); + } + + class ComputeReleaseArtworkAssociationsJob : public core::IJob + { + public: + ComputeReleaseArtworkAssociationsJob(db::IDb& db, const SearchReleaseArtworkParams& searchParams, db::IdRange artistIdRange) + : _db{ db } + , _searchParams{ searchParams } + , _artistIdRange{ artistIdRange } + { + } + + std::span getAssociations() const { return _associations; } + std::size_t getProcessedReleaseCount() const { return _processedReleaseCount; } + + private: + core::LiteralString getName() const override { return "Associate Release Artworks"; } + void run() override + { + auto& session{ _db.getTLSSession() }; + auto transaction{ session.createReadTransaction() }; + + db::Release::find(session, _artistIdRange, [this, &session](const db::Release::pointer& release) { + const db::Artwork::pointer preferredArtwork{ computePreferredReleaseArtwork(session, _searchParams, release) }; + + if (release->getPreferredArtwork() != preferredArtwork) + { + _associations.push_back(ReleaseArtworkAssociation{ release->getId(), preferredArtwork ? preferredArtwork->getId() : db::ArtworkId{} }); + + if (preferredArtwork) + LMS_LOG(DBUPDATER, DEBUG, "Updating preferred artwork for release '" << release->getName() << "' with image in " << preferredArtwork->getAbsoluteFilePath()); + else + LMS_LOG(DBUPDATER, DEBUG, "Removing preferred artwork from release '" << release->getName() << "'"); + } + + _processedReleaseCount++; + }); + } + + db::IDb& _db; + const SearchReleaseArtworkParams& _searchParams; + db::IdRange _artistIdRange; + std::vector _associations; + std::size_t _processedReleaseCount{}; + }; + } // namespace ScanStepAssociateReleaseImages::ScanStepAssociateReleaseImages(InitParams& initParams) @@ -266,7 +266,7 @@ namespace lms::scanner bool ScanStepAssociateReleaseImages::needProcess(const ScanContext& context) const { - return context.stats.nbChanges() > 0; + return context.stats.getChangesCount() > 0; } void ScanStepAssociateReleaseImages::process(ScanContext& context) @@ -278,21 +278,39 @@ namespace lms::scanner context.currentStepStats.totalElems = db::Release::getCount(session); } - SearchReleaseArtworkContext searchContext{ - .session = session, - .lastRetrievedReleaseId = {}, + const SearchReleaseArtworkParams searchParams{ .releaseImageFileNames = _releaseImageFileNames, }; - ReleaseArtworkAssociationContainer ReleaseArtworkAssociations; - while (fetchNextReleaseArtworksToUpdate(searchContext, ReleaseArtworkAssociations)) - { + ReleaseArtworkAssociationContainer artistArtworkAssociations; + auto processJobsDone = [&](std::span> jobs) { if (_abortScan) return; - updateReleaseArtworks(session, ReleaseArtworkAssociations); - context.currentStepStats.processedElems = searchContext.processedReleaseCount; + for (const auto& job : jobs) + { + const auto& associationJob{ static_cast(*job) }; + const auto& artistAssociations{ associationJob.getAssociations() }; + + artistArtworkAssociations.insert(std::end(artistArtworkAssociations), std::cbegin(artistAssociations), std::cend(artistAssociations)); + + context.currentStepStats.processedElems += associationJob.getProcessedReleaseCount(); + } + + updateReleasePreferredArtworks(session, artistArtworkAssociations, true); _progressCallback(context.currentStepStats); - } + }; + + JobQueue queue{ getJobScheduler(), 20, processJobsDone, 1, 0.85F }; + + db::ReleaseId lastRetrievedReleaseId{}; + db::IdRange artistIdRange; + while (fetchNextReleaseIdRange(session, lastRetrievedReleaseId, artistIdRange)) + queue.push(std::make_unique(_db, searchParams, artistIdRange)); + + queue.finish(); + + // process all remaining associations + updateReleasePreferredArtworks(session, artistArtworkAssociations, false); } } // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/steps/ScanStepAssociateTrackImages.cpp b/src/libs/services/scanner/impl/steps/ScanStepAssociateTrackImages.cpp index 6d32e21f..e7871c3b 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepAssociateTrackImages.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepAssociateTrackImages.cpp @@ -21,20 +21,23 @@ #include #include -#include +#include +#include "core/IJob.hpp" +#include "core/IJobScheduler.hpp" #include "core/ILogger.hpp" -#include "database/Artwork.hpp" -#include "database/Db.hpp" -#include "database/Directory.hpp" -#include "database/Image.hpp" -#include "database/Release.hpp" +#include "core/LiteralString.hpp" +#include "database/IDb.hpp" +#include "database/IdRange.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackEmbeddedImage.hpp" -#include "database/TrackEmbeddedImageId.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/Image.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" -#include "ArtworkUtils.hpp" +#include "JobQueue.hpp" #include "ScanContext.hpp" namespace lms::scanner @@ -42,208 +45,104 @@ namespace lms::scanner namespace { // May come from an embedded image in a track, or from what has been previously resolved for the release - using TrackArtwork = std::variant; - bool isSameArtwork(TrackArtwork preferredArtwork, const db::ObjectPtr& artwork) - { - if (std::holds_alternative(preferredArtwork)) - return !artwork; - - if (const db::TrackEmbeddedImageId* trackEmbeddedImageId = std::get_if(&preferredArtwork)) - return artwork && *trackEmbeddedImageId == artwork->getTrackEmbeddedImageId(); - - if (const db::ArtworkId* artworkId = std::get_if(&preferredArtwork)) - return artwork && *artworkId == artwork->getId(); - - return false; - } - - bool isValid(const TrackArtwork& res) - { - return !std::holds_alternative(res); - } - struct TrackArtworksAssociation { - db::Track::pointer track; - TrackArtwork preferredArtwork; - TrackArtwork preferredMediaArtwork; + db::TrackId trackId; + std::optional preferredArtworkId; + std::optional preferredMediaArtworkId; }; using TrackArtworksAssociationContainer = std::deque; - struct SearchTrackArtworkContext + db::Artwork::pointer computePreferredTrackArtwork(db::Session& session, const db::Track::pointer& track, const db::Artwork::pointer& preferredMediaArtwork) { - db::Session& session; - db::TrackId lastRetrievedTrackId; - std::size_t processedTrackCount{}; - }; - - TrackArtwork computePreferredTrackArtwork(SearchTrackArtworkContext& searchContext, const db::Track::pointer& track) - { - // Try to get a media image - TrackArtwork res; - { - db::TrackEmbeddedImage::FindParameters params; - params.setTrack(track->getId()); - params.setImageTypes({ db::ImageType::Media }); - params.setSortMethod(db::TrackEmbeddedImageSortMethod::SizeDesc); - params.setRange(db::Range{ .offset = 0, .size = 1 }); - db::TrackEmbeddedImage::find(searchContext.session, params, [&](const db::TrackEmbeddedImage::pointer& image) { res = image->getId(); }); - } - - if (isValid(res)) - return res; - - // Fallback on another track of the same disc - const db::ReleaseId releaseId{ track->getReleaseId() }; - if (!releaseId.isValid()) - return res; - - { - db::TrackEmbeddedImage::FindParameters params; - params.setRelease(releaseId); - params.setDiscNumber(track->getDiscNumber()); - params.setImageTypes({ db::ImageType::Media }); - params.setSortMethod(db::TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc); - params.setRange(db::Range{ .offset = 0, .size = 1 }); - db::TrackEmbeddedImage::find(searchContext.session, params, [&](const db::TrackEmbeddedImage::pointer& image) { res = image->getId(); }); - } - - if (isValid(res)) + db::Artwork::pointer res{ preferredMediaArtwork }; + if (res) return res; // Fallback on front cover for this track { db::TrackEmbeddedImage::FindParameters params; params.setTrack(track->getId()); - params.setImageTypes({ db::ImageType::FrontCover }); + params.setImageType(db::ImageType::FrontCover); params.setSortMethod(db::TrackEmbeddedImageSortMethod::SizeDesc); - params.setRange(db::Range{ .offset = 0, .size = 1 }); - db::TrackEmbeddedImage::find(searchContext.session, params, [&](const db::TrackEmbeddedImage::pointer& image) { res = image->getId(); }); - } - if (isValid(res)) - return res; - - // Fallback on the artwork already resolved for the release - if (const db::Release::pointer release{ db::Release::find(searchContext.session, releaseId) }) - res = release->getPreferredArtworkId(); - - return res; - } - - TrackArtwork computePreferredTrackMediaArtwork(SearchTrackArtworkContext& searchContext, const db::Track::pointer& track) - { - TrackArtwork res; - { - db::TrackEmbeddedImage::FindParameters params; - params.setTrack(track->getId()); - params.setImageTypes({ db::ImageType::Media }); - params.setSortMethod(db::TrackEmbeddedImageSortMethod::SizeDesc); - params.setRange(db::Range{ .offset = 0, .size = 1 }); - db::TrackEmbeddedImage::find(searchContext.session, params, [&](const db::TrackEmbeddedImage::pointer& image) { res = image->getId(); }); - } - - if (isValid(res)) - return res; - - // fallback on another track of the same disc - if (const db::ReleaseId releaseId{ track->getReleaseId() }; releaseId.isValid()) - { - db::TrackEmbeddedImage::FindParameters params; - params.setRelease(releaseId); - params.setDiscNumber(track->getDiscNumber()); - params.setImageTypes({ db::ImageType::Media }); - params.setSortMethod(db::TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc); - params.setRange(db::Range{ .offset = 0, .size = 1 }); - db::TrackEmbeddedImage::find(searchContext.session, params, [&](const db::TrackEmbeddedImage::pointer& image) { res = image->getId(); }); - } - - return res; - } - - bool fetchNextTrackArtworksToUpdate(SearchTrackArtworkContext& searchContext, TrackArtworksAssociationContainer& TrackArtworksAssociations) - { - const db::TrackId trackId{ searchContext.lastRetrievedTrackId }; - - { - constexpr std::size_t readBatchSize{ 100 }; - - auto transaction{ searchContext.session.createReadTransaction() }; - - db::Track::find(searchContext.session, searchContext.lastRetrievedTrackId, readBatchSize, [&](const db::Track::pointer& track) { - const TrackArtwork preferredArtwork{ computePreferredTrackArtwork(searchContext, track) }; - const TrackArtwork preferredMediaArtwork{ computePreferredTrackMediaArtwork(searchContext, track) }; - - const db::Artwork::pointer currentPreferredArtwork{ track->getPreferredArtwork() }; - const db::Artwork::pointer currentPreferredMediaArtwork{ track->getPreferredMediaArtwork() }; - - if (!isSameArtwork(preferredArtwork, currentPreferredArtwork) - || !isSameArtwork(preferredMediaArtwork, currentPreferredMediaArtwork)) - { - TrackArtworksAssociations.push_back(TrackArtworksAssociation{ track, preferredArtwork, preferredMediaArtwork }); - } - - searchContext.processedTrackCount++; + db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { + if (!res) + res = db::Artwork::find(session, image->getId()); }); } - return trackId != searchContext.lastRetrievedTrackId; + if (res) + return res; + + // Fallback on the artwork already resolved for the release + const db::ReleaseId releaseId{ track->getReleaseId() }; + if (!releaseId.isValid()) + return res; + + if (const db::Release::pointer release{ db::Release::find(session, releaseId) }) + res = release->getPreferredArtwork(); + + return res; } - void updateTrackPreferredArtwork(db::Session& session, db::Track::pointer& track, TrackArtwork preferredArtwork) + db::Artwork::pointer computePreferredTrackMediaArtwork(db::Session& session, const db::Track::pointer& track) { - db::Artwork::pointer artwork; - if (const db::TrackEmbeddedImageId * trackEmbeddedImageId{ std::get_if(&preferredArtwork) }) - artwork = utils::getOrCreateArtworkFromTrackEmbeddedImage(session, *trackEmbeddedImageId); - else if (const db::ArtworkId * artworkId{ std::get_if(&preferredArtwork) }) - artwork = db::Artwork::find(session, *artworkId); - - // Using track.modify() is quite CPU intensive as the track class has too many fields - db::Track::updatePreferredArtwork(session, track->getId(), artwork ? artwork->getId() : db::ArtworkId{}); - if (artwork) - LMS_LOG(DBUPDATER, DEBUG, "Updated preferred artwork in track " << track->getAbsoluteFilePath() << " with image in " << utils::toPath(session, artwork->getId())); - else - LMS_LOG(DBUPDATER, DEBUG, "Removed preferred artwork from track " << track->getAbsoluteFilePath()); - } - - void updateTrackPreferredMediaArtwork(db::Session& session, db::Track::pointer& track, TrackArtwork preferredArtwork) - { - db::Artwork::pointer artwork; - if (const db::TrackEmbeddedImageId * trackEmbeddedImageId{ std::get_if(&preferredArtwork) }) - artwork = utils::getOrCreateArtworkFromTrackEmbeddedImage(session, *trackEmbeddedImageId); - else if (const db::ArtworkId * artworkId{ std::get_if(&preferredArtwork) }) - artwork = db::Artwork::find(session, *artworkId); - - // Using track.modify() is quite CPU intensive as the track class has too many fields - db::Track::updatePreferredMediaArtwork(session, track->getId(), artwork ? artwork->getId() : db::ArtworkId{}); - if (artwork) - LMS_LOG(DBUPDATER, DEBUG, "Updated preferred media artwork in track '" << track->getAbsoluteFilePath() << "' with image in " << utils::toPath(session, artwork->getId())); - else - LMS_LOG(DBUPDATER, DEBUG, "Removed preferred media artwork from track '" << track->getAbsoluteFilePath() << "'"); - } - - void updateTrackPreferredArtworks(db::Session& session, const TrackArtworksAssociation& TrackArtworksAssociation) - { - db::Track::pointer track{ TrackArtworksAssociation.track }; + db::Artwork::pointer res; { - const db::Artwork::pointer currentPreferredArtwork{ track->getPreferredArtwork() }; - if (!isSameArtwork(TrackArtworksAssociation.preferredArtwork, currentPreferredArtwork)) - updateTrackPreferredArtwork(session, track, TrackArtworksAssociation.preferredArtwork); + db::TrackEmbeddedImage::FindParameters params; + params.setTrack(track->getId()); + params.setImageType(db::ImageType::Media); + params.setSortMethod(db::TrackEmbeddedImageSortMethod::SizeDesc); + + db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { + if (!res) + res = db::Artwork::find(session, image->getId()); + }); } + if (res) + return res; + + // fallback on another track of the same disc + const db::ReleaseId releaseId{ track->getReleaseId() }; + if (!releaseId.isValid()) + return res; + + if (const auto discNumber{ track->getDiscNumber() }) { - const db::Artwork::pointer currentPreferredMediaArtwork{ track->getPreferredMediaArtwork() }; - if (!isSameArtwork(TrackArtworksAssociation.preferredMediaArtwork, currentPreferredMediaArtwork)) - updateTrackPreferredMediaArtwork(session, track, TrackArtworksAssociation.preferredMediaArtwork); + db::TrackEmbeddedImage::FindParameters params; + params.setRelease(releaseId); + params.setDiscNumber(discNumber); + params.setImageType(db::ImageType::Media); + params.setSortMethod(db::TrackEmbeddedImageSortMethod::TrackNumberThenSizeDesc); + + db::TrackEmbeddedImage::find(session, params, [&](const db::TrackEmbeddedImage::pointer& image) { + if (!res) + res = db::Artwork::find(session, image->getId()); + }); } + + return res; } - void updateTrackPreferredArtworks(db::Session& session, TrackArtworksAssociationContainer& imageAssociations) + void updateTrackPreferredArtworks(db::Session& session, const TrackArtworksAssociation& trackArtworksAssociation) + { + assert(trackArtworksAssociation.preferredArtworkId || trackArtworksAssociation.preferredMediaArtworkId); + + if (trackArtworksAssociation.preferredArtworkId) + db::Track::updatePreferredArtwork(session, trackArtworksAssociation.trackId, *trackArtworksAssociation.preferredArtworkId); + + if (trackArtworksAssociation.preferredMediaArtworkId) + db::Track::updatePreferredMediaArtwork(session, trackArtworksAssociation.trackId, *trackArtworksAssociation.preferredMediaArtworkId); + } + + void updateTrackPreferredArtworks(db::Session& session, TrackArtworksAssociationContainer& imageAssociations, bool forceFullBatch) { constexpr std::size_t writeBatchSize{ 50 }; - while (!imageAssociations.empty()) + while ((forceFullBatch && imageAssociations.size() >= writeBatchSize) || !imageAssociations.empty()) { auto transaction{ session.createWriteTransaction() }; @@ -254,6 +153,76 @@ namespace lms::scanner } } } + + bool fetchNextTrackIdRange(db::Session& session, db::TrackId& lastRetrievedTrackId, db::IdRange& trackIdRange) + { + constexpr std::size_t readBatchSize{ 100 }; + + auto transaction{ session.createReadTransaction() }; + + trackIdRange = db::Track::findNextIdRange(session, lastRetrievedTrackId, readBatchSize); + lastRetrievedTrackId = trackIdRange.last; + + return trackIdRange.isValid(); + } + + class ComputeTrackArtworkAssociationsJob : public core::IJob + { + public: + ComputeTrackArtworkAssociationsJob(db::IDb& db, db::IdRange trackIdRange) + : _db{ db } + , _trackIdRange{ trackIdRange } + { + } + + std::span getTrackAssociations() const { return _trackAssociations; } + std::size_t getProcessedTrackCount() const { return _processedTrackCount; } + + private: + core::LiteralString getName() const override { return "Associate Track Artworks"; } + void run() override + { + auto& session{ _db.getTLSSession() }; + auto transaction{ session.createReadTransaction() }; + + db::Track::find(session, _trackIdRange, [&](const db::Track::pointer& track) { + const db::Artwork::pointer preferredMediaArtwork{ computePreferredTrackMediaArtwork(session, track) }; + const db::Artwork::pointer preferredArtwork{ computePreferredTrackArtwork(session, track, preferredMediaArtwork) }; + + TrackArtworksAssociation artworksAssociation{ .trackId = track->getId(), .preferredArtworkId = std::nullopt, .preferredMediaArtworkId = std::nullopt }; + + if (track->getPreferredArtwork() != preferredArtwork) + { + artworksAssociation.preferredArtworkId = preferredArtwork ? preferredArtwork->getId() : db::ArtworkId{}; + + if (preferredArtwork) + LMS_LOG(DBUPDATER, DEBUG, "Updating preferred artwork in track " << track->getAbsoluteFilePath() << " with image in " << preferredArtwork->getAbsoluteFilePath()); + else + LMS_LOG(DBUPDATER, DEBUG, "Removing preferred artwork from track " << track->getAbsoluteFilePath()); + } + + if (track->getPreferredMediaArtwork() != preferredMediaArtwork) + { + artworksAssociation.preferredMediaArtworkId = preferredMediaArtwork ? preferredMediaArtwork->getId() : db::ArtworkId{}; + + if (preferredMediaArtwork) + LMS_LOG(DBUPDATER, DEBUG, "Updating preferred media artwork in track " << track->getAbsoluteFilePath() << " with image in " << preferredMediaArtwork->getAbsoluteFilePath()); + else + LMS_LOG(DBUPDATER, DEBUG, "Removing preferred media artwork from track " << track->getAbsoluteFilePath()); + } + + if (artworksAssociation.preferredArtworkId || artworksAssociation.preferredMediaArtworkId) + _trackAssociations.push_back(artworksAssociation); + + _processedTrackCount++; + }); + } + + db::IDb& _db; + db::IdRange _trackIdRange; + std::vector _trackAssociations; + std::size_t _processedTrackCount{}; + }; } // namespace ScanStepAssociateTrackImages::ScanStepAssociateTrackImages(InitParams& initParams) @@ -263,7 +232,7 @@ namespace lms::scanner bool ScanStepAssociateTrackImages::needProcess(const ScanContext& context) const { - return context.stats.nbChanges() > 0; + return context.stats.getChangesCount() > 0; } void ScanStepAssociateTrackImages::process(ScanContext& context) @@ -275,20 +244,35 @@ namespace lms::scanner context.currentStepStats.totalElems = db::Track::getCount(session); } - SearchTrackArtworkContext searchContext{ - .session = session, - .lastRetrievedTrackId = {}, - }; - - TrackArtworksAssociationContainer TrackArtworksAssociations; - while (fetchNextTrackArtworksToUpdate(searchContext, TrackArtworksAssociations)) - { + TrackArtworksAssociationContainer trackArtworksAssociations; + auto processTracks = [&](std::span> jobs) { if (_abortScan) return; - updateTrackPreferredArtworks(session, TrackArtworksAssociations); - context.currentStepStats.processedElems = searchContext.processedTrackCount; + for (const auto& job : jobs) + { + const auto& associationJob{ static_cast(*job) }; + const auto& trackAssociations{ associationJob.getTrackAssociations() }; + + trackArtworksAssociations.insert(std::end(trackArtworksAssociations), std::cbegin(trackAssociations), std::cend(trackAssociations)); + + context.currentStepStats.processedElems += associationJob.getProcessedTrackCount(); + } + + updateTrackPreferredArtworks(session, trackArtworksAssociations, true); _progressCallback(context.currentStepStats); + }; + + { + JobQueue queue{ getJobScheduler(), 20, processTracks, 1, 0.85F }; + + db::TrackId lastRetrievedTrackId; + db::IdRange trackIdRange; + while (fetchNextTrackIdRange(session, lastRetrievedTrackId, trackIdRange)) + queue.push(std::make_unique(_db, trackIdRange)); } + + // process all remaining associations + updateTrackPreferredArtworks(session, trackArtworksAssociations, false); } } // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/steps/ScanStepBase.cpp b/src/libs/services/scanner/impl/steps/ScanStepBase.cpp index a82d7fb7..ba1be78c 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepBase.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepBase.cpp @@ -19,10 +19,7 @@ #include "ScanStepBase.hpp" -#include "core/String.hpp" - #include "ScanContext.hpp" -#include "scanners/IFileScanner.hpp" namespace lms::scanner { @@ -31,54 +28,14 @@ namespace lms::scanner , _progressCallback{ initParams.progressCallback } , _abortScan{ initParams.abortScan } , _db{ initParams.db } - , _fileScanners(std::cbegin(initParams.fileScanners), std::cend(initParams.fileScanners)) + , _jobScheduler{ initParams.jobScheduler } + , _fileScanners(initParams.fileScanners) , _lastScanSettings{ initParams.lastScanSettings } { - for (IFileScanner* scanner : _fileScanners) - { - for (const std::filesystem::path& file : scanner->getSupportedFiles()) - { - [[maybe_unused]] auto [it, inserted]{ _scannerByFile.emplace(file, scanner) }; - assert(inserted); - } - - for (const std::filesystem::path& extension : scanner->getSupportedExtensions()) - { - [[maybe_unused]] auto [it, inserted]{ _scannerByExtension.emplace(extension, scanner) }; - assert(inserted); - } - } } ScanStepBase::~ScanStepBase() = default; - IFileScanner* ScanStepBase::selectFileScanner(const std::filesystem::path& filePath) const - { - { - const std::string fileName{ core::stringUtils::stringToLower(filePath.filename().string()) }; - - auto itScanner{ _scannerByFile.find(fileName) }; - if (itScanner != std::cend(_scannerByFile)) - return itScanner->second; - } - - { - const std::string extension{ core::stringUtils::stringToLower(filePath.extension().string()) }; - - auto itScanner{ _scannerByExtension.find(extension) }; - if (itScanner != std::cend(_scannerByExtension)) - return itScanner->second; - } - - return nullptr; - } - - void ScanStepBase::visitFileScanners(const std::function& visitor) const - { - for (IFileScanner* scanner : _fileScanners) - visitor(scanner); - } - void ScanStepBase::addError(ScanContext& context, std::shared_ptr error) { error->accept(_scanErrorLogger); diff --git a/src/libs/services/scanner/impl/steps/ScanStepBase.hpp b/src/libs/services/scanner/impl/steps/ScanStepBase.hpp index a467ddab..93b35343 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepBase.hpp +++ b/src/libs/services/scanner/impl/steps/ScanStepBase.hpp @@ -20,21 +20,23 @@ #pragma once #include -#include -#include -#include #include "IScanStep.hpp" #include "ScanErrorLogger.hpp" +namespace lms::core +{ + class IJobScheduler; +} + namespace lms::db { - class Db; + class IDb; } namespace lms::scanner { - class IFileScanner; + class FileScanners; struct ScannerSettings; struct ScanStepStats; struct ScanContext; @@ -46,12 +48,13 @@ namespace lms::scanner struct InitParams { + core::IJobScheduler& jobScheduler; const ScannerSettings& settings; const ScannerSettings* lastScanSettings{}; ProgressCallback progressCallback; bool& abortScan; - db::Db& db; - std::span fileScanners; + db::IDb& db; + const FileScanners& fileScanners; }; ScanStepBase(InitParams& initParams); ~ScanStepBase() override; @@ -59,9 +62,9 @@ namespace lms::scanner ScanStepBase& operator=(const ScanStepBase&) = delete; protected: + core::IJobScheduler& getJobScheduler() { return _jobScheduler; }; const ScannerSettings* getLastScanSettings() const { return _lastScanSettings; } - IFileScanner* selectFileScanner(const std::filesystem::path& filePath) const; - void visitFileScanners(const std::function& visitor) const; + const FileScanners& getFileScanners() const { return _fileScanners; } void addError(ScanContext& context, std::shared_ptr error); @@ -75,12 +78,11 @@ namespace lms::scanner const ScannerSettings& _settings; ProgressCallback _progressCallback; bool& _abortScan; - db::Db& _db; + db::IDb& _db; private: - std::unordered_map _scannerByFile; - std::unordered_map _scannerByExtension; - std::vector _fileScanners; + core::IJobScheduler& _jobScheduler; + const FileScanners& _fileScanners; const ScannerSettings* _lastScanSettings{}; ScanErrorLogger _scanErrorLogger; diff --git a/src/libs/services/scanner/impl/steps/ScanStepCheckForDuplicatedFiles.cpp b/src/libs/services/scanner/impl/steps/ScanStepCheckForDuplicatedFiles.cpp index cea51cc6..3b236cd7 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepCheckForDuplicatedFiles.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepCheckForDuplicatedFiles.cpp @@ -20,9 +20,9 @@ #include "ScanStepCheckForDuplicatedFiles.hpp" #include "core/ILogger.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Track.hpp" #include "ScanContext.hpp" diff --git a/src/libs/services/scanner/impl/steps/ScanStepCheckForRemovedFiles.cpp b/src/libs/services/scanner/impl/steps/ScanStepCheckForRemovedFiles.cpp index e1707efb..9a6bee1a 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepCheckForRemovedFiles.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepCheckForRemovedFiles.cpp @@ -19,23 +19,169 @@ #include "ScanStepCheckForRemovedFiles.hpp" +#include +#include +#include #include +#include "core/IJob.hpp" #include "core/ILogger.hpp" #include "core/Path.hpp" -#include "database/ArtistInfo.hpp" -#include "database/Db.hpp" -#include "database/Image.hpp" -#include "database/PlayListFile.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackLyrics.hpp" +#include "database/objects/ArtistInfo.hpp" +#include "database/objects/Image.hpp" +#include "database/objects/PlayListFile.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackLyrics.hpp" +#include "FileScanners.hpp" +#include "JobQueue.hpp" #include "ScanContext.hpp" #include "ScannerSettings.hpp" +#include "services/scanner/ScannerStats.hpp" namespace lms::scanner { + namespace + { + template + struct FileToCheck + { + IdType objectId; + std::filesystem::path file; + }; + + template + class CheckForRemovedFilesJob : public core::IJob + { + public: + CheckForRemovedFilesJob(const ScannerSettings& settings, const FileScanners& scanners, std::span> filesToCheck) + : _settings{ settings } + , _scanners{ scanners } + , _filesToCheck{ std::cbegin(filesToCheck), std::cend(filesToCheck) } + { + } + + std::size_t getProcessedCount() const { return _processedCount; } + std::span getObjectsToRemove() const { return _objectsToRemove; } + + private: + core::LiteralString getName() const override { return "Check For Removed Files"; } + void run() override + { + for (const FileToCheck& fileToCheck : _filesToCheck) + { + if (!checkFile(fileToCheck.file)) + _objectsToRemove.push_back(fileToCheck.objectId); + } + + _processedCount += _filesToCheck.size(); + } + + bool checkFile(const std::filesystem::path& p) + { + std::error_code ec; + const std::filesystem::directory_entry fileEntry{ p, ec }; + if (ec) + { + // TODO store error? + LMS_LOG(DBUPDATER, ERROR, "Error while checking file " << p << ": " << ec.message()); + return false; + } + + // For each track, make sure the the file still exists + // and still belongs to a media directory + if (!fileEntry.exists() || !fileEntry.is_regular_file()) + { + LMS_LOG(DBUPDATER, DEBUG, "Removing " << p << ": missing"); + return false; + } + + if (std::none_of(std::cbegin(_settings.mediaLibraries), std::cend(_settings.mediaLibraries), + [&](const MediaLibraryInfo& libraryInfo) { + return core::pathUtils::isPathInRootPath(p, libraryInfo.rootDirectory, &excludeDirFileName); + })) + { + LMS_LOG(DBUPDATER, DEBUG, "Removing " << p << ": out of media directory"); + return false; + } + + if (!_scanners.select(p)) + { + LMS_LOG(DBUPDATER, DEBUG, "Removing " << p << ": file format no longer handled"); + return false; + } + + return true; + } + + const ScannerSettings& _settings; + const FileScanners& _scanners; + std::vector> _filesToCheck; + std::vector _objectsToRemove; + std::size_t _processedCount{}; + }; + + template + std::size_t removeObjects(db::Session& session, std::deque& objectIdsToRemove, bool forceFullBatch) + { + std::size_t removedObjectCount{}; + constexpr std::size_t writeBatchSize{ 50 }; + + std::vector ids; + while ((forceFullBatch && objectIdsToRemove.size() >= writeBatchSize) || !objectIdsToRemove.empty()) + { + for (std::size_t i{}; !objectIdsToRemove.empty() && i < writeBatchSize; ++i) + { + ids.push_back(objectIdsToRemove.front()); + objectIdsToRemove.pop_front(); + } + + { + auto transaction{ session.createWriteTransaction() }; + session.destroy(ids); + } + + removedObjectCount += ids.size(); + ids.clear(); + } + + return removedObjectCount; + } + + template + bool fetchNextFilesToCheck(db::Session& session, typename Object::IdType& lastCheckedId, std::vector>& filesToCheck) + { + constexpr std::size_t batchSize{ 200 }; + + filesToCheck.clear(); + filesToCheck.reserve(batchSize); + + auto transaction{ session.createReadTransaction() }; + + while (filesToCheck.size() < batchSize) + { + const typename Object::IdType previousLastCheckedId{ lastCheckedId }; + Object::findAbsoluteFilePath(session, lastCheckedId, batchSize, [&](Object::IdType objectId, const std::filesystem::path& filePath) { + // special case for track lyrics, only check external lyrics + if constexpr (std::is_same_v) + { + if (filePath.empty()) + return; + } + + filesToCheck.emplace_back(objectId, filePath); + }); + + if (previousLastCheckedId == lastCheckedId) + break; + } + + return !filesToCheck.empty(); + } + } // namespace + bool ScanStepCheckForRemovedFiles::needProcess([[maybe_unused]] const ScanContext& context) const { // always check for removed files @@ -48,12 +194,7 @@ namespace lms::scanner { auto transaction{ session.createReadTransaction() }; - context.currentStepStats.totalElems = 0; - context.currentStepStats.totalElems += db::Track::getCount(session); - context.currentStepStats.totalElems += db::Image::getCount(session); - context.currentStepStats.totalElems += db::TrackLyrics::getExternalLyricsCount(session); - context.currentStepStats.totalElems += db::PlayListFile::getCount(session); - context.currentStepStats.totalElems += db::ArtistInfo::getCount(session); + context.currentStepStats.totalElems = session.getFileStats().getTotalFileCount(); } LMS_LOG(DBUPDATER, DEBUG, context.currentStepStats.totalElems << " files to be checked..."); @@ -67,94 +208,46 @@ namespace lms::scanner template void ScanStepCheckForRemovedFiles::checkForRemovedFiles(ScanContext& context) { - using namespace db; + using ObjectIdType = typename Object::IdType; if (_abortScan) return; - Session& session{ _db.getTLSSession() }; + db::Session& session{ _db.getTLSSession() }; - std::vector objectsToRemove; + std::deque objectIdsToRemove; - typename Object::IdType lastCheckedId; - bool endReached{}; - while (!endReached) - { + auto processJobsDone = [&](std::span> jobs) { if (_abortScan) - break; + return; - objectsToRemove.clear(); + for (const auto& job : jobs) { - constexpr std::size_t batchSize = 100; + const auto& checkJob{ static_cast&>(*job) }; + std::span objectsToRemove{ checkJob.getObjectsToRemove() }; - auto transaction{ session.createReadTransaction() }; + objectIdsToRemove.insert(std::end(objectIdsToRemove), std::cbegin(objectsToRemove), std::cend(objectsToRemove)); - endReached = true; - Object::find(session, lastCheckedId, batchSize, [&](const typename Object::pointer& object) { - endReached = false; - - // special case for track lyrics, only check external lyrics - if constexpr (std::is_same_v) - { - if (object->getAbsoluteFilePath().empty()) - return; - } - - if (!checkFile(object->getAbsoluteFilePath())) - objectsToRemove.push_back(object); - - context.currentStepStats.processedElems++; - }); + context.currentStepStats.processedElems += checkJob.getProcessedCount(); } - if (!objectsToRemove.empty()) - { - auto transaction{ session.createWriteTransaction() }; - - for (typename Object::pointer& object : objectsToRemove) - { - object.remove(); - context.stats.deletions++; - } - } + if (!objectIdsToRemove.empty()) + context.stats.deletions += removeObjects(session, objectIdsToRemove, true); _progressCallback(context.currentStepStats); + }; + + { + JobQueue queue{ getJobScheduler(), 50, processJobsDone, 1, 0.85F }; + + ObjectIdType lastCheckedId; + std::vector> filesToCheck; + while (fetchNextFilesToCheck(session, lastCheckedId, filesToCheck)) + queue.push(std::make_unique>(_settings, getFileScanners(), filesToCheck)); } + + // process all remaining objects + context.stats.deletions += removeObjects(session, objectIdsToRemove, false); } - bool ScanStepCheckForRemovedFiles::checkFile(const std::filesystem::path& p) - { - try - { - // For each track, make sure the the file still exists - // and still belongs to a media directory - if (!std::filesystem::exists(p) || !std::filesystem::is_regular_file(p)) - { - LMS_LOG(DBUPDATER, DEBUG, "Removing " << p << ": missing"); - return false; - } - - if (std::none_of(std::cbegin(_settings.mediaLibraries), std::cend(_settings.mediaLibraries), - [&](const MediaLibraryInfo& libraryInfo) { - return core::pathUtils::isPathInRootPath(p, libraryInfo.rootDirectory, &excludeDirFileName); - })) - { - LMS_LOG(DBUPDATER, DEBUG, "Removing " << p << ": out of media directory"); - return false; - } - - if (!selectFileScanner(p)) - { - LMS_LOG(DBUPDATER, DEBUG, "Removing " << p.string() << ": file format no longer handled"); - return false; - } - - return true; - } - catch (std::filesystem::filesystem_error& e) - { - LMS_LOG(DBUPDATER, ERROR, "Caught exception while checking file '" << p.string() << "': " << e.what()); - return false; - } - } } // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/steps/ScanStepCompact.cpp b/src/libs/services/scanner/impl/steps/ScanStepCompact.cpp index 130d3408..e231bc7e 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepCompact.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepCompact.cpp @@ -19,7 +19,7 @@ #include "ScanStepCompact.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" #include "ScanContext.hpp" diff --git a/src/libs/services/scanner/impl/steps/ScanStepComputeClusterStats.cpp b/src/libs/services/scanner/impl/steps/ScanStepComputeClusterStats.cpp index e2fd438a..3089aa8a 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepComputeClusterStats.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepComputeClusterStats.cpp @@ -19,9 +19,9 @@ #include "ScanStepComputeClusterStats.hpp" #include "core/ILogger.hpp" -#include "database/Cluster.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" +#include "database/objects/Cluster.hpp" #include "ScanContext.hpp" @@ -29,10 +29,7 @@ namespace lms::scanner { bool ScanStepComputeClusterStats::needProcess(const ScanContext& context) const { - if (context.stats.nbChanges() > 0) - return true; - - return false; + return context.stats.getChangesCount() > 0; } void ScanStepComputeClusterStats::process(ScanContext& context) diff --git a/src/libs/services/scanner/impl/steps/ScanStepDiscoverFiles.cpp b/src/libs/services/scanner/impl/steps/ScanStepDiscoverFiles.cpp deleted file mode 100644 index 61056433..00000000 --- a/src/libs/services/scanner/impl/steps/ScanStepDiscoverFiles.cpp +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 "ScanStepDiscoverFiles.hpp" - -#include "core/ILogger.hpp" -#include "core/Path.hpp" - -#include "MediaLibraryInfo.hpp" -#include "ScannerSettings.hpp" - -#include "ScanContext.hpp" - -namespace lms::scanner -{ - bool ScanStepDiscoverFiles::needProcess([[maybe_unused]] const ScanContext& context) const - { - // always discover files - return true; - } - - void ScanStepDiscoverFiles::process(ScanContext& context) - { - context.stats.totalFileCount = 0; - - for (const MediaLibraryInfo& mediaLibrary : _settings.mediaLibraries) - { - std::size_t currentDirectoryProcessElemsCount{}; - core::pathUtils::exploreFilesRecursive( - mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path) { - if (_abortScan) - return false; - - // we don't report errors here (done in the actual scan step) - if (!ec && selectFileScanner(path)) - { - context.currentStepStats.processedElems++; - currentDirectoryProcessElemsCount++; - _progressCallback(context.currentStepStats); - } - - return true; - }, - &excludeDirFileName); - - LMS_LOG(DBUPDATER, DEBUG, "Discovered " << currentDirectoryProcessElemsCount << " files in " << mediaLibrary.rootDirectory); - } - - context.stats.totalFileCount = context.currentStepStats.processedElems; - - LMS_LOG(DBUPDATER, DEBUG, "Discovered " << context.stats.totalFileCount << " files in all directories"); - } -} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/steps/ScanStepOptimize.cpp b/src/libs/services/scanner/impl/steps/ScanStepOptimize.cpp index 576f79d9..dfc87d65 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepOptimize.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepOptimize.cpp @@ -20,7 +20,7 @@ #include "ScanStepOptimize.hpp" #include "core/ILogger.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" #include "ScanContext.hpp" @@ -32,8 +32,13 @@ namespace lms::scanner if (context.scanOptions.forceOptimize) return true; - if (context.stats.nbChanges() > (context.stats.nbFiles() / 10)) + // Don't optimize if there are too few files: it may lead to some indexes not being used + // and will drastically slow down the scan process when adding more files later + if (context.stats.getChangesCount() > (context.stats.getTotalFileCount() / 5) + && context.stats.getTotalFileCount() >= 1'000) + { return true; + } return false; } diff --git a/src/libs/services/scanner/impl/steps/ScanStepRemoveOrphanedDbEntries.cpp b/src/libs/services/scanner/impl/steps/ScanStepRemoveOrphanedDbEntries.cpp index 360331bd..abaac7a8 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepRemoveOrphanedDbEntries.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepRemoveOrphanedDbEntries.cpp @@ -20,14 +20,14 @@ #include "ScanStepRemoveOrphanedDbEntries.hpp" #include "core/ILogger.hpp" -#include "database/Artist.hpp" -#include "database/Cluster.hpp" -#include "database/Db.hpp" -#include "database/Directory.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackEmbeddedImage.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" #include "ScanContext.hpp" @@ -109,7 +109,7 @@ namespace lms::scanner template void ScanStepRemoveOrphanedDbEntries::removeOrphanedEntries(ScanContext& context) { - constexpr std::size_t batchSize = 100; + constexpr std::size_t batchSize = 200; using IdType = typename T::IdType; @@ -130,14 +130,7 @@ namespace lms::scanner { auto transaction{ session.createWriteTransaction() }; - for (const IdType objectId : entries.results) - { - if (_abortScan) - break; - - typename T::pointer entry{ T::find(session, objectId) }; - entry.remove(); - } + session.destroy(entries.results); } context.currentStepStats.processedElems += entries.results.size(); diff --git a/src/libs/services/scanner/impl/steps/ScanStepScanFiles.cpp b/src/libs/services/scanner/impl/steps/ScanStepScanFiles.cpp index 1ed23520..85af7f8c 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepScanFiles.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepScanFiles.cpp @@ -19,71 +19,147 @@ #include "ScanStepScanFiles.hpp" -#include "FileScanQueue.hpp" +#include + #include "ScannerSettings.hpp" -#include "core/IConfig.hpp" +#include "core/IJob.hpp" +#include "core/IJobScheduler.hpp" #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" -#include "core/Path.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" #include "scanners/FileToScan.hpp" #include "scanners/IFileScanOperation.hpp" #include "scanners/IFileScanner.hpp" +#include "FileScanners.hpp" +#include "JobQueue.hpp" #include "ScanContext.hpp" namespace lms::scanner { - using namespace db; - namespace { - std::size_t getScanMetaDataThreadCount() + using ExploreFileCallback = std::function; + bool exploreFilesRecursive(const std::filesystem::path& directory, ExploreFileCallback cb, const std::filesystem::path* excludeDirFileName) { - std::size_t threadCount{ core::Service::get()->getULong("scanner-metadata-thread-count", 0) }; + std::error_code ec; + std::filesystem::directory_iterator itPath{ directory, std::filesystem::directory_options::follow_directory_symlink, ec }; - if (threadCount == 0) - threadCount = std::max(std::thread::hardware_concurrency() / 2, 1); - - return threadCount; - } - - FileToScan retrieveFileInfo(const std::filesystem::path& file, const MediaLibraryInfo& mediaLibrary, std::error_code& ec) - { - FileToScan res; - - res.lastWriteTime = core::pathUtils::getLastWriteTime(file, ec); - if (!ec) - res.relativePath = std::filesystem::relative(file, mediaLibrary.rootDirectory, ec); - if (!ec) - res.fileSize = std::filesystem::file_size(file, ec); - - if (!ec) + if (ec) { - res.filePath = file; - res.mediaLibrary = mediaLibrary; + cb(ec, directory, nullptr); + return true; // try to continue exploring anyway } - return res; + if (excludeDirFileName && !excludeDirFileName->empty()) + { + const std::filesystem::path excludePath{ directory / *excludeDirFileName }; + if (std::filesystem::exists(excludePath, ec)) + { + LMS_LOG(DBUPDATER, DEBUG, "Found " << excludePath << ": skipping directory"); + return true; + } + } + + std::filesystem::directory_iterator itEnd; + while (itPath != itEnd) + { + bool continueExploring{ true }; + + const std::filesystem::directory_entry& entry{ *itPath }; + const std::filesystem::path& path{ entry.path() }; + + if (ec) + { + continueExploring = cb(ec, path, nullptr); + } + else + { + if (entry.is_regular_file()) + continueExploring = cb(ec, path, &entry); + else if (entry.is_directory()) + continueExploring = exploreFilesRecursive(path, cb, excludeDirFileName); + } + + if (!continueExploring) + return false; + + itPath.increment(ec); + } + + return true; } + + class FileScanJob : public core::IJob + { + public: + FileScanJob(const FileScanners& fileScanners, const MediaLibraryInfo& mediaLibrary, bool fullScan, std::span files) + : _fileScanners{ fileScanners } + , _mediaLibrary{ mediaLibrary } + , _fullScan{ fullScan } + , _processCount{} + , _skipCount{} + , _files{ std::cbegin(files), std::cend(files) } + { + } + + std::size_t getFileCount() const { return _processCount; }; + std::size_t getSkipCount() const { return _skipCount; } + + std::span> getScanOperations() + { + return _scanOperations; + } + + private: + core::LiteralString getName() const override + { + return "Scan Files"; + } + + void run() override + { + for (const auto& file : _files) + { + IFileScanner* scanner{ _fileScanners.select(file.path()) }; + if (!scanner) + continue; + + FileToScan fileToScan; + + fileToScan.filePath = file.path(); + fileToScan.mediaLibrary = _mediaLibrary; + fileToScan.lastWriteTime.setTime_t(Wt::WDateTime{ std::chrono::file_clock::to_sys(file.last_write_time()) }.toTime_t()); // sec resolution, as stored in the database + fileToScan.fileSize = file.file_size(); + + if (_fullScan || scanner->needsScan(fileToScan)) + { + auto scanOperation{ scanner->createScanOperation(std::move(fileToScan)) }; + + { + LMS_SCOPED_TRACE_DETAILED("Scanner", scanOperation->getName()); + scanOperation->scan(); + } + _scanOperations.push_back(std::move(scanOperation)); + } + else + _skipCount++; + + _processCount++; + } + } + + const FileScanners& _fileScanners; + const MediaLibraryInfo& _mediaLibrary; + const bool _fullScan; + std::size_t _processCount; + std::size_t _skipCount; + std::vector _files; + std::vector> _scanOperations; + }; } // namespace - ScanStepScanFiles::ScanStepScanFiles(InitParams& initParams) - : ScanStepBase{ initParams } - , _fileScanQueue{ getScanMetaDataThreadCount(), _abortScan } - { - visitFileScanners([](IFileScanner* scanner) { - for (const std::filesystem::path& file : scanner->getSupportedFiles()) - LMS_LOG(DBUPDATER, INFO, scanner->getName() << ": supporting file " << file); - - for (const std::filesystem::path& extension : scanner->getSupportedExtensions()) - LMS_LOG(DBUPDATER, INFO, scanner->getName() << ": supporting file extension " << extension); - }); - - LMS_LOG(DBUPDATER, INFO, "Using " << _fileScanQueue.getThreadCount() << " thread(s) for scanning file metadata"); - } - bool ScanStepScanFiles::needProcess([[maybe_unused]] const ScanContext& context) const { // Always need to scan files @@ -92,34 +168,52 @@ namespace lms::scanner void ScanStepScanFiles::process(ScanContext& context) { - context.currentStepStats.totalElems = context.stats.totalFileCount; - for (const MediaLibraryInfo& mediaLibrary : _settings.mediaLibraries) process(context, mediaLibrary); + + context.stats.totalFileCount = context.currentStepStats.processedElems; } void ScanStepScanFiles::process(ScanContext& context, const MediaLibraryInfo& mediaLibrary) { - const std::size_t scanQueueMaxScanRequestCount{ 100 * _fileScanQueue.getThreadCount() }; - const std::size_t processFileResultsBatchSize{ 10 }; + constexpr std::size_t filesPerScanJob{ 10 }; + constexpr std::size_t scanQueueMaxSize{ 50 }; + constexpr std::size_t processFileResultsBatchSize{ 1 }; + constexpr float drainRatio{ 0.85 }; - std::vector> scanOperations; + std::deque> operations; - core::pathUtils::exploreFilesRecursive( - mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path) { - LMS_SCOPED_TRACE_DETAILED("Scanner", "OnExploreFile"); + auto processDoneJobs = [&](std::span> jobsDone) { + for (const auto& jobDone : jobsDone) + { + auto& fileScanJob{ static_cast(*jobDone) }; + for (std::unique_ptr& scanOperation : fileScanJob.getScanOperations()) + operations.push_back(std::move(scanOperation)); - if (_abortScan) - return false; // stop iterating + context.currentStepStats.processedElems += fileScanJob.getFileCount(); + context.stats.skips += fileScanJob.getSkipCount(); + } + + if (!_abortScan) + processFileScanOperations(context, operations, true /* force batch */); + + _progressCallback(context.currentStepStats); + }; + + { + JobQueue queue{ getJobScheduler(), scanQueueMaxSize, processDoneJobs, processFileResultsBatchSize, drainRatio }; + + std::vector filesToScan; + + exploreFilesRecursive( + mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path, const std::filesystem::directory_entry* fileEntry) { + LMS_SCOPED_TRACE_DETAILED("Scanner", "OnExploreFile"); + + assert((ec && !fileEntry) || (!ec && fileEntry)); + + if (_abortScan) + return false; // stop iterating - if (ec) - { - addError(context, path, ec); - context.stats.skips++; - } - else if (IFileScanner * scanner{ selectFileScanner(path) }) - { - FileToScan fileToScan{ retrieveFileInfo(path, mediaLibrary, ec) }; if (ec) { addError(context, path, ec); @@ -127,68 +221,74 @@ namespace lms::scanner } else { - if (context.scanOptions.fullScan || scanner->needsScan(fileToScan)) + filesToScan.push_back(*fileEntry); + + if (filesToScan.size() >= filesPerScanJob) { - auto scanOperation{ scanner->createScanOperation(std::move(fileToScan)) }; - _fileScanQueue.pushScanRequest(std::move(scanOperation)); + queue.push(std::make_unique(getFileScanners(), mediaLibrary, context.scanOptions.fullScan, filesToScan)); + filesToScan.clear(); } } - context.currentStepStats.processedElems++; - _progressCallback(context.currentStepStats); - } + return true; + }, + &excludeDirFileName); - while (_fileScanQueue.getResultsCount() > (scanQueueMaxScanRequestCount / 2)) - { - _fileScanQueue.popResults(scanOperations, processFileResultsBatchSize); - processFileScanResults(context, scanOperations); - } + if (!filesToScan.empty()) + queue.push(std::make_unique(getFileScanners(), mediaLibrary, context.scanOptions.fullScan, filesToScan)); - _fileScanQueue.wait(scanQueueMaxScanRequestCount); + _progressCallback(context.currentStepStats); + } - return true; - }, - &excludeDirFileName); - - _fileScanQueue.wait(); - - while (!_abortScan && _fileScanQueue.popResults(scanOperations, processFileResultsBatchSize) > 0) - processFileScanResults(context, scanOperations); + // Process remaining objects + processFileScanOperations(context, operations, false /* force batch */); } - void ScanStepScanFiles::processFileScanResults(ScanContext& context, std::span> scanOperations) + std::size_t ScanStepScanFiles::processFileScanOperations(ScanContext& context, std::deque>& scanOperations, bool forceBatch) { + std::size_t count{}; + constexpr std::size_t writeBatchSize{ 10 }; + LMS_SCOPED_TRACE_OVERVIEW("Scanner", "ProcessScanResults"); - db::Session& dbSession{ _db.getTLSSession() }; - auto transaction{ dbSession.createWriteTransaction() }; - - for (auto& scanOperation : scanOperations) + while ((forceBatch && scanOperations.size() >= writeBatchSize) || !scanOperations.empty()) { - if (_abortScan) - return; + db::Session& dbSession{ _db.getTLSSession() }; + auto transaction{ dbSession.createWriteTransaction() }; - LMS_LOG(DBUPDATER, DEBUG, scanOperation->getName() << ": processing result for " << scanOperation->getFilePath()); - const IFileScanOperation::OperationResult res{ scanOperation->processResult() }; - switch (res) + for (std::size_t i{}; !scanOperations.empty() && i < writeBatchSize; ++i) { - case IFileScanOperation::OperationResult::Added: - context.stats.additions++; - break; - case IFileScanOperation::OperationResult::Removed: - context.stats.deletions++; - break; - case IFileScanOperation::OperationResult::Skipped: - context.stats.failures++; - break; - case IFileScanOperation::OperationResult::Updated: - context.stats.updates++; - break; + processFileScanOperation(context, *scanOperations.front()); + scanOperations.pop_front(); + count++; } - context.stats.scans++; - - for (const auto& error : scanOperation->getErrors()) - addError(context, error); } + + return count; + } + + void ScanStepScanFiles::processFileScanOperation(ScanContext& context, IFileScanOperation& scanOperation) + { + LMS_LOG(DBUPDATER, DEBUG, scanOperation.getName() << ": processing result for " << scanOperation.getFilePath()); + const IFileScanOperation::OperationResult res{ scanOperation.processResult() }; + switch (res) + { + case IFileScanOperation::OperationResult::Added: + context.stats.additions++; + break; + case IFileScanOperation::OperationResult::Removed: + context.stats.deletions++; + break; + case IFileScanOperation::OperationResult::Skipped: + context.stats.failures++; + break; + case IFileScanOperation::OperationResult::Updated: + context.stats.updates++; + break; + } + context.stats.scans++; + + for (const auto& error : scanOperation.getErrors()) + addError(context, error); } } // namespace lms::scanner \ No newline at end of file diff --git a/src/libs/services/scanner/impl/steps/ScanStepScanFiles.hpp b/src/libs/services/scanner/impl/steps/ScanStepScanFiles.hpp index 90e9ce87..0713454a 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepScanFiles.hpp +++ b/src/libs/services/scanner/impl/steps/ScanStepScanFiles.hpp @@ -19,21 +19,24 @@ #pragma once -#include -#include +#include -#include "FileScanQueue.hpp" #include "ScanStepBase.hpp" +namespace lms::core +{ + class IJob; +} + namespace lms::scanner { - class IFileScanner; + class IFileScanOperation; struct MediaLibraryInfo; class ScanStepScanFiles : public ScanStepBase { public: - ScanStepScanFiles(InitParams& initParams); + using ScanStepBase::ScanStepBase; private: ScanStep getStep() const override { return ScanStep::ScanFiles; } @@ -42,8 +45,7 @@ namespace lms::scanner void process(ScanContext& context) override; void process(ScanContext& context, const MediaLibraryInfo& mediaLibrary); - void processFileScanResults(ScanContext& context, std::span> scanOperations); - - FileScanQueue _fileScanQueue; + std::size_t processFileScanOperations(ScanContext& context, std::deque>& scanOperations, bool forceBatch); + void processFileScanOperation(ScanContext& context, IFileScanOperation& operation); }; } // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/steps/ScanStepUpdateLibraryFields.cpp b/src/libs/services/scanner/impl/steps/ScanStepUpdateLibraryFields.cpp index 1e9b52fd..22500381 100644 --- a/src/libs/services/scanner/impl/steps/ScanStepUpdateLibraryFields.cpp +++ b/src/libs/services/scanner/impl/steps/ScanStepUpdateLibraryFields.cpp @@ -19,10 +19,10 @@ #include "ScanStepUpdateLibraryFields.hpp" -#include "database/Db.hpp" -#include "database/Directory.hpp" -#include "database/MediaLibrary.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/MediaLibrary.hpp" #include "MediaLibraryInfo.hpp" #include "ScanContext.hpp" diff --git a/src/libs/services/scanner/include/services/scanner/IScannerService.hpp b/src/libs/services/scanner/include/services/scanner/IScannerService.hpp index b811ce29..fa272396 100644 --- a/src/libs/services/scanner/include/services/scanner/IScannerService.hpp +++ b/src/libs/services/scanner/include/services/scanner/IScannerService.hpp @@ -27,7 +27,7 @@ namespace lms::db { - class Db; + class IDb; } namespace lms::scanner @@ -61,5 +61,5 @@ namespace lms::scanner virtual Events& getEvents() = 0; }; - std::unique_ptr createScannerService(db::Db& db); + std::unique_ptr createScannerService(db::IDb& db); } // namespace lms::scanner diff --git a/src/libs/services/scanner/include/services/scanner/ScannerStats.hpp b/src/libs/services/scanner/include/services/scanner/ScannerStats.hpp index 2de12172..1015cdbb 100644 --- a/src/libs/services/scanner/include/services/scanner/ScannerStats.hpp +++ b/src/libs/services/scanner/include/services/scanner/ScannerStats.hpp @@ -24,7 +24,7 @@ #include #include -#include "database/TrackId.hpp" +#include "database/objects/TrackId.hpp" #include "ScanErrors.hpp" @@ -54,7 +54,6 @@ namespace lms::scanner CheckForRemovedFiles, ComputeClusterStats, Compact, - DiscoverFiles, FetchTrackFeatures, Optimize, ReconciliateArtists, @@ -84,7 +83,7 @@ namespace lms::scanner Wt::WDateTime startTime; Wt::WDateTime stopTime; - std::size_t totalFileCount{}; // Total number of files (estimated) + std::size_t totalFileCount{}; // Total number of files (only valid after the file scan step) std::size_t skips{}; // no change since last scan std::size_t scans{}; // count of scanned files @@ -101,7 +100,7 @@ namespace lms::scanner std::size_t errorsCount{}; // maybe bigger than errors.size() if too many errors std::vector duplicates; - std::size_t nbFiles() const; - std::size_t nbChanges() const; + std::size_t getTotalFileCount() const; + std::size_t getChangesCount() const; }; } // namespace lms::scanner diff --git a/src/libs/services/scrobbling/impl/ScrobblingService.cpp b/src/libs/services/scrobbling/impl/ScrobblingService.cpp index 7ace3afd..5e97a9a7 100644 --- a/src/libs/services/scrobbling/impl/ScrobblingService.cpp +++ b/src/libs/services/scrobbling/impl/ScrobblingService.cpp @@ -20,13 +20,13 @@ #include "ScrobblingService.hpp" #include "core/ILogger.hpp" -#include "database/Artist.hpp" -#include "database/Db.hpp" -#include "database/Listen.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Listen.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "internal/InternalBackend.hpp" #include "listenbrainz/ListenBrainzBackend.hpp" @@ -55,12 +55,12 @@ namespace lms::scrobbling } } // namespace - std::unique_ptr createScrobblingService(boost::asio::io_context& ioContext, Db& db) + std::unique_ptr createScrobblingService(boost::asio::io_context& ioContext, db::IDb& db) { return std::make_unique(ioContext, db); } - ScrobblingService::ScrobblingService(boost::asio::io_context& ioContext, Db& db) + ScrobblingService::ScrobblingService(boost::asio::io_context& ioContext, db::IDb& db) : _db{ db } { LMS_LOG(SCROBBLING, INFO, "Starting service..."); diff --git a/src/libs/services/scrobbling/impl/ScrobblingService.hpp b/src/libs/services/scrobbling/impl/ScrobblingService.hpp index 1bde3e49..6fc26444 100644 --- a/src/libs/services/scrobbling/impl/ScrobblingService.hpp +++ b/src/libs/services/scrobbling/impl/ScrobblingService.hpp @@ -32,7 +32,7 @@ namespace lms::scrobbling class ScrobblingService : public IScrobblingService { public: - ScrobblingService(boost::asio::io_context& ioContext, db::Db& db); + ScrobblingService(boost::asio::io_context& ioContext, db::IDb& db); ~ScrobblingService(); private: @@ -56,7 +56,7 @@ namespace lms::scrobbling std::optional getUserBackend(db::UserId userId); - db::Db& _db; + db::IDb& _db; std::unordered_map> _scrobblingBackends; }; diff --git a/src/libs/services/scrobbling/impl/internal/InternalBackend.cpp b/src/libs/services/scrobbling/impl/internal/InternalBackend.cpp index b70ab4b7..2fef72d0 100644 --- a/src/libs/services/scrobbling/impl/internal/InternalBackend.cpp +++ b/src/libs/services/scrobbling/impl/internal/InternalBackend.cpp @@ -19,15 +19,15 @@ #include "InternalBackend.hpp" -#include "database/Db.hpp" -#include "database/Listen.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/Listen.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" namespace lms::scrobbling { - InternalBackend::InternalBackend(db::Db& db) + InternalBackend::InternalBackend(db::IDb& db) : _db{ db } { } diff --git a/src/libs/services/scrobbling/impl/internal/InternalBackend.hpp b/src/libs/services/scrobbling/impl/internal/InternalBackend.hpp index a0fd45d7..6a952373 100644 --- a/src/libs/services/scrobbling/impl/internal/InternalBackend.hpp +++ b/src/libs/services/scrobbling/impl/internal/InternalBackend.hpp @@ -23,7 +23,7 @@ namespace lms::db { - class Db; + class IDb; } namespace lms::scrobbling @@ -31,13 +31,13 @@ namespace lms::scrobbling class InternalBackend final : public IScrobblingBackend { public: - InternalBackend(db::Db& db); + InternalBackend(db::IDb& db); private: void listenStarted(const Listen& listen) override; void listenFinished(const Listen& listen, std::optional duration) override; void addTimedListen(const TimedListen& listen) override; - db::Db& _db; + db::IDb& _db; }; } // namespace lms::scrobbling diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.cpp index 1d0ac7a5..f241f01e 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.cpp @@ -23,9 +23,9 @@ #include "core/ILogger.hpp" #include "core/Service.hpp" #include "core/http/IClient.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Track.hpp" #include "Utils.hpp" @@ -51,7 +51,7 @@ namespace lms::scrobbling::listenBrainz } } // namespace - ListenBrainzBackend::ListenBrainzBackend(boost::asio::io_context& ioContext, Db& db) + ListenBrainzBackend::ListenBrainzBackend(boost::asio::io_context& ioContext, db::IDb& db) : _ioContext{ ioContext } , _db{ db } , _baseAPIUrl{ core::Service::get()->getString("listenbrainz-api-base-url", "https://api.listenbrainz.org") } diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.hpp b/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.hpp index 3ab08fd5..e04a5be4 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.hpp @@ -29,7 +29,7 @@ namespace lms::db { - class Db; + class IDb; } namespace lms::scrobbling::listenBrainz @@ -37,7 +37,7 @@ namespace lms::scrobbling::listenBrainz class ListenBrainzBackend final : public IScrobblingBackend { public: - ListenBrainzBackend(boost::asio::io_context& ioContext, db::Db& db); + ListenBrainzBackend(boost::asio::io_context& ioContext, db::IDb& db); ~ListenBrainzBackend() override; private: @@ -52,7 +52,7 @@ namespace lms::scrobbling::listenBrainz void enqueListen(const Listen& listen, const Wt::WDateTime& timePoint); boost::asio::io_context& _ioContext; - db::Db& _db; + db::IDb& _db; std::string _baseAPIUrl; std::unique_ptr _client; ListensSynchronizer _listensSynchronizer; diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp index e08baab2..192695fa 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -30,13 +31,13 @@ #include "core/IConfig.hpp" #include "core/Service.hpp" #include "core/http/IClient.hpp" -#include "database/Artist.hpp" -#include "database/Db.hpp" -#include "database/Listen.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Listen.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "services/scrobbling/Exception.hpp" #include "Utils.hpp" @@ -210,7 +211,7 @@ namespace lms::scrobbling::listenBrainz } } // namespace - ListensSynchronizer::ListensSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client) + ListensSynchronizer::ListensSynchronizer(boost::asio::io_context& ioContext, db::IDb& db, core::http::IClient& client) : _ioContext{ ioContext } , _db{ db } , _client{ client } diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.hpp b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.hpp index dd851e45..25523e3b 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.hpp @@ -27,7 +27,7 @@ #include #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/UserId.hpp" #include "services/scrobbling/Listen.hpp" @@ -39,7 +39,7 @@ namespace lms } namespace db { - class Db; + class IDb; } } // namespace lms @@ -48,7 +48,7 @@ namespace lms::scrobbling::listenBrainz class ListensSynchronizer { public: - ListensSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client); + ListensSynchronizer(boost::asio::io_context& ioContext, db::IDb& db, core::http::IClient& client); void enqueListen(const TimedListen& listen); void enqueListenNow(const Listen& listen); @@ -94,7 +94,7 @@ namespace lms::scrobbling::listenBrainz boost::asio::io_context& _ioContext; boost::asio::io_context::strand _strand{ _ioContext }; - db::Db& _db; + db::IDb& _db; boost::asio::steady_timer _syncTimer{ _ioContext }; core::http::IClient& _client; diff --git a/src/libs/services/scrobbling/impl/listenbrainz/Utils.cpp b/src/libs/services/scrobbling/impl/listenbrainz/Utils.cpp index 3c0d47bf..230f9df1 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/Utils.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/Utils.cpp @@ -23,7 +23,7 @@ #include #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" namespace lms::scrobbling::listenBrainz::utils { diff --git a/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp b/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp index 0e3801c8..6bd75648 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp @@ -21,7 +21,7 @@ #include "core/ILogger.hpp" #include "core/UUID.hpp" -#include "database/UserId.hpp" +#include "database/objects/UserId.hpp" #define LOG(sev, message) LMS_LOG(SCROBBLING, sev, "[listenbrainz] " << message) diff --git a/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp b/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp index 9eb60100..3d0b2e09 100644 --- a/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp +++ b/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp @@ -26,16 +26,16 @@ #include #include -#include "database/ArtistId.hpp" -#include "database/Filters.hpp" -#include "database/ReleaseId.hpp" -#include "database/TrackId.hpp" #include "database/Types.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/Filters.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackId.hpp" #include "services/scrobbling/Listen.hpp" namespace lms::db { - class Db; + class IDb; } namespace lms::scrobbling @@ -126,5 +126,5 @@ namespace lms::scrobbling virtual TrackContainer getTopTracks(const FindParameters& params) = 0; }; - std::unique_ptr createScrobblingService(boost::asio::io_context& ioContext, db::Db& db); + std::unique_ptr createScrobblingService(boost::asio::io_context& ioContext, db::IDb& db); } // namespace lms::scrobbling diff --git a/src/libs/services/scrobbling/include/services/scrobbling/Listen.hpp b/src/libs/services/scrobbling/include/services/scrobbling/Listen.hpp index 7491bd04..f586f0f9 100644 --- a/src/libs/services/scrobbling/include/services/scrobbling/Listen.hpp +++ b/src/libs/services/scrobbling/include/services/scrobbling/Listen.hpp @@ -21,8 +21,8 @@ #include -#include "database/TrackId.hpp" -#include "database/UserId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/UserId.hpp" namespace lms::scrobbling { diff --git a/src/libs/services/transcoding/impl/TranscodingService.cpp b/src/libs/services/transcoding/impl/TranscodingService.cpp index 09270a19..5a14820c 100644 --- a/src/libs/services/transcoding/impl/TranscodingService.cpp +++ b/src/libs/services/transcoding/impl/TranscodingService.cpp @@ -21,9 +21,9 @@ #include "av/ITranscoder.hpp" #include "core/ILogger.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Track.hpp" #include "TranscodingResourceHandler.hpp" @@ -43,12 +43,12 @@ namespace lms::transcoding } } // namespace - std::unique_ptr createTranscodingService(db::Db& db, core::IChildProcessManager& childProcessManager) + std::unique_ptr createTranscodingService(db::IDb& db, core::IChildProcessManager& childProcessManager) { return std::make_unique(db, childProcessManager); } - TranscodingService::TranscodingService(db::Db& db, core::IChildProcessManager& childProcessManager) + TranscodingService::TranscodingService(db::IDb& db, core::IChildProcessManager& childProcessManager) : _db{ db } , _childProcessManager(childProcessManager) { diff --git a/src/libs/services/transcoding/impl/TranscodingService.hpp b/src/libs/services/transcoding/impl/TranscodingService.hpp index cfa45eaf..d5ea4471 100644 --- a/src/libs/services/transcoding/impl/TranscodingService.hpp +++ b/src/libs/services/transcoding/impl/TranscodingService.hpp @@ -26,7 +26,7 @@ namespace lms::transcoding class TranscodingService : public ITranscodingService { public: - explicit TranscodingService(db::Db& db, core::IChildProcessManager& childProcessManager); + explicit TranscodingService(db::IDb& db, core::IChildProcessManager& childProcessManager); ~TranscodingService() override; TranscodingService(const TranscodingService&) = delete; @@ -35,7 +35,7 @@ namespace lms::transcoding private: std::unique_ptr createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength) override; - db::Db& _db; + db::IDb& _db; core::IChildProcessManager& _childProcessManager; }; } // namespace lms::transcoding diff --git a/src/libs/services/transcoding/include/services/transcoding/ITranscodingService.hpp b/src/libs/services/transcoding/include/services/transcoding/ITranscodingService.hpp index c6fccd01..593a47e6 100644 --- a/src/libs/services/transcoding/include/services/transcoding/ITranscodingService.hpp +++ b/src/libs/services/transcoding/include/services/transcoding/ITranscodingService.hpp @@ -23,7 +23,7 @@ #include #include -#include "database/TrackId.hpp" +#include "database/objects/TrackId.hpp" namespace lms { @@ -35,7 +35,7 @@ namespace lms namespace db { - class Db; + class IDb; } } // namespace lms @@ -72,5 +72,5 @@ namespace lms::transcoding virtual std::unique_ptr createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength) = 0; }; - std::unique_ptr createTranscodingService(db::Db& db, core::IChildProcessManager& childProcessManager); + std::unique_ptr createTranscodingService(db::IDb& db, core::IChildProcessManager& childProcessManager); } // namespace lms::transcoding diff --git a/src/libs/subsonic/impl/CoverArtId.cpp b/src/libs/subsonic/impl/CoverArtId.cpp index fc91c008..9f184a21 100644 --- a/src/libs/subsonic/impl/CoverArtId.cpp +++ b/src/libs/subsonic/impl/CoverArtId.cpp @@ -22,7 +22,7 @@ #include #include "core/String.hpp" -#include "database/ArtworkId.hpp" +#include "database/objects/ArtworkId.hpp" namespace lms::api::subsonic { diff --git a/src/libs/subsonic/impl/CoverArtId.hpp b/src/libs/subsonic/impl/CoverArtId.hpp index 11a763e6..ec0c3ccb 100644 --- a/src/libs/subsonic/impl/CoverArtId.hpp +++ b/src/libs/subsonic/impl/CoverArtId.hpp @@ -22,7 +22,7 @@ #include #include "core/String.hpp" -#include "database/ArtworkId.hpp" +#include "database/objects/ArtworkId.hpp" namespace lms::api::subsonic { diff --git a/src/libs/subsonic/impl/SubsonicId.hpp b/src/libs/subsonic/impl/SubsonicId.hpp index f0db1e48..7dac636c 100644 --- a/src/libs/subsonic/impl/SubsonicId.hpp +++ b/src/libs/subsonic/impl/SubsonicId.hpp @@ -20,12 +20,12 @@ #pragma once #include "core/String.hpp" -#include "database/ArtistId.hpp" -#include "database/DirectoryId.hpp" -#include "database/MediaLibraryId.hpp" -#include "database/ReleaseId.hpp" -#include "database/TrackId.hpp" -#include "database/TrackListId.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/DirectoryId.hpp" +#include "database/objects/MediaLibraryId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/TrackListId.hpp" namespace lms::api::subsonic { diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index 8d5e95fe..91871541 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -29,9 +29,9 @@ #include "core/LiteralString.hpp" #include "core/Service.hpp" #include "core/String.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" #include "services/auth/IAuthTokenService.hpp" #include "services/auth/IPasswordService.hpp" @@ -52,7 +52,7 @@ namespace lms::api::subsonic { - std::unique_ptr createSubsonicResource(db::Db& db) + std::unique_ptr createSubsonicResource(db::IDb& db) { return std::make_unique(db); } @@ -295,7 +295,7 @@ namespace lms::api::subsonic } } // namespace - SubsonicResource::SubsonicResource(db::Db& db) + SubsonicResource::SubsonicResource(db::IDb& db) : _serverProtocolVersionsByClient{ readConfigProtocolVersions() } , _openSubsonicDisabledClients{ readOpenSubsonicDisabledClients() } , _supportUserPasswordAuthentication{ core::Service::get()->getBool("api-subsonic-support-user-password-auth", true) } diff --git a/src/libs/subsonic/impl/SubsonicResource.hpp b/src/libs/subsonic/impl/SubsonicResource.hpp index af8ace3d..b0d89217 100644 --- a/src/libs/subsonic/impl/SubsonicResource.hpp +++ b/src/libs/subsonic/impl/SubsonicResource.hpp @@ -25,13 +25,13 @@ #include #include -#include "database/UserId.hpp" +#include "database/objects/UserId.hpp" #include "RequestContext.hpp" namespace lms::db { - class Db; + class IDb; } namespace lms::api::subsonic @@ -39,7 +39,7 @@ namespace lms::api::subsonic class SubsonicResource final : public Wt::WResource { public: - SubsonicResource(db::Db& db); + SubsonicResource(db::IDb& db); private: void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override; @@ -53,6 +53,6 @@ namespace lms::api::subsonic const std::unordered_set _openSubsonicDisabledClients; const bool _supportUserPasswordAuthentication; - db::Db& _db; + db::IDb& _db; }; } // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/endpoints/AlbumSongLists.cpp b/src/libs/subsonic/impl/endpoints/AlbumSongLists.cpp index 179269b1..2c9871d7 100644 --- a/src/libs/subsonic/impl/endpoints/AlbumSongLists.cpp +++ b/src/libs/subsonic/impl/endpoints/AlbumSongLists.cpp @@ -22,13 +22,13 @@ #include #include "core/Service.hpp" -#include "database/Artist.hpp" -#include "database/Cluster.hpp" -#include "database/Release.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" #include "database/Types.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" diff --git a/src/libs/subsonic/impl/endpoints/Bookmarks.cpp b/src/libs/subsonic/impl/endpoints/Bookmarks.cpp index fe75e9b1..7587bed7 100644 --- a/src/libs/subsonic/impl/endpoints/Bookmarks.cpp +++ b/src/libs/subsonic/impl/endpoints/Bookmarks.cpp @@ -19,11 +19,11 @@ #include "Bookmarks.hpp" -#include "database/PlayQueue.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackBookmark.hpp" -#include "database/User.hpp" +#include "database/objects/PlayQueue.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackBookmark.hpp" +#include "database/objects/User.hpp" #include "ParameterParsing.hpp" #include "SubsonicId.hpp" diff --git a/src/libs/subsonic/impl/endpoints/Browsing.cpp b/src/libs/subsonic/impl/endpoints/Browsing.cpp index cc1b1a5e..9f783714 100644 --- a/src/libs/subsonic/impl/endpoints/Browsing.cpp +++ b/src/libs/subsonic/impl/endpoints/Browsing.cpp @@ -22,15 +22,15 @@ #include "core/ILogger.hpp" #include "core/Random.hpp" #include "core/Service.hpp" -#include "database/Artist.hpp" -#include "database/ArtistInfo.hpp" -#include "database/Cluster.hpp" -#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 "database/objects/Artist.hpp" +#include "database/objects/ArtistInfo.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/recommendation/IRecommendationService.hpp" #include "services/scrobbling/IScrobblingService.hpp" @@ -341,7 +341,7 @@ namespace lms::api::subsonic { Track::FindParameters params; params.setDirectory(directory->getId()); - params.setSortMethod(TrackSortMethod::FileName); + params.setSortMethod(TrackSortMethod::AbsoluteFilePath); Track::find(context.dbSession, params, [&](const Track::pointer& track) { directoryNode.addArrayChild("child", createSongNode(context, track, context.user)); diff --git a/src/libs/subsonic/impl/endpoints/MediaAnnotation.cpp b/src/libs/subsonic/impl/endpoints/MediaAnnotation.cpp index 55749da6..3bd79e77 100644 --- a/src/libs/subsonic/impl/endpoints/MediaAnnotation.cpp +++ b/src/libs/subsonic/impl/endpoints/MediaAnnotation.cpp @@ -23,12 +23,12 @@ #include #include "core/Service.hpp" -#include "database/ArtistId.hpp" -#include "database/Release.hpp" -#include "database/ReleaseId.hpp" #include "database/Session.hpp" -#include "database/TrackId.hpp" -#include "database/User.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" diff --git a/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp b/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp index 805a0468..8aa5c93a 100644 --- a/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp +++ b/src/libs/subsonic/impl/endpoints/MediaRetrieval.cpp @@ -27,10 +27,10 @@ #include "core/String.hpp" #include "core/Utils.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackEmbeddedImageId.hpp" -#include "database/TrackLyrics.hpp" -#include "database/User.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackEmbeddedImageId.hpp" +#include "database/objects/TrackLyrics.hpp" +#include "database/objects/User.hpp" #include "services/artwork/IArtworkService.hpp" #include "services/transcoding/ITranscodingService.hpp" @@ -207,9 +207,9 @@ namespace lms::api::subsonic auto transaction{ context.dbSession.createReadTransaction() }; db::Track::FindParameters params; - params.name = titleName; - params.artistName = artistName; - params.range = Range{ 0, 2 }; + params.setName(titleName); + params.setArtistName(artistName); + params.setRange(db::Range{ .offset = 0, .size = 2 }); // Choice: we return nothing if there are too many results const auto tracks{ db::Track::findIds(context.dbSession, params) }; diff --git a/src/libs/subsonic/impl/endpoints/Playlists.cpp b/src/libs/subsonic/impl/endpoints/Playlists.cpp index a3d2e4bb..cfa46fff 100644 --- a/src/libs/subsonic/impl/endpoints/Playlists.cpp +++ b/src/libs/subsonic/impl/endpoints/Playlists.cpp @@ -20,9 +20,9 @@ #include "Playlists.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackList.hpp" -#include "database/User.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackList.hpp" +#include "database/objects/User.hpp" #include "ParameterParsing.hpp" #include "SubsonicId.hpp" diff --git a/src/libs/subsonic/impl/endpoints/Searching.cpp b/src/libs/subsonic/impl/endpoints/Searching.cpp index 9cc979cb..f38f1519 100644 --- a/src/libs/subsonic/impl/endpoints/Searching.cpp +++ b/src/libs/subsonic/impl/endpoints/Searching.cpp @@ -24,12 +24,12 @@ #include #include "core/Random.hpp" -#include "database/Artist.hpp" -#include "database/Directory.hpp" -#include "database/Release.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "ParameterParsing.hpp" #include "SubsonicId.hpp" diff --git a/src/libs/subsonic/impl/endpoints/UserManagement.cpp b/src/libs/subsonic/impl/endpoints/UserManagement.cpp index 9f47adaf..541b2a46 100644 --- a/src/libs/subsonic/impl/endpoints/UserManagement.cpp +++ b/src/libs/subsonic/impl/endpoints/UserManagement.cpp @@ -2,7 +2,7 @@ #include "core/Service.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" #include "services/auth/IPasswordService.hpp" #include "ParameterParsing.hpp" diff --git a/src/libs/subsonic/impl/responses/Album.cpp b/src/libs/subsonic/impl/responses/Album.cpp index 914d79f1..07f71b96 100644 --- a/src/libs/subsonic/impl/responses/Album.cpp +++ b/src/libs/subsonic/impl/responses/Album.cpp @@ -22,14 +22,14 @@ #include "core/ITraceLogger.hpp" #include "core/Service.hpp" #include "core/String.hpp" -#include "database/Artist.hpp" -#include "database/Artwork.hpp" -#include "database/Cluster.hpp" -#include "database/Directory.hpp" -#include "database/Release.hpp" -#include "database/Track.hpp" #include "database/Types.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" @@ -198,6 +198,7 @@ namespace lms::api::subsonic albumNode.setAttribute("displayArtist", ""); } albumNode.addChild("originalReleaseDate", createItemDateNode(release->getOriginalDate())); + albumNode.addChild("releaseDate", createItemDateNode(release->getDate())); albumNode.setAttribute("isCompilation", release->isCompilation()); diff --git a/src/libs/subsonic/impl/responses/AlbumInfo.cpp b/src/libs/subsonic/impl/responses/AlbumInfo.cpp index 4d3a4e5a..0463609a 100644 --- a/src/libs/subsonic/impl/responses/AlbumInfo.cpp +++ b/src/libs/subsonic/impl/responses/AlbumInfo.cpp @@ -19,7 +19,7 @@ #include "AlbumInfo.hpp" -#include "database/Release.hpp" +#include "database/objects/Release.hpp" #include "RequestContext.hpp" diff --git a/src/libs/subsonic/impl/responses/Artist.cpp b/src/libs/subsonic/impl/responses/Artist.cpp index f47a76bf..53dbf7d3 100644 --- a/src/libs/subsonic/impl/responses/Artist.cpp +++ b/src/libs/subsonic/impl/responses/Artist.cpp @@ -22,11 +22,11 @@ #include "core/ITraceLogger.hpp" #include "core/Service.hpp" #include "core/String.hpp" -#include "database/Artist.hpp" -#include "database/Artwork.hpp" -#include "database/Release.hpp" -#include "database/TrackArtistLink.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "CoverArtId.hpp" diff --git a/src/libs/subsonic/impl/responses/Bookmark.cpp b/src/libs/subsonic/impl/responses/Bookmark.cpp index 063f330c..07069412 100644 --- a/src/libs/subsonic/impl/responses/Bookmark.cpp +++ b/src/libs/subsonic/impl/responses/Bookmark.cpp @@ -19,8 +19,8 @@ #include "responses/Bookmark.hpp" -#include "database/TrackBookmark.hpp" -#include "database/User.hpp" +#include "database/objects/TrackBookmark.hpp" +#include "database/objects/User.hpp" namespace lms::api::subsonic { diff --git a/src/libs/subsonic/impl/responses/Contributor.cpp b/src/libs/subsonic/impl/responses/Contributor.cpp index 952fc662..430f5d5a 100644 --- a/src/libs/subsonic/impl/responses/Contributor.cpp +++ b/src/libs/subsonic/impl/responses/Contributor.cpp @@ -20,7 +20,7 @@ #include "responses/Contributor.hpp" #include "database/Object.hpp" -#include "database/TrackArtistLink.hpp" +#include "database/objects/TrackArtistLink.hpp" #include "SubsonicResponse.hpp" #include "responses/Artist.hpp" diff --git a/src/libs/subsonic/impl/responses/Genre.cpp b/src/libs/subsonic/impl/responses/Genre.cpp index 2ae28e9f..0b0504d4 100644 --- a/src/libs/subsonic/impl/responses/Genre.cpp +++ b/src/libs/subsonic/impl/responses/Genre.cpp @@ -19,7 +19,7 @@ #include "responses/Genre.hpp" -#include "database/Cluster.hpp" +#include "database/objects/Cluster.hpp" #include "RequestContext.hpp" diff --git a/src/libs/subsonic/impl/responses/ItemDate.cpp b/src/libs/subsonic/impl/responses/ItemDate.cpp index c5c92c99..98441b15 100644 --- a/src/libs/subsonic/impl/responses/ItemDate.cpp +++ b/src/libs/subsonic/impl/responses/ItemDate.cpp @@ -19,7 +19,7 @@ #include "responses/Genre.hpp" -#include "database/Cluster.hpp" +#include "database/objects/Cluster.hpp" namespace lms::api::subsonic { diff --git a/src/libs/subsonic/impl/responses/ItemGenre.cpp b/src/libs/subsonic/impl/responses/ItemGenre.cpp index db4dea6c..0850ffdb 100644 --- a/src/libs/subsonic/impl/responses/ItemGenre.cpp +++ b/src/libs/subsonic/impl/responses/ItemGenre.cpp @@ -19,7 +19,7 @@ #include "responses/ItemGenre.hpp" -#include "database/Cluster.hpp" +#include "database/objects/Cluster.hpp" namespace lms::api::subsonic { diff --git a/src/libs/subsonic/impl/responses/Lyrics.cpp b/src/libs/subsonic/impl/responses/Lyrics.cpp index 67b91454..2273d046 100644 --- a/src/libs/subsonic/impl/responses/Lyrics.cpp +++ b/src/libs/subsonic/impl/responses/Lyrics.cpp @@ -19,7 +19,7 @@ #include "responses/Lyrics.hpp" -#include "database/TrackLyrics.hpp" +#include "database/objects/TrackLyrics.hpp" #include "RequestContext.hpp" diff --git a/src/libs/subsonic/impl/responses/Playlist.cpp b/src/libs/subsonic/impl/responses/Playlist.cpp index d053c14a..e3a9befb 100644 --- a/src/libs/subsonic/impl/responses/Playlist.cpp +++ b/src/libs/subsonic/impl/responses/Playlist.cpp @@ -21,9 +21,9 @@ #include "core/Service.hpp" #include "core/String.hpp" -#include "database/Artwork.hpp" -#include "database/TrackList.hpp" -#include "database/User.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/TrackList.hpp" +#include "database/objects/User.hpp" #include "services/artwork/IArtworkService.hpp" #include "CoverArtId.hpp" diff --git a/src/libs/subsonic/impl/responses/RecordLabel.cpp b/src/libs/subsonic/impl/responses/RecordLabel.cpp index 749a3443..81f813cd 100644 --- a/src/libs/subsonic/impl/responses/RecordLabel.cpp +++ b/src/libs/subsonic/impl/responses/RecordLabel.cpp @@ -19,7 +19,7 @@ #include "responses/RecordLabel.hpp" -#include "database/Release.hpp" +#include "database/objects/Release.hpp" namespace lms::api::subsonic { diff --git a/src/libs/subsonic/impl/responses/ReplayGain.cpp b/src/libs/subsonic/impl/responses/ReplayGain.cpp index 5fd9d821..a2fd1dd9 100644 --- a/src/libs/subsonic/impl/responses/ReplayGain.cpp +++ b/src/libs/subsonic/impl/responses/ReplayGain.cpp @@ -19,7 +19,7 @@ #include "responses/ReplayGain.hpp" -#include "database/Track.hpp" +#include "database/objects/Track.hpp" namespace lms::api::subsonic { diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index 7e762666..5462bb9c 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -19,21 +19,24 @@ #include "responses/Song.hpp" +#include #include +#include #include "core/ITraceLogger.hpp" #include "core/MimeTypes.hpp" #include "core/Service.hpp" #include "core/String.hpp" -#include "database/Artist.hpp" -#include "database/Artwork.hpp" -#include "database/Cluster.hpp" -#include "database/Directory.hpp" -#include "database/Release.hpp" -#include "database/Track.hpp" -#include "database/TrackArtistLink.hpp" #include "database/Types.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Artwork.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Directory.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" @@ -93,7 +96,16 @@ namespace lms::api::subsonic if (track->getYear()) trackResponse.setAttribute("year", *track->getYear()); trackResponse.setAttribute("playCount", core::Service::get()->getCount(context.user->getId(), track->getId())); - trackResponse.setAttribute("path", track->getRelativeFilePath().string()); + + // maybe not available if user just removed the library without rescanning + if (const db::MediaLibrary::pointer library{ track->getMediaLibrary() }) + { + std::error_code ec; + const std::filesystem::path relativeTrackPath{ std::filesystem::relative(track->getAbsoluteFilePath(), library->getPath(), ec) }; + if (!ec && !relativeTrackPath.empty()) + trackResponse.setAttribute("path", relativeTrackPath.c_str()); + } + trackResponse.setAttribute("size", track->getFileSize()); if (track->getAbsoluteFilePath().has_extension()) diff --git a/src/libs/subsonic/impl/responses/User.cpp b/src/libs/subsonic/impl/responses/User.cpp index 885d2189..8859b2c1 100644 --- a/src/libs/subsonic/impl/responses/User.cpp +++ b/src/libs/subsonic/impl/responses/User.cpp @@ -19,8 +19,8 @@ #include "responses/User.hpp" -#include "database/MediaLibrary.hpp" -#include "database/User.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/User.hpp" #include "RequestContext.hpp" #include "SubsonicId.hpp" diff --git a/src/libs/subsonic/include/subsonic/SubsonicResource.hpp b/src/libs/subsonic/include/subsonic/SubsonicResource.hpp index 2c52bac4..d719c3c5 100644 --- a/src/libs/subsonic/include/subsonic/SubsonicResource.hpp +++ b/src/libs/subsonic/include/subsonic/SubsonicResource.hpp @@ -24,10 +24,10 @@ namespace lms::db { - class Db; + class IDb; } namespace lms::api::subsonic { - std::unique_ptr createSubsonicResource(db::Db& db); + std::unique_ptr createSubsonicResource(db::IDb& db); } // namespace lms::api::subsonic diff --git a/src/lms/CMakeLists.txt b/src/lms/CMakeLists.txt index 9db7d25a..2adef2e1 100644 --- a/src/lms/CMakeLists.txt +++ b/src/lms/CMakeLists.txt @@ -14,10 +14,12 @@ add_executable(lms ui/State.cpp ui/Tooltip.cpp ui/Utils.cpp + ui/admin/debug/Database.cpp + ui/admin/debug/Tracing.cpp + ui/admin/DebugToolsView.cpp ui/admin/InitWizardView.cpp ui/admin/MediaLibrariesView.cpp ui/admin/MediaLibraryModal.cpp - ui/admin/TracingView.cpp ui/admin/ScannerController.cpp ui/admin/ScannerReportResource.cpp ui/admin/ScanSettingsView.cpp diff --git a/src/lms/main.cpp b/src/lms/main.cpp index 0a96921e..2c1440d7 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -32,7 +32,8 @@ #include "core/String.hpp" #include "core/SystemPaths.hpp" #include "core/WtLogger.hpp" -#include "database/Db.hpp" +#include "database/IDb.hpp" +#include "database/IQueryPlanRecorder.hpp" #include "database/Session.hpp" #include "image/Image.hpp" #include "services/artwork/IArtworkService.hpp" @@ -315,10 +316,14 @@ namespace lms core::IOContextRunner ioContextRunner{ ioContext, getThreadCount(), "Misc" }; + core::Service queryPlanRecorder; + if (config->getBool("db-record-query-plans", false)) + queryPlanRecorder.assign(db::createQueryPlanRecorder()); + // Connection pool size must be twice the number of threads: we have at least 2 io pools with getThreadCount() each and they all may access the database - db::Db database{ config->getPath("working-dir", "/var/lms") / "lms.db", getThreadCount() * 2 }; + auto database{ db::createDb(config->getPath("working-dir", "/var/lms") / "lms.db", getThreadCount() * 2) }; { - db::Session session{ database }; + db::Session session{ *database }; session.prepareTablesIfNeeded(); bool migrationPerformed{ session.migrateSchemaIfNeeded() }; session.createIndexesIfNeeded(); @@ -328,11 +333,6 @@ namespace lms session.vacuum(); else session.vacuumIfNeeded(); - - // force optimize in case scanner aborted during a large import: - // queries may be too slow to even be able to relaunch a scan using the web interface - session.fullAnalyze(); - database.getTLSSession().refreshTracingLoggerStats(); } ui::LmsApplicationManager appManager; @@ -342,7 +342,7 @@ namespace lms core::Service childProcessManagerService{ core::createChildProcessManager(ioContext) }; const ui::AuthenticationBackend uiAuthenticationBackend{ getUIAuthenticationBackend() }; - core::Service authTokenService{ auth::createAuthTokenService(database, config->getULong("login-throttler-max-entriees", 10'000)) }; + core::Service authTokenService{ auth::createAuthTokenService(*database, config->getULong("login-throttler-max-entriees", 10'000)) }; core::Service authPasswordService; core::Service authEnvService; @@ -359,32 +359,31 @@ namespace lms switch (uiAuthenticationBackend) { case ui::AuthenticationBackend::Internal: - authPasswordService.assign(auth::createPasswordService("internal", database, loginThrottlerMaxEntries)); + authPasswordService.assign(auth::createPasswordService("internal", *database, loginThrottlerMaxEntries)); break; case ui::AuthenticationBackend::PAM: - authPasswordService.assign(auth::createPasswordService("PAM", database, loginThrottlerMaxEntries)); + authPasswordService.assign(auth::createPasswordService("PAM", *database, loginThrottlerMaxEntries)); break; case ui::AuthenticationBackend::Env: - authEnvService.assign(auth::createEnvService("http-headers", database)); + authEnvService.assign(auth::createEnvService("http-headers", *database)); break; } image::init(argv[0]); - core::Service artworkService{ artwork::createArtworkService(database, server.appRoot() + "/images/unknown-cover.svg", server.appRoot() + "/images/unknown-artist.svg") }; - core::Service recommendationService{ recommendation::createRecommendationService(database) }; - core::Service playlistGeneratorService{ recommendation::createPlaylistGeneratorService(database, *recommendationService.get()) }; - core::Service scannerService{ scanner::createScannerService(database) }; - core::Service transcodingService{ transcoding::createTranscodingService(database, *childProcessManagerService.get()) }; + core::Service artworkService{ artwork::createArtworkService(*database, server.appRoot() + "/images/unknown-cover.svg", server.appRoot() + "/images/unknown-artist.svg") }; + core::Service recommendationService{ recommendation::createRecommendationService(*database) }; + core::Service playlistGeneratorService{ recommendation::createPlaylistGeneratorService(*database, *recommendationService) }; + core::Service scannerService{ scanner::createScannerService(*database) }; + core::Service transcodingService{ transcoding::createTranscodingService(*database, *childProcessManagerService) }; scannerService->getEvents().scanComplete.connect([&] { // Flush cover cache even if no changes: // covers may be external files that changed and we don't keep track of them for now (but we should) artworkService->flushCache(); - database.getTLSSession().refreshTracingLoggerStats(); }); - core::Service feedbackService{ feedback::createFeedbackService(ioContext, database) }; - core::Service scrobblingService{ scrobbling::createScrobblingService(ioContext, database) }; + core::Service feedbackService{ feedback::createFeedbackService(ioContext, *database) }; + core::Service scrobblingService{ scrobbling::createScrobblingService(ioContext, *database) }; LMS_LOG(MAIN, INFO, "Stopping init web server..."); server.stop(); @@ -395,14 +394,14 @@ namespace lms // bind API resources if (config->getBool("api-subsonic", true)) { - subsonicResource = api::subsonic::createSubsonicResource(database); + subsonicResource = api::subsonic::createSubsonicResource(*database); server.addResource(subsonicResource.get(), "/rest"); } // bind UI entry point server.addEntryPoint(Wt::EntryPointType::Application, [&database, &appManager, uiAuthenticationBackend](const Wt::WEnvironment& env) { - return ui::LmsApplication::create(env, database, appManager, uiAuthenticationBackend); + return ui::LmsApplication::create(env, *database, appManager, uiAuthenticationBackend); }); proxyScannerEventsToApplication(*scannerService, server); diff --git a/src/lms/ui/Auth.cpp b/src/lms/ui/Auth.cpp index 1ffac05b..20a2f297 100644 --- a/src/lms/ui/Auth.cpp +++ b/src/lms/ui/Auth.cpp @@ -30,7 +30,7 @@ #include "core/Service.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" #include "services/auth/IAuthTokenService.hpp" #include "services/auth/IPasswordService.hpp" diff --git a/src/lms/ui/Auth.hpp b/src/lms/ui/Auth.hpp index 9763d7fe..a0496d78 100644 --- a/src/lms/ui/Auth.hpp +++ b/src/lms/ui/Auth.hpp @@ -21,7 +21,7 @@ #include -#include "database/UserId.hpp" +#include "database/objects/UserId.hpp" namespace lms::auth { diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp index 01ecca92..a1e3aa10 100644 --- a/src/lms/ui/LmsApplication.cpp +++ b/src/lms/ui/LmsApplication.cpp @@ -29,13 +29,14 @@ #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" #include "core/Service.hpp" -#include "database/Artist.hpp" -#include "database/Cluster.hpp" -#include "database/Db.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" +#include "database/IQueryPlanRecorder.hpp" #include "database/Session.hpp" -#include "database/TrackList.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/TrackList.hpp" +#include "database/objects/User.hpp" #include "services/artwork/IArtworkService.hpp" #include "services/auth/IAuthTokenService.hpp" #include "services/auth/IEnvService.hpp" @@ -51,11 +52,11 @@ #include "NotificationContainer.hpp" #include "PlayQueue.hpp" #include "SettingsView.hpp" +#include "admin/DebugToolsView.hpp" #include "admin/InitWizardView.hpp" #include "admin/MediaLibrariesView.hpp" #include "admin/ScanSettingsView.hpp" #include "admin/ScannerController.hpp" -#include "admin/TracingView.hpp" #include "admin/UserView.hpp" #include "admin/UsersView.hpp" #include "common/Template.hpp" @@ -75,6 +76,8 @@ namespace lms::ui const std::string appRoot{ Wt::WApplication::appRoot() }; auto res{ std::make_shared() }; + res->use(appRoot + "admin-db"); + res->use(appRoot + "admin-debugtools"); res->use(appRoot + "admin-initwizard"); res->use(appRoot + "admin-medialibraries"); res->use(appRoot + "admin-medialibrary"); @@ -132,7 +135,7 @@ namespace lms::ui IdxAdminScanner, IdxAdminUsers, IdxAdminUser, - IdxAdminTracing, + IdxAdminDebugTools, }; void handlePathChange(Wt::WStackedWidget& stack, bool isAdmin) @@ -158,7 +161,7 @@ namespace lms::ui { "/admin/scanner", IdxAdminScanner, true, Wt::WString::tr("Lms.Admin.ScannerController.scanner") }, { "/admin/users", IdxAdminUsers, true, Wt::WString::tr("Lms.Admin.Users.users") }, { "/admin/user", IdxAdminUser, true, std::nullopt }, - { "/admin/tracing", IdxAdminTracing, true, Wt::WString::tr("Lms.Admin.Tracing.tracing") }, + { "/admin/debug-tools", IdxAdminDebugTools, true, Wt::WString::tr("Lms.Admin.DebugTools.debug-tools") }, }; LMS_LOG(UI, DEBUG, "Internal path changed to '" << wApp->internalPath() << "'"); @@ -183,7 +186,7 @@ namespace lms::ui } } // namespace - std::unique_ptr LmsApplication::create(const Wt::WEnvironment& env, db::Db& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend) + std::unique_ptr LmsApplication::create(const Wt::WEnvironment& env, db::IDb& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend) { return std::make_unique(env, db, appManager, authBackend); } @@ -193,7 +196,7 @@ namespace lms::ui return static_cast(Wt::WApplication::instance()); } - db::Db& LmsApplication::getDb() + db::IDb& LmsApplication::getDb() { return _db; } @@ -235,7 +238,7 @@ namespace lms::ui return _user->userLoginName; } - LmsApplication::LmsApplication(const Wt::WEnvironment& env, db::Db& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend) + LmsApplication::LmsApplication(const Wt::WEnvironment& env, db::IDb& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend) : Wt::WApplication{ env } , _db{ db } , _appManager{ appManager } @@ -461,11 +464,13 @@ namespace lms::ui navbar->bindNew("scan-settings", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/scan-settings" }, Wt::WString::tr("Lms.Admin.menu-scan-settings")); navbar->bindNew("scanner", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/scanner" }, Wt::WString::tr("Lms.Admin.menu-scanner")); navbar->bindNew("users", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/users" }, Wt::WString::tr("Lms.Admin.menu-users")); - // Hide the entry if the trace logger is not enabled - if (core::Service::get()) - navbar->bindNew("tracing", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/tracing" }, Wt::WString::tr("Lms.Admin.menu-tracing")); - else - navbar->bindEmpty("tracing"); + // Hide the entry if no debug service is enabled + if (core::Service::get() + || core::Service::get()) + { + navbar->setCondition("if-debug-tools", true); + navbar->bindNew("debug-tools", Wt::WLink{ Wt::LinkType::InternalPath, "/admin/debug-tools" }, Wt::WString::tr("Lms.Admin.menu-debug-tools")); + } } // Contents @@ -486,7 +491,7 @@ namespace lms::ui mainStack->addNew(); mainStack->addNew(); mainStack->addNew(); - mainStack->addNew(); + mainStack->addNew(); } explore->getPlayQueueController().setMaxTrackCountToEnqueue(_playQueue->getCapacity()); @@ -537,7 +542,7 @@ namespace lms::ui notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.Database.database"), Wt::WString::tr("Lms.Admin.Database.scan-complete") - .arg(static_cast(stats.nbFiles())) + .arg(static_cast(stats.getTotalFileCount())) .arg(static_cast(stats.additions)) .arg(static_cast(stats.updates)) .arg(static_cast(stats.deletions)) diff --git a/src/lms/ui/LmsApplication.hpp b/src/lms/ui/LmsApplication.hpp index 8f943561..f5a54586 100644 --- a/src/lms/ui/LmsApplication.hpp +++ b/src/lms/ui/LmsApplication.hpp @@ -27,7 +27,7 @@ #include "database/Object.hpp" #include "database/Types.hpp" -#include "database/UserId.hpp" +#include "database/objects/UserId.hpp" #include "services/scanner/ScannerEvents.hpp" #include "Auth.hpp" @@ -35,7 +35,7 @@ namespace lms::db { - class Db; + class IDb; class Session; class User; } // namespace lms::db @@ -53,15 +53,15 @@ namespace lms::ui class LmsApplication : public Wt::WApplication { public: - LmsApplication(const Wt::WEnvironment& env, db::Db& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend); + LmsApplication(const Wt::WEnvironment& env, db::IDb& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend); ~LmsApplication(); - static std::unique_ptr create(const Wt::WEnvironment& env, db::Db& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend); + static std::unique_ptr create(const Wt::WEnvironment& env, db::IDb& db, LmsApplicationManager& appManager, AuthenticationBackend authBackend); static LmsApplication* instance(); // Session application data std::shared_ptr getArtworkResource() { return _artworkResource; } - db::Db& getDb(); + db::IDb& getDb(); db::Session& getDbSession(); // always thread safe db::ObjectPtr getUser(); @@ -105,7 +105,7 @@ namespace lms::ui void setUserInfo(db::UserId userId, bool strongAuth); void createHome(); - db::Db& _db; + db::IDb& _db; Wt::Signal<> _preQuit; LmsApplicationManager& _appManager; const AuthenticationBackend _authBackend; diff --git a/src/lms/ui/LmsApplicationManager.hpp b/src/lms/ui/LmsApplicationManager.hpp index 92c14189..dfe40a3d 100644 --- a/src/lms/ui/LmsApplicationManager.hpp +++ b/src/lms/ui/LmsApplicationManager.hpp @@ -25,7 +25,7 @@ #include -#include "database/UserId.hpp" +#include "database/objects/UserId.hpp" namespace lms::ui { diff --git a/src/lms/ui/MediaPlayer.cpp b/src/lms/ui/MediaPlayer.cpp index 58a29e14..f423ebf7 100644 --- a/src/lms/ui/MediaPlayer.cpp +++ b/src/lms/ui/MediaPlayer.cpp @@ -20,6 +20,7 @@ #include "MediaPlayer.hpp" #include +#include #include #include #include @@ -27,13 +28,13 @@ #include "core/ILogger.hpp" #include "core/String.hpp" #include "core/Utils.hpp" -#include "database/Artist.hpp" -#include "database/Release.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackList.hpp" #include "database/Types.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackList.hpp" +#include "database/objects/User.hpp" #include "LmsApplication.hpp" #include "Utils.hpp" @@ -258,15 +259,17 @@ namespace lms::ui artworkId = track->getPreferredArtworkId(); if (artworkId.isValid()) { + // Potential issue: If the image is really not here, we fall back on a default svg image: the provided type does not match, so we don't put type here + // Another solution would be to test the image presence and put type accordingly oss << " artwork: [" - << " { src: \"" << LmsApp->getArtworkResource()->getArtworkUrl(artworkId, ArtworkResource::Size::Small) << "\", sizes: \"128x128\", type: \"image/jpeg\" }," - << " { src: \"" << LmsApp->getArtworkResource()->getArtworkUrl(artworkId, ArtworkResource::Size::Large) << "\", sizes: \"512x512\", type: \"image/jpeg\" }," + << " { src: \"" << LmsApp->getArtworkResource()->getArtworkUrl(artworkId, ArtworkResource::DefaultArtworkType::Track, ArtworkResource::Size::Small) << "\", sizes: \"128x128\" }," + << " { src: \"" << LmsApp->getArtworkResource()->getArtworkUrl(artworkId, ArtworkResource::DefaultArtworkType::Track, ArtworkResource::Size::Large) << "\", sizes: \"512x512\" }," << " ]"; } else { oss << " artwork: [" - << " { src: \"" << LmsApp->getArtworkResource()->getDefaultTrackArtworkUrl() << "\", type: \"image/svg+xml\" }," + << " { src: \"" << LmsApp->getArtworkResource()->getDefaultArtworkUrl(ArtworkResource::DefaultArtworkType::Track) << "\", type: \"image/svg+xml\" }," << " ]"; } oss << "};"; diff --git a/src/lms/ui/MediaPlayer.hpp b/src/lms/ui/MediaPlayer.hpp index 0bdc41e7..ea5472ac 100644 --- a/src/lms/ui/MediaPlayer.hpp +++ b/src/lms/ui/MediaPlayer.hpp @@ -27,8 +27,8 @@ #include #include -#include "database/TrackId.hpp" #include "database/Types.hpp" +#include "database/objects/TrackId.hpp" namespace lms::ui { diff --git a/src/lms/ui/PlayQueue.cpp b/src/lms/ui/PlayQueue.cpp index b7835fe1..f152b434 100644 --- a/src/lms/ui/PlayQueue.cpp +++ b/src/lms/ui/PlayQueue.cpp @@ -32,12 +32,12 @@ #include "core/ILogger.hpp" #include "core/Random.hpp" #include "core/Service.hpp" -#include "database/Artist.hpp" -#include "database/Release.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackList.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackList.hpp" +#include "database/objects/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/recommendation/IPlaylistGeneratorService.hpp" @@ -496,9 +496,9 @@ namespace lms::ui std::unique_ptr image; if (artworkId.isValid()) - image = utils::createArtworkImage(artworkId, ArtworkResource::Size::Small); + image = utils::createArtworkImage(artworkId, ArtworkResource::DefaultArtworkType::Track, ArtworkResource::Size::Small); else - image = utils::createDefaultTrackArtworkImage(); + image = utils::createDefaultArtworkImage(ArtworkResource::DefaultArtworkType::Track); image->addStyleClass("Lms-cover-track rounded"); // HACK diff --git a/src/lms/ui/PlayQueue.hpp b/src/lms/ui/PlayQueue.hpp index a6debe27..01186781 100644 --- a/src/lms/ui/PlayQueue.hpp +++ b/src/lms/ui/PlayQueue.hpp @@ -27,8 +27,8 @@ #include #include "database/Object.hpp" -#include "database/TrackId.hpp" -#include "database/TrackListId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/TrackListId.hpp" #include "common/Template.hpp" diff --git a/src/lms/ui/SettingsView.cpp b/src/lms/ui/SettingsView.cpp index 6507d5aa..6849717b 100644 --- a/src/lms/ui/SettingsView.cpp +++ b/src/lms/ui/SettingsView.cpp @@ -34,7 +34,7 @@ #include "core/String.hpp" #include "database/Session.hpp" #include "database/Types.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" #include "services/auth/IAuthTokenService.hpp" #include "services/auth/IPasswordService.hpp" diff --git a/src/lms/ui/State.cpp b/src/lms/ui/State.cpp index 46d0790a..52f6c7e1 100644 --- a/src/lms/ui/State.cpp +++ b/src/lms/ui/State.cpp @@ -20,8 +20,8 @@ #include "State.hpp" #include "database/Session.hpp" -#include "database/UIState.hpp" -#include "database/User.hpp" +#include "database/objects/UIState.hpp" +#include "database/objects/User.hpp" #include "LmsApplication.hpp" diff --git a/src/lms/ui/Utils.cpp b/src/lms/ui/Utils.cpp index ba11e50c..ac813775 100644 --- a/src/lms/ui/Utils.cpp +++ b/src/lms/ui/Utils.cpp @@ -25,14 +25,14 @@ #include #include -#include "database/Artist.hpp" -#include "database/Cluster.hpp" -#include "database/Image.hpp" -#include "database/Release.hpp" -#include "database/ScanSettings.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackList.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Image.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/ScanSettings.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackList.hpp" #include "LmsApplication.hpp" #include "ModalManager.hpp" @@ -88,31 +88,17 @@ namespace lms::ui::utils LmsApp->getModalManager().show(std::move(rawImage)); } - std::unique_ptr createArtworkImage(db::ArtworkId artworkId, ArtworkResource::Size size) + std::unique_ptr createArtworkImage(db::ArtworkId artworkId, ArtworkResource::DefaultArtworkType type, ArtworkResource::Size size) { auto image{ createArtworkImage() }; - image->setImageLink(LmsApp->getArtworkResource()->getArtworkUrl(artworkId, size)); + image->setImageLink(LmsApp->getArtworkResource()->getArtworkUrl(artworkId, type, size)); return image; } - std::unique_ptr createDefaultArtistArtworkImage() + std::unique_ptr createDefaultArtworkImage(ArtworkResource::DefaultArtworkType type) { auto image{ createArtworkImage() }; - image->setImageLink(LmsApp->getArtworkResource()->getDefaultArtistArtworkUrl()); - return image; - } - - std::unique_ptr createDefaultReleaseArtworkImage() - { - auto image{ createArtworkImage() }; - image->setImageLink(LmsApp->getArtworkResource()->getDefaultReleaseArtworkUrl()); - return image; - } - - std::unique_ptr createDefaultTrackArtworkImage() - { - auto image{ createArtworkImage() }; - image->setImageLink(LmsApp->getArtworkResource()->getDefaultTrackArtworkUrl()); + image->setImageLink(LmsApp->getArtworkResource()->getDefaultArtworkUrl(type)); return image; } diff --git a/src/lms/ui/Utils.hpp b/src/lms/ui/Utils.hpp index f2cdc20a..0c849fde 100644 --- a/src/lms/ui/Utils.hpp +++ b/src/lms/ui/Utils.hpp @@ -28,11 +28,11 @@ #include #include -#include "database/ArtistId.hpp" -#include "database/ClusterId.hpp" #include "database/Object.hpp" -#include "database/ReleaseId.hpp" -#include "database/TrackId.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/ClusterId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackId.hpp" #include "database/Types.hpp" #include "resource/ArtworkResource.hpp" @@ -57,10 +57,8 @@ namespace lms::ui::utils void showArtworkModal(Wt::WLink image); - std::unique_ptr createArtworkImage(db::ArtworkId artworkId, ArtworkResource::Size size); - std::unique_ptr createDefaultArtistArtworkImage(); - std::unique_ptr createDefaultReleaseArtworkImage(); - std::unique_ptr createDefaultTrackArtworkImage(); + std::unique_ptr createArtworkImage(db::ArtworkId artworkId, ArtworkResource::DefaultArtworkType type, ArtworkResource::Size size); + std::unique_ptr createDefaultArtworkImage(ArtworkResource::DefaultArtworkType type); std::unique_ptr createFilter(const Wt::WString& name, const Wt::WString& tooltip, std::string_view colorStyleClass, bool canDelete = false); std::unique_ptr createFilterCluster(db::ClusterId clusterId, bool canDelete = false); diff --git a/src/lms/ui/admin/DebugToolsView.cpp b/src/lms/ui/admin/DebugToolsView.cpp new file mode 100644 index 00000000..570c97fa --- /dev/null +++ b/src/lms/ui/admin/DebugToolsView.cpp @@ -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 . + */ + +#include "DebugToolsView.hpp" + +#include "admin/debug/Database.hpp" +#include "debug/Database.hpp" +#include "debug/Tracing.hpp" + +namespace lms::ui +{ + DebugToolsView::DebugToolsView() + : Wt::WTemplate{ Wt::WString::tr("Lms.Admin.DebugTools.template") } + { + addFunction("tr", &Wt::WTemplate::Functions::tr); + + bindNew("tracing"); + bindNew("db"); + } +} // namespace lms::ui diff --git a/src/lms/ui/admin/DebugToolsView.hpp b/src/lms/ui/admin/DebugToolsView.hpp new file mode 100644 index 00000000..152f4fd5 --- /dev/null +++ b/src/lms/ui/admin/DebugToolsView.hpp @@ -0,0 +1,31 @@ +/* + * 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 + +namespace lms::ui +{ + class DebugToolsView : public Wt::WTemplate + { + public: + DebugToolsView(); + }; +} // namespace lms::ui diff --git a/src/lms/ui/admin/InitWizardView.cpp b/src/lms/ui/admin/InitWizardView.cpp index 3cec9844..0e2838ab 100644 --- a/src/lms/ui/admin/InitWizardView.cpp +++ b/src/lms/ui/admin/InitWizardView.cpp @@ -28,7 +28,7 @@ #include "core/ILogger.hpp" #include "core/Service.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" #include "services/auth/IPasswordService.hpp" #include "LmsApplication.hpp" diff --git a/src/lms/ui/admin/MediaLibrariesView.cpp b/src/lms/ui/admin/MediaLibrariesView.cpp index 57731d91..344d0aa1 100644 --- a/src/lms/ui/admin/MediaLibrariesView.cpp +++ b/src/lms/ui/admin/MediaLibrariesView.cpp @@ -22,8 +22,8 @@ #include #include "core/Service.hpp" -#include "database/MediaLibrary.hpp" #include "database/Session.hpp" +#include "database/objects/MediaLibrary.hpp" #include "services/scanner/IScannerService.hpp" #include "LmsApplication.hpp" diff --git a/src/lms/ui/admin/MediaLibrariesView.hpp b/src/lms/ui/admin/MediaLibrariesView.hpp index 8ba277e7..f201c0cf 100644 --- a/src/lms/ui/admin/MediaLibrariesView.hpp +++ b/src/lms/ui/admin/MediaLibrariesView.hpp @@ -22,7 +22,7 @@ #include #include -#include "database/MediaLibraryId.hpp" +#include "database/objects/MediaLibraryId.hpp" namespace lms::ui { diff --git a/src/lms/ui/admin/MediaLibraryModal.cpp b/src/lms/ui/admin/MediaLibraryModal.cpp index fb9a20ba..b5b3ef3b 100644 --- a/src/lms/ui/admin/MediaLibraryModal.cpp +++ b/src/lms/ui/admin/MediaLibraryModal.cpp @@ -26,8 +26,8 @@ #include "core/Path.hpp" #include "core/String.hpp" -#include "database/MediaLibrary.hpp" #include "database/Session.hpp" +#include "database/objects/MediaLibrary.hpp" #include "LmsApplication.hpp" diff --git a/src/lms/ui/admin/MediaLibraryModal.hpp b/src/lms/ui/admin/MediaLibraryModal.hpp index 03f20689..137b5a5c 100644 --- a/src/lms/ui/admin/MediaLibraryModal.hpp +++ b/src/lms/ui/admin/MediaLibraryModal.hpp @@ -22,7 +22,7 @@ #include #include -#include "database/MediaLibraryId.hpp" +#include "database/objects/MediaLibraryId.hpp" namespace lms::ui { diff --git a/src/lms/ui/admin/ScanSettingsView.cpp b/src/lms/ui/admin/ScanSettingsView.cpp index 807d7c17..0ec0cacd 100644 --- a/src/lms/ui/admin/ScanSettingsView.cpp +++ b/src/lms/ui/admin/ScanSettingsView.cpp @@ -30,8 +30,8 @@ #include "core/Service.hpp" #include "core/String.hpp" -#include "database/ScanSettings.hpp" #include "database/Session.hpp" +#include "database/objects/ScanSettings.hpp" #include "services/recommendation/IRecommendationService.hpp" #include "services/scanner/IScannerService.hpp" diff --git a/src/lms/ui/admin/ScannerController.cpp b/src/lms/ui/admin/ScannerController.cpp index 2f3e196e..fd45bb0b 100644 --- a/src/lms/ui/admin/ScannerController.cpp +++ b/src/lms/ui/admin/ScannerController.cpp @@ -115,7 +115,7 @@ namespace lms::ui if (status.lastCompleteScanStats) { _lastScanStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.last-scan-status") - .arg(status.lastCompleteScanStats->nbFiles()) + .arg(status.lastCompleteScanStats->getTotalFileCount()) .arg(durationToString(status.lastCompleteScanStats->startTime, status.lastCompleteScanStats->stopTime)) .arg(status.lastCompleteScanStats->stopTime.date().toString(Wt::WLocale::currentLocale().dateFormat())) .arg(status.lastCompleteScanStats->stopTime.time().toString(Wt::WLocale::currentLocale().timeFormat())) @@ -212,11 +212,6 @@ namespace lms::ui .arg(stepStats.progress())); break; - case ScanStep::DiscoverFiles: - _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-discovering-files") - .arg(stepStats.processedElems)); - break; - case ScanStep::FetchTrackFeatures: _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-fetching-track-features") .arg(stepStats.processedElems) @@ -246,9 +241,7 @@ namespace lms::ui case ScanStep::ScanFiles: _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-scanning-files") - .arg(stepStats.processedElems) - .arg(stepStats.totalElems) - .arg(stepStats.progress())); + .arg(stepStats.processedElems)); break; case ScanStep::UpdateLibraryFields: diff --git a/src/lms/ui/admin/ScannerReportResource.cpp b/src/lms/ui/admin/ScannerReportResource.cpp index e430757f..5279d1a8 100644 --- a/src/lms/ui/admin/ScannerReportResource.cpp +++ b/src/lms/ui/admin/ScannerReportResource.cpp @@ -24,7 +24,7 @@ #include "core/String.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Track.hpp" #include "LmsApplication.hpp" #include "services/scanner/ScanErrors.hpp" diff --git a/src/lms/ui/admin/UserView.cpp b/src/lms/ui/admin/UserView.cpp index 2f6869d8..206c30ae 100644 --- a/src/lms/ui/admin/UserView.cpp +++ b/src/lms/ui/admin/UserView.cpp @@ -31,7 +31,7 @@ #include "core/Service.hpp" #include "core/String.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" #include "services/auth/IAuthTokenService.hpp" #include "services/auth/IPasswordService.hpp" diff --git a/src/lms/ui/admin/UsersView.cpp b/src/lms/ui/admin/UsersView.cpp index 65d08173..39239e59 100644 --- a/src/lms/ui/admin/UsersView.cpp +++ b/src/lms/ui/admin/UsersView.cpp @@ -26,7 +26,7 @@ #include "core/ILogger.hpp" #include "core/Service.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/User.hpp" #include "services/auth/IPasswordService.hpp" #include "LmsApplication.hpp" diff --git a/src/lms/ui/admin/debug/Database.cpp b/src/lms/ui/admin/debug/Database.cpp new file mode 100644 index 00000000..811afb58 --- /dev/null +++ b/src/lms/ui/admin/debug/Database.cpp @@ -0,0 +1,91 @@ +/* + * 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.hpp" + +#include +#include +#include +#include +#include + +#include "core/ITraceLogger.hpp" +#include "core/String.hpp" +#include "database/IQueryPlanRecorder.hpp" + +namespace lms::ui +{ + namespace + { + class QueryPlansReportResource : public Wt::WResource + { + public: + QueryPlansReportResource(const db::IQueryPlanRecorder& recorder) + : _recorder{ recorder } + { + } + + ~QueryPlansReportResource() + { + beingDeleted(); + } + QueryPlansReportResource(const QueryPlansReportResource&) = delete; + QueryPlansReportResource& operator=(const QueryPlansReportResource&) = delete; + + private: + void handleRequest(const Wt::Http::Request&, Wt::Http::Response& response) + { + response.setMimeType("application/text"); + + auto encodeHttpHeaderField = [](const std::string& fieldName, const std::string& fieldValue) { + // This implements RFC 5987 + return fieldName + "*=UTF-8''" + Wt::Utils::urlEncode(fieldValue); + }; + + const std::string cdp{ encodeHttpHeaderField("filename", "LMS_db_query_plans_" + core::stringUtils::toISO8601String(Wt::WDateTime::currentDateTime()) + ".txt") }; + response.addHeader("Content-Disposition", "attachment; " + cdp); + + _recorder.visitQueryPlans([&](std::string_view query, std::string_view plan) { + response.out() << query << '\n'; + response.out() << plan << "\n-------------------------\n"; + }); + } + + const db::IQueryPlanRecorder& _recorder; + }; + } // namespace + + Database::Database() + : Wt::WTemplate{ Wt::WString::tr("Lms.Admin.DebugTools.Db.template") } + { + addFunction("tr", &Wt::WTemplate::Functions::tr); + + Wt::WPushButton* dumpBtn{ bindNew("export-query-plans-btn", Wt::WString::tr("Lms.Admin.DebugTools.Db.export-query-plans")) }; + + if (const auto* recorder{ core::Service::get() }) + { + Wt::WLink link{ std::make_shared(*recorder) }; + link.setTarget(Wt::LinkTarget::NewWindow); + dumpBtn->setLink(link); + } + else + dumpBtn->setEnabled(false); + } + +} // namespace lms::ui diff --git a/src/lms/ui/admin/TracingView.hpp b/src/lms/ui/admin/debug/Database.hpp similarity index 92% rename from src/lms/ui/admin/TracingView.hpp rename to src/lms/ui/admin/debug/Database.hpp index 95318c9e..131bb017 100644 --- a/src/lms/ui/admin/TracingView.hpp +++ b/src/lms/ui/admin/debug/Database.hpp @@ -23,9 +23,9 @@ namespace lms::ui { - class TracingView : public Wt::WTemplate + class Database : public Wt::WTemplate { public: - TracingView(); + Database(); }; } // namespace lms::ui diff --git a/src/lms/ui/admin/TracingView.cpp b/src/lms/ui/admin/debug/Tracing.cpp similarity index 93% rename from src/lms/ui/admin/TracingView.cpp rename to src/lms/ui/admin/debug/Tracing.cpp index 948e32c6..26af6712 100644 --- a/src/lms/ui/admin/TracingView.cpp +++ b/src/lms/ui/admin/debug/Tracing.cpp @@ -17,7 +17,7 @@ * along with LMS. If not, see . */ -#include "TracingView.hpp" +#include "Tracing.hpp" #include #include @@ -73,12 +73,12 @@ namespace lms::ui }; } // namespace - TracingView::TracingView() - : Wt::WTemplate{ Wt::WString::tr("Lms.Admin.Tracing.template") } + Tracing::Tracing() + : Wt::WTemplate{ Wt::WString::tr("Lms.Admin.DebugTools.Tracing.template") } { addFunction("tr", &Wt::WTemplate::Functions::tr); - Wt::WPushButton* dumpBtn{ bindNew("export-btn", Wt::WString::tr("Lms.Admin.Tracing.export-current-buffer")) }; + Wt::WPushButton* dumpBtn{ bindNew("export-btn", Wt::WString::tr("Lms.Admin.DebugTools.Tracing.export-current-buffer")) }; if (auto traceLogger{ core::Service::get() }) { @@ -89,4 +89,5 @@ namespace lms::ui else dumpBtn->setEnabled(false); } + } // namespace lms::ui diff --git a/src/lms/ui/admin/debug/Tracing.hpp b/src/lms/ui/admin/debug/Tracing.hpp new file mode 100644 index 00000000..60a2964d --- /dev/null +++ b/src/lms/ui/admin/debug/Tracing.hpp @@ -0,0 +1,31 @@ +/* + * 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 + +namespace lms::ui +{ + class Tracing : public Wt::WTemplate + { + public: + Tracing(); + }; +} // namespace lms::ui diff --git a/src/lms/ui/common/LoginNameValidator.cpp b/src/lms/ui/common/LoginNameValidator.cpp index b1abc51e..c712f200 100644 --- a/src/lms/ui/common/LoginNameValidator.cpp +++ b/src/lms/ui/common/LoginNameValidator.cpp @@ -21,7 +21,7 @@ #include -#include "database/User.hpp" +#include "database/objects/User.hpp" namespace lms::ui { diff --git a/src/lms/ui/explore/ArtistCollector.cpp b/src/lms/ui/explore/ArtistCollector.cpp index e0d4907b..058dfe59 100644 --- a/src/lms/ui/explore/ArtistCollector.cpp +++ b/src/lms/ui/explore/ArtistCollector.cpp @@ -20,10 +20,10 @@ #include "ArtistCollector.hpp" #include "core/Service.hpp" -#include "database/Artist.hpp" #include "database/Session.hpp" -#include "database/TrackList.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/TrackList.hpp" +#include "database/objects/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" diff --git a/src/lms/ui/explore/ArtistCollector.hpp b/src/lms/ui/explore/ArtistCollector.hpp index 9e5967ac..7d09cdb1 100644 --- a/src/lms/ui/explore/ArtistCollector.hpp +++ b/src/lms/ui/explore/ArtistCollector.hpp @@ -22,8 +22,8 @@ #include #include "DatabaseCollectorBase.hpp" -#include "database/ArtistId.hpp" #include "database/Types.hpp" +#include "database/objects/ArtistId.hpp" namespace lms::db { diff --git a/src/lms/ui/explore/ArtistListHelpers.cpp b/src/lms/ui/explore/ArtistListHelpers.cpp index 0dadb5dc..300e6d58 100644 --- a/src/lms/ui/explore/ArtistListHelpers.cpp +++ b/src/lms/ui/explore/ArtistListHelpers.cpp @@ -20,7 +20,7 @@ #include -#include "database/Artist.hpp" +#include "database/objects/Artist.hpp" #include "Utils.hpp" @@ -35,9 +35,9 @@ namespace lms::ui::ArtistListHelpers std::unique_ptr image; if (artist->getPreferredArtworkId().isValid()) - image = utils::createArtworkImage(artist->getPreferredArtworkId(), ArtworkResource::Size::Large); + image = utils::createArtworkImage(artist->getPreferredArtworkId(), ArtworkResource::DefaultArtworkType::Artist, ArtworkResource::Size::Large); else - image = utils::createDefaultArtistArtworkImage(); + image = utils::createDefaultArtworkImage(ArtworkResource::DefaultArtworkType::Artist); image->addStyleClass("Lms-image-artist Lms-cover-anchor"); // hack anchor->setImage(std::move(image)); diff --git a/src/lms/ui/explore/ArtistView.cpp b/src/lms/ui/explore/ArtistView.cpp index c2da9b5c..38a8c8bc 100644 --- a/src/lms/ui/explore/ArtistView.cpp +++ b/src/lms/ui/explore/ArtistView.cpp @@ -22,15 +22,15 @@ #include #include "core/String.hpp" -#include "database/Artist.hpp" -#include "database/ArtistInfo.hpp" -#include "database/ArtworkId.hpp" -#include "database/Cluster.hpp" -#include "database/Release.hpp" -#include "database/ScanSettings.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/ArtistInfo.hpp" +#include "database/objects/ArtworkId.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/ScanSettings.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/recommendation/IRecommendationService.hpp" @@ -197,17 +197,17 @@ namespace lms::ui std::unique_ptr artworkImage; if (artworkId.isValid()) { - artworkImage = utils::createArtworkImage(artworkId, ArtworkResource::Size::Large); + artworkImage = utils::createArtworkImage(artworkId, ArtworkResource::DefaultArtworkType::Artist, ArtworkResource::Size::Large); artworkImage->addStyleClass("Lms-cursor-pointer"); // HACK } else - artworkImage = utils::createDefaultArtistArtworkImage(); + artworkImage = utils::createDefaultArtworkImage(ArtworkResource::DefaultArtworkType::Artist); auto* image{ bindWidget("artwork", std::move(artworkImage)) }; if (artworkId.isValid()) { image->clicked().connect([artworkId] { - utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtworkUrl(artworkId) }); + utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtworkUrl(artworkId, ArtworkResource::DefaultArtworkType::Artist) }); }); } } diff --git a/src/lms/ui/explore/ArtistView.hpp b/src/lms/ui/explore/ArtistView.hpp index 4a613508..a05f3de3 100644 --- a/src/lms/ui/explore/ArtistView.hpp +++ b/src/lms/ui/explore/ArtistView.hpp @@ -21,10 +21,10 @@ #include -#include "database/ArtistId.hpp" -#include "database/ArtworkId.hpp" #include "database/Object.hpp" -#include "database/ReleaseId.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/ArtworkId.hpp" +#include "database/objects/ReleaseId.hpp" #include "ReleaseTypes.hpp" #include "common/Template.hpp" diff --git a/src/lms/ui/explore/ArtistsView.cpp b/src/lms/ui/explore/ArtistsView.cpp index 630d1f4f..24ef72a6 100644 --- a/src/lms/ui/explore/ArtistsView.cpp +++ b/src/lms/ui/explore/ArtistsView.cpp @@ -22,9 +22,9 @@ #include #include -#include "database/Artist.hpp" #include "database/Session.hpp" -#include "database/TrackArtistLink.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/TrackArtistLink.hpp" #include "ArtistListHelpers.hpp" #include "Filters.hpp" diff --git a/src/lms/ui/explore/Filters.cpp b/src/lms/ui/explore/Filters.cpp index 1f565c7e..e618e9e1 100644 --- a/src/lms/ui/explore/Filters.cpp +++ b/src/lms/ui/explore/Filters.cpp @@ -26,12 +26,12 @@ #include #include -#include "database/Cluster.hpp" -#include "database/LabelId.hpp" -#include "database/MediaLibrary.hpp" -#include "database/Release.hpp" -#include "database/ReleaseTypeId.hpp" #include "database/Session.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/LabelId.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/ReleaseTypeId.hpp" #include "LmsApplication.hpp" #include "ModalManager.hpp" diff --git a/src/lms/ui/explore/Filters.hpp b/src/lms/ui/explore/Filters.hpp index 1407c847..c63b7398 100644 --- a/src/lms/ui/explore/Filters.hpp +++ b/src/lms/ui/explore/Filters.hpp @@ -23,7 +23,7 @@ #include #include -#include "database/Filters.hpp" +#include "database/objects/Filters.hpp" namespace lms::ui { diff --git a/src/lms/ui/explore/PlayQueueController.cpp b/src/lms/ui/explore/PlayQueueController.cpp index 2bfa0fe6..77bc8443 100644 --- a/src/lms/ui/explore/PlayQueueController.cpp +++ b/src/lms/ui/explore/PlayQueueController.cpp @@ -19,9 +19,9 @@ #include "explore/PlayQueueController.hpp" -#include "database/Release.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" #include "LmsApplication.hpp" #include "PlayQueue.hpp" diff --git a/src/lms/ui/explore/PlayQueueController.hpp b/src/lms/ui/explore/PlayQueueController.hpp index 03771646..21b80255 100644 --- a/src/lms/ui/explore/PlayQueueController.hpp +++ b/src/lms/ui/explore/PlayQueueController.hpp @@ -21,10 +21,10 @@ #include -#include "database/ArtistId.hpp" -#include "database/ReleaseId.hpp" -#include "database/TrackId.hpp" -#include "database/TrackListId.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/TrackListId.hpp" namespace lms::ui { diff --git a/src/lms/ui/explore/ReleaseCollector.cpp b/src/lms/ui/explore/ReleaseCollector.cpp index b4e25cc7..c404990c 100644 --- a/src/lms/ui/explore/ReleaseCollector.cpp +++ b/src/lms/ui/explore/ReleaseCollector.cpp @@ -20,9 +20,9 @@ #include "ReleaseCollector.hpp" #include "core/Service.hpp" -#include "database/Release.hpp" #include "database/Session.hpp" -#include "database/User.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" diff --git a/src/lms/ui/explore/ReleaseCollector.hpp b/src/lms/ui/explore/ReleaseCollector.hpp index 4b002e95..cd434b54 100644 --- a/src/lms/ui/explore/ReleaseCollector.hpp +++ b/src/lms/ui/explore/ReleaseCollector.hpp @@ -23,8 +23,8 @@ #include "DatabaseCollectorBase.hpp" -#include "database/ReleaseId.hpp" #include "database/Types.hpp" +#include "database/objects/ReleaseId.hpp" namespace lms::db { diff --git a/src/lms/ui/explore/ReleaseHelpers.cpp b/src/lms/ui/explore/ReleaseHelpers.cpp index 45143712..f01ec00f 100644 --- a/src/lms/ui/explore/ReleaseHelpers.cpp +++ b/src/lms/ui/explore/ReleaseHelpers.cpp @@ -24,8 +24,8 @@ #include #include "core/EnumSet.hpp" -#include "database/Artist.hpp" -#include "database/Release.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Release.hpp" #include "Utils.hpp" @@ -54,9 +54,9 @@ namespace lms::ui::releaseListHelpers std::unique_ptr image; if (release->getPreferredArtworkId().isValid()) - image = utils::createArtworkImage(release->getPreferredArtworkId(), ArtworkResource::Size::Large); + image = utils::createArtworkImage(release->getPreferredArtworkId(), ArtworkResource::DefaultArtworkType::Release, ArtworkResource::Size::Large); else - image = utils::createDefaultReleaseArtworkImage(); + image = utils::createDefaultArtworkImage(ArtworkResource::DefaultArtworkType::Release); image->addStyleClass("Lms-cover-release Lms-cover-anchor rounded"); // hack anchor->setImage(std::move(image)); diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index 8fcfa166..4b5697be 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -28,15 +28,15 @@ #include "av/IAudioFile.hpp" #include "core/String.hpp" -#include "database/Artist.hpp" -#include "database/Cluster.hpp" -#include "database/Release.hpp" -#include "database/ScanSettings.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackArtistLink.hpp" #include "database/Types.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/ScanSettings.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/recommendation/IRecommendationService.hpp" #include "services/scrobbling/IScrobblingService.hpp" @@ -396,13 +396,13 @@ namespace lms::ui if (mediaArtworkId.isValid()) { - auto image{ utils::createArtworkImage(mediaArtworkId, ArtworkResource::Size::Small) }; + auto image{ utils::createArtworkImage(mediaArtworkId, ArtworkResource::DefaultArtworkType::Release, ArtworkResource::Size::Small) }; disc->setCondition("if-has-artwork", true); image->addStyleClass("Lms-cover-track rounded"); // HACK image->clicked().connect([=] { - utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtworkUrl(mediaArtworkId) }); + utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtworkUrl(mediaArtworkId, ArtworkResource::DefaultArtworkType::Release) }); }); disc->bindWidget("artwork", std::move(image)); } @@ -570,17 +570,17 @@ namespace lms::ui std::unique_ptr artworkImage; if (artworkId.isValid()) { - artworkImage = utils::createArtworkImage(artworkId, ArtworkResource::Size::Large); + artworkImage = utils::createArtworkImage(artworkId, ArtworkResource::DefaultArtworkType::Release, ArtworkResource::Size::Large); artworkImage->addStyleClass("Lms-cursor-pointer"); // HACK } else - artworkImage = utils::createDefaultReleaseArtworkImage(); + artworkImage = utils::createDefaultArtworkImage(ArtworkResource::DefaultArtworkType::Release); auto* image{ bindWidget("artwork", std::move(artworkImage)) }; if (artworkId.isValid()) { image->clicked().connect([artworkId] { - utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtworkUrl(artworkId) }); + utils::showArtworkModal(Wt::WLink{ LmsApp->getArtworkResource()->getArtworkUrl(artworkId, ArtworkResource::DefaultArtworkType::Release) }); }); } } diff --git a/src/lms/ui/explore/ReleaseView.hpp b/src/lms/ui/explore/ReleaseView.hpp index 2100998b..55c66ca2 100644 --- a/src/lms/ui/explore/ReleaseView.hpp +++ b/src/lms/ui/explore/ReleaseView.hpp @@ -19,9 +19,9 @@ #pragma once -#include "database/ArtworkId.hpp" #include "database/Object.hpp" -#include "database/ReleaseId.hpp" +#include "database/objects/ArtworkId.hpp" +#include "database/objects/ReleaseId.hpp" #include "common/Template.hpp" diff --git a/src/lms/ui/explore/ReleasesView.cpp b/src/lms/ui/explore/ReleasesView.cpp index 527df48f..769baafb 100644 --- a/src/lms/ui/explore/ReleasesView.cpp +++ b/src/lms/ui/explore/ReleasesView.cpp @@ -22,8 +22,8 @@ #include #include -#include "database/Release.hpp" #include "database/Session.hpp" +#include "database/objects/Release.hpp" #include "LmsApplication.hpp" #include "SortModeSelector.hpp" diff --git a/src/lms/ui/explore/TrackCollector.cpp b/src/lms/ui/explore/TrackCollector.cpp index 74baa2bf..5c7fac86 100644 --- a/src/lms/ui/explore/TrackCollector.cpp +++ b/src/lms/ui/explore/TrackCollector.cpp @@ -21,9 +21,9 @@ #include "core/Service.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackList.hpp" -#include "database/User.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackList.hpp" +#include "database/objects/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" diff --git a/src/lms/ui/explore/TrackCollector.hpp b/src/lms/ui/explore/TrackCollector.hpp index 034d4cb6..b27ea867 100644 --- a/src/lms/ui/explore/TrackCollector.hpp +++ b/src/lms/ui/explore/TrackCollector.hpp @@ -21,7 +21,7 @@ #include -#include "database/TrackId.hpp" +#include "database/objects/TrackId.hpp" #include "DatabaseCollectorBase.hpp" diff --git a/src/lms/ui/explore/TrackListHelpers.cpp b/src/lms/ui/explore/TrackListHelpers.cpp index 5f320bff..2cc0bc2f 100644 --- a/src/lms/ui/explore/TrackListHelpers.cpp +++ b/src/lms/ui/explore/TrackListHelpers.cpp @@ -25,14 +25,14 @@ #include "av/IAudioFile.hpp" #include "core/Service.hpp" -#include "database/Artist.hpp" -#include "database/Release.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackArtistLink.hpp" -#include "database/TrackLyrics.hpp" #include "database/Types.hpp" -#include "database/User.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/TrackLyrics.hpp" +#include "database/objects/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" @@ -230,11 +230,11 @@ namespace lms::ui::TrackListHelpers std::unique_ptr image; if (track->getPreferredMediaArtworkId().isValid()) - image = utils::createArtworkImage(track->getPreferredMediaArtworkId(), ArtworkResource::Size::Small); + image = utils::createArtworkImage(track->getPreferredMediaArtworkId(), ArtworkResource::DefaultArtworkType::Track, ArtworkResource::Size::Small); else if (track->getPreferredArtworkId().isValid()) - image = utils::createArtworkImage(track->getPreferredArtworkId(), ArtworkResource::Size::Small); + image = utils::createArtworkImage(track->getPreferredArtworkId(), ArtworkResource::DefaultArtworkType::Track, ArtworkResource::Size::Small); else - image = utils::createDefaultTrackArtworkImage(); + image = utils::createDefaultArtworkImage(ArtworkResource::DefaultArtworkType::Track); image->addStyleClass("Lms-cover-track rounded"); // hack if (track->getRelease()) diff --git a/src/lms/ui/explore/TrackListHelpers.hpp b/src/lms/ui/explore/TrackListHelpers.hpp index 57f90b3c..3dbae791 100644 --- a/src/lms/ui/explore/TrackListHelpers.hpp +++ b/src/lms/ui/explore/TrackListHelpers.hpp @@ -27,10 +27,10 @@ #include #include "core/EnumSet.hpp" -#include "database/ArtistId.hpp" #include "database/Object.hpp" -#include "database/TrackId.hpp" #include "database/Types.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/TrackId.hpp" namespace lms::db { diff --git a/src/lms/ui/explore/TrackListView.cpp b/src/lms/ui/explore/TrackListView.cpp index 2fcb8fbe..61e9a62d 100644 --- a/src/lms/ui/explore/TrackListView.cpp +++ b/src/lms/ui/explore/TrackListView.cpp @@ -22,11 +22,11 @@ #include #include "core/String.hpp" -#include "database/Cluster.hpp" -#include "database/ScanSettings.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackList.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/ScanSettings.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackList.hpp" #include "LmsApplication.hpp" #include "LmsApplicationException.hpp" diff --git a/src/lms/ui/explore/TrackListView.hpp b/src/lms/ui/explore/TrackListView.hpp index 2e381f3d..8ead2f0f 100644 --- a/src/lms/ui/explore/TrackListView.hpp +++ b/src/lms/ui/explore/TrackListView.hpp @@ -19,7 +19,7 @@ #pragma once -#include "database/TrackListId.hpp" +#include "database/objects/TrackListId.hpp" #include "common/Template.hpp" diff --git a/src/lms/ui/explore/TrackListsView.cpp b/src/lms/ui/explore/TrackListsView.cpp index 4ed2bf22..7b2faca9 100644 --- a/src/lms/ui/explore/TrackListsView.cpp +++ b/src/lms/ui/explore/TrackListsView.cpp @@ -23,7 +23,7 @@ #include #include "database/Session.hpp" -#include "database/TrackList.hpp" +#include "database/objects/TrackList.hpp" #include "DropDownMenuSelector.hpp" #include "Filters.hpp" diff --git a/src/lms/ui/explore/TrackListsView.hpp b/src/lms/ui/explore/TrackListsView.hpp index 37cf3f40..03478d13 100644 --- a/src/lms/ui/explore/TrackListsView.hpp +++ b/src/lms/ui/explore/TrackListsView.hpp @@ -24,7 +24,7 @@ #include #include "database/Object.hpp" -#include "database/TrackListId.hpp" +#include "database/objects/TrackListId.hpp" #include "common/Template.hpp" diff --git a/src/lms/ui/explore/TracksView.cpp b/src/lms/ui/explore/TracksView.cpp index b83aef68..218ecb60 100644 --- a/src/lms/ui/explore/TracksView.cpp +++ b/src/lms/ui/explore/TracksView.cpp @@ -23,7 +23,7 @@ #include #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Track.hpp" #include "LmsApplication.hpp" #include "SortModeSelector.hpp" diff --git a/src/lms/ui/resource/ArtworkResource.cpp b/src/lms/ui/resource/ArtworkResource.cpp index 20b341ed..b160bf65 100644 --- a/src/lms/ui/resource/ArtworkResource.cpp +++ b/src/lms/ui/resource/ArtworkResource.cpp @@ -34,10 +34,30 @@ namespace lms::ui { + namespace + { + std::string_view getDefaultArtworkPart(ArtworkResource::DefaultArtworkType fallbackType) + { + std::string_view res; + switch (fallbackType) + { + case ArtworkResource::DefaultArtworkType::Release: + case ArtworkResource::DefaultArtworkType::Track: + res = "&fallback=defrelease"; + break; + + case ArtworkResource::DefaultArtworkType::Artist: + res = "&fallback=defartist"; + } + + return res; + } + } // namespace + ArtworkResource::ArtworkResource() { LmsApp->getScannerEvents().scanComplete.connect(this, [this](const scanner::ScanStats& stats) { - if (stats.nbChanges()) + if (stats.getChangesCount()) setChanged(); }); } @@ -47,28 +67,22 @@ namespace lms::ui beingDeleted(); } - std::string ArtworkResource::getArtworkUrl(db::ArtworkId artworkId, std::optional size) const + std::string ArtworkResource::getArtworkUrl(db::ArtworkId artworkId, DefaultArtworkType fallbackType, std::optional size) const { std::string res{ url() + "&artworkid=" + artworkId.toString() }; if (size) res += "&size=" + std::to_string(static_cast(*size)); + res += getDefaultArtworkPart(fallbackType); + return res; } - std::string ArtworkResource::getDefaultArtistArtworkUrl() const + std::string ArtworkResource::getDefaultArtworkUrl(DefaultArtworkType type) const { - return url() + "&type=defartist"; - } - - std::string ArtworkResource::getDefaultReleaseArtworkUrl() const - { - return url() + "&type=defrelease"; - } - - std::string ArtworkResource::getDefaultTrackArtworkUrl() const - { - return url() + "&type=defrelease"; + std::string res{ url() }; + res += getDefaultArtworkPart(type); + return res; } void ArtworkResource::handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) @@ -78,20 +92,13 @@ namespace lms::ui // Retrieve parameters const std::string* artworkIdStr = request.getParameter("artworkid"); const std::string* sizeStr = request.getParameter("size"); - const std::string* typeStr = request.getParameter("type"); + const std::string* fallbackStr = request.getParameter("fallback"); std::shared_ptr image; - if (artworkIdStr && typeStr) + if (!artworkIdStr && !fallbackStr) { - ARTWORK_RESOURCE_LOG(DEBUG, "both artwork ID and type provided, only one is allowed"); - response.setStatus(400); - return; - } - - if (!artworkIdStr && !typeStr) - { - ARTWORK_RESOURCE_LOG(DEBUG, "no artwork ID or type provided"); + ARTWORK_RESOURCE_LOG(DEBUG, "no artwork ID or fallback provided"); response.setStatus(400); return; } @@ -118,22 +125,22 @@ namespace lms::ui if (!image) ARTWORK_RESOURCE_LOG(DEBUG, "no image found for artwork ID: '" << *artworkIdStr << "'"); } - else + + if (!image && fallbackStr) { - assert(typeStr); - if (*typeStr == "defartist") + if (*fallbackStr == "defartist") image = core::Service::get()->getDefaultArtistArtwork(); - else if (*typeStr == "defrelease") + else if (*fallbackStr == "defrelease") image = core::Service::get()->getDefaultReleaseArtwork(); else { - ARTWORK_RESOURCE_LOG(DEBUG, "invalid type provided: '" << *typeStr << "'"); + ARTWORK_RESOURCE_LOG(DEBUG, "invalid type provided: '" << *fallbackStr << "'"); response.setStatus(400); return; } if (!image) - ARTWORK_RESOURCE_LOG(DEBUG, "no default image found for type: '" << *typeStr << "'"); + ARTWORK_RESOURCE_LOG(DEBUG, "no default image found for type: '" << *fallbackStr << "'"); } if (image) diff --git a/src/lms/ui/resource/ArtworkResource.hpp b/src/lms/ui/resource/ArtworkResource.hpp index 7b5d6a25..03d4b4f0 100644 --- a/src/lms/ui/resource/ArtworkResource.hpp +++ b/src/lms/ui/resource/ArtworkResource.hpp @@ -23,7 +23,7 @@ #include -#include "database/ArtworkId.hpp" +#include "database/objects/ArtworkId.hpp" namespace lms::ui { @@ -41,11 +41,15 @@ namespace lms::ui Large = 512, }; - std::string getArtworkUrl(db::ArtworkId artworkId, std::optional size = std::nullopt) const; + enum class DefaultArtworkType + { + Artist, + Release, + Track, + }; - std::string getDefaultArtistArtworkUrl() const; - std::string getDefaultReleaseArtworkUrl() const; - std::string getDefaultTrackArtworkUrl() const; + std::string getArtworkUrl(db::ArtworkId artworkId, DefaultArtworkType fallbackType, std::optional size = std::nullopt) const; + std::string getDefaultArtworkUrl(DefaultArtworkType type) const; private: void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override; diff --git a/src/lms/ui/resource/AudioFileResource.cpp b/src/lms/ui/resource/AudioFileResource.cpp index 1a90142a..3225d8e1 100644 --- a/src/lms/ui/resource/AudioFileResource.cpp +++ b/src/lms/ui/resource/AudioFileResource.cpp @@ -26,7 +26,7 @@ #include "core/ITraceLogger.hpp" #include "core/String.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" +#include "database/objects/Track.hpp" #include "LmsApplication.hpp" diff --git a/src/lms/ui/resource/AudioFileResource.hpp b/src/lms/ui/resource/AudioFileResource.hpp index da00ae7d..f9ac913f 100644 --- a/src/lms/ui/resource/AudioFileResource.hpp +++ b/src/lms/ui/resource/AudioFileResource.hpp @@ -21,7 +21,7 @@ #include -#include "database/TrackId.hpp" +#include "database/objects/TrackId.hpp" namespace lms::ui { diff --git a/src/lms/ui/resource/AudioTranscodingResource.cpp b/src/lms/ui/resource/AudioTranscodingResource.cpp index 926985cc..0d67d7e3 100644 --- a/src/lms/ui/resource/AudioTranscodingResource.cpp +++ b/src/lms/ui/resource/AudioTranscodingResource.cpp @@ -28,8 +28,8 @@ #include "core/Service.hpp" #include "core/String.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/User.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/User.hpp" #include "services/transcoding/ITranscodingService.hpp" #include "LmsApplication.hpp" diff --git a/src/lms/ui/resource/AudioTranscodingResource.hpp b/src/lms/ui/resource/AudioTranscodingResource.hpp index 8002279f..362e1c9f 100644 --- a/src/lms/ui/resource/AudioTranscodingResource.hpp +++ b/src/lms/ui/resource/AudioTranscodingResource.hpp @@ -22,7 +22,7 @@ #include #include -#include "database/TrackId.hpp" +#include "database/objects/TrackId.hpp" namespace lms::db { diff --git a/src/lms/ui/resource/DownloadResource.cpp b/src/lms/ui/resource/DownloadResource.cpp index 3dec9d7f..0f2e8092 100644 --- a/src/lms/ui/resource/DownloadResource.cpp +++ b/src/lms/ui/resource/DownloadResource.cpp @@ -23,11 +23,11 @@ #include #include "core/ILogger.hpp" -#include "database/Artist.hpp" -#include "database/Release.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackList.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackList.hpp" #include "LmsApplication.hpp" diff --git a/src/lms/ui/resource/DownloadResource.hpp b/src/lms/ui/resource/DownloadResource.hpp index b525b560..f8e91fac 100644 --- a/src/lms/ui/resource/DownloadResource.hpp +++ b/src/lms/ui/resource/DownloadResource.hpp @@ -23,10 +23,10 @@ #include #include "core/IZipper.hpp" -#include "database/ArtistId.hpp" -#include "database/ReleaseId.hpp" -#include "database/TrackId.hpp" -#include "database/TrackListId.hpp" +#include "database/objects/ArtistId.hpp" +#include "database/objects/ReleaseId.hpp" +#include "database/objects/TrackId.hpp" +#include "database/objects/TrackListId.hpp" namespace lms::ui { diff --git a/src/tools/db-generator/LmsDbGenerator.cpp b/src/tools/db-generator/LmsDbGenerator.cpp index f251facb..f6daa1b5 100644 --- a/src/tools/db-generator/LmsDbGenerator.cpp +++ b/src/tools/db-generator/LmsDbGenerator.cpp @@ -32,14 +32,16 @@ #include "core/Service.hpp" #include "core/StreamLogger.hpp" #include "core/SystemPaths.hpp" -#include "database/Artist.hpp" -#include "database/Cluster.hpp" -#include "database/Db.hpp" -#include "database/MediaLibrary.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" -#include "database/TrackArtistLink.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/MediaLibrary.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackArtistLink.hpp" +#include "database/objects/TrackEmbeddedImage.hpp" +#include "database/objects/TrackEmbeddedImageLink.hpp" namespace lms { @@ -52,6 +54,7 @@ namespace lms float compilationRatio{ 0.1 }; std::size_t genreCountPerTrack{ 3 }; std::size_t moodCountPerTrack{ 3 }; + std::size_t trackEmbeddedImagePerRelease{ 1 }; // usual case: one same image saved on each track std::size_t genreCount{ 50 }; std::size_t moodCount{ 25 }; std::filesystem::path trackPath; @@ -94,6 +97,10 @@ namespace lms if (!context.mediaLibraries.empty()) mediaLibrary = *core::random::pickRandom(context.mediaLibraries); + std::vector trackEmbeddedImages; + for (std::size_t i{}; i < params.trackEmbeddedImagePerRelease; ++i) + trackEmbeddedImages.push_back(context.session.create()); + for (std::size_t i{}; i < params.trackCountPerRelease; ++i) { Track::pointer track{ context.session.create() }; @@ -112,6 +119,9 @@ namespace lms TrackArtistLink::create(context.session, track, artist, TrackArtistLinkType::Artist); TrackArtistLink::create(context.session, track, artist, TrackArtistLinkType::ReleaseArtist); + if (!trackEmbeddedImages.empty()) + context.session.create(track, *core::random::pickRandom(trackEmbeddedImages)); + std::vector> clusters; if (!context.genres.empty()) clusters.push_back(*core::random::pickRandom(context.genres)); @@ -177,7 +187,19 @@ int main(int argc, char* argv[]) const GeneratorParameters defaultParams; program_options::options_description options{ "Options" }; - options.add_options()("conf,c", program_options::value()->default_value(core::sysconfDirectory / "lms.conf"), "lms config file")("media-library-count", program_options::value()->default_value(defaultParams.mediaLibraryCount), "Number of media libraries to use")("release-count-per-batch", program_options::value()->default_value(defaultParams.releaseCountPerBatch), "Number of releases to generate before committing transaction")("release-count", program_options::value()->default_value(defaultParams.releaseCount), "Number of releases to generate")("track-count-per-release", program_options::value()->default_value(defaultParams.trackCountPerRelease), "Number of tracks per release")("compilation-ratio", program_options::value()->default_value(defaultParams.compilationRatio), "Compilation ratio (compilation means all tracks have a different artist)")("track-path", program_options::value()->required(), "Path of a valid track file, that will be used for all generated tracks")("genre-count", program_options::value()->default_value(defaultParams.genreCount), "Number of genres to generate")("genre-count-per-track", program_options::value()->default_value(defaultParams.genreCountPerTrack), "Number of genres to assign to each track")("mood-count", program_options::value()->default_value(defaultParams.moodCount), "Number of moods to generate")("mood-count-per-track", program_options::value()->default_value(defaultParams.moodCountPerTrack), "Number of moods to assign to each track")("help,h", "produce help message"); + + // clang-format off + options.add_options() + ("conf,c", program_options::value()->default_value(core::sysconfDirectory / "lms.conf"), "lms config file") + ("media-library-count", program_options::value()->default_value(defaultParams.mediaLibraryCount), "Number of media libraries to use") + ("release-count-per-batch",program_options::value()->default_value(defaultParams.releaseCountPerBatch), "Number of releases to generate before committing transaction") + ("release-count",program_options::value()->default_value(defaultParams.releaseCount), "Number of releases to generate") + ("track-count-per-release", program_options::value()->default_value(defaultParams.trackCountPerRelease), "Number of tracks per release") + ("track-embedded-image-count",program_options::value()->default_value(defaultParams.trackEmbeddedImagePerRelease), "Number of different embedded track images for the whole release (each track has one different embedded image)") + ("compilation-ratio",program_options::value()->default_value(defaultParams.compilationRatio), "Compilation ratio (compilation means all tracks have a different artist)") + ("track-path",program_options::value()->required(), "Path of a valid track file, that will be used for all generated tracks")("genre-count", program_options::value()->default_value(defaultParams.genreCount), "Number of genres to generate")("genre-count-per-track", program_options::value()->default_value(defaultParams.genreCountPerTrack), "Number of genres to assign to each track")("mood-count", program_options::value()->default_value(defaultParams.moodCount), "Number of moods to generate") + ("mood-count-per-track", program_options::value()->default_value(defaultParams.moodCountPerTrack), "Number of moods to assign to each track")("help,h", "produce help message"); + // clang-format on program_options::variables_map vm; program_options::store(program_options::parse_command_line(argc, argv, options), vm); @@ -197,14 +219,15 @@ int main(int argc, char* argv[]) genParams.releaseCount = vm["release-count"].as(); genParams.trackCountPerRelease = vm["track-count-per-release"].as(); genParams.compilationRatio = vm["compilation-ratio"].as(); + genParams.trackEmbeddedImagePerRelease = vm["track-embedded-image-count"].as(); genParams.trackPath = std::filesystem::path{ vm["track-path"].as() }; if (!std::filesystem::exists(genParams.trackPath)) throw std::runtime_error{ "File '" + genParams.trackPath.string() + "' does not exist!" }; core::Service config{ core::createConfig(vm["conf"].as()) }; - db::Db db{ config->getPath("working-dir", "/var/lms") / "lms.db" }; - db::Session session{ db }; + auto db{ db::createDb(config->getPath("working-dir", "/var/lms") / "lms.db") }; + db::Session session{ *db }; std::cout << "Starting generation..." << std::endl; GenerationContext genContext{ session }; diff --git a/src/tools/recommendation/LmsRecommendation.cpp b/src/tools/recommendation/LmsRecommendation.cpp index 42acf637..b5235951 100644 --- a/src/tools/recommendation/LmsRecommendation.cpp +++ b/src/tools/recommendation/LmsRecommendation.cpp @@ -27,13 +27,13 @@ #include "core/Service.hpp" #include "core/StreamLogger.hpp" #include "core/SystemPaths.hpp" -#include "database/Artist.hpp" -#include "database/Cluster.hpp" -#include "database/Db.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/Session.hpp" -#include "database/Track.hpp" #include "database/Types.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" #include "services/recommendation/IRecommendationService.hpp" namespace lms @@ -145,11 +145,11 @@ int main(int argc, char* argv[]) core::Service config{ core::createConfig(vm["conf"].as()) }; - Db db{ config->getPath("working-dir", "/var/lms") / "lms.db" }; - Session session{ db }; + auto db{ db::createDb(config->getPath("working-dir", "/var/lms") / "lms.db") }; + Session session{ *db }; std::cout << "Creating recommendation service..." << std::endl; - const auto recommendationService{ recommendation::createRecommendationService(db) }; + const auto recommendationService{ recommendation::createRecommendationService(*db) }; std::cout << "Recommendation service created!" << std::endl; std::cout << "Loading recommendation service..." << std::endl; @@ -160,13 +160,13 @@ int main(int argc, char* argv[]) std::cout << "Recommendation service loaded!" << std::endl; if (vm.count("tracks")) - dumpTracksRecommendation(db, *recommendationService, maxSimilarityCount); + dumpTracksRecommendation(*db, *recommendationService, maxSimilarityCount); if (vm.count("releases")) - dumpReleasesRecommendation(db, *recommendationService, maxSimilarityCount); + dumpReleasesRecommendation(*db, *recommendationService, maxSimilarityCount); if (vm.count("artists")) - dumpArtistsRecommendation(db, *recommendationService, maxSimilarityCount); + dumpArtistsRecommendation(*db, *recommendationService, maxSimilarityCount); } catch (std::exception& e) { diff --git a/src/tools/similarity-parameters/LmsSimilarityParameters.cpp b/src/tools/similarity-parameters/LmsSimilarityParameters.cpp index 65fe041b..1f2d8a24 100644 --- a/src/tools/similarity-parameters/LmsSimilarityParameters.cpp +++ b/src/tools/similarity-parameters/LmsSimilarityParameters.cpp @@ -24,13 +24,13 @@ #include "core/Config.hpp" #include "core/Service.hpp" #include "core/StreamLogger.hpp" -#include "database/Artist.hpp" -#include "database/Cluster.hpp" -#include "database/Db.hpp" -#include "database/Release.hpp" +#include "database/IDb.hpp" #include "database/SessionPool.hpp" -#include "database/Track.hpp" -#include "database/TrackFeatures.hpp" +#include "database/objects/Artist.hpp" +#include "database/objects/Cluster.hpp" +#include "database/objects/Release.hpp" +#include "database/objects/Track.hpp" +#include "database/objects/TrackFeatures.hpp" #include "similarity/features/SimilarityFeaturesSearcher.hpp" #include "GeneticAlgorithm.hpp"