Merge branch 'develop' for release v3.54.0

This commit is contained in:
emeric
2024-06-10 08:51:00 +02:00
470 changed files with 9756 additions and 8729 deletions
+30
View File
@@ -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
+13
View File
@@ -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'
+21 -17
View File
@@ -36,35 +36,39 @@
<div class="col-12">
<label class="form-label" for="${id:extra-tags-to-scan}">
${tr:Lms.Admin.Database.extra-tags-to-scan}
${extra-tags-to-scan-add-btn class="ms-1 btn btn-sm btn-secondary"}
</label>
${extra-tags-to-scan class="form-control"}
<div class="invalid-feedback">
${extra-tags-to-scan-info}
</div>
${extra-tags-to-scan-container class="row gy-3"}
</div>
<div class="col-12">
<label class="form-label" for="${id:artist-tag-delimiter}">
${tr:Lms.Admin.Database.artist-tag-delimiter}
<label class="form-label">
${tr:Lms.Admin.Database.artist-tag-delimiters}
${artist-tag-delimiter-add-btn class="ms-1 btn btn-sm btn-secondary"}
</label>
${artist-tag-delimiter class="form-control"}
<div class="invalid-feedback">
${artist-tag-delimiter-info}
</div>
${artist-tag-delimiter-container class="row gy-3"}
</div>
<div class="col-12">
<label class="form-label" for="${id:default-tag-delimiter}">
${tr:Lms.Admin.Database.default-tag-delimiter}
<label class="form-label">
${tr:Lms.Admin.Database.default-tag-delimiters}
${default-tag-delimiter-add-btn class="ms-1 btn btn-sm btn-secondary"}
</label>
${default-tag-delimiter class="form-control"}
<div class="invalid-feedback">
${default-tag-delimiter-info}
</div>
${default-tag-delimiter-container class="row gy-3"}
</div>
<div class="col-12">
${save-btn class="btn btn-primary me-1"}${discard-btn class="btn btn-secondary"}
</div>
</form>
<br/>
</message>
<message id="Lms.Admin.Database.template.line-edit-entry">
<div class="input-group has-validation">
${value class="form-control"}
${del-btn class="btn btn-sm btn-outline-warning"}
<div class="invalid-feedback">
${value-info}
</div>
</div>
</message>
</messages>
+3 -3
View File
@@ -65,10 +65,10 @@
<message id="Lms.Admin.MediaLibrary.root-path">Root directory</message>
<!--Scan settings-->
<message id="Lms.Admin.Database.artist-tag-delimiter">Delimiter to be used for splitting artist tags</message>
<message id="Lms.Admin.Database.artist-tag-delimiters">Delimiters to be used for splitting artist tags</message>
<message id="Lms.Admin.Database.daily">Daily</message>
<message id="Lms.Admin.Database.default-tag-delimiter">Delimiter to be used for splitting other tags</message>
<message id="Lms.Admin.Database.extra-tags-to-scan">Extra tags to scan (use <kbd>;</kbd> as separator)</message>
<message id="Lms.Admin.Database.default-tag-delimiters">Delimiters to be used for splitting other tags</message>
<message id="Lms.Admin.Database.extra-tags-to-scan">Extra tags to scan</message>
<message id="Lms.Admin.Database.hourly">Hourly</message>
<message id="Lms.Admin.Database.immediate-scan">Scan now!</message>
<message id="Lms.Admin.Database.monthly">Monthly</message>
+3 -3
View File
@@ -65,10 +65,10 @@
<message id="Lms.Admin.MediaLibrary.root-path">Répertoire racine</message>
<!--Scan settings-->
<message id="Lms.Admin.Database.artist-tag-delimiter">Délimiteur à utiliser pour séparer les tags d'artistes</message>
<message id="Lms.Admin.Database.artist-tag-delimiters">Délimiteurs à utiliser pour séparer les tags d'artistes</message>
<message id="Lms.Admin.Database.daily">Tous les jours</message>
<message id="Lms.Admin.Database.default-tag-delimiter">Délimiteur à utiliser pour séparer les autres tags</message>
<message id="Lms.Admin.Database.extra-tags-to-scan">Tags supplémentaires à scanner (utiliser <kbd>;</kbd> comme séparateur)</message>
<message id="Lms.Admin.Database.default-tag-delimiters">Délimiteurs à utiliser pour séparer les autres tags</message>
<message id="Lms.Admin.Database.extra-tags-to-scan">Tags supplémentaires à scanner</message>
<message id="Lms.Admin.Database.hourly">Toutes les heures</message>
<message id="Lms.Admin.Database.immediate-scan">Scanner maintenant !</message>
<message id="Lms.Admin.Database.monthly">Tous les mois</message>
+3 -3
View File
@@ -65,10 +65,10 @@
<message id="Lms.Admin.MediaLibrary.root-path">Cartella principale</message>
<!--Scan settings-->
<message id="Lms.Admin.Database.artist-tag-delimiter">Delimitatore da utilizzare per separare i tag degli artisti</message>
<message id="Lms.Admin.Database.artist-tag-delimiters">Delimitatori da utilizzare per separare i tag degli artisti</message>
<message id="Lms.Admin.Database.daily">Giornaliera</message>
<message id="Lms.Admin.Database.default-tag-delimiter">Delimitatore da utilizzare per separare gli altri tag</message>
<message id="Lms.Admin.Database.extra-tags-to-scan">Tag aggiuntivi da scansionare (usa <kbd>;</kbd> come separatore)</message>
<message id="Lms.Admin.Database.default-tag-delimiters">Delimitatori da utilizzare per separare gli altri tag</message>
<message id="Lms.Admin.Database.extra-tags-to-scan">Tag aggiuntivi da scansionare</message>
<message id="Lms.Admin.Database.hourly">Ogni ora</message>
<message id="Lms.Admin.Database.immediate-scan">Scansiona ora!</message>
<message id="Lms.Admin.Database.monthly">Mensile</message>
+344
View File
@@ -0,0 +1,344 @@
<?xml version="1.0" encoding="UTF-8" ?>
<messages xmlns:if="Wt.WTemplate.conditions"
nplurals="3" plural="n==1 ? 0 : n%10>=2 &amp;&amp; n%10<=4 &amp;&amp; (n%100<10 || n%100>=20) ? 1 : 2">
<!--Common-->
<message id="Lms.add">Dodaj</message>
<message id="Lms.administration">Zarządzanie</message>
<message id="Lms.cancel">Cofnij</message>
<message id="Lms.clusters">Znaczniki</message>
<message id="Lms.create">Utwórz</message>
<message id="Lms.delete">Usuń</message>
<message id="Lms.discard">Odrzuć</message>
<message id="Lms.edit">Zmień</message>
<message id="Lms.field-must-be-in-upper-case">Wartość musi się zaczynać wielką literą</message>
<message id="Lms.loading">Wczytywanie...</message>
<message id="Lms.login">Login</message>
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Wyloguj</message>
<message id="Lms.ok">Ok</message>
<message id="Lms.password">Hasło</message>
<message id="Lms.password-bad-login-combination">Błędny login lub hasło</message>
<message id="Lms.password-client-throttled">Logowanie spowolnione, spróbuj później</message>
<message id="Lms.password-confirm">Potwierdź hasło</message>
<message id="Lms.password-must-match-login">Hasło musi się zgadzać z loginem!</message>
<message id="Lms.password-new">Nowe hasło</message>
<message id="Lms.password-old">Stare hasło</message>
<message id="Lms.password-too-weak">Hasło zbyt słabe</message>
<message id="Lms.passwords-dont-match">Hasła się nie zgadzają</message>
<message id="Lms.quit-other-session">Rozpoczęto inną sesję. Odtworzyć tę?</message>
<message id="Lms.save">Zapisz</message>
<message id="Lms.settings-saved">Ustawienia zapisane!</message>
<message id="Lms.track-count">
<plural case="0">{1} ścieżka</plural>
<plural case="1">{1} ścieżki</plural>
<plural case="2">{1} ścieżek</plural>
</message>
<message id="Lms.user">Użytkownik</message>
<!--Errors-->
<message id="Lms.Error.artist-not-found">Artysta nie odnaleziony</message>
<message id="Lms.Error.error-occurred">Wystąpił błąd!</message>
<message id="Lms.Error.go-home">Strona główna</message>
<message id="Lms.Error.release-not-found">Wydanie nie odnalezione</message>
<message id="Lms.Error.tracklist-not-found">Lista odtwarzania nie odnaleziona</message>
<message id="Lms.Error.user-not-allowed">Brak uprawnień by wykonać tę operację</message>
<message id="Lms.Error.user-not-found">Użytkownik nie odnaleziony</message>
<!--Administration-->
<message id="Lms.Admin.menu-media-libraries"><i class="fa fa-fw fa-database" aria-hidden="true"></i> Biblioteki</message>
<message id="Lms.Admin.menu-scan-settings"><i class="fa fa-fw fa-cogs" aria-hidden="true"></i> Ustawienia skanowania</message>
<message id="Lms.Admin.menu-scanner"><i class="fa fa-fw fa-wrench" aria-hidden="true"></i> Skaner</message>
<message id="Lms.Admin.menu-tracing"><i class="fa fa-fw fa-bar-chart" aria-hidden="true"></i> Śledzenie</message>
<message id="Lms.Admin.menu-users"><i class="fa fa-fw fa-users" aria-hidden="true"></i> Użytkownicy</message>
<!--MediaLibraries-->
<message id="Lms.Admin.MediaLibraries.media-libraries">Biblioteki muzyczne</message>
<message id="Lms.Admin.MediaLibrary.create-library">Utwórz bibliotekę</message>
<message id="Lms.Admin.MediaLibrary.del-library-confirm">Usunąć bibliotekę?</message>
<message id="Lms.Admin.MediaLibrary.edit-library">Edytuj bibliotekę</message>
<message id="Lms.Admin.MediaLibrary.library-created">Biblioteka utworzona!</message>
<message id="Lms.Admin.MediaLibrary.library-deleted">Biblioteka usunięta!</message>
<message id="Lms.Admin.MediaLibrary.name">Nazwa biblioteki</message>
<message id="Lms.Admin.MediaLibrary.name-already-exists">Nazwa biblioteki już istnieje</message>
<message id="Lms.Admin.MediaLibrary.path-must-be-absolute">Ścieżka musi być bezwzględna</message>
<message id="Lms.Admin.MediaLibrary.path-must-not-overlap">Ścieżka nie może się zazębiać z innymi bibliotekami</message>
<message id="Lms.Admin.MediaLibrary.path-must-be-existing-directory">Ścieżka musi prowadzić do istniejącego katalogu</message>
<message id="Lms.Admin.MediaLibrary.root-path">Katalog główny</message>
<!--Scan settings-->
<message id="Lms.Admin.Database.artist-tag-delimiters">Znaki rozdzielające artystów</message>
<message id="Lms.Admin.Database.daily">Codziennie</message>
<message id="Lms.Admin.Database.default-tag-delimiters">Znaki rozdzielające inne oznaczenia</message>
<message id="Lms.Admin.Database.extra-tags-to-scan">Szukaj dodatkowych znaczników</message>
<message id="Lms.Admin.Database.hourly">Co godzinę</message>
<message id="Lms.Admin.Database.immediate-scan">Skanuj teraz!</message>
<message id="Lms.Admin.Database.monthly">Co miesiąc</message>
<message id="Lms.Admin.Database.never">Nigdy</message>
<message id="Lms.Admin.Database.scan-aborted">Skanowanie przerwane!</message>
<message id="Lms.Admin.Database.scan-complete">Skanowanie ukończone: {1} wszystkich plików, {2} dodane, {3} zmienione, {4} usunięte, {5} duplikaty, {6} błędy</message>
<message id="Lms.Admin.Database.scan-launched">Skanowanie uruchomione!</message>
<message id="Lms.Admin.Database.scan-settings">Ustawienia skanowania</message>
<message id="Lms.Admin.Database.similarity-engine-type">Metoda sprawdzania podobieństwa</message>
<message id="Lms.Admin.Database.similarity-engine-type.clusters">Oparta o znaczniki</message>
<message id="Lms.Admin.Database.similarity-engine-type.none">Żadna</message>
<message id="Lms.Admin.Database.tag-delimiter-must-not-contain-only-spaces">Rozdzielacz nie może się składać z samych białych znaków</message>
<message id="Lms.Admin.Database.update-period">Okres aktualizacji</message>
<message id="Lms.Admin.Database.update-start-time">Czas startu aktualizacji</message>
<message id="Lms.Admin.Database.weekly">Co tydzień</message>
<!--Scanner Controller-->
<message id="Lms.Admin.ScannerController.bad-duration">Nie udało się ustalić długości ścieżki</message>
<message id="Lms.Admin.ScannerController.cannot-parse-file">Nie udało się przeparsować pliku</message>
<message id="Lms.Admin.ScannerController.cannot-read-file">Nie udało się odczytać pliku</message>
<message id="Lms.Admin.ScannerController.compact">Sprasuj bazę danych. <strong>Uwaga:</strong> może to trochę zająć, a cała aplikacja będzie w tym czasie zablokowana!</message>
<message id="Lms.Admin.ScannerController.duplicates-header">
<plural case="0">{1} zduplikowany plik:</plural>
<plural case="1">{1} zduplikowane pliki:</plural>
<plural case="2">{1} zduplikowanych plików:</plural>
</message>
<message id="Lms.Admin.ScannerController.errors-header">
<plural case="0">{1} błąd:</plural>
<plural case="1">{1} błędy:</plural>
<plural case="2">{1} błędów:</plural>
</message>
<message id="Lms.Admin.ScannerController.force-optimize">Wymuś optymalizację bazy danych</message>
<message id="Lms.Admin.ScannerController.full-scan">Ponownie przeskanuj wszystkie pliki</message>
<message id="Lms.Admin.ScannerController.get-report">Pokaż raport</message>
<message id="Lms.Admin.ScannerController.last-scan">Ostatnie skanowanie</message>
<message id="Lms.Admin.ScannerController.last-scan-not-available">Niedostępne</message>
<message id="Lms.Admin.ScannerController.last-scan-status">Przeskanowano {1} plików w {2} o {3} ({4} błędów, {5} duplikatów)</message>
<message id="Lms.Admin.ScannerController.no-audio-track">Brak ścieżki dźwiękowej</message>
<message id="Lms.Admin.ScannerController.same-hash">Zduplikowny hash pliku</message>
<message id="Lms.Admin.ScannerController.same-mbid">Zduplikowany MBID pliku</message>
<message id="Lms.Admin.ScannerController.scan-now">Skanuj teraz</message>
<message id="Lms.Admin.ScannerController.scan-options">Opcje skanowania</message>
<message id="Lms.Admin.ScannerController.scanner">Skaner</message>
<message id="Lms.Admin.ScannerController.status">Status</message>
<message id="Lms.Admin.ScannerController.status-not-scheduled">Nie zaplanowano</message>
<message id="Lms.Admin.ScannerController.status-scheduled">Zaplanowano na {1}</message>
<message id="Lms.Admin.ScannerController.status-in-progress">Skanowanie: krok {1}/{2}</message>
<message id="Lms.Admin.ScannerController.step-checking-for-duplicate-files">
<plural case="0">Sprawdzanie duplikatów... {1} plik</plural>
<plural case="1">Sprawdzanie duplikatów... {1} pliki</plural>
<plural case="2">Sprawdzanie duplikatów... {1} plików</plural>
</message>
<message id="Lms.Admin.ScannerController.step-checking-for-missing-files">Sprawdzanie plików... {1}%</message>
<message id="Lms.Admin.ScannerController.step-compact">Prasowanie bazy danych...</message>
<message id="Lms.Admin.ScannerController.step-compute-cluster-stats">Obliczanie statystyk... {1}%</message>
<message id="Lms.Admin.ScannerController.step-discovering-files">
<plural case="0">Odkrywanie plików: {1} plik</plural>
<plural case="1">Odkrywanie plików: {1} pliki</plural>
<plural case="2">Odkrywanie plików: {1} plików</plural>
</message>
<message id="Lms.Admin.ScannerController.step-fetching-track-features">Pobieranie danych o ścieżce z AcousticBrainz: {1}/{2} ścieżek ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-optimize">Optymalizowanie bazy danych... {1}/{2} wpisów ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-reloading-similarity-engine">Przeładowywanie silnika podobieństw: {1}%...</message>
<message id="Lms.Admin.ScannerController.step-scanning-files">Skanowanie plików: {1}/{2} plików ({3}%)...</message>
<message id="Lms.Admin.ScannerController.step-status">Obecny krok</message>
<!--Tracing-->
<message id="Lms.Admin.Tracing.export-current-buffer">Eksportuj ślady</message>
<message id="Lms.Admin.Tracing.tracing">Śledzenie</message>
<!--Users-->
<message id="Lms.Admin.Users.add">Nowy użytkownik</message>
<message id="Lms.Admin.Users.admin">Admin</message>
<message id="Lms.Admin.Users.del-user-confirm">Usunąć użytkownika?</message>
<message id="Lms.Admin.Users.demo">Demo</message>
<message id="Lms.Admin.Users.users">Użytkownicy</message>
<!--User-->
<message id="Lms.Admin.User.demo-account">Konto demonstacyjne</message>
<message id="Lms.Admin.User.demo-account-already-exists">Konto demonstacyjne już istnieje!</message>
<message id="Lms.Admin.User.last-login">Ostatnie logowanie</message>
<message id="Lms.Admin.User.user-already-exists">Użytkownik już istnieje!</message>
<message id="Lms.Admin.User.user-create">Nowy użytkownik</message>
<message id="Lms.Admin.User.user-created">Użytkownik utworzony!</message>
<message id="Lms.Admin.User.user-edit">Użytkownik "{1}"</message>
<message id="Lms.Admin.User.user-updated">Użytkownik zaktualizowany!</message>
<!--Wizard-->
<message id="Lms.Admin.InitWizard.done">Konto administratora utworzone. Proszę odświerzyć stronę!</message>
<message id="Lms.Admin.InitWizard.header">Utwórz konto administratora</message>
<!--Auth-->
<message id="Lms.Auth.remember-me">Zapamiętaj mnie</message>
<message id="Lms.Auth.welcome">Witam!</message>
<!--Explore-->
<message id="Lms.Explore.add-filter">Dodaj filtr</message>
<message id="Lms.Explore.all">Wszystko</message>
<message id="Lms.Explore.artists">Artyści</message>
<message id="Lms.Explore.bitrate">Przepływność</message>
<message id="Lms.Explore.codec">Kodek</message>
<message id="Lms.Explore.download">Pobierz</message>
<message id="Lms.Explore.duration">Długość</message>
<message id="Lms.Explore.filter-added">Filtr dodany</message>
<message id="Lms.Explore.filters">Filtry</message>
<message id="Lms.Explore.media-library">Biblioteka muzyczna</message>
<message id="Lms.Explore.most-played">Najczęściej odtwarzane</message>
<message id="Lms.Explore.musicbrainz-artist">Artysta w MusicBrainz</message>
<message id="Lms.Explore.musicbrainz-release">Wydanie w MusicBrainz</message>
<message id="Lms.Explore.play">Odtwarzaj</message>
<message id="Lms.Explore.play-last">Odtwórz na końcu</message>
<message id="Lms.Explore.play-next">Odtwórz po obecnym</message>
<message id="Lms.Explore.play-shuffled">Odtwarzaj losowo</message>
<message id="Lms.Explore.playcount">Liczba odtworzeń</message>
<message id="Lms.Explore.random">Losowo</message>
<message id="Lms.Explore.recently-added">Ostatnio dodane</message>
<message id="Lms.Explore.recently-modified">Ostatnio zmienione</message>
<message id="Lms.Explore.recently-played">Ostatnio odtwarzane</message>
<message id="Lms.Explore.release-info">Informacje o wydaniu</message>
<message id="Lms.Explore.releases">Albumy</message>
<message id="Lms.Explore.search">Szukaj</message>
<message id="Lms.Explore.star">Wyróżnij</message>
<message id="Lms.Explore.starred">Wyróżnione</message>
<message id="Lms.Explore.track-info">Informacje o ścieżce</message>
<message id="Lms.Explore.tracklists">Listy odtwarzania</message>
<message id="Lms.Explore.tracks">Ścieżki</message>
<message id="Lms.Explore.type">Rodzaj</message>
<message id="Lms.Explore.unstar">Przestań wyróżniać</message>
<message id="Lms.Explore.value">Wartość</message>
<message id="Lms.Explore.various-artists">Różni artyści</message>
<!--Explore:Artist-->
<message id="Lms.Explore.Artist.appears-on">Pojawia się na</message>
<message id="Lms.Explore.Artist.similar-artists">Podobni artyści</message>
<!--Explore:Artists-->
<message id="Lms.Explore.Artists.linktype-all">Wszyscy artyści</message>
<message id="Lms.Explore.Artists.linktype-artist">
<plural case="0">Artysta ze ścieżki</plural>
<plural case="1">Artyści ze ścieżki</plural>
<plural case="2">Artyści ze ścieżki</plural>
</message>
<message id="Lms.Explore.Artists.linktype-composer">
<plural case="0">Kompozytor</plural>
<plural case="1">Kompozytorzy</plural>
<plural case="2">Kompozytorzy</plural>
</message>
<message id="Lms.Explore.Artists.linktype-conductor">
<plural case="0">Dyrygent</plural>
<plural case="1">Dyrygenci</plural>
<plural case="2">Dyrygenci</plural>
</message>
<message id="Lms.Explore.Artists.linktype-lyricist">
<plural case="0">Tekściarz</plural>
<plural case="1">Tekściarze</plural>
<plural case="2">Tekściarze</plural>
</message>
<message id="Lms.Explore.Artists.linktype-mixer">
<plural case="0">Realizator</plural>
<plural case="1">Realizatorzy</plural>
<plural case="2">Realizatorzy</plural>
</message>
<message id="Lms.Explore.Artists.linktype-performer">
<plural case="0">Wykonawca</plural>
<plural case="1">Wykonawcy</plural>
<plural case="2">Wykonawcy</plural>
</message>
<message id="Lms.Explore.Artists.linktype-producer">
<plural case="0">Producent</plural>
<plural case="1">Producenci</plural>
<plural case="2">Producenci</plural>
</message>
<message id="Lms.Explore.Artists.linktype-releaseartist">
<plural case="0">Artysta z albumu</plural>
<plural case="1">Artyści z albumu</plural>
<plural case="2">Artyści z albumu</plural>
</message>
<message id="Lms.Explore.Artists.linktype-remixer">
<plural case="0">Remixer</plural>
<plural case="1">Remikserzy</plural>
<plural case="2">Remikserzy</plural>
</message>
<!--Explore:Release-->
<message id="Lms.Explore.Release.similar-releases">Podobne albumy</message>
<message id="Lms.Explore.Release.copyright">Prawa autorskie</message>
<message id="Lms.Explore.Release.disc">Dysk {1}</message>
<message id="Lms.Explore.Release.type">Typ</message>
<message id="Lms.Explore.Release.type-primary-album">Album</message>
<message id="Lms.Explore.Release.type-primary-broadcast">Audycja</message>
<message id="Lms.Explore.Release.type-primary-ep">EP</message>
<message id="Lms.Explore.Release.type-primary-other">Inny</message>
<message id="Lms.Explore.Release.type-primary-single">Singiel</message>
<message id="Lms.Explore.Release.type-secondary-audiobook">Audiobook</message>
<message id="Lms.Explore.Release.type-secondary-audiodrama">Audiodrama</message>
<message id="Lms.Explore.Release.type-secondary-compilation">Kompilacja</message>
<message id="Lms.Explore.Release.type-secondary-demo">Demo</message>
<message id="Lms.Explore.Release.type-secondary-djmix">DJ-mix</message>
<message id="Lms.Explore.Release.type-secondary-field-recording">Nagranie polowe</message>
<message id="Lms.Explore.Release.type-secondary-interview">Wywiad</message>
<message id="Lms.Explore.Release.type-secondary-live">Na żywo</message>
<message id="Lms.Explore.Release.type-secondary-mixtape-street">Mixtape/Street</message>
<message id="Lms.Explore.Release.type-secondary-remix">Remix</message>
<message id="Lms.Explore.Release.type-secondary-soundtrack">Ścieżka dźwiękowa</message>
<message id="Lms.Explore.Release.type-secondary-spokenword">Mówione</message>
<!--Explore:TrackLists-->
<message id="Lms.Explore.TrackLists.del-tracklist-confirm">Usunąć listę odtwarzania?</message>
<!--Explore:Search-->
<message id="Lms.Explore.Search.search-placeholder">Szukaj...</message>
<!--Player-->
<message id="Lms.Player.transcoding-active">Transkodowanie włączone</message>
<!--Playqueue-->
<message id="Lms.PlayQueue.clear">Wyczyść</message>
<message id="Lms.PlayQueue.create-tracklist">Utwórz nową listę odtwarzania</message>
<message id="Lms.PlayQueue.playqueue">Odtwarzaj kolejkę</message>
<message id="Lms.PlayQueue.radio-mode">Tryb radiowy</message>
<message id="Lms.PlayQueue.repeat">Powtarzaj</message>
<message id="Lms.PlayQueue.replace-tracklist">Zastąp obecną listę</message>
<message id="Lms.PlayQueue.shuffle">Przemieszaj</message>
<!--PlayHistory-->
<message id="Lms.PlayHistory.playhistory">Historia odtwarzania</message>
<!--Settings-->
<message id="Lms.Settings.audio">Dźwięk</message>
<message id="Lms.Settings.audio-settings-are-local">Te ustawienia dotyczą wyłącznie tej przeglądarki!</message>
<message id="Lms.Settings.backend.internal">Wewnętrzny</message>
<message id="Lms.Settings.backend.listenbrainz">ListenBrainz</message>
<message id="Lms.Settings.backend.listenbrainz-token">ListenBrainz API token</message>
<message id="Lms.Settings.change-password">Zmień hasło</message>
<message id="Lms.Settings.default-transcoding-output-bitrate">Domyślna przepływność transkodowania</message>
<message id="Lms.Settings.default-transcoding-output-format">Domyślny format wyjściowy transkodowania</message>
<message id="Lms.Settings.demo-cannot-save">Nie można zapisywać na koncie demonstracyjnym!</message>
<message id="Lms.Settings.enable-transcoding-by-default">Aktywuj domyślnie transkodowanie</message>
<message id="Lms.Settings.feedback">Feedback</message>
<message id="Lms.Settings.menu-settings"><i class="fa fa-fw fa-cog" aria-hidden="true"></i> Ustawienia</message>
<message id="Lms.Settings.password-bad">Błędne hasło</message>
<message id="Lms.Settings.password-must-fill-old-password">Należy podać stare hasło</message>
<message id="Lms.Settings.replaygain-mode">Tryb ReplayGain</message>
<message id="Lms.Settings.replaygain-mode.none">Bez ReplayGain</message>
<message id="Lms.Settings.replaygain-mode.auto">Auto</message>
<message id="Lms.Settings.replaygain-mode.track">Ścieżka</message>
<message id="Lms.Settings.replaygain-mode.release">Album</message>
<message id="Lms.Settings.replaygain-preamp">ReplayGain preamp</message>
<message id="Lms.Settings.replaygain-preamp-no-rg-info">ReplayGain preamp (jeśli brak informacji)</message>
<message id="Lms.Settings.scrobbling">Scrobbling</message>
<message id="Lms.Settings.services">Usługi</message>
<message id="Lms.Settings.settings">Ustawienia</message>
<message id="Lms.Settings.subsonic-artist-list-mode">Tryb listy artystów</message>
<message id="Lms.Settings.subsonic-artist-list-mode.all-artists">Wszyscy artyści</message>
<message id="Lms.Settings.subsonic-artist-list-mode.release-artists">Artyści z albumów</message>
<message id="Lms.Settings.subsonic-artist-list-mode.track-artists">Artyści ze ścieżek</message>
<message id="Lms.Settings.subsonic-api">Subsonic API</message>
<message id="Lms.Settings.transcoding">Transkodowanie</message>
<message id="Lms.Settings.transcoding-output-bitrate">Przepływność transkodowania</message>
<message id="Lms.Settings.transcoding-output-format">Format wyjściowy transkodowania</message>
<message id="Lms.Settings.transcoding-output-format.matroska_opus">Matroska/Opus</message>
<message id="Lms.Settings.transcoding-output-format.mp3">MP3</message>
<message id="Lms.Settings.transcoding-output-format.ogg_opus">Ogg/Opus</message>
<message id="Lms.Settings.transcoding-output-format.ogg_vorbis">Ogg/Vorbis</message>
<message id="Lms.Settings.transcoding-output-format.webm_vorbis">WebM/Vorbis</message>
<message id="Lms.Settings.transcoding-mode">Aktywuj transkodowanie</message>
<message id="Lms.Settings.transcoding-mode.always">Zawsze</message>
<message id="Lms.Settings.transcoding-mode.if-format-not-supported">Tylko gdy przeglądarka nie wspiera formatu</message>
<message id="Lms.Settings.transcoding-mode.never">Nigdy</message>
<message id="Lms.Settings.settings-saved">Ustawienia zapisane!</message>
</messages>
+71 -47
View File
@@ -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<IAudioFile> 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<void(const Picture&)> func) const
{
static const std::unordered_map<int, std::string> codecMimeMap =
{
static const std::unordered_map<int, std::string> 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<std::string, std::string_view> 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/dsd"},
{".wv", "audio/x-wavpack"},
{".wvp", "audio/x-wavpack"},
{".mka", "audio/x-matroska"},
static const std::unordered_map<std::string, std::string_view> 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())) };
+9 -12
View File
@@ -17,8 +17,6 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
/* 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<StreamInfo> getStreamInfo() const override;
std::optional<StreamInfo> getBestStreamInfo() const override;
std::optional<std::size_t> getBestStreamIndex() const override;
bool hasAttachedPictures() const override;
void visitAttachedPictures(std::function<void(const Picture&)> func) const override;
ContainerInfo getContainerInfo() const override;
MetadataMap getMetaData() const override;
std::vector<StreamInfo> getStreamInfo() const override;
std::optional<StreamInfo> getBestStreamInfo() const override;
std::optional<std::size_t> getBestStreamIndex() const override;
bool hasAttachedPictures() const override;
void visitAttachedPictures(std::function<void(const Picture&)> func) const override;
private:
AudioFile(const AudioFile&) = delete;
AudioFile& operator=(const AudioFile&) = delete;
std::optional<StreamInfo> getStreamInfo(std::size_t streamIndex) const;
std::optional<StreamInfo> getStreamInfo(std::size_t streamIndex) const;
const std::filesystem::path _p;
const std::filesystem::path _p;
AVFormatContext* _context{};
};
} // namespace lms::av
@@ -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
+18 -14
View File
@@ -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<size_t> globalId{};
static std::filesystem::path ffmpegPath;
static std::atomic<size_t> 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
+9 -9
View File
@@ -45,24 +45,24 @@ namespace lms::av::transcoding
// non blocking calls
using ReadCallback = std::function<void(std::size_t nbReadBytes)>;
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<core::IChildProcess> _childProcess;
std::unique_ptr<core::IChildProcess> _childProcess;
};
}
} // namespace lms::av::transcoding
@@ -29,7 +29,7 @@ namespace lms::av::transcoding
const std::size_t estimatedContentLength{ outputParameters.bitrate / 8 * static_cast<std::size_t>(std::chrono::duration_cast<std::chrono::milliseconds>(inputParameters.duration).count()) / 1000 };
return estimatedContentLength;
}
}
} // namespace
std::unique_ptr<IResourceHandler> 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
@@ -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<std::size_t> _estimatedContentLength;
@@ -45,5 +46,4 @@ namespace lms::av::transcoding
std::size_t _totalServedByteCount{};
Transcoder _transcoder;
};
}
} // namespace lms::av::transcoding
+30 -29
View File
@@ -17,17 +17,15 @@
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
*/
/* This file contains some classes in order to get info from file using the libavconv */
#pragma once
#include <chrono>
#include <filesystem>
#include <functional>
#include <unordered_map>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#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<std::string, std::string>;
virtual const std::filesystem::path& getPath() const = 0;
virtual ContainerInfo getContainerInfo() const = 0;
virtual MetadataMap getMetaData() const = 0;
virtual std::vector<StreamInfo> getStreamInfo() const = 0;
virtual std::optional<StreamInfo> getBestStreamInfo() const = 0; // none if failure/unknown
virtual std::optional<std::size_t> getBestStreamIndex() const = 0; // none if failure/unknown
virtual bool hasAttachedPictures() const = 0;
virtual void visitAttachedPictures(std::function<void(const Picture&)> func) const = 0;
virtual ContainerInfo getContainerInfo() const = 0;
virtual MetadataMap getMetaData() const = 0;
virtual std::vector<StreamInfo> getStreamInfo() const = 0;
virtual std::optional<StreamInfo> getBestStreamInfo() const = 0; // none if failure/unknown
virtual std::optional<std::size_t> getBestStreamIndex() const = 0; // none if failure/unknown
virtual bool hasAttachedPictures() const = 0;
virtual void visitAttachedPictures(std::function<void(const Picture&)> func) const = 0;
};
std::unique_ptr<IAudioFile> 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
@@ -46,11 +46,10 @@ namespace lms::av::transcoding
struct OutputParameters
{
OutputFormat format;
std::size_t bitrate{ 128000 };
std::optional<std::size_t> 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<std::size_t> 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
@@ -25,8 +25,8 @@
namespace lms::av::transcoding
{
struct InputParameters;
struct OutputParameters;
struct InputParameters;
struct OutputParameters;
std::unique_ptr<IResourceHandler> createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength);
}
std::unique_ptr<IResourceHandler> createResourceHandler(const InputParameters& inputParameters, const OutputParameters& outputParameters, bool estimateContentLength);
} // namespace lms::av::transcoding
+1 -1
View File
@@ -28,4 +28,4 @@ namespace lms::av
public:
using LmsException::LmsException;
};
}
} // namespace lms::av
+3 -3
View File
@@ -19,6 +19,7 @@
#include <iostream>
#include <thread>
#include <benchmark/benchmark.h>
#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();
+15 -15
View File
@@ -23,6 +23,7 @@
#include <cassert>
#include <cstring> // strerror
#include <fstream>
#include <archive.h>
#include <archive_entry.h>
@@ -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<ArchiveZipper*>(clientData)};
auto archiveWrite{ [](struct ::archive*, void* clientData, const void* buff, ::size_t n) -> la_ssize_t {
ArchiveZipper* zipper{ static_cast<ArchiveZipper*>(clientData) };
return zipper->onWriteCallback(static_cast<const std::byte*>(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
+2 -1
View File
@@ -21,6 +21,7 @@
#include <cstddef>
#include <memory>
#include "core/IZipper.hpp"
extern "C"
@@ -77,4 +78,4 @@ namespace lms::zip
std::uint64_t _bytesWrittenInCurrentOutputStream{};
};
}
} // namespace lms::zip
+10 -9
View File
@@ -19,21 +19,21 @@
#include "ChildProcess.hpp"
#include <cstring>
#include <cerrno>
#include <cstring>
#include <fcntl.h>
#include <signal.h>
#include <stdexcept>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <unistd.h>
#include <algorithm>
#include <iostream>
#include <mutex>
#include <boost/asio/read.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/read.hpp>
#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;
}
}
} // namespace lms::core
+11 -11
View File
@@ -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<int> _exitCode;
FileDescriptor _childStdout;
::pid_t _childPID{};
bool _waited{};
bool _finished{};
std::optional<int> _exitCode;
};
}
} // namespace lms::core
+1 -1
View File
@@ -39,4 +39,4 @@ namespace lms::core
{
return std::make_unique<ChildProcess>(_ioContext, path, args);
}
}
} // namespace lms::core
+1 -1
View File
@@ -44,4 +44,4 @@ namespace lms::core
boost::asio::io_context& _ioContext;
};
}
} // namespace lms::core
+3 -3
View File
@@ -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<const char*>(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;
}
}
}
} // namespace lms::core
+7 -8
View File
@@ -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<void(std::string_view)> _func, std::initializer_list<std::string_view> 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<void(std::string_view)> _func, std::initializer_list<std::string_view> 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;
};
}
} // namespace lms::core
+2 -2
View File
@@ -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;
}
}
} // namespace lms
+7 -6
View File
@@ -22,6 +22,7 @@
#include <filesystem>
#include <string>
#include <string_view>
#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{};
};
}
} // namespace lms
+16 -17
View File
@@ -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<tracing::ITraceLogger>::get() })
traceLogger->setThreadName(std::this_thread::get_id(), threadName);
}
if (auto* traceLogger{ Service<tracing::ITraceLogger>::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();
}
}
} // namespace lms::core
+51 -26
View File
@@ -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();
}
}
} // namespace lms::core::logging
+15 -15
View File
@@ -23,24 +23,24 @@
namespace std
{
std::size_t hash<boost::asio::ip::address>::operator()(const boost::asio::ip::address& ipAddr) const
{
if (ipAddr.is_v4())
return ipAddr.to_v4().to_ulong();
std::size_t hash<boost::asio::ip::address>::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<char>{}(static_cast<char>(b));
for (auto b : range)
res ^= std::hash<char>{}(static_cast<char>(b));
return res;
}
return res;
}
return std::hash<std::string>{}(ipAddr.to_string());
}
}
return std::hash<std::string>{}(ipAddr.to_string());
}
} // namespace std
#endif // BOOST_ASIO_HAS_STD_HASH
+8 -7
View File
@@ -19,12 +19,11 @@
#include "core/Path.hpp"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <array>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <boost/tokenizer.hpp>
@@ -44,7 +43,7 @@ namespace lms::core::pathUtils
{
do
{
std::array<char, 1024> buffer;
std::array<char, 1024> buffer;
ifs.read(buffer.data(), buffer.size());
crc32.processBytes(reinterpret_cast<const std::byte*>(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
+1 -1
View File
@@ -35,4 +35,4 @@ namespace lms::core::random
return RandGenerator{ seed };
}
}
} // namespace lms::core::random
+1 -1
View File
@@ -131,4 +131,4 @@ namespace lms::core
return _sharedCounts[thisThreadId] > 0;
}
#endif
}
} // namespace lms::core
+1 -1
View File
@@ -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;
}
}
} // namespace lms::core::logging
+16 -17
View File
@@ -23,15 +23,14 @@
#include <iomanip>
#include <utility>
#include <Wt/WDateTime.h>
#include <Wt/WDate.h>
#include <Wt/WDateTime.h>
namespace lms::core::stringUtils
{
namespace details
{
constexpr std::pair<char, std::string_view> jsEscapeChars[]
{
constexpr std::pair<char, std::string_view> jsEscapeChars[]{
{ '\\', "\\\\" },
{ '\n', "\\n" },
{ '\r', "\\r" },
@@ -40,8 +39,7 @@ namespace lms::core::stringUtils
{ '\'', "\\\'" },
};
constexpr std::pair<char, std::string_view> jsonEscapeChars[]
{
constexpr std::pair<char, std::string_view> jsonEscapeChars[]{
{ '\\', "\\\\" },
{ '\n', "\\n" },
{ '\r', "\\r" },
@@ -49,8 +47,8 @@ namespace lms::core::stringUtils
{ '"', "\\\"" },
};
template <std::size_t N>
std::string escape(std::string_view str, const std::pair<char, std::string_view>(&charsToEscape)[N])
template<std::size_t N>
std::string escape(std::string_view str, const std::pair<char, std::string_view> (&charsToEscape)[N])
{
std::string escaped;
escaped.reserve(str.length());
@@ -70,12 +68,12 @@ namespace lms::core::stringUtils
return escaped;
}
template <std::size_t N>
void writeEscapedString(std::ostream& os, std::string_view str, const std::pair<char, std::string_view>(&charsToEscape)[N])
template<std::size_t N>
void writeEscapedString(std::ostream& os, std::string_view str, const std::pair<char, std::string_view> (&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 <typename StringType>
template<typename StringType>
std::string joinStrings(std::span<const StringType> strings, std::string_view delimiter)
{
std::string res;
@@ -99,7 +97,7 @@ namespace lms::core::stringUtils
return res;
}
}
} // namespace details
template<>
std::optional<std::string> 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
+10 -7
View File
@@ -22,6 +22,7 @@
#include <iomanip>
#include <memory>
#include <string>
#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<std::uint32_t>(id);
}
}
} // namespace lms::core::tracing
+1 -1
View File
@@ -103,4 +103,4 @@ namespace lms::core::tracing
static thread_local Buffer* _currentBuffer;
};
}
} // namespace lms::core::tracing
+7 -8
View File
@@ -31,13 +31,13 @@ namespace lms::core
{
namespace stringUtils
{
template <>
template<>
std::optional<UUID>
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<int>(random::getRandom<std::uint8_t>(0, 255));
} };
@@ -88,4 +87,4 @@ namespace lms::core
assert(uuid);
return uuid.value();
}
}
} // namespace lms::core
+15 -9
View File
@@ -19,10 +19,11 @@
#include "core/WtLogger.hpp"
#include <thread>
#include <sstream>
#include <Wt/WServer.h>
#include <thread>
#include <Wt/WLogger.h>
#include <Wt/WServer.h>
#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();
}
}
} // namespace lms::core::logging
+1 -1
View File
@@ -36,4 +36,4 @@ namespace lms::core::http
{
_sendQueue.sendRequest(std::make_unique<ClientRequest>(std::move(POSTParams)));
}
}
} // namespace lms::core::http
+6 -4
View File
@@ -19,11 +19,12 @@
#pragma once
#include <unordered_map>
#include <string>
#include <shared_mutex>
#include <string>
#include <unordered_map>
#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;
};
}
} // namespace lms::core::http
+10 -7
View File
@@ -21,6 +21,7 @@
#include <memory>
#include <variant>
#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<const ClientRequestParameters&>(parameters);
}, _parameters);
std::visit([&](const auto& parameters) {
res = &static_cast<const ClientRequestParameters&>(parameters);
},
_parameters);
return *res;
}
@@ -71,4 +74,4 @@ namespace lms::core::http
private:
std::variant<ClientGETRequestParameters, ClientPOSTRequestParameters> _parameters;
};
}
} // namespace lms::core::http
+28 -32
View File
@@ -19,15 +19,15 @@
#include "SendQueue.hpp"
#include <boost/asio/dispatch.hpp>
#include <boost/asio/bind_executor.hpp>
#include <boost/asio/dispatch.hpp>
#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 <typename T>
template<typename T>
std::optional<T> headerReadAs(const Wt::Http::Message& msg, std::string_view headerName)
{
std::optional<T> res;
if (const std::string * headerValue{ msg.getHeader(std::string {headerName}) })
if (const std::string * headerValue{ msg.getHeader(std::string{ headerName }) })
res = stringUtils::readAs<T>(*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<ClientRequest> 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()} };
}
LOG(DEBUG, "Throttle aborted");
return;
}
else if (ec)
{
throw LmsException{ "Throttle timer failure: " + std::string{ ec.message() } };
}
_state = State::Idle;
sendNextQueuedRequest();
});
_state = State::Idle;
sendNextQueuedRequest();
});
_state = State::Throttled;
}
}
} // namespace lms::core::http
+13 -13
View File
@@ -20,14 +20,14 @@
#pragma once
#include <deque>
#include <vector>
#include <string_view>
#include <vector>
#include <Wt/Http/Client.h>
#include <boost/asio/io_context.hpp>
#include <boost/asio/io_context_strand.hpp>
#include <boost/asio/steady_timer.hpp>
#include <Wt/Http/Client.h>
#include "ClientRequest.hpp"
namespace lms::core::http
@@ -53,15 +53,15 @@ namespace lms::core::http
void onClientDoneSuccess(std::unique_ptr<ClientRequest> 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<ClientRequestParameters::Priority, std::deque<std::unique_ptr<ClientRequest>>> _sendQueue;
std::unique_ptr<ClientRequest> _currentRequest;
std::unique_ptr<ClientRequest> _currentRequest;
};
}
} // namespace lms::core::http
@@ -19,7 +19,7 @@
#pragma once
#include <boost/crc.hpp> // for boost::crc_32_type
#include <boost/crc.hpp> // 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
+8 -5
View File
@@ -27,7 +27,7 @@
namespace lms::core
{
template <typename T, typename underlying_type = std::uint32_t>
template<typename T, typename underlying_type = std::uint32_t>
class EnumSet
{
static_assert(std::is_enum<T>::value);
@@ -45,13 +45,13 @@ namespace lms::core
insert(value);
}
template <typename It>
template<typename It>
constexpr EnumSet(It begin, It end)
{
assign(begin, end);
}
template <typename It>
template<typename It>
constexpr void assign(It begin, It end)
{
clear();
@@ -158,7 +158,10 @@ namespace lms::core
private:
static_assert(std::numeric_limits<IndexType>::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{};
};
}
} // namespace lms::core
+3 -2
View File
@@ -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 } } {}
};
}
} // namespace lms::core
+4 -4
View File
@@ -48,9 +48,9 @@ namespace lms::core
};
using ReadCallback = std::function<void(ReadResult, std::size_t)>;
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;
};
}
} // namespace lms::core
@@ -20,6 +20,7 @@
#include <filesystem>
#include <memory>
#include <boost/asio/io_service.hpp>
#include "IChildProcess.hpp"
@@ -35,4 +36,4 @@ namespace lms::core
};
std::unique_ptr<IChildProcessManager> createChildProcessManager(boost::asio::io_service& ioService);
}
} // namespace lms::core
+7 -7
View File
@@ -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<void(std::string_view)> _func, std::initializer_list<std::string_view> 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<void(std::string_view)> _func, std::initializer_list<std::string_view> 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<IConfig> createConfig(const std::filesystem::path& p);
}
} // namespace lms::core
+9 -9
View File
@@ -19,11 +19,11 @@
#pragma once
#include <string>
#include <sstream>
#include <string>
#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)
@@ -21,6 +21,7 @@
#include <optional>
#include <thread>
#include <boost/asio/io_service.hpp>
namespace lms::core
@@ -39,7 +40,7 @@ namespace lms::core
IOContextRunner& operator=(const IOContextRunner&) = delete;
boost::asio::io_service& _ioService;
std::optional<boost::asio::io_service::work> _work;
std::vector<std::thread> _threads;
std::optional<boost::asio::io_service::work> _work;
std::vector<std::thread> _threads;
};
}
} // namespace lms::core
@@ -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;
};
}
[[nodiscard]] virtual Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) = 0;
virtual void abort() = 0;
};
} // namespace lms
+11 -11
View File
@@ -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;
};
}
} // namespace lms::core::tracing
+2 -2
View File
@@ -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<IZipper> createArchiveZipper(const EntryContainer& entries);
}
} // namespace lms::zip
+24 -12
View File
@@ -31,7 +31,11 @@ namespace lms::core
public:
constexpr LiteralString() noexcept = default;
template<std::size_t N>
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<std::string_view>{}(str.str());
}
};
}
} // namespace std
namespace lms::core
{
@@ -69,15 +73,18 @@ namespace lms::core
using hash_type = std::hash<std::string_view>;
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();
}
};
}
} // namespace lms::core
+7 -6
View File
@@ -22,15 +22,16 @@
#include <boost/asio/ip/address.hpp>
#ifndef BOOST_ASIO_HAS_STD_HASH
#include <functional>
#include <functional>
namespace std
{
template<> struct hash<boost::asio::ip::address>
{
std::size_t operator()(const boost::asio::ip::address& ipAddr) const;
};
template<>
struct hash<boost::asio::ip::address>
{
std::size_t operator()(const boost::asio::ip::address& ipAddr) const;
};
}
} // namespace std
#endif // BOOST_ASIO_HAS_STD_HASH
+2 -2
View File
@@ -49,7 +49,7 @@ namespace lms::core::pathUtils
std::filesystem::path getLongestCommonPath(const std::filesystem::path& path1, const std::filesystem::path& path2);
template <typename Iterator>
template<typename Iterator>
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
+5 -5
View File
@@ -29,27 +29,27 @@ namespace lms::core::random
RandGenerator createSeededGenerator(uint_fast32_t seed);
template <typename T>
template<typename T>
T getRandom(T min, T max)
{
std::uniform_int_distribution<> dist{ min, max };
return dist(getRandGenerator());
}
template <typename T>
template<typename T>
T getRealRandom(T min, T max)
{
std::uniform_real_distribution<> dist{ min, max };
return dist(getRandGenerator());
}
template <typename Container>
template<typename Container>
void shuffleContainer(Container& container)
{
std::shuffle(std::begin(container), std::end(container), getRandGenerator());
}
template <typename Container>
template<typename Container>
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<int>(container.size() - 1)));
}
}
} // namespace lms::core::random
@@ -48,4 +48,4 @@ namespace lms::core
std::mutex _sharedCountMutex;
std::unordered_map<std::thread::id, std::size_t> _sharedCounts;
};
}
} // namespace lms::core
+3 -3
View File
@@ -24,7 +24,7 @@
namespace lms::core
{
template <typename Class>
template<typename Class>
class Service
{
public:
@@ -57,7 +57,7 @@ namespace lms::core
static Class* get() { return _service.get(); }
static bool exists() { return _service.get(); }
template <typename SubClass>
template<typename SubClass>
static Class& assign(std::unique_ptr<SubClass> service)
{
assert(!_service);
@@ -70,4 +70,4 @@ namespace lms::core
static inline std::unique_ptr<Class> _service;
};
}
} // namespace lms::core
+1 -1
View File
@@ -39,4 +39,4 @@ namespace lms::core::logging
std::ostream& _os;
const EnumSet<Severity> _severities;
};
}
} // namespace lms::core::logging
+4 -4
View File
@@ -22,9 +22,9 @@
#include <initializer_list>
#include <optional>
#include <span>
#include <sstream>
#include <string>
#include <string_view>
#include <sstream>
#include <vector>
#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);
}
} // namespace lms::core::stringUtils
+26 -20
View File
@@ -23,27 +23,33 @@
namespace lms::core
{
namespace details
{
template<int... Is>
struct Seq { };
namespace details
{
template<int... Is>
struct Seq
{
};
template<int N, int... Is>
struct GenSeq : GenSeq<N - 1, N - 1, Is...> { };
template<int N, int... Is>
struct GenSeq : GenSeq<N - 1, N - 1, Is...>
{
};
template<int... Is>
struct GenSeq<0, Is...> : Seq<Is...> { };
template<int... Is>
struct GenSeq<0, Is...> : Seq<Is...>
{
};
template<typename T, typename Func, int... Is>
void forEachTypeInTuple(T&& t, Func f, Seq<Is...>)
{
auto l = { (f(std::get<Is>(t)), 0)... };
}
}
template<typename T, typename Func, int... Is>
void forEachTypeInTuple(T&& t, Func f, Seq<Is...>)
{
auto l = { (f(std::get<Is>(t)), 0)... };
}
} // namespace details
template<typename... Ts, typename Func>
void forEachTypeInTuple(std::tuple<Ts...> const& t, Func f)
{
details::forEachTypeInTuple(t, f, details::GenSeq<sizeof...(Ts)>());
}
}
template<typename... Ts, typename Func>
void forEachTypeInTuple(std::tuple<Ts...> const& t, Func f)
{
details::forEachTypeInTuple(t, f, details::GenSeq<sizeof...(Ts)>());
}
} // namespace lms::core
+3 -3
View File
@@ -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<UUID>
readAs(std::string_view str);
readAs(std::string_view str);
}
+15 -14
View File
@@ -24,18 +24,19 @@
namespace lms::core::utils
{
template<class T, class Compare = std::less<>>
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<class T, class Compare = std::less<>>
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 <typename Container, typename T>
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);
}
}
template<typename Container, typename T>
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
+1 -1
View File
@@ -37,4 +37,4 @@ namespace lms::core::logging
void processLog(const Log& log) override;
const Severity _minSeverity;
};
}
} // namespace lms::core::logging
@@ -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<void(std::string_view msgBody)>;
@@ -56,4 +56,4 @@ namespace lms::core::http
Wt::Http::Message message;
};
}
} // namespace lms::core::http
+10 -9
View File
@@ -20,20 +20,21 @@
#pragma once
#include <string_view>
#include <boost/asio/io_context.hpp>
#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<IClient> createClient(boost::asio::io_context& ioContext, std::string_view baseUrl);
}
std::unique_ptr<IClient> createClient(boost::asio::io_context& ioContext, std::string_view baseUrl);
} // namespace lms::core::http
+1 -1
View File
@@ -60,4 +60,4 @@ namespace lms::core
EXPECT_EQ(test, test2);
}
}
}
} // namespace lms::core
+4 -4
View File
@@ -18,6 +18,7 @@
*/
#include <unordered_map>
#include <gtest/gtest.h>
#include "core/LiteralString.hpp"
@@ -53,7 +54,7 @@ namespace lms::core
TEST(LiteralString, unordered_map)
{
{
std::unordered_map<LiteralString, int> myMap{ {"abc", 42} };
std::unordered_map<LiteralString, int> myMap{ { "abc", 42 } };
EXPECT_TRUE(myMap.contains("abc"));
EXPECT_TRUE(myMap.contains(LiteralString{ "abc" }));
@@ -61,12 +62,11 @@ namespace lms::core
}
{
std::unordered_map<LiteralString, int, LiteralStringHash, LiteralStringEqual> myMap{ {"abc", 42} };
std::unordered_map<LiteralString, int, LiteralStringHash, LiteralStringEqual> 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" }));
}
}
}
} // namespace lms::core
+30 -34
View File
@@ -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;
}
}
}
} // namespace lms::core::pathUtils::tests
+24 -25
View File
@@ -66,40 +66,39 @@ namespace lms::core
std::atomic<std::size_t> nbShared{};
for (std::size_t i{}; i < nbThreads; ++i)
{
threads.emplace_back([&]
threads.emplace_back([&] {
{
{
std::unique_lock lock{ mutex };
std::unique_lock lock{ mutex };
std::shared_lock lock2{ 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--;
}
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 };
{
std::shared_lock lock{ mutex };
std::shared_lock lock2{ mutex };
assert(nbUnique == 0);
nbShared++;
assert(nbUnique == 0);
nbShared++;
std::this_thread::sleep_for(std::chrono::milliseconds(15));
std::this_thread::sleep_for(std::chrono::milliseconds(15));
assert(nbShared > 0);
assert(nbShared <= nbThreads);
assert(nbUnique == 0);
nbShared--;
}
});
assert(nbShared > 0);
assert(nbShared <= nbThreads);
assert(nbUnique == 0);
nbShared--;
}
});
}
for (std::thread& t : threads)
t.join();
}
}
} // namespace lms::core
+69 -74
View File
@@ -19,9 +19,10 @@
#include <gtest/gtest.h>
#include <Wt/WDateTime.h>
#include <Wt/WDate.h>
#include <Wt/WDateTime.h>
#include <Wt/WTime.h>
#include "core/String.hpp"
namespace lms::core::stringUtils::tests
@@ -35,30 +36,29 @@ namespace lms::core::stringUtils::tests
std::vector<std::string_view> 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,18 @@ namespace lms::core::stringUtils::tests
std::vector<std::string_view> 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 +107,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 +134,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 +159,12 @@ namespace lms::core::stringUtils::tests
std::vector<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)
@@ -250,17 +246,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 +274,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 +289,4 @@ namespace lms::core::stringUtils::tests
EXPECT_FALSE(stringEndsWith("FooBar", "1FooBar"));
EXPECT_FALSE(stringEndsWith("FooBar", "R"));
}
}
} // namespace lms::core::stringUtils::tests
+6 -6
View File
@@ -19,6 +19,7 @@
#include <sstream>
#include <thread>
#include <gtest/gtest.h>
#include "core/ITraceLogger.hpp"
@@ -33,11 +34,10 @@ namespace lms::core::tracing::tests
std::vector<std::thread> 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);
}
}
} // namespace lms::core::tracing::tests
+1 -1
View File
@@ -37,4 +37,4 @@ namespace lms::core
EXPECT_TRUE(uuid1 >= uuid2);
EXPECT_TRUE(uuid1 <= uuid2);
}
}
} // namespace lms::core
+3 -4
View File
@@ -19,9 +19,8 @@
#include <gtest/gtest.h>
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();
}
+42 -49
View File
@@ -20,22 +20,23 @@
#include <Wt/Dbo/WtSqlTraits.h>
#include "core/ILogger.hpp"
#include "database/Cluster.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 <typename ResultType>
template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Artist::FindParameters& params)
{
session.checkReadTransaction();
@@ -97,21 +98,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 +164,7 @@ namespace lms::db
return query;
}
template <typename ResultType>
template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, const Artist::FindParameters& params)
{
std::string_view itemToSelect;
@@ -173,12 +178,12 @@ namespace lms::db
return createQuery<ResultType>(session, itemToSelect, params);
}
}
} // namespace
Artist::Artist(const std::string& name, const std::optional<core::UUID>& 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 +203,7 @@ namespace lms::db
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Artist>>("SELECT a FROM artist a")
.orderBy("a.id")
.where("a.id > ?").bind(lastRetrievedArtist)
.limit(static_cast<int>(count)) };
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Artist>>("SELECT a FROM artist a").orderBy("a.id").where("a.id > ?").bind(lastRetrievedArtist).limit(static_cast<int>(count)) };
if (library.isValid())
{
@@ -209,20 +211,17 @@ 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::pointer> Artist::find(Session& session, std::string_view name)
{
session.checkReadTransaction();
return utils::fetchQueryResults<Artist::pointer>(session.getDboSession()->find<Artist>()
.where("name = ?").bind(std::string{ name, 0, _maxNameLength })
.orderBy("LENGTH(mbid) DESC")); // put mbid entries first
return utils::fetchQueryResults<Artist::pointer>(session.getDboSession()->find<Artist>().where("name = ?").bind(std::string{ name, 0, _maxNameLength }).orderBy("LENGTH(mbid) DESC")); // put mbid entries first
}
Artist::pointer Artist::find(Session& session, const core::UUID& mbid)
@@ -279,19 +278,18 @@ namespace lms::db
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 +307,7 @@ namespace lms::db
oss << ")";
}
auto query{ session()->query<ArtistId>(oss.str())
.bind(getId())
.bind(getId())
.groupBy("a.id")
.orderBy("COUNT(*) DESC, RANDOM()") };
auto query{ session()->query<ArtistId>(oss.str()).bind(getId()).bind(getId()).groupBy("a.id").orderBy("COUNT(*) DESC, RANDOM()") };
for (const TrackArtistLinkType type : artistLinkTypes)
query.bind(type);
@@ -347,11 +341,10 @@ namespace lms::db
query.bind(bindArg);
std::map<ClusterTypeId, std::vector<Cluster::pointer>> 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<std::vector<Cluster::pointer>> res;
for (const auto& [clusterTypeId, clusters] : clustersByType)
+5 -3
View File
@@ -20,10 +20,12 @@
#include "database/AuthToken.hpp"
#include <Wt/Dbo/WtSqlTraits.h>
#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> user)
{
return session.getDboSession()->add(std::unique_ptr<AuthToken> {new AuthToken{ value, expiry, user }});
return session.getDboSession()->add(std::unique_ptr<AuthToken>{ 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<AuthToken>().where("value = ?").bind(value));
}
}
} // namespace lms::db
+20 -26
View File
@@ -25,16 +25,17 @@
#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 <typename ResultType>
template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Cluster::FindParameters& params)
{
session.checkReadTransaction();
@@ -44,7 +45,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 +78,7 @@ namespace lms::db
return query;
}
template <typename ResultType>
template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, const Cluster::FindParameters& params)
{
std::string_view itemToSelect;
@@ -91,17 +92,17 @@ namespace lms::db
return createQuery<ResultType>(session, itemToSelect, params);
}
}
} // namespace
Cluster::Cluster(ObjectPtr<ClusterType> 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<ClusterType> type, std::string_view name)
{
return session.getDboSession()->add(std::unique_ptr<Cluster> {new Cluster{ type, name }});
return session.getDboSession()->add(std::unique_ptr<Cluster>{ new Cluster{ type, name } });
}
std::size_t Cluster::getCount(Session& session)
@@ -154,16 +155,14 @@ namespace lms::db
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("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<int>("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<int>("SELECT COUNT(DISTINCT r.id) FROM release r INNER JOIN track t on t.release_id = r.id INNER JOIN track_cluster t_c ON t_c.track_id = t.id")
.where("t_c.cluster_id = ?").bind(id));
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(DISTINCT 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> track)
@@ -175,8 +174,7 @@ namespace lms::db
{
assert(session());
auto query{ session()->query<TrackId>("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<TrackId>("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<TrackId>(query, range);
}
@@ -188,7 +186,7 @@ namespace lms::db
ClusterType::pointer ClusterType::create(Session& session, std::string_view name)
{
return session.getDboSession()->add(std::unique_ptr<ClusterType> {new ClusterType{ name }});
return session.getDboSession()->add(std::unique_ptr<ClusterType>{ new ClusterType{ name } });
}
std::size_t ClusterType::getCount(Session& session)
@@ -203,9 +201,9 @@ namespace lms::db
session.checkReadTransaction();
auto query{ session.getDboSession()->query<ClusterTypeId>(
"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<ClusterTypeId>(query, range);
}
@@ -215,8 +213,8 @@ namespace lms::db
session.checkReadTransaction();
auto query{ session.getDboSession()->query<ClusterTypeId>(
"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<ClusterTypeId>(query, range);
}
@@ -255,9 +253,7 @@ namespace lms::db
assert(self());
assert(session());
return utils::fetchQuerySingleResult(session()->find<Cluster>()
.where("name = ?").bind(name)
.where("cluster_type_id = ?").bind(getId()));
return utils::fetchQuerySingleResult(session()->find<Cluster>().where("name = ?").bind(name).where("cluster_type_id = ?").bind(getId()));
}
std::vector<Cluster::pointer> ClusterType::getClusters() const
@@ -265,8 +261,6 @@ namespace lms::db
assert(self());
assert(session());
return utils::fetchQueryResults<Cluster::pointer>(session()->find<Cluster>()
.where("cluster_type_id = ?").bind(getId())
.orderBy("name"));
return utils::fetchQueryResults<Cluster::pointer>(session()->find<Cluster>().where("cluster_type_id = ?").bind(getId()).orderBy("name"));
}
} // namespace lms::db
+7 -17
View File
@@ -22,12 +22,12 @@
#include <Wt/Dbo/FixedSqlConnectionPool.h>
#include <Wt/Dbo/backend/Sqlite3.h>
#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
{
@@ -50,12 +50,6 @@ namespace lms::db
prepare();
}
~Connection() override
{
// make use of per-connection usage stats to optimize
optimize();
}
private:
Connection& operator=(const Connection&) = delete;
@@ -72,16 +66,9 @@ namespace lms::db
LMS_LOG(DB, DEBUG, "Setting per-connection settings done!");
}
void optimize()
{
LMS_LOG(DB, DEBUG, "connection close: Running pragma optimize...");
executeSql("PRAGMA optimize");
LMS_LOG(DB, DEBUG, "connection close: pragma optimize complete");
}
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)
@@ -89,7 +76,7 @@ namespace lms::db
LMS_LOG(DB, INFO, "Creating connection pool on file " << dbPath.string());
auto connection{ std::make_unique<Connection>(dbPath.string()) };
if (core::IConfig * config{ core::Service<core::IConfig>::get() })// may not be here on testU
if (core::IConfig * config{ core::Service<core::IConfig>::get() }) // may not be here on testU
connection->setProperty("show-queries", config->getBool("db-show-queries", false) ? "true" : "false");
auto connectionPool{ std::make_unique<Wt::Dbo::FixedSqlConnectionPool>(std::move(connection), connectionCount) };
@@ -119,6 +106,9 @@ namespace lms::db
}
}
// For now, multiple databases are not handled
assert(&tlsSession->getDb() == this);
return *tlsSession;
}
+2 -2
View File
@@ -20,6 +20,7 @@
#pragma once
#include <type_traits>
#include <Wt/Dbo/StdSqlTraits.h>
#include "core/EnumSet.hpp"
@@ -49,5 +50,4 @@ namespace Wt::Dbo
return false;
}
};
}
} // namespace Wt::Dbo
+2 -2
View File
@@ -20,6 +20,7 @@
#pragma once
#include <type_traits>
#include <Wt/Dbo/StdSqlTraits.h>
#include "database/Types.hpp"
@@ -55,5 +56,4 @@ namespace Wt::Dbo
return false;
}
};
}
} // namespace Wt::Dbo
+41 -59
View File
@@ -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<ArtistId> createArtistsQuery(Session& session, const Listen::ArtistStatsFindParameters& params)
{
auto query{ session.getDboSession()->query<ArtistId>("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<ArtistId>("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<ReleaseId> createReleasesQuery(Session& session, const Listen::StatsFindParameters& params)
{
auto query{ session.getDboSession()->query<ReleaseId>("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<ReleaseId>("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<TrackId> createTracksQuery(Session& session, const Listen::StatsFindParameters& params)
{
auto query{ session.getDboSession()->query<TrackId>("SELECT t.id from track t")
.join("listen l ON l.track_id = t.id") };
auto query{ session.getDboSession()->query<TrackId>("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> user, ObjectPtr<Track> 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> user, ObjectPtr<Track> track, ScrobblingBackend backend, const Wt::WDateTime& dateTime)
{
session.checkWriteTransaction();
return session.getDboSession()->add(std::unique_ptr<Listen> {new Listen{ user, track, backend, dateTime }});
return session.getDboSession()->add(std::unique_ptr<Listen>{ new Listen{ user, track, backend, dateTime } });
}
std::size_t Listen::getCount(Session& session)
@@ -218,8 +217,7 @@ namespace lms::db
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<ListenId>("SELECT id FROM listen")
.orderBy("date_time") };
auto query{ session.getDboSession()->query<ListenId>("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<Listen>()
.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<Listen>().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<ArtistId> 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<ArtistId>(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<ArtistId>(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<ReleaseId>(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<TrackId>(query, params.range);
@@ -310,11 +307,7 @@ namespace lms::db
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("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<int>("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<int>(
"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<Wt::Dbo::ptr<Listen>>("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<Wt::Dbo::ptr<Listen>>("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<Wt::Dbo::ptr<Listen>>("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<Wt::Dbo::ptr<Listen>>("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
+6 -6
View File
@@ -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<MediaLibrary>(), [&](const MediaLibrary::pointer& mediaLibrary)
{
func(mediaLibrary);
});
utils::forEachQueryResult(session.getDboSession()->find<MediaLibrary>(), [&](const MediaLibrary::pointer& mediaLibrary) {
func(mediaLibrary);
});
}
} // namespace lms::db
+44 -41
View File
@@ -21,13 +21,14 @@
#include <Wt/Dbo/WtSqlTraits.h>
#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
@@ -39,7 +40,8 @@ namespace lms::db
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<VersionInfo>());
}
}
} // 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");
}
@@ -74,12 +77,12 @@ namespace lms::db::Migration
_db.executeSql("PRAGMA foreign_keys=ON");
}
private:
ScopedNoForeignKeys(const ScopedNoForeignKeys&) = delete;
ScopedNoForeignKeys(ScopedNoForeignKeys&&) = delete;
ScopedNoForeignKeys& operator=(const ScopedNoForeignKeys&) = delete;
ScopedNoForeignKeys& operator=(ScopedNoForeignKeys&&) = delete;
private:
Db& _db;
};
@@ -107,9 +110,9 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" (
{
// 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<int>(/*ScrobblingState::PendingAdd*/0)) + ")");
session.getDboSession()->execute("ALTER TABLE starred_release ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*ScrobblingState::PendingAdd*/0)) + ")");
session.getDboSession()->execute("ALTER TABLE starred_track ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*ScrobblingState::PendingAdd*/0)) + ")");
session.getDboSession()->execute("ALTER TABLE starred_artist ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*ScrobblingState::PendingAdd*/ 0)) + ")");
session.getDboSession()->execute("ALTER TABLE starred_release ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*ScrobblingState::PendingAdd*/ 0)) + ")");
session.getDboSession()->execute("ALTER TABLE starred_track ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*ScrobblingState::PendingAdd*/ 0)) + ")");
}
static void migrateFromV35(Session& session)
@@ -252,7 +255,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
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<int>(/*User::defaultSubsonicEnableTranscodingByDefault*/0)) + ")");
session.getDboSession()->execute("ALTER TABLE user ADD subsonic_enable_transcoding_by_default INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*User::defaultSubsonicEnableTranscodingByDefault*/ 0)) + ")");
}
void migrateFromV46(Session& session)
@@ -331,7 +334,8 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
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");
@@ -480,34 +484,33 @@ SELECT
using MigrationFunction = std::function<void(Session&)>;
const std::map<unsigned, MigrationFunction> 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<unsigned, MigrationFunction> 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 },
};
bool migrationPerformed{};
@@ -551,4 +554,4 @@ SELECT
return migrationPerformed;
}
}
} // namespace lms::db::Migration
+1 -1
View File
@@ -54,4 +54,4 @@ namespace lms::db
{
bool doDbMigration(Session& session); // return true if migration was performed
}
}
} // namespace lms::db
+3 -3
View File
@@ -19,8 +19,9 @@
#pragma once
#include <string>
#include <filesystem>
#include <string>
#include <Wt/Dbo/SqlTraits.h>
namespace Wt::Dbo
@@ -50,5 +51,4 @@ namespace Wt::Dbo
return true;
}
};
}
} // namespace Wt::Dbo
+72 -96
View File
@@ -21,15 +21,16 @@
#include <Wt/Dbo/WtSqlTraits.h>
#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,7 +38,7 @@ namespace lms::db
{
namespace
{
template <typename ResultType>
template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Release::FindParameters& params)
{
auto query{ session.getDboSession()->query<ResultType>("SELECT " + std::string{ itemToSelect } + " from release r") };
@@ -63,7 +64,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 +84,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 +124,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 +148,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)
@@ -200,69 +206,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<ReleaseType> {new ReleaseType{ name }});
return session.getDboSession()->add(std::unique_ptr<ReleaseType>{ new ReleaseType{ name } });
}
ReleaseType::pointer ReleaseType::find(Session& session, ReleaseTypeId id)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<ReleaseType>()
.where("id = ?").bind(id));
return utils::fetchQuerySingleResult(session.getDboSession()->find<ReleaseType>().where("id = ?").bind(id));
}
ReleaseType::pointer ReleaseType::find(Session& session, std::string_view name)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<ReleaseType>()
.where("name = ?").bind(name));
return utils::fetchQuerySingleResult(session.getDboSession()->find<ReleaseType>().where("name = ?").bind(name));
}
Release::Release(const std::string& name, const std::optional<core::UUID>& 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<core::UUID>& MBID)
{
return session.getDboSession()->add(std::unique_ptr<Release> {new Release{ name, MBID }});
return session.getDboSession()->add(std::unique_ptr<Release>{ new Release{ name, MBID } });
}
std::vector<Release::pointer> Release::find(Session& session, const std::string& name, const std::filesystem::path& releaseDirectory)
{
session.checkReadTransaction();
return utils::fetchQueryResults<Release::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Release>>("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<Release::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Release>>("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<Release>()
.where("mbid = ?").bind(mbid.getAsString()));
return utils::fetchQuerySingleResult(session.getDboSession()->find<Release>().where("mbid = ?").bind(mbid.getAsString()));
}
Release::pointer Release::find(Session& session, ReleaseId id)
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<Release>()
.where("id = ?").bind(id));
return utils::fetchQuerySingleResult(session.getDboSession()->find<Release>().where("id = ?").bind(id));
}
bool Release::exists(Session& session, ReleaseId id)
@@ -290,10 +289,7 @@ namespace lms::db
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Release>>("SELECT r FROM release r")
.orderBy("r.id")
.where("r.id > ?").bind(lastRetrievedRelease)
.limit(static_cast<int>(count)) };
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Release>>("SELECT r FROM release r").orderBy("r.id").where("r.id > ?").bind(lastRetrievedRelease).limit(static_cast<int>(count)) };
if (library.isValid())
{
@@ -301,11 +297,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::pointer> Release::find(Session& session, const FindParameters& params)
@@ -342,8 +337,7 @@ namespace lms::db
std::size_t Release::getDiscCount() const
{
assert(session());
int res{ utils::fetchQuerySingleResult(session()->query<int>("SELECT COUNT(DISTINCT disc_number) FROM track t")
.where("t.release_id = ?").bind(getId()))};
int res{ utils::fetchQuerySingleResult(session()->query<int>("SELECT COUNT(DISTINCT disc_number) FROM track t").where("t.release_id = ?").bind(getId())) };
return res;
}
@@ -352,15 +346,12 @@ namespace lms::db
assert(session());
using ResultType = std::tuple<int, std::string>;
const auto query{ session()->query<ResultType>("SELECT DISTINCT disc_number, disc_subtitle FROM track t")
.where("t.release_id = ?").bind(getId())
.orderBy("disc_number")};
const auto query{ session()->query<ResultType>("SELECT DISTINCT disc_number, disc_subtitle FROM track t").where("t.release_id = ?").bind(getId()).orderBy("disc_number") };
std::vector<DiscInfo> discs;
utils::forEachQueryResult(query, [&](ResultType&& res)
{
discs.emplace_back(DiscInfo{ static_cast<std::size_t>(std::get<int>(res)), std::move(std::get<std::string>(res)) });
});
utils::forEachQueryResult(query, [&](ResultType&& res) {
discs.emplace_back(DiscInfo{ static_cast<std::size_t>(std::get<int>(res)), std::move(std::get<std::string>(res)) });
});
return discs;
}
@@ -380,10 +371,7 @@ namespace lms::db
assert(session());
const char* field{ original ? "original_date" : "date" };
auto query{ (session()->query<Wt::WDate>(std::string {"SELECT "} + "t." + field + " FROM track t")
.where("t.release_id = ?")
.groupBy(field)
.bind(getId())) };
auto query{ (session()->query<Wt::WDate>(std::string{ "SELECT " } + "t." + field + " FROM track t").where("t.release_id = ?").groupBy(field).bind(getId())) };
const auto dates{ utils::fetchQueryResults(query) };
@@ -409,9 +397,7 @@ namespace lms::db
assert(session());
const char* field{ original ? "original_year" : "year" };
auto query{ session()->query<std::optional<int>>(std::string {"SELECT "} + "t." + field + " FROM track t")
.where("t.release_id = ?").bind(getId())
.groupBy(field) };
auto query{ session()->query<std::optional<int>>(std::string{ "SELECT " } + "t." + field + " FROM track t").where("t.release_id = ?").bind(getId()).groupBy(field) };
const auto years{ utils::fetchQueryResults(query) };
@@ -427,10 +413,7 @@ namespace lms::db
{
assert(session());
auto query{ session()->query<std::string>("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<std::string>("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 +428,7 @@ namespace lms::db
{
assert(session());
const auto query{ session()->query<std::string>
("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<std::string>("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 +443,7 @@ namespace lms::db
{
assert(session());
return utils::fetchQuerySingleResult(session()->query<int>("SELECT COALESCE(AVG(t.bitrate), 0) FROM track t")
.where("release_id = ?").bind(getId())
.where("bitrate > 0"));
return utils::fetchQuerySingleResult(session()->query<int>("SELECT COALESCE(AVG(t.bitrate), 0) FROM track t").where("release_id = ?").bind(getId()).where("bitrate > 0"));
}
std::vector<Artist::pointer> Release::getArtists(TrackArtistLinkType linkType) const
@@ -473,12 +451,14 @@ namespace lms::db
assert(session());
const auto query{ session()->query<Wt::Dbo::ptr<Artist>>(
"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<Artist::pointer>(query);
}
@@ -489,24 +469,23 @@ namespace lms::db
// Select the similar releases using the 5 most used clusters of the release
auto query{ session()->query<Wt::Dbo::ptr<Release>>(
"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<int>(*count) : -1)
.offset(offset ? static_cast<int>(*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<int>(*count) : -1)
.offset(offset ? static_cast<int>(*offset) : -1) };
return utils::fetchQueryResults<Release::pointer>(query);
}
@@ -530,8 +509,7 @@ namespace lms::db
std::size_t Release::getTrackCount() const
{
assert(session());
return utils::fetchQuerySingleResult(session()->query<int>("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<int>("SELECT COUNT(t.id) FROM track t INNER JOIN release r ON r.id = t.release_id").where("r.id = ?").bind(getId()));
}
std::vector<ReleaseType::pointer> Release::getReleaseTypes() const
@@ -556,16 +534,14 @@ namespace lms::db
using milli = std::chrono::duration<int, std::milli>;
return utils::fetchQuerySingleResult(session()->query<milli>("SELECT COALESCE(SUM(duration), 0) FROM track t")
.where("t.release_id = ?").bind(getId()));
return utils::fetchQuerySingleResult(session()->query<milli>("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<Wt::WDateTime>("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<Wt::WDateTime>("SELECT COALESCE(MAX(file_last_write), '1970-01-01T00:00:00') FROM track t").where("t.release_id = ?").bind(getId()));
}
std::vector<std::vector<Cluster::pointer>> Release::getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const
+3 -2
View File
@@ -21,9 +21,10 @@
#include <Wt/Dbo/WtSqlTraits.h>
#include "core/String.hpp"
#include "database/MediaLibrary.hpp"
#include "database/Session.hpp"
#include "core/String.hpp"
#include "Utils.hpp"
namespace lms::db
@@ -71,7 +72,7 @@ namespace lms::db
return core::stringUtils::splitEscapedStrings(_defaultTagDelimiters, ';', '\\');
}
void ScanSettings::setExtraTagsToScan(const std::vector<std::string_view>& extraTags)
void ScanSettings::setExtraTagsToScan(std::span<const std::string_view> extraTags)
{
std::string newTagsToScan{ core::stringUtils::joinStrings(extraTags, ";") };
if (newTagsToScan != _extraTagsToScan)
+11 -8
View File
@@ -19,12 +19,9 @@
#include "database/Session.hpp"
#include <cassert>
#include "core/Exception.hpp"
#include "core/ILogger.hpp"
#include "core/ITraceLogger.hpp"
#include "database/Artist.hpp"
#include "database/AuthToken.hpp"
#include "database/Cluster.hpp"
@@ -37,22 +34,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());
@@ -121,6 +119,11 @@ namespace lms::db
return ReadTransaction{ _session };
}
void Session::execute(std::string_view statement)
{
_session.execute(std::string{ statement });
}
void Session::prepareTablesIfNeeded()
{
LMS_LOG(DB, INFO, "Preparing tables...");
+8 -5
View File
@@ -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();
}
}
} // namespace lms::db
+23 -15
View File
@@ -19,8 +19,8 @@
#pragma once
#include <vector>
#include <string>
#include <vector>
namespace lms::db
{
@@ -40,8 +40,8 @@ namespace lms::db
const std::vector<std::string>& getBindArgs() const { return _bindArgs; }
private:
std::string _clause; // WHERE clause
std::vector<std::string> _bindArgs;
std::string _clause; // WHERE clause
std::vector<std::string> _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<std::string> _statement;
std::vector<std::string> _statement;
};
class FromClause
@@ -96,16 +96,24 @@ namespace lms::db
std::string get() const;
private:
std::vector<std::string> _clause;
std::vector<std::string> _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
};
}
} // namespace lms::db
+5 -11
View File
@@ -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> artist, ObjectPtr<User> user, FeedbackBackend backend)
{
return session.getDboSession()->add(std::unique_ptr<StarredArtist> {new StarredArtist{ artist, user, backend }});
return session.getDboSession()->add(std::unique_ptr<StarredArtist>{ 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<Wt::Dbo::ptr<StarredArtist>>("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<Wt::Dbo::ptr<StarredArtist>>("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<StarredArtist>()
.where("artist_id = ?").bind(artistId)
.where("user_id = ?").bind(userId)
.where("backend = ?").bind(backend));
return utils::fetchQuerySingleResult(session.getDboSession()->find<StarredArtist>().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
+5 -11
View File
@@ -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> release, ObjectPtr<User> user, FeedbackBackend backend)
{
return session.getDboSession()->add(std::unique_ptr<StarredRelease>{new StarredRelease{ release, user, backend }});
return session.getDboSession()->add(std::unique_ptr<StarredRelease>{ 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<Wt::Dbo::ptr<StarredRelease>>("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<Wt::Dbo::ptr<StarredRelease>>("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<StarredRelease>()
.where("release_id = ?").bind(releaseId)
.where("user_id = ?").bind(userId)
.where("backend = ?").bind(backend));
return utils::fetchQuerySingleResult(session.getDboSession()->find<StarredRelease>().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
+7 -16
View File
@@ -21,9 +21,10 @@
#include <Wt/Dbo/WtSqlTraits.h>
#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> track, ObjectPtr<User> user, FeedbackBackend backend)
{
return session.getDboSession()->add(std::unique_ptr<StarredTrack> {new StarredTrack{ track, user, backend }});
return session.getDboSession()->add(std::unique_ptr<StarredTrack>{ 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<Wt::Dbo::ptr<StarredTrack>>("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<Wt::Dbo::ptr<StarredTrack>>("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<StarredTrack>()
.where("track_id = ?").bind(trackId)
.where("user_id = ?").bind(userId)
.where("backend = ?").bind(backend));
return utils::fetchQuerySingleResult(session.getDboSession()->find<StarredTrack>().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<int>("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<int>("SELECT 1 from starred_track").where("track_id = ?").bind(trackId).where("user_id = ?").bind(userId).where("backend = ?").bind(backend));
}
RangeResults<StarredTrackId> StarredTrack::find(Session& session, const FindParameters& params)
@@ -100,4 +91,4 @@ namespace lms::db
{
_dateTime = utils::normalizeDateTime(dateTime);
}
}
} // namespace lms::db
+2 -2
View File
@@ -20,6 +20,7 @@
#pragma once
#include <string_view>
#include <Wt/Dbo/SqlTraits.h>
namespace Wt::Dbo
@@ -32,5 +33,4 @@ namespace Wt::Dbo
statement->bind(column, std::string{ str });
}
};
}
} // namespace Wt::Dbo
+43 -53
View File
@@ -21,15 +21,15 @@
#include <Wt/Dbo/WtSqlTraits.h>
#include "core/ILogger.hpp"
#include "database/Artist.hpp"
#include "database/Cluster.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 +41,7 @@ namespace lms::db
{
namespace
{
template <typename ResultType>
template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Track::FindParameters& params)
{
session.checkReadTransaction();
@@ -62,22 +62,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 +187,7 @@ namespace lms::db
return query;
}
template <typename ResultType>
template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, const Track::FindParameters& params)
{
std::string_view itemToSelect;
@@ -197,7 +201,7 @@ namespace lms::db
return createQuery<ResultType>(session, itemToSelect, params);
}
}
} // namespace
Track::pointer Track::create(Session& session)
{
@@ -222,27 +226,22 @@ namespace lms::db
{
session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<Track>()
.where("id = ?").bind(id));
return utils::fetchQuerySingleResult(session.getDboSession()->find<Track>().where("id = ?").bind(id));
}
void Track::find(Session& session, TrackId& lastRetrievedTrack, std::size_t count, const std::function<void(const Track::pointer&)>& func, MediaLibraryId library)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->find<Track>()
.orderBy("id")
.where("id > ?").bind(lastRetrievedTrack)
.limit(static_cast<int>(count)) };
auto query{ session.getDboSession()->find<Track>().orderBy("id").where("id > ?").bind(lastRetrievedTrack).limit(static_cast<int>(count)) };
if (library.isValid())
query.where("media_library_id = ?").bind(library);
utils::forEachQueryResult(query, [&](const Track::pointer& track)
{
func(track);
lastRetrievedTrack = track->getId();
});
utils::forEachQueryResult(query, [&](const Track::pointer& track) {
func(track);
lastRetrievedTrack = track->getId();
});
}
bool Track::exists(Session& session, TrackId id)
@@ -256,24 +255,21 @@ namespace lms::db
{
session.checkReadTransaction();
return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->find<Track>()
.where("mbid = ?").bind(mbid.getAsString()));
return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->find<Track>().where("mbid = ?").bind(mbid.getAsString()));
}
std::vector<Track::pointer> Track::findByRecordingMBID(Session& session, const core::UUID& mbid)
{
session.checkReadTransaction();
return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->find<Track>()
.where("recording_mbid = ?").bind(mbid.getAsString()));
return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->find<Track>().where("recording_mbid = ?").bind(mbid.getAsString()));
}
RangeResults<TrackId> Track::findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range)
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<TrackId>("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<TrackId>("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<TrackId>(query, range);
}
@@ -282,9 +278,7 @@ namespace lms::db
{
session.checkReadTransaction();
auto query{ session.getDboSession()->query<TrackId>("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<TrackId>("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<TrackId>(query, range);
}
@@ -298,10 +292,7 @@ namespace lms::db
{
assert(session());
const auto query{ session()->query<ClusterId>
("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<ClusterId>("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 +343,14 @@ namespace lms::db
}
auto query{ session.getDboSession()->query<TrackId>(
"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 +405,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())
{
@@ -447,9 +439,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())
{
@@ -507,11 +498,10 @@ namespace lms::db
query.bind(bindArg);
std::map<ClusterTypeId, std::vector<Cluster::pointer>> 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<std::vector<Cluster::pointer>> res;
for (const auto& [type, clusters] : clusters)
@@ -545,6 +535,6 @@ namespace lms::db
return os;
}
}
} // namespace Debug
} // namespace lms::db

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