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