diff --git a/.clang-format b/.clang-format new file mode 100644 index 000000000..bb47648f4 --- /dev/null +++ b/.clang-format @@ -0,0 +1,30 @@ +Language: Cpp +BasedOnStyle: Microsoft +Standard: c++20 +AccessModifierOffset: -4 +AlignAfterOpenBracket: DontAlign +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignOperands: AlignAfterOperator +AlignTrailingComments: true +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortFunctionsOnASingleLine: InlineOnly +AllowShortIfStatementsOnASingleLine: true +AlwaysBreakTemplateDeclarations: Yes +BraceWrapping: + AfterUnion: true +BreakBeforeBinaryOperators: All +BreakBeforeTernaryOperators: false +BreakConstructorInitializers: BeforeComma +ColumnLimit: 0 +ConstructorInitializerAllOnOneLineOrOnePerLine: true +Cpp11BracedListStyle: false +IndentCaseBlocks: true +IndentPPDirectives: BeforeHash +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +PointerAlignment: Left +SortIncludes: true +SpaceAfterTemplateKeyword: false \ No newline at end of file diff --git a/.github/workflows/clang-format-check.yml b/.github/workflows/clang-format-check.yml new file mode 100644 index 000000000..60f10a421 --- /dev/null +++ b/.github/workflows/clang-format-check.yml @@ -0,0 +1,13 @@ +name: clang-format Check +on: [push, pull_request] +jobs: + formatting-check: + name: Formatting Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Run clang-format style check + uses: jidicula/clang-format-action@v4.13.0 + with: + clang-format-version: '13' + check-path: 'src' \ No newline at end of file diff --git a/INSTALL.md b/INSTALL.md index b97d2fe60..7cb0abe51 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -33,7 +33,7 @@ Please refer to [Deployment](#deployment) for further configuration options. __Note__: this installation process and the default values of the configuration files have been written for _Debian Bookworm_. Therefore, you may have to adapt commands and/or paths in order to fit to your distribution. ### Build dependencies __Notes__: -* a C++17 compiler is needed +* a C++20 compiler is needed * ffmpeg version 4 minimum is required ```sh apt-get install g++ cmake libboost-program-options-dev libboost-system-dev libavutil-dev libavformat-dev libstb-dev libconfig++-dev ffmpeg libtag1-dev libpam0g-dev libgtest-dev libarchive-dev @@ -41,7 +41,7 @@ apt-get install g++ cmake libboost-program-options-dev libboost-system-dev libav __Notes__: * libpam0g-dev is optional (only for using PAM authentication) * libstb-dev can be replaced by libgraphicsmagick++1-dev (the latter will likely use more RAM) -You also need _Wt4_, which is not packaged yet on _Debian_. See [installation instructions](https://www.webtoolkit.eu/wt/doc/reference/html/InstallationUnix.html).
+You also need _Wt4_, which is not packaged on _Debian_. See [installation instructions](https://www.webtoolkit.eu/wt/doc/reference/html/InstallationUnix.html).
No optional requirement is needed, except openSSL if you plan not to deploy behind a reverse proxy (which is not recommended). ### Build Get the latest stable release and build it: diff --git a/approot/admin-scansettings.xml b/approot/admin-scansettings.xml index 0e760a7a7..17c244af9 100644 --- a/approot/admin-scansettings.xml +++ b/approot/admin-scansettings.xml @@ -36,35 +36,39 @@
- ${extra-tags-to-scan class="form-control"} -
- ${extra-tags-to-scan-info} -
+ ${extra-tags-to-scan-container class="row gy-3"}
-
-
${save-btn class="btn btn-primary me-1"}${discard-btn class="btn btn-secondary"}
-
+ + + +
+ ${value class="form-control"} + ${del-btn class="btn btn-sm btn-outline-warning"} +
+ ${value-info} +
+
+
diff --git a/approot/messages.xml b/approot/messages.xml index e5cf23b41..0de665d51 100644 --- a/approot/messages.xml +++ b/approot/messages.xml @@ -65,10 +65,10 @@ Root directory -Delimiter to be used for splitting artist tags +Delimiters to be used for splitting artist tags Daily -Delimiter to be used for splitting other tags -Extra tags to scan (use ; as separator) +Delimiters to be used for splitting other tags +Extra tags to scan Hourly Scan now! Monthly @@ -87,7 +87,8 @@ Cannot get track duration -Cannot parse file +Cannot parse audio file +Cannot parse image file Cannot read file Compact the database. Caution: this may take a while and will block the whole application during the compact step! {1} duplicate files: @@ -108,15 +109,17 @@ Not scheduled Scheduled on {1} Scanning: step {1}/{2} +Associating artist images: {1}%... Checking for duplicate files... {1} files -Checking files... {1}% +Checking for removed files... {1}% Compacting database... Computing stats... {1}% Discovering files: {1} files Fetching track features from AcousticBrainz: {1}/{2} tracks ({3}%)... -Optimizing database... {1}/{2} entries ({3}%)... +Optimizing database... {1}%... Reloading similarity engine: {1}%... -Scanning files: {1}/{2} files ({3}%)... +Removing orphaned entries: {1} entries... +Scanning files: {1}/{2} ({3}%)... Step status diff --git a/approot/messages_fr.xml b/approot/messages_fr.xml index 54b33a6f7..d07e92ec9 100644 --- a/approot/messages_fr.xml +++ b/approot/messages_fr.xml @@ -65,10 +65,10 @@ Répertoire racine -Délimiteur à utiliser pour séparer les tags d'artistes +Délimiteurs à utiliser pour séparer les tags d'artistes Tous les jours -Délimiteur à utiliser pour séparer les autres tags -Tags supplémentaires à scanner (utiliser ; comme séparateur) +Délimiteurs à utiliser pour séparer les autres tags +Tags supplémentaires à scanner Toutes les heures Scanner maintenant ! Tous les mois @@ -87,7 +87,8 @@ Impossible de récupérer la durée de la piste -Impossible d'analyser le fichier +Impossible d'analyser le fichier audio +Impossible d'analyser le fichier image Impossible de lire le fichier Compacter la base de données. Attention : cette opération peut prendre du temps et va vérouiller l'application pendant toute l'étape de compactage! {1} fichiers dupliqués : @@ -108,15 +109,17 @@ Non planifié Planifié le {1} En cours de scan : étape {1}/{2} +Association des images des artistes: {1}%... Vérification des fichiers dupliqués... {1} fichiers -Vérification des fichiers... {1}% +Vérification des fichiers supprimés... {1}% Compactage de la base de données... Calcul des statistiques... {1}% Découverte des fichiers : {1} fichiers Récupération des métadonnées AcousticBrainz : {1}/{2} fichiers ({3}%)... -Optimisation de la base de données... {1}/{2} entrées ({3}%)... +Optimisation de la base de données... {1}%... Rechargement du moteur de recommandation : {1}%... -Scan des fichiers : {1}/{2} fichiers ({3}%)... +Retrait des entrées orphelines: {1} entrées... +Scan des fichiers : {1}/{2} ({3}%)... Statut de l'étape diff --git a/approot/messages_it.xml b/approot/messages_it.xml index 409b048c8..7f8b7fa6b 100644 --- a/approot/messages_it.xml +++ b/approot/messages_it.xml @@ -65,10 +65,10 @@ Cartella principale -Delimitatore da utilizzare per separare i tag degli artisti +Delimitatori da utilizzare per separare i tag degli artisti Giornaliera -Delimitatore da utilizzare per separare gli altri tag -Tag aggiuntivi da scansionare (usa ; come separatore) +Delimitatori da utilizzare per separare gli altri tag +Tag aggiuntivi da scansionare Ogni ora Scansiona ora! Mensile @@ -87,7 +87,8 @@ Non sono stato in grado di determinare la durata della traccia -Non in grado di analizzare il file +Impossibile analizzare il file audio +Impossibile analizzare il file immagine Non in grado di leggere il file Compatta il database. Attenzione: ciò potrebbe richiedere del tempo e bloccherà l'intera applicazione durante il passaggio di compattazione! {1} file duplicati: @@ -108,15 +109,17 @@ Non pianificato Pianificato il {1} Scansione: passo {1}/{2} +Associando immagini degli artisti: {1}%... Controllo duplicati... {1} files -Controllo file... {1}% +Controllo file... {1}% Compattazione del database... Calcolo statistiche... {1}% File trovati: {1} files Recupero metadati da AcousticBrainz: {1}/{2} tracce ({3}%)... -Ottimizzazione del database... {1}/{2} voci ({3}%)... +Ottimizzazione del database... {1}%... Ricarica motore di tracce simili: {1}%... -Scansione files: {1}/{2} files ({3}%)... +Rimozione voci orfane: {1} voci... +Scansione dei file: {1}/{2} ({3}%)... Stato passo diff --git a/approot/messages_pl.xml b/approot/messages_pl.xml index 2707e0b32..bf83a8af4 100644 --- a/approot/messages_pl.xml +++ b/approot/messages_pl.xml @@ -66,10 +66,10 @@ Katalog główny -Znak rozdzielający artystów +Znaki rozdzielające artystów Codziennie -Znak rozdzielający inne oznaczenia -Szukaj dodatkowych znaczników (użyj ; do rozdzielania) +Znaki rozdzielające inne oznaczenia +Szukaj dodatkowych znaczników Co godzinę Skanuj teraz! Co miesiąc @@ -88,7 +88,8 @@ Nie udało się ustalić długości ścieżki -Nie udało się przeparsować pliku +Nie można przeanalizować pliku audio +Nie można przeanalizować pliku obrazu Nie udało się odczytać pliku Sprasuj bazę danych. Uwaga: może to trochę zająć, a cała aplikacja będzie w tym czasie zablokowana! @@ -117,12 +118,13 @@ Nie zaplanowano Zaplanowano na {1} Skanowanie: krok {1}/{2} +Kojarzenie obrazów artystów: {1}%... Sprawdzanie duplikatów... {1} plik Sprawdzanie duplikatów... {1} pliki Sprawdzanie duplikatów... {1} plików -Sprawdzanie plików... {1}% +Sprawdzanie plików... {1}% Prasowanie bazy danych... Obliczanie statystyk... {1}% @@ -131,9 +133,10 @@ Odkrywanie plików: {1} plików Pobieranie danych o ścieżce z AcousticBrainz: {1}/{2} ścieżek ({3}%)... -Optymalizowanie bazy danych... {1}/{2} wpisów ({3}%)... +Optymalizowanie bazy danych... {1}%... Przeładowywanie silnika podobieństw: {1}%... -Skanowanie plików: {1}/{2} plików ({3}%)... +Usuwanie osieroconych wpisów: {1} wpisów... +Skanowanie plików: {1}/{2} ({3}%)... Obecny krok diff --git a/approot/messages_zh.xml b/approot/messages_zh.xml index 745cfe82b..c8608334b 100644 --- a/approot/messages_zh.xml +++ b/approot/messages_zh.xml @@ -87,7 +87,8 @@ 无法获得音轨时间 -无法解析文件 +无法解析文件 + 无法读取文件 {1} 个重复文件: @@ -109,13 +110,15 @@ 计划于 {1} 扫描中: 阶段 {1}/{2} -检查文件中... {1}% +检查文件中... {1}% + 检索文件中: {1} 文件 从 AcousticBrainz 获取音轨特征: {1}/{2} 音轨 ({3}%)... 重载相似引擎中 {1}%... + 扫描文件中: {1}/{2} 个文件 ({3}%)... 当前步骤状态 diff --git a/conf/lms.conf b/conf/lms.conf index 58d89bbb9..4fcca840e 100644 --- a/conf/lms.conf +++ b/conf/lms.conf @@ -87,7 +87,7 @@ cover-jpeg-quality = 75; cover-preferred-file-names = ("cover", "front"); # File names for artist images (order is important) -# Files whose name is the artist's MBID, then the artist's name, are searched before the names in this list +# Note: files whose name is the artist's MBID are always searched before the names in this list. You can place the MBID files anywhere in your libraries. artist-image-file-names = ("artist"); # Playqueue max entry count diff --git a/src/libs/av/impl/AudioFile.cpp b/src/libs/av/impl/AudioFile.cpp index 5075af501..31ad7267f 100644 --- a/src/libs/av/impl/AudioFile.cpp +++ b/src/libs/av/impl/AudioFile.cpp @@ -53,7 +53,8 @@ namespace lms::av public: AudioFileException(int avError) : Exception{ "AudioFileException: " + averror_to_string(avError) } - {} + { + } }; void getMetaDataFromDictionnary(AVDictionary* dictionnary, AudioFile::MetadataMap& res) @@ -72,27 +73,52 @@ namespace lms::av { switch (codec) { - case AV_CODEC_ID_MP3: return DecodingCodec::MP3; - case AV_CODEC_ID_AAC: return DecodingCodec::AAC; - case AV_CODEC_ID_AC3: return DecodingCodec::AC3; - case AV_CODEC_ID_VORBIS: return DecodingCodec::VORBIS; - case AV_CODEC_ID_WMAV1: return DecodingCodec::WMAV1; - case AV_CODEC_ID_WMAV2: return DecodingCodec::WMAV2; - case AV_CODEC_ID_FLAC: return DecodingCodec::FLAC; - case AV_CODEC_ID_ALAC: return DecodingCodec::ALAC; - case AV_CODEC_ID_WAVPACK: return DecodingCodec::WAVPACK; - case AV_CODEC_ID_MUSEPACK7: return DecodingCodec::MUSEPACK7; - case AV_CODEC_ID_MUSEPACK8: return DecodingCodec::MUSEPACK8; - case AV_CODEC_ID_APE: return DecodingCodec::APE; - case AV_CODEC_ID_EAC3: return DecodingCodec::EAC3; - case AV_CODEC_ID_MP4ALS: return DecodingCodec::MP4ALS; - case AV_CODEC_ID_OPUS: return DecodingCodec::OPUS; - case AV_CODEC_ID_SHORTEN: return DecodingCodec::SHORTEN; + case AV_CODEC_ID_MP3: + return DecodingCodec::MP3; + case AV_CODEC_ID_AAC: + return DecodingCodec::AAC; + case AV_CODEC_ID_AC3: + return DecodingCodec::AC3; + case AV_CODEC_ID_VORBIS: + return DecodingCodec::VORBIS; + case AV_CODEC_ID_WMAV1: + return DecodingCodec::WMAV1; + case AV_CODEC_ID_WMAV2: + return DecodingCodec::WMAV2; + case AV_CODEC_ID_FLAC: + return DecodingCodec::FLAC; + case AV_CODEC_ID_ALAC: + return DecodingCodec::ALAC; + case AV_CODEC_ID_WAVPACK: + return DecodingCodec::WAVPACK; + case AV_CODEC_ID_MUSEPACK7: + return DecodingCodec::MUSEPACK7; + case AV_CODEC_ID_MUSEPACK8: + return DecodingCodec::MUSEPACK8; + case AV_CODEC_ID_APE: + return DecodingCodec::APE; + case AV_CODEC_ID_EAC3: + return DecodingCodec::EAC3; + case AV_CODEC_ID_MP4ALS: + return DecodingCodec::MP4ALS; + case AV_CODEC_ID_OPUS: + return DecodingCodec::OPUS; + case AV_CODEC_ID_SHORTEN: + return DecodingCodec::SHORTEN; + case AV_CODEC_ID_DSD_LSBF: + return DecodingCodec::DSD_LSBF; + case AV_CODEC_ID_DSD_LSBF_PLANAR: + return DecodingCodec::DSD_LSBF_PLANAR; + case AV_CODEC_ID_DSD_MSBF: + return DecodingCodec::DSD_MSBF; + case AV_CODEC_ID_DSD_MSBF_PLANAR: + return DecodingCodec::DSD_MSBF_PLANAR; + default: return DecodingCodec::UNKNOWN; } } - } + } // namespace std::unique_ptr parseAudioFile(const std::filesystem::path& p) { @@ -178,8 +204,8 @@ namespace lms::av { int res = ::av_find_best_stream(_context, AVMEDIA_TYPE_AUDIO, - -1, // Auto - -1, // Auto + -1, // Auto + -1, // Auto NULL, 0); @@ -213,8 +239,7 @@ namespace lms::av void AudioFile::visitAttachedPictures(std::function func) const { - static const std::unordered_map codecMimeMap = - { + static const std::unordered_map codecMimeMap{ { AV_CODEC_ID_BMP, "image/x-bmp" }, { AV_CODEC_ID_GIF, "image/gif" }, { AV_CODEC_ID_MJPEG, "image/jpeg" }, @@ -300,31 +325,30 @@ namespace lms::av // List should be sync with the demuxers shipped in the lms's docker version // + the _audioFileExtensions in ScanSettings // std::filesystem::path does not seem to have std::hash specialization on freebsd - static const std::unordered_map entries - { - {".mp3", "audio/mpeg"}, - {".ogg", "audio/ogg"}, - {".oga", "audio/ogg"}, - {".opus", "audio/opus"}, - {".aac", "audio/aac"}, - {".alac", "audio/mp4"}, - {".m4a", "audio/mp4"}, - {".m4b", "audio/mp4"}, - {".flac", "audio/flac"}, - {".webm", "audio/webm"}, - {".wav", "audio/x-wav"}, - {".wma", "audio/x-ms-wma"}, - {".ape", "audio/x-monkeys-audio"}, - {".mpc", "audio/x-musepack"}, - {".shn", "audio/x-shn"}, - {".aif", "audio/x-aiff"}, - {".aiff", "audio/x-aiff"}, - {".m3u", "audio/x-mpegurl"}, - {".pls", "audio/x-scpls"}, - {".dsf", "audio/x-dsd"}, - {".wv", "audio/x-wavpack"}, - {".wvp", "audio/x-wavpack"}, - {".mka", "audio/x-matroska"}, + static const std::unordered_map entries{ + { ".mp3", "audio/mpeg" }, + { ".ogg", "audio/ogg" }, + { ".oga", "audio/ogg" }, + { ".opus", "audio/opus" }, + { ".aac", "audio/aac" }, + { ".alac", "audio/mp4" }, + { ".m4a", "audio/mp4" }, + { ".m4b", "audio/mp4" }, + { ".flac", "audio/flac" }, + { ".webm", "audio/webm" }, + { ".wav", "audio/x-wav" }, + { ".wma", "audio/x-ms-wma" }, + { ".ape", "audio/x-monkeys-audio" }, + { ".mpc", "audio/x-musepack" }, + { ".shn", "audio/x-shn" }, + { ".aif", "audio/x-aiff" }, + { ".aiff", "audio/x-aiff" }, + { ".m3u", "audio/x-mpegurl" }, + { ".pls", "audio/x-scpls" }, + { ".dsf", "audio/x-dsd" }, + { ".wv", "audio/x-wavpack" }, + { ".wvp", "audio/x-wavpack" }, + { ".mka", "audio/x-matroska" }, }; auto it{ entries.find(core::stringUtils::stringToLower(fileExtension.string())) }; diff --git a/src/libs/av/impl/AudioFile.hpp b/src/libs/av/impl/AudioFile.hpp index 838e63021..b1b7f1b6d 100644 --- a/src/libs/av/impl/AudioFile.hpp +++ b/src/libs/av/impl/AudioFile.hpp @@ -17,8 +17,6 @@ * along with LMS. If not, see . */ - /* This file contains some classes in order to get info from file using the libavconv */ - #pragma once #include "av/IAudioFile.hpp" @@ -35,23 +33,22 @@ namespace lms::av ~AudioFile(); const std::filesystem::path& getPath() const override; - ContainerInfo getContainerInfo() const override; - MetadataMap getMetaData() const override; - std::vector getStreamInfo() const override; - std::optional getBestStreamInfo() const override; - std::optional getBestStreamIndex() const override; - bool hasAttachedPictures() const override; - void visitAttachedPictures(std::function func) const override; + ContainerInfo getContainerInfo() const override; + MetadataMap getMetaData() const override; + std::vector getStreamInfo() const override; + std::optional getBestStreamInfo() const override; + std::optional getBestStreamIndex() const override; + bool hasAttachedPictures() const override; + void visitAttachedPictures(std::function func) const override; private: AudioFile(const AudioFile&) = delete; AudioFile& operator=(const AudioFile&) = delete; - std::optional getStreamInfo(std::size_t streamIndex) const; + std::optional getStreamInfo(std::size_t streamIndex) const; - const std::filesystem::path _p; + const std::filesystem::path _p; AVFormatContext* _context{}; }; } // namespace lms::av - diff --git a/src/libs/av/impl/RawResourceHandlerCreator.cpp b/src/libs/av/impl/RawResourceHandlerCreator.cpp index 5d4467433..42c200b84 100644 --- a/src/libs/av/impl/RawResourceHandlerCreator.cpp +++ b/src/libs/av/impl/RawResourceHandlerCreator.cpp @@ -29,4 +29,4 @@ namespace lms::av std::string_view mimeType{ getMimeType(path.extension()) }; return createFileResourceHandler(path, mimeType.empty() ? "application/octet-stream" : mimeType); } -} +} // namespace lms::av diff --git a/src/libs/av/impl/Transcoder.cpp b/src/libs/av/impl/Transcoder.cpp index 663c94b2e..760376ef3 100644 --- a/src/libs/av/impl/Transcoder.cpp +++ b/src/libs/av/impl/Transcoder.cpp @@ -24,27 +24,32 @@ #include "core/IChildProcessManager.hpp" #include "core/IConfig.hpp" -#include "core/Path.hpp" #include "core/ILogger.hpp" +#include "core/Path.hpp" #include "core/Service.hpp" namespace lms::av::transcoding { -#define LOG(severity, message) LMS_LOG(TRANSCODING, severity, "[" << _debugId << "] - " << message) +#define LOG(severity, message) LMS_LOG(TRANSCODING, severity, "[" << _debugId << "] - " << message) - static std::atomic globalId{}; - static std::filesystem::path ffmpegPath; + static std::atomic globalId{}; + static std::filesystem::path ffmpegPath; std::string_view formatToMimetype(OutputFormat format) { switch (format) { - case OutputFormat::MP3: return "audio/mpeg"; - case OutputFormat::OGG_OPUS: return "audio/opus"; - case OutputFormat::MATROSKA_OPUS: return "audio/x-matroska"; - case OutputFormat::OGG_VORBIS: return "audio/ogg"; - case OutputFormat::WEBM_VORBIS: return "audio/webm"; + case OutputFormat::MP3: + return "audio/mpeg"; + case OutputFormat::OGG_OPUS: + return "audio/opus"; + case OutputFormat::MATROSKA_OPUS: + return "audio/x-matroska"; + case OutputFormat::OGG_VORBIS: + return "audio/ogg"; + case OutputFormat::WEBM_VORBIS: + return "audio/webm"; } throw Exception{ "Invalid encoding" }; @@ -195,10 +200,9 @@ namespace lms::av::transcoding { assert(_childProcess); - return _childProcess->asyncRead(buffer, bufferSize, [readCallback{ std::move(readCallback) }](core::IChildProcess::ReadResult /*res*/, std::size_t nbBytesRead) - { - readCallback(nbBytesRead); - }); + return _childProcess->asyncRead(buffer, bufferSize, [readCallback{ std::move(readCallback) }](core::IChildProcess::ReadResult /*res*/, std::size_t nbBytesRead) { + readCallback(nbBytesRead); + }); } std::size_t Transcoder::readSome(std::byte* buffer, std::size_t bufferSize) @@ -215,4 +219,4 @@ namespace lms::av::transcoding return _childProcess->finished(); } -} // namespace lms::av::Transcoding +} // namespace lms::av::transcoding diff --git a/src/libs/av/impl/Transcoder.hpp b/src/libs/av/impl/Transcoder.hpp index febc37d58..4e24c4f33 100644 --- a/src/libs/av/impl/Transcoder.hpp +++ b/src/libs/av/impl/Transcoder.hpp @@ -45,24 +45,24 @@ namespace lms::av::transcoding // non blocking calls using ReadCallback = std::function; - void asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback); - std::size_t readSome(std::byte* buffer, std::size_t bufferSize); + void asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback); + std::size_t readSome(std::byte* buffer, std::size_t bufferSize); const std::string& getOutputMimeType() const { return _outputMimeType; } const OutputParameters& getOutputParameters() const { return _outputParameters; } - bool finished() const; + bool finished() const; private: static void init(); void start(); - const std::size_t _debugId{}; - const InputParameters _inputParameters; - const OutputParameters _outputParameters; - std::string _outputMimeType; + const std::size_t _debugId{}; + const InputParameters _inputParameters; + const OutputParameters _outputParameters; + std::string _outputMimeType; - std::unique_ptr _childProcess; + std::unique_ptr _childProcess; }; -} \ No newline at end of file +} // namespace lms::av::transcoding \ No newline at end of file diff --git a/src/libs/av/impl/TranscodingResourceHandler.cpp b/src/libs/av/impl/TranscodingResourceHandler.cpp index d88821035..09932835d 100644 --- a/src/libs/av/impl/TranscodingResourceHandler.cpp +++ b/src/libs/av/impl/TranscodingResourceHandler.cpp @@ -29,7 +29,7 @@ namespace lms::av::transcoding const std::size_t estimatedContentLength{ outputParameters.bitrate / 8 * static_cast(std::chrono::duration_cast(inputParameters.duration).count()) / 1000 }; return estimatedContentLength; } - } + } // namespace std::unique_ptr createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength) { @@ -68,14 +68,13 @@ namespace lms::av::transcoding { Wt::Http::ResponseContinuation* continuation{ response.createContinuation() }; continuation->waitForMoreData(); - _transcoder.asyncRead(_buffer.data(), _buffer.size(), [this, continuation](std::size_t nbBytesRead) - { - LMS_LOG(TRANSCODING, DEBUG, "Have " << nbBytesRead << " more bytes to send back"); + _transcoder.asyncRead(_buffer.data(), _buffer.size(), [this, continuation](std::size_t nbBytesRead) { + LMS_LOG(TRANSCODING, DEBUG, "Have " << nbBytesRead << " more bytes to send back"); - assert(_bytesReadyCount == 0); - _bytesReadyCount = nbBytesRead; - continuation->haveMoreData(); - }); + assert(_bytesReadyCount == 0); + _bytesReadyCount = nbBytesRead; + continuation->haveMoreData(); + }); return continuation; } @@ -99,5 +98,4 @@ namespace lms::av::transcoding return {}; } -} - +} // namespace lms::av::transcoding diff --git a/src/libs/av/impl/TranscodingResourceHandler.hpp b/src/libs/av/impl/TranscodingResourceHandler.hpp index 8da15af5e..037ebe16f 100644 --- a/src/libs/av/impl/TranscodingResourceHandler.hpp +++ b/src/libs/av/impl/TranscodingResourceHandler.hpp @@ -25,6 +25,7 @@ #include "av/TranscodingParameters.hpp" #include "core/IResourceHandler.hpp" + #include "Transcoder.hpp" namespace lms::av::transcoding @@ -36,7 +37,7 @@ namespace lms::av::transcoding private: Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& reponse) override; - void abort() override {}; + void abort() override{}; static constexpr std::size_t _chunkSize{ 262'144 }; std::optional _estimatedContentLength; @@ -45,5 +46,4 @@ namespace lms::av::transcoding std::size_t _totalServedByteCount{}; Transcoder _transcoder; }; -} - +} // namespace lms::av::transcoding diff --git a/src/libs/av/include/av/IAudioFile.hpp b/src/libs/av/include/av/IAudioFile.hpp index 6d9915c98..d6618da6a 100644 --- a/src/libs/av/include/av/IAudioFile.hpp +++ b/src/libs/av/include/av/IAudioFile.hpp @@ -17,17 +17,15 @@ * along with LMS. If not, see . */ - /* This file contains some classes in order to get info from file using the libavconv */ - #pragma once #include #include #include -#include #include #include #include +#include #include #include "Types.hpp" @@ -44,24 +42,28 @@ namespace lms::av VORBIS, WMAV1, WMAV2, - FLAC, // Flac - ALAC, // Apple Lossless Audio Codec (ALAC) - WAVPACK, // WavPack - MUSEPACK7, // Musepack + FLAC, // Flac + ALAC, // Apple Lossless Audio Codec (ALAC) + WAVPACK, // WavPack + MUSEPACK7, // Musepack MUSEPACK8, - APE, // // Monkey's Audio - EAC3, // Enhanced AC-3 - MP4ALS, // MPEG-4 Audio Lossless Coding - OPUS, // Opus - SHORTEN, // Shorten (shn) + APE, // Monkey's Audio + EAC3, // Enhanced AC-3 + MP4ALS, // MPEG-4 Audio Lossless Coding + OPUS, // Opus + SHORTEN, // Shorten (shn) + DSD_LSBF, // DSD (Direct Stream Digital), least significant bit first + DSD_LSBF_PLANAR, // DSD (Direct Stream Digital), least significant bit first, planar + DSD_MSBF, // DSD (Direct Stream Digital), most significant bit first + DSD_MSBF_PLANAR, // DSD (Direct Stream Digital), most significant bit first, planar // TODO add PCM codecs }; struct Picture { - std::string mimeType; + std::string mimeType; const std::byte* data{}; - std::size_t dataSize{}; + std::size_t dataSize{}; }; struct ContainerInfo @@ -73,13 +75,13 @@ namespace lms::av struct StreamInfo { - size_t index{}; - std::size_t bitrate{}; - std::size_t bitsPerSample{}; - std::size_t channelCount{}; - std::size_t sampleRate{}; - DecodingCodec codec; - std::string codecName; + size_t index{}; + std::size_t bitrate{}; + std::size_t bitsPerSample{}; + std::size_t channelCount{}; + std::size_t sampleRate{}; + DecodingCodec codec; + std::string codecName; }; class IAudioFile @@ -91,13 +93,13 @@ namespace lms::av using MetadataMap = std::unordered_map; virtual const std::filesystem::path& getPath() const = 0; - virtual ContainerInfo getContainerInfo() const = 0; - virtual MetadataMap getMetaData() const = 0; - virtual std::vector getStreamInfo() const = 0; - virtual std::optional getBestStreamInfo() const = 0; // none if failure/unknown - virtual std::optional getBestStreamIndex() const = 0; // none if failure/unknown - virtual bool hasAttachedPictures() const = 0; - virtual void visitAttachedPictures(std::function func) const = 0; + virtual ContainerInfo getContainerInfo() const = 0; + virtual MetadataMap getMetaData() const = 0; + virtual std::vector getStreamInfo() const = 0; + virtual std::optional getBestStreamInfo() const = 0; // none if failure/unknown + virtual std::optional getBestStreamIndex() const = 0; // none if failure/unknown + virtual bool hasAttachedPictures() const = 0; + virtual void visitAttachedPictures(std::function func) const = 0; }; std::unique_ptr parseAudioFile(const std::filesystem::path& p); @@ -111,4 +113,3 @@ namespace lms::av std::string_view getMimeType(const std::filesystem::path& fileExtension); } // namespace lms::av - diff --git a/src/libs/av/include/av/TranscodingParameters.hpp b/src/libs/av/include/av/TranscodingParameters.hpp index 2fd7e3583..cc2a9a791 100644 --- a/src/libs/av/include/av/TranscodingParameters.hpp +++ b/src/libs/av/include/av/TranscodingParameters.hpp @@ -46,11 +46,10 @@ namespace lms::av::transcoding struct OutputParameters { - OutputFormat format; - std::size_t bitrate{ 128000 }; - std::optional stream; // Id of the stream to be transcoded (auto detect by default) - std::chrono::milliseconds offset{ 0 }; - bool stripMetadata{ true }; + OutputFormat format; + std::size_t bitrate{ 128000 }; + std::optional stream; // Id of the stream to be transcoded (auto detect by default) + std::chrono::milliseconds offset{ 0 }; + bool stripMetadata{ true }; }; -} // namespace lms::av::Transcoding - +} // namespace lms::av::transcoding diff --git a/src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp b/src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp index 35c13ed79..553ab5eaf 100644 --- a/src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp +++ b/src/libs/av/include/av/TranscodingResourceHandlerCreator.hpp @@ -25,8 +25,8 @@ namespace lms::av::transcoding { - struct InputParameters; - struct OutputParameters; + struct InputParameters; + struct OutputParameters; - std::unique_ptr createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength); -} + std::unique_ptr createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength); +} // namespace lms::av::transcoding diff --git a/src/libs/av/include/av/Types.hpp b/src/libs/av/include/av/Types.hpp index 3de4d3fed..baf7809b8 100644 --- a/src/libs/av/include/av/Types.hpp +++ b/src/libs/av/include/av/Types.hpp @@ -28,4 +28,4 @@ namespace lms::av public: using LmsException::LmsException; }; -} +} // namespace lms::av diff --git a/src/libs/core/bench/TraceLoggerBench.cpp b/src/libs/core/bench/TraceLoggerBench.cpp index 34652885b..1134fb42a 100644 --- a/src/libs/core/bench/TraceLoggerBench.cpp +++ b/src/libs/core/bench/TraceLoggerBench.cpp @@ -19,6 +19,7 @@ #include #include + #include #include "core/ILogger.hpp" @@ -58,8 +59,7 @@ namespace lms::core static void BM_TraceLogger_Detailed_withArg(benchmark::State& state) { - auto someExpensiveArgComputation{ []() -> std::string - { + auto someExpensiveArgComputation{ []() -> std::string { std::this_thread::sleep_for(std::chrono::microseconds{ 1 }); return "foo"; } }; @@ -76,6 +76,6 @@ namespace lms::core BENCHMARK(BM_TraceLogger_Detailed)->Threads(1)->Threads(std::thread::hardware_concurrency()); BENCHMARK(BM_TraceLogger_Detailed_withArg)->Threads(1)->Threads(std::thread::hardware_concurrency()); -} +} // namespace lms::core BENCHMARK_MAIN(); \ No newline at end of file diff --git a/src/libs/core/impl/ArchiveZipper.cpp b/src/libs/core/impl/ArchiveZipper.cpp index 911022628..5f3fcecd1 100644 --- a/src/libs/core/impl/ArchiveZipper.cpp +++ b/src/libs/core/impl/ArchiveZipper.cpp @@ -23,6 +23,7 @@ #include #include // strerror #include + #include #include @@ -39,12 +40,14 @@ namespace lms::zip { public: FileException(const std::filesystem::path& p, std::string_view message) - : Exception{ "File '" + p.string() + "': " + std::string {message} } - {} + : Exception{ "File '" + p.string() + "': " + std::string{ message } } + { + } FileException(const std::filesystem::path& p, std::string_view message, int err) - : Exception{ "File '" + p.string() + "': " + std::string {message} + ": " + ::strerror(err) } - {} + : Exception{ "File '" + p.string() + "': " + std::string{ message } + ": " + ::strerror(err) } + { + } }; class ArchiveException : public Exception @@ -52,7 +55,8 @@ namespace lms::zip public: ArchiveException(struct ::archive* arch) : Exception{ getError(arch) } - {} + { + } static std::string_view getError(struct ::archive* arch) { @@ -88,19 +92,16 @@ namespace lms::zip if (!_archive) throw Exception{ "Cannot create archive control struct" }; - auto archiveOpen{ [](struct ::archive*, void*) - { + auto archiveOpen{ [](struct ::archive*, void*) { return ARCHIVE_OK; } }; - auto archiveWrite{ [](struct ::archive*, void* clientData, const void* buff, ::size_t n) -> la_ssize_t - { - ArchiveZipper* zipper {static_cast(clientData)}; + auto archiveWrite{ [](struct ::archive*, void* clientData, const void* buff, ::size_t n) -> la_ssize_t { + ArchiveZipper* zipper{ static_cast(clientData) }; return zipper->onWriteCallback(static_cast(buff), n); } }; - auto archiveClose{ [](struct ::archive*, void*) - { + auto archiveClose{ [](struct ::archive*, void*) { return ARCHIVE_OK; } }; @@ -183,8 +184,7 @@ namespace lms::zip using std::filesystem::perms; ::mode_t mode{}; - auto testPerm{ [](perms p, perms permToTest) - { + auto testPerm{ [](perms p, perms permToTest) { return (p & permToTest) == permToTest; } }; @@ -295,4 +295,4 @@ namespace lms::zip return bufferSize; } -} +} // namespace lms::zip diff --git a/src/libs/core/impl/ArchiveZipper.hpp b/src/libs/core/impl/ArchiveZipper.hpp index 43fead7e9..5d5b97e5c 100644 --- a/src/libs/core/impl/ArchiveZipper.hpp +++ b/src/libs/core/impl/ArchiveZipper.hpp @@ -21,6 +21,7 @@ #include #include + #include "core/IZipper.hpp" extern "C" @@ -77,4 +78,4 @@ namespace lms::zip std::uint64_t _bytesWrittenInCurrentOutputStream{}; }; -} \ No newline at end of file +} // namespace lms::zip \ No newline at end of file diff --git a/src/libs/core/impl/ChildProcess.cpp b/src/libs/core/impl/ChildProcess.cpp index 3c9cd1586..d2956a3df 100644 --- a/src/libs/core/impl/ChildProcess.cpp +++ b/src/libs/core/impl/ChildProcess.cpp @@ -19,21 +19,21 @@ #include "ChildProcess.hpp" -#include #include +#include #include +#include #include #include #include -#include #include #include #include #include -#include #include +#include #include "core/Exception.hpp" #include "core/ILogger.hpp" @@ -47,13 +47,15 @@ namespace lms::core public: SystemException(int err, const std::string& errMsg) : ChildProcessException{ errMsg + ": " + ::strerror(err) } - {} + { + } SystemException(boost::system::error_code ec, const std::string& errMsg) : ChildProcessException{ errMsg + ": " + ec.message() } - {} + { + } }; - } + } // namespace ChildProcess::ChildProcess(boost::asio::io_context& ioContext, const std::filesystem::path& path, const Args& args) : _ioContext{ ioContext } @@ -170,8 +172,7 @@ namespace lms::core LMS_LOG(CHILDPROCESS, DEBUG, "Async read, bufferSize = " << bufferSize); boost::asio::async_read(_childStdout, boost::asio::buffer(data, bufferSize), - [this, callback{ std::move(callback) }](const boost::system::error_code& error, std::size_t bytesTransferred) - { + [this, callback{ std::move(callback) }](const boost::system::error_code& error, std::size_t bytesTransferred) { LMS_LOG(CHILDPROCESS, DEBUG, "Async read cb - ec = '" << error.message() << "' (" << error.value() << "), bytesTransferred = " << bytesTransferred); ReadResult readResult{ ReadResult::Success }; @@ -206,4 +207,4 @@ namespace lms::core { return _finished; } -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/impl/ChildProcess.hpp b/src/libs/core/impl/ChildProcess.hpp index 9f5aea09b..a1628e144 100644 --- a/src/libs/core/impl/ChildProcess.hpp +++ b/src/libs/core/impl/ChildProcess.hpp @@ -40,20 +40,20 @@ namespace lms::core ChildProcess(boost::asio::io_context& ioContext, const std::filesystem::path& path, const Args& args); private: - void asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) override; - std::size_t readSome(std::byte* data, std::size_t bufferSize) override; - bool finished() const override; + void asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) override; + std::size_t readSome(std::byte* data, std::size_t bufferSize) override; + bool finished() const override; - void kill(); - bool wait(bool block); // return true if waited + void kill(); + bool wait(bool block); // return true if waited using FileDescriptor = boost::asio::posix::stream_descriptor; boost::asio::io_context& _ioContext; - FileDescriptor _childStdout; - ::pid_t _childPID{}; - bool _waited{}; - bool _finished{}; - std::optional _exitCode; + FileDescriptor _childStdout; + ::pid_t _childPID{}; + bool _waited{}; + bool _finished{}; + std::optional _exitCode; }; -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/impl/ChildProcessManager.cpp b/src/libs/core/impl/ChildProcessManager.cpp index e7dc459e8..3afa7d600 100644 --- a/src/libs/core/impl/ChildProcessManager.cpp +++ b/src/libs/core/impl/ChildProcessManager.cpp @@ -39,4 +39,4 @@ namespace lms::core { return std::make_unique(_ioContext, path, args); } -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/impl/ChildProcessManager.hpp b/src/libs/core/impl/ChildProcessManager.hpp index 39a569981..d7e5bc298 100644 --- a/src/libs/core/impl/ChildProcessManager.hpp +++ b/src/libs/core/impl/ChildProcessManager.hpp @@ -44,4 +44,4 @@ namespace lms::core boost::asio::io_context& _ioContext; }; -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/impl/Config.cpp b/src/libs/core/impl/Config.cpp index 334f634a0..c45231712 100644 --- a/src/libs/core/impl/Config.cpp +++ b/src/libs/core/impl/Config.cpp @@ -65,7 +65,7 @@ namespace lms::core { try { - const libconfig::Setting& values{ _config.lookup(std::string {setting}) }; + const libconfig::Setting& values{ _config.lookup(std::string{ setting }) }; for (int i{}; i < values.getLength(); ++i) _func(static_cast(values[i])); } @@ -83,7 +83,7 @@ namespace lms::core { try { - const char* res{ _config.lookup(std::string {setting}) }; + const char* res{ _config.lookup(std::string{ setting }) }; return std::filesystem::path{ std::string(res) }; } catch (libconfig::ConfigException&) @@ -127,4 +127,4 @@ namespace lms::core return def; } } -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/impl/Config.hpp b/src/libs/core/impl/Config.hpp index a267d47b9..b1d323ffb 100644 --- a/src/libs/core/impl/Config.hpp +++ b/src/libs/core/impl/Config.hpp @@ -37,15 +37,14 @@ namespace lms::core Config& operator=(Config&&) = delete; // Default values are returned in case of setting not found - std::string_view getString(std::string_view setting, std::string_view def = "") override; - void visitStrings(std::string_view setting, std::function _func, std::initializer_list defs) override; - std::filesystem::path getPath(std::string_view setting, const std::filesystem::path& def = std::filesystem::path()) override; - unsigned long getULong(std::string_view setting, unsigned long def = 0) override; - long getLong(std::string_view setting, long def = 0) override; - bool getBool(std::string_view setting, bool def = false) override; + std::string_view getString(std::string_view setting, std::string_view def = "") override; + void visitStrings(std::string_view setting, std::function _func, std::initializer_list defs) override; + std::filesystem::path getPath(std::string_view setting, const std::filesystem::path& def = std::filesystem::path()) override; + unsigned long getULong(std::string_view setting, unsigned long def = 0) override; + long getLong(std::string_view setting, long def = 0) override; + bool getBool(std::string_view setting, bool def = false) override; private: - libconfig::Config _config; }; -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/impl/FileResourceHandler.cpp b/src/libs/core/impl/FileResourceHandler.cpp index 5a5ef8b8a..49f871c5d 100644 --- a/src/libs/core/impl/FileResourceHandler.cpp +++ b/src/libs/core/impl/FileResourceHandler.cpp @@ -80,7 +80,7 @@ namespace lms std::ostringstream contentRange; contentRange << "bytes " << startByte << "-" - << _beyondLastByte - 1 << "/" << fileSize; + << _beyondLastByte - 1 << "/" << fileSize; response.addHeader("Content-Range", contentRange.str()); response.setContentLength(_beyondLastByte - startByte); @@ -134,4 +134,4 @@ namespace lms LMS_LOG(UTILS, DEBUG, "Job complete!"); return nullptr; } -} \ No newline at end of file +} // namespace lms \ No newline at end of file diff --git a/src/libs/core/impl/FileResourceHandler.hpp b/src/libs/core/impl/FileResourceHandler.hpp index 5c2392775..6b79d4e50 100644 --- a/src/libs/core/impl/FileResourceHandler.hpp +++ b/src/libs/core/impl/FileResourceHandler.hpp @@ -22,6 +22,7 @@ #include #include #include + #include "core/IResourceHandler.hpp" namespace lms @@ -33,13 +34,13 @@ namespace lms private: Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override; - void abort() override {}; + void abort() override{}; static constexpr std::size_t _chunkSize{ 262'144 }; - std::filesystem::path _path; - std::string _mimeType; - ::uint64_t _beyondLastByte{}; - ::uint64_t _offset{}; + std::filesystem::path _path; + std::string _mimeType; + ::uint64_t _beyondLastByte{}; + ::uint64_t _offset{}; }; -} \ No newline at end of file +} // namespace lms \ No newline at end of file diff --git a/src/libs/core/impl/IOContextRunner.cpp b/src/libs/core/impl/IOContextRunner.cpp index c78fcd800..2094f691f 100644 --- a/src/libs/core/impl/IOContextRunner.cpp +++ b/src/libs/core/impl/IOContextRunner.cpp @@ -41,24 +41,23 @@ namespace lms::core threadName += std::to_string(i); } - _threads.emplace_back([this, threadName] + _threads.emplace_back([this, threadName] { + if (!threadName.empty()) { - if (!threadName.empty()) - { - if (auto * traceLogger{ Service::get() }) - traceLogger->setThreadName(std::this_thread::get_id(), threadName); - } + if (auto* traceLogger{ Service::get() }) + traceLogger->setThreadName(std::this_thread::get_id(), threadName); + } - try - { - _ioService.run(); - } - catch (const std::exception& e) - { - LMS_LOG(UTILS, FATAL, "Exception caught in IO context: " << e.what()); - std::abort(); - } - }); + try + { + _ioService.run(); + } + catch (const std::exception& e) + { + LMS_LOG(UTILS, FATAL, "Exception caught in IO context: " << e.what()); + std::abort(); + } + }); } } @@ -82,4 +81,4 @@ namespace lms::core for (std::thread& t : _threads) t.join(); } -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/impl/Logger.cpp b/src/libs/core/impl/Logger.cpp index a859e7308..a4765e445 100644 --- a/src/libs/core/impl/Logger.cpp +++ b/src/libs/core/impl/Logger.cpp @@ -25,25 +25,44 @@ namespace lms::core::logging { switch (mod) { - case Module::API_SUBSONIC: return "API_SUBSONIC"; - case Module::AUTH: return "AUTH"; - case Module::AV: return "AV"; - case Module::CHILDPROCESS: return "CHILDPROC"; - case Module::COVER: return "COVER"; - case Module::DB: return "DB"; - case Module::DBUPDATER: return "DB UPDATER"; - case Module::FEATURE: return "FEATURE"; - case Module::FEEDBACK: return "FEEDBACK"; - case Module::HTTP: return "HTTP"; - case Module::MAIN: return "MAIN"; - case Module::METADATA: return "METADATA"; - case Module::REMOTE: return "REMOTE"; - case Module::SCROBBLING: return "SCROBBLING"; - case Module::SERVICE: return "SERVICE"; - case Module::RECOMMENDATION: return "RECOMMENDATION"; - case Module::TRANSCODING: return "TRANSCODING"; - case Module::UI: return "UI"; - case Module::UTILS: return "UTILS"; + case Module::API_SUBSONIC: + return "API_SUBSONIC"; + case Module::AUTH: + return "AUTH"; + case Module::AV: + return "AV"; + case Module::CHILDPROCESS: + return "CHILDPROC"; + case Module::COVER: + return "COVER"; + case Module::DB: + return "DB"; + case Module::DBUPDATER: + return "DB UPDATER"; + case Module::FEATURE: + return "FEATURE"; + case Module::FEEDBACK: + return "FEEDBACK"; + case Module::HTTP: + return "HTTP"; + case Module::MAIN: + return "MAIN"; + case Module::METADATA: + return "METADATA"; + case Module::REMOTE: + return "REMOTE"; + case Module::SCROBBLING: + return "SCROBBLING"; + case Module::SERVICE: + return "SERVICE"; + case Module::RECOMMENDATION: + return "RECOMMENDATION"; + case Module::TRANSCODING: + return "TRANSCODING"; + case Module::UI: + return "UI"; + case Module::UTILS: + return "UTILS"; } return ""; } @@ -52,11 +71,16 @@ namespace lms::core::logging { switch (sev) { - case Severity::FATAL: return "fatal"; - case Severity::ERROR: return "error"; - case Severity::WARNING: return "warning"; - case Severity::INFO: return "info"; - case Severity::DEBUG: return "debug"; + case Severity::FATAL: + return "fatal"; + case Severity::ERROR: + return "error"; + case Severity::WARNING: + return "warning"; + case Severity::INFO: + return "info"; + case Severity::DEBUG: + return "debug"; } return ""; } @@ -66,7 +90,8 @@ namespace lms::core::logging , _module{ module } , _severity{ severity } - {} + { + } Log::~Log() { @@ -78,4 +103,4 @@ namespace lms::core::logging { return _oss.str(); } -} \ No newline at end of file +} // namespace lms::core::logging \ No newline at end of file diff --git a/src/libs/core/impl/NetAddress.cpp b/src/libs/core/impl/NetAddress.cpp index 7b8bc4271..565ee4105 100644 --- a/src/libs/core/impl/NetAddress.cpp +++ b/src/libs/core/impl/NetAddress.cpp @@ -23,24 +23,24 @@ namespace std { - std::size_t hash::operator()(const boost::asio::ip::address& ipAddr) const - { - if (ipAddr.is_v4()) - return ipAddr.to_v4().to_ulong(); + std::size_t hash::operator()(const boost::asio::ip::address& ipAddr) const + { + if (ipAddr.is_v4()) + return ipAddr.to_v4().to_ulong(); - if (ipAddr.is_v6()) - { - const auto& range {ipAddr.to_v6().to_bytes()}; - std::size_t res {}; + if (ipAddr.is_v6()) + { + const auto& range{ ipAddr.to_v6().to_bytes() }; + std::size_t res{}; - for (auto b : range) - res ^= std::hash{}(static_cast(b)); + for (auto b : range) + res ^= std::hash{}(static_cast(b)); - return res; - } + return res; + } - return std::hash{}(ipAddr.to_string()); - } -} + return std::hash{}(ipAddr.to_string()); + } +} // namespace std #endif // BOOST_ASIO_HAS_STD_HASH diff --git a/src/libs/core/impl/Path.cpp b/src/libs/core/impl/Path.cpp index 5e55bf72e..75b30c5ac 100644 --- a/src/libs/core/impl/Path.cpp +++ b/src/libs/core/impl/Path.cpp @@ -19,12 +19,11 @@ #include "core/Path.hpp" -#include -#include -#include - #include #include +#include +#include +#include #include @@ -44,7 +43,7 @@ namespace lms::core::pathUtils { do { - std::array buffer; + std::array buffer; ifs.read(buffer.data(), buffer.size()); crc32.processBytes(reinterpret_cast(buffer.data()), ifs.gcount()); @@ -69,7 +68,9 @@ namespace lms::core::pathUtils Wt::WDateTime getLastWriteTime(const std::filesystem::path& file) { - struct stat sb {}; + struct stat sb + { + }; if (stat(file.string().c_str(), &sb) == -1) throw LmsException("Failed to get stats on file '" + file.string() + "'"); @@ -183,4 +184,4 @@ namespace lms::core::pathUtils return longestCommonPath; } -} +} // namespace lms::core::pathUtils diff --git a/src/libs/core/impl/Random.cpp b/src/libs/core/impl/Random.cpp index 18b654627..3a7ae7377 100644 --- a/src/libs/core/impl/Random.cpp +++ b/src/libs/core/impl/Random.cpp @@ -35,4 +35,4 @@ namespace lms::core::random return RandGenerator{ seed }; } -} \ No newline at end of file +} // namespace lms::core::random \ No newline at end of file diff --git a/src/libs/core/impl/RecursiveSharedMutex.cpp b/src/libs/core/impl/RecursiveSharedMutex.cpp index 74ff9babb..cd7b98563 100644 --- a/src/libs/core/impl/RecursiveSharedMutex.cpp +++ b/src/libs/core/impl/RecursiveSharedMutex.cpp @@ -131,4 +131,4 @@ namespace lms::core return _sharedCounts[thisThreadId] > 0; } #endif -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/impl/StreamLogger.cpp b/src/libs/core/impl/StreamLogger.cpp index 4e6e26d51..e8d23736f 100644 --- a/src/libs/core/impl/StreamLogger.cpp +++ b/src/libs/core/impl/StreamLogger.cpp @@ -35,4 +35,4 @@ namespace lms::core::logging assert(isSeverityActive(log.getSeverity())); _os << std::this_thread::get_id() << " [" << getSeverityName(log.getSeverity()) << "] [" << getModuleName(log.getModule()) << "] " << log.getMessage() << std::endl; } -} \ No newline at end of file +} // namespace lms::core::logging \ No newline at end of file diff --git a/src/libs/core/impl/String.cpp b/src/libs/core/impl/String.cpp index 35fb876ee..a891e766d 100644 --- a/src/libs/core/impl/String.cpp +++ b/src/libs/core/impl/String.cpp @@ -23,15 +23,14 @@ #include #include -#include #include +#include namespace lms::core::stringUtils { namespace details { - constexpr std::pair jsEscapeChars[] - { + constexpr std::pair jsEscapeChars[]{ { '\\', "\\\\" }, { '\n', "\\n" }, { '\r', "\\r" }, @@ -40,8 +39,7 @@ namespace lms::core::stringUtils { '\'', "\\\'" }, }; - constexpr std::pair jsonEscapeChars[] - { + constexpr std::pair jsonEscapeChars[]{ { '\\', "\\\\" }, { '\n', "\\n" }, { '\r', "\\r" }, @@ -49,8 +47,8 @@ namespace lms::core::stringUtils { '"', "\\\"" }, }; - template - std::string escape(std::string_view str, const std::pair(&charsToEscape)[N]) + template + std::string escape(std::string_view str, const std::pair (&charsToEscape)[N]) { std::string escaped; escaped.reserve(str.length()); @@ -70,12 +68,12 @@ namespace lms::core::stringUtils return escaped; } - template - void writeEscapedString(std::ostream& os, std::string_view str, const std::pair(&charsToEscape)[N]) + template + void writeEscapedString(std::ostream& os, std::string_view str, const std::pair (&charsToEscape)[N]) { for (const char c : str) { - auto itEntry{ std::find_if(std::cbegin(charsToEscape), std::cend(charsToEscape), [=](const auto& entry) { return entry.first == c;}) }; + auto itEntry{ std::find_if(std::cbegin(charsToEscape), std::cend(charsToEscape), [=](const auto& entry) { return entry.first == c; }) }; if (itEntry != std::cend(charsToEscape)) os << itEntry->second; else @@ -83,7 +81,7 @@ namespace lms::core::stringUtils } } - template + template std::string joinStrings(std::span strings, std::string_view delimiter) { std::string res; @@ -99,7 +97,7 @@ namespace lms::core::stringUtils return res; } - } + } // namespace details template<> std::optional readAs(std::string_view str) @@ -198,7 +196,8 @@ namespace lms::core::stringUtils for (char c : str) { - if (escaped) { + if (escaped) + { current.push_back(c); escaped = false; } @@ -248,14 +247,14 @@ namespace lms::core::stringUtils std::string res; res.reserve(str.size()); - std::transform(std::cbegin(str), std::cend(str), std::back_inserter(res), [](unsigned char c) { return std::tolower(c);}); + std::transform(std::cbegin(str), std::cend(str), std::back_inserter(res), [](unsigned char c) { return std::tolower(c); }); return res; } void stringToLower(std::string& str) { - std::transform(std::cbegin(str), std::cend(str), std::begin(str), [](unsigned char c) { return std::tolower(c);}); + std::transform(std::cbegin(str), std::cend(str), std::begin(str), [](unsigned char c) { return std::tolower(c); }); } std::string stringToUpper(const std::string& str) @@ -263,7 +262,7 @@ namespace lms::core::stringUtils std::string res; res.reserve(str.size()); - std::transform(std::cbegin(str), std::cend(str), std::back_inserter(res), [](char c) { return std::toupper(c);}); + std::transform(std::cbegin(str), std::cend(str), std::back_inserter(res), [](char c) { return std::toupper(c); }); return res; } @@ -436,4 +435,4 @@ namespace lms::core::stringUtils // assume UTC return date.toString("yyyy-MM-dd").toUTF8(); } -} +} // namespace lms::core::stringUtils diff --git a/src/libs/core/impl/TraceLogger.cpp b/src/libs/core/impl/TraceLogger.cpp index adddad9d9..88b0f2696 100644 --- a/src/libs/core/impl/TraceLogger.cpp +++ b/src/libs/core/impl/TraceLogger.cpp @@ -22,6 +22,7 @@ #include #include #include + #include "core/Exception.hpp" #include "core/ILogger.hpp" @@ -32,7 +33,8 @@ namespace lms::core::tracing class CurrentThreadUnregisterer { public: - CurrentThreadUnregisterer(TraceLogger* logger) : _logger{ logger } {} + CurrentThreadUnregisterer(TraceLogger* logger) + : _logger{ logger } {} ~CurrentThreadUnregisterer() { if (_logger) @@ -45,7 +47,7 @@ namespace lms::core::tracing TraceLogger* _logger; }; - } + } // namespace thread_local TraceLogger::Buffer* TraceLogger::_currentBuffer{}; @@ -71,11 +73,11 @@ namespace lms::core::tracing LMS_LOG(UTILS, INFO, "TraceLogger: using " << _buffers.size() << " buffers. Buffer size = " << std::to_string(BufferSize) << ", entry size = " << sizeof(CompleteEventEntry) << ", entry count per buffer = " << Buffer::CompleteEventCount); setMetadata("cpu_count", std::to_string(std::thread::hardware_concurrency())); - setMetadata("build_type", + setMetadata("build_type", #ifndef NDEBUG - "debug" + "debug" #else - "release" + "release" #endif ); } @@ -182,7 +184,8 @@ namespace lms::core::tracing if (first) first = false; else - os << ", " << std::endl;; + os << ", " << std::endl; + ; os << "\t\t{ "; os << "\"name\" : \"" << event.name << "\", "; @@ -297,4 +300,4 @@ namespace lms::core::tracing return static_cast(id); } -} \ No newline at end of file +} // namespace lms::core::tracing \ No newline at end of file diff --git a/src/libs/core/impl/TraceLogger.hpp b/src/libs/core/impl/TraceLogger.hpp index 79cc256b9..4e338fdd9 100644 --- a/src/libs/core/impl/TraceLogger.hpp +++ b/src/libs/core/impl/TraceLogger.hpp @@ -103,4 +103,4 @@ namespace lms::core::tracing static thread_local Buffer* _currentBuffer; }; -} \ No newline at end of file +} // namespace lms::core::tracing \ No newline at end of file diff --git a/src/libs/core/impl/UUID.cpp b/src/libs/core/impl/UUID.cpp index 6573b44a5..756bcb1aa 100644 --- a/src/libs/core/impl/UUID.cpp +++ b/src/libs/core/impl/UUID.cpp @@ -31,13 +31,13 @@ namespace lms::core { namespace stringUtils { - template <> + template<> std::optional - readAs(std::string_view str) + readAs(std::string_view str) { return UUID::fromString(str); } - } + } // namespace stringUtils namespace { bool stringIsUUID(std::string_view str) @@ -46,7 +46,7 @@ namespace lms::core return std::regex_match(std::cbegin(str), std::cend(str), re); } - } + } // namespace UUID::UUID(std::string_view str) : _value{ stringUtils::stringToLower(str) } @@ -68,9 +68,8 @@ namespace lms::core std::ostringstream oss; - auto concatRandomBytes{ [](std::ostream& os, std::size_t byteCount) - { - for (std::size_t i {}; i < byteCount; ++i) + auto concatRandomBytes{ [](std::ostream& os, std::size_t byteCount) { + for (std::size_t i{}; i < byteCount; ++i) os << std::hex << std::setfill('0') << std::setw(2) << static_cast(random::getRandom(0, 255)); } }; @@ -88,4 +87,4 @@ namespace lms::core assert(uuid); return uuid.value(); } -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/impl/WtLogger.cpp b/src/libs/core/impl/WtLogger.cpp index 6d7ac150c..4714e6697 100644 --- a/src/libs/core/impl/WtLogger.cpp +++ b/src/libs/core/impl/WtLogger.cpp @@ -19,10 +19,11 @@ #include "core/WtLogger.hpp" -#include #include -#include +#include + #include +#include #include "core/Exception.hpp" @@ -36,7 +37,7 @@ namespace lms::core::logging oss << id; return oss.str(); } - } + } // namespace WtLogger::WtLogger(Severity minSeverity) : _minSeverity{ minSeverity } @@ -47,11 +48,16 @@ namespace lms::core::logging { switch (minSeverity) { - case Severity::DEBUG: return "*"; - case Severity::INFO: return "* -debug"; - case Severity::WARNING: return "* -debug -info"; - case Severity::ERROR: return "* -debug -info -warning"; - case Severity::FATAL: return "* -debug -info -warning -error"; + case Severity::DEBUG: + return "*"; + case Severity::INFO: + return "* -debug"; + case Severity::WARNING: + return "* -debug -info"; + case Severity::ERROR: + return "* -debug -info -warning"; + case Severity::FATAL: + return "* -debug -info -warning -error"; } throw LmsException{ "Unhandled severity" }; @@ -66,4 +72,4 @@ namespace lms::core::logging { Wt::log(getSeverityName(log.getSeverity())) << Wt::WLogger::sep << to_string(std::this_thread::get_id()) << Wt::WLogger::sep << "[" << getModuleName(log.getModule()) << "]" << Wt::WLogger::sep << log.getMessage(); } -} \ No newline at end of file +} // namespace lms::core::logging \ No newline at end of file diff --git a/src/libs/core/impl/http/Client.cpp b/src/libs/core/impl/http/Client.cpp index c49dc433e..a60401227 100644 --- a/src/libs/core/impl/http/Client.cpp +++ b/src/libs/core/impl/http/Client.cpp @@ -36,4 +36,4 @@ namespace lms::core::http { _sendQueue.sendRequest(std::make_unique(std::move(POSTParams))); } -} \ No newline at end of file +} // namespace lms::core::http \ No newline at end of file diff --git a/src/libs/core/impl/http/Client.hpp b/src/libs/core/impl/http/Client.hpp index 6d00dac38..c7cf32e12 100644 --- a/src/libs/core/impl/http/Client.hpp +++ b/src/libs/core/impl/http/Client.hpp @@ -19,11 +19,12 @@ #pragma once -#include -#include #include +#include +#include #include "core/http/IClient.hpp" + #include "SendQueue.hpp" namespace lms::core::http @@ -33,7 +34,8 @@ namespace lms::core::http public: Client(boost::asio::io_context& ioContext, std::string_view baseUrl) : _sendQueue{ ioContext, baseUrl } - {} + { + } private: void sendGETRequest(ClientGETRequestParameters&& request) override; @@ -41,4 +43,4 @@ namespace lms::core::http SendQueue _sendQueue; }; -} \ No newline at end of file +} // namespace lms::core::http \ No newline at end of file diff --git a/src/libs/core/impl/http/ClientRequest.hpp b/src/libs/core/impl/http/ClientRequest.hpp index e5bd12d75..81b7e38af 100644 --- a/src/libs/core/impl/http/ClientRequest.hpp +++ b/src/libs/core/impl/http/ClientRequest.hpp @@ -21,6 +21,7 @@ #include #include + #include "core/http/ClientRequestParameters.hpp" namespace lms::core::http @@ -28,8 +29,10 @@ namespace lms::core::http class ClientRequest { public: - ClientRequest(ClientGETRequestParameters&& GETParams) : _parameters{ std::move(GETParams) } {} - ClientRequest(ClientPOSTRequestParameters&& POSTParams) : _parameters{ std::move(POSTParams) } {} + ClientRequest(ClientGETRequestParameters&& GETParams) + : _parameters{ std::move(GETParams) } {} + ClientRequest(ClientPOSTRequestParameters&& POSTParams) + : _parameters{ std::move(POSTParams) } {} std::size_t retryCount{}; @@ -37,10 +40,10 @@ namespace lms::core::http { const ClientRequestParameters* res; - std::visit([&](const auto& parameters) - { - res = &static_cast(parameters); - }, _parameters); + std::visit([&](const auto& parameters) { + res = &static_cast(parameters); + }, + _parameters); return *res; } @@ -71,4 +74,4 @@ namespace lms::core::http private: std::variant _parameters; }; -} +} // namespace lms::core::http diff --git a/src/libs/core/impl/http/SendQueue.cpp b/src/libs/core/impl/http/SendQueue.cpp index 221273af7..3ed4655cd 100644 --- a/src/libs/core/impl/http/SendQueue.cpp +++ b/src/libs/core/impl/http/SendQueue.cpp @@ -19,15 +19,15 @@ #include "SendQueue.hpp" -#include #include +#include #include "core/Exception.hpp" #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" #include "core/String.hpp" -#define LOG(sev, message) LMS_LOG(SCROBBLING, sev, "[Http SendQueue] - " << message) +#define LOG(sev, message) LMS_LOG(SCROBBLING, sev, "[Http SendQueue] - " << message) namespace lms::core::stringUtils { @@ -41,35 +41,33 @@ namespace lms::core::stringUtils return res; } -} +} // namespace lms::core::stringUtils namespace lms::core::http { namespace { - template + template std::optional headerReadAs(const Wt::Http::Message& msg, std::string_view headerName) { std::optional res; - if (const std::string * headerValue{ msg.getHeader(std::string {headerName}) }) + if (const std::string * headerValue{ msg.getHeader(std::string{ headerName }) }) res = stringUtils::readAs(*headerValue); return res; } - } + } // namespace SendQueue::SendQueue(boost::asio::io_context& ioContext, std::string_view baseUrl) : _ioContext{ ioContext } , _baseUrl{ baseUrl } { - _client.done().connect([this](Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg) - { - _strand.dispatch([this, ec, msg = std::move(msg)] - { - onClientDone(ec, msg); - }); + _client.done().connect([this](Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg) { + _strand.dispatch([this, ec, msg = std::move(msg)] { + onClientDone(ec, msg); }); + }); } SendQueue::~SendQueue() @@ -79,13 +77,12 @@ namespace lms::core::http void SendQueue::sendRequest(std::unique_ptr request) { - boost::asio::dispatch(_strand, [this, request = std::move(request)]() mutable - { - _sendQueue[request->getParameters().priority].emplace_back(std::move(request)); + boost::asio::dispatch(_strand, [this, request = std::move(request)]() mutable { + _sendQueue[request->getParameters().priority].emplace_back(std::move(request)); - if (_state == State::Idle) - sendNextQueuedRequest(); - }); + if (_state == State::Idle) + sendNextQueuedRequest(); + }); } void SendQueue::sendNextQueuedRequest() @@ -220,21 +217,20 @@ namespace lms::core::http LOG(DEBUG, "Throttling for " << duration.count() << " seconds"); _throttleTimer.expires_after(duration); - _throttleTimer.async_wait([this](const boost::system::error_code& ec) + _throttleTimer.async_wait([this](const boost::system::error_code& ec) { + if (ec == boost::asio::error::operation_aborted) { - if (ec == boost::asio::error::operation_aborted) - { - LOG(DEBUG, "Throttle aborted"); - return; - } - else if (ec) - { - throw LmsException{ "Throttle timer failure: " + std::string {ec.message()} }; - } - - _state = State::Idle; - sendNextQueuedRequest(); - }); + LOG(DEBUG, "Throttle aborted"); + return; + } + else if (ec) + { + throw LmsException{ "Throttle timer failure: " + std::string{ ec.message() } }; + } + + _state = State::Idle; + sendNextQueuedRequest(); + }); _state = State::Throttled; } -} +} // namespace lms::core::http diff --git a/src/libs/core/impl/http/SendQueue.hpp b/src/libs/core/impl/http/SendQueue.hpp index 439ef4800..8da04d9c3 100644 --- a/src/libs/core/impl/http/SendQueue.hpp +++ b/src/libs/core/impl/http/SendQueue.hpp @@ -20,14 +20,14 @@ #pragma once #include -#include #include +#include +#include #include #include #include -#include #include "ClientRequest.hpp" namespace lms::core::http @@ -53,15 +53,15 @@ namespace lms::core::http void onClientDoneSuccess(std::unique_ptr request, const Wt::Http::Message& msg); void throttle(std::chrono::seconds duration); - const std::size_t _maxRetryCount{ 2 }; - const std::chrono::seconds _defaultRetryWaitDuration{ 30 }; - const std::chrono::seconds _minRetryWaitDuration{ 1 }; - const std::chrono::seconds _maxRetryWaitDuration{ 300 }; + const std::size_t _maxRetryCount{ 2 }; + const std::chrono::seconds _defaultRetryWaitDuration{ 30 }; + const std::chrono::seconds _minRetryWaitDuration{ 1 }; + const std::chrono::seconds _maxRetryWaitDuration{ 300 }; boost::asio::io_context& _ioContext; - boost::asio::io_context::strand _strand{ _ioContext }; - boost::asio::steady_timer _throttleTimer{ _ioContext }; - std::string _baseUrl; + boost::asio::io_context::strand _strand{ _ioContext }; + boost::asio::steady_timer _throttleTimer{ _ioContext }; + std::string _baseUrl; enum class State { @@ -69,10 +69,10 @@ namespace lms::core::http Throttled, Sending, }; - State _state{ State::Idle }; - Wt::Http::Client _client{ _ioContext }; + State _state{ State::Idle }; + Wt::Http::Client _client{ _ioContext }; std::map>> _sendQueue; - std::unique_ptr _currentRequest; + std::unique_ptr _currentRequest; }; -} \ No newline at end of file +} // namespace lms::core::http \ No newline at end of file diff --git a/src/libs/core/include/core/Crc32Calculator.hpp b/src/libs/core/include/core/Crc32Calculator.hpp index e9d081ba6..604910cf0 100644 --- a/src/libs/core/include/core/Crc32Calculator.hpp +++ b/src/libs/core/include/core/Crc32Calculator.hpp @@ -19,7 +19,7 @@ #pragma once -#include // for boost::crc_32_type +#include // for boost::crc_32_type namespace lms::core { @@ -40,4 +40,4 @@ namespace lms::core using Crc32Type = boost::crc_32_type; Crc32Type _result; }; -} +} // namespace lms::core diff --git a/src/libs/core/include/core/EnumSet.hpp b/src/libs/core/include/core/EnumSet.hpp index d880dff71..dc0356107 100644 --- a/src/libs/core/include/core/EnumSet.hpp +++ b/src/libs/core/include/core/EnumSet.hpp @@ -27,7 +27,7 @@ namespace lms::core { - template + template class EnumSet { static_assert(std::is_enum::value); @@ -45,13 +45,13 @@ namespace lms::core insert(value); } - template + template constexpr EnumSet(It begin, It end) { assign(begin, end); } - template + template constexpr void assign(It begin, It end) { clear(); @@ -158,7 +158,10 @@ namespace lms::core private: static_assert(std::numeric_limits::max() >= sizeof(underlying_type) * 8); - enum : IndexType { npos = sizeof(underlying_type) * 8 }; + enum : IndexType + { + npos = sizeof(underlying_type) * 8 + }; constexpr IndexType getFirstBitSetIndex(IndexType start = {}) const { @@ -190,4 +193,4 @@ namespace lms::core underlying_type _bitfield{}; }; -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/include/core/Exception.hpp b/src/libs/core/include/core/Exception.hpp index 8c68e1658..77a3aafd7 100644 --- a/src/libs/core/include/core/Exception.hpp +++ b/src/libs/core/include/core/Exception.hpp @@ -29,6 +29,7 @@ namespace lms::core class LmsException : public std::runtime_error { public: - LmsException(std::string_view error = "") : std::runtime_error{ std::string{ error } } {} + LmsException(std::string_view error = "") + : std::runtime_error{ std::string{ error } } {} }; -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/include/core/IChildProcess.hpp b/src/libs/core/include/core/IChildProcess.hpp index 40fc573a0..5f9bb0cb3 100644 --- a/src/libs/core/include/core/IChildProcess.hpp +++ b/src/libs/core/include/core/IChildProcess.hpp @@ -48,9 +48,9 @@ namespace lms::core }; using ReadCallback = std::function; - virtual void asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) = 0; + virtual void asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) = 0; - virtual std::size_t readSome(std::byte* data, std::size_t bufferSize) = 0; - virtual bool finished() const = 0; + virtual std::size_t readSome(std::byte* data, std::size_t bufferSize) = 0; + virtual bool finished() const = 0; }; -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/include/core/IChildProcessManager.hpp b/src/libs/core/include/core/IChildProcessManager.hpp index 26c64df8c..1df237ddd 100644 --- a/src/libs/core/include/core/IChildProcessManager.hpp +++ b/src/libs/core/include/core/IChildProcessManager.hpp @@ -20,6 +20,7 @@ #include #include + #include #include "IChildProcess.hpp" @@ -35,4 +36,4 @@ namespace lms::core }; std::unique_ptr createChildProcessManager(boost::asio::io_service& ioService); -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/include/core/IConfig.hpp b/src/libs/core/include/core/IConfig.hpp index 07e4b8623..2d703552f 100644 --- a/src/libs/core/include/core/IConfig.hpp +++ b/src/libs/core/include/core/IConfig.hpp @@ -31,13 +31,13 @@ namespace lms::core virtual ~IConfig() = default; // Default values are returned in case of setting not found - virtual std::string_view getString(std::string_view setting, std::string_view def = "") = 0; - virtual void visitStrings(std::string_view setting, std::function _func, std::initializer_list def = {}) = 0; - virtual std::filesystem::path getPath(std::string_view setting, const std::filesystem::path& def = std::filesystem::path()) = 0; - virtual unsigned long getULong(std::string_view setting, unsigned long def = 0) = 0; - virtual long getLong(std::string_view setting, long def = 0) = 0; - virtual bool getBool(std::string_view setting, bool def = false) = 0; + virtual std::string_view getString(std::string_view setting, std::string_view def = "") = 0; + virtual void visitStrings(std::string_view setting, std::function _func, std::initializer_list def = {}) = 0; + virtual std::filesystem::path getPath(std::string_view setting, const std::filesystem::path& def = std::filesystem::path()) = 0; + virtual unsigned long getULong(std::string_view setting, unsigned long def = 0) = 0; + virtual long getLong(std::string_view setting, long def = 0) = 0; + virtual bool getBool(std::string_view setting, bool def = false) = 0; }; std::unique_ptr createConfig(const std::filesystem::path& p); -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/include/core/ILogger.hpp b/src/libs/core/include/core/ILogger.hpp index 339978acf..8c7ee446d 100644 --- a/src/libs/core/include/core/ILogger.hpp +++ b/src/libs/core/include/core/ILogger.hpp @@ -19,11 +19,11 @@ #pragma once -#include #include +#include +#include "core/Service.hpp" #include "core/String.hpp" -#include "Service.hpp" namespace lms::core::logging { @@ -93,11 +93,11 @@ namespace lms::core::logging virtual bool isSeverityActive(Severity severity) const = 0; virtual void processLog(const Log& log) = 0; }; -} +} // namespace lms::core::logging -#define LMS_LOG(module, severity, message) \ - do \ - { \ - 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(module, severity, message) \ + do \ + { \ + 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) diff --git a/src/libs/core/include/core/IOContextRunner.hpp b/src/libs/core/include/core/IOContextRunner.hpp index 9beeec6e6..a3fb30e0a 100644 --- a/src/libs/core/include/core/IOContextRunner.hpp +++ b/src/libs/core/include/core/IOContextRunner.hpp @@ -21,6 +21,7 @@ #include #include + #include namespace lms::core @@ -39,7 +40,7 @@ namespace lms::core IOContextRunner& operator=(const IOContextRunner&) = delete; boost::asio::io_service& _ioService; - std::optional _work; - std::vector _threads; + std::optional _work; + std::vector _threads; }; -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/include/core/IResourceHandler.hpp b/src/libs/core/include/core/IResourceHandler.hpp index 7fbc83964..49433b65a 100644 --- a/src/libs/core/include/core/IResourceHandler.hpp +++ b/src/libs/core/include/core/IResourceHandler.hpp @@ -25,13 +25,13 @@ // TODO, move elsewhere namespace lms { - // Helper class to serve a resource (must be saved as continuation data if not complete) - class IResourceHandler - { - public: - virtual ~IResourceHandler() = default; + // Helper class to serve a resource (must be saved as continuation data if not complete) + class IResourceHandler + { + public: + virtual ~IResourceHandler() = default; - [[nodiscard]] virtual Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) = 0; - virtual void abort() = 0; - }; -} \ No newline at end of file + [[nodiscard]] virtual Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) = 0; + virtual void abort() = 0; + }; +} // namespace lms \ No newline at end of file diff --git a/src/libs/core/include/core/ITraceLogger.hpp b/src/libs/core/include/core/ITraceLogger.hpp index aad89edfa..e0e198749 100644 --- a/src/libs/core/include/core/ITraceLogger.hpp +++ b/src/libs/core/include/core/ITraceLogger.hpp @@ -34,20 +34,20 @@ #define LMS_CONCAT_IMPL(x, y) x##y #define LMS_CONCAT(x, y) LMS_CONCAT_IMPL(x, y) -#if LMS_SUPPORT_TRACING -#define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME, ARGTYPE, ARGVALUE) \ -std::optional<::lms::core::tracing::ScopedTrace> LMS_CONCAT(ScopedTrace_, __LINE__); \ -if (::lms::core::tracing::ITraceLogger* traceLogger{ ::lms::core::Service<::lms::core::tracing::ITraceLogger>::get() }; traceLogger && traceLogger->isLevelActive(LEVEL)) \ - LMS_CONCAT(ScopedTrace_, __LINE__).emplace(CATEGORY, LEVEL, NAME, ARGTYPE, ARGVALUE, traceLogger); +#if LMS_SUPPORT_TRACING + #define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME, ARGTYPE, ARGVALUE) \ + std::optional<::lms::core::tracing::ScopedTrace> LMS_CONCAT(ScopedTrace_, __LINE__); \ + if (::lms::core::tracing::ITraceLogger * traceLogger{ ::lms::core::Service<::lms::core::tracing::ITraceLogger>::get() }; traceLogger && traceLogger->isLevelActive(LEVEL)) \ + LMS_CONCAT(ScopedTrace_, __LINE__).emplace(CATEGORY, LEVEL, NAME, ARGTYPE, ARGVALUE, traceLogger); #else -#define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME) (void)0 + #define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME) (void)0 #endif -#define LMS_SCOPED_TRACE_OVERVIEW_WITH_ARG(CATEGORY, NAME, ARGTYPE, ARGVALUE) LMS_SCOPED_TRACE(CATEGORY, ::lms::core::tracing::Level::Overview, NAME, ARGTYPE, ARGVALUE) -#define LMS_SCOPED_TRACE_DETAILED_WITH_ARG(CATEGORY, NAME, ARGTYPE, ARGVALUE) LMS_SCOPED_TRACE(CATEGORY, ::lms::core::tracing::Level::Detailed, NAME, ARGTYPE, ARGVALUE) +#define LMS_SCOPED_TRACE_OVERVIEW_WITH_ARG(CATEGORY, NAME, ARGTYPE, ARGVALUE) LMS_SCOPED_TRACE(CATEGORY, ::lms::core::tracing::Level::Overview, NAME, ARGTYPE, ARGVALUE) +#define LMS_SCOPED_TRACE_DETAILED_WITH_ARG(CATEGORY, NAME, ARGTYPE, ARGVALUE) LMS_SCOPED_TRACE(CATEGORY, ::lms::core::tracing::Level::Detailed, NAME, ARGTYPE, ARGVALUE) -#define LMS_SCOPED_TRACE_OVERVIEW(CATEGORY, NAME) LMS_SCOPED_TRACE_OVERVIEW_WITH_ARG(CATEGORY, NAME, "", "") -#define LMS_SCOPED_TRACE_DETAILED(CATEGORY, NAME) LMS_SCOPED_TRACE_DETAILED_WITH_ARG(CATEGORY, NAME, "", "") +#define LMS_SCOPED_TRACE_OVERVIEW(CATEGORY, NAME) LMS_SCOPED_TRACE_OVERVIEW_WITH_ARG(CATEGORY, NAME, "", "") +#define LMS_SCOPED_TRACE_DETAILED(CATEGORY, NAME) LMS_SCOPED_TRACE_DETAILED_WITH_ARG(CATEGORY, NAME, "", "") namespace lms::core::tracing { @@ -124,4 +124,4 @@ namespace lms::core::tracing ITraceLogger* _traceLogger; ITraceLogger::CompleteEvent _event; }; -} \ No newline at end of file +} // namespace lms::core::tracing \ No newline at end of file diff --git a/src/libs/core/include/core/IZipper.hpp b/src/libs/core/include/core/IZipper.hpp index f8542a0b3..cd7962262 100644 --- a/src/libs/core/include/core/IZipper.hpp +++ b/src/libs/core/include/core/IZipper.hpp @@ -25,7 +25,7 @@ #include "Exception.hpp" - // TODO, move elsewhere? +// TODO, move elsewhere? namespace lms::zip { struct Entry @@ -51,4 +51,4 @@ namespace lms::zip }; std::unique_ptr createArchiveZipper(const EntryContainer& entries); -} +} // namespace lms::zip diff --git a/src/libs/core/include/core/LiteralString.hpp b/src/libs/core/include/core/LiteralString.hpp index 11b7f668b..1d5709717 100644 --- a/src/libs/core/include/core/LiteralString.hpp +++ b/src/libs/core/include/core/LiteralString.hpp @@ -31,7 +31,11 @@ namespace lms::core public: constexpr LiteralString() noexcept = default; template - constexpr LiteralString(const char(&str)[N]) noexcept : _str{ str, N - 1 } { static_assert(N > 0); } + constexpr LiteralString(const char (&str)[N]) noexcept + : _str{ str, N - 1 } + { + static_assert(N > 0); + } constexpr bool empty() const noexcept { return _str.empty(); } constexpr const char* c_str() const noexcept { return _str.data(); } @@ -48,7 +52,7 @@ namespace lms::core os << str.str(); return os; } -} +} // namespace lms::core namespace std { @@ -60,7 +64,7 @@ namespace std return hash{}(str.str()); } }; -} +} // namespace std namespace lms::core { @@ -69,15 +73,18 @@ namespace lms::core using hash_type = std::hash; using is_transparent = void; - [[nodiscard]] size_t operator()(const LiteralString& str) const { + [[nodiscard]] size_t operator()(const LiteralString& str) const + { return hash_type{}(str.str()); } - [[nodiscard]] size_t operator()(std::string_view str) const { + [[nodiscard]] size_t operator()(std::string_view str) const + { return hash_type{}(str); } - [[nodiscard]] size_t operator()(const std::string& str) const { + [[nodiscard]] size_t operator()(const std::string& str) const + { return hash_type{}(str); } }; @@ -86,24 +93,29 @@ namespace lms::core { using is_transparent = void; - [[nodiscard]] bool operator()(const LiteralString& lhs, const LiteralString& rhs) const { + [[nodiscard]] bool operator()(const LiteralString& lhs, const LiteralString& rhs) const + { return lhs == rhs; } - [[nodiscard]] bool operator()(const LiteralString& lhs, const std::string& rhs) const { + [[nodiscard]] bool operator()(const LiteralString& lhs, const std::string& rhs) const + { return lhs.str() == rhs; } - [[nodiscard]] bool operator()(const LiteralString& lhs, std::string_view rhs) const { + [[nodiscard]] bool operator()(const LiteralString& lhs, std::string_view rhs) const + { return lhs.str() == rhs; } - [[nodiscard]] bool operator()(const std::string_view& lhs, LiteralString rhs) const { + [[nodiscard]] bool operator()(const std::string_view& lhs, LiteralString rhs) const + { return lhs == rhs.str(); } - [[nodiscard]] bool operator()(const std::string& lhs, const LiteralString& rhs) const { + [[nodiscard]] bool operator()(const std::string& lhs, const LiteralString& rhs) const + { return lhs == rhs.str(); } }; -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/include/core/NetAddress.hpp b/src/libs/core/include/core/NetAddress.hpp index 86575e1a1..98292495d 100644 --- a/src/libs/core/include/core/NetAddress.hpp +++ b/src/libs/core/include/core/NetAddress.hpp @@ -22,15 +22,16 @@ #include #ifndef BOOST_ASIO_HAS_STD_HASH -#include + #include namespace std { - template<> struct hash - { - std::size_t operator()(const boost::asio::ip::address& ipAddr) const; - }; + template<> + struct hash + { + std::size_t operator()(const boost::asio::ip::address& ipAddr) const; + }; -} +} // namespace std #endif // BOOST_ASIO_HAS_STD_HASH diff --git a/src/libs/core/include/core/Path.hpp b/src/libs/core/include/core/Path.hpp index 8f8d11df4..f49edaf4c 100644 --- a/src/libs/core/include/core/Path.hpp +++ b/src/libs/core/include/core/Path.hpp @@ -49,7 +49,7 @@ namespace lms::core::pathUtils std::filesystem::path getLongestCommonPath(const std::filesystem::path& path1, const std::filesystem::path& path2); - template + template std::filesystem::path getLongestCommonPath(Iterator first, Iterator last) { std::filesystem::path longestCommonPath; @@ -64,4 +64,4 @@ namespace lms::core::pathUtils return longestCommonPath; } -} +} // namespace lms::core::pathUtils diff --git a/src/libs/core/include/core/Random.hpp b/src/libs/core/include/core/Random.hpp index 7bfa03d40..898a49bc6 100644 --- a/src/libs/core/include/core/Random.hpp +++ b/src/libs/core/include/core/Random.hpp @@ -29,27 +29,27 @@ namespace lms::core::random RandGenerator createSeededGenerator(uint_fast32_t seed); - template + template T getRandom(T min, T max) { std::uniform_int_distribution<> dist{ min, max }; return dist(getRandGenerator()); } - template + template T getRealRandom(T min, T max) { std::uniform_real_distribution<> dist{ min, max }; return dist(getRandGenerator()); } - template + template void shuffleContainer(Container& container) { std::shuffle(std::begin(container), std::end(container), getRandGenerator()); } - template + template typename Container::const_iterator pickRandom(const Container& container) { if (container.empty()) @@ -57,4 +57,4 @@ namespace lms::core::random return std::next(std::begin(container), getRandom(0, static_cast(container.size() - 1))); } -} \ No newline at end of file +} // namespace lms::core::random \ No newline at end of file diff --git a/src/libs/core/include/core/RecursiveSharedMutex.hpp b/src/libs/core/include/core/RecursiveSharedMutex.hpp index b38a0dece..aa8c669ef 100644 --- a/src/libs/core/include/core/RecursiveSharedMutex.hpp +++ b/src/libs/core/include/core/RecursiveSharedMutex.hpp @@ -48,4 +48,4 @@ namespace lms::core std::mutex _sharedCountMutex; std::unordered_map _sharedCounts; }; -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/include/core/Service.hpp b/src/libs/core/include/core/Service.hpp index 021417403..b6603fc2a 100644 --- a/src/libs/core/include/core/Service.hpp +++ b/src/libs/core/include/core/Service.hpp @@ -24,7 +24,7 @@ namespace lms::core { - template + template class Service { public: @@ -57,7 +57,7 @@ namespace lms::core static Class* get() { return _service.get(); } static bool exists() { return _service.get(); } - template + template static Class& assign(std::unique_ptr service) { assert(!_service); @@ -70,4 +70,4 @@ namespace lms::core static inline std::unique_ptr _service; }; -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/include/core/StreamLogger.hpp b/src/libs/core/include/core/StreamLogger.hpp index f82d728a8..2e694fe2b 100644 --- a/src/libs/core/include/core/StreamLogger.hpp +++ b/src/libs/core/include/core/StreamLogger.hpp @@ -39,4 +39,4 @@ namespace lms::core::logging std::ostream& _os; const EnumSet _severities; }; -} \ No newline at end of file +} // namespace lms::core::logging \ No newline at end of file diff --git a/src/libs/core/include/core/String.hpp b/src/libs/core/include/core/String.hpp index 88122a496..2cf47885d 100644 --- a/src/libs/core/include/core/String.hpp +++ b/src/libs/core/include/core/String.hpp @@ -22,9 +22,9 @@ #include #include #include +#include #include #include -#include #include #define QUOTEME(x) QUOTEME_1(x) @@ -34,7 +34,7 @@ namespace Wt { class WDate; class WDateTime; -} +} // namespace Wt namespace lms::core::stringUtils { @@ -67,7 +67,7 @@ namespace lms::core::stringUtils { T res; - std::istringstream iss{ std::string {str} }; + std::istringstream iss{ std::string{ str } }; iss >> res; if (iss.fail()) return std::nullopt; @@ -100,4 +100,4 @@ namespace lms::core::stringUtils [[nodiscard]] std::string toISO8601String(const Wt::WDateTime& dateTime); [[nodiscard]] std::string toISO8601String(const Wt::WDate& date); -} \ No newline at end of file +} // namespace lms::core::stringUtils \ No newline at end of file diff --git a/src/libs/core/include/core/Tuple.hpp b/src/libs/core/include/core/Tuple.hpp index ad2f6adf5..128ad0e37 100644 --- a/src/libs/core/include/core/Tuple.hpp +++ b/src/libs/core/include/core/Tuple.hpp @@ -23,27 +23,33 @@ namespace lms::core { - namespace details - { - template - struct Seq { }; + namespace details + { + template + struct Seq + { + }; - template - struct GenSeq : GenSeq { }; + template + struct GenSeq : GenSeq + { + }; - template - struct GenSeq<0, Is...> : Seq { }; + template + struct GenSeq<0, Is...> : Seq + { + }; - template - void forEachTypeInTuple(T&& t, Func f, Seq) - { - auto l = { (f(std::get(t)), 0)... }; - } - } + template + void forEachTypeInTuple(T&& t, Func f, Seq) + { + auto l = { (f(std::get(t)), 0)... }; + } + } // namespace details - template - void forEachTypeInTuple(std::tuple const& t, Func f) - { - details::forEachTypeInTuple(t, f, details::GenSeq()); - } -} \ No newline at end of file + template + void forEachTypeInTuple(std::tuple const& t, Func f) + { + details::forEachTypeInTuple(t, f, details::GenSeq()); + } +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/include/core/UUID.hpp b/src/libs/core/include/core/UUID.hpp index 1b47a416d..b57d46283 100644 --- a/src/libs/core/include/core/UUID.hpp +++ b/src/libs/core/include/core/UUID.hpp @@ -41,11 +41,11 @@ namespace lms::core UUID(std::string_view value); std::string _value; }; -} +} // namespace lms::core namespace lms::core::stringUtils { - template <> + template<> std::optional - readAs(std::string_view str); + readAs(std::string_view str); } diff --git a/src/libs/core/include/core/Utils.hpp b/src/libs/core/include/core/Utils.hpp index 3965859f2..1a13f33fb 100644 --- a/src/libs/core/include/core/Utils.hpp +++ b/src/libs/core/include/core/Utils.hpp @@ -24,18 +24,19 @@ namespace lms::core::utils { - template> - constexpr T clamp(T v, T lo, T hi, Compare comp = {}) - { - assert(!comp(hi, lo)); - return comp(v, lo) ? lo : comp(hi, v) ? hi : v; - } + template> + constexpr T clamp(T v, T lo, T hi, Compare comp = {}) + { + assert(!comp(hi, lo)); + return comp(v, lo) ? lo : comp(hi, v) ? hi : + v; + } - template - void - push_back_if_not_present(Container& container, const T& val) - { - if (std::find(std::cbegin(container), std::cend(container), val) == std::cend(container)) - container.push_back(val); - } -} \ No newline at end of file + template + void + push_back_if_not_present(Container& container, const T& val) + { + if (std::find(std::cbegin(container), std::cend(container), val) == std::cend(container)) + container.push_back(val); + } +} // namespace lms::core::utils \ No newline at end of file diff --git a/src/libs/core/include/core/WtLogger.hpp b/src/libs/core/include/core/WtLogger.hpp index 6cb7b2be7..af615ab9a 100644 --- a/src/libs/core/include/core/WtLogger.hpp +++ b/src/libs/core/include/core/WtLogger.hpp @@ -37,4 +37,4 @@ namespace lms::core::logging void processLog(const Log& log) override; const Severity _minSeverity; }; -} \ No newline at end of file +} // namespace lms::core::logging \ No newline at end of file diff --git a/src/libs/core/include/core/http/ClientRequestParameters.hpp b/src/libs/core/include/core/http/ClientRequestParameters.hpp index ced63f9af..9e7e49e9a 100644 --- a/src/libs/core/include/core/http/ClientRequestParameters.hpp +++ b/src/libs/core/include/core/http/ClientRequestParameters.hpp @@ -36,7 +36,7 @@ namespace lms::core::http Low, }; - Priority priority{ Priority::Normal }; + Priority priority{ Priority::Normal }; std::string relativeUrl; // relative to baseUrl used by the client using OnSuccessFunc = std::function; @@ -56,4 +56,4 @@ namespace lms::core::http Wt::Http::Message message; }; -} \ No newline at end of file +} // namespace lms::core::http \ No newline at end of file diff --git a/src/libs/core/include/core/http/IClient.hpp b/src/libs/core/include/core/http/IClient.hpp index 30cc2d019..f32cc2ab4 100644 --- a/src/libs/core/include/core/http/IClient.hpp +++ b/src/libs/core/include/core/http/IClient.hpp @@ -20,20 +20,21 @@ #pragma once #include + #include #include "core/http/ClientRequestParameters.hpp" namespace lms::core::http { - class IClient - { - public: - virtual ~IClient() = default; + class IClient + { + public: + virtual ~IClient() = default; - virtual void sendGETRequest(ClientGETRequestParameters&& request) = 0; - virtual void sendPOSTRequest(ClientPOSTRequestParameters&& request) = 0; - }; + virtual void sendGETRequest(ClientGETRequestParameters&& request) = 0; + virtual void sendPOSTRequest(ClientPOSTRequestParameters&& request) = 0; + }; - std::unique_ptr createClient(boost::asio::io_context& ioContext, std::string_view baseUrl); -} \ No newline at end of file + std::unique_ptr createClient(boost::asio::io_context& ioContext, std::string_view baseUrl); +} // namespace lms::core::http \ No newline at end of file diff --git a/src/libs/core/test/EnumSet.cpp b/src/libs/core/test/EnumSet.cpp index ad83ea114..cc37ce8e0 100644 --- a/src/libs/core/test/EnumSet.cpp +++ b/src/libs/core/test/EnumSet.cpp @@ -60,4 +60,4 @@ namespace lms::core EXPECT_EQ(test, test2); } } -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/test/LiteralString.cpp b/src/libs/core/test/LiteralString.cpp index 4997b7e27..900850895 100644 --- a/src/libs/core/test/LiteralString.cpp +++ b/src/libs/core/test/LiteralString.cpp @@ -18,6 +18,7 @@ */ #include + #include #include "core/LiteralString.hpp" @@ -53,7 +54,7 @@ namespace lms::core TEST(LiteralString, unordered_map) { { - std::unordered_map myMap{ {"abc", 42} }; + std::unordered_map myMap{ { "abc", 42 } }; EXPECT_TRUE(myMap.contains("abc")); EXPECT_TRUE(myMap.contains(LiteralString{ "abc" })); @@ -61,12 +62,11 @@ namespace lms::core } { - std::unordered_map myMap{ {"abc", 42} }; + std::unordered_map myMap{ { "abc", 42 } }; EXPECT_TRUE(myMap.contains(std::string{ "abc" })); EXPECT_TRUE(myMap.contains(std::string_view{ "abc" })); EXPECT_FALSE(myMap.contains(std::string{ "abcd" })); EXPECT_FALSE(myMap.contains(std::string_view{ "abcd" })); } - } -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/test/Path.cpp b/src/libs/core/test/Path.cpp index 83fc70873..56cf530e7 100644 --- a/src/libs/core/test/Path.cpp +++ b/src/libs/core/test/Path.cpp @@ -32,14 +32,13 @@ namespace lms::core::pathUtils::tests std::filesystem::path expectedCommonPath; }; - TestCase tests[] - { - {"foo.txt", "/foo/foo.txt", ""}, - {"/", "/file.txt", "/"}, - {"/foo/bar/file1.txt", "/foo/bar/file2.txt", "/foo/bar"}, - {"/foo/bar/file.txt", "/foo/bar/file.txt", "/foo/bar/file.txt"}, - {"/dir1/file.txt", "/dir2/file.txt", "/"}, - {"/prefix/folder/file.txt", "/prefix/folder/subfolder/file.txt", "/prefix/folder"}, + TestCase tests[]{ + { "foo.txt", "/foo/foo.txt", "" }, + { "/", "/file.txt", "/" }, + { "/foo/bar/file1.txt", "/foo/bar/file2.txt", "/foo/bar" }, + { "/foo/bar/file.txt", "/foo/bar/file.txt", "/foo/bar/file.txt" }, + { "/dir1/file.txt", "/dir2/file.txt", "/" }, + { "/prefix/folder/file.txt", "/prefix/folder/subfolder/file.txt", "/prefix/folder" }, }; for (const TestCase& test : tests) @@ -48,7 +47,6 @@ namespace lms::core::pathUtils::tests } } - TEST(Path, getLongestCommonPathIterator) { struct TestCase @@ -57,17 +55,16 @@ namespace lms::core::pathUtils::tests std::filesystem::path expectedCommonPath; }; - TestCase tests[] - { - {{}, ""}, - {{"/"}, "/"}, - {{"/foo", "/bar"}, "/"}, - {{"/foo/bar/file1.txt", "/foo/bar/file2.txt"}, "/foo/bar"}, - {{"/foo", "/foo/"}, "/foo"}, - {{"/foo", "/foo"}, "/foo"}, - {{"/foo/", "/foo/"}, "/foo/"}, - {{"/foo/", "/foo/", "/bar"}, "/"}, - {{"/foo/", "/foo/", "/foo/bar"}, "/foo"}, + TestCase tests[]{ + { {}, "" }, + { { "/" }, "/" }, + { { "/foo", "/bar" }, "/" }, + { { "/foo/bar/file1.txt", "/foo/bar/file2.txt" }, "/foo/bar" }, + { { "/foo", "/foo/" }, "/foo" }, + { { "/foo", "/foo" }, "/foo" }, + { { "/foo/", "/foo/" }, "/foo/" }, + { { "/foo/", "/foo/", "/bar" }, "/" }, + { { "/foo/", "/foo/", "/foo/bar" }, "/foo" }, }; for (const TestCase& test : tests) @@ -85,19 +82,18 @@ namespace lms::core::pathUtils::tests bool expectedResult; }; - TestCase tests[] - { - {"/file.txt", "/", true}, - {"/root/folder/file.txt", "/root", true}, - {"/root/file.txt", "/root", true}, - {"/root/file.txt", "/root/", true}, - {"/root", "/root", true}, - {"/root", "/root/", true}, - {"/root/", "/root", true}, - {"/root/", "/root/", true}, - {"/folder/file.txt", "/root", false}, - {"/folder/file.txt", "/root/", false}, - {"", "/root", false}, + TestCase tests[]{ + { "/file.txt", "/", true }, + { "/root/folder/file.txt", "/root", true }, + { "/root/file.txt", "/root", true }, + { "/root/file.txt", "/root/", true }, + { "/root", "/root", true }, + { "/root", "/root/", true }, + { "/root/", "/root", true }, + { "/root/", "/root/", true }, + { "/folder/file.txt", "/root", false }, + { "/folder/file.txt", "/root/", false }, + { "", "/root", false }, }; for (const TestCase& test : tests) @@ -105,4 +101,4 @@ namespace lms::core::pathUtils::tests EXPECT_EQ(core::pathUtils::isPathInRootPath(test.path, test.rootPath), test.expectedResult) << "Failed: path = " << test.path << ", rootPath = " << test.rootPath; } } -} \ No newline at end of file +} // namespace lms::core::pathUtils::tests \ No newline at end of file diff --git a/src/libs/core/test/RecursiveSharedMutex.cpp b/src/libs/core/test/RecursiveSharedMutex.cpp index cb60d26ca..6d3127b75 100644 --- a/src/libs/core/test/RecursiveSharedMutex.cpp +++ b/src/libs/core/test/RecursiveSharedMutex.cpp @@ -66,40 +66,39 @@ namespace lms::core std::atomic nbShared{}; for (std::size_t i{}; i < nbThreads; ++i) { - threads.emplace_back([&] + threads.emplace_back([&] { { - { - std::unique_lock lock{ mutex }; - - std::shared_lock lock2{ mutex }; - - assert(nbUnique == 0); - assert(nbShared == 0); - nbUnique++; - std::this_thread::sleep_for(std::chrono::milliseconds(5)); - assert(nbUnique == 1); - assert(nbShared == 0); - nbUnique--; - } - - { - std::shared_lock lock{ mutex }; - std::shared_lock lock2{ mutex }; - - assert(nbUnique == 0); - nbShared++; - - std::this_thread::sleep_for(std::chrono::milliseconds(15)); - - assert(nbShared > 0); - assert(nbShared <= nbThreads); - assert(nbUnique == 0); - nbShared--; - } - }); + std::unique_lock lock{ mutex }; + + std::shared_lock lock2{ mutex }; + + assert(nbUnique == 0); + assert(nbShared == 0); + nbUnique++; + std::this_thread::sleep_for(std::chrono::milliseconds(5)); + assert(nbUnique == 1); + assert(nbShared == 0); + nbUnique--; + } + + { + std::shared_lock lock{ mutex }; + std::shared_lock lock2{ mutex }; + + assert(nbUnique == 0); + nbShared++; + + std::this_thread::sleep_for(std::chrono::milliseconds(15)); + + assert(nbShared > 0); + assert(nbShared <= nbThreads); + assert(nbUnique == 0); + nbShared--; + } + }); } for (std::thread& t : threads) t.join(); } -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/test/String.cpp b/src/libs/core/test/String.cpp index 71c45cdea..142145c84 100644 --- a/src/libs/core/test/String.cpp +++ b/src/libs/core/test/String.cpp @@ -19,9 +19,10 @@ #include -#include #include +#include #include + #include "core/String.hpp" namespace lms::core::stringUtils::tests @@ -35,30 +36,29 @@ namespace lms::core::stringUtils::tests std::vector expectedOutput; }; - TestCase tests[] - { - {"abc", '-', {"abc"}}, - {"a", '-', {"a"}}, - {"", '-', {""}}, - {"a-b-c", '-', {"a", "b", "c"}}, - {"a|b|c", '|', {"a", "b", "c"}}, - {"a;b;c", ';', {"a", "b", "c"}}, - {";b;c", ';', {"", "b", "c"}}, - {" ;b;c", ';', {" ", "b", "c"}}, - {" ;;c", ';', {" ", "", "c"}}, - {" ; ;c", ';', {" ", " ", "c"}}, - {"a;b; ", ';', {"a", "b", " "}}, - {"a;b", ';', {"a", "b"}}, - {";b", ';', {"", "b"}}, - {";", ';', {"", ""}}, - {";;", ';', {"", "", ""}}, - {";;;", ';', {"", "", "", ""}}, - {";;a;;b;;", ';', {"", "", "a", "", "b", "", ""}}, - {"a b", ' ', {"a", "b"}}, - {"", ' ', {""}}, - {"a-b|c", '-', {"a","b|c"}}, - {"a|b-c", '-', {"a|b", "c"}}, - {"test=foo bar", '=', {"test", "foo bar"}}, + TestCase tests[]{ + { "abc", '-', { "abc" } }, + { "a", '-', { "a" } }, + { "", '-', { "" } }, + { "a-b-c", '-', { "a", "b", "c" } }, + { "a|b|c", '|', { "a", "b", "c" } }, + { "a;b;c", ';', { "a", "b", "c" } }, + { ";b;c", ';', { "", "b", "c" } }, + { " ;b;c", ';', { " ", "b", "c" } }, + { " ;;c", ';', { " ", "", "c" } }, + { " ; ;c", ';', { " ", " ", "c" } }, + { "a;b; ", ';', { "a", "b", " " } }, + { "a;b", ';', { "a", "b" } }, + { ";b", ';', { "", "b" } }, + { ";", ';', { "", "" } }, + { ";;", ';', { "", "", "" } }, + { ";;;", ';', { "", "", "", "" } }, + { ";;a;;b;;", ';', { "", "", "a", "", "b", "", "" } }, + { "a b", ' ', { "a", "b" } }, + { "", ' ', { "" } }, + { "a-b|c", '-', { "a", "b|c" } }, + { "a|b-c", '-', { "a|b", "c" } }, + { "test=foo bar", '=', { "test", "foo bar" } }, }; for (const TestCase& test : tests) @@ -77,19 +77,19 @@ namespace lms::core::stringUtils::tests std::vector expectedOutput; }; - TestCase tests[] - { - {"abc", "", {"abc"}}, - {"abc", "-", {"abc"}}, - {"abc", "b", {"a", "c"}}, - {"ab/cd", "/", {"ab", "cd"}}, - {"ab/cd", "/ ", {"ab/cd"}}, - {"ab/cd", " /", {"ab/cd"}}, - {"ab /cd", " /", {"ab", "cd"}}, - {"ab/ cd", "/ ", {"ab", "cd"}}, - {"ab / cd", " / ", {"ab", "cd"}}, - {"ab/cd", " / ", {"ab/cd"}}, - {"ab/cd / ", " / ", {"ab/cd", ""}}, + TestCase tests[]{ + { "", "", { "" } }, + { "abc", "", { "abc" } }, + { "abc", "-", { "abc" } }, + { "abc", "b", { "a", "c" } }, + { "ab/cd", "/", { "ab", "cd" } }, + { "ab/cd", "/ ", { "ab/cd" } }, + { "ab/cd", " /", { "ab/cd" } }, + { "ab /cd", " /", { "ab", "cd" } }, + { "ab/ cd", "/ ", { "ab", "cd" } }, + { "ab / cd", " / ", { "ab", "cd" } }, + { "ab/cd", " / ", { "ab/cd" } }, + { "ab/cd / ", " / ", { "ab/cd", "" } }, }; for (const TestCase& test : tests) @@ -108,15 +108,14 @@ namespace lms::core::stringUtils::tests std::string expectedOutput; }; - TestCase tests[] - { - {{"a", "b", "c"}, "-", "a-b-c"}, - {{"a", "b", "c"}, ",", "a,b,c"}, - {{"a", "b", "c"}, "***", "a***b***c"}, - {{"a", "", "c"}, "-", "a--c"}, - {{"", "b", "c"}, "-", "-b-c"}, - {{"a"}, "-", "a"}, - {{"a"}, ",", "a"}, + TestCase tests[]{ + { { "a", "b", "c" }, "-", "a-b-c" }, + { { "a", "b", "c" }, ",", "a,b,c" }, + { { "a", "b", "c" }, "***", "a***b***c" }, + { { "a", "", "c" }, "-", "a--c" }, + { { "", "b", "c" }, "-", "-b-c" }, + { { "a" }, "-", "a" }, + { { "a" }, ",", "a" }, }; for (const TestCase& test : tests) @@ -136,13 +135,12 @@ namespace lms::core::stringUtils::tests std::string expectedOutput; }; - TestCase tests[] - { - {{""}, ';', '\\', ""}, - {{";"}, ';', '\\', "\\;"}, - {{";;"}, ';', '\\', "\\;\\;"}, - {{"a;", "b"}, ';', '\\', "a\\;;b"}, - {{"a;", "b;"}, ';', '\\', "a\\;;b\\;"}, + TestCase tests[]{ + { { "" }, ';', '\\', "" }, + { { ";" }, ';', '\\', "\\;" }, + { { ";;" }, ';', '\\', "\\;\\;" }, + { { "a;", "b" }, ';', '\\', "a\\;;b" }, + { { "a;", "b;" }, ';', '\\', "a\\;;b\\;" }, }; for (const TestCase& test : tests) @@ -162,13 +160,12 @@ namespace lms::core::stringUtils::tests std::vector expectedOutput; }; - TestCase tests[] - { - {"", ';', '\\', {}}, - {"\\;", ';', '\\', {";"}}, - {"\\;\\;", ';', '\\', {";;"}}, - {"a\\;;b", ';', '\\', {"a;", "b"}}, - {"a\\;;b\\;", ';', '\\', {"a;", "b;"}}, + TestCase tests[]{ + { "", ';', '\\', {} }, + { "\\;", ';', '\\', { ";" } }, + { "\\;\\;", ';', '\\', { ";;" } }, + { "a\\;;b", ';', '\\', { "a;", "b" } }, + { "a\\;;b\\;", ';', '\\', { "a;", "b;" } }, }; for (const TestCase& test : tests) @@ -250,17 +247,16 @@ namespace lms::core::stringUtils::tests std::string expectedOutput; }; - TestCase tests[] - { - {"", ""}, - {"C", "C"}, - {"c", "C"}, - {" c", " C"}, - {" cc", " Cc"}, - {"(c", "(c"}, - {"1c", "1c"}, - {"&c", "&c"}, - {"c c", "C c"} + TestCase tests[]{ + { "", "" }, + { "C", "C" }, + { "c", "C" }, + { " c", " C" }, + { " cc", " Cc" }, + { "(c", "(c" }, + { "1c", "1c" }, + { "&c", "&c" }, + { "c c", "C c" } }; for (const TestCase& test : tests) @@ -279,7 +275,7 @@ namespace lms::core::stringUtils::tests TEST(Stringutils, dateTime) { - const Wt::WDateTime dateTime{ Wt::WDate {2020, 01, 03 }, Wt::WTime{9, 8, 11, 75} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2020, 01, 03 }, Wt::WTime{ 9, 8, 11, 75 } }; EXPECT_EQ(toISO8601String(dateTime), "2020-01-03T09:08:11.075"); } @@ -294,4 +290,4 @@ namespace lms::core::stringUtils::tests EXPECT_FALSE(stringEndsWith("FooBar", "1FooBar")); EXPECT_FALSE(stringEndsWith("FooBar", "R")); } -} \ No newline at end of file +} // namespace lms::core::stringUtils::tests \ No newline at end of file diff --git a/src/libs/core/test/TraceLogger.cpp b/src/libs/core/test/TraceLogger.cpp index fcee9ea27..592faf330 100644 --- a/src/libs/core/test/TraceLogger.cpp +++ b/src/libs/core/test/TraceLogger.cpp @@ -19,6 +19,7 @@ #include #include + #include #include "core/ITraceLogger.hpp" @@ -33,11 +34,10 @@ namespace lms::core::tracing::tests std::vector threads; for (std::size_t i{}; i < 16; ++i) { - threads.emplace_back([&] - { - ScopedTrace loggedEvent{ "MyCategory", Level::Overview, "MyEventLogged", "SomeArgType", "SomeArg", traceLogger.get() }; - ScopedTrace notLoggedEvent{ "MyNotLoggedCategory", Level::Detailed, "MyEventNotLogged", "SomeNotLoggedArgType", "SomeNotLoggedArg", traceLogger.get() }; - }); + threads.emplace_back([&] { + ScopedTrace loggedEvent{ "MyCategory", Level::Overview, "MyEventLogged", "SomeArgType", "SomeArg", traceLogger.get() }; + ScopedTrace notLoggedEvent{ "MyNotLoggedCategory", Level::Detailed, "MyEventNotLogged", "SomeNotLoggedArgType", "SomeNotLoggedArg", traceLogger.get() }; + }); } for (std::thread& t : threads) @@ -56,4 +56,4 @@ namespace lms::core::tracing::tests EXPECT_EQ(oss.str().find("SomeNotLoggedArgType"), std::string::npos); EXPECT_EQ(oss.str().find("SomeNotLoggedArg"), std::string::npos); } -} \ No newline at end of file +} // namespace lms::core::tracing::tests \ No newline at end of file diff --git a/src/libs/core/test/UUID.cpp b/src/libs/core/test/UUID.cpp index 8e61d71a6..9dad6f098 100644 --- a/src/libs/core/test/UUID.cpp +++ b/src/libs/core/test/UUID.cpp @@ -37,4 +37,4 @@ namespace lms::core EXPECT_TRUE(uuid1 >= uuid2); EXPECT_TRUE(uuid1 <= uuid2); } -} \ No newline at end of file +} // namespace lms::core \ No newline at end of file diff --git a/src/libs/core/test/Utils.cpp b/src/libs/core/test/Utils.cpp index 88325e8ba..dc6733728 100644 --- a/src/libs/core/test/Utils.cpp +++ b/src/libs/core/test/Utils.cpp @@ -19,9 +19,8 @@ #include -int main(int argc, char **argv) +int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } - diff --git a/src/libs/database/CMakeLists.txt b/src/libs/database/CMakeLists.txt index b5e5a194a..2d3c2a163 100644 --- a/src/libs/database/CMakeLists.txt +++ b/src/libs/database/CMakeLists.txt @@ -3,6 +3,8 @@ add_library(lmsdatabase SHARED impl/AuthToken.cpp impl/Cluster.cpp impl/Db.cpp + impl/Directory.cpp + impl/Image.cpp impl/Listen.cpp impl/MediaLibrary.cpp impl/Migration.cpp diff --git a/src/libs/database/impl/Artist.cpp b/src/libs/database/impl/Artist.cpp index 92a0e3075..94125bd32 100644 --- a/src/libs/database/impl/Artist.cpp +++ b/src/libs/database/impl/Artist.cpp @@ -20,22 +20,25 @@ #include +#include "core/ILogger.hpp" #include "database/Cluster.hpp" +#include "database/Directory.hpp" +#include "database/Image.hpp" #include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" -#include "core/ILogger.hpp" -#include "SqlQuery.hpp" -#include "Utils.hpp" + #include "EnumSetTraits.hpp" #include "IdTypeTraits.hpp" +#include "SqlQuery.hpp" +#include "Utils.hpp" namespace lms::db { namespace { - template + template Wt::Dbo::Query createQuery(Session& session, std::string_view itemToSelect, const Artist::FindParameters& params) { session.checkReadTransaction(); @@ -97,21 +100,25 @@ namespace lms::db { assert(params.feedbackBackend); query.join("starred_artist s_a ON s_a.artist_id = a.id") - .where("s_a.user_id = ?").bind(params.starringUser) - .where("s_a.backend = ?").bind(*params.feedbackBackend) - .where("s_a.sync_state <> ?").bind(SyncState::PendingRemove); + .where("s_a.user_id = ?") + .bind(params.starringUser) + .where("s_a.backend = ?") + .bind(*params.feedbackBackend) + .where("s_a.sync_state <> ?") + .bind(SyncState::PendingRemove); } if (params.clusters.size() == 1) { query.join("track_cluster t_c ON t_c.track_id = t_a_l.track_id") - .where("t_c.cluster_id = ?").bind(params.clusters.front()); + .where("t_c.cluster_id = ?") + .bind(params.clusters.front()); } else if (params.clusters.size() > 1) { std::ostringstream oss; oss << "a.id IN (SELECT DISTINCT t_a_l.artist_id FROM track_artist_link t_a_l" - " INNER JOIN track_cluster t_c ON t_c.track_id = t_a_l.track_id"; + " INNER JOIN track_cluster t_c ON t_c.track_id = t_a_l.track_id"; WhereClause clusterClause; for (const ClusterId clusterId : params.clusters) @@ -159,7 +166,7 @@ namespace lms::db return query; } - template + template Wt::Dbo::Query createQuery(Session& session, const Artist::FindParameters& params) { std::string_view itemToSelect; @@ -173,12 +180,12 @@ namespace lms::db return createQuery(session, itemToSelect, params); } - } + } // namespace Artist::Artist(const std::string& name, const std::optional& MBID) - : _name{ std::string(name, 0 , _maxNameLength) }, - _sortName{ _name }, - _MBID{ MBID ? MBID->getAsString() : "" } + : _name{ std::string(name, 0, _maxNameLength) } + , _sortName{ _name } + , _MBID{ MBID ? MBID->getAsString() : "" } { } @@ -198,10 +205,7 @@ namespace lms::db { session.checkReadTransaction(); - auto query{ session.getDboSession()->query>("SELECT a FROM artist a") - .orderBy("a.id") - .where("a.id > ?").bind(lastRetrievedArtist) - .limit(static_cast(count)) }; + auto query{ session.getDboSession()->query>("SELECT a FROM artist a").orderBy("a.id").where("a.id > ?").bind(lastRetrievedArtist).limit(static_cast(count)) }; if (library.isValid()) { @@ -209,32 +213,29 @@ namespace lms::db query.where("EXISTS (SELECT 1 FROM track_artist_link t_a_l JOIN track t ON t.id = t_a_l.track_id WHERE t_a_l.artist_id = a.id AND t.media_library_id = ?)").bind(library); } - utils::forEachQueryResult(query, [&](const Artist::pointer& artist) - { - func(artist); - lastRetrievedArtist = artist->getId(); - }); + utils::forEachQueryResult(query, [&](const Artist::pointer& artist) { + func(artist); + lastRetrievedArtist = artist->getId(); + }); } std::vector Artist::find(Session& session, std::string_view name) { session.checkReadTransaction(); - return utils::fetchQueryResults(session.getDboSession()->find() - .where("name = ?").bind(std::string{ name, 0, _maxNameLength }) - .orderBy("LENGTH(mbid) DESC")); // put mbid entries first + return utils::fetchQueryResults(session.getDboSession()->query>("SELECT a FROM artist a").where("a.name = ?").bind(std::string{ name, 0, _maxNameLength }).orderBy("LENGTH(a.mbid) DESC")); // put mbid entries first } Artist::pointer Artist::find(Session& session, const core::UUID& mbid) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find().where("mbid = ?").bind(std::string{ mbid.getAsString() })); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT a FROM artist a").where("a.mbid = ?").bind(std::string{ mbid.getAsString() })); } Artist::pointer Artist::find(Session& session, ArtistId id) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find().where("id = ?").bind(id)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT a FROM artist a").where("a.id = ?").bind(id)); } bool Artist::exists(Session& session, ArtistId id) @@ -274,24 +275,28 @@ namespace lms::db utils::forEachQueryRangeResult(query, params.range, func); } + ObjectPtr Artist::getImage() const + { + return ObjectPtr{ _image.lock() }; + } + RangeResults Artist::findSimilarArtistIds(core::EnumSet artistLinkTypes, std::optional range) const { assert(session()); std::ostringstream oss; - oss << - "SELECT a.id FROM artist a" - " INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id" - " INNER JOIN track t ON t.id = t_a_l.track_id" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" - " WHERE " - " t_c.cluster_id IN (SELECT DISTINCT c.id from cluster c" - " INNER JOIN track t ON c.id = t_c.cluster_id" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" - " INNER JOIN artist a ON a.id = t_a_l.artist_id" - " INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id" - " WHERE a.id = ?)" - " AND a.id <> ?"; + oss << "SELECT a.id FROM artist a" + " INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id" + " INNER JOIN track t ON t.id = t_a_l.track_id" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" + " WHERE " + " t_c.cluster_id IN (SELECT DISTINCT c.id from cluster c" + " INNER JOIN track t ON c.id = t_c.cluster_id" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" + " INNER JOIN artist a ON a.id = t_a_l.artist_id" + " INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id" + " WHERE a.id = ?)" + " AND a.id <> ?"; if (!artistLinkTypes.empty()) { @@ -309,11 +314,7 @@ namespace lms::db oss << ")"; } - auto query{ session()->query(oss.str()) - .bind(getId()) - .bind(getId()) - .groupBy("a.id") - .orderBy("COUNT(*) DESC, RANDOM()") }; + auto query{ session()->query(oss.str()).bind(getId()).bind(getId()).groupBy("a.id").orderBy("COUNT(*) DESC, RANDOM()") }; for (const TrackArtistLinkType type : artistLinkTypes) query.bind(type); @@ -347,11 +348,10 @@ namespace lms::db query.bind(bindArg); std::map> clustersByType; - utils::forEachQueryResult(query, [&](const Cluster::pointer& cluster) - { - if (clustersByType[cluster->getType()->getId()].size() < size) - clustersByType[cluster->getType()->getId()].push_back(cluster); - }); + utils::forEachQueryResult(query, [&](const Cluster::pointer& cluster) { + if (clustersByType[cluster->getType()->getId()].size() < size) + clustersByType[cluster->getType()->getId()].push_back(cluster); + }); std::vector> res; for (const auto& [clusterTypeId, clusters] : clustersByType) @@ -365,4 +365,9 @@ namespace lms::db _sortName = std::string(sortName, 0, _maxNameLength); } + void Artist::setImage(ObjectPtr image) + { + _image = getDboPtr(image); + } + } // namespace lms::db diff --git a/src/libs/database/impl/AuthToken.cpp b/src/libs/database/impl/AuthToken.cpp index acdf936d9..79bc2abb5 100644 --- a/src/libs/database/impl/AuthToken.cpp +++ b/src/libs/database/impl/AuthToken.cpp @@ -20,10 +20,12 @@ #include "database/AuthToken.hpp" #include + #include "database/Session.hpp" #include "database/User.hpp" -#include "StringViewTraits.hpp" + #include "IdTypeTraits.hpp" +#include "StringViewTraits.hpp" #include "Utils.hpp" namespace lms::db @@ -37,7 +39,7 @@ namespace lms::db AuthToken::pointer AuthToken::create(Session& session, std::string_view value, const Wt::WDateTime& expiry, ObjectPtr user) { - return session.getDboSession()->add(std::unique_ptr {new AuthToken{ value, expiry, user }}); + return session.getDboSession()->add(std::unique_ptr{ new AuthToken{ value, expiry, user } }); } void AuthToken::removeExpiredTokens(Session& session, const Wt::WDateTime& now) @@ -53,4 +55,4 @@ namespace lms::db return utils::fetchQuerySingleResult(session.getDboSession()->find().where("value = ?").bind(value)); } -} +} // namespace lms::db diff --git a/src/libs/database/impl/Cluster.cpp b/src/libs/database/impl/Cluster.cpp index ee1dab9b9..af2cabb6c 100644 --- a/src/libs/database/impl/Cluster.cpp +++ b/src/libs/database/impl/Cluster.cpp @@ -20,21 +20,23 @@ #include "database/Cluster.hpp" #include "database/Artist.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 "IdTypeTraits.hpp" -#include "StringViewTraits.hpp" #include "SqlQuery.hpp" +#include "StringViewTraits.hpp" #include "Utils.hpp" namespace lms::db { namespace { - template + template Wt::Dbo::Query createQuery(Session& session, std::string_view itemToSelect, const Cluster::FindParameters& params) { session.checkReadTransaction(); @@ -44,7 +46,7 @@ namespace lms::db if (params.track.isValid() || params.release.isValid()) query.join("track_cluster t_c ON t_c.cluster_id = c.id"); - + if (!params.clusterTypeName.empty()) query.join("cluster_type c_t ON c_t.id = c.cluster_type_id"); @@ -77,7 +79,7 @@ namespace lms::db return query; } - template + template Wt::Dbo::Query createQuery(Session& session, const Cluster::FindParameters& params) { std::string_view itemToSelect; @@ -91,17 +93,17 @@ namespace lms::db return createQuery(session, itemToSelect, params); } - } + } // namespace Cluster::Cluster(ObjectPtr type, std::string_view name) - : _name{ std::string {name, 0, _maxNameLength} }, - _clusterType{ getDboPtr(type) } + : _name{ std::string{ name, 0, _maxNameLength } } + , _clusterType{ getDboPtr(type) } { } Cluster::pointer Cluster::create(Session& session, ObjectPtr type, std::string_view name) { - return session.getDboSession()->add(std::unique_ptr {new Cluster{ type, name }}); + return session.getDboSession()->add(std::unique_ptr{ new Cluster{ type, name } }); } std::size_t Cluster::getCount(Session& session) @@ -154,16 +156,14 @@ namespace lms::db { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(t.id) FROM track t INNER JOIN track_cluster t_c ON t_c.track_id = t.id") - .where("t_c.cluster_id = ?").bind(id)); + return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(t.id) FROM track t INNER JOIN track_cluster t_c ON t_c.track_id = t.id").where("t_c.cluster_id = ?").bind(id)); } std::size_t Cluster::computeReleaseCount(Session& session, ClusterId id) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(DISTINCT r.id) FROM release r INNER JOIN track t on t.release_id = r.id INNER JOIN track_cluster t_c ON t_c.track_id = t.id") - .where("t_c.cluster_id = ?").bind(id)); + return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(DISTINCT 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)); } void Cluster::addTrack(ObjectPtr track) @@ -175,8 +175,7 @@ namespace lms::db { assert(session()); - auto query{ session()->query("SELECT t.id FROM track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id") - .where("c.id = ?").bind(getId()) }; + auto query{ session()->query("SELECT t.id FROM track t INNER JOIN cluster c ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id").where("c.id = ?").bind(getId()) }; return utils::execRangeQuery(query, range); } @@ -188,7 +187,7 @@ namespace lms::db ClusterType::pointer ClusterType::create(Session& session, std::string_view name) { - return session.getDboSession()->add(std::unique_ptr {new ClusterType{ name }}); + return session.getDboSession()->add(std::unique_ptr{ new ClusterType{ name } }); } std::size_t ClusterType::getCount(Session& session) @@ -203,9 +202,9 @@ namespace lms::db session.checkReadTransaction(); auto query{ session.getDboSession()->query( - "SELECT c_t.id from cluster_type c_t" - " LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id") - .where("c.id IS NULL") }; + "SELECT c_t.id from cluster_type c_t" + " LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id") + .where("c.id IS NULL") }; return utils::execRangeQuery(query, range); } @@ -215,8 +214,8 @@ namespace lms::db session.checkReadTransaction(); auto query{ session.getDboSession()->query( - "SELECT DISTINCT c_t.id from cluster_type c_t") - .join("cluster c ON c_t.id = c.cluster_type_id") }; + "SELECT DISTINCT c_t.id from cluster_type c_t") + .join("cluster c ON c_t.id = c.cluster_type_id") }; return utils::execRangeQuery(query, range); } @@ -255,9 +254,7 @@ namespace lms::db assert(self()); assert(session()); - return utils::fetchQuerySingleResult(session()->find() - .where("name = ?").bind(name) - .where("cluster_type_id = ?").bind(getId())); + return utils::fetchQuerySingleResult(session()->find().where("name = ?").bind(name).where("cluster_type_id = ?").bind(getId())); } std::vector ClusterType::getClusters() const @@ -265,8 +262,6 @@ namespace lms::db assert(self()); assert(session()); - return utils::fetchQueryResults(session()->find() - .where("cluster_type_id = ?").bind(getId()) - .orderBy("name")); + return utils::fetchQueryResults(session()->find().where("cluster_type_id = ?").bind(getId()).orderBy("name")); } } // namespace lms::db diff --git a/src/libs/database/impl/Db.cpp b/src/libs/database/impl/Db.cpp index 253d82c7a..e4d95a717 100644 --- a/src/libs/database/impl/Db.cpp +++ b/src/libs/database/impl/Db.cpp @@ -22,12 +22,12 @@ #include #include -#include "database/Session.hpp" -#include "database/User.hpp" #include "core/IConfig.hpp" #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" #include "core/Service.hpp" +#include "database/Session.hpp" +#include "database/User.hpp" namespace lms::db { @@ -68,7 +68,7 @@ namespace lms::db std::filesystem::path _dbPath; }; - } + } // namespace // Session living class handling the database and the login Db::Db(const std::filesystem::path& dbPath, std::size_t connectionCount) @@ -76,7 +76,7 @@ namespace lms::db LMS_LOG(DB, INFO, "Creating connection pool on file " << dbPath.string()); auto connection{ std::make_unique(dbPath.string()) }; - if (core::IConfig * config{ core::Service::get() })// may not be here on testU + if (core::IConfig * config{ core::Service::get() }) // may not be here on testU connection->setProperty("show-queries", config->getBool("db-show-queries", false) ? "true" : "false"); auto connectionPool{ std::make_unique(std::move(connection), connectionCount) }; diff --git a/src/libs/database/impl/Directory.cpp b/src/libs/database/impl/Directory.cpp new file mode 100644 index 000000000..8b9ba07ae --- /dev/null +++ b/src/libs/database/impl/Directory.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "database/Directory.hpp" + +#include "database/Session.hpp" + +#include "IdTypeTraits.hpp" +#include "PathTraits.hpp" +#include "Utils.hpp" + +namespace lms::db +{ + namespace + { + Wt::Dbo::Query> createQuery(Session& session, const Directory::FindParameters& params) + { + auto query{ session.getDboSession()->query>("SELECT d FROM directory d") }; + + if (params.artist.isValid()) + { + query.join("track t ON t.directory_id = d.id") + .join("artist a ON a.id = t_a_l.artist_id") + .join("track_artist_link t_a_l ON t_a_l.track_id = t.id") + .where("a.id = ?") + .bind(params.artist); + + if (!params.trackArtistLinkTypes.empty()) + { + std::ostringstream oss; + + bool first{ true }; + for (TrackArtistLinkType linkType : params.trackArtistLinkTypes) + { + if (!first) + oss << " OR "; + oss << "t_a_l.type = ?"; + query.bind(linkType); + + first = false; + } + query.where(oss.str()); + } + + query.groupBy("d.id"); + } + + return query; + } + } // namespace + + Directory::Directory(const std::filesystem::path& p) + { + setAbsolutePath(p); + } + + Directory::pointer Directory::create(Session& session, const std::filesystem::path& p) + { + return session.getDboSession()->add(std::unique_ptr{ new Directory{ p } }); + } + + std::size_t Directory::getCount(Session& session) + { + session.checkReadTransaction(); + + return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(*) FROM directory")); + } + + Directory::pointer Directory::find(Session& session, DirectoryId id) + { + session.checkReadTransaction(); + + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT d from directory d").where("d.id = ?").bind(id)); + } + + Directory::pointer Directory::find(Session& session, const std::filesystem::path& path) + { + session.checkReadTransaction(); + + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT d from directory d").where("d.absolute_path = ?").bind(path)); + } + + void Directory::find(Session& session, DirectoryId& lastRetrievedDirectory, std::size_t count, const std::function& func) + { + session.checkReadTransaction(); + + auto query{ session.getDboSession()->query>("SELECT d from directory d").orderBy("d.id").where("d.id > ?").bind(lastRetrievedDirectory).limit(static_cast(count)) }; + + utils::forEachQueryResult(query, [&](const Directory::pointer& image) { + func(image); + lastRetrievedDirectory = image->getId(); + }); + } + + void Directory::find(Session& session, const FindParameters& params, const std::function& func) + { + auto query{ createQuery(session, params) }; + utils::forEachQueryResult(query, [&func](const Directory::pointer& dir) { + func(dir); + }); + } + + RangeResults Directory::findOrphanIds(Session& session, std::optional range) + { + session.checkReadTransaction(); + + auto query{ session.getDboSession()->query("SELECT d.id FROM directory d") }; + query.leftJoin("directory d_child ON d.id = d_child.parent_directory_id"); + query.leftJoin("track t ON d.id = t.directory_id"); + query.leftJoin("image i ON d.id = i.directory_id"); + query.where("d_child.id IS NULL"); + query.where("t.directory_id IS NULL"); + query.where("i.directory_id IS NULL"); + + return utils::execRangeQuery(query, range); + } + + void Directory::setAbsolutePath(const std::filesystem::path& p) + { + assert(p.is_absolute()); + + if (!p.has_filename() && p.has_parent_path()) + { + _absolutePath = p.parent_path(); + _name = _absolutePath.filename(); + } + else + { + _absolutePath = p; + _name = p.filename(); + } + } + + void Directory::setParent(ObjectPtr parent) + { +#ifndef NDEBUG + if (parent) + { + assert(_absolutePath.has_parent_path()); + assert(parent->getAbsolutePath() == _absolutePath.parent_path()); + } +#endif + + _parent = getDboPtr(parent); + } +} // namespace lms::db diff --git a/src/libs/database/impl/EnumSetTraits.hpp b/src/libs/database/impl/EnumSetTraits.hpp index 3852babdc..6e2547aed 100644 --- a/src/libs/database/impl/EnumSetTraits.hpp +++ b/src/libs/database/impl/EnumSetTraits.hpp @@ -20,6 +20,7 @@ #pragma once #include + #include #include "core/EnumSet.hpp" @@ -49,5 +50,4 @@ namespace Wt::Dbo return false; } }; -} - +} // namespace Wt::Dbo diff --git a/src/libs/database/impl/IdTypeTraits.hpp b/src/libs/database/impl/IdTypeTraits.hpp index 8158d50a5..2313bb4b4 100644 --- a/src/libs/database/impl/IdTypeTraits.hpp +++ b/src/libs/database/impl/IdTypeTraits.hpp @@ -20,6 +20,7 @@ #pragma once #include + #include #include "database/Types.hpp" @@ -55,5 +56,4 @@ namespace Wt::Dbo return false; } }; -} - +} // namespace Wt::Dbo diff --git a/src/libs/database/impl/Image.cpp b/src/libs/database/impl/Image.cpp new file mode 100644 index 000000000..62db5af7f --- /dev/null +++ b/src/libs/database/impl/Image.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "database/Image.hpp" + +#include + +#include "database/Artist.hpp" +#include "database/Directory.hpp" +#include "database/Session.hpp" + +#include "IdTypeTraits.hpp" +#include "PathTraits.hpp" +#include "Utils.hpp" + +namespace lms::db +{ + namespace + { + Wt::Dbo::Query> createQuery(Session& session, const Image::FindParameters& params) + { + auto query{ session.getDboSession()->query>("SELECT i FROM image i") }; + + if (params.directory.isValid()) + query.where("i.directory_id = ?").bind(params.directory); + if (!params.fileStem.empty()) + query.where("i.stem = ?").bind(params.fileStem); + + return query; + } + } // namespace + + Image::Image(const std::filesystem::path& p) + { + setAbsoluteFilePath(p); + } + + Image::pointer Image::create(Session& session, const std::filesystem::path& p) + { + return session.getDboSession()->add(std::unique_ptr{ new Image{ p } }); + } + + std::size_t Image::getCount(Session& session) + { + session.checkReadTransaction(); + + return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(*) FROM image")); + } + + Image::pointer Image::find(Session& session, ImageId id) + { + session.checkReadTransaction(); + + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT i from image i").where("i.id = ?").bind(id)); + } + + Image::pointer Image::find(Session& session, const std::filesystem::path& path) + { + session.checkReadTransaction(); + + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT i from image i").where("i.absolute_file_path = ?").bind(path)); + } + + void Image::find(Session& session, ImageId& lastRetrievedImage, std::size_t count, const std::function& func) + { + session.checkReadTransaction(); + + auto query{ session.getDboSession()->query>("SELECT i from image i").orderBy("i.id").where("i.id > ?").bind(lastRetrievedImage).limit(static_cast(count)) }; + + utils::forEachQueryResult(query, [&](const Image::pointer& image) { + func(image); + lastRetrievedImage = image->getId(); + }); + } + + RangeResults Image::find(Session& session, const FindParameters& params) + { + session.checkReadTransaction(); + + auto query{ createQuery(session, params) }; + return utils::execRangeQuery(query, params.range); + } + + void Image::find(Session& session, const FindParameters& params, const std::function& func) + { + auto query{ createQuery(session, params) }; + utils::forEachQueryResult(query, [&](const Image::pointer& image) { + func(image); + }); + } + + void Image::setAbsoluteFilePath(const std::filesystem::path& p) + { + assert(p.is_absolute()); + _fileAbsolutePath = p; + _fileStem = p.stem().string(); + } + +} // namespace lms::db diff --git a/src/libs/database/impl/Listen.cpp b/src/libs/database/impl/Listen.cpp index dc67f690d..9c5b6c17b 100644 --- a/src/libs/database/impl/Listen.cpp +++ b/src/libs/database/impl/Listen.cpp @@ -21,6 +21,7 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" + #include "IdTypeTraits.hpp" #include "SqlQuery.hpp" #include "Utils.hpp" @@ -31,9 +32,7 @@ namespace lms::db { Wt::Dbo::Query createArtistsQuery(Session& session, const Listen::ArtistStatsFindParameters& params) { - auto query{ session.getDboSession()->query("SELECT a.id from artist a") - .join("track_artist_link t_a_l ON t_a_l.artist_id = a.id") - .join("listen l ON l.track_id = t_a_l.track_id") }; + auto query{ session.getDboSession()->query("SELECT a.id from artist a").join("track_artist_link t_a_l ON t_a_l.artist_id = a.id").join("listen l ON l.track_id = t_a_l.track_id") }; if (params.user.isValid()) query.where("l.user_id = ?").bind(params.user); @@ -56,7 +55,7 @@ namespace lms::db { std::ostringstream oss; oss << "a.id IN (SELECT DISTINCT t_a_l.artist_id FROM track_artist_link t_a_l" - " INNER JOIN track_cluster t_c ON t_c.track_id = t_a_l.track_id"; + " INNER JOIN track_cluster t_c ON t_c.track_id = t_a_l.track_id"; WhereClause clusterClause; for (auto id : params.clusters) @@ -96,9 +95,7 @@ namespace lms::db Wt::Dbo::Query createReleasesQuery(Session& session, const Listen::StatsFindParameters& params) { - auto query{ session.getDboSession()->query("SELECT r.id from release r") - .join("track t ON t.release_id = r.id") - .join("listen l ON l.track_id = t.id") }; + auto query{ session.getDboSession()->query("SELECT r.id from release r").join("track t ON t.release_id = r.id").join("listen l ON l.track_id = t.id") }; if (params.user.isValid()) query.where("l.user_id = ?").bind(params.user); @@ -109,7 +106,8 @@ namespace lms::db if (params.artist.isValid()) { query.join("track_artist_link t_a_l ON t_a_l.track_id = t.id") - .where("t_a_l.artist_id = ?").bind(params.artist); + .where("t_a_l.artist_id = ?") + .bind(params.artist); } if (params.library.isValid()) @@ -119,9 +117,9 @@ namespace lms::db { std::ostringstream oss; oss << "r.id IN (SELECT DISTINCT r.id FROM release r" - " INNER JOIN track t ON t.release_id = r.id" - " INNER JOIN cluster c ON c.id = t_c.cluster_id" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + " INNER JOIN track t ON t.release_id = r.id" + " INNER JOIN cluster c ON c.id = t_c.cluster_id" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; WhereClause clusterClause; for (ClusterId id : params.clusters) @@ -144,8 +142,7 @@ namespace lms::db Wt::Dbo::Query createTracksQuery(Session& session, const Listen::StatsFindParameters& params) { - auto query{ session.getDboSession()->query("SELECT t.id from track t") - .join("listen l ON l.track_id = t.id") }; + auto query{ session.getDboSession()->query("SELECT t.id from track t").join("listen l ON l.track_id = t.id") }; if (params.user.isValid()) query.where("l.user_id = ?").bind(params.user); @@ -156,7 +153,8 @@ namespace lms::db if (params.artist.isValid()) { query.join("track_artist_link t_a_l ON t_a_l.track_id = t.id") - .where("t_a_l.artist_id = ?").bind(params.artist); + .where("t_a_l.artist_id = ?") + .bind(params.artist); } if (params.library.isValid()) @@ -166,8 +164,8 @@ namespace lms::db { std::ostringstream oss; oss << "t.id IN (SELECT DISTINCT t.id FROM track t" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" - " INNER JOIN cluster c ON c.id = t_c.cluster_id"; + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" + " INNER JOIN cluster c ON c.id = t_c.cluster_id"; WhereClause clusterClause; for (auto id : params.clusters) @@ -187,19 +185,20 @@ namespace lms::db return query; } - } + } // namespace Listen::Listen(ObjectPtr user, ObjectPtr track, ScrobblingBackend backend, const Wt::WDateTime& dateTime) : _dateTime{ Wt::WDateTime::fromTime_t(dateTime.toTime_t()) } , _backend{ backend } , _user{ getDboPtr(user) } , _track{ getDboPtr(track) } - {} + { + } Listen::pointer Listen::create(Session& session, ObjectPtr user, ObjectPtr track, ScrobblingBackend backend, const Wt::WDateTime& dateTime) { session.checkWriteTransaction(); - return session.getDboSession()->add(std::unique_ptr {new Listen{ user, track, backend, dateTime }}); + return session.getDboSession()->add(std::unique_ptr{ new Listen{ user, track, backend, dateTime } }); } std::size_t Listen::getCount(Session& session) @@ -211,15 +210,14 @@ namespace lms::db Listen::pointer Listen::find(Session& session, ListenId id) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find().where("id = ?").bind(id)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT l from listen l").where("l.id = ?").bind(id)); } RangeResults Listen::find(Session& session, const FindParameters& parameters) { session.checkReadTransaction(); - auto query{ session.getDboSession()->query("SELECT id FROM listen") - .orderBy("date_time") }; + auto query{ session.getDboSession()->query("SELECT id FROM listen").orderBy("date_time") }; if (parameters.user.isValid()) query.where("user_id = ?").bind(parameters.user); @@ -237,11 +235,7 @@ namespace lms::db { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("user_id = ?").bind(userId) - .where("track_id = ?").bind(trackId) - .where("backend = ?").bind(backend) - .where("date_time = ?").bind(Wt::WDateTime::fromTime_t(dateTime.toTime_t()))); + return utils::fetchQuerySingleResult(session.getDboSession()->find().where("user_id = ?").bind(userId).where("track_id = ?").bind(trackId).where("backend = ?").bind(backend).where("date_time = ?").bind(Wt::WDateTime::fromTime_t(dateTime.toTime_t()))); } RangeResults Listen::getTopArtists(Session& session, const ArtistStatsFindParameters& params) @@ -250,8 +244,8 @@ namespace lms::db auto query{ createArtistsQuery(session, params) }; auto collection{ query - .orderBy("COUNT(a.id) DESC") - .groupBy("a.id") }; + .orderBy("COUNT(a.id) DESC") + .groupBy("a.id") }; return utils::execRangeQuery(query, params.range); } @@ -280,7 +274,8 @@ namespace lms::db { session.checkReadTransaction(); auto query{ createArtistsQuery(session, params) - .groupBy("a.id").having("l.date_time = MAX(l.date_time)") + .groupBy("a.id") + .having("l.date_time = MAX(l.date_time)") .orderBy("l.date_time DESC") }; return utils::execRangeQuery(query, params.range); @@ -290,7 +285,8 @@ namespace lms::db { session.checkReadTransaction(); auto query{ createReleasesQuery(session, params) - .groupBy("r.id").having("l.date_time = MAX(l.date_time)") + .groupBy("r.id") + .having("l.date_time = MAX(l.date_time)") .orderBy("l.date_time DESC") }; return utils::execRangeQuery(query, params.range); @@ -300,7 +296,8 @@ namespace lms::db { session.checkReadTransaction(); auto query{ createTracksQuery(session, params) - .groupBy("t.id").having("l.date_time = MAX(l.date_time)") + .groupBy("t.id") + .having("l.date_time = MAX(l.date_time)") .orderBy("l.date_time DESC") }; return utils::execRangeQuery(query, params.range); @@ -310,11 +307,7 @@ namespace lms::db { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(*) from listen l") - .join("user u ON u.id = l.user_id") - .where("l.track_id = ?").bind(trackId) - .where("l.user_id = ?").bind(userId) - .where("l.backend = u.scrobbling_backend")); + return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT COUNT(*) from listen l").join("user u ON u.id = l.user_id").where("l.track_id = ?").bind(trackId).where("l.user_id = ?").bind(userId).where("l.backend = u.scrobbling_backend")); } std::size_t Listen::getCount(Session& session, UserId userId, ReleaseId releaseId) @@ -322,16 +315,16 @@ namespace lms::db session.checkReadTransaction(); return utils::fetchQuerySingleResult(session.getDboSession()->query( - "SELECT IFNULL(MIN(count_result), 0)" - " FROM (" - " SELECT COUNT(l.track_id) AS count_result" - " FROM track t" - " LEFT JOIN listen l ON t.id = l.track_id AND l.backend = (SELECT scrobbling_backend FROM user WHERE id = ?) AND l.user_id = ?" - " WHERE t.release_id = ?" - " GROUP BY t.id)") - .bind(userId) - .bind(userId) - .bind(releaseId)); + "SELECT IFNULL(MIN(count_result), 0)" + " FROM (" + " SELECT COUNT(l.track_id) AS count_result" + " FROM track t" + " LEFT JOIN listen l ON t.id = l.track_id AND l.backend = (SELECT scrobbling_backend FROM user WHERE id = ?) AND l.user_id = ?" + " WHERE t.release_id = ?" + " GROUP BY t.id)") + .bind(userId) + .bind(userId) + .bind(releaseId)); } Listen::pointer Listen::getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId releaseId) @@ -339,24 +332,13 @@ namespace lms::db session.checkReadTransaction(); // TODO not pending remove? - return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT l from listen l") - .join("track t ON l.track_id = t.id") - .where("t.release_id = ?").bind(releaseId) - .where("l.user_id = ?").bind(userId) - .where("l.backend = ?").bind(backend) - .orderBy("l.date_time DESC") - .limit(1)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT l from listen l").join("track t ON l.track_id = t.id").where("t.release_id = ?").bind(releaseId).where("l.user_id = ?").bind(userId).where("l.backend = ?").bind(backend).orderBy("l.date_time DESC").limit(1)); } Listen::pointer Listen::getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, TrackId trackId) { session.checkReadTransaction(); // TODO not pending remove? - return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT l from listen l") - .where("l.track_id = ?").bind(trackId) - .where("l.user_id = ?").bind(userId) - .where("l.backend = ?").bind(backend) - .orderBy("l.date_time DESC") - .limit(1)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT l from listen l").where("l.track_id = ?").bind(trackId).where("l.user_id = ?").bind(userId).where("l.backend = ?").bind(backend).orderBy("l.date_time DESC").limit(1)); } } // namespace lms::db diff --git a/src/libs/database/impl/MediaLibrary.cpp b/src/libs/database/impl/MediaLibrary.cpp index f30105c75..59655bc17 100644 --- a/src/libs/database/impl/MediaLibrary.cpp +++ b/src/libs/database/impl/MediaLibrary.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013-2016 Emeric Poupon + * Copyright (C) 2024 Emeric Poupon * * This file is part of LMS. * @@ -22,6 +22,7 @@ #include "database/ScanSettings.hpp" #include "database/Session.hpp" #include "database/Track.hpp" + #include "IdTypeTraits.hpp" #include "PathTraits.hpp" #include "StringViewTraits.hpp" @@ -30,8 +31,8 @@ namespace lms::db { MediaLibrary::MediaLibrary(const std::filesystem::path& p, std::string_view name) - : _path{ p }, - _name{ std::string {name, 0, maxNameLength} } + : _path{ p } + , _name{ std::string{ name, 0, maxNameLength } } { } @@ -72,9 +73,8 @@ namespace lms::db { session.checkReadTransaction(); - utils::forEachQueryResult(session.getDboSession()->find(), [&](const MediaLibrary::pointer& mediaLibrary) - { - func(mediaLibrary); - }); + utils::forEachQueryResult(session.getDboSession()->find(), [&](const MediaLibrary::pointer& mediaLibrary) { + func(mediaLibrary); + }); } } // namespace lms::db diff --git a/src/libs/database/impl/Migration.cpp b/src/libs/database/impl/Migration.cpp index 7c76f7940..8efda1f19 100644 --- a/src/libs/database/impl/Migration.cpp +++ b/src/libs/database/impl/Migration.cpp @@ -21,25 +21,27 @@ #include +#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 "core/Exception.hpp" -#include "core/ILogger.hpp" -#include "core/ITraceLogger.hpp" + #include "Utils.hpp" namespace lms::db { namespace { - static constexpr Version LMS_DATABASE_VERSION{ 59 }; + static constexpr Version LMS_DATABASE_VERSION{ 61 }; } VersionInfo::VersionInfo() : _version{ LMS_DATABASE_VERSION } - {} + { + } VersionInfo::pointer VersionInfo::getOrCreate(Session& session) { @@ -58,14 +60,15 @@ namespace lms::db return utils::fetchQuerySingleResult(session.getDboSession()->find()); } -} +} // namespace lms::db namespace lms::db::Migration { class ScopedNoForeignKeys { public: - ScopedNoForeignKeys(Db& db) : _db{ db } + ScopedNoForeignKeys(Db& db) + : _db{ db } { _db.executeSql("PRAGMA foreign_keys=OFF"); } @@ -73,6 +76,7 @@ namespace lms::db::Migration { _db.executeSql("PRAGMA foreign_keys=ON"); } + private: ScopedNoForeignKeys(const ScopedNoForeignKeys&) = delete; ScopedNoForeignKeys(ScopedNoForeignKeys&&) = delete; @@ -82,11 +86,13 @@ namespace lms::db::Migration Db& _db; }; - static void migrateFromV33(Session& session) + namespace { - // remove name from track_artist_link - // Drop Auth mode - session.getDboSession()->execute(R"( + void migrateFromV33(Session& session) + { + // remove name from track_artist_link + // Drop Auth mode + session.getDboSession()->execute(R"( CREATE TABLE IF NOT EXISTS "track_artist_link_backup" ( "id" integer primary key autoincrement, "version" integer not null, @@ -97,49 +103,49 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" ( constraint "fk_track_artist_link_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred ); ))"); - session.getDboSession()->execute("INSERT INTO track_artist_link_backup SELECT id, version, type, track_id, artist_id FROM track_artist_link"); - session.getDboSession()->execute("DROP TABLE track_artist_link"); - session.getDboSession()->execute("ALTER TABLE track_artist_link_backup RENAME TO track_artist_link"); - } + session.getDboSession()->execute("INSERT INTO track_artist_link_backup SELECT id, version, type, track_id, artist_id FROM track_artist_link"); + session.getDboSession()->execute("DROP TABLE track_artist_link"); + session.getDboSession()->execute("ALTER TABLE track_artist_link_backup RENAME TO track_artist_link"); + } - static void migrateFromV34(Session& session) - { - // Add scrobbling state - // By default, everything needs to be sent - session.getDboSession()->execute("ALTER TABLE starred_artist ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast(/*ScrobblingState::PendingAdd*/0)) + ")"); - session.getDboSession()->execute("ALTER TABLE starred_release ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast(/*ScrobblingState::PendingAdd*/0)) + ")"); - session.getDboSession()->execute("ALTER TABLE starred_track ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast(/*ScrobblingState::PendingAdd*/0)) + ")"); - } + void migrateFromV34(Session& session) + { + // Add scrobbling state + // By default, everything needs to be sent + session.getDboSession()->execute("ALTER TABLE starred_artist ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast(/*ScrobblingState::PendingAdd*/ 0)) + ")"); + session.getDboSession()->execute("ALTER TABLE starred_release ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast(/*ScrobblingState::PendingAdd*/ 0)) + ")"); + session.getDboSession()->execute("ALTER TABLE starred_track ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast(/*ScrobblingState::PendingAdd*/ 0)) + ")"); + } - static void migrateFromV35(Session& session) - { - // Add creattion/last modif date time for tracklists - session.getDboSession()->execute("ALTER TABLE tracklist ADD creation_date_time TEXT"); - session.getDboSession()->execute("ALTER TABLE tracklist ADD last_modified_date_time TEXT"); - } + void migrateFromV35(Session& session) + { + // Add creattion/last modif date time for tracklists + session.getDboSession()->execute("ALTER TABLE tracklist ADD creation_date_time TEXT"); + session.getDboSession()->execute("ALTER TABLE tracklist ADD last_modified_date_time TEXT"); + } - static void migrateFromV36(Session& session) - { - // Increased precision for track durations (now in milliseconds instead of secodns) - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + void migrateFromV36(Session& session) + { + // Increased precision for track durations (now in milliseconds instead of secodns) + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - static void migrateFromV37(Session& session) - { - // Support Performer tags (via subtypes) - session.getDboSession()->execute("ALTER TABLE track_artist_link ADD subtype TEXT"); + void migrateFromV37(Session& session) + { + // Support Performer tags (via subtypes) + session.getDboSession()->execute("ALTER TABLE track_artist_link ADD subtype TEXT"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - static void migrateFromV38(Session& session) - { - // migrate release-specific tags from Track to Release - session.getDboSession()->execute("ALTER TABLE release ADD total_disc INTEGER"); + void migrateFromV38(Session& session) + { + // migrate release-specific tags from Track to Release + session.getDboSession()->execute("ALTER TABLE release ADD total_disc INTEGER"); - session.getDboSession()->execute(R"( + session.getDboSession()->execute(R"( CREATE TABLE IF NOT EXISTS "track_backup" ( "id" integer primary key autoincrement, "version" integer not null, @@ -167,177 +173,178 @@ CREATE TABLE IF NOT EXISTS "track_backup" ( constraint "fk_track_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred ); ))"); - session.getDboSession()->execute("INSERT INTO track_backup SELECT id, version, scan_version, rating, track_number, disc_number, total_track, disc_subtitle, name, duration, date, original_date, file_path, file_last_write, file_added, has_cover, mbid, recording_mbid, copyright, copyright_url, track_replay_gain, release_replay_gain, release_id FROM track"); - session.getDboSession()->execute("DROP TABLE track"); - session.getDboSession()->execute("ALTER TABLE track_backup RENAME TO track"); + session.getDboSession()->execute("INSERT INTO track_backup SELECT id, version, scan_version, rating, track_number, disc_number, total_track, disc_subtitle, name, duration, date, original_date, file_path, file_last_write, file_added, has_cover, mbid, recording_mbid, copyright, copyright_url, track_replay_gain, release_replay_gain, release_id FROM track"); + session.getDboSession()->execute("DROP TABLE track"); + session.getDboSession()->execute("ALTER TABLE track_backup RENAME TO track"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - static void migrateFromV39(Session& session) - { - // add release type - session.getDboSession()->execute("ALTER TABLE release ADD primary_type INTEGER"); - session.getDboSession()->execute("ALTER TABLE release ADD secondary_types INTEGER"); + void migrateFromV39(Session& session) + { + // add release type + session.getDboSession()->execute("ALTER TABLE release ADD primary_type INTEGER"); + session.getDboSession()->execute("ALTER TABLE release ADD secondary_types INTEGER"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - static void migrateFromV40(Session& session) - { - // add artist_display_name in Release and Track - session.getDboSession()->execute("ALTER TABLE release ADD artist_display_name TEXT NOT NULL DEFAULT ''"); - session.getDboSession()->execute("ALTER TABLE track ADD artist_display_name TEXT NOT NULL DEFAULT ''"); + void migrateFromV40(Session& session) + { + // add artist_display_name in Release and Track + session.getDboSession()->execute("ALTER TABLE release ADD artist_display_name TEXT NOT NULL DEFAULT ''"); + session.getDboSession()->execute("ALTER TABLE track ADD artist_display_name TEXT NOT NULL DEFAULT ''"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - static void migrateFromV41(Session& session) - { - // add artist_display_name in Release and Track - session.getDboSession()->execute("ALTER TABLE user RENAME COLUMN subsonic_transcode_format TO subsonic_default_transcode_format"); - session.getDboSession()->execute("ALTER TABLE user RENAME COLUMN subsonic_transcode_bitrate TO subsonic_default_transcode_bitrate"); - session.getDboSession()->execute("ALTER TABLE user DROP COLUMN subsonic_transcode_enable"); - } + void migrateFromV41(Session& session) + { + // add artist_display_name in Release and Track + session.getDboSession()->execute("ALTER TABLE user RENAME COLUMN subsonic_transcode_format TO subsonic_default_transcode_format"); + session.getDboSession()->execute("ALTER TABLE user RENAME COLUMN subsonic_transcode_bitrate TO subsonic_default_transcode_bitrate"); + session.getDboSession()->execute("ALTER TABLE user DROP COLUMN subsonic_transcode_enable"); + } - static void migrateFromV42(Session& session) - { - session.getDboSession()->execute("DROP INDEX IF EXISTS listen_scrobbler_idx"); - session.getDboSession()->execute("DROP INDEX IF EXISTS listen_user_scrobbler_idx"); - session.getDboSession()->execute("DROP INDEX IF EXISTS listen_user_track_scrobbler_date_time_idx"); - session.getDboSession()->execute("DROP INDEX IF EXISTS starred_artist_user_scrobbler_idx"); - session.getDboSession()->execute("DROP INDEX IF EXISTS starred_artist_artist_user_scrobbler_idx"); - session.getDboSession()->execute("DROP INDEX IF EXISTS starred_release_user_scrobbler_idx"); - session.getDboSession()->execute("DROP INDEX IF EXISTS starred_release_release_user_scrobbler_idx"); - session.getDboSession()->execute("DROP INDEX IF EXISTS starred_track_user_scrobbler_idx"); - session.getDboSession()->execute("DROP INDEX IF EXISTS starred_track_track_user_scrobbler_idx"); - - // New feedback service that now handles the star/unstar stuff (that was previously handled by the scrobbling service) - session.getDboSession()->execute("ALTER TABLE user RENAME COLUMN scrobbler TO scrobbling_backend"); - session.getDboSession()->execute("ALTER TABLE user ADD feedback_backend INTEGER"); - session.getDboSession()->execute("ALTER TABLE listen RENAME COLUMN scrobbler TO backend"); - session.getDboSession()->execute("ALTER TABLE listen RENAME COLUMN scrobbling_state TO sync_state"); - session.getDboSession()->execute("ALTER TABLE starred_artist RENAME COLUMN scrobbler TO backend"); - session.getDboSession()->execute("ALTER TABLE starred_artist RENAME COLUMN scrobbling_state TO sync_state"); - session.getDboSession()->execute("ALTER TABLE starred_release RENAME COLUMN scrobbler TO backend"); - session.getDboSession()->execute("ALTER TABLE starred_release RENAME COLUMN scrobbling_state TO sync_state"); - session.getDboSession()->execute("ALTER TABLE starred_track RENAME COLUMN scrobbler TO backend"); - session.getDboSession()->execute("ALTER TABLE starred_track RENAME COLUMN scrobbling_state TO sync_state"); - - session.getDboSession()->execute("UPDATE user SET feedback_backend = scrobbling_backend"); - } + void migrateFromV42(Session& session) + { + session.getDboSession()->execute("DROP INDEX IF EXISTS listen_scrobbler_idx"); + session.getDboSession()->execute("DROP INDEX IF EXISTS listen_user_scrobbler_idx"); + session.getDboSession()->execute("DROP INDEX IF EXISTS listen_user_track_scrobbler_date_time_idx"); + session.getDboSession()->execute("DROP INDEX IF EXISTS starred_artist_user_scrobbler_idx"); + session.getDboSession()->execute("DROP INDEX IF EXISTS starred_artist_artist_user_scrobbler_idx"); + session.getDboSession()->execute("DROP INDEX IF EXISTS starred_release_user_scrobbler_idx"); + session.getDboSession()->execute("DROP INDEX IF EXISTS starred_release_release_user_scrobbler_idx"); + session.getDboSession()->execute("DROP INDEX IF EXISTS starred_track_user_scrobbler_idx"); + session.getDboSession()->execute("DROP INDEX IF EXISTS starred_track_track_user_scrobbler_idx"); + + // New feedback service that now handles the star/unstar stuff (that was previously handled by the scrobbling service) + session.getDboSession()->execute("ALTER TABLE user RENAME COLUMN scrobbler TO scrobbling_backend"); + session.getDboSession()->execute("ALTER TABLE user ADD feedback_backend INTEGER"); + session.getDboSession()->execute("ALTER TABLE listen RENAME COLUMN scrobbler TO backend"); + session.getDboSession()->execute("ALTER TABLE listen RENAME COLUMN scrobbling_state TO sync_state"); + session.getDboSession()->execute("ALTER TABLE starred_artist RENAME COLUMN scrobbler TO backend"); + session.getDboSession()->execute("ALTER TABLE starred_artist RENAME COLUMN scrobbling_state TO sync_state"); + session.getDboSession()->execute("ALTER TABLE starred_release RENAME COLUMN scrobbler TO backend"); + session.getDboSession()->execute("ALTER TABLE starred_release RENAME COLUMN scrobbling_state TO sync_state"); + session.getDboSession()->execute("ALTER TABLE starred_track RENAME COLUMN scrobbler TO backend"); + session.getDboSession()->execute("ALTER TABLE starred_track RENAME COLUMN scrobbling_state TO sync_state"); + + session.getDboSession()->execute("UPDATE user SET feedback_backend = scrobbling_backend"); + } - static void migrateFromV43(Session& session) - { - // add counts in genre table - session.getDboSession()->execute("ALTER TABLE cluster ADD track_count INTEGER"); - session.getDboSession()->execute("ALTER TABLE cluster ADD release_count INTEGER"); + void migrateFromV43(Session& session) + { + // add counts in genre table + session.getDboSession()->execute("ALTER TABLE cluster ADD track_count INTEGER"); + session.getDboSession()->execute("ALTER TABLE cluster ADD release_count INTEGER"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - static void migrateFromV44(Session& session) - { - // add bitrate - session.getDboSession()->execute("ALTER TABLE track ADD bitrate INTEGER NOT NULL DEFAULT 0"); + void migrateFromV44(Session& session) + { + // add bitrate + session.getDboSession()->execute("ALTER TABLE track ADD bitrate INTEGER NOT NULL DEFAULT 0"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - void migrateFromV45(Session& session) - { - // add subsonic_enable_transcoding_by_default, default is disabled - session.getDboSession()->execute("ALTER TABLE user ADD subsonic_enable_transcoding_by_default INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast(/*User::defaultSubsonicEnableTranscodingByDefault*/0)) + ")"); - } + void migrateFromV45(Session& session) + { + // add subsonic_enable_transcoding_by_default, default is disabled + session.getDboSession()->execute("ALTER TABLE user ADD subsonic_enable_transcoding_by_default INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast(/*User::defaultSubsonicEnableTranscodingByDefault*/ 0)) + ")"); + } - void migrateFromV46(Session& session) - { - // add extra tags to parse - session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "cluster_type_backup" ( + void migrateFromV46(Session& session) + { + // add extra tags to parse + session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "cluster_type_backup" ( "id" integer primary key autoincrement, "version" integer not null, "name" text not null );)"); - session.getDboSession()->execute("INSERT INTO cluster_type_backup SELECT id, version, name FROM cluster_type"); - session.getDboSession()->execute("DROP TABLE cluster_type"); - session.getDboSession()->execute("ALTER TABLE cluster_type_backup RENAME TO cluster_type"); + session.getDboSession()->execute("INSERT INTO cluster_type_backup SELECT id, version, name FROM cluster_type"); + session.getDboSession()->execute("DROP TABLE cluster_type"); + session.getDboSession()->execute("ALTER TABLE cluster_type_backup RENAME TO cluster_type"); - session.getDboSession()->execute("ALTER TABLE scan_settings ADD COLUMN extra_tags_to_scan TEXT"); + session.getDboSession()->execute("ALTER TABLE scan_settings ADD COLUMN extra_tags_to_scan TEXT"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - void migrateFromV47(Session& session) - { - // release type, new way - session.getDboSession()->execute("ALTER TABLE release DROP primary_type"); - session.getDboSession()->execute("ALTER TABLE release DROP secondary_types"); + void migrateFromV47(Session& session) + { + // release type, new way + session.getDboSession()->execute("ALTER TABLE release DROP primary_type"); + session.getDboSession()->execute("ALTER TABLE release DROP secondary_types"); - session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "release_type" ( + session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "release_type" ( "id" integer primary key autoincrement, "version" integer not null, "name" text not null))"); - session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "release_release_type" ( + session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "release_release_type" ( "release_type_id" bigint, "release_id" bigint, primary key ("release_type_id", "release_id"), constraint "fk_release_release_type_key1" foreign key ("release_type_id") references "release_type" ("id") on delete cascade deferrable initially deferred, constraint "fk_release_release_type_key2" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred ))"); - session.getDboSession()->execute(R"(CREATE INDEX "release_release_type_release_type" on "release_release_type" ("release_type_id"))"); - session.getDboSession()->execute(R"(CREATE INDEX "release_release_type_release" on "release_release_type" ("release_id"))"); + session.getDboSession()->execute(R"(CREATE INDEX "release_release_type_release_type" on "release_release_type" ("release_type_id"))"); + session.getDboSession()->execute(R"(CREATE INDEX "release_release_type_release" on "release_release_type" ("release_id"))"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - void migrateFromV48(Session& session) - { - // Regression for the extra tags not being parsed - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + void migrateFromV48(Session& session) + { + // Regression for the extra tags not being parsed + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - void migrateFromV49(Session& session) - { - // Add year / originalYear fields, as date / originalDate are not enough (we don't want a wrong date but year or nothing) - session.getDboSession()->execute("ALTER TABLE track ADD year INTEGER"); - session.getDboSession()->execute("ALTER TABLE track ADD original_year INTEGER"); + void migrateFromV49(Session& session) + { + // Add year / originalYear fields, as date / originalDate are not enough (we don't want a wrong date but year or nothing) + session.getDboSession()->execute("ALTER TABLE track ADD year INTEGER"); + session.getDboSession()->execute("ALTER TABLE track ADD original_year INTEGER"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - void migrateFromV50(Session& session) - { - // MediaLibrary support - session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "media_library" ( + void migrateFromV50(Session& session) + { + // MediaLibrary support + session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "media_library" ( "id" integer primary key autoincrement, "version" integer not null, "path" text not null, "name" text not null ))"); - const int scanSettingsId{ session.getDboSession()->query("SELECT id FROM scan_settings") }; + const int scanSettingsId{ session.getDboSession()->query("SELECT id FROM scan_settings") }; - // Convert the existing media_directory in the scan_settings table to a media_library with id '1' - session.getDboSession()->execute(R"(INSERT INTO "media_library" ("id", "version", "path", "name") + // Convert the existing media_directory in the scan_settings table to a media_library with id '1' + session.getDboSession()->execute(R"(INSERT INTO "media_library" ("id", "version", "path", "name") SELECT 1, 0, s_s.media_directory, "Main" FROM scan_settings s_s -WHERE id = ?)").bind(scanSettingsId); +WHERE id = ?)") + .bind(scanSettingsId); - // Remove the outdated column in scan_settings - session.getDboSession()->execute("ALTER TABLE scan_settings DROP media_directory"); + // Remove the outdated column in scan_settings + session.getDboSession()->execute("ALTER TABLE scan_settings DROP media_directory"); - // Add the media_library column in tracks, with id '1' - session.getDboSession()->execute(R"( + // Add the media_library column in tracks, with id '1' + session.getDboSession()->execute(R"( CREATE TABLE IF NOT EXISTS "track_backup" ( "id" integer primary key autoincrement, "version" integer not null, @@ -371,8 +378,8 @@ CREATE TABLE IF NOT EXISTS "track_backup" ( constraint "fk_track_media_library" foreign key ("media_library_id") references "media_library" ("id") on delete set null deferrable initially deferred ))"); - // Migrate data, with the new media_library_id field set to 1 - session.getDboSession()->execute(R"(INSERT INTO track_backup + // Migrate data, with the new media_library_id field set to 1 + session.getDboSession()->execute(R"(INSERT INTO track_backup SELECT id, version, @@ -403,76 +410,229 @@ SELECT release_id, 1 FROM track)"); - session.getDboSession()->execute("DROP TABLE track"); - session.getDboSession()->execute("ALTER TABLE track_backup RENAME TO track"); - } + session.getDboSession()->execute("DROP TABLE track"); + session.getDboSession()->execute("ALTER TABLE track_backup RENAME TO track"); + } - void migrateFromV51(Session& session) - { - // Add custom artist tag delimiters, no need to rescan since it has no effect when empty - session.getDboSession()->execute("ALTER TABLE scan_settings ADD artist_tag_delimiters TEXT NOT NULL DEFAULT ''"); - session.getDboSession()->execute("ALTER TABLE scan_settings ADD default_tag_delimiters TEXT NOT NULL DEFAULT ''"); - } + void migrateFromV51(Session& session) + { + // Add custom artist tag delimiters, no need to rescan since it has no effect when empty + session.getDboSession()->execute("ALTER TABLE scan_settings ADD artist_tag_delimiters TEXT NOT NULL DEFAULT ''"); + session.getDboSession()->execute("ALTER TABLE scan_settings ADD default_tag_delimiters TEXT NOT NULL DEFAULT ''"); + } - void migrateFromV52(Session& session) - { - // Add sort name for releases - session.getDboSession()->execute("ALTER TABLE release ADD sort_name TEXT NOT NULL DEFAULT ''"); + void migrateFromV52(Session& session) + { + // Add sort name for releases + session.getDboSession()->execute("ALTER TABLE release ADD sort_name TEXT NOT NULL DEFAULT ''"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - void migrateFromV53(Session& session) - { - // Add release group mbid - session.getDboSession()->execute("ALTER TABLE release ADD group_mbid TEXT NOT NULL DEFAULT ''"); + void migrateFromV53(Session& session) + { + // Add release group mbid + session.getDboSession()->execute("ALTER TABLE release ADD group_mbid TEXT NOT NULL DEFAULT ''"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - void migrateFromV54(Session& session) - { - // Add file size + relative file path - session.getDboSession()->execute("ALTER TABLE track RENAME COLUMN file_path TO absolute_file_path"); - session.getDboSession()->execute("ALTER TABLE track ADD file_size BIGINT NOT NULL DEFAULT(0)"); - session.getDboSession()->execute("ALTER TABLE track ADD relative_file_path TEXT NOT NULL DEFAULT ''"); + void migrateFromV54(Session& session) + { + // Add file size + relative file path + session.getDboSession()->execute("ALTER TABLE track RENAME COLUMN file_path TO absolute_file_path"); + session.getDboSession()->execute("ALTER TABLE track ADD file_size BIGINT NOT NULL DEFAULT(0)"); + session.getDboSession()->execute("ALTER TABLE track ADD relative_file_path TEXT NOT NULL DEFAULT ''"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - void migrateFromV55(Session& session) - { - // Add bitsPerSample, channelCount and sampleRate - session.getDboSession()->execute("ALTER TABLE track ADD bits_per_sample INTEGER NOT NULL DEFAULT(0)"); - session.getDboSession()->execute("ALTER TABLE track ADD channel_count INTEGER NOT NULL DEFAULT(0)"); - session.getDboSession()->execute("ALTER TABLE track ADD sample_rate INTEGER NOT NULL DEFAULT(0)"); + void migrateFromV55(Session& session) + { + // Add bitsPerSample, channelCount and sampleRate + session.getDboSession()->execute("ALTER TABLE track ADD bits_per_sample INTEGER NOT NULL DEFAULT(0)"); + session.getDboSession()->execute("ALTER TABLE track ADD channel_count INTEGER NOT NULL DEFAULT(0)"); + session.getDboSession()->execute("ALTER TABLE track ADD sample_rate INTEGER NOT NULL DEFAULT(0)"); - // Just increment the scan version of the settings to make the next scheduled scan rescan everything - session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); - } + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } - void migrateFromV56(Session& session) - { - // Make sure we remove all the previoulsy created index, the createIndexesIfNeeded will recreate them all - std::vector indexeNames{ utils::fetchQueryResults(session.getDboSession()->query(R"(SELECT name FROM sqlite_master WHERE type = 'index' AND name LIKE '%_idx')")) }; - for (const auto& indexName : indexeNames) - session.getDboSession()->execute("DROP INDEX " + indexName); - } + void migrateFromV56(Session& session) + { + // Make sure we remove all the previoulsy created index, the createIndexesIfNeeded will recreate them all + std::vector indexeNames{ utils::fetchQueryResults(session.getDboSession()->query(R"(SELECT name FROM sqlite_master WHERE type = 'index' AND name LIKE '%_idx')")) }; + for (const auto& indexName : indexeNames) + session.getDboSession()->execute("DROP INDEX " + indexName); + } - void migrateFromV57(Session& session) - { - // useless index, may have been already removed in the previous step - session.getDboSession()->execute("DROP INDEX IF EXISTS cluster_name_idx"); - } + void migrateFromV57(Session& session) + { + // useless index, may have been already removed in the previous step + session.getDboSession()->execute("DROP INDEX IF EXISTS cluster_name_idx"); + } - void migrateFromV58(Session& session) - { - // DSF support - session.getDboSession()->execute("UPDATE scan_settings SET audio_file_extensions = audio_file_extensions || ' .dsf'"); - } + void migrateFromV58(Session& session) + { + // DSF support + session.getDboSession()->execute("UPDATE scan_settings SET audio_file_extensions = audio_file_extensions || ' .dsf'"); + } + + void migrateFromV59(Session& session) + { + // Dedicated image table + session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "image" ( + "id" integer primary key autoincrement, + "version" integer not null, + "path" text not null, + "stem" text not null, + "file_last_write" text, + "file_size" integer not null, + "width" integer not null, + "height" integer not null, + "artist_id" bigint, + constraint "fk_image_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred +))"); + + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } + + void migrateFromV60(Session& session) + { + // Dedicated directory table + session.getDboSession()->execute(R"(CREATE TABLE IF NOT EXISTS "directory" ( + "id" integer primary key autoincrement, + "version" integer not null, + "absolute_path" text not null, + "name" text not null, + "parent_directory_id" bigint, + constraint "fk_directory_directory" foreign key ("parent_directory_id") references "directory" ("id") on delete cascade deferrable initially deferred +))"); + + // Add a ref in track, need to recreate a new table + session.getDboSession()->execute(R"( +CREATE TABLE IF NOT EXISTS "track_backup" ( + "id" integer primary key autoincrement, + "version" integer not null, + "scan_version" integer not null, + "track_number" integer, + "disc_number" integer, + "total_track" integer, + "disc_subtitle" text not null, + "name" text not null, + "duration" integer, + "bitrate" integer not null, + "bits_per_sample" integer not null, + "channel_count" integer not null, + "sample_rate" integer not null, + "date" text, + "year" integer, + "original_date" text, + "original_year" integer, + "absolute_file_path" text not null, + "relative_file_path" text not null, + "file_size" bigint not null, + "file_last_write" text, + "file_added" text, + "has_cover" boolean not null, + "mbid" text not null, + "recording_mbid" text not null, + "copyright" text not null, + "copyright_url" text not null, + "track_replay_gain" real, + "release_replay_gain" real, + "artist_display_name" text not null, + "release_id" bigint, + "media_library_id" bigint, + "directory_id" bigint, + constraint "fk_track_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred, + constraint "fk_track_media_library" foreign key ("media_library_id") references "media_library" ("id") on delete set null deferrable initially deferred, + constraint "fk_track_directory" foreign key ("directory_id") references "directory" ("id") on delete cascade deferrable initially deferred +))"); + // Migrate data, with the new directory_id field set to null + session.getDboSession()->execute(R"(INSERT INTO track_backup +SELECT + id, + version, + scan_version, + track_number, + disc_number, + total_track, + disc_subtitle, + name, + duration, + bitrate, + bits_per_sample, + channel_count, + sample_rate, + date, + year, + original_date, + original_year, + absolute_file_path, + relative_file_path, + file_size, + file_last_write, + file_added, + has_cover, + mbid, + recording_mbid, + copyright, + copyright_url, + track_replay_gain, + release_replay_gain, + artist_display_name, + release_id, + media_library_id, + NULL + FROM track)"); + session.getDboSession()->execute("DROP TABLE track"); + session.getDboSession()->execute("ALTER TABLE track_backup RENAME TO track"); + + // Add a ref in image + rename path to absolute_file_path, need to recreate a new table + session.getDboSession()->execute(R"( + CREATE TABLE IF NOT EXISTS "image_backup" ( + "id" integer primary key autoincrement, + "version" integer not null, + "absolute_file_path" text not null, + "stem" text not null, + "file_last_write" text, + "file_size" integer not null, + "width" integer not null, + "height" integer not null, + "artist_id" bigint, + "directory_id" bigint, + constraint "fk_image_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred, + constraint "fk_image_directory" foreign key ("directory_id") references "directory" ("id") on delete cascade deferrable initially deferred +))"); + + // Migrate data, with the new directory_id field set to null + session.getDboSession()->execute(R"(INSERT INTO image_backup +SELECT + id, + version, + path, + stem, + file_last_write, + file_size, + width, + height, + artist_id, + NULL + FROM image + )"); + session.getDboSession()->execute("DROP TABLE image"); + session.getDboSession()->execute("ALTER TABLE image_backup RENAME TO image"); + + // Just increment the scan version of the settings to make the next scheduled scan rescan everything + session.getDboSession()->execute("UPDATE scan_settings SET scan_version = scan_version + 1"); + } + + } // namespace bool doDbMigration(Session& session) { @@ -482,34 +642,35 @@ SELECT using MigrationFunction = std::function; - const std::map migrationFunctions - { - {33, migrateFromV33}, - {34, migrateFromV34}, - {35, migrateFromV35}, - {36, migrateFromV36}, - {37, migrateFromV37}, - {38, migrateFromV38}, - {39, migrateFromV39}, - {40, migrateFromV40}, - {41, migrateFromV41}, - {42, migrateFromV42}, - {43, migrateFromV43}, - {44, migrateFromV44}, - {45, migrateFromV45}, - {46, migrateFromV46}, - {47, migrateFromV47}, - {48, migrateFromV48}, - {49, migrateFromV49}, - {50, migrateFromV50}, - {51, migrateFromV51}, - {52, migrateFromV52}, - {53, migrateFromV53}, - {54, migrateFromV54}, - {55, migrateFromV55}, - {56, migrateFromV56}, - {57, migrateFromV57}, - {58, migrateFromV58}, + const std::map migrationFunctions{ + { 33, migrateFromV33 }, + { 34, migrateFromV34 }, + { 35, migrateFromV35 }, + { 36, migrateFromV36 }, + { 37, migrateFromV37 }, + { 38, migrateFromV38 }, + { 39, migrateFromV39 }, + { 40, migrateFromV40 }, + { 41, migrateFromV41 }, + { 42, migrateFromV42 }, + { 43, migrateFromV43 }, + { 44, migrateFromV44 }, + { 45, migrateFromV45 }, + { 46, migrateFromV46 }, + { 47, migrateFromV47 }, + { 48, migrateFromV48 }, + { 49, migrateFromV49 }, + { 50, migrateFromV50 }, + { 51, migrateFromV51 }, + { 52, migrateFromV52 }, + { 53, migrateFromV53 }, + { 54, migrateFromV54 }, + { 55, migrateFromV55 }, + { 56, migrateFromV56 }, + { 57, migrateFromV57 }, + { 58, migrateFromV58 }, + { 59, migrateFromV59 }, + { 60, migrateFromV60 }, }; bool migrationPerformed{}; @@ -553,4 +714,4 @@ SELECT return migrationPerformed; } -} +} // namespace lms::db::Migration diff --git a/src/libs/database/impl/Migration.hpp b/src/libs/database/impl/Migration.hpp index ac528c8f0..57b758614 100644 --- a/src/libs/database/impl/Migration.hpp +++ b/src/libs/database/impl/Migration.hpp @@ -54,4 +54,4 @@ namespace lms::db { bool doDbMigration(Session& session); // return true if migration was performed } -} +} // namespace lms::db diff --git a/src/libs/database/impl/PathTraits.hpp b/src/libs/database/impl/PathTraits.hpp index 7ea12684d..2fccde63f 100644 --- a/src/libs/database/impl/PathTraits.hpp +++ b/src/libs/database/impl/PathTraits.hpp @@ -19,8 +19,9 @@ #pragma once -#include #include +#include + #include namespace Wt::Dbo @@ -50,5 +51,4 @@ namespace Wt::Dbo return true; } }; -} - +} // namespace Wt::Dbo diff --git a/src/libs/database/impl/Release.cpp b/src/libs/database/impl/Release.cpp index fea7ffe8c..b7322ff48 100644 --- a/src/libs/database/impl/Release.cpp +++ b/src/libs/database/impl/Release.cpp @@ -21,15 +21,16 @@ #include +#include "core/ILogger.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" -#include "core/ILogger.hpp" -#include "SqlQuery.hpp" + #include "EnumSetTraits.hpp" #include "IdTypeTraits.hpp" +#include "SqlQuery.hpp" #include "StringViewTraits.hpp" #include "Utils.hpp" @@ -37,14 +38,15 @@ namespace lms::db { namespace { - template + template Wt::Dbo::Query createQuery(Session& session, std::string_view itemToSelect, const Release::FindParameters& params) { auto query{ session.getDboSession()->query("SELECT " + std::string{ itemToSelect } + " from release r") }; if (params.sortMethod == ReleaseSortMethod::ArtistNameThenName || params.sortMethod == ReleaseSortMethod::LastWritten - || params.sortMethod == ReleaseSortMethod::Date + || params.sortMethod == ReleaseSortMethod::DateAsc + || params.sortMethod == ReleaseSortMethod::DateDesc || params.sortMethod == ReleaseSortMethod::OriginalDate || params.sortMethod == ReleaseSortMethod::OriginalDateDesc || params.writtenAfter.isValid() @@ -63,7 +65,8 @@ namespace lms::db { query.join("release_release_type r_r_t ON r_r_t.release_id = r.id"); query.join("release_type r_t ON r_t.id = r_r_t.release_type_id") - .where("r_t.name = ?").bind(params.releaseType); + .where("r_t.name = ?") + .bind(params.releaseType); } if (params.writtenAfter.isValid()) @@ -82,9 +85,12 @@ namespace lms::db { assert(params.feedbackBackend); query.join("starred_release s_r ON s_r.release_id = r.id") - .where("s_r.user_id = ?").bind(params.starringUser) - .where("s_r.backend = ?").bind(*params.feedbackBackend) - .where("s_r.sync_state <> ?").bind(SyncState::PendingRemove); + .where("s_r.user_id = ?") + .bind(params.starringUser) + .where("s_r.backend = ?") + .bind(*params.feedbackBackend) + .where("s_r.sync_state <> ?") + .bind(SyncState::PendingRemove); } if (params.artist.isValid() @@ -119,9 +125,9 @@ namespace lms::db { std::ostringstream oss; oss << "r.id NOT IN (SELECT DISTINCT r.id FROM release r" - " INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id" - " INNER JOIN track t ON t.release_id = r.id" - " WHERE (t_a_l.artist_id = ? AND ("; + " INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id" + " INNER JOIN track t ON t.release_id = r.id" + " WHERE (t_a_l.artist_id = ? AND ("; query.bind(params.artist); @@ -143,13 +149,14 @@ namespace lms::db if (params.clusters.size() == 1) { query.join("track_cluster t_c ON t_c.track_id = t.id") - .where("t_c.cluster_id = ?").bind(params.clusters.front()); + .where("t_c.cluster_id = ?") + .bind(params.clusters.front()); } else if (params.clusters.size() > 1) { std::ostringstream oss; oss << "r.id IN (SELECT DISTINCT t.release_id FROM track t" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; WhereClause clusterClause; for (const ClusterId clusterId : params.clusters) @@ -183,8 +190,11 @@ namespace lms::db case ReleaseSortMethod::LastWritten: query.orderBy("t.file_last_write DESC"); break; - case ReleaseSortMethod::Date: - query.orderBy("COALESCE(t.date, CAST(t.year AS TEXT)), r.name COLLATE NOCASE"); + case ReleaseSortMethod::DateAsc: + query.orderBy("COALESCE(t.date, CAST(t.year AS TEXT)) ASC, r.name COLLATE NOCASE"); + break; + case ReleaseSortMethod::DateDesc: + query.orderBy("COALESCE(t.date, CAST(t.year AS TEXT)) DESC, r.name COLLATE NOCASE"); break; case ReleaseSortMethod::OriginalDate: query.orderBy("COALESCE(original_date, CAST(original_year AS TEXT), date, CAST(year AS TEXT)), r.name COLLATE NOCASE"); @@ -200,69 +210,62 @@ namespace lms::db return query; } - } + } // namespace ReleaseType::ReleaseType(std::string_view name) - : _name{ std::string(name, 0 , _maxNameLength) } + : _name{ std::string(name, 0, _maxNameLength) } { } ReleaseType::pointer ReleaseType::create(Session& session, std::string_view name) { - return session.getDboSession()->add(std::unique_ptr {new ReleaseType{ name }}); + return session.getDboSession()->add(std::unique_ptr{ new ReleaseType{ name } }); } ReleaseType::pointer ReleaseType::find(Session& session, ReleaseTypeId id) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("id = ?").bind(id)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT r_t from release_type r_t").where("r_t.id = ?").bind(id)); } ReleaseType::pointer ReleaseType::find(Session& session, std::string_view name) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("name = ?").bind(name)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT r_t from release_type r_t").where("r_t.name = ?").bind(name)); } Release::Release(const std::string& name, const std::optional& MBID) - : _name{ std::string(name, 0 , _maxNameLength) }, - _MBID{ MBID ? MBID->getAsString() : "" } + : _name{ std::string(name, 0, _maxNameLength) } + , _MBID{ MBID ? MBID->getAsString() : "" } { } Release::pointer Release::create(Session& session, const std::string& name, const std::optional& MBID) { - return session.getDboSession()->add(std::unique_ptr {new Release{ name, MBID }}); + return session.getDboSession()->add(std::unique_ptr{ new Release{ name, MBID } }); } std::vector Release::find(Session& session, const std::string& name, const std::filesystem::path& releaseDirectory) { session.checkReadTransaction(); - return utils::fetchQueryResults(session.getDboSession()->query>("SELECT DISTINCT r from release r") - .join("track t ON t.release_id = r.id") - .where("r.name = ?").bind(std::string(name, 0, _maxNameLength)) - .where("t.absolute_file_path LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind(utils::escapeLikeKeyword(releaseDirectory.string()) + "%")); + return utils::fetchQueryResults(session.getDboSession()->query>("SELECT DISTINCT r from release r").join("track t ON t.release_id = r.id").where("r.name = ?").bind(std::string(name, 0, _maxNameLength)).where("t.absolute_file_path LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind(utils::escapeLikeKeyword(releaseDirectory.string()) + "%")); } Release::pointer Release::find(Session& session, const core::UUID& mbid) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("mbid = ?").bind(mbid.getAsString())); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT r from release r").where("r.mbid = ?").bind(mbid.getAsString())); } Release::pointer Release::find(Session& session, ReleaseId id) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("id = ?").bind(id)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT r from release r").where("r.id = ?").bind(id)); } bool Release::exists(Session& session, ReleaseId id) @@ -290,10 +293,7 @@ namespace lms::db { session.checkReadTransaction(); - auto query{ session.getDboSession()->query>("SELECT r FROM release r") - .orderBy("r.id") - .where("r.id > ?").bind(lastRetrievedRelease) - .limit(static_cast(count)) }; + auto query{ session.getDboSession()->query>("SELECT r FROM release r").orderBy("r.id").where("r.id > ?").bind(lastRetrievedRelease).limit(static_cast(count)) }; if (library.isValid()) { @@ -301,11 +301,10 @@ namespace lms::db query.where("EXISTS (SELECT 1 FROM track t WHERE t.release_id = r.id AND t.media_library_id = ?)").bind(library); } - utils::forEachQueryResult(query, [&](const Release::pointer& release) - { - func(release); - lastRetrievedRelease = release->getId(); - }); + utils::forEachQueryResult(query, [&](const Release::pointer& release) { + func(release); + lastRetrievedRelease = release->getId(); + }); } RangeResults Release::find(Session& session, const FindParameters& params) @@ -336,14 +335,13 @@ namespace lms::db { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(createQuery(session, "COUNT(r.id)", params)); + return utils::fetchQuerySingleResult(createQuery(session, "COUNT(DISTINCT r.id)", params)); } std::size_t Release::getDiscCount() const { assert(session()); - int res{ utils::fetchQuerySingleResult(session()->query("SELECT COUNT(DISTINCT disc_number) FROM track t") - .where("t.release_id = ?").bind(getId()))}; + int res{ utils::fetchQuerySingleResult(session()->query("SELECT COUNT(DISTINCT disc_number) FROM track t").where("t.release_id = ?").bind(getId())) }; return res; } @@ -352,15 +350,12 @@ namespace lms::db assert(session()); using ResultType = std::tuple; - const auto query{ session()->query("SELECT DISTINCT disc_number, disc_subtitle FROM track t") - .where("t.release_id = ?").bind(getId()) - .orderBy("disc_number")}; + const auto query{ session()->query("SELECT DISTINCT disc_number, disc_subtitle FROM track t").where("t.release_id = ?").bind(getId()).orderBy("disc_number") }; std::vector discs; - utils::forEachQueryResult(query, [&](ResultType&& res) - { - discs.emplace_back(DiscInfo{ static_cast(std::get(res)), std::move(std::get(res)) }); - }); + utils::forEachQueryResult(query, [&](ResultType&& res) { + discs.emplace_back(DiscInfo{ static_cast(std::get(res)), std::move(std::get(res)) }); + }); return discs; } @@ -380,10 +375,7 @@ namespace lms::db assert(session()); const char* field{ original ? "original_date" : "date" }; - auto query{ (session()->query(std::string {"SELECT "} + "t." + field + " FROM track t") - .where("t.release_id = ?") - .groupBy(field) - .bind(getId())) }; + auto query{ (session()->query(std::string{ "SELECT " } + "t." + field + " FROM track t").where("t.release_id = ?").groupBy(field).bind(getId())) }; const auto dates{ utils::fetchQueryResults(query) }; @@ -409,9 +401,7 @@ namespace lms::db assert(session()); const char* field{ original ? "original_year" : "year" }; - auto query{ session()->query>(std::string {"SELECT "} + "t." + field + " FROM track t") - .where("t.release_id = ?").bind(getId()) - .groupBy(field) }; + auto query{ session()->query>(std::string{ "SELECT " } + "t." + field + " FROM track t").where("t.release_id = ?").bind(getId()).groupBy(field) }; const auto years{ utils::fetchQueryResults(query) }; @@ -427,10 +417,7 @@ namespace lms::db { assert(session()); - auto query{ session()->query("SELECT copyright FROM track t INNER JOIN release r ON r.id = t.release_id") - .where("r.id = ?") - .groupBy("copyright") - .bind(getId()) }; + auto query{ session()->query("SELECT copyright FROM track t INNER JOIN release r ON r.id = t.release_id").where("r.id = ?").groupBy("copyright").bind(getId()) }; const auto copyrights{ utils::fetchQueryResults(query) }; @@ -445,10 +432,7 @@ namespace lms::db { assert(session()); - const auto query{ session()->query - ("SELECT copyright_url FROM track t INNER JOIN release r ON r.id = t.release_id") - .where("r.id = ?").bind(getId()) - .groupBy("copyright_url") }; + const auto query{ session()->query("SELECT copyright_url FROM track t INNER JOIN release r ON r.id = t.release_id").where("r.id = ?").bind(getId()).groupBy("copyright_url") }; const auto copyrights{ utils::fetchQueryResults(query) }; @@ -463,9 +447,7 @@ namespace lms::db { assert(session()); - return utils::fetchQuerySingleResult(session()->query("SELECT COALESCE(AVG(t.bitrate), 0) FROM track t") - .where("release_id = ?").bind(getId()) - .where("bitrate > 0")); + return utils::fetchQuerySingleResult(session()->query("SELECT COALESCE(AVG(t.bitrate), 0) FROM track t").where("release_id = ?").bind(getId()).where("bitrate > 0")); } std::vector Release::getArtists(TrackArtistLinkType linkType) const @@ -473,12 +455,14 @@ namespace lms::db assert(session()); const auto query{ session()->query>( - "SELECT a FROM artist a" - " INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id" - " INNER JOIN track t ON t.id = t_a_l.track_id") - .where("t.release_id = ?").bind(getId()) - .where("+t_a_l.type = ?").bind(linkType) // adding + since the query planner does not a good job when analyze is not performed - .groupBy("a.id") }; + "SELECT a FROM artist a" + " INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id" + " INNER JOIN track t ON t.id = t_a_l.track_id") + .where("t.release_id = ?") + .bind(getId()) + .where("+t_a_l.type = ?") + .bind(linkType) // adding + since the query planner does not a good job when analyze is not performed + .groupBy("a.id") }; return utils::fetchQueryResults(query); } @@ -489,24 +473,23 @@ namespace lms::db // Select the similar releases using the 5 most used clusters of the release auto query{ session()->query>( - "SELECT r 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 IN " - "(SELECT DISTINCT c.id FROM cluster c" - " INNER JOIN track t ON c.id = t_c.cluster_id" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" - " INNER JOIN release r ON r.id = t.release_id" - " WHERE r.id = ?)" - " AND r.id <> ?" - ) - .bind(getId()) - .bind(getId()) - .groupBy("r.id") - .orderBy("COUNT(*) DESC, RANDOM()") - .limit(count ? static_cast(*count) : -1) - .offset(offset ? static_cast(*offset) : -1) }; + "SELECT r 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 IN " + "(SELECT DISTINCT c.id FROM cluster c" + " INNER JOIN track t ON c.id = t_c.cluster_id" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" + " INNER JOIN release r ON r.id = t.release_id" + " WHERE r.id = ?)" + " AND r.id <> ?") + .bind(getId()) + .bind(getId()) + .groupBy("r.id") + .orderBy("COUNT(*) DESC, RANDOM()") + .limit(count ? static_cast(*count) : -1) + .offset(offset ? static_cast(*offset) : -1) }; return utils::fetchQueryResults(query); } @@ -530,8 +513,7 @@ namespace lms::db std::size_t Release::getTrackCount() const { assert(session()); - return utils::fetchQuerySingleResult(session()->query("SELECT COUNT(t.id) FROM track t INNER JOIN release r ON r.id = t.release_id") - .where("r.id = ?").bind(getId())); + return utils::fetchQuerySingleResult(session()->query("SELECT COUNT(t.id) FROM track t INNER JOIN release r ON r.id = t.release_id").where("r.id = ?").bind(getId())); } std::vector Release::getReleaseTypes() const @@ -556,16 +538,14 @@ namespace lms::db using milli = std::chrono::duration; - return utils::fetchQuerySingleResult(session()->query("SELECT COALESCE(SUM(duration), 0) FROM track t") - .where("t.release_id = ?").bind(getId())); + return utils::fetchQuerySingleResult(session()->query("SELECT COALESCE(SUM(duration), 0) FROM track t").where("t.release_id = ?").bind(getId())); } Wt::WDateTime Release::getLastWritten() const { assert(session()); - return utils::fetchQuerySingleResult(session()->query("SELECT COALESCE(MAX(file_last_write), '1970-01-01T00:00:00') FROM track t") - .where("t.release_id = ?").bind(getId())); + return utils::fetchQuerySingleResult(session()->query("SELECT COALESCE(MAX(file_last_write), '1970-01-01T00:00:00') FROM track t").where("t.release_id = ?").bind(getId())); } std::vector> Release::getClusterGroups(const std::vector& clusterTypeIds, std::size_t size) const diff --git a/src/libs/database/impl/ScanSettings.cpp b/src/libs/database/impl/ScanSettings.cpp index 40c39b81a..c031fb818 100644 --- a/src/libs/database/impl/ScanSettings.cpp +++ b/src/libs/database/impl/ScanSettings.cpp @@ -21,9 +21,10 @@ #include +#include "core/String.hpp" #include "database/MediaLibrary.hpp" #include "database/Session.hpp" -#include "core/String.hpp" + #include "Utils.hpp" namespace lms::db @@ -58,7 +59,11 @@ namespace lms::db std::vector ScanSettings::getExtraTagsToScan() const { - return core::stringUtils::splitString(_extraTagsToScan, ';'); + std::vector tags{ core::stringUtils::splitString(_extraTagsToScan, ';') }; + if (tags.size() == 1 && tags.front().empty()) + tags.clear(); + + return tags; } std::vector ScanSettings::getArtistTagDelimiters() const @@ -71,7 +76,7 @@ namespace lms::db return core::stringUtils::splitEscapedStrings(_defaultTagDelimiters, ';', '\\'); } - void ScanSettings::setExtraTagsToScan(const std::vector& extraTags) + void ScanSettings::setExtraTagsToScan(std::span extraTags) { std::string newTagsToScan{ core::stringUtils::joinStrings(extraTags, ";") }; if (newTagsToScan != _extraTagsToScan) diff --git a/src/libs/database/impl/Session.cpp b/src/libs/database/impl/Session.cpp index a61fa8236..03c9a2f77 100644 --- a/src/libs/database/impl/Session.cpp +++ b/src/libs/database/impl/Session.cpp @@ -22,11 +22,12 @@ #include "core/Exception.hpp" #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" - #include "database/Artist.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/Release.hpp" @@ -35,22 +36,23 @@ #include "database/StarredRelease.hpp" #include "database/StarredTrack.hpp" #include "database/Track.hpp" -#include "database/TrackBookmark.hpp" #include "database/TrackArtistLink.hpp" -#include "database/TrackList.hpp" +#include "database/TrackBookmark.hpp" #include "database/TrackFeatures.hpp" +#include "database/TrackList.hpp" #include "database/TransactionChecker.hpp" #include "database/User.hpp" + #include "EnumSetTraits.hpp" -#include "PathTraits.hpp" #include "Migration.hpp" +#include "PathTraits.hpp" #include "Utils.hpp" namespace lms::db { WriteTransaction::WriteTransaction(core::RecursiveSharedMutex& mutex, Wt::Dbo::Session& session) - : _lock{ mutex }, - _transaction{ session } + : _lock{ mutex } + , _transaction{ session } { #if LMS_CHECK_TRANSACTION_ACCESSES TransactionChecker::pushWriteTransaction(_transaction.session()); @@ -92,6 +94,8 @@ namespace lms::db _session.mapClass("auth_token"); _session.mapClass("cluster"); _session.mapClass("cluster_type"); + _session.mapClass("directory"); + _session.mapClass("image"); _session.mapClass("listen"); _session.mapClass("media_library"); _session.mapClass("release"); @@ -177,6 +181,15 @@ namespace lms::db _session.execute("CREATE INDEX IF NOT EXISTS cluster_cluster_type_idx ON cluster(cluster_type_id)"); _session.execute("CREATE INDEX IF NOT EXISTS cluster_type_name_idx ON cluster_type(name)"); + _session.execute("CREATE INDEX IF NOT EXISTS directory_id_idx ON directory(id)"); + _session.execute("CREATE INDEX IF NOT EXISTS directory_path_idx ON directory(absolute_path)"); + + _session.execute("CREATE INDEX IF NOT EXISTS image_artist_idx ON image(artist_id)"); + _session.execute("CREATE INDEX IF NOT EXISTS image_directory_idx ON image(directory_id)"); + _session.execute("CREATE INDEX IF NOT EXISTS image_id_idx ON image(id)"); + _session.execute("CREATE INDEX IF NOT EXISTS image_path_idx ON image(absolute_file_path)"); + _session.execute("CREATE INDEX IF NOT EXISTS image_stem_idx ON image(stem)"); + _session.execute("CREATE INDEX IF NOT EXISTS listen_backend_idx ON listen(backend)"); _session.execute("CREATE INDEX IF NOT EXISTS listen_id_idx ON listen(id)"); _session.execute("CREATE INDEX IF NOT EXISTS listen_user_backend_idx ON listen(user_id,backend)"); diff --git a/src/libs/database/impl/SqlQuery.cpp b/src/libs/database/impl/SqlQuery.cpp index ca88032c3..430e0b366 100644 --- a/src/libs/database/impl/SqlQuery.cpp +++ b/src/libs/database/impl/SqlQuery.cpp @@ -27,7 +27,8 @@ namespace lms::db { WhereClause& WhereClause::And(const WhereClause& otherClause) { - if (!otherClause._clause.empty()) { + if (!otherClause._clause.empty()) + { if (!_clause.empty()) _clause += " AND "; _clause += "(" + otherClause._clause + ")"; @@ -43,7 +44,8 @@ namespace lms::db WhereClause& WhereClause::Or(const WhereClause& otherClause) { - if (!otherClause._clause.empty()) { + if (!otherClause._clause.empty()) + { if (!_clause.empty()) _clause += " OR "; _clause += "(" + otherClause._clause + ")"; @@ -75,7 +77,7 @@ namespace lms::db } InnerJoinClause::InnerJoinClause(const std::string& clause) - :_clause(clause) + : _clause(clause) { } @@ -154,7 +156,8 @@ namespace lms::db if (!_clause.empty()) { oss << "FROM "; - for (auto it = _clause.begin(); it != _clause.end(); ++it) { + for (auto it = _clause.begin(); it != _clause.end(); ++it) + { if (it != _clause.begin()) oss << ","; @@ -185,4 +188,4 @@ namespace lms::db return oss.str(); } -} \ No newline at end of file +} // namespace lms::db \ No newline at end of file diff --git a/src/libs/database/impl/SqlQuery.hpp b/src/libs/database/impl/SqlQuery.hpp index cd3afeec9..507c07af4 100644 --- a/src/libs/database/impl/SqlQuery.hpp +++ b/src/libs/database/impl/SqlQuery.hpp @@ -19,8 +19,8 @@ #pragma once -#include #include +#include namespace lms::db { @@ -40,8 +40,8 @@ namespace lms::db const std::vector& getBindArgs() const { return _bindArgs; } private: - std::string _clause; // WHERE clause - std::vector _bindArgs; + std::string _clause; // WHERE clause + std::vector _bindArgs; }; class InnerJoinClause @@ -68,13 +68,13 @@ namespace lms::db std::string get() const { return _statement; } private: - std::string _statement; // SELECT statement + std::string _statement; // SELECT statement }; class SelectStatement { public: - SelectStatement() {}; + SelectStatement(){}; SelectStatement(const std::string& item); SelectStatement& And(const std::string& item); @@ -82,7 +82,7 @@ namespace lms::db std::string get() const; private: - std::vector _statement; + std::vector _statement; }; class FromClause @@ -96,16 +96,24 @@ namespace lms::db std::string get() const; private: - std::vector _clause; + std::vector _clause; }; class SqlQuery { public: SelectStatement& select() { return _selectStatement; } - SelectStatement& select(const std::string& statement) { _selectStatement = SelectStatement(statement); return _selectStatement; } + SelectStatement& select(const std::string& statement) + { + _selectStatement = SelectStatement(statement); + return _selectStatement; + } FromClause& from() { return _fromClause; } - FromClause& from(const std::string& clause) { _whereClause = WhereClause(clause); return _fromClause; } + FromClause& from(const std::string& clause) + { + _whereClause = WhereClause(clause); + return _fromClause; + } InnerJoinClause& innerJoin() { return _innerJoinClause; } WhereClause& where() { return _whereClause; } const WhereClause& where() const { return _whereClause; } @@ -115,10 +123,10 @@ namespace lms::db std::string get() const; private: - SelectStatement _selectStatement; // SELECT statement - InnerJoinClause _innerJoinClause; // INNER JOIN - FromClause _fromClause; // FROM tables - WhereClause _whereClause; // WHERE clause - GroupByStatement _groupByStatement; // GROUP BY statement + SelectStatement _selectStatement; // SELECT statement + InnerJoinClause _innerJoinClause; // INNER JOIN + FromClause _fromClause; // FROM tables + WhereClause _whereClause; // WHERE clause + GroupByStatement _groupByStatement; // GROUP BY statement }; -} \ No newline at end of file +} // namespace lms::db \ No newline at end of file diff --git a/src/libs/database/impl/StarredArtist.cpp b/src/libs/database/impl/StarredArtist.cpp index 69b7e340a..df53f6f65 100644 --- a/src/libs/database/impl/StarredArtist.cpp +++ b/src/libs/database/impl/StarredArtist.cpp @@ -24,6 +24,7 @@ #include "database/Artist.hpp" #include "database/Session.hpp" #include "database/User.hpp" + #include "IdTypeTraits.hpp" #include "Utils.hpp" @@ -38,7 +39,7 @@ namespace lms::db StarredArtist::pointer StarredArtist::create(Session& session, ObjectPtr artist, ObjectPtr user, FeedbackBackend backend) { - return session.getDboSession()->add(std::unique_ptr {new StarredArtist{ artist, user, backend }}); + return session.getDboSession()->add(std::unique_ptr{ new StarredArtist{ artist, user, backend } }); } std::size_t StarredArtist::getCount(Session& session) @@ -56,24 +57,17 @@ namespace lms::db StarredArtist::pointer StarredArtist::find(Session& session, ArtistId artistId, UserId userId) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT s_a from starred_artist s_a") - .join("user u ON u.id = s_a.user_id") - .where("s_a.artist_id = ?").bind(artistId) - .where("s_a.user_id = ?").bind(userId) - .where("s_a.backend = u.feedback_backend")); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT s_a from starred_artist s_a").join("user u ON u.id = s_a.user_id").where("s_a.artist_id = ?").bind(artistId).where("s_a.user_id = ?").bind(userId).where("s_a.backend = u.feedback_backend")); } StarredArtist::pointer StarredArtist::find(Session& session, ArtistId artistId, UserId userId, FeedbackBackend backend) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("artist_id = ?").bind(artistId) - .where("user_id = ?").bind(userId) - .where("backend = ?").bind(backend)); + return utils::fetchQuerySingleResult(session.getDboSession()->find().where("artist_id = ?").bind(artistId).where("user_id = ?").bind(userId).where("backend = ?").bind(backend)); } void StarredArtist::setDateTime(const Wt::WDateTime& dateTime) { _dateTime = utils::normalizeDateTime(dateTime); } -} +} // namespace lms::db diff --git a/src/libs/database/impl/StarredRelease.cpp b/src/libs/database/impl/StarredRelease.cpp index 1b42407a6..183112138 100644 --- a/src/libs/database/impl/StarredRelease.cpp +++ b/src/libs/database/impl/StarredRelease.cpp @@ -24,6 +24,7 @@ #include "database/Release.hpp" #include "database/Session.hpp" #include "database/User.hpp" + #include "IdTypeTraits.hpp" #include "Utils.hpp" @@ -38,7 +39,7 @@ namespace lms::db StarredRelease::pointer StarredRelease::create(Session& session, ObjectPtr release, ObjectPtr user, FeedbackBackend backend) { - return session.getDboSession()->add(std::unique_ptr{new StarredRelease{ release, user, backend }}); + return session.getDboSession()->add(std::unique_ptr{ new StarredRelease{ release, user, backend } }); } std::size_t StarredRelease::getCount(Session& session) @@ -56,24 +57,17 @@ namespace lms::db StarredRelease::pointer StarredRelease::find(Session& session, ReleaseId releaseId, UserId userId) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT s_r from starred_release s_r") - .join("user u ON u.id = s_r.user_id") - .where("s_r.release_id = ?").bind(releaseId) - .where("s_r.user_id = ?").bind(userId) - .where("s_r.backend = u.feedback_backend")); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT s_r from starred_release s_r").join("user u ON u.id = s_r.user_id").where("s_r.release_id = ?").bind(releaseId).where("s_r.user_id = ?").bind(userId).where("s_r.backend = u.feedback_backend")); } StarredRelease::pointer StarredRelease::find(Session& session, ReleaseId releaseId, UserId userId, FeedbackBackend backend) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("release_id = ?").bind(releaseId) - .where("user_id = ?").bind(userId) - .where("backend = ?").bind(backend)); + return utils::fetchQuerySingleResult(session.getDboSession()->find().where("release_id = ?").bind(releaseId).where("user_id = ?").bind(userId).where("backend = ?").bind(backend)); } void StarredRelease::setDateTime(const Wt::WDateTime& dateTime) { _dateTime = utils::normalizeDateTime(dateTime); } -} +} // namespace lms::db diff --git a/src/libs/database/impl/StarredTrack.cpp b/src/libs/database/impl/StarredTrack.cpp index f2b42ed1b..e9e3ae4ea 100644 --- a/src/libs/database/impl/StarredTrack.cpp +++ b/src/libs/database/impl/StarredTrack.cpp @@ -21,9 +21,10 @@ #include -#include "database/Track.hpp" #include "database/Session.hpp" +#include "database/Track.hpp" #include "database/User.hpp" + #include "IdTypeTraits.hpp" #include "Utils.hpp" @@ -38,7 +39,7 @@ namespace lms::db StarredTrack::pointer StarredTrack::create(Session& session, ObjectPtr track, ObjectPtr user, FeedbackBackend backend) { - return session.getDboSession()->add(std::unique_ptr {new StarredTrack{ track, user, backend }}); + return session.getDboSession()->add(std::unique_ptr{ new StarredTrack{ track, user, backend } }); } std::size_t StarredTrack::getCount(Session& session) @@ -56,28 +57,18 @@ namespace lms::db StarredTrack::pointer StarredTrack::find(Session& session, TrackId trackId, UserId userId) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT s_t from starred_track s_t") - .join("user u ON u.id = s_t.user_id") - .where("s_t.track_id = ?").bind(trackId) - .where("s_t.user_id = ?").bind(userId) - .where("s_t.backend = u.feedback_backend")); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT s_t from starred_track s_t").join("user u ON u.id = s_t.user_id").where("s_t.track_id = ?").bind(trackId).where("s_t.user_id = ?").bind(userId).where("s_t.backend = u.feedback_backend")); } StarredTrack::pointer StarredTrack::find(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("track_id = ?").bind(trackId) - .where("user_id = ?").bind(userId) - .where("backend = ?").bind(backend)); + return utils::fetchQuerySingleResult(session.getDboSession()->find().where("track_id = ?").bind(trackId).where("user_id = ?").bind(userId).where("backend = ?").bind(backend)); } bool StarredTrack::exists(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend) { - return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT 1 from starred_track") - .where("track_id = ?").bind(trackId) - .where("user_id = ?").bind(userId) - .where("backend = ?").bind(backend)); + return utils::fetchQuerySingleResult(session.getDboSession()->query("SELECT 1 from starred_track").where("track_id = ?").bind(trackId).where("user_id = ?").bind(userId).where("backend = ?").bind(backend)); } RangeResults StarredTrack::find(Session& session, const FindParameters& params) @@ -100,4 +91,4 @@ namespace lms::db { _dateTime = utils::normalizeDateTime(dateTime); } -} +} // namespace lms::db diff --git a/src/libs/database/impl/StringViewTraits.hpp b/src/libs/database/impl/StringViewTraits.hpp index c82443c0e..3092556c1 100644 --- a/src/libs/database/impl/StringViewTraits.hpp +++ b/src/libs/database/impl/StringViewTraits.hpp @@ -20,6 +20,7 @@ #pragma once #include + #include namespace Wt::Dbo @@ -32,5 +33,4 @@ namespace Wt::Dbo statement->bind(column, std::string{ str }); } }; -} - +} // namespace Wt::Dbo diff --git a/src/libs/database/impl/Track.cpp b/src/libs/database/impl/Track.cpp index 5a459397e..80c10c189 100644 --- a/src/libs/database/impl/Track.cpp +++ b/src/libs/database/impl/Track.cpp @@ -21,15 +21,16 @@ #include +#include "core/ILogger.hpp" #include "database/Artist.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/TrackFeatures.hpp" -#include "database/Session.hpp" #include "database/User.hpp" -#include "core/ILogger.hpp" #include "IdTypeTraits.hpp" #include "PathTraits.hpp" @@ -41,7 +42,7 @@ namespace lms::db { namespace { - template + template Wt::Dbo::Query createQuery(Session& session, std::string_view itemToSelect, const Track::FindParameters& params) { session.checkReadTransaction(); @@ -62,22 +63,26 @@ namespace lms::db { assert(params.feedbackBackend); query.join("starred_track s_t ON s_t.track_id = t.id") - .where("s_t.user_id = ?").bind(params.starringUser) - .where("s_t.backend = ?").bind(*params.feedbackBackend) - .where("s_t.sync_state <> ?").bind(SyncState::PendingRemove); + .where("s_t.user_id = ?") + .bind(params.starringUser) + .where("s_t.backend = ?") + .bind(*params.feedbackBackend) + .where("s_t.sync_state <> ?") + .bind(SyncState::PendingRemove); } if (params.clusters.size() == 1) { // optim query.join("track_cluster t_c ON t_c.track_id = t.id") - .where("t_c.cluster_id = ?").bind(params.clusters.front()); + .where("t_c.cluster_id = ?") + .bind(params.clusters.front()); } else if (params.clusters.size() > 1) { std::ostringstream oss; oss << "t.id IN (SELECT DISTINCT t.id FROM track t" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id"; WhereClause clusterClause; for (const ClusterId clusterId : params.clusters) @@ -183,7 +188,7 @@ namespace lms::db return query; } - template + template Wt::Dbo::Query createQuery(Session& session, const Track::FindParameters& params) { std::string_view itemToSelect; @@ -197,7 +202,7 @@ namespace lms::db return createQuery(session, itemToSelect, params); } - } + } // namespace Track::pointer Track::create(Session& session) { @@ -215,34 +220,29 @@ namespace lms::db { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find().where("absolute_file_path = ?").bind(p.string())); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT t from track t").where("t.absolute_file_path = ?").bind(p.string())); } Track::pointer Track::find(Session& session, TrackId id) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("id = ?").bind(id)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT t from track t").where("t.id = ?").bind(id)); } void Track::find(Session& session, TrackId& lastRetrievedTrack, std::size_t count, const std::function& func, MediaLibraryId library) { session.checkReadTransaction(); - auto query{ session.getDboSession()->find() - .orderBy("id") - .where("id > ?").bind(lastRetrievedTrack) - .limit(static_cast(count)) }; + auto query{ session.getDboSession()->query>("SELECT t from track t").orderBy("t.id").where("t.id > ?").bind(lastRetrievedTrack).limit(static_cast(count)) }; if (library.isValid()) query.where("media_library_id = ?").bind(library); - utils::forEachQueryResult(query, [&](const Track::pointer& track) - { - func(track); - lastRetrievedTrack = track->getId(); - }); + utils::forEachQueryResult(query, [&](const Track::pointer& track) { + func(track); + lastRetrievedTrack = track->getId(); + }); } bool Track::exists(Session& session, TrackId id) @@ -256,24 +256,21 @@ namespace lms::db { session.checkReadTransaction(); - return utils::fetchQueryResults(session.getDboSession()->find() - .where("mbid = ?").bind(mbid.getAsString())); + return utils::fetchQueryResults(session.getDboSession()->query>("SELECT t from track t").where("t.mbid = ?").bind(mbid.getAsString())); } std::vector Track::findByRecordingMBID(Session& session, const core::UUID& mbid) { session.checkReadTransaction(); - return utils::fetchQueryResults(session.getDboSession()->find() - .where("recording_mbid = ?").bind(mbid.getAsString())); + return utils::fetchQueryResults(session.getDboSession()->query>("SELECT t from track t").where("t.recording_mbid = ?").bind(mbid.getAsString())); } RangeResults Track::findIdsTrackMBIDDuplicates(Session& session, std::optional range) { session.checkReadTransaction(); - auto query{ session.getDboSession()->query("SELECT track.id FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)") - .orderBy("track.release_id,track.disc_number,track.track_number,track.mbid") }; + auto query{ session.getDboSession()->query("SELECT track.id FROM track WHERE mbid in (SELECT mbid FROM track WHERE mbid <> '' GROUP BY mbid HAVING COUNT (*) > 1)").orderBy("track.release_id,track.disc_number,track.track_number,track.mbid") }; return utils::execRangeQuery(query, range); } @@ -282,9 +279,7 @@ namespace lms::db { session.checkReadTransaction(); - auto query{ session.getDboSession()->query("SELECT t.id FROM track t") - .where("LENGTH(t.recording_mbid) > 0") - .where("NOT EXISTS (SELECT * FROM track_features t_f WHERE t_f.track_id = t.id)") }; + auto query{ session.getDboSession()->query("SELECT t.id FROM track t").where("LENGTH(t.recording_mbid) > 0").where("NOT EXISTS (SELECT * FROM track_features t_f WHERE t_f.track_id = t.id)") }; return utils::execRangeQuery(query, range); } @@ -298,10 +293,7 @@ namespace lms::db { assert(session()); - const auto query{ session()->query - ("SELECT t_c.cluster_id FROM track_cluster t_c") - .where("t_c.track_id = ?").bind(getId()) - .groupBy("t_c.cluster_id") }; + const auto query{ session()->query("SELECT t_c.cluster_id FROM track_cluster t_c").where("t_c.track_id = ?").bind(getId()).groupBy("t_c.cluster_id") }; return utils::fetchQueryResults(query); } @@ -352,12 +344,14 @@ namespace lms::db } auto query{ session.getDboSession()->query( - "SELECT t.id FROM track t" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" - " AND t_c.cluster_id IN (SELECT DISTINCT c.id FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id WHERE t_c.track_id IN (" + oss.str() + "))" - " AND t.id NOT IN (" + oss.str() + ")") - .groupBy("t.id") - .orderBy("COUNT(*) DESC, RANDOM()") }; + "SELECT t.id FROM track t" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" + " AND t_c.cluster_id IN (SELECT DISTINCT c.id FROM cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id WHERE t_c.track_id IN (" + + oss.str() + "))" + " AND t.id NOT IN (" + + oss.str() + ")") + .groupBy("t.id") + .orderBy("COUNT(*) DESC, RANDOM()") }; for (TrackId trackId : tracks) query.bind(trackId); @@ -412,10 +406,9 @@ namespace lms::db assert(session()); std::ostringstream oss; - oss << - "SELECT a from artist a" - " INNER JOIN track_artist_link t_a_l ON a.id = t_a_l.artist_id" - " INNER JOIN track t ON t.id = t_a_l.track_id"; + oss << "SELECT a from artist a" + " INNER JOIN track_artist_link t_a_l ON a.id = t_a_l.artist_id" + " INNER JOIN track t ON t.id = t_a_l.track_id"; if (!linkTypes.empty()) { @@ -437,6 +430,8 @@ namespace lms::db query.bind(type); query.where("t.id = ?").bind(getId()); + query.groupBy("t_a_l.artist_id"); + query.orderBy("t_a_l.id"); return utils::fetchQueryResults(query); } @@ -447,9 +442,8 @@ namespace lms::db assert(session()); std::ostringstream oss; - oss << - "SELECT t_a_l.artist_id FROM track_artist_link t_a_l" - " INNER JOIN track t ON t.id = t_a_l.track_id"; + oss << "SELECT t_a_l.artist_id FROM track_artist_link t_a_l" + " INNER JOIN track t ON t.id = t_a_l.track_id"; if (!linkTypes.empty()) { @@ -472,6 +466,7 @@ namespace lms::db query.where("t.id = ?").bind(getId()); query.groupBy("t_a_l.artist_id"); + query.orderBy("t_a_l.id"); return utils::fetchQueryResults(query); } @@ -507,11 +502,10 @@ namespace lms::db query.bind(bindArg); std::map> clusters; - utils::forEachQueryResult(query, [&](const Cluster::pointer& cluster) - { - if (clusters[cluster->getType()->getId()].size() < size) - clusters[cluster->getType()->getId()].push_back(cluster); - }); + utils::forEachQueryResult(query, [&](const Cluster::pointer& cluster) { + if (clusters[cluster->getType()->getId()].size() < size) + clusters[cluster->getType()->getId()].push_back(cluster); + }); std::vector> res; for (const auto& [type, clusters] : clusters) @@ -545,6 +539,6 @@ namespace lms::db return os; } - } + } // namespace Debug } // namespace lms::db diff --git a/src/libs/database/impl/TrackArtistLink.cpp b/src/libs/database/impl/TrackArtistLink.cpp index 06e2056e5..0817df8d7 100644 --- a/src/libs/database/impl/TrackArtistLink.cpp +++ b/src/libs/database/impl/TrackArtistLink.cpp @@ -53,7 +53,7 @@ namespace lms::db return query; } - } + } // namespace TrackArtistLink::TrackArtistLink(ObjectPtr track, ObjectPtr artist, TrackArtistLinkType type, std::string_view subType) : _type{ type } @@ -83,40 +83,34 @@ namespace lms::db { session.checkReadTransaction(); - using ResultType = std::tuple < Wt::Dbo::ptr, Wt::Dbo::ptr>; + using ResultType = std::tuple, Wt::Dbo::ptr>; - const auto query{ session.getDboSession()->query("SELECT t_a_l, a FROM track_artist_link t_a_l") - .join("artist a ON t_a_l.artist_id = a.id") - .where("t_a_l.track_id = ?").bind(trackId) }; + const auto query{ session.getDboSession()->query("SELECT t_a_l, a FROM track_artist_link t_a_l").join("artist a ON t_a_l.artist_id = a.id").where("t_a_l.track_id = ?").bind(trackId) }; - utils::forEachQueryResult(query, [&](const ResultType& result) - { - func(std::get>(result), std::get>(result)); - }); + utils::forEachQueryResult(query, [&](const ResultType& result) { + func(std::get>(result), std::get>(result)); + }); } void TrackArtistLink::find(Session& session, const FindParameters& parameters, const std::function& func) { const auto query{ createQuery(session, parameters) }; - utils::forEachQueryResult(query, [&](const TrackArtistLink::pointer& link) - { - func(link); - }); + utils::forEachQueryResult(query, [&](const TrackArtistLink::pointer& link) { + func(link); + }); } core::EnumSet TrackArtistLink::findUsedTypes(Session& session, ArtistId artistId) { session.checkReadTransaction(); - const auto query{ session.getDboSession()->query("SELECT DISTINCT type from track_artist_link") - .where("artist_id = ?").bind(artistId) }; + const auto query{ session.getDboSession()->query("SELECT DISTINCT type from track_artist_link").where("artist_id = ?").bind(artistId) }; core::EnumSet res; - utils::forEachQueryResult(query, [&](TrackArtistLinkType linkType) - { - res.insert(linkType); - }); + utils::forEachQueryResult(query, [&](TrackArtistLinkType linkType) { + res.insert(linkType); + }); return res; } -} +} // namespace lms::db diff --git a/src/libs/database/impl/TrackBookmark.cpp b/src/libs/database/impl/TrackBookmark.cpp index d63c63c4b..601ce35c8 100644 --- a/src/libs/database/impl/TrackBookmark.cpp +++ b/src/libs/database/impl/TrackBookmark.cpp @@ -22,20 +22,21 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" + #include "IdTypeTraits.hpp" #include "Utils.hpp" namespace lms::db { TrackBookmark::TrackBookmark(ObjectPtr user, ObjectPtr track) - : _user{ getDboPtr(user) }, - _track{ getDboPtr(track) } + : _user{ getDboPtr(user) } + , _track{ getDboPtr(track) } { } TrackBookmark::pointer TrackBookmark::create(Session& session, ObjectPtr user, ObjectPtr track) { - return session.getDboSession()->add(std::unique_ptr {new TrackBookmark{ user, track }}); + return session.getDboSession()->add(std::unique_ptr{ new TrackBookmark{ user, track } }); } std::size_t TrackBookmark::getCount(Session& session) @@ -49,8 +50,7 @@ namespace lms::db { session.checkReadTransaction(); - auto query{ session.getDboSession()->query("SELECT id from track_bookmark") - .where("user_id = ?").bind(userId) }; + auto query{ session.getDboSession()->query("SELECT id from track_bookmark").where("user_id = ?").bind(userId) }; return utils::execRangeQuery(query, range); } @@ -59,18 +59,14 @@ namespace lms::db { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("user_id = ?").bind(userId) - .where("track_id = ?").bind(trackId)); + return utils::fetchQuerySingleResult(session.getDboSession()->find().where("user_id = ?").bind(userId).where("track_id = ?").bind(trackId)); } TrackBookmark::pointer TrackBookmark::find(Session& session, TrackBookmarkId id) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("id = ?").bind(id)); + return utils::fetchQuerySingleResult(session.getDboSession()->find().where("id = ?").bind(id)); } } // namespace lms::db - diff --git a/src/libs/database/impl/TrackFeatures.cpp b/src/libs/database/impl/TrackFeatures.cpp index 40a2a6d97..53e68c7f6 100644 --- a/src/libs/database/impl/TrackFeatures.cpp +++ b/src/libs/database/impl/TrackFeatures.cpp @@ -19,26 +19,29 @@ #include "database/TrackFeatures.hpp" -#include #include +#include +#include "core/ILogger.hpp" +#include "database/Directory.hpp" #include "database/Session.hpp" #include "database/Track.hpp" -#include "core/ILogger.hpp" + #include "IdTypeTraits.hpp" #include "Utils.hpp" -namespace lms::db { +namespace lms::db +{ TrackFeatures::TrackFeatures(ObjectPtr track, const std::string& jsonEncodedFeatures) - : _data{ jsonEncodedFeatures }, - _track{ getDboPtr(track) } + : _data{ jsonEncodedFeatures } + , _track{ getDboPtr(track) } { } TrackFeatures::pointer TrackFeatures::create(Session& session, ObjectPtr track, const std::string& jsonEncodedFeatures) { - return session.getDboSession()->add(std::unique_ptr {new TrackFeatures{ track, jsonEncodedFeatures }}); + return session.getDboSession()->add(std::unique_ptr{ new TrackFeatures{ track, jsonEncodedFeatures } }); } std::size_t TrackFeatures::getCount(Session& session) @@ -52,16 +55,14 @@ namespace lms::db { { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("id = ?").bind(id)); + return utils::fetchQuerySingleResult(session.getDboSession()->find().where("id = ?").bind(id)); } TrackFeatures::pointer TrackFeatures::find(Session& session, TrackId trackId) { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("track_id = ?").bind(trackId)); + return utils::fetchQuerySingleResult(session.getDboSession()->find().where("track_id = ?").bind(trackId)); } RangeResults TrackFeatures::find(Session& session, std::optional range) @@ -75,7 +76,7 @@ namespace lms::db { FeatureValues TrackFeatures::getFeatureValues(const FeatureName& featureNode) const { - FeatureValuesMap featuresValuesMap{ getFeatureValuesMap({featureNode}) }; + FeatureValuesMap featuresValuesMap{ getFeatureValuesMap({ featureNode }) }; return std::move(featuresValuesMap[featureNode]); } diff --git a/src/libs/database/impl/TrackList.cpp b/src/libs/database/impl/TrackList.cpp index aa6d85db4..1e1018e95 100644 --- a/src/libs/database/impl/TrackList.cpp +++ b/src/libs/database/impl/TrackList.cpp @@ -21,23 +21,23 @@ #include #include "core/ILogger.hpp" - #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/Release.hpp" #include "database/Session.hpp" -#include "database/User.hpp" #include "database/Track.hpp" +#include "database/User.hpp" + +#include "IdTypeTraits.hpp" #include "SqlQuery.hpp" #include "StringViewTraits.hpp" -#include "IdTypeTraits.hpp" #include "Utils.hpp" namespace lms::db { namespace { - template + template Wt::Dbo::Query createQuery(Session& session, std::string_view itemToSelect, const TrackList::FindParameters& params) { auto query{ session.getDboSession()->query("SELECT DISTINCT " + std::string{ itemToSelect } + " FROM tracklist t_l") }; @@ -61,8 +61,8 @@ namespace lms::db { std::ostringstream oss; oss << "t_l_e.track_id IN (SELECT DISTINCT t.id FROM track t" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" - " INNER JOIN cluster c ON c.id = t_c.cluster_id"; + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" + " INNER JOIN cluster c ON c.id = t_c.cluster_id"; WhereClause clusterClause; for (const ClusterId clusterId : params.clusters) @@ -92,7 +92,7 @@ namespace lms::db return query; } - template + template Wt::Dbo::Query createQuery(Session& session, const TrackList::FindParameters& params) { std::string_view itemToSelect; @@ -106,7 +106,7 @@ namespace lms::db return createQuery(session, itemToSelect, params); } - } + } // namespace TrackList::TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr user) : _name{ name } @@ -121,7 +121,7 @@ namespace lms::db TrackList::pointer TrackList::create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr user) { - return session.getDboSession()->add(std::unique_ptr {new TrackList{ name, type, isPublic, user }}); + return session.getDboSession()->add(std::unique_ptr{ new TrackList{ name, type, isPublic, user } }); } std::size_t TrackList::getCount(Session& session) @@ -136,10 +136,7 @@ namespace lms::db session.checkReadTransaction(); assert(userId.isValid()); - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("name = ?").bind(name) - .where("type = ?").bind(type) - .where("user_id = ?").bind(userId)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("select t_l from tracklist t_l").where("t_l.name = ?").bind(name).where("t_l.type = ?").bind(type).where("t_l.user_id = ?").bind(userId)); } RangeResults TrackList::find(Session& session, const FindParameters& params) @@ -160,7 +157,7 @@ namespace lms::db { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find().where("id = ?").bind(id)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("select t_l from tracklist t_l").where("t_l.id = ?").bind(id)); } bool TrackList::isEmpty() const @@ -188,9 +185,7 @@ namespace lms::db { assert(session()); - auto query{session()->find() - .where("tracklist_id = ?").bind(getId()) - .orderBy("id") }; + auto query{ session()->find().where("tracklist_id = ?").bind(getId()).orderBy("id") }; return utils::execRangeQuery(query, range); } @@ -199,20 +194,14 @@ namespace lms::db { assert(session()); - return utils::fetchQuerySingleResult(session()->find() - .where("tracklist_id = ?").bind(getId()) - .where("track_id = ?").bind(track->getId()) - .where("date_time = ?").bind(utils::normalizeDateTime(dateTime))); + return utils::fetchQuerySingleResult(session()->find().where("tracklist_id = ?").bind(getId()).where("track_id = ?").bind(track->getId()).where("date_time = ?").bind(utils::normalizeDateTime(dateTime))); } std::vector TrackList::getClusters() const { assert(session()); - const auto query{ session()->query>("SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id") - .where("p.id = ?").bind(getId()) - .groupBy("c.id") - .orderBy("COUNT(c.id) DESC") }; + const auto query{ session()->query>("SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id").where("p.id = ?").bind(getId()).groupBy("c.id").orderBy("COUNT(c.id) DESC") }; return utils::fetchQueryResults(query); } @@ -232,7 +221,8 @@ namespace lms::db .join("cluster_type c_type ON c.cluster_type_id = c_type.id") .join("tracklist_entry t_l_e ON t_l_e.track_id = t.id") .join("tracklist t_l ON t_l.id = t_l_e.tracklist_id") - .where("t_l.id = ?").bind(getId()); + .where("t_l.id = ?") + .bind(getId()); { std::ostringstream oss; @@ -253,11 +243,10 @@ namespace lms::db query.orderBy("COUNT(c.id) DESC"); std::map> clustersByType; - utils::forEachQueryResult(query, [&](const Cluster::pointer& cluster) - { - if (clustersByType[cluster->getType()->getId()].size() < size) - clustersByType[cluster->getType()->getId()].push_back(cluster); - }); + utils::forEachQueryResult(query, [&](const Cluster::pointer& cluster) { + if (clustersByType[cluster->getType()->getId()].size() < size) + clustersByType[cluster->getType()->getId()].push_back(cluster); + }); for (const auto& [clusterTypeId, clusters] : clustersByType) res.push_back(clusters); @@ -270,18 +259,17 @@ namespace lms::db assert(session()); auto query{ session()->query>( - "SELECT t FROM track t" - " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" - " WHERE " - " (t_c.cluster_id IN (SELECT DISTINCT c.id from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id WHERE p.id = ?)" - " AND t.id NOT IN (SELECT tracklist_t.id FROM track tracklist_t INNER JOIN tracklist_entry t_e ON t_e.track_id = tracklist_t.id WHERE t_e.tracklist_id = ?))" - ) - .bind(getId()) - .bind(getId()) - .groupBy("t.id") - .orderBy("COUNT(*) DESC, RANDOM()") - .limit(size ? static_cast(*size) : -1) - .offset(offset ? static_cast(*offset) : -1) }; + "SELECT t FROM track t" + " INNER JOIN track_cluster t_c ON t_c.track_id = t.id" + " WHERE " + " (t_c.cluster_id IN (SELECT DISTINCT c.id from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN tracklist_entry p_e ON p_e.track_id = t.id INNER JOIN tracklist p ON p.id = p_e.tracklist_id WHERE p.id = ?)" + " AND t.id NOT IN (SELECT tracklist_t.id FROM track tracklist_t INNER JOIN tracklist_entry t_e ON t_e.track_id = tracklist_t.id WHERE t_e.tracklist_id = ?))") + .bind(getId()) + .bind(getId()) + .groupBy("t.id") + .orderBy("COUNT(*) DESC, RANDOM()") + .limit(size ? static_cast(*size) : -1) + .offset(offset ? static_cast(*offset) : -1) }; return utils::fetchQueryResults(query); } @@ -290,8 +278,7 @@ namespace lms::db { assert(session()); - auto query{ session()->query("SELECT p_e.track_id from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id") - .where("p.id = ?").bind(getId()) }; + auto query{ session()->query("SELECT p_e.track_id from tracklist_entry p_e INNER JOIN tracklist p ON p_e.tracklist_id = p.id").where("p.id = ?").bind(getId()) }; return utils::fetchQueryResults(query); } @@ -302,8 +289,7 @@ namespace lms::db using milli = std::chrono::duration; - return utils::fetchQuerySingleResult(session()->query("SELECT COALESCE(SUM(duration), 0) FROM track t INNER JOIN tracklist_entry p_e ON t.id = p_e.track_id") - .where("p_e.tracklist_id = ?").bind(getId())); + return utils::fetchQuerySingleResult(session()->query("SELECT COALESCE(SUM(duration), 0) FROM track t INNER JOIN tracklist_entry p_e ON t.id = p_e.track_id").where("p_e.tracklist_id = ?").bind(getId())); } void TrackList::setLastModifiedDateTime(const Wt::WDateTime& dateTime) @@ -322,7 +308,7 @@ namespace lms::db TrackListEntry::pointer TrackListEntry::create(Session& session, ObjectPtr track, ObjectPtr tracklist, const Wt::WDateTime& dateTime) { - return session.getDboSession()->add(std::unique_ptr {new TrackListEntry{ track, tracklist, dateTime }}); + return session.getDboSession()->add(std::unique_ptr{ new TrackListEntry{ track, tracklist, dateTime } }); } void TrackListEntry::onPostCreated() diff --git a/src/libs/database/impl/TransactionChecker.cpp b/src/libs/database/impl/TransactionChecker.cpp index 9966345dd..003e84bfd 100644 --- a/src/libs/database/impl/TransactionChecker.cpp +++ b/src/libs/database/impl/TransactionChecker.cpp @@ -22,6 +22,7 @@ static_assert(LMS_CHECK_TRANSACTION_ACCESSES, "File should be excluded from build"); #include + #include "database/Session.hpp" namespace lms::db @@ -35,7 +36,7 @@ namespace lms::db }; static thread_local std::vector transactionStack; - } + } // namespace void TransactionChecker::pushWriteTransaction(Wt::Dbo::Session& session) { @@ -93,4 +94,4 @@ namespace lms::db { checkReadTransaction(*session.getDboSession()); } -} \ No newline at end of file +} // namespace lms::db \ No newline at end of file diff --git a/src/libs/database/impl/Types.cpp b/src/libs/database/impl/Types.cpp index f40ac8809..fb5220f9f 100644 --- a/src/libs/database/impl/Types.cpp +++ b/src/libs/database/impl/Types.cpp @@ -23,8 +23,7 @@ namespace lms::db { - static const std::set allowedAudioBitrates - { + static const std::set allowedAudioBitrates{ 64000, 96000, 128000, @@ -47,5 +46,4 @@ namespace lms::db { return DateRange{ from, to }; } -} - +} // namespace lms::db diff --git a/src/libs/database/impl/User.cpp b/src/libs/database/impl/User.cpp index 97efe9d87..22dd0bbb9 100644 --- a/src/libs/database/impl/User.cpp +++ b/src/libs/database/impl/User.cpp @@ -19,11 +19,12 @@ #include "database/User.hpp" +#include "core/ILogger.hpp" #include "database/Artist.hpp" #include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" -#include "core/ILogger.hpp" + #include "IdTypeTraits.hpp" #include "StringViewTraits.hpp" #include "Utils.hpp" @@ -37,7 +38,7 @@ namespace lms::db User::pointer User::create(Session& session, std::string_view loginName) { - return session.getDboSession()->add(std::unique_ptr {new User{ loginName }}); + return session.getDboSession()->add(std::unique_ptr{ new User{ loginName } }); } std::size_t User::getCount(Session& session) @@ -77,18 +78,17 @@ namespace lms::db { session.checkReadTransaction(); - return utils::fetchQuerySingleResult(session.getDboSession()->find().where("type = ?").bind(UserType::DEMO)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT u from user u").where("u.type = ?").bind(UserType::DEMO)); } User::pointer User::find(Session& session, UserId id) { - return utils::fetchQuerySingleResult(session.getDboSession()->find().where("id = ?").bind(id)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT u from user u").where("u.id = ?").bind(id)); } User::pointer User::find(Session& session, std::string_view name) { - return utils::fetchQuerySingleResult(session.getDboSession()->find() - .where("login_name = ?").bind(name)); + return utils::fetchQuerySingleResult(session.getDboSession()->query>("SELECT u from user u").where("u.login_name = ?").bind(name)); } void User::setSubsonicDefaultTranscodingOutputBitrate(Bitrate bitrate) diff --git a/src/libs/database/impl/Utils.cpp b/src/libs/database/impl/Utils.cpp index 6bafab61d..c3648b436 100644 --- a/src/libs/database/impl/Utils.cpp +++ b/src/libs/database/impl/Utils.cpp @@ -33,5 +33,4 @@ namespace lms::db::utils // force second resolution return Wt::WDateTime::fromTime_t(dateTime.toTime_t()); } -} // namespace lms::db::Utils - +} // namespace lms::db::utils diff --git a/src/libs/database/impl/Utils.hpp b/src/libs/database/impl/Utils.hpp index 56db65d88..2c7386d2b 100644 --- a/src/libs/database/impl/Utils.hpp +++ b/src/libs/database/impl/Utils.hpp @@ -26,8 +26,8 @@ #include #include -#include "database/Types.hpp" #include "core/ITraceLogger.hpp" +#include "database/Types.hpp" namespace lms::db::utils { @@ -35,7 +35,7 @@ namespace lms::db::utils static inline constexpr char escapeChar{ '\\' }; std::string escapeLikeKeyword(std::string_view keywords); - template + template void applyRange(Query& query, std::optional range) { if (range) @@ -45,21 +45,21 @@ namespace lms::db::utils } } - template + template auto fetchFirstResult(const Wt::Dbo::collection& collection) { LMS_SCOPED_TRACE_DETAILED("Database", "FetchFirstResult"); return collection.begin(); } - template + template void fetchNextResult(typename Wt::Dbo::collection::const_iterator& it) { LMS_SCOPED_TRACE_DETAILED("Database", "FetchNextResult"); it++; } - template + template void forEachResult(const Wt::Dbo::collection& collection, Func&& func) { typename Wt::Dbo::collection::const_iterator it{ fetchFirstResult(collection) }; @@ -70,23 +70,23 @@ namespace lms::db::utils } } - template + template struct QueryResultType; - template + template struct QueryResultType> { using type = ResultType; }; - template + template void forEachQueryResult(const Query& query, UnaryFunc&& func) { LMS_SCOPED_TRACE_DETAILED_WITH_ARG("Database", "ForEachQueryResult", "Query", query.asString()); forEachResult(query.resultList(), std::forward(func)); } - template + template std::vector fetchQueryResults(const Query& query) { LMS_SCOPED_TRACE_DETAILED_WITH_ARG("Database", "FetchQueryResults", "Query", query.asString()); @@ -95,7 +95,7 @@ namespace lms::db::utils return std::vector(collection.begin(), collection.end()); } - template + template std::vector::type> fetchQueryResults(const Query& query) { LMS_SCOPED_TRACE_DETAILED_WITH_ARG("Database", "FetchQueryResults", "Query", query.asString()); @@ -103,15 +103,15 @@ namespace lms::db::utils auto collection{ query.resultList() }; return std::vector::type>(collection.begin(), collection.end()); } - - template + + template auto fetchQuerySingleResult(const Query& query) { LMS_SCOPED_TRACE_DETAILED_WITH_ARG("Database", "FetchQuerySingleResult", "Query", query.asString()); return query.resultValue(); } - template + template RangeResults execRangeQuery(Query& query, const std::optional range) { RangeResults res; @@ -137,7 +137,7 @@ namespace lms::db::utils return res; } - template + template void forEachQueryRangeResult(Query& query, std::optional range, UnaryFunc&& func) { if (range) @@ -146,7 +146,7 @@ namespace lms::db::utils forEachQueryResult(query, std::forward(func)); } - template + template void forEachQueryRangeResult(Query& query, std::optional range, bool& moreResults, UnaryFunc&& func) { using ResultType = typename QueryResultType::type; @@ -173,4 +173,4 @@ namespace lms::db::utils } Wt::WDateTime normalizeDateTime(const Wt::WDateTime& dateTime); -} \ No newline at end of file +} // namespace lms::db::utils \ No newline at end of file diff --git a/src/libs/database/include/database/Artist.hpp b/src/libs/database/include/database/Artist.hpp index 12f81161c..4f1dbbd43 100644 --- a/src/libs/database/include/database/Artist.hpp +++ b/src/libs/database/include/database/Artist.hpp @@ -25,8 +25,8 @@ #include #include -#include #include +#include #include "core/EnumSet.hpp" #include "core/UUID.hpp" @@ -35,15 +35,16 @@ #include "database/MediaLibraryId.hpp" #include "database/Object.hpp" #include "database/ReleaseId.hpp" +#include "database/TrackId.hpp" #include "database/Types.hpp" #include "database/UserId.hpp" -#include "database/TrackId.hpp" namespace lms::db { class Cluster; class ClusterType; + class Image; class Release; class Session; class StarredArtist; @@ -56,51 +57,93 @@ namespace lms::db public: struct FindParameters { - std::vector clusters; // if non empty, at least one artist that belongs to these clusters - std::vector keywords; // if non empty, name must match all of these keywords (on either name field OR sort name field) - std::optional linkType; // if set, only artists that have produced at least one track with this link type - ArtistSortMethod sortMethod{ ArtistSortMethod::None }; - std::optional range; - Wt::WDateTime writtenAfter; - UserId starringUser; // only artists starred by this user - std::optional feedbackBackend; // and for this feedback backend - TrackId track; // artists involved in this track - ReleaseId release; // artists involved in this release - MediaLibraryId mediaLibrary; // artists that belong to this library - - FindParameters& setClusters(std::span _clusters) { clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); return *this; } - FindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } - FindParameters& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } - FindParameters& setSortMethod(ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; } - FindParameters& setRange(std::optional _range) { range = _range; return *this; } - FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; } - FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) { starringUser = _user; feedbackBackend = _feedbackBackend; return *this; } - FindParameters& setTrack(TrackId _track) { track = _track; return *this; } - FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } - FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) { mediaLibrary = _mediaLibrary; return *this; } + std::vector clusters; // if non empty, at least one artist that belongs to these clusters + std::vector keywords; // if non empty, name must match all of these keywords (on either name field OR sort name field) + std::optional linkType; // if set, only artists that have produced at least one track with this link type + ArtistSortMethod sortMethod{ ArtistSortMethod::None }; + std::optional range; + Wt::WDateTime writtenAfter; + UserId starringUser; // only artists starred by this user + std::optional feedbackBackend; // and for this feedback backend + TrackId track; // artists involved in this track + ReleaseId release; // artists involved in this release + MediaLibraryId mediaLibrary; // artists that belong to this library + + FindParameters& setClusters(std::span _clusters) + { + clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); + return *this; + } + FindParameters& setKeywords(const std::vector& _keywords) + { + keywords = _keywords; + return *this; + } + FindParameters& setLinkType(std::optional _linkType) + { + linkType = _linkType; + return *this; + } + FindParameters& setSortMethod(ArtistSortMethod _sortMethod) + { + sortMethod = _sortMethod; + return *this; + } + FindParameters& setRange(std::optional _range) + { + range = _range; + return *this; + } + FindParameters& setWrittenAfter(const Wt::WDateTime& _after) + { + writtenAfter = _after; + return *this; + } + FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) + { + starringUser = _user; + feedbackBackend = _feedbackBackend; + return *this; + } + FindParameters& setTrack(TrackId _track) + { + track = _track; + return *this; + } + FindParameters& setRelease(ReleaseId _release) + { + release = _release; + return *this; + } + FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) + { + mediaLibrary = _mediaLibrary; + return *this; + } }; Artist() = default; // Accessors - static std::size_t getCount(Session& session); - static pointer find(Session& session, const core::UUID& MBID); - static pointer find(Session& session, ArtistId id); - static std::vector find(Session& session, std::string_view name); // exact match on name field - static void find(Session& session, ArtistId& lastRetrievedArtist, std::size_t count, const std::function& func, MediaLibraryId library = {}); - static RangeResults find(Session& session, const FindParameters& parameters); - static void find(Session& session, const FindParameters& parameters, std::function func); - static RangeResults findIds(Session& session, const FindParameters& parameters); - static RangeResults findOrphanIds(Session& session, std::optional range = std::nullopt); // No track related - static bool exists(Session& session, ArtistId id); + static std::size_t getCount(Session& session); + static pointer find(Session& session, const core::UUID& MBID); + static pointer find(Session& session, ArtistId id); + static std::vector find(Session& session, std::string_view name); // exact match on name field + static void find(Session& session, ArtistId& lastRetrievedArtist, std::size_t count, const std::function& func, MediaLibraryId library = {}); + static RangeResults find(Session& session, const FindParameters& parameters); + static void find(Session& session, const FindParameters& parameters, std::function func); + static RangeResults findIds(Session& session, const FindParameters& parameters); + static RangeResults findOrphanIds(Session& session, std::optional range = std::nullopt); // No track related + static bool exists(Session& session, ArtistId id); // Accessors const std::string& getName() const { return _name; } const std::string& getSortName() const { return _sortName; } - std::optional getMBID() const { return core::UUID::fromString(_MBID); } + std::optional getMBID() const { return core::UUID::fromString(_MBID); } + ObjectPtr getImage() const; // No artistLinkTypes means get them all - RangeResults findSimilarArtistIds(core::EnumSet artistLinkTypes = {}, std::optional range = std::nullopt) const; + RangeResults findSimilarArtistIds(core::EnumSet artistLinkTypes = {}, std::optional range = std::nullopt) const; // Get the cluster of the tracks made by this artist // Each clusters are grouped by cluster type, sorted by the number of occurence @@ -110,6 +153,7 @@ namespace lms::db void setName(std::string_view name) { _name = name; } void setMBID(const std::optional& mbid) { _MBID = mbid ? mbid->getAsString() : ""; } void setSortName(const std::string& sortName); + void setImage(ObjectPtr image); template void persist(Action& a) @@ -118,6 +162,7 @@ namespace lms::db Wt::Dbo::field(a, _sortName, "sort_name"); Wt::Dbo::field(a, _MBID, "mbid"); + Wt::Dbo::hasOne(a, _image, "artist"); Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "artist"); Wt::Dbo::hasMany(a, _starredArtists, Wt::Dbo::ManyToMany, "user_starred_artists", "", Wt::Dbo::OnDeleteCascade); } @@ -132,11 +177,11 @@ namespace lms::db std::string _name; std::string _sortName; - std::string _MBID; // Musicbrainz Identifier + std::string _MBID; // Musicbrainz Identifier - Wt::Dbo::collection> _trackArtistLinks; // Tracks involving this artist - Wt::Dbo::collection> _starredArtists; // starred entries for this artist + Wt::Dbo::weak_ptr _image; + Wt::Dbo::collection> _trackArtistLinks; // Tracks involving this artist + Wt::Dbo::collection> _starredArtists; // starred entries for this artist }; } // namespace lms::db - diff --git a/src/libs/database/include/database/ArtistId.hpp b/src/libs/database/include/database/ArtistId.hpp index 4735f37b5..b9320263f 100644 --- a/src/libs/database/include/database/ArtistId.hpp +++ b/src/libs/database/include/database/ArtistId.hpp @@ -22,4 +22,3 @@ #include "database/IdType.hpp" LMS_DECLARE_IDTYPE(ArtistId) - diff --git a/src/libs/database/include/database/AuthToken.hpp b/src/libs/database/include/database/AuthToken.hpp index 0648e2b88..474f0386d 100644 --- a/src/libs/database/include/database/AuthToken.hpp +++ b/src/libs/database/include/database/AuthToken.hpp @@ -29,39 +29,38 @@ namespace lms::db { - class Session; + class Session; - class User; - class AuthToken final : public Object - { - public: - AuthToken() = default; + class User; + class AuthToken final : public Object + { + public: + AuthToken() = default; - // Utility - static void removeExpiredTokens(Session& session, const Wt::WDateTime& now); - static pointer find(Session& session, std::string_view value); + // Utility + static void removeExpiredTokens(Session& session, const Wt::WDateTime& now); + static pointer find(Session& session, std::string_view value); - // Accessors - const Wt::WDateTime& getExpiry() const { return _expiry; } - ObjectPtr getUser() const { return _user; } - const std::string& getValue() const { return _value; } + // Accessors + const Wt::WDateTime& getExpiry() const { return _expiry; } + ObjectPtr getUser() const { return _user; } + const std::string& getValue() const { return _value; } - template - void persist(Action& a) - { - Wt::Dbo::field(a, _value, "value"); - Wt::Dbo::field(a, _expiry, "expiry"); - Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade); - } + template + void persist(Action& a) + { + Wt::Dbo::field(a, _value, "value"); + Wt::Dbo::field(a, _expiry, "expiry"); + Wt::Dbo::belongsTo(a, _user, "user", Wt::Dbo::OnDeleteCascade); + } - private: - friend class Session; - AuthToken(std::string_view value, const Wt::WDateTime& expiry, ObjectPtr user); - static pointer create(Session& session, std::string_view value, const Wt::WDateTime&expiry, ObjectPtr user); - - std::string _value; - Wt::WDateTime _expiry; - Wt::Dbo::ptr _user; - }; -} // namespace Databas' + private: + friend class Session; + AuthToken(std::string_view value, const Wt::WDateTime& expiry, ObjectPtr user); + static pointer create(Session& session, std::string_view value, const Wt::WDateTime& expiry, ObjectPtr user); + std::string _value; + Wt::WDateTime _expiry; + Wt::Dbo::ptr _user; + }; +} // namespace lms::db diff --git a/src/libs/database/include/database/Cluster.hpp b/src/libs/database/include/database/Cluster.hpp index fbdc1a722..8b6ce020b 100644 --- a/src/libs/database/include/database/Cluster.hpp +++ b/src/libs/database/include/database/Cluster.hpp @@ -45,41 +45,65 @@ namespace lms::db public: struct FindParameters { - std::optional range; - ClusterSortMethod sortMethod; - ClusterTypeId clusterType; // if non empty, clusters that belong to this cluster type - std::string clusterTypeName; // if non empty, clusters that belong to this cluster type - TrackId track; // if set, clusters involved in this track - ReleaseId release; // if set, clusters involved in this release - - FindParameters& setRange(std::optional _range) { range = _range; return *this; } - FindParameters& setSortMethod(ClusterSortMethod _method) { sortMethod = _method; return *this; } - FindParameters& setClusterType(ClusterTypeId _clusterType) { clusterType = _clusterType; return *this; } - FindParameters& setClusterTypeName(std::string_view _name) { clusterTypeName = _name; return *this; } - FindParameters& setTrack(TrackId _track) { track = _track; return *this; } - FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } + std::optional range; + ClusterSortMethod sortMethod; + ClusterTypeId clusterType; // if non empty, clusters that belong to this cluster type + std::string clusterTypeName; // if non empty, clusters that belong to this cluster type + TrackId track; // if set, clusters involved in this track + ReleaseId release; // if set, clusters involved in this release + + FindParameters& setRange(std::optional _range) + { + range = _range; + return *this; + } + FindParameters& setSortMethod(ClusterSortMethod _method) + { + sortMethod = _method; + return *this; + } + FindParameters& setClusterType(ClusterTypeId _clusterType) + { + clusterType = _clusterType; + return *this; + } + FindParameters& setClusterTypeName(std::string_view _name) + { + clusterTypeName = _name; + return *this; + } + FindParameters& setTrack(TrackId _track) + { + track = _track; + return *this; + } + FindParameters& setRelease(ReleaseId _release) + { + release = _release; + return *this; + } }; Cluster() = default; // Find utility - static std::size_t getCount(Session& session); - static RangeResults findIds(Session& session, const FindParameters& params); - static RangeResults find(Session& session, const FindParameters& params); - static void find(Session& session, const FindParameters& params, std::function _func); - static pointer find(Session& session, ClusterId id); - static RangeResults findOrphanIds(Session& session, std::optional range = std::nullopt); + static std::size_t getCount(Session& session); + static RangeResults findIds(Session& session, const FindParameters& params); + static RangeResults find(Session& session, const FindParameters& params); + static void find(Session& session, const FindParameters& params, std::function _func); + static pointer find(Session& session, ClusterId id); + static RangeResults findOrphanIds(Session& session, std::optional range = std::nullopt); // May be very slow - static std::size_t computeTrackCount(Session& session, ClusterId id); - static std::size_t computeReleaseCount(Session& session, ClusterId id); + static std::size_t computeTrackCount(Session& session, ClusterId id); + static std::size_t computeReleaseCount(Session& session, ClusterId id); // Accessors - std::string_view getName() const { return _name; } - ObjectPtr getType() const { return _clusterType; } - std::size_t getTrackCount() const { return _trackCount; } - RangeResults getTracks(std::optional range = std::nullopt) const; - std::size_t getReleasesCount() const { return _releaseCount; }; + std::string_view getName() const { return _name; } + ObjectPtr getType() const { return _clusterType; } + std::size_t getTrackCount() const { return _trackCount; } + RangeResults getTracks(std::optional range = std::nullopt) const; + std::size_t getReleasesCount() const { return _releaseCount; }; void setReleaseCount(std::size_t releaseCount) { _releaseCount = releaseCount; } void setTrackCount(std::size_t trackCount) { _trackCount = trackCount; } @@ -104,35 +128,34 @@ namespace lms::db static const std::size_t _maxNameLength = 128; - std::string _name; + std::string _name; int _trackCount{}; int _releaseCount{}; Wt::Dbo::ptr _clusterType; - Wt::Dbo::collection< Wt::Dbo::ptr > _tracks; + Wt::Dbo::collection> _tracks; }; - class ClusterType final : public Object { public: ClusterType() = default; // Getters - static std::size_t getCount(Session& session); - static RangeResults findIds(Session& session, std::optional range = std::nullopt); - static void find(Session& session, const std::function& func); - static pointer find(Session& session, std::string_view name); - static pointer find(Session& session, ClusterTypeId id); - static RangeResults findOrphanIds(Session& session, std::optional range = std::nullopt); - static RangeResults findUsed(Session& session, std::optional range = std::nullopt); + static std::size_t getCount(Session& session); + static RangeResults findIds(Session& session, std::optional range = std::nullopt); + static void find(Session& session, const std::function& func); + static pointer find(Session& session, std::string_view name); + static pointer find(Session& session, ClusterTypeId id); + static RangeResults findOrphanIds(Session& session, std::optional range = std::nullopt); + static RangeResults findUsed(Session& session, std::optional range = std::nullopt); static void remove(Session& session, const std::string& name); // Accessors std::string_view getName() const { return _name; } - std::vector getClusters() const; - Cluster::pointer getCluster(const std::string& name) const; + std::vector getClusters() const; + Cluster::pointer getCluster(const std::string& name) const; template void persist(Action& a) @@ -148,9 +171,8 @@ namespace lms::db static const std::size_t _maxNameLength = 128; - std::string _name; - Wt::Dbo::collection< Wt::Dbo::ptr > _clusters; + std::string _name; + Wt::Dbo::collection> _clusters; }; } // namespace lms::db - diff --git a/src/libs/database/include/database/ClusterId.hpp b/src/libs/database/include/database/ClusterId.hpp index c6ebbfd1e..4ee317416 100644 --- a/src/libs/database/include/database/ClusterId.hpp +++ b/src/libs/database/include/database/ClusterId.hpp @@ -23,4 +23,3 @@ LMS_DECLARE_IDTYPE(ClusterId) LMS_DECLARE_IDTYPE(ClusterTypeId) - diff --git a/src/libs/database/include/database/Db.hpp b/src/libs/database/include/database/Db.hpp index fe444ebcf..a90f319ab 100644 --- a/src/libs/database/include/database/Db.hpp +++ b/src/libs/database/include/database/Db.hpp @@ -63,10 +63,10 @@ namespace lms::db }; core::RecursiveSharedMutex _sharedMutex; - std::unique_ptr _connectionPool; + std::unique_ptr _connectionPool; std::mutex _tlsSessionsMutex; std::vector> _tlsSessions; }; -} +} // namespace lms::db diff --git a/src/libs/database/include/database/Directory.hpp b/src/libs/database/include/database/Directory.hpp new file mode 100644 index 000000000..51564cf6d --- /dev/null +++ b/src/libs/database/include/database/Directory.hpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include +#include + +#include + +#include "core/EnumSet.hpp" +#include "database/ArtistId.hpp" +#include "database/DirectoryId.hpp" +#include "database/Object.hpp" +#include "database/Types.hpp" + +namespace lms::db +{ + class Session; + + class Directory final : public Object + { + public: + Directory() = default; + + struct FindParameters + { + std::optional range; + ArtistId artist; // only tracks that involve this artist + core::EnumSet trackArtistLinkTypes; // and for these link types + + FindParameters& setRange(std::optional _range) + { + range = _range; + return *this; + } + FindParameters& setArtist(ArtistId _artist, core::EnumSet _trackArtistLinkTypes = {}) + { + artist = _artist; + trackArtistLinkTypes = _trackArtistLinkTypes; + return *this; + } + }; + + // find + static std::size_t getCount(Session& session); + static pointer find(Session& session, DirectoryId id); + static pointer find(Session& session, const std::filesystem::path& path); + static void find(Session& session, DirectoryId& lastRetrievedDirectory, std::size_t count, const std::function& func); + static void find(Session& session, const FindParameters& parameters, const std::function& func); + static RangeResults findOrphanIds(Session& session, std::optional range = std::nullopt); + + // getters + const std::filesystem::path& getAbsolutePath() const { return _absolutePath; } + std::string_view getName() const { return _name; } + ObjectPtr getParent() const { return _parent; } + + // setters + void setAbsolutePath(const std::filesystem::path& p); + void setParent(ObjectPtr parent); + + template + void persist(Action& a) + { + Wt::Dbo::field(a, _absolutePath, "absolute_path"); + Wt::Dbo::field(a, _name, "name"); + + Wt::Dbo::belongsTo(a, _parent, "parent_directory", Wt::Dbo::OnDeleteCascade); + } + + private: + friend class Session; + Directory(const std::filesystem::path& p); + static pointer create(Session& session, const std::filesystem::path& p); + + std::filesystem::path _absolutePath; + std::string _name; + + Wt::Dbo::ptr _parent; + }; +} // namespace lms::db diff --git a/src/libs/database/include/database/DirectoryId.hpp b/src/libs/database/include/database/DirectoryId.hpp new file mode 100644 index 000000000..1e557e60c --- /dev/null +++ b/src/libs/database/include/database/DirectoryId.hpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include "database/IdType.hpp" + +LMS_DECLARE_IDTYPE(DirectoryId) diff --git a/src/libs/database/include/database/IdType.hpp b/src/libs/database/include/database/IdType.hpp index 972c9c46d..bbfb202ab 100644 --- a/src/libs/database/include/database/IdType.hpp +++ b/src/libs/database/include/database/IdType.hpp @@ -19,10 +19,9 @@ #pragma once - +#include #include #include -#include namespace lms::db { @@ -32,38 +31,43 @@ namespace lms::db using ValueType = Wt::Dbo::dbo_default_traits::IdType; IdType() = default; - IdType(ValueType id) : _id{ id } { assert(isValid()); } + IdType(ValueType id) + : _id{ id } { assert(isValid()); } bool isValid() const { return _id != Wt::Dbo::dbo_default_traits::invalidId(); } - std::string toString() const { assert(isValid()); return std::to_string(_id); } + std::string toString() const + { + assert(isValid()); + return std::to_string(_id); + } ValueType getValue() const { return _id; } - auto operator<=>(const IdType& other) const = default; \ + auto operator<=>(const IdType& other) const = default; private: Wt::Dbo::dbo_default_traits::IdType _id{ Wt::Dbo::dbo_default_traits::invalidId() }; }; -#define LMS_DECLARE_IDTYPE(name) \ - namespace lms::db { \ - class name : public IdType \ - { \ - public: \ - using IdType::IdType; \ - auto operator<=>(const name& other) const = default; \ - };\ - } \ - namespace std \ - { \ - template<> \ - class hash \ - { \ - public: \ - size_t operator()(lms::db::name id) const \ - { \ +#define LMS_DECLARE_IDTYPE(name) \ + namespace lms::db \ + { \ + class name : public IdType \ + { \ + public: \ + using IdType::IdType; \ + auto operator<=>(const name& other) const = default; \ + }; \ + } \ + namespace std \ + { \ + template<> \ + class hash \ + { \ + public: \ + size_t operator()(lms::db::name id) const \ + { \ return std::hash()(id.getValue()); \ - } \ - }; \ + } \ + }; \ } // ns std } // namespace lms::db - diff --git a/src/libs/database/include/database/Image.hpp b/src/libs/database/include/database/Image.hpp new file mode 100644 index 000000000..d20818353 --- /dev/null +++ b/src/libs/database/include/database/Image.hpp @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include +#include + +#include +#include + +#include "database/ArtistId.hpp" +#include "database/DirectoryId.hpp" +#include "database/ImageId.hpp" +#include "database/Object.hpp" +#include "database/Types.hpp" + +namespace lms::db +{ + class Artist; + class Directory; + class Session; + + class Image final : public Object + { + public: + Image() = default; + + struct FindParameters + { + std::optional range; + std::string fileStem; // if set, images with this file stem + DirectoryId directory; // if set, images in this directory + + FindParameters& setRange(std::optional _range) + { + range = _range; + return *this; + } + FindParameters& setFileStem(std::string_view _fileStem) + { + fileStem = _fileStem; + return *this; + } + FindParameters& setDirectory(DirectoryId _directory) + { + directory = _directory; + return *this; + } + }; + + // find + static std::size_t getCount(Session& session); + static pointer find(Session& session, ImageId id); + static pointer find(Session& session, const std::filesystem::path& file); + static RangeResults find(Session& session, const FindParameters& params); + static void find(Session& session, const FindParameters& parameters, const std::function& func); + static void find(Session& session, ImageId& lastRetrievedImage, std::size_t count, const std::function& func); + + // getters + const std::filesystem::path& getAbsoluteFilePath() const { return _fileAbsolutePath; } + std::string_view getFileStem() const { return _fileStem; } + const Wt::WDateTime& getLastWriteTime() const { return _fileLastWrite; } + std::size_t getFileSize() const { return _fileSize; } + std::size_t getWidth() const { return _width; } + std::size_t getHeight() const { return _height; } + + // setters + void setAbsoluteFilePath(const std::filesystem::path& p); + void setLastWriteTime(Wt::WDateTime time) { _fileLastWrite = time; } + void setFileSize(std::size_t fileSize) { _fileSize = fileSize; } + void setWidth(std::size_t width) { _width = width; } + void setHeight(std::size_t height) { _height = height; } + void setArtist(const ObjectPtr& artist) { _artist = getDboPtr(artist); } + void setDirectory(const ObjectPtr& directory) { _directory = getDboPtr(directory); } + + template + void persist(Action& a) + { + Wt::Dbo::field(a, _fileAbsolutePath, "absolute_file_path"); + Wt::Dbo::field(a, _fileStem, "stem"); + Wt::Dbo::field(a, _fileLastWrite, "file_last_write"); + Wt::Dbo::field(a, _fileSize, "file_size"); + + Wt::Dbo::field(a, _width, "width"); + Wt::Dbo::field(a, _height, "height"); + + Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade); + Wt::Dbo::belongsTo(a, _directory, "directory", Wt::Dbo::OnDeleteCascade); + } + + private: + friend class Session; + Image(const std::filesystem::path& p); + static pointer create(Session& session, const std::filesystem::path& p); + + std::filesystem::path _fileAbsolutePath; + std::string _fileStem; + Wt::WDateTime _fileLastWrite; + int _fileSize{}; + int _width{}; + int _height{}; + + Wt::Dbo::ptr _artist; + Wt::Dbo::ptr _directory; + }; +} // namespace lms::db diff --git a/src/libs/database/include/database/ImageId.hpp b/src/libs/database/include/database/ImageId.hpp new file mode 100644 index 000000000..5b4424f05 --- /dev/null +++ b/src/libs/database/include/database/ImageId.hpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include "database/IdType.hpp" + +LMS_DECLARE_IDTYPE(ImageId) diff --git a/src/libs/database/include/database/Listen.hpp b/src/libs/database/include/database/Listen.hpp index e4d41d692..8e55a0256 100644 --- a/src/libs/database/include/database/Listen.hpp +++ b/src/libs/database/include/database/Listen.hpp @@ -47,76 +47,124 @@ namespace lms::db struct FindParameters { - UserId user; - std::optional backend; - std::optional syncState; - std::optional range; - - FindParameters& setUser(UserId _user) { user = _user; return *this; } - FindParameters& setScrobblingBackend(ScrobblingBackend _backend) { backend = _backend; return *this; } - FindParameters& setSyncState(SyncState _syncState) { syncState = _syncState; return *this; } - FindParameters& setRange(Range _range) { range = _range; return *this; } + UserId user; + std::optional backend; + std::optional syncState; + std::optional range; + + FindParameters& setUser(UserId _user) + { + user = _user; + return *this; + } + FindParameters& setScrobblingBackend(ScrobblingBackend _backend) + { + backend = _backend; + return *this; + } + FindParameters& setSyncState(SyncState _syncState) + { + syncState = _syncState; + return *this; + } + FindParameters& setRange(Range _range) + { + range = _range; + return *this; + } }; // Accessors - static std::size_t getCount(Session& session); - static pointer find(Session& session, ListenId id); - static pointer find(Session& session, UserId userId, TrackId trackId, ScrobblingBackend backend, const Wt::WDateTime& dateTime); - static RangeResults find(Session& session, const FindParameters& parameters); + static std::size_t getCount(Session& session); + static pointer find(Session& session, ListenId id); + static pointer find(Session& session, UserId userId, TrackId trackId, ScrobblingBackend backend, const Wt::WDateTime& dateTime); + static RangeResults find(Session& session, const FindParameters& parameters); // Stats struct StatsFindParameters { - UserId user; - std::optional backend; - std::vector clusters; // if non empty, entities that belong to these clusters - std::vector keywords; // if non empty, name must match all of these keywords - std::optional range; - ArtistId artist; // if set, matching this artist - MediaLibraryId library; - - StatsFindParameters& setUser(UserId _user) { user = _user; return *this; } - StatsFindParameters& setScrobblingBackend(std::optional _backend) { backend = _backend; return *this; } - StatsFindParameters& setClusters(const std::vector& _clusters) { clusters = _clusters; return *this; } - StatsFindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } - StatsFindParameters& setRange(std::optional _range) { range = _range; return *this; } - StatsFindParameters& setArtist(ArtistId _artist) { artist = _artist; return *this; } - StatsFindParameters& setMediaLibrary(MediaLibraryId _library) { library = _library; return *this; } + UserId user; + std::optional backend; + std::vector clusters; // if non empty, entities that belong to these clusters + std::vector keywords; // if non empty, name must match all of these keywords + std::optional range; + ArtistId artist; // if set, matching this artist + MediaLibraryId library; + + StatsFindParameters& setUser(UserId _user) + { + user = _user; + return *this; + } + StatsFindParameters& setScrobblingBackend(std::optional _backend) + { + backend = _backend; + return *this; + } + StatsFindParameters& setClusters(const std::vector& _clusters) + { + clusters = _clusters; + return *this; + } + StatsFindParameters& setKeywords(const std::vector& _keywords) + { + keywords = _keywords; + return *this; + } + StatsFindParameters& setRange(std::optional _range) + { + range = _range; + return *this; + } + StatsFindParameters& setArtist(ArtistId _artist) + { + artist = _artist; + return *this; + } + StatsFindParameters& setMediaLibrary(MediaLibraryId _library) + { + library = _library; + return *this; + } }; struct ArtistStatsFindParameters : public StatsFindParameters { - std::optional linkType; // if set, only artists that have produced at least one track with this link type + std::optional linkType; // if set, only artists that have produced at least one track with this link type - ArtistStatsFindParameters& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } + ArtistStatsFindParameters& setLinkType(std::optional _linkType) + { + linkType = _linkType; + return *this; + } }; - static RangeResults getTopArtists(Session& session, const ArtistStatsFindParameters& params); - static RangeResults getTopReleases(Session& session, const StatsFindParameters& params); - static RangeResults getTopTracks(Session& session, const StatsFindParameters& params); + static RangeResults getTopArtists(Session& session, const ArtistStatsFindParameters& params); + static RangeResults getTopReleases(Session& session, const StatsFindParameters& params); + static RangeResults getTopTracks(Session& session, const StatsFindParameters& params); - static RangeResults getRecentArtists(Session& session, const ArtistStatsFindParameters& params); - static RangeResults getRecentReleases(Session& session, const StatsFindParameters& params); - static RangeResults getRecentTracks(Session& session, const StatsFindParameters& params); + static RangeResults getRecentArtists(Session& session, const ArtistStatsFindParameters& params); + static RangeResults getRecentReleases(Session& session, const StatsFindParameters& params); + static RangeResults getRecentTracks(Session& session, const StatsFindParameters& params); - static std::size_t getCount(Session& session, UserId userId, TrackId trackId); // for the current backend - static std::size_t getCount(Session& session, UserId userId, ReleaseId trackId); // for the current backend + static std::size_t getCount(Session& session, UserId userId, TrackId trackId); // for the current backend + static std::size_t getCount(Session& session, UserId userId, ReleaseId trackId); // for the current backend - static pointer getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId releaseId); - static pointer getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, TrackId releaseId); + static pointer getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId releaseId); + static pointer getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, TrackId releaseId); - SyncState getSyncState() const { return _syncState; } - ObjectPtr getUser() const { return _user; } - ObjectPtr getTrack() const { return _track; } + SyncState getSyncState() const { return _syncState; } + ObjectPtr getUser() const { return _user; } + ObjectPtr getTrack() const { return _track; } const Wt::WDateTime& getDateTime() const { return _dateTime; } - void setSyncState(SyncState state) { _syncState = state; } + void setSyncState(SyncState state) { _syncState = state; } template void persist(Action& a) { Wt::Dbo::field(a, _dateTime, "date_time"); - Wt::Dbo::field(a, _backend, "backend"); // TODO rename + Wt::Dbo::field(a, _backend, "backend"); // TODO rename Wt::Dbo::field(a, _syncState, "sync_state"); // TODO rename Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade); @@ -128,13 +176,12 @@ namespace lms::db Listen(ObjectPtr user, ObjectPtr track, ScrobblingBackend backend, const Wt::WDateTime& dateTime); static pointer create(Session& session, ObjectPtr user, ObjectPtr track, ScrobblingBackend backend, const Wt::WDateTime& dateTime); - Wt::WDateTime _dateTime; - ScrobblingBackend _backend; - SyncState _syncState{ SyncState::PendingAdd }; + Wt::WDateTime _dateTime; + ScrobblingBackend _backend; + SyncState _syncState{ SyncState::PendingAdd }; - Wt::Dbo::ptr _user; - Wt::Dbo::ptr _track; + Wt::Dbo::ptr _user; + Wt::Dbo::ptr _track; }; } // namespace lms::db - diff --git a/src/libs/database/include/database/ListenId.hpp b/src/libs/database/include/database/ListenId.hpp index 690d21149..271a33572 100644 --- a/src/libs/database/include/database/ListenId.hpp +++ b/src/libs/database/include/database/ListenId.hpp @@ -22,4 +22,3 @@ #include "database/IdType.hpp" LMS_DECLARE_IDTYPE(ListenId) - diff --git a/src/libs/database/include/database/MediaLibrary.hpp b/src/libs/database/include/database/MediaLibrary.hpp index 1e452463d..6d3c391cb 100644 --- a/src/libs/database/include/database/MediaLibrary.hpp +++ b/src/libs/database/include/database/MediaLibrary.hpp @@ -44,7 +44,7 @@ namespace lms::db static pointer find(Session& session, MediaLibraryId id); static pointer find(Session& session, std::string_view name); static pointer find(Session& session, const std::filesystem::path& path); - static void find(Session& session, std::function func); + static void find(Session& session, std::function func); static std::vector find(Session& session); // getters @@ -67,7 +67,7 @@ namespace lms::db MediaLibrary(const std::filesystem::path& p, std::string_view name); static pointer create(Session& session, const std::filesystem::path& p = {}, std::string_view name = {}); - std::filesystem::path _path; - std::string _name; + std::filesystem::path _path; + std::string _name; }; } // namespace lms::db diff --git a/src/libs/database/include/database/Object.hpp b/src/libs/database/include/database/Object.hpp index 5fa33f690..b7acee4fb 100644 --- a/src/libs/database/include/database/Object.hpp +++ b/src/libs/database/include/database/Object.hpp @@ -19,20 +19,23 @@ #pragma once -#include #include +#include + #include "database/IdType.hpp" #include "database/TransactionChecker.hpp" namespace lms::db { - template + template class ObjectPtr { public: ObjectPtr() = default; - ObjectPtr(const Wt::Dbo::ptr& obj) : _obj{ obj } {} - ObjectPtr(Wt::Dbo::ptr&& obj) : _obj{ std::move(obj) } {} + ObjectPtr(const Wt::Dbo::ptr& obj) + : _obj{ obj } {} + ObjectPtr(Wt::Dbo::ptr&& obj) + : _obj{ std::move(obj) } {} const T* operator->() const { return _obj.get(); } operator bool() const { return _obj.get(); } @@ -60,11 +63,12 @@ namespace lms::db } private: - template friend class Object; + template + friend class Object; Wt::Dbo::ptr _obj; }; - template + template class Object : public Wt::Dbo::Dbo { static_assert(std::is_base_of_v); @@ -80,7 +84,8 @@ namespace lms::db typename Wt::Dbo::dbo_traits::IdType id() const = delete; protected: - template friend class ObjectPtr; + template + friend class ObjectPtr; virtual bool hasOnPreRemove() const { return false; } virtual void onPreRemove() {} @@ -89,8 +94,10 @@ namespace lms::db virtual void onPostCreated() {} // Can get raw dbo ptr only from Objects - template - static - Wt::Dbo::ptr getDboPtr(ObjectPtr ptr) { return ptr._obj; } + template + static Wt::Dbo::ptr getDboPtr(const ObjectPtr& ptr) + { + return ptr._obj; + } }; -} +} // namespace lms::db diff --git a/src/libs/database/include/database/Release.hpp b/src/libs/database/include/database/Release.hpp index d2d3349c9..7af4e2bc6 100644 --- a/src/libs/database/include/database/Release.hpp +++ b/src/libs/database/include/database/Release.hpp @@ -26,8 +26,8 @@ #include #include -#include #include +#include #include "core/EnumSet.hpp" #include "core/UUID.hpp" @@ -54,8 +54,8 @@ namespace lms::db { public: ReleaseType() = default; - static pointer find(Session& session, ReleaseTypeId id); - static pointer find(Session& session, std::string_view name); + static pointer find(Session& session, ReleaseTypeId id); + static pointer find(Session& session, std::string_view name); // Accessors std::string_view getName() const { return _name; } @@ -75,7 +75,7 @@ namespace lms::db static pointer create(Session& session, std::string_view name); std::string _name; - Wt::Dbo::collection> _releases; // releases that match this type + Wt::Dbo::collection> _releases; // releases that match this type }; class Release final : public Object @@ -83,27 +83,56 @@ namespace lms::db public: struct FindParameters { - std::vector clusters; // if non empty, releases that belong to these clusters - std::vector keywords; // if non empty, name must match all of these keywords - ReleaseSortMethod sortMethod{ ReleaseSortMethod::None }; - std::optional range; - Wt::WDateTime writtenAfter; - std::optional dateRange; - UserId starringUser; // only releases starred by this user - std::optional feedbackBackend; // and for this backend - ArtistId artist; // only releases that involved this user - core::EnumSet trackArtistLinkTypes; // and for these link types - core::EnumSet excludedTrackArtistLinkTypes; // but not for these link types - std::string releaseType; // If set, albums that has this release type - MediaLibraryId mediaLibrary; // If set, releases that has at least a track in this library - - FindParameters& setClusters(std::span _clusters) { clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); return *this; } - FindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } - FindParameters& setSortMethod(ReleaseSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; } - FindParameters& setRange(std::optional _range) { range = _range; return *this; } - FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; } - FindParameters& setDateRange(const std::optional& _dateRange) { dateRange = _dateRange; return *this; } - FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) { starringUser = _user; feedbackBackend = _feedbackBackend; return *this; } + std::vector clusters; // if non empty, releases that belong to these clusters + std::vector keywords; // if non empty, name must match all of these keywords + ReleaseSortMethod sortMethod{ ReleaseSortMethod::None }; + std::optional range; + Wt::WDateTime writtenAfter; + std::optional dateRange; + UserId starringUser; // only releases starred by this user + std::optional feedbackBackend; // and for this backend + ArtistId artist; // only releases that involved this user + core::EnumSet trackArtistLinkTypes; // and for these link types + core::EnumSet excludedTrackArtistLinkTypes; // but not for these link types + std::string releaseType; // If set, albums that has this release type + MediaLibraryId mediaLibrary; // If set, releases that has at least a track in this library + + FindParameters& setClusters(std::span _clusters) + { + clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); + return *this; + } + FindParameters& setKeywords(const std::vector& _keywords) + { + keywords = _keywords; + return *this; + } + FindParameters& setSortMethod(ReleaseSortMethod _sortMethod) + { + sortMethod = _sortMethod; + return *this; + } + FindParameters& setRange(std::optional _range) + { + range = _range; + return *this; + } + FindParameters& setWrittenAfter(const Wt::WDateTime& _after) + { + writtenAfter = _after; + return *this; + } + FindParameters& setDateRange(const std::optional& _dateRange) + { + dateRange = _dateRange; + return *this; + } + FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) + { + starringUser = _user; + feedbackBackend = _feedbackBackend; + return *this; + } FindParameters& setArtist(ArtistId _artist, core::EnumSet _trackArtistLinkTypes = {}, core::EnumSet _excludedTrackArtistLinkTypes = {}) { artist = _artist; @@ -111,24 +140,32 @@ namespace lms::db excludedTrackArtistLinkTypes = _excludedTrackArtistLinkTypes; return *this; } - FindParameters& setReleaseType(std::string_view _releaseType) { releaseType = _releaseType; return *this; } - FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) { mediaLibrary = _mediaLibrary; return *this; } + FindParameters& setReleaseType(std::string_view _releaseType) + { + releaseType = _releaseType; + return *this; + } + FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) + { + mediaLibrary = _mediaLibrary; + return *this; + } }; Release() = default; // Accessors - static std::size_t getCount(Session& session); - static bool exists(Session& session, ReleaseId id); - static pointer find(Session& session, const core::UUID& MBID); - static std::vector find(Session& session, const std::string& name, const std::filesystem::path& releaseDirectory); - static pointer find(Session& session, ReleaseId id); - static void find(Session& session, ReleaseId& lastRetrievedRelease, std::size_t count, const std::function& func, MediaLibraryId library = {}); - static RangeResults find(Session& session, const FindParameters& parameters); - static void find(Session& session, const FindParameters& parameters, const std::function& func); - static RangeResults findIds(Session& session, const FindParameters& parameters); - static std::size_t getCount(Session& session, const FindParameters& parameters); - static RangeResults findOrphanIds(Session& session, std::optional range = std::nullopt); // not track related + static std::size_t getCount(Session& session); + static bool exists(Session& session, ReleaseId id); + static pointer find(Session& session, const core::UUID& MBID); + static std::vector find(Session& session, const std::string& name, const std::filesystem::path& releaseDirectory); + static pointer find(Session& session, ReleaseId id); + static void find(Session& session, ReleaseId& lastRetrievedRelease, std::size_t count, const std::function& func, MediaLibraryId library = {}); + static RangeResults find(Session& session, const FindParameters& parameters); + static void find(Session& session, const FindParameters& parameters, const std::function& func); + static RangeResults findIds(Session& session, const FindParameters& parameters); + static std::size_t getCount(Session& session, const FindParameters& parameters); + static RangeResults findOrphanIds(Session& session, std::optional range = std::nullopt); // not track related // Get the cluster of the tracks that belong to this release // Each clusters are grouped by cluster type, sorted by the number of occurence (max to min) @@ -136,28 +173,28 @@ namespace lms::db std::vector>> getClusterGroups(const std::vector& clusterTypeIds, std::size_t size) const; // Utility functions (if all tracks have the same values, which is legit to not be the case) - Wt::WDate getDate() const; - std::optional getYear() const; - Wt::WDate getOriginalDate() const; - std::optional getOriginalYear() const; - std::optional getCopyright() const; - std::optional getCopyrightURL() const; - std::size_t getMeanBitrate() const; + Wt::WDate getDate() const; + std::optional getYear() const; + Wt::WDate getOriginalDate() const; + std::optional getOriginalYear() const; + std::optional getCopyright() const; + std::optional getCopyrightURL() const; + std::size_t getMeanBitrate() const; // Accessors - std::string_view getName() const { return _name; } - std::string_view getSortName() const { return _sortName; } - std::optional getMBID() const { return core::UUID::fromString(_MBID); } - std::optional getGroupMBID() const { return core::UUID::fromString(_groupMBID); } - std::optional getTotalDisc() const { return _totalDisc; } - std::size_t getDiscCount() const; // may not be total disc (if incomplete for example) - std::vector getDiscs() const; - std::chrono::milliseconds getDuration() const; - Wt::WDateTime getLastWritten() const; - std::string_view getArtistDisplayName() const { return _artistDisplayName; } - std::size_t getTrackCount() const; + std::string_view getName() const { return _name; } + std::string_view getSortName() const { return _sortName; } + std::optional getMBID() const { return core::UUID::fromString(_MBID); } + std::optional getGroupMBID() const { return core::UUID::fromString(_groupMBID); } + std::optional getTotalDisc() const { return _totalDisc; } + std::size_t getDiscCount() const; // may not be total disc (if incomplete for example) + std::vector getDiscs() const; + std::chrono::milliseconds getDuration() const; + Wt::WDateTime getLastWritten() const; + std::string_view getArtistDisplayName() const { return _artistDisplayName; } + std::size_t getTrackCount() const; std::vector> getReleaseTypes() const; - std::vector getReleaseTypeNames() const; + std::vector getReleaseTypeNames() const; // Setters void setName(std::string_view name) { _name = name; } @@ -170,10 +207,10 @@ namespace lms::db void addReleaseType(ObjectPtr releaseType); // Get the artists of this release - std::vector> getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const; - std::vector> getReleaseArtists() const { return getArtists(TrackArtistLinkType::ReleaseArtist); } - bool hasVariousArtists() const; - std::vector getSimilarReleases(std::optional offset = {}, std::optional count = {}) const; + std::vector> getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const; + std::vector> getReleaseArtists() const { return getArtists(TrackArtistLinkType::ReleaseArtist); } + bool hasVariousArtists() const; + std::vector getSimilarReleases(std::optional offset = {}, std::optional count = {}) const; template void persist(Action& a) @@ -198,15 +235,15 @@ namespace lms::db static constexpr std::size_t _maxNameLength{ 256 }; - std::string _name; - std::string _sortName; - std::string _MBID; - std::string _groupMBID; - std::optional _totalDisc{}; - std::string _artistDisplayName; - - Wt::Dbo::collection> _tracks; // Tracks in the release - Wt::Dbo::collection> _releaseTypes; // Release types + std::string _name; + std::string _sortName; + std::string _MBID; + std::string _groupMBID; + std::optional _totalDisc{}; + std::string _artistDisplayName; + + Wt::Dbo::collection> _tracks; // Tracks in the release + Wt::Dbo::collection> _releaseTypes; // Release types }; } // namespace lms::db diff --git a/src/libs/database/include/database/ScanSettings.hpp b/src/libs/database/include/database/ScanSettings.hpp index 3275f2adf..95ac28d0f 100644 --- a/src/libs/database/include/database/ScanSettings.hpp +++ b/src/libs/database/include/database/ScanSettings.hpp @@ -63,19 +63,19 @@ namespace lms::db static pointer get(Session& session); // Getters - std::size_t getScanVersion() const { return _scanVersion; } - Wt::WTime getUpdateStartTime() const { return _startTime; } - UpdatePeriod getUpdatePeriod() const { return _updatePeriod; } - std::vector getExtraTagsToScan() const; - std::vector getAudioFileExtensions() const; - SimilarityEngineType getSimilarityEngineType() const { return _similarityEngineType; } - std::vector getArtistTagDelimiters() const; - std::vector getDefaultTagDelimiters() const; + std::size_t getScanVersion() const { return _scanVersion; } + Wt::WTime getUpdateStartTime() const { return _startTime; } + UpdatePeriod getUpdatePeriod() const { return _updatePeriod; } + std::vector getExtraTagsToScan() const; + std::vector getAudioFileExtensions() const; + SimilarityEngineType getSimilarityEngineType() const { return _similarityEngineType; } + std::vector getArtistTagDelimiters() const; + std::vector getDefaultTagDelimiters() const; // Setters void setUpdateStartTime(Wt::WTime t) { _startTime = t; } void setUpdatePeriod(UpdatePeriod p) { _updatePeriod = p; } - void setExtraTagsToScan(const std::vector& extraTags); + void setExtraTagsToScan(std::span extraTags); void setSimilarityEngineType(SimilarityEngineType type) { _similarityEngineType = type; } void setArtistTagDelimiters(std::span delimiters); void setDefaultTagDelimiters(std::span delimiters); @@ -95,13 +95,13 @@ namespace lms::db } private: - int _scanVersion{}; - Wt::WTime _startTime = Wt::WTime{ 0,0,0 }; - UpdatePeriod _updatePeriod{ UpdatePeriod::Never }; - SimilarityEngineType _similarityEngineType{ SimilarityEngineType::Clusters }; - std::string _audioFileExtensions{ ".alac .mp3 .ogg .oga .aac .m4a .m4b .flac .wav .wma .aif .aiff .ape .mpc .shn .opus .wv .dsf" }; - std::string _extraTagsToScan; - std::string _artistTagDelimiters; - std::string _defaultTagDelimiters; + int _scanVersion{}; + Wt::WTime _startTime = Wt::WTime{ 0, 0, 0 }; + UpdatePeriod _updatePeriod{ UpdatePeriod::Never }; + SimilarityEngineType _similarityEngineType{ SimilarityEngineType::Clusters }; + std::string _audioFileExtensions{ ".alac .mp3 .ogg .oga .aac .m4a .m4b .flac .wav .wma .aif .aiff .ape .mpc .shn .opus .wv .dsf" }; + std::string _extraTagsToScan; + std::string _artistTagDelimiters; + std::string _defaultTagDelimiters; }; } // namespace lms::db diff --git a/src/libs/database/include/database/Session.hpp b/src/libs/database/include/database/Session.hpp index cecc72251..80aafd3f4 100644 --- a/src/libs/database/include/database/Session.hpp +++ b/src/libs/database/include/database/Session.hpp @@ -24,6 +24,7 @@ #include #include + #include "core/ITraceLogger.hpp" #include "core/RecursiveSharedMutex.hpp" #include "database/Object.hpp" @@ -101,10 +102,16 @@ namespace lms::db 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; + } + Db& getDb() + { + return _db; + } - template + template typename Object::pointer create(Args&&... args) { checkWriteTransaction(); @@ -123,6 +130,6 @@ namespace lms::db Session& operator=(const Session&) = delete; Db& _db; - Wt::Dbo::Session _session; + Wt::Dbo::Session _session; }; } // namespace lms::db diff --git a/src/libs/database/include/database/StarredArtist.hpp b/src/libs/database/include/database/StarredArtist.hpp index acf6196ae..0e05137ea 100644 --- a/src/libs/database/include/database/StarredArtist.hpp +++ b/src/libs/database/include/database/StarredArtist.hpp @@ -19,8 +19,8 @@ #pragma once -#include #include +#include #include "database/ArtistId.hpp" #include "database/Object.hpp" @@ -40,17 +40,17 @@ namespace lms::db StarredArtist() = default; // Search utility - static std::size_t getCount(Session& session); - static pointer find(Session& session, StarredArtistId id); - static pointer find(Session& session, ArtistId artistId, UserId userId); // current backend - static pointer find(Session& session, ArtistId artistId, UserId userId, FeedbackBackend backend); + static std::size_t getCount(Session& session); + static pointer find(Session& session, StarredArtistId id); + static pointer find(Session& session, ArtistId artistId, UserId userId); // current backend + static pointer find(Session& session, ArtistId artistId, UserId userId, FeedbackBackend backend); // Accessors - ObjectPtr getArtist() const { return _artist; } - ObjectPtr getUser() const { return _user; } - FeedbackBackend getFeedbackBackend() const { return _backend; } + ObjectPtr getArtist() const { return _artist; } + ObjectPtr getUser() const { return _user; } + FeedbackBackend getFeedbackBackend() const { return _backend; } const Wt::WDateTime& getDateTime() const { return _dateTime; } - SyncState getSyncState() const { return _syncState; } + SyncState getSyncState() const { return _syncState; } // Setters void setDateTime(const Wt::WDateTime& dateTime); @@ -72,12 +72,11 @@ namespace lms::db StarredArtist(ObjectPtr artist, ObjectPtr user, FeedbackBackend scrobblingbackend); static pointer create(Session& session, ObjectPtr artist, ObjectPtr user, FeedbackBackend scrobblingbackend); - FeedbackBackend _backend; // for which backend - SyncState _syncState{ SyncState::PendingAdd }; - Wt::WDateTime _dateTime; // when it was starred + FeedbackBackend _backend; // for which backend + SyncState _syncState{ SyncState::PendingAdd }; + Wt::WDateTime _dateTime; // when it was starred - Wt::Dbo::ptr _artist; - Wt::Dbo::ptr _user; + Wt::Dbo::ptr _artist; + Wt::Dbo::ptr _user; }; } // namespace lms::db - diff --git a/src/libs/database/include/database/StarredRelease.hpp b/src/libs/database/include/database/StarredRelease.hpp index 13a9b7df4..7894f990d 100644 --- a/src/libs/database/include/database/StarredRelease.hpp +++ b/src/libs/database/include/database/StarredRelease.hpp @@ -19,8 +19,8 @@ #pragma once -#include #include +#include #include "database/Object.hpp" #include "database/ReleaseId.hpp" @@ -47,8 +47,8 @@ namespace lms::db // Accessors ObjectPtr getRelease() const { return _release; } - ObjectPtr getUser() const { return _user; } - FeedbackBackend getFeedbackBackend() const { return _backend; } + ObjectPtr getUser() const { return _user; } + FeedbackBackend getFeedbackBackend() const { return _backend; } const Wt::WDateTime& getDateTime() const { return _dateTime; } SyncState getSyncState() const { return _syncState; } @@ -72,12 +72,11 @@ namespace lms::db StarredRelease(ObjectPtr release, ObjectPtr user, FeedbackBackend backend); static pointer create(Session& session, ObjectPtr release, ObjectPtr user, FeedbackBackend backend); - FeedbackBackend _backend; // for which backend - SyncState _syncState{ SyncState::PendingAdd }; - Wt::WDateTime _dateTime; // when it was starred + FeedbackBackend _backend; // for which backend + SyncState _syncState{ SyncState::PendingAdd }; + Wt::WDateTime _dateTime; // when it was starred - Wt::Dbo::ptr _release; - Wt::Dbo::ptr _user; + Wt::Dbo::ptr _release; + Wt::Dbo::ptr _user; }; } // namespace lms::db - diff --git a/src/libs/database/include/database/StarredTrack.hpp b/src/libs/database/include/database/StarredTrack.hpp index 4b037f474..8ee2e3fba 100644 --- a/src/libs/database/include/database/StarredTrack.hpp +++ b/src/libs/database/include/database/StarredTrack.hpp @@ -19,13 +19,13 @@ #pragma once -#include #include +#include #include "core/EnumSet.hpp" -#include "database/TrackId.hpp" #include "database/Object.hpp" #include "database/StarredTrackId.hpp" +#include "database/TrackId.hpp" #include "database/Types.hpp" #include "database/UserId.hpp" @@ -42,30 +42,43 @@ namespace lms::db struct FindParameters { - std::optional backend; // for this backend - std::optional syncState; // and these states - UserId user; // and this user - std::optional range; - - FindParameters& setFeedbackBackend(FeedbackBackend _backend, SyncState _syncState) { backend = _backend; syncState = _syncState; return *this; } - FindParameters& setUser(UserId _user) { user = _user; return *this; } - FindParameters& setRange(std::optional _range) { range = _range; return *this; } + std::optional backend; // for this backend + std::optional syncState; // and these states + UserId user; // and this user + std::optional range; + + FindParameters& setFeedbackBackend(FeedbackBackend _backend, SyncState _syncState) + { + backend = _backend; + syncState = _syncState; + return *this; + } + FindParameters& setUser(UserId _user) + { + user = _user; + return *this; + } + FindParameters& setRange(std::optional _range) + { + range = _range; + return *this; + } }; // Search utility - static std::size_t getCount(Session& session); - static pointer find(Session& session, StarredTrackId id); - static pointer find(Session& session, TrackId trackId, UserId userId); // current feedback backend - static pointer find(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend); - static bool exists(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend); - static RangeResults find(Session& session, const FindParameters& findParams); + static std::size_t getCount(Session& session); + static pointer find(Session& session, StarredTrackId id); + static pointer find(Session& session, TrackId trackId, UserId userId); // current feedback backend + static pointer find(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend); + static bool exists(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend); + static RangeResults find(Session& session, const FindParameters& findParams); // Accessors - ObjectPtr getTrack() const { return _track; } - ObjectPtr getUser() const { return _user; } - FeedbackBackend getBackend() const { return _backend; } + ObjectPtr getTrack() const { return _track; } + ObjectPtr getUser() const { return _user; } + FeedbackBackend getBackend() const { return _backend; } const Wt::WDateTime& getDateTime() const { return _dateTime; } - SyncState getSyncState() const { return _syncState; } + SyncState getSyncState() const { return _syncState; } // Setters void setDateTime(const Wt::WDateTime& dateTime); @@ -87,12 +100,11 @@ namespace lms::db StarredTrack(ObjectPtr track, ObjectPtr user, FeedbackBackend backend); static pointer create(Session& session, ObjectPtr track, ObjectPtr user, FeedbackBackend backend); - FeedbackBackend _backend; // for which backend - SyncState _syncState{ SyncState::PendingAdd }; - Wt::WDateTime _dateTime; // when it was starred + FeedbackBackend _backend; // for which backend + SyncState _syncState{ SyncState::PendingAdd }; + Wt::WDateTime _dateTime; // when it was starred Wt::Dbo::ptr _track; - Wt::Dbo::ptr _user; + Wt::Dbo::ptr _user; }; } // namespace lms::db - diff --git a/src/libs/database/include/database/Track.hpp b/src/libs/database/include/database/Track.hpp index 39230cd63..3f9468c90 100644 --- a/src/libs/database/include/database/Track.hpp +++ b/src/libs/database/include/database/Track.hpp @@ -21,17 +21,17 @@ #include #include -#include #include +#include #include #include #include #include #include -#include #include #include +#include #include "core/EnumSet.hpp" #include "core/UUID.hpp" @@ -50,6 +50,7 @@ namespace lms::db class Artist; class Cluster; class ClusterType; + class Directory; class MediaLibrary; class Release; class Session; @@ -62,72 +63,140 @@ namespace lms::db public: struct FindParameters { - std::vector clusters; // if non empty, tracks that belong to these clusters - std::vector keywords; // if non empty, name must match all of these keywords - std::string name; // if non empty, must match this name - TrackSortMethod sortMethod{ TrackSortMethod::None }; - std::optional range; - Wt::WDateTime writtenAfter; - UserId starringUser; // only tracks starred by this user - std::optional feedbackBackend; // and for this feedback backend - ArtistId artist; // only tracks that involve this artist - std::string artistName; // only tracks that involve this artist name - core::EnumSet trackArtistLinkTypes; // and for these link types - bool nonRelease{}; // only tracks that do not belong to a release - ReleaseId release; // matching this release - std::string releaseName; // matching this release name - TrackListId trackList; // matching this trackList - std::optional trackNumber; // matching this track number - bool distinct{ true }; - std::optional discNumber; // matching this disc number - MediaLibraryId mediaLibrary; // If set, tracks in this library - std::optional rating; + std::vector clusters; // if non empty, tracks that belong to these clusters + std::vector keywords; // if non empty, name must match all of these keywords + std::string name; // if non empty, must match this name + TrackSortMethod sortMethod{ TrackSortMethod::None }; + std::optional range; + Wt::WDateTime writtenAfter; + UserId starringUser; // only tracks starred by this user + std::optional feedbackBackend; // and for this feedback backend + ArtistId artist; // only tracks that involve this artist + std::string artistName; // only tracks that involve this artist name + core::EnumSet trackArtistLinkTypes; // and for these link types + bool nonRelease{}; // only tracks that do not belong to a release + ReleaseId release; // matching this release + std::string releaseName; // matching this release name + TrackListId trackList; // matching this trackList + std::optional trackNumber; // matching this track number + std::optional discNumber; // matching this disc number + MediaLibraryId mediaLibrary; // If set, tracks in this library + std::optional rating; - FindParameters& setClusters(std::span _clusters) { clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); return *this; } - FindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } - FindParameters& setName(std::string_view _name) { name = _name; return *this; } - FindParameters& setSortMethod(TrackSortMethod _method) { sortMethod = _method; return *this; } - FindParameters& setRange(std::optional _range) { range = _range; return *this; } - FindParameters& setWrittenAfter(const Wt::WDateTime& _after) { writtenAfter = _after; return *this; } - FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) { starringUser = _user; feedbackBackend = _feedbackBackend; return *this; } - FindParameters& setArtist(ArtistId _artist, core::EnumSet _trackArtistLinkTypes = {}) { artist = _artist; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; } - FindParameters& setArtistName(std::string_view _artistName, core::EnumSet _trackArtistLinkTypes = {}) { artistName = _artistName; trackArtistLinkTypes = _trackArtistLinkTypes; return *this; } - FindParameters& setNonRelease(bool _nonRelease) { nonRelease = _nonRelease; return *this; } - FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } - FindParameters& setReleaseName(std::string_view _releaseName) { releaseName = _releaseName; return *this; } - FindParameters& setTrackList(TrackListId _trackList) { trackList = _trackList; return *this; } - FindParameters& setTrackNumber(int _trackNumber) { trackNumber = _trackNumber; return *this; } - FindParameters& setDistinct(bool _distinct) { distinct = _distinct; return *this; } - FindParameters& setDiscNumber(int _discNumber) { discNumber = _discNumber; return *this; } - FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) { mediaLibrary = _mediaLibrary; return *this; } - FindParameters& setRating(int _rating) { rating = _rating; return *this; } + FindParameters& setClusters(std::span _clusters) + { + clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); + return *this; + } + FindParameters& setKeywords(const std::vector& _keywords) + { + keywords = _keywords; + return *this; + } + FindParameters& setName(std::string_view _name) + { + name = _name; + return *this; + } + FindParameters& setSortMethod(TrackSortMethod _method) + { + sortMethod = _method; + return *this; + } + FindParameters& setRange(std::optional _range) + { + range = _range; + return *this; + } + FindParameters& setWrittenAfter(const Wt::WDateTime& _after) + { + writtenAfter = _after; + return *this; + } + FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend) + { + starringUser = _user; + feedbackBackend = _feedbackBackend; + return *this; + } + FindParameters& setArtist(ArtistId _artist, core::EnumSet _trackArtistLinkTypes = {}) + { + artist = _artist; + trackArtistLinkTypes = _trackArtistLinkTypes; + return *this; + } + FindParameters& setArtistName(std::string_view _artistName, core::EnumSet _trackArtistLinkTypes = {}) + { + artistName = _artistName; + trackArtistLinkTypes = _trackArtistLinkTypes; + return *this; + } + FindParameters& setNonRelease(bool _nonRelease) + { + nonRelease = _nonRelease; + return *this; + } + FindParameters& setRelease(ReleaseId _release) + { + release = _release; + return *this; + } + FindParameters& setReleaseName(std::string_view _releaseName) + { + releaseName = _releaseName; + return *this; + } + FindParameters& setTrackList(TrackListId _trackList) + { + trackList = _trackList; + return *this; + } + FindParameters& setTrackNumber(int _trackNumber) + { + trackNumber = _trackNumber; + return *this; + } + FindParameters& setDiscNumber(int _discNumber) + { + discNumber = _discNumber; + return *this; + } + FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) + { + mediaLibrary = _mediaLibrary; + return *this; + } + FindParameters& setRating(int _rating) + { + rating = _rating; + return *this; + } }; struct PathResult { - TrackId trackId; - std::filesystem::path path; + TrackId trackId; + std::filesystem::path path; }; Track() = default; // Find utility functions - static std::size_t getCount(Session& session); - static pointer findByPath(Session& session, const std::filesystem::path& p); - static pointer find(Session& session, TrackId id); - static void find(Session& session, TrackId& lastRetrievedTrack, std::size_t count, const std::function& func, MediaLibraryId library = {}); - static bool exists(Session& session, TrackId id); - static std::vector findByRecordingMBID(Session& session, const core::UUID& MBID); - static std::vector findByMBID(Session& session, const core::UUID& MBID); - static RangeResults findSimilarTrackIds(Session& session, const std::vector& trackIds, std::optional range = std::nullopt); + static std::size_t getCount(Session& session); + static pointer findByPath(Session& session, const std::filesystem::path& p); + static pointer find(Session& session, TrackId id); + static void find(Session& session, TrackId& lastRetrievedTrack, std::size_t count, const std::function& func, MediaLibraryId library = {}); + static bool exists(Session& session, TrackId id); + static std::vector findByRecordingMBID(Session& session, const core::UUID& MBID); + static std::vector findByMBID(Session& session, const core::UUID& MBID); + static RangeResults findSimilarTrackIds(Session& session, const std::vector& trackIds, std::optional range = std::nullopt); - static RangeResults findIds(Session& session, const FindParameters& parameters); - static RangeResults find(Session& session, const FindParameters& parameters); - static void find(Session& session, const FindParameters& parameters, const std::function& func); - static void find(Session& session, const FindParameters& parameters, bool& moreResults, const std::function& func); - static RangeResults findIdsTrackMBIDDuplicates(Session& session, std::optional range = std::nullopt); - static RangeResults findIdsWithRecordingMBIDAndMissingFeatures(Session& session, std::optional range = std::nullopt); - static std::vector getByYear(Session& session, int yearFrom, int yearTo, std::optional range = std::nullopt); + static RangeResults findIds(Session& session, const FindParameters& parameters); + static RangeResults find(Session& session, const FindParameters& parameters); + static void find(Session& session, const FindParameters& parameters, const std::function& func); + static void find(Session& session, const FindParameters& parameters, bool& moreResults, const std::function& func); + static RangeResults findIdsTrackMBIDDuplicates(Session& session, std::optional range = std::nullopt); + static RangeResults findIdsWithRecordingMBIDAndMissingFeatures(Session& session, std::optional range = std::nullopt); // Accessors void setScanVersion(std::size_t version) { _scanVersion = version; } @@ -164,45 +233,47 @@ namespace lms::db void setRelease(ObjectPtr release) { _release = getDboPtr(release); } void setClusters(const std::vector>& clusters); void setMediaLibrary(ObjectPtr mediaLibrary) { _mediaLibrary = getDboPtr(mediaLibrary); } + void setDirectory(ObjectPtr directory) { _directory = getDboPtr(directory); } - std::size_t getScanVersion() const { return _scanVersion; } - std::optional getTrackNumber() const { return _trackNumber; } - std::optional getTotalTrack() const { return _totalTrack; } - std::optional getDiscNumber() const { return _discNumber; } + std::size_t getScanVersion() const { return _scanVersion; } + std::optional getTrackNumber() const { return _trackNumber; } + std::optional getTotalTrack() const { return _totalTrack; } + std::optional getDiscNumber() const { return _discNumber; } std::optional getRating() const { return _rating; } const std::string& getDiscSubtitle() const { return _discSubtitle; } - std::string getName() const { return _name; } + std::string getName() const { return _name; } const std::filesystem::path& getAbsoluteFilePath() const { return _absoluteFilePath; } const std::filesystem::path& getRelativeFilePath() const { return _relativeFilePath; } - long long getFileSize() const { return _fileSize; } - std::size_t getBitrate() const { return _bitrate; } - std::size_t getBitsPerSample() const { return _bitsPerSample; } - std::size_t getChannelCount() const { return _channelCount; } - std::chrono::milliseconds getDuration() const { return _duration; } - std::size_t getSampleRate() const { return _sampleRate; } + long long getFileSize() const { return _fileSize; } + std::size_t getBitrate() const { return _bitrate; } + std::size_t getBitsPerSample() const { return _bitsPerSample; } + std::size_t getChannelCount() const { return _channelCount; } + std::chrono::milliseconds getDuration() const { return _duration; } + std::size_t getSampleRate() const { return _sampleRate; } const Wt::WDateTime& getLastWritten() const { return _fileLastWrite; } const Wt::WDate& getDate() const { return _date; } - std::optional getYear() const { return _year; } + std::optional getYear() const { return _year; } const Wt::WDate& getOriginalDate() const { return _originalDate; } - std::optional getOriginalYear() const { return _originalYear; }; + std::optional getOriginalYear() const { return _originalYear; }; const Wt::WDateTime& getLastWriteTime() const { return _fileLastWrite; } const Wt::WDateTime& getAddedTime() const { return _fileAdded; } - bool hasCover() const { return _hasCover; } - std::optional getTrackMBID() const { return core::UUID::fromString(_trackMBID); } - std::optional getRecordingMBID() const { return core::UUID::fromString(_recordingMBID); } - std::optional getCopyright() const; - std::optional getCopyrightURL() const; - std::optional getTrackReplayGain() const { return _trackReplayGain; } - std::optional getReleaseReplayGain() const { return _releaseReplayGain; } - std::string_view getArtistDisplayName() const { return _artistDisplayName; } + bool hasCover() const { return _hasCover; } + std::optional getTrackMBID() const { return core::UUID::fromString(_trackMBID); } + std::optional getRecordingMBID() const { return core::UUID::fromString(_recordingMBID); } + std::optional getCopyright() const; + std::optional getCopyrightURL() const; + std::optional getTrackReplayGain() const { return _trackReplayGain; } + std::optional getReleaseReplayGain() const { return _releaseReplayGain; } + std::string_view getArtistDisplayName() const { return _artistDisplayName; } // no artistLinkTypes means get all - std::vector> getArtists(core::EnumSet artistLinkTypes) const; // no type means all - std::vector getArtistIds(core::EnumSet artistLinkTypes) const; // no type means all - std::vector> getArtistLinks() const; - ObjectPtr getRelease() const { return _release; } - std::vector> getClusters() const; - std::vector getClusterIds() const; - ObjectPtr getMediaLibrary() const { return _mediaLibrary; } + std::vector> getArtists(core::EnumSet artistLinkTypes) const; // no type means all + std::vector getArtistIds(core::EnumSet artistLinkTypes) const; // no type means all + std::vector> getArtistLinks() const; + ObjectPtr getRelease() const { return _release; } + std::vector> getClusters() const; + std::vector getClusterIds() const; + ObjectPtr getMediaLibrary() const { return _mediaLibrary; } + ObjectPtr getDirectory() const { return _directory; } std::vector>> getClusterGroups(const std::vector& clusterTypes, std::size_t size) const; @@ -213,7 +284,7 @@ namespace lms::db Wt::Dbo::field(a, _trackNumber, "track_number"); Wt::Dbo::field(a, _rating, "rating"); Wt::Dbo::field(a, _discNumber, "disc_number"); - Wt::Dbo::field(a, _totalTrack, "total_track"); // here in Track since Release does not have concept of "disc" (yet?) + Wt::Dbo::field(a, _totalTrack, "total_track"); // here in Track since Release does not have concept of "disc" (yet?) Wt::Dbo::field(a, _discSubtitle, "disc_subtitle"); // here in Track since Release does not have concept of "disc" (yet?) Wt::Dbo::field(a, _name, "name"); Wt::Dbo::field(a, _duration, "duration"); @@ -240,6 +311,7 @@ namespace lms::db Wt::Dbo::field(a, _artistDisplayName, "artist_display_name"); Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade); Wt::Dbo::belongsTo(a, _mediaLibrary, "media_library", Wt::Dbo::OnDeleteSetNull); // don't delete track on media library removal, we want to wait for the next scan to have a chance to migrate files + Wt::Dbo::belongsTo(a, _directory, "directory", Wt::Dbo::OnDeleteCascade); Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track"); Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade); } @@ -252,40 +324,41 @@ namespace lms::db static constexpr std::size_t _maxCopyrightLength{ 256 }; static constexpr std::size_t _maxCopyrightURLLength{ 256 }; - int _scanVersion{}; - std::optional _trackNumber{}; + int _scanVersion{}; + std::optional _trackNumber{}; + std::optional _discNumber{}; std::optional _rating{}; - std::optional _discNumber{}; - std::optional _totalTrack{}; - std::string _discSubtitle; - std::string _name; - int _bitrate{}; // in bps - int _bitsPerSample{}; - int _channelCount{}; - std::chrono::duration _duration{}; - int _sampleRate{}; - Wt::WDate _date; - std::optional _year; - Wt::WDate _originalDate; - std::optional _originalYear; - std::filesystem::path _absoluteFilePath; // full path - std::filesystem::path _relativeFilePath; // relative to root (that may be deleted) - long long _fileSize{}; - Wt::WDateTime _fileLastWrite; - Wt::WDateTime _fileAdded; - bool _hasCover{}; - std::string _trackMBID; - std::string _recordingMBID; - std::string _copyright; - std::string _copyrightURL; - std::optional _trackReplayGain; - std::optional _releaseReplayGain; - std::string _artistDisplayName; + std::optional _totalTrack{}; + std::string _discSubtitle; + std::string _name; + int _bitrate{}; // in bps + int _bitsPerSample{}; + int _channelCount{}; + std::chrono::duration _duration{}; + int _sampleRate{}; + Wt::WDate _date; + std::optional _year; + Wt::WDate _originalDate; + std::optional _originalYear; + std::filesystem::path _absoluteFilePath; // full path + std::filesystem::path _relativeFilePath; // relative to root (that may be deleted) + long long _fileSize{}; + Wt::WDateTime _fileLastWrite; + Wt::WDateTime _fileAdded; + bool _hasCover{}; + std::string _trackMBID; + std::string _recordingMBID; + std::string _copyright; + std::string _copyrightURL; + std::optional _trackReplayGain; + std::optional _releaseReplayGain; + std::string _artistDisplayName; - Wt::Dbo::ptr _release; - Wt::Dbo::ptr _mediaLibrary; - Wt::Dbo::collection> _trackArtistLinks; - Wt::Dbo::collection> _clusters; + Wt::Dbo::ptr _release; + Wt::Dbo::ptr _mediaLibrary; + Wt::Dbo::ptr _directory; + Wt::Dbo::collection> _trackArtistLinks; + Wt::Dbo::collection> _clusters; }; namespace Debug @@ -296,5 +369,5 @@ namespace lms::db TrackId trackId; }; std::ostream& operator<<(std::ostream& os, const TrackInfo& trackInfo); - } + } // namespace Debug } // namespace lms::db diff --git a/src/libs/database/include/database/TrackArtistLink.hpp b/src/libs/database/include/database/TrackArtistLink.hpp index dafae759a..5e823b938 100644 --- a/src/libs/database/include/database/TrackArtistLink.hpp +++ b/src/libs/database/include/database/TrackArtistLink.hpp @@ -46,32 +46,52 @@ namespace lms::db public: struct FindParameters { - std::optional range; - std::optional linkType; // if set, only artists that have produced at least one track with this link type - ArtistId artist; // if set, links involved with this artist - ReleaseId release; // if set, artists involved in this release - TrackId track; // if set, artists involved in this track - - FindParameters& setRange(std::optional _range) { range = _range; return *this; } - FindParameters& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } - FindParameters& setArtist(ArtistId _artist) { artist = _artist; return *this; } - FindParameters& setRelease(ReleaseId _release) { release = _release; return *this; } - FindParameters& setTrack(TrackId _track) { track = _track; return *this; } + std::optional range; + std::optional linkType; // if set, only artists that have produced at least one track with this link type + ArtistId artist; // if set, links involved with this artist + ReleaseId release; // if set, artists involved in this release + TrackId track; // if set, artists involved in this track + + FindParameters& setRange(std::optional _range) + { + range = _range; + return *this; + } + FindParameters& setLinkType(std::optional _linkType) + { + linkType = _linkType; + return *this; + } + FindParameters& setArtist(ArtistId _artist) + { + artist = _artist; + return *this; + } + FindParameters& setRelease(ReleaseId _release) + { + release = _release; + return *this; + } + FindParameters& setTrack(TrackId _track) + { + track = _track; + return *this; + } }; TrackArtistLink() = default; TrackArtistLink(ObjectPtr track, ObjectPtr artist, TrackArtistLinkType type, std::string_view subType); - static void find(Session& session, TrackId trackId, const std::function&)>&); - static void find(Session& session, const FindParameters& parameters, const std::function&); - static pointer find(Session& session, TrackArtistLinkId linkId); - static pointer create(Session& session, ObjectPtr track, ObjectPtr artist, TrackArtistLinkType type, std::string_view subType = {}); - static core::EnumSet findUsedTypes(Session& session, ArtistId _artist); + static void find(Session& session, TrackId trackId, const std::function&)>&); + static void find(Session& session, const FindParameters& parameters, const std::function&); + static pointer find(Session& session, TrackArtistLinkId linkId); + static pointer create(Session& session, ObjectPtr track, ObjectPtr artist, TrackArtistLinkType type, std::string_view subType = {}); + static core::EnumSet findUsedTypes(Session& session, ArtistId _artist); - ObjectPtr getTrack() const { return _track; } - ObjectPtr getArtist() const { return _artist; } - TrackArtistLinkType getType() const { return _type; } - std::string_view getSubType() const { return _subType; } + ObjectPtr getTrack() const { return _track; } + ObjectPtr getArtist() const { return _artist; } + TrackArtistLinkType getType() const { return _type; } + std::string_view getSubType() const { return _subType; } template void persist(Action& a) @@ -90,5 +110,4 @@ namespace lms::db Wt::Dbo::ptr _track; Wt::Dbo::ptr _artist; }; -} - +} // namespace lms::db diff --git a/src/libs/database/include/database/TrackBookmark.hpp b/src/libs/database/include/database/TrackBookmark.hpp index d02287b0f..61a606a2c 100644 --- a/src/libs/database/include/database/TrackBookmark.hpp +++ b/src/libs/database/include/database/TrackBookmark.hpp @@ -44,20 +44,20 @@ namespace lms::db TrackBookmark() = default; // Find utility functions - static std::size_t getCount(Session& session); - static pointer find(Session& session, TrackBookmarkId id); - static RangeResults find(Session& session, UserId userId, std::optional range = std::nullopt); - static pointer find(Session& session, UserId userId, TrackId trackId); + static std::size_t getCount(Session& session); + static pointer find(Session& session, TrackBookmarkId id); + static RangeResults find(Session& session, UserId userId, std::optional range = std::nullopt); + static pointer find(Session& session, UserId userId, TrackId trackId); // Setters void setOffset(std::chrono::milliseconds offset) { _offset = offset; } void setComment(std::string_view comment) { _comment = comment; } // Getters - std::chrono::milliseconds getOffset() const { return _offset; } - std::string_view getComment() const { return _comment; } - ObjectPtr getTrack() const { return _track; } - ObjectPtr getUser() const { return _user; } + std::chrono::milliseconds getOffset() const { return _offset; } + std::string_view getComment() const { return _comment; } + ObjectPtr getTrack() const { return _track; } + ObjectPtr getUser() const { return _user; } template void persist(Action& a) @@ -75,11 +75,11 @@ namespace lms::db static const std::size_t _maxCommentLength = 128; - std::chrono::duration _offset; - std::string _comment; + std::chrono::duration _offset; + std::string _comment; - Wt::Dbo::ptr _user; - Wt::Dbo::ptr _track; + Wt::Dbo::ptr _user; + Wt::Dbo::ptr _track; }; } // namespace lms::db diff --git a/src/libs/database/include/database/TrackFeatures.hpp b/src/libs/database/include/database/TrackFeatures.hpp index 7c10ac80c..4ff8e1e9d 100644 --- a/src/libs/database/include/database/TrackFeatures.hpp +++ b/src/libs/database/include/database/TrackFeatures.hpp @@ -49,13 +49,13 @@ namespace lms::db TrackFeatures() = default; // Find utilities - static std::size_t getCount(Session& session); - static pointer find(Session& session, TrackFeaturesId id); - static pointer find(Session& session, TrackId trackId); - static RangeResults find(Session& session, std::optional range = std::nullopt); + static std::size_t getCount(Session& session); + static pointer find(Session& session, TrackFeaturesId id); + static pointer find(Session& session, TrackId trackId); + static RangeResults find(Session& session, std::optional range = std::nullopt); - FeatureValues getFeatureValues(const FeatureName& feature) const; - FeatureValuesMap getFeatureValuesMap(const std::unordered_set& featureNames) const; + FeatureValues getFeatureValues(const FeatureName& feature) const; + FeatureValuesMap getFeatureValuesMap(const std::unordered_set& featureNames) const; // Accessors Wt::Dbo::ptr getTrack() const { return _track; } diff --git a/src/libs/database/include/database/TrackList.hpp b/src/libs/database/include/database/TrackList.hpp index 91dc92e17..1284261a8 100644 --- a/src/libs/database/include/database/TrackList.hpp +++ b/src/libs/database/include/database/TrackList.hpp @@ -55,48 +55,72 @@ namespace lms::db // Search utility struct FindParameters { - std::vector clusters; // if non empty, tracklists that have tracks that belong to these clusters - std::optional range; - std::optional type; - UserId user; // only tracklists owned by this user - MediaLibraryId mediaLibrary; // only tracklists that have songs in this media library - TrackListSortMethod sortMethod{ TrackListSortMethod::None }; - - FindParameters& setClusters(std::span _clusters) { clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); return *this; } - FindParameters& setRange(std::optional _range) { range = _range; return *this; } - FindParameters& setType(TrackListType _type) { type = _type; return *this; } - FindParameters& setUser(UserId _user) { user = _user; return *this; } - FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) { mediaLibrary = _mediaLibrary; return *this; } - FindParameters& setSortMethod(TrackListSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; } + std::vector clusters; // if non empty, tracklists that have tracks that belong to these clusters + std::optional range; + std::optional type; + UserId user; // only tracklists owned by this user + MediaLibraryId mediaLibrary; // only tracklists that have songs in this media library + TrackListSortMethod sortMethod{ TrackListSortMethod::None }; + + FindParameters& setClusters(std::span _clusters) + { + clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); + return *this; + } + FindParameters& setRange(std::optional _range) + { + range = _range; + return *this; + } + FindParameters& setType(TrackListType _type) + { + type = _type; + return *this; + } + FindParameters& setUser(UserId _user) + { + user = _user; + return *this; + } + FindParameters& setMediaLibrary(MediaLibraryId _mediaLibrary) + { + mediaLibrary = _mediaLibrary; + return *this; + } + FindParameters& setSortMethod(TrackListSortMethod _sortMethod) + { + sortMethod = _sortMethod; + return *this; + } }; - static std::size_t getCount(Session& session); - static pointer find(Session& session, std::string_view name, TrackListType type, UserId userId); - static pointer find(Session& session, TrackListId tracklistId); - static RangeResults find(Session& session, const FindParameters& params); - static void find(Session& session, const FindParameters& params, const std::function& func); + static std::size_t getCount(Session& session); + static pointer find(Session& session, std::string_view name, TrackListType type, UserId userId); + static pointer find(Session& session, TrackListId tracklistId); + static RangeResults find(Session& session, const FindParameters& params); + static void find(Session& session, const FindParameters& params, const std::function& func); // Accessors - std::string_view getName() const { return _name; } - bool isPublic() const { return _isPublic; } - TrackListType getType() const { return _type; } - ObjectPtr getUser() const { return _user; } + std::string_view getName() const { return _name; } + bool isPublic() const { return _isPublic; } + TrackListType getType() const { return _type; } + ObjectPtr getUser() const { return _user; } // Modifiers - void setName(const std::string& name) { _name = name; } - void setIsPublic(bool isPublic) { _isPublic = isPublic; } - void clear() { _entries.clear(); } + void setName(const std::string& name) { _name = name; } + void setIsPublic(bool isPublic) { _isPublic = isPublic; } + void clear() { _entries.clear(); } // Get tracks, ordered by position - bool isEmpty() const; - std::size_t getCount() const; - ObjectPtr getEntry(std::size_t pos) const; - RangeResults> getEntries(std::optional range = {}) const; - ObjectPtr getEntryByTrackAndDateTime(ObjectPtr track, const Wt::WDateTime& dateTime) const; + bool isEmpty() const; + std::size_t getCount() const; + ObjectPtr getEntry(std::size_t pos) const; + RangeResults> getEntries(std::optional range = {}) const; + ObjectPtr getEntryByTrackAndDateTime(ObjectPtr track, const Wt::WDateTime& dateTime) const; - std::vector getTrackIds() const; - std::chrono::milliseconds getDuration() const; + std::vector getTrackIds() const; + std::chrono::milliseconds getDuration() const; - void setLastModifiedDateTime(const Wt::WDateTime& dateTime); + void setLastModifiedDateTime(const Wt::WDateTime& dateTime); // Get clusters, order by occurence std::vector> getClusters() const; @@ -123,13 +147,13 @@ namespace lms::db TrackList(std::string_view name, TrackListType type, bool isPublic, ObjectPtr user); static pointer create(Session& session, std::string_view name, TrackListType type, bool isPublic, ObjectPtr user); - std::string _name; - TrackListType _type{ TrackListType::Playlist }; - bool _isPublic{ false }; - Wt::WDateTime _creationDateTime; - Wt::WDateTime _lastModifiedDateTime; + std::string _name; + TrackListType _type{ TrackListType::Playlist }; + bool _isPublic{ false }; + Wt::WDateTime _creationDateTime; + Wt::WDateTime _lastModifiedDateTime; - Wt::Dbo::ptr _user; + Wt::Dbo::ptr _user; Wt::Dbo::collection> _entries; }; @@ -148,7 +172,7 @@ namespace lms::db static pointer getById(Session& session, TrackListEntryId id); // Accessors - ObjectPtr getTrack() const { return _track; } + ObjectPtr getTrack() const { return _track; } const Wt::WDateTime& getDateTime() const { return _dateTime; } template @@ -166,10 +190,9 @@ namespace lms::db TrackListEntry(ObjectPtr track, ObjectPtr tracklist); static pointer create(Session& session, ObjectPtr track, ObjectPtr tracklist, const Wt::WDateTime& dateTime = {}); - Wt::WDateTime _dateTime; // optional date time - Wt::Dbo::ptr _track; - Wt::Dbo::ptr _tracklist; + Wt::WDateTime _dateTime; // optional date time + Wt::Dbo::ptr _track; + Wt::Dbo::ptr _tracklist; }; } // namespace lms::db - diff --git a/src/libs/database/include/database/TransactionChecker.hpp b/src/libs/database/include/database/TransactionChecker.hpp index 58128e00d..fffeedb73 100644 --- a/src/libs/database/include/database/TransactionChecker.hpp +++ b/src/libs/database/include/database/TransactionChecker.hpp @@ -20,15 +20,14 @@ #pragma once #if !defined(NDEBUG) -#define LMS_CHECK_TRANSACTION_ACCESSES 1 + #define LMS_CHECK_TRANSACTION_ACCESSES 1 #else -#define LMS_CHECK_TRANSACTION_ACCESSES 0 + #define LMS_CHECK_TRANSACTION_ACCESSES 0 #endif #if LMS_CHECK_TRANSACTION_ACCESSES - -#include -#include + #include + #include namespace lms::db { @@ -58,6 +57,6 @@ namespace lms::db static void pushTransaction(TransactionType type, Wt::Dbo::Session& session); static void popTransaction(TransactionType type, Wt::Dbo::Session& session); }; -} +} // namespace lms::db #endif \ No newline at end of file diff --git a/src/libs/database/include/database/Types.hpp b/src/libs/database/include/database/Types.hpp index 17b6bd27b..4c3303132 100644 --- a/src/libs/database/include/database/Types.hpp +++ b/src/libs/database/include/database/Types.hpp @@ -19,9 +19,10 @@ #pragma once -#include #include +#include #include + #include namespace lms::db @@ -41,7 +42,7 @@ namespace lms::db }; // Func must return true to continue iterating - template + template void foreachSubRange(Range range, std::size_t subRangeSize, Func&& func) { assert(subRangeSize > 0); @@ -57,7 +58,7 @@ namespace lms::db } } - template + template struct RangeResults { Range range; @@ -128,7 +129,8 @@ namespace lms::db Id, Name, ArtistNameThenName, - Date, + DateAsc, + DateDesc, OriginalDate, OriginalDateDesc, Random, @@ -152,13 +154,13 @@ namespace lms::db StarredDateDesc, Name, DateDescAndRelease, - Release, // order by disc/track number + Release, // order by disc/track number TrackList, // order by asc order in tracklist }; enum class TrackArtistLinkType { - Artist = 0, // regular track artist + Artist = 0, // regular track artist Arranger = 1, Composer = 2, Conductor = 3, @@ -227,8 +229,7 @@ namespace lms::db enum class TrackListType { - Playlist, // user controlled playlists - Internal, // internal usage (current playqueue, history, ...) + Playlist, // user controlled playlists + Internal, // internal usage (current playqueue, history, ...) }; -} - +} // namespace lms::db diff --git a/src/libs/database/include/database/User.hpp b/src/libs/database/include/database/User.hpp index aa5e00df5..d26a29b9b 100644 --- a/src/libs/database/include/database/User.hpp +++ b/src/libs/database/include/database/User.hpp @@ -47,43 +47,59 @@ namespace lms::db struct FindParameters { - std::optional scrobblingBackend; - std::optional feedbackBackend; - std::optional range; - - FindParameters& setFeedbackBackend(FeedbackBackend _feedbackBackend) { feedbackBackend = _feedbackBackend; return *this; } - FindParameters& setScrobblingBackend(ScrobblingBackend _scrobblingBackend) { scrobblingBackend = _scrobblingBackend; return *this; } - FindParameters& setRange(std::optional _range) { range = _range; return *this; } + std::optional scrobblingBackend; + std::optional feedbackBackend; + std::optional range; + + FindParameters& setFeedbackBackend(FeedbackBackend _feedbackBackend) + { + feedbackBackend = _feedbackBackend; + return *this; + } + FindParameters& setScrobblingBackend(ScrobblingBackend _scrobblingBackend) + { + scrobblingBackend = _scrobblingBackend; + return *this; + } + FindParameters& setRange(std::optional _range) + { + range = _range; + return *this; + } }; - static inline constexpr std::size_t MinNameLength{ 3 }; - static inline constexpr std::size_t MaxNameLength{ 15 }; - static inline constexpr bool defaultSubsonicEnableTranscodingByDefault{ false }; + static inline constexpr std::size_t MinNameLength{ 3 }; + static inline constexpr std::size_t MaxNameLength{ 15 }; + static inline constexpr bool defaultSubsonicEnableTranscodingByDefault{ false }; static inline constexpr TranscodingOutputFormat defaultSubsonicTranscodingOutputFormat{ TranscodingOutputFormat::OGG_OPUS }; - static inline constexpr Bitrate defaultSubsonicTranscodingOutputBitrate{ 128000 }; - static inline constexpr UITheme defaultUITheme{ UITheme::Dark }; - static inline constexpr SubsonicArtistListMode defaultSubsonicArtistListMode{ SubsonicArtistListMode::AllArtists }; - static inline constexpr ScrobblingBackend defaultScrobblingBackend{ ScrobblingBackend::Internal }; - static inline constexpr FeedbackBackend defaultFeedbackBackend{ FeedbackBackend::Internal }; + static inline constexpr Bitrate defaultSubsonicTranscodingOutputBitrate{ 128000 }; + static inline constexpr UITheme defaultUITheme{ UITheme::Dark }; + static inline constexpr SubsonicArtistListMode defaultSubsonicArtistListMode{ SubsonicArtistListMode::AllArtists }; + static inline constexpr ScrobblingBackend defaultScrobblingBackend{ ScrobblingBackend::Internal }; + static inline constexpr FeedbackBackend defaultFeedbackBackend{ FeedbackBackend::Internal }; User() = default; - static std::size_t getCount(Session& session); - static pointer find(Session& session, UserId id); - static pointer find(Session& session, std::string_view loginName); + static std::size_t getCount(Session& session); + static pointer find(Session& session, UserId id); + static pointer find(Session& session, std::string_view loginName); static RangeResults find(Session& session, const FindParameters& params); - static void find(Session& session, const FindParameters& params, const std::function& func); - static pointer findDemoUser(Session& session); + static void find(Session& session, const FindParameters& params, const std::function& func); + static pointer findDemoUser(Session& session); // accessors const std::string& getLoginName() const { return _loginName; } - PasswordHash getPasswordHash() const { return PasswordHash{ _passwordSalt, _passwordHash }; } + PasswordHash getPasswordHash() const { return PasswordHash{ _passwordSalt, _passwordHash }; } const Wt::WDateTime& getLastLogin() const { return _lastLogin; } - std::size_t getAuthTokensCount() const { return _authTokens.size(); } + std::size_t getAuthTokensCount() const { return _authTokens.size(); } // write void setLastLogin(const Wt::WDateTime& dateTime) { _lastLogin = dateTime; } - void setPasswordHash(const PasswordHash& passwordHash) { _passwordSalt = passwordHash.salt; _passwordHash = passwordHash.hash; } + void setPasswordHash(const PasswordHash& passwordHash) + { + _passwordSalt = passwordHash.salt; + _passwordHash = passwordHash.hash; + } void setType(UserType type) { _type = type; } void setSubsonicEnableTranscodingByDefault(bool value) { _subsonicEnableTranscodingByDefault = value; } void setSubsonicDefaultTranscodintOutputFormat(TranscodingOutputFormat encoding) { _subsonicDefaultTranscodingOutputFormat = encoding; } @@ -99,19 +115,19 @@ namespace lms::db void setListenBrainzToken(const std::optional& MBID) { _listenbrainzToken = MBID ? MBID->getAsString() : ""; } // read - bool isAdmin() const { return _type == UserType::ADMIN; } - bool isDemo() const { return _type == UserType::DEMO; } - UserType getType() const { return _type; } - bool getSubsonicEnableTranscodingByDefault() const { return _subsonicEnableTranscodingByDefault; } + bool isAdmin() const { return _type == UserType::ADMIN; } + bool isDemo() const { return _type == UserType::DEMO; } + UserType getType() const { return _type; } + bool getSubsonicEnableTranscodingByDefault() const { return _subsonicEnableTranscodingByDefault; } TranscodingOutputFormat getSubsonicDefaultTranscodingOutputFormat() const { return _subsonicDefaultTranscodingOutputFormat; } - Bitrate getSubsonicDefaultTranscodingOutputBitrate() const { return _subsonicDefaultTranscodingOutputBitrate; } - std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; } - bool isRepeatAllSet() const { return _repeatAll; } - bool isRadioSet() const { return _radio; } - UITheme getUITheme() const { return _uiTheme; } - SubsonicArtistListMode getSubsonicArtistListMode() const { return _subsonicArtistListMode; } - FeedbackBackend getFeedbackBackend() const { return _feedbackBackend; } - ScrobblingBackend getScrobblingBackend() const { return _scrobblingBackend; } + Bitrate getSubsonicDefaultTranscodingOutputBitrate() const { return _subsonicDefaultTranscodingOutputBitrate; } + std::size_t getCurPlayingTrackPos() const { return _curPlayingTrackPos; } + bool isRepeatAllSet() const { return _repeatAll; } + bool isRadioSet() const { return _radio; } + UITheme getUITheme() const { return _uiTheme; } + SubsonicArtistListMode getSubsonicArtistListMode() const { return _subsonicArtistListMode; } + FeedbackBackend getFeedbackBackend() const { return _feedbackBackend; } + ScrobblingBackend getScrobblingBackend() const { return _scrobblingBackend; } std::optional getListenBrainzToken() const { return core::UUID::fromString(_listenbrainzToken); } template @@ -144,30 +160,30 @@ namespace lms::db User(std::string_view loginName); static pointer create(Session& session, std::string_view loginName); - std::string _loginName; - std::string _passwordSalt; - std::string _passwordHash; - Wt::WDateTime _lastLogin; - UITheme _uiTheme{ defaultUITheme }; + std::string _loginName; + std::string _passwordSalt; + std::string _passwordHash; + Wt::WDateTime _lastLogin; + UITheme _uiTheme{ defaultUITheme }; FeedbackBackend _feedbackBackend{ defaultFeedbackBackend }; ScrobblingBackend _scrobblingBackend{ defaultScrobblingBackend }; - std::string _listenbrainzToken; // Musicbrainz Identifier + std::string _listenbrainzToken; // Musicbrainz Identifier // Admin defined settings - UserType _type{ UserType::REGULAR }; + UserType _type{ UserType::REGULAR }; // User defined settings - SubsonicArtistListMode _subsonicArtistListMode{ defaultSubsonicArtistListMode }; - bool _subsonicEnableTranscodingByDefault{ defaultSubsonicEnableTranscodingByDefault }; + SubsonicArtistListMode _subsonicArtistListMode{ defaultSubsonicArtistListMode }; + bool _subsonicEnableTranscodingByDefault{ defaultSubsonicEnableTranscodingByDefault }; TranscodingOutputFormat _subsonicDefaultTranscodingOutputFormat{ defaultSubsonicTranscodingOutputFormat }; - int _subsonicDefaultTranscodingOutputBitrate{ defaultSubsonicTranscodingOutputBitrate }; + int _subsonicDefaultTranscodingOutputBitrate{ defaultSubsonicTranscodingOutputBitrate }; // User's dynamic data (UI) - int _curPlayingTrackPos{}; // Current track position in queue - bool _repeatAll{}; - bool _radio{}; + int _curPlayingTrackPos{}; // Current track position in queue + bool _repeatAll{}; + bool _radio{}; Wt::Dbo::collection> _authTokens; }; -} // namespace Databas' +} // namespace lms::db diff --git a/src/libs/database/test/Artist.cpp b/src/libs/database/test/Artist.cpp index 49d3e729c..abef4914a 100644 --- a/src/libs/database/test/Artist.cpp +++ b/src/libs/database/test/Artist.cpp @@ -47,7 +47,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters {}) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}) }; ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist.getId()); @@ -59,7 +59,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::find(session, Artist::FindParameters {}) }; + auto artists{ Artist::find(session, Artist::FindParameters{}) }; ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front()->getId(), artist.getId()); } @@ -68,11 +68,10 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; bool visited{}; - Artist::find(session, Artist::FindParameters{}, [&](const Artist::pointer& a) - { - visited = true; - EXPECT_EQ(a->getId(), artist.getId()); - }); + Artist::find(session, Artist::FindParameters{}, [&](const Artist::pointer& a) { + visited = true; + EXPECT_EQ(a->getId(), artist.getId()); + }); EXPECT_TRUE(visited); } } @@ -104,10 +103,9 @@ namespace lms::db::tests ArtistId lastRetrievedId; std::vector visitedArtists; - Artist::find(session, lastRetrievedId, 10, [&](const Artist::pointer& artist) - { - visitedArtists.push_back(artist); - }); + Artist::find(session, lastRetrievedId, 10, [&](const Artist::pointer& artist) { + visitedArtists.push_back(artist); + }); ASSERT_EQ(visitedArtists.size(), 3); EXPECT_EQ(visitedArtists[0]->getId(), artist1.getId()); EXPECT_EQ(visitedArtists[1]->getId(), artist2.getId()); @@ -120,10 +118,9 @@ namespace lms::db::tests ArtistId lastRetrievedId{ artist1.getId() }; std::vector visitedArtists; - Artist::find(session, lastRetrievedId, 1, [&](const Artist::pointer& artist) - { - visitedArtists.push_back(artist); - }); + Artist::find(session, lastRetrievedId, 1, [&](const Artist::pointer& artist) { + visitedArtists.push_back(artist); + }); ASSERT_EQ(visitedArtists.size(), 1); EXPECT_EQ(visitedArtists[0]->getId(), artist2.getId()); EXPECT_EQ(lastRetrievedId, artist2.getId()); @@ -134,10 +131,9 @@ namespace lms::db::tests ArtistId lastRetrievedId{ artist1.getId() }; std::vector visitedArtists; - Artist::find(session, lastRetrievedId, 0, [&](const Artist::pointer& artist) - { - visitedArtists.push_back(artist); - }); + Artist::find(session, lastRetrievedId, 0, [&](const Artist::pointer& artist) { + visitedArtists.push_back(artist); + }); ASSERT_EQ(visitedArtists.size(), 0); EXPECT_EQ(lastRetrievedId, artist1.getId()); } @@ -147,10 +143,11 @@ namespace lms::db::tests ArtistId lastRetrievedId; std::vector visitedArtists; - Artist::find(session, lastRetrievedId, 10, [&](const Artist::pointer& artist) - { + Artist::find( + session, lastRetrievedId, 10, [&](const Artist::pointer& artist) { visitedArtists.push_back(artist); - }, otherLibrary.getId()); + }, + otherLibrary.getId()); ASSERT_EQ(visitedArtists.size(), 0); EXPECT_EQ(lastRetrievedId, ArtistId{}); } @@ -160,10 +157,11 @@ namespace lms::db::tests ArtistId lastRetrievedId; std::vector visitedArtists; - Artist::find(session, lastRetrievedId, 10, [&](const Artist::pointer& artist) - { + Artist::find( + session, lastRetrievedId, 10, [&](const Artist::pointer& artist) { visitedArtists.push_back(artist); - }, library.getId()); + }, + library.getId()); ASSERT_EQ(visitedArtists.size(), 1); EXPECT_EQ(visitedArtists[0]->getId(), artist2.getId()); EXPECT_EQ(lastRetrievedId, artist2.getId()); @@ -175,7 +173,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters {}) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}) }; ASSERT_EQ(artists.results.size(), 0); ASSERT_FALSE(artists.moreResults); ASSERT_EQ(artists.range.offset, 0); @@ -199,7 +197,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRange(Range{0,1})) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRange(Range{ 0, 1 })) }; ASSERT_EQ(artists.results.size(), 1); ASSERT_TRUE(artists.moreResults); ASSERT_EQ(artists.range.offset, 0); @@ -210,7 +208,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRange(Range{1,1})) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRange(Range{ 1, 1 })) }; ASSERT_EQ(artists.results.size(), 1); ASSERT_TRUE(artists.moreResults); ASSERT_EQ(artists.range.offset, 1); @@ -221,7 +219,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRange(Range{2,1})) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRange(Range{ 2, 1 })) }; ASSERT_EQ(artists.results.size(), 1); ASSERT_FALSE(artists.moreResults); ASSERT_EQ(artists.range.offset, 2); @@ -250,7 +248,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ track->getArtists({TrackArtistLinkType::Artist}) }; + auto artists{ track->getArtists({ TrackArtistLinkType::Artist }) }; ASSERT_EQ(artists.size(), 1); EXPECT_EQ(artists.front()->getId(), artist.getId()); @@ -267,7 +265,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ track->getArtistIds({TrackArtistLinkType::Artist}) }; + auto artists{ track->getArtistIds({ TrackArtistLinkType::Artist }) }; ASSERT_EQ(artists.size(), 1); EXPECT_EQ(artists.front(), artist.getId()); @@ -362,7 +360,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ track->getArtists({TrackArtistLinkType::Artist}) }; + auto artists{ track->getArtists({ TrackArtistLinkType::Artist }) }; ASSERT_EQ(artists.size(), 1); EXPECT_EQ(artists.front()->getId(), artist.getId()); @@ -372,7 +370,7 @@ namespace lms::db::tests EXPECT_EQ(track->getArtistLinks().size(), 3); - auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist.getId())) }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId())) }; EXPECT_EQ(tracks.results.size(), 1); tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist })); @@ -399,19 +397,17 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; std::vector visitedLinks; - TrackArtistLink::find(session, TrackArtistLink::FindParameters{}.setTrack(track.getId()), [&](const TrackArtistLink::pointer& link) - { - visitedLinks.push_back(link); - }); + TrackArtistLink::find(session, TrackArtistLink::FindParameters{}.setTrack(track.getId()), [&](const TrackArtistLink::pointer& link) { + visitedLinks.push_back(link); + }); ASSERT_EQ(visitedLinks.size(), 3); EXPECT_EQ(visitedLinks[0]->getArtist()->getId(), artist.getId()); EXPECT_EQ(visitedLinks[1]->getArtist()->getId(), artist.getId()); EXPECT_EQ(visitedLinks[2]->getArtist()->getId(), artist.getId()); - auto containsType = [&](TrackArtistLinkType type) - { - return std::any_of(std::cbegin(visitedLinks), std::cend(visitedLinks), [type](const TrackArtistLink::pointer& link) { return link->getType() == type;}); - }; + auto containsType = [&](TrackArtistLinkType type) { + return std::any_of(std::cbegin(visitedLinks), std::cend(visitedLinks), [type](const TrackArtistLink::pointer& link) { return link->getType() == type; }); + }; EXPECT_TRUE(containsType(TrackArtistLinkType::Artist)); EXPECT_TRUE(containsType(TrackArtistLinkType::ReleaseArtist)); @@ -441,10 +437,10 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ track->getArtists({TrackArtistLinkType::Artist}) }; + auto artists{ track->getArtists({ TrackArtistLinkType::Artist }) }; ASSERT_EQ(artists.size(), 2); EXPECT_TRUE((artists[0]->getId() == artist1.getId() && artists[1]->getId() == artist2.getId()) - || (artists[0]->getId() == artist2.getId() && artists[1]->getId() == artist1.getId())); + || (artists[0]->getId() == artist2.getId() && artists[1]->getId() == artist1.getId())); EXPECT_EQ(track->getArtists({}).size(), 2); EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 2); @@ -456,7 +452,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist1->getId())) }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId())) }; ASSERT_EQ(tracks.results.size(), 1); EXPECT_EQ(tracks.results.front(), track->getId()); @@ -481,10 +477,9 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; std::vector visitedLinks; - TrackArtistLink::find(session, TrackArtistLink::FindParameters{}.setTrack(track.getId()), [&](const TrackArtistLink::pointer& link) - { - visitedLinks.push_back(link); - }); + TrackArtistLink::find(session, TrackArtistLink::FindParameters{}.setTrack(track.getId()), [&](const TrackArtistLink::pointer& link) { + visitedLinks.push_back(link); + }); ASSERT_EQ(visitedLinks.size(), 2); EXPECT_EQ(visitedLinks[0]->getArtist()->getId(), artist1.getId()); EXPECT_EQ(visitedLinks[1]->getArtist()->getId(), artist2.getId()); @@ -494,10 +489,9 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; std::vector visitedLinks; - TrackArtistLink::find(session, TrackArtistLink::FindParameters{}.setArtist(artist2.getId()), [&](const TrackArtistLink::pointer& link) - { - visitedLinks.push_back(link); - }); + TrackArtistLink::find(session, TrackArtistLink::FindParameters{}.setArtist(artist2.getId()), [&](const TrackArtistLink::pointer& link) { + visitedLinks.push_back(link); + }); ASSERT_EQ(visitedLinks.size(), 1); EXPECT_EQ(visitedLinks[0]->getArtist()->getId(), artist2.getId()); EXPECT_EQ(visitedLinks[0]->getTrack()->getId(), track.getId()); @@ -507,10 +501,9 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; std::vector> visitedEntries; - TrackArtistLink::find(session, track.getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist) - { - visitedEntries.push_back(std::make_pair(link, artist)); - }); + TrackArtistLink::find(session, track.getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist) { + visitedEntries.push_back(std::make_pair(link, artist)); + }); ASSERT_EQ(visitedEntries.size(), 2); EXPECT_EQ(visitedEntries[0].first->getArtist()->getId(), artist1.getId()); EXPECT_EQ(visitedEntries[0].second->getId(), artist1.getId()); @@ -535,11 +528,11 @@ namespace lms::db::tests EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "N" })).results.size(), 0); - const auto artistsByAAA{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"A"})) }; + const auto artistsByAAA{ Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "A" })) }; ASSERT_EQ(artistsByAAA.results.size(), 1); EXPECT_EQ(artistsByAAA.results.front(), artist.getId()); - const auto artistsByZZZ{ Artist::Artist::findIds(session, Artist::FindParameters {}.setKeywords({"Z"})) }; + const auto artistsByZZZ{ Artist::Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "Z" })) }; ASSERT_EQ(artistsByZZZ.results.size(), 1); EXPECT_EQ(artistsByZZZ.results.front(), artist.getId()); @@ -583,26 +576,26 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; { - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"MyArtist"})) }; + const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "MyArtist" })) }; EXPECT_EQ(artists.results.size(), 6); } { - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"MyArtist%"}).setSortMethod(ArtistSortMethod::Name)) }; + const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "MyArtist%" }).setSortMethod(ArtistSortMethod::Name)) }; ASSERT_EQ(artists.results.size(), 2); EXPECT_EQ(artists.results[0], artist1.getId()); EXPECT_EQ(artists.results[1], artist4.getId()); } { - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"%MyArtist"}).setSortMethod(ArtistSortMethod::Name)) }; + const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "%MyArtist" }).setSortMethod(ArtistSortMethod::Name)) }; ASSERT_EQ(artists.results.size(), 2); EXPECT_EQ(artists.results[0], artist2.getId()); EXPECT_EQ(artists.results[1], artist5.getId()); } { - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setKeywords({"_MyArtist"}).setSortMethod(ArtistSortMethod::Name)) }; + const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "_MyArtist" }).setSortMethod(ArtistSortMethod::Name)) }; ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results[0], artist3.getId()); } @@ -624,8 +617,8 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto allArtistsByName{ Artist::findIds(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::Name)) }; - auto allArtistsBySortName{ Artist::findIds(session, Artist::FindParameters {}.setSortMethod(ArtistSortMethod::SortName)) }; + auto allArtistsByName{ Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::Name)) }; + auto allArtistsBySortName{ Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::SortName)) }; ASSERT_EQ(allArtistsByName.results.size(), 2); EXPECT_EQ(allArtistsByName.results.front(), artistA.getId()); @@ -647,7 +640,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters {}.setNonRelease(true).setArtist(artist->getId())) }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setNonRelease(true).setArtist(artist->getId())) }; EXPECT_EQ(tracks.results.size(), 0); } @@ -663,7 +656,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setArtist(artist.getId()).setNonRelease(true)) }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId()).setNonRelease(true)) }; ASSERT_EQ(tracks.results.size(), 1); EXPECT_EQ(tracks.results.front(), track2.getId()); } @@ -677,7 +670,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setRelease(release.getId())) }; + const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRelease(release.getId())) }; EXPECT_EQ(artists.results.size(), 0); } @@ -688,7 +681,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setRelease(release.getId())) }; + const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRelease(release.getId())) }; EXPECT_EQ(artists.results.size(), 0); } @@ -699,9 +692,9 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto artists{ Artist::findIds(session, Artist::FindParameters {}.setRelease(release.getId())) }; + const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRelease(release.getId())) }; ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist.getId()); } } -} +} // namespace lms::db::tests diff --git a/src/libs/database/test/CMakeLists.txt b/src/libs/database/test/CMakeLists.txt index 451581c85..f6cfe3bf6 100644 --- a/src/libs/database/test/CMakeLists.txt +++ b/src/libs/database/test/CMakeLists.txt @@ -4,6 +4,8 @@ add_executable(test-database Cluster.cpp Common.cpp DatabaseTest.cpp + Directory.cpp + Image.cpp Listen.cpp Migration.cpp Release.cpp diff --git a/src/libs/database/test/Cluster.cpp b/src/libs/database/test/Cluster.cpp index edb28da24..d6c5ac0eb 100644 --- a/src/libs/database/test/Cluster.cpp +++ b/src/libs/database/test/Cluster.cpp @@ -49,7 +49,7 @@ namespace lms::db::tests EXPECT_EQ(cluster->getType()->getId(), clusterType.getId()); { - const auto clusters{ Cluster::findIds(session, Cluster::FindParameters {}) }; + const auto clusters{ Cluster::findIds(session, Cluster::FindParameters{}) }; ASSERT_EQ(clusters.results.size(), 1); EXPECT_EQ(clusters.results.front(), cluster.getId()); } @@ -118,7 +118,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto clusters{ Cluster::findIds(session, Cluster::FindParameters {}.setTrack(track.getId())) }; + auto clusters{ Cluster::findIds(session, Cluster::FindParameters{}.setTrack(track.getId())) }; ASSERT_EQ(clusters.results.size(), 1); EXPECT_EQ(clusters.results.front(), cluster1.getId()); EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1); @@ -137,7 +137,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters {}.setClusters(std::initializer_list{cluster1.getId()})) }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setClusters(std::initializer_list{ cluster1.getId() })) }; ASSERT_EQ(tracks.results.size(), 1); EXPECT_EQ(tracks.results.front(), track.getId()); @@ -243,10 +243,9 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; bool visited{}; - ClusterType::find(session, [&](const ClusterType::pointer&) - { - visited = true; - }); + ClusterType::find(session, [&](const ClusterType::pointer&) { + visited = true; + }); EXPECT_FALSE(visited); } @@ -257,10 +256,9 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; std::vector visitedClusterTypes; - ClusterType::find(session, [&](const ClusterType::pointer& clusterType) - { - visitedClusterTypes.push_back(clusterType->getId()); - }); + ClusterType::find(session, [&](const ClusterType::pointer& clusterType) { + visitedClusterTypes.push_back(clusterType->getId()); + }); ASSERT_EQ(visitedClusterTypes.size(), 2); EXPECT_EQ(visitedClusterTypes[0], clusterType1->getId()); EXPECT_EQ(visitedClusterTypes[1], clusterType2->getId()); @@ -350,7 +348,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters(std::initializer_list{cluster.getId()})) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setClusters(std::initializer_list{ cluster.getId() })) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); } @@ -358,7 +356,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}.setClusters(std::initializer_list{unusedCluster.getId()})) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setClusters(std::initializer_list{ unusedCluster.getId() })) }; EXPECT_EQ(releases.results.size(), 0); } @@ -404,7 +402,7 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters(std::initializer_list{cluster1.getId()})) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setClusters(std::initializer_list{ cluster1.getId() })) }; ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist.getId()); @@ -417,7 +415,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters(std::initializer_list{cluster1.getId()})) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setClusters(std::initializer_list{ cluster1.getId() })) }; ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist.getId()); @@ -458,7 +456,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters(std::initializer_list{cluster.getId()})) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setClusters(std::initializer_list{ cluster.getId() })) }; ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist.getId()); } @@ -500,13 +498,12 @@ namespace lms::db::tests std::vector clusterIds; std::transform(std::cbegin(clusters), std::cend(clusters), std::back_inserter(clusterIds), [](const ScopedCluster& cluster) { return cluster.getId(); }); - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters(clusterIds)) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setClusters(clusterIds)) }; ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist.getId()); } } - TEST_F(DatabaseFixture, MultipleTracksSingleClusterSimilarity) { std::list tracks; @@ -526,7 +523,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}) }; + const auto similarTracks{ Track::findSimilarTrackIds(session, { tracks.front().getId() }) }; EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1); for (const TrackId similarTrackId : similarTracks.results) { @@ -567,14 +564,14 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; { - auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.back().getId()}, Range {0, 4}) }; + auto similarTracks{ Track::findSimilarTrackIds(session, { tracks.back().getId() }, Range{ 0, 4 }) }; EXPECT_EQ(similarTracks.results.size(), 4); for (const TrackId similarTrackId : similarTracks.results) EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 5), std::next(std::cend(tracks), -1), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks)); } { - auto similarTracks{ Track::findSimilarTrackIds(session, {tracks.front().getId()}) }; + auto similarTracks{ Track::findSimilarTrackIds(session, { tracks.front().getId() }) }; EXPECT_EQ(similarTracks.results.size(), tracks.size() - 1); for (const TrackId similarTrackId : similarTracks.results) EXPECT_TRUE(std::find_if(std::next(std::cbegin(tracks), 1), std::cend(tracks), [&](const auto& track) { return similarTrackId == track.getId(); }) != std::cend(tracks)); @@ -610,11 +607,11 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setClusters(std::initializer_list{cluster.getId()})) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setClusters(std::initializer_list{ cluster.getId() })) }; ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist.getId()); - auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) }; + auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); @@ -645,7 +642,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) }; + auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); @@ -790,24 +787,24 @@ namespace lms::db::tests } { - auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::Artist}) }; + auto artists{ artist1->findSimilarArtistIds({ TrackArtistLinkType::Artist }) }; ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist2.getId()); } { - auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::ReleaseArtist}) }; + auto artists{ artist1->findSimilarArtistIds({ TrackArtistLinkType::ReleaseArtist }) }; EXPECT_EQ(artists.results.size(), 0); } { - auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}) }; + auto artists{ artist1->findSimilarArtistIds({ TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }) }; ASSERT_EQ(artists.results.size(), 1); EXPECT_EQ(artists.results.front(), artist2.getId()); } { - auto artists{ artist1->findSimilarArtistIds({TrackArtistLinkType::Composer}) }; + auto artists{ artist1->findSimilarArtistIds({ TrackArtistLinkType::Composer }) }; EXPECT_EQ(artists.results.size(), 0); } @@ -878,4 +875,4 @@ namespace lms::db::tests } } } -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/Common.cpp b/src/libs/database/test/Common.cpp index 4ffe33d2e..f9b277e02 100644 --- a/src/libs/database/test/Common.cpp +++ b/src/libs/database/test/Common.cpp @@ -22,6 +22,7 @@ #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/Db.hpp" +#include "database/Image.hpp" #include "database/Listen.hpp" #include "database/MediaLibrary.hpp" #include "database/Release.hpp" @@ -80,6 +81,7 @@ namespace lms::db::tests EXPECT_EQ(Cluster::getCount(session), 0); EXPECT_EQ(ClusterType::getCount(session), 0); EXPECT_EQ(Listen::getCount(session), 0); + EXPECT_EQ(Image::getCount(session), 0); EXPECT_EQ(MediaLibrary::getCount(session), 0); EXPECT_EQ(Release::getCount(session), 0); EXPECT_EQ(StarredArtist::getCount(session), 0); @@ -109,13 +111,13 @@ namespace lms::db::tests results.moreResults = false; { - auto subRange{ results.getSubRange(Range {0, 0}) }; + auto subRange{ results.getSubRange(Range{ 0, 0 }) }; EXPECT_FALSE(subRange.moreResults); ASSERT_EQ(subRange.results.size(), 0); EXPECT_EQ(subRange.range, Range{}); } { - auto subRange{ results.getSubRange(Range {0, 1}) }; + auto subRange{ results.getSubRange(Range{ 0, 1 }) }; EXPECT_FALSE(subRange.moreResults); ASSERT_EQ(subRange.results.size(), 0); } @@ -130,28 +132,26 @@ namespace lms::db::tests std::vector expectedSubRanges; }; - TestCase testCases[] - { - {Range{0, 0}, 1, {}}, - {Range{1, 0}, 1, {}}, - {Range{1, 1}, 1, { Range{ 1,1 } }}, - {Range{1, 3}, 1, { Range{ 1,1 }, Range {2,1}, Range{3,1} }}, - {Range{0, 100}, 100, { Range{0,100} }}, - {Range{0, 50}, 100, { Range{0,50} }}, - {Range{100, 200}, 100, { Range{100,100}, Range{200,100} }}, - {Range{100, 101}, 100, { Range{100,100}, Range{200,1}}}, - {Range{1000, 10}, 100, { Range{1000,10} }}, - {Range{1, 100}, 50, { Range{1,50}, Range{51, 50} }}, + TestCase testCases[]{ + { Range{ 0, 0 }, 1, {} }, + { Range{ 1, 0 }, 1, {} }, + { Range{ 1, 1 }, 1, { Range{ 1, 1 } } }, + { Range{ 1, 3 }, 1, { Range{ 1, 1 }, Range{ 2, 1 }, Range{ 3, 1 } } }, + { Range{ 0, 100 }, 100, { Range{ 0, 100 } } }, + { Range{ 0, 50 }, 100, { Range{ 0, 50 } } }, + { Range{ 100, 200 }, 100, { Range{ 100, 100 }, Range{ 200, 100 } } }, + { Range{ 100, 101 }, 100, { Range{ 100, 100 }, Range{ 200, 1 } } }, + { Range{ 1000, 10 }, 100, { Range{ 1000, 10 } } }, + { Range{ 1, 100 }, 50, { Range{ 1, 50 }, Range{ 51, 50 } } }, }; for (const TestCase& test : testCases) { std::vector subRanges; - foreachSubRange(test.range, test.subRangeSize, [&](Range subRange) - { - subRanges.push_back(subRange); - return true; - }); + foreachSubRange(test.range, test.subRangeSize, [&](Range subRange) { + subRanges.push_back(subRange); + return true; + }); EXPECT_EQ(subRanges, test.expectedSubRanges) << ", test index = " << std::distance(std::cbegin(testCases), &test); } @@ -192,26 +192,26 @@ namespace lms::db::tests results.moreResults = false; { - auto subRange{ results.getSubRange(Range {0, 1}) }; + auto subRange{ results.getSubRange(Range{ 0, 1 }) }; EXPECT_TRUE(subRange.moreResults); ASSERT_EQ(subRange.results.size(), 1); EXPECT_EQ(subRange.results.front(), 5); } { - auto subRange{ results.getSubRange(Range {1, 1}) }; + auto subRange{ results.getSubRange(Range{ 1, 1 }) }; EXPECT_FALSE(subRange.moreResults); ASSERT_EQ(subRange.results.size(), 1); EXPECT_EQ(subRange.results.front(), 6); } { - auto subRange{ results.getSubRange(Range {0, 2}) }; + auto subRange{ results.getSubRange(Range{ 0, 2 }) }; EXPECT_FALSE(subRange.moreResults); ASSERT_EQ(subRange.results.size(), 2); EXPECT_EQ(subRange.results.front(), 5); EXPECT_EQ(subRange.results.back(), 6); } { - auto subRange{ results.getSubRange(Range {}) }; + auto subRange{ results.getSubRange(Range{}) }; EXPECT_FALSE(subRange.moreResults); ASSERT_EQ(subRange.results.size(), 2); EXPECT_EQ(subRange.results.front(), 5); @@ -220,7 +220,7 @@ namespace lms::db::tests } { - auto subRange{ results.getSubRange(Range {1, 0}) }; + auto subRange{ results.getSubRange(Range{ 1, 0 }) }; EXPECT_FALSE(subRange.moreResults); ASSERT_EQ(subRange.results.size(), 1); EXPECT_EQ(subRange.results.front(), 6); @@ -228,11 +228,11 @@ namespace lms::db::tests EXPECT_EQ(subRange.range, expectedRange); } { - auto subRange{ results.getSubRange(Range {3, 2}) }; + auto subRange{ results.getSubRange(Range{ 3, 2 }) }; EXPECT_FALSE(subRange.moreResults); ASSERT_EQ(subRange.results.size(), 0); const Range expectedRange{ 2, 0 }; EXPECT_EQ(subRange.range, expectedRange); } } -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/Common.hpp b/src/libs/database/test/Common.hpp index 0b9268cda..45f53db5c 100644 --- a/src/libs/database/test/Common.hpp +++ b/src/libs/database/test/Common.hpp @@ -42,14 +42,14 @@ namespace lms::db::tests { - template + template class [[nodiscard]] ScopedEntity { public: using IdType = typename T::IdType; - template - ScopedEntity(db::Session& session, Args&& ...args) + template + ScopedEntity(db::Session& session, Args&&... args) : _session{ session } { auto transaction{ _session.createWriteTransaction() }; @@ -113,7 +113,8 @@ namespace lms::db::tests class ScopedFileDeleter final { public: - ScopedFileDeleter(const std::filesystem::path& path) : _path{ path } {} + ScopedFileDeleter(const std::filesystem::path& path) + : _path{ path } {} ~ScopedFileDeleter() { std::filesystem::remove(_path); } private: @@ -155,4 +156,4 @@ namespace lms::db::tests public: db::Session session{ _tmpDb->getDb() }; }; -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/DatabaseTest.cpp b/src/libs/database/test/DatabaseTest.cpp index 7d2c48402..5454f718e 100644 --- a/src/libs/database/test/DatabaseTest.cpp +++ b/src/libs/database/test/DatabaseTest.cpp @@ -49,11 +49,11 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) }; + auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); - const auto releaseTracks{ Track::find(session, Track::FindParameters {}.setRelease(release.getId())) }; + const auto releaseTracks{ Track::find(session, Track::FindParameters{}.setRelease(release.getId())) }; EXPECT_EQ(releaseTracks.results.size(), nbTracks); } } @@ -74,7 +74,7 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; - auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId())) }; + auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); @@ -101,7 +101,7 @@ namespace lms::db::tests EXPECT_EQ(User::getCount(session), 1); } } -} +} // namespace lms::db::tests int main(int argc, char** argv) { diff --git a/src/libs/database/test/Directory.cpp b/src/libs/database/test/Directory.cpp new file mode 100644 index 000000000..da7152ed6 --- /dev/null +++ b/src/libs/database/test/Directory.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "Common.hpp" + +#include "database/Directory.hpp" + +namespace lms::db::tests +{ + using ScopedDirectory = ScopedEntity; + + TEST_F(DatabaseFixture, Directory) + { + ScopedDirectory directory{ session, "/path/to/dir/" }; + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Directory::getCount(session), 1); + + Directory::pointer dir{ Directory::find(session, directory.getId()) }; + ASSERT_NE(dir, Directory::pointer{}); + EXPECT_EQ(dir->getAbsolutePath(), "/path/to/dir"); + EXPECT_EQ(dir->getName(), "dir"); + } + + { + auto transaction{ session.createWriteTransaction() }; + + Directory::pointer dir{ Directory::find(session, directory.getId()) }; + ASSERT_NE(dir, Directory::pointer{}); + dir.modify()->setAbsolutePath("/path/to/another/dir2"); + } + + { + auto transaction{ session.createReadTransaction() }; + + Directory::pointer dir{ Directory::find(session, directory.getId()) }; + ASSERT_NE(dir, Directory::pointer{}); + EXPECT_EQ(dir->getAbsolutePath(), "/path/to/another/dir2"); + EXPECT_EQ(dir->getName(), "dir2"); + } + + { + auto transaction{ session.createWriteTransaction() }; + + Directory::pointer dir{ Directory::find(session, directory.getId()) }; + ASSERT_NE(dir, Directory::pointer{}); + dir.modify()->setAbsolutePath("/foo/"); + } + + { + auto transaction{ session.createReadTransaction() }; + + Directory::pointer dir{ Directory::find(session, directory.getId()) }; + ASSERT_NE(dir, Directory::pointer{}); + EXPECT_EQ(dir->getAbsolutePath(), "/foo"); + EXPECT_EQ(dir->getName(), "foo"); + } + + { + auto transaction{ session.createWriteTransaction() }; + + Directory::pointer dir{ Directory::find(session, directory.getId()) }; + ASSERT_NE(dir, Directory::pointer{}); + dir.modify()->setAbsolutePath("/"); + } + + { + auto transaction{ session.createReadTransaction() }; + + Directory::pointer dir{ Directory::find(session, directory.getId()) }; + ASSERT_NE(dir, Directory::pointer{}); + EXPECT_EQ(dir->getAbsolutePath(), "/"); + EXPECT_EQ(dir->getName(), ""); + } + + { + auto transaction{ session.createReadTransaction() }; + + Directory::pointer dir{ Directory::find(session, "/") }; + ASSERT_NE(dir, Directory::pointer{}); + EXPECT_EQ(dir->getId(), directory.getId()); + } + } + + TEST_F(DatabaseFixture, parent) + { + ScopedDirectory parent{ session, "/path/to/dir/" }; + ScopedDirectory child{ session, "/path/to/dir/child" }; + + { + auto transaction{ session.createReadTransaction() }; + + auto dir{ child->getParent() }; + EXPECT_EQ(dir, Directory::pointer{}); + } + + { + auto transaction{ session.createWriteTransaction() }; + + child.get().modify()->setParent(parent.lockAndGet()); + } + + { + auto transaction{ session.createReadTransaction() }; + + auto dir{ child->getParent() }; + ASSERT_NE(dir, Directory::pointer{}); + EXPECT_EQ(dir->getId(), parent.getId()); + } + } + + TEST_F(DatabaseFixture, Directory_orphaned) + { + ScopedDirectory parent{ session, "/path/to/dir/" }; + ScopedDirectory child{ session, "/path/to/dir/child" }; + + { + auto transaction{ session.createReadTransaction() }; + + const auto directories{ Directory::findOrphanIds(session).results }; + EXPECT_EQ(directories.size(), 2); + } + + { + auto transaction{ session.createWriteTransaction() }; + + child.get().modify()->setParent(parent.lockAndGet()); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto directories{ Directory::findOrphanIds(session).results }; + ASSERT_EQ(directories.size(), 1); + EXPECT_EQ(directories.front(), child.getId()); + } + } +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/Image.cpp b/src/libs/database/test/Image.cpp new file mode 100644 index 000000000..b32cdd5fb --- /dev/null +++ b/src/libs/database/test/Image.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "Common.hpp" + +#include "database/Directory.hpp" +#include "database/Image.hpp" + +namespace lms::db::tests +{ + using ScopedDirectory = ScopedEntity; + using ScopedImage = ScopedEntity; + + TEST_F(DatabaseFixture, Image) + { + ScopedImage image{ session, "/path/to/image" }; + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Image::getCount(session), 1); + + Image::pointer img{ Image::find(session, image.getId()) }; + ASSERT_NE(img, Image::pointer{}); + EXPECT_EQ(img->getAbsoluteFilePath(), "/path/to/image"); + EXPECT_EQ(img->getFileStem(), "image"); + EXPECT_EQ(img->getWidth(), 0); + EXPECT_EQ(img->getHeight(), 0); + EXPECT_EQ(img->getFileSize(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + + Image::pointer img{ Image::find(session, image.getId()) }; + ASSERT_NE(img, Image::pointer{}); + img.modify()->setAbsoluteFilePath("/path/to/another/image2"); + img.modify()->setWidth(640); + img.modify()->setHeight(480); + img.modify()->setFileSize(1024 * 1024); + } + + { + auto transaction{ session.createReadTransaction() }; + + Image::pointer img{ Image::find(session, image.getId()) }; + ASSERT_NE(img, Image::pointer{}); + EXPECT_EQ(img->getAbsoluteFilePath(), "/path/to/another/image2"); + EXPECT_EQ(img->getFileStem(), "image2"); + EXPECT_EQ(img->getWidth(), 640); + EXPECT_EQ(img->getHeight(), 480); + EXPECT_EQ(img->getFileSize(), 1024 * 1024); + } + + { + auto transaction{ session.createReadTransaction() }; + + Image::pointer img{ Image::find(session, "/path/to/another/image2") }; + ASSERT_NE(img, Image::pointer{}); + EXPECT_EQ(img->getId(), image->getId()); + } + } + + TEST_F(DatabaseFixture, Image_inDirectory) + { + ScopedImage image{ session, "/path/to/image" }; + ScopedDirectory directory{ session, "/path/to" }; + + { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(Image::find(session, Image::FindParameters{}.setDirectory(directory.getId())).results.size(), 0); + } + + { + auto transaction{ session.createWriteTransaction() }; + image.get().modify()->setDirectory(directory.get()); + } + + { + auto transaction{ session.createReadTransaction() }; + const auto results{ Image::find(session, Image::FindParameters{}.setDirectory(directory.getId())).results }; + ASSERT_EQ(results.size(), 1); + EXPECT_EQ(results.front()->getId(), image.getId()); + } + } +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/Listen.cpp b/src/libs/database/test/Listen.cpp index f50246d6d..7f299798b 100644 --- a/src/libs/database/test/Listen.cpp +++ b/src/libs/database/test/Listen.cpp @@ -17,9 +17,10 @@ * along with LMS. If not, see . */ -#include "Common.hpp" #include "database/Listen.hpp" +#include "Common.hpp" + namespace lms::db::tests { using ScopedListen = ScopedEntity; @@ -35,7 +36,7 @@ namespace lms::db::tests EXPECT_EQ(Listen::getCount(session), 0); } - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } } }; { auto transaction{ session.createReadTransaction() }; @@ -45,7 +46,7 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; - ScopedListen listen2{ session, user.get(), track.get(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{13, 0, 1}} }; + ScopedListen listen2{ session, user.get(), track.get(), ScrobblingBackend::Internal, Wt::WDateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 13, 0, 1 } } }; EXPECT_EQ(Listen::getCount(session), 2); } @@ -61,7 +62,7 @@ namespace lms::db::tests { ScopedTrack track{ session }; ScopedUser user{ session, "MyUser" }; - ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} }; + ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } } }; { auto transaction{ session.createReadTransaction() }; @@ -95,9 +96,9 @@ namespace lms::db::tests { ScopedTrack track{ session }; ScopedUser user{ session, "MyUser" }; - ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 3}} }; - ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 1}} }; - ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime {Wt::WDate{2000, 1, 2}, Wt::WTime{12, 0, 2}} }; + ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 3 } } }; + ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } } }; + ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, Wt::WDateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 2 } } }; { auto transaction{ session.createReadTransaction() }; @@ -114,8 +115,8 @@ namespace lms::db::tests { ScopedTrack track{ session }; ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime1{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; - const Wt::WDateTime dateTime2{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 2} }; + const Wt::WDateTime dateTime1{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; + const Wt::WDateTime dateTime2{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 2 } }; ASSERT_GT(dateTime2, dateTime1); ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; @@ -147,7 +148,7 @@ namespace lms::db::tests { ScopedTrack track1{ session }; ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime1{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime1{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; { @@ -230,7 +231,7 @@ namespace lms::db::tests ScopedArtist artist1{ session, "MyArtist1" }; ScopedTrack track2{ session }; ScopedArtist artist2{ session, "MyArtist2" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; { auto transaction{ session.createWriteTransaction() }; @@ -296,7 +297,7 @@ namespace lms::db::tests ScopedTrack track{ session }; ScopedUser user{ session, "MyUser" }; ScopedArtist artist{ session, "MyArtist" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; ScopedClusterType clusterType{ session, "MyType" }; ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; @@ -340,7 +341,7 @@ namespace lms::db::tests ScopedTrack track{ session }; ScopedArtist artist{ session, "MyArtist" }; ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime1{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime1{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; ScopedMediaLibrary library{ session }; ScopedMediaLibrary otherLibrary{ session }; @@ -405,7 +406,7 @@ namespace lms::db::tests { ScopedTrack track{ session }; ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedRelease release{ session, "MyRelease" }; { auto transaction{ session.createWriteTransaction() }; @@ -456,7 +457,7 @@ namespace lms::db::tests ScopedTrack track1{ session }; ScopedTrack track2{ session }; ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; ScopedRelease release1{ session, "MyRelease1" }; ScopedRelease release2{ session, "MyRelease2" }; @@ -515,7 +516,7 @@ namespace lms::db::tests { ScopedTrack track{ session }; ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; ScopedClusterType clusterType{ session, "MyType" }; ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; @@ -560,7 +561,7 @@ namespace lms::db::tests { ScopedTrack track{ session }; ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedRelease release{ session, "MyRelease" }; ScopedMediaLibrary library{ session }; ScopedMediaLibrary otherLibrary{ session }; @@ -617,7 +618,7 @@ namespace lms::db::tests { ScopedTrack track{ session }; ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; { auto transaction{ session.createReadTransaction() }; @@ -663,7 +664,7 @@ namespace lms::db::tests ScopedTrack track{ session }; ScopedUser user{ session, "MyUser" }; ScopedArtist artist{ session, "MyArtist" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; { auto transaction{ session.createReadTransaction() }; @@ -717,7 +718,7 @@ namespace lms::db::tests ScopedTrack track1{ session }; ScopedTrack track2{ session }; ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime }; { @@ -768,7 +769,7 @@ namespace lms::db::tests { ScopedTrack track{ session }; ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; ScopedClusterType clusterType{ session, "MyType" }; ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; @@ -807,7 +808,7 @@ namespace lms::db::tests { ScopedTrack track{ session }; ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedMediaLibrary library{ session }; ScopedMediaLibrary otherLibrary{ session }; @@ -881,7 +882,7 @@ namespace lms::db::tests EXPECT_EQ(artists.moreResults, false); } - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; { @@ -941,7 +942,7 @@ namespace lms::db::tests ScopedArtist artist1{ session, "MyArtist1" }; ScopedTrack track2{ session }; ScopedArtist artist2{ session, "MyArtist2" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; { auto transaction{ session.createWriteTransaction() }; @@ -1006,7 +1007,7 @@ namespace lms::db::tests ScopedTrack track{ session }; ScopedUser user{ session, "MyUser" }; ScopedArtist artist{ session, "MyArtist" }; - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; ScopedClusterType clusterType{ session, "MyType" }; ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; @@ -1045,7 +1046,6 @@ namespace lms::db::tests } } - TEST_F(DatabaseFixture, Listen_getRecentArtists_mediaLibrary) { ScopedTrack track{ session }; @@ -1060,7 +1060,7 @@ namespace lms::db::tests track.get().modify()->setMediaLibrary(library.get()); } - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; { @@ -1107,7 +1107,7 @@ namespace lms::db::tests ASSERT_EQ(releases.results.size(), 0); } - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; { @@ -1153,7 +1153,7 @@ namespace lms::db::tests EXPECT_FALSE(listen); } - const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime1{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; { @@ -1164,7 +1164,7 @@ namespace lms::db::tests EXPECT_EQ(listen->getDateTime(), dateTime1); } - const Wt::WDateTime dateTime2{ Wt::WDate {1999, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime2{ Wt::WDate{ 1999, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime2 }; { @@ -1175,7 +1175,7 @@ namespace lms::db::tests EXPECT_EQ(listen->getDateTime(), dateTime1); } - const Wt::WDateTime dateTime3{ Wt::WDate {2001, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime3{ Wt::WDate{ 2001, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime3 }; { @@ -1201,7 +1201,7 @@ namespace lms::db::tests track2.get().modify()->setRelease(release2.get()); } - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; { @@ -1287,7 +1287,7 @@ namespace lms::db::tests EXPECT_EQ(releases.results.size(), 0); } - const Wt::WDateTime dateTime{ Wt::WDate{2000, 1, 2}, Wt::WTime{12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; { @@ -1334,7 +1334,7 @@ namespace lms::db::tests track.get().modify()->setMediaLibrary(library.get()); } - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; { @@ -1377,7 +1377,7 @@ namespace lms::db::tests ASSERT_EQ(tracks.results.size(), 0); } - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; { @@ -1406,7 +1406,6 @@ namespace lms::db::tests } } - TEST_F(DatabaseFixture, Listen_getRecentTracks_mediaLibrary) { ScopedTrack track{ session }; @@ -1414,7 +1413,7 @@ namespace lms::db::tests ScopedMediaLibrary library{ session }; ScopedMediaLibrary otherLibrary{ session }; - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; { @@ -1458,7 +1457,7 @@ namespace lms::db::tests EXPECT_EQ(count, 0); } - const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime1{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; { @@ -1487,15 +1486,14 @@ namespace lms::db::tests ScopedUser user{ session, "MyUser" }; ScopedRelease release{ session, "MyRelease" }; - auto getReleaseListenCount{ [&] - { + auto getReleaseListenCount{ [&] { auto transaction{ session.createReadTransaction() }; return Listen::getCount(session, user->getId(), release.getId()); } }; EXPECT_EQ(getReleaseListenCount(), 0); - const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime1{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track1.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; EXPECT_EQ(getReleaseListenCount(), 0); @@ -1540,7 +1538,7 @@ namespace lms::db::tests EXPECT_FALSE(listen); } - const Wt::WDateTime dateTime1{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime1{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime1 }; { @@ -1551,7 +1549,7 @@ namespace lms::db::tests EXPECT_EQ(listen->getDateTime(), dateTime1); } - const Wt::WDateTime dateTime2{ Wt::WDate {1999, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime2{ Wt::WDate{ 1999, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen2{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime2 }; { @@ -1562,7 +1560,7 @@ namespace lms::db::tests EXPECT_EQ(listen->getDateTime(), dateTime1); } - const Wt::WDateTime dateTime3{ Wt::WDate {2001, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime3{ Wt::WDate{ 2001, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen3{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime3 }; { @@ -1580,7 +1578,7 @@ namespace lms::db::tests ScopedTrack track2{ session }; ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen1{ session, user.lockAndGet(), track2.lockAndGet(), ScrobblingBackend::Internal, dateTime }; { @@ -1646,7 +1644,7 @@ namespace lms::db::tests { ScopedTrack track{ session }; ScopedUser user{ session, "MyUser" }; - const Wt::WDateTime dateTime{ Wt::WDate {2000, 1, 2}, Wt::WTime {12,0, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } }; ScopedListen listen{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime }; ScopedClusterType clusterType{ session, "MyType" }; ScopedCluster cluster{ session, clusterType.lockAndGet(), "MyCluster" }; @@ -1680,4 +1678,4 @@ namespace lms::db::tests EXPECT_EQ(tracks.results[0], track.getId()); } } -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/Migration.cpp b/src/libs/database/test/Migration.cpp index 7afe38138..e44a268f2 100644 --- a/src/libs/database/test/Migration.cpp +++ b/src/libs/database/test/Migration.cpp @@ -18,8 +18,14 @@ */ #include "Common.hpp" + #include "core/String.hpp" #include "database/Db.hpp" +#include "database/Directory.hpp" +#include "database/Image.hpp" +#include "database/StarredArtist.hpp" +#include "database/StarredRelease.hpp" +#include "database/StarredTrack.hpp" namespace lms::db::tests { @@ -34,7 +40,7 @@ namespace lms::db::tests session.execute(statement); } } - } + } // namespace TEST(Database, migration) { @@ -264,7 +270,7 @@ CREATE INDEX starred_artist_user_scrobbler_idx ON starred_artist(user_id,scrobbl CREATE INDEX starred_release_user_scrobbler_idx ON starred_release(user_id,scrobbler); CREATE INDEX starred_track_user_scrobbler_idx ON starred_track(user_id,scrobbler);)" }; - const std::string_view createDummyData{R"( + const std::string_view createDummyData{ R"( -- Inserting artists INSERT INTO artist (version, name, sort_name, mbid) VALUES (1, 'Artist A', 'Artist A', 'mbid_artist_a'), @@ -303,7 +309,7 @@ VALUES INSERT INTO track_artist_link (version, type, name, track_id, artist_id) VALUES (1, 1, 'Artist A', 5, 1), -(2, 1, 'Artist B', 6, 2);)"}; +(2, 1, 'Artist B', 6, 2);)" }; Session session{ db }; @@ -319,5 +325,24 @@ VALUES // Now perform full migration db.getTLSSession().migrateSchemaIfNeeded(); + + // Now perform some dummy finds to ensure all fields are correctly mapped + { + auto transaction{ session.createReadTransaction() }; + + EXPECT_FALSE(Artist::find(session, ArtistId{})); + EXPECT_FALSE(Cluster::find(session, ClusterId{})); + EXPECT_FALSE(ClusterType::find(session, ClusterTypeId{})); + EXPECT_FALSE(Directory::find(session, DirectoryId{})); + EXPECT_FALSE(Image::find(session, ImageId{})); + EXPECT_FALSE(Listen::find(session, ListenId{})); + EXPECT_FALSE(Release::find(session, ReleaseId{})); + EXPECT_FALSE(StarredArtist::find(session, StarredArtistId{})); + EXPECT_FALSE(StarredRelease::find(session, StarredReleaseId{})); + EXPECT_FALSE(StarredTrack::find(session, StarredTrackId{})); + EXPECT_FALSE(Track::find(session, TrackId{})); + EXPECT_FALSE(TrackList::find(session, TrackListId{})); + EXPECT_FALSE(User::find(session, UserId{})); + } } -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/Release.cpp b/src/libs/database/test/Release.cpp index 8f78a71d4..9a12d4497 100644 --- a/src/libs/database/test/Release.cpp +++ b/src/libs/database/test/Release.cpp @@ -50,25 +50,24 @@ namespace lms::db::tests } { - const auto releases{ Release::findIds(session, Release::FindParameters {}) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); EXPECT_EQ(release->getDuration(), std::chrono::seconds{ 0 }); } { - const auto releases{ Release::find(session, Release::FindParameters {}) }; + const auto releases{ Release::find(session, Release::FindParameters{}) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front()->getId(), release.getId()); } { bool visited{}; - Release::find(session, Release::FindParameters{}, [&](const Release::pointer& r) - { - visited = true; - EXPECT_EQ(r->getId(), release.getId()); - }); + Release::find(session, Release::FindParameters{}, [&](const Release::pointer& r) { + visited = true; + EXPECT_EQ(r->getId(), release.getId()); + }); EXPECT_TRUE(visited); } } @@ -101,10 +100,9 @@ namespace lms::db::tests ReleaseId lastRetrievedId; std::vector visitedReleases; - Release::find(session, lastRetrievedId, 10, [&](const Release::pointer& release) - { - visitedReleases.push_back(release); - }); + Release::find(session, lastRetrievedId, 10, [&](const Release::pointer& release) { + visitedReleases.push_back(release); + }); ASSERT_EQ(visitedReleases.size(), 3); EXPECT_EQ(visitedReleases[0]->getId(), release1.getId()); EXPECT_EQ(visitedReleases[1]->getId(), release2.getId()); @@ -117,10 +115,9 @@ namespace lms::db::tests ReleaseId lastRetrievedId{ release1.getId() }; std::vector visitedReleases; - Release::find(session, lastRetrievedId, 1, [&](const Release::pointer& release) - { - visitedReleases.push_back(release); - }); + Release::find(session, lastRetrievedId, 1, [&](const Release::pointer& release) { + visitedReleases.push_back(release); + }); ASSERT_EQ(visitedReleases.size(), 1); EXPECT_EQ(visitedReleases[0]->getId(), release2.getId()); EXPECT_EQ(lastRetrievedId, release2.getId()); @@ -131,10 +128,9 @@ namespace lms::db::tests ReleaseId lastRetrievedId{ release1.getId() }; std::vector visitedReleases; - Release::find(session, lastRetrievedId, 0, [&](const Release::pointer& release) - { - visitedReleases.push_back(release); - }); + Release::find(session, lastRetrievedId, 0, [&](const Release::pointer& release) { + visitedReleases.push_back(release); + }); ASSERT_EQ(visitedReleases.size(), 0); EXPECT_EQ(lastRetrievedId, release1.getId()); } @@ -144,10 +140,11 @@ namespace lms::db::tests ReleaseId lastRetrievedId; std::vector visitedReleases; - Release::find(session, lastRetrievedId, 10, [&](const Release::pointer& release) - { + Release::find( + session, lastRetrievedId, 10, [&](const Release::pointer& release) { visitedReleases.push_back(release); - }, otherLibrary.getId()); + }, + otherLibrary.getId()); ASSERT_EQ(visitedReleases.size(), 0); EXPECT_EQ(lastRetrievedId, ReleaseId{}); } @@ -157,10 +154,11 @@ namespace lms::db::tests ReleaseId lastRetrievedId; std::vector visitedReleases; - Release::find(session, lastRetrievedId, 10, [&](const Release::pointer& release) - { + Release::find( + session, lastRetrievedId, 10, [&](const Release::pointer& release) { visitedReleases.push_back(release); - }, library.getId()); + }, + library.getId()); ASSERT_EQ(visitedReleases.size(), 1); EXPECT_EQ(visitedReleases[0]->getId(), release2.getId()); EXPECT_EQ(lastRetrievedId, release2.getId()); @@ -185,7 +183,7 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0); - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setRelease(release.getId())) }; ASSERT_EQ(tracks.results.size(), 1); EXPECT_EQ(tracks.results.front(), track.getId()); } @@ -218,7 +216,7 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release.getId())) }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setRelease(release.getId())) }; EXPECT_EQ(tracks.results.size(), 0); auto releases{ Release::findOrphanIds(session) }; @@ -318,34 +316,34 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; { - const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"Release"})) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setKeywords({ "Release" })) }; EXPECT_EQ(releases.results.size(), 6); } { - const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"MyRelease"})) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setKeywords({ "MyRelease" })) }; ASSERT_EQ(releases.results.size(), 5); EXPECT_TRUE(std::none_of(std::cbegin(releases.results), std::cend(releases.results), [&](const ReleaseId releaseId) { return releaseId == release6.getId(); })); } { - const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"MyRelease%"})) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setKeywords({ "MyRelease%" })) }; ASSERT_EQ(releases.results.size(), 2); EXPECT_EQ(releases.results[0], release2.getId()); EXPECT_EQ(releases.results[1], release4.getId()); } { - const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"%MyRelease"})) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setKeywords({ "%MyRelease" })) }; ASSERT_EQ(releases.results.size(), 2); EXPECT_EQ(releases.results[0], release3.getId()); EXPECT_EQ(releases.results[1], release5.getId()); } { - const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"Foo%MyRelease"})) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setKeywords({ "Foo%MyRelease" })) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results[0], release5.getId()); } { - const auto releases{ Release::findIds(session, Release::FindParameters {}.setKeywords({"MyRelease%Foo"})) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setKeywords({ "MyRelease%Foo" })) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results[0], release4.getId()); } @@ -476,13 +474,13 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; { - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release1.getId()).setSortMethod(TrackSortMethod::Release)) }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setRelease(release1.getId()).setSortMethod(TrackSortMethod::Release)) }; EXPECT_EQ(tracks.results.size(), 2); EXPECT_EQ(tracks.results.front(), track1A.getId()); } { - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setRelease(release2.getId()).setSortMethod(TrackSortMethod::Release)) }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setRelease(release2.getId()).setSortMethod(TrackSortMethod::Release)) }; EXPECT_EQ(tracks.results.size(), 2); EXPECT_EQ(tracks.results.front(), track2B.getId()); } @@ -493,8 +491,8 @@ namespace lms::db::tests { ScopedRelease release1{ session, "MyRelease1" }; ScopedRelease release2{ session, "MyRelease2" }; - const Wt::WDate release1Date{ Wt::WDate {1994, 2, 3} }; - const Wt::WDate release1OriginalDate{ Wt::WDate {1993, 4, 5} }; + const Wt::WDate release1Date{ Wt::WDate{ 1994, 2, 3 } }; + const Wt::WDate release1OriginalDate{ Wt::WDate{ 1993, 4, 5 } }; ScopedTrack track1A{ session }; ScopedTrack track1B{ session }; @@ -504,7 +502,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}.setDateRange(DateRange::fromYearRange(0, 3000))) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setDateRange(DateRange::fromYearRange(0, 3000))) }; EXPECT_EQ(releases.results.size(), 0); } @@ -528,7 +526,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto releases{ Release::findIds(session, Release::FindParameters {}.setDateRange(DateRange::fromYearRange(1950, 2000))) }; + auto releases{ Release::findIds(session, Release::FindParameters{}.setDateRange(DateRange::fromYearRange(1950, 2000))) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release1.getId()); @@ -556,7 +554,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}.setDateRange(DateRange::fromYearRange(0, 3000))) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setDateRange(DateRange::fromYearRange(0, 3000))) }; EXPECT_EQ(releases.results.size(), 0); } @@ -568,7 +566,6 @@ namespace lms::db::tests track2A.get().modify()->setRelease(release2.get()); track2B.get().modify()->setRelease(release2.get()); - track1A.get().modify()->setYear(release1Year); track1B.get().modify()->setYear(release1Year); track1A.get().modify()->setOriginalYear(release1OriginalYear); @@ -581,7 +578,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto releases{ Release::findIds(session, Release::FindParameters {}.setDateRange(DateRange::fromYearRange(1950, 2000))) }; + auto releases{ Release::findIds(session, Release::FindParameters{}.setDateRange(DateRange::fromYearRange(1950, 2000))) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release1.getId()); @@ -599,7 +596,7 @@ namespace lms::db::tests ScopedRelease release{ session, "MyRelease" }; ScopedTrack track{ session }; - const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 1}, Wt::WTime {12, 30, 20} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 1950, 1, 1 }, Wt::WTime{ 12, 30, 20 } }; { auto transaction{ session.createWriteTransaction() }; @@ -609,19 +606,19 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}) }; EXPECT_EQ(releases.results.size(), 1); } { auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}.setWrittenAfter(dateTime.addSecs(-1))) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setWrittenAfter(dateTime.addSecs(-1))) }; EXPECT_EQ(releases.results.size(), 1); } { auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}.setWrittenAfter(dateTime.addSecs(+1))) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setWrittenAfter(dateTime.addSecs(+1))) }; EXPECT_EQ(releases.results.size(), 0); } } @@ -640,24 +637,38 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Artist})) }; + auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })) }; EXPECT_EQ(releases.results.size(), 0); + EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })), 0); + EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId())), 0); releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId(), { TrackArtistLinkType::Artist })); EXPECT_EQ(releases.results.size(), 0); + EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist2.getId(), { TrackArtistLinkType::Artist })), 0); + EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist2.getId())), 0); } { auto transaction{ session.createWriteTransaction() }; TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist); + TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Producer); + } + + { + auto transaction{ session.createReadTransaction() }; - auto releases{ Release::findIds(session, Release::FindParameters {}.setArtist(artist.getId(), {TrackArtistLinkType::Artist})) }; + EXPECT_EQ(Release::getCount(session, Release::FindParameters{}), 1); + + auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })) }; ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); + EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })), 1); + EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Remixer })), 0); releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist, TrackArtistLinkType::Mixer })); EXPECT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); + EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist, TrackArtistLinkType::Mixer })), 1); releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId(), { TrackArtistLinkType::Artist })); EXPECT_EQ(releases.results.size(), 0); @@ -672,6 +683,7 @@ namespace lms::db::tests releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())); ASSERT_EQ(releases.results.size(), 1); EXPECT_EQ(releases.results.front(), release.getId()); + EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId())), 1); releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer })); EXPECT_EQ(releases.results.size(), 0); @@ -810,11 +822,11 @@ namespace lms::db::tests TEST_F(DatabaseFixture, Release_sortMethod) { ScopedRelease release1{ session, "MyRelease1" }; - const Wt::WDate release1Date{ Wt::WDate {2000, 2, 3} }; - const Wt::WDate release1OriginalDate{ Wt::WDate {1993, 4, 5} }; + const Wt::WDate release1Date{ Wt::WDate{ 2000, 2, 3 } }; + const Wt::WDate release1OriginalDate{ Wt::WDate{ 1993, 4, 5 } }; ScopedRelease release2{ session, "MyRelease2" }; - const Wt::WDate release2Date{ Wt::WDate {1994, 2, 3} }; + const Wt::WDate release2Date{ Wt::WDate{ 1994, 2, 3 } }; ScopedTrack track1{ session }; ScopedTrack track2{ session }; @@ -836,7 +848,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::Name)) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setSortMethod(ReleaseSortMethod::Name)) }; ASSERT_EQ(releases.results.size(), 2); EXPECT_EQ(releases.results.front(), release1.getId()); EXPECT_EQ(releases.results.back(), release2.getId()); @@ -845,14 +857,14 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::Random)) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setSortMethod(ReleaseSortMethod::Random)) }; ASSERT_EQ(releases.results.size(), 2); } { auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::Date)) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setSortMethod(ReleaseSortMethod::DateAsc)) }; ASSERT_EQ(releases.results.size(), 2); EXPECT_EQ(releases.results.front(), release2.getId()); EXPECT_EQ(releases.results.back(), release1.getId()); @@ -861,7 +873,16 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::OriginalDate)) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setSortMethod(ReleaseSortMethod::DateDesc)) }; + ASSERT_EQ(releases.results.size(), 2); + EXPECT_EQ(releases.results.front(), release1.getId()); + EXPECT_EQ(releases.results.back(), release2.getId()); + } + + { + auto transaction{ session.createReadTransaction() }; + + const auto releases{ Release::findIds(session, Release::FindParameters{}.setSortMethod(ReleaseSortMethod::OriginalDate)) }; ASSERT_EQ(releases.results.size(), 2); EXPECT_EQ(releases.results.front(), release1.getId()); EXPECT_EQ(releases.results.back(), release2.getId()); @@ -869,7 +890,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto releases{ Release::findIds(session, Release::FindParameters {}.setSortMethod(ReleaseSortMethod::OriginalDateDesc)) }; + const auto releases{ Release::findIds(session, Release::FindParameters{}.setSortMethod(ReleaseSortMethod::OriginalDateDesc)) }; ASSERT_EQ(releases.results.size(), 2); EXPECT_EQ(releases.results.front(), release2.getId()); EXPECT_EQ(releases.results.back(), release1.getId()); @@ -883,11 +904,10 @@ namespace lms::db::tests ScopedTrack track2{ session }; ScopedTrack track3{ session }; - auto checkExpectedBitrate = [&](std::size_t bitrate) - { - auto transaction{ session.createReadTransaction() }; - EXPECT_EQ(release1->getMeanBitrate(), bitrate); - }; + auto checkExpectedBitrate = [&](std::size_t bitrate) { + auto transaction{ session.createReadTransaction() }; + EXPECT_EQ(release1->getMeanBitrate(), bitrate); + }; checkExpectedBitrate(0); @@ -938,4 +958,4 @@ namespace lms::db::tests EXPECT_EQ(release3->getTrackCount(), 0); } } -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/StarredArtist.cpp b/src/libs/database/test/StarredArtist.cpp index ebd54357b..dac890562 100644 --- a/src/libs/database/test/StarredArtist.cpp +++ b/src/libs/database/test/StarredArtist.cpp @@ -17,9 +17,10 @@ * along with LMS. If not, see . */ -#include "Common.hpp" #include "database/StarredArtist.hpp" +#include "Common.hpp" + namespace lms::db::tests { using ScopedStarredArtist = ScopedEntity; @@ -37,7 +38,7 @@ namespace lms::db::tests EXPECT_FALSE(starredArtist); EXPECT_EQ(StarredArtist::getCount(session), 0); - auto artists{ Artist::findIds(session, Artist::FindParameters {}) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}) }; EXPECT_EQ(artists.results.size(), 1); } @@ -53,7 +54,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters {}) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}) }; EXPECT_EQ(artists.results.size(), 1); artists = Artist::findIds(session, Artist::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); @@ -96,7 +97,7 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; EXPECT_EQ(artists.results.size(), 1); starredArtist.get().modify()->setSyncState(SyncState::PendingRemove); @@ -114,12 +115,12 @@ namespace lms::db::tests ScopedStarredArtist starredArtist1{ session, artist1.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; ScopedStarredArtist starredArtist2{ session, artist2.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 2}, Wt::WTime {12, 30, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 1950, 1, 2 }, Wt::WTime{ 12, 30, 1 } }; { auto transaction{ session.createReadTransaction() }; - auto artists{ Artist::find(session, Artist::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; + auto artists{ Artist::find(session, Artist::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; EXPECT_EQ(artists.results.size(), 2); } @@ -129,7 +130,7 @@ namespace lms::db::tests starredArtist1.get().modify()->setDateTime(dateTime); starredArtist2.get().modify()->setDateTime(dateTime.addSecs(-1)); - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ArtistSortMethod::StarredDateDesc)) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ArtistSortMethod::StarredDateDesc)) }; ASSERT_EQ(artists.results.size(), 2); EXPECT_EQ(artists.results[0], starredArtist1->getArtist()->getId()); EXPECT_EQ(artists.results[1], starredArtist2->getArtist()->getId()); @@ -140,10 +141,10 @@ namespace lms::db::tests starredArtist1.get().modify()->setDateTime(dateTime); starredArtist2.get().modify()->setDateTime(dateTime.addSecs(1)); - auto artists{ Artist::findIds(session, Artist::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ArtistSortMethod::StarredDateDesc)) }; + auto artists{ Artist::findIds(session, Artist::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ArtistSortMethod::StarredDateDesc)) }; ASSERT_EQ(artists.results.size(), 2); EXPECT_EQ(artists.results[0], starredArtist2->getArtist()->getId()); EXPECT_EQ(artists.results[1], starredArtist1->getArtist()->getId()); } } -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/StarredRelease.cpp b/src/libs/database/test/StarredRelease.cpp index bc88509a3..6fc161a4f 100644 --- a/src/libs/database/test/StarredRelease.cpp +++ b/src/libs/database/test/StarredRelease.cpp @@ -17,9 +17,10 @@ * along with LMS. If not, see . */ -#include "Common.hpp" #include "database/StarredRelease.hpp" +#include "Common.hpp" + namespace lms::db::tests { using ScopedStarredRelease = ScopedEntity; @@ -37,7 +38,7 @@ namespace lms::db::tests EXPECT_FALSE(starredRelease); EXPECT_EQ(StarredRelease::getCount(session), 0); - auto releases{ Release::find(session, Release::FindParameters {}) }; + auto releases{ Release::find(session, Release::FindParameters{}) }; EXPECT_EQ(releases.results.size(), 1); } @@ -53,7 +54,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto releases{ Release::find(session, Release::FindParameters {}) }; + auto releases{ Release::find(session, Release::FindParameters{}) }; EXPECT_EQ(releases.results.size(), 1); releases = Release::find(session, Release::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); @@ -85,7 +86,7 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; - auto releases{ Release::find(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; + auto releases{ Release::find(session, Release::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; EXPECT_EQ(releases.results.size(), 1); starredRelease.get().modify()->setSyncState(SyncState::PendingRemove); @@ -103,12 +104,12 @@ namespace lms::db::tests ScopedStarredRelease starredRelease1{ session, release1.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; ScopedStarredRelease starredRelease2{ session, release2.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 2}, Wt::WTime {12, 30, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 1950, 1, 2 }, Wt::WTime{ 12, 30, 1 } }; { auto transaction{ session.createReadTransaction() }; - auto releases{ Release::findIds(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; + auto releases{ Release::findIds(session, Release::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; EXPECT_EQ(releases.results.size(), 2); } @@ -118,7 +119,7 @@ namespace lms::db::tests starredRelease1.get().modify()->setDateTime(dateTime); starredRelease2.get().modify()->setDateTime(dateTime.addSecs(-1)); - auto releases{ Release::findIds(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ReleaseSortMethod::StarredDateDesc)) }; + auto releases{ Release::findIds(session, Release::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ReleaseSortMethod::StarredDateDesc)) }; ASSERT_EQ(releases.results.size(), 2); EXPECT_EQ(releases.results[0], starredRelease1->getRelease()->getId()); EXPECT_EQ(releases.results[1], starredRelease2->getRelease()->getId()); @@ -129,10 +130,10 @@ namespace lms::db::tests starredRelease1.get().modify()->setDateTime(dateTime); starredRelease2.get().modify()->setDateTime(dateTime.addSecs(1)); - auto releases{ Release::findIds(session, Release::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ReleaseSortMethod::StarredDateDesc)) }; + auto releases{ Release::findIds(session, Release::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(ReleaseSortMethod::StarredDateDesc)) }; ASSERT_EQ(releases.results.size(), 2); EXPECT_EQ(releases.results[0], starredRelease2->getRelease()->getId()); EXPECT_EQ(releases.results[1], starredRelease1->getRelease()->getId()); } } -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/StarredTrack.cpp b/src/libs/database/test/StarredTrack.cpp index 8ef779979..03e560c3b 100644 --- a/src/libs/database/test/StarredTrack.cpp +++ b/src/libs/database/test/StarredTrack.cpp @@ -17,9 +17,10 @@ * along with LMS. If not, see . */ -#include "Common.hpp" #include "database/StarredTrack.hpp" +#include "Common.hpp" + namespace lms::db::tests { using ScopedStarredTrack = ScopedEntity; @@ -37,7 +38,7 @@ namespace lms::db::tests EXPECT_FALSE(starredTrack); EXPECT_EQ(StarredTrack::getCount(session), 0); - auto tracks{ Track::findIds(session, Track::FindParameters {}) }; + auto tracks{ Track::findIds(session, Track::FindParameters{}) }; EXPECT_EQ(tracks.results.size(), 1); } @@ -53,7 +54,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters {}) }; + auto tracks{ Track::findIds(session, Track::FindParameters{}) }; EXPECT_EQ(tracks.results.size(), 1); tracks = Track::findIds(session, Track::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)); @@ -85,7 +86,7 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; EXPECT_EQ(tracks.results.size(), 1); starredTrack.get().modify()->setSyncState(SyncState::PendingRemove); @@ -103,12 +104,12 @@ namespace lms::db::tests ScopedStarredTrack starredTrack1{ session, track1.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; ScopedStarredTrack starredTrack2{ session, track2.lockAndGet(), user.lockAndGet(), FeedbackBackend::Internal }; - const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 2}, Wt::WTime {12, 30, 1} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 1950, 1, 2 }, Wt::WTime{ 12, 30, 1 } }; { auto transaction{ session.createReadTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal)) }; EXPECT_EQ(tracks.results.size(), 2); } @@ -118,7 +119,7 @@ namespace lms::db::tests starredTrack1.get().modify()->setDateTime(dateTime); starredTrack2.get().modify()->setDateTime(dateTime.addSecs(-1)); - auto tracks{ Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(TrackSortMethod::StarredDateDesc)) }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(TrackSortMethod::StarredDateDesc)) }; ASSERT_EQ(tracks.results.size(), 2); EXPECT_EQ(tracks.results[0], starredTrack1->getTrack()->getId()); EXPECT_EQ(tracks.results[1], starredTrack2->getTrack()->getId()); @@ -129,10 +130,10 @@ namespace lms::db::tests starredTrack1.get().modify()->setDateTime(dateTime); starredTrack2.get().modify()->setDateTime(dateTime.addSecs(1)); - auto tracks{ Track::findIds(session, Track::FindParameters {}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(TrackSortMethod::StarredDateDesc)) }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setStarringUser(user.getId(), FeedbackBackend::Internal).setSortMethod(TrackSortMethod::StarredDateDesc)) }; ASSERT_EQ(tracks.results.size(), 2); EXPECT_EQ(tracks.results[0], starredTrack2->getTrack()->getId()); EXPECT_EQ(tracks.results[1], starredTrack1->getTrack()->getId()); } } -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/Track.cpp b/src/libs/database/test/Track.cpp index ec3a6b770..28bc730a4 100644 --- a/src/libs/database/test/Track.cpp +++ b/src/libs/database/test/Track.cpp @@ -34,7 +34,7 @@ namespace lms::db::tests { bool visited{}; - Track::find(session, Track::FindParameters{}, [&](const Track::pointer&) {visited = true;}); + Track::find(session, Track::FindParameters{}, [&](const Track::pointer&) { visited = true; }); EXPECT_FALSE(visited); } } @@ -53,11 +53,10 @@ namespace lms::db::tests { bool visited{}; - Track::find(session, Track::FindParameters{}, [&](const Track::pointer& t) - { - visited = true; - EXPECT_EQ(t->getId(), track.getId()); - }); + Track::find(session, Track::FindParameters{}, [&](const Track::pointer& t) { + visited = true; + EXPECT_EQ(t->getId(), track.getId()); + }); EXPECT_TRUE(visited); } } @@ -81,10 +80,9 @@ namespace lms::db::tests TrackId lastRetrievedTrackId; std::vector visitedTracks; - Track::find(session, lastRetrievedTrackId, 10, [&](const Track::pointer& track) - { - visitedTracks.push_back(track); - }); + Track::find(session, lastRetrievedTrackId, 10, [&](const Track::pointer& track) { + visitedTracks.push_back(track); + }); ASSERT_EQ(visitedTracks.size(), 3); EXPECT_EQ(visitedTracks[0]->getId(), track1.getId()); EXPECT_EQ(visitedTracks[1]->getId(), track2.getId()); @@ -97,10 +95,9 @@ namespace lms::db::tests TrackId lastRetrievedTrackId{ track1.getId() }; std::vector visitedTracks; - Track::find(session, lastRetrievedTrackId, 1, [&](const Track::pointer& track) - { - visitedTracks.push_back(track); - }); + Track::find(session, lastRetrievedTrackId, 1, [&](const Track::pointer& track) { + visitedTracks.push_back(track); + }); ASSERT_EQ(visitedTracks.size(), 1); EXPECT_EQ(visitedTracks[0]->getId(), track2.getId()); EXPECT_EQ(lastRetrievedTrackId, track2.getId()); @@ -111,10 +108,9 @@ namespace lms::db::tests TrackId lastRetrievedTrackId{ track1.getId() }; std::vector visitedTracks; - Track::find(session, lastRetrievedTrackId, 0, [&](const Track::pointer& track) - { - visitedTracks.push_back(track); - }); + Track::find(session, lastRetrievedTrackId, 0, [&](const Track::pointer& track) { + visitedTracks.push_back(track); + }); ASSERT_EQ(visitedTracks.size(), 0); EXPECT_EQ(lastRetrievedTrackId, track1.getId()); } @@ -124,10 +120,11 @@ namespace lms::db::tests TrackId lastRetrievedTrackId{}; std::vector visitedTracks; - Track::find(session, lastRetrievedTrackId, 10, [&](const Track::pointer& track) - { + Track::find( + session, lastRetrievedTrackId, 10, [&](const Track::pointer& track) { visitedTracks.push_back(track); - }, otherLibrary.getId()); + }, + otherLibrary.getId()); ASSERT_EQ(visitedTracks.size(), 0); EXPECT_EQ(lastRetrievedTrackId, TrackId{}); } @@ -137,10 +134,11 @@ namespace lms::db::tests TrackId lastRetrievedTrackId{}; std::vector visitedTracks; - Track::find(session, lastRetrievedTrackId, 10, [&](const Track::pointer& track) - { + Track::find( + session, lastRetrievedTrackId, 10, [&](const Track::pointer& track) { visitedTracks.push_back(track); - }, library.getId()); + }, + library.getId()); ASSERT_EQ(visitedTracks.size(), 1); EXPECT_EQ(visitedTracks[0]->getId(), track2.getId()); EXPECT_EQ(lastRetrievedTrackId, track2.getId()); @@ -235,22 +233,22 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; { - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"Track"})) }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setKeywords({ "Track" })) }; EXPECT_EQ(tracks.results.size(), 6); } { - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"MyTrack"})) }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setKeywords({ "MyTrack" })) }; EXPECT_EQ(tracks.results.size(), 5); EXPECT_TRUE(std::none_of(std::cbegin(tracks.results), std::cend(tracks.results), [&](const TrackId trackId) { return trackId == track6.getId(); })); } { - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"MyTrack%"})) }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setKeywords({ "MyTrack%" })) }; ASSERT_EQ(tracks.results.size(), 2); EXPECT_EQ(tracks.results[0], track2.getId()); EXPECT_EQ(tracks.results[1], track3.getId()); } { - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setKeywords({"%MyTrack"})) }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setKeywords({ "%MyTrack" })) }; ASSERT_EQ(tracks.results.size(), 2); EXPECT_EQ(tracks.results[0], track4.getId()); EXPECT_EQ(tracks.results[1], track5.getId()); @@ -300,7 +298,7 @@ namespace lms::db::tests { ScopedTrack track{ session }; - const Wt::WDateTime dateTime{ Wt::WDate {1950, 1, 1}, Wt::WTime {12, 30, 20} }; + const Wt::WDateTime dateTime{ Wt::WDate{ 1950, 1, 1 }, Wt::WTime{ 12, 30, 20 } }; { auto transaction{ session.createWriteTransaction() }; @@ -309,19 +307,19 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto tracks{ Track::findIds(session, Track::FindParameters {}) }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}) }; EXPECT_EQ(tracks.results.size(), 1); } { auto transaction{ session.createReadTransaction() }; - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setWrittenAfter(dateTime.addSecs(-1))) }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setWrittenAfter(dateTime.addSecs(-1))) }; EXPECT_EQ(tracks.results.size(), 1); } { auto transaction{ session.createReadTransaction() }; - const auto tracks{ Track::findIds(session, Track::FindParameters {}.setWrittenAfter(dateTime.addSecs(+1))) }; + const auto tracks{ Track::findIds(session, Track::FindParameters{}.setWrittenAfter(dateTime.addSecs(+1))) }; EXPECT_EQ(tracks.results.size(), 0); } } @@ -343,7 +341,6 @@ namespace lms::db::tests } } - TEST_F(DatabaseFixture, Track_audioProperties) { ScopedTrack track{ session }; @@ -366,4 +363,4 @@ namespace lms::db::tests EXPECT_EQ(track->getSampleRate(), 44100); } } -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/TrackBookmark.cpp b/src/libs/database/test/TrackBookmark.cpp index 91a533be5..2b30eb07c 100644 --- a/src/libs/database/test/TrackBookmark.cpp +++ b/src/libs/database/test/TrackBookmark.cpp @@ -64,4 +64,4 @@ namespace lms::db::tests EXPECT_EQ(userBookmark->getComment(), "MyComment"); } } -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/TrackFeatures.cpp b/src/libs/database/test/TrackFeatures.cpp index ed42ee296..cb7996849 100644 --- a/src/libs/database/test/TrackFeatures.cpp +++ b/src/libs/database/test/TrackFeatures.cpp @@ -46,4 +46,4 @@ namespace lms::db::tests EXPECT_EQ(allTrackFeatures.results.front(), trackFeatures.getId()); } } -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/TrackList.cpp b/src/libs/database/test/TrackList.cpp index 091e4cc3f..4fde7fa48 100644 --- a/src/libs/database/test/TrackList.cpp +++ b/src/libs/database/test/TrackList.cpp @@ -49,7 +49,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId())) }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setTrackList(trackList1.getId())) }; EXPECT_EQ(tracks.results.size(), 0); tracks = Track::findIds(session, Track::FindParameters{}.setTrackList(trackList2.getId())); @@ -65,7 +65,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto tracks{ Track::findIds(session, Track::FindParameters {}.setTrackList(trackList1.getId())) }; + auto tracks{ Track::findIds(session, Track::FindParameters{}.setTrackList(trackList1.getId())) }; ASSERT_EQ(tracks.results.size(), 1); EXPECT_EQ(tracks.results.front(), track.getId()); @@ -84,7 +84,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::Name)) }; + const auto trackLists{ TrackList::find(session, TrackList::FindParameters{}.setSortMethod(TrackListSortMethod::Name)) }; ASSERT_EQ(trackLists.results.size(), 2); EXPECT_EQ(trackLists.results[0], trackList1.getId()); EXPECT_EQ(trackLists.results[1], trackList2.getId()); @@ -93,14 +93,14 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; - trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,1} }); - trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,2} }); + trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate{ 1900, 1, 1 } }); + trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate{ 1900, 1, 2 } }); } { auto transaction{ session.createReadTransaction() }; - const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) }; + const auto trackLists{ TrackList::find(session, TrackList::FindParameters{}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) }; ASSERT_EQ(trackLists.results.size(), 2); EXPECT_EQ(trackLists.results[0], trackList2.getId()); EXPECT_EQ(trackLists.results[1], trackList1.getId()); @@ -109,14 +109,14 @@ namespace lms::db::tests { auto transaction{ session.createWriteTransaction() }; - trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,2} }); - trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate {1900,1,1} }); + trackList1.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate{ 1900, 1, 2 } }); + trackList2.get().modify()->setLastModifiedDateTime(Wt::WDateTime{ Wt::WDate{ 1900, 1, 1 } }); } { auto transaction{ session.createReadTransaction() }; - const auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) }; + const auto trackLists{ TrackList::find(session, TrackList::FindParameters{}.setSortMethod(TrackListSortMethod::LastModifiedDesc)) }; ASSERT_EQ(trackLists.results.size(), 2); EXPECT_EQ(trackLists.results[0], trackList1.getId()); EXPECT_EQ(trackLists.results[1], trackList2.getId()); @@ -170,10 +170,9 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; std::vector visitedTrackLists; - TrackList::find(session, TrackList::FindParameters{}, [&](const TrackList::pointer& trackList) - { - visitedTrackLists.push_back(trackList->getId()); - }); + TrackList::find(session, TrackList::FindParameters{}, [&](const TrackList::pointer& trackList) { + visitedTrackLists.push_back(trackList->getId()); + }); ASSERT_EQ(visitedTrackLists.size(), 2); EXPECT_EQ(visitedTrackLists[0], trackList1->getId()); EXPECT_EQ(visitedTrackLists[1], trackList2->getId()); @@ -182,10 +181,9 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; std::vector visitedTrackLists; - TrackList::find(session, TrackList::FindParameters{}.setMediaLibrary(library->getId()), [&](const TrackList::pointer& trackList) - { - visitedTrackLists.push_back(trackList->getId()); - }); + TrackList::find(session, TrackList::FindParameters{}.setMediaLibrary(library->getId()), [&](const TrackList::pointer& trackList) { + visitedTrackLists.push_back(trackList->getId()); + }); ASSERT_EQ(visitedTrackLists.size(), 1); EXPECT_EQ(visitedTrackLists[0], trackList2->getId()); } @@ -203,7 +201,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters(std::initializer_list{cluster.getId()})) }; + auto trackLists{ TrackList::find(session, TrackList::FindParameters{}.setClusters(std::initializer_list{ cluster.getId() })) }; EXPECT_EQ(trackLists.results.size(), 0); } @@ -217,7 +215,7 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto trackLists{ TrackList::find(session, TrackList::FindParameters {}.setClusters(std::initializer_list{cluster.getId()})) }; + auto trackLists{ TrackList::find(session, TrackList::FindParameters{}.setClusters(std::initializer_list{ cluster.getId() })) }; ASSERT_EQ(trackLists.results.size(), 1); EXPECT_EQ(trackLists.results.front(), trackList1.getId()); } @@ -257,9 +255,9 @@ namespace lms::db::tests { auto transaction{ session.createReadTransaction() }; - auto entries{ trackList.get()->getEntries(Range {1, 1}) }; + auto entries{ trackList.get()->getEntries(Range{ 1, 1 }) }; ASSERT_EQ(entries.results.size(), 1); EXPECT_EQ(entries.results[0]->getTrack()->getId(), track2.getId()); } } -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/database/test/User.cpp b/src/libs/database/test/User.cpp index 039e485c1..233c6379e 100644 --- a/src/libs/database/test/User.cpp +++ b/src/libs/database/test/User.cpp @@ -27,10 +27,9 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; bool visited{}; - User::find(session, User::FindParameters{}, [&](const User::pointer&) - { - visited = true; - }); + User::find(session, User::FindParameters{}, [&](const User::pointer&) { + visited = true; + }); EXPECT_FALSE(visited); } @@ -41,13 +40,12 @@ namespace lms::db::tests auto transaction{ session.createReadTransaction() }; std::vector visitedUsers; - User::find(session, User::FindParameters{}, [&](const User::pointer& user) - { - visitedUsers.push_back(user->getId()); - }); + User::find(session, User::FindParameters{}, [&](const User::pointer& user) { + visitedUsers.push_back(user->getId()); + }); EXPECT_EQ(visitedUsers.size(), 2); EXPECT_EQ(visitedUsers[0], user1->getId()); EXPECT_EQ(visitedUsers[1], user2->getId()); } } -} \ No newline at end of file +} // namespace lms::db::tests \ No newline at end of file diff --git a/src/libs/image/CMakeLists.txt b/src/libs/image/CMakeLists.txt index 586b038bf..5c0419430 100644 --- a/src/libs/image/CMakeLists.txt +++ b/src/libs/image/CMakeLists.txt @@ -25,6 +25,7 @@ if (${LMS_IMAGE_BACKEND} STREQUAL "stb") message(STATUS "Using stb (resize version ${STB_IMAGE_RESIZE_VERSION})") target_sources(lmsimage PRIVATE + impl/stb/Image.cpp impl/stb/JPEGImage.cpp impl/stb/RawImage.cpp ) @@ -36,6 +37,7 @@ elseif (${LMS_IMAGE_BACKEND} STREQUAL "graphicsmagick") message(STATUS "Using graphicsmagick") target_sources(lmsimage PRIVATE + impl/graphicsmagick/Image.cpp impl/graphicsmagick/JPEGImage.cpp impl/graphicsmagick/RawImage.cpp ) diff --git a/src/libs/image/impl/SvgImage.cpp b/src/libs/image/impl/SvgImage.cpp index 876369372..85eb62df4 100644 --- a/src/libs/image/impl/SvgImage.cpp +++ b/src/libs/image/impl/SvgImage.cpp @@ -20,6 +20,7 @@ #include "SvgImage.hpp" #include + #include "core/ITraceLogger.hpp" #include "image/Exception.hpp" @@ -50,4 +51,4 @@ namespace lms::image return std::make_unique(std::move(data)); } -} \ No newline at end of file +} // namespace lms::image \ No newline at end of file diff --git a/src/libs/image/impl/SvgImage.hpp b/src/libs/image/impl/SvgImage.hpp index ce54cb30e..5ae53aa33 100644 --- a/src/libs/image/impl/SvgImage.hpp +++ b/src/libs/image/impl/SvgImage.hpp @@ -30,7 +30,8 @@ namespace lms::image class SvgImage : public IEncodedImage { public: - SvgImage(std::vector&& data) : _data{ std::move(data) } {} + SvgImage(std::vector&& data) + : _data{ std::move(data) } {} const std::byte* getData() const { return &_data.front(); } std::size_t getDataSize() const { return _data.size(); } @@ -39,4 +40,4 @@ namespace lms::image private: const std::vector _data; }; -} \ No newline at end of file +} // namespace lms::image \ No newline at end of file diff --git a/src/libs/image/impl/graphicsmagick/Image.cpp b/src/libs/image/impl/graphicsmagick/Image.cpp new file mode 100644 index 000000000..44a597866 --- /dev/null +++ b/src/libs/image/impl/graphicsmagick/Image.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "image/Image.hpp" + +#include + +#include "RawImage.hpp" +#include "core/ILogger.hpp" +#include "core/ITraceLogger.hpp" + +namespace lms::image +{ + std::unique_ptr decodeImage(const std::byte* encodedData, std::size_t encodedDataSize) + { + LMS_SCOPED_TRACE_DETAILED("Image", "DecodeBuffer"); + return std::make_unique(encodedData, encodedDataSize); + } + + std::unique_ptr decodeImage(const std::filesystem::path& path) + { + LMS_SCOPED_TRACE_DETAILED("Image", "DecodeFile"); + return std::make_unique(path); + } + + void init(const std::filesystem::path& path) + { + Magick::InitializeMagick(path.string().c_str()); + + if (auto nbThreads{ MagickLib::GetMagickResourceLimit(MagickLib::ThreadsResource) }; nbThreads != 1) + LMS_LOG(COVER, WARNING, "Consider setting env var OMP_NUM_THREADS=1 to save resources"); + + if (!MagickLib::SetMagickResourceLimit(MagickLib::ThreadsResource, 1)) + LMS_LOG(COVER, ERROR, "Cannot set Magick thread resource limit to 1!"); + + if (!MagickLib::SetMagickResourceLimit(MagickLib::DiskResource, 0)) + LMS_LOG(COVER, ERROR, "Cannot set Magick disk resource limit to 0!"); + + LMS_LOG(COVER, INFO, "Magick threads resource limit = " << GetMagickResourceLimit(MagickLib::ThreadsResource)); + LMS_LOG(COVER, INFO, "Magick Disk resource limit = " << GetMagickResourceLimit(MagickLib::DiskResource)); + } + + std::span getSupportedFileExtensions() + { + static const std::array fileExtensions{ ".jpg", ".jpeg", ".png", ".bmp" }; + return fileExtensions; + } +} // namespace lms::image diff --git a/src/libs/image/impl/graphicsmagick/JPEGImage.cpp b/src/libs/image/impl/graphicsmagick/JPEGImage.cpp index 8465a0c2c..94738b20e 100644 --- a/src/libs/image/impl/graphicsmagick/JPEGImage.cpp +++ b/src/libs/image/impl/graphicsmagick/JPEGImage.cpp @@ -19,37 +19,39 @@ #include "JPEGImage.hpp" -#include "RawImage.hpp" -#include "image/Exception.hpp" #include "core/ILogger.hpp" +#include "core/ITraceLogger.hpp" +#include "image/Exception.hpp" + +#include "RawImage.hpp" namespace lms::image::GraphicsMagick { - JPEGImage::JPEGImage(const RawImage& rawImage, unsigned quality) - { - try - { - Magick::Image image {rawImage.getMagickImage()}; - image.magick("JPEG"); - image.quality(quality); - image.write(&_blob); - } - catch (Magick::Exception& e) - { - LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what()); - throw Exception {std::string {"Magick read error: "} + e.what()}; - } - } + JPEGImage::JPEGImage(const RawImage& rawImage, unsigned quality) + { + LMS_SCOPED_TRACE_DETAILED("Image", "WriteJPEG"); + + try + { + Magick::Image image{ rawImage.getMagickImage() }; + image.magick("JPEG"); + image.quality(quality); + image.write(&_blob); + } + catch (Magick::Exception& e) + { + LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what()); + throw Exception{ std::string{ "Magick read error: " } + e.what() }; + } + } - const std::byte* - JPEGImage::getData() const - { - return reinterpret_cast(_blob.data()); - } + const std::byte* JPEGImage::getData() const + { + return reinterpret_cast(_blob.data()); + } - std::size_t - JPEGImage::getDataSize() const - { - return _blob.length(); - } -} + std::size_t JPEGImage::getDataSize() const + { + return _blob.length(); + } +} // namespace lms::image::GraphicsMagick diff --git a/src/libs/image/impl/graphicsmagick/JPEGImage.hpp b/src/libs/image/impl/graphicsmagick/JPEGImage.hpp index 934d2ed5f..4d9386c04 100644 --- a/src/libs/image/impl/graphicsmagick/JPEGImage.hpp +++ b/src/libs/image/impl/graphicsmagick/JPEGImage.hpp @@ -25,17 +25,17 @@ namespace lms::image::GraphicsMagick { - class RawImage; - class JPEGImage : public IEncodedImage - { - public: - JPEGImage(const RawImage& rawImage, unsigned quality); + class RawImage; + class JPEGImage : public IEncodedImage + { + public: + JPEGImage(const RawImage& rawImage, unsigned quality); - private: - const std::byte* getData() const override; - std::size_t getDataSize() const override; - std::string_view getMimeType() const override { return "image/jpeg"; } + private: + const std::byte* getData() const override; + std::size_t getDataSize() const override; + std::string_view getMimeType() const override { return "image/jpeg"; } - Magick::Blob _blob; - }; -} + Magick::Blob _blob; + }; +} // namespace lms::image::GraphicsMagick diff --git a/src/libs/image/impl/graphicsmagick/RawImage.cpp b/src/libs/image/impl/graphicsmagick/RawImage.cpp index e0f74ebd7..f3550478a 100644 --- a/src/libs/image/impl/graphicsmagick/RawImage.cpp +++ b/src/libs/image/impl/graphicsmagick/RawImage.cpp @@ -19,116 +19,97 @@ #include "RawImage.hpp" +#include +#include + #include -#include "JPEGImage.hpp" -#include "image/Exception.hpp" #include "core/ILogger.hpp" +#include "core/ITraceLogger.hpp" +#include "image/Exception.hpp" -namespace lms::image -{ - std::unique_ptr decodeImage(const std::byte* encodedData, std::size_t encodedDataSize) - { - return std::make_unique(encodedData, encodedDataSize); - } - - std::unique_ptr decodeImage(const std::filesystem::path& path) - { - return std::make_unique(path); - } - - void - init(const std::filesystem::path& path) - { - Magick::InitializeMagick(path.string().c_str()); - - if (auto nbThreads {MagickLib::GetMagickResourceLimit(MagickLib::ThreadsResource)}; nbThreads != 1) - LMS_LOG(COVER, WARNING, "Consider setting env var OMP_NUM_THREADS=1 to save resources"); - - if (!MagickLib::SetMagickResourceLimit(MagickLib::ThreadsResource, 1)) - LMS_LOG(COVER, ERROR, "Cannot set Magick thread resource limit to 1!"); - - if (!MagickLib::SetMagickResourceLimit(MagickLib::DiskResource, 0)) - LMS_LOG(COVER, ERROR, "Cannot set Magick disk resource limit to 0!"); - - LMS_LOG(COVER, INFO, "Magick threads resource limit = " << GetMagickResourceLimit(MagickLib::ThreadsResource)); - LMS_LOG(COVER, INFO, "Magick Disk resource limit = " << GetMagickResourceLimit(MagickLib::DiskResource)); - } -} +#include "JPEGImage.hpp" namespace lms::image::GraphicsMagick { - -RawImage::RawImage(const std::byte* encodedData, std::size_t encodedDataSize) -{ - try - { - Magick::Blob blob {encodedData, encodedDataSize}; - _image.read(blob); - } - catch (Magick::WarningCoder& e) - { - LMS_LOG(COVER, WARNING, "Caught Magick WarningCoder: " << e.what()); - } - catch (Magick::Warning& e) - { - LMS_LOG(COVER, WARNING, "Caught Magick warning: " << e.what()); - throw Exception {std::string {"Magick read warning: "} + e.what()}; - } - catch (Magick::Exception& e) - { - LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what()); - throw Exception {std::string {"Magick read error: "} + e.what()}; - } -} - -RawImage::RawImage(const std::filesystem::path& p) -{ - try - { - _image.read(p.string().c_str()); - } - catch (Magick::WarningCoder& e) - { - LMS_LOG(COVER, WARNING, "Caught Magick WarningCoder: " << e.what()); - } - catch (Magick::Warning& e) - { - LMS_LOG(COVER, WARNING, "Caught Magick warning: " << e.what()); - throw Exception {std::string {"Magick read warning: "} + e.what()}; - } - catch (Magick::Exception& e) - { - LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what()); - throw Exception {std::string {"Magick read error: "} + e.what()}; - } -} - -void -RawImage::resize(ImageSize width) -{ - try - { - _image.resize(Magick::Geometry {static_cast(width), static_cast(width)}); - } - catch (Magick::Exception& e) - { - LMS_LOG(COVER, ERROR, "Caught Magick exception while resizing: " << e.what()); - throw Exception {std::string {"Magick resize error: "} + e.what()}; - } -} - -std::unique_ptr -RawImage::encodeToJPEG(unsigned quality) const -{ - return std::make_unique(*this, quality); -} - -Magick::Image -RawImage::getMagickImage() const -{ - return _image; -} + RawImage::RawImage(const std::byte* encodedData, std::size_t encodedDataSize) + { + try + { + Magick::Blob blob{ encodedData, encodedDataSize }; + _image.read(blob); + } + catch (Magick::WarningCoder& e) + { + LMS_LOG(COVER, WARNING, "Caught Magick WarningCoder: " << e.what()); + } + catch (Magick::Warning& e) + { + LMS_LOG(COVER, WARNING, "Caught Magick warning: " << e.what()); + throw Exception{ std::string{ "Magick read warning: " } + e.what() }; + } + catch (Magick::Exception& e) + { + LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what()); + throw Exception{ std::string{ "Magick read error: " } + e.what() }; + } + } + + RawImage::RawImage(const std::filesystem::path& p) + { + try + { + _image.read(p.string().c_str()); + } + catch (Magick::WarningCoder& e) + { + LMS_LOG(COVER, WARNING, "Caught Magick WarningCoder: " << e.what()); + } + catch (Magick::Warning& e) + { + LMS_LOG(COVER, WARNING, "Caught Magick warning: " << e.what()); + throw Exception{ std::string{ "Magick read warning: " } + e.what() }; + } + catch (Magick::Exception& e) + { + LMS_LOG(COVER, ERROR, "Caught Magick exception: " << e.what()); + throw Exception{ std::string{ "Magick read error: " } + e.what() }; + } + } + + ImageSize RawImage::getWidth() const + { + return _image.size().width(); + } + + ImageSize RawImage::getHeight() const + { + return _image.size().height(); + } + + void RawImage::resize(ImageSize width) + { + try + { + LMS_SCOPED_TRACE_DETAILED("Image", "Resize"); + + _image.resize(Magick::Geometry{ static_cast(width), static_cast(width) }); + } + catch (Magick::Exception& e) + { + LMS_LOG(COVER, ERROR, "Caught Magick exception while resizing: " << e.what()); + throw Exception{ std::string{ "Magick resize error: " } + e.what() }; + } + } + + std::unique_ptr RawImage::encodeToJPEG(unsigned quality) const + { + return std::make_unique(*this, quality); + } + + Magick::Image RawImage::getMagickImage() const + { + return _image; + } } // namespace lms::image::GraphicsMagick - diff --git a/src/libs/image/impl/graphicsmagick/RawImage.hpp b/src/libs/image/impl/graphicsmagick/RawImage.hpp index d65813e19..13fc7b598 100644 --- a/src/libs/image/impl/graphicsmagick/RawImage.hpp +++ b/src/libs/image/impl/graphicsmagick/RawImage.hpp @@ -19,30 +19,32 @@ #pragma once -#include - #include #include +#include + #include "image/IEncodedImage.hpp" #include "image/IRawImage.hpp" namespace lms::image::GraphicsMagick { - class RawImage : public IRawImage - { - public: - RawImage(const std::byte* encodedData, std::size_t encodedDataSize); - RawImage(const std::filesystem::path& path); + class RawImage : public IRawImage + { + public: + RawImage(const std::byte* encodedData, std::size_t encodedDataSize); + RawImage(const std::filesystem::path& path); - void resize(ImageSize width) override; - std::unique_ptr encodeToJPEG(unsigned quality) const override; + ImageSize getWidth() const override; + ImageSize getHeight() const override; - private: - friend class JPEGImage; - Magick::Image getMagickImage() const; + void resize(ImageSize width) override; + std::unique_ptr encodeToJPEG(unsigned quality) const override; - Magick::Image _image; - }; -} + private: + friend class JPEGImage; + Magick::Image getMagickImage() const; + Magick::Image _image; + }; +} // namespace lms::image::GraphicsMagick diff --git a/src/libs/image/impl/stb/Image.cpp b/src/libs/image/impl/stb/Image.cpp new file mode 100644 index 000000000..06b953aa6 --- /dev/null +++ b/src/libs/image/impl/stb/Image.cpp @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "image/Image.hpp" + +#include + +#include "RawImage.hpp" +#include "core/ITraceLogger.hpp" + +namespace lms::image +{ + std::unique_ptr decodeImage(const std::byte* encodedData, std::size_t encodedDataSize) + { + LMS_SCOPED_TRACE_DETAILED("Image", "DecodeBuffer"); + return std::make_unique(encodedData, encodedDataSize); + } + + std::unique_ptr decodeImage(const std::filesystem::path& path) + { + LMS_SCOPED_TRACE_DETAILED("Image", "DecodeFile"); + return std::make_unique(path); + } + + void init(const std::filesystem::path&) + { + } + + std::span getSupportedFileExtensions() + { + static const std::array fileExtensions{ ".jpg", ".jpeg", ".png", ".bmp" }; + return fileExtensions; + } +} // namespace lms::image \ No newline at end of file diff --git a/src/libs/image/impl/stb/JPEGImage.cpp b/src/libs/image/impl/stb/JPEGImage.cpp index 681d93ea6..f64f831a8 100644 --- a/src/libs/image/impl/stb/JPEGImage.cpp +++ b/src/libs/image/impl/stb/JPEGImage.cpp @@ -24,41 +24,41 @@ #include "core/ITraceLogger.hpp" #include "image/Exception.hpp" + #include "RawImage.hpp" namespace lms::image::STB { - JPEGImage::JPEGImage(const RawImage& rawImage, unsigned quality) - { + JPEGImage::JPEGImage(const RawImage& rawImage, unsigned quality) + { LMS_SCOPED_TRACE_DETAILED("Image", "WriteJPEG"); - auto writeCb {[](void* ctx, void* writeData, int writeSize) - { - auto& output {*reinterpret_cast*>(ctx)}; - const std::size_t currentOutputSize {output.size()}; - output.resize(currentOutputSize + writeSize); - std::copy(reinterpret_cast(writeData), reinterpret_cast(writeData) + writeSize, output.data() + currentOutputSize); - }}; + auto writeCb{ [](void* ctx, void* writeData, int writeSize) { + auto& output{ *reinterpret_cast*>(ctx) }; + const std::size_t currentOutputSize{ output.size() }; + output.resize(currentOutputSize + writeSize); + std::copy(reinterpret_cast(writeData), reinterpret_cast(writeData) + writeSize, output.data() + currentOutputSize); + } }; - if (stbi_write_jpg_to_func(writeCb, &_data, rawImage.getWidth(), rawImage.getHeight(), 3, rawImage.getData(), quality) == 0) - { - _data.clear(); - throw Exception {"Failed to export in jpeg format!"}; - } - } + if (stbi_write_jpg_to_func(writeCb, &_data, rawImage.getWidth(), rawImage.getHeight(), 3, rawImage.getData(), quality) == 0) + { + _data.clear(); + throw Exception{ "Failed to export in jpeg format!" }; + } + } - const std::byte* - JPEGImage::getData() const - { - if (_data.empty()) - return nullptr; + const std::byte* + JPEGImage::getData() const + { + if (_data.empty()) + return nullptr; - return &_data.front(); - } + return &_data.front(); + } - std::size_t - JPEGImage::getDataSize() const - { - return _data.size(); - } -} + std::size_t + JPEGImage::getDataSize() const + { + return _data.size(); + } +} // namespace lms::image::STB diff --git a/src/libs/image/impl/stb/JPEGImage.hpp b/src/libs/image/impl/stb/JPEGImage.hpp index 34394d17b..3b4ad706e 100644 --- a/src/libs/image/impl/stb/JPEGImage.hpp +++ b/src/libs/image/impl/stb/JPEGImage.hpp @@ -25,17 +25,17 @@ namespace lms::image::STB { - class RawImage; - class JPEGImage : public IEncodedImage - { - public: - JPEGImage(const RawImage& rawImage, unsigned quality); + class RawImage; + class JPEGImage : public IEncodedImage + { + public: + JPEGImage(const RawImage& rawImage, unsigned quality); - private: - const std::byte* getData() const override; - std::size_t getDataSize() const override; - std::string_view getMimeType() const override { return "image/jpeg"; } + private: + const std::byte* getData() const override; + std::size_t getDataSize() const override; + std::string_view getMimeType() const override { return "image/jpeg"; } - std::vector _data; - }; -} + std::vector _data; + }; +} // namespace lms::image::STB diff --git a/src/libs/image/impl/stb/RawImage.cpp b/src/libs/image/impl/stb/RawImage.cpp index 286082175..44a021010 100644 --- a/src/libs/image/impl/stb/RawImage.cpp +++ b/src/libs/image/impl/stb/RawImage.cpp @@ -22,42 +22,24 @@ #define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_RESIZE_IMPLEMENTATION -#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL -#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM +#define STBIR_DEFAULT_FILTER_DOWNSAMPLE STBIR_FILTER_MITCHELL +#define STBIR_DEFAULT_FILTER_UPSAMPLE STBIR_FILTER_CATMULLROM #define STBI_FAILURE_USERMSG #include #if STB_IMAGE_RESIZE_VERSION == 1 -#include + #include #elif STB_IMAGE_RESIZE_VERSION == 2 -#include + #include #else -#error "Unhandled STB image resize version"! + #error "Unhandled STB image resize version"! #endif #include "core/ITraceLogger.hpp" #include "image/Exception.hpp" -#include "JPEGImage.hpp" - -namespace lms::image -{ - std::unique_ptr decodeImage(const std::byte* encodedData, std::size_t encodedDataSize) - { - LMS_SCOPED_TRACE_DETAILED("Image", "DecodeBuffer"); - return std::make_unique(encodedData, encodedDataSize); - } - std::unique_ptr decodeImage(const std::filesystem::path& path) - { - LMS_SCOPED_TRACE_DETAILED("Image", "DecodeFile"); - return std::make_unique(path); - } - - void init(const std::filesystem::path&) - { - } -} +#include "JPEGImage.hpp" namespace lms::image::STB { @@ -102,12 +84,14 @@ namespace lms::image::STB #if STB_IMAGE_RESIZE_VERSION == 1 if (::stbir_resize_uint8_srgb(reinterpret_cast(_data.get()), _width, _height, 0, - reinterpret_cast(resizedData.get()), width, height, 0, - 3, STBIR_ALPHA_CHANNEL_NONE, 0) == 0) + reinterpret_cast(resizedData.get()), width, height, 0, + 3, STBIR_ALPHA_CHANNEL_NONE, 0) + == 0) #elif STB_IMAGE_RESIZE_VERSION == 2 if (::stbir_resize_uint8_srgb(reinterpret_cast(_data.get()), _width, _height, 0, - reinterpret_cast(resizedData.get()), width, height, 0, - STBIR_RGB) == 0) + reinterpret_cast(resizedData.get()), width, height, 0, + STBIR_RGB) + == 0) #else #error "Unhandled STB image resize version"! #endif @@ -142,4 +126,4 @@ namespace lms::image::STB return reinterpret_cast(_data.get()); } -} \ No newline at end of file +} // namespace lms::image::STB \ No newline at end of file diff --git a/src/libs/image/impl/stb/RawImage.hpp b/src/libs/image/impl/stb/RawImage.hpp index d4788d4d9..ceaccd8c9 100644 --- a/src/libs/image/impl/stb/RawImage.hpp +++ b/src/libs/image/impl/stb/RawImage.hpp @@ -33,11 +33,12 @@ namespace lms::image::STB RawImage(const std::byte* encodedData, std::size_t encodedDataSize); RawImage(const std::filesystem::path& path); + ImageSize getWidth() const override; + ImageSize getHeight() const override; + void resize(ImageSize width) override; std::unique_ptr encodeToJPEG(unsigned quality) const override; - ImageSize getWidth() const; - ImageSize getHeight() const; const std::byte* getData() const; private: @@ -46,5 +47,4 @@ namespace lms::image::STB using UniquePtrFree = std::unique_ptr; UniquePtrFree _data{ nullptr, std::free }; }; -} - +} // namespace lms::image::STB diff --git a/src/libs/image/include/image/Exception.hpp b/src/libs/image/include/image/Exception.hpp index 7e0d83bab..65d416ae2 100644 --- a/src/libs/image/include/image/Exception.hpp +++ b/src/libs/image/include/image/Exception.hpp @@ -25,7 +25,7 @@ namespace lms::image { class Exception : public core::LmsException { - public: - using LmsException::LmsException; + public: + using LmsException::LmsException; }; -} // namespace lms::cover +} // namespace lms::image diff --git a/src/libs/image/include/image/IEncodedImage.hpp b/src/libs/image/include/image/IEncodedImage.hpp index 896851438..9bed88c02 100644 --- a/src/libs/image/include/image/IEncodedImage.hpp +++ b/src/libs/image/include/image/IEncodedImage.hpp @@ -35,4 +35,4 @@ namespace lms::image virtual std::size_t getDataSize() const = 0; virtual std::string_view getMimeType() const = 0; }; -} \ No newline at end of file +} // namespace lms::image \ No newline at end of file diff --git a/src/libs/image/include/image/IRawImage.hpp b/src/libs/image/include/image/IRawImage.hpp index 178da9586..75ed8c88d 100644 --- a/src/libs/image/include/image/IRawImage.hpp +++ b/src/libs/image/include/image/IRawImage.hpp @@ -27,8 +27,11 @@ namespace lms::image { public: virtual ~IRawImage() = default; + + virtual ImageSize getWidth() const = 0; + virtual ImageSize getHeight() const = 0; + virtual void resize(ImageSize width) = 0; virtual std::unique_ptr encodeToJPEG(unsigned quality) const = 0; }; -} - +} // namespace lms::image diff --git a/src/libs/image/include/image/Image.hpp b/src/libs/image/include/image/Image.hpp index a0ee4182e..7bf6592fd 100644 --- a/src/libs/image/include/image/Image.hpp +++ b/src/libs/image/include/image/Image.hpp @@ -21,6 +21,7 @@ #include #include +#include #include "image/IEncodedImage.hpp" #include "image/IRawImage.hpp" @@ -28,7 +29,8 @@ namespace lms::image { void init(const std::filesystem::path& path); + std::span getSupportedFileExtensions(); std::unique_ptr decodeImage(const std::byte* encodedData, std::size_t encodedDataSize); std::unique_ptr decodeImage(const std::filesystem::path& path); std::unique_ptr readSvgFile(const std::filesystem::path& path); -} \ No newline at end of file +} // namespace lms::image \ No newline at end of file diff --git a/src/libs/metadata/impl/AvFormatTagReader.cpp b/src/libs/metadata/impl/AvFormatTagReader.cpp index 4ead1f90e..34f237473 100644 --- a/src/libs/metadata/impl/AvFormatTagReader.cpp +++ b/src/libs/metadata/impl/AvFormatTagReader.cpp @@ -23,9 +23,10 @@ #include #include "av/IAudioFile.hpp" -#include "metadata/Exception.hpp" #include "core/ILogger.hpp" #include "core/String.hpp" +#include "metadata/Exception.hpp" + #include "Utils.hpp" namespace lms::metadata @@ -33,8 +34,7 @@ namespace lms::metadata namespace { // Mapping to internal avformat names and/or common alternative custom names - static const std::unordered_map> tagMapping - { + static const std::unordered_map> tagMapping{ { TagType::AcoustID, { "ACOUSTID_ID", "ACOUSTID ID" } }, { TagType::Album, { "ALBUM", "TALB", "WM/ALBUMTITLE" } }, { TagType::AlbumArtist, { "ALBUMARTIST", "ALBUM_ARTIST" } }, @@ -64,7 +64,7 @@ namespace lms::metadata { TagType::CopyrightURL, { "COPYRIGHTURL" } }, { TagType::Date, { "DATE", "YEAR", "WM/YEAR" } }, { TagType::Director, { "DIRECTOR" } }, - { TagType::DiscNumber, { "TPOS", "DISC", "DISK", "DISCNUMBER", "WM/PARTOFSET" } }, + { TagType::DiscNumber, { "TPOS", "DISC", "DISK", "DISCNUMBER", "WM/PARTOFSET" } }, { TagType::DiscSubtitle, { "TSST", "DISCSUBTITLE", "SETSUBTITLE" } }, { TagType::EncodedBy, { "ENCODEDBY" } }, { TagType::Engineer, { "ENGINEER" } }, @@ -130,7 +130,7 @@ namespace lms::metadata { TagType::Script, { "SCRIPT", "WM/SCRIPT" } }, { TagType::ShowWorkAndMovement, { "SHOWWORKMOVEMENT", "SHOWMOVEMENT" } }, { TagType::Subtitle, { "SUBTITLE" } }, - { TagType::TotalDiscs, { "DISCTOTAL", "TOTALDISCS"} }, + { TagType::TotalDiscs, { "DISCTOTAL", "TOTALDISCS" } }, { TagType::TotalTracks, { "TRACKTOTAL", "TOTALTRACKS" } }, { TagType::TrackNumber, { "TRCK", "TRACK", "TRACKNUMBER", "TRKN", "WM/TRACKNUMBER" } }, { TagType::TrackTitle, { "TITLE" } }, @@ -138,14 +138,14 @@ namespace lms::metadata { TagType::WorkTitle, { "WORK" } }, { TagType::Writer, { "WRITER" } }, }; - } + } // namespace AvFormatTagReader::AvFormatTagReader(const std::filesystem::path& p, bool debug) { try { const auto audioFile{ av::parseAudioFile(p) }; - + _audioProperties.duration = audioFile->getContainerInfo().duration; const auto bestAudioStream{ audioFile->getBestStreamInfo() }; @@ -183,11 +183,10 @@ namespace lms::metadata { bool visited{}; - visitTagValues(tagName, [&](std::string_view value) - { - visited = true; - visitor(value); - }); + visitTagValues(tagName, [&](std::string_view value) { + visited = true; + visitor(value); + }); if (visited) break; @@ -205,8 +204,7 @@ namespace lms::metadata void AvFormatTagReader::visitPerformerTags(PerformerVisitor visitor) const { - visitTagValues("PERFORMER", [&](std::string_view value) - { + visitTagValues("PERFORMER", [&](std::string_view value) { visitor("", value); }); } diff --git a/src/libs/metadata/impl/AvFormatTagReader.hpp b/src/libs/metadata/impl/AvFormatTagReader.hpp index 857de190c..15b806b0d 100644 --- a/src/libs/metadata/impl/AvFormatTagReader.hpp +++ b/src/libs/metadata/impl/AvFormatTagReader.hpp @@ -23,13 +23,14 @@ #include "av/IAudioFile.hpp" #include "metadata/IParser.hpp" + #include "ITagReader.hpp" namespace lms::metadata { class AvFormatTagReader : public ITagReader { - public: + public: AvFormatTagReader(const std::filesystem::path& path, bool debug); private: @@ -48,4 +49,3 @@ namespace lms::metadata bool _hasEmbeddedCover{}; }; } // namespace lms::metadata - diff --git a/src/libs/metadata/impl/ITagReader.hpp b/src/libs/metadata/impl/ITagReader.hpp index ee80933a1..da1a0d8b7 100644 --- a/src/libs/metadata/impl/ITagReader.hpp +++ b/src/libs/metadata/impl/ITagReader.hpp @@ -22,6 +22,8 @@ #include #include +#include "metadata/Types.hpp" + namespace lms::metadata { // using picard internal names @@ -48,11 +50,11 @@ namespace lms::metadata Compilation, Composer, ComposerSortOrder, - Composers, // non standard + Composers, // non standard ComposersSortOrder, // non standard Conductor, - ConductorSortOrder, // non standard - Conductors, // non standard + ConductorSortOrder, // non standard + Conductors, // non standard ConductorsSortOrder, // non standard Copyright, CopyrightURL, // non standard @@ -71,15 +73,15 @@ namespace lms::metadata Language, License, Lyricist, - LyricistSortOrder, // non standard - Lyricists, // non standard + LyricistSortOrder, // non standard + Lyricists, // non standard LyricistsSortOrder, // non standard Lyrics, Media, MixDJ, Mixer, - MixerSortOrder, // non standard - Mixers, // non standard + MixerSortOrder, // non standard + Mixers, // non standard MixersSortOrder, // non standard Mood, Movement, @@ -105,8 +107,8 @@ namespace lms::metadata Podcast, PodcastURL, Producer, - ProducerSortOrder, // non standard - Producers, // non standard + ProducerSortOrder, // non standard + Producers, // non standard ProducersSortOrder, // non standard Rating, RecordLabel, diff --git a/src/libs/metadata/impl/Parser.cpp b/src/libs/metadata/impl/Parser.cpp index 5fe97de7e..d6805e352 100644 --- a/src/libs/metadata/impl/Parser.cpp +++ b/src/libs/metadata/impl/Parser.cpp @@ -21,9 +21,9 @@ #include -#include "metadata/Exception.hpp" #include "core/ILogger.hpp" #include "core/String.hpp" +#include "metadata/Exception.hpp" #include "AvFormatTagReader.hpp" #include "TagLibTagReader.hpp" @@ -35,29 +35,27 @@ namespace lms::metadata { void visitTagValues(const ITagReader& tagReader, std::string_view tagType, std::span tagDelimiters, ITagReader::TagValueVisitor visitor) { - tagReader.visitTagValues(tagType, [&](std::string_view value) + tagReader.visitTagValues(tagType, [&](std::string_view value) { + auto visitTagIfNonEmpty{ [&](std::string_view tag) { + tag = core::stringUtils::stringTrim(tag); + if (!tag.empty()) + visitor(tag); + } }; + + for (std::string_view tagDelimiter : tagDelimiters) { - auto visitTagIfNonEmpty{ [&](std::string_view tag) + if (value.find(tagDelimiter) != std::string_view::npos) { - tag = core::stringUtils::stringTrim(tag); - if (!tag.empty()) - visitor(tag); - } }; + for (std::string_view splitTag : core::stringUtils::splitString(value, tagDelimiter)) + visitTagIfNonEmpty(splitTag); - for (std::string_view tagDelimiter : tagDelimiters) - { - if (value.find(tagDelimiter) != std::string_view::npos) - { - for (std::string_view splitTag : core::stringUtils::splitString(value, tagDelimiter)) - visitTagIfNonEmpty(splitTag); - - return; - } + return; } + } - // no delimiter found, or no delimiter to be used - visitTagIfNonEmpty(value); - }); + // no delimiter found, or no delimiter to be used + visitTagIfNonEmpty(value); + }); } template @@ -67,8 +65,7 @@ namespace lms::metadata for (const TagType tagType : tagTypes) { - auto addTagIfNonEmpty{ [&res](std::string_view tag) - { + auto addTagIfNonEmpty{ [&res](std::string_view tag) { tag = core::stringUtils::stringTrim(tag); if (!tag.empty()) { @@ -78,22 +75,21 @@ namespace lms::metadata } } }; - tagReader.visitTagValues(tagType, [&](std::string_view value) + tagReader.visitTagValues(tagType, [&](std::string_view value) { + for (std::string_view tagDelimiter : tagDelimiters) { - for (std::string_view tagDelimiter : tagDelimiters) + if (value.find(tagDelimiter) != std::string_view::npos) { - if (value.find(tagDelimiter) != std::string_view::npos) - { - for (std::string_view splitTag : core::stringUtils::splitString(value, tagDelimiter)) - addTagIfNonEmpty(splitTag); + for (std::string_view splitTag : core::stringUtils::splitString(value, tagDelimiter)) + addTagIfNonEmpty(splitTag); - return; - } + return; } + } - // no delimiter found, or no delimiter to be used - addTagIfNonEmpty(value); - }); + // no delimiter found, or no delimiter to be used + addTagIfNonEmpty(value); + }); if (!res.empty()) break; @@ -102,7 +98,7 @@ namespace lms::metadata return res; } - template + template std::optional getTagValueFirstMatchAs(const ITagReader& tagReader, std::initializer_list tagTypes) { std::optional res; @@ -113,13 +109,13 @@ namespace lms::metadata return res; } - template + template std::vector getTagValuesAs(const ITagReader& tagReader, TagType tagType, std::span tagDelimiters) { return getTagValuesFirstMatchAs(tagReader, { tagType }, tagDelimiters); } - template + template std::optional getTagValueAs(const ITagReader& tagReader, TagType tagType) { return getTagValueFirstMatchAs(tagReader, { tagType }); @@ -129,8 +125,7 @@ namespace lms::metadata std::initializer_list artistTagNames, std::initializer_list artistSortTagNames, std::initializer_list artistMBIDTagNames, - std::span artistTagDelimiters - ) + std::span artistTagDelimiters) { std::vector artistNames{ getTagValuesFirstMatchAs(tagReader, artistTagNames, artistTagDelimiters) }; if (artistNames.empty()) @@ -159,29 +154,28 @@ namespace lms::metadata { PerformerContainer performers; - tagReader.visitPerformerTags([&](std::string_view role, std::string_view name) + tagReader.visitPerformerTags([&](std::string_view role, std::string_view name) { + // picard stores like this: (see https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html#performer) + // We consider we may hit both styles for the same track + if (role.empty()) { - // picard stores like this: (see https://picard-docs.musicbrainz.org/en/appendices/tag_mapping.html#performer) - // We consider we may hit both styles for the same track - if (role.empty()) - { - // "PERFORMER" "artist (role)" - utils::PerformerArtist performer{ utils::extractPerformerAndRole(name) }; - core::stringUtils::capitalize(performer.role); - performers[performer.role].push_back(std::move(performer.artist)); - } - else - { - // "PERFORMER:role", "artist" (MP3) - std::string roleCapitalized{ core::stringUtils::stringToLower(role) }; - core::stringUtils::capitalize(roleCapitalized); - performers[roleCapitalized].push_back(Artist{ name }); - } - }); + // "PERFORMER" "artist (role)" + utils::PerformerArtist performer{ utils::extractPerformerAndRole(name) }; + core::stringUtils::capitalize(performer.role); + performers[performer.role].push_back(std::move(performer.artist)); + } + else + { + // "PERFORMER:role", "artist" (MP3) + std::string roleCapitalized{ core::stringUtils::stringToLower(role) }; + core::stringUtils::capitalize(roleCapitalized); + performers[roleCapitalized].push_back(Artist{ name }); + } + }); return performers; } - } + } // namespace std::unique_ptr createParser(ParserBackend parserBackend, ParserReadStyle parserReadStyle) { @@ -282,16 +276,14 @@ namespace lms::metadata track.copyright = getTagValueAs(tagReader, TagType::Copyright).value_or(""); track.copyrightURL = getTagValueAs(tagReader, TagType::CopyrightURL).value_or(""); track.replayGain = getTagValueAs(tagReader, TagType::ReplayGainTrackGain); - track.artistDisplayName = getTagValueAs(tagReader, TagType::Artist).value_or(""); // TODO join on artists if present for (const std::string& userExtraTag : _userExtraTags) { - visitTagValues(tagReader, userExtraTag, _defaultTagDelimiters, [&](std::string_view value) - { - value = core::stringUtils::stringTrim(value); - if (!value.empty()) - track.userExtraTags[userExtraTag].push_back(std::string{ value }); - }); + visitTagValues(tagReader, userExtraTag, _defaultTagDelimiters, [&](std::string_view value) { + value = core::stringUtils::stringTrim(value); + if (!value.empty()) + track.userExtraTags[userExtraTag].push_back(std::string{ value }); + }); } track.genres = getTagValuesAs(tagReader, TagType::Genre, _defaultTagDelimiters); @@ -304,6 +296,21 @@ namespace lms::metadata track.medium = getMedium(tagReader); track.artists = getArtists(tagReader, { TagType::Artists, TagType::Artist }, { TagType::ArtistSortOrder }, { TagType::MusicBrainzArtistID }, _artistTagDelimiters); + + // We consider the artist display name is put in the Artist tag (picard case) + // But to please most users, if we find a custom delimiter in the Artist tag, we construct the artist diplay string with a "nicer" join + if (!_artistTagDelimiters.empty() + && track.artists.size() > 1 + && getTagValuesAs(tagReader, TagType::Artist, _artistTagDelimiters).size() > 1) + { + std::vector artistNames; + std::transform(std::cbegin(track.artists), std::cend(track.artists), std::back_inserter(artistNames), [](const Artist& artist) -> std::string_view { return artist.name; }); + track.artistDisplayName = core::stringUtils::joinStrings(artistNames, ", "); + } + else + { + track.artistDisplayName = getTagValueAs(tagReader, TagType::Artist).value_or(""); + } track.conductorArtists = getArtists(tagReader, { TagType::Conductors, TagType::Conductor }, { TagType::ConductorsSortOrder, TagType::ConductorSortOrder }, {}, _artistTagDelimiters); track.composerArtists = getArtists(tagReader, { TagType::Composers, TagType::Composer }, { TagType::ComposersSortOrder, TagType::ComposerSortOrder }, {}, _artistTagDelimiters); track.lyricistArtists = getArtists(tagReader, { TagType::Lyricists, TagType::Lyricist }, { TagType::LyricistsSortOrder, TagType::LyricistSortOrder }, {}, _artistTagDelimiters); diff --git a/src/libs/metadata/impl/Parser.hpp b/src/libs/metadata/impl/Parser.hpp index 38b4e1f89..f69b0f480 100644 --- a/src/libs/metadata/impl/Parser.hpp +++ b/src/libs/metadata/impl/Parser.hpp @@ -20,6 +20,7 @@ #pragma once #include "metadata/IParser.hpp" + #include "ITagReader.hpp" namespace lms::metadata @@ -51,4 +52,3 @@ namespace lms::metadata std::vector _defaultTagDelimiters; }; } // namespace lms::metadata - diff --git a/src/libs/metadata/impl/TagLibTagReader.cpp b/src/libs/metadata/impl/TagLibTagReader.cpp index b1ecb6d8f..13e9e5577 100644 --- a/src/libs/metadata/impl/TagLibTagReader.cpp +++ b/src/libs/metadata/impl/TagLibTagReader.cpp @@ -25,11 +25,11 @@ #include #include #if TAGLIB_MAJOR_VERSION >= 2 -#include + #include #endif -#include #include #include +#include #include #include #include @@ -39,20 +39,21 @@ #include #include -#include "metadata/Exception.hpp" #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" #include "core/String.hpp" +#include "metadata/Exception.hpp" namespace lms::metadata { namespace { - class ParsingFailedException : public Exception {}; + class ParsingFailedException : public Exception + { + }; // Mapping to internal taglib names and/or common alternative custom names - static const std::unordered_map> tagMapping - { + static const std::unordered_map> tagMapping{ { TagType::AcoustID, { "ACOUSTID_ID", "ACOUSTID ID" } }, { TagType::Album, { "ALBUM" } }, { TagType::AlbumArtist, { "ALBUMARTIST" } }, @@ -148,7 +149,7 @@ namespace lms::metadata { TagType::Script, { "SCRIPT" } }, { TagType::ShowWorkAndMovement, { "SHOWWORKMOVEMENT", "SHOWMOVEMENT" } }, { TagType::Subtitle, { "SUBTITLE" } }, - { TagType::TotalDiscs, { "DISCTOTAL", "TOTALDISCS"} }, + { TagType::TotalDiscs, { "DISCTOTAL", "TOTALDISCS" } }, { TagType::TotalTracks, { "TRACKTOTAL", "TOTALTRACKS" } }, { TagType::TrackNumber, { "TRACKNUMBER" } }, { TagType::TrackTitle, { "TITLE" } }, @@ -161,9 +162,12 @@ namespace lms::metadata { switch (readStyle) { - case ParserReadStyle::Fast: return TagLib::AudioProperties::ReadStyle::Fast; - case ParserReadStyle::Average: return TagLib::AudioProperties::ReadStyle::Average; - case ParserReadStyle::Accurate: return TagLib::AudioProperties::ReadStyle::Accurate; + case ParserReadStyle::Fast: + return TagLib::AudioProperties::ReadStyle::Fast; + case ParserReadStyle::Average: + return TagLib::AudioProperties::ReadStyle::Average; + case ParserReadStyle::Accurate: + return TagLib::AudioProperties::ReadStyle::Accurate; } throw core::LmsException{ "Cannot convert read style" }; @@ -182,11 +186,11 @@ namespace lms::metadata { LMS_SCOPED_TRACE_DETAILED("MetaData", "TagLibParseFile"); - return TagLib::FileRef{ p.string().c_str() - , true // read audio properties - , readStyleToTagLibReadStyle(parserReadStyle) }; + return TagLib::FileRef{ p.string().c_str(), true // read audio properties + , + readStyleToTagLibReadStyle(parserReadStyle) }; } - } + } // namespace TagLibTagReader::TagLibTagReader(const std::filesystem::path& p, ParserReadStyle parserReadStyle, bool debug) : _file{ parseFile(p, parserReadStyle) } @@ -208,13 +212,12 @@ namespace lms::metadata _propertyMap = _file.file()->properties(); // Some tags may not be known by TagLib - auto getAPETags = [&](const TagLib::APE::Tag* apeTag) - { - if (!apeTag) - return; + auto getAPETags = [&](const TagLib::APE::Tag* apeTag) { + if (!apeTag) + return; - mergeTagMaps(_propertyMap, apeTag->properties()); - }; + mergeTagMaps(_propertyMap, apeTag->properties()); + }; // Not that good embedded pictures handling // + get some extra tags that may not be known by taglib @@ -274,7 +277,7 @@ namespace lms::metadata getAPETags(mp3File->APETag()); } - //MP4 + // MP4 else if (TagLib::MP4::File * mp4File{ dynamic_cast(_file.file()) }) { TagLib::MP4::Item coverItem{ mp4File->tag()->item("covr") }; @@ -332,18 +335,18 @@ namespace lms::metadata _audioProperties.duration = std::chrono::milliseconds{ properties->lengthInMilliseconds() }; _audioProperties.sampleRate = static_cast(properties->sampleRate()); - if (const auto * apeProperties{ dynamic_cast(properties) }) + if (const auto* apeProperties{ dynamic_cast(properties) }) _audioProperties.bitsPerSample = apeProperties->bitsPerSample(); - if (const auto * asfProperties{ dynamic_cast(properties) }) + if (const auto* asfProperties{ dynamic_cast(properties) }) _audioProperties.bitsPerSample = asfProperties->bitsPerSample(); - else if (const auto * flacProperties{ dynamic_cast(properties) }) + else if (const auto* flacProperties{ dynamic_cast(properties) }) _audioProperties.bitsPerSample = flacProperties->bitsPerSample(); - else if (const auto * mp4Properties{ dynamic_cast(properties) }) + else if (const auto* mp4Properties{ dynamic_cast(properties) }) _audioProperties.bitsPerSample = mp4Properties->bitsPerSample(); - else if (const auto * wavePackProperties{ dynamic_cast(properties) }) + else if (const auto* wavePackProperties{ dynamic_cast(properties) }) _audioProperties.bitsPerSample = wavePackProperties->bitsPerSample(); #if TAGLIB_MAJOR_VERSION >= 2 - else if (const auto * dsfProperties{ dynamic_cast(properties) }) + else if (const auto* dsfProperties{ dynamic_cast(properties) }) _audioProperties.bitsPerSample = dsfProperties->bitsPerSample(); #endif } @@ -358,11 +361,10 @@ namespace lms::metadata { bool visited{}; - visitTagValues(tagName, [&](std::string_view value) - { - visited = true; - visitor(value); - }); + visitTagValues(tagName, [&](std::string_view value) { + visited = true; + visitor(value); + }); if (visited) break; @@ -383,10 +385,9 @@ namespace lms::metadata void TagLibTagReader::visitPerformerTags(PerformerVisitor visitor) const { - visitTagValues("PERFORMER", [&](std::string_view value) - { - visitor("", value); - }); + visitTagValues("PERFORMER", [&](std::string_view value) { + visitor("", value); + }); for (const auto& [key, values] : _propertyMap) { diff --git a/src/libs/metadata/impl/TagLibTagReader.hpp b/src/libs/metadata/impl/TagLibTagReader.hpp index 3ada79af1..0b13853df 100644 --- a/src/libs/metadata/impl/TagLibTagReader.hpp +++ b/src/libs/metadata/impl/TagLibTagReader.hpp @@ -25,6 +25,7 @@ #include #include "metadata/IParser.hpp" + #include "ITagReader.hpp" namespace lms::metadata diff --git a/src/libs/metadata/impl/Utils.cpp b/src/libs/metadata/impl/Utils.cpp index 1d9c5c708..6f87545f6 100644 --- a/src/libs/metadata/impl/Utils.cpp +++ b/src/libs/metadata/impl/Utils.cpp @@ -19,9 +19,9 @@ #include "Utils.hpp" #include -#include #include #include +#include #include "core/Exception.hpp" @@ -29,8 +29,7 @@ namespace lms::metadata::utils { Wt::WDate parseDate(std::string_view dateStr) { - static constexpr const char* formats[] - { + static constexpr const char* formats[]{ "%Y-%m-%d", "%Y/%m/%d", }; @@ -41,7 +40,7 @@ namespace lms::metadata::utils tm.tm_mon = -1; tm.tm_mday = -1; - std::istringstream ss{ std::string {dateStr} }; // TODO, remove extra copy here + std::istringstream ss{ std::string{ dateStr } }; // TODO, remove extra copy here ss >> std::get_time(&tm, format); if (ss.fail()) continue; @@ -49,11 +48,10 @@ namespace lms::metadata::utils if (tm.tm_mday <= 0 || tm.tm_mon < 0) continue; - const Wt::WDate res - { - tm.tm_year + 1900, // tm.tm_year: years since 1900 - tm.tm_mon + 1, // tm.tm_mon: months since January – [00, 11] - tm.tm_mday // tm.tm_mday: day of the month – [1, 31] + const Wt::WDate res{ + tm.tm_year + 1900, // tm.tm_year: years since 1900 + tm.tm_mon + 1, // tm.tm_mon: months since January – [00, 11] + tm.tm_mday // tm.tm_mday: day of the month – [1, 31] }; if (!res.isValid()) continue; @@ -87,7 +85,8 @@ namespace lms::metadata::utils int result{}; for (std::size_t i{}; i < yearStr.size() && i < 4; ++i) { - if (!std::isdigit(yearStr[i])) { + if (!std::isdigit(yearStr[i])) + { break; } result = result * 10 + (yearStr[i] - '0'); @@ -100,9 +99,12 @@ namespace lms::metadata::utils { switch (readStyle) { - case ParserReadStyle::Fast: return "fast"; - case ParserReadStyle::Average: return "average"; - case ParserReadStyle::Accurate: return "accurate"; + case ParserReadStyle::Fast: + return "fast"; + case ParserReadStyle::Average: + return "average"; + case ParserReadStyle::Accurate: + return "accurate"; } throw core::LmsException{ "Unknown read style" }; @@ -150,6 +152,6 @@ namespace lms::metadata::utils if (!roleEnd || !roleBegin) artistName = core::stringUtils::stringTrim(entry); - return PerformerArtist{ Artist {artistName}, std::string {role} }; + return PerformerArtist{ Artist{ artistName }, std::string{ role } }; } -} \ No newline at end of file +} // namespace lms::metadata::utils \ No newline at end of file diff --git a/src/libs/metadata/impl/Utils.hpp b/src/libs/metadata/impl/Utils.hpp index 3e844da26..dc7671e8f 100644 --- a/src/libs/metadata/impl/Utils.hpp +++ b/src/libs/metadata/impl/Utils.hpp @@ -22,22 +22,23 @@ #include #include + #include #include "metadata/IParser.hpp" namespace lms::metadata::utils { - Wt::WDate parseDate(std::string_view dateStr); - std::optional parseYear(std::string_view yearStr); - std::string_view readStyleToString(ParserReadStyle readStyle); - - struct PerformerArtist - { - Artist artist; - std::string role; - }; - - // format is "artist name (role)" - PerformerArtist extractPerformerAndRole(std::string_view entry); -} + Wt::WDate parseDate(std::string_view dateStr); + std::optional parseYear(std::string_view yearStr); + std::string_view readStyleToString(ParserReadStyle readStyle); + + struct PerformerArtist + { + Artist artist; + std::string role; + }; + + // format is "artist name (role)" + PerformerArtist extractPerformerAndRole(std::string_view entry); +} // namespace lms::metadata::utils diff --git a/src/libs/metadata/include/metadata/Exception.hpp b/src/libs/metadata/include/metadata/Exception.hpp index 7f0c6ac0c..7a8784205 100644 --- a/src/libs/metadata/include/metadata/Exception.hpp +++ b/src/libs/metadata/include/metadata/Exception.hpp @@ -34,4 +34,4 @@ namespace lms::metadata public: using Exception::Exception; }; -} \ No newline at end of file +} // namespace lms::metadata \ No newline at end of file diff --git a/src/libs/metadata/include/metadata/IParser.hpp b/src/libs/metadata/include/metadata/IParser.hpp index 5fb287798..f1553c5db 100644 --- a/src/libs/metadata/include/metadata/IParser.hpp +++ b/src/libs/metadata/include/metadata/IParser.hpp @@ -19,114 +19,14 @@ #pragma once -#include #include -#include -#include +#include #include -#include -#include -#include -#include -#include "core/UUID.hpp" +#include "metadata/Types.hpp" namespace lms::metadata { - using Tags = std::map /* values */>; - - // Very simplified version of https://musicbrainz.org/doc/MusicBrainz_Database/Schema - - struct Artist - { - std::optional mbid; - std::string name; - std::optional sortName; - - Artist(std::string_view _name) : name{ _name } {} - Artist(std::optional _mbid, std::string_view _name, std::optional _sortName) : mbid{ std::move(_mbid) }, name{ _name }, sortName{ std::move(_sortName) } {} - - auto operator<=>(const Artist&) const = default; - }; - - using PerformerContainer = std::map>; - - struct Release - { - std::optional mbid; - std::optional groupMBID; - std::string name; - std::string sortName; - std::string artistDisplayName; - std::vector artists; - std::optional mediumCount; - std::vector releaseTypes; - - auto operator<=>(const Release&) const = default; - }; - - struct Medium - { - std::string media; // CD, etc. - std::string name; - std::optional release; - std::optional position; // in release - std::optional trackCount; - std::optional replayGain; - - auto operator<=>(const Medium&) const = default; - - bool isDefault() const - { - static Medium defaultMedium; - return *this == defaultMedium; - } - }; - - struct AudioProperties - { - std::size_t bitrate{}; - std::size_t bitsPerSample{}; - std::size_t channelCount{}; - std::chrono::milliseconds duration{}; - std::size_t sampleRate{}; - }; - - struct Track - { - AudioProperties audioProperties; - std::optional mbid; - std::optional recordingMBID; - std::string title; - std::optional rating; - std::optional medium; - std::optional position; // in medium - std::vector groupings; - std::vector genres; - std::vector moods; - std::vector labels; - std::vector languages; - Tags userExtraTags; - std::optional year{}; - Wt::WDate date; - std::optional originalYear{}; - Wt::WDate originalDate; - bool hasCover{}; - std::optional acoustID; - std::string copyright; - std::string copyrightURL; - std::optional replayGain; - std::string artistDisplayName; - std::vector artists; - std::vector conductorArtists; - std::vector composerArtists; - std::vector lyricistArtists; - std::vector mixerArtists; - PerformerContainer performerArtists; - std::vector producerArtists; - std::vector remixerArtists; - }; - class IParser { public: @@ -152,4 +52,4 @@ namespace lms::metadata Accurate, }; std::unique_ptr createParser(ParserBackend parserBackend, ParserReadStyle parserReadStyle); -} +} // namespace lms::metadata diff --git a/src/libs/metadata/include/metadata/Types.hpp b/src/libs/metadata/include/metadata/Types.hpp new file mode 100644 index 000000000..9f69fb19b --- /dev/null +++ b/src/libs/metadata/include/metadata/Types.hpp @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +#include "core/UUID.hpp" + +namespace lms::metadata +{ + using Tags = std::map /* values */>; + + // Very simplified version of https://musicbrainz.org/doc/MusicBrainz_Database/Schema + + struct Artist + { + std::optional mbid; + std::string name; + std::optional sortName; + + Artist(std::string_view _name) + : name{ _name } {} + Artist(std::optional _mbid, std::string_view _name, std::optional _sortName) + : mbid{ std::move(_mbid) } + , name{ _name } + , sortName{ std::move(_sortName) } {} + + auto operator<=>(const Artist&) const = default; + }; + + using PerformerContainer = std::map>; + + struct Release + { + std::optional mbid; + std::optional groupMBID; + std::string name; + std::string sortName; + std::string artistDisplayName; + std::vector artists; + std::optional mediumCount; + std::vector releaseTypes; + + auto operator<=>(const Release&) const = default; + }; + + struct Medium + { + std::string media; // CD, etc. + std::string name; + std::optional release; + std::optional position; // in release + std::optional trackCount; + std::optional replayGain; + + auto operator<=>(const Medium&) const = default; + + bool isDefault() const + { + static Medium defaultMedium; + return *this == defaultMedium; + } + }; + + struct AudioProperties + { + std::size_t bitrate{}; + std::size_t bitsPerSample{}; + std::size_t channelCount{}; + std::chrono::milliseconds duration{}; + std::size_t sampleRate{}; + }; + + struct Track + { + AudioProperties audioProperties; + std::optional mbid; + std::optional recordingMBID; + std::string title; + std::optional medium; + std::optional position; // in medium + std::vector groupings; + std::vector genres; + std::vector moods; + std::vector labels; + std::vector languages; + Tags userExtraTags; + std::optional year{}; + Wt::WDate date; + std::optional originalYear{}; + Wt::WDate originalDate; + bool hasCover{}; + std::optional acoustID; + std::string copyright; + std::string copyrightURL; + std::optional replayGain; + std::string artistDisplayName; + std::vector artists; + std::vector conductorArtists; + std::vector composerArtists; + std::vector lyricistArtists; + std::vector mixerArtists; + PerformerContainer performerArtists; + std::vector producerArtists; + std::vector remixerArtists; + }; +} // namespace lms::metadata \ No newline at end of file diff --git a/src/libs/metadata/test/Metadata.cpp b/src/libs/metadata/test/Metadata.cpp index 88325e8ba..dc6733728 100644 --- a/src/libs/metadata/test/Metadata.cpp +++ b/src/libs/metadata/test/Metadata.cpp @@ -19,9 +19,8 @@ #include -int main(int argc, char **argv) +int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } - diff --git a/src/libs/metadata/test/Parser.cpp b/src/libs/metadata/test/Parser.cpp index 0f3e721c7..27f8fc9fc 100644 --- a/src/libs/metadata/test/Parser.cpp +++ b/src/libs/metadata/test/Parser.cpp @@ -19,10 +19,11 @@ #include #include + #include -#include "TestTagReader.hpp" #include "Parser.hpp" +#include "TestTagReader.hpp" namespace lms::metadata { @@ -65,24 +66,19 @@ namespace lms::metadata { TagType::Language, { "Language1", "Language2" } }, { TagType::Lyricist, { "MyLyricist1", "MyLyricist2" } }, { TagType::OriginalReleaseDate, { "2019/02/03" } }, - { TagType::ReleaseType, {"Album", "Compilation"} }, + { TagType::ReleaseType, { "Album", "Compilation" } }, { TagType::Rating, {"255"} }, - { TagType::ReplayGainTrackGain, {"-0.33"} }, - { TagType::ReplayGainAlbumGain, {"-0.5"} }, - { TagType::TrackTitle, {"MyTitle"} }, + { TagType::ReplayGainTrackGain, { "-0.33" } }, + { TagType::ReplayGainAlbumGain, { "-0.5" } }, + { TagType::TrackTitle, { "MyTitle" } }, { TagType::TrackNumber, { "7" } }, { TagType::TotalTracks, { "12" } }, { TagType::TotalDiscs, { "3" } }, - } - , - { - { "RoleA", { "MyPerformer1ForRoleA", "MyPerformer2ForRoleA" } }, - { "RoleB", { "MyPerformer1ForRoleB", "MyPerformer2ForRoleB" } } }, - { - { "MY_AWESOME_TAG_A", { "MyTagValue1ForTagA", "MyTagValue2ForTagA" } }, - { "MY_AWESOME_TAG_B", { "MyTagValue1ForTagB", "MyTagValue2ForTagB" } } - } + { { "RoleA", { "MyPerformer1ForRoleA", "MyPerformer2ForRoleA" } }, + { "RoleB", { "MyPerformer1ForRoleB", "MyPerformer2ForRoleB" } } }, + { { "MY_AWESOME_TAG_A", { "MyTagValue1ForTagA", "MyTagValue2ForTagA" } }, + { "MY_AWESOME_TAG_B", { "MyTagValue1ForTagB", "MyTagValue2ForTagB" } } } }; static_cast(parser).setUserExtraTags(std::vector{ "MY_AWESOME_TAG_A", "MY_AWESOME_TAG_B", "MY_AWESOME_MISSING_TAG" }); @@ -236,25 +232,27 @@ namespace lms::metadata { const TestTagReader testTags{ { - { TagType::Genre, { "Genre1; Genre2" } }, - { TagType::Language, { " Lang1 ; Lang2 ; " } }, + { TagType::Genre, { "Genre1 ; Genre2" } }, + { TagType::Language, { " Lang1/Lang2 / Lang3" } }, { TagType::Artist, { " This / is ; One Artist \\ Other Artist " } }, } }; Parser parser; - static_cast(parser).setDefaultTagDelimiters(std::vector{ " ; " }); - static_cast(parser).setArtistTagDelimiters(std::vector{ " \\ ", " / " }); + static_cast(parser).setDefaultTagDelimiters(std::vector{ " ; ", "/" }); + static_cast(parser).setArtistTagDelimiters(std::vector{ " \\ ", " / " }); // The first delimiter found will be used std::unique_ptr track{ parser.parse(testTags) }; - ASSERT_EQ(track->genres.size(), 1); - EXPECT_EQ(track->genres[0], "Genre1; Genre2"); - ASSERT_EQ(track->languages.size(), 2); + ASSERT_EQ(track->genres.size(), 2); + EXPECT_EQ(track->genres[0], "Genre1"); + EXPECT_EQ(track->genres[1], "Genre2"); + ASSERT_EQ(track->languages.size(), 3); EXPECT_EQ(track->languages[0], "Lang1"); EXPECT_EQ(track->languages[1], "Lang2"); + EXPECT_EQ(track->languages[2], "Lang3"); ASSERT_EQ(track->artists.size(), 2); EXPECT_EQ(track->artists[0].name, "This / is ; One Artist"); EXPECT_EQ(track->artists[1].name, "Other Artist"); - EXPECT_EQ(track->artistDisplayName, "This / is ; One Artist \\ Other Artist"); + EXPECT_EQ(track->artistDisplayName, "This / is ; One Artist, Other Artist"); // reconstruct artist display name since a custom delimiter is hit } -} +} // namespace lms::metadata diff --git a/src/libs/metadata/test/TestTagReader.hpp b/src/libs/metadata/test/TestTagReader.hpp index e7f968f4c..d27acfaf5 100644 --- a/src/libs/metadata/test/TestTagReader.hpp +++ b/src/libs/metadata/test/TestTagReader.hpp @@ -19,6 +19,7 @@ #include #include + #include #include "Parser.hpp" @@ -28,12 +29,11 @@ namespace lms::metadata class TestTagReader : public ITagReader { public: - static constexpr AudioProperties audioProperties - { + static constexpr AudioProperties audioProperties{ .bitrate = 128000, .bitsPerSample = 16, .channelCount = 2, - .duration = std::chrono::seconds{180}, + .duration = std::chrono::seconds{ 180 }, .sampleRate = 44000, }; @@ -84,4 +84,4 @@ namespace lms::metadata const Performers _performers; const ExtraUserTags _extraUserTags; }; -} \ No newline at end of file +} // namespace lms::metadata \ No newline at end of file diff --git a/src/libs/metadata/test/Utils.cpp b/src/libs/metadata/test/Utils.cpp index 4d766c702..925675eae 100644 --- a/src/libs/metadata/test/Utils.cpp +++ b/src/libs/metadata/test/Utils.cpp @@ -28,42 +28,41 @@ namespace lms::metadata::utils::tests { struct TestCase { - std::string str; - Wt::WDate result; - } testCases[] - { - { "1995-05-09", Wt::WDate {1995, 5, 9} }, - { "1995-01-01", Wt::WDate {1995, 1, 1} }, - { "1900-01-01", Wt::WDate {1900, 1, 1} }, - { "1899-01-01", Wt::WDate {1899, 1, 1} }, - { "1899-12-31", Wt::WDate {1899, 12, 31} }, - { "1899-11-30", Wt::WDate {1899, 11, 30} }, - { "1500-11-30", Wt::WDate {1500, 11, 30} }, - { "1000-11-30", Wt::WDate {1000, 11, 30} }, - { "1899-11-31", Wt::WDate {} }, // invalid day - { "1899-11-00", Wt::WDate {} }, // invalid day - { "1899-13-01", Wt::WDate {} }, // invalid month - { "1899-00-01", Wt::WDate {} }, // invalid month - { "1899-11", Wt::WDate {} }, // missing day - { "1899", Wt::WDate {} }, // missing month and days - { "1600", Wt::WDate {} }, // missing month and days - { "1995/05/09", Wt::WDate {1995, 5, 9} }, - { "1995/01/01", Wt::WDate {1995, 1, 1} }, - { "1900/01/01", Wt::WDate {1900, 1, 1} }, - { "1899/01/01", Wt::WDate {1899, 1, 1} }, - { "1899/12/31", Wt::WDate {1899, 12, 31} }, - { "1899/11/30", Wt::WDate {1899, 11, 30} }, - { "1500/11/30", Wt::WDate {1500, 11, 30} }, - { "1000/11/30", Wt::WDate {1000, 11, 30} }, - { "1899/11/31", Wt::WDate {} }, // invalid day - { "1899/11/00", Wt::WDate {} }, // invalid day - { "1899/13/01", Wt::WDate {} }, // invalid month - { "1899/00/01", Wt::WDate {} }, // invalid month - { "1899/11", Wt::WDate {} }, // missing day - { "1899", Wt::WDate {} }, // missing month and days - { "1600", Wt::WDate {} }, // missing month and days - { "1995/05-09", Wt::WDate {} }, // invalid mixup separators - { "1995-05/09", Wt::WDate {} }, // invalid mixup separators + std::string str; + Wt::WDate result; + } testCases[]{ + { "1995-05-09", Wt::WDate{ 1995, 5, 9 } }, + { "1995-01-01", Wt::WDate{ 1995, 1, 1 } }, + { "1900-01-01", Wt::WDate{ 1900, 1, 1 } }, + { "1899-01-01", Wt::WDate{ 1899, 1, 1 } }, + { "1899-12-31", Wt::WDate{ 1899, 12, 31 } }, + { "1899-11-30", Wt::WDate{ 1899, 11, 30 } }, + { "1500-11-30", Wt::WDate{ 1500, 11, 30 } }, + { "1000-11-30", Wt::WDate{ 1000, 11, 30 } }, + { "1899-11-31", Wt::WDate{} }, // invalid day + { "1899-11-00", Wt::WDate{} }, // invalid day + { "1899-13-01", Wt::WDate{} }, // invalid month + { "1899-00-01", Wt::WDate{} }, // invalid month + { "1899-11", Wt::WDate{} }, // missing day + { "1899", Wt::WDate{} }, // missing month and days + { "1600", Wt::WDate{} }, // missing month and days + { "1995/05/09", Wt::WDate{ 1995, 5, 9 } }, + { "1995/01/01", Wt::WDate{ 1995, 1, 1 } }, + { "1900/01/01", Wt::WDate{ 1900, 1, 1 } }, + { "1899/01/01", Wt::WDate{ 1899, 1, 1 } }, + { "1899/12/31", Wt::WDate{ 1899, 12, 31 } }, + { "1899/11/30", Wt::WDate{ 1899, 11, 30 } }, + { "1500/11/30", Wt::WDate{ 1500, 11, 30 } }, + { "1000/11/30", Wt::WDate{ 1000, 11, 30 } }, + { "1899/11/31", Wt::WDate{} }, // invalid day + { "1899/11/00", Wt::WDate{} }, // invalid day + { "1899/13/01", Wt::WDate{} }, // invalid month + { "1899/00/01", Wt::WDate{} }, // invalid month + { "1899/11", Wt::WDate{} }, // missing day + { "1899", Wt::WDate{} }, // missing month and days + { "1600", Wt::WDate{} }, // missing month and days + { "1995/05-09", Wt::WDate{} }, // invalid mixup separators + { "1995-05/09", Wt::WDate{} }, // invalid mixup separators }; for (const TestCase& testCase : testCases) @@ -80,10 +79,9 @@ namespace lms::metadata::utils::tests { struct TestCase { - std::string str; - std::optional result; - } testCases[] - { + std::string str; + std::optional result; + } testCases[]{ { "1995-05-09", 1995 }, { "1995", 1995 }, { "-0", 0 }, @@ -115,11 +113,10 @@ namespace lms::metadata::utils::tests { struct TestCase { - std::string str; + std::string str; std::string expectedArtistName; std::string expectedRole; - } testCases[] - { + } testCases[]{ { "", "", "" }, { "(myrole)", "", "myrole" }, { "(my role)", "", "my role" }, @@ -152,4 +149,4 @@ namespace lms::metadata::utils::tests EXPECT_EQ(performer.role, testCase.expectedRole) << " str was '" << testCase.str << "'"; } } -} \ No newline at end of file +} // namespace lms::metadata::utils::tests \ No newline at end of file diff --git a/src/libs/services/auth/impl/AuthServiceBase.cpp b/src/libs/services/auth/impl/AuthServiceBase.cpp index 9d72e2ddf..3dfed8d25 100644 --- a/src/libs/services/auth/impl/AuthServiceBase.cpp +++ b/src/libs/services/auth/impl/AuthServiceBase.cpp @@ -19,11 +19,10 @@ #include "AuthServiceBase.hpp" -#include +#include "core/ILogger.hpp" #include "database/Db.hpp" #include "database/Session.hpp" #include "database/User.hpp" -#include "core/ILogger.hpp" namespace lms::auth { @@ -31,7 +30,8 @@ namespace lms::auth AuthServiceBase::AuthServiceBase(Db& db) : _db{ db } - {} + { + } UserId AuthServiceBase::getOrCreateUser(std::string_view loginName) { @@ -91,4 +91,4 @@ namespace lms::auth { return _db.getTLSSession(); } -} +} // namespace lms::auth diff --git a/src/libs/services/auth/impl/AuthServiceBase.hpp b/src/libs/services/auth/impl/AuthServiceBase.hpp index 918b43170..bba5f54e7 100644 --- a/src/libs/services/auth/impl/AuthServiceBase.hpp +++ b/src/libs/services/auth/impl/AuthServiceBase.hpp @@ -20,13 +20,14 @@ #pragma once #include + #include "database/UserId.hpp" namespace lms::db { class Db; class Session; -} +} // namespace lms::db namespace lms::auth { @@ -35,12 +36,12 @@ namespace lms::auth protected: AuthServiceBase(db::Db& db); - db::UserId getOrCreateUser(std::string_view loginName); - void onUserAuthenticated(db::UserId userId); + db::UserId getOrCreateUser(std::string_view loginName); + void onUserAuthenticated(db::UserId userId); - db::Session& getDbSession(); + db::Session& getDbSession(); private: db::Db& _db; }; -} +} // namespace lms::auth diff --git a/src/libs/services/auth/impl/AuthTokenService.cpp b/src/libs/services/auth/impl/AuthTokenService.cpp index 38c5fce0b..85196f72c 100644 --- a/src/libs/services/auth/impl/AuthTokenService.cpp +++ b/src/libs/services/auth/impl/AuthTokenService.cpp @@ -23,121 +23,121 @@ #include #include -#include "services/auth/Types.hpp" +#include "core/Exception.hpp" +#include "core/ILogger.hpp" #include "database/AuthToken.hpp" #include "database/Session.hpp" #include "database/User.hpp" -#include "core/Exception.hpp" -#include "core/ILogger.hpp" +#include "services/auth/Types.hpp" namespace lms::auth { - std::unique_ptr createAuthTokenService(db::Db& db, std::size_t maxThrottlerEntries) - { - return std::make_unique(db, maxThrottlerEntries); - } + std::unique_ptr createAuthTokenService(db::Db& db, std::size_t maxThrottlerEntries) + { + return std::make_unique(db, maxThrottlerEntries); + } - static const Wt::Auth::SHA1HashFunction sha1Function; + static const Wt::Auth::SHA1HashFunction sha1Function; - AuthTokenService::AuthTokenService(db::Db& db, std::size_t maxThrottlerEntries) - : AuthServiceBase {db} - , _loginThrottler {maxThrottlerEntries} - { - } + AuthTokenService::AuthTokenService(db::Db& db, std::size_t maxThrottlerEntries) + : AuthServiceBase{ db } + , _loginThrottler{ maxThrottlerEntries } + { + } - std::string - AuthTokenService::createAuthToken(db::UserId userId, const Wt::WDateTime& expiry) - { - const std::string secret {Wt::WRandom::generateId(32)}; - const std::string secretHash {sha1Function.compute(secret, {})}; + std::string + AuthTokenService::createAuthToken(db::UserId userId, const Wt::WDateTime& expiry) + { + const std::string secret{ Wt::WRandom::generateId(32) }; + const std::string secretHash{ sha1Function.compute(secret, {}) }; - db::Session& session {getDbSession()}; + db::Session& session{ getDbSession() }; - auto transaction {session.createWriteTransaction()}; + auto transaction{ session.createWriteTransaction() }; - db::User::pointer user {db::User::find(session, userId)}; - if (!user) - throw Exception {"User deleted"}; + db::User::pointer user{ db::User::find(session, userId) }; + if (!user) + throw Exception{ "User deleted" }; - db::AuthToken::pointer authToken {session.create(secretHash, expiry, user)}; + db::AuthToken::pointer authToken{ session.create(secretHash, expiry, user) }; - LMS_LOG(UI, DEBUG, "Created auth token for user '" << user->getLoginName() << "', expiry = " << expiry.toString()); + LMS_LOG(UI, DEBUG, "Created auth token for user '" << user->getLoginName() << "', expiry = " << expiry.toString()); - if (user->getAuthTokensCount() >= 50) - db::AuthToken::removeExpiredTokens(session, Wt::WDateTime::currentDateTime()); + if (user->getAuthTokensCount() >= 50) + db::AuthToken::removeExpiredTokens(session, Wt::WDateTime::currentDateTime()); - return secret; - } + return secret; + } - std::optional - AuthTokenService::processAuthToken(std::string_view secret) - { - const std::string secretHash {sha1Function.compute(std::string {secret}, {})}; + std::optional + AuthTokenService::processAuthToken(std::string_view secret) + { + const std::string secretHash{ sha1Function.compute(std::string{ secret }, {}) }; - db::Session& session {getDbSession()}; - auto transaction {session.createWriteTransaction()}; + db::Session& session{ getDbSession() }; + auto transaction{ session.createWriteTransaction() }; - db::AuthToken::pointer authToken {db::AuthToken::find(session, secretHash)}; - if (!authToken) - return std::nullopt; + db::AuthToken::pointer authToken{ db::AuthToken::find(session, secretHash) }; + if (!authToken) + return std::nullopt; - if (authToken->getExpiry() < Wt::WDateTime::currentDateTime()) - { - authToken.remove(); - return std::nullopt; - } + if (authToken->getExpiry() < Wt::WDateTime::currentDateTime()) + { + authToken.remove(); + return std::nullopt; + } - LMS_LOG(UI, DEBUG, "Found auth token for user '" << authToken->getUser()->getLoginName() << "'!"); + LMS_LOG(UI, DEBUG, "Found auth token for user '" << authToken->getUser()->getLoginName() << "'!"); - AuthTokenService::AuthTokenProcessResult::AuthTokenInfo res {authToken->getUser()->getId(), authToken->getExpiry()}; - authToken.remove(); + AuthTokenService::AuthTokenProcessResult::AuthTokenInfo res{ authToken->getUser()->getId(), authToken->getExpiry() }; + authToken.remove(); - return res; - } + return res; + } - AuthTokenService::AuthTokenProcessResult - AuthTokenService::processAuthToken(const boost::asio::ip::address& clientAddress, std::string_view tokenValue) - { - // Do not waste too much resource on brute force attacks (optim) - { - std::shared_lock lock {_mutex}; + AuthTokenService::AuthTokenProcessResult + AuthTokenService::processAuthToken(const boost::asio::ip::address& clientAddress, std::string_view tokenValue) + { + // Do not waste too much resource on brute force attacks (optim) + { + std::shared_lock lock{ _mutex }; - if (_loginThrottler.isClientThrottled(clientAddress)) - return AuthTokenProcessResult {AuthTokenProcessResult::State::Throttled}; - } + if (_loginThrottler.isClientThrottled(clientAddress)) + return AuthTokenProcessResult{ AuthTokenProcessResult::State::Throttled }; + } - auto res {processAuthToken(tokenValue)}; - { - std::unique_lock lock {_mutex}; + auto res{ processAuthToken(tokenValue) }; + { + std::unique_lock lock{ _mutex }; - if (_loginThrottler.isClientThrottled(clientAddress)) - return AuthTokenProcessResult {AuthTokenProcessResult::State::Throttled}; + if (_loginThrottler.isClientThrottled(clientAddress)) + return AuthTokenProcessResult{ AuthTokenProcessResult::State::Throttled }; - if (!res) - { - _loginThrottler.onBadClientAttempt(clientAddress); - return AuthTokenProcessResult {AuthTokenProcessResult::State::Denied}; - } + if (!res) + { + _loginThrottler.onBadClientAttempt(clientAddress); + return AuthTokenProcessResult{ AuthTokenProcessResult::State::Denied }; + } - _loginThrottler.onGoodClientAttempt(clientAddress); - onUserAuthenticated(res->userId); - return AuthTokenProcessResult {AuthTokenProcessResult::State::Granted, std::move(*res)}; - } - } + _loginThrottler.onGoodClientAttempt(clientAddress); + onUserAuthenticated(res->userId); + return AuthTokenProcessResult{ AuthTokenProcessResult::State::Granted, std::move(*res) }; + } + } - void - AuthTokenService::clearAuthTokens(db::UserId userId) - { - db::Session& session {getDbSession()}; + void + AuthTokenService::clearAuthTokens(db::UserId userId) + { + db::Session& session{ getDbSession() }; - auto transaction {session.createWriteTransaction()}; + auto transaction{ session.createWriteTransaction() }; - db::User::pointer user {db::User::find(session, userId)}; - if (!user) - throw Exception {"User deleted"}; + db::User::pointer user{ db::User::find(session, userId) }; + if (!user) + throw Exception{ "User deleted" }; - user.modify()->clearAuthTokens(); - } + user.modify()->clearAuthTokens(); + } } // namespace lms::auth diff --git a/src/libs/services/auth/impl/AuthTokenService.hpp b/src/libs/services/auth/impl/AuthTokenService.hpp index 18234c240..f3160abad 100644 --- a/src/libs/services/auth/impl/AuthTokenService.hpp +++ b/src/libs/services/auth/impl/AuthTokenService.hpp @@ -22,34 +22,35 @@ #include #include "services/auth/IAuthTokenService.hpp" + #include "AuthServiceBase.hpp" #include "LoginThrottler.hpp" namespace lms::db { - class Session; + class Session; } namespace lms::auth { - class AuthTokenService : public IAuthTokenService, public AuthServiceBase - { - public: - AuthTokenService(db::Db& db, std::size_t maxThrottlerEntries); - - AuthTokenService(const AuthTokenService&) = delete; - AuthTokenService& operator=(const AuthTokenService&) = delete; - AuthTokenService(AuthTokenService&&) = delete; - AuthTokenService& operator=(AuthTokenService&&) = delete; - - private: - AuthTokenProcessResult processAuthToken(const boost::asio::ip::address& clientAddress, std::string_view tokenValue) override; - std::string createAuthToken(db::UserId userId, const Wt::WDateTime& expiry) override; - void clearAuthTokens(db::UserId userId) override; - - std::optional processAuthToken(std::string_view secret); - - std::shared_mutex _mutex; - LoginThrottler _loginThrottler; - }; -} + class AuthTokenService : public IAuthTokenService, public AuthServiceBase + { + public: + AuthTokenService(db::Db& db, std::size_t maxThrottlerEntries); + + AuthTokenService(const AuthTokenService&) = delete; + AuthTokenService& operator=(const AuthTokenService&) = delete; + AuthTokenService(AuthTokenService&&) = delete; + AuthTokenService& operator=(AuthTokenService&&) = delete; + + private: + AuthTokenProcessResult processAuthToken(const boost::asio::ip::address& clientAddress, std::string_view tokenValue) override; + std::string createAuthToken(db::UserId userId, const Wt::WDateTime& expiry) override; + void clearAuthTokens(db::UserId userId) override; + + std::optional processAuthToken(std::string_view secret); + + std::shared_mutex _mutex; + LoginThrottler _loginThrottler; + }; +} // namespace lms::auth diff --git a/src/libs/services/auth/impl/EnvService.cpp b/src/libs/services/auth/impl/EnvService.cpp index f0b32c60c..113303c76 100644 --- a/src/libs/services/auth/impl/EnvService.cpp +++ b/src/libs/services/auth/impl/EnvService.cpp @@ -20,16 +20,17 @@ #include "services/auth/IEnvService.hpp" #include "services/auth/Types.hpp" + #include "http-headers/HttpHeadersEnvService.hpp" namespace lms::auth { - std::unique_ptr - createEnvService(std::string_view backendName, db::Db& db) - { - if (backendName == "http-headers") - return std::make_unique(db); + std::unique_ptr + createEnvService(std::string_view backendName, db::Db& db) + { + if (backendName == "http-headers") + return std::make_unique(db); - throw Exception {"Authentication backend '" + std::string {backendName} + "' is not supported!"}; - } -} + throw Exception{ "Authentication backend '" + std::string{ backendName } + "' is not supported!" }; + } +} // namespace lms::auth diff --git a/src/libs/services/auth/impl/LoginThrottler.cpp b/src/libs/services/auth/impl/LoginThrottler.cpp index be3e5be8b..c04364fd6 100644 --- a/src/libs/services/auth/impl/LoginThrottler.cpp +++ b/src/libs/services/auth/impl/LoginThrottler.cpp @@ -17,8 +17,6 @@ * along with LMS. If not, see . */ - /* This file contains some classes in order to get info from file using the libavconv */ - #include "LoginThrottler.hpp" #include "core/ILogger.hpp" @@ -44,13 +42,13 @@ namespace lms::auth { return address.is_v6() ? getAddressWithMask(address.to_v6(), 64) : address; } - } + } // namespace void LoginThrottler::removeOutdatedEntries() { const Wt::WDateTime now{ Wt::WDateTime::currentDateTime() }; - for (auto it{ std::begin(_attemptsInfo) }; it != std::end(_attemptsInfo); ) + for (auto it{ std::begin(_attemptsInfo) }; it != std::end(_attemptsInfo);) { if (it->second.nextAttempt <= now) it = _attemptsInfo.erase(it); @@ -110,4 +108,4 @@ namespace lms::auth return it->second.nextAttempt > Wt::WDateTime::currentDateTime(); } -} \ No newline at end of file +} // namespace lms::auth \ No newline at end of file diff --git a/src/libs/services/auth/impl/LoginThrottler.hpp b/src/libs/services/auth/impl/LoginThrottler.hpp index 035d092d1..42a20fb89 100644 --- a/src/libs/services/auth/impl/LoginThrottler.hpp +++ b/src/libs/services/auth/impl/LoginThrottler.hpp @@ -24,34 +24,34 @@ #include -#include "core/NetAddress.hpp" #include "core/Exception.hpp" +#include "core/NetAddress.hpp" namespace lms::auth { - class LoginThrottler - { - public: - LoginThrottler(std::size_t maxEntries) : _maxEntries {maxEntries} {} - - // user must lock these calls to avoid races - bool isClientThrottled(const boost::asio::ip::address& address) const; - void onBadClientAttempt(const boost::asio::ip::address& address); - void onGoodClientAttempt(const boost::asio::ip::address& address); - - private: - void removeOutdatedEntries(); - - const std::size_t _maxEntries; - static constexpr std::size_t _maxBadConsecutiveAttemptCount {5}; - static constexpr std::chrono::seconds _throttlingDuration {3}; - - struct AttemptInfo - { - Wt::WDateTime nextAttempt; - std::size_t badConsecutiveAttemptCount{}; - }; - std::unordered_map _attemptsInfo; - }; -} // Auth - + class LoginThrottler + { + public: + LoginThrottler(std::size_t maxEntries) + : _maxEntries{ maxEntries } {} + + // user must lock these calls to avoid races + bool isClientThrottled(const boost::asio::ip::address& address) const; + void onBadClientAttempt(const boost::asio::ip::address& address); + void onGoodClientAttempt(const boost::asio::ip::address& address); + + private: + void removeOutdatedEntries(); + + const std::size_t _maxEntries; + static constexpr std::size_t _maxBadConsecutiveAttemptCount{ 5 }; + static constexpr std::chrono::seconds _throttlingDuration{ 3 }; + + struct AttemptInfo + { + Wt::WDateTime nextAttempt; + std::size_t badConsecutiveAttemptCount{}; + }; + std::unordered_map _attemptsInfo; + }; +} // namespace lms::auth diff --git a/src/libs/services/auth/impl/PasswordServiceBase.cpp b/src/libs/services/auth/impl/PasswordServiceBase.cpp index 5290b78f6..4628e845b 100644 --- a/src/libs/services/auth/impl/PasswordServiceBase.cpp +++ b/src/libs/services/auth/impl/PasswordServiceBase.cpp @@ -24,73 +24,72 @@ #include "internal/InternalPasswordService.hpp" #ifdef LMS_SUPPORT_PAM -#include "pam/PAMPasswordService.hpp" + #include "pam/PAMPasswordService.hpp" #endif // LMS_SUPPORT_PAM -#include "services/auth/Types.hpp" -#include "database/Session.hpp" -#include "database/User.hpp" #include "core/Exception.hpp" #include "core/ILogger.hpp" +#include "database/Session.hpp" +#include "database/User.hpp" +#include "services/auth/Types.hpp" namespace lms::auth { - static const Wt::Auth::SHA1HashFunction sha1Function; + static const Wt::Auth::SHA1HashFunction sha1Function; - std::unique_ptr - createPasswordService(std::string_view passwordAuthenticationBackend, db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService) - { - if (passwordAuthenticationBackend == "internal") - return std::make_unique(db, maxThrottlerEntries, authTokenService); + std::unique_ptr + createPasswordService(std::string_view passwordAuthenticationBackend, db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService) + { + if (passwordAuthenticationBackend == "internal") + return std::make_unique(db, maxThrottlerEntries, authTokenService); #ifdef LMS_SUPPORT_PAM - else if (passwordAuthenticationBackend == "pam") - return std::make_unique(db, maxThrottlerEntries, authTokenService); + else if (passwordAuthenticationBackend == "pam") + return std::make_unique(db, maxThrottlerEntries, authTokenService); #endif // LMS_SUPPORT_PAM - throw Exception {"Authentication backend '" + std::string {passwordAuthenticationBackend} + "' is not supported!"}; - } - - PasswordServiceBase::PasswordServiceBase(db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService) - : AuthServiceBase {db} - , _loginThrottler {maxThrottlerEntries} - , _authTokenService {authTokenService} - { - } - - PasswordServiceBase::CheckResult - PasswordServiceBase::checkUserPassword(const boost::asio::ip::address& clientAddress, std::string_view loginName, std::string_view password) - { - LMS_LOG(AUTH, DEBUG, "Checking password for user '" << loginName << "'"); - - // Do not waste too much resource on brute force attacks (optim) - { - std::shared_lock lock {_mutex}; - - if (_loginThrottler.isClientThrottled(clientAddress)) - return {CheckResult::State::Throttled}; - } - - const bool match {checkUserPassword(loginName, password)}; - { - std::unique_lock lock {_mutex}; - - if (_loginThrottler.isClientThrottled(clientAddress)) - return {CheckResult::State::Throttled}; - - if (match) - { - _loginThrottler.onGoodClientAttempt(clientAddress); - - const db::UserId userId {getOrCreateUser(loginName)}; - onUserAuthenticated(userId); - return {CheckResult::State::Granted, userId}; - } - else - { - _loginThrottler.onBadClientAttempt(clientAddress); - return {CheckResult::State::Denied}; - } - } - } + throw Exception{ "Authentication backend '" + std::string{ passwordAuthenticationBackend } + "' is not supported!" }; + } + + PasswordServiceBase::PasswordServiceBase(db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService) + : AuthServiceBase{ db } + , _loginThrottler{ maxThrottlerEntries } + , _authTokenService{ authTokenService } + { + } + + PasswordServiceBase::CheckResult + PasswordServiceBase::checkUserPassword(const boost::asio::ip::address& clientAddress, std::string_view loginName, std::string_view password) + { + LMS_LOG(AUTH, DEBUG, "Checking password for user '" << loginName << "'"); + + // Do not waste too much resource on brute force attacks (optim) + { + std::shared_lock lock{ _mutex }; + + if (_loginThrottler.isClientThrottled(clientAddress)) + return { CheckResult::State::Throttled }; + } + + const bool match{ checkUserPassword(loginName, password) }; + { + std::unique_lock lock{ _mutex }; + + if (_loginThrottler.isClientThrottled(clientAddress)) + return { CheckResult::State::Throttled }; + + if (match) + { + _loginThrottler.onGoodClientAttempt(clientAddress); + + const db::UserId userId{ getOrCreateUser(loginName) }; + onUserAuthenticated(userId); + return { CheckResult::State::Granted, userId }; + } + else + { + _loginThrottler.onBadClientAttempt(clientAddress); + return { CheckResult::State::Denied }; + } + } + } } // namespace lms::auth - diff --git a/src/libs/services/auth/impl/PasswordServiceBase.hpp b/src/libs/services/auth/impl/PasswordServiceBase.hpp index da431e84f..1c32f5b9b 100644 --- a/src/libs/services/auth/impl/PasswordServiceBase.hpp +++ b/src/libs/services/auth/impl/PasswordServiceBase.hpp @@ -21,40 +21,40 @@ #include -#include "services/auth/IPasswordService.hpp" #include "AuthServiceBase.hpp" #include "LoginThrottler.hpp" +#include "services/auth/IPasswordService.hpp" namespace lms::db { - class Db; - class Session; -} + class Db; + class Session; +} // namespace lms::db namespace lms::auth { - class PasswordServiceBase : public IPasswordService, public AuthServiceBase - { - public: - PasswordServiceBase(db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService); - - PasswordServiceBase(const PasswordServiceBase&) = delete; - PasswordServiceBase& operator=(const PasswordServiceBase&) = delete; - PasswordServiceBase(PasswordServiceBase&&) = delete; - PasswordServiceBase& operator=(PasswordServiceBase&&) = delete; - - protected: - IAuthTokenService& getAuthTokenService() { return _authTokenService; } - - private: - virtual bool checkUserPassword(std::string_view loginName, std::string_view password) = 0; - - CheckResult checkUserPassword(const boost::asio::ip::address& clientAddress, - std::string_view loginName, - std::string_view password) override; - - std::shared_mutex _mutex; - LoginThrottler _loginThrottler; - IAuthTokenService& _authTokenService; - }; -} + class PasswordServiceBase : public IPasswordService, public AuthServiceBase + { + public: + PasswordServiceBase(db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService); + + PasswordServiceBase(const PasswordServiceBase&) = delete; + PasswordServiceBase& operator=(const PasswordServiceBase&) = delete; + PasswordServiceBase(PasswordServiceBase&&) = delete; + PasswordServiceBase& operator=(PasswordServiceBase&&) = delete; + + protected: + IAuthTokenService& getAuthTokenService() { return _authTokenService; } + + private: + virtual bool checkUserPassword(std::string_view loginName, std::string_view password) = 0; + + CheckResult checkUserPassword(const boost::asio::ip::address& clientAddress, + std::string_view loginName, + std::string_view password) override; + + std::shared_mutex _mutex; + LoginThrottler _loginThrottler; + IAuthTokenService& _authTokenService; + }; +} // namespace lms::auth diff --git a/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.hpp b/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.hpp index 86cf48d08..0e3f9ffea 100644 --- a/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.hpp +++ b/src/libs/services/auth/impl/http-headers/HttpHeadersEnvService.hpp @@ -20,21 +20,21 @@ #pragma once #include "services/auth/IEnvService.hpp" + #include "AuthServiceBase.hpp" namespace lms::auth { - class HttpHeadersEnvService : public IEnvService, public AuthServiceBase - { - public: - HttpHeadersEnvService(db::Db& db); + class HttpHeadersEnvService : public IEnvService, public AuthServiceBase + { + public: + HttpHeadersEnvService(db::Db& db); - private: - CheckResult processEnv(const Wt::WEnvironment& env) override; - CheckResult processRequest(const Wt::Http::Request& request) override; + private: + CheckResult processEnv(const Wt::WEnvironment& env) override; + CheckResult processRequest(const Wt::Http::Request& request) override; - std::string _fieldName; - }; + std::string _fieldName; + }; } // namespace lms::auth - diff --git a/src/libs/services/auth/impl/internal/InternalPasswordService.cpp b/src/libs/services/auth/impl/internal/InternalPasswordService.cpp index 9aecde91a..47b9fefea 100644 --- a/src/libs/services/auth/impl/internal/InternalPasswordService.cpp +++ b/src/libs/services/auth/impl/internal/InternalPasswordService.cpp @@ -18,14 +18,15 @@ */ #include "InternalPasswordService.hpp" + #include -#include "services/auth/IAuthTokenService.hpp" -#include "services/auth/Types.hpp" -#include "database/Session.hpp" -#include "database/User.hpp" #include "core/Exception.hpp" #include "core/ILogger.hpp" +#include "database/Session.hpp" +#include "database/User.hpp" +#include "services/auth/IAuthTokenService.hpp" +#include "services/auth/Types.hpp" namespace lms::auth { @@ -120,14 +121,13 @@ namespace lms::auth { const std::string salt{ Wt::WRandom::generateId(32) }; - return { salt, _hashFunc.compute(std::string {password}, salt) }; + return { salt, _hashFunc.compute(std::string{ password }, salt) }; } void - InternalPasswordService::hashRandomPassword() const + InternalPasswordService::hashRandomPassword() const { hashPassword(Wt::WRandom::generateId(32)); } } // namespace lms::auth - diff --git a/src/libs/services/auth/impl/internal/InternalPasswordService.hpp b/src/libs/services/auth/impl/internal/InternalPasswordService.hpp index c913d7565..9c3e07d4d 100644 --- a/src/libs/services/auth/impl/internal/InternalPasswordService.hpp +++ b/src/libs/services/auth/impl/internal/InternalPasswordService.hpp @@ -23,8 +23,9 @@ #include #include "database/User.hpp" -#include "PasswordServiceBase.hpp" + #include "LoginThrottler.hpp" +#include "PasswordServiceBase.hpp" namespace lms::auth { @@ -36,17 +37,17 @@ namespace lms::auth InternalPasswordService(db::Db& db, std::size_t maxThrottlerEntries, IAuthTokenService& authTokenService); private: - bool checkUserPassword(std::string_view loginName, std::string_view password) override; + bool checkUserPassword(std::string_view loginName, std::string_view password) override; - bool canSetPasswords() const override; - PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override; - void setPassword(db::UserId userId, std::string_view newPassword) override; + bool canSetPasswords() const override; + PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override; + void setPassword(db::UserId userId, std::string_view newPassword) override; - db::User::PasswordHash hashPassword(std::string_view password) const; - void hashRandomPassword() const; + db::User::PasswordHash hashPassword(std::string_view password) const; + void hashRandomPassword() const; - const Wt::Auth::BCryptHashFunction _hashFunc{ 7 }; // TODO parametrize this - Wt::Auth::PasswordStrengthValidator _validator; + const Wt::Auth::BCryptHashFunction _hashFunc{ 7 }; // TODO parametrize this + Wt::Auth::PasswordStrengthValidator _validator; }; -} +} // namespace lms::auth diff --git a/src/libs/services/auth/impl/pam/PAMPasswordService.cpp b/src/libs/services/auth/impl/pam/PAMPasswordService.cpp index 3d7d6be49..12cfe99ce 100644 --- a/src/libs/services/auth/impl/pam/PAMPasswordService.cpp +++ b/src/libs/services/auth/impl/pam/PAMPasswordService.cpp @@ -20,15 +20,15 @@ #include "PAMPasswordService.hpp" #ifndef LMS_SUPPORT_PAM -#error "Should not compile this" + #error "Should not compile this" #endif #include #include -#include "services/auth/Types.hpp" -#include "database/Session.hpp" #include "core/ILogger.hpp" +#include "database/Session.hpp" +#include "services/auth/Types.hpp" namespace lms::auth { @@ -53,7 +53,7 @@ namespace lms::auth public: PAMContext(std::string_view loginName) { - int err{ pam_start("lms", std::string {loginName}.c_str(), &_conv, &_pamh) }; + int err{ pam_start("lms", std::string{ loginName }.c_str(), &_conv, &_pamh) }; if (err != PAM_SUCCESS) throw PAMError{ "start failed", _pamh, err }; } @@ -92,7 +92,8 @@ namespace lms::auth class AuthenticateConvContext final : public ConvContext { public: - AuthenticateConvContext(std::string_view password) : _password{ password } {} + AuthenticateConvContext(std::string_view password) + : _password{ password } {} std::string_view getPassword() const { return _password; } @@ -160,7 +161,7 @@ namespace lms::auth pam_conv _conv{ &PAMContext::conv, this }; pam_handle_t* _pamh{}; }; - } + } // namespace bool PAMPasswordService::checkUserPassword(std::string_view loginName, std::string_view password) { @@ -197,4 +198,3 @@ namespace lms::auth } } // namespace lms::auth - diff --git a/src/libs/services/auth/impl/pam/PAMPasswordService.hpp b/src/libs/services/auth/impl/pam/PAMPasswordService.hpp index b2c2b80e4..38bb6d53e 100644 --- a/src/libs/services/auth/impl/pam/PAMPasswordService.hpp +++ b/src/libs/services/auth/impl/pam/PAMPasswordService.hpp @@ -25,15 +25,15 @@ namespace lms::auth { - class PAMPasswordService: public PasswordServiceBase - { - public: - using PasswordServiceBase::PasswordServiceBase; + class PAMPasswordService : public PasswordServiceBase + { + public: + using PasswordServiceBase::PasswordServiceBase; - private: - bool checkUserPassword(std::string_view loginName,std::string_view password) override; - bool canSetPasswords() const override; - PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override; - void setPassword(db::UserId userId, std::string_view newPassword) override; - }; -} + private: + bool checkUserPassword(std::string_view loginName, std::string_view password) override; + bool canSetPasswords() const override; + PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view loginName, const PasswordValidationContext& context) const override; + void setPassword(db::UserId userId, std::string_view newPassword) override; + }; +} // namespace lms::auth diff --git a/src/libs/services/auth/include/services/auth/IAuthTokenService.hpp b/src/libs/services/auth/include/services/auth/IAuthTokenService.hpp index 65941cdee..f18d52444 100644 --- a/src/libs/services/auth/include/services/auth/IAuthTokenService.hpp +++ b/src/libs/services/auth/include/services/auth/IAuthTokenService.hpp @@ -17,58 +17,56 @@ * along with LMS. If not, see . */ -/* This file contains some classes in order to get info from file using the libavconv */ - #pragma once +#include +#include #include #include #include -#include -#include #include "database/UserId.hpp" namespace lms::db { - class Db; - class User; -} + class Db; + class User; +} // namespace lms::db namespace lms::auth { - class IAuthTokenService - { - public: - virtual ~IAuthTokenService() = default; + class IAuthTokenService + { + public: + virtual ~IAuthTokenService() = default; - // Auth Token services - struct AuthTokenProcessResult - { - enum class State - { - Granted, - Throttled, - Denied, - }; + // Auth Token services + struct AuthTokenProcessResult + { + enum class State + { + Granted, + Throttled, + Denied, + }; - struct AuthTokenInfo - { - db::UserId userId; - Wt::WDateTime expiry; - }; + struct AuthTokenInfo + { + db::UserId userId; + Wt::WDateTime expiry; + }; - State state {State::Denied}; - std::optional authTokenInfo {}; - }; + State state{ State::Denied }; + std::optional authTokenInfo{}; + }; - // Provided token is only accepted once - virtual AuthTokenProcessResult processAuthToken(const boost::asio::ip::address& clientAddress, std::string_view tokenValue) = 0; + // Provided token is only accepted once + virtual AuthTokenProcessResult processAuthToken(const boost::asio::ip::address& clientAddress, std::string_view tokenValue) = 0; - // Returns a one time token - virtual std::string createAuthToken(db::UserId userid, const Wt::WDateTime& expiry) = 0; - virtual void clearAuthTokens(db::UserId userid) = 0; - }; + // Returns a one time token + virtual std::string createAuthToken(db::UserId userid, const Wt::WDateTime& expiry) = 0; + virtual void clearAuthTokens(db::UserId userid) = 0; + }; - std::unique_ptr createAuthTokenService(db::Db& db, std::size_t maxThrottlerEntryCount); -} + std::unique_ptr createAuthTokenService(db::Db& db, std::size_t maxThrottlerEntryCount); +} // namespace lms::auth diff --git a/src/libs/services/auth/include/services/auth/IEnvService.hpp b/src/libs/services/auth/include/services/auth/IEnvService.hpp index d35b6cefc..6dd5d85b4 100644 --- a/src/libs/services/auth/include/services/auth/IEnvService.hpp +++ b/src/libs/services/auth/include/services/auth/IEnvService.hpp @@ -26,44 +26,44 @@ namespace lms::db { - class Db; - class Session; -} + class Db; + class Session; +} // namespace lms::db namespace Wt { - class WEnvironment; + class WEnvironment; } namespace Wt::Http { - class Request; + class Request; } namespace lms::auth { - class IEnvService - { - public: - virtual ~IEnvService() = default; + class IEnvService + { + public: + virtual ~IEnvService() = default; - // Auth Token services - struct CheckResult - { - enum class State - { - Granted, - Denied, - Throttled, - }; + // Auth Token services + struct CheckResult + { + enum class State + { + Granted, + Denied, + Throttled, + }; - State state {State::Denied}; - std::optional userId {}; - }; + State state{ State::Denied }; + std::optional userId{}; + }; - virtual CheckResult processEnv(const Wt::WEnvironment& env) = 0; - virtual CheckResult processRequest(const Wt::Http::Request& request) = 0; - }; + virtual CheckResult processEnv(const Wt::WEnvironment& env) = 0; + virtual CheckResult processRequest(const Wt::Http::Request& request) = 0; + }; - std::unique_ptr createEnvService(std::string_view backendName, db::Db& db); + std::unique_ptr createEnvService(std::string_view backendName, db::Db& db); } // namespace lms::auth diff --git a/src/libs/services/auth/include/services/auth/IPasswordService.hpp b/src/libs/services/auth/include/services/auth/IPasswordService.hpp index 9138e62c6..21b12fab5 100644 --- a/src/libs/services/auth/include/services/auth/IPasswordService.hpp +++ b/src/libs/services/auth/include/services/auth/IPasswordService.hpp @@ -19,59 +19,59 @@ #pragma once -#include #include +#include -#include -#include #include +#include +#include -#include "services/auth/Types.hpp" #include "database/UserId.hpp" +#include "services/auth/Types.hpp" namespace lms::db { - class Db; - class User; -} + class Db; + class User; +} // namespace lms::db namespace lms::auth { - class IAuthTokenService; - - class IPasswordService - { - public: - virtual ~IPasswordService() = default; + class IAuthTokenService; - struct CheckResult - { - enum class State - { - Granted, - Denied, - Throttled, - }; - State state {State::Denied}; - std::optional userId {}; - std::optional expiry {}; - }; - virtual CheckResult checkUserPassword(const boost::asio::ip::address& clientAddress, - std::string_view loginName, - std::string_view password) = 0; + class IPasswordService + { + public: + virtual ~IPasswordService() = default; - virtual bool canSetPasswords() const = 0; + struct CheckResult + { + enum class State + { + Granted, + Denied, + Throttled, + }; + State state{ State::Denied }; + std::optional userId{}; + std::optional expiry{}; + }; + virtual CheckResult checkUserPassword(const boost::asio::ip::address& clientAddress, + std::string_view loginName, + std::string_view password) + = 0; - enum class PasswordAcceptabilityResult - { - OK, - TooWeak, - MustMatchLoginName, - }; - virtual PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view password, const PasswordValidationContext& context) const = 0; - virtual void setPassword(db::UserId userId, std::string_view newPassword) = 0; - }; + virtual bool canSetPasswords() const = 0; - std::unique_ptr createPasswordService(std::string_view authPasswordBackend, db::Db& db, std::size_t maxThrottlerEntryCount, IAuthTokenService& authTokenService); -} + enum class PasswordAcceptabilityResult + { + OK, + TooWeak, + MustMatchLoginName, + }; + virtual PasswordAcceptabilityResult checkPasswordAcceptability(std::string_view password, const PasswordValidationContext& context) const = 0; + virtual void setPassword(db::UserId userId, std::string_view newPassword) = 0; + }; + std::unique_ptr createPasswordService(std::string_view authPasswordBackend, db::Db& db, std::size_t maxThrottlerEntryCount, IAuthTokenService& authTokenService); +} // namespace lms::auth diff --git a/src/libs/services/auth/include/services/auth/Types.hpp b/src/libs/services/auth/include/services/auth/Types.hpp index 7d4b67aac..4122b2d24 100644 --- a/src/libs/services/auth/include/services/auth/Types.hpp +++ b/src/libs/services/auth/include/services/auth/Types.hpp @@ -20,50 +20,54 @@ #pragma once #include -#include "database/Types.hpp" + #include "core/Exception.hpp" +#include "database/Types.hpp" namespace lms::auth { - class Exception : public core::LmsException - { - using core::LmsException::LmsException; - }; - - class NotImplementedException : public Exception - { - public: - NotImplementedException() : Exception {"Not implemented"} {} - }; + class Exception : public core::LmsException + { + using core::LmsException::LmsException; + }; - class UserNotFoundException : public Exception - { - public: - UserNotFoundException() : Exception {"User not found"} {} - }; + class NotImplementedException : public Exception + { + public: + NotImplementedException() + : Exception{ "Not implemented" } {} + }; - struct PasswordValidationContext - { - std::string loginName; - db::UserType userType; - }; + class UserNotFoundException : public Exception + { + public: + UserNotFoundException() + : Exception{ "User not found" } {} + }; - class PasswordException : public Exception - { - public: - using Exception::Exception; - }; + struct PasswordValidationContext + { + std::string loginName; + db::UserType userType; + }; - class PasswordTooWeakException : public PasswordException - { - public: - PasswordTooWeakException() : PasswordException {"Password too weak"} {} - }; + class PasswordException : public Exception + { + public: + using Exception::Exception; + }; - class PasswordMustMatchLoginNameException : public PasswordException - { - public: - PasswordMustMatchLoginNameException() : PasswordException {"Password must match login name"} {} - }; -} + class PasswordTooWeakException : public PasswordException + { + public: + PasswordTooWeakException() + : PasswordException{ "Password too weak" } {} + }; + class PasswordMustMatchLoginNameException : public PasswordException + { + public: + PasswordMustMatchLoginNameException() + : PasswordException{ "Password must match login name" } {} + }; +} // namespace lms::auth diff --git a/src/libs/services/cover/impl/CoverService.cpp b/src/libs/services/cover/impl/CoverService.cpp index c3a041761..7c621aed6 100644 --- a/src/libs/services/cover/impl/CoverService.cpp +++ b/src/libs/services/cover/impl/CoverService.cpp @@ -22,21 +22,20 @@ #include #include "av/IAudioFile.hpp" - -#include "database/Db.hpp" -#include "database/Artist.hpp" -#include "database/Release.hpp" -#include "database/Session.hpp" -#include "database/Track.hpp" - -#include "image/Exception.hpp" -#include "image/Image.hpp" #include "core/IConfig.hpp" #include "core/ILogger.hpp" #include "core/Path.hpp" #include "core/Random.hpp" #include "core/String.hpp" #include "core/Utils.hpp" +#include "database/Artist.hpp" +#include "database/Db.hpp" +#include "database/Image.hpp" +#include "database/Release.hpp" +#include "database/Session.hpp" +#include "database/Track.hpp" +#include "image/Exception.hpp" +#include "image/Image.hpp" namespace lms::cover { @@ -80,23 +79,10 @@ namespace lms::cover std::vector res; core::Service::get()->visitStrings("cover-preferred-file-names", - [&res](std::string_view fileName) - { - res.emplace_back(fileName); - }, { "cover", "front" }); - - return res; - } - - std::vector constructArtistFileNames() - { - std::vector res; - - core::Service::get()->visitStrings("artist-image-file-names", - [&res](std::string_view fileName) - { + [&res](std::string_view fileName) { res.emplace_back(fileName); - }, { "artist" }); + }, + { "cover", "front" }); return res; } @@ -105,7 +91,7 @@ namespace lms::cover { return (std::find(std::cbegin(extensions), std::cend(extensions), file.extension()) != std::cend(extensions)); } - } + } // namespace std::unique_ptr createCoverService(db::Db& db, const std::filesystem::path& defaultSvgCoverPath) { @@ -120,7 +106,6 @@ namespace lms::cover , _cache{ core::Service::get()->getULong("cover-max-cache-size", 30) * 1000 * 1000 } , _maxFileSize{ core::Service::get()->getULong("cover-max-file-size", 10) * 1000 * 1000 } , _preferredFileNames{ constructPreferredFileNames() } - , _artistFileNames{ constructArtistFileNames() } { setJpegQuality(core::Service::get()->getULong("cover-jpeg-quality", 75)); @@ -136,22 +121,21 @@ namespace lms::cover { std::unique_ptr image; - input.visitAttachedPictures([&](const av::Picture& picture) + input.visitAttachedPictures([&](const av::Picture& picture) { + if (image) + return; + + try { - if (image) - return; - - try - { - std::unique_ptr rawImage{ decodeImage(picture.data, picture.dataSize) }; - rawImage->resize(width); - image = rawImage->encodeToJPEG(_jpegQuality); - } - catch (const image::Exception& e) - { - LMS_LOG(COVER, ERROR, "Cannot read embedded cover: " << e.what()); - } - }); + std::unique_ptr rawImage{ decodeImage(picture.data, picture.dataSize) }; + rawImage->resize(width); + image = rawImage->encodeToJPEG(_jpegQuality); + } + catch (const image::Exception& e) + { + LMS_LOG(COVER, ERROR, "Cannot read embedded cover: " << e.what()); + } + }); return image; } @@ -183,19 +167,18 @@ namespace lms::cover { const std::multimap coverPaths{ getCoverPaths(directory) }; - auto tryLoadImageFromFilename = [&](std::string_view fileName) + auto tryLoadImageFromFilename = [&](std::string_view fileName) { + std::unique_ptr image; + + auto range{ coverPaths.equal_range(std::string{ fileName }) }; + for (auto it{ range.first }; it != range.second; ++it) { - std::unique_ptr image; - - auto range{ coverPaths.equal_range(std::string {fileName}) }; - for (auto it{ range.first }; it != range.second; ++it) - { - image = getFromCoverFile(it->second, width); - if (image) - break; - } - return image; - }; + image = getFromCoverFile(it->second, width); + if (image) + break; + } + return image; + }; std::unique_ptr image; @@ -353,14 +336,13 @@ namespace lms::cover Session& session{ _db.getTLSSession() }; - auto getReleaseInfo{ [&] - { + auto getReleaseInfo{ [&] { std::optional res; auto transaction{ session.createReadTransaction() }; // get a track in this release, consider the release is in a single directory - const auto tracks{ Track::find(session, Track::FindParameters {}.setRelease(releaseId).setRange(Range{ 0, 1 }).setSortMethod(TrackSortMethod::Release)) }; + const auto tracks{ Track::find(session, Track::FindParameters{}.setRelease(releaseId).setRange(Range{ 0, 1 }).setSortMethod(TrackSortMethod::Release)) }; if (!tracks.results.empty()) { const Track::pointer& track{ tracks.results.front() }; @@ -394,88 +376,15 @@ namespace lms::cover if (artistImage) return artistImage; - std::string artistName; - std::string artistMBID; - - std::set releasePaths; - std::set multiArtistReleasePaths; - { Session& session{ _db.getTLSSession() }; auto transaction{ session.createReadTransaction() }; - const Artist::pointer artist{ Artist::find(session, artistId) }; - if (!artist) - return artistImage; - - artistName = artist->getName(); - if (auto mbid{ artist->getMBID() }) - artistMBID = mbid->getAsString(); - - Track::FindParameters params; - params.setArtist(artistId, { TrackArtistLinkType::ReleaseArtist }); - - Track::find(session, params, [&](const Track::pointer& track) - { - Artist::FindParameters artistFindParams; - artistFindParams.setTrack(track->getId()); - artistFindParams.setLinkType(TrackArtistLinkType::ReleaseArtist); - - const auto releaseArtists{ Artist::findIds(session, artistFindParams) }; - if (releaseArtists.results.size() == 1) - releasePaths.insert(track->getAbsoluteFilePath().parent_path()); - else - multiArtistReleasePaths.insert(track->getAbsoluteFilePath().parent_path()); - }); - } - - std::vector artistFileNames; - if (!artistMBID.empty()) - artistFileNames.push_back(artistMBID); - artistFileNames.push_back(artistName); - - std::vector artistFileNamesWithGenericNames{ artistFileNames }; - artistFileNamesWithGenericNames.insert(artistFileNamesWithGenericNames.end(), std::cbegin(_artistFileNames), std::cend(_artistFileNames)); - - // Expect layout like this: - // ReleaseArtist/Release/Tracks' - // /artist-mbid.jpg - // /artist-name.jpg - // /artist.jpg - if (!releasePaths.empty()) - { - const std::filesystem::path artistPath{ releasePaths.size() == 1 ? releasePaths.begin()->parent_path() : core::pathUtils::getLongestCommonPath(std::cbegin(releasePaths), std::cend(releasePaths)) }; - artistImage = getFromDirectory(artistPath, width, artistFileNamesWithGenericNames, false); - } - - // Expect layout like this: - // ReleaseArtist/Release/Tracks' - // /artist-mbid.jpg - // /artist-name.jpg - // /artist.jpg - if (!artistImage) - { - for (const std::filesystem::path& releasePath : releasePaths) - { - artistImage = getFromDirectory(releasePath, width, artistFileNamesWithGenericNames, false); - if (artistImage) - break; - } - } - - // Expect layout like this: - // Only search for the artist's name in the release path, as we can't map a generic name to several artists - // ReleaseArtist/Release/Tracks' - // /artist-name.jpg - // /artist-mbid.jpg - if (!artistImage) - { - for (const std::filesystem::path& releasePath : multiArtistReleasePaths) + if (const Artist::pointer artist{ Artist::find(session, artistId) }) { - artistImage = getFromDirectory(releasePath, width, artistFileNames, false); - if (artistImage) - break; + if (const db::Image::pointer image{ artist->getImage() }) + artistImage = getFromCoverFile(image->getAbsoluteFilePath(), width); } } @@ -487,6 +396,7 @@ namespace lms::cover void CoverService::flushCache() { + _cache.flush(); } void CoverService::setJpegQuality(unsigned quality) @@ -497,4 +407,3 @@ namespace lms::cover } } // namespace lms::cover - diff --git a/src/libs/services/cover/impl/CoverService.hpp b/src/libs/services/cover/impl/CoverService.hpp index 946d38451..a5899bc49 100644 --- a/src/libs/services/cover/impl/CoverService.hpp +++ b/src/libs/services/cover/impl/CoverService.hpp @@ -23,9 +23,10 @@ #include #include -#include "services/cover/ICoverService.hpp" -#include "image/IEncodedImage.hpp" #include "database/Types.hpp" +#include "image/IEncodedImage.hpp" +#include "services/cover/ICoverService.hpp" + #include "ImageCache.hpp" namespace lms::db @@ -49,23 +50,23 @@ namespace lms::cover CoverService(const CoverService&) = delete; CoverService& operator=(const CoverService&) = delete; - std::shared_ptr getFromTrack(db::TrackId trackId, image::ImageSize width) override; - std::shared_ptr getFromRelease(db::ReleaseId releaseId, image::ImageSize width) override; - std::shared_ptr getFromArtist(db::ArtistId artistId, image::ImageSize width) override; - std::shared_ptr getDefaultSvgCover() override; - void flushCache() override; - void setJpegQuality(unsigned quality) override; + std::shared_ptr getFromTrack(db::TrackId trackId, image::ImageSize width) override; + std::shared_ptr getFromRelease(db::ReleaseId releaseId, image::ImageSize width) override; + std::shared_ptr getFromArtist(db::ArtistId artistId, image::ImageSize width) override; + std::shared_ptr getDefaultSvgCover() override; + void flushCache() override; + void setJpegQuality(unsigned quality) override; - std::shared_ptr getFromTrack(db::Session& dbSession, db::TrackId trackId, image::ImageSize width, bool allowReleaseFallback); - std::unique_ptr getFromAvMediaFile(const av::IAudioFile& input, image::ImageSize width) const; - std::unique_ptr getFromCoverFile(const std::filesystem::path& p, image::ImageSize width) const; + std::shared_ptr getFromTrack(db::Session& dbSession, db::TrackId trackId, image::ImageSize width, bool allowReleaseFallback); + std::unique_ptr getFromAvMediaFile(const av::IAudioFile& input, image::ImageSize width) const; + std::unique_ptr getFromCoverFile(const std::filesystem::path& p, image::ImageSize width) const; - std::unique_ptr getFromTrack(const std::filesystem::path& path, image::ImageSize width) const; - std::multimap getCoverPaths(const std::filesystem::path& directoryPath) const; - std::unique_ptr getFromDirectory(const std::filesystem::path& directory, image::ImageSize width, const std::vector& preferredFileNames, bool allowPickRandom) const; - std::unique_ptr getFromSameNamedFile(const std::filesystem::path& filePath, image::ImageSize width) const; + std::unique_ptr getFromTrack(const std::filesystem::path& path, image::ImageSize width) const; + std::multimap getCoverPaths(const std::filesystem::path& directoryPath) const; + std::unique_ptr getFromDirectory(const std::filesystem::path& directory, image::ImageSize width, const std::vector& preferredFileNames, bool allowPickRandom) const; + std::unique_ptr getFromSameNamedFile(const std::filesystem::path& filePath, image::ImageSize width) const; - bool checkCoverFile(const std::filesystem::path& filePath) const; + bool checkCoverFile(const std::filesystem::path& filePath) const; db::Db& _db; @@ -75,9 +76,7 @@ namespace lms::cover static inline const std::vector _fileExtensions{ ".jpg", ".jpeg", ".png", ".bmp" }; // TODO parametrize const std::size_t _maxFileSize; const std::vector _preferredFileNames; - const std::vector _artistFileNames; unsigned _jpegQuality; }; } // namespace lms::cover - diff --git a/src/libs/services/cover/impl/ImageCache.cpp b/src/libs/services/cover/impl/ImageCache.cpp index 1409ab1a8..13ffc31e5 100644 --- a/src/libs/services/cover/impl/ImageCache.cpp +++ b/src/libs/services/cover/impl/ImageCache.cpp @@ -21,14 +21,15 @@ #include -#include "core/Random.hpp" #include "core/ILogger.hpp" +#include "core/Random.hpp" namespace lms::cover { ImageCache::ImageCache(std::size_t maxCacheSize) : _maxCacheSize{ maxCacheSize } - {} + { + } void ImageCache::addImage(const EntryDesc& entryDesc, std::shared_ptr image) { @@ -70,4 +71,4 @@ namespace lms::cover _cacheSize = 0; _cache.clear(); } -} \ No newline at end of file +} // namespace lms::cover \ No newline at end of file diff --git a/src/libs/services/cover/impl/ImageCache.hpp b/src/libs/services/cover/impl/ImageCache.hpp index a57553e8a..414452962 100644 --- a/src/libs/services/cover/impl/ImageCache.hpp +++ b/src/libs/services/cover/impl/ImageCache.hpp @@ -65,8 +65,8 @@ namespace lms::cover }; std::unordered_map, EntryHasher> _cache; - std::size_t _cacheSize{}; - mutable std::atomic _cacheMisses{}; - mutable std::atomic _cacheHits{}; + std::size_t _cacheSize{}; + mutable std::atomic _cacheMisses{}; + mutable std::atomic _cacheHits{}; }; -} \ No newline at end of file +} // namespace lms::cover \ No newline at end of file diff --git a/src/libs/services/cover/include/services/cover/ICoverService.hpp b/src/libs/services/cover/include/services/cover/ICoverService.hpp index 6c740d23b..cbed5e7ff 100644 --- a/src/libs/services/cover/include/services/cover/ICoverService.hpp +++ b/src/libs/services/cover/include/services/cover/ICoverService.hpp @@ -52,5 +52,4 @@ namespace lms::cover std::unique_ptr createCoverService(db::Db& db, const std::filesystem::path& defaultSvgCoverPath); -} // namespace lms::coverArt - +} // namespace lms::cover diff --git a/src/libs/services/feedback/impl/FeedbackService.cpp b/src/libs/services/feedback/impl/FeedbackService.cpp index a92791566..f7c0bfe6a 100644 --- a/src/libs/services/feedback/impl/FeedbackService.cpp +++ b/src/libs/services/feedback/impl/FeedbackService.cpp @@ -20,6 +20,7 @@ #include "FeedbackService.hpp" #include "FeedbackService.impl.hpp" +#include "core/ILogger.hpp" #include "database/Artist.hpp" #include "database/Db.hpp" #include "database/Release.hpp" @@ -29,7 +30,6 @@ #include "database/StarredTrack.hpp" #include "database/Track.hpp" #include "database/User.hpp" -#include "core/ILogger.hpp" #include "internal/InternalBackend.hpp" #include "listenbrainz/ListenBrainzBackend.hpp" @@ -187,5 +187,4 @@ namespace lms::feedback return Track::findIds(session, searchParams); } -} // ns Feedback - +} // namespace lms::feedback diff --git a/src/libs/services/feedback/impl/FeedbackService.hpp b/src/libs/services/feedback/impl/FeedbackService.hpp index 893d517d8..33d6bf27f 100644 --- a/src/libs/services/feedback/impl/FeedbackService.hpp +++ b/src/libs/services/feedback/impl/FeedbackService.hpp @@ -23,6 +23,7 @@ #include #include "services/feedback/IFeedbackService.hpp" + #include "IFeedbackBackend.hpp" namespace lms::db @@ -46,7 +47,7 @@ namespace lms::feedback void unstar(db::UserId userId, db::ArtistId artistId) override; bool isStarred(db::UserId userId, db::ArtistId artistId) override; Wt::WDateTime getStarredDateTime(db::UserId userId, db::ArtistId artistId) override; - ArtistContainer findStarredArtists(const ArtistFindParameters& params) override; + ArtistContainer findStarredArtists(const ArtistFindParameters& params) override; void star(db::UserId userId, db::ReleaseId releaseId) override; void unstar(db::UserId userId, db::ReleaseId releaseId) override; @@ -62,17 +63,17 @@ namespace lms::feedback std::optional getUserFeedbackBackend(db::UserId userId); - template + template void star(db::UserId userId, ObjIdType id); - template + template void unstar(db::UserId userId, ObjIdType id); - template + template bool isStarred(db::UserId userId, ObjIdType id); - template + template Wt::WDateTime getStarredDateTime(db::UserId userId, ObjIdType id); db::Db& _db; std::unordered_map> _backends; }; -} // ns Feedback +} // namespace lms::feedback diff --git a/src/libs/services/feedback/impl/FeedbackService.impl.hpp b/src/libs/services/feedback/impl/FeedbackService.impl.hpp index bf8f6537f..d6d22e775 100644 --- a/src/libs/services/feedback/impl/FeedbackService.impl.hpp +++ b/src/libs/services/feedback/impl/FeedbackService.impl.hpp @@ -27,7 +27,7 @@ namespace lms::feedback { using namespace db; - template + template void FeedbackService::star(UserId userId, ObjIdType objId) { const auto backend{ getUserFeedbackBackend(userId) }; @@ -58,7 +58,7 @@ namespace lms::feedback _backends[*backend]->onStarred(starredObjId); } - template + template void FeedbackService::unstar(UserId userId, ObjIdType objId) { const auto backend{ getUserFeedbackBackend(userId) }; @@ -79,7 +79,7 @@ namespace lms::feedback _backends[*backend]->onUnstarred(starredObjId); } - template + template bool FeedbackService::isStarred(UserId userId, ObjIdType objId) { Session& session{ _db.getTLSSession() }; @@ -89,7 +89,7 @@ namespace lms::feedback return starredObj && (starredObj->getSyncState() != SyncState::PendingRemove); } - template + template Wt::WDateTime FeedbackService::getStarredDateTime(UserId userId, ObjIdType objId) { Session& session{ _db.getTLSSession() }; @@ -102,4 +102,4 @@ namespace lms::feedback return {}; } -} // ns Feedback \ No newline at end of file +} // namespace lms::feedback \ No newline at end of file diff --git a/src/libs/services/feedback/impl/IFeedbackBackend.hpp b/src/libs/services/feedback/impl/IFeedbackBackend.hpp index 0c0490db7..1d6a6b79b 100644 --- a/src/libs/services/feedback/impl/IFeedbackBackend.hpp +++ b/src/libs/services/feedback/impl/IFeedbackBackend.hpp @@ -40,4 +40,4 @@ namespace lms::feedback std::unique_ptr createFeedbackBackend(std::string_view backendName); -} // ns Feedback +} // namespace lms::feedback diff --git a/src/libs/services/feedback/impl/internal/InternalBackend.cpp b/src/libs/services/feedback/impl/internal/InternalBackend.cpp index c20521bd0..1fa510222 100644 --- a/src/libs/services/feedback/impl/internal/InternalBackend.cpp +++ b/src/libs/services/feedback/impl/internal/InternalBackend.cpp @@ -29,7 +29,7 @@ namespace lms::feedback { namespace details { - template + template void onStarred(db::Session& session, typename StarredObjType::IdType id) { auto transaction{ session.createWriteTransaction() }; @@ -38,7 +38,7 @@ namespace lms::feedback starredObj.modify()->setSyncState(db::SyncState::Synchronized); } - template + template void onUnstarred(db::Session& session, typename StarredObjType::IdType id) { auto transaction{ session.createWriteTransaction() }; @@ -46,11 +46,12 @@ namespace lms::feedback if (auto starredObj{ StarredObjType::find(session, id) }) starredObj.remove(); } - } + } // namespace details InternalBackend::InternalBackend(db::Db& db) : _db{ db } - {} + { + } void InternalBackend::onStarred(db::StarredArtistId starredArtistId) { @@ -81,4 +82,4 @@ namespace lms::feedback { details::onUnstarred(_db.getTLSSession(), starredTrackId); } -} // Feedback +} // namespace lms::feedback diff --git a/src/libs/services/feedback/impl/internal/InternalBackend.hpp b/src/libs/services/feedback/impl/internal/InternalBackend.hpp index c74343a8d..a7447d170 100644 --- a/src/libs/services/feedback/impl/internal/InternalBackend.hpp +++ b/src/libs/services/feedback/impl/internal/InternalBackend.hpp @@ -43,5 +43,4 @@ namespace lms::feedback db::Db& _db; }; -} // Feedback - +} // namespace lms::feedback diff --git a/src/libs/services/feedback/impl/listenbrainz/Exception.hpp b/src/libs/services/feedback/impl/listenbrainz/Exception.hpp index f040178c8..7574c5c67 100644 --- a/src/libs/services/feedback/impl/listenbrainz/Exception.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/Exception.hpp @@ -23,9 +23,9 @@ namespace lms::feedback::listenBrainz { - class Exception : public feedback::Exception - { - public: - using feedback::Exception::Exception; - }; -} + class Exception : public feedback::Exception + { + public: + using feedback::Exception::Exception; + }; +} // namespace lms::feedback::listenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.cpp b/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.cpp index d967aca4b..328cf2a64 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.cpp @@ -21,10 +21,9 @@ namespace lms::feedback::listenBrainz { - std::ostream& - operator<<(std::ostream& os, const Feedback& feedback) - { - os << "created = '" << feedback.created.toString() << "', recording MBID = '" << feedback.recordingMBID.getAsString() << "', score = " << static_cast(feedback.score); - return os; - } -} // feedback::ListenBrainz + std::ostream& operator<<(std::ostream& os, const Feedback& feedback) + { + os << "created = '" << feedback.created.toString() << "', recording MBID = '" << feedback.recordingMBID.getAsString() << "', score = " << static_cast(feedback.score); + return os; + } +} // namespace lms::feedback::listenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.hpp b/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.hpp index 6b8f7be59..354b1aa1c 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbackTypes.hpp @@ -20,26 +20,28 @@ #pragma once #include + #include + #include "core/UUID.hpp" namespace lms::feedback::listenBrainz { - // See https://listenbrainz.readthedocs.io/en/production/dev/feedback-json/#feedback-json-doc - enum class FeedbackType - { - Love = 1, - Hate = -1, - Erase = 0, - }; - - struct Feedback - { - Wt::WDateTime created; - core::UUID recordingMBID; - FeedbackType score; - }; - - std::ostream& operator<<(std::ostream& os, const Feedback& feedback); - -} // feedback::ListenBrainz + // See https://listenbrainz.readthedocs.io/en/production/dev/feedback-json/#feedback-json-doc + enum class FeedbackType + { + Love = 1, + Hate = -1, + Erase = 0, + }; + + struct Feedback + { + Wt::WDateTime created; + core::UUID recordingMBID; + FeedbackType score; + }; + + std::ostream& operator<<(std::ostream& os, const Feedback& feedback); + +} // namespace lms::feedback::listenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.cpp b/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.cpp index 3ed62f512..fe6738b6b 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.cpp @@ -21,8 +21,8 @@ #include #include -#include #include +#include #include "Exception.hpp" #include "Utils.hpp" @@ -37,14 +37,13 @@ namespace lms::feedback::listenBrainz if (!recordingMBID) throw Exception{ "MBID not found!" }; - return Feedback - { + return Feedback{ Wt::WDateTime::fromTime_t(static_cast(feedbackObj.get("created"))), - *recordingMBID, - static_cast(static_cast(feedbackObj.get("score"))) + *recordingMBID, + static_cast(static_cast(feedbackObj.get("score"))) }; } - } + } // namespace FeedbacksParser::Result FeedbacksParser::parse(std::string_view msgBody) { @@ -87,4 +86,4 @@ namespace lms::feedback::listenBrainz return res; } -} // feedback::ListenBrainz +} // namespace lms::feedback::listenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.hpp b/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.hpp index 8f67a8c9a..faa74b137 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbacksParser.hpp @@ -25,16 +25,16 @@ namespace lms::feedback::listenBrainz { - class FeedbacksParser - { - public: - struct Result - { - std::size_t feedbackCount {}; // >= feedbacks.size() - std::vector feedbacks; - }; + class FeedbacksParser + { + public: + struct Result + { + std::size_t feedbackCount{}; // >= feedbacks.size() + std::vector feedbacks; + }; - static Result parse(std::string_view msgBody); - }; + static Result parse(std::string_view msgBody); + }; -} // feedback::ListenBrainz +} // namespace lms::feedback::listenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp index 27b48d5ab..2961858fb 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.cpp @@ -19,20 +19,20 @@ #include "FeedbacksSynchronizer.hpp" -#include #include #include -#include #include +#include +#include +#include "core/IConfig.hpp" +#include "core/Service.hpp" +#include "core/http/IClient.hpp" #include "database/Db.hpp" #include "database/Session.hpp" #include "database/StarredTrack.hpp" #include "database/Track.hpp" #include "database/User.hpp" -#include "core/IConfig.hpp" -#include "core/http/IClient.hpp" -#include "core/Service.hpp" #include "Exception.hpp" #include "FeedbacksParser.hpp" @@ -57,7 +57,7 @@ namespace lms::feedback::listenBrainz return std::nullopt; } } - } + } // namespace FeedbacksSynchronizer::FeedbacksSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client) : _ioContext{ ioContext } @@ -125,19 +125,17 @@ namespace lms::feedback::listenBrainz request.message.addHeader("Authorization", "Token " + std::string{ listenBrainzToken->getAsString() }); Wt::Json::Object root; - root["recording_mbid"] = Wt::Json::Value{ std::string {recordingMBID->getAsString()} }; + root["recording_mbid"] = Wt::Json::Value{ std::string{ recordingMBID->getAsString() } }; root["score"] = Wt::Json::Value{ static_cast(type) }; request.message.addBodyText(Wt::Json::serialize(root)); request.message.addHeader("Content-Type", "application/json"); - request.onSuccessFunc = [this, type, starredTrackId](std::string_view /*msgBody*/) - { - _strand.dispatch([this, type, starredTrackId] - { - onFeedbackSent(type, starredTrackId); - }); - }; + request.onSuccessFunc = [this, type, starredTrackId](std::string_view /*msgBody*/) { + _strand.dispatch([this, type, starredTrackId] { + onFeedbackSent(type, starredTrackId); + }); + }; _client.sendPOSTRequest(std::move(request)); } catch (Exception& e) @@ -193,20 +191,19 @@ namespace lms::feedback::listenBrainz void FeedbacksSynchronizer::enquePendingFeedbacks() { - using namespace db; + using namespace db; - auto processPendingFeedbacks{ [this](SyncState scrobblingState, FeedbackType feedbackType) - { + auto processPendingFeedbacks{ [this](SyncState scrobblingState, FeedbackType feedbackType) { RangeResults pendingFeedbacks; { - db::Session& session {_db.getTLSSession()}; + db::Session& session{ _db.getTLSSession() }; - auto transaction {session.createReadTransaction()}; + auto transaction{ session.createReadTransaction() }; StarredTrack::FindParameters params; params.setFeedbackBackend(db::FeedbackBackend::ListenBrainz, scrobblingState) - .setRange(db::Range {0, 100}); // don't flood too much? + .setRange(db::Range{ 0, 100 }); // don't flood too much? pendingFeedbacks = StarredTrack::find(session, params); } @@ -236,10 +233,9 @@ namespace lms::feedback::listenBrainz bool FeedbacksSynchronizer::isSyncing() const { - return std::any_of(std::cbegin(_userContexts), std::cend(_userContexts), [](const auto& contextEntry) - { - return contextEntry.second.syncing; - }); + return std::any_of(std::cbegin(_userContexts), std::cend(_userContexts), [](const auto& contextEntry) { + return contextEntry.second.syncing; + }); } void FeedbacksSynchronizer::scheduleSync(std::chrono::seconds fromNow) @@ -249,20 +245,19 @@ namespace lms::feedback::listenBrainz LOG(DEBUG, "Scheduled sync in " << fromNow.count() << " seconds..."); _syncTimer.expires_after(fromNow); - _syncTimer.async_wait(boost::asio::bind_executor(_strand, [this](const boost::system::error_code& ec) + _syncTimer.async_wait(boost::asio::bind_executor(_strand, [this](const boost::system::error_code& ec) { + if (ec == boost::asio::error::operation_aborted) { - if (ec == boost::asio::error::operation_aborted) - { - LOG(DEBUG, "getFeedbacks aborted"); - return; - } - else if (ec) - { - throw Exception{ "GetFeedbacks timer failure: " + std::string {ec.message()} }; - } + LOG(DEBUG, "getFeedbacks aborted"); + return; + } + else if (ec) + { + throw Exception{ "GetFeedbacks timer failure: " + std::string{ ec.message() } }; + } - startSync(); - })); + startSync(); + })); } void FeedbacksSynchronizer::startSync() @@ -301,14 +296,13 @@ namespace lms::feedback::listenBrainz void FeedbacksSynchronizer::onSyncEnded(UserContext& context) { - _strand.dispatch([this, &context] - { - LOG(INFO, "Feedback sync done for user '" << context.listenBrainzUserName << "', fetched: " << context.fetchedFeedbackCount << ", matched: " << context.matchedFeedbackCount << ", imported: " << context.importedFeedbackCount); - context.syncing = false; + _strand.dispatch([this, &context] { + LOG(INFO, "Feedback sync done for user '" << context.listenBrainzUserName << "', fetched: " << context.fetchedFeedbackCount << ", matched: " << context.matchedFeedbackCount << ", imported: " << context.importedFeedbackCount); + context.syncing = false; - if (!isSyncing()) - scheduleSync(_syncFeedbacksPeriod); - }); + if (!isSyncing()) + scheduleSync(_syncFeedbacksPeriod); + }); } void FeedbacksSynchronizer::enqueValidateToken(UserContext& context) @@ -325,21 +319,19 @@ namespace lms::feedback::listenBrainz core::http::ClientGETRequestParameters request; request.priority = core::http::ClientRequestParameters::Priority::Low; request.relativeUrl = "/1/validate-token"; - request.headers = { {"Authorization", "Token " + std::string {listenBrainzToken->getAsString()}} }; - request.onSuccessFunc = [this, &context](std::string_view msgBody) - { - context.listenBrainzUserName = utils::parseValidateToken(msgBody); - if (context.listenBrainzUserName.empty()) - { - onSyncEnded(context); - return; - } - enqueGetFeedbackCount(context); - }; - request.onFailureFunc = [this, &context] + request.headers = { { "Authorization", "Token " + std::string{ listenBrainzToken->getAsString() } } }; + request.onSuccessFunc = [this, &context](std::string_view msgBody) { + context.listenBrainzUserName = utils::parseValidateToken(msgBody); + if (context.listenBrainzUserName.empty()) { onSyncEnded(context); - }; + return; + } + enqueGetFeedbackCount(context); + }; + request.onFailureFunc = [this, &context] { + onSyncEnded(context); + }; _client.sendGETRequest(std::move(request)); } @@ -351,30 +343,27 @@ namespace lms::feedback::listenBrainz core::http::ClientGETRequestParameters request; request.relativeUrl = "/1/feedback/user/" + std::string{ context.listenBrainzUserName } + "/get-feedback?score=1&count=0"; request.priority = core::http::ClientRequestParameters::Priority::Low; - request.onSuccessFunc = [this, &context](std::string_view msgBody) - { - std::string msgBodyCopy{ msgBody }; - _strand.dispatch([this, msgBodyCopy, &context] - { - LOG(DEBUG, "Current feedback count = " << (context.feedbackCount ? *context.feedbackCount : 0) << " for user '" << context.listenBrainzUserName << "'"); - - const auto totalFeedbackCount = parseTotalFeedbackCount(msgBodyCopy); - if (totalFeedbackCount) - LOG(DEBUG, "Feedback count for listenbrainz user '" << context.listenBrainzUserName << "' = " << *totalFeedbackCount); - - bool needSync{ totalFeedbackCount && (!context.feedbackCount || *context.feedbackCount != *totalFeedbackCount) }; - context.feedbackCount = totalFeedbackCount; - - if (needSync) - enqueGetFeedbacks(context); - else - onSyncEnded(context); - }); - }; - request.onFailureFunc = [this, &context] - { - onSyncEnded(context); - }; + request.onSuccessFunc = [this, &context](std::string_view msgBody) { + std::string msgBodyCopy{ msgBody }; + _strand.dispatch([this, msgBodyCopy, &context] { + LOG(DEBUG, "Current feedback count = " << (context.feedbackCount ? *context.feedbackCount : 0) << " for user '" << context.listenBrainzUserName << "'"); + + const auto totalFeedbackCount = parseTotalFeedbackCount(msgBodyCopy); + if (totalFeedbackCount) + LOG(DEBUG, "Feedback count for listenbrainz user '" << context.listenBrainzUserName << "' = " << *totalFeedbackCount); + + bool needSync{ totalFeedbackCount && (!context.feedbackCount || *context.feedbackCount != *totalFeedbackCount) }; + context.feedbackCount = totalFeedbackCount; + + if (needSync) + enqueGetFeedbacks(context); + else + onSyncEnded(context); + }); + }; + request.onFailureFunc = [this, &context] { + onSyncEnded(context); + }; _client.sendGETRequest(std::move(request)); } @@ -386,28 +375,25 @@ namespace lms::feedback::listenBrainz core::http::ClientGETRequestParameters request; request.relativeUrl = "/1/feedback/user/" + context.listenBrainzUserName + "/get-feedback?offset=" + std::to_string(context.fetchedFeedbackCount); request.priority = core::http::ClientRequestParameters::Priority::Low; - request.onSuccessFunc = [this, &context](std::string_view msgBody) - { - std::string msgBodyCopy{ msgBody }; - _strand.dispatch([this, msgBodyCopy, &context] - { - const std::size_t fetchedFeedbackCount{ processGetFeedbacks(msgBodyCopy, context) }; - if (fetchedFeedbackCount == 0 // no more thing available on server - || context.fetchedFeedbackCount >= context.feedbackCount // we may miss something, but we will get it next time - || context.fetchedFeedbackCount >= _maxSyncFeedbackCount) - { - onSyncEnded(context); - } - else - { - enqueGetFeedbacks(context); - } - }); - }; - request.onFailureFunc = [this, &context] - { - onSyncEnded(context); - }; + request.onSuccessFunc = [this, &context](std::string_view msgBody) { + std::string msgBodyCopy{ msgBody }; + _strand.dispatch([this, msgBodyCopy, &context] { + const std::size_t fetchedFeedbackCount{ processGetFeedbacks(msgBodyCopy, context) }; + if (fetchedFeedbackCount == 0 // no more thing available on server + || context.fetchedFeedbackCount >= context.feedbackCount // we may miss something, but we will get it next time + || context.fetchedFeedbackCount >= _maxSyncFeedbackCount) + { + onSyncEnded(context); + } + else + { + enqueGetFeedbacks(context); + } + }); + }; + request.onFailureFunc = [this, &context] { + onSyncEnded(context); + }; _client.sendGETRequest(std::move(request)); } diff --git a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.hpp b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.hpp index 8415f635d..38c010302 100644 --- a/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/FeedbacksSynchronizer.hpp @@ -21,12 +21,13 @@ #include #include + #include #include #include -#include "database/Types.hpp" #include "database/StarredTrackId.hpp" +#include "database/Types.hpp" #include "database/UserId.hpp" #include "FeedbackTypes.hpp" @@ -35,13 +36,13 @@ namespace lms { namespace core::http { - class IClient; + class IClient; } namespace db { class Db; } -} +} // namespace lms namespace lms::feedback::listenBrainz { @@ -59,22 +60,23 @@ namespace lms::feedback::listenBrainz struct UserContext { - UserContext(db::UserId id) : userId{ id } {} + UserContext(db::UserId id) + : userId{ id } {} UserContext(const UserContext&) = delete; UserContext& operator=(const UserContext&) = delete; - - const db::UserId userId; - bool syncing{}; - std::optional feedbackCount{}; + + const db::UserId userId; + bool syncing{}; + std::optional feedbackCount{}; // resetted at each sync - std::string listenBrainzUserName; // need to be resolved first + std::string listenBrainzUserName; // need to be resolved first - std::size_t currentOffset{}; - std::size_t fetchedFeedbackCount{}; - std::size_t matchedFeedbackCount{}; - std::size_t importedFeedbackCount{}; + std::size_t currentOffset{}; + std::size_t fetchedFeedbackCount{}; + std::size_t matchedFeedbackCount{}; + std::size_t importedFeedbackCount{}; }; UserContext& getUserContext(db::UserId userId); @@ -90,15 +92,14 @@ namespace lms::feedback::listenBrainz void tryImportFeedback(const Feedback& feedback, UserContext& context); boost::asio::io_context& _ioContext; - boost::asio::io_context::strand _strand{ _ioContext }; + boost::asio::io_context::strand _strand{ _ioContext }; db::Db& _db; - boost::asio::steady_timer _syncTimer{ _ioContext }; + boost::asio::steady_timer _syncTimer{ _ioContext }; core::http::IClient& _client; std::unordered_map _userContexts; - const std::size_t _maxSyncFeedbackCount; - const std::chrono::hours _syncFeedbacksPeriod; + const std::size_t _maxSyncFeedbackCount; + const std::chrono::hours _syncFeedbacksPeriod; }; -} // feedback::ListenBrainz - +} // namespace lms::feedback::listenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.cpp b/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.cpp index b8d02727f..3789c3325 100644 --- a/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.cpp @@ -19,22 +19,23 @@ #include "ListenBrainzBackend.hpp" +#include "core/IConfig.hpp" +#include "core/ILogger.hpp" +#include "core/Service.hpp" +#include "core/http/IClient.hpp" #include "database/Db.hpp" #include "database/Session.hpp" #include "database/StarredArtist.hpp" #include "database/StarredRelease.hpp" #include "database/Track.hpp" -#include "core/IConfig.hpp" -#include "core/http/IClient.hpp" -#include "core/ILogger.hpp" -#include "core/Service.hpp" + #include "Utils.hpp" namespace lms::feedback::listenBrainz { namespace details { - template + template void onStarred(db::Session& session, typename StarredObjType::IdType id) { auto transaction{ session.createWriteTransaction() }; @@ -46,7 +47,7 @@ namespace lms::feedback::listenBrainz } } - template + template void onUnstarred(db::Session& session, typename StarredObjType::IdType id) { auto transaction{ session.createWriteTransaction() }; @@ -54,7 +55,7 @@ namespace lms::feedback::listenBrainz if (auto starredObj{ StarredObjType::find(session, id) }) starredObj.remove(); } - } + } // namespace details ListenBrainzBackend::ListenBrainzBackend(boost::asio::io_context& ioContext, db::Db& db) : _ioContext{ ioContext } @@ -100,4 +101,4 @@ namespace lms::feedback::listenBrainz { _feedbacksSynchronizer.enqueFeedback(FeedbackType::Erase, starredtrackId); } -} // namespace lms::scrobbling::listenBrainz +} // namespace lms::feedback::listenBrainz diff --git a/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.hpp b/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.hpp index df69788f8..f73590957 100644 --- a/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/ListenBrainzBackend.hpp @@ -19,12 +19,12 @@ #pragma once +#include #include #include -#include -#include "IFeedbackBackend.hpp" #include "FeedbacksSynchronizer.hpp" +#include "IFeedbackBackend.hpp" namespace lms::db { @@ -52,8 +52,8 @@ namespace lms::feedback::listenBrainz boost::asio::io_context& _ioContext; db::Db& _db; - std::string _baseAPIUrl; - std::unique_ptr _client; - FeedbacksSynchronizer _feedbacksSynchronizer; + std::string _baseAPIUrl; + std::unique_ptr _client; + FeedbacksSynchronizer _feedbacksSynchronizer; }; -} \ No newline at end of file +} // namespace lms::feedback::listenBrainz \ No newline at end of file diff --git a/src/libs/services/feedback/impl/listenbrainz/Utils.cpp b/src/libs/services/feedback/impl/listenbrainz/Utils.cpp index ad545bf94..e240c8c60 100644 --- a/src/libs/services/feedback/impl/listenbrainz/Utils.cpp +++ b/src/libs/services/feedback/impl/listenbrainz/Utils.cpp @@ -59,4 +59,4 @@ namespace lms::feedback::listenBrainz::utils listenBrainzUserName = root.get("user_name").orIfNull(""); return listenBrainzUserName; } -} +} // namespace lms::feedback::listenBrainz::utils diff --git a/src/libs/services/feedback/impl/listenbrainz/Utils.hpp b/src/libs/services/feedback/impl/listenbrainz/Utils.hpp index 3ae8ce461..a55641eea 100644 --- a/src/libs/services/feedback/impl/listenbrainz/Utils.hpp +++ b/src/libs/services/feedback/impl/listenbrainz/Utils.hpp @@ -19,11 +19,11 @@ #pragma once -#include "database/UserId.hpp" #include "core/ILogger.hpp" #include "core/UUID.hpp" +#include "database/UserId.hpp" -#define LOG(sev, message) LMS_LOG(FEEDBACK, sev, "[listenbrainz] " << message) +#define LOG(sev, message) LMS_LOG(FEEDBACK, sev, "[listenbrainz] " << message) namespace lms::db { @@ -32,6 +32,6 @@ namespace lms::db namespace lms::feedback::listenBrainz::utils { - std::optional getListenBrainzToken(db::Session& session, db::UserId userId); + std::optional getListenBrainzToken(db::Session& session, db::UserId userId); std::string parseValidateToken(std::string_view msgBody); -} +} // namespace lms::feedback::listenBrainz::utils diff --git a/src/libs/services/feedback/include/services/feedback/Exception.hpp b/src/libs/services/feedback/include/services/feedback/Exception.hpp index 03a3d09ea..b0e085478 100644 --- a/src/libs/services/feedback/include/services/feedback/Exception.hpp +++ b/src/libs/services/feedback/include/services/feedback/Exception.hpp @@ -28,4 +28,4 @@ namespace lms::feedback public: using LmsException::LmsException; }; -} +} // namespace lms::feedback diff --git a/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp b/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp index 208ba1d15..7bab297e8 100644 --- a/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp +++ b/src/libs/services/feedback/include/services/feedback/IFeedbackService.hpp @@ -22,17 +22,17 @@ #include #include #include -#include + #include +#include -#include "database/Types.hpp" #include "database/ArtistId.hpp" #include "database/ClusterId.hpp" #include "database/MediaLibraryId.hpp" #include "database/ReleaseId.hpp" #include "database/TrackId.hpp" -#include "database/UserId.hpp" #include "database/Types.hpp" +#include "database/UserId.hpp" namespace lms::db { @@ -52,51 +52,78 @@ namespace lms::feedback struct FindParameters { - db::UserId user; - std::vector clusters; // if non empty, at least one artist that belongs to these clusters - std::vector keywords; // if non empty, name must match all of these keywords - std::optional range; - db::MediaLibraryId library; - - FindParameters& setUser(const db::UserId _user) { user = _user; return *this; } - FindParameters& setClusters(std::span _clusters) { clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); return *this; } - FindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } - FindParameters& setRange(std::optional _range) { range = _range; return *this; } - FindParameters& setMediaLibrary(db::MediaLibraryId _library) { library = _library; return *this; } + db::UserId user; + std::vector clusters; // if non empty, at least one artist that belongs to these clusters + std::vector keywords; // if non empty, name must match all of these keywords + std::optional range; + db::MediaLibraryId library; + + FindParameters& setUser(const db::UserId _user) + { + user = _user; + return *this; + } + FindParameters& setClusters(std::span _clusters) + { + clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); + return *this; + } + FindParameters& setKeywords(const std::vector& _keywords) + { + keywords = _keywords; + return *this; + } + FindParameters& setRange(std::optional _range) + { + range = _range; + return *this; + } + FindParameters& setMediaLibrary(db::MediaLibraryId _library) + { + library = _library; + return *this; + } }; // Artists struct ArtistFindParameters : public FindParameters { - std::optional linkType; // if set, only artists that have produced at least one track with this link type - db::ArtistSortMethod sortMethod{ db::ArtistSortMethod::None }; - - ArtistFindParameters& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } - ArtistFindParameters& setSortMethod(db::ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; } + std::optional linkType; // if set, only artists that have produced at least one track with this link type + db::ArtistSortMethod sortMethod{ db::ArtistSortMethod::None }; + + ArtistFindParameters& setLinkType(std::optional _linkType) + { + linkType = _linkType; + return *this; + } + ArtistFindParameters& setSortMethod(db::ArtistSortMethod _sortMethod) + { + sortMethod = _sortMethod; + return *this; + } }; - virtual void star(db::UserId userId, db::ArtistId artistId) = 0; - virtual void unstar(db::UserId userId, db::ArtistId artistId) = 0; - virtual bool isStarred(db::UserId userId, db::ArtistId artistId) = 0; - virtual Wt::WDateTime getStarredDateTime(db::UserId userId, db::ArtistId artistId) = 0; - virtual ArtistContainer findStarredArtists(const ArtistFindParameters& params) = 0; + virtual void star(db::UserId userId, db::ArtistId artistId) = 0; + virtual void unstar(db::UserId userId, db::ArtistId artistId) = 0; + virtual bool isStarred(db::UserId userId, db::ArtistId artistId) = 0; + virtual Wt::WDateTime getStarredDateTime(db::UserId userId, db::ArtistId artistId) = 0; + virtual ArtistContainer findStarredArtists(const ArtistFindParameters& params) = 0; // Releases - virtual void star(db::UserId userId, db::ReleaseId releaseId) = 0; - virtual void unstar(db::UserId userId, db::ReleaseId releaseId) = 0; - virtual bool isStarred(db::UserId userId, db::ReleaseId artistId) = 0; - virtual Wt::WDateTime getStarredDateTime(db::UserId userId, db::ReleaseId artistId) = 0; - virtual ReleaseContainer findStarredReleases(const FindParameters& params) = 0; + virtual void star(db::UserId userId, db::ReleaseId releaseId) = 0; + virtual void unstar(db::UserId userId, db::ReleaseId releaseId) = 0; + virtual bool isStarred(db::UserId userId, db::ReleaseId artistId) = 0; + virtual Wt::WDateTime getStarredDateTime(db::UserId userId, db::ReleaseId artistId) = 0; + virtual ReleaseContainer findStarredReleases(const FindParameters& params) = 0; // Tracks - virtual void star(db::UserId userId, db::TrackId trackId) = 0; - virtual void unstar(db::UserId userId, db::TrackId trackId) = 0; - virtual bool isStarred(db::UserId userId, db::TrackId artistId) = 0; - virtual Wt::WDateTime getStarredDateTime(db::UserId userId, db::TrackId artistId) = 0; - virtual TrackContainer findStarredTracks(const FindParameters& params) = 0; + virtual void star(db::UserId userId, db::TrackId trackId) = 0; + virtual void unstar(db::UserId userId, db::TrackId trackId) = 0; + virtual bool isStarred(db::UserId userId, db::TrackId artistId) = 0; + virtual Wt::WDateTime getStarredDateTime(db::UserId userId, db::TrackId artistId) = 0; + virtual TrackContainer findStarredTracks(const FindParameters& params) = 0; }; std::unique_ptr createFeedbackService(boost::asio::io_service& ioService, db::Db& db); -} // ns Feedback - +} // namespace lms::feedback diff --git a/src/libs/services/recommendation/impl/ClustersEngineCreator.hpp b/src/libs/services/recommendation/impl/ClustersEngineCreator.hpp index eb3d36f07..d83328c89 100644 --- a/src/libs/services/recommendation/impl/ClustersEngineCreator.hpp +++ b/src/libs/services/recommendation/impl/ClustersEngineCreator.hpp @@ -23,12 +23,11 @@ namespace lms::db { - class Db; + class Db; } namespace lms::recommendation { - class IEngine; - std::unique_ptr createClustersEngine(db::Db& db); -} - + class IEngine; + std::unique_ptr createClustersEngine(db::Db& db); +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/FeaturesEngineCreator.hpp b/src/libs/services/recommendation/impl/FeaturesEngineCreator.hpp index f1b335e19..107e250a7 100644 --- a/src/libs/services/recommendation/impl/FeaturesEngineCreator.hpp +++ b/src/libs/services/recommendation/impl/FeaturesEngineCreator.hpp @@ -20,15 +20,15 @@ #pragma once #include + #include "IEngine.hpp" namespace lms::db { - class Db; + class Db; } namespace lms::recommendation { - std::unique_ptr createFeaturesEngine(db::Db& db); + std::unique_ptr createFeaturesEngine(db::Db& db); } - diff --git a/src/libs/services/recommendation/impl/IEngine.hpp b/src/libs/services/recommendation/impl/IEngine.hpp index aaf9b907f..890631dc5 100644 --- a/src/libs/services/recommendation/impl/IEngine.hpp +++ b/src/libs/services/recommendation/impl/IEngine.hpp @@ -20,33 +20,33 @@ #pragma once #include -#include "database/Types.hpp" + +#include "core/EnumSet.hpp" #include "database/TrackListId.hpp" +#include "database/Types.hpp" #include "services/recommendation/Types.hpp" -#include "core/EnumSet.hpp" namespace lms::db { - class Db; + class Db; } namespace lms::recommendation { - class IEngine - { - public: - virtual ~IEngine() = default; - - virtual void load(bool forceReload, const ProgressCallback& progressCallback = {}) = 0; - virtual void requestCancelLoad() = 0; + class IEngine + { + public: + virtual ~IEngine() = default; - virtual TrackContainer findSimilarTracksFromTrackList(db::TrackListId tracklistId, std::size_t maxCount) const = 0; - virtual TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const = 0; - virtual ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const = 0; - virtual ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const = 0; - }; + virtual void load(bool forceReload, const ProgressCallback& progressCallback = {}) = 0; + virtual void requestCancelLoad() = 0; - std::unique_ptr createEngine(db::Db& db); + virtual TrackContainer findSimilarTracksFromTrackList(db::TrackListId tracklistId, std::size_t maxCount) const = 0; + virtual TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const = 0; + virtual ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const = 0; + virtual ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const = 0; + }; -} // ns Recommendation + std::unique_ptr createEngine(db::Db& db); +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp b/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp index 0d03a40bc..2e25b1fc3 100644 --- a/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp +++ b/src/libs/services/recommendation/impl/PlaylistGeneratorService.cpp @@ -19,14 +19,15 @@ #include "PlaylistGeneratorService.hpp" +#include "core/ILogger.hpp" #include "database/Db.hpp" #include "database/Session.hpp" #include "database/Track.hpp" #include "services/recommendation/IRecommendationService.hpp" + #include "playlist-constraints/ConsecutiveArtists.hpp" #include "playlist-constraints/ConsecutiveReleases.hpp" #include "playlist-constraints/DuplicateTracks.hpp" -#include "core/ILogger.hpp" namespace lms::recommendation { @@ -111,4 +112,4 @@ namespace lms::recommendation return tracks; } -} +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/PlaylistGeneratorService.hpp b/src/libs/services/recommendation/impl/PlaylistGeneratorService.hpp index afdf0273d..6287d7788 100644 --- a/src/libs/services/recommendation/impl/PlaylistGeneratorService.hpp +++ b/src/libs/services/recommendation/impl/PlaylistGeneratorService.hpp @@ -21,22 +21,23 @@ #include "services/recommendation/IPlaylistGeneratorService.hpp" #include "services/recommendation/IRecommendationService.hpp" + #include "playlist-constraints/IConstraint.hpp" namespace lms::recommendation { - class PlaylistGeneratorService : public IPlaylistGeneratorService - { - public: - PlaylistGeneratorService(db::Db& db, IRecommendationService& recommendationService); + class PlaylistGeneratorService : public IPlaylistGeneratorService + { + public: + PlaylistGeneratorService(db::Db& db, IRecommendationService& recommendationService); - private: - TrackContainer extendPlaylist(db::TrackListId tracklistId, std::size_t maxCount) const override; + private: + TrackContainer extendPlaylist(db::TrackListId tracklistId, std::size_t maxCount) const override; - TrackContainer getTracksFromTrackList(db::TrackListId tracklistId) const; + TrackContainer getTracksFromTrackList(db::TrackListId tracklistId) const; - db::Db& _db; - IRecommendationService& _recommendationService; - std::vector> _constraints; - }; -} // namespace Radio + db::Db& _db; + IRecommendationService& _recommendationService; + std::vector> _constraints; + }; +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/RecommendationService.cpp b/src/libs/services/recommendation/impl/RecommendationService.cpp index d1f95b9af..333bfdd29 100644 --- a/src/libs/services/recommendation/impl/RecommendationService.cpp +++ b/src/libs/services/recommendation/impl/RecommendationService.cpp @@ -22,14 +22,14 @@ #include #include -#include "ClustersEngineCreator.hpp" -#include "FeaturesEngineCreator.hpp" - -#include "database/Db.hpp" -#include "database/Session.hpp" -#include "database/ScanSettings.hpp" #include "core/Exception.hpp" #include "core/ILogger.hpp" +#include "database/Db.hpp" +#include "database/ScanSettings.hpp" +#include "database/Session.hpp" + +#include "ClustersEngineCreator.hpp" +#include "FeaturesEngineCreator.hpp" namespace lms::recommendation { @@ -41,7 +41,7 @@ namespace lms::recommendation return db::ScanSettings::get(session)->getSimilarityEngineType(); } - } + } // namespace std::unique_ptr createRecommendationService(db::Db& db) { @@ -81,7 +81,8 @@ namespace lms::recommendation if (!_engine) return res; - return _engine->getSimilarReleases(releaseId, maxCount);; + return _engine->getSimilarReleases(releaseId, maxCount); + ; } ArtistContainer RecommendationService::getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const @@ -120,4 +121,4 @@ namespace lms::recommendation if (_engine) _engine->load(false); } -} // ns Similarity +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/RecommendationService.hpp b/src/libs/services/recommendation/impl/RecommendationService.hpp index 4389aad05..721acc1a0 100644 --- a/src/libs/services/recommendation/impl/RecommendationService.hpp +++ b/src/libs/services/recommendation/impl/RecommendationService.hpp @@ -22,6 +22,7 @@ #include #include "services/recommendation/IRecommendationService.hpp" + #include "IEngine.hpp" namespace lms::db @@ -63,5 +64,4 @@ namespace lms::recommendation std::unique_ptr _engine; }; -} // ns Recommendation - +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp b/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp index d8a8df8f5..b1e43fad5 100644 --- a/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp +++ b/src/libs/services/recommendation/impl/clusters/ClustersEngine.cpp @@ -27,7 +27,8 @@ #include "database/Track.hpp" #include "database/TrackList.hpp" -namespace lms::recommendation { +namespace lms::recommendation +{ using namespace db; @@ -44,7 +45,7 @@ namespace lms::recommendation { Session& dbSession{ _db.getTLSSession() }; auto transaction{ dbSession.createReadTransaction() }; - const auto similarTrackIds{ Track::findSimilarTrackIds(dbSession, trackIds, Range {0, maxCount}) }; + const auto similarTrackIds{ Track::findSimilarTrackIds(dbSession, trackIds, Range{ 0, maxCount }) }; return std::move(similarTrackIds.results); } @@ -104,7 +105,7 @@ namespace lms::recommendation { if (!artist) return {}; - const auto similarArtistIds{ artist->findSimilarArtistIds(artistLinkTypes, Range {0, maxCount}) }; + const auto similarArtistIds{ artist->findSimilarArtistIds(artistLinkTypes, Range{ 0, maxCount }) }; return std::move(similarArtistIds.results); } diff --git a/src/libs/services/recommendation/impl/clusters/ClustersEngine.hpp b/src/libs/services/recommendation/impl/clusters/ClustersEngine.hpp index da600bdeb..4d019ccd7 100644 --- a/src/libs/services/recommendation/impl/clusters/ClustersEngine.hpp +++ b/src/libs/services/recommendation/impl/clusters/ClustersEngine.hpp @@ -24,27 +24,27 @@ namespace lms::recommendation { - class ClusterEngine : public IEngine - { - public: - ClusterEngine(db::Db& db) : _db {db} {} + class ClusterEngine : public IEngine + { + public: + ClusterEngine(db::Db& db) + : _db{ db } {} - ClusterEngine(const ClusterEngine&) = delete; - ClusterEngine(ClusterEngine&&) = delete; - ClusterEngine& operator=(const ClusterEngine&) = delete; - ClusterEngine& operator=(ClusterEngine&&) = delete; + ClusterEngine(const ClusterEngine&) = delete; + ClusterEngine(ClusterEngine&&) = delete; + ClusterEngine& operator=(const ClusterEngine&) = delete; + ClusterEngine& operator=(ClusterEngine&&) = delete; - private: - void load(bool, const ProgressCallback&) override {} - void requestCancelLoad() override {} + private: + void load(bool, const ProgressCallback&) override {} + void requestCancelLoad() override {} - TrackContainer findSimilarTracksFromTrackList(db::TrackListId tracklistId, std::size_t maxCount) const override; - TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const override; - ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const override; - ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const override; + TrackContainer findSimilarTracksFromTrackList(db::TrackListId tracklistId, std::size_t maxCount) const override; + TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const override; + ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const override; + ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const override; - db::Db& _db; - }; + db::Db& _db; + }; } // namespace lms::recommendation - diff --git a/src/libs/services/recommendation/impl/features/FeaturesDefs.cpp b/src/libs/services/recommendation/impl/features/FeaturesDefs.cpp index 418ef8632..5a9e6b8e3 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesDefs.cpp +++ b/src/libs/services/recommendation/impl/features/FeaturesDefs.cpp @@ -24,370 +24,367 @@ #include "core/Exception.hpp" -namespace lms::recommendation { - -static const std::unordered_map featureDefinitions +namespace lms::recommendation { - { "lowlevel.average_loudness", {1}}, - { "lowlevel.barkbands.dmean", {27}}, - { "lowlevel.barkbands.dmean2", {27}}, - { "lowlevel.barkbands.dvar", {27}}, - { "lowlevel.barkbands.dvar2", {27}}, - { "lowlevel.barkbands.max", {27}}, - { "lowlevel.barkbands.mean", {27}}, - { "lowlevel.barkbands.median", {27}}, - { "lowlevel.barkbands.min", {27}}, - { "lowlevel.barkbands.var", {27}}, - { "lowlevel.barkbands_crest.dmean", {1}}, - { "lowlevel.barkbands_crest.dmean2", {1}}, - { "lowlevel.barkbands_crest.dvar", {1}}, - { "lowlevel.barkbands_crest.dvar2", {1}}, - { "lowlevel.barkbands_crest.max", {1}}, - { "lowlevel.barkbands_crest.mean", {1}}, - { "lowlevel.barkbands_crest.median", {1}}, - { "lowlevel.barkbands_crest.min", {1}}, - { "lowlevel.barkbands_crest.var", {1}}, - { "lowlevel.barkbands_flatness_db.dmean", {1}}, - { "lowlevel.barkbands_flatness_db.dmean2", {1}}, - { "lowlevel.barkbands_flatness_db.dvar", {1}}, - { "lowlevel.barkbands_flatness_db.dvar2", {1}}, - { "lowlevel.barkbands_flatness_db.max", {1}}, - { "lowlevel.barkbands_flatness_db.mean", {1}}, - { "lowlevel.barkbands_flatness_db.median", {1}}, - { "lowlevel.barkbands_flatness_db.min", {1}}, - { "lowlevel.barkbands_flatness_db.var", {1}}, - { "lowlevel.barkbands_kurtosis.dmean", {1}}, - { "lowlevel.barkbands_kurtosis.dmean2", {1}}, - { "lowlevel.barkbands_kurtosis.dvar", {1}}, - { "lowlevel.barkbands_kurtosis.dvar2", {1}}, - { "lowlevel.barkbands_kurtosis.max", {1}}, - { "lowlevel.barkbands_kurtosis.mean", {1}}, - { "lowlevel.barkbands_kurtosis.median", {1}}, - { "lowlevel.barkbands_kurtosis.min", {1}}, - { "lowlevel.barkbands_kurtosis.var", {1}}, - { "lowlevel.barkbands_skewness.dmean", {1}}, - { "lowlevel.barkbands_skewness.dmean2", {1}}, - { "lowlevel.barkbands_skewness.dvar", {1}}, - { "lowlevel.barkbands_skewness.dvar2", {1}}, - { "lowlevel.barkbands_skewness.max", {1}}, - { "lowlevel.barkbands_skewness.mean", {1}}, - { "lowlevel.barkbands_skewness.median", {1}}, - { "lowlevel.barkbands_skewness.min", {1}}, - { "lowlevel.barkbands_skewness.var", {1}}, - { "lowlevel.barkbands_spread.dmean", {1}}, - { "lowlevel.barkbands_spread.dmean2", {1}}, - { "lowlevel.barkbands_spread.dvar", {1}}, - { "lowlevel.barkbands_spread.dvar2", {1}}, - { "lowlevel.barkbands_spread.max", {1}}, - { "lowlevel.barkbands_spread.mean", {1}}, - { "lowlevel.barkbands_spread.median", {1}}, - { "lowlevel.barkbands_spread.min", {1}}, - { "lowlevel.barkbands_spread.var", {1}}, - { "lowlevel.dissonance.dmean", {1}}, - { "lowlevel.dissonance.dmean2", {1}}, - { "lowlevel.dissonance.dvar", {1}}, - { "lowlevel.dissonance.dvar2", {1}}, - { "lowlevel.dissonance.max", {1}}, - { "lowlevel.dissonance.mean", {1}}, - { "lowlevel.dissonance.median", {1}}, - { "lowlevel.dissonance.min", {1}}, - { "lowlevel.dissonance.var", {1}}, - { "lowlevel.dynamic_complexity", {1}}, - { "lowlevel.erbbands.dmean", {40}}, - { "lowlevel.erbbands.dmean2", {40}}, - { "lowlevel.erbbands.dvar", {40}}, - { "lowlevel.erbbands.dvar2", {40}}, - { "lowlevel.erbbands.max", {40}}, - { "lowlevel.erbbands.mean", {40}}, - { "lowlevel.erbbands.median", {40}}, - { "lowlevel.erbbands.min", {40}}, - { "lowlevel.erbbands.var", {40}}, - { "lowlevel.gfcc.mean", {13}}, - { "lowlevel.hfc.dmean", {1}}, - { "lowlevel.hfc.dmean2", {1}}, - { "lowlevel.hfc.dvar", {1}}, - { "lowlevel.hfc.dvar2", {1}}, - { "lowlevel.hfc.max", {1}}, - { "lowlevel.hfc.mean", {1}}, - { "lowlevel.hfc.median", {1}}, - { "lowlevel.hfc.min", {1}}, - { "lowlevel.hfc.var", {1}}, - { "tonal.hpcp.median", {36}}, - { "lowlevel.melbands.dmean", {40}}, - { "lowlevel.melbands.dmean2", {40}}, - { "lowlevel.melbands.dvar", {40}}, - { "lowlevel.melbands.dvar2", {40}}, - { "lowlevel.melbands.max", {40}}, - { "lowlevel.melbands.mean", {40}}, - { "lowlevel.melbands.median", {40}}, - { "lowlevel.melbands.min", {40}}, - { "lowlevel.melbands.var", {40}}, - { "lowlevel.melbands_crest.dmean", {1}}, - { "lowlevel.melbands_crest.dmean2", {1}}, - { "lowlevel.melbands_crest.dvar", {1}}, - { "lowlevel.melbands_crest.dvar2", {1}}, - { "lowlevel.melbands_crest.max", {1}}, - { "lowlevel.melbands_crest.mean", {1}}, - { "lowlevel.melbands_crest.median", {1}}, - { "lowlevel.melbands_crest.min", {1}}, - { "lowlevel.melbands_crest.var", {1}}, - { "lowlevel.melbands_flatness_db.dmean", {1}}, - { "lowlevel.melbands_flatness_db.dmean2", {1}}, - { "lowlevel.melbands_flatness_db.dvar", {1}}, - { "lowlevel.melbands_flatness_db.dvar2", {1}}, - { "lowlevel.melbands_flatness_db.max", {1}}, - { "lowlevel.melbands_flatness_db.mean", {1}}, - { "lowlevel.melbands_flatness_db.median", {1}}, - { "lowlevel.melbands_flatness_db.min", {1}}, - { "lowlevel.melbands_flatness_db.var", {1}}, - { "lowlevel.melbands_kurtosis.dmean", {1}}, - { "lowlevel.melbands_kurtosis.dmean2", {1}}, - { "lowlevel.melbands_kurtosis.dvar", {1}}, - { "lowlevel.melbands_kurtosis.dvar2", {1}}, - { "lowlevel.melbands_kurtosis.max", {1}}, - { "lowlevel.melbands_kurtosis.mean", {1}}, - { "lowlevel.melbands_kurtosis.median", {1}}, - { "lowlevel.melbands_kurtosis.min", {1}}, - { "lowlevel.melbands_kurtosis.var", {1}}, - { "lowlevel.melbands_skewness.dmean", {1}}, - { "lowlevel.melbands_skewness.dmean2", {1}}, - { "lowlevel.melbands_skewness.dvar", {1}}, - { "lowlevel.melbands_skewness.dvar2", {1}}, - { "lowlevel.melbands_skewness.max", {1}}, - { "lowlevel.melbands_skewness.mean", {1}}, - { "lowlevel.melbands_skewness.median", {1}}, - { "lowlevel.melbands_skewness.min", {1}}, - { "lowlevel.melbands_skewness.var", {1}}, - { "lowlevel.melbands_spread.dmean", {1}}, - { "lowlevel.melbands_spread.dmean2", {1}}, - { "lowlevel.melbands_spread.dvar", {1}}, - { "lowlevel.melbands_spread.dvar2", {1}}, - { "lowlevel.melbands_spread.max", {1}}, - { "lowlevel.melbands_spread.mean", {1}}, - { "lowlevel.melbands_spread.median", {1}}, - { "lowlevel.melbands_spread.min", {1}}, - { "lowlevel.melbands_spread.var", {1}}, - { "lowlevel.mfcc.mean", {13}}, - { "lowlevel.pitch_salience.dmean", {1}}, - { "lowlevel.pitch_salience.dmean2", {1}}, - { "lowlevel.pitch_salience.dvar", {1}}, - { "lowlevel.pitch_salience.dvar2", {1}}, - { "lowlevel.pitch_salience.max", {1}}, - { "lowlevel.pitch_salience.mean", {1}}, - { "lowlevel.pitch_salience.median", {1}}, - { "lowlevel.pitch_salience.min", {1}}, - { "lowlevel.pitch_salience.var", {1}}, - { "lowlevel.silence_rate_30dB.dmean", {1}}, - { "lowlevel.silence_rate_30dB.dmean2", {1}}, - { "lowlevel.silence_rate_30dB.dvar", {1}}, - { "lowlevel.silence_rate_30dB.dvar2", {1}}, - { "lowlevel.silence_rate_30dB.max", {1}}, - { "lowlevel.silence_rate_30dB.mean", {1}}, - { "lowlevel.silence_rate_30dB.median", {1}}, - { "lowlevel.silence_rate_30dB.min", {1}}, - { "lowlevel.silence_rate_30dB.var", {1}}, - { "lowlevel.silence_rate_60dB.dmean", {1}}, - { "lowlevel.silence_rate_60dB.dmean2", {1}}, - { "lowlevel.silence_rate_60dB.dvar", {1}}, - { "lowlevel.silence_rate_60dB.dvar2", {1}}, - { "lowlevel.silence_rate_60dB.max", {1}}, - { "lowlevel.silence_rate_60dB.mean", {1}}, - { "lowlevel.silence_rate_60dB.median", {1}}, - { "lowlevel.silence_rate_60dB.min", {1}}, - { "lowlevel.silence_rate_60dB.var", {1}}, - { "lowlevel.spectral_centroid.dmean", {1}}, - { "lowlevel.spectral_centroid.dmean2", {1}}, - { "lowlevel.spectral_centroid.dvar", {1}}, - { "lowlevel.spectral_centroid.dvar2", {1}}, - { "lowlevel.spectral_centroid.max", {1}}, - { "lowlevel.spectral_centroid.mean", {1}}, - { "lowlevel.spectral_centroid.median", {1}}, - { "lowlevel.spectral_centroid.min", {1}}, - { "lowlevel.spectral_centroid.var", {1}}, - { "lowlevel.spectral_complexity.dmean", {1}}, - { "lowlevel.spectral_complexity.dmean2", {1}}, - { "lowlevel.spectral_complexity.dvar", {1}}, - { "lowlevel.spectral_complexity.dvar2", {1}}, - { "lowlevel.spectral_complexity.max", {1}}, - { "lowlevel.spectral_complexity.mean", {1}}, - { "lowlevel.spectral_complexity.median", {1}}, - { "lowlevel.spectral_complexity.min", {1}}, - { "lowlevel.spectral_complexity.var", {1}}, - { "lowlevel.spectral_contrast_coeffs.dmean", {6}}, - { "lowlevel.spectral_contrast_coeffs.dmean2", {6}}, - { "lowlevel.spectral_contrast_coeffs.dvar", {6}}, - { "lowlevel.spectral_contrast_coeffs.dvar2", {6}}, - { "lowlevel.spectral_contrast_coeffs.max", {6}}, - { "lowlevel.spectral_contrast_coeffs.mean", {6}}, - { "lowlevel.spectral_contrast_coeffs.median", {6}}, - { "lowlevel.spectral_contrast_coeffs.min", {6}}, - { "lowlevel.spectral_contrast_coeffs.var", {6}}, - { "lowlevel.spectral_contrast_valleys.dmean", {6}}, - { "lowlevel.spectral_contrast_valleys.dmean2", {6}}, - { "lowlevel.spectral_contrast_valleys.dvar", {6}}, - { "lowlevel.spectral_contrast_valleys.dvar2", {6}}, - { "lowlevel.spectral_contrast_valleys.max", {6}}, - { "lowlevel.spectral_contrast_valleys.mean", {6}}, - { "lowlevel.spectral_contrast_valleys.median", {6}}, - { "lowlevel.spectral_contrast_valleys.min", {6}}, - { "lowlevel.spectral_contrast_valleys.var", {6}}, - { "lowlevel.spectral_decrease.dmean", {1}}, - { "lowlevel.spectral_decrease.dmean2", {1}}, - { "lowlevel.spectral_decrease.dvar", {1}}, - { "lowlevel.spectral_decrease.dvar2", {1}}, - { "lowlevel.spectral_decrease.max", {1}}, - { "lowlevel.spectral_decrease.mean", {1}}, - { "lowlevel.spectral_decrease.median", {1}}, - { "lowlevel.spectral_decrease.min", {1}}, - { "lowlevel.spectral_decrease.var", {1}}, - { "lowlevel.spectral_energy.dmean", {1}}, - { "lowlevel.spectral_energy.dmean2", {1}}, - { "lowlevel.spectral_energy.dvar", {1}}, - { "lowlevel.spectral_energy.dvar2", {1}}, - { "lowlevel.spectral_energy.max", {1}}, - { "lowlevel.spectral_energy.mean", {1}}, - { "lowlevel.spectral_energy.median", {1}}, - { "lowlevel.spectral_energy.min", {1}}, - { "lowlevel.spectral_energy.var", {1}}, - { "lowlevel.spectral_energyband_high.dmean", {1}}, - { "lowlevel.spectral_energyband_high.dmean2", {1}}, - { "lowlevel.spectral_energyband_high.dvar", {1}}, - { "lowlevel.spectral_energyband_high.dvar2", {1}}, - { "lowlevel.spectral_energyband_high.max", {1}}, - { "lowlevel.spectral_energyband_high.mean", {1}}, - { "lowlevel.spectral_energyband_high.median", {1}}, - { "lowlevel.spectral_energyband_high.min", {1}}, - { "lowlevel.spectral_energyband_high.var", {1}}, - { "lowlevel.spectral_energyband_low.dmean", {1}}, - { "lowlevel.spectral_energyband_low.dmean2", {1}}, - { "lowlevel.spectral_energyband_low.dvar", {1}}, - { "lowlevel.spectral_energyband_low.dvar2", {1}}, - { "lowlevel.spectral_energyband_low.max", {1}}, - { "lowlevel.spectral_energyband_low.mean", {1}}, - { "lowlevel.spectral_energyband_low.median", {1}}, - { "lowlevel.spectral_energyband_low.min", {1}}, - { "lowlevel.spectral_energyband_low.var", {1}}, - { "lowlevel.spectral_energyband_middle_high.dmean", {1}}, - { "lowlevel.spectral_energyband_middle_high.dmean2", {1}}, - { "lowlevel.spectral_energyband_middle_high.dvar", {1}}, - { "lowlevel.spectral_energyband_middle_high.dvar2", {1}}, - { "lowlevel.spectral_energyband_middle_high.max", {1}}, - { "lowlevel.spectral_energyband_middle_high.mean", {1}}, - { "lowlevel.spectral_energyband_middle_high.median", {1}}, - { "lowlevel.spectral_energyband_middle_high.min", {1}}, - { "lowlevel.spectral_energyband_middle_high.var", {1}}, - { "lowlevel.spectral_energyband_middle_low.dmean", {1}}, - { "lowlevel.spectral_energyband_middle_low.dmean2", {1}}, - { "lowlevel.spectral_energyband_middle_low.dvar", {1}}, - { "lowlevel.spectral_energyband_middle_low.dvar2", {1}}, - { "lowlevel.spectral_energyband_middle_low.max", {1}}, - { "lowlevel.spectral_energyband_middle_low.mean", {1}}, - { "lowlevel.spectral_energyband_middle_low.median", {1}}, - { "lowlevel.spectral_energyband_middle_low.min", {1}}, - { "lowlevel.spectral_energyband_middle_low.var", {1}}, - { "lowlevel.spectral_entropy.dmean", {1}}, - { "lowlevel.spectral_entropy.dmean2", {1}}, - { "lowlevel.spectral_entropy.dvar", {1}}, - { "lowlevel.spectral_entropy.dvar2", {1}}, - { "lowlevel.spectral_entropy.max", {1}}, - { "lowlevel.spectral_entropy.mean", {1}}, - { "lowlevel.spectral_entropy.median", {1}}, - { "lowlevel.spectral_entropy.min", {1}}, - { "lowlevel.spectral_entropy.var", {1}}, - { "lowlevel.spectral_flux.dmean", {1}}, - { "lowlevel.spectral_flux.dmean2", {1}}, - { "lowlevel.spectral_flux.dvar", {1}}, - { "lowlevel.spectral_flux.dvar2", {1}}, - { "lowlevel.spectral_flux.max", {1}}, - { "lowlevel.spectral_flux.mean", {1}}, - { "lowlevel.spectral_flux.median", {1}}, - { "lowlevel.spectral_flux.min", {1}}, - { "lowlevel.spectral_flux.var", {1}}, - { "lowlevel.spectral_kurtosis.dmean", {1}}, - { "lowlevel.spectral_kurtosis.dmean2", {1}}, - { "lowlevel.spectral_kurtosis.dvar", {1}}, - { "lowlevel.spectral_kurtosis.dvar2", {1}}, - { "lowlevel.spectral_kurtosis.max", {1}}, - { "lowlevel.spectral_kurtosis.mean", {1}}, - { "lowlevel.spectral_kurtosis.median", {1}}, - { "lowlevel.spectral_kurtosis.min", {1}}, - { "lowlevel.spectral_kurtosis.var", {1}}, - { "lowlevel.spectral_rms.dmean", {1}}, - { "lowlevel.spectral_rms.dmean2", {1}}, - { "lowlevel.spectral_rms.dvar", {1}}, - { "lowlevel.spectral_rms.dvar2", {1}}, - { "lowlevel.spectral_rms.max", {1}}, - { "lowlevel.spectral_rms.mean", {1}}, - { "lowlevel.spectral_rms.median", {1}}, - { "lowlevel.spectral_rms.min", {1}}, - { "lowlevel.spectral_rms.var", {1}}, - { "lowlevel.spectral_rolloff.dmean", {1}}, - { "lowlevel.spectral_rolloff.dmean2", {1}}, - { "lowlevel.spectral_rolloff.dvar", {1}}, - { "lowlevel.spectral_rolloff.dvar2", {1}}, - { "lowlevel.spectral_rolloff.max", {1}}, - { "lowlevel.spectral_rolloff.mean", {1}}, - { "lowlevel.spectral_rolloff.median", {1}}, - { "lowlevel.spectral_rolloff.min", {1}}, - { "lowlevel.spectral_rolloff.var", {1}}, - { "lowlevel.spectral_skewness.dmean", {1}}, - { "lowlevel.spectral_skewness.dmean2", {1}}, - { "lowlevel.spectral_skewness.dvar", {1}}, - { "lowlevel.spectral_skewness.dvar2", {1}}, - { "lowlevel.spectral_skewness.max", {1}}, - { "lowlevel.spectral_skewness.mean", {1}}, - { "lowlevel.spectral_skewness.median", {1}}, - { "lowlevel.spectral_skewness.min", {1}}, - { "lowlevel.spectral_skewness.var", {1}}, - { "lowlevel.spectral_spread.dmean", {1}}, - { "lowlevel.spectral_spread.dmean2", {1}}, - { "lowlevel.spectral_spread.dvar", {1}}, - { "lowlevel.spectral_spread.dvar2", {1}}, - { "lowlevel.spectral_spread.max", {1}}, - { "lowlevel.spectral_spread.mean", {1}}, - { "lowlevel.spectral_spread.median", {1}}, - { "lowlevel.spectral_spread.min", {1}}, - { "lowlevel.spectral_spread.var", {1}}, - { "lowlevel.spectral_strongpeak.dmean", {1}}, - { "lowlevel.spectral_strongpeak.dmean2", {1}}, - { "lowlevel.spectral_strongpeak.dvar", {1}}, - { "lowlevel.spectral_strongpeak.dvar2", {1}}, - { "lowlevel.spectral_strongpeak.max", {1}}, - { "lowlevel.spectral_strongpeak.mean", {1}}, - { "lowlevel.spectral_strongpeak.median", {1}}, - { "lowlevel.spectral_strongpeak.min", {1}}, - { "lowlevel.spectral_strongpeak.var", {1}}, - { "lowlevel.zerocrossingrate.dmean", {1}}, - { "lowlevel.zerocrossingrate.dmean2", {1}}, - { "lowlevel.zerocrossingrate.dvar", {1}}, - { "lowlevel.zerocrossingrate.dvar2", {1}}, - { "lowlevel.zerocrossingrate.max", {1}}, - { "lowlevel.zerocrossingrate.mean", {1}}, - { "lowlevel.zerocrossingrate.median", {1}}, - { "lowlevel.zerocrossingrate.min", {1}}, - { "lowlevel.zerocrossingrate.var", {1}}, -}; -FeatureDef -getFeatureDef(const FeatureName& featureName) -{ - auto it {featureDefinitions.find(featureName)}; - if (it == std::cend(featureDefinitions)) - throw core::LmsException {"Unhandled requested feature '" + featureName + "'"}; + static const std::unordered_map featureDefinitions{ + { "lowlevel.average_loudness", { 1 } }, + { "lowlevel.barkbands.dmean", { 27 } }, + { "lowlevel.barkbands.dmean2", { 27 } }, + { "lowlevel.barkbands.dvar", { 27 } }, + { "lowlevel.barkbands.dvar2", { 27 } }, + { "lowlevel.barkbands.max", { 27 } }, + { "lowlevel.barkbands.mean", { 27 } }, + { "lowlevel.barkbands.median", { 27 } }, + { "lowlevel.barkbands.min", { 27 } }, + { "lowlevel.barkbands.var", { 27 } }, + { "lowlevel.barkbands_crest.dmean", { 1 } }, + { "lowlevel.barkbands_crest.dmean2", { 1 } }, + { "lowlevel.barkbands_crest.dvar", { 1 } }, + { "lowlevel.barkbands_crest.dvar2", { 1 } }, + { "lowlevel.barkbands_crest.max", { 1 } }, + { "lowlevel.barkbands_crest.mean", { 1 } }, + { "lowlevel.barkbands_crest.median", { 1 } }, + { "lowlevel.barkbands_crest.min", { 1 } }, + { "lowlevel.barkbands_crest.var", { 1 } }, + { "lowlevel.barkbands_flatness_db.dmean", { 1 } }, + { "lowlevel.barkbands_flatness_db.dmean2", { 1 } }, + { "lowlevel.barkbands_flatness_db.dvar", { 1 } }, + { "lowlevel.barkbands_flatness_db.dvar2", { 1 } }, + { "lowlevel.barkbands_flatness_db.max", { 1 } }, + { "lowlevel.barkbands_flatness_db.mean", { 1 } }, + { "lowlevel.barkbands_flatness_db.median", { 1 } }, + { "lowlevel.barkbands_flatness_db.min", { 1 } }, + { "lowlevel.barkbands_flatness_db.var", { 1 } }, + { "lowlevel.barkbands_kurtosis.dmean", { 1 } }, + { "lowlevel.barkbands_kurtosis.dmean2", { 1 } }, + { "lowlevel.barkbands_kurtosis.dvar", { 1 } }, + { "lowlevel.barkbands_kurtosis.dvar2", { 1 } }, + { "lowlevel.barkbands_kurtosis.max", { 1 } }, + { "lowlevel.barkbands_kurtosis.mean", { 1 } }, + { "lowlevel.barkbands_kurtosis.median", { 1 } }, + { "lowlevel.barkbands_kurtosis.min", { 1 } }, + { "lowlevel.barkbands_kurtosis.var", { 1 } }, + { "lowlevel.barkbands_skewness.dmean", { 1 } }, + { "lowlevel.barkbands_skewness.dmean2", { 1 } }, + { "lowlevel.barkbands_skewness.dvar", { 1 } }, + { "lowlevel.barkbands_skewness.dvar2", { 1 } }, + { "lowlevel.barkbands_skewness.max", { 1 } }, + { "lowlevel.barkbands_skewness.mean", { 1 } }, + { "lowlevel.barkbands_skewness.median", { 1 } }, + { "lowlevel.barkbands_skewness.min", { 1 } }, + { "lowlevel.barkbands_skewness.var", { 1 } }, + { "lowlevel.barkbands_spread.dmean", { 1 } }, + { "lowlevel.barkbands_spread.dmean2", { 1 } }, + { "lowlevel.barkbands_spread.dvar", { 1 } }, + { "lowlevel.barkbands_spread.dvar2", { 1 } }, + { "lowlevel.barkbands_spread.max", { 1 } }, + { "lowlevel.barkbands_spread.mean", { 1 } }, + { "lowlevel.barkbands_spread.median", { 1 } }, + { "lowlevel.barkbands_spread.min", { 1 } }, + { "lowlevel.barkbands_spread.var", { 1 } }, + { "lowlevel.dissonance.dmean", { 1 } }, + { "lowlevel.dissonance.dmean2", { 1 } }, + { "lowlevel.dissonance.dvar", { 1 } }, + { "lowlevel.dissonance.dvar2", { 1 } }, + { "lowlevel.dissonance.max", { 1 } }, + { "lowlevel.dissonance.mean", { 1 } }, + { "lowlevel.dissonance.median", { 1 } }, + { "lowlevel.dissonance.min", { 1 } }, + { "lowlevel.dissonance.var", { 1 } }, + { "lowlevel.dynamic_complexity", { 1 } }, + { "lowlevel.erbbands.dmean", { 40 } }, + { "lowlevel.erbbands.dmean2", { 40 } }, + { "lowlevel.erbbands.dvar", { 40 } }, + { "lowlevel.erbbands.dvar2", { 40 } }, + { "lowlevel.erbbands.max", { 40 } }, + { "lowlevel.erbbands.mean", { 40 } }, + { "lowlevel.erbbands.median", { 40 } }, + { "lowlevel.erbbands.min", { 40 } }, + { "lowlevel.erbbands.var", { 40 } }, + { "lowlevel.gfcc.mean", { 13 } }, + { "lowlevel.hfc.dmean", { 1 } }, + { "lowlevel.hfc.dmean2", { 1 } }, + { "lowlevel.hfc.dvar", { 1 } }, + { "lowlevel.hfc.dvar2", { 1 } }, + { "lowlevel.hfc.max", { 1 } }, + { "lowlevel.hfc.mean", { 1 } }, + { "lowlevel.hfc.median", { 1 } }, + { "lowlevel.hfc.min", { 1 } }, + { "lowlevel.hfc.var", { 1 } }, + { "tonal.hpcp.median", { 36 } }, + { "lowlevel.melbands.dmean", { 40 } }, + { "lowlevel.melbands.dmean2", { 40 } }, + { "lowlevel.melbands.dvar", { 40 } }, + { "lowlevel.melbands.dvar2", { 40 } }, + { "lowlevel.melbands.max", { 40 } }, + { "lowlevel.melbands.mean", { 40 } }, + { "lowlevel.melbands.median", { 40 } }, + { "lowlevel.melbands.min", { 40 } }, + { "lowlevel.melbands.var", { 40 } }, + { "lowlevel.melbands_crest.dmean", { 1 } }, + { "lowlevel.melbands_crest.dmean2", { 1 } }, + { "lowlevel.melbands_crest.dvar", { 1 } }, + { "lowlevel.melbands_crest.dvar2", { 1 } }, + { "lowlevel.melbands_crest.max", { 1 } }, + { "lowlevel.melbands_crest.mean", { 1 } }, + { "lowlevel.melbands_crest.median", { 1 } }, + { "lowlevel.melbands_crest.min", { 1 } }, + { "lowlevel.melbands_crest.var", { 1 } }, + { "lowlevel.melbands_flatness_db.dmean", { 1 } }, + { "lowlevel.melbands_flatness_db.dmean2", { 1 } }, + { "lowlevel.melbands_flatness_db.dvar", { 1 } }, + { "lowlevel.melbands_flatness_db.dvar2", { 1 } }, + { "lowlevel.melbands_flatness_db.max", { 1 } }, + { "lowlevel.melbands_flatness_db.mean", { 1 } }, + { "lowlevel.melbands_flatness_db.median", { 1 } }, + { "lowlevel.melbands_flatness_db.min", { 1 } }, + { "lowlevel.melbands_flatness_db.var", { 1 } }, + { "lowlevel.melbands_kurtosis.dmean", { 1 } }, + { "lowlevel.melbands_kurtosis.dmean2", { 1 } }, + { "lowlevel.melbands_kurtosis.dvar", { 1 } }, + { "lowlevel.melbands_kurtosis.dvar2", { 1 } }, + { "lowlevel.melbands_kurtosis.max", { 1 } }, + { "lowlevel.melbands_kurtosis.mean", { 1 } }, + { "lowlevel.melbands_kurtosis.median", { 1 } }, + { "lowlevel.melbands_kurtosis.min", { 1 } }, + { "lowlevel.melbands_kurtosis.var", { 1 } }, + { "lowlevel.melbands_skewness.dmean", { 1 } }, + { "lowlevel.melbands_skewness.dmean2", { 1 } }, + { "lowlevel.melbands_skewness.dvar", { 1 } }, + { "lowlevel.melbands_skewness.dvar2", { 1 } }, + { "lowlevel.melbands_skewness.max", { 1 } }, + { "lowlevel.melbands_skewness.mean", { 1 } }, + { "lowlevel.melbands_skewness.median", { 1 } }, + { "lowlevel.melbands_skewness.min", { 1 } }, + { "lowlevel.melbands_skewness.var", { 1 } }, + { "lowlevel.melbands_spread.dmean", { 1 } }, + { "lowlevel.melbands_spread.dmean2", { 1 } }, + { "lowlevel.melbands_spread.dvar", { 1 } }, + { "lowlevel.melbands_spread.dvar2", { 1 } }, + { "lowlevel.melbands_spread.max", { 1 } }, + { "lowlevel.melbands_spread.mean", { 1 } }, + { "lowlevel.melbands_spread.median", { 1 } }, + { "lowlevel.melbands_spread.min", { 1 } }, + { "lowlevel.melbands_spread.var", { 1 } }, + { "lowlevel.mfcc.mean", { 13 } }, + { "lowlevel.pitch_salience.dmean", { 1 } }, + { "lowlevel.pitch_salience.dmean2", { 1 } }, + { "lowlevel.pitch_salience.dvar", { 1 } }, + { "lowlevel.pitch_salience.dvar2", { 1 } }, + { "lowlevel.pitch_salience.max", { 1 } }, + { "lowlevel.pitch_salience.mean", { 1 } }, + { "lowlevel.pitch_salience.median", { 1 } }, + { "lowlevel.pitch_salience.min", { 1 } }, + { "lowlevel.pitch_salience.var", { 1 } }, + { "lowlevel.silence_rate_30dB.dmean", { 1 } }, + { "lowlevel.silence_rate_30dB.dmean2", { 1 } }, + { "lowlevel.silence_rate_30dB.dvar", { 1 } }, + { "lowlevel.silence_rate_30dB.dvar2", { 1 } }, + { "lowlevel.silence_rate_30dB.max", { 1 } }, + { "lowlevel.silence_rate_30dB.mean", { 1 } }, + { "lowlevel.silence_rate_30dB.median", { 1 } }, + { "lowlevel.silence_rate_30dB.min", { 1 } }, + { "lowlevel.silence_rate_30dB.var", { 1 } }, + { "lowlevel.silence_rate_60dB.dmean", { 1 } }, + { "lowlevel.silence_rate_60dB.dmean2", { 1 } }, + { "lowlevel.silence_rate_60dB.dvar", { 1 } }, + { "lowlevel.silence_rate_60dB.dvar2", { 1 } }, + { "lowlevel.silence_rate_60dB.max", { 1 } }, + { "lowlevel.silence_rate_60dB.mean", { 1 } }, + { "lowlevel.silence_rate_60dB.median", { 1 } }, + { "lowlevel.silence_rate_60dB.min", { 1 } }, + { "lowlevel.silence_rate_60dB.var", { 1 } }, + { "lowlevel.spectral_centroid.dmean", { 1 } }, + { "lowlevel.spectral_centroid.dmean2", { 1 } }, + { "lowlevel.spectral_centroid.dvar", { 1 } }, + { "lowlevel.spectral_centroid.dvar2", { 1 } }, + { "lowlevel.spectral_centroid.max", { 1 } }, + { "lowlevel.spectral_centroid.mean", { 1 } }, + { "lowlevel.spectral_centroid.median", { 1 } }, + { "lowlevel.spectral_centroid.min", { 1 } }, + { "lowlevel.spectral_centroid.var", { 1 } }, + { "lowlevel.spectral_complexity.dmean", { 1 } }, + { "lowlevel.spectral_complexity.dmean2", { 1 } }, + { "lowlevel.spectral_complexity.dvar", { 1 } }, + { "lowlevel.spectral_complexity.dvar2", { 1 } }, + { "lowlevel.spectral_complexity.max", { 1 } }, + { "lowlevel.spectral_complexity.mean", { 1 } }, + { "lowlevel.spectral_complexity.median", { 1 } }, + { "lowlevel.spectral_complexity.min", { 1 } }, + { "lowlevel.spectral_complexity.var", { 1 } }, + { "lowlevel.spectral_contrast_coeffs.dmean", { 6 } }, + { "lowlevel.spectral_contrast_coeffs.dmean2", { 6 } }, + { "lowlevel.spectral_contrast_coeffs.dvar", { 6 } }, + { "lowlevel.spectral_contrast_coeffs.dvar2", { 6 } }, + { "lowlevel.spectral_contrast_coeffs.max", { 6 } }, + { "lowlevel.spectral_contrast_coeffs.mean", { 6 } }, + { "lowlevel.spectral_contrast_coeffs.median", { 6 } }, + { "lowlevel.spectral_contrast_coeffs.min", { 6 } }, + { "lowlevel.spectral_contrast_coeffs.var", { 6 } }, + { "lowlevel.spectral_contrast_valleys.dmean", { 6 } }, + { "lowlevel.spectral_contrast_valleys.dmean2", { 6 } }, + { "lowlevel.spectral_contrast_valleys.dvar", { 6 } }, + { "lowlevel.spectral_contrast_valleys.dvar2", { 6 } }, + { "lowlevel.spectral_contrast_valleys.max", { 6 } }, + { "lowlevel.spectral_contrast_valleys.mean", { 6 } }, + { "lowlevel.spectral_contrast_valleys.median", { 6 } }, + { "lowlevel.spectral_contrast_valleys.min", { 6 } }, + { "lowlevel.spectral_contrast_valleys.var", { 6 } }, + { "lowlevel.spectral_decrease.dmean", { 1 } }, + { "lowlevel.spectral_decrease.dmean2", { 1 } }, + { "lowlevel.spectral_decrease.dvar", { 1 } }, + { "lowlevel.spectral_decrease.dvar2", { 1 } }, + { "lowlevel.spectral_decrease.max", { 1 } }, + { "lowlevel.spectral_decrease.mean", { 1 } }, + { "lowlevel.spectral_decrease.median", { 1 } }, + { "lowlevel.spectral_decrease.min", { 1 } }, + { "lowlevel.spectral_decrease.var", { 1 } }, + { "lowlevel.spectral_energy.dmean", { 1 } }, + { "lowlevel.spectral_energy.dmean2", { 1 } }, + { "lowlevel.spectral_energy.dvar", { 1 } }, + { "lowlevel.spectral_energy.dvar2", { 1 } }, + { "lowlevel.spectral_energy.max", { 1 } }, + { "lowlevel.spectral_energy.mean", { 1 } }, + { "lowlevel.spectral_energy.median", { 1 } }, + { "lowlevel.spectral_energy.min", { 1 } }, + { "lowlevel.spectral_energy.var", { 1 } }, + { "lowlevel.spectral_energyband_high.dmean", { 1 } }, + { "lowlevel.spectral_energyband_high.dmean2", { 1 } }, + { "lowlevel.spectral_energyband_high.dvar", { 1 } }, + { "lowlevel.spectral_energyband_high.dvar2", { 1 } }, + { "lowlevel.spectral_energyband_high.max", { 1 } }, + { "lowlevel.spectral_energyband_high.mean", { 1 } }, + { "lowlevel.spectral_energyband_high.median", { 1 } }, + { "lowlevel.spectral_energyband_high.min", { 1 } }, + { "lowlevel.spectral_energyband_high.var", { 1 } }, + { "lowlevel.spectral_energyband_low.dmean", { 1 } }, + { "lowlevel.spectral_energyband_low.dmean2", { 1 } }, + { "lowlevel.spectral_energyband_low.dvar", { 1 } }, + { "lowlevel.spectral_energyband_low.dvar2", { 1 } }, + { "lowlevel.spectral_energyband_low.max", { 1 } }, + { "lowlevel.spectral_energyband_low.mean", { 1 } }, + { "lowlevel.spectral_energyband_low.median", { 1 } }, + { "lowlevel.spectral_energyband_low.min", { 1 } }, + { "lowlevel.spectral_energyband_low.var", { 1 } }, + { "lowlevel.spectral_energyband_middle_high.dmean", { 1 } }, + { "lowlevel.spectral_energyband_middle_high.dmean2", { 1 } }, + { "lowlevel.spectral_energyband_middle_high.dvar", { 1 } }, + { "lowlevel.spectral_energyband_middle_high.dvar2", { 1 } }, + { "lowlevel.spectral_energyband_middle_high.max", { 1 } }, + { "lowlevel.spectral_energyband_middle_high.mean", { 1 } }, + { "lowlevel.spectral_energyband_middle_high.median", { 1 } }, + { "lowlevel.spectral_energyband_middle_high.min", { 1 } }, + { "lowlevel.spectral_energyband_middle_high.var", { 1 } }, + { "lowlevel.spectral_energyband_middle_low.dmean", { 1 } }, + { "lowlevel.spectral_energyband_middle_low.dmean2", { 1 } }, + { "lowlevel.spectral_energyband_middle_low.dvar", { 1 } }, + { "lowlevel.spectral_energyband_middle_low.dvar2", { 1 } }, + { "lowlevel.spectral_energyband_middle_low.max", { 1 } }, + { "lowlevel.spectral_energyband_middle_low.mean", { 1 } }, + { "lowlevel.spectral_energyband_middle_low.median", { 1 } }, + { "lowlevel.spectral_energyband_middle_low.min", { 1 } }, + { "lowlevel.spectral_energyband_middle_low.var", { 1 } }, + { "lowlevel.spectral_entropy.dmean", { 1 } }, + { "lowlevel.spectral_entropy.dmean2", { 1 } }, + { "lowlevel.spectral_entropy.dvar", { 1 } }, + { "lowlevel.spectral_entropy.dvar2", { 1 } }, + { "lowlevel.spectral_entropy.max", { 1 } }, + { "lowlevel.spectral_entropy.mean", { 1 } }, + { "lowlevel.spectral_entropy.median", { 1 } }, + { "lowlevel.spectral_entropy.min", { 1 } }, + { "lowlevel.spectral_entropy.var", { 1 } }, + { "lowlevel.spectral_flux.dmean", { 1 } }, + { "lowlevel.spectral_flux.dmean2", { 1 } }, + { "lowlevel.spectral_flux.dvar", { 1 } }, + { "lowlevel.spectral_flux.dvar2", { 1 } }, + { "lowlevel.spectral_flux.max", { 1 } }, + { "lowlevel.spectral_flux.mean", { 1 } }, + { "lowlevel.spectral_flux.median", { 1 } }, + { "lowlevel.spectral_flux.min", { 1 } }, + { "lowlevel.spectral_flux.var", { 1 } }, + { "lowlevel.spectral_kurtosis.dmean", { 1 } }, + { "lowlevel.spectral_kurtosis.dmean2", { 1 } }, + { "lowlevel.spectral_kurtosis.dvar", { 1 } }, + { "lowlevel.spectral_kurtosis.dvar2", { 1 } }, + { "lowlevel.spectral_kurtosis.max", { 1 } }, + { "lowlevel.spectral_kurtosis.mean", { 1 } }, + { "lowlevel.spectral_kurtosis.median", { 1 } }, + { "lowlevel.spectral_kurtosis.min", { 1 } }, + { "lowlevel.spectral_kurtosis.var", { 1 } }, + { "lowlevel.spectral_rms.dmean", { 1 } }, + { "lowlevel.spectral_rms.dmean2", { 1 } }, + { "lowlevel.spectral_rms.dvar", { 1 } }, + { "lowlevel.spectral_rms.dvar2", { 1 } }, + { "lowlevel.spectral_rms.max", { 1 } }, + { "lowlevel.spectral_rms.mean", { 1 } }, + { "lowlevel.spectral_rms.median", { 1 } }, + { "lowlevel.spectral_rms.min", { 1 } }, + { "lowlevel.spectral_rms.var", { 1 } }, + { "lowlevel.spectral_rolloff.dmean", { 1 } }, + { "lowlevel.spectral_rolloff.dmean2", { 1 } }, + { "lowlevel.spectral_rolloff.dvar", { 1 } }, + { "lowlevel.spectral_rolloff.dvar2", { 1 } }, + { "lowlevel.spectral_rolloff.max", { 1 } }, + { "lowlevel.spectral_rolloff.mean", { 1 } }, + { "lowlevel.spectral_rolloff.median", { 1 } }, + { "lowlevel.spectral_rolloff.min", { 1 } }, + { "lowlevel.spectral_rolloff.var", { 1 } }, + { "lowlevel.spectral_skewness.dmean", { 1 } }, + { "lowlevel.spectral_skewness.dmean2", { 1 } }, + { "lowlevel.spectral_skewness.dvar", { 1 } }, + { "lowlevel.spectral_skewness.dvar2", { 1 } }, + { "lowlevel.spectral_skewness.max", { 1 } }, + { "lowlevel.spectral_skewness.mean", { 1 } }, + { "lowlevel.spectral_skewness.median", { 1 } }, + { "lowlevel.spectral_skewness.min", { 1 } }, + { "lowlevel.spectral_skewness.var", { 1 } }, + { "lowlevel.spectral_spread.dmean", { 1 } }, + { "lowlevel.spectral_spread.dmean2", { 1 } }, + { "lowlevel.spectral_spread.dvar", { 1 } }, + { "lowlevel.spectral_spread.dvar2", { 1 } }, + { "lowlevel.spectral_spread.max", { 1 } }, + { "lowlevel.spectral_spread.mean", { 1 } }, + { "lowlevel.spectral_spread.median", { 1 } }, + { "lowlevel.spectral_spread.min", { 1 } }, + { "lowlevel.spectral_spread.var", { 1 } }, + { "lowlevel.spectral_strongpeak.dmean", { 1 } }, + { "lowlevel.spectral_strongpeak.dmean2", { 1 } }, + { "lowlevel.spectral_strongpeak.dvar", { 1 } }, + { "lowlevel.spectral_strongpeak.dvar2", { 1 } }, + { "lowlevel.spectral_strongpeak.max", { 1 } }, + { "lowlevel.spectral_strongpeak.mean", { 1 } }, + { "lowlevel.spectral_strongpeak.median", { 1 } }, + { "lowlevel.spectral_strongpeak.min", { 1 } }, + { "lowlevel.spectral_strongpeak.var", { 1 } }, + { "lowlevel.zerocrossingrate.dmean", { 1 } }, + { "lowlevel.zerocrossingrate.dmean2", { 1 } }, + { "lowlevel.zerocrossingrate.dvar", { 1 } }, + { "lowlevel.zerocrossingrate.dvar2", { 1 } }, + { "lowlevel.zerocrossingrate.max", { 1 } }, + { "lowlevel.zerocrossingrate.mean", { 1 } }, + { "lowlevel.zerocrossingrate.median", { 1 } }, + { "lowlevel.zerocrossingrate.min", { 1 } }, + { "lowlevel.zerocrossingrate.var", { 1 } }, + }; - return it->second; -} + FeatureDef getFeatureDef(const FeatureName& featureName) + { + auto it{ featureDefinitions.find(featureName) }; + if (it == std::cend(featureDefinitions)) + throw core::LmsException{ "Unhandled requested feature '" + featureName + "'" }; -FeatureNames -getFeatureNames() -{ - FeatureNames res; + return it->second; + } - std::transform(std::cbegin(featureDefinitions), std::cend(featureDefinitions), - std::inserter(res, std::begin(res)), [](auto itFeature) { return itFeature.first; }); + FeatureNames getFeatureNames() + { + FeatureNames res; - return res; -} + std::transform(std::cbegin(featureDefinitions), std::cend(featureDefinitions), + std::inserter(res, std::begin(res)), [](auto itFeature) { return itFeature.first; }); -} // namespace lms::recommendation + return res; + } +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/features/FeaturesDefs.hpp b/src/libs/services/recommendation/impl/features/FeaturesDefs.hpp index 6a83b7fbb..dbb0ddf6c 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesDefs.hpp +++ b/src/libs/services/recommendation/impl/features/FeaturesDefs.hpp @@ -24,26 +24,27 @@ #include #include -namespace lms::recommendation { - -using FeatureName = std::string; -using FeatureNames = std::unordered_set; -using FeatureValue = double; -using FeatureValues = std::vector; -using FeatureValuesMap = std::unordered_map; - -struct FeatureDef +namespace lms::recommendation { - std::size_t nbDimensions {}; -}; -FeatureDef getFeatureDef(const FeatureName& featureName); -FeatureNames getFeatureNames(); - -struct FeatureSettings -{ - double weight {}; -}; -using FeatureSettingsMap = std::unordered_map; + using FeatureName = std::string; + using FeatureNames = std::unordered_set; + using FeatureValue = double; + using FeatureValues = std::vector; + using FeatureValuesMap = std::unordered_map; + + struct FeatureDef + { + std::size_t nbDimensions{}; + }; + + FeatureDef getFeatureDef(const FeatureName& featureName); + FeatureNames getFeatureNames(); + + struct FeatureSettings + { + double weight{}; + }; + using FeatureSettingsMap = std::unordered_map; } // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp b/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp index 0cec15bae..cca183fcf 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp +++ b/src/libs/services/recommendation/impl/features/FeaturesEngine.cpp @@ -21,6 +21,8 @@ #include +#include "core/ILogger.hpp" +#include "core/Random.hpp" #include "database/Artist.hpp" #include "database/Db.hpp" #include "database/Release.hpp" @@ -30,8 +32,6 @@ #include "database/TrackFeatures.hpp" #include "database/TrackList.hpp" #include "som/DataNormalizer.hpp" -#include "core/ILogger.hpp" -#include "core/Random.hpp" namespace lms::recommendation { @@ -47,7 +47,7 @@ namespace lms::recommendation std::optional convertFeatureValuesMapToInputVector(const FeatureValuesMap& featureValuesMap, std::size_t nbDimensions) { std::size_t i{}; - std::optional res{ som::InputVector {nbDimensions} }; + std::optional res{ som::InputVector{ nbDimensions } }; for (const auto& [featureName, values] : featureValuesMap) { if (values.size() != getFeatureDef(featureName).nbDimensions) @@ -80,17 +80,16 @@ namespace lms::recommendation return weights; } - } + } // namespace const FeatureSettingsMap& FeaturesEngine::getDefaultTrainFeatureSettings() { - static const FeatureSettingsMap defaultTrainFeatureSettings - { - { "lowlevel.spectral_energyband_high.mean", {1}}, - { "lowlevel.spectral_rolloff.median", {1}}, - { "lowlevel.spectral_contrast_valleys.var", {1}}, - { "lowlevel.erbbands.mean", {1}}, - { "lowlevel.gfcc.mean", {1}}, + static const FeatureSettingsMap defaultTrainFeatureSettings{ + { "lowlevel.spectral_energyband_high.mean", { 1 } }, + { "lowlevel.spectral_rolloff.median", { 1 } }, + { "lowlevel.spectral_contrast_valleys.var", { 1 } }, + { "lowlevel.erbbands.mean", { 1 } }, + { "lowlevel.gfcc.mean", { 1 } }, }; return defaultTrainFeatureSettings; @@ -104,12 +103,12 @@ namespace lms::recommendation std::transform(std::cbegin(trainSettings.featureSettingsMap), std::cend(trainSettings.featureSettingsMap), std::inserter(featureNames, std::begin(featureNames)), [](const auto& itFeatureSetting) { return itFeatureSetting.first; }); - const std::size_t nbDimensions{ std::accumulate(std::cbegin(featureNames), std::cend(featureNames), std::size_t {0}, - [](std::size_t sum, const FeatureName& featureName) { return sum + getFeatureDef(featureName).nbDimensions; }) }; + const std::size_t nbDimensions{ std::accumulate(std::cbegin(featureNames), std::cend(featureNames), std::size_t{ 0 }, + [](std::size_t sum, const FeatureName& featureName) { return sum + getFeatureDef(featureName).nbDimensions; }) }; LMS_LOG(RECOMMENDATION, DEBUG, "Features dimension = " << nbDimensions); - Session & session{ _db.getTLSSession() }; + Session& session{ _db.getTLSSession() }; RangeResults trackFeaturesIds; { @@ -178,10 +177,9 @@ namespace lms::recommendation som::InputVector weights{ getInputVectorWeights(trainSettings.featureSettingsMap, nbDimensions) }; network.setDataWeights(weights); - auto somProgressCallback{ [&](const som::Network::CurrentIteration& iter) - { + auto somProgressCallback{ [&](const som::Network::CurrentIteration& iter) { LMS_LOG(RECOMMENDATION, DEBUG, "Current pass = " << iter.idIteration << " / " << iter.iterationCount); - progressCallback(Progress {iter.idIteration, iter.iterationCount}); + progressCallback(Progress{ iter.idIteration, iter.iterationCount }); } }; LMS_LOG(RECOMMENDATION, DEBUG, "Training network..."); @@ -216,15 +214,14 @@ namespace lms::recommendation TrackContainer FeaturesEngine::findSimilarTracksFromTrackList(TrackListId trackListId, std::size_t maxCount) const { - const TrackContainer trackIds{ [&] - { + const TrackContainer trackIds{ [&] { TrackContainer res; - Session& session {_db.getTLSSession()}; + Session& session{ _db.getTLSSession() }; - auto transaction {session.createReadTransaction()}; + auto transaction{ session.createReadTransaction() }; - const TrackList::pointer trackList {TrackList::find(session, trackListId)}; + const TrackList::pointer trackList{ TrackList::find(session, trackListId) }; if (trackList) res = trackList->getTrackIds(); @@ -245,10 +242,10 @@ namespace lms::recommendation auto transaction{ session.createReadTransaction() }; similarTrackIds.erase(std::remove_if(std::begin(similarTrackIds), std::end(similarTrackIds), - [&](TrackId trackId) - { - return !Track::exists(session, trackId); - }), std::end(similarTrackIds)); + [&](TrackId trackId) { + return !Track::exists(session, trackId); + }), + std::end(similarTrackIds)); } return similarTrackIds; @@ -256,7 +253,7 @@ namespace lms::recommendation ReleaseContainer FeaturesEngine::getSimilarReleases(ReleaseId releaseId, std::size_t maxCount) const { - auto similarReleaseIds{ getSimilarObjects({releaseId}, _releaseMatrix, _releasePositions, maxCount) }; + auto similarReleaseIds{ getSimilarObjects({ releaseId }, _releaseMatrix, _releasePositions, maxCount) }; Session& session{ _db.getTLSSession() }; @@ -266,10 +263,10 @@ namespace lms::recommendation auto transaction{ session.createReadTransaction() }; similarReleaseIds.erase(std::remove_if(std::begin(similarReleaseIds), std::end(similarReleaseIds), - [&](ReleaseId releaseId) - { - return !Release::exists(session, releaseId); - }), std::end(similarReleaseIds)); + [&](ReleaseId releaseId) { + return !Release::exists(session, releaseId); + }), + std::end(similarReleaseIds)); } return similarReleaseIds; @@ -277,17 +274,16 @@ namespace lms::recommendation ArtistContainer FeaturesEngine::getSimilarArtists(ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const { - auto getSimilarArtistIdsForLinkType{ [&](TrackArtistLinkType linkType) - { + auto getSimilarArtistIdsForLinkType{ [&](TrackArtistLinkType linkType) { ArtistContainer similarArtistIds; - const auto itArtists {_artistMatrix.find(linkType)}; + const auto itArtists{ _artistMatrix.find(linkType) }; if (itArtists == std::cend(_artistMatrix)) { return similarArtistIds; } - return getSimilarObjects({artistId}, itArtists->second, _artistPositions, maxCount); + return getSimilarObjects({ artistId }, itArtists->second, _artistPositions, maxCount); } }; std::unordered_set similarArtistIds; @@ -306,10 +302,10 @@ namespace lms::recommendation auto transaction{ session.createReadTransaction() }; res.erase(std::remove_if(std::begin(res), std::end(res), - [&](ArtistId artistId) - { - return !Artist::exists(session, artistId); - }), std::end(res)); + [&](ArtistId artistId) { + return !Artist::exists(session, artistId); + }), + std::end(res)); } while (res.size() > maxCount) @@ -364,7 +360,7 @@ namespace lms::recommendation LMS_LOG(RECOMMENDATION, DEBUG, "Constructing maps..."); - Session & session{ _db.getTLSSession() }; + Session& session{ _db.getTLSSession() }; for (const auto& [trackId, positions] : trackPositions) { @@ -410,4 +406,4 @@ namespace lms::recommendation LMS_LOG(RECOMMENDATION, INFO, "Classifier successfully loaded!"); } -} // ns Recommendation +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/features/FeaturesEngine.hpp b/src/libs/services/recommendation/impl/features/FeaturesEngine.hpp index 8506dee7f..1e0c91fa2 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesEngine.hpp +++ b/src/libs/services/recommendation/impl/features/FeaturesEngine.hpp @@ -21,17 +21,18 @@ #include #include -#include #include #include +#include #include +#include "core/Utils.hpp" #include "som/DataNormalizer.hpp" #include "som/Network.hpp" -#include "core/Utils.hpp" -#include "IEngine.hpp" -#include "FeaturesEngineCache.hpp" + #include "FeaturesDefs.hpp" +#include "FeaturesEngineCache.hpp" +#include "IEngine.hpp" namespace lms::db { @@ -45,7 +46,8 @@ namespace lms::recommendation class FeaturesEngine : public IEngine { public: - FeaturesEngine(db::Db& db) : _db{ db } {} + FeaturesEngine(db::Db& db) + : _db{ db } {} FeaturesEngine(const FeaturesEngine&) = delete; FeaturesEngine(FeaturesEngine&&) = delete; @@ -74,14 +76,14 @@ namespace lms::recommendation }; void loadFromTraining(const TrainSettings& trainSettings, const ProgressCallback& progressCallback); - template + template using ObjectPositions = std::unordered_map>; using ArtistPositions = ObjectPositions; using ReleasePositions = ObjectPositions; using TrackPositions = ObjectPositions; - template + template using ObjectMatrix = som::Matrix>; using ArtistMatrix = ObjectMatrix; using ReleaseMatrix = ObjectMatrix; @@ -91,34 +93,34 @@ namespace lms::recommendation FeaturesEngineCache toCache() const; - template + template static std::vector getMatchingRefVectorsPosition(const std::vector& ids, const ObjectPositions& objectPositions); - template + template static std::vector getObjectsIds(const std::vector& positions, const ObjectMatrix& objectsMatrix); - template + template std::vector getSimilarObjects(const std::vector& ids, const ObjectMatrix& objectMatrix, const ObjectPositions& objectPositions, std::size_t maxCount) const; db::Db& _db; - bool _loadCancelled{}; - std::unique_ptr _network; - double _networkRefVectorsDistanceMedian{}; + bool _loadCancelled{}; + std::unique_ptr _network; + double _networkRefVectorsDistanceMedian{}; - ArtistPositions _artistPositions; + ArtistPositions _artistPositions; std::unordered_map _artistMatrix; - ReleasePositions _releasePositions; - ReleaseMatrix _releaseMatrix; + ReleasePositions _releasePositions; + ReleaseMatrix _releaseMatrix; - TrackPositions _trackPositions; - TrackMatrix _trackMatrix; + TrackPositions _trackPositions; + TrackMatrix _trackMatrix; }; - template + template std::vector FeaturesEngine::getMatchingRefVectorsPosition(const std::vector& ids, const ObjectPositions& objectPositions) { std::vector res; @@ -139,7 +141,7 @@ namespace lms::recommendation return res; } - template + template std::vector FeaturesEngine::getObjectsIds(const std::vector& positions, const ObjectMatrix& objectMatrix) { std::vector res; @@ -153,11 +155,11 @@ namespace lms::recommendation return res; } - template + template std::vector FeaturesEngine::getSimilarObjects(const std::vector& ids, - const ObjectMatrix& objectMatrix, - const ObjectPositions& objectPositions, - std::size_t maxCount) const + const ObjectMatrix& objectMatrix, + const ObjectPositions& objectPositions, + std::size_t maxCount) const { std::vector res; @@ -171,11 +173,10 @@ namespace lms::recommendation // Remove objects that are already in input or already reported closestObjectIds.erase(std::remove_if(std::begin(closestObjectIds), std::end(closestObjectIds), - [&](IdType id) - { - return std::find(std::cbegin(ids), std::cend(ids), id) != std::cend(ids); - }) - , std::end(closestObjectIds)); + [&](IdType id) { + return std::find(std::cbegin(ids), std::cend(ids), id) != std::cend(ids); + }), + std::end(closestObjectIds)); for (IdType id : closestObjectIds) { @@ -198,4 +199,4 @@ namespace lms::recommendation return res; } -} +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/features/FeaturesEngineCache.cpp b/src/libs/services/recommendation/impl/features/FeaturesEngineCache.cpp index dd3279ddc..3934e3a19 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesEngineCache.cpp +++ b/src/libs/services/recommendation/impl/features/FeaturesEngineCache.cpp @@ -86,7 +86,7 @@ namespace lms::recommendation return false; } } - } + } // namespace std::optional FeaturesEngineCache::createNetworkFromCacheFile(const std::filesystem::path& path) { @@ -240,8 +240,8 @@ namespace lms::recommendation } FeaturesEngineCache::FeaturesEngineCache(som::Network network, TrackPositions trackPositions) - : _network{ std::move(network) }, - _trackPositions{ std::move(trackPositions) } + : _network{ std::move(network) } + , _trackPositions{ std::move(trackPositions) } { } diff --git a/src/libs/services/recommendation/impl/features/FeaturesEngineCache.hpp b/src/libs/services/recommendation/impl/features/FeaturesEngineCache.hpp index c83bbf500..84ca86975 100644 --- a/src/libs/services/recommendation/impl/features/FeaturesEngineCache.hpp +++ b/src/libs/services/recommendation/impl/features/FeaturesEngineCache.hpp @@ -47,8 +47,8 @@ namespace lms::recommendation friend class FeaturesEngine; - som::Network _network; - TrackPositions _trackPositions; + som::Network _network; + TrackPositions _trackPositions; }; } // namespace lms::recommendation diff --git a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.cpp b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.cpp index 831041c49..0174a9723 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.cpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.cpp @@ -21,77 +21,73 @@ #include +#include "core/ILogger.hpp" #include "database/Db.hpp" #include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" -#include "core/ILogger.hpp" namespace lms::recommendation::PlaylistGeneratorConstraint { - namespace - { - std::size_t - countCommonArtists(const ArtistContainer& artists1, const ArtistContainer& artists2) - { - ArtistContainer intersection; - - std::set_intersection(std::cbegin(artists1), std::cend(artists1), - std::cbegin(artists2), std::cend(artists2), - std::back_inserter(intersection)); - - return intersection.size(); - } - } + namespace + { + std::size_t countCommonArtists(const ArtistContainer& artists1, const ArtistContainer& artists2) + { + ArtistContainer intersection; - ConsecutiveArtists::ConsecutiveArtists(db::Db& db) - : _db {db} - {} + std::set_intersection(std::cbegin(artists1), std::cend(artists1), + std::cbegin(artists2), std::cend(artists2), + std::back_inserter(intersection)); - float - ConsecutiveArtists::computeScore(const std::vector& trackIds, std::size_t trackIndex) - { - assert(!trackIds.empty()); - assert(trackIndex <= trackIds.size() - 1); + return intersection.size(); + } + } // namespace - const ArtistContainer artists {getArtists(trackIds[trackIndex])}; + ConsecutiveArtists::ConsecutiveArtists(db::Db& db) + : _db{ db } + { + } - constexpr std::size_t rangeSize{ 3 }; // check up to rangeSize tracks before/after the target track - static_assert(rangeSize > 0); + float ConsecutiveArtists::computeScore(const std::vector& trackIds, std::size_t trackIndex) + { + assert(!trackIds.empty()); + assert(trackIndex <= trackIds.size() - 1); - float score {}; - for (std::size_t i {1}; i < rangeSize; ++i) - { - if (trackIndex >= i) - score += countCommonArtists(artists, getArtists(trackIds[trackIndex - i])) / static_cast(i); + const ArtistContainer artists{ getArtists(trackIds[trackIndex]) }; - if (trackIndex + i < trackIds.size()) - score += countCommonArtists(artists, getArtists(trackIds[trackIndex + i])) / static_cast(i); - } + constexpr std::size_t rangeSize{ 3 }; // check up to rangeSize tracks before/after the target track + static_assert(rangeSize > 0); - return score; - } + float score{}; + for (std::size_t i{ 1 }; i < rangeSize; ++i) + { + if (trackIndex >= i) + score += countCommonArtists(artists, getArtists(trackIds[trackIndex - i])) / static_cast(i); - ArtistContainer - ConsecutiveArtists::getArtists(db::TrackId trackId) - { - using namespace db; + if (trackIndex + i < trackIds.size()) + score += countCommonArtists(artists, getArtists(trackIds[trackIndex + i])) / static_cast(i); + } - ArtistContainer res; + return score; + } - Session& dbSession {_db.getTLSSession()}; - auto transaction {dbSession.createReadTransaction()}; + ArtistContainer ConsecutiveArtists::getArtists(db::TrackId trackId) + { + using namespace db; - const Track::pointer track {Track::find(dbSession, trackId)}; - if (!track) - return res; + ArtistContainer res; - res = track->getArtistIds({}); - std::sort(std::begin(res), std::end(res)); + Session& dbSession{ _db.getTLSSession() }; + auto transaction{ dbSession.createReadTransaction() }; - return res; - } + const Track::pointer track{ Track::find(dbSession, trackId) }; + if (!track) + return res; + res = track->getArtistIds({}); + std::sort(std::begin(res), std::end(res)); -} // namespace lms::recommendation + return res; + } +} // namespace lms::recommendation::PlaylistGeneratorConstraint diff --git a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.hpp b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.hpp index 36dc4e262..2ae751445 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.hpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveArtists.hpp @@ -25,21 +25,20 @@ namespace lms::db { - class Db; + class Db; } namespace lms::recommendation::PlaylistGeneratorConstraint { - class ConsecutiveArtists : public IConstraint - { - public: - ConsecutiveArtists(db::Db& db); + class ConsecutiveArtists : public IConstraint + { + public: + ConsecutiveArtists(db::Db& db); - private: - float computeScore(const TrackContainer& trackIds, std::size_t trackIndex) override; - ArtistContainer getArtists(db::TrackId trackId); + private: + float computeScore(const TrackContainer& trackIds, std::size_t trackIndex) override; + ArtistContainer getArtists(db::TrackId trackId); - db::Db& _db; - }; + db::Db& _db; + }; } // namespace lms::recommendation::PlaylistGeneratorConstraint - diff --git a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.cpp b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.cpp index 9bd832315..ab14bbc4c 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.cpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.cpp @@ -19,59 +19,57 @@ #include "ConsecutiveReleases.hpp" +#include "core/ILogger.hpp" #include "database/Db.hpp" #include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" -#include "core/ILogger.hpp" namespace lms::recommendation::PlaylistGeneratorConstraint { - ConsecutiveReleases::ConsecutiveReleases(db::Db& db) - : _db {db} - {} - - float - ConsecutiveReleases::computeScore(const std::vector& trackIds, std::size_t trackIndex) - { - assert(!trackIds.empty()); - assert(trackIndex <= trackIds.size() - 1); + ConsecutiveReleases::ConsecutiveReleases(db::Db& db) + : _db{ db } + { + } - const db::ReleaseId releaseId {getReleaseId(trackIds[trackIndex])}; + float ConsecutiveReleases::computeScore(const std::vector& trackIds, std::size_t trackIndex) + { + assert(!trackIds.empty()); + assert(trackIndex <= trackIds.size() - 1); - constexpr std::size_t rangeSize{ 3 }; // check up to rangeSize tracks before/after the target track - static_assert(rangeSize > 0); + const db::ReleaseId releaseId{ getReleaseId(trackIds[trackIndex]) }; - float score {}; - for (std::size_t i {1}; i < rangeSize; ++i) - { - if ((trackIndex >= i) && getReleaseId(trackIds[trackIndex - i]) == releaseId) - score += (1.f / static_cast(i)); + constexpr std::size_t rangeSize{ 3 }; // check up to rangeSize tracks before/after the target track + static_assert(rangeSize > 0); - if ((trackIndex + i < trackIds.size()) && getReleaseId(trackIds[trackIndex + i]) == releaseId) - score += (1.f / static_cast(i)); - } + float score{}; + for (std::size_t i{ 1 }; i < rangeSize; ++i) + { + if ((trackIndex >= i) && getReleaseId(trackIds[trackIndex - i]) == releaseId) + score += (1.f / static_cast(i)); - return score; - } + if ((trackIndex + i < trackIds.size()) && getReleaseId(trackIds[trackIndex + i]) == releaseId) + score += (1.f / static_cast(i)); + } - db::ReleaseId - ConsecutiveReleases::getReleaseId(db::TrackId trackId) - { - using namespace db; + return score; + } - Session& dbSession {_db.getTLSSession()}; - auto transaction {dbSession.createReadTransaction()}; + db::ReleaseId ConsecutiveReleases::getReleaseId(db::TrackId trackId) + { + using namespace db; - const Track::pointer track {Track::find(dbSession, trackId)}; - if (!track) - return {}; + Session& dbSession{ _db.getTLSSession() }; + auto transaction{ dbSession.createReadTransaction() }; - const Release::pointer release {track->getRelease()}; - if (!release) - return {}; + const Track::pointer track{ Track::find(dbSession, trackId) }; + if (!track) + return {}; - return release->getId(); - } -} // namespace lms::recommendation + const Release::pointer release{ track->getRelease() }; + if (!release) + return {}; + return release->getId(); + } +} // namespace lms::recommendation::PlaylistGeneratorConstraint diff --git a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.hpp b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.hpp index 7c9ace335..fc997a720 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.hpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/ConsecutiveReleases.hpp @@ -25,22 +25,21 @@ namespace lms::db { - class Db; + class Db; } namespace lms::recommendation::PlaylistGeneratorConstraint { - class ConsecutiveReleases : public IConstraint - { - public: - ConsecutiveReleases(db::Db& db); + class ConsecutiveReleases : public IConstraint + { + public: + ConsecutiveReleases(db::Db& db); - private: - float computeScore(const std::vector& trackIds, std::size_t trackIndex) override; + private: + float computeScore(const std::vector& trackIds, std::size_t trackIndex) override; - db::ReleaseId getReleaseId(db::TrackId trackId); - - db::Db& _db; - }; -} // namespace lms::recommendation + db::ReleaseId getReleaseId(db::TrackId trackId); + db::Db& _db; + }; +} // namespace lms::recommendation::PlaylistGeneratorConstraint diff --git a/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.cpp b/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.cpp index aff6c47f2..8c61f970b 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.cpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.cpp @@ -23,11 +23,9 @@ namespace lms::recommendation::PlaylistGeneratorConstraint { - float - DuplicateTracks::computeScore(const std::vector& trackIds, std::size_t trackIndex) - { - const auto count {std::count(std::cbegin(trackIds), std::cend(trackIds), trackIds[trackIndex])}; - return count == 1 ? 0 : 1000; - } -} // namespace lms::recommendation - + float DuplicateTracks::computeScore(const std::vector& trackIds, std::size_t trackIndex) + { + const auto count{ std::count(std::cbegin(trackIds), std::cend(trackIds), trackIds[trackIndex]) }; + return count == 1 ? 0 : 1000; + } +} // namespace lms::recommendation::PlaylistGeneratorConstraint diff --git a/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.hpp b/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.hpp index 5b76c3e59..86bc267a6 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.hpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/DuplicateTracks.hpp @@ -23,10 +23,9 @@ namespace lms::recommendation::PlaylistGeneratorConstraint { - class DuplicateTracks : public IConstraint - { - private: - float computeScore(const std::vector& trackIds, std::size_t trackIndex) override; - }; -} // namespace lms::recommendation::PlaylistGeneratorConstraints - + class DuplicateTracks : public IConstraint + { + private: + float computeScore(const std::vector& trackIds, std::size_t trackIndex) override; + }; +} // namespace lms::recommendation::PlaylistGeneratorConstraint diff --git a/src/libs/services/recommendation/impl/playlist-constraints/IConstraint.hpp b/src/libs/services/recommendation/impl/playlist-constraints/IConstraint.hpp index e73c44f52..cf5119fb0 100644 --- a/src/libs/services/recommendation/impl/playlist-constraints/IConstraint.hpp +++ b/src/libs/services/recommendation/impl/playlist-constraints/IConstraint.hpp @@ -25,15 +25,15 @@ namespace lms::recommendation::PlaylistGeneratorConstraint { - class IConstraint - { - public: - virtual ~IConstraint() = default; + class IConstraint + { + public: + virtual ~IConstraint() = default; - // compute the score of the track at index trackIndex - // 0: best - // 1: worst - // > 1 : violation - virtual float computeScore(const TrackContainer& trackIds, std::size_t trackIndex) = 0; - }; -} // namespace lms::recommendation + // compute the score of the track at index trackIndex + // 0: best + // 1: worst + // > 1 : violation + virtual float computeScore(const TrackContainer& trackIds, std::size_t trackIndex) = 0; + }; +} // namespace lms::recommendation::PlaylistGeneratorConstraint diff --git a/src/libs/services/recommendation/include/services/recommendation/IPlaylistGeneratorService.hpp b/src/libs/services/recommendation/include/services/recommendation/IPlaylistGeneratorService.hpp index 54d59ef40..0676f13f7 100644 --- a/src/libs/services/recommendation/include/services/recommendation/IPlaylistGeneratorService.hpp +++ b/src/libs/services/recommendation/include/services/recommendation/IPlaylistGeneratorService.hpp @@ -20,27 +20,27 @@ #pragma once #include + #include "database/TrackListId.hpp" #include "database/Types.hpp" #include "services/recommendation/Types.hpp" namespace lms::db { - class Db; + class Db; } namespace lms::recommendation { - class IRecommendationService; - class IPlaylistGeneratorService - { - public: - virtual ~IPlaylistGeneratorService() = default; - - // extend an existing playlist with similar tracks (but use playlist contraints) - virtual TrackContainer extendPlaylist(db::TrackListId tracklistId, std::size_t maxCount) const = 0; - }; + class IRecommendationService; + class IPlaylistGeneratorService + { + public: + virtual ~IPlaylistGeneratorService() = default; - std::unique_ptr createPlaylistGeneratorService(db::Db& db, IRecommendationService& recommandationService); -} // ns Recommendation + // extend an existing playlist with similar tracks (but use playlist contraints) + virtual TrackContainer extendPlaylist(db::TrackListId tracklistId, std::size_t maxCount) const = 0; + }; + std::unique_ptr createPlaylistGeneratorService(db::Db& db, IRecommendationService& recommandationService); +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/include/services/recommendation/IRecommendationService.hpp b/src/libs/services/recommendation/include/services/recommendation/IRecommendationService.hpp index 31bb8deaf..89ef69fc7 100644 --- a/src/libs/services/recommendation/include/services/recommendation/IRecommendationService.hpp +++ b/src/libs/services/recommendation/include/services/recommendation/IRecommendationService.hpp @@ -21,6 +21,7 @@ #include #include + #include "core/EnumSet.hpp" #include "database/TrackListId.hpp" #include "database/Types.hpp" @@ -28,24 +29,23 @@ namespace lms::db { - class Db; + class Db; } namespace lms::recommendation { - class IRecommendationService - { - public: - virtual ~IRecommendationService() = default; - - virtual void load() = 0; + class IRecommendationService + { + public: + virtual ~IRecommendationService() = default; - virtual TrackContainer findSimilarTracks(db::TrackListId tracklistId, std::size_t maxCount) const = 0; - virtual TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const = 0; - virtual ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const = 0; - virtual ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const = 0; - }; + virtual void load() = 0; - std::unique_ptr createRecommendationService(db::Db& db); -} // ns Recommendation + virtual TrackContainer findSimilarTracks(db::TrackListId tracklistId, std::size_t maxCount) const = 0; + virtual TrackContainer findSimilarTracks(const std::vector& tracksId, std::size_t maxCount) const = 0; + virtual ReleaseContainer getSimilarReleases(db::ReleaseId releaseId, std::size_t maxCount) const = 0; + virtual ArtistContainer getSimilarArtists(db::ArtistId artistId, core::EnumSet linkTypes, std::size_t maxCount) const = 0; + }; + std::unique_ptr createRecommendationService(db::Db& db); +} // namespace lms::recommendation diff --git a/src/libs/services/recommendation/include/services/recommendation/Types.hpp b/src/libs/services/recommendation/include/services/recommendation/Types.hpp index 84fe37752..53a7c9b86 100644 --- a/src/libs/services/recommendation/include/services/recommendation/Types.hpp +++ b/src/libs/services/recommendation/include/services/recommendation/Types.hpp @@ -1,24 +1,25 @@ #pragma once #include + #include "database/ArtistId.hpp" #include "database/ReleaseId.hpp" #include "database/TrackId.hpp" namespace lms::recommendation { - struct Progress - { - std::size_t totalElems {}; - std::size_t processedElems {}; - }; - using ProgressCallback = std::function; + struct Progress + { + std::size_t totalElems{}; + std::size_t processedElems{}; + }; + using ProgressCallback = std::function; - template - using ResultContainer = std::vector; + template + using ResultContainer = std::vector; - using ArtistContainer = ResultContainer; - using ReleaseContainer = ResultContainer; - using TrackContainer = ResultContainer; + using ArtistContainer = ResultContainer; + using ReleaseContainer = ResultContainer; + using TrackContainer = ResultContainer; } // namespace lms::recommendation diff --git a/src/libs/services/scanner/CMakeLists.txt b/src/libs/services/scanner/CMakeLists.txt index dbe27c9c1..41df9cc98 100644 --- a/src/libs/services/scanner/CMakeLists.txt +++ b/src/libs/services/scanner/CMakeLists.txt @@ -1,13 +1,16 @@ add_library(lmsscanner SHARED + impl/FileScanQueue.cpp impl/ScannerService.cpp impl/ScannerStats.cpp - impl/ScanStepCheckDuplicatedDbFiles.cpp + impl/ScanStepAssociateArtistImages.cpp + impl/ScanStepCheckForDuplicatedFiles.cpp + impl/ScanStepCheckForRemovedFiles.cpp impl/ScanStepCompact.cpp impl/ScanStepComputeClusterStats.cpp impl/ScanStepDiscoverFiles.cpp impl/ScanStepOptimize.cpp - impl/ScanStepRemoveOrphanDbFiles.cpp + impl/ScanStepRemoveOrphanedDbEntries.cpp impl/ScanStepScanFiles.cpp ) @@ -20,10 +23,11 @@ target_include_directories(lmsscanner PRIVATE ) target_link_libraries(lmsscanner PRIVATE + lmscore lmsdatabase + lmsimage lmsmetadata lmsrecommendation - lmscore ) target_link_libraries(lmsscanner PUBLIC diff --git a/src/libs/services/scanner/impl/FileScanQueue.cpp b/src/libs/services/scanner/impl/FileScanQueue.cpp new file mode 100644 index 000000000..0e083663b --- /dev/null +++ b/src/libs/services/scanner/impl/FileScanQueue.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "FileScanQueue.hpp" + +#include "core/Exception.hpp" +#include "core/IConfig.hpp" +#include "core/ILogger.hpp" +#include "core/ITraceLogger.hpp" +#include "core/Path.hpp" +#include "image/Exception.hpp" +#include "image/Image.hpp" +#include "metadata/Exception.hpp" + +namespace lms::scanner +{ + FileScanQueue::FileScanQueue(metadata::IParser& parser, std::size_t threadCount, bool& abort) + : _metadataParser{ parser } + , _scanContextRunner{ _scanContext, threadCount, "FileScan" } + , _abort{ abort } + { + } + + void FileScanQueue::pushScanRequest(const std::filesystem::path& path, ScanRequestType type) + { + { + std::scoped_lock lock{ _mutex }; + _ongoingScanCount += 1; + } + + _scanContext.post([=, this] { + if (_abort) + { + std::scoped_lock lock{ _mutex }; + _ongoingScanCount -= 1; + } + else + { + FileScanResult result; + result.path = path; + + switch (type) + { + case ScanRequestType::AudioFile: + result.scanData = scanAudioFile(path); + break; + case ScanRequestType::ImageFile: + result.scanData = scanImageFile(path); + } + + { + std::scoped_lock lock{ _mutex }; + + _scanResults.emplace_back(std::move(result)); + _ongoingScanCount -= 1; + } + } + + _condVar.notify_all(); + }); + } + + AudioFileScanData FileScanQueue::scanAudioFile(const std::filesystem::path& path) + { + LMS_SCOPED_TRACE_OVERVIEW("Scanner", "ScanAudioFile"); + std::unique_ptr track; + + try + { + track = _metadataParser.parse(path); + } + catch (const metadata::Exception& e) + { + LMS_LOG(DBUPDATER, INFO, "Failed to parse audio file '" << path.string() << "'"); + } + + return track; + } + + ImageFileScanData FileScanQueue::scanImageFile(const std::filesystem::path& path) + { + LMS_SCOPED_TRACE_OVERVIEW("Scanner", "ScanImageFile"); + + std::optional optInfo; + + try + { + std::unique_ptr rawImage{ image::decodeImage(path) }; + ImageInfo& imageInfo{ optInfo.emplace() }; + imageInfo.width = rawImage->getWidth(); + imageInfo.height = rawImage->getHeight(); + } + catch (const image::Exception& e) + { + LMS_LOG(DBUPDATER, ERROR, "Cannot read image in file '" << path.string() << "': " << e.what()); + } + + return optInfo; + } + + std::size_t FileScanQueue::getResultsCount() const + { + std::scoped_lock lock{ _mutex }; + return _scanResults.size(); + } + + size_t FileScanQueue::popResults(std::vector& results, std::size_t maxCount) + { + results.clear(); + results.reserve(maxCount); + + { + std::scoped_lock lock{ _mutex }; + + while (results.size() < maxCount && !_scanResults.empty()) + { + results.push_back(std::move(_scanResults.front())); + _scanResults.pop_front(); + } + } + + return results.size(); + } + + void FileScanQueue::wait(std::size_t maxScanRequestCount) + { + LMS_SCOPED_TRACE_OVERVIEW("Scanner", "WaitParseResults"); + + std::unique_lock lock{ _mutex }; + _condVar.wait(lock, [=, this] { return _ongoingScanCount <= maxScanRequestCount; }); + } + +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/FileScanQueue.hpp b/src/libs/services/scanner/impl/FileScanQueue.hpp new file mode 100644 index 000000000..c850a30b1 --- /dev/null +++ b/src/libs/services/scanner/impl/FileScanQueue.hpp @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "core/IOContextRunner.hpp" +#include "metadata/IParser.hpp" + +namespace lms::scanner +{ + struct ImageInfo + { + std::size_t height{}; + std::size_t width{}; + }; + + using AudioFileScanData = std::unique_ptr; + using ImageFileScanData = std::optional; + struct FileScanResult + { + std::filesystem::path path; + std::variant scanData; + }; + + class FileScanQueue + { + public: + FileScanQueue(metadata::IParser& parser, std::size_t threadCount, bool& abort); + + std::size_t getThreadCount() const { return _scanContextRunner.getThreadCount(); } + + enum ScanRequestType + { + AudioFile, + ImageFile, + }; + void pushScanRequest(const std::filesystem::path& path, ScanRequestType type); + + std::size_t getResultsCount() const; + size_t popResults(std::vector& results, std::size_t maxCount); + + void wait(std::size_t maxScanRequestCount = 0); // wait until ongoing scan request count <= maxScanRequestCount + + private: + AudioFileScanData scanAudioFile(const std::filesystem::path& path); + ImageFileScanData scanImageFile(const std::filesystem::path& path); + + metadata::IParser& _metadataParser; + boost::asio::io_context _scanContext; + core::IOContextRunner _scanContextRunner; + + mutable std::mutex _mutex; + std::size_t _ongoingScanCount{}; + std::deque _scanResults; + std::condition_variable _condVar; + bool& _abort; + }; + +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/IScanStep.hpp b/src/libs/services/scanner/impl/IScanStep.hpp index 0d00af850..ee30062d4 100644 --- a/src/libs/services/scanner/impl/IScanStep.hpp +++ b/src/libs/services/scanner/impl/IScanStep.hpp @@ -43,4 +43,4 @@ namespace lms::scanner }; virtual void process(ScanContext& context) = 0; }; -} +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepAssociateArtistImages.cpp b/src/libs/services/scanner/impl/ScanStepAssociateArtistImages.cpp new file mode 100644 index 000000000..66d8ba0ee --- /dev/null +++ b/src/libs/services/scanner/impl/ScanStepAssociateArtistImages.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "ScanStepAssociateArtistImages.hpp" + +#include +#include +#include +#include + +#include "core/IConfig.hpp" +#include "core/ILogger.hpp" +#include "core/Path.hpp" +#include "database/Artist.hpp" +#include "database/Db.hpp" +#include "database/Directory.hpp" +#include "database/Image.hpp" +#include "database/Session.hpp" +#include "database/Track.hpp" +#include "image/Exception.hpp" +#include "image/Image.hpp" + +namespace lms::scanner +{ + namespace + { + constexpr std::size_t readBatchSize{ 100 }; + constexpr std::size_t writeBatchSize{ 10 }; + + struct ArtistImageAssociation + { + db::ArtistId artistId; + db::ImageId imageId; + }; + using ArtistImageAssociationContainer = std::deque; + + struct SearchImageContext + { + db::Session& session; + db::ArtistId lastRetrievedArtistId; + const std::vector& artistFileNames; + }; + + db::Image::pointer findImageInDirectory(SearchImageContext& searchContext, const std::filesystem::path& directoryPath) + { + db::Image::pointer image; + + const db::Directory::pointer directory{ db::Directory::find(searchContext.session, directoryPath) }; + if (directory) // may not exist for artists that are split on different media libraries + { + for (std::string_view fileStem : searchContext.artistFileNames) + { + db::Image::FindParameters params; + params.setDirectory(directory->getId()); + params.setFileStem(fileStem); + + db::Image::find(searchContext.session, params, [&](const db::Image::pointer foundImg) { + if (!image) + image = foundImg; + }); + + if (image) + break; + } + } + + return image; + } + + db::Image::pointer computeBestArtistImage(SearchImageContext& searchContext, const db::Artist::pointer& artist) + { + db::Image::pointer image; + + const auto mbid{ artist->getMBID() }; + if (mbid) + { + // Find anywhere, since it is suppoed to be unique! + db::Image::find(searchContext.session, db::Image::FindParameters{}.setFileStem(mbid->getAsString()), [&](const db::Image::pointer foundImg) { + if (!image) + image = foundImg; + }); + } + + if (!image) + { + std::set releasePaths; + db::Directory::FindParameters params; + params.setArtist(artist->getId(), { db::TrackArtistLinkType::ReleaseArtist }); + + db::Directory::find(searchContext.session, params, [&](const db::Directory::pointer& directory) { + releasePaths.insert(directory->getAbsolutePath()); + }); + + // Expect layout like this: + // ReleaseArtist/Release/Tracks' + // /artist.jpg + // /someOtherUserConfiguredArtistFile.jpg + if (!releasePaths.empty()) + { + const std::filesystem::path artistPath{ releasePaths.size() == 1 ? releasePaths.begin()->parent_path() : core::pathUtils::getLongestCommonPath(std::cbegin(releasePaths), std::cend(releasePaths)) }; + image = findImageInDirectory(searchContext, artistPath); + } + + if (!image) + { + // Expect layout like this: + // ReleaseArtist/Release/Tracks' + // /artist.jpg + // /someOtherUserConfiguredArtistFile.jpg + for (const std::filesystem::path& releasePath : releasePaths) + { + image = findImageInDirectory(searchContext, releasePath); + if (image) + break; + } + } + } + + return image; + } + + bool fetchNextArtistImagesToUpdate(SearchImageContext& searchContext, ArtistImageAssociationContainer& artistImageAssociations) + { + const db::ArtistId artistId{ searchContext.lastRetrievedArtistId }; + + { + auto transaction{ searchContext.session.createReadTransaction() }; + + db::Artist::find(searchContext.session, searchContext.lastRetrievedArtistId, readBatchSize, [&](const db::Artist::pointer& artist) { + db::Image::pointer image{ computeBestArtistImage(searchContext, artist) }; + + if (image != artist->getImage()) + { + LMS_LOG(DBUPDATER, DEBUG, "Updating artist image for artist '" << artist->getName() << "', using '" << (image ? image->getAbsoluteFilePath().c_str() : "") << "'"); + artistImageAssociations.push_back(ArtistImageAssociation{ artist->getId(), image ? image->getId() : db::ImageId{} }); + } + }); + } + + return artistId != searchContext.lastRetrievedArtistId; + } + + void updateArtistImage(db::Session& session, const ArtistImageAssociation& artistImageAssociation) + { + db::Artist::pointer artist{ db::Artist::find(session, artistImageAssociation.artistId) }; + assert(artist); + + db::Image::pointer image; + if (artistImageAssociation.imageId.isValid()) + image = db::Image::find(session, artistImageAssociation.imageId); + + artist.modify()->setImage(image); + } + + void updateArtistImages(db::Session& session, ArtistImageAssociationContainer& imageAssociations) + { + if (imageAssociations.empty()) + return; + + auto transaction{ session.createWriteTransaction() }; + + for (std::size_t i{}; !imageAssociations.empty() && i < writeBatchSize; ++i) + { + updateArtistImage(session, imageAssociations.front()); + imageAssociations.pop_front(); + } + } + + std::vector constructArtistFileNames() + { + std::vector res; + + core::Service::get()->visitStrings("artist-image-file-names", + [&res](std::string_view fileName) { + res.emplace_back(fileName); + }, + { "artist" }); + + return res; + } + + } // namespace + + ScanStepAssociateArtistImages::ScanStepAssociateArtistImages(InitParams& initParams) + : ScanStepBase{ initParams } + , _artistFileNames{ constructArtistFileNames() } + { + } + + void ScanStepAssociateArtistImages::process(ScanContext& context) + { + if (context.stats.nbChanges() == 0) + return; + + auto& session{ _db.getTLSSession() }; + + { + auto transaction{ session.createReadTransaction() }; + context.currentStepStats.totalElems = db::Artist::getCount(session); + } + + SearchImageContext searchContext{ + .session = session, + .lastRetrievedArtistId = {}, + .artistFileNames = _artistFileNames, + }; + + ArtistImageAssociationContainer artistImageAssociations; + while (fetchNextArtistImagesToUpdate(searchContext, artistImageAssociations)) + { + updateArtistImages(session, artistImageAssociations); + context.currentStepStats.processedElems += readBatchSize; + _progressCallback(context.currentStepStats); + } + } +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepAssociateArtistImages.hpp b/src/libs/services/scanner/impl/ScanStepAssociateArtistImages.hpp new file mode 100644 index 000000000..4d65399d9 --- /dev/null +++ b/src/libs/services/scanner/impl/ScanStepAssociateArtistImages.hpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include +#include + +#include "ScanStepBase.hpp" + +namespace lms::scanner +{ + class ScanStepAssociateArtistImages : public ScanStepBase + { + public: + ScanStepAssociateArtistImages(InitParams& initParams); + + private: + ScanStep getStep() const override { return ScanStep::AssociateArtistImages; } + core::LiteralString getStepName() const override { return "Associate artist images"; } + void process(ScanContext& context) override; + + const std::vector _artistFileNames; + }; +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepBase.hpp b/src/libs/services/scanner/impl/ScanStepBase.hpp index 21bd8dd51..a530dc1d3 100644 --- a/src/libs/services/scanner/impl/ScanStepBase.hpp +++ b/src/libs/services/scanner/impl/ScanStepBase.hpp @@ -22,40 +22,42 @@ #include #include "services/scanner/ScannerStats.hpp" + #include "IScanStep.hpp" #include "ScannerSettings.hpp" namespace lms::db { - class Db; + class Db; } namespace lms::scanner { - class ScanStepBase : public IScanStep - { - public: - static inline const std::filesystem::path excludeDirFileName {".lmsignore"}; - using ProgressCallback = std::function; - - struct InitParams - { - const ScannerSettings& settings; - ProgressCallback progressCallback; - bool& abortScan; - db::Db& db; - }; - ScanStepBase(InitParams& initParams) - : _settings {initParams.settings} - , _progressCallback {initParams.progressCallback} - , _abortScan {initParams.abortScan} - , _db {initParams.db} - {} - - protected: - const ScannerSettings& _settings; - ProgressCallback _progressCallback; - bool& _abortScan; - db::Db& _db; - }; -} + class ScanStepBase : public IScanStep + { + public: + static inline const std::filesystem::path excludeDirFileName{ ".lmsignore" }; + using ProgressCallback = std::function; + + struct InitParams + { + const ScannerSettings& settings; + ProgressCallback progressCallback; + bool& abortScan; + db::Db& db; + }; + ScanStepBase(InitParams& initParams) + : _settings{ initParams.settings } + , _progressCallback{ initParams.progressCallback } + , _abortScan{ initParams.abortScan } + , _db{ initParams.db } + { + } + + protected: + const ScannerSettings& _settings; + ProgressCallback _progressCallback; + bool& _abortScan; + db::Db& _db; + }; +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.cpp b/src/libs/services/scanner/impl/ScanStepCheckForDuplicatedFiles.cpp similarity index 87% rename from src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.cpp rename to src/libs/services/scanner/impl/ScanStepCheckForDuplicatedFiles.cpp index 10f30e14d..9b92321b3 100644 --- a/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepCheckForDuplicatedFiles.cpp @@ -17,24 +17,24 @@ * along with LMS. If not, see . */ -#include "ScanStepCheckDuplicatedDbFiles.hpp" +#include "ScanStepCheckForDuplicatedFiles.hpp" +#include "core/ILogger.hpp" #include "database/Db.hpp" #include "database/Session.hpp" #include "database/Track.hpp" -#include "core/ILogger.hpp" namespace lms::scanner { - void ScanStepCheckDuplicatedDbFiles::process(ScanContext& context) + void ScanStepCheckForDuplicatedFiles::process(ScanContext& context) { using namespace db; if (_abortScan) return; - Session& session {_db.getTLSSession()}; - auto transaction {session.createReadTransaction()}; + Session& session{ _db.getTLSSession() }; + auto transaction{ session.createReadTransaction() }; const RangeResults tracks = Track::findIdsTrackMBIDDuplicates(session); for (const TrackId trackId : tracks.results) @@ -54,4 +54,4 @@ namespace lms::scanner LMS_LOG(DBUPDATER, DEBUG, "Found " << context.currentStepStats.processedElems << " duplicated audio files"); } -} +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.hpp b/src/libs/services/scanner/impl/ScanStepCheckForDuplicatedFiles.hpp similarity index 65% rename from src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.hpp rename to src/libs/services/scanner/impl/ScanStepCheckForDuplicatedFiles.hpp index d67625998..ca16031f1 100644 --- a/src/libs/services/scanner/impl/ScanStepCheckDuplicatedDbFiles.hpp +++ b/src/libs/services/scanner/impl/ScanStepCheckForDuplicatedFiles.hpp @@ -23,14 +23,14 @@ namespace lms::scanner { - class ScanStepCheckDuplicatedDbFiles : public ScanStepBase - { - public: - using ScanStepBase::ScanStepBase; + class ScanStepCheckForDuplicatedFiles : public ScanStepBase + { + public: + using ScanStepBase::ScanStepBase; - private: - core::LiteralString getStepName() const override { return "Check for duplicated files"; } - ScanStep getStep() const override { return ScanStep::CheckForDuplicateFiles; } - void process(ScanContext& context) override; - }; -} + private: + core::LiteralString getStepName() const override { return "Check for duplicated files"; } + ScanStep getStep() const override { return ScanStep::CheckForDuplicatedFiles; } + void process(ScanContext& context) override; + }; +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepCheckForRemovedFiles.cpp b/src/libs/services/scanner/impl/ScanStepCheckForRemovedFiles.cpp new file mode 100644 index 000000000..0fb4875bf --- /dev/null +++ b/src/libs/services/scanner/impl/ScanStepCheckForRemovedFiles.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2023 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "ScanStepCheckForRemovedFiles.hpp" + +#include "core/ILogger.hpp" +#include "core/Path.hpp" +#include "database/Db.hpp" +#include "database/Image.hpp" +#include "database/Session.hpp" +#include "database/Track.hpp" + +namespace lms::scanner +{ + namespace + { + constexpr std::size_t batchSize = 100; + } + + void ScanStepCheckForRemovedFiles::process(ScanContext& context) + { + if (_abortScan) + return; + + db::Session& session{ _db.getTLSSession() }; + + { + auto transaction{ session.createReadTransaction() }; + context.currentStepStats.totalElems = 0; + context.currentStepStats.totalElems += db::Track::getCount(session); + context.currentStepStats.totalElems += db::Image::getCount(session); + } + LMS_LOG(DBUPDATER, DEBUG, context.currentStepStats.totalElems << " files to be checked..."); + + checkForRemovedFiles(context, _settings.supportedAudioFileExtensions); + checkForRemovedFiles(context, _settings.supportedImageFileExtensions); + } + + template + void ScanStepCheckForRemovedFiles::checkForRemovedFiles(ScanContext& context, const std::vector& supportedFileExtensions) + { + using namespace db; + + if (_abortScan) + return; + + Session& session{ _db.getTLSSession() }; + + std::vector objectsToRemove; + + typename Object::IdType lastCheckedId; + bool endReached{}; + while (!endReached) + { + if (_abortScan) + break; + + objectsToRemove.clear(); + { + auto transaction{ session.createReadTransaction() }; + + endReached = true; + Object::find(session, lastCheckedId, batchSize, [&](const typename Object::pointer& object) { + endReached = false; + + if (!checkFile(object->getAbsoluteFilePath(), supportedFileExtensions)) + objectsToRemove.push_back(object); + + context.currentStepStats.processedElems++; + }); + } + + if (!objectsToRemove.empty()) + { + auto transaction{ session.createWriteTransaction() }; + + for (typename Object::pointer& object : objectsToRemove) + { + object.remove(); + context.stats.deletions++; + } + } + + _progressCallback(context.currentStepStats); + } + } + + bool ScanStepCheckForRemovedFiles::checkFile(const std::filesystem::path& p, const std::vector& allowedExtensions) + { + try + { + // For each track, make sure the the file still exists + // and still belongs to a media directory + if (!std::filesystem::exists(p) || !std::filesystem::is_regular_file(p)) + { + LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': missing"); + return false; + } + + if (std::none_of(std::cbegin(_settings.mediaLibraries), std::cend(_settings.mediaLibraries), + [&](const ScannerSettings::MediaLibraryInfo& libraryInfo) { + return core::pathUtils::isPathInRootPath(p, libraryInfo.rootDirectory, &excludeDirFileName); + })) + { + LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': out of media directory"); + return false; + } + + if (!core::pathUtils::hasFileAnyExtension(p, allowedExtensions)) + { + LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': file format no longer handled"); + return false; + } + + return true; + } + catch (std::filesystem::filesystem_error& e) + { + LMS_LOG(DBUPDATER, ERROR, "Caught exception while checking file '" << p.string() << "': " << e.what()); + return false; + } + } +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.hpp b/src/libs/services/scanner/impl/ScanStepCheckForRemovedFiles.hpp similarity index 53% rename from src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.hpp rename to src/libs/services/scanner/impl/ScanStepCheckForRemovedFiles.hpp index fbc6391b2..5c79ca724 100644 --- a/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.hpp +++ b/src/libs/services/scanner/impl/ScanStepCheckForRemovedFiles.hpp @@ -25,21 +25,19 @@ namespace lms::scanner { - class ScanStepRemoveOrphanDbFiles : public ScanStepBase - { - public: - using ScanStepBase::ScanStepBase; + class ScanStepCheckForRemovedFiles : public ScanStepBase + { + public: + using ScanStepBase::ScanStepBase; - private: - core::LiteralString getStepName() const override { return "Check orphaned entries"; } - ScanStep getStep() const override { return ScanStep::CheckForMissingFiles; } - void process(ScanContext& context) override; + private: + core::LiteralString getStepName() const override { return "Check for removed files"; } + ScanStep getStep() const override { return ScanStep::CheckForRemovedFiles; } + void process(ScanContext& context) override; - void removeOrphanTracks(ScanContext& context); - void removeOrphanClusters(); - void removeOrphanClusterTypes(); - void removeOrphanArtists(); - void removeOrphanReleases(); - bool checkFile(const std::filesystem::path& p); - }; -} + template + void checkForRemovedFiles(ScanContext& context, const std::vector& supportedFileExtensions); + + bool checkFile(const std::filesystem::path& p, const std::vector& allowedExtensions); + }; +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepCompact.cpp b/src/libs/services/scanner/impl/ScanStepCompact.cpp index b60780556..9a4f469fc 100644 --- a/src/libs/services/scanner/impl/ScanStepCompact.cpp +++ b/src/libs/services/scanner/impl/ScanStepCompact.cpp @@ -30,4 +30,4 @@ namespace lms::scanner if (context.scanOptions.compact) _db.getTLSSession().vacuum(); } -} +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepCompact.hpp b/src/libs/services/scanner/impl/ScanStepCompact.hpp index f56b4f24e..d1c4e195c 100644 --- a/src/libs/services/scanner/impl/ScanStepCompact.hpp +++ b/src/libs/services/scanner/impl/ScanStepCompact.hpp @@ -23,14 +23,14 @@ namespace lms::scanner { - class ScanStepCompact : public ScanStepBase - { - public: - using ScanStepBase::ScanStepBase; + class ScanStepCompact : public ScanStepBase + { + public: + using ScanStepBase::ScanStepBase; - private: - ScanStep getStep() const override { return ScanStep::Compact; } - core::LiteralString getStepName() const override { return "Compact"; } - void process(ScanContext& context) override; - }; -} + private: + ScanStep getStep() const override { return ScanStep::Compact; } + core::LiteralString getStepName() const override { return "Compact"; } + void process(ScanContext& context) override; + }; +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepComputeClusterStats.cpp b/src/libs/services/scanner/impl/ScanStepComputeClusterStats.cpp index 331973fce..bc2f99b18 100644 --- a/src/libs/services/scanner/impl/ScanStepComputeClusterStats.cpp +++ b/src/libs/services/scanner/impl/ScanStepComputeClusterStats.cpp @@ -18,11 +18,11 @@ */ #include "ScanStepComputeClusterStats.hpp" -#include "database/Db.hpp" -#include "database/Cluster.hpp" -#include "database/Session.hpp" #include "core/ILogger.hpp" #include "core/Path.hpp" +#include "database/Cluster.hpp" +#include "database/Db.hpp" +#include "database/Session.hpp" namespace lms::scanner { @@ -38,53 +38,51 @@ namespace lms::scanner const std::size_t clusterCount{ [&] { auto transaction{ dbSession.createReadTransaction() }; return Cluster::getCount(dbSession); - }() }; + }() }; context.currentStepStats.totalElems = clusterCount; - foreachSubRange(Range{ 0, clusterCount }, 100, [&](Range range) - { - const std::vector clusterIds{ [&] - { - Cluster::FindParameters params; - params.setRange(range); + foreachSubRange(Range{ 0, clusterCount }, 100, [&](Range range) { + const std::vector clusterIds{ [&] { + Cluster::FindParameters params; + params.setRange(range); - { - auto transaction{ dbSession.createReadTransaction() }; - return std::move(Cluster::findIds(dbSession, params).results); - } - }() }; - - for (const ClusterId clusterId : clusterIds) { - if (_abortScan) - break; + auto transaction{ dbSession.createReadTransaction() }; + return std::move(Cluster::findIds(dbSession, params).results); + } + }() }; - std::size_t trackCount; - std::size_t releaseCount; + for (const ClusterId clusterId : clusterIds) + { + if (_abortScan) + break; - { - auto transaction{ dbSession.createReadTransaction() }; + std::size_t trackCount; + std::size_t releaseCount; - trackCount = Cluster::computeTrackCount(dbSession, clusterId); - releaseCount = Cluster::computeReleaseCount(dbSession, clusterId); - } + { + auto transaction{ dbSession.createReadTransaction() }; - { - auto transaction{ dbSession.createWriteTransaction() }; + trackCount = Cluster::computeTrackCount(dbSession, clusterId); + releaseCount = Cluster::computeReleaseCount(dbSession, clusterId); + } - auto cluster{ Cluster::find(dbSession, clusterId) }; - cluster.modify()->setTrackCount(trackCount); - cluster.modify()->setReleaseCount(releaseCount); - } + { + auto transaction{ dbSession.createWriteTransaction() }; - context.currentStepStats.processedElems++; - _progressCallback(context.currentStepStats); + auto cluster{ Cluster::find(dbSession, clusterId) }; + cluster.modify()->setTrackCount(trackCount); + cluster.modify()->setReleaseCount(releaseCount); } - return true; - }); + context.currentStepStats.processedElems++; + _progressCallback(context.currentStepStats); + } + + return true; + }); LMS_LOG(DBUPDATER, DEBUG, "Recomputed stats for " << context.currentStepStats.processedElems << " clusters!"); } -} +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepComputeClusterStats.hpp b/src/libs/services/scanner/impl/ScanStepComputeClusterStats.hpp index 43aeaf365..1139c7fca 100644 --- a/src/libs/services/scanner/impl/ScanStepComputeClusterStats.hpp +++ b/src/libs/services/scanner/impl/ScanStepComputeClusterStats.hpp @@ -33,4 +33,4 @@ namespace lms::scanner core::LiteralString getStepName() const override { return "Compute cluster stats"; } void process(ScanContext& context) override; }; -} +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepDiscoverFiles.cpp b/src/libs/services/scanner/impl/ScanStepDiscoverFiles.cpp index d582bf181..b9f9aceea 100644 --- a/src/libs/services/scanner/impl/ScanStepDiscoverFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepDiscoverFiles.cpp @@ -26,17 +26,17 @@ namespace lms::scanner { void ScanStepDiscoverFiles::process(ScanContext& context) { - context.stats.filesScanned = 0; + context.stats.totalFileCount = 0; for (const ScannerSettings::MediaLibraryInfo& mediaLibrary : _settings.mediaLibraries) { std::size_t currentDirectoryProcessElemsCount{}; - core::pathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path) - { + core::pathUtils::exploreFilesRecursive( + mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path) { if (_abortScan) return false; - if (!ec && core::pathUtils::hasFileAnyExtension(path, _settings.supportedExtensions)) + if (!ec && (core::pathUtils::hasFileAnyExtension(path, _settings.supportedAudioFileExtensions) || core::pathUtils::hasFileAnyExtension(path, _settings.supportedImageFileExtensions))) { context.currentStepStats.processedElems++; currentDirectoryProcessElemsCount++; @@ -44,13 +44,14 @@ namespace lms::scanner } return true; - }, &excludeDirFileName); + }, + &excludeDirFileName); LMS_LOG(DBUPDATER, DEBUG, "Discovered " << currentDirectoryProcessElemsCount << " files in '" << mediaLibrary.rootDirectory << "'"); } - context.stats.filesScanned = context.currentStepStats.processedElems; + context.stats.totalFileCount = context.currentStepStats.processedElems; - LMS_LOG(DBUPDATER, DEBUG, "Discovered " << context.stats.filesScanned << " files in all directories"); + LMS_LOG(DBUPDATER, DEBUG, "Discovered " << context.stats.totalFileCount << " files in all directories"); } -} +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepDiscoverFiles.hpp b/src/libs/services/scanner/impl/ScanStepDiscoverFiles.hpp index 3a350e4f6..29d6d3afb 100644 --- a/src/libs/services/scanner/impl/ScanStepDiscoverFiles.hpp +++ b/src/libs/services/scanner/impl/ScanStepDiscoverFiles.hpp @@ -23,14 +23,14 @@ namespace lms::scanner { - class ScanStepDiscoverFiles : public ScanStepBase - { - public: - using ScanStepBase::ScanStepBase; + class ScanStepDiscoverFiles : public ScanStepBase + { + public: + using ScanStepBase::ScanStepBase; - private: - ScanStep getStep() const override { return ScanStep::DiscoverFiles; } - core::LiteralString getStepName() const override { return "Discover files"; } - void process(ScanContext& context) override; - }; -} + private: + ScanStep getStep() const override { return ScanStep::DiscoverFiles; } + core::LiteralString getStepName() const override { return "Discover files"; } + void process(ScanContext& context) override; + }; +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepOptimize.cpp b/src/libs/services/scanner/impl/ScanStepOptimize.cpp index 0bcfc7df5..7ab5f69b7 100644 --- a/src/libs/services/scanner/impl/ScanStepOptimize.cpp +++ b/src/libs/services/scanner/impl/ScanStepOptimize.cpp @@ -53,4 +53,4 @@ namespace lms::scanner LMS_LOG(DBUPDATER, INFO, "Database analyze complete"); } } -} +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepOptimize.hpp b/src/libs/services/scanner/impl/ScanStepOptimize.hpp index a0fac8527..117cda3a0 100644 --- a/src/libs/services/scanner/impl/ScanStepOptimize.hpp +++ b/src/libs/services/scanner/impl/ScanStepOptimize.hpp @@ -23,14 +23,14 @@ namespace lms::scanner { - class ScanStepOptimize : public ScanStepBase - { - public: - using ScanStepBase::ScanStepBase; + class ScanStepOptimize : public ScanStepBase + { + public: + using ScanStepBase::ScanStepBase; - private: - ScanStep getStep() const override { return ScanStep::Optimize; } - core::LiteralString getStepName() const override { return "Optimize"; } - void process(ScanContext& context) override; - }; -} + private: + ScanStep getStep() const override { return ScanStep::Optimize; } + core::LiteralString getStepName() const override { return "Optimize"; } + void process(ScanContext& context) override; + }; +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.cpp b/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.cpp deleted file mode 100644 index 64009ebef..000000000 --- a/src/libs/services/scanner/impl/ScanStepRemoveOrphanDbFiles.cpp +++ /dev/null @@ -1,201 +0,0 @@ -/* - * Copyright (C) 2023 Emeric Poupon - * - * This file is part of LMS. - * - * LMS is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * LMS is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with LMS. If not, see . - */ - -#include "ScanStepRemoveOrphanDbFiles.hpp" - -#include "database/Artist.hpp" -#include "database/Cluster.hpp" -#include "database/Db.hpp" -#include "database/Release.hpp" -#include "database/Session.hpp" -#include "database/Track.hpp" -#include "core/ILogger.hpp" -#include "core/Path.hpp" - -namespace lms::scanner -{ - using namespace db; - - namespace - { - constexpr std::size_t batchSize = 100; - - template - void removeOrphanEntries(Session& session, bool& abortScan) - { - using IdType = typename T::IdType; - - RangeResults entries; - while (!abortScan) - { - { - auto transaction{ session.createReadTransaction() }; - - entries = T::findOrphanIds(session, Range{ 0, batchSize }); - }; - - { - auto transaction{ session.createWriteTransaction() }; - - for (const IdType objectId : entries.results) - { - if (abortScan) - break; - - typename T::pointer entry{ T::find(session, objectId) }; - - entry.remove(); - } - } - - if (!entries.moreResults) - break; - } - } - } - - void ScanStepRemoveOrphanDbFiles::process(ScanContext& context) - { - removeOrphanTracks(context); - removeOrphanClusters(); - removeOrphanClusterTypes(); - removeOrphanArtists(); - removeOrphanReleases(); - } - - void ScanStepRemoveOrphanDbFiles::removeOrphanTracks(ScanContext& context) - { - using namespace db; - - if (_abortScan) - return; - - Session& session{ _db.getTLSSession() }; - - LMS_LOG(DBUPDATER, DEBUG, "Checking tracks to be removed..."); - { - auto transaction{ session.createReadTransaction() }; - context.currentStepStats.totalElems = Track::getCount(session); - } - LMS_LOG(DBUPDATER, DEBUG, context.currentStepStats.totalElems << " tracks to be checked..."); - - // TODO handle only files in context.directory? - std::vector tracksToRemove; - - TrackId lastCheckedTrackID; - bool endReached{}; - while (!endReached) - { - if (_abortScan) - break; - - tracksToRemove.clear(); - { - auto transaction{ session.createReadTransaction() }; - - endReached = true; - Track::find(session, lastCheckedTrackID, batchSize, [&](const Track::pointer& track) - { - endReached = false; - - if (!checkFile(track->getAbsoluteFilePath())) - tracksToRemove.push_back(track); - - context.currentStepStats.processedElems++; - }); - } - - if (!tracksToRemove.empty()) - { - auto transaction{ session.createWriteTransaction() }; - - for (Track::pointer& track : tracksToRemove) - { - track.remove(); - context.stats.deletions++; - } - } - - _progressCallback(context.currentStepStats); - } - - LMS_LOG(DBUPDATER, DEBUG, context.currentStepStats.processedElems << " tracks checked!"); - } - - void ScanStepRemoveOrphanDbFiles::removeOrphanClusters() - { - LMS_LOG(DBUPDATER, DEBUG, "Checking orphan clusters..."); - removeOrphanEntries(_db.getTLSSession(), _abortScan); - } - - void ScanStepRemoveOrphanDbFiles::removeOrphanClusterTypes() - { - LMS_LOG(DBUPDATER, DEBUG, "Checking orphan cluster types..."); - removeOrphanEntries(_db.getTLSSession(), _abortScan); - } - - void ScanStepRemoveOrphanDbFiles::removeOrphanArtists() - { - LMS_LOG(DBUPDATER, DEBUG, "Checking orphan artists..."); - removeOrphanEntries(_db.getTLSSession(), _abortScan); - } - - void ScanStepRemoveOrphanDbFiles::removeOrphanReleases() - { - LMS_LOG(DBUPDATER, DEBUG, "Checking orphan releases..."); - removeOrphanEntries(_db.getTLSSession(), _abortScan); - } - - bool ScanStepRemoveOrphanDbFiles::checkFile(const std::filesystem::path& p) - { - try - { - // For each track, make sure the the file still exists - // and still belongs to a media directory - if (!std::filesystem::exists(p) || !std::filesystem::is_regular_file(p)) - { - LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': missing"); - return false; - } - - if (std::none_of(std::cbegin(_settings.mediaLibraries), std::cend(_settings.mediaLibraries), - [&](const ScannerSettings::MediaLibraryInfo& libraryInfo) - { - return core::pathUtils::isPathInRootPath(p, libraryInfo.rootDirectory, &excludeDirFileName); - })) - { - LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': out of media directory"); - return false; - } - - if (!core::pathUtils::hasFileAnyExtension(p, _settings.supportedExtensions)) - { - LMS_LOG(DBUPDATER, INFO, "Removing '" << p.string() << "': file format no longer handled"); - return false; - } - - return true; - } - catch (std::filesystem::filesystem_error& e) - { - LMS_LOG(DBUPDATER, ERROR, "Caught exception while checking file '" << p.string() << "': " << e.what()); - return false; - } - } -} diff --git a/src/libs/services/scanner/impl/ScanStepRemoveOrphanedDbEntries.cpp b/src/libs/services/scanner/impl/ScanStepRemoveOrphanedDbEntries.cpp new file mode 100644 index 000000000..5ffd546a3 --- /dev/null +++ b/src/libs/services/scanner/impl/ScanStepRemoveOrphanedDbEntries.cpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2023 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#include "ScanStepRemoveOrphanedDbEntries.hpp" + +#include "core/ILogger.hpp" +#include "core/Path.hpp" +#include "database/Artist.hpp" +#include "database/Cluster.hpp" +#include "database/Db.hpp" +#include "database/Directory.hpp" +#include "database/Release.hpp" +#include "database/Session.hpp" +#include "database/Track.hpp" + +namespace lms::scanner +{ + void ScanStepRemoveOrphanedDbEntries::process(ScanContext& context) + { + removeOrphanedClusters(context); + removeOrphanedClusterTypes(context); + removeOrphanedArtists(context); + removeOrphanedReleases(context); + removeOrphanedDirectories(context); + } + + void ScanStepRemoveOrphanedDbEntries::removeOrphanedClusters(ScanContext& context) + { + LMS_LOG(DBUPDATER, DEBUG, "Checking orphaned clusters..."); + removeOrphanedEntries(context); + } + + void ScanStepRemoveOrphanedDbEntries::removeOrphanedClusterTypes(ScanContext& context) + { + LMS_LOG(DBUPDATER, DEBUG, "Checking orphaned cluster types..."); + removeOrphanedEntries(context); + } + + void ScanStepRemoveOrphanedDbEntries::removeOrphanedArtists(ScanContext& context) + { + LMS_LOG(DBUPDATER, DEBUG, "Checking orphaned artists..."); + removeOrphanedEntries(context); + } + + void ScanStepRemoveOrphanedDbEntries::removeOrphanedReleases(ScanContext& context) + { + LMS_LOG(DBUPDATER, DEBUG, "Checking orphaned releases..."); + removeOrphanedEntries(context); + } + + void ScanStepRemoveOrphanedDbEntries::removeOrphanedDirectories(ScanContext& context) + { + LMS_LOG(DBUPDATER, DEBUG, "Checking orphaned directories..."); + removeOrphanedEntries(context); + } + + template + void ScanStepRemoveOrphanedDbEntries::removeOrphanedEntries(ScanStepRemoveOrphanedDbEntries::ScanContext& context) + { + constexpr std::size_t batchSize = 100; + + using IdType = typename T::IdType; + + db::Session& session{ _db.getTLSSession() }; + + db::RangeResults entries; + while (!_abortScan) + { + { + auto transaction{ session.createReadTransaction() }; + + entries = T::findOrphanIds(session, db::Range{ 0, batchSize }); + }; + + if (entries.results.empty()) + break; + + { + auto transaction{ session.createWriteTransaction() }; + + for (const IdType objectId : entries.results) + { + if (_abortScan) + break; + + typename T::pointer entry{ T::find(session, objectId) }; + entry.remove(); + } + } + + context.currentStepStats.processedElems += entries.results.size(); + _progressCallback(context.currentStepStats); + } + } +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepRemoveOrphanedDbEntries.hpp b/src/libs/services/scanner/impl/ScanStepRemoveOrphanedDbEntries.hpp new file mode 100644 index 000000000..3dea1d334 --- /dev/null +++ b/src/libs/services/scanner/impl/ScanStepRemoveOrphanedDbEntries.hpp @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2023 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + +#pragma once + +#include + +#include "ScanStepBase.hpp" + +namespace lms::scanner +{ + class ScanStepRemoveOrphanedDbEntries : public ScanStepBase + { + public: + using ScanStepBase::ScanStepBase; + + private: + core::LiteralString getStepName() const override { return "Remove orphaned DB entries"; } + ScanStep getStep() const override { return ScanStep::RemoveOrphanedDbEntries; } + void process(ScanContext& context) override; + + void removeOrphanedClusters(ScanContext& context); + void removeOrphanedClusterTypes(ScanContext& context); + void removeOrphanedArtists(ScanContext& context); + void removeOrphanedReleases(ScanContext& context); + void removeOrphanedDirectories(ScanContext& context); + + template + void removeOrphanedEntries(ScanStepRemoveOrphanedDbEntries::ScanContext& context); + }; +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp index e201c3ba0..d2275bcb5 100644 --- a/src/libs/services/scanner/impl/ScanStepScanFiles.cpp +++ b/src/libs/services/scanner/impl/ScanStepScanFiles.cpp @@ -19,22 +19,24 @@ #include "ScanStepScanFiles.hpp" +#include "core/Exception.hpp" +#include "core/IConfig.hpp" +#include "core/ILogger.hpp" +#include "core/ITraceLogger.hpp" +#include "core/Path.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/Db.hpp" +#include "database/Directory.hpp" +#include "database/Image.hpp" #include "database/MediaLibrary.hpp" #include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" -#include "database/TrackFeatures.hpp" #include "database/TrackArtistLink.hpp" +#include "database/TrackFeatures.hpp" #include "metadata/Exception.hpp" #include "metadata/IParser.hpp" -#include "core/Exception.hpp" -#include "core/IConfig.hpp" -#include "core/ILogger.hpp" -#include "core/Path.hpp" -#include "core/ITraceLogger.hpp" namespace lms::scanner { @@ -102,6 +104,22 @@ namespace lms::scanner return res; } + Directory::pointer getOrCreateDirectory(Session& session, const std::filesystem::path& path, const std::filesystem::path& rootPath) + { + Directory::pointer directory{ Directory::find(session, path) }; + if (!directory) + { + Directory::pointer parentDirectory; + if (path != rootPath) + parentDirectory = getOrCreateDirectory(session, path.parent_path(), rootPath); + + directory = session.create(path); + directory.modify()->setParent(parentDirectory); + } + + return directory; + } + Artist::pointer createArtist(Session& session, const metadata::Artist& artistInfo) { Artist::pointer artist{ session.create(artistInfo.name) }; @@ -249,8 +267,7 @@ namespace lms::scanner { std::vector clusters; - auto getOrCreateClusters{ [&](std::string tag, std::span values) - { + auto getOrCreateClusters{ [&](std::string tag, std::span values) { auto clusterType = ClusterType::find(session, tag); if (!clusterType) clusterType = session.create(tag); @@ -302,98 +319,16 @@ namespace lms::scanner } } // namespace - ScanStepScanFiles::MetadataScanQueue::MetadataScanQueue(metadata::IParser& parser, std::size_t threadCount, bool& abort) - : _metadataParser{ parser } - , _scanContextRunner{ _scanContext, threadCount, "ScannerMetadata" } - , _abort{ abort } - {} - - void ScanStepScanFiles::MetadataScanQueue::pushScanRequest(const std::filesystem::path& path) - { - { - std::scoped_lock lock{ _mutex }; - _ongoingScanCount += 1; - } - - _scanContext.post([=, this] - { - LMS_SCOPED_TRACE_OVERVIEW("Scanner", "AudioFileParseJob"); - - std::unique_ptr track; - - if (_abort) - { - std::scoped_lock lock{ _mutex }; - _ongoingScanCount -= 1; - } - else - { - try - { - track = _metadataParser.parse(path, true); - } - catch (const metadata::Exception& e) - { - LMS_LOG(DBUPDATER, INFO, "Failed to parse '" << path.string() << "'"); - } - - { - std::scoped_lock lock{ _mutex }; - - _scanResults.emplace_back(MetaDataScanResult{ std::move(path), std::move(track) }); - _ongoingScanCount -= 1; - } - } - - _condVar.notify_all(); - }); - } - - std::size_t ScanStepScanFiles::MetadataScanQueue::getResultsCount() const - { - std::scoped_lock lock{ _mutex }; - return _scanResults.size(); - } - - size_t ScanStepScanFiles::MetadataScanQueue::popResults(std::vector& results, std::size_t maxCount) - { - results.clear(); - results.reserve(maxCount); - - { - std::scoped_lock lock{ _mutex }; - - while (results.size() < maxCount && !_scanResults.empty()) - { - results.push_back(std::move(_scanResults.front())); - _scanResults.pop_front(); - } - } - - return results.size(); - } - - void ScanStepScanFiles::MetadataScanQueue::wait(std::size_t maxScanRequestCount) - { - LMS_SCOPED_TRACE_OVERVIEW("Scanner", "WaitParseResults"); - - std::unique_lock lock{ _mutex }; - _condVar.wait(lock, [=, this] { return _ongoingScanCount <= maxScanRequestCount; }); - } - ScanStepScanFiles::ScanStepScanFiles(InitParams& initParams) : ScanStepBase{ initParams } , _metadataParser{ metadata::createParser(metadata::ParserBackend::TagLib, getParserReadStyle()) } // For now, always use TagLib - , _metadataScanQueue{ *_metadataParser, getScanMetaDataThreadCount(), _abortScan } + , _fileScanQueue{ *_metadataParser, getScanMetaDataThreadCount(), _abortScan } { - LMS_LOG(DBUPDATER, INFO, "Using " << _metadataScanQueue.getThreadCount() << " thread(s) for scanning file metadata"); + LMS_LOG(DBUPDATER, INFO, "Using " << _fileScanQueue.getThreadCount() << " thread(s) for scanning file metadata"); } void ScanStepScanFiles::process(ScanContext& context) { - const std::size_t scanQueueMaxScanRequestCount{ 100 * _metadataScanQueue.getThreadCount() }; - const std::size_t processMetaDataBatchSize{ 5 }; - { std::vector tagsToParse{ _extraTagsToParse }; tagsToParse.insert(std::end(tagsToParse), std::cbegin(_settings.extraTags), std::cend(_settings.extraTags)); @@ -402,55 +337,77 @@ namespace lms::scanner _metadataParser->setDefaultTagDelimiters(_settings.defaultTagDelimiters); } - std::vector scanResults; - context.currentStepStats.totalElems = context.stats.filesScanned; + context.currentStepStats.totalElems = context.stats.totalFileCount; for (const ScannerSettings::MediaLibraryInfo& mediaLibrary : _settings.mediaLibraries) - { - core::pathUtils::exploreFilesRecursive(mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path) - { - LMS_SCOPED_TRACE_DETAILED("Scanner", "OnExploreFile"); + process(context, mediaLibrary); + } - if (_abortScan) - return false; + void ScanStepScanFiles::process(ScanContext& context, const ScannerSettings::MediaLibraryInfo& mediaLibrary) + { + const std::size_t scanQueueMaxScanRequestCount{ 100 * _fileScanQueue.getThreadCount() }; + const std::size_t processFileResultsBatchSize{ 5 }; + + std::vector scanResults; + + core::pathUtils::exploreFilesRecursive( + mediaLibrary.rootDirectory, [&](std::error_code ec, const std::filesystem::path& path) { + LMS_SCOPED_TRACE_DETAILED("Scanner", "OnExploreFile"); + + if (_abortScan) + return false; - if (ec) + if (ec) + { + LMS_LOG(DBUPDATER, ERROR, "Cannot scan file '" << path.string() << "': " << ec.message()); + context.stats.errors.emplace_back(ScanError{ path, ScanErrorType::CannotReadFile, ec.message() }); + } + else + { + bool fileToProcess{}; + if (core::pathUtils::hasFileAnyExtension(path, _settings.supportedAudioFileExtensions)) { - LMS_LOG(DBUPDATER, ERROR, "Cannot process entry '" << path.string() << "': " << ec.message()); - context.stats.errors.emplace_back(ScanError{ path, ScanErrorType::CannotReadFile, ec.message() }); + fileToProcess = true; + if (checkAudioFileNeedScan(context, path, mediaLibrary)) + _fileScanQueue.pushScanRequest(path, FileScanQueue::ScanRequestType::AudioFile); } - else if (core::pathUtils::hasFileAnyExtension(path, _settings.supportedExtensions)) + else if (core::pathUtils::hasFileAnyExtension(path, _settings.supportedImageFileExtensions)) { - if (checkFileNeedScan(context, path, mediaLibrary)) - _metadataScanQueue.pushScanRequest(path); + fileToProcess = true; + if (checkImageFileNeedScan(context, path)) + _fileScanQueue.pushScanRequest(path, FileScanQueue::ScanRequestType::ImageFile); + } + if (fileToProcess) + { context.currentStepStats.processedElems++; _progressCallback(context.currentStepStats); } + } - while (_metadataScanQueue.getResultsCount() > (scanQueueMaxScanRequestCount / 2)) - { - _metadataScanQueue.popResults(scanResults, processMetaDataBatchSize); - processMetaDataScanResults(context, scanResults, mediaLibrary); - } + while (_fileScanQueue.getResultsCount() > (scanQueueMaxScanRequestCount / 2)) + { + _fileScanQueue.popResults(scanResults, processFileResultsBatchSize); + processFileScanResults(context, scanResults, mediaLibrary); + } - _metadataScanQueue.wait(scanQueueMaxScanRequestCount); + _fileScanQueue.wait(scanQueueMaxScanRequestCount); - return true; - }, &excludeDirFileName); + return true; + }, + &excludeDirFileName); - _metadataScanQueue.wait(); + _fileScanQueue.wait(); - while (!_abortScan && _metadataScanQueue.popResults(scanResults, processMetaDataBatchSize) > 0) - processMetaDataScanResults(context, scanResults, mediaLibrary); - } + while (!_abortScan && _fileScanQueue.popResults(scanResults, processFileResultsBatchSize) > 0) + processFileScanResults(context, scanResults, mediaLibrary); } - bool ScanStepScanFiles::checkFileNeedScan(ScanContext& context, const std::filesystem::path& file, const ScannerSettings::MediaLibraryInfo& libraryInfo) + bool ScanStepScanFiles::checkAudioFileNeedScan(ScanContext& context, const std::filesystem::path& file, const ScannerSettings::MediaLibraryInfo& libraryInfo) { ScanStats& stats{ context.stats }; - Wt::WDateTime lastWriteTime{ retrieveFileGetLastWrite(file) }; + const Wt::WDateTime lastWriteTime{ retrieveFileGetLastWrite(file) }; // Should rarely fail as we are currently iterating it if (!lastWriteTime.isValid()) { @@ -469,8 +426,7 @@ namespace lms::scanner if (track && track->getLastWriteTime().toTime_t() == lastWriteTime.toTime_t() - && track->getScanVersion() == _settings.scanVersion - ) + && track->getScanVersion() == _settings.scanVersion) { // this file may have been moved from one library to another, then we just need to update the media library id instead of a full rescan const auto trackMediaLibrary{ track->getMediaLibrary() }; @@ -499,35 +455,63 @@ namespace lms::scanner return true; // need to scan } - void ScanStepScanFiles::processMetaDataScanResults(ScanContext& context, std::span scanResults, const ScannerSettings::MediaLibraryInfo& libraryInfo) + bool ScanStepScanFiles::checkImageFileNeedScan(ScanContext& context, const std::filesystem::path& file) + { + ScanStats& stats{ context.stats }; + + const Wt::WDateTime lastWriteTime{ retrieveFileGetLastWrite(file) }; + // Should rarely fail as we are currently iterating it + if (!lastWriteTime.isValid()) + { + stats.skips++; + return false; + } + + if (!context.scanOptions.fullScan) + { + db::Session& dbSession{ _db.getTLSSession() }; + auto transaction{ _db.getTLSSession().createReadTransaction() }; + + const db::Image::pointer image{ db::Image::find(dbSession, file) }; + if (image && image->getLastWriteTime() == lastWriteTime) + { + stats.skips++; + return false; + } + } + + return true; // need to scan + } + + void ScanStepScanFiles::processFileScanResults(ScanContext& context, std::span scanResults, const ScannerSettings::MediaLibraryInfo& libraryInfo) { LMS_SCOPED_TRACE_OVERVIEW("Scanner", "ProcessScanResults"); db::Session& dbSession{ _db.getTLSSession() }; auto transaction{ dbSession.createWriteTransaction() }; - for (const MetaDataScanResult& scanResult : scanResults) + for (const FileScanResult& scanResult : scanResults) { - LMS_SCOPED_TRACE_DETAILED("Scanner", "ProcessScanResult"); - if (_abortScan) return; - if (scanResult.trackMetaData) + if (const AudioFileScanData * scanData{ std::get_if(&scanResult.scanData) }) { context.stats.scans++; - - processFileMetaData(context, scanResult.path, *scanResult.trackMetaData, libraryInfo); + processAudioFileScanData(context, scanResult.path, scanData->get(), libraryInfo); } - else + else if (const ImageFileScanData * scanData{ std::get_if(&scanResult.scanData) }) { - context.stats.errors.emplace_back(scanResult.path, ScanErrorType::CannotParseFile); + context.stats.scans++; + processImageFileScanData(context, scanResult.path, scanData->has_value() ? &scanData->value() : nullptr, libraryInfo); } } } - void ScanStepScanFiles::processFileMetaData(ScanContext& context, const std::filesystem::path& file, const metadata::Track& trackMetadata, const ScannerSettings::MediaLibraryInfo& libraryInfo) + void ScanStepScanFiles::processAudioFileScanData(ScanContext& context, const std::filesystem::path& file, const metadata::Track* trackMetadata, const ScannerSettings::MediaLibraryInfo& libraryInfo) { + LMS_SCOPED_TRACE_DETAILED("Scanner", "ProcessAudioScanData"); + ScanStats& stats{ context.stats }; const std::optional fileInfo{ retrieveFileInfo(file, libraryInfo.rootDirectory) }; @@ -540,9 +524,20 @@ namespace lms::scanner db::Session& dbSession{ _db.getTLSSession() }; Track::pointer track{ Track::findByPath(dbSession, file) }; - if (trackMetadata.mbid && (!track || _settings.skipDuplicateMBID)) + if (!trackMetadata) + { + if (track) + { + track.remove(); + stats.deletions++; + } + context.stats.errors.emplace_back(file, ScanErrorType::CannotReadAudioFile); + return; + } + + if (trackMetadata->mbid && (!track || _settings.skipDuplicateMBID)) { - std::vector duplicateTracks{ Track::findByMBID(dbSession, *trackMetadata.mbid) }; + std::vector duplicateTracks{ Track::findByMBID(dbSession, *trackMetadata->mbid) }; // find for an existing track MBID as the file may have just been moved if (!track && duplicateTracks.size() == 1) @@ -568,10 +563,9 @@ namespace lms::scanner // Skip if duplicate files no longer in media root: as it will be removed later, we will end up with no file if (std::none_of(std::cbegin(_settings.mediaLibraries), std::cend(_settings.mediaLibraries), - [&](const ScannerSettings::MediaLibraryInfo& libraryInfo) - { - return core::pathUtils::isPathInRootPath(file, libraryInfo.rootDirectory, &excludeDirFileName); - })) + [&](const ScannerSettings::MediaLibraryInfo& libraryInfo) { + return core::pathUtils::isPathInRootPath(file, libraryInfo.rootDirectory, &excludeDirFileName); + })) { continue; } @@ -589,7 +583,7 @@ namespace lms::scanner } // We estimate this is an audio file if the duration is not null - if (trackMetadata.audioProperties.duration == std::chrono::milliseconds::zero()) + if (trackMetadata->audioProperties.duration == std::chrono::milliseconds::zero()) { LMS_LOG(DBUPDATER, DEBUG, "Skipped '" << file.string() << "' (duration is 0)"); @@ -599,14 +593,14 @@ namespace lms::scanner track.remove(); stats.deletions++; } - stats.errors.emplace_back(ScanError{ file, ScanErrorType::BadDuration }); + stats.errors.emplace_back(file, ScanErrorType::BadDuration); return; } // ***** Title std::string title; - if (!trackMetadata.title.empty()) - title = trackMetadata.title; + if (!trackMetadata->title.empty()) + title = trackMetadata->title; else { // TODO parse file name guess track etc. @@ -628,100 +622,158 @@ namespace lms::scanner assert(track); // Audio properties - track.modify()->setBitrate(trackMetadata.audioProperties.bitrate); - track.modify()->setBitsPerSample(trackMetadata.audioProperties.bitsPerSample); - track.modify()->setChannelCount(trackMetadata.audioProperties.channelCount); - track.modify()->setDuration(trackMetadata.audioProperties.duration); - track.modify()->setSampleRate(trackMetadata.audioProperties.sampleRate); + track.modify()->setBitrate(trackMetadata->audioProperties.bitrate); + track.modify()->setBitsPerSample(trackMetadata->audioProperties.bitsPerSample); + track.modify()->setChannelCount(trackMetadata->audioProperties.channelCount); + track.modify()->setDuration(trackMetadata->audioProperties.duration); + track.modify()->setSampleRate(trackMetadata->audioProperties.sampleRate); track.modify()->setRelativeFilePath(fileInfo->relativePath); track.modify()->setFileSize(fileInfo->fileSize); track.modify()->setLastWriteTime(fileInfo->lastWriteTime); track.modify()->setMediaLibrary(MediaLibrary::find(dbSession, libraryInfo.id)); // may be null if settings are updated in // => next scan will correct this + track.modify()->setDirectory(getOrCreateDirectory(dbSession, file.parent_path(), libraryInfo.rootDirectory)); + track.modify()->clearArtistLinks(); // Do not fallback on artists with the same name but having a MBID for artist and releaseArtists, as it may be corrected by properly tagging files - for (const Artist::pointer& artist : getOrCreateArtists(dbSession, trackMetadata.artists, false)) + for (const Artist::pointer& artist : getOrCreateArtists(dbSession, trackMetadata->artists, false)) track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, artist, TrackArtistLinkType::Artist)); - if (trackMetadata.medium && trackMetadata.medium->release) + if (trackMetadata->medium && trackMetadata->medium->release) { - for (const Artist::pointer& releaseArtist : getOrCreateArtists(dbSession, trackMetadata.medium->release->artists, false)) + for (const Artist::pointer& releaseArtist : getOrCreateArtists(dbSession, trackMetadata->medium->release->artists, false)) track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, releaseArtist, TrackArtistLinkType::ReleaseArtist)); } // Allow fallbacks on artists with the same name even if they have MBID, since there is no tag to indicate the MBID of these artists // We could ask MusicBrainz to get all the information, but that would heavily slow down the import process - for (const Artist::pointer& conductor : getOrCreateArtists(dbSession, trackMetadata.conductorArtists, true)) + for (const Artist::pointer& conductor : getOrCreateArtists(dbSession, trackMetadata->conductorArtists, true)) track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, conductor, TrackArtistLinkType::Conductor)); - for (const Artist::pointer& composer : getOrCreateArtists(dbSession, trackMetadata.composerArtists, true)) + for (const Artist::pointer& composer : getOrCreateArtists(dbSession, trackMetadata->composerArtists, true)) track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, composer, TrackArtistLinkType::Composer)); - for (const Artist::pointer& lyricist : getOrCreateArtists(dbSession, trackMetadata.lyricistArtists, true)) + for (const Artist::pointer& lyricist : getOrCreateArtists(dbSession, trackMetadata->lyricistArtists, true)) track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, lyricist, TrackArtistLinkType::Lyricist)); - for (const Artist::pointer& mixer : getOrCreateArtists(dbSession, trackMetadata.mixerArtists, true)) + for (const Artist::pointer& mixer : getOrCreateArtists(dbSession, trackMetadata->mixerArtists, true)) track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, mixer, TrackArtistLinkType::Mixer)); - for (const auto& [role, performers] : trackMetadata.performerArtists) + for (const auto& [role, performers] : trackMetadata->performerArtists) { for (const Artist::pointer& performer : getOrCreateArtists(dbSession, performers, true)) track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, performer, TrackArtistLinkType::Performer, role)); } - for (const Artist::pointer& producer : getOrCreateArtists(dbSession, trackMetadata.producerArtists, true)) + for (const Artist::pointer& producer : getOrCreateArtists(dbSession, trackMetadata->producerArtists, true)) track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, producer, TrackArtistLinkType::Producer)); - for (const Artist::pointer& remixer : getOrCreateArtists(dbSession, trackMetadata.remixerArtists, true)) + for (const Artist::pointer& remixer : getOrCreateArtists(dbSession, trackMetadata->remixerArtists, true)) track.modify()->addArtistLink(TrackArtistLink::create(dbSession, track, remixer, TrackArtistLinkType::Remixer)); track.modify()->setScanVersion(_settings.scanVersion); - if (trackMetadata.medium && trackMetadata.medium->release) - track.modify()->setRelease(getOrCreateRelease(dbSession, *trackMetadata.medium->release, file.parent_path())); + if (trackMetadata->medium && trackMetadata->medium->release) + track.modify()->setRelease(getOrCreateRelease(dbSession, *trackMetadata->medium->release, file.parent_path())); else track.modify()->setRelease({}); - track.modify()->setTotalTrack(trackMetadata.medium ? trackMetadata.medium->trackCount : std::nullopt); - track.modify()->setReleaseReplayGain(trackMetadata.medium ? trackMetadata.medium->replayGain : std::nullopt); - track.modify()->setDiscSubtitle(trackMetadata.medium ? trackMetadata.medium->name : ""); - track.modify()->setClusters(getOrCreateClusters(dbSession, trackMetadata)); + track.modify()->setTotalTrack(trackMetadata->medium ? trackMetadata->medium->trackCount : std::nullopt); + track.modify()->setReleaseReplayGain(trackMetadata->medium ? trackMetadata->medium->replayGain : std::nullopt); + track.modify()->setDiscSubtitle(trackMetadata->medium ? trackMetadata->medium->name : ""); + track.modify()->setClusters(getOrCreateClusters(dbSession, *trackMetadata)); track.modify()->setName(title); track.modify()->setAddedTime(Wt::WDateTime::currentDateTime()); - track.modify()->setTrackNumber(trackMetadata.position); - track.modify()->setRating(trackMetadata.rating); - track.modify()->setDiscNumber(trackMetadata.medium ? trackMetadata.medium->position : std::nullopt); - track.modify()->setDate(trackMetadata.date); - track.modify()->setYear(trackMetadata.year); - track.modify()->setOriginalDate(trackMetadata.originalDate); - track.modify()->setOriginalYear(trackMetadata.originalYear); + track.modify()->setTrackNumber(trackMetadata->position); + track.modify()->setRating(trackMetadata->rating); + track.modify()->setDiscNumber(trackMetadata->medium ? trackMetadata->medium->position : std::nullopt); + track.modify()->setDate(trackMetadata->date); + track.modify()->setYear(trackMetadata->year); + track.modify()->setOriginalDate(trackMetadata->originalDate); + track.modify()->setOriginalYear(trackMetadata->originalYear); // If a file has an OriginalDate but no date, set it to ease filtering - if (!trackMetadata.date.isValid() && trackMetadata.originalDate.isValid()) - track.modify()->setDate(trackMetadata.originalDate); + if (!trackMetadata->date.isValid() && trackMetadata->originalDate.isValid()) + track.modify()->setDate(trackMetadata->originalDate); // If a file has an OriginalYear but no Year, set it to ease filtering - if (!trackMetadata.year && trackMetadata.originalYear) - track.modify()->setYear(trackMetadata.originalYear); + if (!trackMetadata->year && trackMetadata->originalYear) + track.modify()->setYear(trackMetadata->originalYear); - track.modify()->setRecordingMBID(trackMetadata.recordingMBID); - track.modify()->setTrackMBID(trackMetadata.mbid); + track.modify()->setRecordingMBID(trackMetadata->recordingMBID); + track.modify()->setTrackMBID(trackMetadata->mbid); if (auto trackFeatures{ TrackFeatures::find(dbSession, track->getId()) }) trackFeatures.remove(); // TODO: only if MBID changed? - track.modify()->setHasCover(trackMetadata.hasCover); - track.modify()->setCopyright(trackMetadata.copyright); - track.modify()->setCopyrightURL(trackMetadata.copyrightURL); - track.modify()->setTrackReplayGain(trackMetadata.replayGain); - track.modify()->setArtistDisplayName(trackMetadata.artistDisplayName); + track.modify()->setHasCover(trackMetadata->hasCover); + track.modify()->setCopyright(trackMetadata->copyright); + track.modify()->setCopyrightURL(trackMetadata->copyrightURL); + track.modify()->setTrackReplayGain(trackMetadata->replayGain); + track.modify()->setArtistDisplayName(trackMetadata->artistDisplayName); + + if (added) + { + LMS_LOG(DBUPDATER, DEBUG, "Added audio file '" << file.string() << "'"); + stats.additions++; + } + else + { + LMS_LOG(DBUPDATER, DEBUG, "Updated audio file '" << file.string() << "'"); + stats.updates++; + } + } + + void ScanStepScanFiles::processImageFileScanData(ScanContext& context, const std::filesystem::path& file, const ImageInfo* imageInfo, const ScannerSettings::MediaLibraryInfo& libraryInfo) + { + LMS_SCOPED_TRACE_DETAILED("Scanner", "ProcessImageScanData"); + + ScanStats& stats{ context.stats }; + + const std::optional fileInfo{ retrieveFileInfo(file, libraryInfo.rootDirectory) }; + if (!fileInfo) + { + stats.skips++; + return; + } + + db::Session& dbSession{ _db.getTLSSession() }; + db::Image::pointer image{ db::Image::find(dbSession, file) }; + + if (!imageInfo) + { + if (image) + { + image.remove(); + stats.deletions++; + } + context.stats.errors.emplace_back(file, ScanErrorType::CannotReadImageFile); + return; + } + + bool added; + if (!image) + { + image = dbSession.create(file); + added = true; + } + else + { + added = false; + } + + image.modify()->setLastWriteTime(fileInfo->lastWriteTime); + image.modify()->setFileSize(fileInfo->fileSize); + image.modify()->setHeight(imageInfo->height); + image.modify()->setWidth(imageInfo->width); + image.modify()->setDirectory(getOrCreateDirectory(dbSession, file.parent_path(), libraryInfo.rootDirectory)); if (added) { - LMS_LOG(DBUPDATER, DEBUG, "Added '" << file.string() << "'"); + LMS_LOG(DBUPDATER, DEBUG, "Added image '" << file.string() << "'"); stats.additions++; } else { - LMS_LOG(DBUPDATER, DEBUG, "Updated '" << file.string() << "'"); + LMS_LOG(DBUPDATER, DEBUG, "Updated image '" << file.string() << "'"); stats.updates++; } } -} +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScanStepScanFiles.hpp b/src/libs/services/scanner/impl/ScanStepScanFiles.hpp index d176857e6..fd9687d91 100644 --- a/src/libs/services/scanner/impl/ScanStepScanFiles.hpp +++ b/src/libs/services/scanner/impl/ScanStepScanFiles.hpp @@ -19,16 +19,14 @@ #pragma once -#include -#include #include -#include #include #include #include #include "metadata/IParser.hpp" -#include "core/IOContextRunner.hpp" + +#include "FileScanQueue.hpp" #include "ScanStepBase.hpp" namespace lms::scanner @@ -42,46 +40,18 @@ namespace lms::scanner ScanStep getStep() const override { return ScanStep::ScanFiles; } core::LiteralString getStepName() const override { return "Scan files"; } void process(ScanContext& context) override; + void process(ScanContext& context, const ScannerSettings::MediaLibraryInfo& mediaLibrary); - bool checkFileNeedScan(ScanContext& context, const std::filesystem::path& file, const ScannerSettings::MediaLibraryInfo& libraryInfo); - struct MetaDataScanResult - { - std::filesystem::path path; - std::unique_ptr trackMetaData; - }; - void processMetaDataScanResults(ScanContext& context, std::span scanResults, const ScannerSettings::MediaLibraryInfo& libraryInfo); - void processFileMetaData(ScanContext& context, const std::filesystem::path& file, const metadata::Track& trackMetadata, const ScannerSettings::MediaLibraryInfo& libraryInfo); - - std::unique_ptr _metadataParser; - const std::vector _extraTagsToParse; - - class MetadataScanQueue - { - public: - MetadataScanQueue(metadata::IParser& parser, std::size_t threadCount, bool& abort); - - std::size_t getThreadCount() const { return _scanContextRunner.getThreadCount(); } - - void pushScanRequest(const std::filesystem::path& path); - - std::size_t getResultsCount() const; - size_t popResults(std::vector& results, std::size_t maxCount); - - void wait(std::size_t maxScanRequestCount = 0); // wait until ongoing scan request count <= maxScanRequestCount + bool checkAudioFileNeedScan(ScanContext& context, const std::filesystem::path& file, const ScannerSettings::MediaLibraryInfo& libraryInfo); + bool checkImageFileNeedScan(ScanContext& context, const std::filesystem::path& file); - private: - metadata::IParser& _metadataParser; - boost::asio::io_context _scanContext; - core::IOContextRunner _scanContextRunner; + void processFileScanResults(ScanContext& context, std::span scanResults, const ScannerSettings::MediaLibraryInfo& libraryInfo); + void processAudioFileScanData(ScanContext& context, const std::filesystem::path& path, const metadata::Track* trackMetadata, const ScannerSettings::MediaLibraryInfo& libraryInfo); + void processImageFileScanData(ScanContext& context, const std::filesystem::path& path, const ImageInfo* imageInfo, const ScannerSettings::MediaLibraryInfo& libraryInfo); - mutable std::mutex _mutex ; - std::size_t _ongoingScanCount{}; - std::deque _scanResults; - std::condition_variable _condVar; - bool& _abort; - }; - MetadataScanQueue _metadataScanQueue; + std::unique_ptr _metadataParser; + const std::vector _extraTagsToParse; - std::deque _metaDataScanResults; + FileScanQueue _fileScanQueue; }; -} +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScannerService.cpp b/src/libs/services/scanner/impl/ScannerService.cpp index f29a9b52a..321116f63 100644 --- a/src/libs/services/scanner/impl/ScannerService.cpp +++ b/src/libs/services/scanner/impl/ScannerService.cpp @@ -21,21 +21,24 @@ #include -#include "database/MediaLibrary.hpp" -#include "database/TrackFeatures.hpp" -#include "database/ScanSettings.hpp" #include "core/Exception.hpp" -#include "core/Path.hpp" #include "core/IConfig.hpp" #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" +#include "core/Path.hpp" +#include "database/MediaLibrary.hpp" +#include "database/ScanSettings.hpp" +#include "database/TrackFeatures.hpp" +#include "image/Image.hpp" -#include "ScanStepCheckDuplicatedDbFiles.hpp" +#include "ScanStepAssociateArtistImages.hpp" +#include "ScanStepCheckForDuplicatedFiles.hpp" +#include "ScanStepCheckForRemovedFiles.hpp" #include "ScanStepCompact.hpp" #include "ScanStepComputeClusterStats.hpp" #include "ScanStepDiscoverFiles.hpp" #include "ScanStepOptimize.hpp" -#include "ScanStepRemoveOrphanDbFiles.hpp" +#include "ScanStepRemoveOrphanedDbEntries.hpp" #include "ScanStepScanFiles.hpp" namespace lms::scanner @@ -91,13 +94,12 @@ namespace lms::scanner { std::scoped_lock lock{ _controlMutex }; - _ioService.post([this] - { - if (_abortScan) - return; + _ioService.post([this] { + if (_abortScan) + return; - scheduleNextScan(); - }); + scheduleNextScan(); + }); _ioService.start(); } @@ -139,25 +141,23 @@ namespace lms::scanner void ScannerService::requestImmediateScan(const ScanOptions& scanOptions) { abortScan(); - _ioService.post([this, scanOptions] - { - if (_abortScan) - return; + _ioService.post([this, scanOptions] { + if (_abortScan) + return; - scheduleScan(scanOptions); - }); + scheduleScan(scanOptions); + }); } void ScannerService::requestReload() { abortScan(); - _ioService.post([this]() - { - if (_abortScan) - return; + _ioService.post([this]() { + if (_abortScan) + return; - scheduleNextScan(); - }); + scheduleNextScan(); + }); } ScannerService::Status ScannerService::getStatus() const @@ -229,8 +229,7 @@ namespace lms::scanner void ScannerService::scheduleScan(const ScanOptions& scanOptions, const Wt::WDateTime& dateTime) { - auto cb{ [this, scanOptions](boost::system::error_code ec) - { + auto cb{ [this, scanOptions](boost::system::error_code ec) { if (ec) return; @@ -270,7 +269,7 @@ namespace lms::scanner refreshScanSettings(); - IScanStep::ScanContext scanContext{ scanOptions, ScanStats {}, ScanStepStats {} }; + IScanStep::ScanContext scanContext{ scanOptions, ScanStats{}, ScanStepStats{} }; ScanStats& stats{ scanContext.stats }; stats.startTime = Wt::WDateTime::currentDateTime(); @@ -329,27 +328,28 @@ namespace lms::scanner _settings = std::move(newSettings); - auto cbFunc{ [this](const ScanStepStats& stats) - { - notifyInProgressIfNeeded(stats); - } }; + auto cbFunc{ [this](const ScanStepStats& stats) { + notifyInProgressIfNeeded(stats); + } }; - ScanStepBase::InitParams params - { + ScanStepBase::InitParams params{ _settings, cbFunc, _abortScan, _db }; + // Order is important _scanSteps.clear(); _scanSteps.push_back(std::make_unique(params)); _scanSteps.push_back(std::make_unique(params)); - _scanSteps.push_back(std::make_unique(params)); + _scanSteps.push_back(std::make_unique(params)); + _scanSteps.push_back(std::make_unique(params)); + _scanSteps.push_back(std::make_unique(params)); _scanSteps.push_back(std::make_unique(params)); _scanSteps.push_back(std::make_unique(params)); _scanSteps.push_back(std::make_unique(params)); - _scanSteps.push_back(std::make_unique(params)); + _scanSteps.push_back(std::make_unique(params)); } ScannerSettings ScannerService::readSettings() @@ -367,20 +367,26 @@ namespace lms::scanner newSettings.updatePeriod = scanSettings->getUpdatePeriod(); { - const auto fileExtensions{ scanSettings->getAudioFileExtensions() }; - newSettings.supportedExtensions.reserve(fileExtensions.size()); - std::transform(std::cbegin(fileExtensions), std::end(fileExtensions), std::back_inserter(newSettings.supportedExtensions), + const auto audioFileExtensions{ scanSettings->getAudioFileExtensions() }; + newSettings.supportedAudioFileExtensions.reserve(audioFileExtensions.size()); + std::transform(std::cbegin(audioFileExtensions), std::end(audioFileExtensions), std::back_inserter(newSettings.supportedAudioFileExtensions), [](const std::filesystem::path& extension) { return std::filesystem::path{ core::stringUtils::stringToLower(extension.string()) }; }); } - MediaLibrary::find(_db.getTLSSession(), [&](const MediaLibrary::pointer& mediaLibrary) - { - newSettings.mediaLibraries.push_back(ScannerSettings::MediaLibraryInfo{ mediaLibrary->getId(), mediaLibrary->getPath().lexically_normal() }); - }); + { + const auto imageFileExtensions{ image::getSupportedFileExtensions() }; + newSettings.supportedImageFileExtensions.reserve(imageFileExtensions.size()); + std::transform(std::cbegin(imageFileExtensions), std::end(imageFileExtensions), std::back_inserter(newSettings.supportedImageFileExtensions), + [](const std::filesystem::path& extension) { return std::filesystem::path{ core::stringUtils::stringToLower(extension.string()) }; }); + } + + MediaLibrary::find(_db.getTLSSession(), [&](const MediaLibrary::pointer& mediaLibrary) { + newSettings.mediaLibraries.push_back(ScannerSettings::MediaLibraryInfo{ mediaLibrary->getId(), mediaLibrary->getPath().lexically_normal() }); + }); { const auto& tags{ scanSettings->getExtraTagsToScan() }; - std::transform(std::cbegin(tags), std::cend(tags), std::back_inserter(newSettings.extraTags), [](std::string_view tag) { return std::string{ tag };}); + std::transform(std::cbegin(tags), std::cend(tags), std::back_inserter(newSettings.extraTags), [](std::string_view tag) { return std::string{ tag }; }); } newSettings.artistTagDelimiters = scanSettings->getArtistTagDelimiters(); diff --git a/src/libs/services/scanner/impl/ScannerService.hpp b/src/libs/services/scanner/impl/ScannerService.hpp index 5750c9150..2a1bbd824 100644 --- a/src/libs/services/scanner/impl/ScannerService.hpp +++ b/src/libs/services/scanner/impl/ScannerService.hpp @@ -20,23 +20,22 @@ #pragma once #include -#include #include +#include #include #include #include #include - #include +#include "IScanStep.hpp" +#include "ScannerSettings.hpp" +#include "core/Path.hpp" #include "database/Db.hpp" #include "database/Session.hpp" #include "database/Types.hpp" #include "services/scanner/IScannerService.hpp" -#include "core/Path.hpp" -#include "IScanStep.hpp" -#include "ScannerSettings.hpp" namespace lms::scanner { @@ -53,7 +52,7 @@ namespace lms::scanner void requestReload() override; void requestImmediateScan(const ScanOptions& scanOptions) override; - Status getStatus() const override; + Status getStatus() const override; Events& getEvents() override { return _events; } private: @@ -78,23 +77,22 @@ namespace lms::scanner void notifyInProgressIfNeeded(const ScanStepStats& stats); void notifyInProgress(const ScanStepStats& stats); - std::vector> _scanSteps; + std::vector> _scanSteps; - std::mutex _controlMutex; - bool _abortScan{}; - Wt::WIOService _ioService; - boost::asio::system_timer _scheduleTimer{ _ioService }; - Events _events; - std::chrono::system_clock::time_point _lastScanInProgressEmit{}; + std::mutex _controlMutex; + bool _abortScan{}; + Wt::WIOService _ioService; + boost::asio::system_timer _scheduleTimer{ _ioService }; + Events _events; + std::chrono::system_clock::time_point _lastScanInProgressEmit{}; db::Db& _db; - mutable std::shared_mutex _statusMutex; - State _curState{ State::NotScheduled }; - std::optional _lastCompleteScanStats; - std::optional _currentScanStepStats; - Wt::WDateTime _nextScheduledScan; + mutable std::shared_mutex _statusMutex; + State _curState{ State::NotScheduled }; + std::optional _lastCompleteScanStats; + std::optional _currentScanStepStats; + Wt::WDateTime _nextScheduledScan; - ScannerSettings _settings; + ScannerSettings _settings; }; -} // Scanner - +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScannerSettings.hpp b/src/libs/services/scanner/impl/ScannerSettings.hpp index eca0f08e4..4b228694c 100644 --- a/src/libs/services/scanner/impl/ScannerSettings.hpp +++ b/src/libs/services/scanner/impl/ScannerSettings.hpp @@ -22,7 +22,9 @@ #include #include #include + #include + #include "database/MediaLibraryId.hpp" #include "database/ScanSettings.hpp" @@ -30,14 +32,15 @@ namespace lms::scanner { struct ScannerSettings { - std::size_t scanVersion{}; - Wt::WTime startTime; - db::ScanSettings::UpdatePeriod updatePeriod{ db::ScanSettings::UpdatePeriod::Never }; - std::vector supportedExtensions; - bool skipDuplicateMBID{}; - std::vector extraTags; - std::vector artistTagDelimiters; - std::vector defaultTagDelimiters; + std::size_t scanVersion{}; + Wt::WTime startTime; + db::ScanSettings::UpdatePeriod updatePeriod{ db::ScanSettings::UpdatePeriod::Never }; + std::vector supportedAudioFileExtensions; + std::vector supportedImageFileExtensions; + bool skipDuplicateMBID{}; + std::vector extraTags; + std::vector artistTagDelimiters; + std::vector defaultTagDelimiters; struct MediaLibraryInfo { @@ -46,8 +49,8 @@ namespace lms::scanner auto operator<=>(const MediaLibraryInfo& other) const = default; }; - std::vector mediaLibraries; + std::vector mediaLibraries; bool operator==(const ScannerSettings& rhs) const = default; }; -} +} // namespace lms::scanner diff --git a/src/libs/services/scanner/impl/ScannerStats.cpp b/src/libs/services/scanner/impl/ScannerStats.cpp index b2df60d6a..be05106b8 100644 --- a/src/libs/services/scanner/impl/ScannerStats.cpp +++ b/src/libs/services/scanner/impl/ScannerStats.cpp @@ -22,9 +22,9 @@ namespace lms::scanner { ScanError::ScanError(const std::filesystem::path& _file, ScanErrorType _error, const std::string& _systemError) - : file{ _file }, - error{ _error }, - systemError{ _systemError } + : file{ _file } + , error{ _error } + , systemError{ _systemError } { } @@ -43,4 +43,3 @@ namespace lms::scanner return (processedElems / static_cast(totalElems ? totalElems : 1)) * 100; } } // namespace lms::scanner - diff --git a/src/libs/services/scanner/include/services/scanner/IScannerService.hpp b/src/libs/services/scanner/include/services/scanner/IScannerService.hpp index 2c7244492..b811ce290 100644 --- a/src/libs/services/scanner/include/services/scanner/IScannerService.hpp +++ b/src/libs/services/scanner/include/services/scanner/IScannerService.hpp @@ -50,10 +50,10 @@ namespace lms::scanner struct Status { - State currentState{ State::NotScheduled }; - Wt::WDateTime nextScheduledScan; - std::optional lastCompleteScanStats; - std::optional currentScanStepStats; + State currentState{ State::NotScheduled }; + Wt::WDateTime nextScheduledScan; + std::optional lastCompleteScanStats; + std::optional currentScanStepStats; }; virtual Status getStatus() const = 0; @@ -62,4 +62,4 @@ namespace lms::scanner }; std::unique_ptr createScannerService(db::Db& db); -} // Scanner +} // namespace lms::scanner diff --git a/src/libs/services/scanner/include/services/scanner/ScannerEvents.hpp b/src/libs/services/scanner/include/services/scanner/ScannerEvents.hpp index 19dfe89d5..5084093ee 100644 --- a/src/libs/services/scanner/include/services/scanner/ScannerEvents.hpp +++ b/src/libs/services/scanner/include/services/scanner/ScannerEvents.hpp @@ -30,20 +30,19 @@ namespace lms::scanner struct Events { // Called if scan was aborted - Wt::Signal<> scanAborted; + Wt::Signal<> scanAborted; // Called just after scan start - Wt::Signal<> scanStarted; + Wt::Signal<> scanStarted; // Called just after scan complete (true if changes have been made) - Wt::Signal scanComplete; + Wt::Signal scanComplete; // Called during scan in progress - Wt::Signal scanInProgress; + Wt::Signal scanInProgress; // Called after a schedule - Wt::Signal scanScheduled; + Wt::Signal scanScheduled; }; -} // ns Scanner - +} // namespace lms::scanner diff --git a/src/libs/services/scanner/include/services/scanner/ScannerOptions.hpp b/src/libs/services/scanner/include/services/scanner/ScannerOptions.hpp index 33ac40b23..4864d0187 100644 --- a/src/libs/services/scanner/include/services/scanner/ScannerOptions.hpp +++ b/src/libs/services/scanner/include/services/scanner/ScannerOptions.hpp @@ -23,8 +23,8 @@ namespace lms::scanner { struct ScanOptions { - bool fullScan{}; // scan files even if not changed + bool fullScan{}; // scan files even if not changed bool forceOptimize{}; // force optimize database - bool compact{}; // compact the database + bool compact{}; // compact the database }; -} \ No newline at end of file +} // namespace lms::scanner \ No newline at end of file diff --git a/src/libs/services/scanner/include/services/scanner/ScannerStats.hpp b/src/libs/services/scanner/include/services/scanner/ScannerStats.hpp index 9c8d96218..22ce1d1e4 100644 --- a/src/libs/services/scanner/include/services/scanner/ScannerStats.hpp +++ b/src/libs/services/scanner/include/services/scanner/ScannerStats.hpp @@ -30,10 +30,11 @@ namespace lms::scanner { enum class ScanErrorType { - CannotReadFile, // cannot read file - CannotParseFile, // cannot parse file - NoAudioTrack, // no audio track found - BadDuration, // bad duration + CannotReadFile, // cannot read file + CannotReadAudioFile, // cannot parse audio file + CannotReadImageFile, // cannot parse image file + NoAudioTrack, // no audio track found + BadDuration, // bad duration }; enum class DuplicateReason @@ -44,68 +45,70 @@ namespace lms::scanner struct ScanError { - std::filesystem::path file; - ScanErrorType error; - std::string systemError; + std::filesystem::path file; + ScanErrorType error; + std::string systemError; ScanError(const std::filesystem::path& file, ScanErrorType error, const std::string& systemError = ""); }; struct ScanDuplicate { - db::TrackId trackId; - DuplicateReason reason; + db::TrackId trackId; + DuplicateReason reason; }; + // Alphabetical order enum class ScanStep { - CheckForMissingFiles, - CheckForDuplicateFiles, + AssociateArtistImages, + CheckForDuplicatedFiles, + CheckForRemovedFiles, ComputeClusterStats, Compact, DiscoverFiles, FetchTrackFeatures, Optimize, ReloadSimilarityEngine, + RemoveOrphanedDbEntries, ScanFiles, }; - static inline constexpr unsigned ScanProgressStepCount{ 9 }; + static inline constexpr unsigned ScanProgressStepCount{ 11 }; // reduced scan stats struct ScanStepStats { - Wt::WDateTime startTime; + Wt::WDateTime startTime; std::size_t stepIndex{}; ScanStep currentStep; - std::size_t totalElems{}; - std::size_t processedElems{}; + std::size_t totalElems{}; + std::size_t processedElems{}; - unsigned progress() const; + unsigned progress() const; }; struct ScanStats { - Wt::WDateTime startTime; - Wt::WDateTime stopTime; + Wt::WDateTime startTime; + Wt::WDateTime stopTime; - std::size_t filesScanned{}; // Total number of files scanned (estimated) + std::size_t totalFileCount{}; // Total number of files (estimated) - std::size_t skips{}; // no change since last scan - std::size_t scans{}; // actually scanned filed + std::size_t skips{}; // no change since last scan + std::size_t scans{}; // actually scanned filed - std::size_t additions{}; // added in DB - std::size_t deletions{}; // removed from DB - std::size_t updates{}; // updated file in DB + std::size_t additions{}; // added in DB + std::size_t deletions{}; // removed from DB + std::size_t updates{}; // updated file in DB - std::size_t featuresFetched{}; // features fetched in DB + std::size_t featuresFetched{}; // features fetched in DB - std::vector errors; - std::vector duplicates; + std::vector errors; + std::vector duplicates; - std::size_t nbFiles() const; - std::size_t nbChanges() const; + std::size_t nbFiles() const; + std::size_t nbChanges() const; }; } // namespace lms::scanner - diff --git a/src/libs/services/scrobbling/impl/IScrobblingBackend.hpp b/src/libs/services/scrobbling/impl/IScrobblingBackend.hpp index 99b1805e3..60c6f1287 100644 --- a/src/libs/services/scrobbling/impl/IScrobblingBackend.hpp +++ b/src/libs/services/scrobbling/impl/IScrobblingBackend.hpp @@ -30,7 +30,7 @@ namespace lms::db class Session; class TrackList; class User; -} +} // namespace lms::db namespace lms::scrobbling { @@ -44,5 +44,4 @@ namespace lms::scrobbling virtual void listenFinished(const Listen& listen, std::optional duration) = 0; virtual void addTimedListen(const TimedListen& listen) = 0; }; -} // ns Scrobbling - +} // namespace lms::scrobbling diff --git a/src/libs/services/scrobbling/impl/ScrobblingService.cpp b/src/libs/services/scrobbling/impl/ScrobblingService.cpp index 6b572efa1..52e430745 100644 --- a/src/libs/services/scrobbling/impl/ScrobblingService.cpp +++ b/src/libs/services/scrobbling/impl/ScrobblingService.cpp @@ -19,6 +19,7 @@ #include "ScrobblingService.hpp" +#include "core/ILogger.hpp" #include "database/Artist.hpp" #include "database/Db.hpp" #include "database/Listen.hpp" @@ -26,7 +27,6 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" -#include "core/ILogger.hpp" #include "internal/InternalBackend.hpp" #include "listenbrainz/ListenBrainzBackend.hpp" @@ -54,7 +54,7 @@ namespace lms::scrobbling { return db::Listen::ArtistStatsFindParameters{ convertToListenFindParameters(static_cast(params)), params.linkType }; } - } + } // namespace std::unique_ptr createScrobblingService(boost::asio::io_context& ioContext, Db& db) { @@ -253,5 +253,4 @@ namespace lms::scrobbling res = db::Listen::getTopTracks(session, listenFindParams); return res; } -} // ns Scrobbling - +} // namespace lms::scrobbling diff --git a/src/libs/services/scrobbling/impl/ScrobblingService.hpp b/src/libs/services/scrobbling/impl/ScrobblingService.hpp index ce816f0ca..1bde3e49c 100644 --- a/src/libs/services/scrobbling/impl/ScrobblingService.hpp +++ b/src/libs/services/scrobbling/impl/ScrobblingService.hpp @@ -24,6 +24,7 @@ #include #include "services/scrobbling/IScrobblingService.hpp" + #include "IScrobblingBackend.hpp" namespace lms::scrobbling @@ -59,5 +60,4 @@ namespace lms::scrobbling std::unordered_map> _scrobblingBackends; }; -} // ns Scrobbling - +} // namespace lms::scrobbling diff --git a/src/libs/services/scrobbling/impl/internal/InternalBackend.cpp b/src/libs/services/scrobbling/impl/internal/InternalBackend.cpp index 5512acbe2..b70ab4b7a 100644 --- a/src/libs/services/scrobbling/impl/internal/InternalBackend.cpp +++ b/src/libs/services/scrobbling/impl/internal/InternalBackend.cpp @@ -29,7 +29,8 @@ namespace lms::scrobbling { InternalBackend::InternalBackend(db::Db& db) : _db{ db } - {} + { + } void InternalBackend::listenStarted(const Listen&) { @@ -64,5 +65,4 @@ namespace lms::scrobbling auto dbListen{ session.create(user, track, db::ScrobblingBackend::Internal, listen.listenedAt) }; dbListen.modify()->setSyncState(db::SyncState::Synchronized); } -} // Scrobbling - +} // namespace lms::scrobbling diff --git a/src/libs/services/scrobbling/impl/internal/InternalBackend.hpp b/src/libs/services/scrobbling/impl/internal/InternalBackend.hpp index 349addf93..a0fd45d71 100644 --- a/src/libs/services/scrobbling/impl/internal/InternalBackend.hpp +++ b/src/libs/services/scrobbling/impl/internal/InternalBackend.hpp @@ -40,5 +40,4 @@ namespace lms::scrobbling db::Db& _db; }; -} // Scrobbling - +} // namespace lms::scrobbling diff --git a/src/libs/services/scrobbling/impl/listenbrainz/Exception.hpp b/src/libs/services/scrobbling/impl/listenbrainz/Exception.hpp index 7bf30fffd..517f8ef3c 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/Exception.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/Exception.hpp @@ -23,9 +23,9 @@ namespace lms::scrobbling::listenBrainz { - class Exception : public scrobbling::Exception - { - public: - using scrobbling::Exception::Exception; - }; -} + class Exception : public scrobbling::Exception + { + public: + using scrobbling::Exception::Exception; + }; +} // namespace lms::scrobbling::listenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.cpp index 7bf6dccd2..1d0ac7a5a 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.cpp @@ -19,13 +19,14 @@ #include "ListenBrainzBackend.hpp" -#include "database/Db.hpp" -#include "database/Session.hpp" -#include "database/Track.hpp" #include "core/IConfig.hpp" -#include "core/http/IClient.hpp" #include "core/ILogger.hpp" #include "core/Service.hpp" +#include "core/http/IClient.hpp" +#include "database/Db.hpp" +#include "database/Session.hpp" +#include "database/Track.hpp" + #include "Utils.hpp" namespace lms::scrobbling::listenBrainz @@ -48,7 +49,7 @@ namespace lms::scrobbling::listenBrainz return res; } - } + } // namespace ListenBrainzBackend::ListenBrainzBackend(boost::asio::io_context& ioContext, Db& db) : _ioContext{ ioContext } @@ -84,4 +85,3 @@ namespace lms::scrobbling::listenBrainz _listensSynchronizer.enqueListen(timedListen); } } // namespace lms::scrobbling::listenBrainz - diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.hpp b/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.hpp index ba1bccbc7..3ab08fd53 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListenBrainzBackend.hpp @@ -19,8 +19,9 @@ #pragma once -#include #include +#include + #include #include "IScrobblingBackend.hpp" @@ -50,11 +51,10 @@ namespace lms::scrobbling::listenBrainz // Submit listens void enqueListen(const Listen& listen, const Wt::WDateTime& timePoint); - boost::asio::io_context& _ioContext; - db::Db& _db; - std::string _baseAPIUrl; - std::unique_ptr _client; - ListensSynchronizer _listensSynchronizer; + boost::asio::io_context& _ioContext; + db::Db& _db; + std::string _baseAPIUrl; + std::unique_ptr _client; + ListensSynchronizer _listensSynchronizer; }; -} // scrobbling::ListenBrainz - +} // namespace lms::scrobbling::listenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.cpp index 690c41860..322ae5f08 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.cpp @@ -21,21 +21,20 @@ namespace lms::scrobbling::listenBrainz { - std::ostream& - operator<<(std::ostream& os, const Listen& listen) - { - os << "track name = '" << listen.trackName << "', artistName = '" << listen.artistName << "'"; - if (listen.listenedAt.isValid()) - os << ", listenedAt = " << listen.listenedAt.toString(); - if (!listen.releaseName.empty()) - os << ", releaseName = '" << listen.releaseName << "'"; - if (listen.trackNumber) - os << ", trackNumber = " << *listen.trackNumber; - if (listen.trackMBID) - os << ", trackMBID = '" << listen.trackMBID->getAsString() << "'"; - if (listen.recordingMBID) - os << ", recordingMBID = '" << listen.recordingMBID->getAsString() << "'"; + std::ostream& operator<<(std::ostream& os, const Listen& listen) + { + os << "track name = '" << listen.trackName << "', artistName = '" << listen.artistName << "'"; + if (listen.listenedAt.isValid()) + os << ", listenedAt = " << listen.listenedAt.toString(); + if (!listen.releaseName.empty()) + os << ", releaseName = '" << listen.releaseName << "'"; + if (listen.trackNumber) + os << ", trackNumber = " << *listen.trackNumber; + if (listen.trackMBID) + os << ", trackMBID = '" << listen.trackMBID->getAsString() << "'"; + if (listen.recordingMBID) + os << ", recordingMBID = '" << listen.recordingMBID->getAsString() << "'"; - return os; - } -} // scrobbling::ListenBrainz + return os; + } +} // namespace lms::scrobbling::listenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.hpp b/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.hpp index 37c64dc60..504f5280b 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListenTypes.hpp @@ -19,25 +19,26 @@ #pragma once -#include #include +#include + #include #include "core/UUID.hpp" namespace lms::scrobbling::listenBrainz { - struct Listen - { - std::string trackName; - std::string releaseName; - std::string artistName; - std::optional recordingMBID; - std::optional trackMBID; - std::optional releaseMBID; - std::optional trackNumber; - Wt::WDateTime listenedAt; - }; + struct Listen + { + std::string trackName; + std::string releaseName; + std::string artistName; + std::optional recordingMBID; + std::optional trackMBID; + std::optional releaseMBID; + std::optional trackNumber; + Wt::WDateTime listenedAt; + }; - std::ostream& operator<<(std::ostream& os, const Listen& listen); -} + std::ostream& operator<<(std::ostream& os, const Listen& listen); +} // namespace lms::scrobbling::listenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.cpp index b7bf7d450..7701a96d6 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.cpp @@ -21,10 +21,11 @@ #include #include -#include #include +#include #include "core/ILogger.hpp" + #include "Utils.hpp" namespace lms::scrobbling::listenBrainz @@ -102,4 +103,4 @@ namespace lms::scrobbling::listenBrainz return result; } -} // scrobbling::ListenBrainz +} // namespace lms::scrobbling::listenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.hpp b/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.hpp index c85a98149..17767b41b 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListensParser.hpp @@ -25,15 +25,15 @@ namespace lms::scrobbling::listenBrainz { - class ListensParser - { - public: - struct Result - { - std::size_t listenCount {}; // may be > than listens.size() - std::vector listens; // successfully parsed listens - }; + class ListensParser + { + public: + struct Result + { + std::size_t listenCount{}; // may be > than listens.size() + std::vector listens; // successfully parsed listens + }; - static Result parse(std::string_view msgBody); - }; -} // scrobbling::ListenBrainz + static Result parse(std::string_view msgBody); + }; +} // namespace lms::scrobbling::listenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp index c221c3ca9..cf6d6a96d 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.cpp @@ -19,12 +19,16 @@ #include "ListensSynchronizer.hpp" -#include #include #include -#include #include +#include +#include +#include "ListensParser.hpp" +#include "core/IConfig.hpp" +#include "core/Service.hpp" +#include "core/http/IClient.hpp" #include "database/Artist.hpp" #include "database/Db.hpp" #include "database/Listen.hpp" @@ -33,10 +37,6 @@ #include "database/Track.hpp" #include "database/User.hpp" #include "services/scrobbling/Exception.hpp" -#include "core/IConfig.hpp" -#include "core/http/IClient.hpp" -#include "core/Service.hpp" -#include "ListensParser.hpp" #include "Utils.hpp" @@ -52,7 +52,7 @@ namespace lms::scrobbling::listenBrainz if (!track) return std::nullopt; - auto artists{ track->getArtists({db::TrackArtistLinkType::Artist}) }; + auto artists{ track->getArtists({ db::TrackArtistLinkType::Artist }) }; if (artists.empty()) artists = track->getArtists({ db::TrackArtistLinkType::ReleaseArtist }); @@ -65,12 +65,12 @@ namespace lms::scrobbling::listenBrainz Wt::Json::Object additionalInfo; additionalInfo["listening_from"] = "LMS"; additionalInfo["duration_ms"] = std::chrono::duration_cast(track->getDuration()).count(); - if (const auto release {track->getRelease()}) + if (const auto release{ track->getRelease() }) { if (auto MBID{ release->getMBID() }) - additionalInfo["release_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} }; + additionalInfo["release_mbid"] = Wt::Json::Value{ std::string{ MBID->getAsString() } }; if (auto groupMBID{ release->getGroupMBID() }) - additionalInfo["release_group_mbid"] = Wt::Json::Value{ std::string {groupMBID->getAsString()} }; + additionalInfo["release_group_mbid"] = Wt::Json::Value{ std::string{ groupMBID->getAsString() } }; } { @@ -78,7 +78,7 @@ namespace lms::scrobbling::listenBrainz for (const db::Artist::pointer& artist : artists) { if (auto MBID{ artist->getMBID() }) - artistMBIDs.push_back(Wt::Json::Value{ std::string {MBID->getAsString()} }); + artistMBIDs.push_back(Wt::Json::Value{ std::string{ MBID->getAsString() } }); } if (!artistMBIDs.empty()) @@ -86,10 +86,10 @@ namespace lms::scrobbling::listenBrainz } if (auto MBID{ track->getTrackMBID() }) - additionalInfo["track_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} }; + additionalInfo["track_mbid"] = Wt::Json::Value{ std::string{ MBID->getAsString() } }; if (auto MBID{ track->getRecordingMBID() }) - additionalInfo["recording_mbid"] = Wt::Json::Value{ std::string {MBID->getAsString()} }; + additionalInfo["recording_mbid"] = Wt::Json::Value{ std::string{ MBID->getAsString() } }; if (const std::optional trackNumber{ track->getTrackNumber() }) additionalInfo["tracknumber"] = Wt::Json::Value{ static_cast(*trackNumber) }; @@ -99,7 +99,7 @@ namespace lms::scrobbling::listenBrainz trackMetadata["artist_name"] = Wt::Json::Value{ std::string{ track->getArtistDisplayName() } }; trackMetadata["track_name"] = Wt::Json::Value{ track->getName() }; if (track->getRelease()) - trackMetadata["release_name"] = Wt::Json::Value{ std::string {track->getRelease()->getName()} }; + trackMetadata["release_name"] = Wt::Json::Value{ std::string{ track->getRelease()->getName() } }; Wt::Json::Object payload; payload["track_metadata"] = std::move(trackMetadata); @@ -118,7 +118,7 @@ namespace lms::scrobbling::listenBrainz return res; Wt::Json::Object root; - root["listen_type"] = Wt::Json::Value{ std::string {listenType} }; + root["listen_type"] = Wt::Json::Value{ std::string{ listenType } }; root["payload"] = Wt::Json::Array{ std::move(*payload) }; res = Wt::Json::serialize(root); @@ -207,7 +207,7 @@ namespace lms::scrobbling::listenBrainz LOG(DEBUG, "No match for listen '" << listen << "'"); return {}; } - } + } // namespace ListensSynchronizer::ListensSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client) : _ioContext{ ioContext } @@ -244,18 +244,16 @@ namespace lms::scrobbling::listenBrainz saveListen(timedListen, db::SyncState::PendingAdd); request.priority = core::http::ClientRequestParameters::Priority::Normal; - request.onSuccessFunc = [this, timedListen](std::string_view) - { - _strand.dispatch([this, timedListen] - { - if (saveListen(timedListen, db::SyncState::Synchronized)) - { - UserContext& context{ getUserContext(timedListen.userId) }; - if (context.listenCount) - (*context.listenCount)++; - } - }); - }; + request.onSuccessFunc = [this, timedListen](std::string_view) { + _strand.dispatch([this, timedListen] { + if (saveListen(timedListen, db::SyncState::Synchronized)) + { + UserContext& context{ getUserContext(timedListen.userId) }; + if (context.listenCount) + (*context.listenCount)++; + } + }); + }; // on failure, this listen will be sent during the next sync } else @@ -369,10 +367,9 @@ namespace lms::scrobbling::listenBrainz bool ListensSynchronizer::isSyncing() const { - return std::any_of(std::cbegin(_userContexts), std::cend(_userContexts), [](const auto& contextEntry) - { - return contextEntry.second.syncing; - }); + return std::any_of(std::cbegin(_userContexts), std::cend(_userContexts), [](const auto& contextEntry) { + return contextEntry.second.syncing; + }); } void ListensSynchronizer::scheduleSync(std::chrono::seconds fromNow) @@ -382,20 +379,19 @@ namespace lms::scrobbling::listenBrainz LOG(DEBUG, "Scheduled sync in " << fromNow.count() << " seconds..."); _syncTimer.expires_after(fromNow); - _syncTimer.async_wait(boost::asio::bind_executor(_strand, [this](const boost::system::error_code& ec) + _syncTimer.async_wait(boost::asio::bind_executor(_strand, [this](const boost::system::error_code& ec) { + if (ec == boost::asio::error::operation_aborted) { - if (ec == boost::asio::error::operation_aborted) - { - LOG(DEBUG, "getListens aborted"); - return; - } - else if (ec) - { - throw Exception{ "GetListens timer failure: " + std::string {ec.message()} }; - } + LOG(DEBUG, "getListens aborted"); + return; + } + else if (ec) + { + throw Exception{ "GetListens timer failure: " + std::string{ ec.message() } }; + } - startSync(); - })); + startSync(); + })); } void ListensSynchronizer::startSync() @@ -434,14 +430,13 @@ namespace lms::scrobbling::listenBrainz void ListensSynchronizer::onSyncEnded(UserContext& context) { - _strand.dispatch([this, &context] - { - LOG(INFO, "Sync done for user '" << context.listenBrainzUserName << "', fetched: " << context.fetchedListenCount << ", matched: " << context.matchedListenCount << ", imported: " << context.importedListenCount); - context.syncing = false; + _strand.dispatch([this, &context] { + LOG(INFO, "Sync done for user '" << context.listenBrainzUserName << "', fetched: " << context.fetchedListenCount << ", matched: " << context.matchedListenCount << ", imported: " << context.importedListenCount); + context.syncing = false; - if (!isSyncing()) - scheduleSync(_syncListensPeriod); - }); + if (!isSyncing()) + scheduleSync(_syncListensPeriod); + }); } void ListensSynchronizer::enqueValidateToken(UserContext& context) @@ -458,21 +453,19 @@ namespace lms::scrobbling::listenBrainz core::http::ClientGETRequestParameters request; request.priority = core::http::ClientRequestParameters::Priority::Low; request.relativeUrl = "/1/validate-token"; - request.headers = { {"Authorization", "Token " + std::string {listenBrainzToken->getAsString()}} }; - request.onSuccessFunc = [this, &context](std::string_view msgBody) - { - context.listenBrainzUserName = utils::parseValidateToken(msgBody); - if (context.listenBrainzUserName.empty()) - { - onSyncEnded(context); - return; - } - enqueGetListenCount(context); - }; - request.onFailureFunc = [this, &context] + request.headers = { { "Authorization", "Token " + std::string{ listenBrainzToken->getAsString() } } }; + request.onSuccessFunc = [this, &context](std::string_view msgBody) { + context.listenBrainzUserName = utils::parseValidateToken(msgBody); + if (context.listenBrainzUserName.empty()) { onSyncEnded(context); - }; + return; + } + enqueGetListenCount(context); + }; + request.onFailureFunc = [this, &context] { + onSyncEnded(context); + }; _client.sendGETRequest(std::move(request)); } @@ -484,31 +477,28 @@ namespace lms::scrobbling::listenBrainz core::http::ClientGETRequestParameters request; request.relativeUrl = "/1/user/" + std::string{ context.listenBrainzUserName } + "/listen-count"; request.priority = core::http::ClientRequestParameters::Priority::Low; - request.onSuccessFunc = [this, &context](std::string_view msgBody) - { - const auto listenCount{ parseListenCount(msgBody) }; - _strand.dispatch([this, listenCount, &context] - { - if (listenCount) - LOG(DEBUG, "Listen count for listenbrainz user '" << context.listenBrainzUserName << "' = " << *listenCount); + request.onSuccessFunc = [this, &context](std::string_view msgBody) { + const auto listenCount{ parseListenCount(msgBody) }; + _strand.dispatch([this, listenCount, &context] { + if (listenCount) + LOG(DEBUG, "Listen count for listenbrainz user '" << context.listenBrainzUserName << "' = " << *listenCount); - bool needSync{ listenCount && (!context.listenCount || *context.listenCount != *listenCount) }; - context.listenCount = listenCount; + bool needSync{ listenCount && (!context.listenCount || *context.listenCount != *listenCount) }; + context.listenCount = listenCount; - if (!needSync) - { - onSyncEnded(context); - return; - } + if (!needSync) + { + onSyncEnded(context); + return; + } - context.maxDateTime = Wt::WDateTime::currentDateTime(); - enqueGetListens(context); - }); - }; - request.onFailureFunc = [this, &context] - { - onSyncEnded(context); - }; + context.maxDateTime = Wt::WDateTime::currentDateTime(); + enqueGetListens(context); + }); + }; + request.onFailureFunc = [this, &context] { + onSyncEnded(context); + }; _client.sendGETRequest(std::move(request)); } @@ -520,21 +510,19 @@ namespace lms::scrobbling::listenBrainz core::http::ClientGETRequestParameters request; request.relativeUrl = "/1/user/" + context.listenBrainzUserName + "/listens?max_ts=" + std::to_string(context.maxDateTime.toTime_t()); request.priority = core::http::ClientRequestParameters::Priority::Low; - request.onSuccessFunc = [this, &context](std::string_view msgBody) - { - processGetListensResponse(msgBody, context); - if (context.fetchedListenCount >= _maxSyncListenCount || !context.maxDateTime.isValid()) - { - onSyncEnded(context); - return; - } - - enqueGetListens(context); - }; - request.onFailureFunc = [this, &context] + request.onSuccessFunc = [this, &context](std::string_view msgBody) { + processGetListensResponse(msgBody, context); + if (context.fetchedListenCount >= _maxSyncListenCount || !context.maxDateTime.isValid()) { onSyncEnded(context); - }; + return; + } + + enqueGetListens(context); + }; + request.onFailureFunc = [this, &context] { + onSyncEnded(context); + }; _client.sendGETRequest(std::move(request)); } @@ -563,7 +551,7 @@ namespace lms::scrobbling::listenBrainz { context.matchedListenCount++; - const scrobbling::TimedListen listen{ {context.userId, trackId}, parsedListen.listenedAt }; + const scrobbling::TimedListen listen{ { context.userId, trackId }, parsedListen.listenedAt }; if (saveListen(listen, db::SyncState::Synchronized)) context.importedListenCount++; } diff --git a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.hpp b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.hpp index afdcdac69..dd851e450 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/ListensSynchronizer.hpp @@ -21,6 +21,7 @@ #include #include + #include #include #include @@ -32,74 +33,74 @@ namespace lms { - namespace core::http - { - class IClient; - } - namespace db - { - class Db; - } -} + namespace core::http + { + class IClient; + } + namespace db + { + class Db; + } +} // namespace lms namespace lms::scrobbling::listenBrainz { - class ListensSynchronizer - { - public: - ListensSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client); - - void enqueListen(const TimedListen& listen); - void enqueListenNow(const Listen& listen); - - private: - void enqueListen(const Listen& listen, const Wt::WDateTime& timePoint); - bool saveListen(const TimedListen& listen, db::SyncState scrobblinState); - - void enquePendingListens(); - - struct UserContext - { - UserContext(db::UserId id) : userId {id} {} - - UserContext(const UserContext&) = delete; - UserContext(UserContext&&) = delete; - UserContext& operator=(const UserContext&) = delete; - UserContext& operator=(UserContext&&) = delete; - - const db::UserId userId; - bool syncing {}; - std::optional listenCount {}; - - // resetted at each sync - std::string listenBrainzUserName; // need to be resolved first - Wt::WDateTime maxDateTime; - std::size_t fetchedListenCount{}; - std::size_t matchedListenCount{}; - std::size_t importedListenCount{}; - }; - - UserContext& getUserContext(db::UserId userId); - bool isSyncing() const; - void scheduleSync(std::chrono::seconds fromNow); - void startSync(); - void startSync(UserContext& context); - void onSyncEnded(UserContext& context); - void enqueValidateToken(UserContext& context); - void enqueGetListenCount(UserContext& context); - void enqueGetListens(UserContext& context); - void processGetListensResponse(std::string_view body, UserContext& context); - - boost::asio::io_context& _ioContext; - boost::asio::io_context::strand _strand {_ioContext}; - db::Db& _db; - boost::asio::steady_timer _syncTimer {_ioContext}; - core::http::IClient& _client; - - std::unordered_map _userContexts; - - const std::size_t _maxSyncListenCount; - const std::chrono::hours _syncListensPeriod; - }; -} // scrobbling::ListenBrainz - + class ListensSynchronizer + { + public: + ListensSynchronizer(boost::asio::io_context& ioContext, db::Db& db, core::http::IClient& client); + + void enqueListen(const TimedListen& listen); + void enqueListenNow(const Listen& listen); + + private: + void enqueListen(const Listen& listen, const Wt::WDateTime& timePoint); + bool saveListen(const TimedListen& listen, db::SyncState scrobblinState); + + void enquePendingListens(); + + struct UserContext + { + UserContext(db::UserId id) + : userId{ id } {} + + UserContext(const UserContext&) = delete; + UserContext(UserContext&&) = delete; + UserContext& operator=(const UserContext&) = delete; + UserContext& operator=(UserContext&&) = delete; + + const db::UserId userId; + bool syncing{}; + std::optional listenCount{}; + + // resetted at each sync + std::string listenBrainzUserName; // need to be resolved first + Wt::WDateTime maxDateTime; + std::size_t fetchedListenCount{}; + std::size_t matchedListenCount{}; + std::size_t importedListenCount{}; + }; + + UserContext& getUserContext(db::UserId userId); + bool isSyncing() const; + void scheduleSync(std::chrono::seconds fromNow); + void startSync(); + void startSync(UserContext& context); + void onSyncEnded(UserContext& context); + void enqueValidateToken(UserContext& context); + void enqueGetListenCount(UserContext& context); + void enqueGetListens(UserContext& context); + void processGetListensResponse(std::string_view body, UserContext& context); + + boost::asio::io_context& _ioContext; + boost::asio::io_context::strand _strand{ _ioContext }; + db::Db& _db; + boost::asio::steady_timer _syncTimer{ _ioContext }; + core::http::IClient& _client; + + std::unordered_map _userContexts; + + const std::size_t _maxSyncListenCount; + const std::chrono::hours _syncListensPeriod; + }; +} // namespace lms::scrobbling::listenBrainz diff --git a/src/libs/services/scrobbling/impl/listenbrainz/Utils.cpp b/src/libs/services/scrobbling/impl/listenbrainz/Utils.cpp index 118ce4fd7..3c0d47bf3 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/Utils.cpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/Utils.cpp @@ -59,4 +59,4 @@ namespace lms::scrobbling::listenBrainz::utils listenBrainzUserName = root.get("user_name").orIfNull(""); return listenBrainzUserName; } -} +} // namespace lms::scrobbling::listenBrainz::utils diff --git a/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp b/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp index ab8204db9..0e3801c8e 100644 --- a/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp +++ b/src/libs/services/scrobbling/impl/listenbrainz/Utils.hpp @@ -19,11 +19,11 @@ #pragma once -#include "database/UserId.hpp" #include "core/ILogger.hpp" #include "core/UUID.hpp" +#include "database/UserId.hpp" -#define LOG(sev, message) LMS_LOG(SCROBBLING, sev, "[listenbrainz] " << message) +#define LOG(sev, message) LMS_LOG(SCROBBLING, sev, "[listenbrainz] " << message) namespace lms::db { @@ -32,6 +32,6 @@ namespace lms::db namespace lms::scrobbling::listenBrainz::utils { - std::optional getListenBrainzToken(db::Session& session, db::UserId userId); + std::optional getListenBrainzToken(db::Session& session, db::UserId userId); std::string parseValidateToken(std::string_view msgBody); -} +} // namespace lms::scrobbling::listenBrainz::utils diff --git a/src/libs/services/scrobbling/include/services/scrobbling/Exception.hpp b/src/libs/services/scrobbling/include/services/scrobbling/Exception.hpp index 85c72db38..944c01023 100644 --- a/src/libs/services/scrobbling/include/services/scrobbling/Exception.hpp +++ b/src/libs/services/scrobbling/include/services/scrobbling/Exception.hpp @@ -28,4 +28,4 @@ namespace lms::scrobbling public: using LmsException::LmsException; }; -} +} // namespace lms::scrobbling diff --git a/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp b/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp index 7f04ca3e6..a0a26008c 100644 --- a/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp +++ b/src/libs/services/scrobbling/include/services/scrobbling/IScrobblingService.hpp @@ -23,16 +23,17 @@ #include #include #include -#include + #include +#include -#include "services/scrobbling/Listen.hpp" #include "database/ArtistId.hpp" #include "database/ClusterId.hpp" #include "database/MediaLibraryId.hpp" #include "database/ReleaseId.hpp" #include "database/TrackId.hpp" #include "database/Types.hpp" +#include "services/scrobbling/Listen.hpp" namespace lms::db { @@ -59,29 +60,61 @@ namespace lms::scrobbling struct FindParameters { - db::UserId user; - std::vector clusters; // if non empty, at least one artist that belongs to these clusters - std::vector keywords; // if non empty, name must match all of these keywords - std::optional range; - db::MediaLibraryId library; // if set, match this library - db::ArtistId artist; // if set, match this artist - - FindParameters& setUser(const db::UserId _user) { user = _user; return *this; } - FindParameters& setClusters(std::span _clusters) { clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); return *this; } - FindParameters& setKeywords(const std::vector& _keywords) { keywords = _keywords; return *this; } - FindParameters& setRange(std::optional _range) { range = _range; return *this; } - FindParameters& setMediaLibrary(db::MediaLibraryId _library) { library = _library; return *this; } - FindParameters& setArtist(db::ArtistId _artist) { artist = _artist; return *this; } + db::UserId user; + std::vector clusters; // if non empty, at least one artist that belongs to these clusters + std::vector keywords; // if non empty, name must match all of these keywords + std::optional range; + db::MediaLibraryId library; // if set, match this library + db::ArtistId artist; // if set, match this artist + + FindParameters& setUser(const db::UserId _user) + { + user = _user; + return *this; + } + FindParameters& setClusters(std::span _clusters) + { + clusters.assign(std::cbegin(_clusters), std::cend(_clusters)); + return *this; + } + FindParameters& setKeywords(const std::vector& _keywords) + { + keywords = _keywords; + return *this; + } + FindParameters& setRange(std::optional _range) + { + range = _range; + return *this; + } + FindParameters& setMediaLibrary(db::MediaLibraryId _library) + { + library = _library; + return *this; + } + FindParameters& setArtist(db::ArtistId _artist) + { + artist = _artist; + return *this; + } }; // Artists struct ArtistFindParameters : public FindParameters { - std::optional linkType; // if set, only artists that have produced at least one track with this link type - db::ArtistSortMethod sortMethod{ db::ArtistSortMethod::None }; - - ArtistFindParameters& setLinkType(std::optional _linkType) { linkType = _linkType; return *this; } - ArtistFindParameters& setSortMethod(db::ArtistSortMethod _sortMethod) { sortMethod = _sortMethod; return *this; } + std::optional linkType; // if set, only artists that have produced at least one track with this link type + db::ArtistSortMethod sortMethod{ db::ArtistSortMethod::None }; + + ArtistFindParameters& setLinkType(std::optional _linkType) + { + linkType = _linkType; + return *this; + } + ArtistFindParameters& setSortMethod(db::ArtistSortMethod _sortMethod) + { + sortMethod = _sortMethod; + return *this; + } }; // From most recent to oldest @@ -102,4 +135,4 @@ namespace lms::scrobbling }; std::unique_ptr createScrobblingService(boost::asio::io_service& ioService, db::Db& db); -} // ns Scrobbling +} // namespace lms::scrobbling diff --git a/src/libs/services/scrobbling/include/services/scrobbling/Listen.hpp b/src/libs/services/scrobbling/include/services/scrobbling/Listen.hpp index 499522bad..7491bd04d 100644 --- a/src/libs/services/scrobbling/include/services/scrobbling/Listen.hpp +++ b/src/libs/services/scrobbling/include/services/scrobbling/Listen.hpp @@ -36,5 +36,4 @@ namespace lms::scrobbling { Wt::WDateTime listenedAt; }; -} // ns Scrobbling - +} // namespace lms::scrobbling diff --git a/src/libs/services/scrobbling/test/Listenbrainz.cpp b/src/libs/services/scrobbling/test/Listenbrainz.cpp index 86ea18406..3b585d0d0 100644 --- a/src/libs/services/scrobbling/test/Listenbrainz.cpp +++ b/src/libs/services/scrobbling/test/Listenbrainz.cpp @@ -25,7 +25,7 @@ namespace lms::scrobbling::listenBrainz::tests { TEST(Listenbrainz, parseListens_empty) { - ListensParser::Result result {ListensParser::parse("")}; + ListensParser::Result result{ ListensParser::parse("") }; EXPECT_EQ(result.listenCount, 0); EXPECT_EQ(result.listens.size(), 0); @@ -36,7 +36,7 @@ namespace lms::scrobbling::listenBrainz::tests TEST(Listenbrainz, parseListens_single_missingMBID) { - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1663159479,"listens":[{"inserted_at":1650541124,"listened_at":1650541124,"recording_msid":"0e1418e3-b485-413a-84af-6316312cb116","track_metadata":{"additional_info":{"artist_msid":"ab5b27ad-e579-441c-ac60-d5dd9975c044","listening_from":"LMS","recording_msid":"0e1418e3-b485-413a-84af-6316312cb116","release_msid":"3f22f274-a9ee-4cb2-8dd1-f3bd18407099","tracknumber":8},"artist_name":"Broke For Free","release_name":"YEKOMS","track_name":"U2B"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; + ListensParser::Result result{ ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1663159479,"listens":[{"inserted_at":1650541124,"listened_at":1650541124,"recording_msid":"0e1418e3-b485-413a-84af-6316312cb116","track_metadata":{"additional_info":{"artist_msid":"ab5b27ad-e579-441c-ac60-d5dd9975c044","listening_from":"LMS","recording_msid":"0e1418e3-b485-413a-84af-6316312cb116","release_msid":"3f22f274-a9ee-4cb2-8dd1-f3bd18407099","tracknumber":8},"artist_name":"Broke For Free","release_name":"YEKOMS","track_name":"U2B"},"user_name":"epoupon"}],"user_id":"epoupon"}})") }; EXPECT_EQ(result.listenCount, 1); ASSERT_EQ(result.listens.size(), 1); EXPECT_EQ(result.listens[0].trackName, "U2B"); @@ -53,7 +53,7 @@ namespace lms::scrobbling::listenBrainz::tests TEST(Listenbrainz, parseListens_tworesults) { - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":2,"latest_listen_ts":1664028167,"listens":[{"inserted_at":1664028167,"listened_at":1664028167,"recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","track_metadata":{"additional_info":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc","release_msid":null,"track_mbid":"5427a943-a096-4d0b-8b9a-53aca9ed61ac","tracknumber":5},"artist_name":"Broke For Free","mbid_mapping":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc"},"release_name":"Petal","track_name":"Juparo"},"user_name":"epoupon"},{"inserted_at":1664027919,"listened_at":1664027918,"recording_msid":"fe5abc47-89cd-4235-80b5-00f47cecbe01","track_metadata":{"additional_info":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"d89d042c-8cc1-4526-9080-5bab728ee15f","recording_msid":"fe5abc47-89cd-4235-80b5-00f47cecbe01","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc","release_msid":null,"track_mbid":"9f33a17f-e33e-492f-85a4-7b2e9e09613e","tracknumber":4},"artist_name":"Broke For Free","mbid_mapping":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"recording_mbid":"d89d042c-8cc1-4526-9080-5bab728ee15f","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc"},"release_name":"Petal","track_name":"Melt"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; + ListensParser::Result result{ ListensParser::parse(R"({"payload":{"count":2,"latest_listen_ts":1664028167,"listens":[{"inserted_at":1664028167,"listened_at":1664028167,"recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","track_metadata":{"additional_info":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc","release_msid":null,"track_mbid":"5427a943-a096-4d0b-8b9a-53aca9ed61ac","tracknumber":5},"artist_name":"Broke For Free","mbid_mapping":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc"},"release_name":"Petal","track_name":"Juparo"},"user_name":"epoupon"},{"inserted_at":1664027919,"listened_at":1664027918,"recording_msid":"fe5abc47-89cd-4235-80b5-00f47cecbe01","track_metadata":{"additional_info":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"d89d042c-8cc1-4526-9080-5bab728ee15f","recording_msid":"fe5abc47-89cd-4235-80b5-00f47cecbe01","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc","release_msid":null,"track_mbid":"9f33a17f-e33e-492f-85a4-7b2e9e09613e","tracknumber":4},"artist_name":"Broke For Free","mbid_mapping":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"recording_mbid":"d89d042c-8cc1-4526-9080-5bab728ee15f","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc"},"release_name":"Petal","track_name":"Melt"},"user_name":"epoupon"}],"user_id":"epoupon"}})") }; EXPECT_EQ(result.listenCount, 2); ASSERT_EQ(result.listens.size(), 2); EXPECT_EQ(result.listens[0].trackName, "Juparo"); @@ -77,7 +77,7 @@ namespace lms::scrobbling::listenBrainz::tests TEST(Listenbrainz, parseListens_tworesults_butinvalid) { - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":2,"latest_listen_ts":1664028167,"listens":[{"inserted_at":1664028167,"listened_at":1664028167,"recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","track_metadata":{"additional_info":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc","release_msid":null,"track_mbid":"5427a943-a096-4d0b-8b9a-53aca9ed61ac","tracknumber":5},"artist_name":"Broke For Free","mbid_mapping":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc"},"release_name":"Petal","track_name":"Juparo"},"user_name":"epoupon"},{"inserted_at":1664027919}],"user_id":"epoupon"}})")}; + ListensParser::Result result{ ListensParser::parse(R"({"payload":{"count":2,"latest_listen_ts":1664028167,"listens":[{"inserted_at":1664028167,"listened_at":1664028167,"recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","track_metadata":{"additional_info":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","recording_msid":"29c11137-e40b-4875-9ec0-9a20a4bdc2d3","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc","release_msid":null,"track_mbid":"5427a943-a096-4d0b-8b9a-53aca9ed61ac","tracknumber":5},"artist_name":"Broke For Free","mbid_mapping":{"artist_mbids":["069a1c1f-14eb-4d36-b0a0-77dffbd67713"],"recording_mbid":"46ae879f-2dbe-46d3-99ad-05c116f97a30","release_mbid":"44915500-fbb9-4060-98ce-59a57a429edc"},"release_name":"Petal","track_name":"Juparo"},"user_name":"epoupon"},{"inserted_at":1664027919}],"user_id":"epoupon"}})") }; EXPECT_EQ(result.listenCount, 2); ASSERT_EQ(result.listens.size(), 1); EXPECT_EQ(result.listens[0].trackName, "Juparo"); @@ -92,7 +92,7 @@ namespace lms::scrobbling::listenBrainz::tests TEST(Listenbrainz, parseListens_entryNotFromLms) { - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1664105730,"listens":[{"inserted_at":1664105730,"listened_at":1664105730,"recording_msid":"6a11ff4d-0623-4b2e-98e0-0e172f1f28d7","track_metadata":{"additional_info":{"artist_msid":null,"media_player":"BrainzPlayer","music_service":"youtube.com","music_service_name":"youtube","origin_url":"https://www.youtube.com/watch?v=EBP5vL3YWTI","recording_msid":"6a11ff4d-0623-4b2e-98e0-0e172f1f28d7","release_msid":null,"submission_client":"BrainzPlayer"},"artist_name":"Dio","brainzplayer_metadata":{"track_name":"Dio - Breathless"},"mbid_mapping":{"artist_mbids":["c55193fb-f5d2-4839-a263-4c044fca1456"],"recording_mbid":"92929526-21d7-4e75-b759-1072951664c4","release_mbid":"16cbf9ba-2e38-3893-9f23-f8567e26c18b"},"release_name":"The Last in Line","track_name":"Breathless"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; + ListensParser::Result result{ ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1664105730,"listens":[{"inserted_at":1664105730,"listened_at":1664105730,"recording_msid":"6a11ff4d-0623-4b2e-98e0-0e172f1f28d7","track_metadata":{"additional_info":{"artist_msid":null,"media_player":"BrainzPlayer","music_service":"youtube.com","music_service_name":"youtube","origin_url":"https://www.youtube.com/watch?v=EBP5vL3YWTI","recording_msid":"6a11ff4d-0623-4b2e-98e0-0e172f1f28d7","release_msid":null,"submission_client":"BrainzPlayer"},"artist_name":"Dio","brainzplayer_metadata":{"track_name":"Dio - Breathless"},"mbid_mapping":{"artist_mbids":["c55193fb-f5d2-4839-a263-4c044fca1456"],"recording_mbid":"92929526-21d7-4e75-b759-1072951664c4","release_mbid":"16cbf9ba-2e38-3893-9f23-f8567e26c18b"},"release_name":"The Last in Line","track_name":"Breathless"},"user_name":"epoupon"}],"user_id":"epoupon"}})") }; EXPECT_EQ(result.listenCount, 1); ASSERT_EQ(result.listens.size(), 1); EXPECT_EQ(result.listens[0].trackName, "Breathless"); @@ -104,7 +104,7 @@ namespace lms::scrobbling::listenBrainz::tests TEST(Listenbrainz, parseListens_multiArtists) { - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1664106427,"listens":[{"inserted_at":1664106427,"listened_at":1664106427,"recording_msid":"b1dad0df-329b-443d-bacf-cdbebdddbfd0","track_metadata":{"additional_info":{"artist_mbids":["04ce0202-043d-4cbe-8f09-8abaf3b80c71","79311c51-9748-49df-baa1-d925fd29f4e8"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"a5f380bc-0a85-4a9f-88db-d41bb9aa2a4b","recording_msid":"b1dad0df-329b-443d-bacf-cdbebdddbfd0","release_mbid":"147b4669-3d20-43f8-89c0-ba1da8b87dd3","release_msid":null,"track_mbid":"a20dd067-29b6-3d38-a0be-eeb86b4671c1","tracknumber":1},"artist_name":"Gloom","release_name":"Demovibes 9: Party, people going","track_name":"Stargazer of Disgrace"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; + ListensParser::Result result{ ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1664106427,"listens":[{"inserted_at":1664106427,"listened_at":1664106427,"recording_msid":"b1dad0df-329b-443d-bacf-cdbebdddbfd0","track_metadata":{"additional_info":{"artist_mbids":["04ce0202-043d-4cbe-8f09-8abaf3b80c71","79311c51-9748-49df-baa1-d925fd29f4e8"],"artist_msid":null,"listening_from":"LMS","recording_mbid":"a5f380bc-0a85-4a9f-88db-d41bb9aa2a4b","recording_msid":"b1dad0df-329b-443d-bacf-cdbebdddbfd0","release_mbid":"147b4669-3d20-43f8-89c0-ba1da8b87dd3","release_msid":null,"track_mbid":"a20dd067-29b6-3d38-a0be-eeb86b4671c1","tracknumber":1},"artist_name":"Gloom","release_name":"Demovibes 9: Party, people going","track_name":"Stargazer of Disgrace"},"user_name":"epoupon"}],"user_id":"epoupon"}})") }; EXPECT_EQ(result.listenCount, 1); ASSERT_EQ(result.listens.size(), 1); EXPECT_EQ(result.listens[0].trackNumber, 1); @@ -112,7 +112,7 @@ namespace lms::scrobbling::listenBrainz::tests TEST(Listenbrainz, parseListens_minPayload) { - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1664106427,"listens":[{"track_metadata":{"artist_name":"Gloom","track_name":"Stargazer of Disgrace"},"user_name":"epoupon"}],"user_id":"epoupon"}})")}; + ListensParser::Result result{ ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1664106427,"listens":[{"track_metadata":{"artist_name":"Gloom","track_name":"Stargazer of Disgrace"},"user_name":"epoupon"}],"user_id":"epoupon"}})") }; EXPECT_EQ(result.listenCount, 1); ASSERT_EQ(result.listens.size(), 1); EXPECT_FALSE(result.listens[0].listenedAt.isValid()); @@ -126,10 +126,10 @@ namespace lms::scrobbling::listenBrainz::tests TEST(Listenbrainz, parseListens_stringInsteadOfInt) { - ListensParser::Result result {ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1683298766,"listens":[{"inserted_at":1683299849,"listened_at":1683298766,"recording_msid":"3841ad1c-f674-491c-b855-f10a82ea38fe","track_metadata":{"additional_info":{"albumartist":"Various Artists","artist_mbids":["80ccfede-c258-4575-a7ad-c982e9932e0f"],"date":"2002-06-10","discnumber":"1","duration":190,"isrc":"USAM10200204","media_player":"foobar2000","media_player_version":"1.6.16","recording_msid":"3841ad1c-f674-491c-b855-f10a82ea38fe","release_group_mbid":"25f44677-2ecd-33fb-8454-9a9776496e3c","release_mbid":"bddad4c6-e9e1-4218-b9d2-b02a5a778a7d","submission_client":"foobar2000","submission_client_version":"1.3.2","totaldiscs":"1","totaltracks":"18","track_mbid":"08b1a5c1-c845-3d3b-9aa4-560a1af7f28c","tracknumber":"4"},"artist_name":"Sheryl Crow","mbid_mapping":{"artist_mbids":["80ccfede-c258-4575-a7ad-c982e9932e0f"],"artists":[{"artist_credit_name":"Sheryl Crow","artist_mbid":"80ccfede-c258-4575-a7ad-c982e9932e0f","join_phrase":""}],"caa_id":22039936498,"caa_release_mbid":"f10b4b40-4db1-4b9a-a223-06915fa06b3c","recording_mbid":"7ca21c82-1345-44df-b7ac-035a6e05d4ed","recording_name":"Strong Enough","release_mbid":"f10b4b40-4db1-4b9a-a223-06915fa06b3c"},"release_name":"The Very Best of MTV Unplugged","track_name":"Strong Enough"},"user_name":"awesomeuser"}],"user_id":"awesomeuser"}})")}; + ListensParser::Result result{ ListensParser::parse(R"({"payload":{"count":1,"latest_listen_ts":1683298766,"listens":[{"inserted_at":1683299849,"listened_at":1683298766,"recording_msid":"3841ad1c-f674-491c-b855-f10a82ea38fe","track_metadata":{"additional_info":{"albumartist":"Various Artists","artist_mbids":["80ccfede-c258-4575-a7ad-c982e9932e0f"],"date":"2002-06-10","discnumber":"1","duration":190,"isrc":"USAM10200204","media_player":"foobar2000","media_player_version":"1.6.16","recording_msid":"3841ad1c-f674-491c-b855-f10a82ea38fe","release_group_mbid":"25f44677-2ecd-33fb-8454-9a9776496e3c","release_mbid":"bddad4c6-e9e1-4218-b9d2-b02a5a778a7d","submission_client":"foobar2000","submission_client_version":"1.3.2","totaldiscs":"1","totaltracks":"18","track_mbid":"08b1a5c1-c845-3d3b-9aa4-560a1af7f28c","tracknumber":"4"},"artist_name":"Sheryl Crow","mbid_mapping":{"artist_mbids":["80ccfede-c258-4575-a7ad-c982e9932e0f"],"artists":[{"artist_credit_name":"Sheryl Crow","artist_mbid":"80ccfede-c258-4575-a7ad-c982e9932e0f","join_phrase":""}],"caa_id":22039936498,"caa_release_mbid":"f10b4b40-4db1-4b9a-a223-06915fa06b3c","recording_mbid":"7ca21c82-1345-44df-b7ac-035a6e05d4ed","recording_name":"Strong Enough","release_mbid":"f10b4b40-4db1-4b9a-a223-06915fa06b3c"},"release_name":"The Very Best of MTV Unplugged","track_name":"Strong Enough"},"user_name":"awesomeuser"}],"user_id":"awesomeuser"}})") }; EXPECT_EQ(result.listenCount, 1); ASSERT_EQ(result.listens.size(), 1); EXPECT_EQ(result.listens[0].trackNumber, 4); } -} \ No newline at end of file +} // namespace lms::scrobbling::listenBrainz::tests \ No newline at end of file diff --git a/src/libs/services/scrobbling/test/Scrobbling.cpp b/src/libs/services/scrobbling/test/Scrobbling.cpp index 5a4f64995..b918b0d44 100644 --- a/src/libs/services/scrobbling/test/Scrobbling.cpp +++ b/src/libs/services/scrobbling/test/Scrobbling.cpp @@ -27,9 +27,8 @@ int main(int argc, char** argv) { using namespace lms; // log to stdout - core::Service logger{ std::make_unique(std::cout, core::EnumSet {core::logging::Severity::FATAL, core::logging::Severity::ERROR}) }; + core::Service logger{ std::make_unique(std::cout, core::EnumSet{ core::logging::Severity::FATAL, core::logging::Severity::ERROR }) }; ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); } - diff --git a/src/libs/som/bench/SomBench.cpp b/src/libs/som/bench/SomBench.cpp index 23eb763f8..57181e742 100644 --- a/src/libs/som/bench/SomBench.cpp +++ b/src/libs/som/bench/SomBench.cpp @@ -18,6 +18,7 @@ */ #include + #include #include "som/Network.hpp" @@ -50,6 +51,6 @@ namespace lms::som // Register the benchmark with custom range BENCHMARK(BM_Matrix)->Arg(3)->Arg(6)->Arg(12)->Arg(24); -} +} // namespace lms::som BENCHMARK_MAIN(); diff --git a/src/libs/som/impl/DataNormalizer.cpp b/src/libs/som/impl/DataNormalizer.cpp index 85cc32b32..df07ac1fc 100644 --- a/src/libs/som/impl/DataNormalizer.cpp +++ b/src/libs/som/impl/DataNormalizer.cpp @@ -25,96 +25,85 @@ namespace lms::som { - -template -static -T -variance(const std::vector& vec) -{ - std::size_t size {vec.size()}; - - if (size == 1) - return T {}; - - const T mean {std::accumulate(vec.begin(), vec.end(), T{}) / size}; - - return std::accumulate(vec.begin(), vec.end(), T {}, - [mean, size] (T accumulator, const T& val) - { - return accumulator + ((val - mean) * (val - mean) / (size - 1)); - }); -} - -DataNormalizer::DataNormalizer(std::size_t inputDimCount) -: _inputDimCount{inputDimCount} -{ -} - -const DataNormalizer::MinMax& -DataNormalizer::getValue(std::size_t index) const -{ - return _minmax[index]; -} - -void -DataNormalizer::setValue(std::size_t index, const MinMax& minMax) -{ - _minmax[index] = minMax; -} - -void -DataNormalizer::computeNormalizationFactors(const std::vector& inputVectors) -{ - if (inputVectors.empty()) - throw Exception("Empty input vectors"); - - // For each dimension of the input, compute the min/max - _minmax.clear(); - _minmax.resize(_inputDimCount); - - for (std::size_t dimId {}; dimId < _inputDimCount; ++dimId) - { - std::vector values; - - for (const auto& inputVector: inputVectors) - { - checkSameDimensions(inputVector, _inputDimCount); - values.push_back(inputVector[dimId]); - } - - auto result {std::minmax_element(values.begin(), values.end())}; - _minmax[dimId] = {*result.first, *result.second}; - } -} - -InputVector::value_type -DataNormalizer::normalizeValue(InputVector::value_type value, std::size_t dimId) const -{ - // clamp - if (value > _minmax[dimId].max) - value = _minmax[dimId].max; - else if (value < _minmax[dimId].min) - value = _minmax[dimId].min; - - return (value - _minmax[dimId].min) / (_minmax[dimId].max - _minmax[dimId].min); -} - -void -DataNormalizer::normalizeData(InputVector& a) const -{ - checkSameDimensions(a, _inputDimCount); - - for (std::size_t dimId {}; dimId < _inputDimCount; ++dimId) - { - a[dimId] = normalizeValue(a[dimId], dimId); - } -} - -void -DataNormalizer::dump(std::ostream& os) const -{ - for (std::size_t i {}; i < _inputDimCount; ++i) - os << "(" << _minmax[i].min << ", " << _minmax[i].max << ")"; -} - + template + static T variance(const std::vector& vec) + { + std::size_t size{ vec.size() }; + + if (size == 1) + return T{}; + + const T mean{ std::accumulate(vec.begin(), vec.end(), T{}) / size }; + + return std::accumulate(vec.begin(), vec.end(), T{}, + [mean, size](T accumulator, const T& val) { + return accumulator + ((val - mean) * (val - mean) / (size - 1)); + }); + } + + DataNormalizer::DataNormalizer(std::size_t inputDimCount) + : _inputDimCount{ inputDimCount } + { + } + + const DataNormalizer::MinMax& DataNormalizer::getValue(std::size_t index) const + { + return _minmax[index]; + } + + void DataNormalizer::setValue(std::size_t index, const MinMax& minMax) + { + _minmax[index] = minMax; + } + + void DataNormalizer::computeNormalizationFactors(const std::vector& inputVectors) + { + if (inputVectors.empty()) + throw Exception("Empty input vectors"); + + // For each dimension of the input, compute the min/max + _minmax.clear(); + _minmax.resize(_inputDimCount); + + for (std::size_t dimId{}; dimId < _inputDimCount; ++dimId) + { + std::vector values; + + for (const auto& inputVector : inputVectors) + { + checkSameDimensions(inputVector, _inputDimCount); + values.push_back(inputVector[dimId]); + } + + auto result{ std::minmax_element(values.begin(), values.end()) }; + _minmax[dimId] = { *result.first, *result.second }; + } + } + + InputVector::value_type DataNormalizer::normalizeValue(InputVector::value_type value, std::size_t dimId) const + { + // clamp + if (value > _minmax[dimId].max) + value = _minmax[dimId].max; + else if (value < _minmax[dimId].min) + value = _minmax[dimId].min; + + return (value - _minmax[dimId].min) / (_minmax[dimId].max - _minmax[dimId].min); + } + + void DataNormalizer::normalizeData(InputVector& a) const + { + checkSameDimensions(a, _inputDimCount); + + for (std::size_t dimId{}; dimId < _inputDimCount; ++dimId) + { + a[dimId] = normalizeValue(a[dimId], dimId); + } + } + + void DataNormalizer::dump(std::ostream& os) const + { + for (std::size_t i{}; i < _inputDimCount; ++i) + os << "(" << _minmax[i].min << ", " << _minmax[i].max << ")"; + } } // namespace lms::som diff --git a/src/libs/som/impl/Network.cpp b/src/libs/som/impl/Network.cpp index 652263429..aa768108a 100644 --- a/src/libs/som/impl/Network.cpp +++ b/src/libs/som/impl/Network.cpp @@ -31,300 +31,270 @@ namespace lms::som { - -void -checkSameDimensions(const InputVector& a, const InputVector& b) -{ - if (!a.hasSameDimension(b)) - throw Exception("Bad data dimension count"); -} - -void -checkSameDimensions(const InputVector& a, std::size_t inputDimCount) -{ - if (a.getNbDimensions() != inputDimCount) - throw Exception("Bad data dimension count"); -} - -static LearningFactor -defaultLearningFactor(Network::CurrentIteration iteration) -{ - static const LearningFactor initialValue{1}; - - return initialValue * exp(-((iteration.idIteration + 1) / static_cast(iteration.iterationCount))); -} - -static InputVector::Distance -euclidianSquareDistance(const InputVector& a, const InputVector& b, const InputVector& weights) -{ - return a.computeEuclidianSquareDistance(b, weights); -} - -static -InputVector::value_type -sigmaFunc(Network::CurrentIteration iteration) -{ - constexpr InputVector::value_type sigma0 {1}; - - return sigma0 * std::exp(- ((iteration.idIteration + 1) / static_cast(iteration.iterationCount))); -} - -static -InputVector::value_type -defaultNeighbourhoodFunc(Norm norm, const Network::CurrentIteration& iteration) -{ - InputVector::value_type sigma {sigmaFunc(iteration)}; - - return exp(-norm / (2 * sigma * sigma)); -} - -Network::Network(Coordinate width, Coordinate height, std::size_t inputDimCount) -: -_inputDimCount {inputDimCount}, -_weights {inputDimCount, static_cast(1)}, -_refVectors {width, height, _inputDimCount}, -_distanceFunc {euclidianSquareDistance}, -_learningFactorFunc {defaultLearningFactor}, -_neighbourhoodFunc {defaultNeighbourhoodFunc} -{ - // init each vector with a random normalized value - for (Coordinate y {}; y < _refVectors.getHeight(); ++y) - { - for (Coordinate x {}; x < _refVectors.getWidth(); ++x) - { - for (InputVector::value_type& val : _refVectors.get({x,y})) - val = core::random::getRealRandom(0, 1); - } - } -} - -void -Network::setDataWeights(const InputVector& weights) -{ - checkSameDimensions(weights, _inputDimCount); - - _weights = weights; -} - -void -Network::setRefVector(const Position& position, const InputVector& data) -{ - checkSameDimensions(data, _inputDimCount); - - _refVectors[position] = data; -} - -InputVector::Distance -Network::getRefVectorsDistance(const Position& position1, const Position& position2) const -{ - return _distanceFunc(_refVectors.get(position1), _refVectors.get(position2), _weights); -} - -InputVector::Distance -Network::computeRefVectorsDistanceMean() const -{ - std::vector values; - values.reserve(2 * _refVectors.getHeight()*_refVectors.getWidth() - _refVectors.getWidth() - _refVectors.getHeight()); - for (Coordinate y {}; y < _refVectors.getHeight(); ++y) - { - for (Coordinate x {}; x < _refVectors.getWidth(); ++x) - { - if (x != _refVectors.getWidth() - 1) - values.emplace_back(getRefVectorsDistance( {x, y}, {x + 1, y})); - if (y != _refVectors.getHeight() - 1) - values.emplace_back(getRefVectorsDistance( {x, y}, {x, y + 1})); - } - } - - return std::accumulate(values.begin(), values.end(), 0.) / values.size(); -} - -double -Network::computeRefVectorsDistanceMedian() const -{ - std::vector values; - values.reserve(2*_refVectors.getHeight()*_refVectors.getWidth() - _refVectors.getWidth() - _refVectors.getHeight()); - for (Coordinate y {}; y < _refVectors.getHeight(); ++y) - { - for (Coordinate x {}; x < _refVectors.getWidth(); ++x) - { - if (x != _refVectors.getWidth() - 1) - values.emplace_back(getRefVectorsDistance( {x, y}, {x + 1, y})); - if (y != _refVectors.getHeight() - 1) - values.emplace_back(getRefVectorsDistance( {x, y}, {x, y + 1})); - } - } - - std::sort(values.begin(), values.end()); - - return values[values.size() > 1 ? values.size()/2 - 1 : 0]; -} - -void -Network::dump(std::ostream& os) const -{ - os << "Width: " << _refVectors.getWidth() << ", Height: " << _refVectors.getHeight() << std::endl;; - - for (Coordinate y {}; y < _refVectors.getHeight(); ++y) - { - for (Coordinate x {}; x < _refVectors.getWidth(); ++x) - { - os << _refVectors.get({x, y}) << " "; - } - - os << std::endl; - } - os << std::endl; -} - -Position -Network::getClosestRefVectorPosition(const InputVector& data) const -{ - return _refVectors.getPositionMinElement([&](const auto& a, const auto& b) - { - return (_distanceFunc(a, data, _weights) < _distanceFunc(b, data, _weights)); - }); -} - -std::optional -Network::getClosestRefVectorPosition(const InputVector& data, InputVector::Distance maxDistance) const -{ - std::optional position {getClosestRefVectorPosition(data)}; - - if (_distanceFunc(data, _refVectors.get(*position), _weights) > maxDistance) - position.reset(); - - return position; -} - -std::optional -Network::getClosestRefVectorPosition(const std::vector& refVectorsPosition, InputVector::Distance maxDistance) const -{ - std::unordered_set neighboursPosition; - for (const Position& refVectorPosition : refVectorsPosition) - { - if (refVectorPosition.y > 0) - neighboursPosition.insert({ refVectorPosition.x, refVectorPosition.y - 1 }); - if (refVectorPosition.y < _refVectors.getHeight() - 1) - neighboursPosition.insert({ refVectorPosition.x, refVectorPosition.y + 1 }); - if (refVectorPosition.x > 0) - neighboursPosition.insert({ refVectorPosition.x - 1, refVectorPosition.y }); - if (refVectorPosition.x < _refVectors.getWidth() - 1) - neighboursPosition.insert({ refVectorPosition.x + 1, refVectorPosition.y }); - } - - // remove position that are in the input position - for (const auto& refVectorPosition : refVectorsPosition) - neighboursPosition.erase(refVectorPosition); - - if (neighboursPosition.empty()) - return std::nullopt; - - // Now compute the distance for each neighbour - struct NeighbourInfo - { - Position position; - double distance; - }; - - std::vector neighboursInfo; - for (const Position& neighbourPosition : neighboursPosition) - { - auto min = std::min_element(refVectorsPosition.begin(), refVectorsPosition.end(), - [this, neighbourPosition](const auto& a, const auto& b) - { - return (this->getRefVectorsDistance(a, neighbourPosition) < this->getRefVectorsDistance(b, neighbourPosition)); - }); - - InputVector::Distance distance {getRefVectorsDistance(neighbourPosition, *min)}; - if (distance > maxDistance) - continue; - - neighboursInfo.emplace_back(NeighbourInfo {neighbourPosition, distance}); - } - - if (neighboursInfo.empty()) - return std::nullopt; - - auto min {std::min_element(std::cbegin(neighboursInfo), std::cend(neighboursInfo), - [&](const auto& a, const auto& b) - { - return a.distance < b.distance; - })}; - - - return min->position; -} - -static Norm -computePositionNorm(const Position& c1, const Position& c2) -{ - return std::sqrt((c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y)); -} - -void -Network::updateRefVectors(const Position& closestRefVectorPosition, const InputVector& input, LearningFactor learningFactor, const CurrentIteration& iteration) -{ - for (Coordinate y {}; y < _refVectors.getHeight(); ++y) - { - for (Coordinate x {}; x < _refVectors.getWidth(); ++x) - { - InputVector& refVector {_refVectors.get({x, y})}; - - const Norm norm {computePositionNorm({x, y}, closestRefVectorPosition)}; - - InputVector delta {input - refVector}; - delta *= (learningFactor * _neighbourhoodFunc(norm, iteration)); - - refVector += delta; - } - } -} - -void -Network::train(const std::vector& inputData, std::size_t nbIterations, ProgressCallback progressCallback, RequestStopCallback requestStopCallback) -{ - bool stopRequested {false}; - std::vector inputDataShuffled; - - inputDataShuffled.reserve(inputData.size()); - for (const auto& input : inputData) - inputDataShuffled.push_back(&input); - - for (std::size_t i {}; i < nbIterations; ++i) - { - CurrentIteration curIter {i, nbIterations}; - - if (progressCallback) - progressCallback(curIter); - - core::random::shuffleContainer(inputDataShuffled); - - const LearningFactor learningFactor {_learningFactorFunc(curIter)}; - - for (const InputVector* input : inputDataShuffled) - { - if (requestStopCallback) - stopRequested = requestStopCallback(); - - if (stopRequested) - return; - - updateRefVectors(getClosestRefVectorPosition(*input), *input, learningFactor, curIter); - } - - if (stopRequested) - return; - } -} - -const InputVector& -Network::getRefVector(const Position& position) const -{ - return _refVectors[position]; -} - - + void checkSameDimensions(const InputVector& a, const InputVector& b) + { + if (!a.hasSameDimension(b)) + throw Exception("Bad data dimension count"); + } + + void checkSameDimensions(const InputVector& a, std::size_t inputDimCount) + { + if (a.getNbDimensions() != inputDimCount) + throw Exception("Bad data dimension count"); + } + + static LearningFactor defaultLearningFactor(Network::CurrentIteration iteration) + { + static const LearningFactor initialValue{ 1 }; + + return initialValue * exp(-((iteration.idIteration + 1) / static_cast(iteration.iterationCount))); + } + + static InputVector::Distance euclidianSquareDistance(const InputVector& a, const InputVector& b, const InputVector& weights) + { + return a.computeEuclidianSquareDistance(b, weights); + } + + static InputVector::value_type sigmaFunc(Network::CurrentIteration iteration) + { + constexpr InputVector::value_type sigma0{ 1 }; + + return sigma0 * std::exp(-((iteration.idIteration + 1) / static_cast(iteration.iterationCount))); + } + + static InputVector::value_type defaultNeighbourhoodFunc(Norm norm, const Network::CurrentIteration& iteration) + { + InputVector::value_type sigma{ sigmaFunc(iteration) }; + + return exp(-norm / (2 * sigma * sigma)); + } + + Network::Network(Coordinate width, Coordinate height, std::size_t inputDimCount) + : _inputDimCount{ inputDimCount } + , _weights{ inputDimCount, static_cast(1) } + , _refVectors{ width, height, _inputDimCount } + , _distanceFunc{ euclidianSquareDistance } + , _learningFactorFunc{ defaultLearningFactor } + , _neighbourhoodFunc{ defaultNeighbourhoodFunc } + { + // init each vector with a random normalized value + for (Coordinate y{}; y < _refVectors.getHeight(); ++y) + { + for (Coordinate x{}; x < _refVectors.getWidth(); ++x) + { + for (InputVector::value_type& val : _refVectors.get({ x, y })) + val = core::random::getRealRandom(0, 1); + } + } + } + + void Network::setDataWeights(const InputVector& weights) + { + checkSameDimensions(weights, _inputDimCount); + + _weights = weights; + } + + void Network::setRefVector(const Position& position, const InputVector& data) + { + checkSameDimensions(data, _inputDimCount); + + _refVectors[position] = data; + } + + InputVector::Distance Network::getRefVectorsDistance(const Position& position1, const Position& position2) const + { + return _distanceFunc(_refVectors.get(position1), _refVectors.get(position2), _weights); + } + + InputVector::Distance Network::computeRefVectorsDistanceMean() const + { + std::vector values; + values.reserve(2 * _refVectors.getHeight() * _refVectors.getWidth() - _refVectors.getWidth() - _refVectors.getHeight()); + for (Coordinate y{}; y < _refVectors.getHeight(); ++y) + { + for (Coordinate x{}; x < _refVectors.getWidth(); ++x) + { + if (x != _refVectors.getWidth() - 1) + values.emplace_back(getRefVectorsDistance({ x, y }, { x + 1, y })); + if (y != _refVectors.getHeight() - 1) + values.emplace_back(getRefVectorsDistance({ x, y }, { x, y + 1 })); + } + } + + return std::accumulate(values.begin(), values.end(), 0.) / values.size(); + } + + double Network::computeRefVectorsDistanceMedian() const + { + std::vector values; + values.reserve(2 * _refVectors.getHeight() * _refVectors.getWidth() - _refVectors.getWidth() - _refVectors.getHeight()); + for (Coordinate y{}; y < _refVectors.getHeight(); ++y) + { + for (Coordinate x{}; x < _refVectors.getWidth(); ++x) + { + if (x != _refVectors.getWidth() - 1) + values.emplace_back(getRefVectorsDistance({ x, y }, { x + 1, y })); + if (y != _refVectors.getHeight() - 1) + values.emplace_back(getRefVectorsDistance({ x, y }, { x, y + 1 })); + } + } + + std::sort(values.begin(), values.end()); + + return values[values.size() > 1 ? values.size() / 2 - 1 : 0]; + } + + void Network::dump(std::ostream& os) const + { + os << "Width: " << _refVectors.getWidth() << ", Height: " << _refVectors.getHeight() << std::endl; + ; + + for (Coordinate y{}; y < _refVectors.getHeight(); ++y) + { + for (Coordinate x{}; x < _refVectors.getWidth(); ++x) + { + os << _refVectors.get({ x, y }) << " "; + } + + os << std::endl; + } + os << std::endl; + } + + Position Network::getClosestRefVectorPosition(const InputVector& data) const + { + return _refVectors.getPositionMinElement([&](const auto& a, const auto& b) { + return (_distanceFunc(a, data, _weights) < _distanceFunc(b, data, _weights)); + }); + } + + std::optional Network::getClosestRefVectorPosition(const InputVector& data, InputVector::Distance maxDistance) const + { + std::optional position{ getClosestRefVectorPosition(data) }; + + if (_distanceFunc(data, _refVectors.get(*position), _weights) > maxDistance) + position.reset(); + + return position; + } + + std::optional Network::getClosestRefVectorPosition(const std::vector& refVectorsPosition, InputVector::Distance maxDistance) const + { + std::unordered_set neighboursPosition; + for (const Position& refVectorPosition : refVectorsPosition) + { + if (refVectorPosition.y > 0) + neighboursPosition.insert({ refVectorPosition.x, refVectorPosition.y - 1 }); + if (refVectorPosition.y < _refVectors.getHeight() - 1) + neighboursPosition.insert({ refVectorPosition.x, refVectorPosition.y + 1 }); + if (refVectorPosition.x > 0) + neighboursPosition.insert({ refVectorPosition.x - 1, refVectorPosition.y }); + if (refVectorPosition.x < _refVectors.getWidth() - 1) + neighboursPosition.insert({ refVectorPosition.x + 1, refVectorPosition.y }); + } + + // remove position that are in the input position + for (const auto& refVectorPosition : refVectorsPosition) + neighboursPosition.erase(refVectorPosition); + + if (neighboursPosition.empty()) + return std::nullopt; + + // Now compute the distance for each neighbour + struct NeighbourInfo + { + Position position; + double distance; + }; + + std::vector neighboursInfo; + for (const Position& neighbourPosition : neighboursPosition) + { + auto min = std::min_element(refVectorsPosition.begin(), refVectorsPosition.end(), + [this, neighbourPosition](const auto& a, const auto& b) { + return (this->getRefVectorsDistance(a, neighbourPosition) < this->getRefVectorsDistance(b, neighbourPosition)); + }); + + InputVector::Distance distance{ getRefVectorsDistance(neighbourPosition, *min) }; + if (distance > maxDistance) + continue; + + neighboursInfo.emplace_back(NeighbourInfo{ neighbourPosition, distance }); + } + + if (neighboursInfo.empty()) + return std::nullopt; + + auto min{ std::min_element(std::cbegin(neighboursInfo), std::cend(neighboursInfo), + [&](const auto& a, const auto& b) { + return a.distance < b.distance; + }) }; + + return min->position; + } + + static Norm computePositionNorm(const Position& c1, const Position& c2) + { + return std::sqrt((c1.x - c2.x) * (c1.x - c2.x) + (c1.y - c2.y) * (c1.y - c2.y)); + } + + void Network::updateRefVectors(const Position& closestRefVectorPosition, const InputVector& input, LearningFactor learningFactor, const CurrentIteration& iteration) + { + for (Coordinate y{}; y < _refVectors.getHeight(); ++y) + { + for (Coordinate x{}; x < _refVectors.getWidth(); ++x) + { + InputVector& refVector{ _refVectors.get({ x, y }) }; + + const Norm norm{ computePositionNorm({ x, y }, closestRefVectorPosition) }; + + InputVector delta{ input - refVector }; + delta *= (learningFactor * _neighbourhoodFunc(norm, iteration)); + + refVector += delta; + } + } + } + + void Network::train(const std::vector& inputData, std::size_t nbIterations, ProgressCallback progressCallback, RequestStopCallback requestStopCallback) + { + bool stopRequested{ false }; + std::vector inputDataShuffled; + + inputDataShuffled.reserve(inputData.size()); + for (const auto& input : inputData) + inputDataShuffled.push_back(&input); + + for (std::size_t i{}; i < nbIterations; ++i) + { + CurrentIteration curIter{ i, nbIterations }; + + if (progressCallback) + progressCallback(curIter); + + core::random::shuffleContainer(inputDataShuffled); + + const LearningFactor learningFactor{ _learningFactorFunc(curIter) }; + + for (const InputVector* input : inputDataShuffled) + { + if (requestStopCallback) + stopRequested = requestStopCallback(); + + if (stopRequested) + return; + + updateRefVectors(getClosestRefVectorPosition(*input), *input, learningFactor, curIter); + } + + if (stopRequested) + return; + } + } + + const InputVector& Network::getRefVector(const Position& position) const + { + return _refVectors[position]; + } } // namespace lms::som - - diff --git a/src/libs/som/include/som/DataNormalizer.hpp b/src/libs/som/include/som/DataNormalizer.hpp index 0ba14ace8..29ee83c49 100644 --- a/src/libs/som/include/som/DataNormalizer.hpp +++ b/src/libs/som/include/som/DataNormalizer.hpp @@ -19,42 +19,40 @@ #pragma once -#include #include +#include #include "Network.hpp" namespace lms::som { + class DataNormalizer + { + public: + struct MinMax + { + InputVector::value_type min; + InputVector::value_type max; + }; -class DataNormalizer -{ - public: - struct MinMax - { - InputVector::value_type min; - InputVector::value_type max; - }; - - DataNormalizer(std::size_t inputDimCount); - - std::size_t getInputDimCount() const { return _inputDimCount; } - const MinMax& getValue(std::size_t index) const; + DataNormalizer(std::size_t inputDimCount); - void setValue(std::size_t index, const MinMax& minMax); + std::size_t getInputDimCount() const { return _inputDimCount; } + const MinMax& getValue(std::size_t index) const; - void computeNormalizationFactors(const std::vector& dataSamples); + void setValue(std::size_t index, const MinMax& minMax); - void normalizeData(InputVector& data) const; + void computeNormalizationFactors(const std::vector& dataSamples); - void dump(std::ostream& os) const; + void normalizeData(InputVector& data) const; - private: - InputVector::value_type normalizeValue(InputVector::value_type value, std::size_t dimensionId) const; + void dump(std::ostream& os) const; - const std::size_t _inputDimCount; + private: + InputVector::value_type normalizeValue(InputVector::value_type value, std::size_t dimensionId) const; - std::vector _minmax; // Indexed min/max used to normalize data -}; + const std::size_t _inputDimCount; + std::vector _minmax; // Indexed min/max used to normalize data + }; } // namespace lms::som diff --git a/src/libs/som/include/som/InputVector.hpp b/src/libs/som/include/som/InputVector.hpp index cc0f920a0..ced4b2e40 100644 --- a/src/libs/som/include/som/InputVector.hpp +++ b/src/libs/som/include/som/InputVector.hpp @@ -20,176 +20,174 @@ #pragma once -#include #include +#include #include "core/Exception.hpp" namespace lms::som { - -class Exception : public core::LmsException -{ - public: - using LmsException::LmsException; -}; - -class InputVector -{ - public: - using value_type = double; - using Norm = double; - using Distance = double; - - InputVector(std::size_t nbDimensions, value_type defaultValue = value_type {}) : _values(nbDimensions, defaultValue) {} - - bool hasSameDimension(const InputVector& other) const - { - return _values.size() == other._values.size(); - } - - std::size_t getNbDimensions() const - { - return _values.size(); - } - - value_type& operator[](std::size_t index) - { - if (index >= getNbDimensions()) - throw Exception("Bad range"); - - return _values[index]; - } - - value_type operator[](std::size_t index) const - { - if (index >= getNbDimensions()) - throw Exception("Bad range"); - - return _values[index]; - } - - InputVector& operator+=(const InputVector& other) - { - if (!hasSameDimension(other.getNbDimensions())) - throw Exception {"Not the same dimension count"}; - - for (std::size_t i {}; i < _values.size(); ++i) - { - _values[i] += other[i]; - } - - return *this; - } - - InputVector& operator-=(const InputVector& other) - { - if (!hasSameDimension(other.getNbDimensions())) - throw Exception {"Not the same dimension count"}; - - for (std::size_t i {}; i < _values.size(); ++i) - { - _values[i] -= other[i]; - } - - return *this; - } - - InputVector& operator*=(value_type factor) - { - for (std::size_t i {}; i < _values.size(); ++i) - { - _values[i] *= factor; - } - - return *this; - } - - Norm computeNorm() const - { - Norm res {}; - for (value_type val : _values) - res += val * val; - return std::sqrt(res); - } - - Distance computeEuclidianSquareDistance(const InputVector& other, const InputVector& weights) const - { - if (!hasSameDimension(other.getNbDimensions()) - || !hasSameDimension(weights.getNbDimensions())) - { - throw Exception {"Not the same dimension count"}; - } - - Distance res {}; - - for (std::size_t i {}; i < getNbDimensions(); ++i) - { - const InputVector::value_type diff {_values[i] - other._values[i]}; - res += diff * diff * weights._values[i]; - } - - return res; - } - - std::vector::iterator begin() - { - return _values.begin(); - } - - std::vector::const_iterator begin() const - { - return _values.cbegin(); - } - - std::vector::const_iterator cbegin() const - { - return _values.cbegin(); - } - - std::vector::iterator end() - { - return _values.end(); - } - - std::vector::const_iterator end() const - { - return _values.cend(); - } - - std::vector::const_iterator cend() const - { - return _values.cend(); - } - - private: - friend class InputVector operator-(const InputVector& a, const InputVector& b) - { - if (!a.hasSameDimension(b.getNbDimensions())) - throw Exception {"Not the same dimension count"}; - - InputVector res {a.getNbDimensions()}; - - for (std::size_t i {}; i < res._values.size(); ++i) - res._values[i] = a._values[i] - b._values[i]; - - return res; - } - - friend std::ostream& - operator<<(std::ostream& os, const InputVector& a) - { - os << "["; - for (value_type val : a._values) - { - os << val << " "; - } - os << "]"; - - return os; - } - - std::vector _values; -}; - -} + class Exception : public core::LmsException + { + public: + using LmsException::LmsException; + }; + + class InputVector + { + public: + using value_type = double; + using Norm = double; + using Distance = double; + + InputVector(std::size_t nbDimensions, value_type defaultValue = value_type{}) + : _values(nbDimensions, defaultValue) {} + + bool hasSameDimension(const InputVector& other) const + { + return _values.size() == other._values.size(); + } + + std::size_t getNbDimensions() const + { + return _values.size(); + } + + value_type& operator[](std::size_t index) + { + if (index >= getNbDimensions()) + throw Exception("Bad range"); + + return _values[index]; + } + + value_type operator[](std::size_t index) const + { + if (index >= getNbDimensions()) + throw Exception("Bad range"); + + return _values[index]; + } + + InputVector& operator+=(const InputVector& other) + { + if (!hasSameDimension(other.getNbDimensions())) + throw Exception{ "Not the same dimension count" }; + + for (std::size_t i{}; i < _values.size(); ++i) + { + _values[i] += other[i]; + } + + return *this; + } + + InputVector& operator-=(const InputVector& other) + { + if (!hasSameDimension(other.getNbDimensions())) + throw Exception{ "Not the same dimension count" }; + + for (std::size_t i{}; i < _values.size(); ++i) + { + _values[i] -= other[i]; + } + + return *this; + } + + InputVector& operator*=(value_type factor) + { + for (std::size_t i{}; i < _values.size(); ++i) + { + _values[i] *= factor; + } + + return *this; + } + + Norm computeNorm() const + { + Norm res{}; + for (value_type val : _values) + res += val * val; + return std::sqrt(res); + } + + Distance computeEuclidianSquareDistance(const InputVector& other, const InputVector& weights) const + { + if (!hasSameDimension(other.getNbDimensions()) + || !hasSameDimension(weights.getNbDimensions())) + { + throw Exception{ "Not the same dimension count" }; + } + + Distance res{}; + + for (std::size_t i{}; i < getNbDimensions(); ++i) + { + const InputVector::value_type diff{ _values[i] - other._values[i] }; + res += diff * diff * weights._values[i]; + } + + return res; + } + + std::vector::iterator begin() + { + return _values.begin(); + } + + std::vector::const_iterator begin() const + { + return _values.cbegin(); + } + + std::vector::const_iterator cbegin() const + { + return _values.cbegin(); + } + + std::vector::iterator end() + { + return _values.end(); + } + + std::vector::const_iterator end() const + { + return _values.cend(); + } + + std::vector::const_iterator cend() const + { + return _values.cend(); + } + + private: + friend class InputVector operator-(const InputVector& a, const InputVector& b) + { + if (!a.hasSameDimension(b.getNbDimensions())) + throw Exception{ "Not the same dimension count" }; + + InputVector res{ a.getNbDimensions() }; + + for (std::size_t i{}; i < res._values.size(); ++i) + res._values[i] = a._values[i] - b._values[i]; + + return res; + } + + friend std::ostream& operator<<(std::ostream& os, const InputVector& a) + { + os << "["; + for (value_type val : a._values) + { + os << val << " "; + } + os << "]"; + + return os; + } + + std::vector _values; + }; +} // namespace lms::som diff --git a/src/libs/som/include/som/Matrix.hpp b/src/libs/som/include/som/Matrix.hpp index 9192a678f..83d8c34e0 100644 --- a/src/libs/som/include/som/Matrix.hpp +++ b/src/libs/som/include/som/Matrix.hpp @@ -47,7 +47,7 @@ namespace lms::som } }; - template + template class Matrix { public: @@ -61,7 +61,7 @@ namespace lms::som } template - Matrix(Coordinate width, Coordinate height, CtrArgs&& ... args) + Matrix(Coordinate width, Coordinate height, CtrArgs&&... args) : _width{ width } , _height{ height } { @@ -93,7 +93,7 @@ namespace lms::som T& operator[](const Position& position) { return get(position); } const T& operator[](const Position& position) const { return get(position); } - template + template Position getPositionMinElement(Func func) const { assert(!_values.empty()); @@ -105,12 +105,12 @@ namespace lms::som } private: - Coordinate _width{}; - Coordinate _height{}; - std::vector _values; + Coordinate _width{}; + Coordinate _height{}; + std::vector _values; }; -} // ns lms::som +} // namespace lms::som namespace std { @@ -125,5 +125,4 @@ namespace std return h1 ^ (h2 << 1); } }; -} // ns std - +} // namespace std diff --git a/src/libs/som/include/som/Network.hpp b/src/libs/som/include/som/Network.hpp index b951c9f89..2bb289016 100644 --- a/src/libs/som/include/som/Network.hpp +++ b/src/libs/som/include/som/Network.hpp @@ -19,17 +19,16 @@ #pragma once -#include +#include #include #include -#include +#include #include "InputVector.hpp" #include "Matrix.hpp" namespace lms::som { - using LearningFactor = InputVector::value_type; using Norm = InputVector::value_type; @@ -92,16 +91,14 @@ namespace lms::som void setNeighbourhoodFunc(NeighbourhoodFunc neighbourhoodFunc); private: - void updateRefVectors(const Position& closestRefVectorPosition, const InputVector& input, LearningFactor learningFactor, const CurrentIteration& iteration); std::size_t _inputDimCount{}; - InputVector _weights; // weight for each dimension + InputVector _weights; // weight for each dimension Matrix _refVectors; DistanceFunc _distanceFunc; LearningFactorFunc _learningFactorFunc; NeighbourhoodFunc _neighbourhoodFunc; }; - } // namespace lms::som diff --git a/src/libs/som/test/SomTest.cpp b/src/libs/som/test/SomTest.cpp index 38bee3925..a6c98f76c 100644 --- a/src/libs/som/test/SomTest.cpp +++ b/src/libs/som/test/SomTest.cpp @@ -18,117 +18,118 @@ */ #include + #include + #include "som/DataNormalizer.hpp" #include "som/Network.hpp" namespace lms::som { - static constexpr InputVector::value_type EPSILON = 0.01; - - TEST(som, Matrix) - { - { - Matrix testMatrix{ 2, 2, 123 }; - { - const Position pos{ 0, 0 }; - EXPECT_EQ(testMatrix[pos], 123); - } - { - const Position pos{ 0, 1 }; - EXPECT_EQ(testMatrix[pos], 123); - } - { - const Position pos{ 1, 0 }; - EXPECT_EQ(testMatrix[pos], 123); - } - { - const Position pos{ 1, 1 }; - EXPECT_EQ(testMatrix[pos], 123); - } - } - } - - TEST(som, InputVector) - { - { - InputVector test1{ 2 }; - test1[0] = 0; - test1[1] = 1; - - InputVector test2{ 2 }; - test2[0] = 1; - test2[1] = 0; - - InputVector test3{ test1 }; - test3 += test2; - EXPECT_LT(std::abs(test3[0] - 1), EPSILON); - EXPECT_LT(std::abs(test3[1] - 1), EPSILON); - } - } - - TEST(som, Network) - { - Network network{ 2, 2, 1 }; - - const InputVector weights{ 1, 1 }; - std::vector trainData - { - { 1, 50 }, - { 1, 100 }, - { 1, 150 }, - { 1, 200 }, - }; - - DataNormalizer normalizer{ 1 }; - normalizer.computeNormalizationFactors(trainData); - for (auto& data : trainData) - normalizer.normalizeData(data); - - network.dump(std::cout); - network.train(trainData, 20); - network.dump(std::cout); - - auto distFunc{ network.getDistanceFunc() }; - - EXPECT_LT((std::abs(distFunc({ 1, 0 }, { 1, 1 }, weights) - 1)), EPSILON); - EXPECT_LT((std::abs(distFunc({ 1, 0 }, { 1, 2 }, weights) - 4)), EPSILON); - EXPECT_LT(std::abs(distFunc({ 1, 0 }, { 1, 0.33 }, weights) - distFunc({ 1, 0.66 }, { 1, 1. }, weights)), EPSILON); - - { - std::unordered_set positions; - for (const InputVector& data : trainData) - positions.insert(network.getClosestRefVectorPosition(data)); - - EXPECT_EQ(positions.size(), 4); - } - - { - Position pos{ network.getClosestRefVectorPosition(InputVector{1, 0.66}) }; - for (std::size_t i{}; i < 40; ++i) - { - InputVector input{ 1, 130 + static_cast(i) }; - normalizer.normalizeData(input); - - EXPECT_EQ(network.getClosestRefVectorPosition(input), pos); - } - } - - { - Position pos{ network.getClosestRefVectorPosition(InputVector{1, 1}) }; - for (std::size_t i{}; i < 40; ++i) - { - InputVector input{ 1, 180 + static_cast(i) }; - normalizer.normalizeData(input); - - EXPECT_EQ(network.getClosestRefVectorPosition(input), pos); - } - } - } -} + static constexpr InputVector::value_type EPSILON = 0.01; + + TEST(som, Matrix) + { + { + Matrix testMatrix{ 2, 2, 123 }; + { + const Position pos{ 0, 0 }; + EXPECT_EQ(testMatrix[pos], 123); + } + { + const Position pos{ 0, 1 }; + EXPECT_EQ(testMatrix[pos], 123); + } + { + const Position pos{ 1, 0 }; + EXPECT_EQ(testMatrix[pos], 123); + } + { + const Position pos{ 1, 1 }; + EXPECT_EQ(testMatrix[pos], 123); + } + } + } + + TEST(som, InputVector) + { + { + InputVector test1{ 2 }; + test1[0] = 0; + test1[1] = 1; + + InputVector test2{ 2 }; + test2[0] = 1; + test2[1] = 0; + + InputVector test3{ test1 }; + test3 += test2; + EXPECT_LT(std::abs(test3[0] - 1), EPSILON); + EXPECT_LT(std::abs(test3[1] - 1), EPSILON); + } + } + + TEST(som, Network) + { + Network network{ 2, 2, 1 }; + + const InputVector weights{ 1, 1 }; + std::vector trainData{ + { 1, 50 }, + { 1, 100 }, + { 1, 150 }, + { 1, 200 }, + }; + + DataNormalizer normalizer{ 1 }; + normalizer.computeNormalizationFactors(trainData); + for (auto& data : trainData) + normalizer.normalizeData(data); + + network.dump(std::cout); + network.train(trainData, 20); + network.dump(std::cout); + + auto distFunc{ network.getDistanceFunc() }; + + EXPECT_LT((std::abs(distFunc({ 1, 0 }, { 1, 1 }, weights) - 1)), EPSILON); + EXPECT_LT((std::abs(distFunc({ 1, 0 }, { 1, 2 }, weights) - 4)), EPSILON); + EXPECT_LT(std::abs(distFunc({ 1, 0 }, { 1, 0.33 }, weights) - distFunc({ 1, 0.66 }, { 1, 1. }, weights)), EPSILON); + + { + std::unordered_set positions; + for (const InputVector& data : trainData) + positions.insert(network.getClosestRefVectorPosition(data)); + + EXPECT_EQ(positions.size(), 4); + } + + { + Position pos{ network.getClosestRefVectorPosition(InputVector{ 1, 0.66 }) }; + for (std::size_t i{}; i < 40; ++i) + { + InputVector input{ 1, 130 + static_cast(i) }; + normalizer.normalizeData(input); + + EXPECT_EQ(network.getClosestRefVectorPosition(input), pos); + } + } + + { + Position pos{ network.getClosestRefVectorPosition(InputVector{ 1, 1 }) }; + for (std::size_t i{}; i < 40; ++i) + { + InputVector input{ 1, 180 + static_cast(i) }; + normalizer.normalizeData(input); + + EXPECT_EQ(network.getClosestRefVectorPosition(input), pos); + } + } + } +} // namespace lms::som int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - return RUN_ALL_TESTS(); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/src/libs/subsonic/bench/SubsonicBench.cpp b/src/libs/subsonic/bench/SubsonicBench.cpp index 123bc39aa..eee953fd4 100644 --- a/src/libs/subsonic/bench/SubsonicBench.cpp +++ b/src/libs/subsonic/bench/SubsonicBench.cpp @@ -18,6 +18,7 @@ */ #include + #include #include "SubsonicResponse.hpp" @@ -47,7 +48,7 @@ namespace lms::api::subsonic::benchs return response; } - } + } // namespace static void BM_SubsonicResponse_generate(benchmark::State& state) { @@ -59,7 +60,7 @@ namespace lms::api::subsonic::benchs } } - template + template static void BM_SubsonicResponse_serialize(benchmark::State& state) { const Response response{ generateFakeResponse() }; @@ -74,6 +75,6 @@ namespace lms::api::subsonic::benchs BENCHMARK(BM_SubsonicResponse_generate)->Threads(1)->Threads(std::thread::hardware_concurrency()); BENCHMARK(BM_SubsonicResponse_serialize); BENCHMARK(BM_SubsonicResponse_serialize); -} +} // namespace lms::api::subsonic::benchs BENCHMARK_MAIN(); \ No newline at end of file diff --git a/src/libs/subsonic/impl/ClientInfo.hpp b/src/libs/subsonic/impl/ClientInfo.hpp index 045bafcfa..1cca28bb8 100644 --- a/src/libs/subsonic/impl/ClientInfo.hpp +++ b/src/libs/subsonic/impl/ClientInfo.hpp @@ -20,6 +20,7 @@ #pragma once #include + #include "ProtocolVersion.hpp" namespace lms::api::subsonic @@ -32,4 +33,4 @@ namespace lms::api::subsonic std::string password; ProtocolVersion version; }; -} +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/ParameterParsing.cpp b/src/libs/subsonic/impl/ParameterParsing.cpp index 6811c6906..5e5aaa62b 100644 --- a/src/libs/subsonic/impl/ParameterParsing.cpp +++ b/src/libs/subsonic/impl/ParameterParsing.cpp @@ -39,4 +39,4 @@ namespace lms::api::subsonic return password; } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/ParameterParsing.hpp b/src/libs/subsonic/impl/ParameterParsing.hpp index 99b96e599..e3a650a00 100644 --- a/src/libs/subsonic/impl/ParameterParsing.hpp +++ b/src/libs/subsonic/impl/ParameterParsing.hpp @@ -22,11 +22,12 @@ #include #include -#include #include +#include -#include "database/Types.hpp" #include "core/String.hpp" +#include "database/Types.hpp" + #include "SubsonicResponse.hpp" namespace lms::api::subsonic @@ -84,5 +85,4 @@ namespace lms::api::subsonic bool hasParameter(const Wt::Http::ParameterMap& parameterMap, const std::string& param); std::string decodePasswordIfNeeded(const std::string& password); -} - +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/ProtocolVersion.cpp b/src/libs/subsonic/impl/ProtocolVersion.cpp index 94b5027ea..c3a6d3a72 100644 --- a/src/libs/subsonic/impl/ProtocolVersion.cpp +++ b/src/libs/subsonic/impl/ProtocolVersion.cpp @@ -51,5 +51,4 @@ namespace lms::core::stringUtils return version; } -} - +} // namespace lms::core::stringUtils diff --git a/src/libs/subsonic/impl/ProtocolVersion.hpp b/src/libs/subsonic/impl/ProtocolVersion.hpp index 7e54a3042..28d63cfaa 100644 --- a/src/libs/subsonic/impl/ProtocolVersion.hpp +++ b/src/libs/subsonic/impl/ProtocolVersion.hpp @@ -32,11 +32,10 @@ namespace lms::api::subsonic static inline constexpr ProtocolVersion defaultServerProtocolVersion{ 1, 16, 0 }; static inline constexpr std::string_view serverVersion{ "6" }; -} +} // namespace lms::api::subsonic namespace lms::core::stringUtils { template<> std::optional readAs(std::string_view str); } - diff --git a/src/libs/subsonic/impl/RequestContext.hpp b/src/libs/subsonic/impl/RequestContext.hpp index 03bf4ccc7..f90b49407 100644 --- a/src/libs/subsonic/impl/RequestContext.hpp +++ b/src/libs/subsonic/impl/RequestContext.hpp @@ -24,6 +24,7 @@ #include #include "database/Object.hpp" + #include "ClientInfo.hpp" #include "ProtocolVersion.hpp" @@ -31,7 +32,7 @@ namespace lms::db { class Session; class User; -} +} // namespace lms::db namespace lms::api::subsonic { @@ -43,7 +44,6 @@ namespace lms::api::subsonic ClientInfo clientInfo; ProtocolVersion serverProtocolVersion; bool enableOpenSubsonic{ true }; - bool enableDefaultCover{ }; + bool enableDefaultCover{}; }; -} - +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/SubsonicId.cpp b/src/libs/subsonic/impl/SubsonicId.cpp index 7d0f3ad4b..9ec69508b 100644 --- a/src/libs/subsonic/impl/SubsonicId.cpp +++ b/src/libs/subsonic/impl/SubsonicId.cpp @@ -141,4 +141,4 @@ namespace lms::core::stringUtils return std::nullopt; } -} +} // namespace lms::core::stringUtils diff --git a/src/libs/subsonic/impl/SubsonicId.hpp b/src/libs/subsonic/impl/SubsonicId.hpp index 770d31774..d7a88efa9 100644 --- a/src/libs/subsonic/impl/SubsonicId.hpp +++ b/src/libs/subsonic/impl/SubsonicId.hpp @@ -19,16 +19,18 @@ #pragma once +#include "core/String.hpp" #include "database/ArtistId.hpp" #include "database/MediaLibraryId.hpp" #include "database/ReleaseId.hpp" #include "database/TrackId.hpp" #include "database/TrackListId.hpp" -#include "core/String.hpp" namespace lms::api::subsonic { - struct RootId {}; + struct RootId + { + }; std::string idToString(db::ArtistId id); std::string idToString(db::MediaLibraryId id); @@ -58,5 +60,4 @@ namespace lms::core::stringUtils template<> std::optional readAs(std::string_view str); -} - +} // namespace lms::core::stringUtils diff --git a/src/libs/subsonic/impl/SubsonicResource.cpp b/src/libs/subsonic/impl/SubsonicResource.cpp index f8c5818b7..d132b5829 100644 --- a/src/libs/subsonic/impl/SubsonicResource.cpp +++ b/src/libs/subsonic/impl/SubsonicResource.cpp @@ -22,23 +22,29 @@ #include #include -#include "services/auth/IPasswordService.hpp" -#include "services/auth/IEnvService.hpp" -#include "database/Db.hpp" -#include "database/Session.hpp" -#include "database/User.hpp" #include "core/EnumSet.hpp" -#include "core/LiteralString.hpp" #include "core/IConfig.hpp" #include "core/ILogger.hpp" #include "core/ITraceLogger.hpp" +#include "core/LiteralString.hpp" #include "core/Service.hpp" #include "core/String.hpp" #include "core/Utils.hpp" +#include "database/Db.hpp" +#include "database/Session.hpp" +#include "database/User.hpp" +#include "services/auth/IEnvService.hpp" +#include "services/auth/IPasswordService.hpp" +#include "ParameterParsing.hpp" +#include "ProtocolVersion.hpp" +#include "RequestContext.hpp" +#include "SubsonicId.hpp" +#include "SubsonicResponse.hpp" +#include "Utils.hpp" #include "entrypoints/AlbumSongLists.hpp" -#include "entrypoints/Browsing.hpp" #include "entrypoints/Bookmarks.hpp" +#include "entrypoints/Browsing.hpp" #include "entrypoints/MediaAnnotation.hpp" #include "entrypoints/MediaLibraryScanning.hpp" #include "entrypoints/MediaRetrieval.hpp" @@ -46,12 +52,6 @@ #include "entrypoints/Searching.hpp" #include "entrypoints/System.hpp" #include "entrypoints/UserManagement.hpp" -#include "ParameterParsing.hpp" -#include "ProtocolVersion.hpp" -#include "RequestContext.hpp" -#include "SubsonicId.hpp" -#include "SubsonicResponse.hpp" -#include "Utils.hpp" namespace lms::api::subsonic { @@ -67,10 +67,10 @@ namespace lms::api::subsonic std::unordered_map res; core::Service::get()->visitStrings("api-subsonic-old-server-protocol-clients", - [&](std::string_view client) - { + [&](std::string_view client) { res.emplace(std::string{ client }, ProtocolVersion{ 1, 12, 0 }); - }, { "DSub" }); + }, + { "DSub" }); return res; } @@ -80,10 +80,10 @@ namespace lms::api::subsonic std::unordered_set res; core::Service::get()->visitStrings("api-open-subsonic-disabled-clients", - [&](std::string_view client) - { + [&](std::string_view client) { res.emplace(std::string{ client }); - }, { "DSub" }); + }, + { "DSub" }); return res; } @@ -93,23 +93,22 @@ namespace lms::api::subsonic std::unordered_set res; core::Service::get()->visitStrings("api-subsonic-default-cover-clients", - [&](std::string_view client) - { + [&](std::string_view client) { res.emplace(std::string{ client }); - }, { "DSub", "substreamer" }); + }, + { "DSub", "substreamer" }); return res; } std::string parameterMapToDebugString(const Wt::Http::ParameterMap& parameterMap) { - auto censorValue = [](const std::string& type, const std::string& value) -> std::string - { - if (type == "p" || type == "password") - return "*REDACTED*"; - else - return value; - }; + auto censorValue = [](const std::string& type, const std::string& value) -> std::string { + if (type == "p" || type == "password") + return "*REDACTED*"; + else + return value; + }; std::string res; @@ -148,130 +147,128 @@ namespace lms::api::subsonic using CheckImplementedFunc = std::function; struct RequestEntryPointInfo { - RequestHandlerFunc func; - core::EnumSet allowedUserTypes{ db::UserType::DEMO, db::UserType::REGULAR, db::UserType::ADMIN }; - CheckImplementedFunc checkFunc{}; + RequestHandlerFunc func; + core::EnumSet allowedUserTypes{ db::UserType::DEMO, db::UserType::REGULAR, db::UserType::ADMIN }; + CheckImplementedFunc checkFunc{}; }; - const std::unordered_map requestEntryPoints - { + const std::unordered_map requestEntryPoints{ // System - {"/ping", {handlePingRequest}}, - {"/getLicense", {handleGetLicenseRequest}}, - {"/getOpenSubsonicExtensions", {handleGetOpenSubsonicExtensions}}, + { "/ping", { handlePingRequest } }, + { "/getLicense", { handleGetLicenseRequest } }, + { "/getOpenSubsonicExtensions", { handleGetOpenSubsonicExtensions } }, // Browsing - {"/getMusicFolders", {handleGetMusicFoldersRequest}}, - {"/getIndexes", {handleGetIndexesRequest}}, - {"/getMusicDirectory", {handleGetMusicDirectoryRequest}}, - {"/getGenres", {handleGetGenresRequest}}, - {"/getMoods", {handleGetMoodRequest}}, - {"/getYears", {handleGetYearsRequest}}, - {"/getArtists", {handleGetArtistsRequest}}, - {"/getArtist", {handleGetArtistRequest}}, - {"/getAlbum", {handleGetAlbumRequest}}, - {"/getSong", {handleGetSongRequest}}, - {"/getVideos", {handleNotImplemented}}, - {"/getArtistInfo", {handleGetArtistInfoRequest}}, - {"/getArtistInfo2", {handleGetArtistInfo2Request}}, - {"/getAlbumInfo", {handleNotImplemented}}, - {"/getAlbumInfo2", {handleNotImplemented}}, - {"/getSimilarSongs", {handleGetSimilarSongsRequest}}, - {"/getSimilarSongs2", {handleGetSimilarSongs2Request}}, - {"/getTopSongs", {handleGetTopSongs}}, + { "/getMusicFolders", { handleGetMusicFoldersRequest } }, + { "/getIndexes", { handleGetIndexesRequest } }, + { "/getMusicDirectory", { handleGetMusicDirectoryRequest } }, + { "/getGenres", { handleGetGenresRequest } }, + {"/getMoods", {handleGetMoodRequest}}, + {"/getYears", {handleGetYearsRequest}}, + { "/getArtists", { handleGetArtistsRequest } }, + { "/getArtist", { handleGetArtistRequest } }, + { "/getAlbum", { handleGetAlbumRequest } }, + { "/getSong", { handleGetSongRequest } }, + { "/getVideos", { handleNotImplemented } }, + { "/getArtistInfo", { handleGetArtistInfoRequest } }, + { "/getArtistInfo2", { handleGetArtistInfo2Request } }, + { "/getAlbumInfo", { handleNotImplemented } }, + { "/getAlbumInfo2", { handleNotImplemented } }, + { "/getSimilarSongs", { handleGetSimilarSongsRequest } }, + { "/getSimilarSongs2", { handleGetSimilarSongs2Request } }, + { "/getTopSongs", { handleGetTopSongs } }, // Album/song lists - {"/getAlbumList", {handleGetAlbumListRequest}}, - {"/getAlbumList2", {handleGetAlbumList2Request}}, - {"/getRandomSongs", {handleGetRandomSongsRequest}}, - {"/getSongsByGenre", {handleGetSongsByGenreRequest}}, - {"/getSongsByGenreAndYear", {handleGetSongsByGenreRequest}}, - {"/getSongsByYear", {handleGetSongsByYearRequest}}, - {"/getSongsByMood", {handleGetSongsByMoodRequest}}, - {"/getSongsByMoodAndYear", {handleGetSongsByMoodRequest}}, - {"/getNowPlaying", {handleNotImplemented}}, - {"/getStarred", {handleGetStarredRequest}}, - {"/getStarred2", {handleGetStarred2Request}}, - - // Searching - {"/search", {handleNotImplemented}}, - {"/search2", {handleSearch2Request}}, - {"/search3", {handleSearch3Request}}, - - // Playlists - {"/getPlaylists", {handleGetPlaylistsRequest}}, - {"/getPlaylist", {handleGetPlaylistRequest}}, - {"/createPlaylist", {handleCreatePlaylistRequest}}, - {"/updatePlaylist", {handleUpdatePlaylistRequest}}, - {"/deletePlaylist", {handleDeletePlaylistRequest}}, - - // Media retrieval - {"/hls", {handleNotImplemented}}, - {"/getCaptions", {handleNotImplemented}}, - {"/getLyrics", {handleNotImplemented}}, - {"/getAvatar", {handleNotImplemented}}, - - // Media annotation - {"/star", {handleStarRequest}}, - {"/unstar", {handleUnstarRequest}}, - {"/setRating", {handleNotImplemented}}, - {"/scrobble", {handleScrobble}}, - - // Sharing - {"/getShares", {handleNotImplemented}}, - {"/createShares", {handleNotImplemented}}, - {"/updateShare", {handleNotImplemented}}, - {"/deleteShare", {handleNotImplemented}}, - - // Podcast - {"/getPodcasts", {handleNotImplemented}}, - {"/getNewestPodcasts", {handleNotImplemented}}, - {"/refreshPodcasts", {handleNotImplemented}}, - {"/createPodcastChannel", {handleNotImplemented}}, - {"/deletePodcastChannel", {handleNotImplemented}}, - {"/deletePodcastEpisode", {handleNotImplemented}}, - {"/downloadPodcastEpisode", {handleNotImplemented}}, - - // Jukebox - {"/jukeboxControl", {handleNotImplemented}}, - - // Internet radio - {"/getInternetRadioStations", {handleNotImplemented}}, - {"/createInternetRadioStation", {handleNotImplemented}}, - {"/updateInternetRadioStation", {handleNotImplemented}}, - {"/deleteInternetRadioStation", {handleNotImplemented}}, - - // Chat - {"/getChatMessages", {handleNotImplemented}}, - {"/addChatMessages", {handleNotImplemented}}, + { "/getAlbumList", { handleGetAlbumListRequest } }, + { "/getAlbumList2", { handleGetAlbumList2Request } }, + { "/getRandomSongs", { handleGetRandomSongsRequest } }, + { "/getSongsByGenre", { handleGetSongsByGenreRequest } }, + {"/getSongsByGenreAndYear", {handleGetSongsByGenreRequest}}, + {"/getSongsByYear", {handleGetSongsByYearRequest}}, + {"/getSongsByMood", {handleGetSongsByMoodRequest}}, + {"/getSongsByMoodAndYear", {handleGetSongsByMoodRequest}}, + { "/getNowPlaying", { handleNotImplemented } }, + { "/getStarred", { handleGetStarredRequest } }, + { "/getStarred2", { handleGetStarred2Request } }, + + // Searching + { "/search", { handleNotImplemented } }, + { "/search2", { handleSearch2Request } }, + { "/search3", { handleSearch3Request } }, + + // Playlists + { "/getPlaylists", { handleGetPlaylistsRequest } }, + { "/getPlaylist", { handleGetPlaylistRequest } }, + { "/createPlaylist", { handleCreatePlaylistRequest } }, + { "/updatePlaylist", { handleUpdatePlaylistRequest } }, + { "/deletePlaylist", { handleDeletePlaylistRequest } }, + + // Media retrieval + { "/hls", { handleNotImplemented } }, + { "/getCaptions", { handleNotImplemented } }, + { "/getLyrics", { handleNotImplemented } }, + { "/getAvatar", { handleNotImplemented } }, + + // Media annotation + { "/star", { handleStarRequest } }, + { "/unstar", { handleUnstarRequest } }, + { "/setRating", { handleNotImplemented } }, + { "/scrobble", { handleScrobble } }, + + // Sharing + { "/getShares", { handleNotImplemented } }, + { "/createShares", { handleNotImplemented } }, + { "/updateShare", { handleNotImplemented } }, + { "/deleteShare", { handleNotImplemented } }, + + // Podcast + { "/getPodcasts", { handleNotImplemented } }, + { "/getNewestPodcasts", { handleNotImplemented } }, + { "/refreshPodcasts", { handleNotImplemented } }, + { "/createPodcastChannel", { handleNotImplemented } }, + { "/deletePodcastChannel", { handleNotImplemented } }, + { "/deletePodcastEpisode", { handleNotImplemented } }, + { "/downloadPodcastEpisode", { handleNotImplemented } }, + + // Jukebox + { "/jukeboxControl", { handleNotImplemented } }, + + // Internet radio + { "/getInternetRadioStations", { handleNotImplemented } }, + { "/createInternetRadioStation", { handleNotImplemented } }, + { "/updateInternetRadioStation", { handleNotImplemented } }, + { "/deleteInternetRadioStation", { handleNotImplemented } }, + + // Chat + { "/getChatMessages", { handleNotImplemented } }, + { "/addChatMessages", { handleNotImplemented } }, // User management - {"/getUser", {handleGetUserRequest}}, - {"/getUsers", {handleGetUsersRequest, {db::UserType::ADMIN}}}, - {"/createUser", {handleCreateUserRequest, {db::UserType::ADMIN}, &utils::checkSetPasswordImplemented}}, - {"/updateUser", {handleUpdateUserRequest, {db::UserType::ADMIN}}}, - {"/deleteUser", {handleDeleteUserRequest, {db::UserType::ADMIN}}}, - {"/changePassword", {handleChangePassword, {db::UserType::REGULAR, db::UserType::ADMIN}, &utils::checkSetPasswordImplemented}}, - - // Bookmarks - {"/getBookmarks", {handleGetBookmarks}}, - {"/createBookmark", {handleCreateBookmark}}, - {"/deleteBookmark", {handleDeleteBookmark}}, - {"/getPlayQueue", {handleNotImplemented}}, - {"/savePlayQueue", {handleNotImplemented}}, + { "/getUser", { handleGetUserRequest } }, + { "/getUsers", { handleGetUsersRequest, { db::UserType::ADMIN } } }, + { "/createUser", { handleCreateUserRequest, { db::UserType::ADMIN }, &utils::checkSetPasswordImplemented } }, + { "/updateUser", { handleUpdateUserRequest, { db::UserType::ADMIN } } }, + { "/deleteUser", { handleDeleteUserRequest, { db::UserType::ADMIN } } }, + { "/changePassword", { handleChangePassword, { db::UserType::REGULAR, db::UserType::ADMIN }, &utils::checkSetPasswordImplemented } }, + + // Bookmarks + { "/getBookmarks", { handleGetBookmarks } }, + { "/createBookmark", { handleCreateBookmark } }, + { "/deleteBookmark", { handleDeleteBookmark } }, + { "/getPlayQueue", { handleNotImplemented } }, + { "/savePlayQueue", { handleNotImplemented } }, // Media library scanning - {"/getScanStatus", {Scan::handleGetScanStatus, {db::UserType::ADMIN}}}, - {"/startScan", {Scan::handleStartScan, {db::UserType::ADMIN}}}, + { "/getScanStatus", { Scan::handleGetScanStatus, { db::UserType::ADMIN } } }, + { "/startScan", { Scan::handleStartScan, { db::UserType::ADMIN } } }, }; using MediaRetrievalHandlerFunc = std::function; - const std::unordered_map mediaRetrievalHandlers - { + const std::unordered_map mediaRetrievalHandlers{ // Media retrieval - {"/download", handleDownload}, - {"/stream", handleStream}, - {"/getCoverArt", handleGetCoverArt}, + { "/download", handleDownload }, + { "/stream", handleStream }, + { "/getCoverArt", handleGetCoverArt }, }; struct TLSMonotonicMemoryResourceCleaner @@ -286,7 +283,7 @@ namespace lms::api::subsonic TLSMonotonicMemoryResourceCleaner(const TLSMonotonicMemoryResourceCleaner&) = delete; TLSMonotonicMemoryResourceCleaner& operator=(const TLSMonotonicMemoryResourceCleaner&) = delete; }; - } + } // namespace SubsonicResource::SubsonicResource(db::Db& db) : _serverProtocolVersionsByClient{ readConfigProtocolVersions() } @@ -300,8 +297,8 @@ namespace lms::api::subsonic { static std::atomic curRequestId{}; - const std::size_t requestId{ curRequestId++ }; - TLSMonotonicMemoryResourceCleaner memoryResourceCleaner; + const std::size_t requestId{ curRequestId++ }; + TLSMonotonicMemoryResourceCleaner memoryResourceCleaner; LMS_LOG(API_SUBSONIC, DEBUG, "Handling request " << requestId << " '" << request.pathInfo() << "', continuation = " << (request.continuation() ? "true" : "false") << ", params = " << parameterMapToDebugString(request.getParameterMap())); std::cout<< "Handling request " << requestId << " '" << request.pathInfo() << "', continuation = " << (request.continuation() ? "true" : "false") << ", params = " << parameterMapToDebugString(request.getParameterMap()); @@ -334,7 +331,7 @@ namespace lms::api::subsonic const Response resp{ [&] { LMS_SCOPED_TRACE_DETAILED("Subsonic", "HandleRequest"); return itEntryPoint->second.func(requestContext); - }()}; + }() }; { LMS_SCOPED_TRACE_DETAILED("Subsonic", "WriteResponse"); @@ -365,9 +362,6 @@ namespace lms::api::subsonic LMS_LOG(API_SUBSONIC, ERROR, "Error while processing request '" << requestPath << "'" << ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]" << ", code = " << static_cast(e.getCode()) << ", msg = '" << e.getMessage() << "'"); - std::cout<<"Error while processing request '" << requestPath << "'" - << ", params = [" << parameterMapToDebugString(request.getParameterMap()) << "]" - << ", code = " << static_cast(e.getCode()) << ", msg = '" << e.getMessage() << "'"<getId(); } - if (auto * authEnvService{ core::Service::get() }) + if (auto* authEnvService{ core::Service::get() }) { const auto checkResult{ authEnvService->processRequest(request) }; if (checkResult.state != auth::IEnvService::CheckResult::State::Granted) @@ -461,7 +455,7 @@ namespace lms::api::subsonic return *checkResult.userId; } - else if (auto * authPasswordService{ core::Service::get() }) + else if (auto* authPasswordService{ core::Service::get() }) { const auto checkResult{ authPasswordService->checkUserPassword(boost::asio::ip::address::from_string(request.clientAddress()), clientInfo.user, clientInfo.password) }; @@ -481,4 +475,3 @@ namespace lms::api::subsonic } } // namespace lms::api::subsonic - diff --git a/src/libs/subsonic/impl/SubsonicResource.hpp b/src/libs/subsonic/impl/SubsonicResource.hpp index 33b99054c..6303d580e 100644 --- a/src/libs/subsonic/impl/SubsonicResource.hpp +++ b/src/libs/subsonic/impl/SubsonicResource.hpp @@ -19,14 +19,15 @@ #pragma once #include -#include #include +#include -#include #include +#include #include "database/Types.hpp" #include "database/UserId.hpp" + #include "ClientInfo.hpp" #include "RequestContext.hpp" @@ -37,26 +38,24 @@ namespace lms::db namespace lms::api::subsonic { - class SubsonicResource final : public Wt::WResource { - public: - SubsonicResource(db::Db& db); + public: + SubsonicResource(db::Db& db); - private: - void handleRequest(const Wt::Http::Request &request, Wt::Http::Response &response) override; - ProtocolVersion getServerProtocolVersion(const std::string& clientName) const; + private: + void handleRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override; + ProtocolVersion getServerProtocolVersion(const std::string& clientName) const; - static void checkProtocolVersion(ProtocolVersion client, ProtocolVersion server); - ClientInfo getClientInfo(const Wt::Http::Request& request); - RequestContext buildRequestContext(const Wt::Http::Request& request); - db::UserId authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo); + static void checkProtocolVersion(ProtocolVersion client, ProtocolVersion server); + ClientInfo getClientInfo(const Wt::Http::Request& request); + RequestContext buildRequestContext(const Wt::Http::Request& request); + db::UserId authenticateUser(const Wt::Http::Request& request, const ClientInfo& clientInfo); - const std::unordered_map _serverProtocolVersionsByClient; - const std::unordered_set _openSubsonicDisabledClients; - const std::unordered_set _defaultCoverClients; + const std::unordered_map _serverProtocolVersionsByClient; + const std::unordered_set _openSubsonicDisabledClients; + const std::unordered_set _defaultCoverClients; - db::Db& _db; + db::Db& _db; }; - -} // namespace +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/SubsonicResponse.cpp b/src/libs/subsonic/impl/SubsonicResponse.cpp index f82f73055..993e2dfcb 100644 --- a/src/libs/subsonic/impl/SubsonicResponse.cpp +++ b/src/libs/subsonic/impl/SubsonicResponse.cpp @@ -20,12 +20,14 @@ #include "SubsonicResponse.hpp" #include -#include #include +#include + #include #include "core/Exception.hpp" #include "core/String.hpp" + #include "ProtocolVersion.hpp" namespace lms::api::subsonic @@ -34,8 +36,10 @@ namespace lms::api::subsonic { switch (format) { - case ResponseFormat::xml: return "text/xml"; - case ResponseFormat::json: return "application/json"; + case ResponseFormat::xml: + return "text/xml"; + case ResponseFormat::json: + return "application/json"; } return ""; @@ -92,7 +96,7 @@ namespace lms::api::subsonic assert(!_children.contains(key)); auto& values{ _childrenValues[key] }; values.emplace_back(string{ value }); - assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) {return value.index() == values.front().index();})); + assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) { return value.index() == values.front().index(); })); } void Response::Node::addArrayValue(Key key, long long value) @@ -100,7 +104,7 @@ namespace lms::api::subsonic assert(!_value); auto& values{ _childrenValues[key] }; values.emplace_back(value); - assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) {return value.index() == values.front().index();})); + assert(std::all_of(std::cbegin(values) + 1, std::cend(values), [&](const ValueType& value) { return value.index() == values.front().index(); })); } Response::Node& Response::Node::createChild(Key key) @@ -186,59 +190,57 @@ namespace lms::api::subsonic void Response::writeXML(std::ostream& os) const { - std::function nodeToPropertyTree = [&](const Node& node) + std::function nodeToPropertyTree = [&](const Node& node) { + boost::property_tree::ptree res; + + for (const auto& [key, value] : node._attributes) { + if (std::holds_alternative(value)) + res.put("." + std::string{ key.str() }, std::get(value)); + else if (std::holds_alternative(value)) + res.put("." + std::string{ key.str() }, std::get(value)); + else if (std::holds_alternative(value)) + res.put("." + std::string{ key.str() }, std::get(value)); + else if (std::holds_alternative(value)) + res.put("." + std::string{ key.str() }, std::get(value)); + } + + auto valueToPropertyTree = [](const Node::ValueType& value) { boost::property_tree::ptree res; + std::visit([&](const auto& rawValue) { + res.put_value(rawValue); + }, + value); + + return res; + }; - for (const auto& [key, value] : node._attributes) + if (node._value) + { + res = valueToPropertyTree(*node._value); + } + else + { + for (const auto& [key, childNode] : node._children) { - if (std::holds_alternative(value)) - res.put("." + std::string{ key.str() }, std::get(value)); - else if (std::holds_alternative(value)) - res.put("." + std::string{ key.str() }, std::get(value)); - else if (std::holds_alternative(value)) - res.put("." + std::string{ key.str() }, std::get(value)); - else if (std::holds_alternative(value)) - res.put("." + std::string{ key.str() }, std::get(value)); + res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode)); } - auto valueToPropertyTree = [](const Node::ValueType& value) - { - boost::property_tree::ptree res; - std::visit([&](const auto& rawValue) - { - res.put_value(rawValue); - }, value); - - return res; - }; - - if (node._value) + for (const auto& [key, childArrayNodes] : node._childrenArrays) { - res = valueToPropertyTree(*node._value); + for (const Node& childNode : childArrayNodes) + res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode)); } - else + + for (const auto& [key, childArrayValues] : node._childrenValues) { - for (const auto& [key, childNode] : node._children) - { - res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode)); - } - - for (const auto& [key, childArrayNodes] : node._childrenArrays) - { - for (const Node& childNode : childArrayNodes) - res.add_child(std::string{ key.str() }, nodeToPropertyTree(childNode)); - } - - for (const auto& [key, childArrayValues] : node._childrenValues) - { - for (const Response::Node::ValueType& value : childArrayValues) - res.add_child(std::string{ key.str() }, valueToPropertyTree(value)); - } + for (const Response::Node::ValueType& value : childArrayValues) + res.add_child(std::string{ key.str() }, valueToPropertyTree(value)); } + } - return res; - }; + return res; + }; const boost::property_tree::ptree root{ nodeToPropertyTree(_root) }; boost::property_tree::write_xml(os, root); @@ -376,4 +378,4 @@ namespace lms::api::subsonic serializer.serializeNode(os, _root); } -} // namespace +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/SubsonicResponse.hpp b/src/libs/subsonic/impl/SubsonicResponse.hpp index 52ad6bcd6..22badb81e 100644 --- a/src/libs/subsonic/impl/SubsonicResponse.hpp +++ b/src/libs/subsonic/impl/SubsonicResponse.hpp @@ -26,6 +26,7 @@ #include #include "core/LiteralString.hpp" + #include "RequestContext.hpp" #include "SubsonicResponseAllocator.hpp" @@ -57,7 +58,8 @@ namespace lms::api::subsonic RequestedDataNotFound = 70, }; - Error(Code code) : _code{ code } {} + Error(Code code) + : _code{ code } {} virtual std::string getMessage() const = 0; @@ -70,7 +72,8 @@ namespace lms::api::subsonic class GenericError : public Error { public: - GenericError() : Error{ Code::Generic } {} + GenericError() + : Error{ Code::Generic } {} }; class RequiredParameterMissingError : public Error @@ -79,7 +82,8 @@ namespace lms::api::subsonic RequiredParameterMissingError(std::string_view param) : Error{ Code::RequiredParameterMissing } , _param{ param } - {} + { + } private: std::string getMessage() const override { return "Required parameter '" + _param + "' is missing."; } @@ -89,7 +93,9 @@ namespace lms::api::subsonic class ClientMustUpgradeError : public Error { public: - ClientMustUpgradeError() : Error{ Code::ClientMustUpgrade } {} + ClientMustUpgradeError() + : Error{ Code::ClientMustUpgrade } {} + private: std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Client must upgrade."; } }; @@ -97,7 +103,9 @@ namespace lms::api::subsonic class ServerMustUpgradeError : public Error { public: - ServerMustUpgradeError() : Error{ Code::ServerMustUpgrade } {} + ServerMustUpgradeError() + : Error{ Code::ServerMustUpgrade } {} + private: std::string getMessage() const override { return "Incompatible Subsonic REST protocol version. Server must upgrade."; } }; @@ -105,7 +113,9 @@ namespace lms::api::subsonic class WrongUsernameOrPasswordError : public Error { public: - WrongUsernameOrPasswordError() : Error{ Code::WrongUsernameOrPassword } {} + WrongUsernameOrPasswordError() + : Error{ Code::WrongUsernameOrPassword } {} + private: std::string getMessage() const override { return "Wrong username or password."; } }; @@ -113,7 +123,9 @@ namespace lms::api::subsonic class TokenAuthenticationNotSupportedForLDAPUsersError : public Error { public: - TokenAuthenticationNotSupportedForLDAPUsersError() : Error{ Code::TokenAuthenticationNotSupportedForLDAPUsers } {} + TokenAuthenticationNotSupportedForLDAPUsersError() + : Error{ Code::TokenAuthenticationNotSupportedForLDAPUsers } {} + private: std::string getMessage() const override { return "Token authentication not supported for LDAP users."; } }; @@ -121,7 +133,9 @@ namespace lms::api::subsonic class UserNotAuthorizedError : public Error { public: - UserNotAuthorizedError() : Error{ Code::UserNotAuthorized } {} + UserNotAuthorizedError() + : Error{ Code::UserNotAuthorized } {} + private: std::string getMessage() const override { return "User is not authorized for the given operation."; } }; @@ -129,7 +143,9 @@ namespace lms::api::subsonic class RequestedDataNotFoundError : public Error { public: - RequestedDataNotFoundError() : Error{ Code::RequestedDataNotFound } {} + RequestedDataNotFoundError() + : Error{ Code::RequestedDataNotFound } {} + private: std::string getMessage() const override { return "The requested data was not found."; } }; @@ -137,7 +153,9 @@ namespace lms::api::subsonic class InternalErrorGenericError : public GenericError { public: - InternalErrorGenericError(const std::string& message) : _message{ message } {} + InternalErrorGenericError(const std::string& message) + : _message{ message } {} + private: std::string getMessage() const override { return "Internal error: " + _message; } const std::string _message; @@ -181,7 +199,8 @@ namespace lms::api::subsonic class BadParameterGenericError : public GenericError { public: - BadParameterGenericError(const std::string& parameterName) : _parameterName{ parameterName } {} + BadParameterGenericError(const std::string& parameterName) + : _parameterName{ parameterName } {} private: std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad value"; } @@ -192,7 +211,9 @@ namespace lms::api::subsonic class ParameterValueTooHighGenericError : public GenericError { public: - ParameterValueTooHighGenericError(std::string_view parameterName, std::size_t max) : _parameterName{ parameterName }, _max{ max } {} + ParameterValueTooHighGenericError(std::string_view parameterName, std::size_t max) + : _parameterName{ parameterName } + , _max{ max } {} private: std::string getMessage() const override { return "Parameter '" + _parameterName + "': bad value (max is " + std::to_string(_max) + ")"; } @@ -211,7 +232,7 @@ namespace lms::api::subsonic void setAttribute(Key key, std::string_view value); - template ::value>* = nullptr> + template::value>* = nullptr> void setAttribute(Key key, T value) { if constexpr (std::is_same::value) @@ -242,10 +263,10 @@ namespace lms::api::subsonic friend class Response; - template - using map = std::map< Key, Value, std::less, ResponseAllocator>>; + template + using map = std::map, ResponseAllocator>>; - template + template using vector = std::vector>; using string = std::basic_string, ResponseAllocator>; @@ -280,7 +301,7 @@ namespace lms::api::subsonic class JsonSerializer { - public: + public: void serializeNode(std::ostream& os, const Node& node); void serializeValue(std::ostream& os, const Node::ValueType& value); void serializeEscapedString(std::ostream&, std::string_view str); @@ -293,5 +314,4 @@ namespace lms::api::subsonic Node _root; }; -} // namespace - +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/SubsonicResponseAllocator.hpp b/src/libs/subsonic/impl/SubsonicResponseAllocator.hpp index b8195a081..b0335a793 100644 --- a/src/libs/subsonic/impl/SubsonicResponseAllocator.hpp +++ b/src/libs/subsonic/impl/SubsonicResponseAllocator.hpp @@ -23,7 +23,7 @@ namespace lms::api::subsonic { // Stateless allocator that uses a shared MemoryResource - template + template class Allocator { public: @@ -37,10 +37,12 @@ namespace lms::api::subsonic constexpr Allocator() noexcept = default; - template - constexpr Allocator(const Allocator&) noexcept {} + template + constexpr Allocator(const Allocator&) noexcept + { + } - template + template struct rebind { using other = Allocator; @@ -59,17 +61,17 @@ namespace lms::api::subsonic }; template - bool operator==(const Allocator &, const Allocator &) + bool operator==(const Allocator&, const Allocator&) { return true; } template - bool operator!=(const Allocator &, const Allocator &) + bool operator!=(const Allocator&, const Allocator&) { return false; } - - template + + template using ResponseAllocator = Allocator; -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/TLSMonotonicMemoryResource.hpp b/src/libs/subsonic/impl/TLSMonotonicMemoryResource.hpp index 07e036394..9ff50e79f 100644 --- a/src/libs/subsonic/impl/TLSMonotonicMemoryResource.hpp +++ b/src/libs/subsonic/impl/TLSMonotonicMemoryResource.hpp @@ -18,10 +18,10 @@ */ #pragma once +#include #include #include #include -#include #include namespace lms::api::subsonic @@ -55,7 +55,7 @@ namespace lms::api::subsonic throw std::bad_alloc{}; assert(currentAddrAligned >= &_currentBlock->front()); - + std::byte* res{ currentAddrAligned }; _currentAddr = currentAddrAligned + byteCount; @@ -100,4 +100,4 @@ namespace lms::api::subsonic BlockType* _currentBlock{}; std::byte* _currentAddr{}; }; -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/Utils.cpp b/src/libs/subsonic/impl/Utils.cpp index 8bf38dbe1..f7746a535 100644 --- a/src/libs/subsonic/impl/Utils.cpp +++ b/src/libs/subsonic/impl/Utils.cpp @@ -22,6 +22,7 @@ #include "core/Service.hpp" #include "core/String.hpp" #include "services/auth/IPasswordService.hpp" + #include "SubsonicResponse.hpp" namespace lms::api::subsonic::utils @@ -38,4 +39,4 @@ namespace lms::api::subsonic::utils return core::stringUtils::replaceInString(name, "/", "_"); } -} \ No newline at end of file +} // namespace lms::api::subsonic::utils \ No newline at end of file diff --git a/src/libs/subsonic/impl/Utils.hpp b/src/libs/subsonic/impl/Utils.hpp index 95b0d3f33..4aff2a809 100644 --- a/src/libs/subsonic/impl/Utils.hpp +++ b/src/libs/subsonic/impl/Utils.hpp @@ -26,4 +26,4 @@ namespace lms::api::subsonic::utils { void checkSetPasswordImplemented(); std::string makeNameFilesystemCompatible(std::string_view name); -} \ No newline at end of file +} // namespace lms::api::subsonic::utils \ No newline at end of file diff --git a/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp b/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp index ea958c752..ca573fd8a 100644 --- a/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp +++ b/src/libs/subsonic/impl/entrypoints/AlbumSongLists.cpp @@ -19,6 +19,7 @@ #include "AlbumSongLists.hpp" +#include "core/Service.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/Release.hpp" @@ -27,12 +28,12 @@ #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" + +#include "ParameterParsing.hpp" +#include "SubsonicId.hpp" #include "responses/Album.hpp" #include "responses/Artist.hpp" #include "responses/Song.hpp" -#include "core/Service.hpp" -#include "ParameterParsing.hpp" -#include "SubsonicId.hpp" namespace lms::api::subsonic { @@ -103,9 +104,9 @@ namespace lms::api::subsonic const int toYear{ getMandatoryParameterAs(context.parameters, "toYear") }; Release::FindParameters params; - params.setSortMethod(ReleaseSortMethod::Date); + params.setSortMethod(fromYear > toYear ? ReleaseSortMethod::DateDesc : ReleaseSortMethod::DateAsc); params.setRange(range); - params.setDateRange(DateRange::fromYearRange(fromYear, toYear)); + params.setDateRange(DateRange::fromYearRange(std::min(fromYear, toYear), std::max(fromYear, toYear))); params.setMediaLibrary(mediaLibraryId); releases = Release::findIds(context.dbSession, params); @@ -245,10 +246,9 @@ namespace lms::api::subsonic params.setRange(Range{ 0, size }); params.setMediaLibrary(mediaLibraryId); - Track::find(context.dbSession, params, [&](const Track::pointer& track) - { - randomSongsNode.addArrayChild("song", createSongNode(context, track, context.user)); - }); + Track::find(context.dbSession, params, [&](const Track::pointer& track) { + randomSongsNode.addArrayChild("song", createSongNode(context, track, context.user)); + }); return response; } @@ -277,7 +277,7 @@ namespace lms::api::subsonic std::size_t ratingMin{ getParameterAs(context.parameters, "ratingMin").value_or(0) }; std::size_t ratingMax{ getParameterAs(context.parameters, "ratingMax").value_or(5) }; if (count > defaultMaxCountSize) - throw ParameterValueTooHighGenericError{"count", defaultMaxCountSize}; + throw ParameterValueTooHighGenericError{ "count", defaultMaxCountSize }; std::size_t offset{ getParameterAs(context.parameters, "offset").value_or(0) }; @@ -308,14 +308,12 @@ namespace lms::api::subsonic params.setRange(Range{ offset, count }); params.setMediaLibrary(mediaLibrary); - - Track::find(context.dbSession, params, [&](const Track::pointer& track) - { - if( + Track::find(context.dbSession, params, [&](const Track::pointer& track) { + if( track->getRating().value_or(0) >= ratingMin && track->getRating().value_or(0) <= ratingMax) - songsByGenreNode.addArrayChild("song", createSongNode(context, track, context.user)); - }); + songsByGenreNode.addArrayChild("song", createSongNode(context, track, context.user)); + }); return response; } @@ -421,4 +419,4 @@ namespace lms::api::subsonic return handleGetStarredRequestCommon(context, true /* id3 */); } -} +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/entrypoints/AlbumSongLists.hpp b/src/libs/subsonic/impl/entrypoints/AlbumSongLists.hpp index 3fc4f88fc..569c0cab3 100644 --- a/src/libs/subsonic/impl/entrypoints/AlbumSongLists.hpp +++ b/src/libs/subsonic/impl/entrypoints/AlbumSongLists.hpp @@ -32,4 +32,4 @@ namespace lms::api::subsonic Response handleGetSongsByMoodRequest(RequestContext& context); Response handleGetStarredRequest(RequestContext& context); Response handleGetStarred2Request(RequestContext& context); -} +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/entrypoints/Bookmarks.cpp b/src/libs/subsonic/impl/entrypoints/Bookmarks.cpp index 98f1a644a..94567443f 100644 --- a/src/libs/subsonic/impl/entrypoints/Bookmarks.cpp +++ b/src/libs/subsonic/impl/entrypoints/Bookmarks.cpp @@ -20,13 +20,14 @@ #include "Bookmarks.hpp" #include "database/Session.hpp" -#include "database/User.hpp" #include "database/Track.hpp" #include "database/TrackBookmark.hpp" -#include "responses/Bookmark.hpp" -#include "responses/Song.hpp" +#include "database/User.hpp" + #include "ParameterParsing.hpp" #include "SubsonicId.hpp" +#include "responses/Bookmark.hpp" +#include "responses/Song.hpp" namespace lms::api::subsonic { @@ -92,4 +93,4 @@ namespace lms::api::subsonic return Response::createOkResponse(context.serverProtocolVersion); } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/entrypoints/Bookmarks.hpp b/src/libs/subsonic/impl/entrypoints/Bookmarks.hpp index bcbb79218..b0a24e22d 100644 --- a/src/libs/subsonic/impl/entrypoints/Bookmarks.hpp +++ b/src/libs/subsonic/impl/entrypoints/Bookmarks.hpp @@ -27,4 +27,4 @@ namespace lms::api::subsonic Response handleGetBookmarks(RequestContext& context); Response handleCreateBookmark(RequestContext& context); Response handleDeleteBookmark(RequestContext& context); -} +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/entrypoints/Browsing.cpp b/src/libs/subsonic/impl/entrypoints/Browsing.cpp index a136058d6..96db189dc 100644 --- a/src/libs/subsonic/impl/entrypoints/Browsing.cpp +++ b/src/libs/subsonic/impl/entrypoints/Browsing.cpp @@ -19,31 +19,32 @@ #include "Browsing.hpp" +#include "core/ILogger.hpp" +#include "core/Random.hpp" +#include "core/Service.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/MediaLibrary.hpp" -#include "database/Session.hpp" #include "database/Release.hpp" +#include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" #include "services/recommendation/IRecommendationService.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "core/ILogger.hpp" -#include "core/Random.hpp" -#include "core/Service.hpp" + +#include "ParameterParsing.hpp" +#include "SubsonicId.hpp" +#include "Utils.hpp" #include "responses/Album.hpp" #include "responses/Artist.hpp" #include "responses/Genre.hpp" #include "responses/Song.hpp" -#include "ParameterParsing.hpp" -#include "SubsonicId.hpp" -#include "Utils.hpp" namespace lms::api::subsonic { using namespace db; - static const unsigned long long reportedDummyDateULong{ 946684800000ULL }; // 2000-01-01T00:00:00 UTC + static const unsigned long long reportedDummyDateULong{ 946684800000ULL }; // 2000-01-01T00:00:00 UTC namespace { @@ -70,7 +71,7 @@ namespace lms::api::subsonic artistInfoNode.createChild("musicBrainzId").setValue(artistMBID->getAsString()); } - auto similarArtistsId{ core::Service::get()->getSimilarArtists(id, {TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}, count) }; + auto similarArtistsId{ core::Service::get()->getSimilarArtists(id, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, count) }; { auto transaction{ context.dbSession.createReadTransaction() }; @@ -170,7 +171,7 @@ namespace lms::api::subsonic { // API says: "Returns a random collection of songs from the given artist and similar artists" const std::size_t similarArtistCount{ count / 5 }; - std::vector artistIds{ core::Service::get()->getSimilarArtists(artistId, {TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}, similarArtistCount) }; + std::vector artistIds{ core::Service::get()->getSimilarArtists(artistId, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, similarArtistCount) }; artistIds.push_back(artistId); const std::size_t meanTrackCountPerArtist{ (count / artistIds.size()) + 1 }; @@ -265,7 +266,7 @@ namespace lms::api::subsonic return response; } - } + } // namespace Response handleGetMusicFoldersRequest(RequestContext& context) { @@ -273,13 +274,12 @@ namespace lms::api::subsonic Response::Node& musicFoldersNode{ response.createNode("musicFolders") }; auto transaction{ context.dbSession.createReadTransaction() }; - MediaLibrary::find(context.dbSession, [&](const MediaLibrary::pointer& library) - { - Response::Node& musicFolderNode{ musicFoldersNode.createArrayChild("musicFolder") }; + MediaLibrary::find(context.dbSession, [&](const MediaLibrary::pointer& library) { + Response::Node& musicFolderNode{ musicFoldersNode.createArrayChild("musicFolder") }; - musicFolderNode.setAttribute("id", idToString(library->getId())); - musicFolderNode.setAttribute("name", library->getName()); - }); + musicFolderNode.setAttribute("id", idToString(library->getId())); + musicFolderNode.setAttribute("name", library->getName()); + }); return response; } @@ -310,10 +310,9 @@ namespace lms::api::subsonic directoryNode.setAttribute("name", "Music"); // TODO: this does not scale when a lot of artists are present - Artist::find(context.dbSession, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::SortName), [&](const Artist::pointer& artist) - { - directoryNode.addArrayChild("child", createArtistNode(context, artist, context.user, false /* no id3 */)); - }); + Artist::find(context.dbSession, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::SortName), [&](const Artist::pointer& artist) { + directoryNode.addArrayChild("child", createArtistNode(context, artist, context.user, false /* no id3 */)); + }); } else if (artistId) { @@ -325,10 +324,9 @@ namespace lms::api::subsonic directoryNode.setAttribute("name", utils::makeNameFilesystemCompatible(artist->getName())); - Release::find(context.dbSession, Release::FindParameters{}.setArtist(*artistId), [&](const Release::pointer& release) - { - directoryNode.addArrayChild("child", createAlbumNode(context, release, context.user, false /* no id3 */)); - }); + Release::find(context.dbSession, Release::FindParameters{}.setArtist(*artistId), [&](const Release::pointer& release) { + directoryNode.addArrayChild("child", createAlbumNode(context, release, context.user, false /* no id3 */)); + }); } else if (releaseId) { @@ -340,10 +338,9 @@ namespace lms::api::subsonic directoryNode.setAttribute("name", utils::makeNameFilesystemCompatible(release->getName())); - Track::find(context.dbSession, Track::FindParameters{}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release), [&](const Track::pointer& track) - { - directoryNode.addArrayChild("child", createSongNode(context, track, context.user)); - }); + Track::find(context.dbSession, Track::FindParameters{}.setRelease(*releaseId).setSortMethod(TrackSortMethod::Release), [&](const Track::pointer& track) { + directoryNode.addArrayChild("child", createSongNode(context, track, context.user)); + }); } else throw BadParameterGenericError{ "id" }; @@ -467,7 +464,7 @@ namespace lms::api::subsonic Response response{ Response::createOkResponse(context.serverProtocolVersion) }; Response::Node artistNode{ createArtistNode(context, artist, context.user, true /* id3 */) }; - const auto releases{ Release::find(context.dbSession, Release::FindParameters {}.setArtist(artist->getId())) }; + const auto releases{ Release::find(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) }; for (const Release::pointer& release : releases.results) artistNode.addArrayChild("album", createAlbumNode(context, release, context.user, true /* id3 */)); @@ -567,4 +564,4 @@ namespace lms::api::subsonic return response; } -} +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/entrypoints/Browsing.hpp b/src/libs/subsonic/impl/entrypoints/Browsing.hpp index 244daf11c..76353d640 100644 --- a/src/libs/subsonic/impl/entrypoints/Browsing.hpp +++ b/src/libs/subsonic/impl/entrypoints/Browsing.hpp @@ -39,4 +39,4 @@ namespace lms::api::subsonic Response handleGetSimilarSongsRequest(RequestContext& context); Response handleGetSimilarSongs2Request(RequestContext& context); Response handleGetTopSongs(RequestContext& context); -} +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/entrypoints/MediaAnnotation.cpp b/src/libs/subsonic/impl/entrypoints/MediaAnnotation.cpp index 27a537c11..2abdd2127 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaAnnotation.cpp +++ b/src/libs/subsonic/impl/entrypoints/MediaAnnotation.cpp @@ -21,13 +21,14 @@ #include +#include "core/Service.hpp" #include "database/ArtistId.hpp" #include "database/ReleaseId.hpp" #include "database/TrackId.hpp" #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "core/Service.hpp" + #include "ParameterParsing.hpp" #include "SubsonicId.hpp" @@ -55,7 +56,7 @@ namespace lms::api::subsonic return res; } - } + } // namespace Response handleStarRequest(RequestContext& context) { @@ -119,11 +120,11 @@ namespace lms::api::subsonic { const TrackId trackId{ ids[i] }; const unsigned long time{ times[i] }; - core::Service::get()->addTimedListen({ {context.user->getId(), trackId}, Wt::WDateTime::fromTime_t(static_cast(time / 1000)) }); + core::Service::get()->addTimedListen({ { context.user->getId(), trackId }, Wt::WDateTime::fromTime_t(static_cast(time / 1000)) }); } } } return Response::createOkResponse(context.serverProtocolVersion); } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/entrypoints/MediaAnnotation.hpp b/src/libs/subsonic/impl/entrypoints/MediaAnnotation.hpp index 0197afb3b..a7b211c8e 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaAnnotation.hpp +++ b/src/libs/subsonic/impl/entrypoints/MediaAnnotation.hpp @@ -27,4 +27,4 @@ namespace lms::api::subsonic Response handleStarRequest(RequestContext& context); Response handleUnstarRequest(RequestContext& context); Response handleScrobble(RequestContext& context); -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.cpp b/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.cpp index a692b7285..3a899b2b0 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.cpp +++ b/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.cpp @@ -19,8 +19,8 @@ #include "MediaLibraryScanning.hpp" -#include "services/scanner/IScannerService.hpp" #include "core/Service.hpp" +#include "services/scanner/IScannerService.hpp" namespace lms::api::subsonic::Scan { @@ -47,7 +47,7 @@ namespace lms::api::subsonic::Scan return statusResponse; } - } + } // namespace Response handleGetScanStatus(RequestContext& context) { @@ -66,5 +66,4 @@ namespace lms::api::subsonic::Scan return response; } -} - +} // namespace lms::api::subsonic::Scan diff --git a/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.hpp b/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.hpp index 9cdf3406b..a36d9d92c 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.hpp +++ b/src/libs/subsonic/impl/entrypoints/MediaLibraryScanning.hpp @@ -26,5 +26,4 @@ namespace lms::api::subsonic::Scan { Response handleGetScanStatus(RequestContext& context); Response handleStartScan(RequestContext& context); -} - +} // namespace lms::api::subsonic::Scan diff --git a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp index 843cbd0c8..781d0f3a9 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp +++ b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.cpp @@ -24,15 +24,16 @@ #include "av/TranscodingParameters.hpp" #include "av/TranscodingResourceHandlerCreator.hpp" #include "av/Types.hpp" -#include "services/cover/ICoverService.hpp" +#include "core/FileResourceHandlerCreator.hpp" +#include "core/ILogger.hpp" +#include "core/IResourceHandler.hpp" +#include "core/String.hpp" +#include "core/Utils.hpp" #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" -#include "core/IResourceHandler.hpp" -#include "core/ILogger.hpp" -#include "core/FileResourceHandlerCreator.hpp" -#include "core/Utils.hpp" -#include "core/String.hpp" +#include "services/cover/ICoverService.hpp" + #include "ParameterParsing.hpp" #include "SubsonicId.hpp" @@ -40,15 +41,15 @@ namespace lms::api::subsonic { using namespace db; - namespace + namespace { std::optional subsonicStreamFormatToAvOutputFormat(std::string_view format) { for (const auto& [str, avFormat] : std::initializer_list>{ - {"mp3", av::transcoding::OutputFormat::MP3}, - {"opus", av::transcoding::OutputFormat::OGG_OPUS}, - {"vorbis", av::transcoding::OutputFormat::OGG_VORBIS}, - }) + { "mp3", av::transcoding::OutputFormat::MP3 }, + { "opus", av::transcoding::OutputFormat::OGG_OPUS }, + { "vorbis", av::transcoding::OutputFormat::OGG_VORBIS }, + }) { if (core::stringUtils::stringCaseInsensitiveEqual(str, format)) return avFormat; @@ -60,11 +61,16 @@ namespace lms::api::subsonic { switch (format) { - case db::TranscodingOutputFormat::MP3: return av::transcoding::OutputFormat::MP3; - case db::TranscodingOutputFormat::OGG_OPUS: return av::transcoding::OutputFormat::OGG_OPUS; - case db::TranscodingOutputFormat::MATROSKA_OPUS: return av::transcoding::OutputFormat::MATROSKA_OPUS; - case db::TranscodingOutputFormat::OGG_VORBIS: return av::transcoding::OutputFormat::OGG_VORBIS; - case db::TranscodingOutputFormat::WEBM_VORBIS: return av::transcoding::OutputFormat::WEBM_VORBIS; + case db::TranscodingOutputFormat::MP3: + return av::transcoding::OutputFormat::MP3; + case db::TranscodingOutputFormat::OGG_OPUS: + return av::transcoding::OutputFormat::OGG_OPUS; + case db::TranscodingOutputFormat::MATROSKA_OPUS: + return av::transcoding::OutputFormat::MATROSKA_OPUS; + case db::TranscodingOutputFormat::OGG_VORBIS: + return av::transcoding::OutputFormat::OGG_VORBIS; + case db::TranscodingOutputFormat::WEBM_VORBIS: + return av::transcoding::OutputFormat::WEBM_VORBIS; } return av::transcoding::OutputFormat::OGG_OPUS; } @@ -147,7 +153,7 @@ namespace lms::api::subsonic requestedFormat = userTranscodeFormatToAvFormat(context.user->getSubsonicDefaultTranscodingOutputFormat()); } - if (!requestedFormat && (maxBitRate == 0 || track->getBitrate() <= maxBitRate )) + if (!requestedFormat && (maxBitRate == 0 || track->getBitrate() <= maxBitRate)) { LMS_LOG(API_SUBSONIC, DEBUG, "File's bitrate is compatible with parameters => no transcoding"); return parameters; // no transcoding needed @@ -166,7 +172,7 @@ namespace lms::api::subsonic } bitrate = maxBitRate; } - + if (!requestedFormat) requestedFormat = userTranscodeFormatToAvFormat(context.user->getSubsonicDefaultTranscodingOutputFormat()); if (!bitrate) @@ -181,7 +187,7 @@ namespace lms::api::subsonic return parameters; } - } + } // namespace void handleDownload(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response) { diff --git a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.hpp b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.hpp index 81b854a92..f99085d13 100644 --- a/src/libs/subsonic/impl/entrypoints/MediaRetrieval.hpp +++ b/src/libs/subsonic/impl/entrypoints/MediaRetrieval.hpp @@ -29,5 +29,4 @@ namespace lms::api::subsonic void handleDownload(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response); void handleStream(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response); void handleGetCoverArt(RequestContext& context, const Wt::Http::Request& request, Wt::Http::Response& response); -} - +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/entrypoints/Playlists.cpp b/src/libs/subsonic/impl/entrypoints/Playlists.cpp index 520e2d9e1..536998bd9 100644 --- a/src/libs/subsonic/impl/entrypoints/Playlists.cpp +++ b/src/libs/subsonic/impl/entrypoints/Playlists.cpp @@ -23,10 +23,11 @@ #include "database/Track.hpp" #include "database/TrackList.hpp" #include "database/User.hpp" -#include "responses/Playlist.hpp" -#include "responses/Song.hpp" + #include "ParameterParsing.hpp" #include "SubsonicId.hpp" +#include "responses/Playlist.hpp" +#include "responses/Song.hpp" namespace lms::api::subsonic { @@ -200,4 +201,4 @@ namespace lms::api::subsonic return Response::createOkResponse(context.serverProtocolVersion); } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/entrypoints/Playlists.hpp b/src/libs/subsonic/impl/entrypoints/Playlists.hpp index 01c39d204..0ef17cf1e 100644 --- a/src/libs/subsonic/impl/entrypoints/Playlists.hpp +++ b/src/libs/subsonic/impl/entrypoints/Playlists.hpp @@ -29,4 +29,4 @@ namespace lms::api::subsonic Response handleCreatePlaylistRequest(RequestContext& context); Response handleUpdatePlaylistRequest(RequestContext& context); Response handleDeletePlaylistRequest(RequestContext& context); -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/entrypoints/Searching.cpp b/src/libs/subsonic/impl/entrypoints/Searching.cpp index 743be7edb..008f3bdf7 100644 --- a/src/libs/subsonic/impl/entrypoints/Searching.cpp +++ b/src/libs/subsonic/impl/entrypoints/Searching.cpp @@ -20,8 +20,8 @@ #include "Searching.hpp" #include -#include #include +#include #include "core/Random.hpp" #include "database/Artist.hpp" @@ -29,11 +29,12 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "database/User.hpp" + +#include "ParameterParsing.hpp" +#include "SubsonicId.hpp" #include "responses/Album.hpp" #include "responses/Artist.hpp" #include "responses/Song.hpp" -#include "ParameterParsing.hpp" -#include "SubsonicId.hpp" namespace lms::api::subsonic { @@ -44,7 +45,7 @@ namespace lms::api::subsonic // Search endpoints can be used to scan/sync the database // This class is used to keep track of the current scans, in order to retrieve the last objectId // to speed up the query of the following range (avoid the 'offset' cost) - template + template class ScanTracker { public: @@ -71,7 +72,7 @@ namespace lms::api::subsonic }; static constexpr std::size_t maxScanCount{ 50 }; - static constexpr ClockType::duration maxEntryDuration{ std::chrono::seconds{30} }; + static constexpr ClockType::duration maxEntryDuration{ std::chrono::seconds{ 30 } }; std::mutex _mutex; std::map _ongoingScans; @@ -126,20 +127,18 @@ namespace lms::api::subsonic const std::size_t artistOffset{ getParameterAs(context.parameters, "artistOffset").value_or(0) }; ArtistId lastRetrievedId; - auto findArtists{ [&] - { - Artist::FindParameters params; - params.setKeywords(keywords); - params.setRange(Range{ artistOffset, artistCount }); - params.setMediaLibrary(mediaLibrary); - params.setSortMethod(ArtistSortMethod::Id); // must be consistent with both methods - - Artist::find(context.dbSession, params, [&](const Artist::pointer& artist) - { - searchResultNode.addArrayChild("artist", createArtistNode(context, artist, user, id3)); - lastRetrievedId = artist->getId(); - }); - } }; + auto findArtists{ [&] { + Artist::FindParameters params; + params.setKeywords(keywords); + params.setRange(Range{ artistOffset, artistCount }); + params.setMediaLibrary(mediaLibrary); + params.setSortMethod(ArtistSortMethod::Id); // must be consistent with both methods + + Artist::find(context.dbSession, params, [&](const Artist::pointer& artist) { + searchResultNode.addArrayChild("artist", createArtistNode(context, artist, user, id3)); + lastRetrievedId = artist->getId(); + }); + } }; if (!keywords.empty()) { @@ -147,8 +146,7 @@ namespace lms::api::subsonic } else { - ScanTracker::ScanInfo scanInfo - { + ScanTracker::ScanInfo scanInfo{ .clientAddress = context.clientInfo.ipAddress, .clientName = context.clientInfo.name, .userName = context.clientInfo.user, @@ -158,10 +156,11 @@ namespace lms::api::subsonic if (ArtistId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid()) { - Artist::find(context.dbSession, cachedLastRetrievedId, artistCount, [&](const Artist::pointer& artist) - { + Artist::find( + context.dbSession, cachedLastRetrievedId, artistCount, [&](const Artist::pointer& artist) { searchResultNode.addArrayChild("artist", createArtistNode(context, artist, user, id3)); - }, mediaLibrary); + }, + mediaLibrary); lastRetrievedId = cachedLastRetrievedId; } else @@ -192,19 +191,17 @@ namespace lms::api::subsonic ReleaseId lastRetrievedId; - auto findReleases{ [&] - { + auto findReleases{ [&] { Release::FindParameters params; params.setKeywords(keywords); params.setRange(Range{ albumOffset, albumCount }); params.setMediaLibrary(mediaLibrary); params.setSortMethod(ReleaseSortMethod::Id); // must be consistent with both methods - Release::find(context.dbSession, params, [&](const Release::pointer& release) - { - searchResultNode.addArrayChild("album", createAlbumNode(context, release, user, id3)); - lastRetrievedId = release->getId(); - }); + Release::find(context.dbSession, params, [&](const Release::pointer& release) { + searchResultNode.addArrayChild("album", createAlbumNode(context, release, user, id3)); + lastRetrievedId = release->getId(); + }); } }; if (!keywords.empty()) @@ -213,8 +210,7 @@ namespace lms::api::subsonic } else { - ScanTracker::ScanInfo scanInfo - { + ScanTracker::ScanInfo scanInfo{ .clientAddress = context.clientInfo.ipAddress, .clientName = context.clientInfo.name, .userName = context.clientInfo.user, @@ -224,10 +220,11 @@ namespace lms::api::subsonic if (ReleaseId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid()) { - Release::find(context.dbSession, cachedLastRetrievedId, albumCount, [&](const Release::pointer& release) - { + Release::find( + context.dbSession, cachedLastRetrievedId, albumCount, [&](const Release::pointer& release) { searchResultNode.addArrayChild("album", createAlbumNode(context, release, user, id3)); - }, mediaLibrary); + }, + mediaLibrary); lastRetrievedId = cachedLastRetrievedId; } else @@ -258,19 +255,17 @@ namespace lms::api::subsonic TrackId lastRetrievedId; - auto findTracks{ [&] - { + auto findTracks{ [&] { Track::FindParameters params; params.setKeywords(keywords); params.setRange(Range{ songOffset, songCount }); params.setMediaLibrary(mediaLibrary); params.setSortMethod(TrackSortMethod::Id); // must be consistent with both methods - Track::find(context.dbSession, params, [&](const Track::pointer& track) - { - searchResultNode.addArrayChild("song", createSongNode(context, track, user)); - lastRetrievedId = track->getId(); - }); + Track::find(context.dbSession, params, [&](const Track::pointer& track) { + searchResultNode.addArrayChild("song", createSongNode(context, track, user)); + lastRetrievedId = track->getId(); + }); } }; if (!keywords.empty()) @@ -279,8 +274,7 @@ namespace lms::api::subsonic } else { - ScanTracker::ScanInfo scanInfo - { + ScanTracker::ScanInfo scanInfo{ .clientAddress = context.clientInfo.ipAddress, .clientName = context.clientInfo.name, .userName = context.clientInfo.user, @@ -290,10 +284,11 @@ namespace lms::api::subsonic if (TrackId cachedLastRetrievedId{ currentScansInProgress.extractLastRetrievedObjectId(scanInfo) }; cachedLastRetrievedId.isValid()) { - Track::find(context.dbSession, cachedLastRetrievedId, songCount, [&](const Track::pointer& track) - { + Track::find( + context.dbSession, cachedLastRetrievedId, songCount, [&](const Track::pointer& track) { searchResultNode.addArrayChild("song", createSongNode(context, track, user)); - }, mediaLibrary); + }, + mediaLibrary); lastRetrievedId = cachedLastRetrievedId; } else @@ -308,7 +303,7 @@ namespace lms::api::subsonic } } } - } + } // namespace namespace { @@ -340,7 +335,7 @@ namespace lms::api::subsonic return response; } - } + } // namespace Response handleSearch2Request(RequestContext& context) { @@ -351,4 +346,4 @@ namespace lms::api::subsonic { return handleSearchRequestCommon(context, true /* id3 */); } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/entrypoints/Searching.hpp b/src/libs/subsonic/impl/entrypoints/Searching.hpp index db433a13f..4d852282b 100644 --- a/src/libs/subsonic/impl/entrypoints/Searching.hpp +++ b/src/libs/subsonic/impl/entrypoints/Searching.hpp @@ -26,4 +26,4 @@ namespace lms::api::subsonic { Response handleSearch2Request(RequestContext& context); Response handleSearch3Request(RequestContext& context); -} +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/entrypoints/System.cpp b/src/libs/subsonic/impl/entrypoints/System.cpp index 45ca2d913..b6ae4be62 100644 --- a/src/libs/subsonic/impl/entrypoints/System.cpp +++ b/src/libs/subsonic/impl/entrypoints/System.cpp @@ -37,4 +37,4 @@ namespace lms::api::subsonic return response; }; -} +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/entrypoints/System.hpp b/src/libs/subsonic/impl/entrypoints/System.hpp index 83e48c334..b5be13470 100644 --- a/src/libs/subsonic/impl/entrypoints/System.hpp +++ b/src/libs/subsonic/impl/entrypoints/System.hpp @@ -27,4 +27,4 @@ namespace lms::api::subsonic Response handlePingRequest(RequestContext& context); Response handleGetLicenseRequest(RequestContext& context); Response handleGetOpenSubsonicExtensions(RequestContext& context); -} +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/entrypoints/UserManagement.cpp b/src/libs/subsonic/impl/entrypoints/UserManagement.cpp index e9576a78d..3e335110d 100644 --- a/src/libs/subsonic/impl/entrypoints/UserManagement.cpp +++ b/src/libs/subsonic/impl/entrypoints/UserManagement.cpp @@ -1,24 +1,26 @@ #include "UserManagement.hpp" +#include "core/Service.hpp" #include "database/Session.hpp" #include "database/User.hpp" #include "services/auth/IPasswordService.hpp" -#include "core/Service.hpp" -#include "responses/User.hpp" + #include "ParameterParsing.hpp" #include "Utils.hpp" +#include "responses/User.hpp" namespace lms::api::subsonic { using namespace db; - namespace { + namespace + { void checkUserIsMySelfOrAdmin(RequestContext& context, const std::string& username) { if (context.user->getLoginName() != username && !context.user->isAdmin()) throw UserNotAuthorizedError{}; } - } + } // namespace Response handleGetUserRequest(RequestContext& context) { @@ -44,10 +46,9 @@ namespace lms::api::subsonic Response::Node& usersNode{ response.createNode("users") }; auto transaction{ context.dbSession.createReadTransaction() }; - User::find(context.dbSession, User::FindParameters{}, [&](const User::pointer& user) - { - usersNode.addArrayChild("user", createUserNode(user)); - }); + User::find(context.dbSession, User::FindParameters{}, [&](const User::pointer& user) { + usersNode.addArrayChild("user", createUserNode(user)); + }); return response; } @@ -70,13 +71,12 @@ namespace lms::api::subsonic userId = user->getId(); } - auto removeCreatedUser{ [&] - { - auto transaction {context.dbSession.createWriteTransaction()}; - User::pointer user{ User::find(context.dbSession, userId) }; - if (user) - user.remove(); - } }; + auto removeCreatedUser{ [&] { + auto transaction{ context.dbSession.createWriteTransaction() }; + User::pointer user{ User::find(context.dbSession, userId) }; + if (user) + user.remove(); + } }; try { @@ -198,4 +198,4 @@ namespace lms::api::subsonic return Response::createOkResponse(context.serverProtocolVersion); } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/entrypoints/UserManagement.hpp b/src/libs/subsonic/impl/entrypoints/UserManagement.hpp index e44203d6f..4425ec764 100644 --- a/src/libs/subsonic/impl/entrypoints/UserManagement.hpp +++ b/src/libs/subsonic/impl/entrypoints/UserManagement.hpp @@ -30,4 +30,4 @@ namespace lms::api::subsonic Response handleUpdateUserRequest(RequestContext& context); Response handleDeleteUserRequest(RequestContext& context); Response handleChangePassword(RequestContext& context); -} +} // namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/responses/Album.cpp b/src/libs/subsonic/impl/responses/Album.cpp index fc3adf034..546a19e7b 100644 --- a/src/libs/subsonic/impl/responses/Album.cpp +++ b/src/libs/subsonic/impl/responses/Album.cpp @@ -19,21 +19,21 @@ #include "responses/Album.hpp" +#include "core/ITraceLogger.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/Release.hpp" #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "core/ITraceLogger.hpp" -#include "core/Service.hpp" -#include "core/String.hpp" +#include "SubsonicId.hpp" #include "responses/Artist.hpp" #include "responses/DiscTitle.hpp" #include "responses/ItemDate.hpp" #include "responses/ItemGenre.hpp" -#include "SubsonicId.hpp" namespace lms::api::subsonic { @@ -45,13 +45,14 @@ namespace lms::api::subsonic Response::Node albumNode; - if (id3) { + if (id3) + { albumNode.setAttribute("name", release->getName()); albumNode.setAttribute("songCount", release->getTrackCount()); albumNode.setAttribute( "duration", std::chrono::duration_cast( - release->getDuration()) - .count()); + release->getDuration()) + .count()); } else { @@ -97,7 +98,7 @@ namespace lms::api::subsonic const ClusterType::pointer genreClusterType{ ClusterType::find(context.dbSession, "GENRE") }; if (genreClusterType) { - auto clusters{ release->getClusterGroups({genreClusterType->getId()}, 1) }; + auto clusters{ release->getClusterGroups({ genreClusterType->getId() }, 1) }; if (!clusters.empty() && !clusters.front().empty()) albumNode.setAttribute("genre", clusters.front().front()->getName()); } @@ -124,19 +125,17 @@ namespace lms::api::subsonic albumNode.setAttribute("musicBrainzId", mbid ? mbid->getAsString() : ""); } - auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName) - { + auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName) { albumNode.createEmptyArrayValue(field); Cluster::FindParameters params; params.setRelease(release->getId()); params.setClusterTypeName(clusterTypeName); - Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster) - { - albumNode.addArrayValue(field, cluster->getName()); - }); - }}; + Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster) { + albumNode.addArrayValue(field, cluster->getName()); + }); + } }; addClusters("moods", "MOOD"); @@ -148,10 +147,9 @@ namespace lms::api::subsonic params.setRelease(release->getId()); params.setClusterType(genreClusterType->getId()); - Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster) - { - albumNode.addArrayChild("genres", createItemGenreNode(cluster->getName())); - }); + Cluster::find(context.dbSession, params, [&](const Cluster::pointer& cluster) { + albumNode.addArrayChild("genres", createItemGenreNode(cluster->getName())); + }); } albumNode.createEmptyArrayChild("artists"); @@ -186,4 +184,4 @@ namespace lms::api::subsonic return albumNode; } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Album.hpp b/src/libs/subsonic/impl/responses/Album.hpp index b6780b712..a9508a053 100644 --- a/src/libs/subsonic/impl/responses/Album.hpp +++ b/src/libs/subsonic/impl/responses/Album.hpp @@ -20,6 +20,7 @@ #pragma once #include "database/Object.hpp" + #include "SubsonicResponse.hpp" namespace lms::db @@ -27,7 +28,7 @@ namespace lms::db class Release; class User; class Session; -} +} // namespace lms::db namespace lms::api::subsonic { diff --git a/src/libs/subsonic/impl/responses/Artist.cpp b/src/libs/subsonic/impl/responses/Artist.cpp index bbc40f311..734b02cd8 100644 --- a/src/libs/subsonic/impl/responses/Artist.cpp +++ b/src/libs/subsonic/impl/responses/Artist.cpp @@ -19,14 +19,15 @@ #include "responses/Artist.hpp" +#include "core/ITraceLogger.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" #include "database/Artist.hpp" +#include "database/Image.hpp" #include "database/Release.hpp" #include "database/TrackArtistLink.hpp" #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" -#include "core/ITraceLogger.hpp" -#include "core/Service.hpp" -#include "core/String.hpp" #include "SubsonicId.hpp" @@ -45,8 +46,7 @@ namespace lms::api::subsonic names.resize(artists.size()); std::transform(std::cbegin(artists), std::cend(artists), std::begin(names), - [](const Artist::pointer& artist) - { + [](const Artist::pointer& artist) { return artist->getName(); }); @@ -57,22 +57,33 @@ namespace lms::api::subsonic { switch (type) { - case TrackArtistLinkType::Arranger: return "arranger"; - case TrackArtistLinkType::Artist: return "artist"; - case TrackArtistLinkType::Composer: return "composer"; - case TrackArtistLinkType::Conductor: return "conductor"; - case TrackArtistLinkType::Lyricist: return "lyricist"; - case TrackArtistLinkType::Mixer: return "mixer"; - case TrackArtistLinkType::Performer: return "performer"; - case TrackArtistLinkType::Producer: return "producer"; - case TrackArtistLinkType::ReleaseArtist: return "albumartist"; - case TrackArtistLinkType::Remixer: return "remixer"; - case TrackArtistLinkType::Writer: return "writer"; + case TrackArtistLinkType::Arranger: + return "arranger"; + case TrackArtistLinkType::Artist: + return "artist"; + case TrackArtistLinkType::Composer: + return "composer"; + case TrackArtistLinkType::Conductor: + return "conductor"; + case TrackArtistLinkType::Lyricist: + return "lyricist"; + case TrackArtistLinkType::Mixer: + return "mixer"; + case TrackArtistLinkType::Performer: + return "performer"; + case TrackArtistLinkType::Producer: + return "producer"; + case TrackArtistLinkType::ReleaseArtist: + return "albumartist"; + case TrackArtistLinkType::Remixer: + return "remixer"; + case TrackArtistLinkType::Writer: + return "writer"; } return "unknown"; } - } + } // namespace utils Response::Node createArtistNode(RequestContext& context, const Artist::pointer& artist, const User::pointer& user, bool id3) { @@ -82,11 +93,12 @@ namespace lms::api::subsonic artistNode.setAttribute("id", idToString(artist->getId())); artistNode.setAttribute("name", artist->getName()); - artistNode.setAttribute("coverArt", idToString(artist->getId())); + if (const db::Image::pointer artistImage{ artist->getImage() }) + artistNode.setAttribute("coverArt", idToString(artist->getId())); if (id3) { - const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters {}.setArtist(artist->getId())) }; + const std::size_t count{ Release::getCount(context.dbSession, Release::FindParameters{}.setArtist(artist->getId())) }; artistNode.setAttribute("albumCount", count); } @@ -125,4 +137,4 @@ namespace lms::api::subsonic return artistNode; } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Artist.hpp b/src/libs/subsonic/impl/responses/Artist.hpp index fe0e2f4b3..b7ebc5163 100644 --- a/src/libs/subsonic/impl/responses/Artist.hpp +++ b/src/libs/subsonic/impl/responses/Artist.hpp @@ -21,8 +21,10 @@ #include #include + #include "database/Object.hpp" #include "database/Types.hpp" + #include "SubsonicResponse.hpp" namespace lms::db @@ -30,7 +32,7 @@ namespace lms::db class Artist; class User; class Session; -} +} // namespace lms::db namespace lms::api::subsonic { @@ -38,7 +40,7 @@ namespace lms::api::subsonic { std::string joinArtistNames(const std::vector>& artists); std::string_view toString(db::TrackArtistLinkType type); - } + } // namespace utils Response::Node createArtistNode(RequestContext& context, const db::ObjectPtr& artist, const db::ObjectPtr& user, bool id3); Response::Node createArtistNode(const db::ObjectPtr& artist); // only minimal info -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Bookmark.cpp b/src/libs/subsonic/impl/responses/Bookmark.cpp index 206b21e50..063f330c5 100644 --- a/src/libs/subsonic/impl/responses/Bookmark.cpp +++ b/src/libs/subsonic/impl/responses/Bookmark.cpp @@ -39,4 +39,4 @@ namespace lms::api::subsonic return trackBookmarkNode; } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Bookmark.hpp b/src/libs/subsonic/impl/responses/Bookmark.hpp index fc8073ade..13ae9bbbf 100644 --- a/src/libs/subsonic/impl/responses/Bookmark.hpp +++ b/src/libs/subsonic/impl/responses/Bookmark.hpp @@ -20,6 +20,7 @@ #pragma once #include "database/Object.hpp" + #include "SubsonicResponse.hpp" namespace lms::db diff --git a/src/libs/subsonic/impl/responses/Contributor.cpp b/src/libs/subsonic/impl/responses/Contributor.cpp index ad2dc2b03..952fc6621 100644 --- a/src/libs/subsonic/impl/responses/Contributor.cpp +++ b/src/libs/subsonic/impl/responses/Contributor.cpp @@ -21,6 +21,7 @@ #include "database/Object.hpp" #include "database/TrackArtistLink.hpp" + #include "SubsonicResponse.hpp" #include "responses/Artist.hpp" @@ -37,4 +38,4 @@ namespace lms::api::subsonic return contributorNode; } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Contributor.hpp b/src/libs/subsonic/impl/responses/Contributor.hpp index 6c89ed017..a563d7fec 100644 --- a/src/libs/subsonic/impl/responses/Contributor.hpp +++ b/src/libs/subsonic/impl/responses/Contributor.hpp @@ -20,13 +20,14 @@ #pragma once #include "database/Object.hpp" + #include "SubsonicResponse.hpp" namespace lms::db { class Artist; class TrackArtistLink; -} +} // namespace lms::db namespace lms::api::subsonic { diff --git a/src/libs/subsonic/impl/responses/DiscTitle.cpp b/src/libs/subsonic/impl/responses/DiscTitle.cpp index 1ba79f2f4..517eb0e2b 100644 --- a/src/libs/subsonic/impl/responses/DiscTitle.cpp +++ b/src/libs/subsonic/impl/responses/DiscTitle.cpp @@ -30,4 +30,4 @@ namespace lms::api::subsonic return discTitleNode; } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/DiscTitle.hpp b/src/libs/subsonic/impl/responses/DiscTitle.hpp index 7881ee382..9ab6aeae1 100644 --- a/src/libs/subsonic/impl/responses/DiscTitle.hpp +++ b/src/libs/subsonic/impl/responses/DiscTitle.hpp @@ -20,6 +20,7 @@ #pragma once #include "database/Types.hpp" + #include "SubsonicResponse.hpp" namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/responses/Genre.cpp b/src/libs/subsonic/impl/responses/Genre.cpp index 6b4fb015b..308fe29f6 100644 --- a/src/libs/subsonic/impl/responses/Genre.cpp +++ b/src/libs/subsonic/impl/responses/Genre.cpp @@ -33,4 +33,4 @@ namespace lms::api::subsonic return clusterNode; } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Genre.hpp b/src/libs/subsonic/impl/responses/Genre.hpp index 15890c1e2..450176255 100644 --- a/src/libs/subsonic/impl/responses/Genre.hpp +++ b/src/libs/subsonic/impl/responses/Genre.hpp @@ -20,6 +20,7 @@ #pragma once #include "database/Object.hpp" + #include "SubsonicResponse.hpp" namespace lms::db diff --git a/src/libs/subsonic/impl/responses/ItemDate.cpp b/src/libs/subsonic/impl/responses/ItemDate.cpp index 1f6c75927..703e25577 100644 --- a/src/libs/subsonic/impl/responses/ItemDate.cpp +++ b/src/libs/subsonic/impl/responses/ItemDate.cpp @@ -40,4 +40,4 @@ namespace lms::api::subsonic return itemDateNode; } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/ItemDate.hpp b/src/libs/subsonic/impl/responses/ItemDate.hpp index 6a9a43200..c071c136c 100644 --- a/src/libs/subsonic/impl/responses/ItemDate.hpp +++ b/src/libs/subsonic/impl/responses/ItemDate.hpp @@ -20,6 +20,7 @@ #pragma once #include + #include "SubsonicResponse.hpp" namespace lms::api::subsonic diff --git a/src/libs/subsonic/impl/responses/ItemGenre.cpp b/src/libs/subsonic/impl/responses/ItemGenre.cpp index c746af276..db4dea6cb 100644 --- a/src/libs/subsonic/impl/responses/ItemGenre.cpp +++ b/src/libs/subsonic/impl/responses/ItemGenre.cpp @@ -31,4 +31,4 @@ namespace lms::api::subsonic return genreNode; } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/ItemGenre.hpp b/src/libs/subsonic/impl/responses/ItemGenre.hpp index 0e7038ff7..c974b3285 100644 --- a/src/libs/subsonic/impl/responses/ItemGenre.hpp +++ b/src/libs/subsonic/impl/responses/ItemGenre.hpp @@ -20,6 +20,7 @@ #pragma once #include + #include "SubsonicResponse.hpp" namespace lms::db diff --git a/src/libs/subsonic/impl/responses/Playlist.cpp b/src/libs/subsonic/impl/responses/Playlist.cpp index 805833634..b0e2e9bbd 100644 --- a/src/libs/subsonic/impl/responses/Playlist.cpp +++ b/src/libs/subsonic/impl/responses/Playlist.cpp @@ -22,6 +22,7 @@ #include "database/Track.hpp" #include "database/TrackList.hpp" #include "database/User.hpp" + #include "SubsonicId.hpp" namespace lms::api::subsonic @@ -42,9 +43,9 @@ namespace lms::api::subsonic playlistNode.setAttribute("created", reportedDummyDate); playlistNode.setAttribute("owner", tracklist->getUser()->getLoginName()); - if (const auto entry {tracklist->getEntry(0)}) + if (const auto entry{ tracklist->getEntry(0) }) playlistNode.setAttribute("coverArt", idToString(entry->getTrack()->getId())); return playlistNode; } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Playlist.hpp b/src/libs/subsonic/impl/responses/Playlist.hpp index 0d62bc0e7..07246674c 100644 --- a/src/libs/subsonic/impl/responses/Playlist.hpp +++ b/src/libs/subsonic/impl/responses/Playlist.hpp @@ -20,13 +20,14 @@ #pragma once #include "database/Object.hpp" + #include "SubsonicResponse.hpp" namespace lms::db { class TrackList; class Session; -} +} // namespace lms::db namespace lms::api::subsonic { diff --git a/src/libs/subsonic/impl/responses/ReplayGain.cpp b/src/libs/subsonic/impl/responses/ReplayGain.cpp index 2fb903055..5fd9d8219 100644 --- a/src/libs/subsonic/impl/responses/ReplayGain.cpp +++ b/src/libs/subsonic/impl/responses/ReplayGain.cpp @@ -35,4 +35,4 @@ namespace lms::api::subsonic return replayGainNode; } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/ReplayGain.hpp b/src/libs/subsonic/impl/responses/ReplayGain.hpp index a19f00ffe..c88f17da3 100644 --- a/src/libs/subsonic/impl/responses/ReplayGain.hpp +++ b/src/libs/subsonic/impl/responses/ReplayGain.hpp @@ -20,6 +20,7 @@ #pragma once #include "database/Object.hpp" + #include "SubsonicResponse.hpp" namespace lms::db diff --git a/src/libs/subsonic/impl/responses/Song.cpp b/src/libs/subsonic/impl/responses/Song.cpp index 1f29a2517..54f4f36f2 100644 --- a/src/libs/subsonic/impl/responses/Song.cpp +++ b/src/libs/subsonic/impl/responses/Song.cpp @@ -22,6 +22,9 @@ #include #include "av/IAudioFile.hpp" +#include "core/ITraceLogger.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/Release.hpp" @@ -30,15 +33,13 @@ #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "core/ITraceLogger.hpp" -#include "core/Service.hpp" -#include "core/String.hpp" + +#include "SubsonicId.hpp" +#include "Utils.hpp" #include "responses/Artist.hpp" #include "responses/Contributor.hpp" #include "responses/ItemGenre.hpp" #include "responses/ReplayGain.hpp" -#include "SubsonicId.hpp" -#include "Utils.hpp" namespace lms::api::subsonic { @@ -50,16 +51,21 @@ namespace lms::api::subsonic { switch (format) { - case TranscodingOutputFormat::MP3: return "mp3"; - case TranscodingOutputFormat::OGG_OPUS: return "opus"; - case TranscodingOutputFormat::MATROSKA_OPUS: return "mka"; - case TranscodingOutputFormat::OGG_VORBIS: return "ogg"; - case TranscodingOutputFormat::WEBM_VORBIS: return "webm"; + case TranscodingOutputFormat::MP3: + return "mp3"; + case TranscodingOutputFormat::OGG_OPUS: + return "opus"; + case TranscodingOutputFormat::MATROSKA_OPUS: + return "mka"; + case TranscodingOutputFormat::OGG_VORBIS: + return "ogg"; + case TranscodingOutputFormat::WEBM_VORBIS: + return "webm"; } return ""; } - } + } // namespace Response::Node createSongNode(RequestContext& context, const Track::pointer& track, const User::pointer& user) { @@ -94,7 +100,7 @@ namespace lms::api::subsonic trackResponse.setAttribute("coverArt", idToString(track->getId())); - const std::vector& artists{ track->getArtists({TrackArtistLinkType::Artist}) }; + const std::vector& artists{ track->getArtists({ TrackArtistLinkType::Artist }) }; if (!artists.empty()) { if (!track->getArtistDisplayName().empty()) @@ -160,18 +166,17 @@ namespace lms::api::subsonic trackResponse.createEmptyArrayChild("artists"); trackResponse.createEmptyArrayChild("contributors"); - TrackArtistLink::find(context.dbSession, track->getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist) - { + TrackArtistLink::find(context.dbSession, track->getId(), [&](const TrackArtistLink::pointer& link, const Artist::pointer& artist) { switch (link->getType()) { - case TrackArtistLinkType::Artist: - trackResponse.addArrayChild("artists", createArtistNode(artist)); - break; - case TrackArtistLinkType::ReleaseArtist: - trackResponse.addArrayChild("albumartists", createArtistNode(artist)); - break; - default: - trackResponse.addArrayChild("contributors", createContributorNode(link, artist)); + case TrackArtistLinkType::Artist: + trackResponse.addArrayChild("artists", createArtistNode(artist)); + break; + case TrackArtistLinkType::ReleaseArtist: + trackResponse.addArrayChild("albumartists", createArtistNode(artist)); + break; + default: + trackResponse.addArrayChild("contributors", createContributorNode(link, artist)); } }); } @@ -180,8 +185,7 @@ namespace lms::api::subsonic if (release) trackResponse.setAttribute("displayAlbumArtist", release->getArtistDisplayName()); - auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName) - { + auto addClusters{ [&](Response::Node::Key field, std::string_view clusterTypeName) { trackResponse.createEmptyArrayValue(field); Cluster::FindParameters params; @@ -204,4 +208,4 @@ namespace lms::api::subsonic return trackResponse; } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/Song.hpp b/src/libs/subsonic/impl/responses/Song.hpp index 4b5101b02..1a91549e5 100644 --- a/src/libs/subsonic/impl/responses/Song.hpp +++ b/src/libs/subsonic/impl/responses/Song.hpp @@ -20,6 +20,7 @@ #pragma once #include "database/Object.hpp" + #include "SubsonicResponse.hpp" namespace lms::db @@ -27,7 +28,7 @@ namespace lms::db class Track; class User; class Session; -} +} // namespace lms::db namespace lms::api::subsonic { diff --git a/src/libs/subsonic/impl/responses/User.cpp b/src/libs/subsonic/impl/responses/User.cpp index 596f7c20b..69c8571ad 100644 --- a/src/libs/subsonic/impl/responses/User.cpp +++ b/src/libs/subsonic/impl/responses/User.cpp @@ -49,4 +49,4 @@ namespace lms::api::subsonic return userNode; } -} \ No newline at end of file +} // namespace lms::api::subsonic \ No newline at end of file diff --git a/src/libs/subsonic/impl/responses/User.hpp b/src/libs/subsonic/impl/responses/User.hpp index b6f23ed2f..9ced3725c 100644 --- a/src/libs/subsonic/impl/responses/User.hpp +++ b/src/libs/subsonic/impl/responses/User.hpp @@ -20,6 +20,7 @@ #pragma once #include "database/Object.hpp" + #include "SubsonicResponse.hpp" namespace lms::db diff --git a/src/libs/subsonic/include/subsonic/SubsonicResource.hpp b/src/libs/subsonic/include/subsonic/SubsonicResource.hpp index c2e89074d..2c52bac4c 100644 --- a/src/libs/subsonic/include/subsonic/SubsonicResource.hpp +++ b/src/libs/subsonic/include/subsonic/SubsonicResource.hpp @@ -24,10 +24,10 @@ namespace lms::db { - class Db; + class Db; } namespace lms::api::subsonic { - std::unique_ptr createSubsonicResource(db::Db& db); -} // namespace + std::unique_ptr createSubsonicResource(db::Db& db); +} // namespace lms::api::subsonic diff --git a/src/lms/main.cpp b/src/lms/main.cpp index 605114aa1..63cb13096 100644 --- a/src/lms/main.cpp +++ b/src/lms/main.cpp @@ -19,19 +19,25 @@ #include +#include +#include #include #include -#include -#include - +#include "core/IChildProcessManager.hpp" +#include "core/IConfig.hpp" +#include "core/IOContextRunner.hpp" +#include "core/ITraceLogger.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" +#include "core/WtLogger.hpp" +#include "database/Db.hpp" +#include "database/Session.hpp" #include "image/Image.hpp" #include "services/auth/IAuthTokenService.hpp" -#include "services/auth/IPasswordService.hpp" #include "services/auth/IEnvService.hpp" +#include "services/auth/IPasswordService.hpp" #include "services/cover/ICoverService.hpp" -#include "database/Db.hpp" -#include "database/Session.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/recommendation/IPlaylistGeneratorService.hpp" #include "services/recommendation/IRecommendationService.hpp" @@ -41,13 +47,6 @@ #include "ui/LmsApplication.hpp" #include "ui/LmsApplicationManager.hpp" #include "ui/LmsInitApplication.hpp" -#include "core/IChildProcessManager.hpp" -#include "core/IConfig.hpp" -#include "core/IOContextRunner.hpp" -#include "core/ITraceLogger.hpp" -#include "core/Service.hpp" -#include "core/String.hpp" -#include "core/WtLogger.hpp" namespace lms { @@ -150,7 +149,6 @@ namespace lms pt.add_child("server.application-settings.head-matter.meta", themeColor); } - { std::ofstream oss{ wtConfigPath.string().c_str(), std::ios::out }; if (!oss) @@ -167,60 +165,48 @@ namespace lms void proxyScannerEventsToApplication(scanner::IScannerService& scanner, Wt::WServer& server) { - auto postAll{ [](Wt::WServer& server, std::function cb) - { - server.postAll([cb = std::move(cb)] - { - // may be nullptr, see https://redmine.webtoolkit.eu/issues/8202 - if (LmsApp) - cb(); - }); - } }; + auto postAll{ [](Wt::WServer& server, std::function cb) { + server.postAll([cb = std::move(cb)] { + // may be nullptr, see https://redmine.webtoolkit.eu/issues/8202 + if (LmsApp) + cb(); + }); + } }; - scanner.getEvents().scanAborted.connect([&] - { - postAll(server, [] - { - LmsApp->getScannerEvents().scanAborted.emit(); - LmsApp->triggerUpdate(); - }); + scanner.getEvents().scanAborted.connect([&] { + postAll(server, [] { + LmsApp->getScannerEvents().scanAborted.emit(); + LmsApp->triggerUpdate(); }); + }); - scanner.getEvents().scanStarted.connect([&] - { - postAll(server, [] - { - LmsApp->getScannerEvents().scanStarted.emit(); - LmsApp->triggerUpdate(); - }); + scanner.getEvents().scanStarted.connect([&] { + postAll(server, [] { + LmsApp->getScannerEvents().scanStarted.emit(); + LmsApp->triggerUpdate(); }); + }); - scanner.getEvents().scanComplete.connect([&](const scanner::ScanStats& stats) - { - postAll(server, [=] - { - LmsApp->getScannerEvents().scanComplete.emit(stats); - LmsApp->triggerUpdate(); - }); + scanner.getEvents().scanComplete.connect([&](const scanner::ScanStats& stats) { + postAll(server, [=] { + LmsApp->getScannerEvents().scanComplete.emit(stats); + LmsApp->triggerUpdate(); }); + }); - scanner.getEvents().scanInProgress.connect([&](const scanner::ScanStepStats& stats) - { - postAll(server, [=] - { - LmsApp->getScannerEvents().scanInProgress.emit(stats); - LmsApp->triggerUpdate(); - }); + scanner.getEvents().scanInProgress.connect([&](const scanner::ScanStepStats& stats) { + postAll(server, [=] { + LmsApp->getScannerEvents().scanInProgress.emit(stats); + LmsApp->triggerUpdate(); }); + }); - scanner.getEvents().scanScheduled.connect([&](const Wt::WDateTime dateTime) - { - postAll(server, [=] - { - LmsApp->getScannerEvents().scanScheduled.emit(dateTime); - LmsApp->triggerUpdate(); - }); + scanner.getEvents().scanScheduled.connect([&](const Wt::WDateTime dateTime) { + postAll(server, [=] { + LmsApp->getScannerEvents().scanScheduled.emit(dateTime); + LmsApp->triggerUpdate(); }); + }); } int main(int argc, char* argv[]) @@ -236,8 +222,8 @@ namespace lms else if (argc > 2) { std::cerr << "Usage:\t" << argv[0] << "\t[conf_file]\n\n" - << "Options:\n" - << "\tconf_file:\t path to the LMS configuration file (defaults to " << configFilePath << ")\n\n"; + << "Options:\n" + << "\tconf_file:\t path to the LMS configuration file (defaults to " << configFilePath << ")\n\n"; return EXIT_FAILURE; } @@ -278,8 +264,7 @@ namespace lms // As initialization can take a while (db migration, analyze, etc.), we bind a temporary init entry point to warn the user server.addEntryPoint(Wt::EntryPointType::Application, - [&](const Wt::WEnvironment& env) - { + [&](const Wt::WEnvironment& env) { return ui::LmsInitApplication::create(env); }); @@ -307,7 +292,7 @@ namespace lms session.fullAnalyze(); database.getTLSSession().refreshTracingLoggerStats(); } - + ui::LmsApplicationManager appManager; // Service initialization order is important (reverse-order for deinit) @@ -335,13 +320,12 @@ namespace lms core::Service playlistGeneratorService{ recommendation::createPlaylistGeneratorService(database, *recommendationService.get()) }; core::Service scannerService{ scanner::createScannerService(database) }; - scannerService->getEvents().scanComplete.connect([&] - { - // Flush cover cache even if no changes: - // covers may be external files that changed and we don't keep track of them for now (but we should) - coverService->flushCache(); - database.getTLSSession().refreshTracingLoggerStats(); - }); + scannerService->getEvents().scanComplete.connect([&] { + // Flush cover cache even if no changes: + // covers may be external files that changed and we don't keep track of them for now (but we should) + coverService->flushCache(); + database.getTLSSession().refreshTracingLoggerStats(); + }); core::Service feedbackService{ feedback::createFeedbackService(ioContext, database) }; core::Service scrobblingService{ scrobbling::createScrobblingService(ioContext, database) }; @@ -361,8 +345,7 @@ namespace lms // bind UI entry point server.addEntryPoint(Wt::EntryPointType::Application, - [&](const Wt::WEnvironment& env) - { + [&](const Wt::WEnvironment& env) { return ui::LmsApplication::create(env, database, appManager); }); @@ -395,7 +378,7 @@ namespace lms return res; } -} +} // namespace lms int main(int argc, char* argv[]) { diff --git a/src/lms/ui/Auth.cpp b/src/lms/ui/Auth.cpp index 8adea6853..9e7448024 100644 --- a/src/lms/ui/Auth.cpp +++ b/src/lms/ui/Auth.cpp @@ -19,24 +19,24 @@ #include "Auth.hpp" +#include #include #include #include -#include #include #include -#include "services/auth/IAuthTokenService.hpp" -#include "services/auth/IPasswordService.hpp" -#include "database/Session.hpp" -#include "database/User.hpp" #include "core/ILogger.hpp" #include "core/Service.hpp" +#include "database/Session.hpp" +#include "database/User.hpp" +#include "services/auth/IAuthTokenService.hpp" +#include "services/auth/IPasswordService.hpp" +#include "LmsApplication.hpp" #include "common/LoginNameValidator.hpp" #include "common/MandatoryValidator.hpp" #include "common/PasswordValidator.hpp" -#include "LmsApplication.hpp" namespace lms::ui { @@ -74,7 +74,6 @@ namespace lms::ui setValidator(PasswordField, createMandatoryValidator()); } - void saveData() { bool isDemo; @@ -103,9 +102,9 @@ namespace lms::ui if (field == PasswordField) { const auto checkResult{ core::Service::get()->checkUserPassword( - boost::asio::ip::address::from_string(LmsApp->environment().clientAddress()), - valueText(LoginNameField).toUTF8(), - valueText(PasswordField).toUTF8()) }; + boost::asio::ip::address::from_string(LmsApp->environment().clientAddress()), + valueText(LoginNameField).toUTF8(), + valueText(PasswordField).toUTF8()) }; switch (checkResult.state) { case auth::IPasswordService::CheckResult::State::Granted: @@ -138,7 +137,7 @@ namespace lms::ui const AuthModel::Field AuthModel::LoginNameField{ "login-name" }; const AuthModel::Field AuthModel::PasswordField{ "password" }; const AuthModel::Field AuthModel::RememberMeField{ "remember-me" }; - } + } // namespace std::optional processAuthToken(const Wt::WEnvironment& env) { @@ -162,24 +161,22 @@ namespace lms::ui return res.authTokenInfo->userId; } - Auth::Auth() : Wt::WTemplateFormView{ Wt::WString::tr("Lms.Auth.template") } { auto model{ std::make_shared() }; - auto processAuth = [this, model] - { - updateModel(model.get()); + auto processAuth = [this, model] { + updateModel(model.get()); - if (model->validate()) - { - model->saveData(); - userLoggedIn.emit(*model->getUserId()); - } - else - updateView(model.get()); - }; + if (model->validate()) + { + model->saveData(); + userLoggedIn.emit(*model->getUserId()); + } + else + updateView(model.get()); + }; // LoginName auto loginName{ std::make_unique() }; @@ -212,4 +209,4 @@ namespace lms::ui updateView(model.get()); } -} +} // namespace lms::ui diff --git a/src/lms/ui/Auth.hpp b/src/lms/ui/Auth.hpp index 8a549ca37..b2383620f 100644 --- a/src/lms/ui/Auth.hpp +++ b/src/lms/ui/Auth.hpp @@ -20,19 +20,20 @@ #pragma once #include + #include #include "database/UserId.hpp" namespace lms::ui { - std::optional processAuthToken(const Wt::WEnvironment& env); + std::optional processAuthToken(const Wt::WEnvironment& env); - class Auth : public Wt::WTemplateFormView - { - public: - Auth(); + class Auth : public Wt::WTemplateFormView + { + public: + Auth(); - Wt::Signal userLoggedIn; - }; + Wt::Signal userLoggedIn; + }; } // namespace lms::ui diff --git a/src/lms/ui/LmsApplication.cpp b/src/lms/ui/LmsApplication.cpp index 4382f9c63..8624bd38a 100644 --- a/src/lms/ui/LmsApplication.cpp +++ b/src/lms/ui/LmsApplication.cpp @@ -26,9 +26,10 @@ #include #include -#include "services/auth/IEnvService.hpp" -#include "services/auth/IPasswordService.hpp" -#include "services/cover/ICoverService.hpp" +#include "core/ILogger.hpp" +#include "core/ITraceLogger.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/Db.hpp" @@ -36,17 +37,25 @@ #include "database/Session.hpp" #include "database/TrackList.hpp" #include "database/User.hpp" +#include "services/auth/IEnvService.hpp" +#include "services/auth/IPasswordService.hpp" +#include "services/cover/ICoverService.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "core/ILogger.hpp" -#include "core/ITraceLogger.hpp" -#include "core/Service.hpp" -#include "core/String.hpp" +#include "Auth.hpp" +#include "LmsApplicationException.hpp" +#include "LmsApplicationManager.hpp" +#include "LmsTheme.hpp" +#include "MediaPlayer.hpp" +#include "ModalManager.hpp" +#include "NotificationContainer.hpp" +#include "PlayQueue.hpp" +#include "SettingsView.hpp" #include "admin/InitWizardView.hpp" #include "admin/MediaLibrariesView.hpp" -#include "admin/TracingView.hpp" -#include "admin/ScannerController.hpp" #include "admin/ScanSettingsView.hpp" +#include "admin/ScannerController.hpp" +#include "admin/TracingView.hpp" #include "admin/UserView.hpp" #include "admin/UsersView.hpp" #include "common/Template.hpp" @@ -55,15 +64,6 @@ #include "resource/AudioFileResource.hpp" #include "resource/AudioTranscodingResource.hpp" #include "resource/CoverResource.hpp" -#include "Auth.hpp" -#include "LmsApplicationException.hpp" -#include "LmsApplicationManager.hpp" -#include "LmsTheme.hpp" -#include "MediaPlayer.hpp" -#include "ModalManager.hpp" -#include "NotificationContainer.hpp" -#include "PlayQueue.hpp" -#include "SettingsView.hpp" namespace lms::ui { @@ -132,23 +132,22 @@ namespace lms::ui int index; bool admin; std::optional title; - } views[] = - { - { "/artists", IdxExplore, false, Wt::WString::tr("Lms.Explore.artists") }, - { "/artist", IdxExplore, false, std::nullopt }, - { "/releases", IdxExplore, false, Wt::WString::tr("Lms.Explore.releases") }, - { "/release", IdxExplore, false, std::nullopt }, - { "/tracks", IdxExplore, false, Wt::WString::tr("Lms.Explore.tracks") }, - { "/tracklists", IdxExplore, false, Wt::WString::tr("Lms.Explore.tracklists") }, - { "/tracklist", IdxExplore, false, std::nullopt }, - { "/playqueue", IdxPlayQueue, false, Wt::WString::tr("Lms.PlayQueue.playqueue") }, - { "/settings", IdxSettings, false, Wt::WString::tr("Lms.Settings.settings") }, - { "/admin/libraries", IdxAdminLibraries, true, Wt::WString::tr("Lms.Admin.MediaLibraries.media-libraries") }, - { "/admin/scan-settings", IdxAdminScanSettings, true, Wt::WString::tr("Lms.Admin.Database.scan-settings") }, - { "/admin/scanner", IdxAdminScanner, true, Wt::WString::tr("Lms.Admin.ScannerController.scanner") }, - { "/admin/users", IdxAdminUsers, true, Wt::WString::tr("Lms.Admin.Users.users") }, - { "/admin/user", IdxAdminUser, true, std::nullopt }, - { "/admin/tracing", IdxAdminTracing, true, Wt::WString::tr("Lms.Admin.Tracing.tracing") }, + } views[] = { + { "/artists", IdxExplore, false, Wt::WString::tr("Lms.Explore.artists") }, + { "/artist", IdxExplore, false, std::nullopt }, + { "/releases", IdxExplore, false, Wt::WString::tr("Lms.Explore.releases") }, + { "/release", IdxExplore, false, std::nullopt }, + { "/tracks", IdxExplore, false, Wt::WString::tr("Lms.Explore.tracks") }, + { "/tracklists", IdxExplore, false, Wt::WString::tr("Lms.Explore.tracklists") }, + { "/tracklist", IdxExplore, false, std::nullopt }, + { "/playqueue", IdxPlayQueue, false, Wt::WString::tr("Lms.PlayQueue.playqueue") }, + { "/settings", IdxSettings, false, Wt::WString::tr("Lms.Settings.settings") }, + { "/admin/libraries", IdxAdminLibraries, true, Wt::WString::tr("Lms.Admin.MediaLibraries.media-libraries") }, + { "/admin/scan-settings", IdxAdminScanSettings, true, Wt::WString::tr("Lms.Admin.Database.scan-settings") }, + { "/admin/scanner", IdxAdminScanner, true, Wt::WString::tr("Lms.Admin.ScannerController.scanner") }, + { "/admin/users", IdxAdminUsers, true, Wt::WString::tr("Lms.Admin.Users.users") }, + { "/admin/user", IdxAdminUser, true, std::nullopt }, + { "/admin/tracing", IdxAdminTracing, true, Wt::WString::tr("Lms.Admin.Tracing.tracing") }, }; LMS_LOG(UI, DEBUG, "Internal path changed to '" << wApp->internalPath() << "'"); @@ -171,11 +170,11 @@ namespace lms::ui wApp->setInternalPath(defaultPath, true); } - } + } // namespace std::unique_ptr LmsApplication::create(const Wt::WEnvironment& env, db::Db& db, LmsApplicationManager& appManager) { - if (auto * authEnvService{ core::Service::get() }) + if (auto* authEnvService{ core::Service::get() }) { const auto checkResult{ authEnvService->processEnv(env) }; if (checkResult.state != auth::IEnvService::CheckResult::State::Granted) @@ -245,7 +244,7 @@ namespace lms::ui : Wt::WApplication{ env } , _db{ db } , _appManager{ appManager } - , _authenticatedUser{ userId ? std::make_optional(UserAuthInfo {*userId, false}) : std::nullopt } + , _authenticatedUser{ userId ? std::make_optional(UserAuthInfo{ *userId, false }) : std::nullopt } { try { @@ -315,11 +314,10 @@ namespace lms::ui else { Auth* auth{ root()->addNew() }; - auth->userLoggedIn.connect(this, [this](db::UserId userId) - { - _authenticatedUser = { userId, true }; - onUserLoggedIn(); - }); + auth->userLoggedIn.connect(this, [this](db::UserId userId) { + _authenticatedUser = { userId, true }; + onUserLoggedIn(); + }); } } @@ -339,10 +337,9 @@ namespace lms::ui t->bindString("error", e.what(), Wt::TextFormat::Plain); Wt::WPushButton* btn{ t->bindNew("btn-go-home", Wt::WString::tr("Lms.Error.go-home")) }; - btn->clicked().connect([this]() - { - redirect(defaultPath); - }); + btn->clicked().connect([this]() { + redirect(defaultPath); + }); } void LmsApplication::goHomeAndQuit() @@ -369,17 +366,16 @@ namespace lms::ui LMS_LOG(UI, INFO, "User '" << getUserLoginName() << "' logged in from '" << environment().clientAddress() << "', user agent = " << environment().userAgent()); _appManager.registerApplication(*this); - _appManager.applicationRegistered.connect(this, [this](LmsApplication& otherApplication) + _appManager.applicationRegistered.connect(this, [this](LmsApplication& otherApplication) { + // Only one active session by user + if (otherApplication.getUserId() == getUserId()) { - // Only one active session by user - if (otherApplication.getUserId() == getUserId()) + if (LmsApp->getUserType() != db::UserType::DEMO) { - if (LmsApp->getUserType() != db::UserType::DEMO) - { - quit(Wt::WString::tr("Lms.quit-other-session")); - } + quit(Wt::WString::tr("Lms.quit-other-session")); } - }); + } + }); createHome(); } @@ -471,73 +467,63 @@ namespace lms::ui explore->getPlayQueueController().setMaxTrackCountToEnqueue(_playQueue->getCapacity()); // Events from MediaPlayer - _mediaPlayer->playNext.connect([this] - { - LMS_LOG(UI, DEBUG, "Received playNext from player"); - _playQueue->playNext(); - }); - _mediaPlayer->playPrevious.connect([this] - { - LMS_LOG(UI, DEBUG, "Received playPrevious from player"); - _playQueue->playPrevious(); - }); - - _mediaPlayer->scrobbleListenNow.connect([this](db::TrackId trackId) - { - LMS_LOG(UI, DEBUG, "Received ScrobbleListenNow from player for trackId = " << trackId.toString()); - const scrobbling::Listen listen{ getUserId(), trackId }; - core::Service::get()->listenStarted(listen); - }); - _mediaPlayer->scrobbleListenFinished.connect([this](db::TrackId trackId, unsigned durationMs) - { - LMS_LOG(UI, DEBUG, "Received ScrobbleListenFinished from player for trackId = " << trackId.toString() << ", duration = " << (durationMs / 1000) << "s"); - const std::chrono::milliseconds duration{ durationMs }; - const scrobbling::Listen listen{ getUserId(), trackId }; - core::Service::get()->listenFinished(listen, std::chrono::duration_cast(duration)); - }); - - _mediaPlayer->playbackEnded.connect([this] - { - LMS_LOG(UI, DEBUG, "Received playbackEnded from player"); - _playQueue->onPlaybackEnded(); - }); - - _playQueue->trackSelected.connect([this](db::TrackId trackId, bool play, float replayGain) - { - _mediaPlayer->loadTrack(trackId, play, replayGain); - }); - - _playQueue->trackUnselected.connect([this] - { - _mediaPlayer->stop(); - }); - _playQueue->trackCountChanged.connect([this](std::size_t trackCount) - { - _mediaPlayer->onPlayQueueUpdated(trackCount); - }); + _mediaPlayer->playNext.connect([this] { + LMS_LOG(UI, DEBUG, "Received playNext from player"); + _playQueue->playNext(); + }); + _mediaPlayer->playPrevious.connect([this] { + LMS_LOG(UI, DEBUG, "Received playPrevious from player"); + _playQueue->playPrevious(); + }); + + _mediaPlayer->scrobbleListenNow.connect([this](db::TrackId trackId) { + LMS_LOG(UI, DEBUG, "Received ScrobbleListenNow from player for trackId = " << trackId.toString()); + const scrobbling::Listen listen{ getUserId(), trackId }; + core::Service::get()->listenStarted(listen); + }); + _mediaPlayer->scrobbleListenFinished.connect([this](db::TrackId trackId, unsigned durationMs) { + LMS_LOG(UI, DEBUG, "Received ScrobbleListenFinished from player for trackId = " << trackId.toString() << ", duration = " << (durationMs / 1000) << "s"); + const std::chrono::milliseconds duration{ durationMs }; + const scrobbling::Listen listen{ getUserId(), trackId }; + core::Service::get()->listenFinished(listen, std::chrono::duration_cast(duration)); + }); + + _mediaPlayer->playbackEnded.connect([this] { + LMS_LOG(UI, DEBUG, "Received playbackEnded from player"); + _playQueue->onPlaybackEnded(); + }); + + _playQueue->trackSelected.connect([this](db::TrackId trackId, bool play, float replayGain) { + _mediaPlayer->loadTrack(trackId, play, replayGain); + }); + + _playQueue->trackUnselected.connect([this] { + _mediaPlayer->stop(); + }); + _playQueue->trackCountChanged.connect([this](std::size_t trackCount) { + _mediaPlayer->onPlayQueueUpdated(trackCount); + }); _mediaPlayer->onPlayQueueUpdated(_playQueue->getCount()); const bool isAdmin{ getUserType() == db::UserType::ADMIN }; if (isAdmin) { - _scannerEvents.scanComplete.connect([this](const scanner::ScanStats& stats) - { - notifyMsg(Notification::Type::Info, + _scannerEvents.scanComplete.connect([this](const scanner::ScanStats& stats) { + notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.Database.database"), Wt::WString::tr("Lms.Admin.Database.scan-complete") - .arg(static_cast(stats.nbFiles())) + .arg(static_cast(stats.nbFiles())) .arg(static_cast(stats.additions)) .arg(static_cast(stats.updates)) .arg(static_cast(stats.deletions)) .arg(static_cast(stats.duplicates.size())) .arg(static_cast(stats.errors.size()))); - }); + }); } - internalPathChanged().connect(mainStack, [=] - { - handlePathChange(*mainStack, isAdmin); - }); + internalPathChanged().connect(mainStack, [=] { + handlePathChange(*mainStack, isAdmin); + }); handlePathChange(*mainStack, isAdmin); } diff --git a/src/lms/ui/LmsApplication.hpp b/src/lms/ui/LmsApplication.hpp index 68ab0d685..141e442c9 100644 --- a/src/lms/ui/LmsApplication.hpp +++ b/src/lms/ui/LmsApplication.hpp @@ -24,18 +24,19 @@ #include #include "database/Object.hpp" -#include "database/UserId.hpp" #include "database/Types.hpp" +#include "database/UserId.hpp" #include "services/scanner/ScannerEvents.hpp" -#include "admin/ScannerController.hpp" + #include "Notification.hpp" +#include "admin/ScannerController.hpp" namespace lms::db { class Db; class Session; class User; -} +} // namespace lms::db namespace lms::ui { @@ -61,11 +62,11 @@ namespace lms::ui db::Db& getDb(); db::Session& getDbSession(); // always thread safe - db::ObjectPtr getUser(); - db::UserId getUserId(); - bool isUserAuthStrong() const; // user must be logged in prior this call - db::UserType getUserType(); // user must be logged in prior this call - std::string getUserLoginName(); // user must be logged in prior this call + db::ObjectPtr getUser(); + db::UserId getUserId(); + bool isUserAuthStrong() const; // user must be logged in prior this call + db::UserType getUserType(); // user must be logged in prior this call + std::string getUserLoginName(); // user must be logged in prior this call // Proxified scanner events scanner::Events& getScannerEvents() { return _scannerEvents; } @@ -100,25 +101,23 @@ namespace lms::ui void createHome(); db::Db& _db; - Wt::Signal<> _preQuit; + Wt::Signal<> _preQuit; LmsApplicationManager& _appManager; - scanner::Events _scannerEvents; + scanner::Events _scannerEvents; struct UserAuthInfo { - db::UserId userId; - bool strongAuth{}; + db::UserId userId; + bool strongAuth{}; }; - std::optional _authenticatedUser; - std::shared_ptr _coverResource; + std::optional _authenticatedUser; + std::shared_ptr _coverResource; MediaPlayer* _mediaPlayer{}; PlayQueue* _playQueue{}; NotificationContainer* _notificationContainer{}; ModalManager* _modalManager{}; }; - // Helper to get session instance -#define LmsApp lms::ui::LmsApplication::instance() +#define LmsApp lms::ui::LmsApplication::instance() } // namespace lms::ui - diff --git a/src/lms/ui/LmsApplicationException.hpp b/src/lms/ui/LmsApplicationException.hpp index 8b07956b9..1b986aeec 100644 --- a/src/lms/ui/LmsApplicationException.hpp +++ b/src/lms/ui/LmsApplicationException.hpp @@ -19,44 +19,50 @@ #pragma once -#include "database/Types.hpp" #include "core/Exception.hpp" +#include "database/Types.hpp" namespace lms::ui { - class LmsApplicationException : public core::LmsException - { - public: - LmsApplicationException(const Wt::WString& error) : LmsException {error.toUTF8()} {} - }; - - class ArtistNotFoundException : public LmsApplicationException - { - public: - ArtistNotFoundException() : LmsApplicationException {Wt::WString::tr("Lms.Error.artist-not-found")} {} - }; - - class ReleaseNotFoundException : public LmsApplicationException - { - public: - ReleaseNotFoundException() : LmsApplicationException {Wt::WString::tr("Lms.Error.release-not-found")} {} - }; - - class TrackListNotFoundException : public LmsApplicationException - { - public: - TrackListNotFoundException() : LmsApplicationException {Wt::WString::tr("Lms.Error.tracklist-not-found")} {} - }; - - class UserNotFoundException : public LmsApplicationException - { - public: - UserNotFoundException() : LmsApplicationException {Wt::WString::tr("Lms.Error.user-not-found")} {} - }; - - class UserNotAllowedException : public LmsApplicationException - { - public: - UserNotAllowedException() : LmsApplicationException {Wt::WString::tr("Lms.Error.user-not-allowed")} {} - }; -} // ns UserInterface + class LmsApplicationException : public core::LmsException + { + public: + LmsApplicationException(const Wt::WString& error) + : LmsException{ error.toUTF8() } {} + }; + + class ArtistNotFoundException : public LmsApplicationException + { + public: + ArtistNotFoundException() + : LmsApplicationException{ Wt::WString::tr("Lms.Error.artist-not-found") } {} + }; + + class ReleaseNotFoundException : public LmsApplicationException + { + public: + ReleaseNotFoundException() + : LmsApplicationException{ Wt::WString::tr("Lms.Error.release-not-found") } {} + }; + + class TrackListNotFoundException : public LmsApplicationException + { + public: + TrackListNotFoundException() + : LmsApplicationException{ Wt::WString::tr("Lms.Error.tracklist-not-found") } {} + }; + + class UserNotFoundException : public LmsApplicationException + { + public: + UserNotFoundException() + : LmsApplicationException{ Wt::WString::tr("Lms.Error.user-not-found") } {} + }; + + class UserNotAllowedException : public LmsApplicationException + { + public: + UserNotAllowedException() + : LmsApplicationException{ Wt::WString::tr("Lms.Error.user-not-allowed") } {} + }; +} // namespace lms::ui diff --git a/src/lms/ui/LmsApplicationManager.cpp b/src/lms/ui/LmsApplicationManager.cpp index 73546f164..f77e43fe2 100644 --- a/src/lms/ui/LmsApplicationManager.cpp +++ b/src/lms/ui/LmsApplicationManager.cpp @@ -23,26 +23,24 @@ namespace lms::ui { - void - LmsApplicationManager::registerApplication(LmsApplication& application) - { - { - std::scoped_lock lock {_mutex}; - m_applications[application.getUserId()].insert(&application); - } + void LmsApplicationManager::registerApplication(LmsApplication& application) + { + { + std::scoped_lock lock{ _mutex }; + m_applications[application.getUserId()].insert(&application); + } - applicationRegistered.emit(application); - } + applicationRegistered.emit(application); + } - void - LmsApplicationManager::unregisterApplication(LmsApplication& application) - { - { - std::scoped_lock lock {_mutex}; - m_applications[application.getUserId()].erase(&application); - } + void LmsApplicationManager::unregisterApplication(LmsApplication& application) + { + { + std::scoped_lock lock{ _mutex }; + m_applications[application.getUserId()].erase(&application); + } - applicationUnregistered.emit(application); - } + applicationUnregistered.emit(application); + } -} // UserInterface +} // namespace lms::ui diff --git a/src/lms/ui/LmsApplicationManager.hpp b/src/lms/ui/LmsApplicationManager.hpp index d9d760ca4..92c141894 100644 --- a/src/lms/ui/LmsApplicationManager.hpp +++ b/src/lms/ui/LmsApplicationManager.hpp @@ -20,8 +20,8 @@ #pragma once #include -#include #include +#include #include @@ -29,20 +29,20 @@ namespace lms::ui { - class LmsApplication; - class LmsApplicationManager - { - public: - Wt::Signal applicationRegistered; - Wt::Signal applicationUnregistered; - - private: - friend class LmsApplication; - - void registerApplication(LmsApplication& application); - void unregisterApplication(LmsApplication& application); - - std::mutex _mutex; - std::unordered_map> m_applications; - }; -} // UserInterface + class LmsApplication; + class LmsApplicationManager + { + public: + Wt::Signal applicationRegistered; + Wt::Signal applicationUnregistered; + + private: + friend class LmsApplication; + + void registerApplication(LmsApplication& application); + void unregisterApplication(LmsApplication& application); + + std::mutex _mutex; + std::unordered_map> m_applications; + }; +} // namespace lms::ui diff --git a/src/lms/ui/LmsTheme.cpp b/src/lms/ui/LmsTheme.cpp index f59a04a55..2f1a59bac 100644 --- a/src/lms/ui/LmsTheme.cpp +++ b/src/lms/ui/LmsTheme.cpp @@ -12,48 +12,42 @@ namespace lms::ui { - void - LmsTheme::init(Wt::WApplication* app) const - { - app->require("js/bootstrap.bundle.min.js"); - } - - std::string - LmsTheme::name() const - { - return "bootstrap5"; - } - - std::string - LmsTheme::resourcesUrl() const - { - return ""; - } - - std::vector - LmsTheme::styleSheets() const - { - static const std::vector files - { - {"css/bootstrap.solar.min.css"}, // TODO parametrize this - {"css/lms.css"}, - }; - - return files; - } - - void LmsTheme::applyValidationStyle(Wt::WWidget* widget, const Wt::WValidator::Result& validation, Wt::WFlags styles) const - { - { - const bool validStyle {(validation.state() == Wt::ValidationState::Valid) && styles.test(Wt::ValidationStyleFlag::ValidStyle)}; - widget->toggleStyleClass("is-valid", validStyle); - } - - { - const bool invalidStyle {(validation.state() != Wt::ValidationState::Valid) && styles.test(Wt::ValidationStyleFlag::InvalidStyle)}; - widget->toggleStyleClass("is-invalid", invalidStyle); - } - } + void LmsTheme::init(Wt::WApplication* app) const + { + app->require("js/bootstrap.bundle.min.js"); + } + + std::string LmsTheme::name() const + { + return "bootstrap5"; + } + + std::string LmsTheme::resourcesUrl() const + { + return ""; + } + + std::vector LmsTheme::styleSheets() const + { + static const std::vector files{ + { "css/bootstrap.solar.min.css" }, // TODO parametrize this + { "css/lms.css" }, + }; + + return files; + } + + void LmsTheme::applyValidationStyle(Wt::WWidget* widget, const Wt::WValidator::Result& validation, Wt::WFlags styles) const + { + { + const bool validStyle{ (validation.state() == Wt::ValidationState::Valid) && styles.test(Wt::ValidationStyleFlag::ValidStyle) }; + widget->toggleStyleClass("is-valid", validStyle); + } + + { + const bool invalidStyle{ (validation.state() != Wt::ValidationState::Valid) && styles.test(Wt::ValidationStyleFlag::InvalidStyle) }; + widget->toggleStyleClass("is-invalid", invalidStyle); + } + } } // namespace lms::ui - diff --git a/src/lms/ui/LmsTheme.hpp b/src/lms/ui/LmsTheme.hpp index 93cb06a58..59009fd7f 100644 --- a/src/lms/ui/LmsTheme.hpp +++ b/src/lms/ui/LmsTheme.hpp @@ -23,23 +23,23 @@ namespace lms::ui { - class LmsTheme : public Wt::WTheme - { - private: - void init(Wt::WApplication *app) const override; + class LmsTheme : public Wt::WTheme + { + private: + void init(Wt::WApplication* app) const override; - std::string name() const override; - std::string resourcesUrl() const override; - std::vector styleSheets() const override; - void apply(Wt::WWidget*, Wt::WWidget*, int) const override {}; - void apply(Wt::WWidget*, Wt::DomElement&, int) const override {}; - std::string disabledClass() const override { return "disabled"; } - std::string activeClass() const override { return "active"; }; - std::string utilityCssClass(int) const override { return ""; }; - bool canStyleAnchorAsButton() const override { return true; }; - void applyValidationStyle(Wt::WWidget* widget, - const Wt::WValidator::Result& validation, - Wt::WFlags flags) const override; - bool canBorderBoxElement(const Wt::DomElement&) const override { return true; } - }; -} + std::string name() const override; + std::string resourcesUrl() const override; + std::vector styleSheets() const override; + void apply(Wt::WWidget*, Wt::WWidget*, int) const override{}; + void apply(Wt::WWidget*, Wt::DomElement&, int) const override{}; + std::string disabledClass() const override { return "disabled"; } + std::string activeClass() const override { return "active"; }; + std::string utilityCssClass(int) const override { return ""; }; + bool canStyleAnchorAsButton() const override { return true; }; + void applyValidationStyle(Wt::WWidget* widget, + const Wt::WValidator::Result& validation, + Wt::WFlags flags) const override; + bool canBorderBoxElement(const Wt::DomElement&) const override { return true; } + }; +} // namespace lms::ui diff --git a/src/lms/ui/MediaPlayer.cpp b/src/lms/ui/MediaPlayer.cpp index ae62ba2d2..0897f8241 100644 --- a/src/lms/ui/MediaPlayer.cpp +++ b/src/lms/ui/MediaPlayer.cpp @@ -20,12 +20,13 @@ #include "MediaPlayer.hpp" #include -#include #include +#include #include #include "core/ILogger.hpp" - +#include "core/String.hpp" +#include "core/Utils.hpp" #include "database/Artist.hpp" #include "database/Release.hpp" #include "database/Session.hpp" @@ -34,15 +35,11 @@ #include "database/Types.hpp" #include "database/User.hpp" -#include "resource/CoverResource.hpp" -#include "resource/AudioTranscodingResource.hpp" -#include "resource/AudioFileResource.hpp" - -#include "core/String.hpp" -#include "core/Utils.hpp" - #include "LmsApplication.hpp" #include "Utils.hpp" +#include "resource/AudioFileResource.hpp" +#include "resource/AudioTranscodingResource.hpp" +#include "resource/CoverResource.hpp" namespace lms::ui { @@ -184,7 +181,7 @@ namespace lms::ui return settings; } - } + } // namespace MediaPlayer::MediaPlayer() : Wt::WTemplate{ Wt::WString::tr("Lms.MediaPlayer.template") } @@ -208,27 +205,26 @@ namespace lms::ui _playQueue->setLink(Wt::WLink{ Wt::LinkType::InternalPath, "/playqueue" }); _playQueue->setToolTip(tr("Lms.PlayQueue.playqueue")); - _settingsLoaded.connect([this](const std::string& settings) - { - LMS_LOG(UI, DEBUG, "Settings loaded! '" << settings << "'"); + _settingsLoaded.connect([this](const std::string& settings) { + LMS_LOG(UI, DEBUG, "Settings loaded! '" << settings << "'"); - _settings = settingsfromJSString(settings); + _settings = settingsfromJSString(settings); - settingsLoaded.emit(); - }); + settingsLoaded.emit(); + }); - { - Settings defaultSettings; + { + Settings defaultSettings; - std::ostringstream oss; - oss << "LMS.mediaplayer.init(" - << jsRef() - << ", defaultSettings = " << settingsToJSString(defaultSettings) - << ")"; + std::ostringstream oss; + oss << "LMS.mediaplayer.init(" + << jsRef() + << ", defaultSettings = " << settingsToJSString(defaultSettings) + << ")"; - LMS_LOG(UI, DEBUG, "Running js = '" << oss.str() << "'"); - doJavaScript(oss.str()); - } + LMS_LOG(UI, DEBUG, "Running js = '" << oss.str() << "'"); + doJavaScript(oss.str()); + } } void MediaPlayer::loadTrack(db::TrackId trackId, bool play, float replayGain) @@ -246,7 +242,7 @@ namespace lms::ui const std::string transcodingResource{ _audioTranscodingResource->getUrl(trackId) }; const std::string nativeResource{ _audioFileResource->getUrl(trackId) }; - const auto artists{ track->getArtists({db::TrackArtistLinkType::Artist}) }; + const auto artists{ track->getArtists({ db::TrackArtistLinkType::Artist }) }; oss << "var params = {" diff --git a/src/lms/ui/MediaPlayer.hpp b/src/lms/ui/MediaPlayer.hpp index 88b178dba..5d627c45b 100644 --- a/src/lms/ui/MediaPlayer.hpp +++ b/src/lms/ui/MediaPlayer.hpp @@ -59,9 +59,9 @@ namespace lms::ui static inline constexpr Format defaultFormat{ Format::OGG_OPUS }; static inline constexpr Bitrate defaultBitrate{ 128000 }; - Mode mode{ defaultMode }; - Format format{ defaultFormat }; - Bitrate bitrate{ defaultBitrate }; + Mode mode{ defaultMode }; + Format format{ defaultFormat }; + Bitrate bitrate{ defaultBitrate }; }; struct ReplayGain @@ -74,14 +74,14 @@ namespace lms::ui Release = 3, }; - static inline constexpr Mode defaultMode{ Mode::None }; - static inline constexpr Gain defaultPreAmpGain{}; - static inline constexpr Gain minPreAmpGain{ -15 }; - static inline constexpr Gain maxPreAmpGain{ 15 }; + static inline constexpr Mode defaultMode{ Mode::None }; + static inline constexpr Gain defaultPreAmpGain{}; + static inline constexpr Gain minPreAmpGain{ -15 }; + static inline constexpr Gain maxPreAmpGain{ 15 }; - Mode mode{ defaultMode }; - Gain preAmpGain{ defaultPreAmpGain }; - Gain preAmpGainIfNoInfo{ defaultPreAmpGain }; + Mode mode{ defaultMode }; + Gain preAmpGain{ defaultPreAmpGain }; + Gain preAmpGainIfNoInfo{ defaultPreAmpGain }; }; Transcoding transcoding; @@ -98,30 +98,30 @@ namespace lms::ui void loadTrack(db::TrackId trackId, bool play, float replayGain); void stop(); - std::optional getSettings() const { return _settings; } - void setSettings(const Settings& settings); + std::optional getSettings() const { return _settings; } + void setSettings(const Settings& settings); - void onPlayQueueUpdated(std::size_t trackCount); + void onPlayQueueUpdated(std::size_t trackCount); // Signals - Wt::JSignal<> playPrevious; - Wt::JSignal<> playNext; - Wt::Signal trackLoaded; - Wt::Signal<> settingsLoaded; + Wt::JSignal<> playPrevious; + Wt::JSignal<> playNext; + Wt::Signal trackLoaded; + Wt::Signal<> settingsLoaded; - Wt::JSignal scrobbleListenNow; - Wt::JSignal scrobbleListenFinished; + Wt::JSignal scrobbleListenNow; + Wt::JSignal scrobbleListenFinished; - Wt::JSignal<> playbackEnded; + Wt::JSignal<> playbackEnded; private: - std::unique_ptr _audioFileResource; - std::unique_ptr _audioTranscodingResource; + std::unique_ptr _audioFileResource; + std::unique_ptr _audioTranscodingResource; std::optional _trackIdLoaded; - std::optional _settings; + std::optional _settings; - Wt::JSignal _settingsLoaded; + Wt::JSignal _settingsLoaded; Wt::WText* _title{}; Wt::WAnchor* _release{}; @@ -131,4 +131,3 @@ namespace lms::ui }; } // namespace lms::ui - diff --git a/src/lms/ui/ModalManager.cpp b/src/lms/ui/ModalManager.cpp index c0679e9c9..d17e5a41b 100644 --- a/src/lms/ui/ModalManager.cpp +++ b/src/lms/ui/ModalManager.cpp @@ -25,20 +25,19 @@ namespace lms::ui ModalManager::ModalManager() : _closed{ this, "closed" } { - _closed.connect([this](const std::string& id) + _closed.connect([this](const std::string& id) { + LMS_LOG(UI, DEBUG, "Received closed for id '" << id << "'"); + for (int i{}; i < count(); ++i) { - LMS_LOG(UI, DEBUG, "Received closed for id '" << id << "'"); - for (int i{}; i < count(); ++i) + Wt::WWidget* widget{ this->widget(i) }; + LMS_LOG(UI, DEBUG, "Widget " << i << ", id = '" << widget->id()); + if (widget->id() == id) { - Wt::WWidget* widget{ this->widget(i) }; - LMS_LOG(UI, DEBUG, "Widget " << i << ", id = '" << widget->id()); - if (widget->id() == id) - { - removeWidget(widget); - break; - } + removeWidget(widget); + break; } - }); + } + }); } void ModalManager::show(std::unique_ptr modalWidget) @@ -76,4 +75,4 @@ namespace lms::ui doJavaScript(oss.str()); } -} +} // namespace lms::ui diff --git a/src/lms/ui/ModalManager.hpp b/src/lms/ui/ModalManager.hpp index 49ac7fc86..17acf0332 100644 --- a/src/lms/ui/ModalManager.hpp +++ b/src/lms/ui/ModalManager.hpp @@ -25,17 +25,17 @@ namespace lms::ui { - class ModalManager : public Wt::WContainerWidget - { - public: - ModalManager(); + class ModalManager : public Wt::WContainerWidget + { + public: + ModalManager(); - // should handle only one modal at a time - // Widget must contain a "modal" element - void show(std::unique_ptr modalWidget); - void dispose(Wt::WWidget* modalWidget); + // should handle only one modal at a time + // Widget must contain a "modal" element + void show(std::unique_ptr modalWidget); + void dispose(Wt::WWidget* modalWidget); - private: - Wt::JSignal _closed; - }; -} + private: + Wt::JSignal _closed; + }; +} // namespace lms::ui diff --git a/src/lms/ui/Notification.hpp b/src/lms/ui/Notification.hpp index abd451a5d..e35dce6c7 100644 --- a/src/lms/ui/Notification.hpp +++ b/src/lms/ui/Notification.hpp @@ -1,11 +1,30 @@ +/* + * Copyright (C) 2024 Emeric Poupon + * + * This file is part of LMS. + * + * LMS is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LMS is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LMS. If not, see . + */ + #pragma once namespace lms::ui::Notification { - enum class Type - { - Info, - Warning, - Danger, - }; -} + enum class Type + { + Info, + Warning, + Danger, + }; +} // namespace lms::ui::Notification diff --git a/src/lms/ui/NotificationContainer.cpp b/src/lms/ui/NotificationContainer.cpp index 20cbd0ab8..d76f2ea2d 100644 --- a/src/lms/ui/NotificationContainer.cpp +++ b/src/lms/ui/NotificationContainer.cpp @@ -20,9 +20,11 @@ #include "NotificationContainer.hpp" #include + #include #include "core/ILogger.hpp" + #include "LmsApplication.hpp" namespace lms::ui @@ -35,7 +37,7 @@ namespace lms::ui NotificationWidget(Notification::Type type, const Wt::WString& category, const Wt::WString& message, std::chrono::milliseconds duration); Wt::JSignal<> closed{ this, "closed" }; }; - } + } // namespace NotificationWidget::NotificationWidget(Notification::Type type, const Wt::WString& category, const Wt::WString& message, std::chrono::milliseconds duration) : Wt::WTemplate{ Wt::WString::tr("Lms.notifications.template.entry") } @@ -80,9 +82,8 @@ namespace lms::ui { NotificationWidget* notification{ addNew(type, category, message, duration) }; - notification->closed.connect([this, notification] - { - removeWidget(notification); - }); + notification->closed.connect([this, notification] { + removeWidget(notification); + }); } -} +} // namespace lms::ui diff --git a/src/lms/ui/NotificationContainer.hpp b/src/lms/ui/NotificationContainer.hpp index 4f7b7e597..d4ba3921f 100644 --- a/src/lms/ui/NotificationContainer.hpp +++ b/src/lms/ui/NotificationContainer.hpp @@ -20,6 +20,7 @@ #pragma once #include + #include #include @@ -27,11 +28,11 @@ namespace lms::ui { - class NotificationContainer : public Wt::WContainerWidget - { - public: - void add(Notification::Type type, const Wt::WString& category, const Wt::WString& message, std::chrono::milliseconds duration); + class NotificationContainer : public Wt::WContainerWidget + { + public: + void add(Notification::Type type, const Wt::WString& category, const Wt::WString& message, std::chrono::milliseconds duration); - private: - }; + private: + }; } // namespace lms::ui diff --git a/src/lms/ui/PlayQueue.cpp b/src/lms/ui/PlayQueue.cpp index d12f78894..3ef281cde 100644 --- a/src/lms/ui/PlayQueue.cpp +++ b/src/lms/ui/PlayQueue.cpp @@ -23,11 +23,16 @@ #include #include #include -#include #include +#include #include #include +#include "core/IConfig.hpp" +#include "core/ILogger.hpp" +#include "core/Random.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" #include "database/Artist.hpp" #include "database/Release.hpp" #include "database/Session.hpp" @@ -36,20 +41,15 @@ #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/recommendation/IPlaylistGeneratorService.hpp" -#include "core/IConfig.hpp" -#include "core/ILogger.hpp" -#include "core/Random.hpp" -#include "core/Service.hpp" -#include "core/String.hpp" -#include "common/InfiniteScrollingContainer.hpp" -#include "common/MandatoryValidator.hpp" -#include "common/ValueStringModel.hpp" -#include "resource/DownloadResource.hpp" #include "LmsApplication.hpp" #include "MediaPlayer.hpp" #include "ModalManager.hpp" #include "Utils.hpp" +#include "common/InfiniteScrollingContainer.hpp" +#include "common/MandatoryValidator.hpp" +#include "common/ValueStringModel.hpp" +#include "resource/DownloadResource.hpp" namespace lms::ui { @@ -100,17 +100,16 @@ namespace lms::ui params.setUser(LmsApp->getUserId()); params.setSortMethod(TrackListSortMethod::Name); - TrackList::find(LmsApp->getDbSession(), params, [&](const TrackList::pointer& trackList) - { - model->add(Wt::WString::fromUTF8(std::string{ trackList->getName() }), trackList->getId()); - }); + TrackList::find(LmsApp->getDbSession(), params, [&](const TrackList::pointer& trackList) { + model->add(Wt::WString::fromUTF8(std::string{ trackList->getName() }), trackList->getId()); + }); return model; } std::shared_ptr trackListModel{ createTrackListModel() }; }; - } + } // namespace PlayQueue::PlayQueue() : Template{ Wt::WString::tr("Lms.PlayQueue.template") } @@ -122,51 +121,46 @@ namespace lms::ui addFunction("tr", &Wt::WTemplate::Functions::tr); Wt::WPushButton* clearBtn{ bindNew("clear-btn", Wt::WString::tr("Lms.PlayQueue.template.clear-btn"), Wt::TextFormat::XHTML) }; - clearBtn->clicked().connect([this] - { - clearTracks(); - }); + clearBtn->clicked().connect([this] { + clearTracks(); + }); Wt::WPushButton* saveBtn{ bindNew("save-btn", Wt::WString::tr("Lms.PlayQueue.template.save-btn"), Wt::TextFormat::XHTML) }; - saveBtn->clicked().connect([this] - { - saveAsTrackList(); - }); + saveBtn->clicked().connect([this] { + saveAsTrackList(); + }); _entriesContainer = bindNew("entries", Wt::WString::tr("Lms.PlayQueue.template.entry-container")); - _entriesContainer->onRequestElements.connect([this] - { - addSome(); - updateCurrentTrack(true); - }); + _entriesContainer->onRequestElements.connect([this] { + addSome(); + updateCurrentTrack(true); + }); Wt::WPushButton* shuffleBtn{ bindNew("shuffle-btn", Wt::WString::tr("Lms.PlayQueue.template.shuffle-btn"), Wt::TextFormat::XHTML) }; - shuffleBtn->clicked().connect([this] + shuffleBtn->clicked().connect([this] { { - { - // TODO write scope could be reduced - auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; + // TODO write scope could be reduced + auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - db::TrackList::pointer queue{ getQueue() }; - auto entries{ queue->getEntries().results }; - core::random::shuffleContainer(entries); + db::TrackList::pointer queue{ getQueue() }; + auto entries{ queue->getEntries().results }; + core::random::shuffleContainer(entries); - queue.modify()->clear(); - for (const db::TrackListEntry::pointer& entry : entries) - LmsApp->getDbSession().create(entry->getTrack(), queue); - } - _entriesContainer->reset(); - addSome(); - }); + queue.modify()->clear(); + for (const db::TrackListEntry::pointer& entry : entries) + LmsApp->getDbSession().create(entry->getTrack(), queue); + } + _entriesContainer->reset(); + addSome(); + }); _repeatBtn = bindNew("repeat-btn"); - _repeatBtn->clicked().connect([this] - { - auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; + _repeatBtn->clicked().connect([this] { + auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - if (!LmsApp->getUser()->isDemo()) - LmsApp->getUser().modify()->setRepeatAll(isRepeatAllSet()); - }); + if (!LmsApp->getUser()->isDemo()) + LmsApp->getUser().modify()->setRepeatAll(isRepeatAllSet()); + }); { auto transaction{ LmsApp->getDbSession().createReadTransaction() }; if (LmsApp->getUser()->isRepeatAllSet()) @@ -174,17 +168,16 @@ namespace lms::ui } _radioBtn = bindNew("radio-btn"); - _radioBtn->clicked().connect([this] + _radioBtn->clicked().connect([this] { { - { - auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; + auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - if (!LmsApp->getUser()->isDemo()) - LmsApp->getUser().modify()->setRadio(isRadioModeSet()); - } - if (isRadioModeSet()) - enqueueRadioTracksIfNeeded(); - }); + if (!LmsApp->getUser()->isDemo()) + LmsApp->getUser().modify()->setRadio(isRadioModeSet()); + } + if (isRadioModeSet()) + enqueueRadioTracksIfNeeded(); + }); bool isRadioModeSet{}; { @@ -200,34 +193,32 @@ namespace lms::ui _nbTracks = bindNew("track-count"); _duration = bindNew("duration"); - LmsApp->getMediaPlayer().settingsLoaded.connect([this] - { - if (_mediaPlayerSettingsLoaded) - return; + LmsApp->getMediaPlayer().settingsLoaded.connect([this] { + if (_mediaPlayerSettingsLoaded) + return; - _mediaPlayerSettingsLoaded = true; + _mediaPlayerSettingsLoaded = true; - std::size_t trackPos{}; + std::size_t trackPos{}; - { - auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - trackPos = LmsApp->getUser()->getCurPlayingTrackPos(); - } + { + auto transaction{ LmsApp->getDbSession().createReadTransaction() }; + trackPos = LmsApp->getUser()->getCurPlayingTrackPos(); + } - loadTrack(trackPos, false); - }); + loadTrack(trackPos, false); + }); - LmsApp->preQuit().connect([this] - { - auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; + LmsApp->preQuit().connect([this] { + auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - if (LmsApp->getUser()->isDemo()) - { - LMS_LOG(UI, DEBUG, "Removing queue (tracklist id " << _queueId.toString() << ")"); - if (db::TrackList::pointer queue{ getQueue() }) - queue.remove(); - } - }); + if (LmsApp->getUser()->isDemo()) + { + LMS_LOG(UI, DEBUG, "Removing queue (tracklist id " << _queueId.toString() << ")"); + if (db::TrackList::pointer queue{ getQueue() }) + queue.remove(); + } + }); updateInfo(); } @@ -427,7 +418,7 @@ namespace lms::ui auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; db::TrackList::pointer queue{ getQueue() }; - auto entries{ queue->getEntries(db::Range {_trackPos ? *_trackPos + 1 : 0, getCapacity()}) }; + auto entries{ queue->getEntries(db::Range{ _trackPos ? *_trackPos + 1 : 0, getCapacity() }) }; tracks.reserve(entries.results.size()); for (db::TrackListEntry::pointer& entry : entries.results) { @@ -489,7 +480,7 @@ namespace lms::ui auto transaction{ LmsApp->getDbSession().createReadTransaction() }; const db::TrackList::pointer queue{ getQueue() }; - const auto entries{ queue->getEntries(db::Range {_entriesContainer->getCount(), _batchSize}) }; + const auto entries{ queue->getEntries(db::Range{ _entriesContainer->getCount(), _batchSize }) }; for (const db::TrackListEntry::pointer& tracklistEntry : entries.results) addEntry(tracklistEntry); @@ -507,7 +498,7 @@ namespace lms::ui entry->bindString("name", Wt::WString::fromUTF8(track->getName()), Wt::TextFormat::Plain); - const auto artists{ track->getArtistIds({db::TrackArtistLinkType::Artist}) }; + const auto artists{ track->getArtistIds({ db::TrackArtistLinkType::Artist }) }; if (!artists.empty()) { entry->setCondition("if-has-artists", true); @@ -536,66 +527,63 @@ namespace lms::ui entry->bindString("duration", utils::durationToString(track->getDuration()), Wt::TextFormat::Plain); Wt::WPushButton* playBtn{ entry->bindNew("play-btn", Wt::WString::tr("Lms.template.play-btn"), Wt::TextFormat::XHTML) }; - playBtn->clicked().connect([this, entry] - { - const std::optional pos{ _entriesContainer->getIndexOf(*entry) }; - if (pos) - loadTrack(*pos, true); - }); + playBtn->clicked().connect([this, entry] { + const std::optional pos{ _entriesContainer->getIndexOf(*entry) }; + if (pos) + loadTrack(*pos, true); + }); Wt::WPushButton* delBtn{ entry->bindNew("del-btn", Wt::WString::tr("Lms.template.delete-btn"), Wt::TextFormat::XHTML) }; delBtn->setToolTip(Wt::WString::tr("Lms.delete")); - delBtn->clicked().connect([this, tracklistEntryId, entry] + delBtn->clicked().connect([this, tracklistEntryId, entry] { + // Remove the entry n both the widget tree and the playqueue { - // Remove the entry n both the widget tree and the playqueue - { - auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; + auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - db::TrackListEntry::pointer entryToRemove{ db::TrackListEntry::getById(LmsApp->getDbSession(), tracklistEntryId) }; - entryToRemove.remove(); - } + db::TrackListEntry::pointer entryToRemove{ db::TrackListEntry::getById(LmsApp->getDbSession(), tracklistEntryId) }; + entryToRemove.remove(); + } - if (_trackPos) - { - const std::optional pos{ _entriesContainer->getIndexOf(*entry) }; - if (pos && *_trackPos >= *pos) - (*_trackPos)--; - else if (*_trackPos >= _entriesContainer->getCount()) - _trackPos.reset(); - } + if (_trackPos) + { + const std::optional pos{ _entriesContainer->getIndexOf(*entry) }; + if (pos && *_trackPos >= *pos) + (*_trackPos)--; + else if (*_trackPos >= _entriesContainer->getCount()) + _trackPos.reset(); + } - _entriesContainer->remove(*entry); + _entriesContainer->remove(*entry); - updateInfo(); - }); + updateInfo(); + }); entry->bindNew("more-btn", Wt::WString::tr("Lms.template.more-btn"), Wt::TextFormat::XHTML); entry->bindNew("play", Wt::WString::tr("Lms.Explore.play")) - ->clicked().connect([this, entry] - { - const std::optional pos{ _entriesContainer->getIndexOf(*entry) }; - if (pos) - loadTrack(*pos, true); - }); + ->clicked() + .connect([this, entry] { + const std::optional pos{ _entriesContainer->getIndexOf(*entry) }; + if (pos) + loadTrack(*pos, true); + }); auto isStarred{ [=] { return core::Service::get()->isStarred(LmsApp->getUserId(), trackId); } }; Wt::WPushButton* starBtn{ entry->bindNew("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) }; - starBtn->clicked().connect([=] - { - auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; + starBtn->clicked().connect([=] { + auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - if (isStarred()) - { - core::Service::get()->unstar(LmsApp->getUserId(), trackId); - starBtn->setText(Wt::WString::tr("Lms.Explore.star")); - } - else - { - core::Service::get()->star(LmsApp->getUserId(), trackId); - starBtn->setText(Wt::WString::tr("Lms.Explore.unstar")); - } - }); + if (isStarred()) + { + core::Service::get()->unstar(LmsApp->getUserId(), trackId); + starBtn->setText(Wt::WString::tr("Lms.Explore.star")); + } + else + { + core::Service::get()->star(LmsApp->getUserId(), trackId); + starBtn->setText(Wt::WString::tr("Lms.Explore.unstar")); + } + }); entry->bindNew("download", Wt::WString::tr("Lms.Explore.download")) ->setLink(Wt::WLink{ std::make_unique(trackId) }); @@ -651,27 +639,26 @@ namespace lms::ui break; case MediaPlayer::Settings::ReplayGain::Mode::Auto: - { - const db::TrackList::pointer queue{ getQueue() }; - const db::TrackListEntry::pointer prevEntry{ pos > 0 ? queue->getEntry(pos - 1) : db::TrackListEntry::pointer {} }; - const db::TrackListEntry::pointer nextEntry{ queue->getEntry(pos + 1) }; - const db::Track::pointer prevTrack{ prevEntry ? prevEntry->getTrack() : db::Track::pointer {} }; - const db::Track::pointer nextTrack{ nextEntry ? nextEntry->getTrack() : db::Track::pointer {} }; - - if ((prevTrack && prevTrack->getRelease() && prevTrack->getRelease() == track->getRelease()) - || - (nextTrack && nextTrack->getRelease() && nextTrack->getRelease() == track->getRelease())) { - gain = track->getReleaseReplayGain(); - if (!gain) + const db::TrackList::pointer queue{ getQueue() }; + const db::TrackListEntry::pointer prevEntry{ pos > 0 ? queue->getEntry(pos - 1) : db::TrackListEntry::pointer{} }; + const db::TrackListEntry::pointer nextEntry{ queue->getEntry(pos + 1) }; + const db::Track::pointer prevTrack{ prevEntry ? prevEntry->getTrack() : db::Track::pointer{} }; + const db::Track::pointer nextTrack{ nextEntry ? nextEntry->getTrack() : db::Track::pointer{} }; + + if ((prevTrack && prevTrack->getRelease() && prevTrack->getRelease() == track->getRelease()) + || (nextTrack && nextTrack->getRelease() && nextTrack->getRelease() == track->getRelease())) + { + gain = track->getReleaseReplayGain(); + if (!gain) + gain = track->getTrackReplayGain(); + } + else + { gain = track->getTrackReplayGain(); + } + break; } - else - { - gain = track->getTrackReplayGain(); - } - break; - } } if (gain) @@ -688,10 +675,9 @@ namespace lms::ui Wt::WWidget* modalPtr{ modal.get() }; auto* cancelBtn{ modal->bindNew("cancel-btn", Wt::WString::tr("Lms.cancel")) }; - cancelBtn->clicked().connect([=] - { - LmsApp->getModalManager().dispose(modalPtr); - }); + cancelBtn->clicked().connect([=] { + LmsApp->getModalManager().dispose(modalPtr); + }); Wt::WStackedWidget* contentStack{ modal->bindNew("contents") }; @@ -703,17 +689,16 @@ namespace lms::ui }; { modal->bindNew("replace-tracklist-btn") - ->clicked().connect([=] - { - contentStack->setCurrentIndex(Index::ReplaceTrackList); - }); + ->clicked() + .connect([=] { + contentStack->setCurrentIndex(Index::ReplaceTrackList); + }); Wt::WRadioButton* createTrackListBtn{ modal->bindNew("create-tracklist-btn") }; createTrackListBtn->setChecked(); - createTrackListBtn->clicked().connect([=] - { - contentStack->setCurrentIndex(Index::CreateTrackList); - }); + createTrackListBtn->clicked().connect([=] { + contentStack->setCurrentIndex(Index::CreateTrackList); + }); } // Create TrackList @@ -733,35 +718,34 @@ namespace lms::ui replaceTrackList->updateView(replaceTrackListModel.get()); auto* saveBtn{ modal->bindNew("save-btn", Wt::WString::tr("Lms.save")) }; - saveBtn->clicked().connect([=, this] + saveBtn->clicked().connect([=, this] { + bool success{}; + switch (contentStack->currentIndex()) { - bool success{}; - switch (contentStack->currentIndex()) + case Index::CreateTrackList: + createTrackList->updateModel(createTrackListModel.get()); + if (createTrackListModel->validate()) { - case Index::CreateTrackList: - createTrackList->updateModel(createTrackListModel.get()); - if (createTrackListModel->validate()) - { - exportToNewTrackList(createTrackListModel->getName()); - success = true; - } - createTrackList->updateView(createTrackListModel.get()); - break; + exportToNewTrackList(createTrackListModel->getName()); + success = true; + } + createTrackList->updateView(createTrackListModel.get()); + break; - case Index::ReplaceTrackList: - replaceTrackList->updateModel(replaceTrackListModel.get()); - if (replaceTrackListModel->validate()) - { - exportToTrackList(replaceTrackListModel->getTrackListId()); - success = true; - } - replaceTrackList->updateView(replaceTrackListModel.get()); - break; + case Index::ReplaceTrackList: + replaceTrackList->updateModel(replaceTrackListModel.get()); + if (replaceTrackListModel->validate()) + { + exportToTrackList(replaceTrackListModel->getTrackListId()); + success = true; } + replaceTrackList->updateView(replaceTrackListModel.get()); + break; + } - if (success) - LmsApp->getModalManager().dispose(modalPtr); - }); + if (success) + LmsApp->getModalManager().dispose(modalPtr); + }); LmsApp->getModalManager().show(std::move(modal)); } @@ -796,9 +780,8 @@ namespace lms::ui params.setTrackList(_queueId); params.setSortMethod(TrackSortMethod::TrackList); - Track::find(session, params, [&](const Track::pointer& track) - { - session.create(track, trackList); - }); + Track::find(session, params, [&](const Track::pointer& track) { + session.create(track, trackList); + }); } } // namespace lms::ui diff --git a/src/lms/ui/PlayQueue.hpp b/src/lms/ui/PlayQueue.hpp index d797e7e3b..a6debe278 100644 --- a/src/lms/ui/PlayQueue.hpp +++ b/src/lms/ui/PlayQueue.hpp @@ -32,90 +32,89 @@ #include "common/Template.hpp" - namespace lms::db { - class Track; - class TrackList; - class TrackListEntry; -} + class Track; + class TrackList; + class TrackListEntry; +} // namespace lms::db -namespace lms::ui { +namespace lms::ui +{ -class InfiniteScrollingContainer; + class InfiniteScrollingContainer; -class PlayQueue : public Template -{ - public: - PlayQueue(); + class PlayQueue : public Template + { + public: + PlayQueue(); - void play(const std::vector& trackIds); - void playNext(const std::vector& trackIds); - void playShuffled(const std::vector& trackIds); - void playOrAddLast(const std::vector& trackIds); // play if queue empty, otherwise just add last - void playAtIndex(const std::vector& trackIds, std::size_t index); + void play(const std::vector& trackIds); + void playNext(const std::vector& trackIds); + void playShuffled(const std::vector& trackIds); + void playOrAddLast(const std::vector& trackIds); // play if queue empty, otherwise just add last + void playAtIndex(const std::vector& trackIds, std::size_t index); - // play the next track in the queue - void playNext(); + // play the next track in the queue + void playNext(); - // play the previous track in the queue - void playPrevious(); + // play the previous track in the queue + void playPrevious(); - // Signal emitted when a track is to be load(and optionally played) - Wt::Signal trackSelected; + // Signal emitted when a track is to be load(and optionally played) + Wt::Signal trackSelected; - // Signal emitted when track is unselected (has to be stopped) - Wt::Signal<> trackUnselected; + // Signal emitted when track is unselected (has to be stopped) + Wt::Signal<> trackUnselected; - // Signal emitted when track count changed - Wt::Signal trackCountChanged; + // Signal emitted when track count changed + Wt::Signal trackCountChanged; - void onPlaybackEnded(); + void onPlaybackEnded(); - std::size_t getCapacity() const { return _capacity; } - std::size_t getCount(); + std::size_t getCapacity() const { return _capacity; } + std::size_t getCount(); - private: - void initTrackLists(); + private: + void initTrackLists(); - void notifyAddedTracks(std::size_t nbAddedTracks) const; - db::ObjectPtr getQueue() const; - bool isFull() const; + void notifyAddedTracks(std::size_t nbAddedTracks) const; + db::ObjectPtr getQueue() const; + bool isFull() const; - void clearTracks(); - void enqueueTracks(const std::vector& trackIds); - std::vector getAndClearNextTracks(); - void addSome(); - void addEntry(const db::ObjectPtr& entry); - void enqueueRadioTracksIfNeeded(); - void enqueueRadioTracks(); - void updateInfo(); - void updateCurrentTrack(bool selected); - bool isRepeatAllSet() const; - bool isRadioModeSet() const; + void clearTracks(); + void enqueueTracks(const std::vector& trackIds); + std::vector getAndClearNextTracks(); + void addSome(); + void addEntry(const db::ObjectPtr& entry); + void enqueueRadioTracksIfNeeded(); + void enqueueRadioTracks(); + void updateInfo(); + void updateCurrentTrack(bool selected); + bool isRepeatAllSet() const; + bool isRadioModeSet() const; - void loadTrack(std::size_t pos, bool play); - void stop(); + void loadTrack(std::size_t pos, bool play); + void stop(); - std::optional getReplayGain(std::size_t pos, const db::ObjectPtr& track) const; - void saveAsTrackList(); + std::optional getReplayGain(std::size_t pos, const db::ObjectPtr& track) const; + void saveAsTrackList(); - void exportToNewTrackList(const Wt::WString& name); - void exportToTrackList(db::TrackListId trackList); + void exportToNewTrackList(const Wt::WString& name); + void exportToTrackList(db::TrackListId trackList); - const std::size_t _capacity; - static inline constexpr std::size_t _batchSize {12}; + const std::size_t _capacity; + static inline constexpr std::size_t _batchSize{ 12 }; - bool _mediaPlayerSettingsLoaded {}; - db::TrackListId _queueId {}; - InfiniteScrollingContainer* _entriesContainer {}; - Wt::WText* _nbTracks {}; - Wt::WText* _duration {}; - Wt::WCheckBox* _repeatBtn {}; - Wt::WCheckBox* _radioBtn {}; - std::optional _trackPos; // current track position, if set - bool _isTrackSelected {}; -}; + bool _mediaPlayerSettingsLoaded{}; + db::TrackListId _queueId{}; + InfiniteScrollingContainer* _entriesContainer{}; + Wt::WText* _nbTracks{}; + Wt::WText* _duration{}; + Wt::WCheckBox* _repeatBtn{}; + Wt::WCheckBox* _radioBtn{}; + std::optional _trackPos; // current track position, if set + bool _isTrackSelected{}; + }; } // namespace lms::ui - diff --git a/src/lms/ui/SettingsView.cpp b/src/lms/ui/SettingsView.cpp index 9604f6728..f82c8445a 100644 --- a/src/lms/ui/SettingsView.cpp +++ b/src/lms/ui/SettingsView.cpp @@ -28,21 +28,20 @@ #include #include -#include "common/DoubleValidator.hpp" -#include "common/MandatoryValidator.hpp" -#include "common/PasswordValidator.hpp" -#include "common/UUIDValidator.hpp" -#include "common/ValueStringModel.hpp" - -#include "services/auth/IPasswordService.hpp" -#include "database/Session.hpp" -#include "database/User.hpp" #include "core/IConfig.hpp" #include "core/ILogger.hpp" #include "core/Service.hpp" +#include "database/Session.hpp" +#include "database/User.hpp" +#include "services/auth/IPasswordService.hpp" #include "LmsApplication.hpp" #include "MediaPlayer.hpp" +#include "common/DoubleValidator.hpp" +#include "common/MandatoryValidator.hpp" +#include "common/PasswordValidator.hpp" +#include "common/UUIDValidator.hpp" +#include "common/ValueStringModel.hpp" namespace lms::ui { @@ -111,8 +110,7 @@ namespace lms::ui setValidator(TranscodeBitrateField, createMandatoryValidator()); setValidator(TranscodeFormatField, createMandatoryValidator()); setValidator(ReplayGainModeField, createMandatoryValidator()); - auto createPreAmpValidator{ [] - { + auto createPreAmpValidator{ [] { return createDoubleValidator(MediaPlayer::Settings::ReplayGain::minPreAmpGain, MediaPlayer::Settings::ReplayGain::maxPreAmpGain); } }; @@ -124,13 +122,13 @@ namespace lms::ui loadData(); } - std::shared_ptr getTranscodingModeModel() { return _transcodingModeModeModel; } - std::shared_ptr getTranscodingOutputBitrateModel() { return _transcodingOutputBitrateModel; } - std::shared_ptr getTranscodingOutputFormatModel() { return _transcodingOutputFormatModel; } - std::shared_ptr getReplayGainModeModel() { return _replayGainModeModel; } - std::shared_ptr getSubsonicArtistListModeModel() { return _subsonicArtistListModeModel; } - std::shared_ptr getFeedbackBackendModel() { return _feedbackBackendModel; } - std::shared_ptr getScrobblingBackendModel() { return _scrobblingBackendModel; } + std::shared_ptr getTranscodingModeModel() { return _transcodingModeModeModel; } + std::shared_ptr getTranscodingOutputBitrateModel() { return _transcodingOutputBitrateModel; } + std::shared_ptr getTranscodingOutputFormatModel() { return _transcodingOutputFormatModel; } + std::shared_ptr getReplayGainModeModel() { return _replayGainModeModel; } + std::shared_ptr getSubsonicArtistListModeModel() { return _subsonicArtistListModeModel; } + std::shared_ptr getFeedbackBackendModel() { return _feedbackBackendModel; } + std::shared_ptr getScrobblingBackendModel() { return _scrobblingBackendModel; } void saveData() { @@ -315,7 +313,6 @@ namespace lms::ui } private: - void initializeModels() { _transcodingModeModeModel = std::make_shared(); @@ -324,10 +321,9 @@ namespace lms::ui _transcodingModeModeModel->add(Wt::WString::tr("Lms.Settings.transcoding-mode.if-format-not-supported"), MediaPlayer::Settings::Transcoding::Mode::IfFormatNotSupported); _transcodingOutputBitrateModel = std::make_shared>(); - visitAllowedAudioBitrates([&](const Bitrate bitrate) - { - _transcodingOutputBitrateModel->add(Wt::WString::fromUTF8(std::to_string(bitrate / 1000)), bitrate); - }); + visitAllowedAudioBitrates([&](const Bitrate bitrate) { + _transcodingOutputBitrateModel->add(Wt::WString::fromUTF8(std::to_string(bitrate / 1000)), bitrate); + }); _transcodingOutputFormatModel = std::make_shared>(); _transcodingOutputFormatModel->add(Wt::WString::tr("Lms.Settings.transcoding-output-format.mp3"), TranscodingOutputFormat::MP3); @@ -359,32 +355,30 @@ namespace lms::ui auth::IPasswordService* _authPasswordService{}; bool _withOldPassword{}; - std::shared_ptr _transcodingModeModeModel; - std::shared_ptr> _transcodingOutputBitrateModel; - std::shared_ptr> _transcodingOutputFormatModel; - std::shared_ptr _replayGainModeModel; - std::shared_ptr> _subsonicArtistListModeModel; - std::shared_ptr _feedbackBackendModel; - std::shared_ptr _scrobblingBackendModel; + std::shared_ptr _transcodingModeModeModel; + std::shared_ptr> _transcodingOutputBitrateModel; + std::shared_ptr> _transcodingOutputFormatModel; + std::shared_ptr _replayGainModeModel; + std::shared_ptr> _subsonicArtistListModeModel; + std::shared_ptr _feedbackBackendModel; + std::shared_ptr _scrobblingBackendModel; }; SettingsView::SettingsView() { - wApp->internalPathChanged().connect(this, [this] - { - refreshView(); - }); + wApp->internalPathChanged().connect(this, [this] { + refreshView(); + }); - LmsApp->getMediaPlayer().settingsLoaded.connect([this] - { - refreshView(); - }); + LmsApp->getMediaPlayer().settingsLoaded.connect([this] { + refreshView(); + }); refreshView(); } void - SettingsView::refreshView() + SettingsView::refreshView() { if (!wApp->internalPathMatches("/settings")) return; @@ -449,14 +443,13 @@ namespace lms::ui transcodingOutputBitrate->setModel(model->getTranscodingOutputBitrateModel()); t->setFormWidget(SettingsModel::TranscodeBitrateField, std::move(transcodingOutputBitrate)); - transcodingModeRaw->activated().connect([=](int row) - { - const bool enable{ model->getTranscodingModeModel()->getValue(row) != MediaPlayer::Settings::Transcoding::Mode::Never }; - model->setReadOnly(SettingsModel::TranscodeFormatField, !enable); - model->setReadOnly(SettingsModel::TranscodeBitrateField, !enable); - t->updateModel(model.get()); - t->updateView(model.get()); - }); + transcodingModeRaw->activated().connect([=](int row) { + const bool enable{ model->getTranscodingModeModel()->getValue(row) != MediaPlayer::Settings::Transcoding::Mode::Never }; + model->setReadOnly(SettingsModel::TranscodeFormatField, !enable); + model->setReadOnly(SettingsModel::TranscodeBitrateField, !enable); + t->updateModel(model.get()); + t->updateView(model.get()); + }); // Replay gain mode auto replayGainMode{ std::make_unique() }; @@ -474,14 +467,13 @@ namespace lms::ui replayGainPreampGainIfNoInfo->setRange(MediaPlayer::Settings::ReplayGain::minPreAmpGain, MediaPlayer::Settings::ReplayGain::maxPreAmpGain); t->setFormWidget(SettingsModel::ReplayGainPreAmpGainIfNoInfoField, std::move(replayGainPreampGainIfNoInfo)); - replayGainModeRaw->activated().connect([=](int row) - { - const bool enable{ model->getReplayGainModeModel()->getValue(row) != MediaPlayer::Settings::ReplayGain::Mode::None }; - model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainField, !enable); - model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainIfNoInfoField, !enable); - t->updateModel(model.get()); - t->updateView(model.get()); - }); + replayGainModeRaw->activated().connect([=](int row) { + const bool enable{ model->getReplayGainModeModel()->getValue(row) != MediaPlayer::Settings::ReplayGain::Mode::None }; + model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainField, !enable); + model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainIfNoInfoField, !enable); + t->updateModel(model.get()); + t->updateView(model.get()); + }); if (LmsApp->getMediaPlayer().getSettings()->replayGain.mode == MediaPlayer::Settings::ReplayGain::Mode::None) { model->setReadOnly(SettingsModel::SettingsModel::ReplayGainPreAmpGainField, true); @@ -536,10 +528,9 @@ namespace lms::ui t->setFormWidget(SettingsModel::ListenBrainzTokenField, std::move(listenbrainzToken)); } - auto updateListenBrainzTokenField{ [=] - { + auto updateListenBrainzTokenField{ [=] { const bool enable{ model->getFeedbackBackendModel()->getValue(feedbackBackendRaw->currentIndex()) == FeedbackBackend::ListenBrainz - || model->getScrobblingBackendModel()->getValue(scrobblingBackendRaw->currentIndex()) == ScrobblingBackend::ListenBrainz }; + || model->getScrobblingBackendModel()->getValue(scrobblingBackendRaw->currentIndex()) == ScrobblingBackend::ListenBrainz }; model->setReadOnly(SettingsModel::ListenBrainzTokenField, !enable); model->validator(SettingsModel::ListenBrainzTokenField)->setMandatory(enable); @@ -548,42 +539,40 @@ namespace lms::ui } }; feedbackBackendRaw->activated().connect([=] { updateListenBrainzTokenField(); }); - scrobblingBackendRaw->activated().connect([=] { updateListenBrainzTokenField();}); + scrobblingBackendRaw->activated().connect([=] { updateListenBrainzTokenField(); }); // Buttons Wt::WPushButton* saveBtn{ t->bindWidget("save-btn", std::make_unique(Wt::WString::tr("Lms.save"))) }; Wt::WPushButton* discardBtn{ t->bindWidget("discard-btn", std::make_unique(Wt::WString::tr("Lms.discard"))) }; - saveBtn->clicked().connect([=] + saveBtn->clicked().connect([=] { { - { - auto transaction{ LmsApp->getDbSession().createReadTransaction() }; + auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - if (LmsApp->getUser()->isDemo()) - { - LmsApp->notifyMsg(Notification::Type::Warning, Wt::WString::tr("Lms.Settings.settings"), Wt::WString::tr("Lms.Settings.demo-cannot-save")); - return; - } - } - - t->updateModel(model.get()); - - if (model->validate()) + if (LmsApp->getUser()->isDemo()) { - model->saveData(); - LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Settings.settings"), Wt::WString::tr("Lms.Settings.settings-saved")); + LmsApp->notifyMsg(Notification::Type::Warning, Wt::WString::tr("Lms.Settings.settings"), Wt::WString::tr("Lms.Settings.demo-cannot-save")); + return; } + } - // Udate the view: Delete any validation message in the view, etc. - t->updateView(model.get()); - }); + t->updateModel(model.get()); - discardBtn->clicked().connect([=] + if (model->validate()) { - model->loadData(); - model->validate(); - t->updateView(model.get()); - }); + model->saveData(); + LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Settings.settings"), Wt::WString::tr("Lms.Settings.settings-saved")); + } + + // Udate the view: Delete any validation message in the view, etc. + t->updateView(model.get()); + }); + + discardBtn->clicked().connect([=] { + model->loadData(); + model->validate(); + t->updateView(model.get()); + }); t->updateView(model.get()); } diff --git a/src/lms/ui/SettingsView.hpp b/src/lms/ui/SettingsView.hpp index fc95809bf..3a4c7db49 100644 --- a/src/lms/ui/SettingsView.hpp +++ b/src/lms/ui/SettingsView.hpp @@ -21,16 +21,16 @@ #include -namespace lms::ui { - -class SettingsView : public Wt::WContainerWidget +namespace lms::ui { - public: - SettingsView(); - private: - void refreshView(); -}; + class SettingsView : public Wt::WContainerWidget + { + public: + SettingsView(); -} // namespace lms::ui + private: + void refreshView(); + }; +} // namespace lms::ui diff --git a/src/lms/ui/Utils.cpp b/src/lms/ui/Utils.cpp index cc36f3fa6..1fef41584 100644 --- a/src/lms/ui/Utils.cpp +++ b/src/lms/ui/Utils.cpp @@ -32,8 +32,9 @@ #include "database/Session.hpp" #include "database/Track.hpp" #include "database/TrackList.hpp" -#include "explore/Filters.hpp" + #include "LmsApplication.hpp" +#include "explore/Filters.hpp" namespace lms::ui::utils { @@ -61,12 +62,11 @@ namespace lms::ui::utils return oss.str(); } - std::unique_ptr createCover(db::ReleaseId releaseId, CoverResource::Size size) { auto cover{ std::make_unique() }; cover->setImageLink(LmsApp->getCoverResource()->getReleaseUrl(releaseId, size)); - cover->setStyleClass("Lms-cover img-fluid"); // HACK + cover->setStyleClass("Lms-cover img-fluid"); // HACK cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK return cover; } @@ -75,7 +75,7 @@ namespace lms::ui::utils { auto cover{ std::make_unique() }; cover->setImageLink(LmsApp->getCoverResource()->getTrackUrl(trackId, size)); - cover->setStyleClass("Lms-cover img-fluid"); // HACK + cover->setStyleClass("Lms-cover img-fluid"); // HACK cover->setAttributeValue("onload", LmsApp->javaScriptClass() + ".onLoadCover(this)"); // HACK return cover; } @@ -99,18 +99,25 @@ namespace lms::ui::utils if (!cluster) return {}; - auto getStyleClass{ [](const db::Cluster::pointer& cluster) -> std::string_view - { + auto getStyleClass{ [](const db::Cluster::pointer& cluster) -> std::string_view { switch (cluster->getType()->getId().getValue() % 8) { - case 0: return "bg-primary"; - case 1: return "bg-secondary"; - case 2: return "bg-success"; - case 3: return "bg-danger"; - case 4: return "bg-warning text-dark"; - case 5: return "bg-info text-dark"; - case 6: return "bg-light text-dark"; - case 7: return "bg-dark"; + case 0: + return "bg-primary"; + case 1: + return "bg-secondary"; + case 2: + return "bg-success"; + case 3: + return "bg-danger"; + case 4: + return "bg-warning text-dark"; + case 5: + return "bg-info text-dark"; + case 6: + return "bg-light text-dark"; + case 7: + return "bg-dark"; } return "bg-primary"; @@ -135,10 +142,9 @@ namespace lms::ui::utils { const ClusterId clusterId{ cluster->getId() }; Wt::WInteractWidget* entry{ clusterContainer->addWidget(createFilterCluster(clusterId)) }; - entry->clicked().connect([&filters, clusterId] - { - filters.add(clusterId); - }); + entry->clicked().connect([&filters, clusterId] { + filters.add(clusterId); + }); } } @@ -199,7 +205,7 @@ namespace lms::ui::utils result->addNew(std::string{ displayName.substr(currentOffset, pos - currentOffset) }, Wt::TextFormat::Plain); auto anchor{ createArtistAnchor(artist) }; - anchor->addStyleClass("text-decoration-none"); // hack + anchor->addStyleClass("text-decoration-none"); // hack anchor->addStyleClass(std::string{ cssAnchorClass }); // hack result->addWidget(std::move(anchor)); currentOffset = pos + artist->getName().size(); @@ -251,7 +257,7 @@ namespace lms::ui::utils Wt::WLink createArtistLink(db::Artist::pointer artist) { if (const auto mbid{ artist->getMBID() }) - return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/mbid/" + std::string {mbid->getAsString()} }; + return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/mbid/" + std::string{ mbid->getAsString() } }; else return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString() }; } @@ -273,7 +279,7 @@ namespace lms::ui::utils Wt::WLink createReleaseLink(db::Release::pointer release) { if (const auto mbid{ release->getMBID() }) - return Wt::WLink{ Wt::LinkType::InternalPath, "/release/mbid/" + std::string {mbid->getAsString()} }; + return Wt::WLink{ Wt::LinkType::InternalPath, "/release/mbid/" + std::string{ mbid->getAsString() } }; else return Wt::WLink{ Wt::LinkType::InternalPath, "/release/" + release->getId().toString() }; } @@ -300,7 +306,7 @@ namespace lms::ui::utils if (setText) { - const Wt::WString name{ Wt::WString::fromUTF8(std::string {trackList->getName()}) }; + const Wt::WString name{ Wt::WString::fromUTF8(std::string{ trackList->getName() }) }; res->setTextFormat(Wt::TextFormat::Plain); res->setText(name); res->setToolTip(name, Wt::TextFormat::Plain); @@ -308,4 +314,4 @@ namespace lms::ui::utils return res; } -} +} // namespace lms::ui::utils diff --git a/src/lms/ui/Utils.hpp b/src/lms/ui/Utils.hpp index f1db77c16..aab8de548 100644 --- a/src/lms/ui/Utils.hpp +++ b/src/lms/ui/Utils.hpp @@ -38,36 +38,36 @@ namespace lms::db { - class Artist; - class Cluster; - class Release; - class Track; - class TrackList; -} + class Artist; + class Cluster; + class Release; + class Track; + class TrackList; +} // namespace lms::db namespace lms::ui { - class Filters; + class Filters; } namespace lms::ui::utils { - std::string durationToString(std::chrono::milliseconds msDuration); + std::string durationToString(std::chrono::milliseconds msDuration); - std::unique_ptr createCover(db::ReleaseId releaseId, CoverResource::Size size); - std::unique_ptr createCover(db::TrackId trackId, CoverResource::Size size); + std::unique_ptr createCover(db::ReleaseId releaseId, CoverResource::Size size); + std::unique_ptr createCover(db::TrackId trackId, CoverResource::Size size); - std::unique_ptr createFilter(const Wt::WString& name, const Wt::WString& tooltip, std::string_view colorStyleClass, bool canDelete = false); - std::unique_ptr createFilterCluster(db::ClusterId clusterId, bool canDelete = false); - std::unique_ptr createFilterClustersForTrack(db::ObjectPtr track, Filters& filters); + std::unique_ptr createFilter(const Wt::WString& name, const Wt::WString& tooltip, std::string_view colorStyleClass, bool canDelete = false); + std::unique_ptr createFilterCluster(db::ClusterId clusterId, bool canDelete = false); + std::unique_ptr createFilterClustersForTrack(db::ObjectPtr track, Filters& filters); - std::unique_ptr createArtistAnchorList(const std::vector& artistIds, std::string_view cssAnchorClass = "link-success"); - std::unique_ptr createArtistDisplayNameWithAnchors(std::string_view displayName, const std::vector& artistIds, std::string_view cssAnchorClass = "link-success"); - std::unique_ptr createArtistsAnchorsForRelease(db::ObjectPtr release, db::ArtistId omitIfMatchThisArtist = {}, std::string_view cssAnchorClass = "link-success"); + std::unique_ptr createArtistAnchorList(const std::vector& artistIds, std::string_view cssAnchorClass = "link-success"); + std::unique_ptr createArtistDisplayNameWithAnchors(std::string_view displayName, const std::vector& artistIds, std::string_view cssAnchorClass = "link-success"); + std::unique_ptr createArtistsAnchorsForRelease(db::ObjectPtr release, db::ArtistId omitIfMatchThisArtist = {}, std::string_view cssAnchorClass = "link-success"); - Wt::WLink createArtistLink(db::ObjectPtr artist); - std::unique_ptr createArtistAnchor(db::ObjectPtr artist, bool setText = true); - Wt::WLink createReleaseLink(db::ObjectPtr release); - std::unique_ptr createReleaseAnchor(db::ObjectPtr release, bool setText = true); - std::unique_ptr createTrackListAnchor(db::ObjectPtr trackList, bool setText = true); -} + Wt::WLink createArtistLink(db::ObjectPtr artist); + std::unique_ptr createArtistAnchor(db::ObjectPtr artist, bool setText = true); + Wt::WLink createReleaseLink(db::ObjectPtr release); + std::unique_ptr createReleaseAnchor(db::ObjectPtr release, bool setText = true); + std::unique_ptr createTrackListAnchor(db::ObjectPtr trackList, bool setText = true); +} // namespace lms::ui::utils diff --git a/src/lms/ui/admin/InitWizardView.cpp b/src/lms/ui/admin/InitWizardView.cpp index da3536d05..0a4ca1eb6 100644 --- a/src/lms/ui/admin/InitWizardView.cpp +++ b/src/lms/ui/admin/InitWizardView.cpp @@ -24,130 +24,129 @@ #include #include -#include "services/auth/IPasswordService.hpp" -#include "database/Session.hpp" -#include "database/User.hpp" #include "core/Exception.hpp" #include "core/ILogger.hpp" #include "core/Service.hpp" +#include "database/Session.hpp" +#include "database/User.hpp" +#include "services/auth/IPasswordService.hpp" +#include "LmsApplication.hpp" #include "common/LoginNameValidator.hpp" #include "common/MandatoryValidator.hpp" #include "common/PasswordValidator.hpp" -#include "LmsApplication.hpp" - -namespace lms::ui { -class InitWizardModel : public Wt::WFormModel +namespace lms::ui { - public: - // Associate each field with a unique string literal. - static inline const Field AdminLoginField {"admin-login"}; - static inline const Field PasswordField {"password"}; - static inline const Field PasswordConfirmField {"password-confirm"}; - - InitWizardModel() : Wt::WFormModel() - { - addField(AdminLoginField); - addField(PasswordField); - addField(PasswordConfirmField); - - setValidator(AdminLoginField, createLoginNameValidator()); - setValidator(PasswordField, createPasswordStrengthValidator([this] { return auth::PasswordValidationContext {valueText(AdminLoginField).toUTF8(), db::UserType::ADMIN}; })); - validator(PasswordField)->setMandatory(true); - setValidator(PasswordConfirmField, createMandatoryValidator()); - } - - void saveData() - { - auto transaction(LmsApp->getDbSession().createWriteTransaction()); - - // Check if a user already exist - // If it's the case, just do nothing - if (db::User::getCount(LmsApp->getDbSession()) > 0) - throw core::LmsException {"Admin user already created"}; - - db::User::pointer user {LmsApp->getDbSession().create(valueText(AdminLoginField).toUTF8())}; - user.modify()->setType(db::UserType::ADMIN); - core::Service::get()->setPassword(user->getId(), valueText(PasswordField).toUTF8()); - } - - bool validateField(Field field) - { - Wt::WString error; - - if (field == PasswordConfirmField) - { - if (validation(PasswordField).state() == Wt::ValidationState::Valid - && valueText(PasswordField) != valueText(PasswordConfirmField)) - { - error = Wt::WString::tr("Lms.passwords-dont-match"); - } - else - return Wt::WFormModel::validateField(field); - } - else - { - return Wt::WFormModel::validateField(field); - } - - setValidation(field, Wt::WValidator::Result {Wt::ValidationState::Invalid, error}); - - return false; - } -}; - -InitWizardView::InitWizardView() -: Wt::WTemplateFormView {Wt::WString::tr("Lms.Admin.InitWizard.template")} -{ - auto model = std::make_shared(); - - // AdminLogin - { - auto adminLogin {std::make_unique()}; - adminLogin->setAttributeValue("autocomplete", "username"); - setFormWidget(InitWizardModel::AdminLoginField, std::move(adminLogin)); - } - - // Password - { - auto passwordEdit = std::make_unique(); - passwordEdit->setEchoMode(Wt::EchoMode::Password); - passwordEdit->setAttributeValue("autocomplete", "current-password"); - setFormWidget(InitWizardModel::PasswordField, std::move(passwordEdit) ); - } - - // Password confirmation - { - auto passwordConfirmEdit = std::make_unique(); - passwordConfirmEdit->setEchoMode(Wt::EchoMode::Password); - passwordConfirmEdit->setAttributeValue("autocomplete", "current-password"); - setFormWidget(InitWizardModel::PasswordConfirmField, std::move(passwordConfirmEdit)); - } - - // Result notification - Wt::WText* resultNotification {bindNew("info")}; - resultNotification->setHidden(true); - - - Wt::WPushButton* saveButton = bindNew("create-btn", Wt::WString::tr("Lms.create")); - saveButton->clicked().connect([=, this] - { - updateModel(model.get()); - - if (model->validate()) - { - model->saveData(); - resultNotification->setText(Wt::WString::tr("Lms.Admin.InitWizard.done")); - resultNotification->setHidden(false); - saveButton->setEnabled(false); - } - - updateView(model.get()); - }); - - updateView(model.get()); -} -} // namespace lms::ui + class InitWizardModel : public Wt::WFormModel + { + public: + // Associate each field with a unique string literal. + static inline const Field AdminLoginField{ "admin-login" }; + static inline const Field PasswordField{ "password" }; + static inline const Field PasswordConfirmField{ "password-confirm" }; + + InitWizardModel() + : Wt::WFormModel() + { + addField(AdminLoginField); + addField(PasswordField); + addField(PasswordConfirmField); + + setValidator(AdminLoginField, createLoginNameValidator()); + setValidator(PasswordField, createPasswordStrengthValidator([this] { return auth::PasswordValidationContext{ valueText(AdminLoginField).toUTF8(), db::UserType::ADMIN }; })); + validator(PasswordField)->setMandatory(true); + setValidator(PasswordConfirmField, createMandatoryValidator()); + } + + void saveData() + { + auto transaction(LmsApp->getDbSession().createWriteTransaction()); + + // Check if a user already exist + // If it's the case, just do nothing + if (db::User::getCount(LmsApp->getDbSession()) > 0) + throw core::LmsException{ "Admin user already created" }; + + db::User::pointer user{ LmsApp->getDbSession().create(valueText(AdminLoginField).toUTF8()) }; + user.modify()->setType(db::UserType::ADMIN); + core::Service::get()->setPassword(user->getId(), valueText(PasswordField).toUTF8()); + } + + bool validateField(Field field) + { + Wt::WString error; + + if (field == PasswordConfirmField) + { + if (validation(PasswordField).state() == Wt::ValidationState::Valid + && valueText(PasswordField) != valueText(PasswordConfirmField)) + { + error = Wt::WString::tr("Lms.passwords-dont-match"); + } + else + return Wt::WFormModel::validateField(field); + } + else + { + return Wt::WFormModel::validateField(field); + } + + setValidation(field, Wt::WValidator::Result{ Wt::ValidationState::Invalid, error }); + + return false; + } + }; + + InitWizardView::InitWizardView() + : Wt::WTemplateFormView{ Wt::WString::tr("Lms.Admin.InitWizard.template") } + { + auto model = std::make_shared(); + + // AdminLogin + { + auto adminLogin{ std::make_unique() }; + adminLogin->setAttributeValue("autocomplete", "username"); + setFormWidget(InitWizardModel::AdminLoginField, std::move(adminLogin)); + } + + // Password + { + auto passwordEdit = std::make_unique(); + passwordEdit->setEchoMode(Wt::EchoMode::Password); + passwordEdit->setAttributeValue("autocomplete", "current-password"); + setFormWidget(InitWizardModel::PasswordField, std::move(passwordEdit)); + } + + // Password confirmation + { + auto passwordConfirmEdit = std::make_unique(); + passwordConfirmEdit->setEchoMode(Wt::EchoMode::Password); + passwordConfirmEdit->setAttributeValue("autocomplete", "current-password"); + setFormWidget(InitWizardModel::PasswordConfirmField, std::move(passwordConfirmEdit)); + } + + // Result notification + Wt::WText* resultNotification{ bindNew("info") }; + resultNotification->setHidden(true); + + Wt::WPushButton* saveButton = bindNew("create-btn", Wt::WString::tr("Lms.create")); + saveButton->clicked().connect([=, this] { + updateModel(model.get()); + + if (model->validate()) + { + model->saveData(); + resultNotification->setText(Wt::WString::tr("Lms.Admin.InitWizard.done")); + resultNotification->setHidden(false); + saveButton->setEnabled(false); + } + + updateView(model.get()); + }); + + updateView(model.get()); + } +} // namespace lms::ui diff --git a/src/lms/ui/admin/InitWizardView.hpp b/src/lms/ui/admin/InitWizardView.hpp index de7813af9..dd416ea0f 100644 --- a/src/lms/ui/admin/InitWizardView.hpp +++ b/src/lms/ui/admin/InitWizardView.hpp @@ -21,14 +21,13 @@ #include -namespace lms::ui { - -class InitWizardView : public Wt::WTemplateFormView +namespace lms::ui { - public: - InitWizardView(); -}; - -} // namespace lms::ui + class InitWizardView : public Wt::WTemplateFormView + { + public: + InitWizardView(); + }; +} // namespace lms::ui diff --git a/src/lms/ui/admin/MediaLibrariesView.cpp b/src/lms/ui/admin/MediaLibrariesView.cpp index 776c213de..57731d91c 100644 --- a/src/lms/ui/admin/MediaLibrariesView.cpp +++ b/src/lms/ui/admin/MediaLibrariesView.cpp @@ -21,13 +21,13 @@ #include +#include "core/Service.hpp" #include "database/MediaLibrary.hpp" #include "database/Session.hpp" #include "services/scanner/IScannerService.hpp" -#include "core/Service.hpp" -#include "MediaLibraryModal.hpp" #include "LmsApplication.hpp" +#include "MediaLibraryModal.hpp" #include "ModalManager.hpp" namespace lms::ui @@ -39,33 +39,29 @@ namespace lms::ui _libraries = bindNew("libraries"); Wt::WPushButton* addBtn{ bindNew("add-btn", Wt::WString::tr("Lms.add")) }; - addBtn->clicked().connect(this, [this] - { - auto mediaLibraryModal{ std::make_unique(db::MediaLibraryId{}) }; - MediaLibraryModal* mediaLibraryModalPtr{ mediaLibraryModal.get() }; - - mediaLibraryModalPtr->saved().connect(this, [this, mediaLibraryModalPtr](db::MediaLibraryId newMediaLibraryId) - { - Wt::WTemplate* entry{ addEntry() }; - updateEntry(newMediaLibraryId, entry); - // No need to stop the current scan if we add stuff - LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.MediaLibraries.media-libraries"), Wt::WString::tr("Lms.Admin.MediaLibrary.library-created")); - LmsApp->getModalManager().dispose(mediaLibraryModalPtr); - }); - - mediaLibraryModalPtr->cancelled().connect(this, [mediaLibraryModalPtr] - { - LmsApp->getModalManager().dispose(mediaLibraryModalPtr); - }); - - LmsApp->getModalManager().show(std::move(mediaLibraryModal)); + addBtn->clicked().connect(this, [this] { + auto mediaLibraryModal{ std::make_unique(db::MediaLibraryId{}) }; + MediaLibraryModal* mediaLibraryModalPtr{ mediaLibraryModal.get() }; + + mediaLibraryModalPtr->saved().connect(this, [this, mediaLibraryModalPtr](db::MediaLibraryId newMediaLibraryId) { + Wt::WTemplate* entry{ addEntry() }; + updateEntry(newMediaLibraryId, entry); + // No need to stop the current scan if we add stuff + LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.MediaLibraries.media-libraries"), Wt::WString::tr("Lms.Admin.MediaLibrary.library-created")); + LmsApp->getModalManager().dispose(mediaLibraryModalPtr); }); - wApp->internalPathChanged().connect(this, [this] - { - refreshView(); + mediaLibraryModalPtr->cancelled().connect(this, [mediaLibraryModalPtr] { + LmsApp->getModalManager().dispose(mediaLibraryModalPtr); }); + LmsApp->getModalManager().show(std::move(mediaLibraryModal)); + }); + + wApp->internalPathChanged().connect(this, [this] { + refreshView(); + }); + refreshView(); } @@ -78,15 +74,14 @@ namespace lms::ui auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - db::MediaLibrary::find(LmsApp->getDbSession(), [&](const db::MediaLibrary::pointer& mediaLibrary) - { - const db::MediaLibraryId mediaLibraryId{ mediaLibrary->getId() }; - Wt::WTemplate* entry{ addEntry() }; - updateEntry(mediaLibraryId, entry); - }); + db::MediaLibrary::find(LmsApp->getDbSession(), [&](const db::MediaLibrary::pointer& mediaLibrary) { + const db::MediaLibraryId mediaLibraryId{ mediaLibrary->getId() }; + Wt::WTemplate* entry{ addEntry() }; + updateEntry(mediaLibraryId, entry); + }); } - void MediaLibrariesView::showDeleteLibraryModal(db::MediaLibraryId mediaLibraryId, Wt::WTemplate* libraryEntry) + void MediaLibrariesView::showDeleteLibraryModal(db::MediaLibraryId mediaLibraryId, Wt::WTemplate* libraryEntry) { using namespace db; @@ -95,30 +90,28 @@ namespace lms::ui Wt::WWidget* modalPtr{ modal.get() }; auto* delBtn{ modal->bindNew("del-btn", Wt::WString::tr("Lms.delete")) }; - delBtn->clicked().connect([=, this] + delBtn->clicked().connect([=, this] { { - { - auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; + auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - db::MediaLibrary::pointer mediaLibrary{ MediaLibrary::find(LmsApp->getDbSession(), mediaLibraryId) }; - if (mediaLibrary) - mediaLibrary.remove(); - } + db::MediaLibrary::pointer mediaLibrary{ MediaLibrary::find(LmsApp->getDbSession(), mediaLibraryId) }; + if (mediaLibrary) + mediaLibrary.remove(); + } - // Don't want the scanner to go on with wrong settings - core::Service::get()->requestReload(); - LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.MediaLibraries.media-libraries"), Wt::WString::tr("Lms.Admin.MediaLibrary.library-deleted")); + // Don't want the scanner to go on with wrong settings + core::Service::get()->requestReload(); + LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.MediaLibraries.media-libraries"), Wt::WString::tr("Lms.Admin.MediaLibrary.library-deleted")); - _libraries->removeWidget(libraryEntry); + _libraries->removeWidget(libraryEntry); - LmsApp->getModalManager().dispose(modalPtr); - }); + LmsApp->getModalManager().dispose(modalPtr); + }); auto* cancelBtn{ modal->bindNew("cancel-btn", Wt::WString::tr("Lms.cancel")) }; - cancelBtn->clicked().connect([=] - { - LmsApp->getModalManager().dispose(modalPtr); - }); + cancelBtn->clicked().connect([=] { + LmsApp->getModalManager().dispose(modalPtr); + }); LmsApp->getModalManager().show(std::move(modal)); } @@ -138,35 +131,31 @@ namespace lms::ui Wt::WPushButton* editBtn{ entry->bindNew("edit-btn", Wt::WString::tr("Lms.template.edit-btn"), Wt::TextFormat::XHTML) }; editBtn->setToolTip(Wt::WString::tr("Lms.edit")); - editBtn->clicked().connect([this, mediaLibraryId, entry] - { - auto mediaLibraryModal{ std::make_unique(mediaLibraryId) }; - MediaLibraryModal* mediaLibraryModalPtr{ mediaLibraryModal.get() }; - - mediaLibraryModalPtr->saved().connect(this, [=, this](db::MediaLibraryId newMediaLibraryId) - { - updateEntry(newMediaLibraryId, entry); + editBtn->clicked().connect([this, mediaLibraryId, entry] { + auto mediaLibraryModal{ std::make_unique(mediaLibraryId) }; + MediaLibraryModal* mediaLibraryModalPtr{ mediaLibraryModal.get() }; - // Don't want the scanner to go on with wrong settings - core::Service::get()->requestReload(); - LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.MediaLibraries.media-libraries"), Wt::WString::tr("Lms.settings-saved")); + mediaLibraryModalPtr->saved().connect(this, [=, this](db::MediaLibraryId newMediaLibraryId) { + updateEntry(newMediaLibraryId, entry); - LmsApp->getModalManager().dispose(mediaLibraryModalPtr); - }); + // Don't want the scanner to go on with wrong settings + core::Service::get()->requestReload(); + LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.MediaLibraries.media-libraries"), Wt::WString::tr("Lms.settings-saved")); - mediaLibraryModalPtr->cancelled().connect(this, [mediaLibraryModalPtr] - { - LmsApp->getModalManager().dispose(mediaLibraryModalPtr); - }); + LmsApp->getModalManager().dispose(mediaLibraryModalPtr); + }); - LmsApp->getModalManager().show(std::move(mediaLibraryModal)); + mediaLibraryModalPtr->cancelled().connect(this, [mediaLibraryModalPtr] { + LmsApp->getModalManager().dispose(mediaLibraryModalPtr); }); + LmsApp->getModalManager().show(std::move(mediaLibraryModal)); + }); + Wt::WPushButton* delBtn{ entry->bindNew("del-btn", Wt::WString::tr("Lms.template.trash-btn"), Wt::TextFormat::XHTML) }; delBtn->setToolTip(Wt::WString::tr("Lms.delete")); - delBtn->clicked().connect([this, mediaLibraryId, entry] - { - showDeleteLibraryModal(mediaLibraryId, entry); - }); + delBtn->clicked().connect([this, mediaLibraryId, entry] { + showDeleteLibraryModal(mediaLibraryId, entry); + }); } -} \ No newline at end of file +} // namespace lms::ui \ No newline at end of file diff --git a/src/lms/ui/admin/MediaLibrariesView.hpp b/src/lms/ui/admin/MediaLibrariesView.hpp index dbe35b8dc..8ba277e7c 100644 --- a/src/lms/ui/admin/MediaLibrariesView.hpp +++ b/src/lms/ui/admin/MediaLibrariesView.hpp @@ -19,8 +19,8 @@ #pragma once -#include #include +#include #include "database/MediaLibraryId.hpp" @@ -39,4 +39,4 @@ namespace lms::ui Wt::WContainerWidget* _libraries{}; }; -} \ No newline at end of file +} // namespace lms::ui \ No newline at end of file diff --git a/src/lms/ui/admin/MediaLibraryModal.cpp b/src/lms/ui/admin/MediaLibraryModal.cpp index 4e33a56f3..d02218504 100644 --- a/src/lms/ui/admin/MediaLibraryModal.cpp +++ b/src/lms/ui/admin/MediaLibraryModal.cpp @@ -24,10 +24,11 @@ #include #include -#include "database/MediaLibrary.hpp" -#include "database/Session.hpp" #include "core/Path.hpp" #include "core/String.hpp" +#include "database/MediaLibrary.hpp" +#include "database/Session.hpp" + #include "LmsApplication.hpp" namespace lms::ui @@ -39,7 +40,8 @@ namespace lms::ui class LibraryNameValidator : public Wt::WValidator { public: - LibraryNameValidator(MediaLibraryId libraryId) : _libraryId{ libraryId } {} + LibraryNameValidator(MediaLibraryId libraryId) + : _libraryId{ libraryId } {} private: Wt::WValidator::Result validate(const Wt::WString& input) const override @@ -53,14 +55,13 @@ namespace lms::ui auto& session{ LmsApp->getDbSession() }; auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - db::MediaLibrary::find(session, [&](const db::MediaLibrary::pointer library) - { - if (library->getId() == _libraryId) - return; + db::MediaLibrary::find(session, [&](const db::MediaLibrary::pointer library) { + if (library->getId() == _libraryId) + return; - if (core::stringUtils::stringCaseInsensitiveEqual(name, library->getName())) - result = Wt::WValidator::Result{ Wt::ValidationState::Invalid, Wt::WString::tr("Lms.Admin.MediaLibrary.name-already-exists") }; - }); + if (core::stringUtils::stringCaseInsensitiveEqual(name, library->getName())) + result = Wt::WValidator::Result{ Wt::ValidationState::Invalid, Wt::WString::tr("Lms.Admin.MediaLibrary.name-already-exists") }; + }); return result; } @@ -71,7 +72,8 @@ namespace lms::ui class LibraryRootPathValidator : public Wt::WValidator { public: - LibraryRootPathValidator(MediaLibraryId libraryId) : _libraryId{ libraryId } {} + LibraryRootPathValidator(MediaLibraryId libraryId) + : _libraryId{ libraryId } {} private: Wt::WValidator::Result validate(const Wt::WString& input) const override @@ -96,20 +98,19 @@ namespace lms::ui auto transaction{ LmsApp->getDbSession().createReadTransaction() }; Wt::WValidator::Result result{ Wt::ValidationState::Valid }; - const std::filesystem::path rootPath{ std::filesystem::path{input.toUTF8()}.lexically_normal() }; - db::MediaLibrary::find(session, [&](const db::MediaLibrary::pointer library) - { - if (library->getId() == _libraryId) - return; + const std::filesystem::path rootPath{ std::filesystem::path{ input.toUTF8() }.lexically_normal() }; + db::MediaLibrary::find(session, [&](const db::MediaLibrary::pointer library) { + if (library->getId() == _libraryId) + return; - const std::filesystem::path libraryRootPath{ library->getPath().lexically_normal() }; + const std::filesystem::path libraryRootPath{ library->getPath().lexically_normal() }; - if (core::pathUtils::isPathInRootPath(rootPath, libraryRootPath) - || core::pathUtils::isPathInRootPath(libraryRootPath, rootPath)) - { - result = Wt::WValidator::Result{ Wt::ValidationState::Invalid, Wt::WString::tr("Lms.Admin.MediaLibrary.path-must-not-overlap") }; - } - }); + if (core::pathUtils::isPathInRootPath(rootPath, libraryRootPath) + || core::pathUtils::isPathInRootPath(libraryRootPath, rootPath)) + { + result = Wt::WValidator::Result{ Wt::ValidationState::Invalid, Wt::WString::tr("Lms.Admin.MediaLibrary.path-must-not-overlap") }; + } + }); return result; } @@ -176,7 +177,7 @@ namespace lms::ui const MediaLibraryId _libraryId; }; - } + } // namespace MediaLibraryModal::MediaLibraryModal(MediaLibraryId mediaLibraryId) : Wt::WTemplateFormView{ Wt::WString::tr("Lms.Admin.MediaLibrary.template") } @@ -189,24 +190,23 @@ namespace lms::ui setFormWidget(MediaLibraryModel::DirectoryField, std::make_unique()); Wt::WPushButton* saveBtn{ bindNew("save-btn", Wt::WString::tr(mediaLibraryId.isValid() ? "Lms.save" : "Lms.create")) }; - saveBtn->clicked().connect(this, [this, model] - { - updateModel(model.get()); + saveBtn->clicked().connect(this, [this, model] { + updateModel(model.get()); - if (model->validate()) - { - db::MediaLibraryId mediaLibraryId{ model->saveData() }; - saved().emit(mediaLibraryId); - } - else - { - updateView(model.get()); - } - }); + if (model->validate()) + { + db::MediaLibraryId mediaLibraryId{ model->saveData() }; + saved().emit(mediaLibraryId); + } + else + { + updateView(model.get()); + } + }); Wt::WPushButton* cancelBtn{ bindNew("cancel-btn", Wt::WString::tr("Lms.cancel")) }; cancelBtn->clicked().connect(this, [this] { cancelled().emit(); }); updateView(model.get()); } -} +} // namespace lms::ui diff --git a/src/lms/ui/admin/MediaLibraryModal.hpp b/src/lms/ui/admin/MediaLibraryModal.hpp index aae9616d2..03f206898 100644 --- a/src/lms/ui/admin/MediaLibraryModal.hpp +++ b/src/lms/ui/admin/MediaLibraryModal.hpp @@ -21,6 +21,7 @@ #include #include + #include "database/MediaLibraryId.hpp" namespace lms::ui @@ -37,4 +38,4 @@ namespace lms::ui Wt::Signal _saved; Wt::Signal<> _cancelled; }; -} \ No newline at end of file +} // namespace lms::ui \ No newline at end of file diff --git a/src/lms/ui/admin/ScanSettingsView.cpp b/src/lms/ui/admin/ScanSettingsView.cpp index f18781019..872bd2c7a 100644 --- a/src/lms/ui/admin/ScanSettingsView.cpp +++ b/src/lms/ui/admin/ScanSettingsView.cpp @@ -26,18 +26,18 @@ #include #include +#include "core/ILogger.hpp" +#include "core/Service.hpp" +#include "core/String.hpp" #include "database/ScanSettings.hpp" #include "database/Session.hpp" #include "services/recommendation/IRecommendationService.hpp" #include "services/scanner/IScannerService.hpp" -#include "core/ILogger.hpp" -#include "core/Service.hpp" -#include "core/String.hpp" +#include "LmsApplication.hpp" #include "common/MandatoryValidator.hpp" #include "common/UppercaseValidator.hpp" #include "common/ValueStringModel.hpp" -#include "LmsApplication.hpp" namespace lms::ui { @@ -51,12 +51,12 @@ namespace lms::ui Wt::WValidator::Result validate(const Wt::WString& input) const override { if (input.empty()) - return Wt::WValidator::Result{ Wt::ValidationState::Valid }; + return Wt::WValidator::validate(input); std::string inputStr{ input.toUTF8() }; if (std::all_of(std::cbegin(inputStr), std::cend(inputStr), [](char c) { return std::isspace(c); })) return Wt::WValidator::Result{ Wt::ValidationState::Invalid, Wt::WString::tr("Lms.Admin.Database.tag-delimiter-must-not-contain-only-spaces") }; - + return Wt::WValidator::Result{ Wt::ValidationState::Valid }; } @@ -69,14 +69,9 @@ namespace lms::ui static inline constexpr Field UpdatePeriodField{ "update-period" }; static inline constexpr Field UpdateStartTimeField{ "update-start-time" }; static inline constexpr Field SimilarityEngineTypeField{ "similarity-engine-type" }; - static inline constexpr Field ExtraTagsField{ "extra-tags-to-scan" }; - static inline constexpr Field ArtistTagDelimiterField{ "artist-tag-delimiter" }; - static inline constexpr Field DefaultTagDelimiterField{ "default-tag-delimiter" }; using UpdatePeriodModel = ValueStringModel; - static inline constexpr char extraTagsDelimiter{ ';' }; - DatabaseSettingsModel() { initializeModels(); @@ -84,26 +79,17 @@ namespace lms::ui addField(UpdatePeriodField); addField(UpdateStartTimeField); addField(SimilarityEngineTypeField); - addField(ExtraTagsField); - addField(ArtistTagDelimiterField); - addField(DefaultTagDelimiterField); setValidator(UpdatePeriodField, createMandatoryValidator()); setValidator(UpdateStartTimeField, createMandatoryValidator()); setValidator(SimilarityEngineTypeField, createMandatoryValidator()); - setValidator(ExtraTagsField, createUppercaseValidator()); - setValidator(ArtistTagDelimiterField, std::make_unique()); - setValidator(DefaultTagDelimiterField, std::make_unique()); - - // populate the model with initial data - loadData(); } std::shared_ptr updatePeriodModel() { return _updatePeriodModel; } std::shared_ptr updateStartTimeModel() { return _updateStartTimeModel; } std::shared_ptr similarityEngineTypeModel() { return _similarityEngineTypeModel; } - void loadData() + void loadData(std::vector& extraTagsToScan, std::vector& artistDelimiters, std::vector& defaultDelimiters) { auto transaction{ LmsApp->getDbSession().createReadTransaction() }; @@ -128,20 +114,13 @@ namespace lms::ui setValue(SimilarityEngineTypeField, _similarityEngineTypeModel->getString(*similarityEngineTypeRow)); const auto extraTags{ scanSettings->getExtraTagsToScan() }; - setValue(ExtraTagsField, core::stringUtils::joinStrings(extraTags, extraTagsDelimiter)); - - { - std::vector delimiters{ scanSettings->getArtistTagDelimiters() }; - setValue(ArtistTagDelimiterField, delimiters.empty() ? "" : delimiters.front()); - } - - { - std::vector delimiters{ scanSettings->getDefaultTagDelimiters() }; - setValue(DefaultTagDelimiterField, delimiters.empty() ? "" : delimiters.front()); - } + extraTagsToScan.clear(); + std::transform(std::cbegin(extraTags), std::cend(extraTags), std::back_inserter(extraTagsToScan), [](std::string_view extraTag) { return std::string{ extraTag }; }); + artistDelimiters = scanSettings->getArtistTagDelimiters(); + defaultDelimiters = scanSettings->getDefaultTagDelimiters(); } - void saveData() + void saveData(std::span extraTagsToScan, std::span artistDelimiters, std::span defaultDelimiters) { auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; @@ -159,21 +138,9 @@ namespace lms::ui if (similarityEngineTypeRow) scanSettings.modify()->setSimilarityEngineType(_similarityEngineTypeModel->getValue(*similarityEngineTypeRow)); - scanSettings.modify()->setExtraTagsToScan(core::stringUtils::splitString(valueText(ExtraTagsField).toUTF8(), extraTagsDelimiter)); - - { - std::vector artistDelimiters; - if (std::string artistDelimiter{ valueText(ArtistTagDelimiterField).toUTF8() }; !artistDelimiter.empty()) - artistDelimiters.push_back(std::move(artistDelimiter)); - scanSettings.modify()->setArtistTagDelimiters(artistDelimiters); - } - - { - std::vector defaultDelimiters; - if (std::string defaultDelimiter{ valueText(DefaultTagDelimiterField).toUTF8() }; !defaultDelimiter.empty()) - defaultDelimiters.push_back(std::move(defaultDelimiter)); - scanSettings.modify()->setDefaultTagDelimiters(defaultDelimiters); - } + scanSettings.modify()->setExtraTagsToScan(extraTagsToScan); + scanSettings.modify()->setArtistTagDelimiters(artistDelimiters); + scanSettings.modify()->setDefaultTagDelimiters(defaultDelimiters); } private: @@ -198,18 +165,129 @@ namespace lms::ui _similarityEngineTypeModel->add(Wt::WString::tr("Lms.Admin.Database.similarity-engine-type.none"), ScanSettings::SimilarityEngineType::None); } - std::shared_ptr _updatePeriodModel; - std::shared_ptr> _updateStartTimeModel; - std::shared_ptr> _similarityEngineTypeModel; + std::shared_ptr _updatePeriodModel; + std::shared_ptr> _updateStartTimeModel; + std::shared_ptr> _similarityEngineTypeModel; }; - } + + class LineEditEntryModel : public Wt::WFormModel + { + public: + static inline constexpr Field ValueField{ "value" }; + + LineEditEntryModel(const Wt::WString& initialValue, std::shared_ptr validator) + : Wt::WFormModel() + { + addField(ValueField); + + setValidator(ValueField, validator); + setValue(ValueField, initialValue); + } + }; + + class LineEditEntryWidget : public Wt::WTemplateFormView + { + public: + LineEditEntryWidget(const Wt::WString& initialValue, std::shared_ptr validator) + : Wt::WTemplateFormView{ Wt::WString::tr("Lms.Admin.Database.template.line-edit-entry") } + , _model{ std::make_shared(initialValue, validator) } + { + setStyleClass("col-sm-4 col-md-3"); // hack + + setFormWidget(LineEditEntryModel::ValueField, std::make_unique()); + + auto* delBtn{ bindNew("del-btn", Wt::WString::tr("Lms.template.trash-btn"), Wt::TextFormat::XHTML) }; + delBtn->clicked().connect(this, [this] { deleted.emit(); }); + } + + bool validate() { return _model->validate(); } + void updateModel() { Wt::WTemplateFormView::updateModel(_model.get()); } + void updateView() { Wt::WTemplateFormView::updateView(_model.get()); } + + Wt::WString getValue() const + { + return _model->valueText(LineEditEntryModel::ValueField); + } + + Wt::Signal<> deleted; + std::shared_ptr _model; + }; + + // Terrible hack to make use of the validation system for each added element + class LineEditContainerWidget : public Wt::WContainerWidget + { + public: + LineEditContainerWidget(std::shared_ptr validator) + : _validator{ validator } {} + + void add(const Wt::WString& value = "") + { + auto* entry{ addNew(value, _validator) }; + + entry->deleted.connect(this, [=, this] { + removeWidget(entry); + }); + } + + bool validate() + { + bool res{ true }; + + for (int i{}; i < count(); ++i) + { + LineEditEntryWidget* entry{ static_cast(widget(i)) }; + res &= entry->validate(); + } + + return res; + } + + void updateModels() + { + for (int i{}; i < count(); ++i) + { + LineEditEntryWidget* entry{ static_cast(widget(i)) }; + entry->updateModel(); + } + } + + void updateViews() + { + for (int i{}; i < count(); ++i) + { + LineEditEntryWidget* entry{ static_cast(widget(i)) }; + entry->updateView(); + } + } + + void visitValues(std::function visitor) const + { + for (int i{}; i < count(); ++i) + { + LineEditEntryWidget* entry{ static_cast(widget(i)) }; + visitor(entry->getValue()); + } + } + + std::vector getValues() const + { + std::vector values; + visitValues([&](const Wt::WString& value) { + values.push_back(value.toUTF8()); + }); + return values; + } + + private: + std::shared_ptr _validator; + }; + } // namespace ScanSettingsView::ScanSettingsView() { - wApp->internalPathChanged().connect(this, [this]() - { - refreshView(); - }); + wApp->internalPathChanged().connect(this, [this]() { + refreshView(); + }); refreshView(); } @@ -227,13 +305,12 @@ namespace lms::ui // Update Period auto updatePeriod{ std::make_unique() }; updatePeriod->setModel(model->updatePeriodModel()); - updatePeriod->activated().connect([=](int row) - { - const ScanSettings::UpdatePeriod period{ model->updatePeriodModel()->getValue(row) }; - model->setReadOnly(DatabaseSettingsModel::UpdateStartTimeField, period == ScanSettings::UpdatePeriod::Hourly || period == ScanSettings::UpdatePeriod::Never); - t->updateModel(model.get()); - t->updateView(model.get()); - }); + updatePeriod->activated().connect([=](int row) { + const ScanSettings::UpdatePeriod period{ model->updatePeriodModel()->getValue(row) }; + model->setReadOnly(DatabaseSettingsModel::UpdateStartTimeField, period == ScanSettings::UpdatePeriod::Hourly || period == ScanSettings::UpdatePeriod::Never); + t->updateModel(model.get()); + t->updateView(model.get()); + }); t->setFormWidget(DatabaseSettingsModel::UpdatePeriodField, std::move(updatePeriod)); // Update Start Time @@ -247,44 +324,120 @@ namespace lms::ui t->setFormWidget(DatabaseSettingsModel::SimilarityEngineTypeField, std::move(similarityEngineType)); // Extra tags - t->setFormWidget(DatabaseSettingsModel::ExtraTagsField, std::make_unique()); + std::shared_ptr extraTagValidator{ createUppercaseValidator() }; + extraTagValidator->setMandatory(true); + auto* extraTagsToScan{ t->bindNew("extra-tags-to-scan-container", extraTagValidator) }; + { + auto* addExtraScanToScanBtn{ t->bindNew("extra-tags-to-scan-add-btn", Wt::WString::tr("Lms.add")) }; + addExtraScanToScanBtn->clicked().connect(this, [=] { + extraTagsToScan->add(); + }); + } // Artist tag delimiter - t->setFormWidget(DatabaseSettingsModel::ArtistTagDelimiterField, std::make_unique()); + std::shared_ptr tagDelimiterValidator{ std::make_shared() }; + tagDelimiterValidator->setMandatory(true); + + auto* artistTagDelimiters{ t->bindNew("artist-tag-delimiter-container", tagDelimiterValidator) }; + { + auto* addArtistDelimiterBtn{ t->bindNew("artist-tag-delimiter-add-btn", Wt::WString::tr("Lms.add")) }; + addArtistDelimiterBtn->clicked().connect(this, [=] { + artistTagDelimiters->add(); + }); + } // Default tag delimiter - t->setFormWidget(DatabaseSettingsModel::DefaultTagDelimiterField, std::make_unique()); + auto* defaultTagDelimiters{ t->bindNew("default-tag-delimiter-container", tagDelimiterValidator) }; + { + auto* addDefaultDelimiterBtn{ t->bindNew("default-tag-delimiter-add-btn", Wt::WString::tr("Lms.add")) }; + addDefaultDelimiterBtn->clicked().connect(this, [=] { + defaultTagDelimiters->add(); + }); + } // Buttons - Wt::WPushButton* saveBtn = t->bindWidget("save-btn", std::make_unique(Wt::WString::tr("Lms.save"))); - Wt::WPushButton* discardBtn = t->bindWidget("discard-btn", std::make_unique(Wt::WString::tr("Lms.discard"))); - - saveBtn->clicked().connect([=] + Wt::WPushButton* saveBtn{ t->bindWidget("save-btn", std::make_unique(Wt::WString::tr("Lms.save"))) }; + Wt::WPushButton* discardBtn{ t->bindWidget("discard-btn", std::make_unique(Wt::WString::tr("Lms.discard"))) }; + + auto validate{ [=] { + bool res{ true }; + + res &= model->validate(); + res &= extraTagsToScan->validate(); + res &= artistTagDelimiters->validate(); + res &= defaultTagDelimiters->validate(); + + return res; + } }; + + auto updateModels{ [=] { + t->updateModel(model.get()); + extraTagsToScan->updateModels(); + artistTagDelimiters->updateModels(); + defaultTagDelimiters->updateModels(); + } }; + + auto updateViews{ [=] { + t->updateView(model.get()); + extraTagsToScan->updateViews(); + artistTagDelimiters->updateViews(); + defaultTagDelimiters->updateViews(); + } }; + + auto loadInitialData{ [=] { + std::vector extraTags; + std::vector artistDelimiters; + std::vector defaultDelimiters; + model->loadData(extraTags, artistDelimiters, defaultDelimiters); + + extraTagsToScan->clear(); + for (const std::string& extraTag : extraTags) + extraTagsToScan->add(Wt::WString::fromUTF8(std::string{ extraTag })); + + artistTagDelimiters->clear(); + for (const std::string& artistDelimiter : artistDelimiters) + artistTagDelimiters->add(Wt::WString::fromUTF8(artistDelimiter)); + + defaultTagDelimiters->clear(); + for (const std::string& defaultDelimiter : defaultDelimiters) + defaultTagDelimiters->add(Wt::WString::fromUTF8(defaultDelimiter)); + } }; + + saveBtn->clicked().connect([=] { + updateModels(); + if (validate()) { - t->updateModel(model.get()); + const std::vector extraTags{ extraTagsToScan->getValues() }; + std::vector extraTagViews; + std::transform(std::cbegin(extraTags), std::cend(extraTags), std::back_inserter(extraTagViews), [](const std::string& tag) -> std::string_view { return tag; }); - if (model->validate()) - { - model->saveData(); + const std::vector artistDelimiters{ artistTagDelimiters->getValues() }; + std::vector artistDelimiterViews; + std::transform(std::cbegin(artistDelimiters), std::cend(artistDelimiters), std::back_inserter(artistDelimiterViews), [](const std::string& delimiter) -> std::string_view { return delimiter; }); - core::Service::get()->load(); - // Don't want the scanner to go on with wrong settings - core::Service::get()->requestReload(); - LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.Database.database"), Wt::WString::tr("Lms.settings-saved")); - } + const std::vector defaultDelimiters{ defaultTagDelimiters->getValues() }; + std::vector defaultDelimiterViews; + std::transform(std::cbegin(defaultDelimiters), std::cend(defaultDelimiters), std::back_inserter(defaultDelimiterViews), [](const std::string& delimiter) -> std::string_view { return delimiter; }); - // Udate the view: Delete any validation message in the view, etc. - t->updateView(model.get()); - }); + model->saveData(extraTagViews, artistDelimiterViews, defaultDelimiterViews); - discardBtn->clicked().connect([=] - { - model->loadData(); - model->validate(); - t->updateView(model.get()); - }); + core::Service::get()->load(); + // Don't want the scanner to go on with wrong settings + core::Service::get()->requestReload(); + LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.Database.database"), Wt::WString::tr("Lms.settings-saved")); + } - t->updateView(model.get()); - } + // Udate the view: Delete any validation message in the view, etc. + updateViews(); + }); + + discardBtn->clicked().connect([=] { + loadInitialData(); + validate(); + updateViews(); + }); + loadInitialData(); + updateViews(); + } } // namespace lms::ui diff --git a/src/lms/ui/admin/ScannerController.cpp b/src/lms/ui/admin/ScannerController.cpp index 467504ec2..4ccf7fdaf 100644 --- a/src/lms/ui/admin/ScannerController.cpp +++ b/src/lms/ui/admin/ScannerController.cpp @@ -27,10 +27,11 @@ #include #include +#include "core/Service.hpp" #include "database/Session.hpp" #include "database/Track.hpp" #include "services/scanner/IScannerService.hpp" -#include "core/Service.hpp" + #include "LmsApplication.hpp" namespace lms::ui @@ -51,7 +52,7 @@ namespace lms::ui return oss.str(); } - } + } // namespace class ReportResource : public Wt::WResource { @@ -116,10 +117,16 @@ namespace lms::ui { switch (error) { - case scanner::ScanErrorType::CannotReadFile: return Wt::WString::tr("Lms.Admin.ScannerController.cannot-read-file"); - case scanner::ScanErrorType::CannotParseFile: return Wt::WString::tr("Lms.Admin.ScannerController.cannot-parse-file"); - case scanner::ScanErrorType::NoAudioTrack: return Wt::WString::tr("Lms.Admin.ScannerController.no-audio-track"); - case scanner::ScanErrorType::BadDuration: return Wt::WString::tr("Lms.Admin.ScannerController.bad-duration"); + case scanner::ScanErrorType::CannotReadFile: + return Wt::WString::tr("Lms.Admin.ScannerController.cannot-read-file"); + case scanner::ScanErrorType::CannotReadAudioFile: + return Wt::WString::tr("Lms.Admin.ScannerController.cannot-read-audio-file"); + case scanner::ScanErrorType::CannotReadImageFile: + return Wt::WString::tr("Lms.Admin.ScannerController.cannot-read-image-file"); + case scanner::ScanErrorType::NoAudioTrack: + return Wt::WString::tr("Lms.Admin.ScannerController.no-audio-track"); + case scanner::ScanErrorType::BadDuration: + return Wt::WString::tr("Lms.Admin.ScannerController.bad-duration"); } return "?"; } @@ -128,8 +135,10 @@ namespace lms::ui { switch (reason) { - case scanner::DuplicateReason::SameHash: return Wt::WString::tr("Lms.Admin.ScannerController.same-hash"); - case scanner::DuplicateReason::SameTrackMBID: return Wt::WString::tr("Lms.Admin.ScannerController.same-mbid"); + case scanner::DuplicateReason::SameHash: + return Wt::WString::tr("Lms.Admin.ScannerController.same-hash"); + case scanner::DuplicateReason::SameTrackMBID: + return Wt::WString::tr("Lms.Admin.ScannerController.same-mbid"); } return "?"; } @@ -161,16 +170,14 @@ namespace lms::ui Wt::WCheckBox* forceOptimize{ bindNew("force-optimize") }; Wt::WCheckBox* compact{ bindNew("compact") }; Wt::WPushButton* scanBtn{ bindNew("scan-btn", Wt::WString::tr("Lms.Admin.ScannerController.scan-now")) }; - scanBtn->clicked().connect([=] - { - const scanner::ScanOptions scanOptions - { - .fullScan = fullScan->isChecked(), - .forceOptimize = forceOptimize->isChecked(), - .compact = compact->isChecked(), - }; - core::Service::get()->requestImmediateScan(scanOptions); - }); + scanBtn->clicked().connect([=] { + const scanner::ScanOptions scanOptions{ + .fullScan = fullScan->isChecked(), + .forceOptimize = forceOptimize->isChecked(), + .compact = compact->isChecked(), + }; + core::Service::get()->requestImmediateScan(scanOptions); + }); _lastScanStatus = bindNew("last-scan"); _lastScanStatus->setReadOnly(true); @@ -183,14 +190,12 @@ namespace lms::ui auto onDbEvent{ [&]() { refreshContents(); } }; - LmsApp->getScannerEvents().scanAborted.connect(this, [] - { - LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.Database.database"), Wt::WString::tr("Lms.Admin.Database.scan-aborted")); - }); - LmsApp->getScannerEvents().scanStarted.connect(this, [] - { - LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.Database.database"), Wt::WString::tr("Lms.Admin.Database.scan-launched")); - }); + LmsApp->getScannerEvents().scanAborted.connect(this, [] { + LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.Database.database"), Wt::WString::tr("Lms.Admin.Database.scan-aborted")); + }); + LmsApp->getScannerEvents().scanStarted.connect(this, [] { + LmsApp->notifyMsg(Notification::Type::Info, Wt::WString::tr("Lms.Admin.Database.database"), Wt::WString::tr("Lms.Admin.Database.scan-launched")); + }); LmsApp->getScannerEvents().scanComplete.connect(this, onDbEvent); LmsApp->getScannerEvents().scanInProgress.connect(this, onDbEvent); LmsApp->getScannerEvents().scanScheduled.connect(this, onDbEvent); @@ -213,16 +218,14 @@ namespace lms::ui if (status.lastCompleteScanStats) { _lastScanStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.last-scan-status") - .arg(status.lastCompleteScanStats->nbFiles()) - .arg(durationToString(status.lastCompleteScanStats->startTime, status.lastCompleteScanStats->stopTime)) - .arg(status.lastCompleteScanStats->stopTime.toString()) - .arg(status.lastCompleteScanStats->errors.size()) - .arg(status.lastCompleteScanStats->duplicates.size()) - ); + .arg(status.lastCompleteScanStats->nbFiles()) + .arg(durationToString(status.lastCompleteScanStats->startTime, status.lastCompleteScanStats->stopTime)) + .arg(status.lastCompleteScanStats->stopTime.toString()) + .arg(status.lastCompleteScanStats->errors.size()) + .arg(status.lastCompleteScanStats->duplicates.size())); _reportResource->setScanStats(*status.lastCompleteScanStats); _reportBtn->setEnabled(true); - } else { @@ -244,7 +247,7 @@ namespace lms::ui case IScannerService::State::Scheduled: _status->setText(Wt::WString::tr("Lms.Admin.ScannerController.status-scheduled") - .arg(status.nextScheduledScan.toString())); + .arg(status.nextScheduledScan.toString())); _stepStatus->setText(""); break; @@ -252,8 +255,8 @@ namespace lms::ui assert(status.currentScanStepStats); _status->setText(Wt::WString::tr("Lms.Admin.ScannerController.status-in-progress") - .arg(status.currentScanStepStats->stepIndex + 1) - .arg(scanner::ScanProgressStepCount)); + .arg(status.currentScanStepStats->stepIndex + 1) + .arg(scanner::ScanProgressStepCount)); refreshCurrentStep(*status.currentScanStepStats); break; @@ -266,54 +269,62 @@ namespace lms::ui switch (stepStats.currentStep) { - case ScanStep::CheckForDuplicateFiles: + case ScanStep::AssociateArtistImages: + _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-associating-artist-images") + .arg(stepStats.progress())); + break; + + case ScanStep::CheckForDuplicatedFiles: _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-checking-for-duplicate-files") - .arg(stepStats.processedElems)); + .arg(stepStats.processedElems)); break; - case scanner::ScanStep::CheckForMissingFiles: - _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-checking-for-missing-files") - .arg(stepStats.progress())); + case ScanStep::CheckForRemovedFiles: + _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-checking-for-removed-files") + .arg(stepStats.progress())); break; - case scanner::ScanStep::Compact: + case ScanStep::Compact: _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-compact")); break; - case scanner::ScanStep::ComputeClusterStats: + case ScanStep::ComputeClusterStats: _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-compute-cluster-stats") - .arg(stepStats.progress())); + .arg(stepStats.progress())); break; - case scanner::ScanStep::DiscoverFiles: + case ScanStep::DiscoverFiles: _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-discovering-files") - .arg(stepStats.processedElems)); + .arg(stepStats.processedElems)); break; - case scanner::ScanStep::FetchTrackFeatures: + case ScanStep::FetchTrackFeatures: _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-fetching-track-features") - .arg(stepStats.processedElems) - .arg(stepStats.totalElems) - .arg(stepStats.progress())); + .arg(stepStats.processedElems) + .arg(stepStats.totalElems) + .arg(stepStats.progress())); break; - case scanner::ScanStep::Optimize: + case ScanStep::Optimize: _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-optimize") - .arg(stepStats.processedElems) - .arg(stepStats.totalElems) - .arg(stepStats.progress())); + .arg(stepStats.progress())); + break; + + case ScanStep::RemoveOrphanedDbEntries: + _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-removing-orphaned-entries") + .arg(stepStats.processedElems)); break; - case scanner::ScanStep::ReloadSimilarityEngine: + case ScanStep::ReloadSimilarityEngine: _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-reloading-similarity-engine") - .arg(stepStats.progress())); + .arg(stepStats.progress())); break; - case scanner::ScanStep::ScanFiles: + case ScanStep::ScanFiles: _stepStatus->setText(Wt::WString::tr("Lms.Admin.ScannerController.step-scanning-files") - .arg(stepStats.processedElems) - .arg(stepStats.totalElems) - .arg(stepStats.progress())); + .arg(stepStats.processedElems) + .arg(stepStats.totalElems) + .arg(stepStats.progress())); break; } } diff --git a/src/lms/ui/admin/ScannerController.hpp b/src/lms/ui/admin/ScannerController.hpp index e8091c7bb..e394f573b 100644 --- a/src/lms/ui/admin/ScannerController.hpp +++ b/src/lms/ui/admin/ScannerController.hpp @@ -19,9 +19,9 @@ #pragma once +#include #include #include -#include #include "services/scanner/IScannerService.hpp" @@ -44,4 +44,4 @@ namespace lms::ui Wt::WLineEdit* _stepStatus; class ReportResource* _reportResource; }; -} // namespace lms::dbStatus +} // namespace lms::ui diff --git a/src/lms/ui/admin/TracingView.cpp b/src/lms/ui/admin/TracingView.cpp index 91265b772..2d6b68125 100644 --- a/src/lms/ui/admin/TracingView.cpp +++ b/src/lms/ui/admin/TracingView.cpp @@ -19,13 +19,12 @@ #include "TracingView.hpp" -#include -#include - #include #include #include #include +#include +#include #include "core/ITraceLogger.hpp" #include "core/String.hpp" @@ -62,7 +61,7 @@ namespace lms::ui private: core::tracing::ITraceLogger& _traceLogger; }; - } + } // namespace TracingView::TracingView() : Wt::WTemplate{ Wt::WString::tr("Lms.Admin.Tracing.template") } diff --git a/src/lms/ui/admin/UserView.cpp b/src/lms/ui/admin/UserView.cpp index 2fee0feb9..cfb15b816 100644 --- a/src/lms/ui/admin/UserView.cpp +++ b/src/lms/ui/admin/UserView.cpp @@ -27,19 +27,19 @@ #include -#include "services/auth/IPasswordService.hpp" -#include "database/User.hpp" -#include "database/Session.hpp" -#include "core/IConfig.hpp" #include "core/Exception.hpp" +#include "core/IConfig.hpp" #include "core/ILogger.hpp" #include "core/Service.hpp" #include "core/String.hpp" +#include "database/Session.hpp" +#include "database/User.hpp" +#include "services/auth/IPasswordService.hpp" -#include "common/LoginNameValidator.hpp" -#include "common/PasswordValidator.hpp" #include "LmsApplication.hpp" #include "LmsApplicationException.hpp" +#include "common/LoginNameValidator.hpp" +#include "common/PasswordValidator.hpp" namespace lms::ui { @@ -181,10 +181,9 @@ namespace lms::ui UserView::UserView() { - wApp->internalPathChanged().connect(this, [this]() - { - refreshView(); - }); + wApp->internalPathChanged().connect(this, [this]() { + refreshView(); + }); refreshView(); } @@ -249,23 +248,22 @@ namespace lms::ui t->setCondition("if-demo", true); Wt::WPushButton* saveBtn{ t->bindNew("save-btn", Wt::WString::tr(userId ? "Lms.save" : "Lms.create")) }; - saveBtn->clicked().connect([=]() + saveBtn->clicked().connect([=]() { + t->updateModel(model.get()); + + if (model->validate()) { - t->updateModel(model.get()); - - if (model->validate()) - { - model->saveData(); - LmsApp->notifyMsg(Notification::Type::Info, - Wt::WString::tr("Lms.Admin.Users.users"), - Wt::WString::tr(userId ? "Lms.Admin.User.user-updated" : "Lms.Admin.User.user-created")); - LmsApp->setInternalPath("/admin/users", true); - } - else - { - t->updateView(model.get()); - } - }); + model->saveData(); + LmsApp->notifyMsg(Notification::Type::Info, + Wt::WString::tr("Lms.Admin.Users.users"), + Wt::WString::tr(userId ? "Lms.Admin.User.user-updated" : "Lms.Admin.User.user-created")); + LmsApp->setInternalPath("/admin/users", true); + } + else + { + t->updateView(model.get()); + } + }); t->updateView(model.get()); } diff --git a/src/lms/ui/admin/UserView.hpp b/src/lms/ui/admin/UserView.hpp index e98eafb0b..2215fa117 100644 --- a/src/lms/ui/admin/UserView.hpp +++ b/src/lms/ui/admin/UserView.hpp @@ -21,17 +21,16 @@ #include -namespace lms::ui { - -class UserView : public Wt::WContainerWidget +namespace lms::ui { - public: - UserView(); - - private: - void refreshView(); -}; -} // namespace lms::ui + class UserView : public Wt::WContainerWidget + { + public: + UserView(); + private: + void refreshView(); + }; +} // namespace lms::ui diff --git a/src/lms/ui/admin/UsersView.cpp b/src/lms/ui/admin/UsersView.cpp index c53409741..7ad07c995 100644 --- a/src/lms/ui/admin/UsersView.cpp +++ b/src/lms/ui/admin/UsersView.cpp @@ -19,15 +19,15 @@ #include "UsersView.hpp" -#include #include +#include #include -#include "services/auth/IPasswordService.hpp" -#include "database/User.hpp" -#include "database/Session.hpp" #include "core/ILogger.hpp" #include "core/Service.hpp" +#include "database/Session.hpp" +#include "database/User.hpp" +#include "services/auth/IPasswordService.hpp" #include "LmsApplication.hpp" #include "ModalManager.hpp" @@ -48,16 +48,14 @@ namespace lms::ui setCondition("if-can-create-user", true); Wt::WPushButton* addBtn = bindNew("add-btn", Wt::WString::tr("Lms.Admin.Users.add")); - addBtn->clicked().connect([] - { - LmsApp->setInternalPath("/admin/user", true); - }); + addBtn->clicked().connect([] { + LmsApp->setInternalPath("/admin/user", true); + }); } - wApp->internalPathChanged().connect(this, [this]() - { - refreshView(); - }); + wApp->internalPathChanged().connect(this, [this]() { + refreshView(); + }); refreshView(); } @@ -72,65 +70,60 @@ namespace lms::ui auto transaction{ LmsApp->getDbSession().createReadTransaction() }; const UserId currentUserId{ LmsApp->getUserId() }; - User::find(LmsApp->getDbSession(), User::FindParameters{}, [&](const User::pointer& user) - { - const UserId userId{ user->getId() }; + User::find(LmsApp->getDbSession(), User::FindParameters{}, [&](const User::pointer& user) { + const UserId userId{ user->getId() }; - Wt::WTemplate* entry{ _container->addNew(Wt::WString::tr("Lms.Admin.Users.template.entry")) }; + Wt::WTemplate* entry{ _container->addNew(Wt::WString::tr("Lms.Admin.Users.template.entry")) }; - entry->bindString("name", user->getLoginName(), Wt::TextFormat::Plain); + entry->bindString("name", user->getLoginName(), Wt::TextFormat::Plain); - // Create tag - if (user->isAdmin() || user->isDemo()) - { - entry->setCondition("if-tag", true); - entry->bindString("tag", Wt::WString::tr(user->isAdmin() ? "Lms.Admin.Users.admin" : "Lms.Admin.Users.demo")); - } + // Create tag + if (user->isAdmin() || user->isDemo()) + { + entry->setCondition("if-tag", true); + entry->bindString("tag", Wt::WString::tr(user->isAdmin() ? "Lms.Admin.Users.admin" : "Lms.Admin.Users.demo")); + } + + // Don't edit ourself this way + if (user->getId() == currentUserId) + return; + + entry->setCondition("if-edit", true); + Wt::WPushButton* editBtn = entry->bindNew("edit-btn", Wt::WString::tr("Lms.template.edit-btn"), Wt::TextFormat::XHTML); + editBtn->setToolTip(Wt::WString::tr("Lms.edit")); + editBtn->clicked().connect([userId]() { + LmsApp->setInternalPath("/admin/user/" + userId.toString(), true); + }); - // Don't edit ourself this way - if (user->getId() == currentUserId) - return; + Wt::WPushButton* delBtn = entry->bindNew("del-btn", Wt::WString::tr("Lms.template.trash-btn"), Wt::TextFormat::XHTML); + delBtn->setToolTip(Wt::WString::tr("Lms.delete")); + delBtn->clicked().connect([this, userId, entry] { + auto modal{ std::make_unique(Wt::WString::tr("Lms.Admin.Users.template.delete-user")) }; + modal->addFunction("tr", &Wt::WTemplate::Functions::tr); + Wt::WWidget* modalPtr{ modal.get() }; - entry->setCondition("if-edit", true); - Wt::WPushButton* editBtn = entry->bindNew("edit-btn", Wt::WString::tr("Lms.template.edit-btn"), Wt::TextFormat::XHTML); - editBtn->setToolTip(Wt::WString::tr("Lms.edit")); - editBtn->clicked().connect([userId]() + auto* delBtn{ modal->bindNew("del-btn", Wt::WString::tr("Lms.delete")) }; + delBtn->clicked().connect([=, this] { { - LmsApp->setInternalPath("/admin/user/" + userId.toString(), true); - }); + auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - Wt::WPushButton* delBtn = entry->bindNew("del-btn", Wt::WString::tr("Lms.template.trash-btn"), Wt::TextFormat::XHTML); - delBtn->setToolTip(Wt::WString::tr("Lms.delete")); - delBtn->clicked().connect([this, userId, entry] - { - auto modal{ std::make_unique(Wt::WString::tr("Lms.Admin.Users.template.delete-user")) }; - modal->addFunction("tr", &Wt::WTemplate::Functions::tr); - Wt::WWidget* modalPtr{ modal.get() }; - - auto* delBtn{ modal->bindNew("del-btn", Wt::WString::tr("Lms.delete")) }; - delBtn->clicked().connect([=, this] - { - { - auto transaction{ LmsApp->getDbSession().createWriteTransaction() }; - - User::pointer user{ User::find(LmsApp->getDbSession(), userId) }; - if (user) - user.remove(); - } - - _container->removeWidget(entry); - - LmsApp->getModalManager().dispose(modalPtr); - }); - - auto* cancelBtn{ modal->bindNew("cancel-btn", Wt::WString::tr("Lms.cancel")) }; - cancelBtn->clicked().connect([=] - { - LmsApp->getModalManager().dispose(modalPtr); - }); - - LmsApp->getModalManager().show(std::move(modal)); - }); + User::pointer user{ User::find(LmsApp->getDbSession(), userId) }; + if (user) + user.remove(); + } + + _container->removeWidget(entry); + + LmsApp->getModalManager().dispose(modalPtr); + }); + + auto* cancelBtn{ modal->bindNew("cancel-btn", Wt::WString::tr("Lms.cancel")) }; + cancelBtn->clicked().connect([=] { + LmsApp->getModalManager().dispose(modalPtr); + }); + + LmsApp->getModalManager().show(std::move(modal)); }); + }); } } // namespace lms::ui diff --git a/src/lms/ui/admin/UsersView.hpp b/src/lms/ui/admin/UsersView.hpp index a3915bb7d..2122de156 100644 --- a/src/lms/ui/admin/UsersView.hpp +++ b/src/lms/ui/admin/UsersView.hpp @@ -22,19 +22,18 @@ #include #include -namespace lms::ui { - -class UsersView : public Wt::WTemplate +namespace lms::ui { - public: - UsersView(); - - private: - void refreshView(); - Wt::WContainerWidget* _container; -}; + class UsersView : public Wt::WTemplate + { + public: + UsersView(); -} // namespace lms::ui + private: + void refreshView(); + Wt::WContainerWidget* _container; + }; +} // namespace lms::ui diff --git a/src/lms/ui/common/DoubleValidator.cpp b/src/lms/ui/common/DoubleValidator.cpp index 02a8be1e2..f9d495976 100644 --- a/src/lms/ui/common/DoubleValidator.cpp +++ b/src/lms/ui/common/DoubleValidator.cpp @@ -23,19 +23,18 @@ namespace lms::ui { - class DoubleValidator : public Wt::WDoubleValidator - { - public: - using Wt::WDoubleValidator::WDoubleValidator; + class DoubleValidator : public Wt::WDoubleValidator + { + public: + using Wt::WDoubleValidator::WDoubleValidator; - private: - std::string javaScriptValidate() const override { return {}; } - }; + private: + std::string javaScriptValidate() const override { return {}; } + }; - std::unique_ptr - createDoubleValidator(double min, double max) - { - auto validator {std::make_unique(min, max)}; - return validator; - } + std::unique_ptr createDoubleValidator(double min, double max) + { + auto validator{ std::make_unique(min, max) }; + return validator; + } } // namespace lms::ui diff --git a/src/lms/ui/common/DoubleValidator.hpp b/src/lms/ui/common/DoubleValidator.hpp index 4bec825a7..132c14a44 100644 --- a/src/lms/ui/common/DoubleValidator.hpp +++ b/src/lms/ui/common/DoubleValidator.hpp @@ -23,6 +23,5 @@ namespace lms::ui { - std::unique_ptr createDoubleValidator(double min, double max); + std::unique_ptr createDoubleValidator(double min, double max); } // namespace lms::ui - diff --git a/src/lms/ui/common/InfiniteScrollingContainer.cpp b/src/lms/ui/common/InfiniteScrollingContainer.cpp index 6f33dd22c..4a008ab07 100644 --- a/src/lms/ui/common/InfiniteScrollingContainer.cpp +++ b/src/lms/ui/common/InfiniteScrollingContainer.cpp @@ -20,6 +20,7 @@ #include "InfiniteScrollingContainer.hpp" #include + #include "LoadingIndicator.hpp" namespace lms::ui @@ -96,17 +97,15 @@ namespace lms::ui return _elements->indexOf(&widget); } - void InfiniteScrollingContainer::displayLoadingIndicator() { _loadingIndicator = bindWidget("loading-indicator", createLoadingIndicator()); - _loadingIndicator->scrollVisibilityChanged().connect([this](bool visible) - { - if (!visible) - return; + _loadingIndicator->scrollVisibilityChanged().connect([this](bool visible) { + if (!visible) + return; - onRequestElements.emit(); - }); + onRequestElements.emit(); + }); } void InfiniteScrollingContainer::hideLoadingIndicator() @@ -114,4 +113,4 @@ namespace lms::ui _loadingIndicator = nullptr; bindEmpty("loading-indicator"); } -} +} // namespace lms::ui diff --git a/src/lms/ui/common/InfiniteScrollingContainer.hpp b/src/lms/ui/common/InfiniteScrollingContainer.hpp index d2fa68881..7167e3987 100644 --- a/src/lms/ui/common/InfiniteScrollingContainer.hpp +++ b/src/lms/ui/common/InfiniteScrollingContainer.hpp @@ -30,41 +30,41 @@ namespace lms::ui { - // Atomatically raises onRequestElements signal when the sentinel is displayed - // can add elements afterwards by calling setHasMoreElements() - class InfiniteScrollingContainer final : public Wt::WTemplate - { - public: - // "text" must contain loading-indicator and "elements" - InfiniteScrollingContainer(const Wt::WString& text = Wt::WString::tr("Lms.infinite-scrolling-container.template")); + // Atomatically raises onRequestElements signal when the sentinel is displayed + // can add elements afterwards by calling setHasMoreElements() + class InfiniteScrollingContainer final : public Wt::WTemplate + { + public: + // "text" must contain loading-indicator and "elements" + InfiniteScrollingContainer(const Wt::WString& text = Wt::WString::tr("Lms.infinite-scrolling-container.template")); - void reset(); - std::size_t getCount(); - void add(std::unique_ptr result); + void reset(); + std::size_t getCount(); + void add(std::unique_ptr result); - template - T* addNew(Args&&... args) - { - return _elements->addNew(std::forward(args)...); - } + template + T* addNew(Args&&... args) + { + return _elements->addNew(std::forward(args)...); + } - void remove(Wt::WWidget& widget); - void remove(std::size_t first, std::size_t last); + void remove(Wt::WWidget& widget); + void remove(std::size_t first, std::size_t last); - Wt::WWidget* getWidget(std::size_t pos) const; - std::optional getIndexOf(Wt::WWidget& widget) const; - void setHasMore(); // can be used to add elements afterwards + Wt::WWidget* getWidget(std::size_t pos) const; + std::optional getIndexOf(Wt::WWidget& widget) const; + void setHasMore(); // can be used to add elements afterwards - Wt::Signal<> onRequestElements; + Wt::Signal<> onRequestElements; - void setHasMore(bool hasMore); // can be used to add elements afterwards + void setHasMore(bool hasMore); // can be used to add elements afterwards - private: - void clear() override; - void displayLoadingIndicator(); - void hideLoadingIndicator(); + private: + void clear() override; + void displayLoadingIndicator(); + void hideLoadingIndicator(); - Wt::WContainerWidget* _elements; - Wt::WTemplate* _loadingIndicator; - }; -} + Wt::WContainerWidget* _elements; + Wt::WTemplate* _loadingIndicator; + }; +} // namespace lms::ui diff --git a/src/lms/ui/common/LoadingIndicator.cpp b/src/lms/ui/common/LoadingIndicator.cpp index 0912f4b67..c5196c5f7 100644 --- a/src/lms/ui/common/LoadingIndicator.cpp +++ b/src/lms/ui/common/LoadingIndicator.cpp @@ -23,7 +23,7 @@ namespace lms::ui { std::unique_ptr createLoadingIndicator() { - auto res {std::make_unique(Wt::WString::tr("Lms.LoadingIndicator.template"))}; + auto res{ std::make_unique(Wt::WString::tr("Lms.LoadingIndicator.template")) }; res->addFunction("tr", &Wt::WTemplate::Functions::tr); res->setScrollVisibilityEnabled(true); @@ -31,5 +31,4 @@ namespace lms::ui return res; } -} - +} // namespace lms::ui diff --git a/src/lms/ui/common/LoadingIndicator.hpp b/src/lms/ui/common/LoadingIndicator.hpp index fcd493978..5d5715f64 100644 --- a/src/lms/ui/common/LoadingIndicator.hpp +++ b/src/lms/ui/common/LoadingIndicator.hpp @@ -23,6 +23,5 @@ namespace lms::ui { - std::unique_ptr createLoadingIndicator(); + std::unique_ptr createLoadingIndicator(); } - diff --git a/src/lms/ui/common/LoginNameValidator.cpp b/src/lms/ui/common/LoginNameValidator.cpp index 8eeb4d200..aeb3e23db 100644 --- a/src/lms/ui/common/LoginNameValidator.cpp +++ b/src/lms/ui/common/LoginNameValidator.cpp @@ -20,6 +20,7 @@ #include "LoginNameValidator.hpp" #include + #include "database/User.hpp" namespace lms::ui @@ -31,7 +32,7 @@ namespace lms::ui private: std::string javaScriptValidate() const override { return {}; } }; - } + } // namespace std::unique_ptr createLoginNameValidator() { diff --git a/src/lms/ui/common/LoginNameValidator.hpp b/src/lms/ui/common/LoginNameValidator.hpp index 6e76eb345..e93691b02 100644 --- a/src/lms/ui/common/LoginNameValidator.hpp +++ b/src/lms/ui/common/LoginNameValidator.hpp @@ -23,6 +23,5 @@ namespace lms::ui { - std::unique_ptr createLoginNameValidator(); + std::unique_ptr createLoginNameValidator(); } // namespace lms::ui - diff --git a/src/lms/ui/common/MandatoryValidator.cpp b/src/lms/ui/common/MandatoryValidator.cpp index f6fcf3db9..065051835 100644 --- a/src/lms/ui/common/MandatoryValidator.cpp +++ b/src/lms/ui/common/MandatoryValidator.cpp @@ -21,17 +21,17 @@ namespace lms::ui { - class MandatoryValidator : public Wt::WValidator - { - private: - std::string javaScriptValidate() const override { return {}; } - }; + class MandatoryValidator : public Wt::WValidator + { + private: + std::string javaScriptValidate() const override { return {}; } + }; - std::unique_ptr - createMandatoryValidator() - { - auto v {std::make_unique()}; - v->setMandatory(true); - return v; - } + std::unique_ptr + createMandatoryValidator() + { + auto v{ std::make_unique() }; + v->setMandatory(true); + return v; + } } // namespace lms::ui diff --git a/src/lms/ui/common/MandatoryValidator.hpp b/src/lms/ui/common/MandatoryValidator.hpp index 66b0f8cd8..b0b78ed12 100644 --- a/src/lms/ui/common/MandatoryValidator.hpp +++ b/src/lms/ui/common/MandatoryValidator.hpp @@ -23,6 +23,5 @@ namespace lms::ui { - std::unique_ptr createMandatoryValidator(); + std::unique_ptr createMandatoryValidator(); } // namespace lms::ui - diff --git a/src/lms/ui/common/PasswordValidator.cpp b/src/lms/ui/common/PasswordValidator.cpp index 259b60ccd..f6cb31d6f 100644 --- a/src/lms/ui/common/PasswordValidator.cpp +++ b/src/lms/ui/common/PasswordValidator.cpp @@ -21,8 +21,9 @@ #include -#include "services/auth/IPasswordService.hpp" #include "core/Service.hpp" +#include "services/auth/IPasswordService.hpp" + #include "LmsApplication.hpp" namespace lms::ui @@ -34,7 +35,8 @@ namespace lms::ui public: PasswordStrengthValidator(PasswordValidationContextGetFunc passwordValidationContextGetFunc) : _passwordValidationContextGetFunc{ std::move(passwordValidationContextGetFunc) } - {} + { + } private: Wt::WValidator::Result validate(const Wt::WString& input) const override; @@ -42,7 +44,7 @@ namespace lms::ui PasswordValidationContextGetFunc _passwordValidationContextGetFunc; }; - } + } // namespace Wt::WValidator::Result PasswordStrengthValidator::validate(const Wt::WString& input) const { @@ -82,9 +84,9 @@ namespace lms::ui return Wt::WValidator::validate(input); const auto checkResult{ core::Service::get()->checkUserPassword( - boost::asio::ip::address::from_string(LmsApp->environment().clientAddress()), - LmsApp->getUserLoginName(), - input.toUTF8()) }; + boost::asio::ip::address::from_string(LmsApp->environment().clientAddress()), + LmsApp->getUserLoginName(), + input.toUTF8()) }; switch (checkResult.state) { case auth::IPasswordService::CheckResult::State::Granted: diff --git a/src/lms/ui/common/PasswordValidator.hpp b/src/lms/ui/common/PasswordValidator.hpp index 757d1831e..adeeebbeb 100644 --- a/src/lms/ui/common/PasswordValidator.hpp +++ b/src/lms/ui/common/PasswordValidator.hpp @@ -20,16 +20,16 @@ #pragma once #include + #include #include "services/auth/Types.hpp" namespace lms::ui { - using PasswordValidationContextGetFunc = std::function; - std::unique_ptr createPasswordStrengthValidator(PasswordValidationContextGetFunc passwordValidationContextGetFunc); + using PasswordValidationContextGetFunc = std::function; + std::unique_ptr createPasswordStrengthValidator(PasswordValidationContextGetFunc passwordValidationContextGetFunc); - // Check current user password - std::unique_ptr createPasswordCheckValidator(); + // Check current user password + std::unique_ptr createPasswordCheckValidator(); } // namespace lms::ui - diff --git a/src/lms/ui/common/Template.hpp b/src/lms/ui/common/Template.hpp index d5cce8a01..840e43f43 100644 --- a/src/lms/ui/common/Template.hpp +++ b/src/lms/ui/common/Template.hpp @@ -23,14 +23,12 @@ namespace lms::ui { + class Template : public Wt::WTemplate + { + public: + using Wt::WTemplate::WTemplate; - class Template : public Wt::WTemplate - { - public: - using Wt::WTemplate::WTemplate; - - private: - void applyArguments(Wt::WWidget* w, const std::vector& args) override; - }; - + private: + void applyArguments(Wt::WWidget* w, const std::vector& args) override; + }; } // namespace lms::ui diff --git a/src/lms/ui/common/UUIDValidator.cpp b/src/lms/ui/common/UUIDValidator.cpp index 4a7da07a6..a0629b166 100644 --- a/src/lms/ui/common/UUIDValidator.cpp +++ b/src/lms/ui/common/UUIDValidator.cpp @@ -23,18 +23,18 @@ namespace lms::ui { - class RegExpValidator : public Wt::WRegExpValidator - { - public: - using Wt::WRegExpValidator::WRegExpValidator; + class RegExpValidator : public Wt::WRegExpValidator + { + public: + using Wt::WRegExpValidator::WRegExpValidator; - private: - std::string javaScriptValidate() const override { return {}; } - }; + private: + std::string javaScriptValidate() const override { return {}; } + }; - std::unique_ptr - createUUIDValidator() - { - return std::make_unique("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"); - } + std::unique_ptr + createUUIDValidator() + { + return std::make_unique("[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}"); + } } // namespace lms::ui diff --git a/src/lms/ui/common/UUIDValidator.hpp b/src/lms/ui/common/UUIDValidator.hpp index a19de2e06..c80297346 100644 --- a/src/lms/ui/common/UUIDValidator.hpp +++ b/src/lms/ui/common/UUIDValidator.hpp @@ -23,6 +23,5 @@ namespace lms::ui { - std::unique_ptr createUUIDValidator(); + std::unique_ptr createUUIDValidator(); } // namespace lms::ui - diff --git a/src/lms/ui/common/UppercaseValidator.cpp b/src/lms/ui/common/UppercaseValidator.cpp index b103dd746..a56237663 100644 --- a/src/lms/ui/common/UppercaseValidator.cpp +++ b/src/lms/ui/common/UppercaseValidator.cpp @@ -38,7 +38,7 @@ namespace lms::ui return Wt::WValidator::validate(input); const std::string str{ input.toUTF8() }; - const bool valid{ std::all_of(std::cbegin(str), std::cend(str), [&](char c) { return !std::isalpha(c) || std::isupper(c);}) }; + const bool valid{ std::all_of(std::cbegin(str), std::cend(str), [&](char c) { return !std::isalpha(c) || std::isupper(c); }) }; if (!valid) return Wt::WValidator::Result(Wt::ValidationState::Invalid, Wt::WString::tr("Lms.field-must-be-in-upper-case")); diff --git a/src/lms/ui/common/ValueStringModel.hpp b/src/lms/ui/common/ValueStringModel.hpp index 39682bd9c..c4fded624 100644 --- a/src/lms/ui/common/ValueStringModel.hpp +++ b/src/lms/ui/common/ValueStringModel.hpp @@ -26,7 +26,7 @@ namespace lms::ui { // Helper class - template + template class ValueStringModel : public Wt::WStringListModel { public: diff --git a/src/lms/ui/explore/ArtistCollector.cpp b/src/lms/ui/explore/ArtistCollector.cpp index 098ffa49a..eaf08daa2 100644 --- a/src/lms/ui/explore/ArtistCollector.cpp +++ b/src/lms/ui/explore/ArtistCollector.cpp @@ -19,13 +19,14 @@ #include "ArtistCollector.hpp" +#include "core/Service.hpp" #include "database/Artist.hpp" #include "database/Session.hpp" #include "database/TrackList.hpp" #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "core/Service.hpp" + #include "Filters.hpp" #include "LmsApplication.hpp" @@ -50,80 +51,80 @@ namespace lms::ui break; case Mode::Starred: - { - feedback::IFeedbackService::ArtistFindParameters params; - params.setUser(LmsApp->getUserId()); - params.setClusters(filters.getClusters()); - params.setKeywords(getSearchKeywords()); - params.setMediaLibrary(filters.getMediaLibrary()); - params.setLinkType(_linkType); - params.setSortMethod(ArtistSortMethod::StarredDateDesc); - params.setRange(range); - artists = feedbackService.findStarredArtists(params); - break; - } + { + feedback::IFeedbackService::ArtistFindParameters params; + params.setUser(LmsApp->getUserId()); + params.setClusters(filters.getClusters()); + params.setKeywords(getSearchKeywords()); + params.setMediaLibrary(filters.getMediaLibrary()); + params.setLinkType(_linkType); + params.setSortMethod(ArtistSortMethod::StarredDateDesc); + params.setRange(range); + artists = feedbackService.findStarredArtists(params); + break; + } case Mode::RecentlyPlayed: - { - scrobbling::IScrobblingService::ArtistFindParameters params; - params.setUser(LmsApp->getUserId()); - params.setClusters(filters.getClusters()); - params.setKeywords(getSearchKeywords()); - params.setMediaLibrary(filters.getMediaLibrary()); - params.setLinkType(_linkType); - params.setRange(range); - - artists = scrobblingService.getRecentArtists(params); - break; - } + { + scrobbling::IScrobblingService::ArtistFindParameters params; + params.setUser(LmsApp->getUserId()); + params.setClusters(filters.getClusters()); + params.setKeywords(getSearchKeywords()); + params.setMediaLibrary(filters.getMediaLibrary()); + params.setLinkType(_linkType); + params.setRange(range); + + artists = scrobblingService.getRecentArtists(params); + break; + } case Mode::MostPlayed: - { - scrobbling::IScrobblingService::ArtistFindParameters params; - params.setUser(LmsApp->getUserId()); - params.setClusters(filters.getClusters()); - params.setKeywords(getSearchKeywords()); - params.setMediaLibrary(filters.getMediaLibrary()); - params.setLinkType(_linkType); - params.setRange(range); - - artists = scrobblingService.getTopArtists(params); - break; - } + { + scrobbling::IScrobblingService::ArtistFindParameters params; + params.setUser(LmsApp->getUserId()); + params.setClusters(filters.getClusters()); + params.setKeywords(getSearchKeywords()); + params.setMediaLibrary(filters.getMediaLibrary()); + params.setLinkType(_linkType); + params.setRange(range); + + artists = scrobblingService.getTopArtists(params); + break; + } case Mode::RecentlyAdded: - { - Artist::FindParameters params; - params.setClusters(filters.getClusters()); - params.setKeywords(getSearchKeywords()); - params.setMediaLibrary(filters.getMediaLibrary()); - params.setLinkType(_linkType); - params.setSortMethod(ArtistSortMethod::LastWritten); - params.setRange(range); - { - auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - artists = Artist::findIds(LmsApp->getDbSession(), params); + Artist::FindParameters params; + params.setClusters(filters.getClusters()); + params.setKeywords(getSearchKeywords()); + params.setMediaLibrary(filters.getMediaLibrary()); + params.setLinkType(_linkType); + params.setSortMethod(ArtistSortMethod::LastWritten); + params.setRange(range); + + { + auto transaction{ LmsApp->getDbSession().createReadTransaction() }; + artists = Artist::findIds(LmsApp->getDbSession(), params); + } + break; } - break; - } case Mode::All: - { - Artist::FindParameters params; - params.setClusters(filters.getClusters()); - params.setMediaLibrary(filters.getMediaLibrary()); - params.setKeywords(getSearchKeywords()); - params.setLinkType(_linkType); - params.setSortMethod(ArtistSortMethod::SortName); - params.setRange(range); - { - auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - artists = Artist::findIds(LmsApp->getDbSession(), params); + Artist::FindParameters params; + params.setClusters(filters.getClusters()); + params.setMediaLibrary(filters.getMediaLibrary()); + params.setKeywords(getSearchKeywords()); + params.setLinkType(_linkType); + params.setSortMethod(ArtistSortMethod::SortName); + params.setRange(range); + + { + auto transaction{ LmsApp->getDbSession().createReadTransaction() }; + artists = Artist::findIds(LmsApp->getDbSession(), params); + } + break; } - break; - } } if (range.offset + range.size == getMaxCount()) @@ -154,4 +155,4 @@ namespace lms::ui return _randomArtists->getSubRange(range); } -} // ns UserInterface +} // namespace lms::ui diff --git a/src/lms/ui/explore/ArtistCollector.hpp b/src/lms/ui/explore/ArtistCollector.hpp index ab6288944..9e5967ac1 100644 --- a/src/lms/ui/explore/ArtistCollector.hpp +++ b/src/lms/ui/explore/ArtistCollector.hpp @@ -27,24 +27,23 @@ namespace lms::db { - class Artist; + class Artist; } namespace lms::ui { - class ArtistCollector : public DatabaseCollectorBase - { - public: - using DatabaseCollectorBase::DatabaseCollectorBase; - - db::RangeResults get(std::optional range = std::nullopt); - void reset() { _randomArtists.reset(); } - void setArtistLinkType(std::optional linkType) { _linkType = linkType; } - - private: - db::RangeResults getRandomArtists(Range range); - std::optional> _randomArtists; - std::optional _linkType; - }; -} // ns UserInterface - + class ArtistCollector : public DatabaseCollectorBase + { + public: + using DatabaseCollectorBase::DatabaseCollectorBase; + + db::RangeResults get(std::optional range = std::nullopt); + void reset() { _randomArtists.reset(); } + void setArtistLinkType(std::optional linkType) { _linkType = linkType; } + + private: + db::RangeResults getRandomArtists(Range range); + std::optional> _randomArtists; + std::optional _linkType; + }; +} // namespace lms::ui diff --git a/src/lms/ui/explore/ArtistListHelpers.cpp b/src/lms/ui/explore/ArtistListHelpers.cpp index cd501789f..c8dbb0387 100644 --- a/src/lms/ui/explore/ArtistListHelpers.cpp +++ b/src/lms/ui/explore/ArtistListHelpers.cpp @@ -20,6 +20,7 @@ #include "database/Artist.hpp" #include "database/Session.hpp" + #include "LmsApplication.hpp" #include "Utils.hpp" @@ -32,4 +33,4 @@ namespace lms::ui::ArtistListHelpers return res; } -} \ No newline at end of file +} // namespace lms::ui::ArtistListHelpers \ No newline at end of file diff --git a/src/lms/ui/explore/ArtistListHelpers.hpp b/src/lms/ui/explore/ArtistListHelpers.hpp index ca9164bad..55a7fd0c1 100644 --- a/src/lms/ui/explore/ArtistListHelpers.hpp +++ b/src/lms/ui/explore/ArtistListHelpers.hpp @@ -29,16 +29,15 @@ namespace lms::db { - class Artist; + class Artist; } namespace lms::ui { - using ArtistLinkTypesModel = ValueStringModel>; - - namespace ArtistListHelpers - { - std::unique_ptr createEntry(const db::ObjectPtr& artist); - } -} + using ArtistLinkTypesModel = ValueStringModel>; + namespace ArtistListHelpers + { + std::unique_ptr createEntry(const db::ObjectPtr& artist); + } +} // namespace lms::ui diff --git a/src/lms/ui/explore/ArtistView.cpp b/src/lms/ui/explore/ArtistView.cpp index 6c6b9d231..a05b9a9e3 100644 --- a/src/lms/ui/explore/ArtistView.cpp +++ b/src/lms/ui/explore/ArtistView.cpp @@ -21,6 +21,8 @@ #include +#include "core/ILogger.hpp" +#include "core/String.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/Release.hpp" @@ -30,11 +32,7 @@ #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/recommendation/IRecommendationService.hpp" -#include "core/ILogger.hpp" -#include "core/String.hpp" -#include "common/InfiniteScrollingContainer.hpp" -#include "resource/DownloadResource.hpp" #include "ArtistListHelpers.hpp" #include "Filters.hpp" #include "LmsApplication.hpp" @@ -43,11 +41,13 @@ #include "ReleaseHelpers.hpp" #include "TrackListHelpers.hpp" #include "Utils.hpp" +#include "common/InfiniteScrollingContainer.hpp" +#include "resource/DownloadResource.hpp" namespace lms::ui { using namespace db; - + namespace { std::optional extractArtistIdFromInternalPath() @@ -67,7 +67,7 @@ namespace lms::ui return core::stringUtils::readAs(wApp->internalPathNextPart("/artist/")); } - } + } // namespace Artist::Artist(Filters& filters, PlayQueueController& controller) : Template{ Wt::WString::tr("Lms.Explore.Artist.template") } @@ -77,16 +77,14 @@ namespace lms::ui addFunction("tr", &Wt::WTemplate::Functions::tr); addFunction("id", &Wt::WTemplate::Functions::id); - LmsApp->internalPathChanged().connect(this, [this] - { - refreshView(); - }); + LmsApp->internalPathChanged().connect(this, [this] { + refreshView(); + }); - filters.updated().connect([this] - { - _needForceRefresh = true; - refreshView(); - }); + filters.updated().connect([this] { + _needForceRefresh = true; + refreshView(); + }); refreshView(); } @@ -110,7 +108,7 @@ namespace lms::ui if (!artistId) throw ArtistNotFoundException{}; - const auto similarArtistIds{ core::Service::get()->getSimilarArtists(*artistId, {TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist}, 5) }; + const auto similarArtistIds{ core::Service::get()->getSimilarArtists(*artistId, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, 5) }; auto transaction{ LmsApp->getDbSession().createReadTransaction() }; @@ -139,10 +137,9 @@ namespace lms::ui { const db::ClusterId clusterId = cluster->getId(); Wt::WInteractWidget* entry{ clusterContainers->addWidget(utils::createFilterCluster(clusterId)) }; - entry->clicked().connect([this, clusterId] - { - _filters.add(clusterId); - }); + entry->clicked().connect([this, clusterId] { + _filters.add(clusterId); + }); } } } @@ -150,26 +147,26 @@ namespace lms::ui bindString("name", Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain); bindNew("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) - ->clicked().connect([this] - { - _playQueueController.processCommand(PlayQueueController::Command::Play, { _artistId }); - }); + ->clicked() + .connect([this] { + _playQueueController.processCommand(PlayQueueController::Command::Play, { _artistId }); + }); bindNew("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) - ->clicked().connect([this] - { - _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, { _artistId }); - }); + ->clicked() + .connect([this] { + _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, { _artistId }); + }); bindNew("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain) - ->clicked().connect([this] - { - _playQueueController.processCommand(PlayQueueController::Command::PlayNext, { _artistId }); - }); + ->clicked() + .connect([this] { + _playQueueController.processCommand(PlayQueueController::Command::PlayNext, { _artistId }); + }); bindNew("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) - ->clicked().connect([this] - { - _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { _artistId }); - }); + ->clicked() + .connect([this] { + _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { _artistId }); + }); bindNew("download", Wt::WString::tr("Lms.Explore.download")) ->setLink(Wt::WLink{ std::make_unique(_artistId) }); @@ -177,19 +174,18 @@ namespace lms::ui auto isStarred{ [this] { return core::Service::get()->isStarred(LmsApp->getUserId(), _artistId); } }; Wt::WPushButton* starBtn{ bindNew("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) }; - starBtn->clicked().connect([=, this] + starBtn->clicked().connect([=, this] { + if (isStarred()) { - if (isStarred()) - { - core::Service::get()->unstar(LmsApp->getUserId(), _artistId); - starBtn->setText(Wt::WString::tr("Lms.Explore.star")); - } - else - { - core::Service::get()->star(LmsApp->getUserId(), _artistId); - starBtn->setText(Wt::WString::tr("Lms.Explore.unstar")); - } - }); + core::Service::get()->unstar(LmsApp->getUserId(), _artistId); + starBtn->setText(Wt::WString::tr("Lms.Explore.star")); + } + else + { + core::Service::get()->star(LmsApp->getUserId(), _artistId); + starBtn->setText(Wt::WString::tr("Lms.Explore.unstar")); + } + }); } } @@ -211,7 +207,7 @@ namespace lms::ui { const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), releaseId) }; - ReleaseType releaseType{ parseReleaseType(release->getReleaseTypeNames())}; + ReleaseType releaseType{ parseReleaseType(release->getReleaseTypeNames()) }; _releaseContainers[releaseType].releases.push_back(releaseId); } @@ -225,12 +221,11 @@ namespace lms::ui releaseContainer->bindString("release-type", releaseHelpers::buildReleaseTypeString(releaseType)); else releaseContainer->bindString("release-type", Wt::WString::tr("Lms.Explore.releases")); // fallback when not tagged with MB or custom type - + releases.container = releaseContainer->bindNew("releases", Wt::WString::tr("Lms.Explore.Releases.template.container")); - releases.container->onRequestElements.connect(this, [this, &releases = releases] - { - addSomeReleases(releases); - }); + releases.container->onRequestElements.connect(this, [this, &releases = releases] { + addSomeReleases(releases); + }); } } else @@ -241,8 +236,7 @@ namespace lms::ui void Artist::refreshAppearsOnReleases() { - constexpr core::EnumSet types - { + constexpr core::EnumSet types{ TrackArtistLinkType::Artist, TrackArtistLinkType::Arranger, TrackArtistLinkType::Composer, @@ -270,10 +264,9 @@ namespace lms::ui releaseContainer->bindString("release-type", Wt::WString::tr("Lms.Explore.Artist.appears-on")); _appearsOnReleaseContainer.releases = releases.results; _appearsOnReleaseContainer.container = releaseContainer->bindNew("releases", Wt::WString::tr("Lms.Explore.Releases.template.container")); - _appearsOnReleaseContainer.container->onRequestElements.connect(this, [this] - { - addSomeReleases(_appearsOnReleaseContainer); - }); + _appearsOnReleaseContainer.container->onRequestElements.connect(this, [this] { + addSomeReleases(_appearsOnReleaseContainer); + }); } else { @@ -285,10 +278,9 @@ namespace lms::ui { setCondition("if-has-non-release-tracks", true); _trackContainer = bindNew("tracks"); - _trackContainer->onRequestElements.connect(this, [this] - { - addSomeNonReleaseTracks(); - }); + _trackContainer->onRequestElements.connect(this, [this] { + addSomeNonReleaseTracks(); + }); const bool added{ addSomeNonReleaseTracks() }; setCondition("if-has-non-release-tracks", added); @@ -372,4 +364,3 @@ namespace lms::ui } } // namespace lms::ui - diff --git a/src/lms/ui/explore/ArtistView.hpp b/src/lms/ui/explore/ArtistView.hpp index 07dfeace1..7220e6252 100644 --- a/src/lms/ui/explore/ArtistView.hpp +++ b/src/lms/ui/explore/ArtistView.hpp @@ -21,18 +21,20 @@ #include #include + +#include "core/EnumSet.hpp" #include "database/ArtistId.hpp" #include "database/Object.hpp" #include "database/ReleaseId.hpp" -#include "core/EnumSet.hpp" -#include "common/Template.hpp" + #include "ReleaseTypes.hpp" +#include "common/Template.hpp" namespace lms::db { class Artist; class Release; -} +} // namespace lms::db namespace lms::ui { @@ -70,10 +72,9 @@ namespace lms::ui std::vector releases; }; std::map _releaseContainers; - ReleaseContainer _appearsOnReleaseContainer{}; + ReleaseContainer _appearsOnReleaseContainer{}; InfiniteScrollingContainer* _trackContainer{}; - db::ArtistId _artistId{}; - bool _needForceRefresh{}; + db::ArtistId _artistId{}; + bool _needForceRefresh{}; }; } // namespace lms::ui - diff --git a/src/lms/ui/explore/ArtistsView.cpp b/src/lms/ui/explore/ArtistsView.cpp index 25d61ebeb..5bce4d67a 100644 --- a/src/lms/ui/explore/ArtistsView.cpp +++ b/src/lms/ui/explore/ArtistsView.cpp @@ -26,12 +26,12 @@ #include "database/Session.hpp" #include "database/TrackArtistLink.hpp" -#include "common/InfiniteScrollingContainer.hpp" #include "ArtistListHelpers.hpp" #include "Filters.hpp" #include "LmsApplication.hpp" #include "SortModeSelector.hpp" #include "TrackArtistLinkTypeSelector.hpp" +#include "common/InfiniteScrollingContainer.hpp" namespace lms::ui { @@ -46,33 +46,28 @@ namespace lms::ui Wt::WLineEdit* searEdit{ bindNew("search") }; searEdit->setPlaceholderText(Wt::WString::tr("Lms.Explore.Search.search-placeholder")); - searEdit->textInput().connect([this, searEdit] - { - refreshView(searEdit->text()); - }); + searEdit->textInput().connect([this, searEdit] { + refreshView(searEdit->text()); + }); SortModeSelector* sortModeSelector{ bindNew("sort-mode", _defaultSortMode) }; - sortModeSelector->itemSelected.connect([this](ArtistCollector::Mode sortMode) - { - refreshView(sortMode); - }); + sortModeSelector->itemSelected.connect([this](ArtistCollector::Mode sortMode) { + refreshView(sortMode); + }); TrackArtistLinkTypeSelector* linkTypeSelector{ bindNew("link-type", _defaultLinkType) }; - linkTypeSelector->itemSelected.connect([this](std::optional linkType) - { - refreshView(linkType); - }); + linkTypeSelector->itemSelected.connect([this](std::optional linkType) { + refreshView(linkType); + }); _container = bindNew("artists", Wt::WString::tr("Lms.Explore.Artists.template.container")); - _container->onRequestElements.connect([this] - { - addSome(); - }); + _container->onRequestElements.connect([this] { + addSome(); + }); - filters.updated().connect([this] - { - refreshView(); - }); + filters.updated().connect([this] { + refreshView(); + }); refreshView(_artistCollector.getMode()); } @@ -103,7 +98,7 @@ namespace lms::ui void Artists::addSome() { - const auto artistIds{ _artistCollector.get(Range {static_cast(_container->getCount()), _batchSize}) }; + const auto artistIds{ _artistCollector.get(Range{ static_cast(_container->getCount()), _batchSize }) }; { auto transaction{ LmsApp->getDbSession().createReadTransaction() }; @@ -119,4 +114,3 @@ namespace lms::ui } } // namespace lms::ui - diff --git a/src/lms/ui/explore/ArtistsView.hpp b/src/lms/ui/explore/ArtistsView.hpp index 95d8fc60f..d22c0a93c 100644 --- a/src/lms/ui/explore/ArtistsView.hpp +++ b/src/lms/ui/explore/ArtistsView.hpp @@ -26,8 +26,9 @@ #include #include "database/Types.hpp" -#include "common/Template.hpp" + #include "ArtistCollector.hpp" +#include "common/Template.hpp" namespace lms::ui { @@ -51,9 +52,8 @@ namespace lms::ui Wt::WWidget* _currentLinkTypeActiveItem{}; InfiniteScrollingContainer* _container{}; - ArtistCollector _artistCollector; + ArtistCollector _artistCollector; static constexpr ArtistCollector::Mode _defaultSortMode{ ArtistCollector::Mode::Random }; static constexpr std::optional _defaultLinkType{ std::nullopt }; }; } // namespace lms::ui - diff --git a/src/lms/ui/explore/DatabaseCollectorBase.cpp b/src/lms/ui/explore/DatabaseCollectorBase.cpp index 601ff01d6..e2e39f736 100644 --- a/src/lms/ui/explore/DatabaseCollectorBase.cpp +++ b/src/lms/ui/explore/DatabaseCollectorBase.cpp @@ -65,5 +65,4 @@ namespace lms::ui _searchKeywords.clear(); } -} // ns UserInterface - +} // namespace lms::ui diff --git a/src/lms/ui/explore/DatabaseCollectorBase.hpp b/src/lms/ui/explore/DatabaseCollectorBase.hpp index 63bfe9608..fe8f03eb5 100644 --- a/src/lms/ui/explore/DatabaseCollectorBase.hpp +++ b/src/lms/ui/explore/DatabaseCollectorBase.hpp @@ -55,16 +55,16 @@ namespace lms::ui void setSearch(std::string_view search); protected: - Range getActualRange(std::optional range) const; + Range getActualRange(std::optional range) const; std::size_t getMaxCount() const; - const Filters& getFilters() { return _filters; } + const Filters& getFilters() { return _filters; } const std::vector& getSearchKeywords() const { return _searchKeywords; } private: Filters& _filters; std::string _searchText; std::vector _searchKeywords; - Mode _mode; + Mode _mode; std::size_t _maxCount; }; -} // ns UserInterface +} // namespace lms::ui diff --git a/src/lms/ui/explore/DropDownMenuSelector.hpp b/src/lms/ui/explore/DropDownMenuSelector.hpp index 16ed69030..43b930f2e 100644 --- a/src/lms/ui/explore/DropDownMenuSelector.hpp +++ b/src/lms/ui/explore/DropDownMenuSelector.hpp @@ -20,14 +20,15 @@ #pragma once #include + #include +#include #include #include -#include namespace lms::ui { - template + template class DropDownMenuSelector : public Wt::WTemplate { public: @@ -43,15 +44,14 @@ namespace lms::ui void bindItem(const std::string& var, const Wt::WString& title, ItemType item) { auto* menuItem{ bindNew(var, title) }; - menuItem->clicked().connect([this, menuItem, title, item] - { - _currentActiveItem->removeStyleClass("active"); - menuItem->addStyleClass("active"); - _currentActiveItem = menuItem; - _selectedItem->setText(title); + menuItem->clicked().connect([this, menuItem, title, item] { + _currentActiveItem->removeStyleClass("active"); + menuItem->addStyleClass("active"); + _currentActiveItem = menuItem; + _selectedItem->setText(title); - itemSelected.emit(item); - }); + itemSelected.emit(item); + }); if (item == _defaultItem) { @@ -68,4 +68,4 @@ namespace lms::ui Wt::WWidget* _currentActiveItem{}; Wt::WText* _selectedItem{}; }; -} \ No newline at end of file +} // namespace lms::ui \ No newline at end of file diff --git a/src/lms/ui/explore/Explore.cpp b/src/lms/ui/explore/Explore.cpp index b1daf7559..6050aea5c 100644 --- a/src/lms/ui/explore/Explore.cpp +++ b/src/lms/ui/explore/Explore.cpp @@ -22,11 +22,11 @@ #include #include -#include "ArtistsView.hpp" #include "ArtistView.hpp" +#include "ArtistsView.hpp" #include "Filters.hpp" -#include "ReleasesView.hpp" #include "ReleaseView.hpp" +#include "ReleasesView.hpp" #include "TrackListView.hpp" #include "TrackListsView.hpp" #include "TracksView.hpp" @@ -48,15 +48,14 @@ namespace lms::ui IdxTracks, }; - static const std::map indexes = - { - { "/artists", IdxArtists }, - { "/artist", IdxArtist }, - { "/tracklists", IdxTrackLists }, - { "/tracklist", IdxTrackList }, - { "/releases", IdxReleases }, - { "/release", IdxRelease }, - { "/tracks", IdxTracks }, + static const std::map indexes = { + { "/artists", IdxArtists }, + { "/artist", IdxArtist }, + { "/tracklists", IdxTrackLists }, + { "/tracklist", IdxTrackList }, + { "/releases", IdxReleases }, + { "/release", IdxRelease }, + { "/tracks", IdxTracks }, }; for (const auto& index : indexes) @@ -103,10 +102,9 @@ namespace lms::ui auto tracks = std::make_unique(filters, _playQueueController); contentsStack->addWidget(std::move(tracks)); - wApp->internalPathChanged().connect(this, [contentsStack] - { - handleContentsPathChange(contentsStack); - }); + wApp->internalPathChanged().connect(this, [contentsStack] { + handleContentsPathChange(contentsStack); + }); handleContentsPathChange(contentsStack); } diff --git a/src/lms/ui/explore/Explore.hpp b/src/lms/ui/explore/Explore.hpp index 48163c3ce..090404f4d 100644 --- a/src/lms/ui/explore/Explore.hpp +++ b/src/lms/ui/explore/Explore.hpp @@ -41,4 +41,3 @@ namespace lms::ui SearchView* _search{}; }; } // namespace lms::ui - diff --git a/src/lms/ui/explore/Filters.cpp b/src/lms/ui/explore/Filters.cpp index 4d6e6d9e2..cc8bd2366 100644 --- a/src/lms/ui/explore/Filters.cpp +++ b/src/lms/ui/explore/Filters.cpp @@ -30,16 +30,18 @@ #include "database/MediaLibrary.hpp" #include "database/Session.hpp" -#include "common/ValueStringModel.hpp" #include "LmsApplication.hpp" -#include "Utils.hpp" #include "ModalManager.hpp" +#include "Utils.hpp" +#include "common/ValueStringModel.hpp" namespace lms::ui { namespace { - struct MediaLibraryTag {}; + struct MediaLibraryTag + { + }; using TypeVariant = std::variant; using TypeModel = ValueStringModel; @@ -50,10 +52,9 @@ namespace lms::ui { auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - db::ClusterType::find(LmsApp->getDbSession(), [&](const db::ClusterType::pointer& clusterType) - { - typeModel->add(Wt::WString::fromUTF8(std::string{ clusterType->getName() }), clusterType->getId()); - }); + db::ClusterType::find(LmsApp->getDbSession(), [&](const db::ClusterType::pointer& clusterType) { + typeModel->add(Wt::WString::fromUTF8(std::string{ clusterType->getName() }), clusterType->getId()); + }); } typeModel->add(Wt::WString::tr("Lms.Explore.media-library"), MediaLibraryTag{}); @@ -74,10 +75,9 @@ namespace lms::ui if (std::holds_alternative(type)) { - db::MediaLibrary::find(session, [&](const db::MediaLibrary::pointer& library) - { - valueModel->add(Wt::WString::fromUTF8(std::string{ library->getName() }), library->getId()); - }); + db::MediaLibrary::find(session, [&](const db::MediaLibrary::pointer& library) { + valueModel->add(Wt::WString::fromUTF8(std::string{ library->getName() }), library->getId()); + }); } else if (const db::ClusterTypeId * clusterTypeId{ std::get_if(&type) }) { @@ -85,15 +85,14 @@ namespace lms::ui params.setClusterType(*clusterTypeId); params.setSortMethod(db::ClusterSortMethod::Name); - db::Cluster::find(session, params, [&](const db::Cluster::pointer& cluster) - { - valueModel->add(Wt::WString::fromUTF8(std::string{ cluster->getName() }), cluster->getId()); - }); + db::Cluster::find(session, params, [&](const db::Cluster::pointer& cluster) { + valueModel->add(Wt::WString::fromUTF8(std::string{ cluster->getName() }), cluster->getId()); + }); } return valueModel; } - } + } // namespace void Filters::showDialog() { @@ -109,38 +108,35 @@ namespace lms::ui Wt::WComboBox* valueCombo{ dialog->bindNew("value") }; Wt::WPushButton* addBtn{ dialog->bindNew("add-btn", Wt::WString::tr("Lms.Explore.add-filter")) }; - addBtn->clicked().connect([this, valueCombo, dialogPtr] + addBtn->clicked().connect([this, valueCombo, dialogPtr] { + const auto valueModel{ std::static_pointer_cast(valueCombo->model()) }; + const ValueVariant value{ valueModel->getValue(valueCombo->currentIndex()) }; + + if (const db::MediaLibraryId * mediaLibraryId{ std::get_if(&value) }) + { + set(*mediaLibraryId); + } + else if (const db::ClusterId * clusterId{ std::get_if(&value) }) { - const auto valueModel{ std::static_pointer_cast(valueCombo->model()) }; - const ValueVariant value{ valueModel->getValue(valueCombo->currentIndex()) }; - - if (const db::MediaLibraryId * mediaLibraryId{ std::get_if(&value) }) - { - set(*mediaLibraryId); - } - else if (const db::ClusterId * clusterId{ std::get_if(&value) }) - { - add(*clusterId); - } - - // TODO - LmsApp->getModalManager().dispose(dialogPtr); - }); + add(*clusterId); + } + + // TODO + LmsApp->getModalManager().dispose(dialogPtr); + }); Wt::WPushButton* cancelBtn{ dialog->bindNew("cancel-btn", Wt::WString::tr("Lms.cancel")) }; - cancelBtn->clicked().connect([=] - { - LmsApp->getModalManager().dispose(dialogPtr); - }); + cancelBtn->clicked().connect([=] { + LmsApp->getModalManager().dispose(dialogPtr); + }); - typeCombo->activated().connect([valueCombo, typeModel](int row) - { - const TypeVariant type{ typeModel->getValue(row) }; + typeCombo->activated().connect([valueCombo, typeModel](int row) { + const TypeVariant type{ typeModel->getValue(row) }; - const std::shared_ptr valueModel{ createValueModel(type) }; - valueCombo->clear(); - valueCombo->setModel(valueModel); - }); + const std::shared_ptr valueModel{ createValueModel(type) }; + valueCombo->clear(); + valueCombo->setModel(valueModel); + }); typeCombo->activated().emit(0); // force emit to refresh the type combo model @@ -176,12 +172,11 @@ namespace lms::ui _clusterIds.push_back(clusterId); - filter->clicked().connect([this, filter, clusterId] - { - _filters->removeWidget(filter); - _clusterIds.erase(std::remove_if(std::begin(_clusterIds), std::end(_clusterIds), [clusterId](db::ClusterId id) { return id == clusterId; }), std::end(_clusterIds)); - _sigUpdated.emit(); - }); + filter->clicked().connect([this, filter, clusterId] { + _filters->removeWidget(filter); + _clusterIds.erase(std::remove_if(std::begin(_clusterIds), std::end(_clusterIds), [clusterId](db::ClusterId id) { return id == clusterId; }), std::end(_clusterIds)); + _sigUpdated.emit(); + }); emitFilterAddedNotification(); } @@ -208,13 +203,12 @@ namespace lms::ui _mediaLibraryId = mediaLibraryId; _mediaLibraryFilter = _filters->addWidget(utils::createFilter(Wt::WString::fromUTF8(libraryName), Wt::WString::tr("Lms.Explore.media-library"), "bg-primary", true)); - _mediaLibraryFilter->clicked().connect(_mediaLibraryFilter, [this] - { - _filters->removeWidget(_mediaLibraryFilter); - _mediaLibraryId = db::MediaLibraryId{}; - _mediaLibraryFilter = nullptr; - _sigUpdated.emit(); - }); + _mediaLibraryFilter->clicked().connect(_mediaLibraryFilter, [this] { + _filters->removeWidget(_mediaLibraryFilter); + _mediaLibraryId = db::MediaLibraryId{}; + _mediaLibraryFilter = nullptr; + _sigUpdated.emit(); + }); emitFilterAddedNotification(); } diff --git a/src/lms/ui/explore/Filters.hpp b/src/lms/ui/explore/Filters.hpp index f13a1d5dd..d1a011755 100644 --- a/src/lms/ui/explore/Filters.hpp +++ b/src/lms/ui/explore/Filters.hpp @@ -21,6 +21,7 @@ #include #include + #include #include #include @@ -57,4 +58,3 @@ namespace lms::ui db::MediaLibraryId _mediaLibraryId; }; } // namespace lms::ui - diff --git a/src/lms/ui/explore/PlayQueueController.cpp b/src/lms/ui/explore/PlayQueueController.cpp index 71606e46d..3af83380b 100644 --- a/src/lms/ui/explore/PlayQueueController.cpp +++ b/src/lms/ui/explore/PlayQueueController.cpp @@ -17,14 +17,16 @@ * along with LMS. If not, see . */ +#include "explore/PlayQueueController.hpp" + #include "database/ClusterId.hpp" #include "database/Release.hpp" #include "database/Session.hpp" #include "database/Track.hpp" -#include "explore/Filters.hpp" -#include "explore/PlayQueueController.hpp" -#include "PlayQueue.hpp" + #include "LmsApplication.hpp" +#include "PlayQueue.hpp" +#include "explore/Filters.hpp" namespace lms::ui { @@ -136,7 +138,7 @@ namespace lms::ui return db::Track::findIds(session, params).results; } - } + } // namespace PlayQueueController::PlayQueueController(Filters& filters, PlayQueue& playQueue) : _filters{ filters } @@ -200,7 +202,7 @@ namespace lms::ui releaseId = track->getRelease()->getId(); } - const std::vector tracks{ getReleasesTracks(LmsApp->getDbSession(), {releaseId}, _filters, _maxTrackCountToEnqueue) }; + const std::vector tracks{ getReleasesTracks(LmsApp->getDbSession(), { releaseId }, _filters, _maxTrackCountToEnqueue) }; auto itTrack{ std::find(std::cbegin(tracks), std::cend(tracks), trackId) }; if (itTrack == std::cend(tracks)) return; diff --git a/src/lms/ui/explore/PlayQueueController.hpp b/src/lms/ui/explore/PlayQueueController.hpp index 29f03c89d..037716462 100644 --- a/src/lms/ui/explore/PlayQueueController.hpp +++ b/src/lms/ui/explore/PlayQueueController.hpp @@ -28,42 +28,41 @@ namespace lms::ui { - class Filters; - class PlayQueue; + class Filters; + class PlayQueue; - // Used to interact with the play queue, using the current exploration filters - class PlayQueueController - { - public: - PlayQueueController(Filters& filters, PlayQueue& playQueue); + // Used to interact with the play queue, using the current exploration filters + class PlayQueueController + { + public: + PlayQueueController(Filters& filters, PlayQueue& playQueue); - enum class Command - { - Play, - PlayNext, - PlayOrAddLast, - PlayShuffled, - }; + enum class Command + { + Play, + PlayNext, + PlayOrAddLast, + PlayShuffled, + }; - void processCommand(Command command, const std::vector& artists); - void processCommand(Command command, const std::vector& releases); - void processCommand(Command command, const std::vector& tracks); + void processCommand(Command command, const std::vector& artists); + void processCommand(Command command, const std::vector& releases); + void processCommand(Command command, const std::vector& tracks); - struct Disc - { - db::ReleaseId releaseId; - size_t discNumber; - }; - void processCommand(Command command, const std::vector& discs); - void processCommand(Command command, db::TrackListId trackList); - void playTrackInRelease(db::TrackId track); + struct Disc + { + db::ReleaseId releaseId; + size_t discNumber; + }; + void processCommand(Command command, const std::vector& discs); + void processCommand(Command command, db::TrackListId trackList); + void playTrackInRelease(db::TrackId track); - void setMaxTrackCountToEnqueue(std::size_t maxTrackCount) { _maxTrackCountToEnqueue = maxTrackCount; } - - private: - Filters& _filters; - PlayQueue& _playQueue; - std::size_t _maxTrackCountToEnqueue {}; - }; -} + void setMaxTrackCountToEnqueue(std::size_t maxTrackCount) { _maxTrackCountToEnqueue = maxTrackCount; } + private: + Filters& _filters; + PlayQueue& _playQueue; + std::size_t _maxTrackCountToEnqueue{}; + }; +} // namespace lms::ui diff --git a/src/lms/ui/explore/ReleaseCollector.cpp b/src/lms/ui/explore/ReleaseCollector.cpp index b166b9e52..8d60907c1 100644 --- a/src/lms/ui/explore/ReleaseCollector.cpp +++ b/src/lms/ui/explore/ReleaseCollector.cpp @@ -19,12 +19,13 @@ #include "ReleaseCollector.hpp" +#include "core/Service.hpp" #include "database/Release.hpp" #include "database/Session.hpp" #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" #include "services/scrobbling/IScrobblingService.hpp" -#include "core/Service.hpp" + #include "Filters.hpp" #include "LmsApplication.hpp" @@ -48,74 +49,74 @@ namespace lms::ui break; case Mode::Starred: - { - feedback::IFeedbackService::FindParameters params; - params.setUser(LmsApp->getUserId()); - params.setClusters(getFilters().getClusters()); - params.setMediaLibrary(getFilters().getMediaLibrary()); - params.setKeywords(getSearchKeywords()); - params.setRange(range); - releases = feedbackService.findStarredReleases(params); - break; - } + { + feedback::IFeedbackService::FindParameters params; + params.setUser(LmsApp->getUserId()); + params.setClusters(getFilters().getClusters()); + params.setMediaLibrary(getFilters().getMediaLibrary()); + params.setKeywords(getSearchKeywords()); + params.setRange(range); + releases = feedbackService.findStarredReleases(params); + break; + } case ReleaseCollector::Mode::RecentlyPlayed: - { - scrobbling::IScrobblingService::FindParameters params; - params.setUser(LmsApp->getUserId()); - params.setClusters(getFilters().getClusters()); - params.setMediaLibrary(getFilters().getMediaLibrary()); - params.setKeywords(getSearchKeywords()); - params.setRange(range); - - releases = scrobblingService.getRecentReleases(params); - break; - } + { + scrobbling::IScrobblingService::FindParameters params; + params.setUser(LmsApp->getUserId()); + params.setClusters(getFilters().getClusters()); + params.setMediaLibrary(getFilters().getMediaLibrary()); + params.setKeywords(getSearchKeywords()); + params.setRange(range); + + releases = scrobblingService.getRecentReleases(params); + break; + } case Mode::MostPlayed: - { - scrobbling::IScrobblingService::FindParameters params; - params.setUser(LmsApp->getUserId()); - params.setClusters(getFilters().getClusters()); - params.setMediaLibrary(getFilters().getMediaLibrary()); - params.setKeywords(getSearchKeywords()); - params.setRange(range); - - releases = scrobblingService.getTopReleases(params); - break; - } + { + scrobbling::IScrobblingService::FindParameters params; + params.setUser(LmsApp->getUserId()); + params.setClusters(getFilters().getClusters()); + params.setMediaLibrary(getFilters().getMediaLibrary()); + params.setKeywords(getSearchKeywords()); + params.setRange(range); + + releases = scrobblingService.getTopReleases(params); + break; + } case Mode::RecentlyAdded: - { - Release::FindParameters params; - params.setClusters(getFilters().getClusters()); - params.setMediaLibrary(getFilters().getMediaLibrary()); - params.setKeywords(getSearchKeywords()); - params.setSortMethod(ReleaseSortMethod::LastWritten); - params.setRange(range); - { - auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - releases = Release::findIds(LmsApp->getDbSession(), params); + Release::FindParameters params; + params.setClusters(getFilters().getClusters()); + params.setMediaLibrary(getFilters().getMediaLibrary()); + params.setKeywords(getSearchKeywords()); + params.setSortMethod(ReleaseSortMethod::LastWritten); + params.setRange(range); + + { + auto transaction{ LmsApp->getDbSession().createReadTransaction() }; + releases = Release::findIds(LmsApp->getDbSession(), params); + } + break; } - break; - } case Mode::All: - { - Release::FindParameters params; - params.setClusters(getFilters().getClusters()); - params.setMediaLibrary(getFilters().getMediaLibrary()); - params.setSortMethod(ReleaseSortMethod::Name); - params.setKeywords(getSearchKeywords()); - params.setRange(range); - { - auto transaction{ LmsApp->getDbSession().createReadTransaction() }; - releases = Release::findIds(LmsApp->getDbSession(), params); + Release::FindParameters params; + params.setClusters(getFilters().getClusters()); + params.setMediaLibrary(getFilters().getMediaLibrary()); + params.setSortMethod(ReleaseSortMethod::Name); + params.setKeywords(getSearchKeywords()); + params.setRange(range); + + { + auto transaction{ LmsApp->getDbSession().createReadTransaction() }; + releases = Release::findIds(LmsApp->getDbSession(), params); + } + break; } - break; - } } if (range.offset + range.size == getMaxCount()) @@ -146,5 +147,4 @@ namespace lms::ui return _randomReleases->getSubRange(range); } -} // ns UserInterface - +} // namespace lms::ui diff --git a/src/lms/ui/explore/ReleaseCollector.hpp b/src/lms/ui/explore/ReleaseCollector.hpp index 999b2cc62..a4a54940d 100644 --- a/src/lms/ui/explore/ReleaseCollector.hpp +++ b/src/lms/ui/explore/ReleaseCollector.hpp @@ -29,22 +29,21 @@ namespace lms::db { - class Release; + class Release; } namespace lms::ui { - class ReleaseCollector : public DatabaseCollectorBase - { - public: - using DatabaseCollectorBase::DatabaseCollectorBase; - - db::RangeResults get(std::optional range = std::nullopt); - void reset() { _randomReleases.reset(); } - - private: - db::RangeResults getRandomReleases(Range range); - std::optional> _randomReleases; - }; -} // ns UserInterface - + class ReleaseCollector : public DatabaseCollectorBase + { + public: + using DatabaseCollectorBase::DatabaseCollectorBase; + + db::RangeResults get(std::optional range = std::nullopt); + void reset() { _randomReleases.reset(); } + + private: + db::RangeResults getRandomReleases(Range range); + std::optional> _randomReleases; + }; +} // namespace lms::ui diff --git a/src/lms/ui/explore/ReleaseHelpers.cpp b/src/lms/ui/explore/ReleaseHelpers.cpp index 4586e120f..675fea52f 100644 --- a/src/lms/ui/explore/ReleaseHelpers.cpp +++ b/src/lms/ui/explore/ReleaseHelpers.cpp @@ -48,7 +48,7 @@ namespace lms::ui::releaseListHelpers anchor->setImage(std::move(cover)); } - auto artistAnchors{ utils::createArtistsAnchorsForRelease(release, artist ? artist->getId() : ArtistId{}, "link-secondary") }; + auto artistAnchors{ utils::createArtistsAnchorsForRelease(release, artist ? artist->getId() : ArtistId{}, "link-secondary") }; if (artistAnchors) { entry->setCondition("if-has-artist", true); @@ -67,7 +67,7 @@ namespace lms::ui::releaseListHelpers return entry; } - } + } // namespace std::unique_ptr createEntry(const Release::pointer& release, const Artist::pointer& artist, bool showYear) { @@ -83,7 +83,7 @@ namespace lms::ui::releaseListHelpers { return createEntry(release, artist, true); } -} // namespace lms::ui +} // namespace lms::ui::releaseListHelpers namespace lms::ui::releaseHelpers { @@ -95,11 +95,21 @@ namespace lms::ui::releaseHelpers { switch (*releaseType.primaryType) { - case PrimaryReleaseType::Album: res = Wt::WString::tr("Lms.Explore.Release.type-primary-album"); break; - case PrimaryReleaseType::Broadcast: res = Wt::WString::tr("Lms.Explore.Release.type-primary-broadcast"); break; - case PrimaryReleaseType::EP: res = Wt::WString::tr("Lms.Explore.Release.type-primary-ep"); break; - case PrimaryReleaseType::Single: res = Wt::WString::tr("Lms.Explore.Release.type-primary-single"); break; - case PrimaryReleaseType::Other: res = Wt::WString::tr("Lms.Explore.Release.type-primary-other"); break; + case PrimaryReleaseType::Album: + res = Wt::WString::tr("Lms.Explore.Release.type-primary-album"); + break; + case PrimaryReleaseType::Broadcast: + res = Wt::WString::tr("Lms.Explore.Release.type-primary-broadcast"); + break; + case PrimaryReleaseType::EP: + res = Wt::WString::tr("Lms.Explore.Release.type-primary-ep"); + break; + case PrimaryReleaseType::Single: + res = Wt::WString::tr("Lms.Explore.Release.type-primary-single"); + break; + case PrimaryReleaseType::Other: + res = Wt::WString::tr("Lms.Explore.Release.type-primary-other"); + break; } } @@ -110,18 +120,42 @@ namespace lms::ui::releaseHelpers switch (secondaryType) { - case SecondaryReleaseType::Compilation: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-compilation"); break; - case SecondaryReleaseType::Spokenword: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-spokenword"); break; - case SecondaryReleaseType::Soundtrack: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-soundtrack"); break; - case SecondaryReleaseType::Interview: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-interview"); break; - case SecondaryReleaseType::Audiobook: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiobook"); break; - case SecondaryReleaseType::AudioDrama: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiodrama"); break; - case SecondaryReleaseType::Live: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-live"); break; - case SecondaryReleaseType::Remix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-remix"); break; - case SecondaryReleaseType::DJMix: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-djmix"); break; - case SecondaryReleaseType::Mixtape_Street: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-mixtape-street"); break; - case SecondaryReleaseType::Demo: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-demo"); break; - case SecondaryReleaseType::FieldRecording: res += Wt::WString::tr("Lms.Explore.Release.type-secondary-field-recording"); break; + case SecondaryReleaseType::Compilation: + res += Wt::WString::tr("Lms.Explore.Release.type-secondary-compilation"); + break; + case SecondaryReleaseType::Spokenword: + res += Wt::WString::tr("Lms.Explore.Release.type-secondary-spokenword"); + break; + case SecondaryReleaseType::Soundtrack: + res += Wt::WString::tr("Lms.Explore.Release.type-secondary-soundtrack"); + break; + case SecondaryReleaseType::Interview: + res += Wt::WString::tr("Lms.Explore.Release.type-secondary-interview"); + break; + case SecondaryReleaseType::Audiobook: + res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiobook"); + break; + case SecondaryReleaseType::AudioDrama: + res += Wt::WString::tr("Lms.Explore.Release.type-secondary-audiodrama"); + break; + case SecondaryReleaseType::Live: + res += Wt::WString::tr("Lms.Explore.Release.type-secondary-live"); + break; + case SecondaryReleaseType::Remix: + res += Wt::WString::tr("Lms.Explore.Release.type-secondary-remix"); + break; + case SecondaryReleaseType::DJMix: + res += Wt::WString::tr("Lms.Explore.Release.type-secondary-djmix"); + break; + case SecondaryReleaseType::Mixtape_Street: + res += Wt::WString::tr("Lms.Explore.Release.type-secondary-mixtape-street"); + break; + case SecondaryReleaseType::Demo: + res += Wt::WString::tr("Lms.Explore.Release.type-secondary-demo"); + break; + case SecondaryReleaseType::FieldRecording: + res += Wt::WString::tr("Lms.Explore.Release.type-secondary-field-recording"); + break; } } diff --git a/src/lms/ui/explore/ReleaseHelpers.hpp b/src/lms/ui/explore/ReleaseHelpers.hpp index 868856205..547efa423 100644 --- a/src/lms/ui/explore/ReleaseHelpers.hpp +++ b/src/lms/ui/explore/ReleaseHelpers.hpp @@ -22,28 +22,30 @@ #include #include +#include #include #include -#include + +#include "core/EnumSet.hpp" #include "database/Object.hpp" #include "database/Types.hpp" -#include "core/EnumSet.hpp" + #include "ReleaseTypes.hpp" namespace lms::db { class Artist; class Release; -} +} // namespace lms::db namespace lms::ui::releaseListHelpers { std::unique_ptr createEntry(const db::ObjectPtr& release); std::unique_ptr createEntryForArtist(const db::ObjectPtr& release, const db::ObjectPtr& artist); -} // namespace lms::ui +} // namespace lms::ui::releaseListHelpers namespace lms::ui::releaseHelpers { Wt::WString buildReleaseTypeString(const ReleaseType& releaseType); Wt::WString buildReleaseYearString(std::optional year, std::optional originalYear); -} +} // namespace lms::ui::releaseHelpers diff --git a/src/lms/ui/explore/ReleaseTypes.cpp b/src/lms/ui/explore/ReleaseTypes.cpp index 65320c70c..b844daac3 100644 --- a/src/lms/ui/explore/ReleaseTypes.cpp +++ b/src/lms/ui/explore/ReleaseTypes.cpp @@ -28,13 +28,12 @@ namespace lms::core::stringUtils template<> std::optional readAs(std::string_view str) { - static const std::unordered_map entries - { - {"album", ui::PrimaryReleaseType::Album}, - {"single", ui::PrimaryReleaseType::Single}, - {"ep", ui::PrimaryReleaseType::EP}, - {"broadcast", ui::PrimaryReleaseType::Broadcast}, - {"other", ui::PrimaryReleaseType::Other}, + static const std::unordered_map entries{ + { "album", ui::PrimaryReleaseType::Album }, + { "single", ui::PrimaryReleaseType::Single }, + { "ep", ui::PrimaryReleaseType::EP }, + { "broadcast", ui::PrimaryReleaseType::Broadcast }, + { "other", ui::PrimaryReleaseType::Other }, }; const auto it{ entries.find(stringToLower(stringTrim(str))) }; @@ -47,20 +46,19 @@ namespace lms::core::stringUtils template<> std::optional readAs(std::string_view str) { - static const std::unordered_map entries - { - {"compilation", ui::SecondaryReleaseType::Compilation}, - {"soundtrack", ui::SecondaryReleaseType::Soundtrack}, - {"spokenword", ui::SecondaryReleaseType::Spokenword}, - {"interview", ui::SecondaryReleaseType::Interview}, - {"audiobook", ui::SecondaryReleaseType::Audiobook}, - {"audio drama", ui::SecondaryReleaseType::AudioDrama}, - {"live", ui::SecondaryReleaseType::Live}, - {"remix", ui::SecondaryReleaseType::Remix}, - {"dj-mix", ui::SecondaryReleaseType::DJMix}, - {"mixtape/street", ui::SecondaryReleaseType::Mixtape_Street}, - {"demo", ui::SecondaryReleaseType::Demo}, - {"field recording", ui::SecondaryReleaseType::FieldRecording}, + static const std::unordered_map entries{ + { "compilation", ui::SecondaryReleaseType::Compilation }, + { "soundtrack", ui::SecondaryReleaseType::Soundtrack }, + { "spokenword", ui::SecondaryReleaseType::Spokenword }, + { "interview", ui::SecondaryReleaseType::Interview }, + { "audiobook", ui::SecondaryReleaseType::Audiobook }, + { "audio drama", ui::SecondaryReleaseType::AudioDrama }, + { "live", ui::SecondaryReleaseType::Live }, + { "remix", ui::SecondaryReleaseType::Remix }, + { "dj-mix", ui::SecondaryReleaseType::DJMix }, + { "mixtape/street", ui::SecondaryReleaseType::Mixtape_Street }, + { "demo", ui::SecondaryReleaseType::Demo }, + { "field recording", ui::SecondaryReleaseType::FieldRecording }, }; const auto it{ entries.find(stringToLower(stringTrim(str))) }; @@ -69,7 +67,7 @@ namespace lms::core::stringUtils return it->second; } -} +} // namespace lms::core::stringUtils namespace lms::ui { @@ -104,7 +102,7 @@ namespace lms::ui else if (typeA && !typeB) return true; else - return static_cast(*typeA) < static_cast(*typeB); + return static_cast(*typeA) < static_cast(*typeB); } bool operator<(core::EnumSet typesA, core::EnumSet typesB) diff --git a/src/lms/ui/explore/ReleaseView.cpp b/src/lms/ui/explore/ReleaseView.cpp index 17e4c9901..a74d3df93 100644 --- a/src/lms/ui/explore/ReleaseView.cpp +++ b/src/lms/ui/explore/ReleaseView.cpp @@ -20,11 +20,13 @@ #include "ReleaseView.hpp" #include + #include #include #include #include "av/IAudioFile.hpp" +#include "core/ILogger.hpp" #include "database/Artist.hpp" #include "database/Cluster.hpp" #include "database/Release.hpp" @@ -34,21 +36,20 @@ #include "database/TrackArtistLink.hpp" #include "database/User.hpp" #include "services/feedback/IFeedbackService.hpp" -#include "services/scrobbling/IScrobblingService.hpp" #include "services/recommendation/IRecommendationService.hpp" -#include "core/ILogger.hpp" +#include "services/scrobbling/IScrobblingService.hpp" -#include "common/Template.hpp" -#include "resource/DownloadResource.hpp" -#include "explore/Filters.hpp" -#include "explore/PlayQueueController.hpp" -#include "explore/ReleaseHelpers.hpp" -#include "explore/TrackListHelpers.hpp" #include "LmsApplication.hpp" #include "LmsApplicationException.hpp" #include "MediaPlayer.hpp" #include "ModalManager.hpp" #include "Utils.hpp" +#include "common/Template.hpp" +#include "explore/Filters.hpp" +#include "explore/PlayQueueController.hpp" +#include "explore/ReleaseHelpers.hpp" +#include "explore/TrackListHelpers.hpp" +#include "resource/DownloadResource.hpp" namespace lms::ui { @@ -76,30 +77,27 @@ namespace lms::ui std::map> artistMap; - auto addArtists = [&](TrackArtistLinkType linkType, const char* type) - { - Artist::FindParameters params; - params.setRelease(releaseId); - params.setLinkType(linkType); - const auto artistIds{ Artist::findIds(LmsApp->getDbSession(), params) }; - if (artistIds.results.empty()) - return; - - Wt::WString typeStr{ Wt::WString::trn(type, artistIds.results.size()) }; - for (ArtistId artistId : artistIds.results) - artistMap[typeStr].insert(artistId); - }; - - auto addPerformerArtists = [&] - { - TrackArtistLink::FindParameters params; - params.setRelease(releaseId); - params.setLinkType(TrackArtistLinkType::Performer); - TrackArtistLink::find(LmsApp->getDbSession(), params, [&](const TrackArtistLink::pointer& link) - { - artistMap[std::string{ link->getSubType() }].insert(link->getArtist()->getId()); - }); - }; + auto addArtists = [&](TrackArtistLinkType linkType, const char* type) { + Artist::FindParameters params; + params.setRelease(releaseId); + params.setLinkType(linkType); + const auto artistIds{ Artist::findIds(LmsApp->getDbSession(), params) }; + if (artistIds.results.empty()) + return; + + Wt::WString typeStr{ Wt::WString::trn(type, artistIds.results.size()) }; + for (ArtistId artistId : artistIds.results) + artistMap[typeStr].insert(artistId); + }; + + auto addPerformerArtists = [&] { + TrackArtistLink::FindParameters params; + params.setRelease(releaseId); + params.setLinkType(TrackArtistLinkType::Performer); + TrackArtistLink::find(LmsApp->getDbSession(), params, [&](const TrackArtistLink::pointer& link) { + artistMap[std::string{ link->getSubType() }].insert(link->getArtist()->getId()); + }); + }; addArtists(TrackArtistLinkType::Composer, "Lms.Explore.Artists.linktype-composer"); addArtists(TrackArtistLinkType::Conductor, "Lms.Explore.Artists.linktype-conductor"); @@ -155,10 +153,9 @@ namespace lms::ui releaseInfo->bindInt("playcount", core::Service::get()->getCount(LmsApp->getUserId(), release->getId())); Wt::WPushButton* okBtn{ releaseInfo->bindNew("ok-btn", Wt::WString::tr("Lms.ok")) }; - okBtn->clicked().connect([=] - { - LmsApp->getModalManager().dispose(releaseInfoPtr); - }); + okBtn->clicked().connect([=] { + LmsApp->getModalManager().dispose(releaseInfoPtr); + }); LmsApp->getModalManager().show(std::move(releaseInfo)); } @@ -180,7 +177,7 @@ namespace lms::ui return core::stringUtils::readAs(wApp->internalPathNextPart("/release/")); } - } + } // namespace Release::Release(Filters& filters, PlayQueueController& playQueueController) : Template{ Wt::WString::tr("Lms.Explore.Release.template") } @@ -190,16 +187,14 @@ namespace lms::ui addFunction("tr", &Wt::WTemplate::Functions::tr); addFunction("id", &Wt::WTemplate::Functions::id); - wApp->internalPathChanged().connect(this, [this] - { - refreshView(); - }); + wApp->internalPathChanged().connect(this, [this] { + refreshView(); + }); - _filters.updated().connect([this] - { - _needForceRefresh = true; - refreshView(); - }); + _filters.updated().connect([this] { + _needForceRefresh = true; + refreshView(); + }); refreshView(); } @@ -263,64 +258,62 @@ namespace lms::ui { const ClusterId clusterId{ cluster->getId() }; Wt::WInteractWidget* entry{ clusterContainers->addWidget(utils::createFilterCluster(clusterId)) }; - entry->clicked().connect([this, clusterId] - { - _filters.add(clusterId); - }); + entry->clicked().connect([this, clusterId] { + _filters.add(clusterId); + }); } } } bindNew("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML) - ->clicked().connect([this] - { - _playQueueController.processCommand(PlayQueueController::Command::Play, { _releaseId }); - }); + ->clicked() + .connect([this] { + _playQueueController.processCommand(PlayQueueController::Command::Play, { _releaseId }); + }); bindNew("play-shuffled", Wt::WString::tr("Lms.Explore.play-shuffled"), Wt::TextFormat::Plain) - ->clicked().connect([this] - { - _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, { _releaseId }); - }); + ->clicked() + .connect([this] { + _playQueueController.processCommand(PlayQueueController::Command::PlayShuffled, { _releaseId }); + }); bindNew("play-next", Wt::WString::tr("Lms.Explore.play-next"), Wt::TextFormat::Plain) - ->clicked().connect([this] - { - _playQueueController.processCommand(PlayQueueController::Command::PlayNext, { _releaseId }); - }); + ->clicked() + .connect([this] { + _playQueueController.processCommand(PlayQueueController::Command::PlayNext, { _releaseId }); + }); bindNew("play-last", Wt::WString::tr("Lms.Explore.play-last"), Wt::TextFormat::Plain) - ->clicked().connect([this] - { - _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { _releaseId }); - }); + ->clicked() + .connect([this] { + _playQueueController.processCommand(PlayQueueController::Command::PlayOrAddLast, { _releaseId }); + }); bindNew("download", Wt::WString::tr("Lms.Explore.download")) ->setLink(Wt::WLink{ std::make_unique(_releaseId) }); bindNew("release-info", Wt::WString::tr("Lms.Explore.release-info")) - ->clicked().connect([this] - { - showReleaseInfoModal(_releaseId); - }); + ->clicked() + .connect([this] { + showReleaseInfoModal(_releaseId); + }); { auto isStarred{ [this] { return core::Service::get()->isStarred(LmsApp->getUserId(), _releaseId); } }; Wt::WPushButton* starBtn{ bindNew("star", Wt::WString::tr(isStarred() ? "Lms.Explore.unstar" : "Lms.Explore.star")) }; - starBtn->clicked().connect([=, this] + starBtn->clicked().connect([=, this] { + if (isStarred()) { - if (isStarred()) - { - core::Service::get()->unstar(LmsApp->getUserId(), _releaseId); - starBtn->setText(Wt::WString::tr("Lms.Explore.star")); - } - else - { - core::Service::get()->star(LmsApp->getUserId(), _releaseId); - starBtn->setText(Wt::WString::tr("Lms.Explore.unstar")); - } - }); + core::Service::get()->unstar(LmsApp->getUserId(), _releaseId); + starBtn->setText(Wt::WString::tr("Lms.Explore.star")); + } + else + { + core::Service::get()->star(LmsApp->getUserId(), _releaseId); + starBtn->setText(Wt::WString::tr("Lms.Explore.unstar")); + } + }); } Wt::WContainerWidget* rootContainer{ bindNew("container") }; @@ -332,64 +325,61 @@ namespace lms::ui // Expect to be called in asc order std::map trackContainers; - auto getOrAddDiscContainer = [&, releaseId = _releaseId](std::size_t discNumber, const std::string& discSubtitle) -> Wt::WContainerWidget* - { - if (auto it{ trackContainers.find(discNumber) }; it != std::cend(trackContainers)) - return it->second; + auto getOrAddDiscContainer = [&, releaseId = _releaseId](std::size_t discNumber, const std::string& discSubtitle) -> Wt::WContainerWidget* { + if (auto it{ trackContainers.find(discNumber) }; it != std::cend(trackContainers)) + return it->second; - Template* disc{ rootContainer->addNew