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"> <div class="col-12">
<label class="form-label" for="${id:extra-tags-to-scan}"> <label class="form-label" for="${id:extra-tags-to-scan}">
${tr:Lms.Admin.Database.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> </label>
${extra-tags-to-scan class="form-control"} ${extra-tags-to-scan-container class="row gy-3"}
<div class="invalid-feedback">
${extra-tags-to-scan-info}
</div>
</div> </div>
<div class="col-12"> <div class="col-12">
<label class="form-label" for="${id:artist-tag-delimiter}"> <label class="form-label">
${tr:Lms.Admin.Database.artist-tag-delimiter} ${tr:Lms.Admin.Database.artist-tag-delimiters}
${artist-tag-delimiter-add-btn class="ms-1 btn btn-sm btn-secondary"}
</label> </label>
${artist-tag-delimiter class="form-control"} ${artist-tag-delimiter-container class="row gy-3"}
<div class="invalid-feedback">
${artist-tag-delimiter-info}
</div>
</div> </div>
<div class="col-12"> <div class="col-12">
<label class="form-label" for="${id:default-tag-delimiter}"> <label class="form-label">
${tr:Lms.Admin.Database.default-tag-delimiter} ${tr:Lms.Admin.Database.default-tag-delimiters}
${default-tag-delimiter-add-btn class="ms-1 btn btn-sm btn-secondary"}
</label> </label>
${default-tag-delimiter class="form-control"} ${default-tag-delimiter-container class="row gy-3"}
<div class="invalid-feedback">
${default-tag-delimiter-info}
</div>
</div> </div>
<div class="col-12"> <div class="col-12">
${save-btn class="btn btn-primary me-1"}${discard-btn class="btn btn-secondary"} ${save-btn class="btn btn-primary me-1"}${discard-btn class="btn btn-secondary"}
</div> </div>
</form> </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> </message>
</messages> </messages>
+3 -3
View File
@@ -65,10 +65,10 @@
<message id="Lms.Admin.MediaLibrary.root-path">Root directory</message> <message id="Lms.Admin.MediaLibrary.root-path">Root directory</message>
<!--Scan settings--> <!--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.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.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 (use <kbd>;</kbd> as separator)</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.hourly">Hourly</message>
<message id="Lms.Admin.Database.immediate-scan">Scan now!</message> <message id="Lms.Admin.Database.immediate-scan">Scan now!</message>
<message id="Lms.Admin.Database.monthly">Monthly</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> <message id="Lms.Admin.MediaLibrary.root-path">Répertoire racine</message>
<!--Scan settings--> <!--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.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.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 (utiliser <kbd>;</kbd> comme séparateur)</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.hourly">Toutes les heures</message>
<message id="Lms.Admin.Database.immediate-scan">Scanner maintenant !</message> <message id="Lms.Admin.Database.immediate-scan">Scanner maintenant !</message>
<message id="Lms.Admin.Database.monthly">Tous les mois</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> <message id="Lms.Admin.MediaLibrary.root-path">Cartella principale</message>
<!--Scan settings--> <!--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.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.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 (usa <kbd>;</kbd> come separatore)</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.hourly">Ogni ora</message>
<message id="Lms.Admin.Database.immediate-scan">Scansiona ora!</message> <message id="Lms.Admin.Database.immediate-scan">Scansiona ora!</message>
<message id="Lms.Admin.Database.monthly">Mensile</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: public:
AudioFileException(int avError) AudioFileException(int avError)
: Exception{ "AudioFileException: " + averror_to_string(avError) } : Exception{ "AudioFileException: " + averror_to_string(avError) }
{} {
}
}; };
void getMetaDataFromDictionnary(AVDictionary* dictionnary, AudioFile::MetadataMap& res) void getMetaDataFromDictionnary(AVDictionary* dictionnary, AudioFile::MetadataMap& res)
@@ -72,27 +73,52 @@ namespace lms::av
{ {
switch (codec) switch (codec)
{ {
case AV_CODEC_ID_MP3: return DecodingCodec::MP3; case AV_CODEC_ID_MP3:
case AV_CODEC_ID_AAC: return DecodingCodec::AAC; return DecodingCodec::MP3;
case AV_CODEC_ID_AC3: return DecodingCodec::AC3; case AV_CODEC_ID_AAC:
case AV_CODEC_ID_VORBIS: return DecodingCodec::VORBIS; return DecodingCodec::AAC;
case AV_CODEC_ID_WMAV1: return DecodingCodec::WMAV1; case AV_CODEC_ID_AC3:
case AV_CODEC_ID_WMAV2: return DecodingCodec::WMAV2; return DecodingCodec::AC3;
case AV_CODEC_ID_FLAC: return DecodingCodec::FLAC; case AV_CODEC_ID_VORBIS:
case AV_CODEC_ID_ALAC: return DecodingCodec::ALAC; return DecodingCodec::VORBIS;
case AV_CODEC_ID_WAVPACK: return DecodingCodec::WAVPACK; case AV_CODEC_ID_WMAV1:
case AV_CODEC_ID_MUSEPACK7: return DecodingCodec::MUSEPACK7; return DecodingCodec::WMAV1;
case AV_CODEC_ID_MUSEPACK8: return DecodingCodec::MUSEPACK8; case AV_CODEC_ID_WMAV2:
case AV_CODEC_ID_APE: return DecodingCodec::APE; return DecodingCodec::WMAV2;
case AV_CODEC_ID_EAC3: return DecodingCodec::EAC3; case AV_CODEC_ID_FLAC:
case AV_CODEC_ID_MP4ALS: return DecodingCodec::MP4ALS; return DecodingCodec::FLAC;
case AV_CODEC_ID_OPUS: return DecodingCodec::OPUS; case AV_CODEC_ID_ALAC:
case AV_CODEC_ID_SHORTEN: return DecodingCodec::SHORTEN; 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: default:
return DecodingCodec::UNKNOWN; return DecodingCodec::UNKNOWN;
} }
} }
} } // namespace
std::unique_ptr<IAudioFile> parseAudioFile(const std::filesystem::path& p) std::unique_ptr<IAudioFile> parseAudioFile(const std::filesystem::path& p)
{ {
@@ -178,8 +204,8 @@ namespace lms::av
{ {
int res = ::av_find_best_stream(_context, int res = ::av_find_best_stream(_context,
AVMEDIA_TYPE_AUDIO, AVMEDIA_TYPE_AUDIO,
-1, // Auto -1, // Auto
-1, // Auto -1, // Auto
NULL, NULL,
0); 0);
@@ -213,8 +239,7 @@ namespace lms::av
void AudioFile::visitAttachedPictures(std::function<void(const Picture&)> func) const 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_BMP, "image/x-bmp" },
{ AV_CODEC_ID_GIF, "image/gif" }, { AV_CODEC_ID_GIF, "image/gif" },
{ AV_CODEC_ID_MJPEG, "image/jpeg" }, { 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 // List should be sync with the demuxers shipped in the lms's docker version
// + the _audioFileExtensions in ScanSettings // + the _audioFileExtensions in ScanSettings
// std::filesystem::path does not seem to have std::hash specialization on freebsd // std::filesystem::path does not seem to have std::hash specialization on freebsd
static const std::unordered_map<std::string, std::string_view> entries static const std::unordered_map<std::string, std::string_view> entries{
{ { ".mp3", "audio/mpeg" },
{".mp3", "audio/mpeg"}, { ".ogg", "audio/ogg" },
{".ogg", "audio/ogg"}, { ".oga", "audio/ogg" },
{".oga", "audio/ogg"}, { ".opus", "audio/opus" },
{".opus", "audio/opus"}, { ".aac", "audio/aac" },
{".aac", "audio/aac"}, { ".alac", "audio/mp4" },
{".alac", "audio/mp4"}, { ".m4a", "audio/mp4" },
{".m4a", "audio/mp4"}, { ".m4b", "audio/mp4" },
{".m4b", "audio/mp4"}, { ".flac", "audio/flac" },
{".flac", "audio/flac"}, { ".webm", "audio/webm" },
{".webm", "audio/webm"}, { ".wav", "audio/x-wav" },
{".wav", "audio/x-wav"}, { ".wma", "audio/x-ms-wma" },
{".wma", "audio/x-ms-wma"}, { ".ape", "audio/x-monkeys-audio" },
{".ape", "audio/x-monkeys-audio"}, { ".mpc", "audio/x-musepack" },
{".mpc", "audio/x-musepack"}, { ".shn", "audio/x-shn" },
{".shn", "audio/x-shn"}, { ".aif", "audio/x-aiff" },
{".aif", "audio/x-aiff"}, { ".aiff", "audio/x-aiff" },
{".aiff", "audio/x-aiff"}, { ".m3u", "audio/x-mpegurl" },
{".m3u", "audio/x-mpegurl"}, { ".pls", "audio/x-scpls" },
{".pls", "audio/x-scpls"}, { ".dsf", "audio/x-dsd" },
{".dsf", "audio/dsd"}, { ".wv", "audio/x-wavpack" },
{".wv", "audio/x-wavpack"}, { ".wvp", "audio/x-wavpack" },
{".wvp", "audio/x-wavpack"}, { ".mka", "audio/x-matroska" },
{".mka", "audio/x-matroska"},
}; };
auto it{ entries.find(core::stringUtils::stringToLower(fileExtension.string())) }; 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/>. * 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 #pragma once
#include "av/IAudioFile.hpp" #include "av/IAudioFile.hpp"
@@ -35,23 +33,22 @@ namespace lms::av
~AudioFile(); ~AudioFile();
const std::filesystem::path& getPath() const override; const std::filesystem::path& getPath() const override;
ContainerInfo getContainerInfo() const override; ContainerInfo getContainerInfo() const override;
MetadataMap getMetaData() const override; MetadataMap getMetaData() const override;
std::vector<StreamInfo> getStreamInfo() const override; std::vector<StreamInfo> getStreamInfo() const override;
std::optional<StreamInfo> getBestStreamInfo() const override; std::optional<StreamInfo> getBestStreamInfo() const override;
std::optional<std::size_t> getBestStreamIndex() const override; std::optional<std::size_t> getBestStreamIndex() const override;
bool hasAttachedPictures() const override; bool hasAttachedPictures() const override;
void visitAttachedPictures(std::function<void(const Picture&)> func) const override; void visitAttachedPictures(std::function<void(const Picture&)> func) const override;
private: private:
AudioFile(const AudioFile&) = delete; AudioFile(const AudioFile&) = delete;
AudioFile& operator=(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{}; AVFormatContext* _context{};
}; };
} // namespace lms::av } // namespace lms::av
@@ -29,4 +29,4 @@ namespace lms::av
std::string_view mimeType{ getMimeType(path.extension()) }; std::string_view mimeType{ getMimeType(path.extension()) };
return createFileResourceHandler(path, mimeType.empty() ? "application/octet-stream" : mimeType); 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/IChildProcessManager.hpp"
#include "core/IConfig.hpp" #include "core/IConfig.hpp"
#include "core/Path.hpp"
#include "core/ILogger.hpp" #include "core/ILogger.hpp"
#include "core/Path.hpp"
#include "core/Service.hpp" #include "core/Service.hpp"
namespace lms::av::transcoding 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::atomic<size_t> globalId{};
static std::filesystem::path ffmpegPath; static std::filesystem::path ffmpegPath;
std::string_view formatToMimetype(OutputFormat format) std::string_view formatToMimetype(OutputFormat format)
{ {
switch (format) switch (format)
{ {
case OutputFormat::MP3: return "audio/mpeg"; case OutputFormat::MP3:
case OutputFormat::OGG_OPUS: return "audio/opus"; return "audio/mpeg";
case OutputFormat::MATROSKA_OPUS: return "audio/x-matroska"; case OutputFormat::OGG_OPUS:
case OutputFormat::OGG_VORBIS: return "audio/ogg"; return "audio/opus";
case OutputFormat::WEBM_VORBIS: return "audio/webm"; 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" }; throw Exception{ "Invalid encoding" };
@@ -195,10 +200,9 @@ namespace lms::av::transcoding
{ {
assert(_childProcess); assert(_childProcess);
return _childProcess->asyncRead(buffer, bufferSize, [readCallback{ std::move(readCallback) }](core::IChildProcess::ReadResult /*res*/, std::size_t nbBytesRead) return _childProcess->asyncRead(buffer, bufferSize, [readCallback{ std::move(readCallback) }](core::IChildProcess::ReadResult /*res*/, std::size_t nbBytesRead) {
{ readCallback(nbBytesRead);
readCallback(nbBytesRead); });
});
} }
std::size_t Transcoder::readSome(std::byte* buffer, std::size_t bufferSize) std::size_t Transcoder::readSome(std::byte* buffer, std::size_t bufferSize)
@@ -215,4 +219,4 @@ namespace lms::av::transcoding
return _childProcess->finished(); 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 // non blocking calls
using ReadCallback = std::function<void(std::size_t nbReadBytes)>; using ReadCallback = std::function<void(std::size_t nbReadBytes)>;
void asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback); void asyncRead(std::byte* buffer, std::size_t bufferSize, ReadCallback);
std::size_t readSome(std::byte* buffer, std::size_t bufferSize); std::size_t readSome(std::byte* buffer, std::size_t bufferSize);
const std::string& getOutputMimeType() const { return _outputMimeType; } const std::string& getOutputMimeType() const { return _outputMimeType; }
const OutputParameters& getOutputParameters() const { return _outputParameters; } const OutputParameters& getOutputParameters() const { return _outputParameters; }
bool finished() const; bool finished() const;
private: private:
static void init(); static void init();
void start(); void start();
const std::size_t _debugId{}; const std::size_t _debugId{};
const InputParameters _inputParameters; const InputParameters _inputParameters;
const OutputParameters _outputParameters; const OutputParameters _outputParameters;
std::string _outputMimeType; 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 }; 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; return estimatedContentLength;
} }
} } // namespace
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)
{ {
@@ -68,14 +68,13 @@ namespace lms::av::transcoding
{ {
Wt::Http::ResponseContinuation* continuation{ response.createContinuation() }; Wt::Http::ResponseContinuation* continuation{ response.createContinuation() };
continuation->waitForMoreData(); continuation->waitForMoreData();
_transcoder.asyncRead(_buffer.data(), _buffer.size(), [this, continuation](std::size_t nbBytesRead) _transcoder.asyncRead(_buffer.data(), _buffer.size(), [this, continuation](std::size_t nbBytesRead) {
{ LMS_LOG(TRANSCODING, DEBUG, "Have " << nbBytesRead << " more bytes to send back");
LMS_LOG(TRANSCODING, DEBUG, "Have " << nbBytesRead << " more bytes to send back");
assert(_bytesReadyCount == 0); assert(_bytesReadyCount == 0);
_bytesReadyCount = nbBytesRead; _bytesReadyCount = nbBytesRead;
continuation->haveMoreData(); continuation->haveMoreData();
}); });
return continuation; return continuation;
} }
@@ -99,5 +98,4 @@ namespace lms::av::transcoding
return {}; return {};
} }
} } // namespace lms::av::transcoding
@@ -25,6 +25,7 @@
#include "av/TranscodingParameters.hpp" #include "av/TranscodingParameters.hpp"
#include "core/IResourceHandler.hpp" #include "core/IResourceHandler.hpp"
#include "Transcoder.hpp" #include "Transcoder.hpp"
namespace lms::av::transcoding namespace lms::av::transcoding
@@ -36,7 +37,7 @@ namespace lms::av::transcoding
private: private:
Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& reponse) override; 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 }; static constexpr std::size_t _chunkSize{ 262'144 };
std::optional<std::size_t> _estimatedContentLength; std::optional<std::size_t> _estimatedContentLength;
@@ -45,5 +46,4 @@ namespace lms::av::transcoding
std::size_t _totalServedByteCount{}; std::size_t _totalServedByteCount{};
Transcoder _transcoder; 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/>. * 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 #pragma once
#include <chrono> #include <chrono>
#include <filesystem> #include <filesystem>
#include <functional> #include <functional>
#include <unordered_map>
#include <optional> #include <optional>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <unordered_map>
#include <vector> #include <vector>
#include "Types.hpp" #include "Types.hpp"
@@ -44,24 +42,28 @@ namespace lms::av
VORBIS, VORBIS,
WMAV1, WMAV1,
WMAV2, WMAV2,
FLAC, // Flac FLAC, // Flac
ALAC, // Apple Lossless Audio Codec (ALAC) ALAC, // Apple Lossless Audio Codec (ALAC)
WAVPACK, // WavPack WAVPACK, // WavPack
MUSEPACK7, // Musepack MUSEPACK7, // Musepack
MUSEPACK8, MUSEPACK8,
APE, // // Monkey's Audio APE, // Monkey's Audio
EAC3, // Enhanced AC-3 EAC3, // Enhanced AC-3
MP4ALS, // MPEG-4 Audio Lossless Coding MP4ALS, // MPEG-4 Audio Lossless Coding
OPUS, // Opus OPUS, // Opus
SHORTEN, // Shorten (shn) 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 // TODO add PCM codecs
}; };
struct Picture struct Picture
{ {
std::string mimeType; std::string mimeType;
const std::byte* data{}; const std::byte* data{};
std::size_t dataSize{}; std::size_t dataSize{};
}; };
struct ContainerInfo struct ContainerInfo
@@ -73,13 +75,13 @@ namespace lms::av
struct StreamInfo struct StreamInfo
{ {
size_t index{}; size_t index{};
std::size_t bitrate{}; std::size_t bitrate{};
std::size_t bitsPerSample{}; std::size_t bitsPerSample{};
std::size_t channelCount{}; std::size_t channelCount{};
std::size_t sampleRate{}; std::size_t sampleRate{};
DecodingCodec codec; DecodingCodec codec;
std::string codecName; std::string codecName;
}; };
class IAudioFile class IAudioFile
@@ -91,13 +93,13 @@ namespace lms::av
using MetadataMap = std::unordered_map<std::string, std::string>; using MetadataMap = std::unordered_map<std::string, std::string>;
virtual const std::filesystem::path& getPath() const = 0; virtual const std::filesystem::path& getPath() const = 0;
virtual ContainerInfo getContainerInfo() const = 0; virtual ContainerInfo getContainerInfo() const = 0;
virtual MetadataMap getMetaData() const = 0; virtual MetadataMap getMetaData() const = 0;
virtual std::vector<StreamInfo> getStreamInfo() const = 0; virtual std::vector<StreamInfo> getStreamInfo() const = 0;
virtual std::optional<StreamInfo> getBestStreamInfo() const = 0; // none if failure/unknown 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 std::optional<std::size_t> getBestStreamIndex() const = 0; // none if failure/unknown
virtual bool hasAttachedPictures() const = 0; virtual bool hasAttachedPictures() const = 0;
virtual void visitAttachedPictures(std::function<void(const Picture&)> func) const = 0; virtual void visitAttachedPictures(std::function<void(const Picture&)> func) const = 0;
}; };
std::unique_ptr<IAudioFile> parseAudioFile(const std::filesystem::path& p); 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); std::string_view getMimeType(const std::filesystem::path& fileExtension);
} // namespace lms::av } // namespace lms::av
@@ -46,11 +46,10 @@ namespace lms::av::transcoding
struct OutputParameters struct OutputParameters
{ {
OutputFormat format; OutputFormat format;
std::size_t bitrate{ 128000 }; std::size_t bitrate{ 128000 };
std::optional<std::size_t> stream; // Id of the stream to be transcoded (auto detect by default) std::optional<std::size_t> stream; // Id of the stream to be transcoded (auto detect by default)
std::chrono::milliseconds offset{ 0 }; std::chrono::milliseconds offset{ 0 };
bool stripMetadata{ true }; bool stripMetadata{ true };
}; };
} // namespace lms::av::Transcoding } // namespace lms::av::transcoding
@@ -25,8 +25,8 @@
namespace lms::av::transcoding namespace lms::av::transcoding
{ {
struct InputParameters; struct InputParameters;
struct OutputParameters; 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: public:
using LmsException::LmsException; using LmsException::LmsException;
}; };
} } // namespace lms::av
+3 -3
View File
@@ -19,6 +19,7 @@
#include <iostream> #include <iostream>
#include <thread> #include <thread>
#include <benchmark/benchmark.h> #include <benchmark/benchmark.h>
#include "core/ILogger.hpp" #include "core/ILogger.hpp"
@@ -58,8 +59,7 @@ namespace lms::core
static void BM_TraceLogger_Detailed_withArg(benchmark::State& state) 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 }); std::this_thread::sleep_for(std::chrono::microseconds{ 1 });
return "foo"; return "foo";
} }; } };
@@ -76,6 +76,6 @@ namespace lms::core
BENCHMARK(BM_TraceLogger_Detailed)->Threads(1)->Threads(std::thread::hardware_concurrency()); BENCHMARK(BM_TraceLogger_Detailed)->Threads(1)->Threads(std::thread::hardware_concurrency());
BENCHMARK(BM_TraceLogger_Detailed_withArg)->Threads(1)->Threads(std::thread::hardware_concurrency()); BENCHMARK(BM_TraceLogger_Detailed_withArg)->Threads(1)->Threads(std::thread::hardware_concurrency());
} } // namespace lms::core
BENCHMARK_MAIN(); BENCHMARK_MAIN();
+15 -15
View File
@@ -23,6 +23,7 @@
#include <cassert> #include <cassert>
#include <cstring> // strerror #include <cstring> // strerror
#include <fstream> #include <fstream>
#include <archive.h> #include <archive.h>
#include <archive_entry.h> #include <archive_entry.h>
@@ -39,12 +40,14 @@ namespace lms::zip
{ {
public: public:
FileException(const std::filesystem::path& p, std::string_view message) 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) 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 class ArchiveException : public Exception
@@ -52,7 +55,8 @@ namespace lms::zip
public: public:
ArchiveException(struct ::archive* arch) ArchiveException(struct ::archive* arch)
: Exception{ getError(arch) } : Exception{ getError(arch) }
{} {
}
static std::string_view getError(struct ::archive* arch) static std::string_view getError(struct ::archive* arch)
{ {
@@ -88,19 +92,16 @@ namespace lms::zip
if (!_archive) if (!_archive)
throw Exception{ "Cannot create archive control struct" }; throw Exception{ "Cannot create archive control struct" };
auto archiveOpen{ [](struct ::archive*, void*) auto archiveOpen{ [](struct ::archive*, void*) {
{
return ARCHIVE_OK; return ARCHIVE_OK;
} }; } };
auto archiveWrite{ [](struct ::archive*, void* clientData, const void* buff, ::size_t n) -> la_ssize_t auto archiveWrite{ [](struct ::archive*, void* clientData, const void* buff, ::size_t n) -> la_ssize_t {
{ ArchiveZipper* zipper{ static_cast<ArchiveZipper*>(clientData) };
ArchiveZipper* zipper {static_cast<ArchiveZipper*>(clientData)};
return zipper->onWriteCallback(static_cast<const std::byte*>(buff), n); return zipper->onWriteCallback(static_cast<const std::byte*>(buff), n);
} }; } };
auto archiveClose{ [](struct ::archive*, void*) auto archiveClose{ [](struct ::archive*, void*) {
{
return ARCHIVE_OK; return ARCHIVE_OK;
} }; } };
@@ -183,8 +184,7 @@ namespace lms::zip
using std::filesystem::perms; using std::filesystem::perms;
::mode_t mode{}; ::mode_t mode{};
auto testPerm{ [](perms p, perms permToTest) auto testPerm{ [](perms p, perms permToTest) {
{
return (p & permToTest) == permToTest; return (p & permToTest) == permToTest;
} }; } };
@@ -295,4 +295,4 @@ namespace lms::zip
return bufferSize; return bufferSize;
} }
} } // namespace lms::zip
+2 -1
View File
@@ -21,6 +21,7 @@
#include <cstddef> #include <cstddef>
#include <memory> #include <memory>
#include "core/IZipper.hpp" #include "core/IZipper.hpp"
extern "C" extern "C"
@@ -77,4 +78,4 @@ namespace lms::zip
std::uint64_t _bytesWrittenInCurrentOutputStream{}; std::uint64_t _bytesWrittenInCurrentOutputStream{};
}; };
} } // namespace lms::zip
+10 -9
View File
@@ -19,21 +19,21 @@
#include "ChildProcess.hpp" #include "ChildProcess.hpp"
#include <cstring>
#include <cerrno> #include <cerrno>
#include <cstring>
#include <fcntl.h> #include <fcntl.h>
#include <signal.h>
#include <stdexcept> #include <stdexcept>
#include <sys/types.h> #include <sys/types.h>
#include <sys/wait.h> #include <sys/wait.h>
#include <signal.h>
#include <unistd.h> #include <unistd.h>
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <mutex> #include <mutex>
#include <boost/asio/read.hpp>
#include <boost/asio/buffer.hpp> #include <boost/asio/buffer.hpp>
#include <boost/asio/read.hpp>
#include "core/Exception.hpp" #include "core/Exception.hpp"
#include "core/ILogger.hpp" #include "core/ILogger.hpp"
@@ -47,13 +47,15 @@ namespace lms::core
public: public:
SystemException(int err, const std::string& errMsg) SystemException(int err, const std::string& errMsg)
: ChildProcessException{ errMsg + ": " + ::strerror(err) } : ChildProcessException{ errMsg + ": " + ::strerror(err) }
{} {
}
SystemException(boost::system::error_code ec, const std::string& errMsg) SystemException(boost::system::error_code ec, const std::string& errMsg)
: ChildProcessException{ errMsg + ": " + ec.message() } : ChildProcessException{ errMsg + ": " + ec.message() }
{} {
}
}; };
} } // namespace
ChildProcess::ChildProcess(boost::asio::io_context& ioContext, const std::filesystem::path& path, const Args& args) ChildProcess::ChildProcess(boost::asio::io_context& ioContext, const std::filesystem::path& path, const Args& args)
: _ioContext{ ioContext } : _ioContext{ ioContext }
@@ -170,8 +172,7 @@ namespace lms::core
LMS_LOG(CHILDPROCESS, DEBUG, "Async read, bufferSize = " << bufferSize); LMS_LOG(CHILDPROCESS, DEBUG, "Async read, bufferSize = " << bufferSize);
boost::asio::async_read(_childStdout, boost::asio::buffer(data, 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); LMS_LOG(CHILDPROCESS, DEBUG, "Async read cb - ec = '" << error.message() << "' (" << error.value() << "), bytesTransferred = " << bytesTransferred);
ReadResult readResult{ ReadResult::Success }; ReadResult readResult{ ReadResult::Success };
@@ -206,4 +207,4 @@ namespace lms::core
{ {
return _finished; 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); ChildProcess(boost::asio::io_context& ioContext, const std::filesystem::path& path, const Args& args);
private: private:
void asyncRead(std::byte* data, std::size_t bufferSize, ReadCallback callback) 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; std::size_t readSome(std::byte* data, std::size_t bufferSize) override;
bool finished() const override; bool finished() const override;
void kill(); void kill();
bool wait(bool block); // return true if waited bool wait(bool block); // return true if waited
using FileDescriptor = boost::asio::posix::stream_descriptor; using FileDescriptor = boost::asio::posix::stream_descriptor;
boost::asio::io_context& _ioContext; boost::asio::io_context& _ioContext;
FileDescriptor _childStdout; FileDescriptor _childStdout;
::pid_t _childPID{}; ::pid_t _childPID{};
bool _waited{}; bool _waited{};
bool _finished{}; bool _finished{};
std::optional<int> _exitCode; 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); 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; boost::asio::io_context& _ioContext;
}; };
} } // namespace lms::core
+3 -3
View File
@@ -65,7 +65,7 @@ namespace lms::core
{ {
try 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) for (int i{}; i < values.getLength(); ++i)
_func(static_cast<const char*>(values[i])); _func(static_cast<const char*>(values[i]));
} }
@@ -83,7 +83,7 @@ namespace lms::core
{ {
try try
{ {
const char* res{ _config.lookup(std::string {setting}) }; const char* res{ _config.lookup(std::string{ setting }) };
return std::filesystem::path{ std::string(res) }; return std::filesystem::path{ std::string(res) };
} }
catch (libconfig::ConfigException&) catch (libconfig::ConfigException&)
@@ -127,4 +127,4 @@ namespace lms::core
return def; return def;
} }
} }
} } // namespace lms::core
+7 -8
View File
@@ -37,15 +37,14 @@ namespace lms::core
Config& operator=(Config&&) = delete; Config& operator=(Config&&) = delete;
// Default values are returned in case of setting not found // Default values are returned in case of setting not found
std::string_view getString(std::string_view setting, std::string_view def = "") 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; 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; 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; unsigned long getULong(std::string_view setting, unsigned long def = 0) override;
long getLong(std::string_view setting, long def = 0) override; long getLong(std::string_view setting, long def = 0) override;
bool getBool(std::string_view setting, bool def = false) override; bool getBool(std::string_view setting, bool def = false) override;
private: private:
libconfig::Config _config; libconfig::Config _config;
}; };
} } // namespace lms::core
+2 -2
View File
@@ -80,7 +80,7 @@ namespace lms
std::ostringstream contentRange; std::ostringstream contentRange;
contentRange << "bytes " << startByte << "-" contentRange << "bytes " << startByte << "-"
<< _beyondLastByte - 1 << "/" << fileSize; << _beyondLastByte - 1 << "/" << fileSize;
response.addHeader("Content-Range", contentRange.str()); response.addHeader("Content-Range", contentRange.str());
response.setContentLength(_beyondLastByte - startByte); response.setContentLength(_beyondLastByte - startByte);
@@ -134,4 +134,4 @@ namespace lms
LMS_LOG(UTILS, DEBUG, "Job complete!"); LMS_LOG(UTILS, DEBUG, "Job complete!");
return nullptr; return nullptr;
} }
} } // namespace lms
+7 -6
View File
@@ -22,6 +22,7 @@
#include <filesystem> #include <filesystem>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include "core/IResourceHandler.hpp" #include "core/IResourceHandler.hpp"
namespace lms namespace lms
@@ -33,13 +34,13 @@ namespace lms
private: private:
Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) override; 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 }; static constexpr std::size_t _chunkSize{ 262'144 };
std::filesystem::path _path; std::filesystem::path _path;
std::string _mimeType; std::string _mimeType;
::uint64_t _beyondLastByte{}; ::uint64_t _beyondLastByte{};
::uint64_t _offset{}; ::uint64_t _offset{};
}; };
} } // namespace lms
+16 -17
View File
@@ -41,24 +41,23 @@ namespace lms::core
threadName += std::to_string(i); 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 try
{ {
_ioService.run(); _ioService.run();
} }
catch (const std::exception& e) catch (const std::exception& e)
{ {
LMS_LOG(UTILS, FATAL, "Exception caught in IO context: " << e.what()); LMS_LOG(UTILS, FATAL, "Exception caught in IO context: " << e.what());
std::abort(); std::abort();
} }
}); });
} }
} }
@@ -82,4 +81,4 @@ namespace lms::core
for (std::thread& t : _threads) for (std::thread& t : _threads)
t.join(); t.join();
} }
} } // namespace lms::core
+51 -26
View File
@@ -25,25 +25,44 @@ namespace lms::core::logging
{ {
switch (mod) switch (mod)
{ {
case Module::API_SUBSONIC: return "API_SUBSONIC"; case Module::API_SUBSONIC:
case Module::AUTH: return "AUTH"; return "API_SUBSONIC";
case Module::AV: return "AV"; case Module::AUTH:
case Module::CHILDPROCESS: return "CHILDPROC"; return "AUTH";
case Module::COVER: return "COVER"; case Module::AV:
case Module::DB: return "DB"; return "AV";
case Module::DBUPDATER: return "DB UPDATER"; case Module::CHILDPROCESS:
case Module::FEATURE: return "FEATURE"; return "CHILDPROC";
case Module::FEEDBACK: return "FEEDBACK"; case Module::COVER:
case Module::HTTP: return "HTTP"; return "COVER";
case Module::MAIN: return "MAIN"; case Module::DB:
case Module::METADATA: return "METADATA"; return "DB";
case Module::REMOTE: return "REMOTE"; case Module::DBUPDATER:
case Module::SCROBBLING: return "SCROBBLING"; return "DB UPDATER";
case Module::SERVICE: return "SERVICE"; case Module::FEATURE:
case Module::RECOMMENDATION: return "RECOMMENDATION"; return "FEATURE";
case Module::TRANSCODING: return "TRANSCODING"; case Module::FEEDBACK:
case Module::UI: return "UI"; return "FEEDBACK";
case Module::UTILS: return "UTILS"; 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 ""; return "";
} }
@@ -52,11 +71,16 @@ namespace lms::core::logging
{ {
switch (sev) switch (sev)
{ {
case Severity::FATAL: return "fatal"; case Severity::FATAL:
case Severity::ERROR: return "error"; return "fatal";
case Severity::WARNING: return "warning"; case Severity::ERROR:
case Severity::INFO: return "info"; return "error";
case Severity::DEBUG: return "debug"; case Severity::WARNING:
return "warning";
case Severity::INFO:
return "info";
case Severity::DEBUG:
return "debug";
} }
return ""; return "";
} }
@@ -66,7 +90,8 @@ namespace lms::core::logging
, _module{ module } , _module{ module }
, _severity{ severity } , _severity{ severity }
{} {
}
Log::~Log() Log::~Log()
{ {
@@ -78,4 +103,4 @@ namespace lms::core::logging
{ {
return _oss.str(); return _oss.str();
} }
} } // namespace lms::core::logging
+15 -15
View File
@@ -23,24 +23,24 @@
namespace std namespace std
{ {
std::size_t hash<boost::asio::ip::address>::operator()(const boost::asio::ip::address& ipAddr) const std::size_t hash<boost::asio::ip::address>::operator()(const boost::asio::ip::address& ipAddr) const
{ {
if (ipAddr.is_v4()) if (ipAddr.is_v4())
return ipAddr.to_v4().to_ulong(); return ipAddr.to_v4().to_ulong();
if (ipAddr.is_v6()) if (ipAddr.is_v6())
{ {
const auto& range {ipAddr.to_v6().to_bytes()}; const auto& range{ ipAddr.to_v6().to_bytes() };
std::size_t res {}; std::size_t res{};
for (auto b : range) for (auto b : range)
res ^= std::hash<char>{}(static_cast<char>(b)); 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 #endif // BOOST_ASIO_HAS_STD_HASH
+8 -7
View File
@@ -19,12 +19,11 @@
#include "core/Path.hpp" #include "core/Path.hpp"
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <array> #include <array>
#include <fstream> #include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <boost/tokenizer.hpp> #include <boost/tokenizer.hpp>
@@ -44,7 +43,7 @@ namespace lms::core::pathUtils
{ {
do do
{ {
std::array<char, 1024> buffer; std::array<char, 1024> buffer;
ifs.read(buffer.data(), buffer.size()); ifs.read(buffer.data(), buffer.size());
crc32.processBytes(reinterpret_cast<const std::byte*>(buffer.data()), ifs.gcount()); 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) Wt::WDateTime getLastWriteTime(const std::filesystem::path& file)
{ {
struct stat sb {}; struct stat sb
{
};
if (stat(file.string().c_str(), &sb) == -1) if (stat(file.string().c_str(), &sb) == -1)
throw LmsException("Failed to get stats on file '" + file.string() + "'"); throw LmsException("Failed to get stats on file '" + file.string() + "'");
@@ -183,4 +184,4 @@ namespace lms::core::pathUtils
return longestCommonPath; return longestCommonPath;
} }
} } // namespace lms::core::pathUtils
+1 -1
View File
@@ -35,4 +35,4 @@ namespace lms::core::random
return RandGenerator{ seed }; return RandGenerator{ seed };
} }
} } // namespace lms::core::random
+1 -1
View File
@@ -131,4 +131,4 @@ namespace lms::core
return _sharedCounts[thisThreadId] > 0; return _sharedCounts[thisThreadId] > 0;
} }
#endif #endif
} } // namespace lms::core
+1 -1
View File
@@ -35,4 +35,4 @@ namespace lms::core::logging
assert(isSeverityActive(log.getSeverity())); assert(isSeverityActive(log.getSeverity()));
_os << std::this_thread::get_id() << " [" << getSeverityName(log.getSeverity()) << "] [" << getModuleName(log.getModule()) << "] " << log.getMessage() << std::endl; _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 <iomanip>
#include <utility> #include <utility>
#include <Wt/WDateTime.h>
#include <Wt/WDate.h> #include <Wt/WDate.h>
#include <Wt/WDateTime.h>
namespace lms::core::stringUtils namespace lms::core::stringUtils
{ {
namespace details namespace details
{ {
constexpr std::pair<char, std::string_view> jsEscapeChars[] constexpr std::pair<char, std::string_view> jsEscapeChars[]{
{
{ '\\', "\\\\" }, { '\\', "\\\\" },
{ '\n', "\\n" }, { '\n', "\\n" },
{ '\r', "\\r" }, { '\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" }, { '\n', "\\n" },
{ '\r', "\\r" }, { '\r', "\\r" },
@@ -49,8 +47,8 @@ namespace lms::core::stringUtils
{ '"', "\\\"" }, { '"', "\\\"" },
}; };
template <std::size_t N> template<std::size_t N>
std::string escape(std::string_view str, const std::pair<char, std::string_view>(&charsToEscape)[N]) std::string escape(std::string_view str, const std::pair<char, std::string_view> (&charsToEscape)[N])
{ {
std::string escaped; std::string escaped;
escaped.reserve(str.length()); escaped.reserve(str.length());
@@ -70,12 +68,12 @@ namespace lms::core::stringUtils
return escaped; return escaped;
} }
template <std::size_t N> template<std::size_t N>
void writeEscapedString(std::ostream& os, std::string_view str, const std::pair<char, std::string_view>(&charsToEscape)[N]) void writeEscapedString(std::ostream& os, std::string_view str, const std::pair<char, std::string_view> (&charsToEscape)[N])
{ {
for (const char c : str) 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)) if (itEntry != std::cend(charsToEscape))
os << itEntry->second; os << itEntry->second;
else 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 joinStrings(std::span<const StringType> strings, std::string_view delimiter)
{ {
std::string res; std::string res;
@@ -99,7 +97,7 @@ namespace lms::core::stringUtils
return res; return res;
} }
} } // namespace details
template<> template<>
std::optional<std::string> readAs(std::string_view str) std::optional<std::string> readAs(std::string_view str)
@@ -198,7 +196,8 @@ namespace lms::core::stringUtils
for (char c : str) for (char c : str)
{ {
if (escaped) { if (escaped)
{
current.push_back(c); current.push_back(c);
escaped = false; escaped = false;
} }
@@ -248,14 +247,14 @@ namespace lms::core::stringUtils
std::string res; std::string res;
res.reserve(str.size()); 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; return res;
} }
void stringToLower(std::string& str) 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) std::string stringToUpper(const std::string& str)
@@ -263,7 +262,7 @@ namespace lms::core::stringUtils
std::string res; std::string res;
res.reserve(str.size()); 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; return res;
} }
@@ -436,4 +435,4 @@ namespace lms::core::stringUtils
// assume UTC // assume UTC
return date.toString("yyyy-MM-dd").toUTF8(); return date.toString("yyyy-MM-dd").toUTF8();
} }
} } // namespace lms::core::stringUtils
+10 -7
View File
@@ -22,6 +22,7 @@
#include <iomanip> #include <iomanip>
#include <memory> #include <memory>
#include <string> #include <string>
#include "core/Exception.hpp" #include "core/Exception.hpp"
#include "core/ILogger.hpp" #include "core/ILogger.hpp"
@@ -32,7 +33,8 @@ namespace lms::core::tracing
class CurrentThreadUnregisterer class CurrentThreadUnregisterer
{ {
public: public:
CurrentThreadUnregisterer(TraceLogger* logger) : _logger{ logger } {} CurrentThreadUnregisterer(TraceLogger* logger)
: _logger{ logger } {}
~CurrentThreadUnregisterer() ~CurrentThreadUnregisterer()
{ {
if (_logger) if (_logger)
@@ -45,7 +47,7 @@ namespace lms::core::tracing
TraceLogger* _logger; TraceLogger* _logger;
}; };
} } // namespace
thread_local TraceLogger::Buffer* TraceLogger::_currentBuffer{}; 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); 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("cpu_count", std::to_string(std::thread::hardware_concurrency()));
setMetadata("build_type", setMetadata("build_type",
#ifndef NDEBUG #ifndef NDEBUG
"debug" "debug"
#else #else
"release" "release"
#endif #endif
); );
} }
@@ -182,7 +184,8 @@ namespace lms::core::tracing
if (first) if (first)
first = false; first = false;
else else
os << ", " << std::endl;; os << ", " << std::endl;
;
os << "\t\t{ "; os << "\t\t{ ";
os << "\"name\" : \"" << event.name << "\", "; os << "\"name\" : \"" << event.name << "\", ";
@@ -297,4 +300,4 @@ namespace lms::core::tracing
return static_cast<std::uint32_t>(id); 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; static thread_local Buffer* _currentBuffer;
}; };
} } // namespace lms::core::tracing
+7 -8
View File
@@ -31,13 +31,13 @@ namespace lms::core
{ {
namespace stringUtils namespace stringUtils
{ {
template <> template<>
std::optional<UUID> std::optional<UUID>
readAs(std::string_view str) readAs(std::string_view str)
{ {
return UUID::fromString(str); return UUID::fromString(str);
} }
} } // namespace stringUtils
namespace namespace
{ {
bool stringIsUUID(std::string_view str) bool stringIsUUID(std::string_view str)
@@ -46,7 +46,7 @@ namespace lms::core
return std::regex_match(std::cbegin(str), std::cend(str), re); return std::regex_match(std::cbegin(str), std::cend(str), re);
} }
} } // namespace
UUID::UUID(std::string_view str) UUID::UUID(std::string_view str)
: _value{ stringUtils::stringToLower(str) } : _value{ stringUtils::stringToLower(str) }
@@ -68,9 +68,8 @@ namespace lms::core
std::ostringstream oss; std::ostringstream oss;
auto concatRandomBytes{ [](std::ostream& os, std::size_t byteCount) auto concatRandomBytes{ [](std::ostream& os, std::size_t byteCount) {
{ for (std::size_t i{}; i < byteCount; ++i)
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)); 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); assert(uuid);
return uuid.value(); return uuid.value();
} }
} } // namespace lms::core
+15 -9
View File
@@ -19,10 +19,11 @@
#include "core/WtLogger.hpp" #include "core/WtLogger.hpp"
#include <thread>
#include <sstream> #include <sstream>
#include <Wt/WServer.h> #include <thread>
#include <Wt/WLogger.h> #include <Wt/WLogger.h>
#include <Wt/WServer.h>
#include "core/Exception.hpp" #include "core/Exception.hpp"
@@ -36,7 +37,7 @@ namespace lms::core::logging
oss << id; oss << id;
return oss.str(); return oss.str();
} }
} } // namespace
WtLogger::WtLogger(Severity minSeverity) WtLogger::WtLogger(Severity minSeverity)
: _minSeverity{ minSeverity } : _minSeverity{ minSeverity }
@@ -47,11 +48,16 @@ namespace lms::core::logging
{ {
switch (minSeverity) switch (minSeverity)
{ {
case Severity::DEBUG: return "*"; case Severity::DEBUG:
case Severity::INFO: return "* -debug"; return "*";
case Severity::WARNING: return "* -debug -info"; case Severity::INFO:
case Severity::ERROR: return "* -debug -info -warning"; return "* -debug";
case Severity::FATAL: return "* -debug -info -warning -error"; 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" }; 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(); 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))); _sendQueue.sendRequest(std::make_unique<ClientRequest>(std::move(POSTParams)));
} }
} } // namespace lms::core::http
+6 -4
View File
@@ -19,11 +19,12 @@
#pragma once #pragma once
#include <unordered_map>
#include <string>
#include <shared_mutex> #include <shared_mutex>
#include <string>
#include <unordered_map>
#include "core/http/IClient.hpp" #include "core/http/IClient.hpp"
#include "SendQueue.hpp" #include "SendQueue.hpp"
namespace lms::core::http namespace lms::core::http
@@ -33,7 +34,8 @@ namespace lms::core::http
public: public:
Client(boost::asio::io_context& ioContext, std::string_view baseUrl) Client(boost::asio::io_context& ioContext, std::string_view baseUrl)
: _sendQueue{ ioContext, baseUrl } : _sendQueue{ ioContext, baseUrl }
{} {
}
private: private:
void sendGETRequest(ClientGETRequestParameters&& request) override; void sendGETRequest(ClientGETRequestParameters&& request) override;
@@ -41,4 +43,4 @@ namespace lms::core::http
SendQueue _sendQueue; SendQueue _sendQueue;
}; };
} } // namespace lms::core::http
+10 -7
View File
@@ -21,6 +21,7 @@
#include <memory> #include <memory>
#include <variant> #include <variant>
#include "core/http/ClientRequestParameters.hpp" #include "core/http/ClientRequestParameters.hpp"
namespace lms::core::http namespace lms::core::http
@@ -28,8 +29,10 @@ namespace lms::core::http
class ClientRequest class ClientRequest
{ {
public: public:
ClientRequest(ClientGETRequestParameters&& GETParams) : _parameters{ std::move(GETParams) } {} ClientRequest(ClientGETRequestParameters&& GETParams)
ClientRequest(ClientPOSTRequestParameters&& POSTParams) : _parameters{ std::move(POSTParams) } {} : _parameters{ std::move(GETParams) } {}
ClientRequest(ClientPOSTRequestParameters&& POSTParams)
: _parameters{ std::move(POSTParams) } {}
std::size_t retryCount{}; std::size_t retryCount{};
@@ -37,10 +40,10 @@ namespace lms::core::http
{ {
const ClientRequestParameters* res; const ClientRequestParameters* res;
std::visit([&](const auto& parameters) std::visit([&](const auto& parameters) {
{ res = &static_cast<const ClientRequestParameters&>(parameters);
res = &static_cast<const ClientRequestParameters&>(parameters); },
}, _parameters); _parameters);
return *res; return *res;
} }
@@ -71,4 +74,4 @@ namespace lms::core::http
private: private:
std::variant<ClientGETRequestParameters, ClientPOSTRequestParameters> _parameters; std::variant<ClientGETRequestParameters, ClientPOSTRequestParameters> _parameters;
}; };
} } // namespace lms::core::http
+28 -32
View File
@@ -19,15 +19,15 @@
#include "SendQueue.hpp" #include "SendQueue.hpp"
#include <boost/asio/dispatch.hpp>
#include <boost/asio/bind_executor.hpp> #include <boost/asio/bind_executor.hpp>
#include <boost/asio/dispatch.hpp>
#include "core/Exception.hpp" #include "core/Exception.hpp"
#include "core/ILogger.hpp" #include "core/ILogger.hpp"
#include "core/ITraceLogger.hpp" #include "core/ITraceLogger.hpp"
#include "core/String.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 namespace lms::core::stringUtils
{ {
@@ -41,35 +41,33 @@ namespace lms::core::stringUtils
return res; return res;
} }
} } // namespace lms::core::stringUtils
namespace lms::core::http namespace lms::core::http
{ {
namespace namespace
{ {
template <typename T> template<typename T>
std::optional<T> headerReadAs(const Wt::Http::Message& msg, std::string_view headerName) std::optional<T> headerReadAs(const Wt::Http::Message& msg, std::string_view headerName)
{ {
std::optional<T> res; 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); res = stringUtils::readAs<T>(*headerValue);
return res; return res;
} }
} } // namespace
SendQueue::SendQueue(boost::asio::io_context& ioContext, std::string_view baseUrl) SendQueue::SendQueue(boost::asio::io_context& ioContext, std::string_view baseUrl)
: _ioContext{ ioContext } : _ioContext{ ioContext }
, _baseUrl{ baseUrl } , _baseUrl{ baseUrl }
{ {
_client.done().connect([this](Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg) _client.done().connect([this](Wt::AsioWrapper::error_code ec, const Wt::Http::Message& msg) {
{ _strand.dispatch([this, ec, msg = std::move(msg)] {
_strand.dispatch([this, ec, msg = std::move(msg)] onClientDone(ec, msg);
{
onClientDone(ec, msg);
});
}); });
});
} }
SendQueue::~SendQueue() SendQueue::~SendQueue()
@@ -79,13 +77,12 @@ namespace lms::core::http
void SendQueue::sendRequest(std::unique_ptr<ClientRequest> request) void SendQueue::sendRequest(std::unique_ptr<ClientRequest> request)
{ {
boost::asio::dispatch(_strand, [this, request = std::move(request)]() mutable boost::asio::dispatch(_strand, [this, request = std::move(request)]() mutable {
{ _sendQueue[request->getParameters().priority].emplace_back(std::move(request));
_sendQueue[request->getParameters().priority].emplace_back(std::move(request));
if (_state == State::Idle) if (_state == State::Idle)
sendNextQueuedRequest(); sendNextQueuedRequest();
}); });
} }
void SendQueue::sendNextQueuedRequest() void SendQueue::sendNextQueuedRequest()
@@ -220,21 +217,20 @@ namespace lms::core::http
LOG(DEBUG, "Throttling for " << duration.count() << " seconds"); LOG(DEBUG, "Throttling for " << duration.count() << " seconds");
_throttleTimer.expires_after(duration); _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;
LOG(DEBUG, "Throttle aborted"); }
return; else if (ec)
} {
else if (ec) throw LmsException{ "Throttle timer failure: " + std::string{ ec.message() } };
{ }
throw LmsException{ "Throttle timer failure: " + std::string {ec.message()} };
}
_state = State::Idle; _state = State::Idle;
sendNextQueuedRequest(); sendNextQueuedRequest();
}); });
_state = State::Throttled; _state = State::Throttled;
} }
} } // namespace lms::core::http
+13 -13
View File
@@ -20,14 +20,14 @@
#pragma once #pragma once
#include <deque> #include <deque>
#include <vector>
#include <string_view> #include <string_view>
#include <vector>
#include <Wt/Http/Client.h>
#include <boost/asio/io_context.hpp> #include <boost/asio/io_context.hpp>
#include <boost/asio/io_context_strand.hpp> #include <boost/asio/io_context_strand.hpp>
#include <boost/asio/steady_timer.hpp> #include <boost/asio/steady_timer.hpp>
#include <Wt/Http/Client.h>
#include "ClientRequest.hpp" #include "ClientRequest.hpp"
namespace lms::core::http 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 onClientDoneSuccess(std::unique_ptr<ClientRequest> request, const Wt::Http::Message& msg);
void throttle(std::chrono::seconds duration); void throttle(std::chrono::seconds duration);
const std::size_t _maxRetryCount{ 2 }; const std::size_t _maxRetryCount{ 2 };
const std::chrono::seconds _defaultRetryWaitDuration{ 30 }; const std::chrono::seconds _defaultRetryWaitDuration{ 30 };
const std::chrono::seconds _minRetryWaitDuration{ 1 }; const std::chrono::seconds _minRetryWaitDuration{ 1 };
const std::chrono::seconds _maxRetryWaitDuration{ 300 }; const std::chrono::seconds _maxRetryWaitDuration{ 300 };
boost::asio::io_context& _ioContext; boost::asio::io_context& _ioContext;
boost::asio::io_context::strand _strand{ _ioContext }; boost::asio::io_context::strand _strand{ _ioContext };
boost::asio::steady_timer _throttleTimer{ _ioContext }; boost::asio::steady_timer _throttleTimer{ _ioContext };
std::string _baseUrl; std::string _baseUrl;
enum class State enum class State
{ {
@@ -69,10 +69,10 @@ namespace lms::core::http
Throttled, Throttled,
Sending, Sending,
}; };
State _state{ State::Idle }; State _state{ State::Idle };
Wt::Http::Client _client{ _ioContext }; Wt::Http::Client _client{ _ioContext };
std::map<ClientRequestParameters::Priority, std::deque<std::unique_ptr<ClientRequest>>> _sendQueue; 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 #pragma once
#include <boost/crc.hpp> // for boost::crc_32_type #include <boost/crc.hpp> // for boost::crc_32_type
namespace lms::core namespace lms::core
{ {
@@ -40,4 +40,4 @@ namespace lms::core
using Crc32Type = boost::crc_32_type; using Crc32Type = boost::crc_32_type;
Crc32Type _result; Crc32Type _result;
}; };
} } // namespace lms::core
+8 -5
View File
@@ -27,7 +27,7 @@
namespace lms::core namespace lms::core
{ {
template <typename T, typename underlying_type = std::uint32_t> template<typename T, typename underlying_type = std::uint32_t>
class EnumSet class EnumSet
{ {
static_assert(std::is_enum<T>::value); static_assert(std::is_enum<T>::value);
@@ -45,13 +45,13 @@ namespace lms::core
insert(value); insert(value);
} }
template <typename It> template<typename It>
constexpr EnumSet(It begin, It end) constexpr EnumSet(It begin, It end)
{ {
assign(begin, end); assign(begin, end);
} }
template <typename It> template<typename It>
constexpr void assign(It begin, It end) constexpr void assign(It begin, It end)
{ {
clear(); clear();
@@ -158,7 +158,10 @@ namespace lms::core
private: private:
static_assert(std::numeric_limits<IndexType>::max() >= sizeof(underlying_type) * 8); 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 constexpr IndexType getFirstBitSetIndex(IndexType start = {}) const
{ {
@@ -190,4 +193,4 @@ namespace lms::core
underlying_type _bitfield{}; underlying_type _bitfield{};
}; };
} } // namespace lms::core
+3 -2
View File
@@ -29,6 +29,7 @@ namespace lms::core
class LmsException : public std::runtime_error class LmsException : public std::runtime_error
{ {
public: 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)>; 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 std::size_t readSome(std::byte* data, std::size_t bufferSize) = 0;
virtual bool finished() const = 0; virtual bool finished() const = 0;
}; };
} } // namespace lms::core
@@ -20,6 +20,7 @@
#include <filesystem> #include <filesystem>
#include <memory> #include <memory>
#include <boost/asio/io_service.hpp> #include <boost/asio/io_service.hpp>
#include "IChildProcess.hpp" #include "IChildProcess.hpp"
@@ -35,4 +36,4 @@ namespace lms::core
}; };
std::unique_ptr<IChildProcessManager> createChildProcessManager(boost::asio::io_service& ioService); 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; virtual ~IConfig() = default;
// Default values are returned in case of setting not found // 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 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 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 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 unsigned long getULong(std::string_view setting, unsigned long def = 0) = 0;
virtual long getLong(std::string_view setting, 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 bool getBool(std::string_view setting, bool def = false) = 0;
}; };
std::unique_ptr<IConfig> createConfig(const std::filesystem::path& p); std::unique_ptr<IConfig> createConfig(const std::filesystem::path& p);
} } // namespace lms::core
+9 -9
View File
@@ -19,11 +19,11 @@
#pragma once #pragma once
#include <string>
#include <sstream> #include <sstream>
#include <string>
#include "core/Service.hpp"
#include "core/String.hpp" #include "core/String.hpp"
#include "Service.hpp"
namespace lms::core::logging namespace lms::core::logging
{ {
@@ -93,11 +93,11 @@ namespace lms::core::logging
virtual bool isSeverityActive(Severity severity) const = 0; virtual bool isSeverityActive(Severity severity) const = 0;
virtual void processLog(const Log& log) = 0; virtual void processLog(const Log& log) = 0;
}; };
} } // namespace lms::core::logging
#define LMS_LOG(module, severity, message) \ #define LMS_LOG(module, severity, message) \
do \ do \
{ \ { \
if (auto* logger_ {::lms::core::Service<::lms::core::logging::ILogger>::get()}; logger_ && logger_->isSeverityActive(::lms::core::logging::Severity::severity)) \ 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; \ ::lms::core::logging::Log{ *logger_, ::lms::core::logging::Module::module, ::lms::core::logging::Severity::severity }.getOstream() << message; \
} while(0) } while (0)
@@ -21,6 +21,7 @@
#include <optional> #include <optional>
#include <thread> #include <thread>
#include <boost/asio/io_service.hpp> #include <boost/asio/io_service.hpp>
namespace lms::core namespace lms::core
@@ -39,7 +40,7 @@ namespace lms::core
IOContextRunner& operator=(const IOContextRunner&) = delete; IOContextRunner& operator=(const IOContextRunner&) = delete;
boost::asio::io_service& _ioService; boost::asio::io_service& _ioService;
std::optional<boost::asio::io_service::work> _work; std::optional<boost::asio::io_service::work> _work;
std::vector<std::thread> _threads; std::vector<std::thread> _threads;
}; };
} } // namespace lms::core
@@ -25,13 +25,13 @@
// TODO, move elsewhere // TODO, move elsewhere
namespace lms namespace lms
{ {
// Helper class to serve a resource (must be saved as continuation data if not complete) // Helper class to serve a resource (must be saved as continuation data if not complete)
class IResourceHandler class IResourceHandler
{ {
public: public:
virtual ~IResourceHandler() = default; virtual ~IResourceHandler() = default;
[[nodiscard]] virtual Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) = 0; [[nodiscard]] virtual Wt::Http::ResponseContinuation* processRequest(const Wt::Http::Request& request, Wt::Http::Response& response) = 0;
virtual void abort() = 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_IMPL(x, y) x##y
#define LMS_CONCAT(x, y) LMS_CONCAT_IMPL(x, y) #define LMS_CONCAT(x, y) LMS_CONCAT_IMPL(x, y)
#if LMS_SUPPORT_TRACING #if LMS_SUPPORT_TRACING
#define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME, ARGTYPE, ARGVALUE) \ #define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME, ARGTYPE, ARGVALUE) \
std::optional<::lms::core::tracing::ScopedTrace> LMS_CONCAT(ScopedTrace_, __LINE__); \ 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)) \ 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); LMS_CONCAT(ScopedTrace_, __LINE__).emplace(CATEGORY, LEVEL, NAME, ARGTYPE, ARGVALUE, traceLogger);
#else #else
#define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME) (void)0 #define LMS_SCOPED_TRACE(CATEGORY, LEVEL, NAME) (void)0
#endif #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_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_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_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_DETAILED(CATEGORY, NAME) LMS_SCOPED_TRACE_DETAILED_WITH_ARG(CATEGORY, NAME, "", "")
namespace lms::core::tracing namespace lms::core::tracing
{ {
@@ -124,4 +124,4 @@ namespace lms::core::tracing
ITraceLogger* _traceLogger; ITraceLogger* _traceLogger;
ITraceLogger::CompleteEvent _event; ITraceLogger::CompleteEvent _event;
}; };
} } // namespace lms::core::tracing
+2 -2
View File
@@ -25,7 +25,7 @@
#include "Exception.hpp" #include "Exception.hpp"
// TODO, move elsewhere? // TODO, move elsewhere?
namespace lms::zip namespace lms::zip
{ {
struct Entry struct Entry
@@ -51,4 +51,4 @@ namespace lms::zip
}; };
std::unique_ptr<IZipper> createArchiveZipper(const EntryContainer& entries); std::unique_ptr<IZipper> createArchiveZipper(const EntryContainer& entries);
} } // namespace lms::zip
+24 -12
View File
@@ -31,7 +31,11 @@ namespace lms::core
public: public:
constexpr LiteralString() noexcept = default; constexpr LiteralString() noexcept = default;
template<std::size_t N> 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 bool empty() const noexcept { return _str.empty(); }
constexpr const char* c_str() const noexcept { return _str.data(); } constexpr const char* c_str() const noexcept { return _str.data(); }
@@ -48,7 +52,7 @@ namespace lms::core
os << str.str(); os << str.str();
return os; return os;
} }
} } // namespace lms::core
namespace std namespace std
{ {
@@ -60,7 +64,7 @@ namespace std
return hash<std::string_view>{}(str.str()); return hash<std::string_view>{}(str.str());
} }
}; };
} } // namespace std
namespace lms::core namespace lms::core
{ {
@@ -69,15 +73,18 @@ namespace lms::core
using hash_type = std::hash<std::string_view>; using hash_type = std::hash<std::string_view>;
using is_transparent = void; 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()); 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); 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); return hash_type{}(str);
} }
}; };
@@ -86,24 +93,29 @@ namespace lms::core
{ {
using is_transparent = void; 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; 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; 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; 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(); 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(); return lhs == rhs.str();
} }
}; };
} } // namespace lms::core
+7 -6
View File
@@ -22,15 +22,16 @@
#include <boost/asio/ip/address.hpp> #include <boost/asio/ip/address.hpp>
#ifndef BOOST_ASIO_HAS_STD_HASH #ifndef BOOST_ASIO_HAS_STD_HASH
#include <functional> #include <functional>
namespace std namespace std
{ {
template<> struct hash<boost::asio::ip::address> template<>
{ struct hash<boost::asio::ip::address>
std::size_t operator()(const boost::asio::ip::address& ipAddr) const; {
}; std::size_t operator()(const boost::asio::ip::address& ipAddr) const;
};
} } // namespace std
#endif // BOOST_ASIO_HAS_STD_HASH #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); 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 getLongestCommonPath(Iterator first, Iterator last)
{ {
std::filesystem::path longestCommonPath; std::filesystem::path longestCommonPath;
@@ -64,4 +64,4 @@ namespace lms::core::pathUtils
return longestCommonPath; return longestCommonPath;
} }
} } // namespace lms::core::pathUtils
+5 -5
View File
@@ -29,27 +29,27 @@ namespace lms::core::random
RandGenerator createSeededGenerator(uint_fast32_t seed); RandGenerator createSeededGenerator(uint_fast32_t seed);
template <typename T> template<typename T>
T getRandom(T min, T max) T getRandom(T min, T max)
{ {
std::uniform_int_distribution<> dist{ min, max }; std::uniform_int_distribution<> dist{ min, max };
return dist(getRandGenerator()); return dist(getRandGenerator());
} }
template <typename T> template<typename T>
T getRealRandom(T min, T max) T getRealRandom(T min, T max)
{ {
std::uniform_real_distribution<> dist{ min, max }; std::uniform_real_distribution<> dist{ min, max };
return dist(getRandGenerator()); return dist(getRandGenerator());
} }
template <typename Container> template<typename Container>
void shuffleContainer(Container& container) void shuffleContainer(Container& container)
{ {
std::shuffle(std::begin(container), std::end(container), getRandGenerator()); std::shuffle(std::begin(container), std::end(container), getRandGenerator());
} }
template <typename Container> template<typename Container>
typename Container::const_iterator pickRandom(const Container& container) typename Container::const_iterator pickRandom(const Container& container)
{ {
if (container.empty()) 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))); 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::mutex _sharedCountMutex;
std::unordered_map<std::thread::id, std::size_t> _sharedCounts; std::unordered_map<std::thread::id, std::size_t> _sharedCounts;
}; };
} } // namespace lms::core
+3 -3
View File
@@ -24,7 +24,7 @@
namespace lms::core namespace lms::core
{ {
template <typename Class> template<typename Class>
class Service class Service
{ {
public: public:
@@ -57,7 +57,7 @@ namespace lms::core
static Class* get() { return _service.get(); } static Class* get() { return _service.get(); }
static bool exists() { return _service.get(); } static bool exists() { return _service.get(); }
template <typename SubClass> template<typename SubClass>
static Class& assign(std::unique_ptr<SubClass> service) static Class& assign(std::unique_ptr<SubClass> service)
{ {
assert(!_service); assert(!_service);
@@ -70,4 +70,4 @@ namespace lms::core
static inline std::unique_ptr<Class> _service; 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; std::ostream& _os;
const EnumSet<Severity> _severities; const EnumSet<Severity> _severities;
}; };
} } // namespace lms::core::logging
+4 -4
View File
@@ -22,9 +22,9 @@
#include <initializer_list> #include <initializer_list>
#include <optional> #include <optional>
#include <span> #include <span>
#include <sstream>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <sstream>
#include <vector> #include <vector>
#define QUOTEME(x) QUOTEME_1(x) #define QUOTEME(x) QUOTEME_1(x)
@@ -34,7 +34,7 @@ namespace Wt
{ {
class WDate; class WDate;
class WDateTime; class WDateTime;
} } // namespace Wt
namespace lms::core::stringUtils namespace lms::core::stringUtils
{ {
@@ -67,7 +67,7 @@ namespace lms::core::stringUtils
{ {
T res; T res;
std::istringstream iss{ std::string {str} }; std::istringstream iss{ std::string{ str } };
iss >> res; iss >> res;
if (iss.fail()) if (iss.fail())
return std::nullopt; 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::WDateTime& dateTime);
[[nodiscard]] std::string toISO8601String(const Wt::WDate& date); [[nodiscard]] std::string toISO8601String(const Wt::WDate& date);
} } // namespace lms::core::stringUtils
+26 -20
View File
@@ -23,27 +23,33 @@
namespace lms::core namespace lms::core
{ {
namespace details namespace details
{ {
template<int... Is> template<int... Is>
struct Seq { }; struct Seq
{
};
template<int N, int... Is> template<int N, int... Is>
struct GenSeq : GenSeq<N - 1, N - 1, Is...> { }; struct GenSeq : GenSeq<N - 1, N - 1, Is...>
{
};
template<int... Is> template<int... Is>
struct GenSeq<0, Is...> : Seq<Is...> { }; struct GenSeq<0, Is...> : Seq<Is...>
{
};
template<typename T, typename Func, int... Is> template<typename T, typename Func, int... Is>
void forEachTypeInTuple(T&& t, Func f, Seq<Is...>) void forEachTypeInTuple(T&& t, Func f, Seq<Is...>)
{ {
auto l = { (f(std::get<Is>(t)), 0)... }; auto l = { (f(std::get<Is>(t)), 0)... };
} }
} } // namespace details
template<typename... Ts, typename Func> template<typename... Ts, typename Func>
void forEachTypeInTuple(std::tuple<Ts...> const& t, Func f) void forEachTypeInTuple(std::tuple<Ts...> const& t, Func f)
{ {
details::forEachTypeInTuple(t, f, details::GenSeq<sizeof...(Ts)>()); 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); UUID(std::string_view value);
std::string _value; std::string _value;
}; };
} } // namespace lms::core
namespace lms::core::stringUtils namespace lms::core::stringUtils
{ {
template <> template<>
std::optional<UUID> 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 namespace lms::core::utils
{ {
template<class T, class Compare = std::less<>> template<class T, class Compare = std::less<>>
constexpr T clamp(T v, T lo, T hi, Compare comp = {}) constexpr T clamp(T v, T lo, T hi, Compare comp = {})
{ {
assert(!comp(hi, lo)); assert(!comp(hi, lo));
return comp(v, lo) ? lo : comp(hi, v) ? hi : v; return comp(v, lo) ? lo : comp(hi, v) ? hi :
} v;
}
template <typename Container, typename T> template<typename Container, typename T>
void void
push_back_if_not_present(Container& container, const T& val) push_back_if_not_present(Container& container, const T& val)
{ {
if (std::find(std::cbegin(container), std::cend(container), val) == std::cend(container)) if (std::find(std::cbegin(container), std::cend(container), val) == std::cend(container))
container.push_back(val); 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; void processLog(const Log& log) override;
const Severity _minSeverity; const Severity _minSeverity;
}; };
} } // namespace lms::core::logging
@@ -36,7 +36,7 @@ namespace lms::core::http
Low, Low,
}; };
Priority priority{ Priority::Normal }; Priority priority{ Priority::Normal };
std::string relativeUrl; // relative to baseUrl used by the client std::string relativeUrl; // relative to baseUrl used by the client
using OnSuccessFunc = std::function<void(std::string_view msgBody)>; using OnSuccessFunc = std::function<void(std::string_view msgBody)>;
@@ -56,4 +56,4 @@ namespace lms::core::http
Wt::Http::Message message; Wt::Http::Message message;
}; };
} } // namespace lms::core::http
+10 -9
View File
@@ -20,20 +20,21 @@
#pragma once #pragma once
#include <string_view> #include <string_view>
#include <boost/asio/io_context.hpp> #include <boost/asio/io_context.hpp>
#include "core/http/ClientRequestParameters.hpp" #include "core/http/ClientRequestParameters.hpp"
namespace lms::core::http namespace lms::core::http
{ {
class IClient class IClient
{ {
public: public:
virtual ~IClient() = default; virtual ~IClient() = default;
virtual void sendGETRequest(ClientGETRequestParameters&& request) = 0; virtual void sendGETRequest(ClientGETRequestParameters&& request) = 0;
virtual void sendPOSTRequest(ClientPOSTRequestParameters&& 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); EXPECT_EQ(test, test2);
} }
} }
} } // namespace lms::core
+4 -4
View File
@@ -18,6 +18,7 @@
*/ */
#include <unordered_map> #include <unordered_map>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "core/LiteralString.hpp" #include "core/LiteralString.hpp"
@@ -53,7 +54,7 @@ namespace lms::core
TEST(LiteralString, unordered_map) 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("abc"));
EXPECT_TRUE(myMap.contains(LiteralString{ "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{ "abc" }));
EXPECT_TRUE(myMap.contains(std::string_view{ "abc" })); EXPECT_TRUE(myMap.contains(std::string_view{ "abc" }));
EXPECT_FALSE(myMap.contains(std::string{ "abcd" })); EXPECT_FALSE(myMap.contains(std::string{ "abcd" }));
EXPECT_FALSE(myMap.contains(std::string_view{ "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; std::filesystem::path expectedCommonPath;
}; };
TestCase tests[] TestCase tests[]{
{ { "foo.txt", "/foo/foo.txt", "" },
{"foo.txt", "/foo/foo.txt", ""}, { "/", "/file.txt", "/" },
{"/", "/file.txt", "/"}, { "/foo/bar/file1.txt", "/foo/bar/file2.txt", "/foo/bar" },
{"/foo/bar/file1.txt", "/foo/bar/file2.txt", "/foo/bar"}, { "/foo/bar/file.txt", "/foo/bar/file.txt", "/foo/bar/file.txt" },
{"/foo/bar/file.txt", "/foo/bar/file.txt", "/foo/bar/file.txt"}, { "/dir1/file.txt", "/dir2/file.txt", "/" },
{"/dir1/file.txt", "/dir2/file.txt", "/"}, { "/prefix/folder/file.txt", "/prefix/folder/subfolder/file.txt", "/prefix/folder" },
{"/prefix/folder/file.txt", "/prefix/folder/subfolder/file.txt", "/prefix/folder"},
}; };
for (const TestCase& test : tests) for (const TestCase& test : tests)
@@ -48,7 +47,6 @@ namespace lms::core::pathUtils::tests
} }
} }
TEST(Path, getLongestCommonPathIterator) TEST(Path, getLongestCommonPathIterator)
{ {
struct TestCase struct TestCase
@@ -57,17 +55,16 @@ namespace lms::core::pathUtils::tests
std::filesystem::path expectedCommonPath; std::filesystem::path expectedCommonPath;
}; };
TestCase tests[] TestCase tests[]{
{ { {}, "" },
{{}, ""}, { { "/" }, "/" },
{{"/"}, "/"}, { { "/foo", "/bar" }, "/" },
{{"/foo", "/bar"}, "/"}, { { "/foo/bar/file1.txt", "/foo/bar/file2.txt" }, "/foo/bar" },
{{"/foo/bar/file1.txt", "/foo/bar/file2.txt"}, "/foo/bar"}, { { "/foo", "/foo/" }, "/foo" },
{{"/foo", "/foo/"}, "/foo"}, { { "/foo", "/foo" }, "/foo" },
{{"/foo", "/foo"}, "/foo"}, { { "/foo/", "/foo/" }, "/foo/" },
{{"/foo/", "/foo/"}, "/foo/"}, { { "/foo/", "/foo/", "/bar" }, "/" },
{{"/foo/", "/foo/", "/bar"}, "/"}, { { "/foo/", "/foo/", "/foo/bar" }, "/foo" },
{{"/foo/", "/foo/", "/foo/bar"}, "/foo"},
}; };
for (const TestCase& test : tests) for (const TestCase& test : tests)
@@ -85,19 +82,18 @@ namespace lms::core::pathUtils::tests
bool expectedResult; bool expectedResult;
}; };
TestCase tests[] TestCase tests[]{
{ { "/file.txt", "/", true },
{"/file.txt", "/", true}, { "/root/folder/file.txt", "/root", true },
{"/root/folder/file.txt", "/root", true}, { "/root/file.txt", "/root", true },
{"/root/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}, { "/root/", "/root", true },
{"/root/", "/root", true}, { "/root/", "/root/", true },
{"/root/", "/root/", true}, { "/folder/file.txt", "/root", false },
{"/folder/file.txt", "/root", false}, { "/folder/file.txt", "/root/", false },
{"/folder/file.txt", "/root/", false}, { "", "/root", false },
{"", "/root", false},
}; };
for (const TestCase& test : tests) 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; 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{}; std::atomic<std::size_t> nbShared{};
for (std::size_t i{}; i < nbThreads; ++i) 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(nbUnique == 0);
assert(nbShared == 0); assert(nbShared == 0);
nbUnique++; nbUnique++;
std::this_thread::sleep_for(std::chrono::milliseconds(5)); std::this_thread::sleep_for(std::chrono::milliseconds(5));
assert(nbUnique == 1); assert(nbUnique == 1);
assert(nbShared == 0); assert(nbShared == 0);
nbUnique--; nbUnique--;
} }
{ {
std::shared_lock lock{ mutex }; std::shared_lock lock{ mutex };
std::shared_lock lock2{ mutex }; std::shared_lock lock2{ mutex };
assert(nbUnique == 0); assert(nbUnique == 0);
nbShared++; nbShared++;
std::this_thread::sleep_for(std::chrono::milliseconds(15)); std::this_thread::sleep_for(std::chrono::milliseconds(15));
assert(nbShared > 0); assert(nbShared > 0);
assert(nbShared <= nbThreads); assert(nbShared <= nbThreads);
assert(nbUnique == 0); assert(nbUnique == 0);
nbShared--; nbShared--;
} }
}); });
} }
for (std::thread& t : threads) for (std::thread& t : threads)
t.join(); t.join();
} }
} } // namespace lms::core
+69 -74
View File
@@ -19,9 +19,10 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include <Wt/WDateTime.h>
#include <Wt/WDate.h> #include <Wt/WDate.h>
#include <Wt/WDateTime.h>
#include <Wt/WTime.h> #include <Wt/WTime.h>
#include "core/String.hpp" #include "core/String.hpp"
namespace lms::core::stringUtils::tests namespace lms::core::stringUtils::tests
@@ -35,30 +36,29 @@ namespace lms::core::stringUtils::tests
std::vector<std::string_view> expectedOutput; std::vector<std::string_view> expectedOutput;
}; };
TestCase tests[] TestCase tests[]{
{ { "abc", '-', { "abc" } },
{"abc", '-', {"abc"}}, { "a", '-', { "a" } },
{"a", '-', {"a"}}, { "", '-', { "" } },
{"", '-', {""}}, { "a-b-c", '-', { "a", "b", "c" } },
{"a-b-c", '-', {"a", "b", "c"}}, { "a|b|c", '|', { "a", "b", "c" } },
{"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"}}, { " ;b;c", ';', { " ", "b", "c" } },
{" ;b;c", ';', {" ", "b", "c"}}, { " ;;c", ';', { " ", "", "c" } },
{" ;;c", ';', {" ", "", "c"}}, { " ; ;c", ';', { " ", " ", "c" } },
{" ; ;c", ';', {" ", " ", "c"}}, { "a;b; ", ';', { "a", "b", " " } },
{"a;b; ", ';', {"a", "b", " "}}, { "a;b", ';', { "a", "b" } },
{"a;b", ';', {"a", "b"}}, { ";b", ';', { "", "b" } },
{";b", ';', {"", "b"}}, { ";", ';', { "", "" } },
{";", ';', {"", ""}}, { ";;", ';', { "", "", "" } },
{";;", ';', {"", "", ""}}, { ";;;", ';', { "", "", "", "" } },
{";;;", ';', {"", "", "", ""}}, { ";;a;;b;;", ';', { "", "", "a", "", "b", "", "" } },
{";;a;;b;;", ';', {"", "", "a", "", "b", "", ""}}, { "a b", ' ', { "a", "b" } },
{"a b", ' ', {"a", "b"}}, { "", ' ', { "" } },
{"", ' ', {""}}, { "a-b|c", '-', { "a", "b|c" } },
{"a-b|c", '-', {"a","b|c"}}, { "a|b-c", '-', { "a|b", "c" } },
{"a|b-c", '-', {"a|b", "c"}}, { "test=foo bar", '=', { "test", "foo bar" } },
{"test=foo bar", '=', {"test", "foo bar"}},
}; };
for (const TestCase& test : tests) for (const TestCase& test : tests)
@@ -77,19 +77,18 @@ namespace lms::core::stringUtils::tests
std::vector<std::string_view> expectedOutput; std::vector<std::string_view> expectedOutput;
}; };
TestCase tests[] TestCase tests[]{
{ { "abc", "", { "abc" } },
{"abc", "", {"abc"}}, { "abc", "-", { "abc" } },
{"abc", "-", {"abc"}}, { "abc", "b", { "a", "c" } },
{"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"}}, { "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) for (const TestCase& test : tests)
@@ -108,15 +107,14 @@ namespace lms::core::stringUtils::tests
std::string expectedOutput; std::string expectedOutput;
}; };
TestCase tests[] TestCase tests[]{
{ { { "a", "b", "c" }, "-", "a-b-c" },
{{"a", "b", "c"}, "-", "a-b-c"}, { { "a", "b", "c" }, ",", "a,b,c" },
{{"a", "b", "c"}, ",", "a,b,c"}, { { "a", "b", "c" }, "***", "a***b***c" },
{{"a", "b", "c"}, "***", "a***b***c"}, { { "a", "", "c" }, "-", "a--c" },
{{"a", "", "c"}, "-", "a--c"}, { { "", "b", "c" }, "-", "-b-c" },
{{"", "b", "c"}, "-", "-b-c"}, { { "a" }, "-", "a" },
{{"a"}, "-", "a"}, { { "a" }, ",", "a" },
{{"a"}, ",", "a"},
}; };
for (const TestCase& test : tests) for (const TestCase& test : tests)
@@ -136,13 +134,12 @@ namespace lms::core::stringUtils::tests
std::string expectedOutput; std::string expectedOutput;
}; };
TestCase tests[] TestCase tests[]{
{ { { "" }, ';', '\\', "" },
{{""}, ';', '\\', ""}, { { ";" }, ';', '\\', "\\;" },
{{";"}, ';', '\\', "\\;"}, { { ";;" }, ';', '\\', "\\;\\;" },
{{";;"}, ';', '\\', "\\;\\;"}, { { "a;", "b" }, ';', '\\', "a\\;;b" },
{{"a;", "b"}, ';', '\\', "a\\;;b"}, { { "a;", "b;" }, ';', '\\', "a\\;;b\\;" },
{{"a;", "b;"}, ';', '\\', "a\\;;b\\;"},
}; };
for (const TestCase& test : tests) for (const TestCase& test : tests)
@@ -162,13 +159,12 @@ namespace lms::core::stringUtils::tests
std::vector<std::string> expectedOutput; std::vector<std::string> expectedOutput;
}; };
TestCase tests[] TestCase tests[]{
{ { "", ';', '\\', {} },
{"", ';', '\\', {}}, { "\\;", ';', '\\', { ";" } },
{"\\;", ';', '\\', {";"}}, { "\\;\\;", ';', '\\', { ";;" } },
{"\\;\\;", ';', '\\', {";;"}}, { "a\\;;b", ';', '\\', { "a;", "b" } },
{"a\\;;b", ';', '\\', {"a;", "b"}}, { "a\\;;b\\;", ';', '\\', { "a;", "b;" } },
{"a\\;;b\\;", ';', '\\', {"a;", "b;"}},
}; };
for (const TestCase& test : tests) for (const TestCase& test : tests)
@@ -250,17 +246,16 @@ namespace lms::core::stringUtils::tests
std::string expectedOutput; std::string expectedOutput;
}; };
TestCase tests[] TestCase tests[]{
{ { "", "" },
{"", ""}, { "C", "C" },
{"C", "C"}, { "c", "C" },
{"c", "C"}, { " c", " C" },
{" c", " C"}, { " cc", " Cc" },
{" cc", " Cc"}, { "(c", "(c" },
{"(c", "(c"}, { "1c", "1c" },
{"1c", "1c"}, { "&c", "&c" },
{"&c", "&c"}, { "c c", "C c" }
{"c c", "C c"}
}; };
for (const TestCase& test : tests) for (const TestCase& test : tests)
@@ -279,7 +274,7 @@ namespace lms::core::stringUtils::tests
TEST(Stringutils, dateTime) 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"); 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", "1FooBar"));
EXPECT_FALSE(stringEndsWith("FooBar", "R")); EXPECT_FALSE(stringEndsWith("FooBar", "R"));
} }
} } // namespace lms::core::stringUtils::tests
+6 -6
View File
@@ -19,6 +19,7 @@
#include <sstream> #include <sstream>
#include <thread> #include <thread>
#include <gtest/gtest.h> #include <gtest/gtest.h>
#include "core/ITraceLogger.hpp" #include "core/ITraceLogger.hpp"
@@ -33,11 +34,10 @@ namespace lms::core::tracing::tests
std::vector<std::thread> threads; std::vector<std::thread> threads;
for (std::size_t i{}; i < 16; ++i) for (std::size_t i{}; i < 16; ++i)
{ {
threads.emplace_back([&] threads.emplace_back([&] {
{ ScopedTrace loggedEvent{ "MyCategory", Level::Overview, "MyEventLogged", "SomeArgType", "SomeArg", traceLogger.get() };
ScopedTrace loggedEvent{ "MyCategory", Level::Overview, "MyEventLogged", "SomeArgType", "SomeArg", traceLogger.get() }; ScopedTrace notLoggedEvent{ "MyNotLoggedCategory", Level::Detailed, "MyEventNotLogged", "SomeNotLoggedArgType", "SomeNotLoggedArg", traceLogger.get() };
ScopedTrace notLoggedEvent{ "MyNotLoggedCategory", Level::Detailed, "MyEventNotLogged", "SomeNotLoggedArgType", "SomeNotLoggedArg", traceLogger.get() }; });
});
} }
for (std::thread& t : threads) 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("SomeNotLoggedArgType"), std::string::npos);
EXPECT_EQ(oss.str().find("SomeNotLoggedArg"), 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);
EXPECT_TRUE(uuid1 <= uuid2); EXPECT_TRUE(uuid1 <= uuid2);
} }
} } // namespace lms::core
+3 -4
View File
@@ -19,9 +19,8 @@
#include <gtest/gtest.h> #include <gtest/gtest.h>
int main(int argc, char **argv) int main(int argc, char** argv)
{ {
::testing::InitGoogleTest(&argc, argv); ::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS(); return RUN_ALL_TESTS();
} }
+42 -49
View File
@@ -20,22 +20,23 @@
#include <Wt/Dbo/WtSqlTraits.h> #include <Wt/Dbo/WtSqlTraits.h>
#include "core/ILogger.hpp"
#include "database/Cluster.hpp" #include "database/Cluster.hpp"
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "database/User.hpp" #include "database/User.hpp"
#include "core/ILogger.hpp"
#include "SqlQuery.hpp"
#include "Utils.hpp"
#include "EnumSetTraits.hpp" #include "EnumSetTraits.hpp"
#include "IdTypeTraits.hpp" #include "IdTypeTraits.hpp"
#include "SqlQuery.hpp"
#include "Utils.hpp"
namespace lms::db namespace lms::db
{ {
namespace namespace
{ {
template <typename ResultType> template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Artist::FindParameters& params) Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Artist::FindParameters& params)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
@@ -97,21 +98,25 @@ namespace lms::db
{ {
assert(params.feedbackBackend); assert(params.feedbackBackend);
query.join("starred_artist s_a ON s_a.artist_id = a.id") query.join("starred_artist s_a ON s_a.artist_id = a.id")
.where("s_a.user_id = ?").bind(params.starringUser) .where("s_a.user_id = ?")
.where("s_a.backend = ?").bind(*params.feedbackBackend) .bind(params.starringUser)
.where("s_a.sync_state <> ?").bind(SyncState::PendingRemove); .where("s_a.backend = ?")
.bind(*params.feedbackBackend)
.where("s_a.sync_state <> ?")
.bind(SyncState::PendingRemove);
} }
if (params.clusters.size() == 1) if (params.clusters.size() == 1)
{ {
query.join("track_cluster t_c ON t_c.track_id = t_a_l.track_id") 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) else if (params.clusters.size() > 1)
{ {
std::ostringstream oss; std::ostringstream oss;
oss << "a.id IN (SELECT DISTINCT t_a_l.artist_id FROM track_artist_link t_a_l" 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; WhereClause clusterClause;
for (const ClusterId clusterId : params.clusters) for (const ClusterId clusterId : params.clusters)
@@ -159,7 +164,7 @@ namespace lms::db
return query; return query;
} }
template <typename ResultType> template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, const Artist::FindParameters& params) Wt::Dbo::Query<ResultType> createQuery(Session& session, const Artist::FindParameters& params)
{ {
std::string_view itemToSelect; std::string_view itemToSelect;
@@ -173,12 +178,12 @@ namespace lms::db
return createQuery<ResultType>(session, itemToSelect, params); return createQuery<ResultType>(session, itemToSelect, params);
} }
} } // namespace
Artist::Artist(const std::string& name, const std::optional<core::UUID>& MBID) Artist::Artist(const std::string& name, const std::optional<core::UUID>& MBID)
: _name{ std::string(name, 0 , _maxNameLength) }, : _name{ std::string(name, 0, _maxNameLength) }
_sortName{ _name }, , _sortName{ _name }
_MBID{ MBID ? MBID->getAsString() : "" } , _MBID{ MBID ? MBID->getAsString() : "" }
{ {
} }
@@ -198,10 +203,7 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Artist>>("SELECT a FROM artist a") 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)) };
.orderBy("a.id")
.where("a.id > ?").bind(lastRetrievedArtist)
.limit(static_cast<int>(count)) };
if (library.isValid()) 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); 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) utils::forEachQueryResult(query, [&](const Artist::pointer& artist) {
{ func(artist);
func(artist); lastRetrievedArtist = artist->getId();
lastRetrievedArtist = artist->getId(); });
});
} }
std::vector<Artist::pointer> Artist::find(Session& session, std::string_view name) std::vector<Artist::pointer> Artist::find(Session& session, std::string_view name)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQueryResults<Artist::pointer>(session.getDboSession()->find<Artist>() 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
.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) Artist::pointer Artist::find(Session& session, const core::UUID& mbid)
@@ -279,19 +278,18 @@ namespace lms::db
assert(session()); assert(session());
std::ostringstream oss; std::ostringstream oss;
oss << oss << "SELECT a.id FROM artist a"
"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_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 t ON t.id = t_a_l.track_id" " 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" " WHERE "
" WHERE " " t_c.cluster_id IN (SELECT DISTINCT c.id from cluster c"
" 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 t ON c.id = t_c.cluster_id" " 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" " INNER JOIN artist a ON a.id = t_a_l.artist_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"
" INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id" " WHERE a.id = ?)"
" WHERE a.id = ?)" " AND a.id <> ?";
" AND a.id <> ?";
if (!artistLinkTypes.empty()) if (!artistLinkTypes.empty())
{ {
@@ -309,11 +307,7 @@ namespace lms::db
oss << ")"; oss << ")";
} }
auto query{ session()->query<ArtistId>(oss.str()) auto query{ session()->query<ArtistId>(oss.str()).bind(getId()).bind(getId()).groupBy("a.id").orderBy("COUNT(*) DESC, RANDOM()") };
.bind(getId())
.bind(getId())
.groupBy("a.id")
.orderBy("COUNT(*) DESC, RANDOM()") };
for (const TrackArtistLinkType type : artistLinkTypes) for (const TrackArtistLinkType type : artistLinkTypes)
query.bind(type); query.bind(type);
@@ -347,11 +341,10 @@ namespace lms::db
query.bind(bindArg); query.bind(bindArg);
std::map<ClusterTypeId, std::vector<Cluster::pointer>> clustersByType; std::map<ClusterTypeId, std::vector<Cluster::pointer>> clustersByType;
utils::forEachQueryResult(query, [&](const Cluster::pointer& cluster) utils::forEachQueryResult(query, [&](const Cluster::pointer& cluster) {
{ if (clustersByType[cluster->getType()->getId()].size() < size)
if (clustersByType[cluster->getType()->getId()].size() < size) clustersByType[cluster->getType()->getId()].push_back(cluster);
clustersByType[cluster->getType()->getId()].push_back(cluster); });
});
std::vector<std::vector<Cluster::pointer>> res; std::vector<std::vector<Cluster::pointer>> res;
for (const auto& [clusterTypeId, clusters] : clustersByType) for (const auto& [clusterTypeId, clusters] : clustersByType)
+5 -3
View File
@@ -20,10 +20,12 @@
#include "database/AuthToken.hpp" #include "database/AuthToken.hpp"
#include <Wt/Dbo/WtSqlTraits.h> #include <Wt/Dbo/WtSqlTraits.h>
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/User.hpp" #include "database/User.hpp"
#include "StringViewTraits.hpp"
#include "IdTypeTraits.hpp" #include "IdTypeTraits.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp" #include "Utils.hpp"
namespace lms::db 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) 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) 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)); 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/ScanSettings.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "IdTypeTraits.hpp" #include "IdTypeTraits.hpp"
#include "StringViewTraits.hpp"
#include "SqlQuery.hpp" #include "SqlQuery.hpp"
#include "StringViewTraits.hpp"
#include "Utils.hpp" #include "Utils.hpp"
namespace lms::db namespace lms::db
{ {
namespace namespace
{ {
template <typename ResultType> template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Cluster::FindParameters& params) Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Cluster::FindParameters& params)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
@@ -44,7 +45,7 @@ namespace lms::db
if (params.track.isValid() || params.release.isValid()) if (params.track.isValid() || params.release.isValid())
query.join("track_cluster t_c ON t_c.cluster_id = c.id"); query.join("track_cluster t_c ON t_c.cluster_id = c.id");
if (!params.clusterTypeName.empty()) if (!params.clusterTypeName.empty())
query.join("cluster_type c_t ON c_t.id = c.cluster_type_id"); query.join("cluster_type c_t ON c_t.id = c.cluster_type_id");
@@ -77,7 +78,7 @@ namespace lms::db
return query; return query;
} }
template <typename ResultType> template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, const Cluster::FindParameters& params) Wt::Dbo::Query<ResultType> createQuery(Session& session, const Cluster::FindParameters& params)
{ {
std::string_view itemToSelect; std::string_view itemToSelect;
@@ -91,17 +92,17 @@ namespace lms::db
return createQuery<ResultType>(session, itemToSelect, params); return createQuery<ResultType>(session, itemToSelect, params);
} }
} } // namespace
Cluster::Cluster(ObjectPtr<ClusterType> type, std::string_view name) Cluster::Cluster(ObjectPtr<ClusterType> type, std::string_view name)
: _name{ std::string {name, 0, _maxNameLength} }, : _name{ std::string{ name, 0, _maxNameLength } }
_clusterType{ getDboPtr(type) } , _clusterType{ getDboPtr(type) }
{ {
} }
Cluster::pointer Cluster::create(Session& session, ObjectPtr<ClusterType> type, std::string_view name) 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) std::size_t Cluster::getCount(Session& session)
@@ -154,16 +155,14 @@ namespace lms::db
{ {
session.checkReadTransaction(); 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") 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));
.where("t_c.cluster_id = ?").bind(id));
} }
std::size_t Cluster::computeReleaseCount(Session& session, ClusterId id) std::size_t Cluster::computeReleaseCount(Session& session, ClusterId id)
{ {
session.checkReadTransaction(); 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") 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));
.where("t_c.cluster_id = ?").bind(id));
} }
void Cluster::addTrack(ObjectPtr<Track> track) void Cluster::addTrack(ObjectPtr<Track> track)
@@ -175,8 +174,7 @@ namespace lms::db
{ {
assert(session()); 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") 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()) };
.where("c.id = ?").bind(getId()) };
return utils::execRangeQuery<TrackId>(query, range); return utils::execRangeQuery<TrackId>(query, range);
} }
@@ -188,7 +186,7 @@ namespace lms::db
ClusterType::pointer ClusterType::create(Session& session, std::string_view name) 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) std::size_t ClusterType::getCount(Session& session)
@@ -203,9 +201,9 @@ namespace lms::db
session.checkReadTransaction(); session.checkReadTransaction();
auto query{ session.getDboSession()->query<ClusterTypeId>( auto query{ session.getDboSession()->query<ClusterTypeId>(
"SELECT c_t.id from cluster_type c_t" "SELECT c_t.id from cluster_type c_t"
" LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id") " LEFT OUTER JOIN cluster c ON c_t.id = c.cluster_type_id")
.where("c.id IS NULL") }; .where("c.id IS NULL") };
return utils::execRangeQuery<ClusterTypeId>(query, range); return utils::execRangeQuery<ClusterTypeId>(query, range);
} }
@@ -215,8 +213,8 @@ namespace lms::db
session.checkReadTransaction(); session.checkReadTransaction();
auto query{ session.getDboSession()->query<ClusterTypeId>( auto query{ session.getDboSession()->query<ClusterTypeId>(
"SELECT DISTINCT c_t.id from cluster_type c_t") "SELECT DISTINCT c_t.id from cluster_type c_t")
.join("cluster c ON c_t.id = c.cluster_type_id") }; .join("cluster c ON c_t.id = c.cluster_type_id") };
return utils::execRangeQuery<ClusterTypeId>(query, range); return utils::execRangeQuery<ClusterTypeId>(query, range);
} }
@@ -255,9 +253,7 @@ namespace lms::db
assert(self()); assert(self());
assert(session()); assert(session());
return utils::fetchQuerySingleResult(session()->find<Cluster>() return utils::fetchQuerySingleResult(session()->find<Cluster>().where("name = ?").bind(name).where("cluster_type_id = ?").bind(getId()));
.where("name = ?").bind(name)
.where("cluster_type_id = ?").bind(getId()));
} }
std::vector<Cluster::pointer> ClusterType::getClusters() const std::vector<Cluster::pointer> ClusterType::getClusters() const
@@ -265,8 +261,6 @@ namespace lms::db
assert(self()); assert(self());
assert(session()); assert(session());
return utils::fetchQueryResults<Cluster::pointer>(session()->find<Cluster>() return utils::fetchQueryResults<Cluster::pointer>(session()->find<Cluster>().where("cluster_type_id = ?").bind(getId()).orderBy("name"));
.where("cluster_type_id = ?").bind(getId())
.orderBy("name"));
} }
} // namespace lms::db } // namespace lms::db
+7 -17
View File
@@ -22,12 +22,12 @@
#include <Wt/Dbo/FixedSqlConnectionPool.h> #include <Wt/Dbo/FixedSqlConnectionPool.h>
#include <Wt/Dbo/backend/Sqlite3.h> #include <Wt/Dbo/backend/Sqlite3.h>
#include "database/Session.hpp"
#include "database/User.hpp"
#include "core/IConfig.hpp" #include "core/IConfig.hpp"
#include "core/ILogger.hpp" #include "core/ILogger.hpp"
#include "core/ITraceLogger.hpp" #include "core/ITraceLogger.hpp"
#include "core/Service.hpp" #include "core/Service.hpp"
#include "database/Session.hpp"
#include "database/User.hpp"
namespace lms::db namespace lms::db
{ {
@@ -50,12 +50,6 @@ namespace lms::db
prepare(); prepare();
} }
~Connection() override
{
// make use of per-connection usage stats to optimize
optimize();
}
private: private:
Connection& operator=(const Connection&) = delete; Connection& operator=(const Connection&) = delete;
@@ -72,16 +66,9 @@ namespace lms::db
LMS_LOG(DB, DEBUG, "Setting per-connection settings done!"); 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; std::filesystem::path _dbPath;
}; };
} } // namespace
// Session living class handling the database and the login // Session living class handling the database and the login
Db::Db(const std::filesystem::path& dbPath, std::size_t connectionCount) 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()); LMS_LOG(DB, INFO, "Creating connection pool on file " << dbPath.string());
auto connection{ std::make_unique<Connection>(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"); connection->setProperty("show-queries", config->getBool("db-show-queries", false) ? "true" : "false");
auto connectionPool{ std::make_unique<Wt::Dbo::FixedSqlConnectionPool>(std::move(connection), connectionCount) }; 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; return *tlsSession;
} }
+2 -2
View File
@@ -20,6 +20,7 @@
#pragma once #pragma once
#include <type_traits> #include <type_traits>
#include <Wt/Dbo/StdSqlTraits.h> #include <Wt/Dbo/StdSqlTraits.h>
#include "core/EnumSet.hpp" #include "core/EnumSet.hpp"
@@ -49,5 +50,4 @@ namespace Wt::Dbo
return false; return false;
} }
}; };
} } // namespace Wt::Dbo
+2 -2
View File
@@ -20,6 +20,7 @@
#pragma once #pragma once
#include <type_traits> #include <type_traits>
#include <Wt/Dbo/StdSqlTraits.h> #include <Wt/Dbo/StdSqlTraits.h>
#include "database/Types.hpp" #include "database/Types.hpp"
@@ -55,5 +56,4 @@ namespace Wt::Dbo
return false; return false;
} }
}; };
} } // namespace Wt::Dbo
+41 -59
View File
@@ -21,6 +21,7 @@
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "database/User.hpp" #include "database/User.hpp"
#include "IdTypeTraits.hpp" #include "IdTypeTraits.hpp"
#include "SqlQuery.hpp" #include "SqlQuery.hpp"
#include "Utils.hpp" #include "Utils.hpp"
@@ -31,9 +32,7 @@ namespace lms::db
{ {
Wt::Dbo::Query<ArtistId> createArtistsQuery(Session& session, const Listen::ArtistStatsFindParameters& params) Wt::Dbo::Query<ArtistId> createArtistsQuery(Session& session, const Listen::ArtistStatsFindParameters& params)
{ {
auto query{ session.getDboSession()->query<ArtistId>("SELECT a.id from artist a") 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") };
.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()) if (params.user.isValid())
query.where("l.user_id = ?").bind(params.user); query.where("l.user_id = ?").bind(params.user);
@@ -56,7 +55,7 @@ namespace lms::db
{ {
std::ostringstream oss; std::ostringstream oss;
oss << "a.id IN (SELECT DISTINCT t_a_l.artist_id FROM track_artist_link t_a_l" 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; WhereClause clusterClause;
for (auto id : params.clusters) for (auto id : params.clusters)
@@ -96,9 +95,7 @@ namespace lms::db
Wt::Dbo::Query<ReleaseId> createReleasesQuery(Session& session, const Listen::StatsFindParameters& params) Wt::Dbo::Query<ReleaseId> createReleasesQuery(Session& session, const Listen::StatsFindParameters& params)
{ {
auto query{ session.getDboSession()->query<ReleaseId>("SELECT r.id from release r") 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") };
.join("track t ON t.release_id = r.id")
.join("listen l ON l.track_id = t.id") };
if (params.user.isValid()) if (params.user.isValid())
query.where("l.user_id = ?").bind(params.user); query.where("l.user_id = ?").bind(params.user);
@@ -109,7 +106,8 @@ namespace lms::db
if (params.artist.isValid()) if (params.artist.isValid())
{ {
query.join("track_artist_link t_a_l ON t_a_l.track_id = t.id") 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()) if (params.library.isValid())
@@ -119,9 +117,9 @@ namespace lms::db
{ {
std::ostringstream oss; std::ostringstream oss;
oss << "r.id IN (SELECT DISTINCT r.id FROM release r" oss << "r.id IN (SELECT DISTINCT r.id FROM release r"
" INNER JOIN track t ON t.release_id = r.id" " INNER JOIN track t ON t.release_id = r.id"
" INNER JOIN cluster c ON c.id = t_c.cluster_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_cluster t_c ON t_c.track_id = t.id";
WhereClause clusterClause; WhereClause clusterClause;
for (ClusterId id : params.clusters) for (ClusterId id : params.clusters)
@@ -144,8 +142,7 @@ namespace lms::db
Wt::Dbo::Query<TrackId> createTracksQuery(Session& session, const Listen::StatsFindParameters& params) Wt::Dbo::Query<TrackId> createTracksQuery(Session& session, const Listen::StatsFindParameters& params)
{ {
auto query{ session.getDboSession()->query<TrackId>("SELECT t.id from track t") auto query{ session.getDboSession()->query<TrackId>("SELECT t.id from track t").join("listen l ON l.track_id = t.id") };
.join("listen l ON l.track_id = t.id") };
if (params.user.isValid()) if (params.user.isValid())
query.where("l.user_id = ?").bind(params.user); query.where("l.user_id = ?").bind(params.user);
@@ -156,7 +153,8 @@ namespace lms::db
if (params.artist.isValid()) if (params.artist.isValid())
{ {
query.join("track_artist_link t_a_l ON t_a_l.track_id = t.id") 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()) if (params.library.isValid())
@@ -166,8 +164,8 @@ namespace lms::db
{ {
std::ostringstream oss; std::ostringstream oss;
oss << "t.id IN (SELECT DISTINCT t.id FROM track t" 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"
" INNER JOIN cluster c ON c.id = t_c.cluster_id"; " INNER JOIN cluster c ON c.id = t_c.cluster_id";
WhereClause clusterClause; WhereClause clusterClause;
for (auto id : params.clusters) for (auto id : params.clusters)
@@ -187,19 +185,20 @@ namespace lms::db
return query; return query;
} }
} } // namespace
Listen::Listen(ObjectPtr<User> user, ObjectPtr<Track> track, ScrobblingBackend backend, const Wt::WDateTime& dateTime) Listen::Listen(ObjectPtr<User> user, ObjectPtr<Track> track, ScrobblingBackend backend, const Wt::WDateTime& dateTime)
: _dateTime{ Wt::WDateTime::fromTime_t(dateTime.toTime_t()) } : _dateTime{ Wt::WDateTime::fromTime_t(dateTime.toTime_t()) }
, _backend{ backend } , _backend{ backend }
, _user{ getDboPtr(user) } , _user{ getDboPtr(user) }
, _track{ getDboPtr(track) } , _track{ getDboPtr(track) }
{} {
}
Listen::pointer Listen::create(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track, ScrobblingBackend backend, const Wt::WDateTime& dateTime) Listen::pointer Listen::create(Session& session, ObjectPtr<User> user, ObjectPtr<Track> track, ScrobblingBackend backend, const Wt::WDateTime& dateTime)
{ {
session.checkWriteTransaction(); 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) std::size_t Listen::getCount(Session& session)
@@ -218,8 +217,7 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
auto query{ session.getDboSession()->query<ListenId>("SELECT id FROM listen") auto query{ session.getDboSession()->query<ListenId>("SELECT id FROM listen").orderBy("date_time") };
.orderBy("date_time") };
if (parameters.user.isValid()) if (parameters.user.isValid())
query.where("user_id = ?").bind(parameters.user); query.where("user_id = ?").bind(parameters.user);
@@ -237,11 +235,7 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<Listen>() 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())));
.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) RangeResults<ArtistId> Listen::getTopArtists(Session& session, const ArtistStatsFindParameters& params)
@@ -250,8 +244,8 @@ namespace lms::db
auto query{ createArtistsQuery(session, params) }; auto query{ createArtistsQuery(session, params) };
auto collection{ query auto collection{ query
.orderBy("COUNT(a.id) DESC") .orderBy("COUNT(a.id) DESC")
.groupBy("a.id") }; .groupBy("a.id") };
return utils::execRangeQuery<ArtistId>(query, params.range); return utils::execRangeQuery<ArtistId>(query, params.range);
} }
@@ -280,7 +274,8 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
auto query{ createArtistsQuery(session, params) 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") }; .orderBy("l.date_time DESC") };
return utils::execRangeQuery<ArtistId>(query, params.range); return utils::execRangeQuery<ArtistId>(query, params.range);
@@ -290,7 +285,8 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
auto query{ createReleasesQuery(session, params) 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") }; .orderBy("l.date_time DESC") };
return utils::execRangeQuery<ReleaseId>(query, params.range); return utils::execRangeQuery<ReleaseId>(query, params.range);
@@ -300,7 +296,8 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
auto query{ createTracksQuery(session, params) 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") }; .orderBy("l.date_time DESC") };
return utils::execRangeQuery<TrackId>(query, params.range); return utils::execRangeQuery<TrackId>(query, params.range);
@@ -310,11 +307,7 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) from listen l") 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"));
.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) std::size_t Listen::getCount(Session& session, UserId userId, ReleaseId releaseId)
@@ -322,16 +315,16 @@ namespace lms::db
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>( return utils::fetchQuerySingleResult(session.getDboSession()->query<int>(
"SELECT IFNULL(MIN(count_result), 0)" "SELECT IFNULL(MIN(count_result), 0)"
" FROM (" " FROM ("
" SELECT COUNT(l.track_id) AS count_result" " SELECT COUNT(l.track_id) AS count_result"
" FROM track t" " 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 = ?" " 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 = ?" " WHERE t.release_id = ?"
" GROUP BY t.id)") " GROUP BY t.id)")
.bind(userId) .bind(userId)
.bind(userId) .bind(userId)
.bind(releaseId)); .bind(releaseId));
} }
Listen::pointer Listen::getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId releaseId) Listen::pointer Listen::getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, ReleaseId releaseId)
@@ -339,24 +332,13 @@ namespace lms::db
session.checkReadTransaction(); session.checkReadTransaction();
// TODO not pending remove? // TODO not pending remove?
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Listen>>("SELECT l from listen l") 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));
.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) Listen::pointer Listen::getMostRecentListen(Session& session, UserId userId, ScrobblingBackend backend, TrackId trackId)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
// TODO not pending remove? // TODO not pending remove?
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Listen>>("SELECT l from listen l") 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));
.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 } // namespace lms::db
+6 -6
View File
@@ -22,6 +22,7 @@
#include "database/ScanSettings.hpp" #include "database/ScanSettings.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "IdTypeTraits.hpp" #include "IdTypeTraits.hpp"
#include "PathTraits.hpp" #include "PathTraits.hpp"
#include "StringViewTraits.hpp" #include "StringViewTraits.hpp"
@@ -30,8 +31,8 @@
namespace lms::db namespace lms::db
{ {
MediaLibrary::MediaLibrary(const std::filesystem::path& p, std::string_view name) MediaLibrary::MediaLibrary(const std::filesystem::path& p, std::string_view name)
: _path{ p }, : _path{ p }
_name{ std::string {name, 0, maxNameLength} } , _name{ std::string{ name, 0, maxNameLength } }
{ {
} }
@@ -72,9 +73,8 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
utils::forEachQueryResult(session.getDboSession()->find<MediaLibrary>(), [&](const MediaLibrary::pointer& mediaLibrary) utils::forEachQueryResult(session.getDboSession()->find<MediaLibrary>(), [&](const MediaLibrary::pointer& mediaLibrary) {
{ func(mediaLibrary);
func(mediaLibrary); });
});
} }
} // namespace lms::db } // namespace lms::db
+44 -41
View File
@@ -21,13 +21,14 @@
#include <Wt/Dbo/WtSqlTraits.h> #include <Wt/Dbo/WtSqlTraits.h>
#include "core/Exception.hpp"
#include "core/ILogger.hpp"
#include "core/ITraceLogger.hpp"
#include "database/Db.hpp" #include "database/Db.hpp"
#include "database/ScanSettings.hpp" #include "database/ScanSettings.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/User.hpp" #include "database/User.hpp"
#include "core/Exception.hpp"
#include "core/ILogger.hpp"
#include "core/ITraceLogger.hpp"
#include "Utils.hpp" #include "Utils.hpp"
namespace lms::db namespace lms::db
@@ -39,7 +40,8 @@ namespace lms::db
VersionInfo::VersionInfo() VersionInfo::VersionInfo()
: _version{ LMS_DATABASE_VERSION } : _version{ LMS_DATABASE_VERSION }
{} {
}
VersionInfo::pointer VersionInfo::getOrCreate(Session& session) VersionInfo::pointer VersionInfo::getOrCreate(Session& session)
{ {
@@ -58,14 +60,15 @@ namespace lms::db
return utils::fetchQuerySingleResult(session.getDboSession()->find<VersionInfo>()); return utils::fetchQuerySingleResult(session.getDboSession()->find<VersionInfo>());
} }
} } // namespace lms::db
namespace lms::db::Migration namespace lms::db::Migration
{ {
class ScopedNoForeignKeys class ScopedNoForeignKeys
{ {
public: public:
ScopedNoForeignKeys(Db& db) : _db{ db } ScopedNoForeignKeys(Db& db)
: _db{ db }
{ {
_db.executeSql("PRAGMA foreign_keys=OFF"); _db.executeSql("PRAGMA foreign_keys=OFF");
} }
@@ -74,12 +77,12 @@ namespace lms::db::Migration
_db.executeSql("PRAGMA foreign_keys=ON"); _db.executeSql("PRAGMA foreign_keys=ON");
} }
private:
ScopedNoForeignKeys(const ScopedNoForeignKeys&) = delete; ScopedNoForeignKeys(const ScopedNoForeignKeys&) = delete;
ScopedNoForeignKeys(ScopedNoForeignKeys&&) = delete; ScopedNoForeignKeys(ScopedNoForeignKeys&&) = delete;
ScopedNoForeignKeys& operator=(const ScopedNoForeignKeys&) = delete; ScopedNoForeignKeys& operator=(const ScopedNoForeignKeys&) = delete;
ScopedNoForeignKeys& operator=(ScopedNoForeignKeys&&) = delete; ScopedNoForeignKeys& operator=(ScopedNoForeignKeys&&) = delete;
private:
Db& _db; Db& _db;
}; };
@@ -107,9 +110,9 @@ CREATE TABLE IF NOT EXISTS "track_artist_link_backup" (
{ {
// Add scrobbling state // Add scrobbling state
// By default, everything needs to be sent // 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_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_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_track ADD scrobbling_state INTEGER NOT NULL DEFAULT(" + std::to_string(static_cast<int>(/*ScrobblingState::PendingAdd*/ 0)) + ")");
} }
static void migrateFromV35(Session& session) static void migrateFromV35(Session& session)
@@ -252,7 +255,7 @@ CREATE TABLE IF NOT EXISTS "track_backup" (
void migrateFromV45(Session& session) void migrateFromV45(Session& session)
{ {
// add subsonic_enable_transcoding_by_default, default is disabled // 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) 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") session.getDboSession()->execute(R"(INSERT INTO "media_library" ("id", "version", "path", "name")
SELECT 1, 0, s_s.media_directory, "Main" SELECT 1, 0, s_s.media_directory, "Main"
FROM scan_settings s_s FROM scan_settings s_s
WHERE id = ?)").bind(scanSettingsId); WHERE id = ?)")
.bind(scanSettingsId);
// Remove the outdated column in scan_settings // Remove the outdated column in scan_settings
session.getDboSession()->execute("ALTER TABLE scan_settings DROP media_directory"); session.getDboSession()->execute("ALTER TABLE scan_settings DROP media_directory");
@@ -480,34 +484,33 @@ SELECT
using MigrationFunction = std::function<void(Session&)>; using MigrationFunction = std::function<void(Session&)>;
const std::map<unsigned, MigrationFunction> migrationFunctions const std::map<unsigned, MigrationFunction> migrationFunctions{
{ { 33, migrateFromV33 },
{33, migrateFromV33}, { 34, migrateFromV34 },
{34, migrateFromV34}, { 35, migrateFromV35 },
{35, migrateFromV35}, { 36, migrateFromV36 },
{36, migrateFromV36}, { 37, migrateFromV37 },
{37, migrateFromV37}, { 38, migrateFromV38 },
{38, migrateFromV38}, { 39, migrateFromV39 },
{39, migrateFromV39}, { 40, migrateFromV40 },
{40, migrateFromV40}, { 41, migrateFromV41 },
{41, migrateFromV41}, { 42, migrateFromV42 },
{42, migrateFromV42}, { 43, migrateFromV43 },
{43, migrateFromV43}, { 44, migrateFromV44 },
{44, migrateFromV44}, { 45, migrateFromV45 },
{45, migrateFromV45}, { 46, migrateFromV46 },
{46, migrateFromV46}, { 47, migrateFromV47 },
{47, migrateFromV47}, { 48, migrateFromV48 },
{48, migrateFromV48}, { 49, migrateFromV49 },
{49, migrateFromV49}, { 50, migrateFromV50 },
{50, migrateFromV50}, { 51, migrateFromV51 },
{51, migrateFromV51}, { 52, migrateFromV52 },
{52, migrateFromV52}, { 53, migrateFromV53 },
{53, migrateFromV53}, { 54, migrateFromV54 },
{54, migrateFromV54}, { 55, migrateFromV55 },
{55, migrateFromV55}, { 56, migrateFromV56 },
{56, migrateFromV56}, { 57, migrateFromV57 },
{57, migrateFromV57}, { 58, migrateFromV58 },
{58, migrateFromV58},
}; };
bool migrationPerformed{}; bool migrationPerformed{};
@@ -551,4 +554,4 @@ SELECT
return migrationPerformed; 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 bool doDbMigration(Session& session); // return true if migration was performed
} }
} } // namespace lms::db
+3 -3
View File
@@ -19,8 +19,9 @@
#pragma once #pragma once
#include <string>
#include <filesystem> #include <filesystem>
#include <string>
#include <Wt/Dbo/SqlTraits.h> #include <Wt/Dbo/SqlTraits.h>
namespace Wt::Dbo namespace Wt::Dbo
@@ -50,5 +51,4 @@ namespace Wt::Dbo
return true; return true;
} }
}; };
} } // namespace Wt::Dbo
+72 -96
View File
@@ -21,15 +21,16 @@
#include <Wt/Dbo/WtSqlTraits.h> #include <Wt/Dbo/WtSqlTraits.h>
#include "core/ILogger.hpp"
#include "database/Artist.hpp" #include "database/Artist.hpp"
#include "database/Cluster.hpp" #include "database/Cluster.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "database/User.hpp" #include "database/User.hpp"
#include "core/ILogger.hpp"
#include "SqlQuery.hpp"
#include "EnumSetTraits.hpp" #include "EnumSetTraits.hpp"
#include "IdTypeTraits.hpp" #include "IdTypeTraits.hpp"
#include "SqlQuery.hpp"
#include "StringViewTraits.hpp" #include "StringViewTraits.hpp"
#include "Utils.hpp" #include "Utils.hpp"
@@ -37,7 +38,7 @@ namespace lms::db
{ {
namespace namespace
{ {
template <typename ResultType> template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Release::FindParameters& params) 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") }; 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_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") 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()) if (params.writtenAfter.isValid())
@@ -82,9 +84,12 @@ namespace lms::db
{ {
assert(params.feedbackBackend); assert(params.feedbackBackend);
query.join("starred_release s_r ON s_r.release_id = r.id") query.join("starred_release s_r ON s_r.release_id = r.id")
.where("s_r.user_id = ?").bind(params.starringUser) .where("s_r.user_id = ?")
.where("s_r.backend = ?").bind(*params.feedbackBackend) .bind(params.starringUser)
.where("s_r.sync_state <> ?").bind(SyncState::PendingRemove); .where("s_r.backend = ?")
.bind(*params.feedbackBackend)
.where("s_r.sync_state <> ?")
.bind(SyncState::PendingRemove);
} }
if (params.artist.isValid() if (params.artist.isValid()
@@ -119,9 +124,9 @@ namespace lms::db
{ {
std::ostringstream oss; std::ostringstream oss;
oss << "r.id NOT IN (SELECT DISTINCT r.id FROM release r" 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_artist_link t_a_l ON t_a_l.track_id = t.id"
" INNER JOIN track t ON t.release_id = r.id" " INNER JOIN track t ON t.release_id = r.id"
" WHERE (t_a_l.artist_id = ? AND ("; " WHERE (t_a_l.artist_id = ? AND (";
query.bind(params.artist); query.bind(params.artist);
@@ -143,13 +148,14 @@ namespace lms::db
if (params.clusters.size() == 1) if (params.clusters.size() == 1)
{ {
query.join("track_cluster t_c ON t_c.track_id = t.id") 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) else if (params.clusters.size() > 1)
{ {
std::ostringstream oss; std::ostringstream oss;
oss << "r.id IN (SELECT DISTINCT t.release_id FROM track t" 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; WhereClause clusterClause;
for (const ClusterId clusterId : params.clusters) for (const ClusterId clusterId : params.clusters)
@@ -200,69 +206,62 @@ namespace lms::db
return query; return query;
} }
} } // namespace
ReleaseType::ReleaseType(std::string_view name) 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) 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) ReleaseType::pointer ReleaseType::find(Session& session, ReleaseTypeId id)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<ReleaseType>() return utils::fetchQuerySingleResult(session.getDboSession()->find<ReleaseType>().where("id = ?").bind(id));
.where("id = ?").bind(id));
} }
ReleaseType::pointer ReleaseType::find(Session& session, std::string_view name) ReleaseType::pointer ReleaseType::find(Session& session, std::string_view name)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<ReleaseType>() return utils::fetchQuerySingleResult(session.getDboSession()->find<ReleaseType>().where("name = ?").bind(name));
.where("name = ?").bind(name));
} }
Release::Release(const std::string& name, const std::optional<core::UUID>& MBID) Release::Release(const std::string& name, const std::optional<core::UUID>& MBID)
: _name{ std::string(name, 0 , _maxNameLength) }, : _name{ std::string(name, 0, _maxNameLength) }
_MBID{ MBID ? MBID->getAsString() : "" } , _MBID{ MBID ? MBID->getAsString() : "" }
{ {
} }
Release::pointer Release::create(Session& session, const std::string& name, const std::optional<core::UUID>& MBID) 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) std::vector<Release::pointer> Release::find(Session& session, const std::string& name, const std::filesystem::path& releaseDirectory)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQueryResults<Release::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Release>>("SELECT DISTINCT r from release r") 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()) + "%"));
.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) Release::pointer Release::find(Session& session, const core::UUID& mbid)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<Release>() return utils::fetchQuerySingleResult(session.getDboSession()->find<Release>().where("mbid = ?").bind(mbid.getAsString()));
.where("mbid = ?").bind(mbid.getAsString()));
} }
Release::pointer Release::find(Session& session, ReleaseId id) Release::pointer Release::find(Session& session, ReleaseId id)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<Release>() return utils::fetchQuerySingleResult(session.getDboSession()->find<Release>().where("id = ?").bind(id));
.where("id = ?").bind(id));
} }
bool Release::exists(Session& session, ReleaseId id) bool Release::exists(Session& session, ReleaseId id)
@@ -290,10 +289,7 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Release>>("SELECT r FROM release r") 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)) };
.orderBy("r.id")
.where("r.id > ?").bind(lastRetrievedRelease)
.limit(static_cast<int>(count)) };
if (library.isValid()) 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); 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) utils::forEachQueryResult(query, [&](const Release::pointer& release) {
{ func(release);
func(release); lastRetrievedRelease = release->getId();
lastRetrievedRelease = release->getId(); });
});
} }
RangeResults<Release::pointer> Release::find(Session& session, const FindParameters& params) RangeResults<Release::pointer> Release::find(Session& session, const FindParameters& params)
@@ -342,8 +337,7 @@ namespace lms::db
std::size_t Release::getDiscCount() const std::size_t Release::getDiscCount() const
{ {
assert(session()); assert(session());
int res{ utils::fetchQuerySingleResult(session()->query<int>("SELECT COUNT(DISTINCT disc_number) FROM track t") int res{ utils::fetchQuerySingleResult(session()->query<int>("SELECT COUNT(DISTINCT disc_number) FROM track t").where("t.release_id = ?").bind(getId())) };
.where("t.release_id = ?").bind(getId()))};
return res; return res;
} }
@@ -352,15 +346,12 @@ namespace lms::db
assert(session()); assert(session());
using ResultType = std::tuple<int, std::string>; using ResultType = std::tuple<int, std::string>;
const auto query{ session()->query<ResultType>("SELECT DISTINCT disc_number, disc_subtitle FROM track t") const auto query{ session()->query<ResultType>("SELECT DISTINCT disc_number, disc_subtitle FROM track t").where("t.release_id = ?").bind(getId()).orderBy("disc_number") };
.where("t.release_id = ?").bind(getId())
.orderBy("disc_number")};
std::vector<DiscInfo> discs; std::vector<DiscInfo> discs;
utils::forEachQueryResult(query, [&](ResultType&& 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)) });
discs.emplace_back(DiscInfo{ static_cast<std::size_t>(std::get<int>(res)), std::move(std::get<std::string>(res)) }); });
});
return discs; return discs;
} }
@@ -380,10 +371,7 @@ namespace lms::db
assert(session()); assert(session());
const char* field{ original ? "original_date" : "date" }; const char* field{ original ? "original_date" : "date" };
auto query{ (session()->query<Wt::WDate>(std::string {"SELECT "} + "t." + field + " FROM track t") auto query{ (session()->query<Wt::WDate>(std::string{ "SELECT " } + "t." + field + " FROM track t").where("t.release_id = ?").groupBy(field).bind(getId())) };
.where("t.release_id = ?")
.groupBy(field)
.bind(getId())) };
const auto dates{ utils::fetchQueryResults(query) }; const auto dates{ utils::fetchQueryResults(query) };
@@ -409,9 +397,7 @@ namespace lms::db
assert(session()); assert(session());
const char* field{ original ? "original_year" : "year" }; const char* field{ original ? "original_year" : "year" };
auto query{ session()->query<std::optional<int>>(std::string {"SELECT "} + "t." + field + " FROM track t") auto query{ session()->query<std::optional<int>>(std::string{ "SELECT " } + "t." + field + " FROM track t").where("t.release_id = ?").bind(getId()).groupBy(field) };
.where("t.release_id = ?").bind(getId())
.groupBy(field) };
const auto years{ utils::fetchQueryResults(query) }; const auto years{ utils::fetchQueryResults(query) };
@@ -427,10 +413,7 @@ namespace lms::db
{ {
assert(session()); assert(session());
auto query{ session()->query<std::string>("SELECT copyright FROM track t INNER JOIN release r ON r.id = t.release_id") 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()) };
.where("r.id = ?")
.groupBy("copyright")
.bind(getId()) };
const auto copyrights{ utils::fetchQueryResults(query) }; const auto copyrights{ utils::fetchQueryResults(query) };
@@ -445,10 +428,7 @@ namespace lms::db
{ {
assert(session()); assert(session());
const auto query{ session()->query<std::string> 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") };
("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) }; const auto copyrights{ utils::fetchQueryResults(query) };
@@ -463,9 +443,7 @@ namespace lms::db
{ {
assert(session()); assert(session());
return utils::fetchQuerySingleResult(session()->query<int>("SELECT COALESCE(AVG(t.bitrate), 0) FROM track t") return utils::fetchQuerySingleResult(session()->query<int>("SELECT COALESCE(AVG(t.bitrate), 0) FROM track t").where("release_id = ?").bind(getId()).where("bitrate > 0"));
.where("release_id = ?").bind(getId())
.where("bitrate > 0"));
} }
std::vector<Artist::pointer> Release::getArtists(TrackArtistLinkType linkType) const std::vector<Artist::pointer> Release::getArtists(TrackArtistLinkType linkType) const
@@ -473,12 +451,14 @@ namespace lms::db
assert(session()); assert(session());
const auto query{ session()->query<Wt::Dbo::ptr<Artist>>( const auto query{ session()->query<Wt::Dbo::ptr<Artist>>(
"SELECT a FROM artist a" "SELECT a FROM artist a"
" INNER JOIN track_artist_link t_a_l ON t_a_l.artist_id = a.id" " 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 t ON t.id = t_a_l.track_id")
.where("t.release_id = ?").bind(getId()) .where("t.release_id = ?")
.where("+t_a_l.type = ?").bind(linkType) // adding + since the query planner does not a good job when analyze is not performed .bind(getId())
.groupBy("a.id") }; .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); 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 // Select the similar releases using the 5 most used clusters of the release
auto query{ session()->query<Wt::Dbo::ptr<Release>>( auto query{ session()->query<Wt::Dbo::ptr<Release>>(
"SELECT r FROM release r" "SELECT r FROM release r"
" INNER JOIN track t ON t.release_id = r.id" " INNER JOIN track t ON t.release_id = r.id"
" 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"
" WHERE " " WHERE "
" t_c.cluster_id IN " " t_c.cluster_id IN "
"(SELECT DISTINCT c.id FROM cluster c" "(SELECT DISTINCT c.id FROM cluster c"
" INNER JOIN track t ON c.id = t_c.cluster_id" " 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 track_cluster t_c ON t_c.track_id = t.id"
" INNER JOIN release r ON r.id = t.release_id" " INNER JOIN release r ON r.id = t.release_id"
" WHERE r.id = ?)" " WHERE r.id = ?)"
" AND r.id <> ?" " AND r.id <> ?")
) .bind(getId())
.bind(getId()) .bind(getId())
.bind(getId()) .groupBy("r.id")
.groupBy("r.id") .orderBy("COUNT(*) DESC, RANDOM()")
.orderBy("COUNT(*) DESC, RANDOM()") .limit(count ? static_cast<int>(*count) : -1)
.limit(count ? static_cast<int>(*count) : -1) .offset(offset ? static_cast<int>(*offset) : -1) };
.offset(offset ? static_cast<int>(*offset) : -1) };
return utils::fetchQueryResults<Release::pointer>(query); return utils::fetchQueryResults<Release::pointer>(query);
} }
@@ -530,8 +509,7 @@ namespace lms::db
std::size_t Release::getTrackCount() const std::size_t Release::getTrackCount() const
{ {
assert(session()); 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") 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()));
.where("r.id = ?").bind(getId()));
} }
std::vector<ReleaseType::pointer> Release::getReleaseTypes() const std::vector<ReleaseType::pointer> Release::getReleaseTypes() const
@@ -556,16 +534,14 @@ namespace lms::db
using milli = std::chrono::duration<int, std::milli>; using milli = std::chrono::duration<int, std::milli>;
return utils::fetchQuerySingleResult(session()->query<milli>("SELECT COALESCE(SUM(duration), 0) FROM track t") return utils::fetchQuerySingleResult(session()->query<milli>("SELECT COALESCE(SUM(duration), 0) FROM track t").where("t.release_id = ?").bind(getId()));
.where("t.release_id = ?").bind(getId()));
} }
Wt::WDateTime Release::getLastWritten() const Wt::WDateTime Release::getLastWritten() const
{ {
assert(session()); assert(session());
return utils::fetchQuerySingleResult(session()->query<Wt::WDateTime>("SELECT COALESCE(MAX(file_last_write), '1970-01-01T00:00:00') FROM track t") 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()));
.where("t.release_id = ?").bind(getId()));
} }
std::vector<std::vector<Cluster::pointer>> Release::getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const 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 <Wt/Dbo/WtSqlTraits.h>
#include "core/String.hpp"
#include "database/MediaLibrary.hpp" #include "database/MediaLibrary.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "core/String.hpp"
#include "Utils.hpp" #include "Utils.hpp"
namespace lms::db namespace lms::db
@@ -71,7 +72,7 @@ namespace lms::db
return core::stringUtils::splitEscapedStrings(_defaultTagDelimiters, ';', '\\'); 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, ";") }; std::string newTagsToScan{ core::stringUtils::joinStrings(extraTags, ";") };
if (newTagsToScan != _extraTagsToScan) if (newTagsToScan != _extraTagsToScan)
+11 -8
View File
@@ -19,12 +19,9 @@
#include "database/Session.hpp" #include "database/Session.hpp"
#include <cassert>
#include "core/Exception.hpp" #include "core/Exception.hpp"
#include "core/ILogger.hpp" #include "core/ILogger.hpp"
#include "core/ITraceLogger.hpp" #include "core/ITraceLogger.hpp"
#include "database/Artist.hpp" #include "database/Artist.hpp"
#include "database/AuthToken.hpp" #include "database/AuthToken.hpp"
#include "database/Cluster.hpp" #include "database/Cluster.hpp"
@@ -37,22 +34,23 @@
#include "database/StarredRelease.hpp" #include "database/StarredRelease.hpp"
#include "database/StarredTrack.hpp" #include "database/StarredTrack.hpp"
#include "database/Track.hpp" #include "database/Track.hpp"
#include "database/TrackBookmark.hpp"
#include "database/TrackArtistLink.hpp" #include "database/TrackArtistLink.hpp"
#include "database/TrackList.hpp" #include "database/TrackBookmark.hpp"
#include "database/TrackFeatures.hpp" #include "database/TrackFeatures.hpp"
#include "database/TrackList.hpp"
#include "database/TransactionChecker.hpp" #include "database/TransactionChecker.hpp"
#include "database/User.hpp" #include "database/User.hpp"
#include "EnumSetTraits.hpp" #include "EnumSetTraits.hpp"
#include "PathTraits.hpp"
#include "Migration.hpp" #include "Migration.hpp"
#include "PathTraits.hpp"
#include "Utils.hpp" #include "Utils.hpp"
namespace lms::db namespace lms::db
{ {
WriteTransaction::WriteTransaction(core::RecursiveSharedMutex& mutex, Wt::Dbo::Session& session) WriteTransaction::WriteTransaction(core::RecursiveSharedMutex& mutex, Wt::Dbo::Session& session)
: _lock{ mutex }, : _lock{ mutex }
_transaction{ session } , _transaction{ session }
{ {
#if LMS_CHECK_TRANSACTION_ACCESSES #if LMS_CHECK_TRANSACTION_ACCESSES
TransactionChecker::pushWriteTransaction(_transaction.session()); TransactionChecker::pushWriteTransaction(_transaction.session());
@@ -121,6 +119,11 @@ namespace lms::db
return ReadTransaction{ _session }; return ReadTransaction{ _session };
} }
void Session::execute(std::string_view statement)
{
_session.execute(std::string{ statement });
}
void Session::prepareTablesIfNeeded() void Session::prepareTablesIfNeeded()
{ {
LMS_LOG(DB, INFO, "Preparing tables..."); LMS_LOG(DB, INFO, "Preparing tables...");
+8 -5
View File
@@ -27,7 +27,8 @@ namespace lms::db
{ {
WhereClause& WhereClause::And(const WhereClause& otherClause) WhereClause& WhereClause::And(const WhereClause& otherClause)
{ {
if (!otherClause._clause.empty()) { if (!otherClause._clause.empty())
{
if (!_clause.empty()) if (!_clause.empty())
_clause += " AND "; _clause += " AND ";
_clause += "(" + otherClause._clause + ")"; _clause += "(" + otherClause._clause + ")";
@@ -43,7 +44,8 @@ namespace lms::db
WhereClause& WhereClause::Or(const WhereClause& otherClause) WhereClause& WhereClause::Or(const WhereClause& otherClause)
{ {
if (!otherClause._clause.empty()) { if (!otherClause._clause.empty())
{
if (!_clause.empty()) if (!_clause.empty())
_clause += " OR "; _clause += " OR ";
_clause += "(" + otherClause._clause + ")"; _clause += "(" + otherClause._clause + ")";
@@ -75,7 +77,7 @@ namespace lms::db
} }
InnerJoinClause::InnerJoinClause(const std::string& clause) InnerJoinClause::InnerJoinClause(const std::string& clause)
:_clause(clause) : _clause(clause)
{ {
} }
@@ -154,7 +156,8 @@ namespace lms::db
if (!_clause.empty()) if (!_clause.empty())
{ {
oss << "FROM "; oss << "FROM ";
for (auto it = _clause.begin(); it != _clause.end(); ++it) { for (auto it = _clause.begin(); it != _clause.end(); ++it)
{
if (it != _clause.begin()) if (it != _clause.begin())
oss << ","; oss << ",";
@@ -185,4 +188,4 @@ namespace lms::db
return oss.str(); return oss.str();
} }
} } // namespace lms::db
+23 -15
View File
@@ -19,8 +19,8 @@
#pragma once #pragma once
#include <vector>
#include <string> #include <string>
#include <vector>
namespace lms::db namespace lms::db
{ {
@@ -40,8 +40,8 @@ namespace lms::db
const std::vector<std::string>& getBindArgs() const { return _bindArgs; } const std::vector<std::string>& getBindArgs() const { return _bindArgs; }
private: private:
std::string _clause; // WHERE clause std::string _clause; // WHERE clause
std::vector<std::string> _bindArgs; std::vector<std::string> _bindArgs;
}; };
class InnerJoinClause class InnerJoinClause
@@ -68,13 +68,13 @@ namespace lms::db
std::string get() const { return _statement; } std::string get() const { return _statement; }
private: private:
std::string _statement; // SELECT statement std::string _statement; // SELECT statement
}; };
class SelectStatement class SelectStatement
{ {
public: public:
SelectStatement() {}; SelectStatement(){};
SelectStatement(const std::string& item); SelectStatement(const std::string& item);
SelectStatement& And(const std::string& item); SelectStatement& And(const std::string& item);
@@ -82,7 +82,7 @@ namespace lms::db
std::string get() const; std::string get() const;
private: private:
std::vector<std::string> _statement; std::vector<std::string> _statement;
}; };
class FromClause class FromClause
@@ -96,16 +96,24 @@ namespace lms::db
std::string get() const; std::string get() const;
private: private:
std::vector<std::string> _clause; std::vector<std::string> _clause;
}; };
class SqlQuery class SqlQuery
{ {
public: public:
SelectStatement& select() { return _selectStatement; } 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() { 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; } InnerJoinClause& innerJoin() { return _innerJoinClause; }
WhereClause& where() { return _whereClause; } WhereClause& where() { return _whereClause; }
const WhereClause& where() const { return _whereClause; } const WhereClause& where() const { return _whereClause; }
@@ -115,10 +123,10 @@ namespace lms::db
std::string get() const; std::string get() const;
private: private:
SelectStatement _selectStatement; // SELECT statement SelectStatement _selectStatement; // SELECT statement
InnerJoinClause _innerJoinClause; // INNER JOIN InnerJoinClause _innerJoinClause; // INNER JOIN
FromClause _fromClause; // FROM tables FromClause _fromClause; // FROM tables
WhereClause _whereClause; // WHERE clause WhereClause _whereClause; // WHERE clause
GroupByStatement _groupByStatement; // GROUP BY statement GroupByStatement _groupByStatement; // GROUP BY statement
}; };
} } // namespace lms::db
+5 -11
View File
@@ -24,6 +24,7 @@
#include "database/Artist.hpp" #include "database/Artist.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/User.hpp" #include "database/User.hpp"
#include "IdTypeTraits.hpp" #include "IdTypeTraits.hpp"
#include "Utils.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) 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) std::size_t StarredArtist::getCount(Session& session)
@@ -56,24 +57,17 @@ namespace lms::db
StarredArtist::pointer StarredArtist::find(Session& session, ArtistId artistId, UserId userId) StarredArtist::pointer StarredArtist::find(Session& session, ArtistId artistId, UserId userId)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<StarredArtist>>("SELECT s_a from starred_artist s_a") 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"));
.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) StarredArtist::pointer StarredArtist::find(Session& session, ArtistId artistId, UserId userId, FeedbackBackend backend)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<StarredArtist>() return utils::fetchQuerySingleResult(session.getDboSession()->find<StarredArtist>().where("artist_id = ?").bind(artistId).where("user_id = ?").bind(userId).where("backend = ?").bind(backend));
.where("artist_id = ?").bind(artistId)
.where("user_id = ?").bind(userId)
.where("backend = ?").bind(backend));
} }
void StarredArtist::setDateTime(const Wt::WDateTime& dateTime) void StarredArtist::setDateTime(const Wt::WDateTime& dateTime)
{ {
_dateTime = utils::normalizeDateTime(dateTime); _dateTime = utils::normalizeDateTime(dateTime);
} }
} } // namespace lms::db
+5 -11
View File
@@ -24,6 +24,7 @@
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/User.hpp" #include "database/User.hpp"
#include "IdTypeTraits.hpp" #include "IdTypeTraits.hpp"
#include "Utils.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) 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) std::size_t StarredRelease::getCount(Session& session)
@@ -56,24 +57,17 @@ namespace lms::db
StarredRelease::pointer StarredRelease::find(Session& session, ReleaseId releaseId, UserId userId) StarredRelease::pointer StarredRelease::find(Session& session, ReleaseId releaseId, UserId userId)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<StarredRelease>>("SELECT s_r from starred_release s_r") 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"));
.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) StarredRelease::pointer StarredRelease::find(Session& session, ReleaseId releaseId, UserId userId, FeedbackBackend backend)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<StarredRelease>() return utils::fetchQuerySingleResult(session.getDboSession()->find<StarredRelease>().where("release_id = ?").bind(releaseId).where("user_id = ?").bind(userId).where("backend = ?").bind(backend));
.where("release_id = ?").bind(releaseId)
.where("user_id = ?").bind(userId)
.where("backend = ?").bind(backend));
} }
void StarredRelease::setDateTime(const Wt::WDateTime& dateTime) void StarredRelease::setDateTime(const Wt::WDateTime& dateTime)
{ {
_dateTime = utils::normalizeDateTime(dateTime); _dateTime = utils::normalizeDateTime(dateTime);
} }
} } // namespace lms::db
+7 -16
View File
@@ -21,9 +21,10 @@
#include <Wt/Dbo/WtSqlTraits.h> #include <Wt/Dbo/WtSqlTraits.h>
#include "database/Track.hpp"
#include "database/Session.hpp" #include "database/Session.hpp"
#include "database/Track.hpp"
#include "database/User.hpp" #include "database/User.hpp"
#include "IdTypeTraits.hpp" #include "IdTypeTraits.hpp"
#include "Utils.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) 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) std::size_t StarredTrack::getCount(Session& session)
@@ -56,28 +57,18 @@ namespace lms::db
StarredTrack::pointer StarredTrack::find(Session& session, TrackId trackId, UserId userId) StarredTrack::pointer StarredTrack::find(Session& session, TrackId trackId, UserId userId)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<StarredTrack>>("SELECT s_t from starred_track s_t") 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"));
.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) StarredTrack::pointer StarredTrack::find(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<StarredTrack>() return utils::fetchQuerySingleResult(session.getDboSession()->find<StarredTrack>().where("track_id = ?").bind(trackId).where("user_id = ?").bind(userId).where("backend = ?").bind(backend));
.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) bool StarredTrack::exists(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend)
{ {
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT 1 from starred_track") 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));
.where("track_id = ?").bind(trackId)
.where("user_id = ?").bind(userId)
.where("backend = ?").bind(backend));
} }
RangeResults<StarredTrackId> StarredTrack::find(Session& session, const FindParameters& params) RangeResults<StarredTrackId> StarredTrack::find(Session& session, const FindParameters& params)
@@ -100,4 +91,4 @@ namespace lms::db
{ {
_dateTime = utils::normalizeDateTime(dateTime); _dateTime = utils::normalizeDateTime(dateTime);
} }
} } // namespace lms::db
+2 -2
View File
@@ -20,6 +20,7 @@
#pragma once #pragma once
#include <string_view> #include <string_view>
#include <Wt/Dbo/SqlTraits.h> #include <Wt/Dbo/SqlTraits.h>
namespace Wt::Dbo namespace Wt::Dbo
@@ -32,5 +33,4 @@ namespace Wt::Dbo
statement->bind(column, std::string{ str }); statement->bind(column, std::string{ str });
} }
}; };
} } // namespace Wt::Dbo
+43 -53
View File
@@ -21,15 +21,15 @@
#include <Wt/Dbo/WtSqlTraits.h> #include <Wt/Dbo/WtSqlTraits.h>
#include "core/ILogger.hpp"
#include "database/Artist.hpp" #include "database/Artist.hpp"
#include "database/Cluster.hpp" #include "database/Cluster.hpp"
#include "database/MediaLibrary.hpp" #include "database/MediaLibrary.hpp"
#include "database/Release.hpp" #include "database/Release.hpp"
#include "database/Session.hpp"
#include "database/TrackArtistLink.hpp" #include "database/TrackArtistLink.hpp"
#include "database/TrackFeatures.hpp" #include "database/TrackFeatures.hpp"
#include "database/Session.hpp"
#include "database/User.hpp" #include "database/User.hpp"
#include "core/ILogger.hpp"
#include "IdTypeTraits.hpp" #include "IdTypeTraits.hpp"
#include "PathTraits.hpp" #include "PathTraits.hpp"
@@ -41,7 +41,7 @@ namespace lms::db
{ {
namespace namespace
{ {
template <typename ResultType> template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Track::FindParameters& params) Wt::Dbo::Query<ResultType> createQuery(Session& session, std::string_view itemToSelect, const Track::FindParameters& params)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
@@ -62,22 +62,26 @@ namespace lms::db
{ {
assert(params.feedbackBackend); assert(params.feedbackBackend);
query.join("starred_track s_t ON s_t.track_id = t.id") query.join("starred_track s_t ON s_t.track_id = t.id")
.where("s_t.user_id = ?").bind(params.starringUser) .where("s_t.user_id = ?")
.where("s_t.backend = ?").bind(*params.feedbackBackend) .bind(params.starringUser)
.where("s_t.sync_state <> ?").bind(SyncState::PendingRemove); .where("s_t.backend = ?")
.bind(*params.feedbackBackend)
.where("s_t.sync_state <> ?")
.bind(SyncState::PendingRemove);
} }
if (params.clusters.size() == 1) if (params.clusters.size() == 1)
{ {
// optim // optim
query.join("track_cluster t_c ON t_c.track_id = t.id") 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) else if (params.clusters.size() > 1)
{ {
std::ostringstream oss; std::ostringstream oss;
oss << "t.id IN (SELECT DISTINCT t.id FROM track t" 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; WhereClause clusterClause;
for (const ClusterId clusterId : params.clusters) for (const ClusterId clusterId : params.clusters)
@@ -183,7 +187,7 @@ namespace lms::db
return query; return query;
} }
template <typename ResultType> template<typename ResultType>
Wt::Dbo::Query<ResultType> createQuery(Session& session, const Track::FindParameters& params) Wt::Dbo::Query<ResultType> createQuery(Session& session, const Track::FindParameters& params)
{ {
std::string_view itemToSelect; std::string_view itemToSelect;
@@ -197,7 +201,7 @@ namespace lms::db
return createQuery<ResultType>(session, itemToSelect, params); return createQuery<ResultType>(session, itemToSelect, params);
} }
} } // namespace
Track::pointer Track::create(Session& session) Track::pointer Track::create(Session& session)
{ {
@@ -222,27 +226,22 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQuerySingleResult(session.getDboSession()->find<Track>() return utils::fetchQuerySingleResult(session.getDboSession()->find<Track>().where("id = ?").bind(id));
.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) void Track::find(Session& session, TrackId& lastRetrievedTrack, std::size_t count, const std::function<void(const Track::pointer&)>& func, MediaLibraryId library)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
auto query{ session.getDboSession()->find<Track>() auto query{ session.getDboSession()->find<Track>().orderBy("id").where("id > ?").bind(lastRetrievedTrack).limit(static_cast<int>(count)) };
.orderBy("id")
.where("id > ?").bind(lastRetrievedTrack)
.limit(static_cast<int>(count)) };
if (library.isValid()) if (library.isValid())
query.where("media_library_id = ?").bind(library); query.where("media_library_id = ?").bind(library);
utils::forEachQueryResult(query, [&](const Track::pointer& track) utils::forEachQueryResult(query, [&](const Track::pointer& track) {
{ func(track);
func(track); lastRetrievedTrack = track->getId();
lastRetrievedTrack = track->getId(); });
});
} }
bool Track::exists(Session& session, TrackId id) bool Track::exists(Session& session, TrackId id)
@@ -256,24 +255,21 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->find<Track>() return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->find<Track>().where("mbid = ?").bind(mbid.getAsString()));
.where("mbid = ?").bind(mbid.getAsString()));
} }
std::vector<Track::pointer> Track::findByRecordingMBID(Session& session, const core::UUID& mbid) std::vector<Track::pointer> Track::findByRecordingMBID(Session& session, const core::UUID& mbid)
{ {
session.checkReadTransaction(); session.checkReadTransaction();
return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->find<Track>() return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->find<Track>().where("recording_mbid = ?").bind(mbid.getAsString()));
.where("recording_mbid = ?").bind(mbid.getAsString()));
} }
RangeResults<TrackId> Track::findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range) RangeResults<TrackId> Track::findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range)
{ {
session.checkReadTransaction(); 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)") 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") };
.orderBy("track.release_id,track.disc_number,track.track_number,track.mbid") };
return utils::execRangeQuery<TrackId>(query, range); return utils::execRangeQuery<TrackId>(query, range);
} }
@@ -282,9 +278,7 @@ namespace lms::db
{ {
session.checkReadTransaction(); session.checkReadTransaction();
auto query{ session.getDboSession()->query<TrackId>("SELECT t.id FROM track t") 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)") };
.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); return utils::execRangeQuery<TrackId>(query, range);
} }
@@ -298,10 +292,7 @@ namespace lms::db
{ {
assert(session()); assert(session());
const auto query{ session()->query<ClusterId> 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") };
("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); return utils::fetchQueryResults(query);
} }
@@ -352,12 +343,14 @@ namespace lms::db
} }
auto query{ session.getDboSession()->query<TrackId>( auto query{ session.getDboSession()->query<TrackId>(
"SELECT t.id FROM track t" "SELECT 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"
" 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_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 ("
" AND t.id NOT IN (" + oss.str() + ")") + oss.str() + "))"
.groupBy("t.id") " AND t.id NOT IN ("
.orderBy("COUNT(*) DESC, RANDOM()") }; + oss.str() + ")")
.groupBy("t.id")
.orderBy("COUNT(*) DESC, RANDOM()") };
for (TrackId trackId : tracks) for (TrackId trackId : tracks)
query.bind(trackId); query.bind(trackId);
@@ -412,10 +405,9 @@ namespace lms::db
assert(session()); assert(session());
std::ostringstream oss; std::ostringstream oss;
oss << oss << "SELECT a from artist a"
"SELECT a from artist a" " INNER JOIN track_artist_link t_a_l ON a.id = t_a_l.artist_id"
" 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";
" INNER JOIN track t ON t.id = t_a_l.track_id";
if (!linkTypes.empty()) if (!linkTypes.empty())
{ {
@@ -447,9 +439,8 @@ namespace lms::db
assert(session()); assert(session());
std::ostringstream oss; std::ostringstream oss;
oss << oss << "SELECT t_a_l.artist_id FROM track_artist_link t_a_l"
"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";
" INNER JOIN track t ON t.id = t_a_l.track_id";
if (!linkTypes.empty()) if (!linkTypes.empty())
{ {
@@ -507,11 +498,10 @@ namespace lms::db
query.bind(bindArg); query.bind(bindArg);
std::map<ClusterTypeId, std::vector<Cluster::pointer>> clusters; std::map<ClusterTypeId, std::vector<Cluster::pointer>> clusters;
utils::forEachQueryResult(query, [&](const Cluster::pointer& cluster) utils::forEachQueryResult(query, [&](const Cluster::pointer& cluster) {
{ if (clusters[cluster->getType()->getId()].size() < size)
if (clusters[cluster->getType()->getId()].size() < size) clusters[cluster->getType()->getId()].push_back(cluster);
clusters[cluster->getType()->getId()].push_back(cluster); });
});
std::vector<std::vector<Cluster::pointer>> res; std::vector<std::vector<Cluster::pointer>> res;
for (const auto& [type, clusters] : clusters) for (const auto& [type, clusters] : clusters)
@@ -545,6 +535,6 @@ namespace lms::db
return os; return os;
} }
} } // namespace Debug
} // namespace lms::db } // namespace lms::db

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