Merge branch 'develop' for release v3.68.0

This commit is contained in:
emeric
2025-07-19 23:16:44 +02:00
372 changed files with 4885 additions and 2795 deletions
+2
View File
@@ -17,4 +17,6 @@ CheckOptions:
value: '1'
- key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor
value: '1'
- key: readability-function-cognitive-complexity.IgnoreMacros
value: '1'
...
+4 -4
View File
@@ -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 <itmfr@yahoo.fr>"
ARG RUNTIME_PACKAGES=" \
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" ?>
<messages xmlns:if="Wt.WTemplate.conditions">
<message id="Lms.Admin.DebugTools.Db.template">
<form>
<div class="row g-3">
<div class="col-12">
${export-query-plans-btn class="btn btn-primary"}
</div>
</div>
</form>
</message>
</messages>
+15
View File
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8" ?>
<messages xmlns:if="Wt.WTemplate.conditions">
<message id="Lms.Admin.DebugTools.template">
<form>
<legend>${tr:Lms.Admin.DebugTools.Tracing.tracing}</legend>
${tracing}
<hr/>
<legend>${tr:Lms.Admin.DebugTools.Db.db}</legend>
${db}
</form>
</message>
</messages>
+1 -2
View File
@@ -1,9 +1,8 @@
<?xml version="1.0" encoding="UTF-8" ?>
<messages xmlns:if="Wt.WTemplate.conditions">
<message id="Lms.Admin.Tracing.template">
<message id="Lms.Admin.DebugTools.Tracing.template">
<form>
<legend>${tr:Lms.Admin.Tracing.tracing}</legend>
<div class="row g-3">
<div class="col-12">
${export-btn class="btn btn-primary"}
+4 -1
View File
@@ -44,7 +44,10 @@
<li>${scan-settings class="dropdown-item"}</li>
<li>${scanner class="dropdown-item"}</li>
<li>${users class="dropdown-item"}</li>
<li>${tracing class="dropdown-item"}</li>
${<if-debug-tools>}
<li class="dropdown-divider"></li>
<li>${debug-tools class="dropdown-item"}</li>
${</if-debug-tools>}
</ul>
</li>
${</if-is-admin>}
+11 -4
View File
@@ -52,10 +52,10 @@
<message id="Lms.Error.user-not-found">User not found</message>
<!--Administration-->
<message id="Lms.Admin.menu-debug-tools"><i class="fa fa-fw fa-bug" aria-hidden="true"></i> Debug tools</message>
<message id="Lms.Admin.menu-media-libraries"><i class="fa fa-fw fa-database" aria-hidden="true"></i> Libraries</message>
<message id="Lms.Admin.menu-scan-settings"><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> Scan settings</message>
<message id="Lms.Admin.menu-scanner"><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> Scanner</message>
<message id="Lms.Admin.menu-tracing"><i class="fa fa-fw fa-bar-chart" aria-hidden="true"></i> Tracing</message>
<message id="Lms.Admin.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> Users</message>
<!--MediaLibraries-->
@@ -145,13 +145,20 @@
<message id="Lms.Admin.ScannerController.step-reconciliate-artists">Reconciliating artists: {1} entries...</message>
<message id="Lms.Admin.ScannerController.step-reloading-similarity-engine">Reloading similarity engine: {1}%...</message>
<message id="Lms.Admin.ScannerController.step-removing-orphaned-entries">Removing orphaned entries: {1} entries...</message>
<message id="Lms.Admin.ScannerController.step-scanning-files">Scanning files: {1}/{2} ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-scanning-files">Library analysis: {1} files...</message>
<message id="Lms.Admin.ScannerController.step-status">Step status</message>
<message id="Lms.Admin.ScannerController.step-updating-library-fields">Updating library fields: {1} entries</message>
<!--Debug Tools-->
<message id="Lms.Admin.DebugTools.debug-tools">Debug tools</message>
<!--Db-->
<message id="Lms.Admin.DebugTools.Db.export-query-plans">Export query plans</message>
<message id="Lms.Admin.DebugTools.Db.db">Database</message>
<!--Tracing-->
<message id="Lms.Admin.Tracing.export-current-buffer">Export traces</message>
<message id="Lms.Admin.Tracing.tracing">Tracing</message>
<message id="Lms.Admin.DebugTools.Tracing.export-current-buffer">Export traces</message>
<message id="Lms.Admin.DebugTools.Tracing.tracing">Tracing</message>
<!--Users-->
<message id="Lms.Admin.Users.add">New user</message>
+11 -5
View File
@@ -52,10 +52,10 @@
<message id="Lms.Error.user-not-found">No existe el usuario</message>
<!--Administration-->
<message id="Lms.Admin.menu-debug-tools"><i class="fa fa-fw fa-bug" aria-hidden="true"></i> Herramientas de depuración</message>
<message id="Lms.Admin.menu-media-libraries"><i class="fa fa-fw fa-database" aria-hidden="true"></i> Bibliotecas</message>
<message id="Lms.Admin.menu-scan-settings"><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> Opciones de escaneo</message>
<message id="Lms.Admin.menu-scanner"><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> Escanear</message>
<message id="Lms.Admin.menu-tracing"><i class="fa fa-fw fa-bar-chart" aria-hidden="true"></i> Traces</message>
<message id="Lms.Admin.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> Usuarios</message>
<!--MediaLibraries-->
@@ -139,19 +139,25 @@
<message id="Lms.Admin.ScannerController.step-checking-for-removed-files">Comprobando ficheros eliminados... {1}%</message>
<message id="Lms.Admin.ScannerController.step-compact">Compactando la base de datos...</message>
<message id="Lms.Admin.ScannerController.step-compute-cluster-stats">Calculando estadísticas... {1}%</message>
<message id="Lms.Admin.ScannerController.step-discovering-files">Descubriendo ficheros: {1} ficheros</message>
<message id="Lms.Admin.ScannerController.step-fetching-track-features">Accediendo a las características de la pista desde AcousticBrainz : {1}/{2} ficheros ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-optimize">Optimizando la base de datos... {1}%...</message>
<message id="Lms.Admin.ScannerController.step-reconciliate-artists">Reconciliando artistas: {1} entradas...</message>
<message id="Lms.Admin.ScannerController.step-reloading-similarity-engine">Recargando el motor de similitud: {1}%...</message>
<message id="Lms.Admin.ScannerController.step-removing-orphaned-entries">Borrando entradas huérfanas: {1} entradas...</message>
<message id="Lms.Admin.ScannerController.step-scanning-files">Escaneando ficheros: {1} / {2} ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-scanning-files">Análisis de bibliotecas: {1} ficheros...</message>
<message id="Lms.Admin.ScannerController.step-status">Estado de las etapas</message>
<message id="Lms.Admin.ScannerController.step-updating-library-fields">Actualizando campos de la biblioteca: {1} entradas</message>
<!--Debug Tools-->
<message id="Lms.Admin.DebugTools.debug-tools">Herramientas de depuración</message>
<!--Db-->
<message id="Lms.Admin.DebugTools.Db.export-query-plans">Exportar planes de consulta</message>
<message id="Lms.Admin.DebugTools.Db.db">Base de datos</message>
<!--Tracing-->
<message id="Lms.Admin.Tracing.export-current-buffer">Exportar las trazas</message>
<message id="Lms.Admin.Tracing.tracing">Trazas</message>
<message id="Lms.Admin.DebugTools.Tracing.export-current-buffer">Exportar las trazas</message>
<message id="Lms.Admin.DebugTools.Tracing.tracing">Trazas</message>
<!--Users-->
<message id="Lms.Admin.Users.add">Añadir</message>
+12 -6
View File
@@ -52,10 +52,10 @@
<message id="Lms.Error.user-not-found">L'utilisateur n'existe pas</message>
<!--Administration-->
<message id="Lms.Admin.menu-debug-tools"><i class="fa fa-fw fa-bug" aria-hidden="true"></i> Outils de débogage</message>
<message id="Lms.Admin.menu-media-libraries"><i class="fa fa-fw fa-database" aria-hidden="true"></i> Bibliothèques</message>
<message id="Lms.Admin.menu-scan-settings"><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> Paramètres du scan</message>
<message id="Lms.Admin.menu-scanner"><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> Scanner</message>
<message id="Lms.Admin.menu-tracing"><i class="fa fa-fw fa-bar-chart" aria-hidden="true"></i> Traces</message>
<message id="Lms.Admin.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> Utilisateurs</message>
<!--MediaLibraries-->
@@ -74,7 +74,7 @@
<!--Scan settings-->
<message id="Lms.Admin.Database.allow-mbid-artist-merge">Permettre la fusion des artistes sans MBID avec ceux qui en ont un</message>
<message id="Lms.Admin.Database.artist-image-fallback-to-release">Utilizza un'immagine dell'album se non è disponibile un'immagine dell'artista</message>
<message id="Lms.Admin.Database.artist-image-fallback-to-release">Utiliser une image d'album si aucune image de l'artiste n'est disponible</message>
<message id="Lms.Admin.Database.artist-tag-delimiters">Délimiteurs à utiliser pour séparer les tags d'artistes</message>
<message id="Lms.Admin.Database.artists-to-not-split">Artistes à ne pas séparer en utilisant les délimiteurs (un artiste par ligne)</message>
<message id="Lms.Admin.Database.daily">Tous les jours</message>
@@ -139,19 +139,25 @@
<message id="Lms.Admin.ScannerController.step-checking-for-removed-files">Vérification des fichiers supprimés... {1}%</message>
<message id="Lms.Admin.ScannerController.step-compact">Compactage de la base de données...</message>
<message id="Lms.Admin.ScannerController.step-compute-cluster-stats">Calcul des statistiques... {1}%</message>
<message id="Lms.Admin.ScannerController.step-discovering-files">Découverte des fichiers : {1} fichiers</message>
<message id="Lms.Admin.ScannerController.step-fetching-track-features">Récupération des métadonnées AcousticBrainz : {1}/{2} fichiers ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-optimize">Optimisation de la base de données... {1}%...</message>
<message id="Lms.Admin.ScannerController.step-reconciliate-artists">Reconciliation des artistes: {1} entrées...</message>
<message id="Lms.Admin.ScannerController.step-reloading-similarity-engine">Rechargement du moteur de recommandation : {1}%...</message>
<message id="Lms.Admin.ScannerController.step-removing-orphaned-entries">Retrait des entrées orphelines: {1} entrées...</message>
<message id="Lms.Admin.ScannerController.step-scanning-files">Scan des fichiers : {1}/{2} ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-scanning-files">Analyse des bibliothèques : {1} fichiers...</message>
<message id="Lms.Admin.ScannerController.step-status">Statut de l'étape</message>
<message id="Lms.Admin.ScannerController.step-updating-library-fields">Mise à jour des champs des bibliothèques: {1} entrées</message>
<!--Debug Tools-->
<message id="Lms.Admin.DebugTools.debug-tools">Outils de débogage</message>
<!--Db-->
<message id="Lms.Admin.DebugTools.Db.export-query-plans">Exporter les plans de requête</message>
<message id="Lms.Admin.DebugTools.Db.db">Base de données</message>
<!--Tracing-->
<message id="Lms.Admin.Tracing.export-current-buffer">Exporter les traces</message>
<message id="Lms.Admin.Tracing.tracing">Traces</message>
<message id="Lms.Admin.DebugTools.Tracing.export-current-buffer">Exporter les traces</message>
<message id="Lms.Admin.DebugTools.Tracing.tracing">Traces</message>
<!--Users-->
<message id="Lms.Admin.Users.add">Ajouter</message>
+12 -6
View File
@@ -52,10 +52,10 @@
<message id="Lms.Error.user-not-found">Utente non trovato</message>
<!--Administration-->
<message id="Lms.Admin.menu-debug-tools"><i class="fa fa-fw fa-bug" aria-hidden="true"></i> Strumenti di debug</message>
<message id="Lms.Admin.menu-media-libraries"><i class="fa fa-fw fa-database" aria-hidden="true"></i> Librerie</message>
<message id="Lms.Admin.menu-scan-settings"><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> Impostazioni di scansione</message>
<message id="Lms.Admin.menu-scanner"><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> Scanner</message>
<message id="Lms.Admin.menu-tracing"><i class="fa fa-fw fa-bar-chart" aria-hidden="true"></i> Tracing</message>
<message id="Lms.Admin.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> Utenti</message>
<!--MediaLibraries-->
@@ -74,7 +74,7 @@
<!--Scan settings-->
<message id="Lms.Admin.Database.allow-mbid-artist-merge">Consentire la fusione degli artisti senza MBID con quelli che ne hanno uno</message>
<message id="Lms.Admin.Database.artist-image-fallback-to-release">Usa l'immagine dell'album se non è disponibile un'immagine dell'artista</message>
<message id="Lms.Admin.Database.artist-image-fallback-to-release">Utilizza un'immagine dell'album se non è disponibile un'immagine dell'artista</message>
<message id="Lms.Admin.Database.artist-tag-delimiters">Delimitatori da utilizzare per separare i tag degli artisti</message>
<message id="Lms.Admin.Database.artists-to-not-split">Artisti da non suddividere utilizzando i delimitatori (un artista per riga)</message>
<message id="Lms.Admin.Database.daily">Giornaliera</message>
@@ -139,19 +139,25 @@
<message id="Lms.Admin.ScannerController.step-checking-for-removed-files">Controllo file... {1}%</message>
<message id="Lms.Admin.ScannerController.step-compact">Compattazione del database...</message>
<message id="Lms.Admin.ScannerController.step-compute-cluster-stats">Calcolo statistiche... {1}%</message>
<message id="Lms.Admin.ScannerController.step-discovering-files">File trovati: {1} files</message>
<message id="Lms.Admin.ScannerController.step-fetching-track-features">Recupero metadati da AcousticBrainz: {1}/{2} tracce ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-optimize">Ottimizzazione del database... {1}%...</message>
<message id="Lms.Admin.ScannerController.step-reconciliate-artists">Riconciliazione artisti: {1} voci...</message>
<message id="Lms.Admin.ScannerController.step-reloading-similarity-engine">Ricarica motore di tracce simili: {1}%...</message>
<message id="Lms.Admin.ScannerController.step-removing-orphaned-entries">Rimozione voci orfane: {1} voci...</message>
<message id="Lms.Admin.ScannerController.step-scanning-files">Scansione dei file: {1}/{2} ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-scanning-files">Analisi delle librerie: {1} file...</message>
<message id="Lms.Admin.ScannerController.step-status">Stato passo</message>
<message id="Lms.Admin.ScannerController.step-updating-library-fields">Aggiornamento dei campi della libreria: {1} voci</message>
<!--Debug Tools-->
<message id="Lms.Admin.DebugTools.debug-tools">Strumenti di debug</message>
<!--Db-->
<message id="Lms.Admin.DebugTools.Db.export-query-plans">Esporta i piani di query</message>
<message id="Lms.Admin.DebugTools.Db.db">Database</message>
<!--Tracing-->
<message id="Lms.Admin.Tracing.export-current-buffer">Esporta tracce</message>
<message id="Lms.Admin.Tracing.tracing">Tracing</message>
<message id="Lms.Admin.DebugTools.Tracing.export-current-buffer">Esporta tracce</message>
<message id="Lms.Admin.DebugTools.Tracing.tracing">Tracing</message>
<!--Users-->
<message id="Lms.Admin.Users.add">Nuovo utente</message>
+15 -9
View File
@@ -53,10 +53,10 @@
<message id="Lms.Error.user-not-found">Użytkownik nie odnaleziony</message>
<!--Administration-->
<message id="Lms.Admin.menu-debug-tools"><i class="fa fa-fw fa-bug" aria-hidden="true"></i> Narzędzia debugowania</message>
<message id="Lms.Admin.menu-media-libraries"><i class="fa fa-fw fa-database" aria-hidden="true"></i> Biblioteki</message>
<message id="Lms.Admin.menu-scan-settings"><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> Ustawienia skanowania</message>
<message id="Lms.Admin.menu-scanner"><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> Skaner</message>
<message id="Lms.Admin.menu-tracing"><i class="fa fa-fw fa-bar-chart" aria-hidden="true"></i> Śledzenie</message>
<message id="Lms.Admin.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> Użytkownicy</message>
<!--MediaLibraries-->
@@ -152,23 +152,29 @@
<message id="Lms.Admin.ScannerController.step-checking-for-removed-files">Sprawdzanie plików... {1}%</message>
<message id="Lms.Admin.ScannerController.step-compact">Prasowanie bazy danych...</message>
<message id="Lms.Admin.ScannerController.step-compute-cluster-stats">Obliczanie statystyk... {1}%</message>
<message id="Lms.Admin.ScannerController.step-discovering-files">
<plural case="0">Odkrywanie plików: {1} plik</plural>
<plural case="1">Odkrywanie plików: {1} pliki</plural>
<plural case="2">Odkrywanie plików: {1} plików</plural>
</message>
<message id="Lms.Admin.ScannerController.step-fetching-track-features">Pobieranie danych o ścieżce z AcousticBrainz: {1}/{2} ścieżek ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-optimize">Optymalizowanie bazy danych... {1}%...</message>
<message id="Lms.Admin.ScannerController.step-reconciliate-artists">Uzgodnianie artystów: {1} wpisów...</message>
<message id="Lms.Admin.ScannerController.step-reloading-similarity-engine">Przeładowywanie silnika podobieństw: {1}%...</message>
<message id="Lms.Admin.ScannerController.step-removing-orphaned-entries">Usuwanie osieroconych wpisów: {1} wpisów...</message>
<message id="Lms.Admin.ScannerController.step-scanning-files">Skanowanie plików: {1}/{2} ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-scanning-files">
<plural case="0">Analiza bibliotek: {1} plik</plural>
<plural case="1">Analiza bibliotek: {1} pliki</plural>
<plural case="2">Analiza bibliotek: {1} plików</plural>
</message>
<message id="Lms.Admin.ScannerController.step-status">Obecny krok</message>
<message id="Lms.Admin.ScannerController.step-updating-library-fields">Aktualizowanie pól biblioteki: {1} wpisów</message>
<!--Debug Tools-->
<message id="Lms.Admin.DebugTools.debug-tools">Narzędzia debugowania</message>
<!--Db-->
<message id="Lms.Admin.DebugTools.Db.export-query-plans">Eksportuj plany zapytań</message>
<message id="Lms.Admin.DebugTools.Db.db">Baza danych</message>
<!--Tracing-->
<message id="Lms.Admin.Tracing.export-current-buffer">Eksportuj ślady</message>
<message id="Lms.Admin.Tracing.tracing">Śledzenie</message>
<message id="Lms.Admin.DebugTools.Tracing.export-current-buffer">Eksportuj ślady</message>
<message id="Lms.Admin.DebugTools.Tracing.tracing">Śledzenie</message>
<!--Users-->
<message id="Lms.Admin.Users.add">Nowy użytkownik</message>
+7 -2
View File
@@ -137,15 +137,20 @@
<message id="Lms.Admin.ScannerController.step-checking-for-removed-files">检查文件中... {1}%</message>
<message id="Lms.Admin.ScannerController.step-discovering-files">检索文件中: {1} 文件</message>
<message id="Lms.Admin.ScannerController.step-fetching-track-features">从 AcousticBrainz 获取音轨特征: {1}/{2} 音轨 ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-reloading-similarity-engine">重载相似引擎中 {1}%...</message>
<message id="Lms.Admin.ScannerController.step-scanning-files">扫描文件中: {1}/{2} 个文件 ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-status">当前步骤状态</message>
<!--Debug Tools-->
<!--Db-->
<!--Tracing-->
+6 -2
View File
@@ -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;
# 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;
-1
View File
@@ -28,7 +28,6 @@ extern "C"
}
#include <array>
#include <map>
#include <unordered_map>
#include "core/ILogger.hpp"
+1
View File
@@ -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
+129
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include "JobScheduler.hpp"
#include <boost/asio/post.hpp>
#include "core/ITraceLogger.hpp"
#include "core/IJob.hpp"
namespace lms::core
{
std::unique_ptr<IJobScheduler> createJobScheduler(core::LiteralString name, std::size_t threadCount)
{
return std::make_unique<JobScheduler>(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<IJob> 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<std::unique_ptr<IJob>>& 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
+61
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <atomic>
#include <condition_variable>
#include <deque>
#include <mutex>
#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<IJob> job) override;
std::size_t getJobsDoneCount() const override;
size_t popJobsDone(std::vector<std::unique_ptr<IJob>>& 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<std::size_t> _ongoingJobCount;
std::deque<std::unique_ptr<IJob>> _doneJobs;
std::condition_variable _condVar;
};
} // namespace lms::core
-107
View File
@@ -20,45 +20,13 @@
#include "core/Path.hpp"
#include <array>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <system_error>
#include <unistd.h>
#include <boost/tokenizer.hpp>
#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<char, 1024> buffer;
ifs.read(buffer.data(), buffer.size());
crc32.processBytes(reinterpret_cast<const std::byte*>(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<bool(std::error_code, const std::filesystem::path&)> 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<const std::filesystem::path> supportedExtensions)
{
const std::filesystem::path extension{ stringUtils::stringToLower(file.extension().c_str()) };
@@ -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
@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <functional>
#include <memory>
#include <vector>
#include "core/LiteralString.hpp"
namespace lms::core
{
class IJob;
class IJobScheduler
{
public:
virtual ~IJobScheduler() = default;
using ShouldAbortCallback = std::function<bool()>;
virtual void setShouldAbortCallback(ShouldAbortCallback callback) = 0;
virtual std::size_t getThreadCount() const = 0;
virtual void scheduleJob(std::unique_ptr<IJob> job) = 0;
virtual std::size_t getJobsDoneCount() const = 0;
virtual size_t popJobsDone(std::vector<std::unique_ptr<IJob>>& jobs, std::size_t maxCount) = 0;
virtual void waitUntilJobCountAtMost(std::size_t maxOngoingJobs) = 0;
virtual void wait() = 0;
};
std::unique_ptr<IJobScheduler> createJobScheduler(core::LiteralString name, std::size_t threadCount);
} // namespace lms::core
+7
View File
@@ -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)
-10
View File
@@ -20,26 +20,16 @@
#pragma once
#include <filesystem>
#include <functional>
#include <span>
#include <system_error>
#include <Wt/WDateTime.h>
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<bool(std::error_code, const std::filesystem::path&)> 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<const std::filesystem::path> extensions);
+1
View File
@@ -2,6 +2,7 @@ include(GoogleTest)
add_executable(test-core
EnumSet.cpp
JobScheduler.cpp
LiteralString.cpp
PartialDateTime.cpp
Path.cpp
+90
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include <gtest/gtest.h>
#include <thread>
#include "core/IJob.hpp"
#include "core/IJobScheduler.hpp"
namespace lms::core
{
namespace
{
class TestJob : public IJob
{
public:
TestJob(std::atomic<std::size_t>& 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<std::size_t>& workCount;
};
} // namespace
TEST(JobScheduler, basic)
{
std::atomic<std::size_t> workCount{ 0 };
auto scheduler{ createJobScheduler("TestScheduler", 2) };
ASSERT_NE(scheduler, nullptr);
for (int i = 0; i < 10; ++i)
scheduler->scheduleJob(std::make_unique<TestJob>(workCount));
// Wait for all jobs to complete
scheduler->wait();
EXPECT_EQ(workCount.load(), 10);
std::vector<std::unique_ptr<IJob>> doneJobs;
scheduler->popJobsDone(doneJobs, 10);
EXPECT_EQ(doneJobs.size(), 10);
}
TEST(JobScheduler, abort)
{
std::atomic<std::size_t> 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<TestJob>(workCount));
// Wait for all jobs to complete
scheduler->wait();
EXPECT_EQ(workCount.load(), 0); // nothing was done
std::vector<std::unique_ptr<IJob>> doneJobs;
scheduler->popJobsDone(doneJobs, 10);
EXPECT_EQ(doneJobs.size(), 0);
}
} // namespace lms::core
+33 -29
View File
@@ -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
)
+98 -7
View File
@@ -17,8 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/Db.hpp"
#include <functional>
#include <memory>
@@ -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<int> getPageSize(Wt::Dbo::SqlConnection& connection)
{
auto statement = connection.prepareStatement("PRAGMA page_size");
statement->execute();
std::optional<int> res;
while (statement->nextRow())
{
assert(!res);
int value{};
if (statement->getResult(0, &value))
res = value;
break;
}
return res;
}
std::optional<int> getCacheSize(Wt::Dbo::SqlConnection& connection)
{
auto statement = connection.prepareStatement("PRAGMA cache_size");
statement->execute();
std::optional<int> 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<void(std::string_view compileOption)> 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<int>(res.capacity())))
callback(res);
}
}
} // namespace
std::unique_ptr<IDb> createDb(const std::filesystem::path& dbPath, std::size_t connectionCount)
{
return std::make_unique<Db>(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<int> pageSize{ getPageSize(*connection) };
if (pageSize)
LMS_LOG(DB, INFO, "Page size set to " << *pageSize);
}
void Db::logCacheSize()
{
ScopedConnection connection{ *_connectionPool };
const std::optional<int> 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 << "'");
}) };
@@ -19,21 +19,22 @@
#pragma once
#include <filesystem>
#include <mutex>
#include <string>
#include <vector>
#include <Wt/Dbo/SqlConnectionPool.h>
#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();
+46 -5
View File
@@ -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<Db&>(session.getDb()) };
using MigrationFunction = std::function<void(Session&)>;
@@ -1456,6 +1493,10 @@ FROM artist)");
{ 91, migrateFromV91 },
{ 92, migrateFromV92 },
{ 93, migrateFromV93 },
{ 94, migrateFromV94 },
{ 95, migrateFromV95 },
{ 96, migrateFromV96 },
{ 97, migrateFromV97 },
};
bool migrationPerformed{};
+32
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#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
@@ -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 <http://www.gnu.org/licenses/>.
*/
#include "QueryPlanRecorder.hpp"
#include <memory>
#include <mutex>
#include <Wt/Dbo/SqlStatement.h>
#include <Wt/Dbo/Transaction.h>
#include "core/ILogger.hpp"
namespace lms::db
{
std::unique_ptr<IQueryPlanRecorder> createQueryPlanRecorder()
{
return std::make_unique<QueryPlanRecorder>();
}
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<int, std::string> entries{ { 0, "" } };
std::map<int, std::vector<int>> 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<int>(detail.capacity())))
{
entries.emplace(id, detail);
relationships[parent].push_back(id);
}
}
// format
std::string result;
std::function<void(int, unsigned)> 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
@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <map>
#include <shared_mutex>
#include <string>
#include <Wt/Dbo/Session.h>
#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<std::string, std::string> _queryPlans;
};
} // namespace lms::db
+78 -102
View File
@@ -19,41 +19,44 @@
#include "database/Session.hpp"
#include <Wt/Dbo/WtSqlTraits.h>
#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&>(_db).getConnectionPool());
_session.mapClass<Artist>("artist");
_session.mapClass<ArtistInfo>("artist_info");
@@ -140,7 +109,7 @@ namespace lms::db
WriteTransaction Session::createWriteTransaction()
{
return WriteTransaction{ _db.getMutex(), _session };
return WriteTransaction{ static_cast<Db&>(_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_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&>(_db).getMutex() };
static_cast<Db&>(_db).executeSql("VACUUM");
}
LMS_LOG(DB, INFO, "Vacuum complete!");
}
void Session::refreshTracingLoggerStats()
{
auto* traceLogger{ core::Service<core::tracing::ITraceLogger>::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<std::string> entryList{ utils::fetchQueryResults(_session.query<std::string>("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<std::string>& 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
+63
View File
@@ -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 <http://www.gnu.org/licenses/>.
*/
#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
+12 -12
View File
@@ -17,7 +17,7 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#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<StackEntry> 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());
}
@@ -26,8 +26,11 @@
#endif
#if LMS_CHECK_TRANSACTION_ACCESSES
#include <Wt/Dbo/Session.h>
#include <vector>
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
+34 -6
View File
@@ -19,28 +19,46 @@
#pragma once
#include <functional>
#include <string>
#include <string_view>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/Call.h>
#include <Wt/Dbo/Query.h>
#include <Wt/Dbo/Session.h>
#include <Wt/Dbo/collection.h>
#include <Wt/WDateTime.h>
#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<typename Query>
void recordQueryPlanIfNeeded(const Query& query)
{
if (IQueryPlanRecorder * recorder{ core::Service<IQueryPlanRecorder>::get() })
static_cast<QueryPlanRecorder*>(recorder)->recordQueryPlanIfNeeded(query.session(), query.asString());
}
} // namespace details
template<typename Query>
void applyRange(Query& query, std::optional<Range> range)
{
if (range)
{
query.limit(static_cast<int>(range->size));
if (range->offset != 0)
query.offset(static_cast<int>(range->offset));
}
}
@@ -82,13 +100,18 @@ namespace lms::db::utils
template<typename Query, typename UnaryFunc>
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<UnaryFunc>(func));
}
template<typename T, typename Query>
std::vector<T> 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<typename Query>
std::vector<typename QueryResultType<Query>::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<typename Query>
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<typename... Args>
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
@@ -16,22 +16,28 @@
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/Artist.hpp"
#include "database/objects/Artist.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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<ArtistId>& idRange, const std::function<void(const Artist::pointer&)>& func)
{
assert(idRange.isValid());
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Artist>>("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::pointer> Artist::find(Session& session, std::string_view name)
{
session.checkReadTransaction();
return utils::fetchQueryResults<Artist::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Artist>>("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<Artist::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Artist>>("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<ArtistId> Artist::findNextIdRange(Session& session, ArtistId lastRetrievedId, std::size_t count)
{
auto query{ session.getDboSession()->query<std::tuple<ArtistId, ArtistId>>("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<int>(count));
auto res{ utils::fetchQuerySingleResult(query) };
return IdRange<ArtistId>{ .first = std::get<0>(res), .last = std::get<1>(res) };
}
RangeResults<ArtistId> Artist::findOrphanIds(Session& session, std::optional<Range> range)
{
session.checkReadTransaction();
@@ -304,6 +334,16 @@ AND NOT EXISTS (
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("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<core::UUID> 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> artwork)
@@ -17,19 +17,22 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/ArtistInfo.hpp"
#include "database/objects/ArtistInfo.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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<void(ArtistInfoId artistInfoId, const std::filesystem::path& absoluteFilePath)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<std::tuple<ArtistInfoId, std::filesystem::path>>("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<int>(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;
@@ -17,16 +17,20 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/Artwork.hpp"
#include "database/objects/Artwork.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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<std::filesystem::path>("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
@@ -17,17 +17,20 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/AuthToken.hpp"
#include "database/objects/AuthToken.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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<long> maxUseCount, ObjectPtr<User> user)
@@ -17,22 +17,29 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/Cluster.hpp"
#include "database/objects/Cluster.hpp"
#include <Wt/Dbo/Impl.h>
#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<int>("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<int>("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> track)
@@ -17,16 +17,20 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/Directory.hpp"
#include "database/objects/Directory.hpp"
#include <Wt/Dbo/Impl.h>
#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
@@ -17,18 +17,21 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/Image.hpp"
#include "database/objects/Image.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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<Wt::Dbo::ptr<Image>>("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<void(const Image::pointer&)>& func)
void Image::find(Session& session, ImageId& lastRetrievedId, std::size_t count, const std::function<void(const Image::pointer&)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Image>>("SELECT i from image i").orderBy("i.id").where("i.id > ?").bind(lastRetrievedImage).limit(static_cast<int>(count)) };
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Image>>("SELECT i from image i").orderBy("i.id").where("i.id > ?").bind(lastRetrievedId).limit(static_cast<int>(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<void(ImageId imageId, const std::filesystem::path& absoluteFilePath)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<std::tuple<ImageId, std::filesystem::path>>("SELECT i.id,i.absolute_file_path from image i").orderBy("i.id").where("i.id > ?").bind(lastRetrievedId).limit(static_cast<int>(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());
@@ -17,15 +17,21 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/Listen.hpp"
#include "database/objects/Listen.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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
@@ -17,17 +17,20 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/MediaLibrary.hpp"
#include "database/objects/MediaLibrary.hpp"
#include <Wt/Dbo/Impl.h>
#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)
@@ -17,22 +17,26 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/PlayListFile.hpp"
#include "database/objects/PlayListFile.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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<void(PlayListFileId playListFileId, const std::filesystem::path& absoluteFilePath)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<std::tuple<PlayListFileId, std::filesystem::path>>("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<int>(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<PlayListFileId>& idRange, const std::function<void(const PlayListFile::pointer&)>& func)
{
assert(idRange.isValid());
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<PlayListFile>>("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<PlayListFileId> PlayListFile::findNextIdRange(Session& session, PlayListFileId lastRetrievedId, std::size_t count)
{
auto query{ session.getDboSession()->query<std::tuple<PlayListFileId, PlayListFileId>>("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<int>(count));
auto res{ utils::fetchQuerySingleResult(query) };
return IdRange<PlayListFileId>{ .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<const std::filesystem::path> files)
@@ -17,22 +17,31 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/PlayQueue.hpp"
#include "database/objects/PlayQueue.hpp"
#include <Wt/Dbo/SqlTraits.h>
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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>& user, std::string_view name)
@@ -17,17 +17,20 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/RatedArtist.hpp"
#include "database/objects/RatedArtist.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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> artist, ObjectPtr<User> user)
@@ -17,17 +17,20 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/RatedRelease.hpp"
#include "database/objects/RatedRelease.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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> release, ObjectPtr<User> user)
@@ -17,17 +17,20 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/RatedTrack.hpp"
#include "database/objects/RatedTrack.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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> track, ObjectPtr<User> user)
@@ -17,19 +17,25 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/Release.hpp"
#include "database/objects/Release.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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<ReleaseId>& idRange, const std::function<void(const Release::pointer&)>& func)
{
assert(idRange.isValid());
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Release>>("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<ReleaseId> Release::findNextIdRange(Session& session, ReleaseId lastRetrievedId, std::size_t count)
{
auto query{ session.getDboSession()->query<std::tuple<ReleaseId, ReleaseId>>("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<int>(count));
auto res{ utils::fetchQuerySingleResult(query) };
return IdRange<ReleaseId>{ .first = std::get<0>(res), .last = std::get<1>(res) };
}
RangeResults<Release::pointer> Release::find(Session& session, const FindParameters& params)
{
session.checkReadTransaction();
@@ -528,6 +560,16 @@ namespace lms::db
return utils::execRangeQuery<ReleaseId>(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();
@@ -17,18 +17,21 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/ScanSettings.hpp"
#include "database/objects/ScanSettings.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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)
@@ -17,17 +17,20 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/StarredArtist.hpp"
#include "database/objects/StarredArtist.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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> artist, ObjectPtr<User> user, FeedbackBackend backend)
@@ -17,17 +17,20 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/StarredRelease.hpp"
#include "database/objects/StarredRelease.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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> release, ObjectPtr<User> user, FeedbackBackend backend)
@@ -17,17 +17,20 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/StarredTrack.hpp"
#include "database/objects/StarredTrack.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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> track, ObjectPtr<User> user, FeedbackBackend backend)
@@ -17,24 +17,25 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/Track.hpp"
#include "database/objects/Track.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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<Wt::Dbo::ptr<Track>>("SELECT t from track t").where("t.absolute_file_path = ?").bind(p));
}
std::optional<FileInfo> Track::findFileInfo(Session& session, const std::filesystem::path& p)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<std::tuple<int, Wt::WDateTime>>("SELECT t.scan_version, t.file_last_write FROM track t WHERE t.absolute_file_path = ?") };
query.bind(p);
std::optional<FileInfo> 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<Wt::Dbo::ptr<Track>>("SELECT t from track t").where("t.id = ?").bind(id));
}
void Track::find(Session& session, TrackId& lastRetrievedTrack, std::size_t count, const std::function<void(const Track::pointer&)>& func, MediaLibraryId library)
void Track::find(Session& session, TrackId& lastRetrievedId, std::size_t count, const std::function<void(const Track::pointer&)>& func, MediaLibraryId library)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t").orderBy("t.id").where("t.id > ?").bind(lastRetrievedTrack).limit(static_cast<int>(count)) };
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t").orderBy("t.id").where("t.id > ?").bind(lastRetrievedId).limit(static_cast<int>(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<void(TrackId trackId, const std::filesystem::path& absoluteFilePath)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<std::tuple<TrackId, std::filesystem::path>>("SELECT t.id,t.absolute_file_path from track t").orderBy("t.id").where("t.id > ?").bind(lastRetrievedId).limit(static_cast<int>(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<TrackId>& idRange, const std::function<void(const Track::pointer&)>& func)
{
assert(idRange.isValid());
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Track>>("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<TrackId> Track::findNextIdRange(Session& session, TrackId lastRetrievedId, std::size_t count)
{
auto query{ session.getDboSession()->query<std::tuple<TrackId, TrackId>>("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<int>(count));
auto res{ utils::fetchQuerySingleResult(query) };
return IdRange<TrackId>{ .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()
@@ -17,15 +17,20 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/TrackArtistLink.hpp"
#include "database/objects/TrackArtistLink.hpp"
#include "database/Artist.hpp"
#include <Wt/Dbo/Impl.h>
#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
@@ -17,15 +17,19 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/TrackBookmark.hpp"
#include "database/objects/TrackBookmark.hpp"
#include <Wt/Dbo/Impl.h>
#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> user, ObjectPtr<Track> track)
@@ -17,17 +17,22 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/Directory.hpp"
#include "database/objects/TrackEmbeddedImage.hpp"
#include <Wt/Dbo/Impl.h>
#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)
@@ -17,15 +17,21 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/TrackEmbeddedImageLink.hpp"
#include "database/objects/TrackEmbeddedImageLink.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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
{
@@ -17,19 +17,22 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/TrackFeatures.hpp"
#include "database/objects/TrackFeatures.hpp"
#include <Wt/Dbo/Impl.h>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#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
{
@@ -16,21 +16,27 @@
* You should have received a copy of the GNU General Public License
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/TrackList.hpp"
#include "database/objects/TrackList.hpp"
#include <cassert>
#include "database/Cluster.hpp"
#include "database/PlayListFile.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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
@@ -17,21 +17,23 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/TrackLyrics.hpp"
#include "database/objects/TrackLyrics.hpp"
#include <Wt/Dbo/SqlTraits.h>
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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<TrackLyricsId>(query, range);
}
void TrackLyrics::findAbsoluteFilePath(Session& session, TrackLyricsId& lastRetrievedId, std::size_t count, const std::function<void(TrackLyricsId trackLyricsId, const std::filesystem::path& absoluteFilePath)>& func)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<std::tuple<TrackLyricsId, std::filesystem::path>>("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<int>(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;
@@ -17,15 +17,19 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/UIState.hpp"
#include "database/objects/UIState.hpp"
#include <Wt/Dbo/Impl.h>
#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> user)
@@ -17,21 +17,27 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
#include "database/User.hpp"
#include "database/objects/User.hpp"
#include <cassert>
#include "database/Artist.hpp"
#include "database/Release.hpp"
#include <Wt/Dbo/Impl.h>
#include <Wt/Dbo/WtSqlTraits.h>
#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)
@@ -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 <filesystem>
#include "database/ArtworkId.hpp"
#include "database/ImageId.hpp"
#include "database/Object.hpp"
#include "database/TrackEmbeddedImageId.hpp"
#include <memory>
namespace lms::db
{
class Artwork;
class Session;
} // namespace lms::db
namespace lms::scanner::utils
class IDb
{
db::ObjectPtr<db::Artwork> getOrCreateArtworkFromTrackEmbeddedImage(db::Session& session, db::TrackEmbeddedImageId trackEmbeddedImageId);
db::ObjectPtr<db::Artwork> getOrCreateArtworkFromImage(db::Session& session, db::ImageId imageId);
public:
virtual ~IDb() = default;
std::filesystem::path toPath(db::Session& session, db::ArtworkId artworkId);
} // namespace lms::scanner::utils
virtual Session& getTLSSession() = 0;
};
std::unique_ptr<IDb> createDb(const std::filesystem::path& dbPath, std::size_t connectionCount = 10);
} // namespace lms::db
@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <functional>
#include <memory>
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<void(std::string_view query, std::string_view plan)>;
virtual void visitQueryPlans(const QueryPlanVisitor& visitor) const = 0;
};
std::unique_ptr<IQueryPlanRecorder> createQueryPlanRecorder();
} // namespace lms::db
@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
namespace lms::db
{
template<typename IdType>
struct IdRange
{
public:
IdType first;
IdType last;
bool isValid() const { return first.isValid() && last.isValid(); }
};
} // namespace lms::db
+9 -10
View File
@@ -20,15 +20,19 @@
#pragma once
#include <Wt/Dbo/ptr.h>
#include <Wt/WSignal.h>
#include "database/IdType.hpp"
#include "database/TransactionChecker.hpp"
namespace lms::db
{
class ObjectPtrBase
{
protected:
static void checkWriteTransaction(Wt::Dbo::Session& session);
};
template<typename T>
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();
}
+33 -62
View File
@@ -19,56 +19,23 @@
#pragma once
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/SqlConnectionPool.h>
#include <Wt/Dbo/Session.h>
#include <span>
#include <string>
#include <string_view>
#include <vector>
#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<core::RecursiveSharedMutex> _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, typename... Args>
typename Object::pointer create(Args&&... args)
@@ -126,8 +79,26 @@ namespace lms::db
return res;
}
template<typename Object>
void destroy(typename Object::IdType id)
{
destroy(std::span{ &id, 1 });
}
template<typename Object>
void destroy(std::span<const typename Object::IdType> ids)
{
checkWriteTransaction();
const std::string query{ std::string{ "DELETE FROM " } + _session.tableName<Object>() + " 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
@@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <mutex>
#include <Wt/Dbo/Transaction.h>
#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<core::RecursiveSharedMutex> _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
+18 -1
View File
@@ -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
@@ -25,21 +25,23 @@
#include <string_view>
#include <vector>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/Field.h>
#include <Wt/Dbo/collection.h>
#include <Wt/WDateTime.h>
#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<Artist, ArtistId>
{
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<pointer> 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<void(const Artist::pointer&)>& func, MediaLibraryId library = {});
static void find(Session& session, const IdRange<ArtistId>& idRange, const std::function<void(const Artist::pointer&)>& func);
static RangeResults<pointer> find(Session& session, const FindParameters& params);
static void find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func);
static IdRange<ArtistId> findNextIdRange(Session& session, ArtistId lastRetrievedId, std::size_t count);
static RangeResults<ArtistId> findIds(Session& session, const FindParameters& params);
static RangeResults<ArtistId> findOrphanIds(Session& session, std::optional<Range> 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<core::UUID>& MBID = {});
@@ -24,14 +24,14 @@
#include <string>
#include <string_view>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/Field.h>
#include <Wt/WDateTime.h>
#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<void(const pointer&)>& func);
static void findArtistNameNoLongerMatch(Session& session, std::optional<Range> range, const std::function<void(const pointer&)>& func);
static void findWithArtistNameAmbiguity(Session& session, std::optional<Range> range, bool allowArtistMBIDFallback, const std::function<void(const pointer&)>& func);
static void findAbsoluteFilePath(Session& session, ArtistInfoId& lastRetrievedId, std::size_t count, const std::function<void(ArtistInfoId artistInfoId, const std::filesystem::path& absoluteFilePath)>& func);
// getters
std::size_t getScanVersion() const { return _scanVersion; }
@@ -19,13 +19,15 @@
#pragma once
#include <Wt/Dbo/Dbo.h>
#include <filesystem>
#include <Wt/Dbo/Field.h>
#include <Wt/WDateTime.h>
#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<class Action>
void persist(Action& a)
@@ -22,13 +22,12 @@
#include <optional>
#include <string_view>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/Field.h>
#include <Wt/WDateTime.h>
#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
{
@@ -22,17 +22,17 @@
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <vector>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/Field.h>
#include <Wt/Dbo/collection.h>
#include <Wt/WDateTime.h>
#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
{
@@ -21,19 +21,20 @@
#include <filesystem>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
#include <vector>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/Field.h>
#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
{
@@ -22,10 +22,10 @@
#include <span>
#include <vector>
#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
{
@@ -21,14 +21,15 @@
#include <filesystem>
#include <functional>
#include <optional>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/Field.h>
#include <Wt/WDateTime.h>
#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<pointer> find(Session& session, const FindParameters& params);
static void find(Session& session, const FindParameters& parameters, const std::function<void(const Image::pointer&)>& func);
static void find(Session& session, ImageId& lastRetrievedImage, std::size_t count, const std::function<void(const Image::pointer&)>& func);
static void find(Session& session, ImageId& lastRetrievedId, std::size_t count, const std::function<void(const Image::pointer&)>& func);
static void findAbsoluteFilePath(Session& session, ImageId& lastRetrievedId, std::size_t count, const std::function<void(ImageId imageId, const std::filesystem::path& absoluteFilePath)>& func);
// getters
const std::filesystem::path& getAbsoluteFilePath() const { return _fileAbsolutePath; }
@@ -21,17 +21,17 @@
#include <optional>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/Field.h>
#include <Wt/WDateTime.h>
#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
{
@@ -20,13 +20,14 @@
#pragma once
#include <filesystem>
#include <functional>
#include <string>
#include <string_view>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/Field.h>
#include "database/MediaLibraryId.hpp"
#include "database/Object.hpp"
#include "database/objects/MediaLibraryId.hpp"
namespace lms::db
{
@@ -24,11 +24,14 @@
#include <string>
#include <string_view>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/Field.h>
#include <Wt/Dbo/collection.h>
#include <Wt/Dbo/weak_ptr.h>
#include <Wt/WDateTime.h>
#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<void(const pointer&)>& func);
static void findAbsoluteFilePath(Session& session, PlayListFileId& lastRetrievedId, std::size_t count, const std::function<void(PlayListFileId playListFileId, const std::filesystem::path& absoluteFilePath)>& func);
static void find(Session& session, const IdRange<PlayListFileId>& idRange, const std::function<void(const PlayListFile::pointer&)>& func);
static IdRange<PlayListFileId> findNextIdRange(Session& session, PlayListFileId lastRetrievedId, std::size_t count);
// getters
const std::filesystem::path& getAbsoluteFilePath() const { return _absoluteFilePath; }
@@ -20,17 +20,16 @@
#pragma once
#include <chrono>
#include <span>
#include <string>
#include <string_view>
#include <Wt/Dbo/Dbo.h>
#include <Wt/Dbo/Field.h>
#include <Wt/Dbo/collection.h>
#include <Wt/WDateTime.h>
#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)
@@ -19,14 +19,16 @@
#pragma once
#include <Wt/Dbo/Dbo.h>
#include <optional>
#include <Wt/Dbo/Field.h>
#include <Wt/WDateTime.h>
#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
{
@@ -19,14 +19,16 @@
#pragma once
#include <Wt/Dbo/Dbo.h>
#include <optional>
#include <Wt/Dbo/Field.h>
#include <Wt/WDateTime.h>
#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
{
@@ -19,14 +19,16 @@
#pragma once
#include <Wt/Dbo/Dbo.h>
#include <optional>
#include <Wt/Dbo/Field.h>
#include <Wt/WDateTime.h>
#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
{

Some files were not shown because too many files have changed in this diff Show More