Merge branch 'develop' for release v3.79.0
This commit is contained in:
+1
-1
@@ -1,6 +1,6 @@
|
||||
cmake_minimum_required(VERSION 3.12)
|
||||
|
||||
project(lms VERSION 3.78.0)
|
||||
project(lms VERSION 3.79.0)
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake/modules/)
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ A [demo instance](http://lms-demo.poupon.dev) is available. Note the administrat
|
||||
* [ListenBrainz](https://listenbrainz.org) support for:
|
||||
* Scrobbling and synchronizing listens
|
||||
* Synchronizing 'love' feedbacks
|
||||
* [Last.fm](https://www.last.fm) support for scrobbling listens
|
||||
* Recommendation engine
|
||||
* Multi-library support
|
||||
* ReplayGain support
|
||||
@@ -41,8 +42,8 @@ _LMS_ provides several ways to help you find the music you like:
|
||||
* ...
|
||||
|
||||
__Note__: the recommendation engine supports two modes:
|
||||
* **Audio similarity** (default): uses MusicNN embeddings. Extraction throughput depends heavily on both the number of scanner threads and the speed of your storage. Typical reported speeds range from ~1k tracks/hour on a Raspberry Pi 4 to ~25k tracks/hour on an Intel Core i5-13500.
|
||||
* **Tag-based**: uses metadata tags `genre`, `mood`, `grouping`, and `language`.
|
||||
* **Audio similarity**: uses MusicNN embeddings. Extraction throughput depends heavily on both the number of scanner threads and the speed of your storage. Typical reported speeds range from ~1k tracks/hour on a Raspberry Pi 4 (3 threads) to ~25k tracks/hour on an Intel Core i5-13500 (10 threads). To speed up extraction, consider tweaking `scanner-thread-count` in `lms.conf` (defaults to half the number of logical CPUs).
|
||||
|
||||
## About tags
|
||||
_LMS_ primarily relies on tags to organize your music collection but also supports browsing by directory using the [Subsonic/OpenSubsonic API](SUBSONIC.md).
|
||||
|
||||
+5
-7
@@ -17,13 +17,7 @@ OpenSubsonic is an initiative to patch and extend the legacy Subsonic API. You'l
|
||||
|
||||
## Authentication
|
||||
_LMS_ supports the [API Key Authentication](https://opensubsonic.netlify.app/docs/extensions/apikeyauth/) method. Each user has to generate their own API key on the settings page to use the Subsonic API.
|
||||
|
||||
By default, API keys can also be used as passwords, provided the `user` parameter matches the API key owner. To disable this fallback authentication method, set the following in `lms.conf`:
|
||||
```
|
||||
api-subsonic-support-user-password-auth = false;
|
||||
```
|
||||
|
||||
__Note__: the token+salt authentication method is not supported; use the API key as the password instead (see above).
|
||||
If a client's login screen has no dedicated API key field, enter the API key as the password instead.
|
||||
|
||||
## Extra fields
|
||||
The following extra fields are implemented:
|
||||
@@ -33,6 +27,7 @@ The following extra fields are implemented:
|
||||
* `displayArtist`
|
||||
* `explicitStatus`
|
||||
* `genres`
|
||||
* `groupings`
|
||||
* `isCompilation`
|
||||
* `played`
|
||||
* `mediaType`
|
||||
@@ -56,12 +51,15 @@ The following extra fields are implemented:
|
||||
* `displayArtist`
|
||||
* `explicitStatus`
|
||||
* `genres`
|
||||
* `groupings`
|
||||
* `mediaType`
|
||||
* `moods`
|
||||
* `movements`
|
||||
* `musicBrainzId`: note this is actually the recording MBID when this response refers to a song
|
||||
* `played`
|
||||
* `replayGain`
|
||||
* `samplingRate`
|
||||
* `works`
|
||||
* `Artist` response:
|
||||
* `mediaType`
|
||||
* `musicBrainzId`
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<message id="Lms.discard">Discard</message>
|
||||
<message id="Lms.edit">Edit</message>
|
||||
<message id="Lms.field-must-be-in-upper-case">This field must be in upper case</message>
|
||||
<message id="Lms.infinite-scrolling-container.limit-reached">Display limit reached — refine your search to see more results</message>
|
||||
<message id="Lms.infinite-scrolling-container.no-results">No results found</message>
|
||||
<message id="Lms.loading">Loading...</message>
|
||||
<message id="Lms.login">Login</message>
|
||||
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Logout</message>
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<message id="Lms.discard">Descartar</message>
|
||||
<message id="Lms.edit">Editar</message>
|
||||
<message id="Lms.field-must-be-in-upper-case">Este campo debe estar en mayúsculas</message>
|
||||
<message id="Lms.infinite-scrolling-container.limit-reached">Límite de visualización alcanzado — refine su búsqueda para ver más resultados</message>
|
||||
<message id="Lms.infinite-scrolling-container.no-results">No se han encontrado resultados</message>
|
||||
<message id="Lms.loading">Cargando...</message>
|
||||
<message id="Lms.login">Usuario</message>
|
||||
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Salir</message>
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<message id="Lms.discard">Annuler</message>
|
||||
<message id="Lms.edit">Editer</message>
|
||||
<message id="Lms.field-must-be-in-upper-case">Ce champ doit être en majuscule</message>
|
||||
<message id="Lms.infinite-scrolling-container.limit-reached">Limite d'affichage atteinte — affinez votre recherche pour voir plus de résultats</message>
|
||||
<message id="Lms.infinite-scrolling-container.no-results">Aucun résultat</message>
|
||||
<message id="Lms.loading">Chargement...</message>
|
||||
<message id="Lms.login">Login</message>
|
||||
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Quitter</message>
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<message id="Lms.discard">Annulla</message>
|
||||
<message id="Lms.edit">Modifica</message>
|
||||
<message id="Lms.field-must-be-in-upper-case">Questo campo deve essere in maiuscolo</message>
|
||||
<message id="Lms.infinite-scrolling-container.limit-reached">Limite di visualizzazione raggiunto — affina la ricerca per vedere più risultati</message>
|
||||
<message id="Lms.infinite-scrolling-container.no-results">Nessun risultato trovato</message>
|
||||
<message id="Lms.loading">Caricamento...</message>
|
||||
<message id="Lms.login">Login</message>
|
||||
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> Logout</message>
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<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.infinite-scrolling-container.limit-reached">Osiągnięto limit wyświetlania — doprecyzuj wyszukiwanie, aby zobaczyć więcej wyników</message>
|
||||
<message id="Lms.infinite-scrolling-container.no-results">Brak wyników</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>
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<message id="Lms.discard">撤销</message>
|
||||
<message id="Lms.edit">编辑</message>
|
||||
<message id="Lms.field-must-be-in-upper-case">此字段必须为大写</message>
|
||||
<message id="Lms.infinite-scrolling-container.limit-reached">已达到显示上限 — 请精简搜索条件以查看更多结果</message>
|
||||
<message id="Lms.infinite-scrolling-container.no-results">未找到结果</message>
|
||||
<message id="Lms.loading">加载中...</message>
|
||||
<message id="Lms.login">登陆</message>
|
||||
<message id="Lms.logout"><i class="fa fa-fw fa-sign-out" aria-hidden="true"></i> 注销</message>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
</div>
|
||||
</message>
|
||||
|
||||
|
||||
<message id="Lms.infinite-scrolling-container.template">
|
||||
${elements}
|
||||
${loading-indicator}
|
||||
|
||||
@@ -40,18 +40,19 @@
|
||||
<div class="p-1">
|
||||
${cover}
|
||||
</div>
|
||||
<div class="row align-items-center flex-fill overflow-hidden">
|
||||
<div class="row align-items-center flex-fill">
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<div class="p-2 overflow-hidden">
|
||||
<div class="d-block text-truncate">${name}</div>
|
||||
${<if-has-artists>}${artists class="d-block d-md-none text-truncate"}${</if-has-artists>}
|
||||
<div class="p-2">
|
||||
<div class="d-block">${name}</div>
|
||||
${<if-has-artists>}${artists class="d-block d-md-none"}${</if-has-artists>}
|
||||
${<if-has-work>}<div class="text-muted small ms-2">${work}</div>${</if-has-work>}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-4">
|
||||
${<if-has-artists>}${artists-md class="d-none d-md-block text-truncate"}${</if-has-artists>}
|
||||
${<if-has-artists>}${artists-md class="d-none d-md-block"}${</if-has-artists>}
|
||||
</div>
|
||||
<div class="col-lg-4 text-truncate">
|
||||
${<if-has-release>}${release class="d-none d-lg-inline text-truncate text-decoration-none link-success"}${</if-has-release>}
|
||||
<div class="col-lg-4">
|
||||
${<if-has-release>}${release class="d-none d-lg-inline text-decoration-none link-success"}${</if-has-release>}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2 d-none d-sm-block text-muted text-center Lms-tracks-entry-duration">
|
||||
|
||||
+28
-21
@@ -55,36 +55,43 @@
|
||||
${artist class="text-decoration-none link-success"}
|
||||
</message>
|
||||
|
||||
<message id="Lms.Explore.Release.template.entry-disc">
|
||||
<h4 class="d-flex align-items-center">
|
||||
${<if-has-artwork>}
|
||||
<div class="p-1">${artwork class="Lms-cursor-pointer"}</div>
|
||||
${</if-has-artwork>}
|
||||
<div class="p-2 flex-fill">${disc-title}</div>
|
||||
<div class="p-2 d-flex">
|
||||
${play-btn class="d-none d-sm-block btn btn-sm btn-outline-secondary border-0"}
|
||||
<div class="dropdown d-inline-block">
|
||||
${more-btn data-bs-toggle="dropdown" aria-expanded="false" class="btn btn-sm btn-outline-secondary border-0"}
|
||||
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
|
||||
<li class="d-block d-sm-none">${play class="dropdown-item"}</li>
|
||||
<li>${play-next class="dropdown-item"}</li>
|
||||
<li>${play-last class="dropdown-item"}</li>
|
||||
<li>${play-shuffled class="dropdown-item"}</li>
|
||||
</ul>
|
||||
</div>
|
||||
<message id="Lms.Explore.Release.template.segment-header">
|
||||
${<if-has-artwork>}
|
||||
<div class="p-1">${artwork class="Lms-cursor-pointer"}</div>
|
||||
${</if-has-artwork>}
|
||||
<div class="p-2 flex-fill">${title}</div>
|
||||
<div class="p-2 d-flex">
|
||||
${play-btn class="d-none d-sm-block btn btn-sm btn-outline-secondary border-0"}
|
||||
<div class="dropdown d-inline-block">
|
||||
${more-btn data-bs-toggle="dropdown" aria-expanded="false" class="btn btn-sm btn-outline-secondary border-0"}
|
||||
<ul class="dropdown-menu" aria-labelledby="${id:more-btn}">
|
||||
<li class="d-block d-sm-none">${play class="dropdown-item"}</li>
|
||||
<li>${play-next class="dropdown-item"}</li>
|
||||
<li>${play-last class="dropdown-item"}</li>
|
||||
<li>${play-shuffled class="dropdown-item"}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</h4>
|
||||
</div>
|
||||
</message>
|
||||
|
||||
<message id="Lms.Explore.Release.template.segment-disc">
|
||||
<h4>${header class="d-flex align-items-center"}</h4>
|
||||
${tracks class="d-grid gap-1"}
|
||||
</message>
|
||||
|
||||
<message id="Lms.Explore.Release.template.segment-work">
|
||||
<h5 class="text-muted">${header class="d-flex align-items-center"}</h5>
|
||||
${tracks class="d-grid gap-1 Lms-row-container"}
|
||||
</message>
|
||||
|
||||
<message id="Lms.Explore.Release.template.entry-nodisc">
|
||||
<message id="Lms.Explore.Release.template.segment-flat">
|
||||
${tracks class="d-grid gap-1 Lms-row-container"}
|
||||
</message>
|
||||
|
||||
<message id="Lms.Explore.Release.template.entry">
|
||||
<message id="Lms.Explore.Release.template.track">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="p-2 text-muted text-end Lms-tracks-entry-position">
|
||||
${<if-has-track-number>}${track-number}${</if-has-track-number>}
|
||||
${<if-has-position>}${position}${</if-has-position>}
|
||||
</div>
|
||||
<div class="row align-items-center flex-fill overflow-hidden">
|
||||
<div class="col-12 col-md-8">
|
||||
|
||||
@@ -54,11 +54,9 @@
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
${tr:Lms.Settings.subsonic-token}
|
||||
${<if-has-subsonic-token-usage>}
|
||||
<button type="button" class="btn btn-sm p-0" data-bs-toggle="tooltip" data-bs-placement="right" title="${tr:Lms.Settings.subsonic-token-usage}">
|
||||
<i class="fa fa-fw fa-info-circle" aria-hidden="true"></i>
|
||||
</button>
|
||||
${</if-has-subsonic-token-usage>}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="input-group mb-3">
|
||||
|
||||
+8
-7
@@ -32,18 +32,19 @@
|
||||
<div class="p-1">
|
||||
${cover}
|
||||
</div>
|
||||
<div class="row align-items-center flex-fill overflow-hidden">
|
||||
<div class="row align-items-center flex-fill">
|
||||
<div class="col-12 col-md-6 col-lg-4">
|
||||
<div class="p-2 overflow-hidden">
|
||||
<div class="d-block text-truncate">${name}</div>
|
||||
${<if-has-artists>}${artists class="d-block d-md-none text-truncate"}${</if-has-artists>}
|
||||
<div class="p-2">
|
||||
<div class="d-block">${name}</div>
|
||||
${<if-has-artists>}${artists class="d-block d-md-none"}${</if-has-artists>}
|
||||
${<if-has-work>}<div class="text-muted small ms-2">${work}</div>${</if-has-work>}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 col-lg-4">
|
||||
${<if-has-artists>}${artists-md class="d-none d-md-block text-truncate"}${</if-has-artists>}
|
||||
${<if-has-artists>}${artists-md class="d-none d-md-block"}${</if-has-artists>}
|
||||
</div>
|
||||
<div class="col-lg-4 text-truncate">
|
||||
${<if-has-release>}${release class="d-none d-lg-inline text-truncate text-decoration-none link-success"}${</if-has-release>}
|
||||
<div class="col-lg-4">
|
||||
${<if-has-release>}${release class="d-none d-lg-inline text-decoration-none link-success"}${</if-has-release>}
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-2 d-none d-sm-block text-muted text-center Lms-tracks-entry-duration">
|
||||
|
||||
@@ -84,14 +84,6 @@ login-throttler-max-entries = 10000;
|
||||
# API
|
||||
api-subsonic = true;
|
||||
|
||||
# Enable or disable user/password authentication for the Subsonic API.
|
||||
# Note: Since token/salt authentication is always disabled, setting this to 'false' means only API keys can be used to access the Subsonic API.
|
||||
api-subsonic-support-user-password-auth = true;
|
||||
|
||||
# Use this list to make the reported server version to 1.12.0 depending on the client's name
|
||||
# Main usage is to make auto detections for the 'p' (password) parameter work
|
||||
api-subsonic-old-server-protocol-clients = ("DSub");
|
||||
|
||||
# List of clients for whom open subsonic extensions and extra fields are disabled
|
||||
api-open-subsonic-disabled-clients = ("DSub");
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ namespace lms::audio::ffmpeg
|
||||
return planar ? AV_SAMPLE_FMT_DBLP : AV_SAMPLE_FMT_DBL;
|
||||
}
|
||||
|
||||
throw Exception("Unsupported PcmSampleType");
|
||||
throw Exception{ "Unsupported PcmSampleType" };
|
||||
}
|
||||
|
||||
SwrContextPtr createResampler(const PcmParameters& params, const AVChannelLayout& inLayout, AVSampleFormat inFmt, int inSampleRate)
|
||||
@@ -104,7 +104,7 @@ namespace lms::audio::ffmpeg
|
||||
: _parameters{ parameters }
|
||||
{
|
||||
if (_parameters.channelCount > AV_NUM_DATA_POINTERS)
|
||||
throw Exception("Channel count exceeds maximum supported channels");
|
||||
throw Exception{ "Channel count exceeds maximum supported channels" };
|
||||
|
||||
utils::init();
|
||||
|
||||
@@ -179,7 +179,7 @@ namespace lms::audio::ffmpeg
|
||||
{
|
||||
int error{ ::avcodec_open2(_decoderContext.get(), decoder, nullptr) };
|
||||
if (error < 0)
|
||||
throw FFmpegException("Cannot open decoder", error);
|
||||
throw FFmpegException{ "Cannot open decoder", error };
|
||||
}
|
||||
|
||||
_decodedFrame = AVFramePtr{ av_frame_alloc() };
|
||||
|
||||
@@ -83,9 +83,9 @@ namespace lms::audio::ffmpeg
|
||||
{ TagType::Mixers, { "MIXERS" } },
|
||||
{ TagType::MixersSortOrder, { "MIXERSSORT" } },
|
||||
{ TagType::Mood, { "MOOD" } },
|
||||
{ TagType::Movement, { "MOVEMENT", "MOVEMENTNAME" } },
|
||||
{ TagType::MovementCount, { "MOVEMENTCOUNT" } },
|
||||
{ TagType::MovementNumber, { "MOVEMENTNUMBER" } },
|
||||
{ TagType::Movement, { "MOVEMENTNAME" } },
|
||||
{ TagType::MovementCount, { "MOVEMENTCOUNT", "MOVEMENTTOTAL" } },
|
||||
{ TagType::MovementNumber, { "MOVEMENTNUMBER", "MOVEMENT" } },
|
||||
{ TagType::MusicBrainzArtistID, { "MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ/ARTIST ID" } },
|
||||
{ TagType::MusicBrainzArrangerID, { "MUSICBRAINZ_ARRANGERID", "MUSICBRAINZ ARRANGER ID", "MUSICBRAINZ/ARRANGER ID" } },
|
||||
{ TagType::MusicBrainzComposerID, { "MUSICBRAINZ_COMPOSERID", "MUSICBRAINZ COMPOSER ID", "MUSICBRAINZ/COMPOSER ID" } },
|
||||
|
||||
@@ -89,13 +89,13 @@ namespace lms::audio::pulseaudio
|
||||
{
|
||||
const int error{ ::pa_context_connect(_context.get(), nullptr, PA_CONTEXT_NOFLAGS, nullptr) };
|
||||
if (error < 0)
|
||||
throw PaException("pa_context_connect failed", error);
|
||||
throw PaException{ "pa_context_connect failed", error };
|
||||
}
|
||||
|
||||
{
|
||||
const int error{ ::pa_threaded_mainloop_start(_mainLoop.get()) };
|
||||
if (error < 0)
|
||||
throw PaException("pa_threaded_mainloop_start failed", error);
|
||||
throw PaException{ "pa_threaded_mainloop_start failed", error };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -226,7 +226,7 @@ namespace lms::audio::pulseaudio
|
||||
{
|
||||
pa_operation* op{ ::pa_stream_flush(_stream.get(), nullptr, nullptr) };
|
||||
if (!op)
|
||||
throw PaException("pa_stream_flush failed", pa_context_errno(_context));
|
||||
throw PaException{ "pa_stream_flush failed", pa_context_errno(_context) };
|
||||
|
||||
::pa_operation_unref(op);
|
||||
}
|
||||
@@ -249,7 +249,7 @@ namespace lms::audio::pulseaudio
|
||||
|
||||
pa_operation* op{ ::pa_stream_cork(_stream.get(), 1, nullptr, nullptr) };
|
||||
if (!op)
|
||||
throw PaException("pa_stream_cork (pause) failed", pa_context_errno(_context));
|
||||
throw PaException{ "pa_stream_cork (pause) failed", pa_context_errno(_context) };
|
||||
|
||||
::pa_operation_unref(op);
|
||||
}
|
||||
@@ -263,14 +263,14 @@ namespace lms::audio::pulseaudio
|
||||
{
|
||||
pa_operation* op{ ::pa_stream_cork(_stream.get(), 0, nullptr, nullptr) };
|
||||
if (!op)
|
||||
throw PaException("pa_stream_cork (resume) failed", pa_context_errno(_context));
|
||||
throw PaException{ "pa_stream_cork (resume) failed", pa_context_errno(_context) };
|
||||
::pa_operation_unref(op);
|
||||
}
|
||||
|
||||
{
|
||||
pa_operation* op{ ::pa_stream_trigger(_stream.get(), NULL, NULL) };
|
||||
if (!op)
|
||||
throw PaException("pa_stream_trigger failed", pa_context_errno(_context));
|
||||
throw PaException{ "pa_stream_trigger failed", pa_context_errno(_context) };
|
||||
::pa_operation_unref(op);
|
||||
}
|
||||
}
|
||||
@@ -301,7 +301,7 @@ namespace lms::audio::pulseaudio
|
||||
nullptr,
|
||||
nullptr) };
|
||||
if (!op)
|
||||
throw PaException("pa_context_set_sink_input_volume failed", pa_context_errno(_context));
|
||||
throw PaException{ "pa_context_set_sink_input_volume failed", pa_context_errno(_context) };
|
||||
|
||||
::pa_operation_unref(op);
|
||||
|
||||
@@ -425,7 +425,7 @@ namespace lms::audio::pulseaudio
|
||||
|
||||
::pa_operation* op{ ::pa_stream_drain(_stream.get(), [](pa_stream*, int success, void* userdata) { static_cast<AudioOutputStream*>(userdata)->onDrainComplete(success); }, this) };
|
||||
if (!op)
|
||||
throw PaException("pa_stream_drain failed", pa_context_errno(_context));
|
||||
throw PaException{ "pa_stream_drain failed", pa_context_errno(_context) };
|
||||
|
||||
::pa_operation_unref(op);
|
||||
}
|
||||
|
||||
@@ -113,9 +113,9 @@ namespace lms::audio::taglib
|
||||
{ TagType::Mixers, { "MIXERS" } },
|
||||
{ TagType::MixersSortOrder, { "MIXERSSORT" } },
|
||||
{ TagType::Mood, { "MOOD" } },
|
||||
{ TagType::Movement, { "MOVEMENT", "MOVEMENTNAME" } },
|
||||
{ TagType::MovementCount, { "MOVEMENTCOUNT" } },
|
||||
{ TagType::MovementNumber, { "MOVEMENTNUMBER" } },
|
||||
{ TagType::Movement, { "MOVEMENTNAME" } },
|
||||
{ TagType::MovementCount, { "MOVEMENTCOUNT", "MOVEMENTTOTAL" } },
|
||||
{ TagType::MovementNumber, { "MOVEMENTNUMBER", "MOVEMENT" } },
|
||||
{ TagType::MusicBrainzArtistID, { "MUSICBRAINZ_ARTISTID", "MUSICBRAINZ ARTIST ID", "MUSICBRAINZ/ARTIST ID" } },
|
||||
{ TagType::MusicBrainzArrangerID, { "MUSICBRAINZ_ARRANGERID", "MUSICBRAINZ ARRANGER ID", "MUSICBRAINZ/ARRANGER ID" } },
|
||||
{ TagType::MusicBrainzComposerID, { "MUSICBRAINZ_COMPOSERID", "MUSICBRAINZ COMPOSER ID", "MUSICBRAINZ/COMPOSER ID" } },
|
||||
|
||||
@@ -13,6 +13,7 @@ configure_file(
|
||||
add_library(lmscore STATIC
|
||||
impl/http/Client.cpp
|
||||
impl/http/SendQueue.cpp
|
||||
impl/http/UrlValidation.cpp
|
||||
impl/media/Codec.cpp
|
||||
impl/media/Container.cpp
|
||||
impl/media/ImageType.cpp
|
||||
|
||||
@@ -341,6 +341,15 @@ namespace lms::core::stringUtils
|
||||
return str.substr(0, str.find_last_not_of(whitespaces) + 1);
|
||||
}
|
||||
|
||||
std::string_view utf8Truncate(std::string_view str, std::size_t maxBytes)
|
||||
{
|
||||
std::size_t len{ std::min(maxBytes, str.size()) };
|
||||
while (len > 0 && len < str.size() && (static_cast<unsigned char>(str[len]) & 0xC0) == 0x80)
|
||||
--len;
|
||||
|
||||
return str.substr(0, len);
|
||||
}
|
||||
|
||||
std::string stringToLower(std::string_view str)
|
||||
{
|
||||
std::string res;
|
||||
@@ -366,18 +375,6 @@ namespace lms::core::stringUtils
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string bufferToString(std::span<const unsigned char> data)
|
||||
{
|
||||
std::ostringstream oss;
|
||||
|
||||
for (unsigned char c : data)
|
||||
{
|
||||
oss << std::setw(2) << std::setfill('0') << std::hex << (int)c;
|
||||
}
|
||||
|
||||
return oss.str();
|
||||
}
|
||||
|
||||
bool stringCaseInsensitiveEqual(std::string_view strA, std::string_view strB)
|
||||
{
|
||||
if (strA.size() != strB.size())
|
||||
@@ -418,6 +415,44 @@ namespace lms::core::stringUtils
|
||||
}
|
||||
}
|
||||
|
||||
std::string toRomanNumeral(std::size_t n)
|
||||
{
|
||||
if (n == 0 || n > 3999)
|
||||
return {};
|
||||
|
||||
static constexpr struct
|
||||
{
|
||||
std::size_t val;
|
||||
const char* sym;
|
||||
} table[]{
|
||||
{ 1000, "m" },
|
||||
{ 900, "cm" },
|
||||
{ 500, "d" },
|
||||
{ 400, "cd" },
|
||||
{ 100, "c" },
|
||||
{ 90, "xc" },
|
||||
{ 50, "l" },
|
||||
{ 40, "xl" },
|
||||
{ 10, "x" },
|
||||
{ 9, "ix" },
|
||||
{ 5, "v" },
|
||||
{ 4, "iv" },
|
||||
{ 1, "i" }
|
||||
};
|
||||
|
||||
std::string res;
|
||||
for (const auto& [val, sym] : table)
|
||||
{
|
||||
while (n >= val)
|
||||
{
|
||||
res += sym;
|
||||
n -= val;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string replaceInString(std::string_view str, std::string_view from, std::string_view to)
|
||||
{
|
||||
std::string res{ str };
|
||||
@@ -561,16 +596,18 @@ namespace lms::core::stringUtils
|
||||
return res;
|
||||
}
|
||||
|
||||
std::string toHexString(std::string_view str)
|
||||
std::string bufferToHexString(std::span<const std::byte> data)
|
||||
{
|
||||
constexpr char lut[]{ "0123456789ABCDEF" };
|
||||
|
||||
std::string res;
|
||||
res.reserve(data.size() * 2);
|
||||
|
||||
for (char c : str)
|
||||
for (const std::byte b : data)
|
||||
{
|
||||
res.push_back(lut[(c >> 4) & 0xF]);
|
||||
res.push_back(lut[c & 0xF]);
|
||||
const unsigned value{ std::to_integer<unsigned>(b) };
|
||||
res.push_back(lut[(value >> 4) & 0xF]);
|
||||
res.push_back(lut[value & 0xF]);
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/ITraceLogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
#include "core/http/UrlValidation.hpp"
|
||||
|
||||
#define LOG(sev, message) LMS_LOG(HTTP, sev, "[Http SendQueue] - " << message)
|
||||
|
||||
@@ -190,6 +191,12 @@ namespace lms::core::http
|
||||
const std::string url{ _baseUrl + request.getParameters().relativeUrl };
|
||||
LOG(DEBUG, "Sending " << (request.getType() == ClientRequest::Type::GET ? "GET" : "POST") << " request to url '" << url << "'");
|
||||
|
||||
if (!isValidUrl(url))
|
||||
{
|
||||
LOG(ERROR, "Refusing request to '" << url << "': invalid URL");
|
||||
return false;
|
||||
}
|
||||
|
||||
_client.setMaximumResponseSize(request.getParameters().onChunkReceived ? 0 : request.getParameters().responseBufferSize);
|
||||
|
||||
bool res{};
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "core/http/UrlValidation.hpp"
|
||||
|
||||
namespace lms::core::http
|
||||
{
|
||||
bool isValidUrl(std::string_view url)
|
||||
{
|
||||
return !url.empty() && (url.starts_with("http://") || url.starts_with("https://"));
|
||||
}
|
||||
} // namespace lms::core::http
|
||||
@@ -40,7 +40,7 @@ namespace lms::core::media
|
||||
CodecDesc{ Codec::MPC7, "MPC7", "Musepack7", false },
|
||||
CodecDesc{ Codec::MPC8, "MPC8", "Musepack8", false },
|
||||
CodecDesc{ Codec::Opus, "Opus", "Opus", false },
|
||||
CodecDesc{ Codec::PCM, "PCM", "Pulse-code modulation", false },
|
||||
CodecDesc{ Codec::PCM, "PCM", "Pulse-code modulation", true },
|
||||
CodecDesc{ Codec::Shorten, "Shorten", "Shorten", true },
|
||||
CodecDesc{ Codec::TrueAudio, "TTA", "The True Audio", true },
|
||||
CodecDesc{ Codec::Vorbis, "Vorbis", "Vorbis", false },
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <sstream>
|
||||
@@ -54,17 +55,21 @@ namespace lms::core::stringUtils
|
||||
[[nodiscard]] std::string_view stringTrim(std::string_view str, std::string_view whitespaces = " \t\r");
|
||||
[[nodiscard]] std::string_view stringTrimEnd(std::string_view str, std::string_view whitespaces = " \t\r");
|
||||
|
||||
// Like str.substr(0, maxBytes), but takes utf8 into account to properly truncate
|
||||
[[nodiscard]] std::string_view utf8Truncate(std::string_view str, std::size_t maxBytes);
|
||||
|
||||
[[nodiscard]] std::string stringToLower(std::string_view str);
|
||||
void stringToLower(std::string& str);
|
||||
[[nodiscard]] std::string stringToUpper(const std::string& str);
|
||||
|
||||
[[nodiscard]] std::string bufferToString(std::span<const unsigned char> data);
|
||||
|
||||
[[nodiscard]] bool stringCaseInsensitiveEqual(std::string_view strA, std::string_view strB);
|
||||
[[nodiscard]] std::string_view::size_type stringCaseInsensitiveContains(std::string_view str, std::string_view strtoFind);
|
||||
|
||||
void capitalize(std::string& str);
|
||||
|
||||
// returns empty string if invalid input
|
||||
[[nodiscard]] std::string toRomanNumeral(std::size_t n);
|
||||
|
||||
template<typename T>
|
||||
[[nodiscard]] std::optional<T> readAs(std::string_view str)
|
||||
{
|
||||
@@ -116,7 +121,7 @@ namespace lms::core::stringUtils
|
||||
[[nodiscard]] bool stringEndsWith(std::string_view str, std::string_view ending);
|
||||
|
||||
[[nodiscard]] std::optional<std::string> stringFromHex(std::string_view str);
|
||||
[[nodiscard]] std::string toHexString(std::string_view str);
|
||||
[[nodiscard]] std::string bufferToHexString(std::span<const std::byte> data);
|
||||
|
||||
[[nodiscard]] std::string toISO8601String(const Wt::WDateTime& dateTime);
|
||||
[[nodiscard]] std::string toISO8601String(const Wt::WDate& date);
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace lms::core::http
|
||||
{
|
||||
// Returns true if url is a well-formed absolute URL with an http:// or https:// scheme.
|
||||
bool isValidUrl(std::string_view url);
|
||||
} // namespace lms::core::http
|
||||
@@ -2,6 +2,7 @@ include(GoogleTest)
|
||||
|
||||
add_executable(test-core
|
||||
EnumSet.cpp
|
||||
HttpUrlValidation.cpp
|
||||
JobScheduler.cpp
|
||||
LiteralString.cpp
|
||||
PartialDateTime.cpp
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "core/http/UrlValidation.hpp"
|
||||
|
||||
namespace lms::core::http::tests
|
||||
{
|
||||
TEST(HttpUrlValidation, AllowedUrls)
|
||||
{
|
||||
EXPECT_TRUE(isValidUrl("http://feeds.example.com/podcast.rss"));
|
||||
EXPECT_TRUE(isValidUrl("https://feeds.example.com/podcast.rss"));
|
||||
EXPECT_TRUE(isValidUrl("http://192.168.1.100/feed.rss"));
|
||||
EXPECT_TRUE(isValidUrl("https://example.com/episode.mp3"));
|
||||
}
|
||||
|
||||
TEST(HttpUrlValidation, DisallowedUrls)
|
||||
{
|
||||
EXPECT_FALSE(isValidUrl(""));
|
||||
EXPECT_FALSE(isValidUrl("ftp://attacker.com/feed"));
|
||||
EXPECT_FALSE(isValidUrl("file:///etc/passwd"));
|
||||
EXPECT_FALSE(isValidUrl("javascript:alert(1)"));
|
||||
EXPECT_FALSE(isValidUrl("//example.com/feed"));
|
||||
EXPECT_FALSE(isValidUrl("HTTP://example.com/feed")); // scheme check is case-sensitive
|
||||
}
|
||||
} // namespace lms::core::http::tests
|
||||
@@ -17,6 +17,9 @@
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <array>
|
||||
#include <limits>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <Wt/WDate.h>
|
||||
@@ -385,6 +388,31 @@ namespace lms::core::stringUtils::tests
|
||||
EXPECT_FALSE(stringEndsWith("FooBar", "R"));
|
||||
}
|
||||
|
||||
TEST(StringUtils, utf8Truncate)
|
||||
{
|
||||
EXPECT_EQ(utf8Truncate("abc", 10), "abc");
|
||||
EXPECT_EQ(utf8Truncate("abcdef", 6), "abcdef");
|
||||
EXPECT_EQ(utf8Truncate("", 10), "");
|
||||
EXPECT_EQ(utf8Truncate("abc", 0), "");
|
||||
|
||||
EXPECT_EQ(utf8Truncate("caf\xC3\xA9", 4), "caf");
|
||||
EXPECT_EQ(utf8Truncate("caf\xC3\xA9", 5), "caf\xC3\xA9");
|
||||
|
||||
EXPECT_EQ(utf8Truncate("\xE2\x82\xAC", 1), "");
|
||||
EXPECT_EQ(utf8Truncate("\xE2\x82\xAC", 2), "");
|
||||
EXPECT_EQ(utf8Truncate("\xE2\x82\xAC", 3), "\xE2\x82\xAC");
|
||||
|
||||
EXPECT_EQ(utf8Truncate("\xF0\x9F\x98\x80", 1), "");
|
||||
EXPECT_EQ(utf8Truncate("\xF0\x9F\x98\x80", 2), "");
|
||||
EXPECT_EQ(utf8Truncate("\xF0\x9F\x98\x80", 3), "");
|
||||
EXPECT_EQ(utf8Truncate("\xF0\x9F\x98\x80", 4), "\xF0\x9F\x98\x80");
|
||||
|
||||
EXPECT_EQ(utf8Truncate("e\xCC\x81", 2), "e");
|
||||
EXPECT_EQ(utf8Truncate("e\xCC\x81", 3), "e\xCC\x81");
|
||||
|
||||
EXPECT_EQ(utf8Truncate("\x80\x80\x80", 2), "");
|
||||
}
|
||||
|
||||
TEST(StringUtils, stringCaseInsensitiveContains)
|
||||
{
|
||||
EXPECT_TRUE(stringCaseInsensitiveContains("FooBar", "Bar"));
|
||||
@@ -397,13 +425,11 @@ namespace lms::core::stringUtils::tests
|
||||
EXPECT_FALSE(stringCaseInsensitiveContains("", "Foo"));
|
||||
}
|
||||
|
||||
TEST(StringUtils, toHexString)
|
||||
TEST(StringUtils, bufferToHexString)
|
||||
{
|
||||
EXPECT_EQ(toHexString(""), "");
|
||||
EXPECT_EQ(toHexString("123"), "313233");
|
||||
EXPECT_EQ(toHexString("1234"), "31323334");
|
||||
EXPECT_EQ(toHexString("12345"), "3132333435");
|
||||
EXPECT_EQ(toHexString("Test"), "54657374");
|
||||
EXPECT_EQ(bufferToHexString({}), "");
|
||||
EXPECT_EQ(bufferToHexString(std::array{ std::byte{ 0x31 }, std::byte{ 0x32 }, std::byte{ 0x33 } }), "313233");
|
||||
EXPECT_EQ(bufferToHexString(std::array{ std::byte{ 0x00 }, std::byte{ 0xab }, std::byte{ 0xcd }, std::byte{ 0xff } }), "00ABCDFF");
|
||||
|
||||
// test back stringFromHex
|
||||
EXPECT_EQ(stringFromHex(""), "");
|
||||
@@ -412,4 +438,32 @@ namespace lms::core::stringUtils::tests
|
||||
EXPECT_EQ(stringFromHex("3132333435"), "12345");
|
||||
EXPECT_EQ(stringFromHex("54657374"), "Test");
|
||||
}
|
||||
|
||||
TEST(StringUtils, toRomanNumeral)
|
||||
{
|
||||
EXPECT_EQ(toRomanNumeral(1), "i");
|
||||
EXPECT_EQ(toRomanNumeral(2), "ii");
|
||||
EXPECT_EQ(toRomanNumeral(3), "iii");
|
||||
EXPECT_EQ(toRomanNumeral(4), "iv");
|
||||
EXPECT_EQ(toRomanNumeral(5), "v");
|
||||
EXPECT_EQ(toRomanNumeral(6), "vi");
|
||||
EXPECT_EQ(toRomanNumeral(7), "vii");
|
||||
EXPECT_EQ(toRomanNumeral(8), "viii");
|
||||
EXPECT_EQ(toRomanNumeral(9), "ix");
|
||||
EXPECT_EQ(toRomanNumeral(10), "x");
|
||||
EXPECT_EQ(toRomanNumeral(11), "xi");
|
||||
EXPECT_EQ(toRomanNumeral(14), "xiv");
|
||||
EXPECT_EQ(toRomanNumeral(16), "xvi");
|
||||
EXPECT_EQ(toRomanNumeral(40), "xl");
|
||||
EXPECT_EQ(toRomanNumeral(50), "l");
|
||||
EXPECT_EQ(toRomanNumeral(90), "xc");
|
||||
EXPECT_EQ(toRomanNumeral(99), "xcix");
|
||||
EXPECT_EQ(toRomanNumeral(444), "cdxliv");
|
||||
EXPECT_EQ(toRomanNumeral(1994), "mcmxciv");
|
||||
EXPECT_EQ(toRomanNumeral(3999), "mmmcmxcix");
|
||||
EXPECT_EQ(toRomanNumeral(0), "");
|
||||
EXPECT_EQ(toRomanNumeral(4000), "");
|
||||
EXPECT_EQ(toRomanNumeral(static_cast<std::size_t>(-1)), ""); // underflows to SIZE_MAX
|
||||
EXPECT_EQ(toRomanNumeral(std::numeric_limits<std::size_t>::max()), "");
|
||||
}
|
||||
} // namespace lms::core::stringUtils::tests
|
||||
@@ -24,11 +24,14 @@ add_library(lmsdatabase STATIC
|
||||
impl/objects/RatedTrack.cpp
|
||||
impl/objects/Release.cpp
|
||||
impl/objects/ScanSettings.cpp
|
||||
impl/objects/ServerInfo.cpp
|
||||
impl/objects/StarredArtist.cpp
|
||||
impl/objects/StarredRelease.cpp
|
||||
impl/objects/StarredTrack.cpp
|
||||
impl/objects/Movement.cpp
|
||||
impl/objects/Track.cpp
|
||||
impl/objects/TrackArtistLink.cpp
|
||||
impl/objects/Work.cpp
|
||||
impl/objects/TrackBookmark.cpp
|
||||
impl/objects/TrackEmbeddedImage.cpp
|
||||
impl/objects/TrackEmbeddedImageLink.cpp
|
||||
|
||||
@@ -248,7 +248,7 @@ namespace lms::db
|
||||
}
|
||||
else if (checkType != "none")
|
||||
{
|
||||
throw Exception("Invalid 'db-integrity-check' value: '" + checkType + "'. Expected 'quick', 'full' or 'none'.");
|
||||
throw Exception{ "Invalid 'db-integrity-check' value: '" + checkType + "'. Expected 'quick', 'full' or 'none'." };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,7 +360,7 @@ namespace lms::db
|
||||
}) };
|
||||
|
||||
if (!foreignKeyConstraintsPassed)
|
||||
throw Exception("Foreign key constraints check failed! Please restore from a backup or recreate the database.");
|
||||
throw Exception{ "Foreign key constraints check failed! Please restore from a backup or recreate the database." };
|
||||
|
||||
LMS_LOG(DB, INFO, "Foreign key constraints check passed!");
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ namespace lms::db
|
||||
{
|
||||
namespace
|
||||
{
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 108 };
|
||||
static constexpr Version LMS_DATABASE_VERSION{ 110 };
|
||||
}
|
||||
|
||||
VersionInfo::VersionInfo()
|
||||
@@ -1922,6 +1922,50 @@ WHERE ct.name = 'GROUPING')");
|
||||
utils::executeCommand(*session.getDboSession(), R"(DELETE FROM cluster_type WHERE name IN ('GENRE', 'MOOD', 'LANGUAGE', 'GROUPING'))");
|
||||
}
|
||||
|
||||
void migrateFromV108(Session& session)
|
||||
{
|
||||
utils::executeCommand(*session.getDboSession(), R"(
|
||||
CREATE TABLE IF NOT EXISTS "work" (
|
||||
"id" integer primary key autoincrement,
|
||||
"version" integer not null,
|
||||
"name" text not null,
|
||||
"mbid" blob
|
||||
))");
|
||||
utils::executeCommand(*session.getDboSession(), R"(
|
||||
CREATE TABLE IF NOT EXISTS "track_work" (
|
||||
"work_id" bigint,
|
||||
"track_id" bigint,
|
||||
primary key ("work_id", "track_id"),
|
||||
constraint "fk_track_work_key1" foreign key ("work_id") references "work" ("id") on delete cascade deferrable initially deferred,
|
||||
constraint "fk_track_work_key2" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred
|
||||
))");
|
||||
utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "track_work_work" on "track_work" ("work_id"))");
|
||||
utils::executeCommand(*session.getDboSession(), R"(CREATE INDEX "track_work_track" on "track_work" ("track_id"))");
|
||||
utils::executeCommand(*session.getDboSession(), R"(
|
||||
CREATE TABLE IF NOT EXISTS "track_movement" (
|
||||
"id" integer primary key autoincrement,
|
||||
"version" integer not null,
|
||||
"name" text not null,
|
||||
"number" integer,
|
||||
"count" integer,
|
||||
"track_id" bigint,
|
||||
constraint "fk_track_movement_track" foreign key ("track_id") references "track" ("id") on delete cascade deferrable initially deferred
|
||||
))");
|
||||
|
||||
// Just increment the scan version of the settings to make the next scan rescan all audio files
|
||||
utils::executeCommand(*session.getDboSession(), "UPDATE scan_settings SET audio_scan_version = audio_scan_version + 1");
|
||||
}
|
||||
|
||||
void migrateFromV109(Session& session)
|
||||
{
|
||||
utils::executeCommand(*session.getDboSession(), R"(
|
||||
CREATE TABLE IF NOT EXISTS "server_info" (
|
||||
"id" integer primary key autoincrement,
|
||||
"version" integer not null,
|
||||
"instance_id" blob not null
|
||||
))");
|
||||
}
|
||||
|
||||
bool doDbMigration(Session& session)
|
||||
{
|
||||
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
|
||||
@@ -2006,6 +2050,8 @@ WHERE ct.name = 'GROUPING')");
|
||||
{ 105, migrateFromV105 },
|
||||
{ 106, migrateFromV106 },
|
||||
{ 107, migrateFromV107 },
|
||||
{ 108, migrateFromV108 },
|
||||
{ 109, migrateFromV109 },
|
||||
};
|
||||
|
||||
LMS_SCOPED_TRACE_OVERVIEW("Database", "Migration");
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/PlayListFile.hpp"
|
||||
#include "database/objects/PlayQueue.hpp"
|
||||
#include "database/objects/Podcast.hpp"
|
||||
@@ -48,6 +49,7 @@
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
#include "database/objects/ScanSettings.hpp"
|
||||
#include "database/objects/ServerInfo.hpp"
|
||||
#include "database/objects/StarredArtist.hpp"
|
||||
#include "database/objects/StarredRelease.hpp"
|
||||
#include "database/objects/StarredTrack.hpp"
|
||||
@@ -61,6 +63,7 @@
|
||||
#include "database/objects/TrackMusicNNEmbeddings.hpp"
|
||||
#include "database/objects/UIState.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Db.hpp"
|
||||
#include "Migration.hpp"
|
||||
@@ -111,6 +114,7 @@ namespace lms::db
|
||||
_session.mapClass<StarredRelease>("starred_release");
|
||||
_session.mapClass<StarredTrack>("starred_track");
|
||||
_session.mapClass<Track>("track");
|
||||
_session.mapClass<Movement>("track_movement");
|
||||
_session.mapClass<TrackBookmark>("track_bookmark");
|
||||
_session.mapClass<TrackArtistLink>("track_artist_link");
|
||||
_session.mapClass<TrackEmbeddedImage>("track_embedded_image");
|
||||
@@ -120,7 +124,9 @@ namespace lms::db
|
||||
_session.mapClass<TrackListEntry>("tracklist_entry");
|
||||
_session.mapClass<TrackLyrics>("track_lyrics");
|
||||
_session.mapClass<UIState>("ui_state");
|
||||
_session.mapClass<Work>("work");
|
||||
_session.mapClass<User>("user");
|
||||
_session.mapClass<ServerInfo>("server_info");
|
||||
_session.mapClass<VersionInfo>("version_info");
|
||||
}
|
||||
|
||||
@@ -176,17 +182,22 @@ namespace lms::db
|
||||
|
||||
bool Session::migrateSchemaIfNeeded()
|
||||
{
|
||||
const bool migrationPerformed{ Migration::doDbMigration(*this) };
|
||||
return Migration::doDbMigration(*this);
|
||||
}
|
||||
|
||||
// TODO: move this elsewhere
|
||||
{
|
||||
auto uniqueTransaction{ createWriteTransaction() };
|
||||
void Session::createScanSettingsIfNeeded(RecommendationEngineType defaultRecommendationEngineType)
|
||||
{
|
||||
auto uniqueTransaction{ createWriteTransaction() };
|
||||
|
||||
if (!ScanSettings::find(*this))
|
||||
create<ScanSettings>();
|
||||
}
|
||||
if (!ScanSettings::find(*this))
|
||||
create<ScanSettings>().modify()->setRecommendationEngineType(defaultRecommendationEngineType);
|
||||
}
|
||||
|
||||
return migrationPerformed;
|
||||
void Session::createServerInfoIfNeeded()
|
||||
{
|
||||
auto uniqueTransaction{ createWriteTransaction() };
|
||||
|
||||
ServerInfo::getOrCreate(*this);
|
||||
}
|
||||
|
||||
void Session::createIndexesIfNeeded()
|
||||
@@ -322,6 +333,10 @@ namespace lms::db
|
||||
"CREATE INDEX IF NOT EXISTS track_lyrics_directory_idx ON track_lyrics(directory_id)",
|
||||
"CREATE INDEX IF NOT EXISTS track_lyrics_track_idx ON track_lyrics(track_id)",
|
||||
|
||||
"CREATE INDEX IF NOT EXISTS track_movement_track_idx ON track_movement(track_id)",
|
||||
|
||||
"CREATE INDEX IF NOT EXISTS work_mbid_idx ON work(mbid)",
|
||||
|
||||
"CREATE INDEX IF NOT EXISTS track_bookmark_user_idx ON track_bookmark(user_id)",
|
||||
"CREATE INDEX IF NOT EXISTS track_bookmark_user_track_idx ON track_bookmark(user_id,track_id)",
|
||||
|
||||
|
||||
@@ -128,29 +128,10 @@ namespace lms::db::utils
|
||||
}
|
||||
|
||||
template<typename ResultType, typename Query>
|
||||
RangeResults<ResultType> execRangeQuery(Query& query, const std::optional<Range> range)
|
||||
std::vector<ResultType> execRangeQuery(Query& query, const std::optional<Range> range)
|
||||
{
|
||||
RangeResults<ResultType> res;
|
||||
|
||||
if (range)
|
||||
{
|
||||
res.range.offset = range->offset;
|
||||
applyRange(query, Range{ range->offset, range->size + 1 });
|
||||
|
||||
res.results.reserve(range->size);
|
||||
}
|
||||
|
||||
// TODO optim useless last copy
|
||||
res.results = utils::fetchQueryResults<ResultType>(query);
|
||||
if (range && (res.results.size() == range->size + 1))
|
||||
{
|
||||
res.moreResults = true;
|
||||
res.results.pop_back();
|
||||
}
|
||||
|
||||
res.range.size = res.results.size();
|
||||
|
||||
return res;
|
||||
applyRange(query, range);
|
||||
return utils::fetchQueryResults<ResultType>(query);
|
||||
}
|
||||
|
||||
template<typename Query, typename UnaryFunc>
|
||||
@@ -162,35 +143,6 @@ namespace lms::db::utils
|
||||
forEachQueryResult(query, std::forward<UnaryFunc>(func));
|
||||
}
|
||||
|
||||
template<typename Query, typename UnaryFunc>
|
||||
void forEachQueryRangeResult(Query& query, std::optional<Range> range, bool& moreResults, UnaryFunc&& func)
|
||||
{
|
||||
using ResultType = typename QueryResultType<Query>::type;
|
||||
|
||||
if (range)
|
||||
applyRange(query, Range{ range->offset, range->size + 1 });
|
||||
|
||||
moreResults = false;
|
||||
|
||||
std::size_t count{};
|
||||
ScopedQueryProfiler queryProfiler{ query };
|
||||
const auto collection{ query.resultList() };
|
||||
auto it{ fetchFirstResult(collection) };
|
||||
while (it != collection.end())
|
||||
{
|
||||
if (range && (count++ == static_cast<std::size_t>(range->size)))
|
||||
{
|
||||
moreResults = true;
|
||||
break;
|
||||
}
|
||||
|
||||
queryProfiler.suspend();
|
||||
func(*it);
|
||||
queryProfiler.resume();
|
||||
fetchNextResult<ResultType>(it);
|
||||
}
|
||||
}
|
||||
|
||||
template<typename... Args>
|
||||
void executeCommand(Wt::Dbo::Session& session, std::string_view command, const Args&... args)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include <Wt/Dbo/WtSqlTraits.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Cluster.hpp"
|
||||
@@ -135,14 +137,13 @@ namespace lms::db
|
||||
|
||||
if (params.starringUser.isValid())
|
||||
{
|
||||
assert(params.feedbackBackend);
|
||||
query.join("starred_artist s_a ON s_a.artist_id = a.id")
|
||||
.join("user u ON u.id = s_a.user_id")
|
||||
.where("s_a.user_id = ?")
|
||||
.bind(params.starringUser)
|
||||
.where("s_a.backend = ?")
|
||||
.bind(*params.feedbackBackend)
|
||||
.where("s_a.sync_state <> ?")
|
||||
.bind(SyncState::PendingRemove);
|
||||
.bind(SyncState::PendingRemove)
|
||||
.where("s_a.backend = u.feedback_backend");
|
||||
}
|
||||
|
||||
if (params.filters.clusters.size() == 1)
|
||||
@@ -160,7 +161,7 @@ namespace lms::db
|
||||
WhereClause clusterClause;
|
||||
for (const ClusterId clusterId : params.filters.clusters)
|
||||
{
|
||||
clusterClause.Or(WhereClause("t_c.cluster_id = ?"));
|
||||
clusterClause.Or(WhereClause{ "t_c.cluster_id = ?" });
|
||||
query.bind(clusterId);
|
||||
}
|
||||
|
||||
@@ -305,8 +306,7 @@ namespace lms::db
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
if (name.size() > maxNameLength)
|
||||
name = name.substr(0, maxNameLength);
|
||||
name = core::stringUtils::utf8Truncate(name, maxNameLength);
|
||||
|
||||
return utils::fetchQueryResults<Artist::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Artist>>("SELECT a FROM artist a").where("a.name = ?").bind(name).orderBy("LENGTH(a.mbid) DESC")); // put mbid entries first
|
||||
}
|
||||
@@ -323,7 +323,7 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Artist>>("SELECT a FROM artist a").where("a.id = ?").bind(id));
|
||||
}
|
||||
|
||||
RangeResults<ArtistId> Artist::findIds(Session& session, const FindParameters& params)
|
||||
std::vector<ArtistId> Artist::findIds(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -331,7 +331,7 @@ namespace lms::db
|
||||
return utils::execRangeQuery<ArtistId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<Artist::pointer> Artist::find(Session& session, const FindParameters& params)
|
||||
std::vector<Artist::pointer> Artist::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -357,7 +357,7 @@ namespace lms::db
|
||||
return IdRange<ArtistId>{ .first = std::get<0>(res), .last = std::get<1>(res) };
|
||||
}
|
||||
|
||||
RangeResults<ArtistId> Artist::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<ArtistId> Artist::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
// TODO extend with release artists
|
||||
session.checkReadTransaction();
|
||||
@@ -388,7 +388,7 @@ AND NOT EXISTS (
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT 1 FROM artist").where("id = ?").bind(id)) == 1;
|
||||
}
|
||||
|
||||
RangeResults<Artist::pointer> Artist::findWithMBIDNameVariants(Session& session, ArtistId& lastRetrievedArtist, std::optional<Range> range)
|
||||
std::vector<Artist::pointer> Artist::findWithMBIDNameVariants(Session& session, ArtistId& lastRetrievedArtist, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -407,8 +407,8 @@ AND NOT EXISTS (
|
||||
|
||||
auto results{ utils::execRangeQuery<Artist::pointer>(query, range) };
|
||||
|
||||
if (!results.results.empty())
|
||||
lastRetrievedArtist = results.results.back()->getId();
|
||||
if (!results.empty())
|
||||
lastRetrievedArtist = results.back()->getId();
|
||||
|
||||
return results;
|
||||
}
|
||||
@@ -442,11 +442,11 @@ AND NOT EXISTS (
|
||||
std::ostringstream oss;
|
||||
oss << "SELECT c FROM cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN cluster_type c_type ON c.cluster_type_id = c_type.id INNER JOIN artist a ON t_a_l.artist_id = a.id INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id";
|
||||
|
||||
where.And(WhereClause("a.id = ?")).bind(getId().toString());
|
||||
where.And(WhereClause{ "a.id = ?" }).bind(getId().toString());
|
||||
{
|
||||
WhereClause clusterClause;
|
||||
for (const ClusterTypeId clusterTypeId : clusterTypeIds)
|
||||
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterTypeId.toString());
|
||||
clusterClause.Or(WhereClause{ "c_type.id = ?" }).bind(clusterTypeId.toString());
|
||||
|
||||
where.And(clusterClause);
|
||||
}
|
||||
@@ -474,13 +474,13 @@ AND NOT EXISTS (
|
||||
|
||||
void Artist::setName(std::string_view name)
|
||||
{
|
||||
_name.assign(name, 0, maxNameLength);
|
||||
_name = core::stringUtils::utf8Truncate(name, maxNameLength);
|
||||
LMS_LOG_IF(DB, WARNING, name.size() > maxNameLength, "Artist name too long, truncated to '" << _name << "'");
|
||||
}
|
||||
|
||||
void Artist::setSortName(std::string_view sortName)
|
||||
{
|
||||
_sortName.assign(sortName, 0, maxNameLength);
|
||||
_sortName = core::stringUtils::utf8Truncate(sortName, maxNameLength);
|
||||
LMS_LOG_IF(DB, WARNING, sortName.size() > maxNameLength, "Artist sort name too long, truncated to '" << _sortName << "'");
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,14 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
@@ -130,7 +132,7 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM cluster"));
|
||||
}
|
||||
|
||||
RangeResults<ClusterId> Cluster::findIds(Session& session, const FindParameters& params)
|
||||
std::vector<ClusterId> Cluster::findIds(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createQuery<ClusterId>(session, params) };
|
||||
@@ -138,7 +140,7 @@ namespace lms::db
|
||||
return utils::execRangeQuery<ClusterId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<Cluster::pointer> Cluster::find(Session& session, const FindParameters& params)
|
||||
std::vector<Cluster::pointer> Cluster::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Cluster>>(session, params) };
|
||||
@@ -154,7 +156,7 @@ namespace lms::db
|
||||
return utils::forEachQueryResult(query, _func);
|
||||
}
|
||||
|
||||
RangeResults<ClusterId> Cluster::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<ClusterId> Cluster::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ session.getDboSession()->query<ClusterId>("SELECT DISTINCT c.id FROM cluster c WHERE NOT EXISTS(SELECT 1 FROM track_cluster t_c WHERE t_c.cluster_id = c.id)") };
|
||||
@@ -188,7 +190,7 @@ namespace lms::db
|
||||
_tracks.insert(getDboPtr(track));
|
||||
}
|
||||
|
||||
RangeResults<TrackId> Cluster::getTracks(std::optional<Range> range) const
|
||||
std::vector<TrackId> Cluster::getTracks(std::optional<Range> range) const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
@@ -217,7 +219,7 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM cluster_type"));
|
||||
}
|
||||
|
||||
RangeResults<ClusterTypeId> ClusterType::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<ClusterTypeId> ClusterType::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -229,7 +231,7 @@ namespace lms::db
|
||||
return utils::execRangeQuery<ClusterTypeId>(query, range);
|
||||
}
|
||||
|
||||
RangeResults<ClusterTypeId> ClusterType::findUsed(Session& session, std::optional<Range> range)
|
||||
std::vector<ClusterTypeId> ClusterType::findUsed(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -263,7 +265,7 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->find<ClusterType>().where("id = ?").bind(id));
|
||||
}
|
||||
|
||||
RangeResults<ClusterTypeId> ClusterType::findIds(Session& session, std::optional<Range> range)
|
||||
std::vector<ClusterTypeId> ClusterType::findIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
|
||||
@@ -173,7 +173,7 @@ namespace lms::db
|
||||
});
|
||||
}
|
||||
|
||||
RangeResults<Directory::pointer> Directory::find(Session& session, const FindParameters& params)
|
||||
std::vector<Directory::pointer> Directory::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
auto query{ createQuery(session, params) };
|
||||
return utils::execRangeQuery<Directory::pointer>(query, params.range);
|
||||
@@ -187,7 +187,7 @@ namespace lms::db
|
||||
});
|
||||
}
|
||||
|
||||
RangeResults<DirectoryId> Directory::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<DirectoryId> Directory::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -208,7 +208,7 @@ namespace lms::db
|
||||
return utils::execRangeQuery<DirectoryId>(query, range);
|
||||
}
|
||||
|
||||
RangeResults<DirectoryId> Directory::findMismatchedLibrary(Session& session, std::optional<Range> range, const std::filesystem::path& rootPath, MediaLibraryId expectedLibraryId)
|
||||
std::vector<DirectoryId> Directory::findMismatchedLibrary(Session& session, std::optional<Range> range, const std::filesystem::path& rootPath, MediaLibraryId expectedLibraryId)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -219,7 +219,7 @@ namespace lms::db
|
||||
return utils::execRangeQuery<DirectoryId>(query, range);
|
||||
}
|
||||
|
||||
RangeResults<Directory::pointer> Directory::findRootDirectories(Session& session, std::optional<Range> range)
|
||||
std::vector<Directory::pointer> Directory::findRootDirectories(Session& session, std::optional<Range> range)
|
||||
{
|
||||
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<Directory>>("SELECT d from directory d").where("d.parent_directory_id IS NULL") };
|
||||
return utils::execRangeQuery<Directory::pointer>(query, range);
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
@@ -33,12 +34,14 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
@@ -112,7 +115,7 @@ namespace lms::db
|
||||
} // namespace
|
||||
|
||||
Genre::Genre(std::string_view name)
|
||||
: _name{ name.substr(0, maxNameLength) }
|
||||
: _name{ core::stringUtils::utf8Truncate(name, maxNameLength) }
|
||||
{
|
||||
LMS_LOG_IF(DB, WARNING, name.size() > maxNameLength, "Genre name too long, truncated to '" << _name << "'");
|
||||
}
|
||||
@@ -128,14 +131,14 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM genre"));
|
||||
}
|
||||
|
||||
RangeResults<GenreId> Genre::findIds(Session& session, const FindParameters& params)
|
||||
std::vector<GenreId> Genre::findIds(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createQuery<GenreId>(session, params) };
|
||||
return utils::execRangeQuery<GenreId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<Genre::pointer> Genre::find(Session& session, const FindParameters& params)
|
||||
std::vector<Genre::pointer> Genre::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Genre>>(session, params) };
|
||||
@@ -159,13 +162,12 @@ namespace lms::db
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
if (name.size() > maxNameLength)
|
||||
name = name.substr(0, maxNameLength);
|
||||
name = core::stringUtils::utf8Truncate(name, maxNameLength);
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->find<Genre>().where("name = ?").bind(name));
|
||||
}
|
||||
|
||||
RangeResults<GenreId> Genre::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<GenreId> Genre::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ session.getDboSession()->query<GenreId>("SELECT g.id FROM genre g WHERE NOT EXISTS (SELECT 1 FROM track_genre t_g WHERE t_g.genre_id = g.id)") };
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
@@ -33,12 +34,14 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
@@ -112,7 +115,7 @@ namespace lms::db
|
||||
} // namespace
|
||||
|
||||
Grouping::Grouping(std::string_view name)
|
||||
: _name{ name.substr(0, maxNameLength) }
|
||||
: _name{ core::stringUtils::utf8Truncate(name, maxNameLength) }
|
||||
{
|
||||
LMS_LOG_IF(DB, WARNING, name.size() > maxNameLength, "Grouping name too long, truncated to '" << _name << "'");
|
||||
}
|
||||
@@ -128,14 +131,14 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM grouping"));
|
||||
}
|
||||
|
||||
RangeResults<GroupingId> Grouping::findIds(Session& session, const FindParameters& params)
|
||||
std::vector<GroupingId> Grouping::findIds(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createQuery<GroupingId>(session, params) };
|
||||
return utils::execRangeQuery<GroupingId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<Grouping::pointer> Grouping::find(Session& session, const FindParameters& params)
|
||||
std::vector<Grouping::pointer> Grouping::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Grouping>>(session, params) };
|
||||
@@ -159,13 +162,12 @@ namespace lms::db
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
if (name.size() > maxNameLength)
|
||||
name = name.substr(0, maxNameLength);
|
||||
name = core::stringUtils::utf8Truncate(name, maxNameLength);
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->find<Grouping>().where("name = ?").bind(name));
|
||||
}
|
||||
|
||||
RangeResults<GroupingId> Grouping::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<GroupingId> Grouping::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ session.getDboSession()->query<GroupingId>("SELECT g.id FROM grouping g WHERE NOT EXISTS (SELECT 1 FROM track_grouping t_gr WHERE t_gr.grouping_id = g.id)") };
|
||||
|
||||
@@ -105,7 +105,7 @@ namespace lms::db
|
||||
});
|
||||
}
|
||||
|
||||
RangeResults<Image::pointer> Image::find(Session& session, const FindParameters& params)
|
||||
std::vector<Image::pointer> Image::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
@@ -33,12 +34,14 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
@@ -112,7 +115,7 @@ namespace lms::db
|
||||
} // namespace
|
||||
|
||||
Language::Language(std::string_view name)
|
||||
: _name{ name.substr(0, maxNameLength) }
|
||||
: _name{ core::stringUtils::utf8Truncate(name, maxNameLength) }
|
||||
{
|
||||
LMS_LOG_IF(DB, WARNING, name.size() > maxNameLength, "Language name too long, truncated to '" << _name << "'");
|
||||
}
|
||||
@@ -128,14 +131,14 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM language"));
|
||||
}
|
||||
|
||||
RangeResults<LanguageId> Language::findIds(Session& session, const FindParameters& params)
|
||||
std::vector<LanguageId> Language::findIds(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createQuery<LanguageId>(session, params) };
|
||||
return utils::execRangeQuery<LanguageId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<Language::pointer> Language::find(Session& session, const FindParameters& params)
|
||||
std::vector<Language::pointer> Language::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Language>>(session, params) };
|
||||
@@ -159,13 +162,12 @@ namespace lms::db
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
if (name.size() > maxNameLength)
|
||||
name = name.substr(0, maxNameLength);
|
||||
name = core::stringUtils::utf8Truncate(name, maxNameLength);
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->find<Language>().where("name = ?").bind(name));
|
||||
}
|
||||
|
||||
RangeResults<LanguageId> Language::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<LanguageId> Language::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ session.getDboSession()->query<LanguageId>("SELECT l.id FROM language l WHERE NOT EXISTS (SELECT 1 FROM track_language t_l WHERE t_l.language_id = l.id)") };
|
||||
|
||||
@@ -23,10 +23,12 @@
|
||||
#include <Wt/Dbo/WtSqlTraits.h>
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Genre.hpp"
|
||||
#include "database/objects/Grouping.hpp"
|
||||
#include "database/objects/Language.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
|
||||
@@ -46,10 +48,12 @@ namespace lms::db
|
||||
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);
|
||||
|
||||
if (params.backend)
|
||||
query.where("l.backend = ?").bind(*params.backend);
|
||||
{
|
||||
query.join("user u ON u.id = l.user_id")
|
||||
.where("l.user_id = ?")
|
||||
.bind(params.user)
|
||||
.where("l.backend = u.scrobbling_backend");
|
||||
}
|
||||
|
||||
assert(!params.artist.isValid()); // poor check
|
||||
|
||||
@@ -123,7 +127,7 @@ namespace lms::db
|
||||
WhereClause clusterClause;
|
||||
for (const db::ClusterId id : params.filters.clusters)
|
||||
{
|
||||
clusterClause.Or(WhereClause("t_c.cluster_id = ?"));
|
||||
clusterClause.Or(WhereClause{ "t_c.cluster_id = ?" });
|
||||
query.bind(id);
|
||||
}
|
||||
|
||||
@@ -161,10 +165,12 @@ namespace lms::db
|
||||
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);
|
||||
|
||||
if (params.backend)
|
||||
query.where("l.backend = ?").bind(*params.backend);
|
||||
{
|
||||
query.join("user u ON u.id = l.user_id")
|
||||
.where("l.user_id = ?")
|
||||
.bind(params.user)
|
||||
.where("l.backend = u.scrobbling_backend");
|
||||
}
|
||||
|
||||
if (params.artist.isValid())
|
||||
{
|
||||
@@ -223,7 +229,7 @@ namespace lms::db
|
||||
WhereClause clusterClause;
|
||||
for (const ClusterId id : params.filters.clusters)
|
||||
{
|
||||
clusterClause.Or(WhereClause("c.id = ?"));
|
||||
clusterClause.Or(WhereClause{ "c.id = ?" });
|
||||
query.bind(id);
|
||||
}
|
||||
|
||||
@@ -244,10 +250,12 @@ namespace lms::db
|
||||
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);
|
||||
|
||||
if (params.backend)
|
||||
query.where("l.backend = ?").bind(*params.backend);
|
||||
{
|
||||
query.join("user u ON u.id = l.user_id")
|
||||
.where("l.user_id = ?")
|
||||
.bind(params.user)
|
||||
.where("l.backend = u.scrobbling_backend");
|
||||
}
|
||||
|
||||
if (params.artist.isValid())
|
||||
{
|
||||
@@ -305,7 +313,7 @@ namespace lms::db
|
||||
WhereClause clusterClause;
|
||||
for (const ClusterId id : params.filters.clusters)
|
||||
{
|
||||
clusterClause.Or(WhereClause("c.id = ?")).bind(id.toString());
|
||||
clusterClause.Or(WhereClause{ "c.id = ?" }).bind(id.toString());
|
||||
query.bind(id);
|
||||
}
|
||||
|
||||
@@ -348,7 +356,7 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Listen>>("SELECT l from listen l").where("l.id = ?").bind(id));
|
||||
}
|
||||
|
||||
RangeResults<ListenId> Listen::find(Session& session, const FindParameters& parameters)
|
||||
std::vector<ListenId> Listen::find(Session& session, const FindParameters& parameters)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -373,39 +381,88 @@ namespace lms::db
|
||||
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)
|
||||
std::vector<ArtistId> Listen::getTopArtists(Session& session, const ArtistStatsFindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createArtistsQuery(session, params) };
|
||||
|
||||
auto collection{ query
|
||||
.orderBy("COUNT(a.id) DESC")
|
||||
.groupBy("a.id") };
|
||||
|
||||
auto query{ createArtistsQuery(session, params).orderBy("COUNT(a.id) DESC").groupBy("a.id") };
|
||||
return utils::execRangeQuery<ArtistId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<ReleaseId> Listen::getTopReleases(Session& session, const StatsFindParameters& params)
|
||||
std::vector<ReleaseId> Listen::getTopReleases(Session& session, const StatsFindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createReleasesQuery(session, params).orderBy("COUNT(r.id) DESC").groupBy("r.id") };
|
||||
return utils::execRangeQuery<ReleaseId>(query, params.range);
|
||||
}
|
||||
|
||||
std::vector<TrackId> Listen::getTopTracks(Session& session, const StatsFindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createTracksQuery(session, params).orderBy("COUNT(t.id) DESC").groupBy("t.id") };
|
||||
return utils::execRangeQuery<TrackId>(query, params.range);
|
||||
}
|
||||
|
||||
std::vector<ArtistId> Listen::getRecentArtists(Session& session, const ArtistStatsFindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createArtistsQuery(session, params).groupBy("a.id").having("l.date_time = MAX(l.date_time)").orderBy("l.date_time DESC") };
|
||||
return utils::execRangeQuery<ArtistId>(query, params.range);
|
||||
}
|
||||
|
||||
std::vector<ReleaseId> Listen::getRecentReleases(Session& session, const StatsFindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createReleasesQuery(session, params).groupBy("r.id").having("l.date_time = MAX(l.date_time)").orderBy("l.date_time DESC") };
|
||||
return utils::execRangeQuery<ReleaseId>(query, params.range);
|
||||
}
|
||||
|
||||
std::vector<TrackId> Listen::getRecentTracks(Session& session, const StatsFindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createTracksQuery(session, params).groupBy("t.id").having("l.date_time = MAX(l.date_time)").orderBy("l.date_time DESC") };
|
||||
return utils::execRangeQuery<TrackId>(query, params.range);
|
||||
}
|
||||
|
||||
void Listen::getTopArtists(Session& session, const ArtistStatsFindParameters& params, const std::function<void(const ObjectPtr<Artist>&)>& func)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createArtistsQuery(session, params)
|
||||
.orderBy("COUNT(a.id) DESC")
|
||||
.groupBy("a.id") };
|
||||
|
||||
utils::forEachQueryRangeResult(query, params.range, [&](const ArtistId& id) {
|
||||
if (const auto artist{ Artist::find(session, id) })
|
||||
func(artist);
|
||||
});
|
||||
}
|
||||
|
||||
void Listen::getTopReleases(Session& session, const StatsFindParameters& params, const std::function<void(const ObjectPtr<Release>&)>& func)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createReleasesQuery(session, params)
|
||||
.orderBy("COUNT(r.id) DESC")
|
||||
.groupBy("r.id") };
|
||||
|
||||
return utils::execRangeQuery<ReleaseId>(query, params.range);
|
||||
utils::forEachQueryRangeResult(query, params.range, [&](const ReleaseId& id) {
|
||||
if (const auto release{ Release::find(session, id) })
|
||||
func(release);
|
||||
});
|
||||
}
|
||||
|
||||
RangeResults<TrackId> Listen::getTopTracks(Session& session, const StatsFindParameters& params)
|
||||
void Listen::getTopTracks(Session& session, const StatsFindParameters& params, const std::function<void(const ObjectPtr<Track>&)>& func)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createTracksQuery(session, params)
|
||||
.orderBy("COUNT(t.id) DESC")
|
||||
.groupBy("t.id") };
|
||||
|
||||
return utils::execRangeQuery<TrackId>(query, params.range);
|
||||
utils::forEachQueryRangeResult(query, params.range, [&](const TrackId& id) {
|
||||
if (const auto track{ Track::find(session, id) })
|
||||
func(track);
|
||||
});
|
||||
}
|
||||
|
||||
RangeResults<ArtistId> Listen::getRecentArtists(Session& session, const ArtistStatsFindParameters& params)
|
||||
void Listen::getRecentArtists(Session& session, const ArtistStatsFindParameters& params, const std::function<void(const ObjectPtr<Artist>&)>& func)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createArtistsQuery(session, params)
|
||||
@@ -413,10 +470,13 @@ namespace lms::db
|
||||
.having("l.date_time = MAX(l.date_time)")
|
||||
.orderBy("l.date_time DESC") };
|
||||
|
||||
return utils::execRangeQuery<ArtistId>(query, params.range);
|
||||
utils::forEachQueryRangeResult(query, params.range, [&](const ArtistId& id) {
|
||||
if (const auto artist{ Artist::find(session, id) })
|
||||
func(artist);
|
||||
});
|
||||
}
|
||||
|
||||
RangeResults<ReleaseId> Listen::getRecentReleases(Session& session, const StatsFindParameters& params)
|
||||
void Listen::getRecentReleases(Session& session, const StatsFindParameters& params, const std::function<void(const ObjectPtr<Release>&)>& func)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createReleasesQuery(session, params)
|
||||
@@ -424,10 +484,13 @@ namespace lms::db
|
||||
.having("l.date_time = MAX(l.date_time)")
|
||||
.orderBy("l.date_time DESC") };
|
||||
|
||||
return utils::execRangeQuery<ReleaseId>(query, params.range);
|
||||
utils::forEachQueryRangeResult(query, params.range, [&](const ReleaseId& id) {
|
||||
if (const auto release{ Release::find(session, id) })
|
||||
func(release);
|
||||
});
|
||||
}
|
||||
|
||||
RangeResults<TrackId> Listen::getRecentTracks(Session& session, const StatsFindParameters& params)
|
||||
void Listen::getRecentTracks(Session& session, const StatsFindParameters& params, const std::function<void(const ObjectPtr<Track>&)>& func)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createTracksQuery(session, params)
|
||||
@@ -435,7 +498,10 @@ namespace lms::db
|
||||
.having("l.date_time = MAX(l.date_time)")
|
||||
.orderBy("l.date_time DESC") };
|
||||
|
||||
return utils::execRangeQuery<TrackId>(query, params.range);
|
||||
utils::forEachQueryRangeResult(query, params.range, [&](const TrackId& id) {
|
||||
if (const auto track{ Track::find(session, id) })
|
||||
func(track);
|
||||
});
|
||||
}
|
||||
|
||||
std::size_t Listen::getCount(Session& session, UserId userId, TrackId trackId)
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Genre.hpp"
|
||||
#include "database/objects/Grouping.hpp"
|
||||
@@ -38,7 +40,7 @@ DBO_INSTANTIATE_TEMPLATES(lms::db::MediaLibrary)
|
||||
namespace lms::db
|
||||
{
|
||||
MediaLibrary::MediaLibrary(std::string_view name, const std::filesystem::path& p)
|
||||
: _name{ std::string{ name, 0, maxNameLength } }
|
||||
: _name{ core::stringUtils::utf8Truncate(name, maxNameLength) }
|
||||
{
|
||||
setPath(p);
|
||||
}
|
||||
|
||||
@@ -31,12 +31,14 @@
|
||||
#include "database/objects/Language.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
@@ -110,7 +112,7 @@ namespace lms::db
|
||||
return IdRange<MediumId>{ .first = std::get<0>(res), .last = std::get<1>(res) };
|
||||
}
|
||||
|
||||
RangeResults<MediumId> Medium::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<MediumId> Medium::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
@@ -33,12 +34,14 @@
|
||||
#include "database/objects/Language.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
@@ -112,7 +115,7 @@ namespace lms::db
|
||||
} // namespace
|
||||
|
||||
Mood::Mood(std::string_view name)
|
||||
: _name{ name.substr(0, maxNameLength) }
|
||||
: _name{ core::stringUtils::utf8Truncate(name, maxNameLength) }
|
||||
{
|
||||
LMS_LOG_IF(DB, WARNING, name.size() > maxNameLength, "Mood name too long, truncated to '" << _name << "'");
|
||||
}
|
||||
@@ -128,14 +131,14 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM mood"));
|
||||
}
|
||||
|
||||
RangeResults<MoodId> Mood::findIds(Session& session, const FindParameters& params)
|
||||
std::vector<MoodId> Mood::findIds(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createQuery<MoodId>(session, params) };
|
||||
return utils::execRangeQuery<MoodId>(query, params.range);
|
||||
}
|
||||
|
||||
RangeResults<Mood::pointer> Mood::find(Session& session, const FindParameters& params)
|
||||
std::vector<Mood::pointer> Mood::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Mood>>(session, params) };
|
||||
@@ -159,13 +162,12 @@ namespace lms::db
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
if (name.size() > maxNameLength)
|
||||
name = name.substr(0, maxNameLength);
|
||||
name = core::stringUtils::utf8Truncate(name, maxNameLength);
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->find<Mood>().where("name = ?").bind(name));
|
||||
}
|
||||
|
||||
RangeResults<MoodId> Mood::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<MoodId> Mood::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ session.getDboSession()->query<MoodId>("SELECT m.id FROM mood m WHERE NOT EXISTS (SELECT 1 FROM track_mood t_m WHERE t_m.mood_id = m.id)") };
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "database/objects/Movement.hpp"
|
||||
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Cluster.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/Genre.hpp"
|
||||
#include "database/objects/Grouping.hpp"
|
||||
#include "database/objects/Language.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
DBO_INSTANTIATE_TEMPLATES(lms::db::Movement)
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
Movement::Movement(std::string_view name, std::optional<std::size_t> number, std::optional<std::size_t> count, const ObjectPtr<Track>& track)
|
||||
: _name{ core::stringUtils::utf8Truncate(name, maxNameLength) }
|
||||
, _number{ number }
|
||||
, _count{ count }
|
||||
, _track{ getDboPtr(track) }
|
||||
{
|
||||
LMS_LOG_IF(DB, WARNING, name.size() > maxNameLength, "Movement name too long, truncated to '" << _name << "'");
|
||||
}
|
||||
|
||||
Movement::pointer Movement::create(Session& session, std::string_view name, std::optional<std::size_t> number, std::optional<std::size_t> count, const ObjectPtr<Track>& track)
|
||||
{
|
||||
return session.getDboSession()->add(std::unique_ptr<Movement>{ new Movement{ name, number, count, track } });
|
||||
}
|
||||
|
||||
} // namespace lms::db
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <Wt/Dbo/WtSqlTraits.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
@@ -171,7 +173,7 @@ namespace lms::db
|
||||
|
||||
void PlayListFile::setName(std::string_view name)
|
||||
{
|
||||
_name = std::string{ name, 0, _maxNameLength };
|
||||
_name = core::stringUtils::utf8Truncate(name, _maxNameLength);
|
||||
LMS_LOG_IF(DB, WARNING, name.size() > _maxNameLength, "PlaylistFile name too long, truncated to '" << _name << "'");
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
@@ -40,6 +41,7 @@
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
#include <Wt/Dbo/WtSqlTraits.h>
|
||||
|
||||
#include "core/PartialDateTime.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
@@ -35,6 +37,7 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/ReleaseArtistLink.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
@@ -42,6 +45,7 @@
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
@@ -93,6 +97,9 @@ namespace lms::db
|
||||
query.join("track t ON t.release_id = r.id");
|
||||
}
|
||||
|
||||
if (!params.keywords.empty())
|
||||
query.leftJoin("medium m ON m.release_id = r.id");
|
||||
|
||||
if (params.parentDirectory.isValid())
|
||||
{
|
||||
query.join("directory d ON t.directory_id = d.id");
|
||||
@@ -145,19 +152,35 @@ namespace lms::db
|
||||
if (!params.name.empty())
|
||||
query.where("r.name = ?").bind(params.name);
|
||||
|
||||
for (std::string_view keyword : params.keywords)
|
||||
query.where("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeForLikeKeyword(keyword) + "%");
|
||||
if (!params.keywords.empty())
|
||||
{
|
||||
std::vector<std::string> nameClauses;
|
||||
std::vector<std::string> mediumNameClauses;
|
||||
|
||||
for (const std::string_view keyword : params.keywords)
|
||||
{
|
||||
nameClauses.push_back("r.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'");
|
||||
query.bind("%" + utils::escapeForLikeKeyword(keyword) + "%");
|
||||
}
|
||||
|
||||
for (const std::string_view keyword : params.keywords)
|
||||
{
|
||||
mediumNameClauses.push_back("m.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'");
|
||||
query.bind("%" + utils::escapeForLikeKeyword(keyword) + "%");
|
||||
}
|
||||
|
||||
query.where("(" + core::stringUtils::joinStrings(nameClauses, " AND ") + ") OR (" + core::stringUtils::joinStrings(mediumNameClauses, " AND ") + ")");
|
||||
}
|
||||
|
||||
if (params.starringUser.isValid())
|
||||
{
|
||||
assert(params.feedbackBackend);
|
||||
query.join("starred_release s_r ON s_r.release_id = r.id")
|
||||
.join("user u ON u.id = s_r.user_id")
|
||||
.where("s_r.user_id = ?")
|
||||
.bind(params.starringUser)
|
||||
.where("s_r.backend = ?")
|
||||
.bind(*params.feedbackBackend)
|
||||
.where("s_r.sync_state <> ?")
|
||||
.bind(SyncState::PendingRemove);
|
||||
.bind(SyncState::PendingRemove)
|
||||
.where("s_r.backend = u.feedback_backend");
|
||||
}
|
||||
|
||||
if (params.artist.isValid())
|
||||
@@ -213,7 +236,7 @@ namespace lms::db
|
||||
WhereClause clusterClause;
|
||||
for (const ClusterId clusterId : params.filters.clusters)
|
||||
{
|
||||
clusterClause.Or(WhereClause("t_c.cluster_id = ?"));
|
||||
clusterClause.Or(WhereClause{ "t_c.cluster_id = ?" });
|
||||
query.bind(clusterId);
|
||||
}
|
||||
|
||||
@@ -356,7 +379,7 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<Country>>("SELECT c from country c").where("c.name = ?").bind(name));
|
||||
}
|
||||
|
||||
RangeResults<CountryId> Country::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<CountryId> Country::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -418,7 +441,7 @@ namespace lms::db
|
||||
});
|
||||
}
|
||||
|
||||
RangeResults<LabelId> Label::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<LabelId> Label::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -480,7 +503,7 @@ namespace lms::db
|
||||
});
|
||||
}
|
||||
|
||||
RangeResults<ReleaseTypeId> ReleaseType::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<ReleaseTypeId> ReleaseType::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -490,7 +513,7 @@ namespace lms::db
|
||||
}
|
||||
|
||||
Release::Release(const std::string& name, const std::optional<core::UUID>& MBID)
|
||||
: _name{ std::string(name, 0, _maxNameLength) }
|
||||
: _name{ core::stringUtils::utf8Truncate(name, _maxNameLength) }
|
||||
, _MBID{ MBID }
|
||||
{
|
||||
}
|
||||
@@ -527,7 +550,7 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM release"));
|
||||
}
|
||||
|
||||
RangeResults<ReleaseId> Release::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<ReleaseId> Release::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -574,7 +597,7 @@ namespace lms::db
|
||||
return IdRange<ReleaseId>{ .first = std::get<0>(res), .last = std::get<1>(res) };
|
||||
}
|
||||
|
||||
RangeResults<Release::pointer> Release::find(Session& session, const FindParameters& params)
|
||||
std::vector<Release::pointer> Release::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -590,7 +613,7 @@ namespace lms::db
|
||||
utils::forEachQueryRangeResult(query, params.range, func);
|
||||
}
|
||||
|
||||
RangeResults<ReleaseId> Release::findIds(Session& session, const FindParameters& params)
|
||||
std::vector<ReleaseId> Release::findIds(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -980,11 +1003,11 @@ namespace lms::db
|
||||
|
||||
oss << "SELECT c from cluster c INNER JOIN track_cluster t_c ON t_c.cluster_id = c.id INNER JOIN track t ON t.id = t_c.track_id ";
|
||||
|
||||
where.And(WhereClause("t.release_id = ?")).bind(getId().toString());
|
||||
where.And(WhereClause{ "t.release_id = ?" }).bind(getId().toString());
|
||||
{
|
||||
WhereClause clusterClause;
|
||||
for (const ClusterTypeId clusterTypeId : clusterTypeIds)
|
||||
clusterClause.Or(WhereClause("c.cluster_type_id = ?")).bind(clusterTypeId.toString());
|
||||
clusterClause.Or(WhereClause{ "c.cluster_type_id = ?" }).bind(clusterTypeId.toString());
|
||||
where.And(clusterClause);
|
||||
}
|
||||
oss << " " << where.get();
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Genre.hpp"
|
||||
@@ -149,13 +151,13 @@ namespace lms::db
|
||||
|
||||
void ReleaseArtistLink::setArtistName(std::string_view artistName)
|
||||
{
|
||||
_artistName.assign(artistName, 0, Artist::maxNameLength);
|
||||
_artistName = core::stringUtils::utf8Truncate(artistName, Artist::maxNameLength);
|
||||
LMS_LOG_IF(DB, WARNING, artistName.size() > Artist::maxNameLength, "Artist link name too long, truncated to '" << _artistName << "'");
|
||||
}
|
||||
|
||||
void ReleaseArtistLink::setArtistSortName(std::string_view artistSortName)
|
||||
{
|
||||
_artistSortName.assign(artistSortName, 0, Artist::maxNameLength);
|
||||
_artistSortName = core::stringUtils::utf8Truncate(artistSortName, Artist::maxNameLength);
|
||||
LMS_LOG_IF(DB, WARNING, artistSortName.size() > Artist::maxNameLength, "Artist link sort name too long, truncated to '" << _artistSortName << "'");
|
||||
}
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "database/objects/ServerInfo.hpp"
|
||||
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
#include <Wt/Dbo/WtSqlTraits.h>
|
||||
|
||||
#include "database/Session.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/UUIDTraits.hpp"
|
||||
|
||||
DBO_INSTANTIATE_TEMPLATES(lms::db::ServerInfo)
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
ServerInfo::ServerInfo(core::UUID instanceId)
|
||||
: _instanceId{ instanceId }
|
||||
{
|
||||
}
|
||||
|
||||
ServerInfo::pointer ServerInfo::getOrCreate(Session& session)
|
||||
{
|
||||
session.checkWriteTransaction();
|
||||
|
||||
pointer serverInfo{ utils::fetchQuerySingleResult(session.getDboSession()->find<ServerInfo>()) };
|
||||
if (!serverInfo)
|
||||
return session.getDboSession()->add(std::unique_ptr<ServerInfo>{ new ServerInfo{ core::UUID::generate() } });
|
||||
|
||||
return serverInfo;
|
||||
}
|
||||
|
||||
ServerInfo::pointer ServerInfo::get(Session& session)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->find<ServerInfo>());
|
||||
}
|
||||
} // namespace lms::db
|
||||
@@ -78,7 +78,7 @@ namespace lms::db
|
||||
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)
|
||||
std::vector<StarredTrackId> StarredTrack::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <Wt/Dbo/WtSqlTraits.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/Types.hpp"
|
||||
@@ -36,12 +37,14 @@
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
@@ -77,14 +80,13 @@ namespace lms::db
|
||||
|
||||
if (params.starringUser.isValid())
|
||||
{
|
||||
assert(params.feedbackBackend);
|
||||
query.join("starred_track s_t ON s_t.track_id = t.id")
|
||||
.join("user u ON u.id = s_t.user_id")
|
||||
.where("s_t.user_id = ?")
|
||||
.bind(params.starringUser)
|
||||
.where("s_t.backend = ?")
|
||||
.bind(*params.feedbackBackend)
|
||||
.where("s_t.sync_state <> ?")
|
||||
.bind(SyncState::PendingRemove);
|
||||
.bind(SyncState::PendingRemove)
|
||||
.where("s_t.backend = u.feedback_backend");
|
||||
}
|
||||
|
||||
if (params.filters.clusters.size() == 1)
|
||||
@@ -103,7 +105,7 @@ namespace lms::db
|
||||
WhereClause clusterClause;
|
||||
for (const ClusterId clusterId : params.filters.clusters)
|
||||
{
|
||||
clusterClause.Or(WhereClause("t_c.cluster_id = ?"));
|
||||
clusterClause.Or(WhereClause{ "t_c.cluster_id = ?" });
|
||||
query.bind(clusterId);
|
||||
}
|
||||
|
||||
@@ -435,7 +437,7 @@ namespace lms::db
|
||||
return utils::fetchQueryResults<Track::pointer>(session.getDboSession()->query<Wt::Dbo::ptr<Track>>("SELECT t from track t").where("t.recording_mbid = ?").bind(mbid));
|
||||
}
|
||||
|
||||
RangeResults<TrackId> Track::findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range)
|
||||
std::vector<TrackId> Track::findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -564,7 +566,7 @@ namespace lms::db
|
||||
return _preferredMediaArtwork.id();
|
||||
}
|
||||
|
||||
RangeResults<TrackId> Track::findIds(Session& session, const FindParameters& parameters)
|
||||
std::vector<TrackId> Track::findIds(Session& session, const FindParameters& parameters)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -572,7 +574,7 @@ namespace lms::db
|
||||
return utils::execRangeQuery<TrackId>(query, parameters.range);
|
||||
}
|
||||
|
||||
RangeResults<Track::pointer> Track::find(Session& session, const FindParameters& parameters)
|
||||
std::vector<Track::pointer> Track::find(Session& session, const FindParameters& parameters)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
@@ -588,14 +590,6 @@ namespace lms::db
|
||||
utils::forEachQueryRangeResult(query, params.range, func);
|
||||
}
|
||||
|
||||
void Track::find(Session& session, const FindParameters& params, bool& moreResults, const std::function<void(const Track::pointer&)>& func)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
auto query{ createQuery<Wt::Dbo::ptr<Track>>(session, params) };
|
||||
utils::forEachQueryRangeResult(query, params.range, moreResults, func);
|
||||
}
|
||||
|
||||
std::size_t Track::getCount(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
@@ -621,19 +615,19 @@ namespace lms::db
|
||||
|
||||
void Track::setName(std::string_view name)
|
||||
{
|
||||
_name = std::string{ name, 0, _maxNameLength };
|
||||
_name = core::stringUtils::utf8Truncate(name, _maxNameLength);
|
||||
LMS_LOG_IF(DB, WARNING, name.size() > _maxNameLength, "Track name too long, truncated to '" << _name << "'");
|
||||
}
|
||||
|
||||
void Track::setCopyright(std::string_view copyright)
|
||||
{
|
||||
_copyright = std::string{ copyright, 0, _maxCopyrightLength };
|
||||
_copyright = core::stringUtils::utf8Truncate(copyright, _maxCopyrightLength);
|
||||
LMS_LOG_IF(DB, WARNING, copyright.size() > _maxCopyrightLength, "Track copyright too long, truncated to '" << _copyright << "'");
|
||||
}
|
||||
|
||||
void Track::setCopyrightURL(std::string_view copyrightURL)
|
||||
{
|
||||
_copyrightURL = std::string{ copyrightURL, 0, _maxCopyrightURLLength };
|
||||
_copyrightURL = core::stringUtils::utf8Truncate(copyrightURL, _maxCopyrightURLLength);
|
||||
LMS_LOG_IF(DB, WARNING, copyrightURL.size() > _maxCopyrightURLLength, "Track copyright URL too long, truncated to '" << _copyrightURL << "'");
|
||||
}
|
||||
|
||||
@@ -682,6 +676,40 @@ namespace lms::db
|
||||
_moods.insert(getDboPtr(mood));
|
||||
}
|
||||
|
||||
void Track::setWorks(std::span<const ObjectPtr<Work>> works)
|
||||
{
|
||||
_works.clear();
|
||||
for (const ObjectPtr<Work>& work : works)
|
||||
_works.insert(getDboPtr(work));
|
||||
}
|
||||
|
||||
std::vector<Work::pointer> Track::getWorks() const
|
||||
{
|
||||
// deterministic order, callers rely on the first entry
|
||||
return utils::fetchQueryResults<Work::pointer>(_works.find().orderBy("id"));
|
||||
}
|
||||
|
||||
bool Track::hasWork() const
|
||||
{
|
||||
return !_works.empty();
|
||||
}
|
||||
|
||||
void Track::clearMovements()
|
||||
{
|
||||
_movements.clear();
|
||||
}
|
||||
|
||||
std::vector<Movement::pointer> Track::getMovements() const
|
||||
{
|
||||
// deterministic order, callers rely on the first entry
|
||||
return utils::fetchQueryResults<Movement::pointer>(_movements.find().orderBy("id"));
|
||||
}
|
||||
|
||||
bool Track::hasMovement() const
|
||||
{
|
||||
return !_movements.empty();
|
||||
}
|
||||
|
||||
void Track::clearLyrics()
|
||||
{
|
||||
_trackLyrics.clear();
|
||||
@@ -869,11 +897,11 @@ namespace lms::db
|
||||
|
||||
oss << "SELECT c from cluster c INNER JOIN track t ON c.id = t_c.cluster_id INNER JOIN track_cluster t_c ON t_c.track_id = t.id INNER JOIN cluster_type c_type ON c.cluster_type_id = c_type.id";
|
||||
|
||||
where.And(WhereClause("t.id = ?")).bind(getId().toString());
|
||||
where.And(WhereClause{ "t.id = ?" }).bind(getId().toString());
|
||||
{
|
||||
WhereClause clusterClause;
|
||||
for (ClusterTypeId clusterTypeId : clusterTypeIds)
|
||||
clusterClause.Or(WhereClause("c_type.id = ?")).bind(clusterTypeId.toString());
|
||||
clusterClause.Or(WhereClause{ "c_type.id = ?" }).bind(clusterTypeId.toString());
|
||||
where.And(clusterClause);
|
||||
}
|
||||
oss << " " << where.get();
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Genre.hpp"
|
||||
@@ -191,13 +193,13 @@ namespace lms::db
|
||||
|
||||
void TrackArtistLink::setArtistName(std::string_view artistName)
|
||||
{
|
||||
_artistName.assign(artistName, 0, Artist::maxNameLength);
|
||||
_artistName = core::stringUtils::utf8Truncate(artistName, Artist::maxNameLength);
|
||||
LMS_LOG_IF(DB, WARNING, artistName.size() > Artist::maxNameLength, "Artist link name too long, truncated to '" << _artistName << "'");
|
||||
}
|
||||
|
||||
void TrackArtistLink::setArtistSortName(std::string_view artistSortName)
|
||||
{
|
||||
_artistSortName.assign(artistSortName, 0, Artist::maxNameLength);
|
||||
_artistSortName = core::stringUtils::utf8Truncate(artistSortName, Artist::maxNameLength);
|
||||
LMS_LOG_IF(DB, WARNING, artistSortName.size() > Artist::maxNameLength, "Artist link sort name too long, truncated to '" << _artistSortName << "'");
|
||||
}
|
||||
} // namespace lms::db
|
||||
|
||||
@@ -54,7 +54,7 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM track_bookmark"));
|
||||
}
|
||||
|
||||
RangeResults<TrackBookmarkId> TrackBookmark::find(Session& session, UserId userId, std::optional<Range> range)
|
||||
std::vector<TrackBookmarkId> TrackBookmark::find(Session& session, UserId userId, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
|
||||
@@ -27,7 +27,9 @@
|
||||
#include "database/objects/Grouping.hpp"
|
||||
#include "database/objects/Language.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
|
||||
@@ -150,7 +152,7 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(query);
|
||||
}
|
||||
|
||||
RangeResults<TrackEmbeddedImageId> TrackEmbeddedImage::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<TrackEmbeddedImageId> TrackEmbeddedImage::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
|
||||
@@ -29,9 +29,11 @@
|
||||
#include "database/objects/Grouping.hpp"
|
||||
#include "database/objects/Language.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/PlayListFile.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/User.hpp"
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include "SqlQuery.hpp"
|
||||
#include "Utils.hpp"
|
||||
@@ -113,7 +115,7 @@ namespace lms::db
|
||||
WhereClause clusterClause;
|
||||
for (const ClusterId clusterId : params.filters.clusters)
|
||||
{
|
||||
clusterClause.Or(WhereClause("c.id = ?"));
|
||||
clusterClause.Or(WhereClause{ "c.id = ?" });
|
||||
query.bind(clusterId);
|
||||
}
|
||||
|
||||
@@ -182,7 +184,7 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<TrackList>>("select t_l from tracklist t_l").where("t_l.name = ?").bind(name).where("t_l.type = ?").bind(type).where("t_l.user_id = ?").bind(userId));
|
||||
}
|
||||
|
||||
RangeResults<TrackListId> TrackList::find(Session& session, const FindParameters& params)
|
||||
std::vector<TrackListId> TrackList::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ createQuery<TrackListId>(session, params) };
|
||||
@@ -218,13 +220,13 @@ namespace lms::db
|
||||
TrackListEntry::pointer res;
|
||||
|
||||
auto entries = getEntries(Range{ pos, 1 });
|
||||
if (!entries.results.empty())
|
||||
res = entries.results.front();
|
||||
if (!entries.empty())
|
||||
res = entries.front();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
RangeResults<ObjectPtr<TrackListEntry>> TrackList::getEntries(std::optional<Range> range) const
|
||||
std::vector<ObjectPtr<TrackListEntry>> TrackList::getEntries(std::optional<Range> range) const
|
||||
{
|
||||
assert(session());
|
||||
|
||||
|
||||
@@ -121,7 +121,7 @@ namespace lms::db
|
||||
});
|
||||
}
|
||||
|
||||
RangeResults<TrackLyricsId> TrackLyrics::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
std::vector<TrackLyricsId> TrackLyrics::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->find<TrackMusicNNEmbeddings>().where("track_id = ?").bind(trackId));
|
||||
}
|
||||
|
||||
RangeResults<TrackMusicNNEmbeddingsId> TrackMusicNNEmbeddings::find(Session& session, std::optional<Range> range)
|
||||
std::vector<TrackMusicNNEmbeddingsId> TrackMusicNNEmbeddings::find(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ namespace lms::db
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM user"));
|
||||
}
|
||||
|
||||
RangeResults<UserId> User::find(Session& session, const FindParameters& params)
|
||||
std::vector<UserId> User::find(Session& session, const FindParameters& params)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "database/objects/Work.hpp"
|
||||
|
||||
#include <Wt/Dbo/Impl.h>
|
||||
|
||||
#include "core/ILogger.hpp"
|
||||
#include "core/String.hpp"
|
||||
|
||||
#include "database/Session.hpp"
|
||||
#include "database/objects/Artist.hpp"
|
||||
#include "database/objects/Artwork.hpp"
|
||||
#include "database/objects/Cluster.hpp"
|
||||
#include "database/objects/Directory.hpp"
|
||||
#include "database/objects/Genre.hpp"
|
||||
#include "database/objects/Grouping.hpp"
|
||||
#include "database/objects/Language.hpp"
|
||||
#include "database/objects/MediaLibrary.hpp"
|
||||
#include "database/objects/Medium.hpp"
|
||||
#include "database/objects/Mood.hpp"
|
||||
#include "database/objects/Movement.hpp"
|
||||
#include "database/objects/Release.hpp"
|
||||
#include "database/objects/Track.hpp"
|
||||
#include "database/objects/TrackArtistLink.hpp"
|
||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageLink.hpp"
|
||||
#include "database/objects/TrackLyrics.hpp"
|
||||
|
||||
#include "Utils.hpp"
|
||||
#include "traits/IdTypeTraits.hpp"
|
||||
#include "traits/UUIDTraits.hpp"
|
||||
|
||||
DBO_INSTANTIATE_TEMPLATES(lms::db::Work)
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
Work::Work(std::string_view name, const std::optional<core::UUID>& mbid)
|
||||
: _mbid{ mbid }
|
||||
{
|
||||
setName(name);
|
||||
}
|
||||
|
||||
void Work::setName(std::string_view name)
|
||||
{
|
||||
_name = core::stringUtils::utf8Truncate(name, maxNameLength);
|
||||
LMS_LOG_IF(DB, WARNING, name.size() > maxNameLength, "Work name too long, truncated to '" << _name << "'");
|
||||
}
|
||||
|
||||
Work::pointer Work::create(Session& session, std::string_view name, const std::optional<core::UUID>& mbid)
|
||||
{
|
||||
return session.getDboSession()->add(std::unique_ptr<Work>{ new Work{ name, mbid } });
|
||||
}
|
||||
|
||||
Work::pointer Work::find(Session& session, WorkId id)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->find<Work>().where("id = ?").bind(id));
|
||||
}
|
||||
|
||||
Work::pointer Work::find(Session& session, const core::UUID& mbid)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
return utils::fetchQuerySingleResult(session.getDboSession()->find<Work>().where("mbid = ?").bind(mbid));
|
||||
}
|
||||
|
||||
Work::pointer Work::find(Session& session, ReleaseId releaseId, std::string_view name)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
|
||||
name = core::stringUtils::utf8Truncate(name, maxNameLength);
|
||||
|
||||
auto query{
|
||||
session.getDboSession()->query<Wt::Dbo::ptr<Work>>("SELECT w FROM work w")
|
||||
// clang-format off
|
||||
.join("track_work t_w ON t_w.work_id = w.id")
|
||||
.join("track t ON t.id = t_w.track_id")
|
||||
.where("t.release_id = ?").bind(releaseId)
|
||||
.where("w.name = ?").bind(std::string{ name })
|
||||
.where("w.mbid IS NULL")
|
||||
.groupBy("w.id")
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
return utils::fetchQuerySingleResult(query);
|
||||
}
|
||||
|
||||
std::vector<WorkId> Work::findOrphanIds(Session& session, std::optional<Range> range)
|
||||
{
|
||||
session.checkReadTransaction();
|
||||
auto query{ session.getDboSession()->query<WorkId>("SELECT w.id FROM work w WHERE NOT EXISTS (SELECT 1 FROM track_work t_w WHERE t_w.work_id = w.id)") };
|
||||
return utils::execRangeQuery<WorkId>(query, range);
|
||||
}
|
||||
|
||||
} // namespace lms::db
|
||||
@@ -28,6 +28,7 @@
|
||||
|
||||
#include "database/Transaction.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/Types.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
@@ -58,6 +59,8 @@ namespace lms::db
|
||||
|
||||
void prepareTablesIfNeeded(); // need to run only once at startup
|
||||
bool migrateSchemaIfNeeded(); // returns true if migration was performed
|
||||
void createScanSettingsIfNeeded(RecommendationEngineType defaultRecommendationEngineType = RecommendationEngineType::Clusters);
|
||||
void createServerInfoIfNeeded();
|
||||
void createIndexesIfNeeded();
|
||||
void vacuumIfNeeded();
|
||||
void vacuum();
|
||||
|
||||
@@ -38,8 +38,6 @@ namespace lms::db
|
||||
|
||||
// Request:
|
||||
// size = 0 => means we don't want data
|
||||
// Response (via RangeResults)
|
||||
// size => results size
|
||||
struct Range
|
||||
{
|
||||
std::size_t offset{};
|
||||
@@ -65,40 +63,6 @@ namespace lms::db
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
struct RangeResults
|
||||
{
|
||||
Range range;
|
||||
std::vector<T> results;
|
||||
bool moreResults{};
|
||||
|
||||
RangeResults getSubRange(Range subRange)
|
||||
{
|
||||
assert(subRange.offset >= range.offset);
|
||||
|
||||
if (!subRange.size)
|
||||
subRange.size = range.size - (subRange.offset - range.offset);
|
||||
|
||||
subRange.offset = std::min(subRange.offset, range.offset + range.size);
|
||||
subRange.size = std::min(subRange.size, range.offset + range.size - subRange.offset);
|
||||
|
||||
RangeResults subResults;
|
||||
|
||||
auto itBegin{ std::cbegin(results) + subRange.offset - range.offset };
|
||||
auto itEnd{ itBegin + subRange.size };
|
||||
subResults.results.reserve(std::distance(itBegin, itEnd));
|
||||
std::copy(itBegin, itEnd, std::back_inserter(subResults.results));
|
||||
|
||||
subResults.range = subRange;
|
||||
if (subRange.offset + subRange.size == range.offset + range.size)
|
||||
subResults.moreResults = moreResults;
|
||||
else
|
||||
subResults.moreResults = true;
|
||||
|
||||
return subResults;
|
||||
}
|
||||
};
|
||||
|
||||
struct FileStats
|
||||
{
|
||||
std::size_t trackCount;
|
||||
|
||||
@@ -66,9 +66,8 @@ namespace lms::db
|
||||
ArtistSortMethod sortMethod{ ArtistSortMethod::None };
|
||||
std::optional<Range> range;
|
||||
Wt::WDateTime writtenAfter;
|
||||
UserId starringUser; // only artists starred by this user
|
||||
std::optional<FeedbackBackend> feedbackBackend; // and for this feedback backend
|
||||
TrackId track; // artists involved in this track
|
||||
UserId starringUser; // only artists starred by this user (uses their current feedback backend)
|
||||
TrackId track; // artists involved in this track
|
||||
|
||||
FindParameters& setFilters(const Filters& _filters)
|
||||
{
|
||||
@@ -105,10 +104,9 @@ namespace lms::db
|
||||
writtenAfter = _after;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend)
|
||||
FindParameters& setStarringUser(UserId _user)
|
||||
{
|
||||
starringUser = _user;
|
||||
feedbackBackend = _feedbackBackend;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setTrack(TrackId _track)
|
||||
@@ -127,13 +125,13 @@ namespace lms::db
|
||||
static std::vector<pointer> find(Session& session, std::string_view name); // exact match on name field
|
||||
static void find(Session& session, ArtistId& lastRetrievedArtist, std::size_t count, const std::function<void(const Artist::pointer&)>& func, MediaLibraryId library = {});
|
||||
static void find(Session& session, const IdRange<ArtistId>& idRange, const std::function<void(const Artist::pointer&)>& func);
|
||||
static RangeResults<pointer> find(Session& session, const FindParameters& params);
|
||||
static std::vector<pointer> find(Session& session, const FindParameters& params);
|
||||
static void find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func);
|
||||
static IdRange<ArtistId> findNextIdRange(Session& session, ArtistId lastRetrievedId, std::size_t count);
|
||||
static RangeResults<ArtistId> findIds(Session& session, const FindParameters& params);
|
||||
static RangeResults<ArtistId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt); // No track related
|
||||
static std::vector<ArtistId> findIds(Session& session, const FindParameters& params);
|
||||
static std::vector<ArtistId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt); // No track related
|
||||
static bool exists(Session& session, ArtistId id);
|
||||
static RangeResults<pointer> findWithMBIDNameVariants(Session& session, ArtistId& lastRetrievedArtist, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<pointer> findWithMBIDNameVariants(Session& session, ArtistId& lastRetrievedArtist, std::optional<Range> range = std::nullopt);
|
||||
|
||||
// Updates
|
||||
static void updatePreferredArtwork(Session& session, ArtistId artistId, ArtworkId artworkId);
|
||||
|
||||
@@ -91,11 +91,11 @@ namespace lms::db
|
||||
|
||||
// Find utility
|
||||
static std::size_t getCount(Session& session);
|
||||
static RangeResults<ClusterId> findIds(Session& session, const FindParameters& params);
|
||||
static RangeResults<pointer> find(Session& session, const FindParameters& params);
|
||||
static std::vector<ClusterId> findIds(Session& session, const FindParameters& params);
|
||||
static std::vector<pointer> find(Session& session, const FindParameters& params);
|
||||
static void find(Session& session, const FindParameters& params, std::function<void(const pointer& cluster)> _func);
|
||||
static pointer find(Session& session, ClusterId id);
|
||||
static RangeResults<ClusterId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<ClusterId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
// May be very slow
|
||||
static std::size_t computeTrackCount(Session& session, ClusterId id);
|
||||
@@ -104,7 +104,7 @@ namespace lms::db
|
||||
// Accessors
|
||||
std::string_view getName() const { return _name; }
|
||||
ObjectPtr<ClusterType> getType() const { return _clusterType; }
|
||||
RangeResults<TrackId> getTracks(std::optional<Range> range = std::nullopt) const;
|
||||
std::vector<TrackId> getTracks(std::optional<Range> range = std::nullopt) const;
|
||||
void addTrack(ObjectPtr<Track> track);
|
||||
|
||||
template<class Action>
|
||||
@@ -136,12 +136,12 @@ namespace lms::db
|
||||
|
||||
// Getters
|
||||
static std::size_t getCount(Session& session);
|
||||
static RangeResults<ClusterTypeId> findIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<ClusterTypeId> findIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static void find(Session& session, const std::function<void(const pointer&)>& func);
|
||||
static pointer find(Session& session, std::string_view name);
|
||||
static pointer find(Session& session, ClusterTypeId id);
|
||||
static RangeResults<ClusterTypeId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static RangeResults<ClusterTypeId> findUsed(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<ClusterTypeId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<ClusterTypeId> findUsed(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
static void remove(Session& session, const std::string& name);
|
||||
|
||||
|
||||
@@ -122,11 +122,11 @@ namespace lms::db
|
||||
static pointer find(Session& session, DirectoryId id);
|
||||
static pointer find(Session& session, const std::filesystem::path& path);
|
||||
static void find(Session& session, DirectoryId& lastRetrievedDirectory, std::size_t count, const std::function<void(const Directory::pointer&)>& func);
|
||||
static RangeResults<Directory::pointer> find(Session& session, const FindParameters& params);
|
||||
static std::vector<Directory::pointer> find(Session& session, const FindParameters& params);
|
||||
static void find(Session& session, const FindParameters& params, const std::function<void(const Directory::pointer&)>& func);
|
||||
static RangeResults<DirectoryId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static RangeResults<DirectoryId> findMismatchedLibrary(Session& session, std::optional<Range> range, const std::filesystem::path& rootPath, MediaLibraryId expectedLibraryId);
|
||||
static RangeResults<pointer> findRootDirectories(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<DirectoryId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<DirectoryId> findMismatchedLibrary(Session& session, std::optional<Range> range, const std::filesystem::path& rootPath, MediaLibraryId expectedLibraryId);
|
||||
static std::vector<pointer> findRootDirectories(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
// getters
|
||||
const std::filesystem::path& getAbsolutePath() const { return _absolutePath; }
|
||||
|
||||
@@ -82,12 +82,12 @@ namespace lms::db
|
||||
Genre() = default;
|
||||
|
||||
static std::size_t getCount(Session& session);
|
||||
static RangeResults<GenreId> findIds(Session& session, const FindParameters& params);
|
||||
static RangeResults<pointer> find(Session& session, const FindParameters& params);
|
||||
static std::vector<GenreId> findIds(Session& session, const FindParameters& params);
|
||||
static std::vector<pointer> find(Session& session, const FindParameters& params);
|
||||
static void find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func);
|
||||
static pointer find(Session& session, GenreId id);
|
||||
static pointer find(Session& session, std::string_view name);
|
||||
static RangeResults<GenreId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<GenreId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
static std::size_t computeTrackCount(Session& session, GenreId id);
|
||||
static std::size_t computeReleaseCount(Session& session, GenreId id);
|
||||
|
||||
@@ -82,12 +82,12 @@ namespace lms::db
|
||||
Grouping() = default;
|
||||
|
||||
static std::size_t getCount(Session& session);
|
||||
static RangeResults<GroupingId> findIds(Session& session, const FindParameters& params);
|
||||
static RangeResults<pointer> find(Session& session, const FindParameters& params);
|
||||
static std::vector<GroupingId> findIds(Session& session, const FindParameters& params);
|
||||
static std::vector<pointer> find(Session& session, const FindParameters& params);
|
||||
static void find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func);
|
||||
static pointer find(Session& session, GroupingId id);
|
||||
static pointer find(Session& session, std::string_view name);
|
||||
static RangeResults<GroupingId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<GroupingId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
std::string_view getName() const { return _name; }
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace lms::db
|
||||
static std::size_t getCount(Session& session);
|
||||
static pointer find(Session& session, ImageId id);
|
||||
static pointer find(Session& session, const std::filesystem::path& file);
|
||||
static RangeResults<pointer> find(Session& session, const FindParameters& params);
|
||||
static std::vector<pointer> find(Session& session, const FindParameters& params);
|
||||
static void find(Session& session, const FindParameters& parameters, const std::function<void(const Image::pointer&)>& func);
|
||||
static void find(Session& session, ImageId& lastRetrievedId, std::size_t count, const std::function<void(const Image::pointer&)>& func);
|
||||
static void findAbsoluteFilePath(Session& session, ImageId& lastRetrievedId, std::size_t count, const std::function<void(ImageId imageId, const std::filesystem::path& absoluteFilePath)>& func);
|
||||
|
||||
@@ -82,12 +82,12 @@ namespace lms::db
|
||||
Language() = default;
|
||||
|
||||
static std::size_t getCount(Session& session);
|
||||
static RangeResults<LanguageId> findIds(Session& session, const FindParameters& params);
|
||||
static RangeResults<pointer> find(Session& session, const FindParameters& params);
|
||||
static std::vector<LanguageId> findIds(Session& session, const FindParameters& params);
|
||||
static std::vector<pointer> find(Session& session, const FindParameters& params);
|
||||
static void find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func);
|
||||
static pointer find(Session& session, LanguageId id);
|
||||
static pointer find(Session& session, std::string_view name);
|
||||
static RangeResults<LanguageId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<LanguageId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
std::string_view getName() const { return _name; }
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
#include <Wt/Dbo/Field.h>
|
||||
@@ -36,6 +37,8 @@
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class Artist;
|
||||
class Release;
|
||||
class Session;
|
||||
class Track;
|
||||
class User;
|
||||
@@ -78,13 +81,12 @@ namespace lms::db
|
||||
static std::size_t getCount(Session& session);
|
||||
static pointer find(Session& session, ListenId id);
|
||||
static pointer find(Session& session, UserId userId, TrackId trackId, ScrobblingBackend backend, const Wt::WDateTime& dateTime);
|
||||
static RangeResults<ListenId> find(Session& session, const FindParameters& parameters);
|
||||
static std::vector<ListenId> find(Session& session, const FindParameters& parameters);
|
||||
|
||||
// Stats
|
||||
struct StatsFindParameters
|
||||
{
|
||||
UserId user;
|
||||
std::optional<ScrobblingBackend> backend;
|
||||
db::Filters filters;
|
||||
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords
|
||||
std::optional<Range> range;
|
||||
@@ -95,11 +97,6 @@ namespace lms::db
|
||||
user = _user;
|
||||
return *this;
|
||||
}
|
||||
StatsFindParameters& setScrobblingBackend(std::optional<ScrobblingBackend> _backend)
|
||||
{
|
||||
backend = _backend;
|
||||
return *this;
|
||||
}
|
||||
StatsFindParameters& setFilters(const db::Filters& _filters)
|
||||
{
|
||||
filters = _filters;
|
||||
@@ -139,13 +136,19 @@ namespace lms::db
|
||||
}
|
||||
};
|
||||
|
||||
static RangeResults<ArtistId> getTopArtists(Session& session, const ArtistStatsFindParameters& params);
|
||||
static RangeResults<ReleaseId> getTopReleases(Session& session, const StatsFindParameters& params);
|
||||
static RangeResults<TrackId> getTopTracks(Session& session, const StatsFindParameters& params);
|
||||
static std::vector<ArtistId> getTopArtists(Session& session, const ArtistStatsFindParameters& params);
|
||||
static std::vector<ReleaseId> getTopReleases(Session& session, const StatsFindParameters& params);
|
||||
static std::vector<TrackId> getTopTracks(Session& session, const StatsFindParameters& params);
|
||||
static std::vector<ArtistId> getRecentArtists(Session& session, const ArtistStatsFindParameters& params);
|
||||
static std::vector<ReleaseId> getRecentReleases(Session& session, const StatsFindParameters& params);
|
||||
static std::vector<TrackId> getRecentTracks(Session& session, const StatsFindParameters& params);
|
||||
|
||||
static RangeResults<ArtistId> getRecentArtists(Session& session, const ArtistStatsFindParameters& params);
|
||||
static RangeResults<ReleaseId> getRecentReleases(Session& session, const StatsFindParameters& params);
|
||||
static RangeResults<TrackId> getRecentTracks(Session& session, const StatsFindParameters& params);
|
||||
static void getTopArtists(Session& session, const ArtistStatsFindParameters& params, const std::function<void(const ObjectPtr<Artist>&)>& func);
|
||||
static void getTopReleases(Session& session, const StatsFindParameters& params, const std::function<void(const ObjectPtr<Release>&)>& func);
|
||||
static void getTopTracks(Session& session, const StatsFindParameters& params, const std::function<void(const ObjectPtr<Track>&)>& func);
|
||||
static void getRecentArtists(Session& session, const ArtistStatsFindParameters& params, const std::function<void(const ObjectPtr<Artist>&)>& func);
|
||||
static void getRecentReleases(Session& session, const StatsFindParameters& params, const std::function<void(const ObjectPtr<Release>&)>& func);
|
||||
static void getRecentTracks(Session& session, const StatsFindParameters& params, const std::function<void(const ObjectPtr<Track>&)>& func);
|
||||
|
||||
static std::size_t getCount(Session& session, UserId userId, TrackId trackId); // for the current backend
|
||||
static std::size_t getCount(Session& session, UserId userId, ReleaseId trackId); // for the current backend
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace lms::db
|
||||
static pointer find(Session& session, ReleaseId id, std::optional<std::size_t> position);
|
||||
static void find(Session& session, const IdRange<MediumId>& idRange, const std::function<void(const Medium::pointer&)>& func);
|
||||
static IdRange<MediumId> findNextIdRange(Session& session, MediumId lastRetrievedId, std::size_t count);
|
||||
static RangeResults<MediumId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<MediumId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
// Updates
|
||||
static void updatePreferredArtwork(Session& session, MediumId mediumId, ArtworkId artworkId);
|
||||
|
||||
@@ -82,12 +82,12 @@ namespace lms::db
|
||||
Mood() = default;
|
||||
|
||||
static std::size_t getCount(Session& session);
|
||||
static RangeResults<MoodId> findIds(Session& session, const FindParameters& params);
|
||||
static RangeResults<pointer> find(Session& session, const FindParameters& params);
|
||||
static std::vector<MoodId> findIds(Session& session, const FindParameters& params);
|
||||
static std::vector<pointer> find(Session& session, const FindParameters& params);
|
||||
static void find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func);
|
||||
static pointer find(Session& session, MoodId id);
|
||||
static pointer find(Session& session, std::string_view name);
|
||||
static RangeResults<MoodId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<MoodId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
std::string_view getName() const { return _name; }
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <Wt/Dbo/Field.h>
|
||||
|
||||
#include "database/Object.hpp"
|
||||
#include "database/objects/MovementId.hpp"
|
||||
#include "database/objects/TrackId.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class Session;
|
||||
class Track;
|
||||
|
||||
class Movement final : public Object<Movement, MovementId>
|
||||
{
|
||||
public:
|
||||
static constexpr std::size_t maxNameLength{ 512 };
|
||||
|
||||
Movement() = default;
|
||||
|
||||
static pointer create(Session& session, std::string_view name, std::optional<std::size_t> number, std::optional<std::size_t> count, const ObjectPtr<Track>& track);
|
||||
|
||||
std::string_view getName() const { return _name; }
|
||||
std::optional<std::size_t> getNumber() const { return _number; }
|
||||
std::optional<std::size_t> getCount() const { return _count; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
Wt::Dbo::field(a, _number, "number");
|
||||
Wt::Dbo::field(a, _count, "count");
|
||||
Wt::Dbo::belongsTo(a, _track, "track", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Session;
|
||||
Movement(std::string_view name, std::optional<std::size_t> number, std::optional<std::size_t> count, const ObjectPtr<Track>& track);
|
||||
|
||||
std::string _name;
|
||||
std::optional<int> _number;
|
||||
std::optional<int> _count;
|
||||
Wt::Dbo::ptr<Track> _track;
|
||||
};
|
||||
|
||||
} // namespace lms::db
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "database/IdType.hpp"
|
||||
|
||||
LMS_DECLARE_IDTYPE(MovementId)
|
||||
@@ -70,7 +70,7 @@ namespace lms::db
|
||||
static std::size_t getCount(Session& session);
|
||||
static pointer find(Session& session, CountryId id);
|
||||
static pointer find(Session& session, std::string_view name);
|
||||
static RangeResults<CountryId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<CountryId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
// Accessors
|
||||
std::string_view getName() const { return _name; }
|
||||
@@ -102,7 +102,7 @@ namespace lms::db
|
||||
static pointer find(Session& session, LabelId id);
|
||||
static pointer find(Session& session, std::string_view name);
|
||||
static void find(Session& session, LabelSortMethod sortMethod, std::function<void(const Label::pointer& label)> func);
|
||||
static RangeResults<LabelId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<LabelId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
// Accessors
|
||||
std::string_view getName() const { return _name; }
|
||||
@@ -134,7 +134,7 @@ namespace lms::db
|
||||
static pointer find(Session& session, ReleaseTypeId id);
|
||||
static pointer find(Session& session, std::string_view name);
|
||||
static void find(Session& session, ReleaseTypeSortMethod sortMethod, std::function<void(const ReleaseType::pointer& releaseType)> func);
|
||||
static RangeResults<ReleaseTypeId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<ReleaseTypeId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
// Accessors
|
||||
std::string_view getName() const { return _name; }
|
||||
@@ -170,8 +170,7 @@ namespace lms::db
|
||||
Wt::WDateTime writtenAfter;
|
||||
std::optional<YearRange> dateRange;
|
||||
std::optional<YearRange> originalDateRange;
|
||||
UserId starringUser; // only releases starred by this user
|
||||
std::optional<FeedbackBackend> feedbackBackend; // and for this backend
|
||||
UserId starringUser; // only releases starred by this user (uses their current feedback backend)
|
||||
ArtistId artist; // only releases by this release artist
|
||||
ArtistId trackArtist; // only releases that involved this track artist
|
||||
core::EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types, if set
|
||||
@@ -220,10 +219,9 @@ namespace lms::db
|
||||
originalDateRange = _originalDateRange;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend)
|
||||
FindParameters& setStarringUser(UserId _user)
|
||||
{
|
||||
starringUser = _user;
|
||||
feedbackBackend = _feedbackBackend;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setArtist(ArtistId _artist)
|
||||
@@ -269,11 +267,11 @@ namespace lms::db
|
||||
static void find(Session& session, ReleaseId& lastRetrievedRelease, std::size_t count, const std::function<void(const Release::pointer&)>& func, MediaLibraryId library = {});
|
||||
static void find(Session& session, const IdRange<ReleaseId>& idRange, const std::function<void(const Release::pointer&)>& func);
|
||||
static IdRange<ReleaseId> findNextIdRange(Session& session, ReleaseId lastRetrievedId, std::size_t count);
|
||||
static RangeResults<pointer> find(Session& session, const FindParameters& parameters);
|
||||
static std::vector<pointer> find(Session& session, const FindParameters& parameters);
|
||||
static void find(Session& session, const FindParameters& parameters, const std::function<void(const pointer&)>& func);
|
||||
static RangeResults<ReleaseId> findIds(Session& session, const FindParameters& parameters);
|
||||
static std::vector<ReleaseId> findIds(Session& session, const FindParameters& parameters);
|
||||
static std::size_t getCount(Session& session, const FindParameters& parameters);
|
||||
static RangeResults<ReleaseId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt); // not track related
|
||||
static std::vector<ReleaseId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt); // not track related
|
||||
|
||||
// Updates
|
||||
static void updatePreferredArtwork(Session& session, ReleaseId id, ArtworkId artworkId);
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
|
||||
#include "database/IdType.hpp"
|
||||
#include "database/Object.hpp"
|
||||
#include "database/objects/Types.hpp"
|
||||
|
||||
LMS_DECLARE_IDTYPE(ScanSettingsId)
|
||||
|
||||
@@ -49,14 +50,6 @@ namespace lms::db
|
||||
Hourly,
|
||||
};
|
||||
|
||||
// Do not modify values (just add)
|
||||
enum class RecommendationEngineType
|
||||
{
|
||||
Clusters = 0,
|
||||
None = 2,
|
||||
AudioSimilarity = 3,
|
||||
};
|
||||
|
||||
ScanSettings() = default;
|
||||
|
||||
static pointer find(Session& session, std::string_view name = "");
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2026 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Wt/Dbo/Field.h>
|
||||
|
||||
#include "core/UUID.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class Session;
|
||||
|
||||
// Singleton row holding server-level metadata (not tied to any particular schema version)
|
||||
class ServerInfo
|
||||
{
|
||||
public:
|
||||
using pointer = Wt::Dbo::ptr<ServerInfo>;
|
||||
|
||||
ServerInfo() = default;
|
||||
|
||||
static pointer getOrCreate(Session& session);
|
||||
static pointer get(Session& session);
|
||||
|
||||
core::UUID getInstanceId() const { return _instanceId; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _instanceId, "instance_id");
|
||||
}
|
||||
|
||||
private:
|
||||
explicit ServerInfo(core::UUID instanceId);
|
||||
|
||||
core::UUID _instanceId;
|
||||
};
|
||||
} // namespace lms::db
|
||||
@@ -73,7 +73,7 @@ namespace lms::db
|
||||
static pointer find(Session& session, TrackId trackId, UserId userId); // current feedback backend
|
||||
static pointer find(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend);
|
||||
static bool exists(Session& session, TrackId trackId, UserId userId, FeedbackBackend backend);
|
||||
static RangeResults<StarredTrackId> find(Session& session, const FindParameters& findParams);
|
||||
static std::vector<StarredTrackId> find(Session& session, const FindParameters& findParams);
|
||||
|
||||
// Accessors
|
||||
ObjectPtr<Track> getTrack() const { return _track; }
|
||||
|
||||
@@ -51,12 +51,14 @@
|
||||
#include "database/objects/MediaLibraryId.hpp"
|
||||
#include "database/objects/MediumId.hpp"
|
||||
#include "database/objects/MoodId.hpp"
|
||||
#include "database/objects/MovementId.hpp"
|
||||
#include "database/objects/ReleaseId.hpp"
|
||||
#include "database/objects/TrackEmbeddedImageId.hpp"
|
||||
#include "database/objects/TrackId.hpp"
|
||||
#include "database/objects/TrackListId.hpp"
|
||||
#include "database/objects/Types.hpp"
|
||||
#include "database/objects/UserId.hpp"
|
||||
#include "database/objects/WorkId.hpp"
|
||||
#include "database/objects/detail/Types.hpp"
|
||||
|
||||
namespace lms::db
|
||||
@@ -69,16 +71,18 @@ namespace lms::db
|
||||
class Genre;
|
||||
class Grouping;
|
||||
class Language;
|
||||
class Medium;
|
||||
class Movement;
|
||||
class Mood;
|
||||
class TrackEmbeddedImageLink;
|
||||
class MediaLibrary;
|
||||
class Medium;
|
||||
class Release;
|
||||
class Session;
|
||||
class TrackArtistLink;
|
||||
class TrackLyrics;
|
||||
class TrackStats;
|
||||
class User;
|
||||
class Work;
|
||||
|
||||
class Track final : public Object<Track, TrackId>
|
||||
{
|
||||
@@ -91,8 +95,7 @@ namespace lms::db
|
||||
TrackSortMethod sortMethod{ TrackSortMethod::None };
|
||||
std::optional<Range> range;
|
||||
Wt::WDateTime writtenAfter;
|
||||
UserId starringUser; // only tracks starred by this user
|
||||
std::optional<FeedbackBackend> feedbackBackend; // and for this feedback backend
|
||||
UserId starringUser; // only tracks starred by this user (uses their current feedback backend)
|
||||
ArtistId artist; // only tracks that involve this artist
|
||||
std::string artistName; // only tracks that involve this artist name
|
||||
core::EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
|
||||
@@ -138,10 +141,9 @@ namespace lms::db
|
||||
writtenAfter = _after;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setStarringUser(UserId _user, FeedbackBackend _feedbackBackend)
|
||||
FindParameters& setStarringUser(UserId _user)
|
||||
{
|
||||
starringUser = _user;
|
||||
feedbackBackend = _feedbackBackend;
|
||||
return *this;
|
||||
}
|
||||
FindParameters& setArtist(ArtistId _artist, core::EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {})
|
||||
@@ -231,12 +233,11 @@ namespace lms::db
|
||||
static bool exists(Session& session, TrackId id);
|
||||
static std::vector<pointer> findByRecordingMBID(Session& session, const core::UUID& MBID);
|
||||
static std::vector<pointer> findByMBID(Session& session, const core::UUID& MBID);
|
||||
static RangeResults<TrackId> findIds(Session& session, const FindParameters& params);
|
||||
static RangeResults<pointer> find(Session& session, const FindParameters& params);
|
||||
static std::vector<TrackId> findIds(Session& session, const FindParameters& params);
|
||||
static std::vector<pointer> find(Session& session, const FindParameters& params);
|
||||
static void find(Session& session, const FindParameters& params, const std::function<void(const Track::pointer&)>& func);
|
||||
static void find(Session& session, const FindParameters& params, bool& moreResults, const std::function<void(const Track::pointer&)>& func);
|
||||
static std::size_t getCount(Session& session, const FindParameters& params);
|
||||
static RangeResults<TrackId> findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<TrackId> findIdsTrackMBIDDuplicates(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
// Update utility functions
|
||||
static void updatePreferredArtwork(Session& session, TrackId trackId, ArtworkId artworkId);
|
||||
@@ -282,6 +283,8 @@ namespace lms::db
|
||||
void setGroupings(std::span<const ObjectPtr<Grouping>> groupings);
|
||||
void setLanguages(std::span<const ObjectPtr<Language>> languages);
|
||||
void setMoods(std::span<const ObjectPtr<Mood>> moods);
|
||||
void setWorks(std::span<const ObjectPtr<Work>> works);
|
||||
void clearMovements();
|
||||
void clearLyrics();
|
||||
void clearEmbeddedLyrics();
|
||||
void addLyrics(const ObjectPtr<TrackLyrics>& lyrics);
|
||||
@@ -313,7 +316,7 @@ namespace lms::db
|
||||
|
||||
// Metadata
|
||||
std::optional<std::size_t> getTrackNumber() const { return _trackNumber; }
|
||||
std::string getName() const { return _name; }
|
||||
std::string_view getName() const { return _name; }
|
||||
const core::PartialDateTime& getDate() const { return _date; }
|
||||
std::optional<int> getYear() const;
|
||||
const core::PartialDateTime& getOriginalDate() const { return _originalDate; }
|
||||
@@ -350,6 +353,10 @@ namespace lms::db
|
||||
std::vector<LanguageId> getLanguageIds() const;
|
||||
std::vector<ObjectPtr<Mood>> getMoods() const;
|
||||
std::vector<MoodId> getMoodIds() const;
|
||||
std::vector<ObjectPtr<Work>> getWorks() const;
|
||||
bool hasWork() const;
|
||||
std::vector<ObjectPtr<Movement>> getMovements() const;
|
||||
bool hasMovement() const;
|
||||
ObjectPtr<MediaLibrary> getMediaLibrary() const;
|
||||
ObjectPtr<Directory> getDirectory() const;
|
||||
ObjectPtr<Artwork> getPreferredArtwork() const;
|
||||
@@ -396,12 +403,14 @@ namespace lms::db
|
||||
Wt::Dbo::belongsTo(a, _directory, "directory", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::belongsTo(a, _preferredArtwork, "preferred_artwork", Wt::Dbo::OnDeleteSetNull);
|
||||
Wt::Dbo::belongsTo(a, _preferredMediaArtwork, "preferred_media_artwork", Wt::Dbo::OnDeleteSetNull);
|
||||
Wt::Dbo::hasMany(a, _movements, Wt::Dbo::ManyToOne, "track");
|
||||
Wt::Dbo::hasMany(a, _trackArtistLinks, Wt::Dbo::ManyToOne, "track");
|
||||
Wt::Dbo::hasMany(a, _clusters, Wt::Dbo::ManyToMany, "track_cluster", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _genres, Wt::Dbo::ManyToMany, "track_genre", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _groupings, Wt::Dbo::ManyToMany, "track_grouping", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _languages, Wt::Dbo::ManyToMany, "track_language", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _moods, Wt::Dbo::ManyToMany, "track_mood", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _works, Wt::Dbo::ManyToMany, "track_work", "", Wt::Dbo::OnDeleteCascade);
|
||||
Wt::Dbo::hasMany(a, _trackLyrics, Wt::Dbo::ManyToOne, "track");
|
||||
Wt::Dbo::hasMany(a, _embeddedImageLinks, Wt::Dbo::ManyToOne, "track");
|
||||
}
|
||||
@@ -444,7 +453,6 @@ namespace lms::db
|
||||
std::string _artistDisplayName;
|
||||
std::string _comment;
|
||||
Advisory _advisory{ Advisory::UnSet };
|
||||
|
||||
Wt::Dbo::ptr<Medium> _medium;
|
||||
Wt::Dbo::ptr<Release> _release;
|
||||
Wt::Dbo::ptr<MediaLibrary> _mediaLibrary;
|
||||
@@ -457,6 +465,8 @@ namespace lms::db
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Grouping>> _groupings;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Language>> _languages;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Mood>> _moods;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Work>> _works;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Movement>> _movements;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<TrackLyrics>> _trackLyrics;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<TrackEmbeddedImageLink>> _embeddedImageLinks;
|
||||
};
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace lms::db
|
||||
// Find utility functions
|
||||
static std::size_t getCount(Session& session);
|
||||
static pointer find(Session& session, TrackBookmarkId id);
|
||||
static RangeResults<TrackBookmarkId> find(Session& session, UserId userId, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<TrackBookmarkId> find(Session& session, UserId userId, std::optional<Range> range = std::nullopt);
|
||||
static pointer find(Session& session, UserId userId, TrackId trackId);
|
||||
|
||||
// Setters
|
||||
|
||||
@@ -100,7 +100,7 @@ namespace lms::db
|
||||
static pointer find(Session& session, TrackEmbeddedImageId id);
|
||||
static void find(Session& session, const FindParameters& params, const std::function<void(const pointer&)>& func);
|
||||
static pointer find(Session& session, std::size_t size, ImageHashType hash);
|
||||
static RangeResults<TrackEmbeddedImageId> findOrphanIds(Session& session, std::optional<Range> range);
|
||||
static std::vector<TrackEmbeddedImageId> findOrphanIds(Session& session, std::optional<Range> range);
|
||||
|
||||
// getters
|
||||
ImageHashType getHash() const { return _hash; }
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace lms::db
|
||||
static std::size_t getCount(Session& session);
|
||||
static pointer find(Session& session, std::string_view name, TrackListType type, UserId userId);
|
||||
static pointer find(Session& session, TrackListId tracklistId);
|
||||
static RangeResults<TrackListId> find(Session& session, const FindParameters& params);
|
||||
static std::vector<TrackListId> find(Session& session, const FindParameters& params);
|
||||
static void find(Session& session, const FindParameters& params, const std::function<void(const TrackList::pointer&)>& func);
|
||||
|
||||
// Accessors
|
||||
@@ -139,7 +139,7 @@ namespace lms::db
|
||||
bool isEmpty() const;
|
||||
std::size_t getCount() const;
|
||||
ObjectPtr<TrackListEntry> getEntry(std::size_t pos) const;
|
||||
RangeResults<ObjectPtr<TrackListEntry>> getEntries(std::optional<Range> range = {}) const;
|
||||
std::vector<ObjectPtr<TrackListEntry>> getEntries(std::optional<Range> range = {}) const;
|
||||
ObjectPtr<TrackListEntry> getEntryByTrackAndDateTime(ObjectPtr<Track> track, const Wt::WDateTime& dateTime) const;
|
||||
|
||||
std::vector<TrackId> getTrackIds() const;
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace lms::db
|
||||
static pointer find(Session& session, const std::filesystem::path& file);
|
||||
static void find(Session& session, const FindParameters& params, const std::function<void(const TrackLyrics::pointer&)>& func);
|
||||
static void find(Session& session, TrackLyricsId& lastRetrievedId, std::size_t count, const std::function<void(const TrackLyrics::pointer&)>& func);
|
||||
static RangeResults<TrackLyricsId> findOrphanIds(Session& session, std::optional<Range> range);
|
||||
static std::vector<TrackLyricsId> findOrphanIds(Session& session, std::optional<Range> range);
|
||||
static void findAbsoluteFilePath(Session& session, TrackLyricsId& lastRetrievedId, std::size_t count, const std::function<void(TrackLyricsId trackLyricsId, const std::filesystem::path& absoluteFilePath)>& func);
|
||||
|
||||
using SynchronizedLines = std::map<std::chrono::milliseconds, std::string>;
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace lms::db
|
||||
static std::size_t getCount(Session& session);
|
||||
static pointer find(Session& session, TrackMusicNNEmbeddingsId id);
|
||||
static pointer find(Session& session, TrackId trackId);
|
||||
static RangeResults<TrackMusicNNEmbeddingsId> find(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static std::vector<TrackMusicNNEmbeddingsId> find(Session& session, std::optional<Range> range = std::nullopt);
|
||||
static void find(Session& session, std::function<void(const pointer&)> func);
|
||||
static void removeAll(Session& session);
|
||||
|
||||
|
||||
@@ -114,6 +114,13 @@ namespace lms::db
|
||||
PubDateDesc,
|
||||
};
|
||||
|
||||
enum class RecommendationEngineType
|
||||
{
|
||||
Clusters = 0,
|
||||
None = 2,
|
||||
AudioSimilarity = 3,
|
||||
};
|
||||
|
||||
enum class ReleaseArtistLinkSortMethod
|
||||
{
|
||||
None,
|
||||
|
||||
@@ -88,7 +88,7 @@ namespace lms::db
|
||||
static std::size_t getCount(Session& session);
|
||||
static pointer find(Session& session, UserId id);
|
||||
static pointer find(Session& session, std::string_view loginName);
|
||||
static RangeResults<UserId> find(Session& session, const FindParameters& params);
|
||||
static std::vector<UserId> find(Session& session, const FindParameters& params);
|
||||
static void find(Session& session, const FindParameters& params, const std::function<void(const pointer&)>& func);
|
||||
static pointer findDemoUser(Session& session);
|
||||
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <Wt/Dbo/Field.h>
|
||||
#include <Wt/Dbo/collection.h>
|
||||
|
||||
#include "core/UUID.hpp"
|
||||
|
||||
#include "database/Object.hpp"
|
||||
#include "database/Types.hpp"
|
||||
#include "database/objects/ReleaseId.hpp"
|
||||
#include "database/objects/TrackId.hpp"
|
||||
#include "database/objects/WorkId.hpp"
|
||||
|
||||
namespace lms::db
|
||||
{
|
||||
class Session;
|
||||
class Track;
|
||||
|
||||
class Work final : public Object<Work, WorkId>
|
||||
{
|
||||
public:
|
||||
static constexpr std::size_t maxNameLength{ 512 };
|
||||
|
||||
Work() = default;
|
||||
|
||||
static pointer find(Session& session, WorkId id);
|
||||
// Global lookup: MusicBrainz Work Id is a strong, unambiguous identity shared across the whole library
|
||||
static pointer find(Session& session, const core::UUID& mbid);
|
||||
// Name-only lookup, scoped to works already linked to a track of the given release: work titles are
|
||||
// often generic (e.g. "Symphony No. 5") and collide across unrelated works, so without an mbid we only
|
||||
// ever match within the same release instead of matching globally by name
|
||||
static pointer find(Session& session, ReleaseId releaseId, std::string_view name);
|
||||
static std::vector<WorkId> findOrphanIds(Session& session, std::optional<Range> range = std::nullopt);
|
||||
|
||||
void setName(std::string_view name);
|
||||
|
||||
std::string_view getName() const { return _name; }
|
||||
std::optional<core::UUID> getMBID() const { return _mbid; }
|
||||
|
||||
template<class Action>
|
||||
void persist(Action& a)
|
||||
{
|
||||
Wt::Dbo::field(a, _name, "name");
|
||||
Wt::Dbo::field(a, _mbid, "mbid");
|
||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToMany, "track_work", "", Wt::Dbo::OnDeleteCascade);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class Session;
|
||||
Work(std::string_view name, const std::optional<core::UUID>& mbid);
|
||||
static pointer create(Session& session, std::string_view name, const std::optional<core::UUID>& mbid);
|
||||
|
||||
std::string _name;
|
||||
std::optional<core::UUID> _mbid;
|
||||
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks;
|
||||
};
|
||||
|
||||
} // namespace lms::db
|
||||
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Copyright (C) 2025 Emeric Poupon
|
||||
*
|
||||
* This file is part of LMS.
|
||||
*
|
||||
* LMS is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LMS is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "database/IdType.hpp"
|
||||
|
||||
LMS_DECLARE_IDTYPE(WorkId)
|
||||
+107
-122
@@ -55,20 +55,20 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
|
||||
artists = Artist::findOrphanIds(session);
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto artists{ Artist::find(session, Artist::FindParameters{}) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front()->getId(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -181,10 +181,7 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}) };
|
||||
ASSERT_EQ(artists.results.size(), 0);
|
||||
ASSERT_FALSE(artists.moreResults);
|
||||
ASSERT_EQ(artists.range.offset, 0);
|
||||
ASSERT_EQ(artists.range.size, 0);
|
||||
ASSERT_EQ(artists.size(), 0);
|
||||
}
|
||||
|
||||
ScopedArtist artist1{ session, "MyArtist1" };
|
||||
@@ -195,43 +192,31 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}) };
|
||||
ASSERT_EQ(artists.results.size(), 3);
|
||||
ASSERT_FALSE(artists.moreResults);
|
||||
ASSERT_EQ(artists.range.offset, 0);
|
||||
ASSERT_EQ(artists.range.size, 3);
|
||||
ASSERT_EQ(artists.size(), 3);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRange(Range{ 0, 1 })) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
ASSERT_TRUE(artists.moreResults);
|
||||
ASSERT_EQ(artists.range.offset, 0);
|
||||
ASSERT_EQ(artists.range.size, 1);
|
||||
EXPECT_EQ(artists.results[0], artist1.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists[0], artist1.getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRange(Range{ 1, 1 })) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
ASSERT_TRUE(artists.moreResults);
|
||||
ASSERT_EQ(artists.range.offset, 1);
|
||||
ASSERT_EQ(artists.range.size, 1);
|
||||
EXPECT_EQ(artists.results[0], artist2.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists[0], artist2.getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRange(Range{ 2, 1 })) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
ASSERT_FALSE(artists.moreResults);
|
||||
ASSERT_EQ(artists.range.offset, 2);
|
||||
ASSERT_EQ(artists.range.size, 1);
|
||||
EXPECT_EQ(artists.results[0], artist3.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists[0], artist3.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,7 +234,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -310,29 +295,29 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtist")) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
ASSERT_EQ(tracks.size(), 1);
|
||||
EXPECT_EQ(tracks.front(), track.getId());
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackName").setArtistName("MyArtistFoo")) };
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
EXPECT_EQ(tracks.size(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters{}.setName("MyTrackNameFoo").setArtistName("MyArtist")) };
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
EXPECT_EQ(tracks.size(), 0);
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setTrack(track->getId())) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).results.size(), 0);
|
||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,7 +328,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).results, std::vector<db::ArtistId>{ artist.getId() });
|
||||
EXPECT_EQ(Artist::findOrphanIds(session), std::vector<db::ArtistId>{ artist.getId() });
|
||||
}
|
||||
|
||||
{
|
||||
@@ -353,7 +338,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -374,19 +359,19 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setTrack(track->getId())) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setMediaLibrary(library->getId()))) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setMediaLibrary(otherLibrary->getId()))) };
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
EXPECT_EQ(artists.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,26 +401,26 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setMediaLibrary(library1->getId()))) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setMediaLibrary(library2->getId()))) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
}
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setMediaLibrary(otherLibrary->getId()))) };
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
EXPECT_EQ(artists.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,15 +445,15 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setMediaLibrary(library->getId()))) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -479,8 +464,8 @@ namespace lms::db::tests
|
||||
params.setFilters(Filters{}.setMediaLibrary(library.getId()));
|
||||
|
||||
auto artists{ Artist::findIds(session, params) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -491,7 +476,7 @@ namespace lms::db::tests
|
||||
params.setFilters(Filters{}.setMediaLibrary(library.getId()));
|
||||
|
||||
auto artists{ Artist::findIds(session, params) };
|
||||
EXPECT_EQ(artists.results.size(), 0);
|
||||
EXPECT_EQ(artists.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -502,8 +487,8 @@ namespace lms::db::tests
|
||||
params.setFilters(Filters{}.setMediaLibrary(library.getId()));
|
||||
|
||||
auto artists{ Artist::findIds(session, params) };
|
||||
EXPECT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
EXPECT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -521,18 +506,18 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::findOrphanIds(session, Range{}).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findOrphanIds(session, Range{}).size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}).results.size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Artist)).results.size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Producer)).results.size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Writer)).results.size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Composer)).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}).size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Artist)).size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Producer)).size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Writer)).size(), 1);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Composer)).size(), 0);
|
||||
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -549,17 +534,17 @@ namespace lms::db::tests
|
||||
EXPECT_EQ(track->getArtistLinks().size(), 3);
|
||||
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId())) };
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.size(), 1);
|
||||
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Producer }));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.size(), 1);
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist }));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.size(), 1);
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Writer }));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.size(), 1);
|
||||
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer }));
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
EXPECT_EQ(tracks.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -609,7 +594,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -623,32 +608,32 @@ namespace lms::db::tests
|
||||
EXPECT_EQ(track->getArtists({}).size(), 2);
|
||||
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 2);
|
||||
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Arranger }).size(), 0);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}).results.size(), 2);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::Random)).results.size(), 2);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}).size(), 2);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::Random)).size(), 2);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId())) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track->getId());
|
||||
ASSERT_EQ(tracks.size(), 1);
|
||||
EXPECT_EQ(tracks.front(), track->getId());
|
||||
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId()));
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track->getId());
|
||||
ASSERT_EQ(tracks.size(), 1);
|
||||
EXPECT_EQ(tracks.front(), track->getId());
|
||||
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::Arranger }));
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
EXPECT_EQ(tracks.size(), 0);
|
||||
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::Artist }));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.size(), 1);
|
||||
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::Arranger }));
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
EXPECT_EQ(tracks.size(), 0);
|
||||
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::Artist }));
|
||||
EXPECT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.size(), 1);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -697,7 +682,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).results.size(), 0);
|
||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -707,7 +692,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).results.size(), 1);
|
||||
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).size(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,8 +719,8 @@ namespace lms::db::tests
|
||||
params.setFilters(Filters{}.setCodec(core::media::Codec::FLAC));
|
||||
|
||||
const auto artists{ Artist::find(session, params) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front()->getId(), artist2.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front()->getId(), artist2.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -753,15 +738,15 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "N" })).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "N" })).size(), 0);
|
||||
|
||||
const auto artistsByAAA{ Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "A" })) };
|
||||
ASSERT_EQ(artistsByAAA.results.size(), 1);
|
||||
EXPECT_EQ(artistsByAAA.results.front(), artist.getId());
|
||||
ASSERT_EQ(artistsByAAA.size(), 1);
|
||||
EXPECT_EQ(artistsByAAA.front(), artist.getId());
|
||||
|
||||
const auto artistsByZZZ{ Artist::Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "Z" })) };
|
||||
ASSERT_EQ(artistsByZZZ.results.size(), 1);
|
||||
EXPECT_EQ(artistsByZZZ.results.front(), artist.getId());
|
||||
ASSERT_EQ(artistsByZZZ.size(), 1);
|
||||
EXPECT_EQ(artistsByZZZ.front(), artist.getId());
|
||||
|
||||
EXPECT_EQ(Artist::find(session, "NNN").size(), 0);
|
||||
EXPECT_EQ(Artist::find(session, "AAA").size(), 1);
|
||||
@@ -804,27 +789,27 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
{
|
||||
const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "MyArtist" })) };
|
||||
EXPECT_EQ(artists.results.size(), 6);
|
||||
EXPECT_EQ(artists.size(), 6);
|
||||
}
|
||||
|
||||
{
|
||||
const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "MyArtist%" }).setSortMethod(ArtistSortMethod::Name)) };
|
||||
ASSERT_EQ(artists.results.size(), 2);
|
||||
EXPECT_EQ(artists.results[0], artist1.getId());
|
||||
EXPECT_EQ(artists.results[1], artist4.getId());
|
||||
ASSERT_EQ(artists.size(), 2);
|
||||
EXPECT_EQ(artists[0], artist1.getId());
|
||||
EXPECT_EQ(artists[1], artist4.getId());
|
||||
}
|
||||
|
||||
{
|
||||
const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "%MyArtist" }).setSortMethod(ArtistSortMethod::Name)) };
|
||||
ASSERT_EQ(artists.results.size(), 2);
|
||||
EXPECT_EQ(artists.results[0], artist2.getId());
|
||||
EXPECT_EQ(artists.results[1], artist5.getId());
|
||||
ASSERT_EQ(artists.size(), 2);
|
||||
EXPECT_EQ(artists[0], artist2.getId());
|
||||
EXPECT_EQ(artists[1], artist5.getId());
|
||||
}
|
||||
|
||||
{
|
||||
const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setKeywords({ "_MyArtist" }).setSortMethod(ArtistSortMethod::Name)) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results[0], artist3.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists[0], artist3.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -934,13 +919,13 @@ namespace lms::db::tests
|
||||
auto allArtistsByName{ Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::Name)) };
|
||||
auto allArtistsBySortName{ Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::SortName)) };
|
||||
|
||||
ASSERT_EQ(allArtistsByName.results.size(), 2);
|
||||
EXPECT_EQ(allArtistsByName.results.front(), artistA.getId());
|
||||
EXPECT_EQ(allArtistsByName.results.back(), artistB.getId());
|
||||
ASSERT_EQ(allArtistsByName.size(), 2);
|
||||
EXPECT_EQ(allArtistsByName.front(), artistA.getId());
|
||||
EXPECT_EQ(allArtistsByName.back(), artistB.getId());
|
||||
|
||||
ASSERT_EQ(allArtistsBySortName.results.size(), 2);
|
||||
EXPECT_EQ(allArtistsBySortName.results.front(), artistB.getId());
|
||||
EXPECT_EQ(allArtistsBySortName.results.back(), artistA.getId());
|
||||
ASSERT_EQ(allArtistsBySortName.size(), 2);
|
||||
EXPECT_EQ(allArtistsBySortName.front(), artistB.getId());
|
||||
EXPECT_EQ(allArtistsBySortName.back(), artistA.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,7 +940,7 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters{}.setNonRelease(true).setArtist(artist->getId())) };
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
EXPECT_EQ(tracks.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -971,8 +956,8 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId()).setNonRelease(true)) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track2.getId());
|
||||
ASSERT_EQ(tracks.size(), 1);
|
||||
EXPECT_EQ(tracks.front(), track2.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1033,11 +1018,11 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::AddedDesc)) };
|
||||
ASSERT_EQ(artists.results.size(), 4);
|
||||
EXPECT_EQ(artists.results[0], artistD.getId());
|
||||
EXPECT_EQ(artists.results[1], artistA.getId());
|
||||
EXPECT_EQ(artists.results[2], artistB.getId());
|
||||
EXPECT_EQ(artists.results[3], artistC.getId());
|
||||
ASSERT_EQ(artists.size(), 4);
|
||||
EXPECT_EQ(artists[0], artistD.getId());
|
||||
EXPECT_EQ(artists[1], artistA.getId());
|
||||
EXPECT_EQ(artists[2], artistB.getId());
|
||||
EXPECT_EQ(artists[3], artistC.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1073,11 +1058,11 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::LastWrittenDesc)) };
|
||||
ASSERT_EQ(artists.results.size(), 4);
|
||||
EXPECT_EQ(artists.results[0], artistA.getId());
|
||||
EXPECT_EQ(artists.results[1], artistD.getId());
|
||||
EXPECT_EQ(artists.results[2], artistB.getId());
|
||||
EXPECT_EQ(artists.results[3], artistC.getId());
|
||||
ASSERT_EQ(artists.size(), 4);
|
||||
EXPECT_EQ(artists[0], artistA.getId());
|
||||
EXPECT_EQ(artists[1], artistD.getId());
|
||||
EXPECT_EQ(artists[2], artistB.getId());
|
||||
EXPECT_EQ(artists[3], artistC.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1144,8 +1129,8 @@ namespace lms::db::tests
|
||||
ArtistId lastRetrievedArtist;
|
||||
const auto results{ Artist::findWithMBIDNameVariants(session, lastRetrievedArtist) };
|
||||
|
||||
ASSERT_EQ(results.results.size(), 1);
|
||||
EXPECT_EQ(results.results[0]->getId(), artistA.getId());
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0]->getId(), artistA.getId());
|
||||
EXPECT_EQ(lastRetrievedArtist, artistA.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ add_executable(test-database
|
||||
Medium.cpp
|
||||
Migration.cpp
|
||||
Mood.cpp
|
||||
Movement.cpp
|
||||
PlayListFile.cpp
|
||||
Podcast.cpp
|
||||
RatedArtist.cpp
|
||||
@@ -29,6 +30,7 @@ add_executable(test-database
|
||||
StarredTrack.cpp
|
||||
Track.cpp
|
||||
TrackArtistLink.cpp
|
||||
Work.cpp
|
||||
TrackBookmark.cpp
|
||||
TrackEmbeddedImage.cpp
|
||||
TrackList.cpp
|
||||
|
||||
@@ -50,26 +50,26 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
const auto clusters{ Cluster::findIds(session, Cluster::FindParameters{}) };
|
||||
ASSERT_EQ(clusters.results.size(), 1);
|
||||
EXPECT_EQ(clusters.results.front(), cluster.getId());
|
||||
ASSERT_EQ(clusters.size(), 1);
|
||||
EXPECT_EQ(clusters.front(), cluster.getId());
|
||||
}
|
||||
|
||||
{
|
||||
const auto clusters{ Cluster::findOrphanIds(session) };
|
||||
ASSERT_EQ(clusters.results.size(), 1);
|
||||
EXPECT_EQ(clusters.results.front(), cluster.getId());
|
||||
ASSERT_EQ(clusters.size(), 1);
|
||||
EXPECT_EQ(clusters.front(), cluster.getId());
|
||||
}
|
||||
|
||||
auto clusterTypes{ ClusterType::findIds(session) };
|
||||
ASSERT_EQ(clusterTypes.results.size(), 1);
|
||||
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId());
|
||||
ASSERT_EQ(clusterTypes.size(), 1);
|
||||
EXPECT_EQ(clusterTypes.front(), clusterType.getId());
|
||||
|
||||
clusterTypes = ClusterType::findUsed(session);
|
||||
ASSERT_EQ(clusterTypes.results.size(), 1);
|
||||
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId());
|
||||
ASSERT_EQ(clusterTypes.size(), 1);
|
||||
EXPECT_EQ(clusterTypes.front(), clusterType.getId());
|
||||
|
||||
clusterTypes = ClusterType::findOrphanIds(session);
|
||||
EXPECT_EQ(clusterTypes.results.size(), 0);
|
||||
EXPECT_EQ(clusterTypes.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,10 +77,10 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto clusterTypes{ ClusterType::findOrphanIds(session) };
|
||||
ASSERT_EQ(clusterTypes.results.size(), 1);
|
||||
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId());
|
||||
ASSERT_EQ(clusterTypes.size(), 1);
|
||||
EXPECT_EQ(clusterTypes.front(), clusterType.getId());
|
||||
|
||||
ASSERT_EQ(ClusterType::findUsed(session).results.size(), 0);
|
||||
ASSERT_EQ(ClusterType::findUsed(session).size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,10 +155,10 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).size(), 0);
|
||||
auto clusterTypes{ ClusterType::findOrphanIds(session) };
|
||||
ASSERT_EQ(clusterTypes.results.size(), 1);
|
||||
EXPECT_EQ(clusterTypes.results.front(), clusterType.getId());
|
||||
ASSERT_EQ(clusterTypes.size(), 1);
|
||||
EXPECT_EQ(clusterTypes.front(), clusterType.getId());
|
||||
}
|
||||
|
||||
ScopedCluster cluster1{ session, clusterType.lockAndGet(), "MyCluster1" };
|
||||
@@ -167,7 +167,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto clusters{ Cluster::findOrphanIds(session) };
|
||||
EXPECT_EQ(clusters.results.size(), 2);
|
||||
EXPECT_EQ(clusters.size(), 2);
|
||||
EXPECT_EQ(track->getClusters().size(), 0);
|
||||
EXPECT_EQ(track->getClusterIds().size(), 0);
|
||||
EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 0);
|
||||
@@ -183,8 +183,8 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto clusters{ Cluster::findIds(session, Cluster::FindParameters{}.setTrack(track.getId())) };
|
||||
ASSERT_EQ(clusters.results.size(), 1);
|
||||
EXPECT_EQ(clusters.results.front(), cluster1.getId());
|
||||
ASSERT_EQ(clusters.size(), 1);
|
||||
EXPECT_EQ(clusters.front(), cluster1.getId());
|
||||
EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1);
|
||||
EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 0);
|
||||
}
|
||||
@@ -192,21 +192,21 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto clusters{ Cluster::findOrphanIds(session) };
|
||||
ASSERT_EQ(clusters.results.size(), 1);
|
||||
EXPECT_EQ(clusters.results.front(), cluster2.getId());
|
||||
ASSERT_EQ(clusters.size(), 1);
|
||||
EXPECT_EQ(clusters.front(), cluster2.getId());
|
||||
|
||||
EXPECT_EQ(ClusterType::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(ClusterType::findOrphanIds(session).size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto tracks{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster1.getId() }))) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
ASSERT_EQ(tracks.size(), 1);
|
||||
EXPECT_EQ(tracks.front(), track.getId());
|
||||
|
||||
tracks = Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster2.getId() })));
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
EXPECT_EQ(tracks.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -236,7 +236,7 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setClusters(clusterIds))) };
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
EXPECT_EQ(tracks.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -249,7 +249,7 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setClusters(clusterIds))) };
|
||||
EXPECT_EQ(tracks.results.size(), 0);
|
||||
EXPECT_EQ(tracks.size(), 0);
|
||||
EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1);
|
||||
EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 0);
|
||||
}
|
||||
@@ -264,8 +264,8 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setClusters(clusterIds))) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
ASSERT_EQ(tracks.size(), 1);
|
||||
EXPECT_EQ(tracks.front(), track.getId());
|
||||
EXPECT_EQ(Cluster::computeTrackCount(session, cluster1.getId()), 1);
|
||||
EXPECT_EQ(Cluster::computeTrackCount(session, cluster2.getId()), 1);
|
||||
}
|
||||
@@ -289,11 +289,11 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).size(), 0);
|
||||
|
||||
EXPECT_EQ(Cluster::computeTrackCount(session, cluster.getId()), tracks.size());
|
||||
|
||||
for (TrackId trackId : cluster->getTracks().results)
|
||||
for (TrackId trackId : cluster->getTracks())
|
||||
{
|
||||
auto it{ std::find_if(std::cbegin(tracks), std::cend(tracks), [&](const ScopedTrack& track) { return trackId == track.getId(); }) };
|
||||
EXPECT_TRUE(it != std::cend(tracks));
|
||||
@@ -333,8 +333,8 @@ namespace lms::db::tests
|
||||
{
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Cluster::find(session, Cluster::FindParameters{}).results.size(), 0);
|
||||
EXPECT_EQ(Cluster::find(session, Cluster::FindParameters{}.setClusterTypeName("Foo")).results.size(), 0);
|
||||
EXPECT_EQ(Cluster::find(session, Cluster::FindParameters{}).size(), 0);
|
||||
EXPECT_EQ(Cluster::find(session, Cluster::FindParameters{}.setClusterTypeName("Foo")).size(), 0);
|
||||
}
|
||||
|
||||
ScopedClusterType clusterType{ session, "MyClusterType" };
|
||||
@@ -342,18 +342,18 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
auto clusters{ Cluster::findIds(session, Cluster::FindParameters{}).results };
|
||||
auto clusters{ Cluster::findIds(session, Cluster::FindParameters{}) };
|
||||
ASSERT_EQ(clusters.size(), 1);
|
||||
EXPECT_EQ(clusters.front(), cluster.getId());
|
||||
|
||||
clusters = Cluster::findIds(session, Cluster::FindParameters{}.setClusterType(clusterType.getId())).results;
|
||||
clusters = Cluster::findIds(session, Cluster::FindParameters{}.setClusterType(clusterType.getId()));
|
||||
ASSERT_EQ(clusters.size(), 1);
|
||||
EXPECT_EQ(clusters.front(), cluster.getId());
|
||||
|
||||
clusters = Cluster::findIds(session, Cluster::FindParameters{}.setClusterTypeName("Foo")).results;
|
||||
clusters = Cluster::findIds(session, Cluster::FindParameters{}.setClusterTypeName("Foo"));
|
||||
EXPECT_EQ(clusters.size(), 0);
|
||||
|
||||
clusters = Cluster::findIds(session, Cluster::FindParameters{}.setClusterTypeName("MyClusterType")).results;
|
||||
clusters = Cluster::findIds(session, Cluster::FindParameters{}.setClusterTypeName("MyClusterType"));
|
||||
ASSERT_EQ(clusters.size(), 1);
|
||||
EXPECT_EQ(clusters.front(), cluster.getId());
|
||||
}
|
||||
@@ -366,7 +366,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).size(), 0);
|
||||
}
|
||||
|
||||
ScopedClusterType clusterType{ session, "MyClusterType" };
|
||||
@@ -375,9 +375,9 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
ASSERT_EQ(Cluster::findOrphanIds(session).results.size(), 2);
|
||||
EXPECT_EQ(Release::find(session, Release::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ unusedCluster.getId() }))).results.size(), 0);
|
||||
EXPECT_EQ(Release::find(session, Release::FindParameters{}).results.size(), 1);
|
||||
ASSERT_EQ(Cluster::findOrphanIds(session).size(), 2);
|
||||
EXPECT_EQ(Release::find(session, Release::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ unusedCluster.getId() }))).size(), 0);
|
||||
EXPECT_EQ(Release::find(session, Release::FindParameters{}).size(), 1);
|
||||
EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 0);
|
||||
EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0);
|
||||
}
|
||||
@@ -394,8 +394,8 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto clusters{ Cluster::findOrphanIds(session) };
|
||||
ASSERT_EQ(clusters.results.size(), 1);
|
||||
EXPECT_EQ(clusters.results.front(), unusedCluster.getId());
|
||||
ASSERT_EQ(clusters.size(), 1);
|
||||
EXPECT_EQ(clusters.front(), unusedCluster.getId());
|
||||
}
|
||||
EXPECT_EQ(Cluster::computeReleaseCount(session, cluster.getId()), 1);
|
||||
EXPECT_EQ(Cluster::computeReleaseCount(session, unusedCluster.getId()), 0);
|
||||
@@ -405,23 +405,23 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto clusters{ Cluster::findIds(session, Cluster::FindParameters{}.setRelease(release.getId())) };
|
||||
ASSERT_EQ(clusters.results.size(), 1);
|
||||
EXPECT_EQ(clusters.results.front(), cluster.getId());
|
||||
ASSERT_EQ(clusters.size(), 1);
|
||||
EXPECT_EQ(clusters.front(), cluster.getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto releases{ Release::findIds(session, Release::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster.getId() }))) };
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
ASSERT_EQ(releases.size(), 1);
|
||||
EXPECT_EQ(releases.front(), release.getId());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto releases{ Release::findIds(session, Release::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ unusedCluster.getId() }))) };
|
||||
EXPECT_EQ(releases.results.size(), 0);
|
||||
EXPECT_EQ(releases.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -451,10 +451,10 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(ClusterType::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 2);
|
||||
EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(ClusterType::findOrphanIds(session).size(), 0);
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).size(), 2);
|
||||
EXPECT_EQ(Release::findOrphanIds(session).size(), 0);
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -467,11 +467,11 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster1.getId() }))) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster2.getId() }))).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster3.getId() }))).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster2.getId() }))).size(), 0);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster3.getId() }))).size(), 0);
|
||||
|
||||
cluster2.get().modify()->addTrack(track.get());
|
||||
}
|
||||
@@ -480,18 +480,18 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster1.getId() }))) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
|
||||
artists = Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster2.getId() })));
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
|
||||
artists = Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster1.getId() })));
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster3.getId() }))).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster3.getId() }))).size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,17 +512,17 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).size(), 0);
|
||||
EXPECT_EQ(Release::findOrphanIds(session).size(), 0);
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster.getId() }))) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -552,8 +552,8 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).size(), 0);
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -563,8 +563,8 @@ namespace lms::db::tests
|
||||
std::transform(std::cbegin(clusters), std::cend(clusters), std::back_inserter(clusterIds), [](const ScopedCluster& cluster) { return cluster.getId(); });
|
||||
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setClusters(clusterIds))) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -587,26 +587,26 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(ClusterType::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Release::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Cluster::findOrphanIds(session).size(), 0);
|
||||
EXPECT_EQ(ClusterType::findOrphanIds(session).size(), 0);
|
||||
EXPECT_EQ(Artist::findOrphanIds(session).size(), 0);
|
||||
EXPECT_EQ(Release::findOrphanIds(session).size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto artists{ Artist::findIds(session, Artist::FindParameters{}.setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster.getId() }))) };
|
||||
ASSERT_EQ(artists.results.size(), 1);
|
||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
EXPECT_EQ(artists.front(), artist.getId());
|
||||
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId())) };
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
ASSERT_EQ(releases.size(), 1);
|
||||
EXPECT_EQ(releases.front(), release.getId());
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId()).setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster.getId() })));
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
ASSERT_EQ(releases.size(), 1);
|
||||
EXPECT_EQ(releases.front(), release.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -632,12 +632,12 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId())) };
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
ASSERT_EQ(releases.size(), 1);
|
||||
EXPECT_EQ(releases.front(), release.getId());
|
||||
|
||||
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId()).setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster1.getId(), cluster2.getId() })));
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
ASSERT_EQ(releases.size(), 1);
|
||||
EXPECT_EQ(releases.front(), release.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -743,17 +743,17 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto genres{ Cluster::find(session, Cluster::FindParameters{}.setTrack(track.getId()).setClusterTypeName("GENRE")) };
|
||||
ASSERT_EQ(genres.results.size(), 2);
|
||||
ASSERT_EQ(genres.size(), 2);
|
||||
|
||||
const auto moods{ Cluster::find(session, Cluster::FindParameters{}.setTrack(track.getId()).setClusterTypeName("MOOD")) };
|
||||
ASSERT_EQ(moods.results.size(), 1);
|
||||
EXPECT_EQ(moods.results.front()->getId(), mood.getId());
|
||||
ASSERT_EQ(moods.size(), 1);
|
||||
EXPECT_EQ(moods.front()->getId(), mood.getId());
|
||||
|
||||
const auto all{ Cluster::find(session, Cluster::FindParameters{}.setTrack(track.getId())) };
|
||||
EXPECT_EQ(all.results.size(), 3);
|
||||
EXPECT_EQ(all.size(), 3);
|
||||
|
||||
const auto none{ Cluster::find(session, Cluster::FindParameters{}.setTrack(track.getId()).setClusterTypeName("GROUPING")) };
|
||||
EXPECT_EQ(none.results.size(), 0);
|
||||
EXPECT_EQ(none.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,26 +74,6 @@ namespace lms::db::tests
|
||||
session.fullAnalyze();
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Common_subRangeEmpty)
|
||||
{
|
||||
RangeResults<int> results;
|
||||
results.range = Range{ 0, 0 };
|
||||
results.results = {};
|
||||
results.moreResults = false;
|
||||
|
||||
{
|
||||
auto subRange{ results.getSubRange(Range{ 0, 0 }) };
|
||||
EXPECT_FALSE(subRange.moreResults);
|
||||
ASSERT_EQ(subRange.results.size(), 0);
|
||||
EXPECT_EQ(subRange.range, Range{});
|
||||
}
|
||||
{
|
||||
auto subRange{ results.getSubRange(Range{ 0, 1 }) };
|
||||
EXPECT_FALSE(subRange.moreResults);
|
||||
ASSERT_EQ(subRange.results.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Common_subRangeForeach)
|
||||
{
|
||||
struct TestCase
|
||||
@@ -155,55 +135,4 @@ namespace lms::db::tests
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(DatabaseFixture, Common_subRange)
|
||||
{
|
||||
RangeResults<int> results;
|
||||
results.range = Range{ 0, 2 };
|
||||
results.results = { 5, 6 };
|
||||
results.moreResults = false;
|
||||
|
||||
{
|
||||
auto subRange{ results.getSubRange(Range{ 0, 1 }) };
|
||||
EXPECT_TRUE(subRange.moreResults);
|
||||
ASSERT_EQ(subRange.results.size(), 1);
|
||||
EXPECT_EQ(subRange.results.front(), 5);
|
||||
}
|
||||
{
|
||||
auto subRange{ results.getSubRange(Range{ 1, 1 }) };
|
||||
EXPECT_FALSE(subRange.moreResults);
|
||||
ASSERT_EQ(subRange.results.size(), 1);
|
||||
EXPECT_EQ(subRange.results.front(), 6);
|
||||
}
|
||||
{
|
||||
auto subRange{ results.getSubRange(Range{ 0, 2 }) };
|
||||
EXPECT_FALSE(subRange.moreResults);
|
||||
ASSERT_EQ(subRange.results.size(), 2);
|
||||
EXPECT_EQ(subRange.results.front(), 5);
|
||||
EXPECT_EQ(subRange.results.back(), 6);
|
||||
}
|
||||
{
|
||||
auto subRange{ results.getSubRange(Range{}) };
|
||||
EXPECT_FALSE(subRange.moreResults);
|
||||
ASSERT_EQ(subRange.results.size(), 2);
|
||||
EXPECT_EQ(subRange.results.front(), 5);
|
||||
EXPECT_EQ(subRange.results.back(), 6);
|
||||
EXPECT_EQ(subRange.range, results.range);
|
||||
}
|
||||
|
||||
{
|
||||
auto subRange{ results.getSubRange(Range{ 1, 0 }) };
|
||||
EXPECT_FALSE(subRange.moreResults);
|
||||
ASSERT_EQ(subRange.results.size(), 1);
|
||||
EXPECT_EQ(subRange.results.front(), 6);
|
||||
const Range expectedRange{ 1, 1 };
|
||||
EXPECT_EQ(subRange.range, expectedRange);
|
||||
}
|
||||
{
|
||||
auto subRange{ results.getSubRange(Range{ 3, 2 }) };
|
||||
EXPECT_FALSE(subRange.moreResults);
|
||||
ASSERT_EQ(subRange.results.size(), 0);
|
||||
const Range expectedRange{ 2, 0 };
|
||||
EXPECT_EQ(subRange.range, expectedRange);
|
||||
}
|
||||
}
|
||||
} // namespace lms::db::tests
|
||||
@@ -42,19 +42,19 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_TRUE(Release::findOrphanIds(session).results.empty());
|
||||
EXPECT_TRUE(Artist::findOrphanIds(session).results.empty());
|
||||
EXPECT_TRUE(Release::findOrphanIds(session).empty());
|
||||
EXPECT_TRUE(Artist::findOrphanIds(session).empty());
|
||||
}
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId())) };
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
ASSERT_EQ(releases.size(), 1);
|
||||
EXPECT_EQ(releases.front(), release.getId());
|
||||
|
||||
const auto releaseTracks{ Track::find(session, Track::FindParameters{}.setRelease(release.getId())) };
|
||||
EXPECT_EQ(releaseTracks.results.size(), nbTracks);
|
||||
EXPECT_EQ(releaseTracks.size(), nbTracks);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,8 +75,8 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createWriteTransaction() };
|
||||
|
||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId())) };
|
||||
ASSERT_EQ(releases.results.size(), 1);
|
||||
EXPECT_EQ(releases.results.front(), release.getId());
|
||||
ASSERT_EQ(releases.size(), 1);
|
||||
EXPECT_EQ(releases.front(), release.getId());
|
||||
|
||||
auto artists{ release->getTrackArtists() };
|
||||
ASSERT_EQ(artists.size(), 1);
|
||||
@@ -88,7 +88,7 @@ namespace lms::db::tests
|
||||
{
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_TRUE(User::find(session, User::FindParameters{}).results.empty());
|
||||
EXPECT_TRUE(User::find(session, User::FindParameters{}).empty());
|
||||
EXPECT_EQ(User::getCount(session), 0);
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
EXPECT_EQ(User::find(session, User::FindParameters{}).results.size(), 1);
|
||||
EXPECT_EQ(User::find(session, User::FindParameters{}).size(), 1);
|
||||
EXPECT_EQ(User::getCount(session), 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto directories{ Directory::findOrphanIds(session).results };
|
||||
const auto directories{ Directory::findOrphanIds(session) };
|
||||
EXPECT_EQ(directories.size(), 2);
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto directories{ Directory::findOrphanIds(session).results };
|
||||
const auto directories{ Directory::findOrphanIds(session) };
|
||||
ASSERT_EQ(directories.size(), 1);
|
||||
EXPECT_EQ(directories.front(), child.getId());
|
||||
}
|
||||
@@ -182,7 +182,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto directories{ Directory::findRootDirectories(session).results };
|
||||
const auto directories{ Directory::findRootDirectories(session) };
|
||||
ASSERT_EQ(directories.size(), 2);
|
||||
EXPECT_EQ(directories[0]->getId(), parent1.getId());
|
||||
EXPECT_EQ(directories[1]->getId(), parent2.getId());
|
||||
@@ -209,7 +209,7 @@ namespace lms::db::tests
|
||||
Directory::FindParameters params;
|
||||
params.setWithNoTrack(true);
|
||||
|
||||
auto res{ Directory::find(session, params).results };
|
||||
auto res{ Directory::find(session, params) };
|
||||
|
||||
ASSERT_EQ(res.size(), 3);
|
||||
EXPECT_EQ(res[0]->getId(), parent.getId());
|
||||
@@ -228,7 +228,7 @@ namespace lms::db::tests
|
||||
Directory::FindParameters params;
|
||||
params.setWithNoTrack(true);
|
||||
|
||||
auto res{ Directory::find(session, params).results };
|
||||
auto res{ Directory::find(session, params) };
|
||||
ASSERT_EQ(res.size(), 2);
|
||||
EXPECT_EQ(res[0]->getId(), parent.getId());
|
||||
EXPECT_EQ(res[1]->getId(), child1.getId());
|
||||
@@ -247,7 +247,7 @@ namespace lms::db::tests
|
||||
Directory::FindParameters params;
|
||||
params.setKeywords({ "foo" });
|
||||
|
||||
auto res{ Directory::find(session, params).results };
|
||||
auto res{ Directory::find(session, params) };
|
||||
|
||||
ASSERT_EQ(res.size(), 2);
|
||||
EXPECT_EQ(res[0]->getId(), child1.getId());
|
||||
@@ -267,7 +267,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto res{ Directory::findMismatchedLibrary(session, std::nullopt, library->getPath(), library->getId()).results };
|
||||
const auto res{ Directory::findMismatchedLibrary(session, std::nullopt, library->getPath(), library->getId()) };
|
||||
ASSERT_EQ(res.size(), 2);
|
||||
EXPECT_EQ(res[0], parent1.getId());
|
||||
EXPECT_EQ(res[1], child1.getId());
|
||||
@@ -283,7 +283,7 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto res{ Directory::findMismatchedLibrary(session, std::nullopt, library->getPath(), library->getId()).results };
|
||||
const auto res{ Directory::findMismatchedLibrary(session, std::nullopt, library->getPath(), library->getId()) };
|
||||
EXPECT_EQ(res.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto orphans{ Genre::findOrphanIds(session) };
|
||||
ASSERT_EQ(orphans.results.size(), 1);
|
||||
EXPECT_EQ(orphans.results.front(), genre.getId());
|
||||
ASSERT_EQ(orphans.size(), 1);
|
||||
EXPECT_EQ(orphans.front(), genre.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Genre::findOrphanIds(session).results.size(), 2);
|
||||
EXPECT_EQ(Genre::findOrphanIds(session).size(), 2);
|
||||
EXPECT_EQ(track->getGenres().size(), 0);
|
||||
EXPECT_EQ(track->getGenreIds().size(), 0);
|
||||
EXPECT_EQ(Genre::computeTrackCount(session, genre1.getId()), 0);
|
||||
@@ -94,15 +94,15 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto genres{ Genre::findIds(session, Genre::FindParameters{}.setTrack(track.getId())) };
|
||||
ASSERT_EQ(genres.results.size(), 1);
|
||||
EXPECT_EQ(genres.results.front(), genre1.getId());
|
||||
ASSERT_EQ(genres.size(), 1);
|
||||
EXPECT_EQ(genres.front(), genre1.getId());
|
||||
|
||||
EXPECT_EQ(Genre::computeTrackCount(session, genre1.getId()), 1);
|
||||
EXPECT_EQ(Genre::computeTrackCount(session, genre2.getId()), 0);
|
||||
|
||||
const auto orphans{ Genre::findOrphanIds(session) };
|
||||
ASSERT_EQ(orphans.results.size(), 1);
|
||||
EXPECT_EQ(orphans.results.front(), genre2.getId());
|
||||
ASSERT_EQ(orphans.size(), 1);
|
||||
EXPECT_EQ(orphans.front(), genre2.getId());
|
||||
|
||||
const auto trackGenres{ track->getGenres() };
|
||||
ASSERT_EQ(trackGenres.size(), 1);
|
||||
@@ -116,11 +116,11 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setGenre(genre1.getId()))) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
ASSERT_EQ(tracks.size(), 1);
|
||||
EXPECT_EQ(tracks.front(), track.getId());
|
||||
|
||||
const auto tracks2{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setGenre(genre2.getId()))) };
|
||||
EXPECT_EQ(tracks2.results.size(), 0);
|
||||
EXPECT_EQ(tracks2.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Genre::computeTrackCount(session, genre1.getId()), 1);
|
||||
EXPECT_EQ(Genre::computeTrackCount(session, genre2.getId()), 1);
|
||||
EXPECT_EQ(Genre::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Genre::findOrphanIds(session).size(), 0);
|
||||
|
||||
const auto trackGenres{ track->getGenres() };
|
||||
EXPECT_EQ(trackGenres.size(), 2);
|
||||
@@ -148,12 +148,12 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setGenre(genre1.getId()))) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
ASSERT_EQ(tracks.size(), 1);
|
||||
EXPECT_EQ(tracks.front(), track.getId());
|
||||
|
||||
const auto tracks2{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setGenre(genre2.getId()))) };
|
||||
ASSERT_EQ(tracks2.results.size(), 1);
|
||||
EXPECT_EQ(tracks2.results.front(), track.getId());
|
||||
ASSERT_EQ(tracks2.size(), 1);
|
||||
EXPECT_EQ(tracks2.front(), track.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,10 +200,10 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto genres{ Genre::findIds(session, Genre::FindParameters{}.setSortMethod(GenreSortMethod::Name)) };
|
||||
ASSERT_EQ(genres.results.size(), 3);
|
||||
EXPECT_EQ(genres.results[0], g2.getId());
|
||||
EXPECT_EQ(genres.results[1], g3.getId());
|
||||
EXPECT_EQ(genres.results[2], g1.getId());
|
||||
ASSERT_EQ(genres.size(), 3);
|
||||
EXPECT_EQ(genres[0], g2.getId());
|
||||
EXPECT_EQ(genres[1], g3.getId());
|
||||
EXPECT_EQ(genres[2], g1.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,9 +225,9 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto genres{ Genre::findIds(session, Genre::FindParameters{}.setSortMethod(GenreSortMethod::TrackCountDesc)) };
|
||||
ASSERT_EQ(genres.results.size(), 2);
|
||||
EXPECT_EQ(genres.results[0], g1.getId());
|
||||
EXPECT_EQ(genres.results[1], g2.getId());
|
||||
ASSERT_EQ(genres.size(), 2);
|
||||
EXPECT_EQ(genres[0], g1.getId());
|
||||
EXPECT_EQ(genres[1], g2.getId());
|
||||
}
|
||||
}
|
||||
} // namespace lms::db::tests
|
||||
|
||||
@@ -63,8 +63,8 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto orphans{ Grouping::findOrphanIds(session) };
|
||||
ASSERT_EQ(orphans.results.size(), 1);
|
||||
EXPECT_EQ(orphans.results.front(), grouping.getId());
|
||||
ASSERT_EQ(orphans.size(), 1);
|
||||
EXPECT_EQ(orphans.front(), grouping.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Grouping::findOrphanIds(session).results.size(), 2);
|
||||
EXPECT_EQ(Grouping::findOrphanIds(session).size(), 2);
|
||||
EXPECT_EQ(track->getGroupings().size(), 0);
|
||||
EXPECT_EQ(track->getGroupingIds().size(), 0);
|
||||
}
|
||||
@@ -90,12 +90,12 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto groupings{ Grouping::findIds(session, Grouping::FindParameters{}.setTrack(track.getId())) };
|
||||
ASSERT_EQ(groupings.results.size(), 1);
|
||||
EXPECT_EQ(groupings.results.front(), grouping1.getId());
|
||||
ASSERT_EQ(groupings.size(), 1);
|
||||
EXPECT_EQ(groupings.front(), grouping1.getId());
|
||||
|
||||
const auto orphans{ Grouping::findOrphanIds(session) };
|
||||
ASSERT_EQ(orphans.results.size(), 1);
|
||||
EXPECT_EQ(orphans.results.front(), grouping2.getId());
|
||||
ASSERT_EQ(orphans.size(), 1);
|
||||
EXPECT_EQ(orphans.front(), grouping2.getId());
|
||||
|
||||
const auto trackGroupings{ track->getGroupings() };
|
||||
ASSERT_EQ(trackGroupings.size(), 1);
|
||||
@@ -109,11 +109,11 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setGrouping(grouping1.getId()))) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
ASSERT_EQ(tracks.size(), 1);
|
||||
EXPECT_EQ(tracks.front(), track.getId());
|
||||
|
||||
const auto tracks2{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setGrouping(grouping2.getId()))) };
|
||||
EXPECT_EQ(tracks2.results.size(), 0);
|
||||
EXPECT_EQ(tracks2.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Grouping::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Grouping::findOrphanIds(session).size(), 0);
|
||||
|
||||
const auto trackGroupings{ track->getGroupings() };
|
||||
EXPECT_EQ(trackGroupings.size(), 2);
|
||||
@@ -139,12 +139,12 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setGrouping(grouping1.getId()))) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
ASSERT_EQ(tracks.size(), 1);
|
||||
EXPECT_EQ(tracks.front(), track.getId());
|
||||
|
||||
const auto tracks2{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setGrouping(grouping2.getId()))) };
|
||||
ASSERT_EQ(tracks2.results.size(), 1);
|
||||
EXPECT_EQ(tracks2.results.front(), track.getId());
|
||||
ASSERT_EQ(tracks2.size(), 1);
|
||||
EXPECT_EQ(tracks2.front(), track.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,10 +157,10 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto groupings{ Grouping::findIds(session, Grouping::FindParameters{}.setSortMethod(GroupingSortMethod::Name)) };
|
||||
ASSERT_EQ(groupings.results.size(), 3);
|
||||
EXPECT_EQ(groupings.results[0], g2.getId());
|
||||
EXPECT_EQ(groupings.results[1], g3.getId());
|
||||
EXPECT_EQ(groupings.results[2], g1.getId());
|
||||
ASSERT_EQ(groupings.size(), 3);
|
||||
EXPECT_EQ(groupings[0], g2.getId());
|
||||
EXPECT_EQ(groupings[1], g3.getId());
|
||||
EXPECT_EQ(groupings[2], g1.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,9 +182,9 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto groupings{ Grouping::findIds(session, Grouping::FindParameters{}.setSortMethod(GroupingSortMethod::TrackCountDesc)) };
|
||||
ASSERT_EQ(groupings.results.size(), 2);
|
||||
EXPECT_EQ(groupings.results[0], g1.getId());
|
||||
EXPECT_EQ(groupings.results[1], g2.getId());
|
||||
ASSERT_EQ(groupings.size(), 2);
|
||||
EXPECT_EQ(groupings[0], g1.getId());
|
||||
EXPECT_EQ(groupings[1], g2.getId());
|
||||
}
|
||||
}
|
||||
} // namespace lms::db::tests
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Image::find(session, Image::FindParameters{}.setDirectory(directory.getId())).results.size(), 0);
|
||||
EXPECT_EQ(Image::find(session, Image::FindParameters{}.setDirectory(directory.getId())).size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -93,7 +93,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto results{ Image::find(session, Image::FindParameters{}.setDirectory(directory.getId())).results };
|
||||
const auto results{ Image::find(session, Image::FindParameters{}.setDirectory(directory.getId())) };
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results.front()->getId(), image.getId());
|
||||
}
|
||||
@@ -138,8 +138,8 @@ namespace lms::db::tests
|
||||
Image::FindParameters params;
|
||||
params.setFileStem(fileStem);
|
||||
const auto results{ Image::find(session, params) };
|
||||
ASSERT_EQ(results.results.size(), 1);
|
||||
EXPECT_EQ(results.results[0]->getId(), image.getId());
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0]->getId(), image.getId());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -147,8 +147,8 @@ namespace lms::db::tests
|
||||
Image::FindParameters params;
|
||||
params.setFileStem(fileStem, Image::FindParameters::ProcessWildcards{ true });
|
||||
const auto results{ Image::find(session, params) };
|
||||
ASSERT_EQ(results.results.size(), 1);
|
||||
EXPECT_EQ(results.results[0]->getId(), image.getId());
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0]->getId(), image.getId());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -156,7 +156,7 @@ namespace lms::db::tests
|
||||
Image::FindParameters params;
|
||||
params.setFileStem("nonexistent");
|
||||
const auto results{ Image::find(session, params) };
|
||||
EXPECT_EQ(results.results.size(), 0);
|
||||
EXPECT_EQ(results.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -164,7 +164,7 @@ namespace lms::db::tests
|
||||
Image::FindParameters params;
|
||||
params.setFileStem("ima*");
|
||||
const auto results{ Image::find(session, params) };
|
||||
ASSERT_EQ(results.results.size(), 0);
|
||||
ASSERT_EQ(results.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -172,8 +172,8 @@ namespace lms::db::tests
|
||||
Image::FindParameters params;
|
||||
params.setFileStem("ima*", Image::FindParameters::ProcessWildcards{ true });
|
||||
const auto results{ Image::find(session, params) };
|
||||
ASSERT_EQ(results.results.size(), 1);
|
||||
EXPECT_EQ(results.results[0]->getId(), image.getId());
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0]->getId(), image.getId());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -181,8 +181,8 @@ namespace lms::db::tests
|
||||
Image::FindParameters params;
|
||||
params.setFileStem("*ge", Image::FindParameters::ProcessWildcards{ true });
|
||||
const auto results{ Image::find(session, params) };
|
||||
ASSERT_EQ(results.results.size(), 1);
|
||||
EXPECT_EQ(results.results[0]->getId(), image.getId());
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0]->getId(), image.getId());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -190,8 +190,8 @@ namespace lms::db::tests
|
||||
Image::FindParameters params;
|
||||
params.setFileStem("*g*", Image::FindParameters::ProcessWildcards{ true });
|
||||
const auto results{ Image::find(session, params) };
|
||||
ASSERT_EQ(results.results.size(), 1);
|
||||
EXPECT_EQ(results.results[0]->getId(), image.getId());
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0]->getId(), image.getId());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -199,8 +199,8 @@ namespace lms::db::tests
|
||||
Image::FindParameters params;
|
||||
params.setFileStem("*", Image::FindParameters::ProcessWildcards{ true });
|
||||
const auto results{ Image::find(session, params) };
|
||||
ASSERT_EQ(results.results.size(), 1);
|
||||
EXPECT_EQ(results.results[0]->getId(), image.getId());
|
||||
ASSERT_EQ(results.size(), 1);
|
||||
EXPECT_EQ(results[0]->getId(), image.getId());
|
||||
}
|
||||
|
||||
{
|
||||
@@ -208,7 +208,7 @@ namespace lms::db::tests
|
||||
Image::FindParameters params;
|
||||
params.setFileStem("ima%");
|
||||
const auto results{ Image::find(session, params) };
|
||||
ASSERT_EQ(results.results.size(), 0);
|
||||
ASSERT_EQ(results.size(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
@@ -216,7 +216,7 @@ namespace lms::db::tests
|
||||
Image::FindParameters params;
|
||||
params.setFileStem("ima%", Image::FindParameters::ProcessWildcards{ true });
|
||||
const auto results{ Image::find(session, params) };
|
||||
ASSERT_EQ(results.results.size(), 0);
|
||||
ASSERT_EQ(results.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto orphans{ Language::findOrphanIds(session) };
|
||||
ASSERT_EQ(orphans.results.size(), 1);
|
||||
EXPECT_EQ(orphans.results.front(), language.getId());
|
||||
ASSERT_EQ(orphans.size(), 1);
|
||||
EXPECT_EQ(orphans.front(), language.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Language::findOrphanIds(session).results.size(), 2);
|
||||
EXPECT_EQ(Language::findOrphanIds(session).size(), 2);
|
||||
EXPECT_EQ(track->getLanguages().size(), 0);
|
||||
EXPECT_EQ(track->getLanguageIds().size(), 0);
|
||||
}
|
||||
@@ -90,12 +90,12 @@ namespace lms::db::tests
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
|
||||
const auto languages{ Language::findIds(session, Language::FindParameters{}.setTrack(track.getId())) };
|
||||
ASSERT_EQ(languages.results.size(), 1);
|
||||
EXPECT_EQ(languages.results.front(), language1.getId());
|
||||
ASSERT_EQ(languages.size(), 1);
|
||||
EXPECT_EQ(languages.front(), language1.getId());
|
||||
|
||||
const auto orphans{ Language::findOrphanIds(session) };
|
||||
ASSERT_EQ(orphans.results.size(), 1);
|
||||
EXPECT_EQ(orphans.results.front(), language2.getId());
|
||||
ASSERT_EQ(orphans.size(), 1);
|
||||
EXPECT_EQ(orphans.front(), language2.getId());
|
||||
|
||||
const auto trackLanguages{ track->getLanguages() };
|
||||
ASSERT_EQ(trackLanguages.size(), 1);
|
||||
@@ -109,11 +109,11 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setLanguage(language1.getId()))) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
ASSERT_EQ(tracks.size(), 1);
|
||||
EXPECT_EQ(tracks.front(), track.getId());
|
||||
|
||||
const auto tracks2{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setLanguage(language2.getId()))) };
|
||||
EXPECT_EQ(tracks2.results.size(), 0);
|
||||
EXPECT_EQ(tracks2.size(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ namespace lms::db::tests
|
||||
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
EXPECT_EQ(Language::findOrphanIds(session).results.size(), 0);
|
||||
EXPECT_EQ(Language::findOrphanIds(session).size(), 0);
|
||||
|
||||
const auto trackLanguages{ track->getLanguages() };
|
||||
EXPECT_EQ(trackLanguages.size(), 2);
|
||||
@@ -139,12 +139,12 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto tracks{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setLanguage(language1.getId()))) };
|
||||
ASSERT_EQ(tracks.results.size(), 1);
|
||||
EXPECT_EQ(tracks.results.front(), track.getId());
|
||||
ASSERT_EQ(tracks.size(), 1);
|
||||
EXPECT_EQ(tracks.front(), track.getId());
|
||||
|
||||
const auto tracks2{ Track::findIds(session, Track::FindParameters{}.setFilters(Filters{}.setLanguage(language2.getId()))) };
|
||||
ASSERT_EQ(tracks2.results.size(), 1);
|
||||
EXPECT_EQ(tracks2.results.front(), track.getId());
|
||||
ASSERT_EQ(tracks2.size(), 1);
|
||||
EXPECT_EQ(tracks2.front(), track.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,10 +157,10 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto languages{ Language::findIds(session, Language::FindParameters{}.setSortMethod(LanguageSortMethod::Name)) };
|
||||
ASSERT_EQ(languages.results.size(), 3);
|
||||
EXPECT_EQ(languages.results[0], l2.getId());
|
||||
EXPECT_EQ(languages.results[1], l3.getId());
|
||||
EXPECT_EQ(languages.results[2], l1.getId());
|
||||
ASSERT_EQ(languages.size(), 3);
|
||||
EXPECT_EQ(languages[0], l2.getId());
|
||||
EXPECT_EQ(languages[1], l3.getId());
|
||||
EXPECT_EQ(languages[2], l1.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,9 +182,9 @@ namespace lms::db::tests
|
||||
{
|
||||
auto transaction{ session.createReadTransaction() };
|
||||
const auto languages{ Language::findIds(session, Language::FindParameters{}.setSortMethod(LanguageSortMethod::TrackCountDesc)) };
|
||||
ASSERT_EQ(languages.results.size(), 2);
|
||||
EXPECT_EQ(languages.results[0], l1.getId());
|
||||
EXPECT_EQ(languages.results[1], l2.getId());
|
||||
ASSERT_EQ(languages.size(), 2);
|
||||
EXPECT_EQ(languages[0], l1.getId());
|
||||
EXPECT_EQ(languages[1], l2.getId());
|
||||
}
|
||||
}
|
||||
} // namespace lms::db::tests
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user