Added a dedicated table for release artists + handle display names in UI/Subsonic API, ref #731
This commit is contained in:
+14
-12
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<message id="Lms.Explore.Artists.template">
|
<message id="Lms.Explore.Artists.template">
|
||||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||||
${link-type class="me-1"}
|
${artist-type class="me-1"}
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
${search class="form-control form-control-sm me-1" type="search"}
|
${search class="form-control form-control-sm me-1" type="search"}
|
||||||
${sort-mode}
|
${sort-mode}
|
||||||
@@ -29,22 +29,24 @@
|
|||||||
${loading-indicator}
|
${loading-indicator}
|
||||||
</message>
|
</message>
|
||||||
|
|
||||||
<message id="Lms.Explore.Artists.template.track-artist-link-type-selector">
|
<message id="Lms.Explore.Artists.template.artist-type-selector">
|
||||||
<div class="dropdown">
|
<div class="dropdown">
|
||||||
<button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
<button class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
${selected-item}
|
${selected-item}
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
<li>${link-type-all class="dropdown-item"}</li>
|
<li>${artist-type-all class="dropdown-item"}</li>
|
||||||
<li>${link-type-artist class="dropdown-item"}</li>
|
<li class="dropdown-divider"></li>
|
||||||
<li>${link-type-releaseartist class="dropdown-item"}</li>
|
<li>${artist-type-release-artist class="dropdown-item"}</li>
|
||||||
<li>${link-type-composer class="dropdown-item"}</li>
|
<li class="dropdown-divider"></li>
|
||||||
<li>${link-type-conductor class="dropdown-item"}</li>
|
<li>${artist-type-track-artist class="dropdown-item"}</li>
|
||||||
<li>${link-type-lyricist class="dropdown-item"}</li>
|
<li>${artist-type-composer class="dropdown-item"}</li>
|
||||||
<li>${link-type-mixer class="dropdown-item"}</li>
|
<li>${artist-type-conductor class="dropdown-item"}</li>
|
||||||
<li>${link-type-performer class="dropdown-item"}</li>
|
<li>${artist-type-lyricist class="dropdown-item"}</li>
|
||||||
<li>${link-type-producer class="dropdown-item"}</li>
|
<li>${artist-type-mixer class="dropdown-item"}</li>
|
||||||
<li>${link-type-remixer class="dropdown-item"}</li>
|
<li>${artist-type-performer class="dropdown-item"}</li>
|
||||||
|
<li>${artist-type-producer class="dropdown-item"}</li>
|
||||||
|
<li>${artist-type-remixer class="dropdown-item"}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|||||||
+2
-1
@@ -58,13 +58,14 @@
|
|||||||
${selected-item}
|
${selected-item}
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu">
|
<ul class="dropdown-menu">
|
||||||
|
<li>${all class="dropdown-item"}</li>
|
||||||
|
<li class="dropdown-divider"></li>
|
||||||
<li>${random class="dropdown-item"}</li>
|
<li>${random class="dropdown-item"}</li>
|
||||||
<li>${starred class="dropdown-item"}</li>
|
<li>${starred class="dropdown-item"}</li>
|
||||||
<li>${recently-played class="dropdown-item"}</li>
|
<li>${recently-played class="dropdown-item"}</li>
|
||||||
<li>${most-played class="dropdown-item"}</li>
|
<li>${most-played class="dropdown-item"}</li>
|
||||||
<li>${recently-added class="dropdown-item"}</li>
|
<li>${recently-added class="dropdown-item"}</li>
|
||||||
<li>${recently-modified class="dropdown-item"}</li>
|
<li>${recently-modified class="dropdown-item"}</li>
|
||||||
<li>${all class="dropdown-item"}</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</message>
|
</message>
|
||||||
|
|||||||
@@ -27,11 +27,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex-fill text-center text-truncate">
|
<div class="flex-fill text-center text-truncate">
|
||||||
${title class="d-block text-truncate"}
|
${title class="d-block text-truncate"}
|
||||||
${artist class="d-inline text-decoration-none text-truncate link-success"}
|
<span>
|
||||||
|
${artist class="d-inline text-decoration-none text-truncate"}
|
||||||
<span class="d-none d-md-inline">
|
<span class="d-none d-md-inline">
|
||||||
${separator}
|
${separator}
|
||||||
${release class="text-decoration-none text-truncate link-success"}
|
${release class="text-decoration-none text-truncate link-success"}
|
||||||
</span>
|
</span>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-2 d-none d-sm-block text-muted text-nowrap">
|
<div class="p-2 d-none d-sm-block text-muted text-nowrap">
|
||||||
<span id="lms-transcoding-active" title="${tr:Lms.Player.transcoding-active}" class="p-2" style="display:none"><i class="fa fa-fw fa-info-circle" aria-hidden="true"></i></span>
|
<span id="lms-transcoding-active" title="${tr:Lms.Player.transcoding-active}" class="p-2" style="display:none"><i class="fa fa-fw fa-info-circle" aria-hidden="true"></i></span>
|
||||||
|
|||||||
+13
-13
@@ -240,43 +240,43 @@
|
|||||||
<message id="Lms.Explore.Artist.similar-artists">Similar artists</message>
|
<message id="Lms.Explore.Artist.similar-artists">Similar artists</message>
|
||||||
|
|
||||||
<!--Explore:Artists-->
|
<!--Explore:Artists-->
|
||||||
<message id="Lms.Explore.Artists.linktype-all">All artists</message>
|
<message id="Lms.Explore.Artists.all">All artists</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-artist">
|
<message id="Lms.Explore.Artists.composer">
|
||||||
<plural case="0">Track artist</plural>
|
|
||||||
<plural case="1">Track artists</plural>
|
|
||||||
</message>
|
|
||||||
<message id="Lms.Explore.Artists.linktype-composer">
|
|
||||||
<plural case="0">Composer</plural>
|
<plural case="0">Composer</plural>
|
||||||
<plural case="1">Composers</plural>
|
<plural case="1">Composers</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-conductor">
|
<message id="Lms.Explore.Artists.conductor">
|
||||||
<plural case="0">Conductor</plural>
|
<plural case="0">Conductor</plural>
|
||||||
<plural case="1">Conductors</plural>
|
<plural case="1">Conductors</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-lyricist">
|
<message id="Lms.Explore.Artists.lyricist">
|
||||||
<plural case="0">Lyricist</plural>
|
<plural case="0">Lyricist</plural>
|
||||||
<plural case="1">Lyricists</plural>
|
<plural case="1">Lyricists</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-mixer">
|
<message id="Lms.Explore.Artists.mixer">
|
||||||
<plural case="0">Mixer</plural>
|
<plural case="0">Mixer</plural>
|
||||||
<plural case="1">Mixers</plural>
|
<plural case="1">Mixers</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-performer">
|
<message id="Lms.Explore.Artists.performer">
|
||||||
<plural case="0">Performer</plural>
|
<plural case="0">Performer</plural>
|
||||||
<plural case="1">Performers</plural>
|
<plural case="1">Performers</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-producer">
|
<message id="Lms.Explore.Artists.producer">
|
||||||
<plural case="0">Producer</plural>
|
<plural case="0">Producer</plural>
|
||||||
<plural case="1">Producers</plural>
|
<plural case="1">Producers</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-releaseartist">
|
<message id="Lms.Explore.Artists.release-artist">
|
||||||
<plural case="0">Album artist</plural>
|
<plural case="0">Album artist</plural>
|
||||||
<plural case="1">Album artists</plural>
|
<plural case="1">Album artists</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-remixer">
|
<message id="Lms.Explore.Artists.remixer">
|
||||||
<plural case="0">Remixer</plural>
|
<plural case="0">Remixer</plural>
|
||||||
<plural case="1">Remixers</plural>
|
<plural case="1">Remixers</plural>
|
||||||
</message>
|
</message>
|
||||||
|
<message id="Lms.Explore.Artists.track-artist">
|
||||||
|
<plural case="0">Track artist</plural>
|
||||||
|
<plural case="1">Track artists</plural>
|
||||||
|
</message>
|
||||||
|
|
||||||
<!--Explore:Release-->
|
<!--Explore:Release-->
|
||||||
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
||||||
|
|||||||
+13
-13
@@ -240,43 +240,43 @@
|
|||||||
<message id="Lms.Explore.Artist.similar-artists">Artistas similares</message>
|
<message id="Lms.Explore.Artist.similar-artists">Artistas similares</message>
|
||||||
|
|
||||||
<!--Explore:Artists-->
|
<!--Explore:Artists-->
|
||||||
<message id="Lms.Explore.Artists.linktype-all">Todos los artistas</message>
|
<message id="Lms.Explore.Artists.all">Todos los artistas</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-artist">
|
<message id="Lms.Explore.Artists.composer">
|
||||||
<plural case="0">Artista de la pista</plural>
|
|
||||||
<plural case="1">Artistas de la pista</plural>
|
|
||||||
</message>
|
|
||||||
<message id="Lms.Explore.Artists.linktype-composer">
|
|
||||||
<plural case="0">Compositor</plural>
|
<plural case="0">Compositor</plural>
|
||||||
<plural case="1">Compositores</plural>
|
<plural case="1">Compositores</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-conductor">
|
<message id="Lms.Explore.Artists.conductor">
|
||||||
<plural case="0">Director de orquesta</plural>
|
<plural case="0">Director de orquesta</plural>
|
||||||
<plural case="1">Directores de orquesta</plural>
|
<plural case="1">Directores de orquesta</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-lyricist">
|
<message id="Lms.Explore.Artists.lyricist">
|
||||||
<plural case="0">Letrista</plural>
|
<plural case="0">Letrista</plural>
|
||||||
<plural case="1">Letristas</plural>
|
<plural case="1">Letristas</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-mixer">
|
<message id="Lms.Explore.Artists.mixer">
|
||||||
<plural case="0">Mezclador</plural>
|
<plural case="0">Mezclador</plural>
|
||||||
<plural case="1">Mezcladores</plural>
|
<plural case="1">Mezcladores</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-performer">
|
<message id="Lms.Explore.Artists.performer">
|
||||||
<plural case="0">Intérprete</plural>
|
<plural case="0">Intérprete</plural>
|
||||||
<plural case="1">Intérpretes</plural>
|
<plural case="1">Intérpretes</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-producer">
|
<message id="Lms.Explore.Artists.producer">
|
||||||
<plural case="0">Productor</plural>
|
<plural case="0">Productor</plural>
|
||||||
<plural case="1">Productores</plural>
|
<plural case="1">Productores</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-releaseartist">
|
<message id="Lms.Explore.Artists.release-artist">
|
||||||
<plural case="0">Artista del álbum</plural>
|
<plural case="0">Artista del álbum</plural>
|
||||||
<plural case="1">Artistas del álbum</plural>
|
<plural case="1">Artistas del álbum</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-remixer">
|
<message id="Lms.Explore.Artists.remixer">
|
||||||
<plural case="0">Remezclador</plural>
|
<plural case="0">Remezclador</plural>
|
||||||
<plural case="1">Remezcladores</plural>
|
<plural case="1">Remezcladores</plural>
|
||||||
</message>
|
</message>
|
||||||
|
<message id="Lms.Explore.Artists.track-artist">
|
||||||
|
<plural case="0">Artista de la pista</plural>
|
||||||
|
<plural case="1">Artistas de la pista</plural>
|
||||||
|
</message>
|
||||||
|
|
||||||
<!--Explore:Release-->
|
<!--Explore:Release-->
|
||||||
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
||||||
|
|||||||
+13
-13
@@ -240,43 +240,43 @@
|
|||||||
<message id="Lms.Explore.Artist.similar-artists">Artistes similaires</message>
|
<message id="Lms.Explore.Artist.similar-artists">Artistes similaires</message>
|
||||||
|
|
||||||
<!--Explore:Artists-->
|
<!--Explore:Artists-->
|
||||||
<message id="Lms.Explore.Artists.linktype-all">Tous les artistes</message>
|
<message id="Lms.Explore.Artists.all">Tous les artistes</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-artist">
|
<message id="Lms.Explore.Artists.composer">
|
||||||
<plural case="0">Artiste de piste</plural>
|
|
||||||
<plural case="1">Artistes de piste</plural>
|
|
||||||
</message>
|
|
||||||
<message id="Lms.Explore.Artists.linktype-composer">
|
|
||||||
<plural case="0">Compositeur</plural>
|
<plural case="0">Compositeur</plural>
|
||||||
<plural case="1">Compositeurs</plural>
|
<plural case="1">Compositeurs</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-conductor">
|
<message id="Lms.Explore.Artists.conductor">
|
||||||
<plural case="0">Chef d'orchestre</plural>
|
<plural case="0">Chef d'orchestre</plural>
|
||||||
<plural case="1">Chefs d'orchestre</plural>
|
<plural case="1">Chefs d'orchestre</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-lyricist">
|
<message id="Lms.Explore.Artists.lyricist">
|
||||||
<plural case="0">Parolier</plural>
|
<plural case="0">Parolier</plural>
|
||||||
<plural case="1">Paroliers</plural>
|
<plural case="1">Paroliers</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-mixer">
|
<message id="Lms.Explore.Artists.mixer">
|
||||||
<plural case="0">Mixeur</plural>
|
<plural case="0">Mixeur</plural>
|
||||||
<plural case="1">Mixeurs</plural>
|
<plural case="1">Mixeurs</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-performer">
|
<message id="Lms.Explore.Artists.performer">
|
||||||
<plural case="0">Interprète</plural>
|
<plural case="0">Interprète</plural>
|
||||||
<plural case="1">Interprètes</plural>
|
<plural case="1">Interprètes</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-producer">
|
<message id="Lms.Explore.Artists.producer">
|
||||||
<plural case="0">Producteur</plural>
|
<plural case="0">Producteur</plural>
|
||||||
<plural case="1">Producteurs</plural>
|
<plural case="1">Producteurs</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-releaseartist">
|
<message id="Lms.Explore.Artists.release-artist">
|
||||||
<plural case="0">Artiste d'album</plural>
|
<plural case="0">Artiste d'album</plural>
|
||||||
<plural case="1">Artistes d'album</plural>
|
<plural case="1">Artistes d'album</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-remixer">
|
<message id="Lms.Explore.Artists.remixer">
|
||||||
<plural case="0">Remixeur</plural>
|
<plural case="0">Remixeur</plural>
|
||||||
<plural case="1">Remixeurs</plural>
|
<plural case="1">Remixeurs</plural>
|
||||||
</message>
|
</message>
|
||||||
|
<message id="Lms.Explore.Artists.track-artist">
|
||||||
|
<plural case="0">Artiste de piste</plural>
|
||||||
|
<plural case="1">Artistes de piste</plural>
|
||||||
|
</message>
|
||||||
|
|
||||||
<!--Explore:Release-->
|
<!--Explore:Release-->
|
||||||
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
||||||
|
|||||||
+13
-13
@@ -240,43 +240,43 @@
|
|||||||
<message id="Lms.Explore.Artist.similar-artists">Artisti simili</message>
|
<message id="Lms.Explore.Artist.similar-artists">Artisti simili</message>
|
||||||
|
|
||||||
<!--Explore:Artists-->
|
<!--Explore:Artists-->
|
||||||
<message id="Lms.Explore.Artists.linktype-all">Tutti gli artisti</message>
|
<message id="Lms.Explore.Artists.all">Tutti gli artisti</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-artist">
|
<message id="Lms.Explore.Artists.composer">
|
||||||
<plural case="0">Artista della traccia</plural>
|
|
||||||
<plural case="1">Artisti della traccia</plural>
|
|
||||||
</message>
|
|
||||||
<message id="Lms.Explore.Artists.linktype-composer">
|
|
||||||
<plural case="0">Compositore</plural>
|
<plural case="0">Compositore</plural>
|
||||||
<plural case="1">Compositori</plural>
|
<plural case="1">Compositori</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-conductor">
|
<message id="Lms.Explore.Artists.conductor">
|
||||||
<plural case="0">Direttore d'orchestra</plural>
|
<plural case="0">Direttore d'orchestra</plural>
|
||||||
<plural case="1">Direttori d'orchestra</plural>
|
<plural case="1">Direttori d'orchestra</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-lyricist">
|
<message id="Lms.Explore.Artists.lyricist">
|
||||||
<plural case="0">Paroliere</plural>
|
<plural case="0">Paroliere</plural>
|
||||||
<plural case="1">Parolieri</plural>
|
<plural case="1">Parolieri</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-mixer">
|
<message id="Lms.Explore.Artists.mixer">
|
||||||
<plural case="0">Tecnico del mixaggio</plural>
|
<plural case="0">Tecnico del mixaggio</plural>
|
||||||
<plural case="1">Tecnici del mixaggio</plural>
|
<plural case="1">Tecnici del mixaggio</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-performer">
|
<message id="Lms.Explore.Artists.performer">
|
||||||
<plural case="0">Interprete</plural>
|
<plural case="0">Interprete</plural>
|
||||||
<plural case="1">Interpreti</plural>
|
<plural case="1">Interpreti</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-producer">
|
<message id="Lms.Explore.Artists.producer">
|
||||||
<plural case="0">Produttore</plural>
|
<plural case="0">Produttore</plural>
|
||||||
<plural case="1">Produttori</plural>
|
<plural case="1">Produttori</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-releaseartist">
|
<message id="Lms.Explore.Artists.release-artist">
|
||||||
<plural case="0">Artista dell'album</plural>
|
<plural case="0">Artista dell'album</plural>
|
||||||
<plural case="1">Artisti dell'album</plural>
|
<plural case="1">Artisti dell'album</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-remixer">
|
<message id="Lms.Explore.Artists.remixer">
|
||||||
<plural case="0">Remixer</plural>
|
<plural case="0">Remixer</plural>
|
||||||
<plural case="1">Remixers</plural>
|
<plural case="1">Remixers</plural>
|
||||||
</message>
|
</message>
|
||||||
|
<message id="Lms.Explore.Artists.track-artist">
|
||||||
|
<plural case="0">Artista della traccia</plural>
|
||||||
|
<plural case="1">Artisti della traccia</plural>
|
||||||
|
</message>
|
||||||
|
|
||||||
<!--Explore:Release-->
|
<!--Explore:Release-->
|
||||||
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
<message id="Lms.Explore.Release.copyright">Copyright</message>
|
||||||
|
|||||||
+14
-14
@@ -257,52 +257,52 @@
|
|||||||
<message id="Lms.Explore.Artist.similar-artists">Podobni artyści</message>
|
<message id="Lms.Explore.Artist.similar-artists">Podobni artyści</message>
|
||||||
|
|
||||||
<!--Explore:Artists-->
|
<!--Explore:Artists-->
|
||||||
<message id="Lms.Explore.Artists.linktype-all">Wszyscy artyści</message>
|
<message id="Lms.Explore.Artists.all">Wszyscy artyści</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-artist">
|
<message id="Lms.Explore.Artists.composer">
|
||||||
<plural case="0">Artysta ze ścieżki</plural>
|
|
||||||
<plural case="1">Artyści ze ścieżki</plural>
|
|
||||||
<plural case="2">Artyści ze ścieżki</plural>
|
|
||||||
</message>
|
|
||||||
<message id="Lms.Explore.Artists.linktype-composer">
|
|
||||||
<plural case="0">Kompozytor</plural>
|
<plural case="0">Kompozytor</plural>
|
||||||
<plural case="1">Kompozytorzy</plural>
|
<plural case="1">Kompozytorzy</plural>
|
||||||
<plural case="2">Kompozytorzy</plural>
|
<plural case="2">Kompozytorzy</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-conductor">
|
<message id="Lms.Explore.Artists.conductor">
|
||||||
<plural case="0">Dyrygent</plural>
|
<plural case="0">Dyrygent</plural>
|
||||||
<plural case="1">Dyrygenci</plural>
|
<plural case="1">Dyrygenci</plural>
|
||||||
<plural case="2">Dyrygenci</plural>
|
<plural case="2">Dyrygenci</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-lyricist">
|
<message id="Lms.Explore.Artists.lyricist">
|
||||||
<plural case="0">Tekściarz</plural>
|
<plural case="0">Tekściarz</plural>
|
||||||
<plural case="1">Tekściarze</plural>
|
<plural case="1">Tekściarze</plural>
|
||||||
<plural case="2">Tekściarze</plural>
|
<plural case="2">Tekściarze</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-mixer">
|
<message id="Lms.Explore.Artists.mixer">
|
||||||
<plural case="0">Realizator</plural>
|
<plural case="0">Realizator</plural>
|
||||||
<plural case="1">Realizatorzy</plural>
|
<plural case="1">Realizatorzy</plural>
|
||||||
<plural case="2">Realizatorzy</plural>
|
<plural case="2">Realizatorzy</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-performer">
|
<message id="Lms.Explore.Artists.performer">
|
||||||
<plural case="0">Wykonawca</plural>
|
<plural case="0">Wykonawca</plural>
|
||||||
<plural case="1">Wykonawcy</plural>
|
<plural case="1">Wykonawcy</plural>
|
||||||
<plural case="2">Wykonawcy</plural>
|
<plural case="2">Wykonawcy</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-producer">
|
<message id="Lms.Explore.Artists.producer">
|
||||||
<plural case="0">Producent</plural>
|
<plural case="0">Producent</plural>
|
||||||
<plural case="1">Producenci</plural>
|
<plural case="1">Producenci</plural>
|
||||||
<plural case="2">Producenci</plural>
|
<plural case="2">Producenci</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-releaseartist">
|
<message id="Lms.Explore.Artists.release-artist">
|
||||||
<plural case="0">Artysta z albumu</plural>
|
<plural case="0">Artysta z albumu</plural>
|
||||||
<plural case="1">Artyści z albumu</plural>
|
<plural case="1">Artyści z albumu</plural>
|
||||||
<plural case="2">Artyści z albumu</plural>
|
<plural case="2">Artyści z albumu</plural>
|
||||||
</message>
|
</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-remixer">
|
<message id="Lms.Explore.Artists.remixer">
|
||||||
<plural case="0">Remixer</plural>
|
<plural case="0">Remixer</plural>
|
||||||
<plural case="1">Remikserzy</plural>
|
<plural case="1">Remikserzy</plural>
|
||||||
<plural case="2">Remikserzy</plural>
|
<plural case="2">Remikserzy</plural>
|
||||||
</message>
|
</message>
|
||||||
|
<message id="Lms.Explore.Artists.track-artist">
|
||||||
|
<plural case="0">Artysta ze ścieżki</plural>
|
||||||
|
<plural case="1">Artyści ze ścieżki</plural>
|
||||||
|
<plural case="2">Artyści ze ścieżki</plural>
|
||||||
|
</message>
|
||||||
|
|
||||||
<!--Explore:Release-->
|
<!--Explore:Release-->
|
||||||
<message id="Lms.Explore.Release.copyright">Prawa autorskie</message>
|
<message id="Lms.Explore.Release.copyright">Prawa autorskie</message>
|
||||||
|
|||||||
+10
-11
@@ -238,17 +238,16 @@
|
|||||||
<message id="Lms.Explore.Artist.similar-artists">相似歌手</message>
|
<message id="Lms.Explore.Artist.similar-artists">相似歌手</message>
|
||||||
|
|
||||||
<!--Explore:Artists-->
|
<!--Explore:Artists-->
|
||||||
<message id="Lms.Explore.Artists.linktype-all">所有艺术家</message>
|
<message id="Lms.Explore.Artists.all">所有艺术家</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-artist">曲目艺术家</message>
|
<message id="Lms.Explore.Artists.composer">作曲者</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-composer">作曲者</message>
|
<message id="Lms.Explore.Artists.conductor">指挥</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-conductor">指挥</message>
|
<message id="Lms.Explore.Artists.lyricist">作词者</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-lyricist">作词者</message>
|
<message id="Lms.Explore.Artists.mixer">混音师</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-mixer">混音师</message>
|
<message id="Lms.Explore.Artists.performer">表演者</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-performer">表演者</message>
|
<message id="Lms.Explore.Artists.producer">制作人</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-producer">制作人</message>
|
<message id="Lms.Explore.Artists.release-artist">专辑艺术家</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-releaseartist">专辑艺术家</message>
|
<message id="Lms.Explore.Artists.remixer">混音师</message>
|
||||||
<message id="Lms.Explore.Artists.linktype-remixer">混音师</message>
|
<message id="Lms.Explore.Artists.track-artist">曲目艺术家</message>
|
||||||
|
|
||||||
|
|
||||||
<!--Explore:Release-->
|
<!--Explore:Release-->
|
||||||
<message id="Lms.Explore.Release.copyright">版权所有</message>
|
<message id="Lms.Explore.Release.copyright">版权所有</message>
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ add_library(lmsdatabase STATIC
|
|||||||
impl/objects/PlayQueue.cpp
|
impl/objects/PlayQueue.cpp
|
||||||
impl/objects/Podcast.cpp
|
impl/objects/Podcast.cpp
|
||||||
impl/objects/PodcastEpisode.cpp
|
impl/objects/PodcastEpisode.cpp
|
||||||
impl/objects/TrackArtistLink.cpp
|
impl/objects/ReleaseArtistLink.cpp
|
||||||
impl/objects/TrackFeatures.cpp
|
|
||||||
impl/objects/TrackList.cpp
|
|
||||||
impl/objects/RatedArtist.cpp
|
impl/objects/RatedArtist.cpp
|
||||||
impl/objects/RatedRelease.cpp
|
impl/objects/RatedRelease.cpp
|
||||||
impl/objects/RatedTrack.cpp
|
impl/objects/RatedTrack.cpp
|
||||||
@@ -26,9 +24,12 @@ add_library(lmsdatabase STATIC
|
|||||||
impl/objects/StarredRelease.cpp
|
impl/objects/StarredRelease.cpp
|
||||||
impl/objects/StarredTrack.cpp
|
impl/objects/StarredTrack.cpp
|
||||||
impl/objects/Track.cpp
|
impl/objects/Track.cpp
|
||||||
|
impl/objects/TrackArtistLink.cpp
|
||||||
impl/objects/TrackBookmark.cpp
|
impl/objects/TrackBookmark.cpp
|
||||||
impl/objects/TrackEmbeddedImage.cpp
|
impl/objects/TrackEmbeddedImage.cpp
|
||||||
impl/objects/TrackEmbeddedImageLink.cpp
|
impl/objects/TrackEmbeddedImageLink.cpp
|
||||||
|
impl/objects/TrackFeatures.cpp
|
||||||
|
impl/objects/TrackList.cpp
|
||||||
impl/objects/TrackLyrics.cpp
|
impl/objects/TrackLyrics.cpp
|
||||||
impl/objects/Types.cpp
|
impl/objects/Types.cpp
|
||||||
impl/objects/UIState.cpp
|
impl/objects/UIState.cpp
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace lms::db
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
static constexpr Version LMS_DATABASE_VERSION{ 102 };
|
static constexpr Version LMS_DATABASE_VERSION{ 103 };
|
||||||
}
|
}
|
||||||
|
|
||||||
VersionInfo::VersionInfo()
|
VersionInfo::VersionInfo()
|
||||||
@@ -1681,6 +1681,31 @@ FROM track)");
|
|||||||
utils::executeCommand(*session.getDboSession(), "ALTER TABLE podcast_episode ADD bits_per_sample INTEGER");
|
utils::executeCommand(*session.getDboSession(), "ALTER TABLE podcast_episode ADD bits_per_sample INTEGER");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void migrateFromV102(Session& session)
|
||||||
|
{
|
||||||
|
// New link to artists from releases
|
||||||
|
utils::executeCommand(*session.getDboSession(), R"(CREATE TABLE IF NOT EXISTS "release_artist_link" (
|
||||||
|
"id" integer primary key autoincrement,
|
||||||
|
"version" integer not null,
|
||||||
|
"artist_name" text not null,
|
||||||
|
"artist_sort_name" text not null,
|
||||||
|
"artist_mbid_matched" boolean not null,
|
||||||
|
"release_id" bigint,
|
||||||
|
"artist_id" bigint,
|
||||||
|
constraint "fk_release_artist_link_release" foreign key ("release_id") references "release" ("id") on delete cascade deferrable initially deferred,
|
||||||
|
constraint "fk_release_artist_link_artist" foreign key ("artist_id") references "artist" ("id") on delete cascade deferrable initially deferred
|
||||||
|
))");
|
||||||
|
|
||||||
|
// Remove all artist links where the link type is TrackArtistLinkType::ReleaseArtist = 8
|
||||||
|
utils::executeCommand(*session.getDboSession(), R"(DELETE FROM track_artist_link WHERE type = 8)");
|
||||||
|
|
||||||
|
// Remove outdated UI state entries
|
||||||
|
utils::executeCommand(*session.getDboSession(), R"(DELETE FROM ui_state WHERE item = 'artists_link_type')");
|
||||||
|
|
||||||
|
// 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");
|
||||||
|
}
|
||||||
|
|
||||||
bool doDbMigration(Session& session)
|
bool doDbMigration(Session& session)
|
||||||
{
|
{
|
||||||
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
|
constexpr std::string_view outdatedMsg{ "Outdated database, please rebuild it (delete the .db file and restart)" };
|
||||||
@@ -1759,6 +1784,7 @@ FROM track)");
|
|||||||
{ 99, migrateFromV99 },
|
{ 99, migrateFromV99 },
|
||||||
{ 100, migrateFromV100 },
|
{ 100, migrateFromV100 },
|
||||||
{ 101, migrateFromV101 },
|
{ 101, migrateFromV101 },
|
||||||
|
{ 102, migrateFromV102 },
|
||||||
};
|
};
|
||||||
|
|
||||||
bool migrationPerformed{};
|
bool migrationPerformed{};
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
#include "database/objects/RatedRelease.hpp"
|
#include "database/objects/RatedRelease.hpp"
|
||||||
#include "database/objects/RatedTrack.hpp"
|
#include "database/objects/RatedTrack.hpp"
|
||||||
#include "database/objects/Release.hpp"
|
#include "database/objects/Release.hpp"
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
#include "database/objects/ScanSettings.hpp"
|
#include "database/objects/ScanSettings.hpp"
|
||||||
#include "database/objects/StarredArtist.hpp"
|
#include "database/objects/StarredArtist.hpp"
|
||||||
#include "database/objects/StarredRelease.hpp"
|
#include "database/objects/StarredRelease.hpp"
|
||||||
@@ -94,6 +95,7 @@ namespace lms::db
|
|||||||
_session.mapClass<RatedRelease>("rated_release");
|
_session.mapClass<RatedRelease>("rated_release");
|
||||||
_session.mapClass<RatedTrack>("rated_track");
|
_session.mapClass<RatedTrack>("rated_track");
|
||||||
_session.mapClass<Release>("release");
|
_session.mapClass<Release>("release");
|
||||||
|
_session.mapClass<ReleaseArtistLink>("release_artist_link");
|
||||||
_session.mapClass<ReleaseType>("release_type");
|
_session.mapClass<ReleaseType>("release_type");
|
||||||
_session.mapClass<ScanSettings>("scan_settings");
|
_session.mapClass<ScanSettings>("scan_settings");
|
||||||
_session.mapClass<StarredArtist>("starred_artist");
|
_session.mapClass<StarredArtist>("starred_artist");
|
||||||
|
|||||||
@@ -27,9 +27,7 @@
|
|||||||
#include "database/objects/Cluster.hpp"
|
#include "database/objects/Cluster.hpp"
|
||||||
#include "database/objects/Directory.hpp"
|
#include "database/objects/Directory.hpp"
|
||||||
#include "database/objects/Release.hpp"
|
#include "database/objects/Release.hpp"
|
||||||
// #include "database/objects/StarredArtist.hpp"
|
|
||||||
#include "database/objects/Track.hpp"
|
#include "database/objects/Track.hpp"
|
||||||
// #include "database/objects/TrackArtistLink.hpp"
|
|
||||||
#include "database/objects/User.hpp"
|
#include "database/objects/User.hpp"
|
||||||
|
|
||||||
#include "SqlQuery.hpp"
|
#include "SqlQuery.hpp"
|
||||||
@@ -53,9 +51,8 @@ namespace lms::db
|
|||||||
if (params.sortMethod == ArtistSortMethod::LastWrittenDesc
|
if (params.sortMethod == ArtistSortMethod::LastWrittenDesc
|
||||||
|| params.sortMethod == ArtistSortMethod::AddedDesc
|
|| params.sortMethod == ArtistSortMethod::AddedDesc
|
||||||
|| params.writtenAfter.isValid()
|
|| params.writtenAfter.isValid()
|
||||||
|| params.linkType
|
|| params.trackArtistLinkType.has_value()
|
||||||
|| params.track.isValid()
|
|| params.track.isValid()
|
||||||
|| params.release.isValid()
|
|
||||||
|| params.filters.clusters.size() == 1
|
|| params.filters.clusters.size() == 1
|
||||||
|| params.filters.codec.has_value()
|
|| params.filters.codec.has_value()
|
||||||
|| params.filters.mediaLibrary.isValid()
|
|| params.filters.mediaLibrary.isValid()
|
||||||
@@ -68,7 +65,6 @@ namespace lms::db
|
|||||||
if (params.sortMethod == ArtistSortMethod::LastWrittenDesc
|
if (params.sortMethod == ArtistSortMethod::LastWrittenDesc
|
||||||
|| params.sortMethod == ArtistSortMethod::AddedDesc
|
|| params.sortMethod == ArtistSortMethod::AddedDesc
|
||||||
|| params.writtenAfter.isValid()
|
|| params.writtenAfter.isValid()
|
||||||
|| params.release.isValid()
|
|
||||||
|| params.filters.codec.has_value()
|
|| params.filters.codec.has_value()
|
||||||
|| params.filters.mediaLibrary.isValid()
|
|| params.filters.mediaLibrary.isValid()
|
||||||
|| params.filters.label.isValid()
|
|| params.filters.label.isValid()
|
||||||
@@ -79,9 +75,6 @@ namespace lms::db
|
|||||||
if (params.writtenAfter.isValid())
|
if (params.writtenAfter.isValid())
|
||||||
query.where("t.file_last_write > ?").bind(params.writtenAfter);
|
query.where("t.file_last_write > ?").bind(params.writtenAfter);
|
||||||
|
|
||||||
if (params.release.isValid())
|
|
||||||
query.where("t.release_id = ?").bind(params.release);
|
|
||||||
|
|
||||||
if (params.filters.codec.has_value())
|
if (params.filters.codec.has_value())
|
||||||
query.where("t.codec = ?").bind(detail::getDbCodec(*params.filters.codec));
|
query.where("t.codec = ?").bind(detail::getDbCodec(*params.filters.codec));
|
||||||
|
|
||||||
@@ -101,8 +94,11 @@ namespace lms::db
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.linkType)
|
if (params.releaseArtistsOnly)
|
||||||
query.where("+t_a_l.type = ?").bind(*params.linkType); // Exclude this since the query planner does not do a good job when db is not analyzed
|
query.join("release_artist_link r_a_l ON r_a_l.artist_id = a.id");
|
||||||
|
|
||||||
|
if (params.trackArtistLinkType.has_value())
|
||||||
|
query.where("+t_a_l.type = ?").bind(*params.trackArtistLinkType); // Exclude this since the query planner does not do a good job when db is not analyzed
|
||||||
|
|
||||||
if (!params.keywords.empty())
|
if (!params.keywords.empty())
|
||||||
{
|
{
|
||||||
@@ -318,6 +314,7 @@ namespace lms::db
|
|||||||
|
|
||||||
RangeResults<ArtistId> Artist::findOrphanIds(Session& session, std::optional<Range> range)
|
RangeResults<ArtistId> Artist::findOrphanIds(Session& session, std::optional<Range> range)
|
||||||
{
|
{
|
||||||
|
// TODO extend with release artists
|
||||||
session.checkReadTransaction();
|
session.checkReadTransaction();
|
||||||
auto query{ session.getDboSession()->query<ArtistId>(R"(SELECT DISTINCT a.id FROM artist a
|
auto query{ session.getDboSession()->query<ArtistId>(R"(SELECT DISTINCT a.id FROM artist a
|
||||||
WHERE NOT EXISTS (
|
WHERE NOT EXISTS (
|
||||||
@@ -327,6 +324,12 @@ WHERE NOT EXISTS (
|
|||||||
ON t_a_l.artist_id = a.id
|
ON t_a_l.artist_id = a.id
|
||||||
WHERE t.id = t_a_l.track_id
|
WHERE t.id = t_a_l.track_id
|
||||||
)
|
)
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM release r
|
||||||
|
INNER JOIN release_artist_link r_a_l
|
||||||
|
ON r_a_l.artist_id = a.id
|
||||||
|
WHERE r.id = r_a_l.release_id)
|
||||||
AND NOT EXISTS (
|
AND NOT EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM artist_info ai
|
FROM artist_info ai
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ namespace lms::db
|
|||||||
for (std::string_view keyword : params.keywords)
|
for (std::string_view keyword : params.keywords)
|
||||||
query.where("d.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeForLikeKeyword(keyword) + "%");
|
query.where("d.name LIKE ? ESCAPE '" ESCAPE_CHAR_STR "'").bind("%" + utils::escapeForLikeKeyword(keyword) + "%");
|
||||||
|
|
||||||
if (params.artist.isValid()
|
if (params.trackArtist.isValid()
|
||||||
|
|| params.releaseArtist.isValid()
|
||||||
|| params.release.isValid()
|
|| params.release.isValid()
|
||||||
|| params.medium.isValid())
|
|| params.medium.isValid())
|
||||||
{
|
{
|
||||||
@@ -62,12 +63,24 @@ namespace lms::db
|
|||||||
if (params.release.isValid())
|
if (params.release.isValid())
|
||||||
query.where("t.release_id = ?").bind(params.release);
|
query.where("t.release_id = ?").bind(params.release);
|
||||||
|
|
||||||
if (params.artist.isValid())
|
if (params.releaseArtist.isValid())
|
||||||
{
|
{
|
||||||
|
assert(!params.trackArtist.isValid());
|
||||||
|
|
||||||
|
query.join("artist a ON a.id = r_a_l.artist_id")
|
||||||
|
.join("release_artist_link r_a_l ON r_a_l.release_id = t.release_id")
|
||||||
|
.where("a.id = ?")
|
||||||
|
.bind(params.releaseArtist);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.trackArtist.isValid())
|
||||||
|
{
|
||||||
|
assert(!params.releaseArtist.isValid());
|
||||||
|
|
||||||
query.join("artist a ON a.id = t_a_l.artist_id")
|
query.join("artist a ON a.id = t_a_l.artist_id")
|
||||||
.join("track_artist_link t_a_l ON t_a_l.track_id = t.id")
|
.join("track_artist_link t_a_l ON t_a_l.track_id = t.id")
|
||||||
.where("a.id = ?")
|
.where("a.id = ?")
|
||||||
.bind(params.artist);
|
.bind(params.trackArtist);
|
||||||
|
|
||||||
if (!params.trackArtistLinkTypes.empty())
|
if (!params.trackArtistLinkTypes.empty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,7 +52,8 @@ namespace lms::db
|
|||||||
if (params.filters.mediaLibrary.isValid()
|
if (params.filters.mediaLibrary.isValid()
|
||||||
|| params.filters.codec.has_value()
|
|| params.filters.codec.has_value()
|
||||||
|| params.filters.label.isValid()
|
|| params.filters.label.isValid()
|
||||||
|| params.filters.releaseType.isValid())
|
|| params.filters.releaseType.isValid()
|
||||||
|
|| params.trackArtistLinkType.has_value())
|
||||||
{
|
{
|
||||||
query.join("track t ON t.id = t_a_l.track_id");
|
query.join("track t ON t.id = t_a_l.track_id");
|
||||||
|
|
||||||
@@ -75,8 +76,11 @@ namespace lms::db
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.linkType)
|
if (params.releaseArtistsOnly)
|
||||||
query.where("t_a_l.type = ?").bind(*params.linkType);
|
query.join("release_artist_link r_a_l ON r_a_l.artist_id = a.id");
|
||||||
|
|
||||||
|
if (params.trackArtistLinkType.has_value())
|
||||||
|
query.where("t_a_l.type = ?").bind(params.trackArtistLinkType.value());
|
||||||
|
|
||||||
if (!params.filters.clusters.empty())
|
if (!params.filters.clusters.empty())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#include "database/objects/Directory.hpp"
|
#include "database/objects/Directory.hpp"
|
||||||
#include "database/objects/MediaLibrary.hpp"
|
#include "database/objects/MediaLibrary.hpp"
|
||||||
#include "database/objects/Medium.hpp"
|
#include "database/objects/Medium.hpp"
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
#include "database/objects/Track.hpp"
|
#include "database/objects/Track.hpp"
|
||||||
#include "database/objects/TrackArtistLink.hpp"
|
#include "database/objects/TrackArtistLink.hpp"
|
||||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||||
@@ -72,7 +73,7 @@ namespace lms::db
|
|||||||
|| params.sortMethod == ReleaseSortMethod::OriginalDateDesc
|
|| params.sortMethod == ReleaseSortMethod::OriginalDateDesc
|
||||||
|| params.writtenAfter.isValid()
|
|| params.writtenAfter.isValid()
|
||||||
|| params.dateRange
|
|| params.dateRange
|
||||||
|| params.artist.isValid()
|
|| params.trackArtist.isValid()
|
||||||
|| params.filters.clusters.size() == 1
|
|| params.filters.clusters.size() == 1
|
||||||
|| params.filters.mediaLibrary.isValid()
|
|| params.filters.mediaLibrary.isValid()
|
||||||
|| params.filters.codec.has_value()
|
|| params.filters.codec.has_value()
|
||||||
@@ -141,13 +142,22 @@ namespace lms::db
|
|||||||
.bind(SyncState::PendingRemove);
|
.bind(SyncState::PendingRemove);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.artist.isValid()
|
if (params.artist.isValid())
|
||||||
|
{
|
||||||
|
assert(!params.trackArtist.isValid());
|
||||||
|
query.join("release_artist_link r_a_l ON r_a_l.release_id = r.id");
|
||||||
|
query.where("r_a_l.artist_id = ?").bind(params.artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.trackArtist.isValid()
|
||||||
|| params.sortMethod == ReleaseSortMethod::ArtistNameThenName)
|
|| params.sortMethod == ReleaseSortMethod::ArtistNameThenName)
|
||||||
{
|
{
|
||||||
|
assert(!params.artist.isValid());
|
||||||
|
|
||||||
query.join("track_artist_link t_a_l ON t_a_l.track_id = t.id");
|
query.join("track_artist_link t_a_l ON t_a_l.track_id = t.id");
|
||||||
|
|
||||||
if (params.artist.isValid())
|
if (params.trackArtist.isValid())
|
||||||
query.where("t_a_l.artist_id = ?").bind(params.artist);
|
query.where("t_a_l.artist_id = ?").bind(params.trackArtist);
|
||||||
|
|
||||||
if (params.sortMethod == ReleaseSortMethod::ArtistNameThenName)
|
if (params.sortMethod == ReleaseSortMethod::ArtistNameThenName)
|
||||||
query.join("artist a ON a.id = t_a_l.artist_id");
|
query.join("artist a ON a.id = t_a_l.artist_id");
|
||||||
@@ -168,30 +178,6 @@ namespace lms::db
|
|||||||
}
|
}
|
||||||
query.where(oss.str());
|
query.where(oss.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params.excludedTrackArtistLinkTypes.empty())
|
|
||||||
{
|
|
||||||
std::ostringstream oss;
|
|
||||||
oss << "r.id NOT IN (SELECT DISTINCT r.id FROM release r"
|
|
||||||
" INNER JOIN track_artist_link t_a_l ON t_a_l.track_id = t.id"
|
|
||||||
" INNER JOIN track t ON t.release_id = r.id"
|
|
||||||
" WHERE (t_a_l.artist_id = ? AND (";
|
|
||||||
|
|
||||||
query.bind(params.artist);
|
|
||||||
|
|
||||||
bool first{ true };
|
|
||||||
for (const TrackArtistLinkType linkType : params.excludedTrackArtistLinkTypes)
|
|
||||||
{
|
|
||||||
if (!first)
|
|
||||||
oss << " OR ";
|
|
||||||
oss << "t_a_l.type = ?";
|
|
||||||
query.bind(linkType);
|
|
||||||
|
|
||||||
first = false;
|
|
||||||
}
|
|
||||||
oss << ")))";
|
|
||||||
query.where(oss.str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.filters.clusters.size() == 1)
|
if (params.filters.clusters.size() == 1)
|
||||||
@@ -272,7 +258,7 @@ namespace lms::db
|
|||||||
}
|
}
|
||||||
|
|
||||||
template<typename ResultType>
|
template<typename ResultType>
|
||||||
Wt::Dbo::Query<ResultType> createArtistQuery(Wt::Dbo::Session& session, std::string_view itemToSelect, ReleaseId releaseId, TrackArtistLinkType linkType)
|
Wt::Dbo::Query<ResultType> createTrackArtistQuery(Wt::Dbo::Session& session, std::string_view itemToSelect, ReleaseId releaseId, TrackArtistLinkType linkType)
|
||||||
{
|
{
|
||||||
auto query{ session.query<ResultType>("SELECT " + std::string{ itemToSelect } + " from artist a")
|
auto query{ session.query<ResultType>("SELECT " + std::string{ itemToSelect } + " from artist a")
|
||||||
.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id")
|
.join("track_artist_link t_a_l ON t_a_l.artist_id = a.id")
|
||||||
@@ -700,19 +686,50 @@ namespace lms::db
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Artist::pointer> Release::getArtists(TrackArtistLinkType linkType) const
|
std::vector<ObjectPtr<Artist>> Release::getArtists() const
|
||||||
{
|
{
|
||||||
assert(session());
|
assert(session());
|
||||||
|
|
||||||
const auto query{ createArtistQuery<Wt::Dbo::ptr<Artist>>(*session(), "a", getId(), linkType) };
|
auto query{ session()->query<Wt::Dbo::ptr<Artist>>("SELECT a from artist a") };
|
||||||
|
query.join("release_artist_link r_a_l ON r_a_l.artist_id = a.id");
|
||||||
|
query.where("r_a_l.release_id = ?").bind(getId());
|
||||||
|
query.groupBy("a.id");
|
||||||
|
|
||||||
return utils::fetchQueryResults<Artist::pointer>(query);
|
return utils::fetchQueryResults<Artist::pointer>(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<ArtistId> Release::getArtistIds(TrackArtistLinkType linkType) const
|
bool Release::hasArtist(ArtistId artistId) const
|
||||||
{
|
{
|
||||||
assert(session());
|
assert(session());
|
||||||
|
|
||||||
const auto query{ createArtistQuery<ArtistId>(*session(), "a.id", getId(), linkType) };
|
auto query{ session()->query<int>("SELECT COUNT(1) FROM release_artist_link r_a_l") };
|
||||||
|
query.where("r_a_l.release_id = ?").bind(getId());
|
||||||
|
query.where("r_a_l.artist_id = ?").bind(artistId);
|
||||||
|
|
||||||
|
return utils::fetchQuerySingleResult(query) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Artist::pointer> Release::getTrackArtists(TrackArtistLinkType type) const
|
||||||
|
{
|
||||||
|
assert(session());
|
||||||
|
|
||||||
|
const auto query{ createTrackArtistQuery<Wt::Dbo::ptr<Artist>>(*session(), "a", getId(), type) };
|
||||||
|
return utils::fetchQueryResults<Artist::pointer>(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Release::visitTrackArtists(TrackArtistLinkType type, std::function<void(const ObjectPtr<Artist>&)> visitor) const
|
||||||
|
{
|
||||||
|
assert(session());
|
||||||
|
|
||||||
|
const auto query{ createTrackArtistQuery<Wt::Dbo::ptr<Artist>>(*session(), "a", getId(), type) };
|
||||||
|
return utils::forEachQueryResult(query, visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ArtistId> Release::getTrackArtistIds(TrackArtistLinkType linkType) const
|
||||||
|
{
|
||||||
|
assert(session());
|
||||||
|
|
||||||
|
const auto query{ createTrackArtistQuery<ArtistId>(*session(), "a.id", getId(), linkType) };
|
||||||
return utils::fetchQueryResults(query);
|
return utils::fetchQueryResults(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -765,6 +782,36 @@ namespace lms::db
|
|||||||
return utils::fetchQueryResults<Medium::pointer>(query);
|
return utils::fetchQueryResults<Medium::pointer>(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<ObjectPtr<ReleaseArtistLink>> Release::getArtistLinks() const
|
||||||
|
{
|
||||||
|
return utils::fetchQueryResults<ReleaseArtistLink::pointer>(_releaseArtistLinks.find());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Release::visitArtistLinks(const std::function<void(const ReleaseArtistLink::pointer& artistLink)>& visitor) const
|
||||||
|
{
|
||||||
|
return utils::forEachQueryResult(_releaseArtistLinks.find(), visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Release::visitTrackArtistLinks(TrackArtistLinkType linkType, const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const
|
||||||
|
{
|
||||||
|
auto query{ session()->query<Wt::Dbo::ptr<TrackArtistLink>>("SELECT t_a_l from track_artist_link t_a_l") };
|
||||||
|
query.join("track t ON t.id = t_a_l.track_id");
|
||||||
|
query.where("t.release_id = ?").bind(getId());
|
||||||
|
query.where("t_a_l.type = ?").bind(linkType);
|
||||||
|
|
||||||
|
utils::forEachQueryResult(query, visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Release::clearArtistLinks()
|
||||||
|
{
|
||||||
|
_releaseArtistLinks.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Release::addArtistLink(const ObjectPtr<ReleaseArtistLink>& artistLink)
|
||||||
|
{
|
||||||
|
_releaseArtistLinks.insert(getDboPtr(artistLink));
|
||||||
|
}
|
||||||
|
|
||||||
void Release::clearLabels()
|
void Release::clearLabels()
|
||||||
{
|
{
|
||||||
_labels.clear();
|
_labels.clear();
|
||||||
@@ -803,7 +850,7 @@ namespace lms::db
|
|||||||
bool Release::hasVariousArtists() const
|
bool Release::hasVariousArtists() const
|
||||||
{
|
{
|
||||||
// TODO optimize
|
// TODO optimize
|
||||||
return getArtists().size() > 1;
|
return getTrackArtists().size() > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t Release::getTrackCount() const
|
std::size_t Release::getTrackCount() const
|
||||||
|
|||||||
@@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* 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/ReleaseArtistLink.hpp"
|
||||||
|
|
||||||
|
#include <Wt/Dbo/Impl.h>
|
||||||
|
|
||||||
|
#include "core/ILogger.hpp"
|
||||||
|
#include "database/Session.hpp"
|
||||||
|
#include "database/objects/Artist.hpp"
|
||||||
|
#include "database/objects/Release.hpp"
|
||||||
|
#include "database/objects/Track.hpp"
|
||||||
|
|
||||||
|
#include "Utils.hpp"
|
||||||
|
#include "traits/IdTypeTraits.hpp"
|
||||||
|
|
||||||
|
DBO_INSTANTIATE_TEMPLATES(lms::db::ReleaseArtistLink)
|
||||||
|
|
||||||
|
namespace lms::db
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
Wt::Dbo::Query<Wt::Dbo::ptr<ReleaseArtistLink>> createQuery(Session& session, const ReleaseArtistLink::FindParameters& params)
|
||||||
|
{
|
||||||
|
session.checkReadTransaction();
|
||||||
|
|
||||||
|
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<ReleaseArtistLink>>("SELECT r_a_l FROM release_artist_link r_a_l") };
|
||||||
|
|
||||||
|
if (params.release.isValid())
|
||||||
|
query.where("r_a_l.release_id = ?").bind(params.release);
|
||||||
|
|
||||||
|
if (params.artist.isValid())
|
||||||
|
query.where("r_a_l.artist_id = ?").bind(params.artist);
|
||||||
|
|
||||||
|
if (params.sortMethod == ReleaseArtistLinkSortMethod::OriginalDateDesc)
|
||||||
|
query.join("track t ON t.release_id = r_a_l.release_id");
|
||||||
|
|
||||||
|
if (params.mbidMatched)
|
||||||
|
query.where("r_a_l.artist_mbid_matched = ?").bind(*params.mbidMatched);
|
||||||
|
|
||||||
|
switch (params.sortMethod)
|
||||||
|
{
|
||||||
|
case ReleaseArtistLinkSortMethod::None:
|
||||||
|
break;
|
||||||
|
case ReleaseArtistLinkSortMethod::OriginalDateDesc:
|
||||||
|
query.orderBy("COALESCE(t.original_date, t.date) DESC");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ReleaseArtistLink::ReleaseArtistLink(const ObjectPtr<Release>& release, const ObjectPtr<Artist>& artist, bool artistMBIDMatched)
|
||||||
|
: _artistMBIDMatched{ artistMBIDMatched }
|
||||||
|
, _release{ getDboPtr(release) }
|
||||||
|
, _artist{ getDboPtr(artist) }
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleaseArtistLink::pointer ReleaseArtistLink::create(Session& session, const ObjectPtr<Release>& release, const ObjectPtr<Artist>& artist, bool artistMBIDMatched)
|
||||||
|
{
|
||||||
|
session.checkWriteTransaction();
|
||||||
|
return session.getDboSession()->add(std::make_unique<ReleaseArtistLink>(release, artist, artistMBIDMatched));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t ReleaseArtistLink::getCount(Session& session)
|
||||||
|
{
|
||||||
|
session.checkReadTransaction();
|
||||||
|
|
||||||
|
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM track_artist_link"));
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleaseArtistLink::pointer ReleaseArtistLink::find(Session& session, ReleaseArtistLinkId id)
|
||||||
|
{
|
||||||
|
session.checkReadTransaction();
|
||||||
|
return utils::fetchQuerySingleResult(session.getDboSession()->query<Wt::Dbo::ptr<ReleaseArtistLink>>("SELECT r_a_l from release_artist_link r_a_l").where("r_a_l.id = ?").bind(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleaseArtistLink::find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func)
|
||||||
|
{
|
||||||
|
auto query{ createQuery(session, params) };
|
||||||
|
utils::forEachQueryRangeResult(query, params.range, func);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleaseArtistLink::findArtistNameNoLongerMatch(Session& session, std::optional<Range> range, const std::function<void(const ReleaseArtistLink::pointer&)>& func)
|
||||||
|
{
|
||||||
|
session.checkReadTransaction();
|
||||||
|
|
||||||
|
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<ReleaseArtistLink>>("SELECT r_a_l from release_artist_link r_a_l") };
|
||||||
|
query.join("artist a ON r_a_l.artist_id = a.id");
|
||||||
|
query.where("r_a_l.artist_mbid_matched = FALSE");
|
||||||
|
query.where("r_a_l.artist_name <> a.name");
|
||||||
|
|
||||||
|
utils::applyRange(query, range);
|
||||||
|
utils::forEachQueryResult(query, [&](const ReleaseArtistLink::pointer& link) {
|
||||||
|
func(link);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleaseArtistLink::findWithArtistNameAmbiguity(Session& session, std::optional<Range> range, bool allowArtistMBIDFallback, const std::function<void(const ReleaseArtistLink::pointer&)>& func)
|
||||||
|
{
|
||||||
|
session.checkReadTransaction();
|
||||||
|
|
||||||
|
auto query{ session.getDboSession()->query<Wt::Dbo::ptr<ReleaseArtistLink>>("SELECT r_a_l from release_artist_link r_a_l") };
|
||||||
|
query.join("artist a ON r_a_l.artist_id = a.id");
|
||||||
|
query.where("r_a_l.artist_mbid_matched = FALSE");
|
||||||
|
if (!allowArtistMBIDFallback)
|
||||||
|
{
|
||||||
|
query.where("a.mbid <> ''");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
query.where(R"(
|
||||||
|
(a.mbid <> '' AND EXISTS (SELECT 1 FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '' AND a2.mbid <> a.mbid))
|
||||||
|
OR (a.mbid = '' AND (SELECT COUNT(*) FROM artist a2 WHERE a2.name = a.name AND a2.mbid <> '') = 1))");
|
||||||
|
}
|
||||||
|
|
||||||
|
utils::applyRange(query, range);
|
||||||
|
utils::forEachQueryResult(query, [&](const ReleaseArtistLink::pointer& link) {
|
||||||
|
func(link);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleaseArtistLink::setArtist(ObjectPtr<Artist> artist)
|
||||||
|
{
|
||||||
|
_artist = getDboPtr(artist);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleaseArtistLink::setArtistName(std::string_view artistName)
|
||||||
|
{
|
||||||
|
_artistName.assign(artistName, 0, 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);
|
||||||
|
LMS_LOG_IF(DB, WARNING, artistSortName.size() > Artist::maxNameLength, "Artist link sort name too long, truncated to '" << _artistSortName << "'");
|
||||||
|
}
|
||||||
|
} // namespace lms::db
|
||||||
@@ -728,6 +728,27 @@ namespace lms::db
|
|||||||
return utils::fetchQueryResults<TrackArtistLink::pointer>(_trackArtistLinks.find());
|
return utils::fetchQueryResults<TrackArtistLink::pointer>(_trackArtistLinks.find());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Track::visitArtistLinks(const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const
|
||||||
|
{
|
||||||
|
utils::forEachQueryResult(_trackArtistLinks.find(), visitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<ObjectPtr<TrackArtistLink>> Track::getArtistLinks(TrackArtistLinkType type) const
|
||||||
|
{
|
||||||
|
std::vector<ObjectPtr<TrackArtistLink>> links;
|
||||||
|
visitArtistLinks(type, [&links](const ObjectPtr<TrackArtistLink>& artistLink) { links.push_back(artistLink); });
|
||||||
|
return links;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Track::visitArtistLinks(TrackArtistLinkType type, const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const
|
||||||
|
{
|
||||||
|
auto query{ session()->query<Wt::Dbo::ptr<TrackArtistLink>>("SELECT t_a_l from track_artist_link t_a_l") };
|
||||||
|
query.where("t_a_l.track_id = ?").bind(getId());
|
||||||
|
query.where("t_a_l.type = ?").bind(type);
|
||||||
|
|
||||||
|
return utils::forEachQueryResult(query, visitor);
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<std::vector<Cluster::pointer>> Track::getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const
|
std::vector<std::vector<Cluster::pointer>> Track::getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const
|
||||||
{
|
{
|
||||||
assert(self());
|
assert(self());
|
||||||
|
|||||||
@@ -88,10 +88,12 @@ namespace lms::db
|
|||||||
{
|
{
|
||||||
session.checkWriteTransaction();
|
session.checkWriteTransaction();
|
||||||
|
|
||||||
TrackArtistLink::pointer res{ session.getDboSession()->add(std::make_unique<TrackArtistLink>(track, artist, type, subType, artistMBIDMatched)) };
|
return session.getDboSession()->add(std::make_unique<TrackArtistLink>(track, artist, type, subType, artistMBIDMatched));
|
||||||
session.getDboSession()->flush();
|
}
|
||||||
|
|
||||||
return res;
|
TrackArtistLink::pointer TrackArtistLink::create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, bool artistMBIDMatched)
|
||||||
|
{
|
||||||
|
return create(session, track, artist, type, std::string_view{}, artistMBIDMatched);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t TrackArtistLink::getCount(Session& session)
|
std::size_t TrackArtistLink::getCount(Session& session)
|
||||||
@@ -101,11 +103,6 @@ namespace lms::db
|
|||||||
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM track_artist_link"));
|
return utils::fetchQuerySingleResult(session.getDboSession()->query<int>("SELECT COUNT(*) FROM track_artist_link"));
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackArtistLink::pointer TrackArtistLink::create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, bool artistMBIDMatched)
|
|
||||||
{
|
|
||||||
return create(session, track, artist, type, std::string_view{}, artistMBIDMatched);
|
|
||||||
}
|
|
||||||
|
|
||||||
TrackArtistLink::pointer TrackArtistLink::find(Session& session, TrackArtistLinkId id)
|
TrackArtistLink::pointer TrackArtistLink::find(Session& session, TrackArtistLinkId id)
|
||||||
{
|
{
|
||||||
session.checkReadTransaction();
|
session.checkReadTransaction();
|
||||||
@@ -125,10 +122,10 @@ namespace lms::db
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void TrackArtistLink::find(Session& session, const FindParameters& parameters, const std::function<void(const TrackArtistLink::pointer&)>& func)
|
void TrackArtistLink::find(Session& session, const FindParameters& params, const std::function<void(const TrackArtistLink::pointer&)>& func)
|
||||||
{
|
{
|
||||||
auto query{ createQuery(session, parameters) };
|
auto query{ createQuery(session, params) };
|
||||||
utils::forEachQueryRangeResult(query, parameters.range, func);
|
utils::forEachQueryRangeResult(query, params.range, func);
|
||||||
}
|
}
|
||||||
|
|
||||||
core::EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session, ArtistId artistId)
|
core::EnumSet<TrackArtistLinkType> TrackArtistLink::findUsedTypes(Session& session, ArtistId artistId)
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ namespace lms::db
|
|||||||
return "performer";
|
return "performer";
|
||||||
case TrackArtistLinkType::Producer:
|
case TrackArtistLinkType::Producer:
|
||||||
return "producer";
|
return "producer";
|
||||||
case TrackArtistLinkType::ReleaseArtist:
|
|
||||||
return "albumartist";
|
|
||||||
case TrackArtistLinkType::Remixer:
|
case TrackArtistLinkType::Remixer:
|
||||||
return "remixer";
|
return "remixer";
|
||||||
case TrackArtistLinkType::Writer:
|
case TrackArtistLinkType::Writer:
|
||||||
|
|||||||
@@ -62,15 +62,15 @@ namespace lms::db
|
|||||||
struct FindParameters
|
struct FindParameters
|
||||||
{
|
{
|
||||||
Filters filters;
|
Filters filters;
|
||||||
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords (on either name field OR sort name field)
|
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords (on either name field OR sort name field)
|
||||||
std::optional<TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type
|
bool releaseArtistsOnly{}; // if set, only release artists
|
||||||
|
std::optional<TrackArtistLinkType> trackArtistLinkType; // if set, only artists that have produced at least one track with this link type
|
||||||
ArtistSortMethod sortMethod{ ArtistSortMethod::None };
|
ArtistSortMethod sortMethod{ ArtistSortMethod::None };
|
||||||
std::optional<Range> range;
|
std::optional<Range> range;
|
||||||
Wt::WDateTime writtenAfter;
|
Wt::WDateTime writtenAfter;
|
||||||
UserId starringUser; // only artists starred by this user
|
UserId starringUser; // only artists starred by this user
|
||||||
std::optional<FeedbackBackend> feedbackBackend; // and for this feedback backend
|
std::optional<FeedbackBackend> feedbackBackend; // and for this feedback backend
|
||||||
TrackId track; // artists involved in this track
|
TrackId track; // artists involved in this track
|
||||||
ReleaseId release; // artists involved in this release
|
|
||||||
|
|
||||||
FindParameters& setFilters(const Filters& _filters)
|
FindParameters& setFilters(const Filters& _filters)
|
||||||
{
|
{
|
||||||
@@ -82,9 +82,14 @@ namespace lms::db
|
|||||||
keywords = _keywords;
|
keywords = _keywords;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
FindParameters& setLinkType(std::optional<TrackArtistLinkType> _linkType)
|
FindParameters& setReleaseArtistsOnly(bool _releaseArtistsOnly)
|
||||||
{
|
{
|
||||||
linkType = _linkType;
|
releaseArtistsOnly = _releaseArtistsOnly;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
FindParameters& setTrackArtistLinkType(std::optional<TrackArtistLinkType> _trackArtistLinkType)
|
||||||
|
{
|
||||||
|
trackArtistLinkType = _trackArtistLinkType;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
FindParameters& setSortMethod(ArtistSortMethod _sortMethod)
|
FindParameters& setSortMethod(ArtistSortMethod _sortMethod)
|
||||||
@@ -113,11 +118,6 @@ namespace lms::db
|
|||||||
track = _track;
|
track = _track;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
FindParameters& setRelease(ReleaseId _release)
|
|
||||||
{
|
|
||||||
release = _release;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Artist() = default;
|
Artist() = default;
|
||||||
|
|||||||
@@ -53,10 +53,11 @@ namespace lms::db
|
|||||||
{
|
{
|
||||||
std::optional<Range> range;
|
std::optional<Range> range;
|
||||||
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords
|
std::vector<std::string_view> keywords; // if non empty, name must match all of these keywords
|
||||||
ArtistId artist; // only directoies that involve this artist
|
ArtistId trackArtist; // only directories that involve this track artist
|
||||||
|
core::EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
|
||||||
MediumId medium; // only directories that involve this medium
|
MediumId medium; // only directories that involve this medium
|
||||||
ReleaseId release; // only directories that involve this release
|
ReleaseId release; // only directories that involve this release
|
||||||
core::EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
|
ArtistId releaseArtist; // only directories that involve this release artist
|
||||||
DirectoryId parentDirectory; // If set, directories that have this parent
|
DirectoryId parentDirectory; // If set, directories that have this parent
|
||||||
bool withNoTrack{}; // If set, directories that do not contain any track
|
bool withNoTrack{}; // If set, directories that do not contain any track
|
||||||
MediaLibraryId mediaLibrary; // If set, directories in this library
|
MediaLibraryId mediaLibrary; // If set, directories in this library
|
||||||
@@ -72,12 +73,18 @@ namespace lms::db
|
|||||||
keywords = _keywords;
|
keywords = _keywords;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
FindParameters& setArtist(ArtistId _artist, core::EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {})
|
FindParameters& setTrackArtist(ArtistId _artist, core::EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {})
|
||||||
{
|
{
|
||||||
artist = _artist;
|
trackArtist = _artist;
|
||||||
trackArtistLinkTypes = _trackArtistLinkTypes;
|
trackArtistLinkTypes = _trackArtistLinkTypes;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
FindParameters& setReleaseArtist(ArtistId _artist)
|
||||||
|
{
|
||||||
|
releaseArtist = _artist;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
FindParameters& setMedium(MediumId _medium)
|
FindParameters& setMedium(MediumId _medium)
|
||||||
{
|
{
|
||||||
medium = _medium;
|
medium = _medium;
|
||||||
|
|||||||
@@ -124,11 +124,17 @@ namespace lms::db
|
|||||||
|
|
||||||
struct ArtistStatsFindParameters : public StatsFindParameters
|
struct ArtistStatsFindParameters : public StatsFindParameters
|
||||||
{
|
{
|
||||||
std::optional<TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type
|
std::optional<TrackArtistLinkType> trackArtistLinkType; // if set, only artists that have produced at least one track with this link type
|
||||||
|
bool releaseArtistsOnly{};
|
||||||
|
|
||||||
ArtistStatsFindParameters& setLinkType(std::optional<TrackArtistLinkType> _linkType)
|
ArtistStatsFindParameters& setReleaseArtistsOnly(bool _releaseArtistsOnly)
|
||||||
{
|
{
|
||||||
linkType = _linkType;
|
releaseArtistsOnly = _releaseArtistsOnly;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ArtistStatsFindParameters& setTrackArtistLinkType(std::optional<TrackArtistLinkType> _trackArtistLinkType)
|
||||||
|
{
|
||||||
|
trackArtistLinkType = _trackArtistLinkType;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -56,8 +56,10 @@ namespace lms::db
|
|||||||
class ClusterType;
|
class ClusterType;
|
||||||
class Medium;
|
class Medium;
|
||||||
class Release;
|
class Release;
|
||||||
|
class ReleaseArtistLink;
|
||||||
class Session;
|
class Session;
|
||||||
class Track;
|
class Track;
|
||||||
|
class TrackArtistLink;
|
||||||
class User;
|
class User;
|
||||||
|
|
||||||
class Country final : public Object<Country, CountryId>
|
class Country final : public Object<Country, CountryId>
|
||||||
@@ -167,15 +169,15 @@ namespace lms::db
|
|||||||
std::optional<Range> range;
|
std::optional<Range> range;
|
||||||
Wt::WDateTime writtenAfter;
|
Wt::WDateTime writtenAfter;
|
||||||
std::optional<YearRange> dateRange;
|
std::optional<YearRange> dateRange;
|
||||||
UserId starringUser; // only releases starred by this user
|
UserId starringUser; // only releases starred by this user
|
||||||
std::optional<FeedbackBackend> feedbackBackend; // and for this backend
|
std::optional<FeedbackBackend> feedbackBackend; // and for this backend
|
||||||
ArtistId artist; // only releases that involved this user
|
ArtistId artist; // only releases by this release artist
|
||||||
core::EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types
|
ArtistId trackArtist; // only releases that involved this track artist
|
||||||
core::EnumSet<TrackArtistLinkType> excludedTrackArtistLinkTypes; // but not for these link types
|
core::EnumSet<TrackArtistLinkType> trackArtistLinkTypes; // and for these link types, if set
|
||||||
std::string releaseType; // If set, releases that has this release type
|
std::string releaseType; // If set, releases that has this release type
|
||||||
std::optional<core::UUID> releaseGroupMBID; // If set, releases that belong to this release group
|
std::optional<core::UUID> releaseGroupMBID; // If set, releases that belong to this release group
|
||||||
DirectoryId directory; // if set, releases in this directory (cannot be set with parent directory)
|
DirectoryId directory; // if set, releases in this directory (cannot be set with parent directory)
|
||||||
DirectoryId parentDirectory; // if set, releases in this parent directory (cannot be set with directory)
|
DirectoryId parentDirectory; // if set, releases in this parent directory (cannot be set with directory)
|
||||||
|
|
||||||
FindParameters& setFilters(const Filters& _filters)
|
FindParameters& setFilters(const Filters& _filters)
|
||||||
{
|
{
|
||||||
@@ -218,11 +220,15 @@ namespace lms::db
|
|||||||
feedbackBackend = _feedbackBackend;
|
feedbackBackend = _feedbackBackend;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
FindParameters& setArtist(ArtistId _artist, core::EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {}, core::EnumSet<TrackArtistLinkType> _excludedTrackArtistLinkTypes = {})
|
FindParameters& setArtist(ArtistId _artist)
|
||||||
{
|
{
|
||||||
artist = _artist;
|
artist = _artist;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
FindParameters& setTrackArtist(ArtistId _trackArtist, core::EnumSet<TrackArtistLinkType> _trackArtistLinkTypes = {})
|
||||||
|
{
|
||||||
|
trackArtist = _trackArtist;
|
||||||
trackArtistLinkTypes = _trackArtistLinkTypes;
|
trackArtistLinkTypes = _trackArtistLinkTypes;
|
||||||
excludedTrackArtistLinkTypes = _excludedTrackArtistLinkTypes;
|
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
FindParameters& setReleaseType(std::string_view _releaseType)
|
FindParameters& setReleaseType(std::string_view _releaseType)
|
||||||
@@ -305,6 +311,9 @@ namespace lms::db
|
|||||||
ObjectPtr<Artwork> getPreferredArtwork() const;
|
ObjectPtr<Artwork> getPreferredArtwork() const;
|
||||||
ArtworkId getPreferredArtworkId() const;
|
ArtworkId getPreferredArtworkId() const;
|
||||||
std::vector<ObjectPtr<Medium>> getMediums() const;
|
std::vector<ObjectPtr<Medium>> getMediums() const;
|
||||||
|
std::vector<ObjectPtr<ReleaseArtistLink>> getArtistLinks() const;
|
||||||
|
void visitArtistLinks(const std::function<void(const ObjectPtr<ReleaseArtistLink>& artistLink)>& visitor) const;
|
||||||
|
void visitTrackArtistLinks(TrackArtistLinkType linkType, const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const;
|
||||||
|
|
||||||
// Setters
|
// Setters
|
||||||
void setName(std::string_view name) { _name = name; }
|
void setName(std::string_view name) { _name = name; }
|
||||||
@@ -313,6 +322,8 @@ namespace lms::db
|
|||||||
void setGroupMBID(const std::optional<core::UUID>& mbid) { _groupMBID = mbid ? mbid->getAsString() : ""; }
|
void setGroupMBID(const std::optional<core::UUID>& mbid) { _groupMBID = mbid ? mbid->getAsString() : ""; }
|
||||||
void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc; }
|
void setTotalDisc(std::optional<int> totalDisc) { _totalDisc = totalDisc; }
|
||||||
void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; }
|
void setArtistDisplayName(std::string_view name) { _artistDisplayName = name; }
|
||||||
|
void clearArtistLinks();
|
||||||
|
void addArtistLink(const ObjectPtr<ReleaseArtistLink>& artistLink);
|
||||||
void setCompilation(bool value) { _isCompilation = value; }
|
void setCompilation(bool value) { _isCompilation = value; }
|
||||||
void clearLabels();
|
void clearLabels();
|
||||||
void clearCountries();
|
void clearCountries();
|
||||||
@@ -325,9 +336,11 @@ namespace lms::db
|
|||||||
void setPreferredArtwork(ObjectPtr<Artwork> artwork);
|
void setPreferredArtwork(ObjectPtr<Artwork> artwork);
|
||||||
|
|
||||||
// Get the artists of this release
|
// Get the artists of this release
|
||||||
std::vector<ObjectPtr<Artist>> getArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
|
std::vector<ObjectPtr<Artist>> getArtists() const;
|
||||||
std::vector<ArtistId> getArtistIds(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
|
bool hasArtist(ArtistId artistId) const;
|
||||||
std::vector<ObjectPtr<Artist>> getReleaseArtists() const { return getArtists(TrackArtistLinkType::ReleaseArtist); }
|
std::vector<ObjectPtr<Artist>> getTrackArtists(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
|
||||||
|
void visitTrackArtists(TrackArtistLinkType type, std::function<void(const ObjectPtr<Artist>&)> visitor) const;
|
||||||
|
std::vector<ArtistId> getTrackArtistIds(TrackArtistLinkType type = TrackArtistLinkType::Artist) const;
|
||||||
bool hasVariousArtists() const;
|
bool hasVariousArtists() const;
|
||||||
std::vector<pointer> getSimilarReleases(std::optional<std::size_t> offset = {}, std::optional<std::size_t> count = {}) const;
|
std::vector<pointer> getSimilarReleases(std::optional<std::size_t> offset = {}, std::optional<std::size_t> count = {}) const;
|
||||||
|
|
||||||
@@ -345,6 +358,7 @@ namespace lms::db
|
|||||||
Wt::Dbo::field(a, _comment, "comment");
|
Wt::Dbo::field(a, _comment, "comment");
|
||||||
|
|
||||||
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
|
Wt::Dbo::hasMany(a, _tracks, Wt::Dbo::ManyToOne, "release");
|
||||||
|
Wt::Dbo::hasMany(a, _releaseArtistLinks, Wt::Dbo::ManyToOne, "release");
|
||||||
Wt::Dbo::belongsTo(a, _preferredArtwork, "preferred_artwork", Wt::Dbo::OnDeleteSetNull);
|
Wt::Dbo::belongsTo(a, _preferredArtwork, "preferred_artwork", Wt::Dbo::OnDeleteSetNull);
|
||||||
Wt::Dbo::hasMany(a, _labels, Wt::Dbo::ManyToMany, "release_label", "", Wt::Dbo::OnDeleteCascade);
|
Wt::Dbo::hasMany(a, _labels, Wt::Dbo::ManyToMany, "release_label", "", Wt::Dbo::OnDeleteCascade);
|
||||||
Wt::Dbo::hasMany(a, _releaseTypes, Wt::Dbo::ManyToMany, "release_release_type", "", Wt::Dbo::OnDeleteCascade);
|
Wt::Dbo::hasMany(a, _releaseTypes, Wt::Dbo::ManyToMany, "release_release_type", "", Wt::Dbo::OnDeleteCascade);
|
||||||
@@ -372,6 +386,7 @@ namespace lms::db
|
|||||||
std::string _comment;
|
std::string _comment;
|
||||||
|
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks;
|
Wt::Dbo::collection<Wt::Dbo::ptr<Track>> _tracks;
|
||||||
|
Wt::Dbo::collection<Wt::Dbo::ptr<ReleaseArtistLink>> _releaseArtistLinks;
|
||||||
Wt::Dbo::ptr<Artwork> _preferredArtwork;
|
Wt::Dbo::ptr<Artwork> _preferredArtwork;
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<Label>> _labels;
|
Wt::Dbo::collection<Wt::Dbo::ptr<Label>> _labels;
|
||||||
Wt::Dbo::collection<Wt::Dbo::ptr<ReleaseType>> _releaseTypes;
|
Wt::Dbo::collection<Wt::Dbo::ptr<ReleaseType>> _releaseTypes;
|
||||||
|
|||||||
@@ -0,0 +1,127 @@
|
|||||||
|
/*
|
||||||
|
* 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>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#include <Wt/Dbo/Field.h>
|
||||||
|
|
||||||
|
#include "database/IdType.hpp"
|
||||||
|
#include "database/Object.hpp"
|
||||||
|
#include "database/Types.hpp"
|
||||||
|
#include "database/objects/ArtistId.hpp"
|
||||||
|
#include "database/objects/ReleaseId.hpp"
|
||||||
|
#include "database/objects/Types.hpp"
|
||||||
|
|
||||||
|
LMS_DECLARE_IDTYPE(ReleaseArtistLinkId)
|
||||||
|
|
||||||
|
namespace lms::db
|
||||||
|
{
|
||||||
|
class Artist;
|
||||||
|
class Session;
|
||||||
|
class Release;
|
||||||
|
|
||||||
|
class ReleaseArtistLink final : public Object<ReleaseArtistLink, ReleaseArtistLinkId>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
struct FindParameters
|
||||||
|
{
|
||||||
|
std::optional<Range> range;
|
||||||
|
ArtistId artist; // if set, links involved with this artist
|
||||||
|
ReleaseId release; // if set, artists involved in this release
|
||||||
|
std::optional<bool> mbidMatched;
|
||||||
|
ReleaseArtistLinkSortMethod sortMethod{ ReleaseArtistLinkSortMethod::None };
|
||||||
|
|
||||||
|
FindParameters& setRange(std::optional<Range> _range)
|
||||||
|
{
|
||||||
|
range = _range;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
FindParameters& setArtist(ArtistId _artist)
|
||||||
|
{
|
||||||
|
artist = _artist;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
FindParameters& setRelease(ReleaseId _release)
|
||||||
|
{
|
||||||
|
release = _release;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
FindParameters& setMBIDMatched(std::optional<bool> _mbidMatched)
|
||||||
|
{
|
||||||
|
mbidMatched = _mbidMatched;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
FindParameters& setSortMethod(ReleaseArtistLinkSortMethod _method)
|
||||||
|
{
|
||||||
|
sortMethod = _method;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
ReleaseArtistLink() = default;
|
||||||
|
ReleaseArtistLink(const ObjectPtr<Release>& release, const ObjectPtr<Artist>& artist, bool artistMBIDMatched);
|
||||||
|
|
||||||
|
static pointer find(Session& session, ReleaseArtistLinkId linkId);
|
||||||
|
static void find(Session& session, const FindParameters& params, std::function<void(const pointer&)> func);
|
||||||
|
static std::size_t getCount(Session& session);
|
||||||
|
|
||||||
|
static void findArtistNameNoLongerMatch(Session& session, std::optional<Range> range, const std::function<void(const pointer&)>& func);
|
||||||
|
static void findWithArtistNameAmbiguity(Session& session, std::optional<Range> range, bool allowArtistMBIDFallback, const std::function<void(const pointer&)>& func);
|
||||||
|
|
||||||
|
// accessors
|
||||||
|
ObjectPtr<Release> getRelease() const { return _release; }
|
||||||
|
ObjectPtr<Artist> getArtist() const { return _artist; }
|
||||||
|
ArtistId getArtistId() const { return _artist.id(); }
|
||||||
|
std::string_view getArtistName() const { return _artistName; }
|
||||||
|
std::string_view getArtistSortName() const { return _artistSortName; }
|
||||||
|
bool isArtistMBIDMatched() const { return _artistMBIDMatched; }
|
||||||
|
|
||||||
|
// setters
|
||||||
|
void setArtist(ObjectPtr<Artist> artist);
|
||||||
|
void setArtistName(std::string_view artistName);
|
||||||
|
void setArtistSortName(std::string_view artistSortName);
|
||||||
|
|
||||||
|
template<class Action>
|
||||||
|
void persist(Action& a)
|
||||||
|
{
|
||||||
|
Wt::Dbo::field(a, _artistName, "artist_name");
|
||||||
|
Wt::Dbo::field(a, _artistSortName, "artist_sort_name");
|
||||||
|
Wt::Dbo::field(a, _artistMBIDMatched, "artist_mbid_matched");
|
||||||
|
|
||||||
|
Wt::Dbo::belongsTo(a, _release, "release", Wt::Dbo::OnDeleteCascade);
|
||||||
|
Wt::Dbo::belongsTo(a, _artist, "artist", Wt::Dbo::OnDeleteCascade);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class Session;
|
||||||
|
static pointer create(Session& session, const ObjectPtr<Release>& release, const ObjectPtr<Artist>& artist, bool artistMBIDMatched);
|
||||||
|
|
||||||
|
TrackArtistLinkType _type{ TrackArtistLinkType::Artist };
|
||||||
|
std::string _subType;
|
||||||
|
std::string _artistName; // as it was in the tags
|
||||||
|
std::string _artistSortName; // as it was in the tags
|
||||||
|
bool _artistMBIDMatched{};
|
||||||
|
|
||||||
|
Wt::Dbo::ptr<Release> _release;
|
||||||
|
Wt::Dbo::ptr<Artist> _artist;
|
||||||
|
};
|
||||||
|
} // namespace lms::db
|
||||||
@@ -307,6 +307,10 @@ namespace lms::db
|
|||||||
std::vector<ObjectPtr<Artist>> getArtists(core::EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
|
std::vector<ObjectPtr<Artist>> getArtists(core::EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
|
||||||
std::vector<ArtistId> getArtistIds(core::EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
|
std::vector<ArtistId> getArtistIds(core::EnumSet<TrackArtistLinkType> artistLinkTypes) const; // no type means all
|
||||||
std::vector<ObjectPtr<TrackArtistLink>> getArtistLinks() const;
|
std::vector<ObjectPtr<TrackArtistLink>> getArtistLinks() const;
|
||||||
|
void visitArtistLinks(const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const;
|
||||||
|
std::vector<ObjectPtr<TrackArtistLink>> getArtistLinks(TrackArtistLinkType type) const;
|
||||||
|
void visitArtistLinks(TrackArtistLinkType type, const std::function<void(const ObjectPtr<TrackArtistLink>& artistLink)>& visitor) const;
|
||||||
|
|
||||||
ReleaseId getReleaseId() const { return _release.id(); }
|
ReleaseId getReleaseId() const { return _release.id(); }
|
||||||
ObjectPtr<Release> getRelease() const { return _release; }
|
ObjectPtr<Release> getRelease() const { return _release; }
|
||||||
MediumId getMediumId() const { return _medium.id(); }
|
MediumId getMediumId() const { return _medium.id(); }
|
||||||
@@ -320,7 +324,7 @@ namespace lms::db
|
|||||||
ObjectPtr<Artwork> getPreferredMediaArtwork() const;
|
ObjectPtr<Artwork> getPreferredMediaArtwork() const;
|
||||||
ArtworkId getPreferredMediaArtworkId() const;
|
ArtworkId getPreferredMediaArtworkId() const;
|
||||||
|
|
||||||
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ClusterTypeId>& clusterTypes, std::size_t size) const;
|
std::vector<std::vector<ObjectPtr<Cluster>>> getClusterGroups(const std::vector<ClusterTypeId>& clusterTypeIds, std::size_t size) const;
|
||||||
|
|
||||||
template<class Action>
|
template<class Action>
|
||||||
void persist(Action& a)
|
void persist(Action& a)
|
||||||
|
|||||||
@@ -97,11 +97,9 @@ namespace lms::db
|
|||||||
TrackArtistLink(const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, std::string_view subType, bool artistMBIDMatched);
|
TrackArtistLink(const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, std::string_view subType, bool artistMBIDMatched);
|
||||||
|
|
||||||
static void find(Session& session, TrackId trackId, const std::function<void(const pointer&, const ObjectPtr<Artist>&)>& func);
|
static void find(Session& session, TrackId trackId, const std::function<void(const pointer&, const ObjectPtr<Artist>&)>& func);
|
||||||
static void find(Session& session, const FindParameters& parameters, const std::function<void(const pointer&)>& func);
|
static void find(Session& session, const FindParameters& params, const std::function<void(const pointer&)>& func);
|
||||||
static pointer find(Session& session, TrackArtistLinkId linkId);
|
static pointer find(Session& session, TrackArtistLinkId linkId);
|
||||||
static std::size_t getCount(Session& session);
|
static std::size_t getCount(Session& session);
|
||||||
static pointer create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, std::string_view subType, bool artistMBIDMatched = false);
|
|
||||||
static pointer create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, bool artistMBIDMatched = false);
|
|
||||||
static core::EnumSet<TrackArtistLinkType> findUsedTypes(Session& session, ArtistId _artist);
|
static core::EnumSet<TrackArtistLinkType> findUsedTypes(Session& session, ArtistId _artist);
|
||||||
static void findArtistNameNoLongerMatch(Session& session, std::optional<Range> range, const std::function<void(const pointer&)>& func);
|
static void findArtistNameNoLongerMatch(Session& session, std::optional<Range> range, const std::function<void(const pointer&)>& func);
|
||||||
static void findWithArtistNameAmbiguity(Session& session, std::optional<Range> range, bool allowArtistMBIDFallback, const std::function<void(const pointer&)>& func);
|
static void findWithArtistNameAmbiguity(Session& session, std::optional<Range> range, bool allowArtistMBIDFallback, const std::function<void(const pointer&)>& func);
|
||||||
@@ -109,6 +107,7 @@ namespace lms::db
|
|||||||
// accessors
|
// accessors
|
||||||
ObjectPtr<Track> getTrack() const { return _track; }
|
ObjectPtr<Track> getTrack() const { return _track; }
|
||||||
ObjectPtr<Artist> getArtist() const { return _artist; }
|
ObjectPtr<Artist> getArtist() const { return _artist; }
|
||||||
|
ArtistId getArtistId() const { return _artist.id(); }
|
||||||
TrackArtistLinkType getType() const { return _type; }
|
TrackArtistLinkType getType() const { return _type; }
|
||||||
std::string_view getSubType() const { return _subType; }
|
std::string_view getSubType() const { return _subType; }
|
||||||
std::string_view getArtistName() const { return _artistName; }
|
std::string_view getArtistName() const { return _artistName; }
|
||||||
@@ -134,6 +133,10 @@ namespace lms::db
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
friend class Session;
|
||||||
|
static pointer create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, std::string_view subType, bool artistMBIDMatched = false);
|
||||||
|
static pointer create(Session& session, const ObjectPtr<Track>& track, const ObjectPtr<Artist>& artist, TrackArtistLinkType type, bool artistMBIDMatched = false);
|
||||||
|
|
||||||
TrackArtistLinkType _type{ TrackArtistLinkType::Artist };
|
TrackArtistLinkType _type{ TrackArtistLinkType::Artist };
|
||||||
std::string _subType;
|
std::string _subType;
|
||||||
std::string _artistName; // as it was in the tags
|
std::string _artistName; // as it was in the tags
|
||||||
|
|||||||
@@ -86,6 +86,12 @@ namespace lms::db
|
|||||||
PubDateDesc,
|
PubDateDesc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class ReleaseArtistLinkSortMethod
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
OriginalDateDesc,
|
||||||
|
};
|
||||||
|
|
||||||
enum class ReleaseSortMethod
|
enum class ReleaseSortMethod
|
||||||
{
|
{
|
||||||
None,
|
None,
|
||||||
@@ -165,7 +171,7 @@ namespace lms::db
|
|||||||
Mixer = 5,
|
Mixer = 5,
|
||||||
Performer = 6,
|
Performer = 6,
|
||||||
Producer = 7,
|
Producer = 7,
|
||||||
ReleaseArtist = 8,
|
// ReleaseArtist = 8,
|
||||||
Remixer = 9,
|
Remixer = 9,
|
||||||
Writer = 10,
|
Writer = 10,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
#include "database/objects/Artwork.hpp"
|
#include "database/objects/Artwork.hpp"
|
||||||
#include "database/objects/Image.hpp"
|
#include "database/objects/Image.hpp"
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
|
|
||||||
namespace lms::db::tests
|
namespace lms::db::tests
|
||||||
{
|
{
|
||||||
@@ -98,10 +99,10 @@ namespace lms::db::tests
|
|||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
track2a.get().modify()->setMediaLibrary(library.get());
|
track2a.get().modify()->setMediaLibrary(library.get());
|
||||||
track2b.get().modify()->setMediaLibrary(library.get());
|
track2b.get().modify()->setMediaLibrary(library.get());
|
||||||
TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, track2a.get(), artist2.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track2a.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, track2b.get(), artist2.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track2b.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, track3.get(), artist3.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track3.get(), artist3.get(), TrackArtistLinkType::Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -243,7 +244,7 @@ namespace lms::db::tests
|
|||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
track.get().modify()->setName("MyTrackName");
|
track.get().modify()->setName("MyTrackName");
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -258,13 +259,39 @@ namespace lms::db::tests
|
|||||||
ASSERT_EQ(artists.size(), 1);
|
ASSERT_EQ(artists.size(), 1);
|
||||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||||
|
|
||||||
|
{
|
||||||
|
bool visited{};
|
||||||
|
track->visitArtistLinks([&](const db::TrackArtistLink::pointer& artistLink) {
|
||||||
|
visited = true;
|
||||||
|
EXPECT_EQ(artistLink->getArtistId(), artist.getId());
|
||||||
|
});
|
||||||
|
EXPECT_TRUE(visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
bool visited{};
|
||||||
|
track->visitArtistLinks(TrackArtistLinkType::Artist, [&](const db::TrackArtistLink::pointer& artistLink) {
|
||||||
|
visited = true;
|
||||||
|
EXPECT_EQ(artistLink->getArtistId(), artist.getId());
|
||||||
|
});
|
||||||
|
EXPECT_TRUE(visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
bool visited{};
|
||||||
|
track->visitArtistLinks(TrackArtistLinkType::Conductor, [&](const db::TrackArtistLink::pointer&) {
|
||||||
|
visited = true;
|
||||||
|
});
|
||||||
|
EXPECT_FALSE(visited);
|
||||||
|
}
|
||||||
|
|
||||||
ASSERT_EQ(track->getArtistLinks().size(), 1);
|
ASSERT_EQ(track->getArtistLinks().size(), 1);
|
||||||
auto artistLink{ track->getArtistLinks().front() };
|
auto artistLink{ track->getArtistLinks().front() };
|
||||||
EXPECT_EQ(artistLink->getTrack()->getId(), track.getId());
|
EXPECT_EQ(artistLink->getTrack()->getId(), track.getId());
|
||||||
EXPECT_EQ(artistLink->getArtist()->getId(), artist.getId());
|
EXPECT_EQ(artistLink->getArtist()->getId(), artist.getId());
|
||||||
|
|
||||||
ASSERT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 1);
|
ASSERT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 1);
|
||||||
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).size(), 0);
|
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Composer }).size(), 0);
|
||||||
EXPECT_EQ(track->getArtists({}).size(), 1);
|
EXPECT_EQ(track->getArtists({}).size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +303,7 @@ namespace lms::db::tests
|
|||||||
EXPECT_EQ(artists.front(), artist.getId());
|
EXPECT_EQ(artists.front(), artist.getId());
|
||||||
|
|
||||||
ASSERT_EQ(track->getArtistIds({ TrackArtistLinkType::Artist }).size(), 1);
|
ASSERT_EQ(track->getArtistIds({ TrackArtistLinkType::Artist }).size(), 1);
|
||||||
EXPECT_EQ(track->getArtistIds({ TrackArtistLinkType::ReleaseArtist }).size(), 0);
|
EXPECT_EQ(track->getArtistIds({ TrackArtistLinkType::Composer }).size(), 0);
|
||||||
EXPECT_EQ(track->getArtistIds({}).size(), 1);
|
EXPECT_EQ(track->getArtistIds({}).size(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +329,32 @@ namespace lms::db::tests
|
|||||||
ASSERT_EQ(artists.results.size(), 1);
|
ASSERT_EQ(artists.results.size(), 1);
|
||||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).results.size(), 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DatabaseFixture, Artist_singleRelease)
|
||||||
|
{
|
||||||
|
ScopedRelease release{ session, "MyRelease" };
|
||||||
|
ScopedArtist artist{ session, "MyArtist" };
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
EXPECT_EQ(Artist::findOrphanIds(session).results, std::vector<db::ArtistId>{ artist.getId() });
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
session.create<ReleaseArtistLink>(release.get(), artist.get(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
EXPECT_EQ(Artist::findOrphanIds(session).results.size(), 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DatabaseFixture, Artist_singleTrack_mediaLibrary)
|
TEST_F(DatabaseFixture, Artist_singleTrack_mediaLibrary)
|
||||||
@@ -315,7 +368,7 @@ namespace lms::db::tests
|
|||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
track.get().modify()->setName("MyTrackName");
|
track.get().modify()->setName("MyTrackName");
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
track.get().modify()->setMediaLibrary(library.get());
|
track.get().modify()->setMediaLibrary(library.get());
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
@@ -344,9 +397,9 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Producer);
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Writer);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -357,10 +410,12 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}).results.size(), 1);
|
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}).results.size(), 1);
|
||||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Artist)).results.size(), 1);
|
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Artist)).results.size(), 1);
|
||||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::ReleaseArtist)).results.size(), 1);
|
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Producer)).results.size(), 1);
|
||||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Writer)).results.size(), 1);
|
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Writer)).results.size(), 1);
|
||||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setLinkType(TrackArtistLinkType::Composer)).results.size(), 0);
|
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setTrackArtistLinkType(TrackArtistLinkType::Composer)).results.size(), 0);
|
||||||
|
|
||||||
|
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).results.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -370,7 +425,7 @@ namespace lms::db::tests
|
|||||||
ASSERT_EQ(artists.size(), 1);
|
ASSERT_EQ(artists.size(), 1);
|
||||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||||
|
|
||||||
artists = track->getArtists({ TrackArtistLinkType::ReleaseArtist });
|
artists = track->getArtists({ TrackArtistLinkType::Producer });
|
||||||
ASSERT_EQ(artists.size(), 1);
|
ASSERT_EQ(artists.size(), 1);
|
||||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
||||||
|
|
||||||
@@ -379,7 +434,7 @@ namespace lms::db::tests
|
|||||||
auto tracks{ Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId())) };
|
auto tracks{ Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId())) };
|
||||||
EXPECT_EQ(tracks.results.size(), 1);
|
EXPECT_EQ(tracks.results.size(), 1);
|
||||||
|
|
||||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist }));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Producer }));
|
||||||
EXPECT_EQ(tracks.results.size(), 1);
|
EXPECT_EQ(tracks.results.size(), 1);
|
||||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist }));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist }));
|
||||||
EXPECT_EQ(tracks.results.size(), 1);
|
EXPECT_EQ(tracks.results.size(), 1);
|
||||||
@@ -393,7 +448,7 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
core::EnumSet<TrackArtistLinkType> types{ TrackArtistLink::findUsedTypes(session, artist.getId()) };
|
core::EnumSet<TrackArtistLinkType> types{ TrackArtistLink::findUsedTypes(session, artist.getId()) };
|
||||||
EXPECT_TRUE(types.contains(TrackArtistLinkType::ReleaseArtist));
|
EXPECT_TRUE(types.contains(TrackArtistLinkType::Producer));
|
||||||
EXPECT_TRUE(types.contains(TrackArtistLinkType::Artist));
|
EXPECT_TRUE(types.contains(TrackArtistLinkType::Artist));
|
||||||
EXPECT_TRUE(types.contains(TrackArtistLinkType::Writer));
|
EXPECT_TRUE(types.contains(TrackArtistLinkType::Writer));
|
||||||
EXPECT_FALSE(types.contains(TrackArtistLinkType::Composer));
|
EXPECT_FALSE(types.contains(TrackArtistLinkType::Composer));
|
||||||
@@ -416,7 +471,7 @@ namespace lms::db::tests
|
|||||||
};
|
};
|
||||||
|
|
||||||
EXPECT_TRUE(containsType(TrackArtistLinkType::Artist));
|
EXPECT_TRUE(containsType(TrackArtistLinkType::Artist));
|
||||||
EXPECT_TRUE(containsType(TrackArtistLinkType::ReleaseArtist));
|
EXPECT_TRUE(containsType(TrackArtistLinkType::Producer));
|
||||||
EXPECT_TRUE(containsType(TrackArtistLinkType::Writer));
|
EXPECT_TRUE(containsType(TrackArtistLinkType::Writer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -431,8 +486,8 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
TrackArtistLink::create(session, track.get(), artist1.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, track.get(), artist2.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -450,7 +505,7 @@ namespace lms::db::tests
|
|||||||
|
|
||||||
EXPECT_EQ(track->getArtists({}).size(), 2);
|
EXPECT_EQ(track->getArtists({}).size(), 2);
|
||||||
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 2);
|
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::Artist }).size(), 2);
|
||||||
EXPECT_EQ(track->getArtists({ TrackArtistLinkType::ReleaseArtist }).size(), 0);
|
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{}).results.size(), 2);
|
||||||
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::Random)).results.size(), 2);
|
EXPECT_EQ(Artist::findIds(session, Artist::FindParameters{}.setSortMethod(ArtistSortMethod::Random)).results.size(), 2);
|
||||||
}
|
}
|
||||||
@@ -466,13 +521,13 @@ namespace lms::db::tests
|
|||||||
ASSERT_EQ(tracks.results.size(), 1);
|
ASSERT_EQ(tracks.results.size(), 1);
|
||||||
EXPECT_EQ(tracks.results.front(), track->getId());
|
EXPECT_EQ(tracks.results.front(), track->getId());
|
||||||
|
|
||||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::ReleaseArtist }));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::Arranger }));
|
||||||
EXPECT_EQ(tracks.results.size(), 0);
|
EXPECT_EQ(tracks.results.size(), 0);
|
||||||
|
|
||||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::Artist }));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist1->getId(), { TrackArtistLinkType::Artist }));
|
||||||
EXPECT_EQ(tracks.results.size(), 1);
|
EXPECT_EQ(tracks.results.size(), 1);
|
||||||
|
|
||||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::ReleaseArtist }));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::Arranger }));
|
||||||
EXPECT_EQ(tracks.results.size(), 0);
|
EXPECT_EQ(tracks.results.size(), 0);
|
||||||
|
|
||||||
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::Artist }));
|
tracks = Track::findIds(session, Track::FindParameters{}.setArtist(artist2->getId(), { TrackArtistLinkType::Artist }));
|
||||||
@@ -518,6 +573,27 @@ namespace lms::db::tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(DatabaseFixture, Artist_findByReleaseArtistLinks)
|
||||||
|
{
|
||||||
|
ScopedRelease release{ session, "release" };
|
||||||
|
ScopedArtist artist{ session, "artist" };
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).results.size(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
session.create<ReleaseArtistLink>(release.get(), artist.get(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
EXPECT_EQ(Artist::find(session, Artist::FindParameters{}.setReleaseArtistsOnly(true)).results.size(), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(DatabaseFixture, Artist_findByCodec)
|
TEST_F(DatabaseFixture, Artist_findByCodec)
|
||||||
{
|
{
|
||||||
ScopedArtist artist1{ session, "A" };
|
ScopedArtist artist1{ session, "A" };
|
||||||
@@ -527,8 +603,8 @@ namespace lms::db::tests
|
|||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track2.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||||
|
|
||||||
track1.get().modify()->setCodec(core::media::Codec::MP3);
|
track1.get().modify()->setCodec(core::media::Codec::MP3);
|
||||||
track2.get().modify()->setCodec(core::media::Codec::FLAC);
|
track2.get().modify()->setCodec(core::media::Codec::FLAC);
|
||||||
@@ -554,7 +630,7 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
artist.get().modify()->setSortName("ZZZ");
|
artist.get().modify()->setSortName("ZZZ");
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -768,8 +844,8 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
TrackArtistLink::create(session, track1.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track1.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, track2.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track2.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
|
|
||||||
track1.get().modify()->setRelease(release.get());
|
track1.get().modify()->setRelease(release.get());
|
||||||
}
|
}
|
||||||
@@ -783,51 +859,6 @@ namespace lms::db::tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DatabaseFixture, Artist_findByRelease)
|
|
||||||
{
|
|
||||||
ScopedArtist artist{ session, "artist" };
|
|
||||||
ScopedTrack track1{ session };
|
|
||||||
ScopedTrack track2{ session };
|
|
||||||
ScopedRelease release{ session, "MyRelease" };
|
|
||||||
|
|
||||||
{
|
|
||||||
auto transaction{ session.createReadTransaction() };
|
|
||||||
const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRelease(release.getId())) };
|
|
||||||
EXPECT_EQ(artists.results.size(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto transaction{ session.createWriteTransaction() };
|
|
||||||
TrackArtistLink::create(session, track1.get(), artist.get(), TrackArtistLinkType::Artist);
|
|
||||||
TrackArtistLink::create(session, track2.get(), artist.get(), TrackArtistLinkType::Artist);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto transaction{ session.createReadTransaction() };
|
|
||||||
const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRelease(release.getId())) };
|
|
||||||
EXPECT_EQ(artists.results.size(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto transaction{ session.createWriteTransaction() };
|
|
||||||
track1.get().modify()->setRelease(release.get());
|
|
||||||
track2.get().modify()->setRelease(release.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto transaction{ session.createReadTransaction() };
|
|
||||||
const auto artists{ Artist::findIds(session, Artist::FindParameters{}.setRelease(release.getId())) };
|
|
||||||
ASSERT_EQ(artists.results.size(), 1);
|
|
||||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
auto transaction{ session.createReadTransaction() };
|
|
||||||
const std::size_t count{ Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId())) };
|
|
||||||
EXPECT_EQ(count, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(DatabaseFixture, Artist_artwork)
|
TEST_F(DatabaseFixture, Artist_artwork)
|
||||||
{
|
{
|
||||||
ScopedImage image{ session, "/image1.jpg" };
|
ScopedImage image{ session, "/image1.jpg" };
|
||||||
@@ -875,11 +906,11 @@ namespace lms::db::tests
|
|||||||
trackD1.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 3 } });
|
trackD1.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 3 } });
|
||||||
trackA2.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 4 } });
|
trackA2.get().modify()->setAddedTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 4 } });
|
||||||
|
|
||||||
TrackArtistLink::create(session, trackA1.get(), artistA.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(trackA1.get(), artistA.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, trackA2.get(), artistA.get(), TrackArtistLinkType::Producer);
|
session.create<TrackArtistLink>(trackA2.get(), artistA.get(), TrackArtistLinkType::Producer);
|
||||||
TrackArtistLink::create(session, trackB1.get(), artistB.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(trackB1.get(), artistB.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, trackC1.get(), artistC.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(trackC1.get(), artistC.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, trackD1.get(), artistD.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(trackD1.get(), artistD.get(), TrackArtistLinkType::Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -915,11 +946,11 @@ namespace lms::db::tests
|
|||||||
trackD1.get().modify()->setLastWriteTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 3 } });
|
trackD1.get().modify()->setLastWriteTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 3 } });
|
||||||
trackA2.get().modify()->setLastWriteTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 4 } });
|
trackA2.get().modify()->setLastWriteTime(Wt::WDateTime{ Wt::WDate{ 2021, 1, 4 } });
|
||||||
|
|
||||||
TrackArtistLink::create(session, trackA1.get(), artistA.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(trackA1.get(), artistA.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, trackA2.get(), artistA.get(), TrackArtistLinkType::Producer);
|
session.create<TrackArtistLink>(trackA2.get(), artistA.get(), TrackArtistLinkType::Producer);
|
||||||
TrackArtistLink::create(session, trackB1.get(), artistB.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(trackB1.get(), artistB.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, trackC1.get(), artistC.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(trackC1.get(), artistC.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, trackD1.get(), artistD.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(trackD1.get(), artistD.get(), TrackArtistLinkType::Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -976,16 +1007,16 @@ namespace lms::db::tests
|
|||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
{
|
{
|
||||||
auto link{ TrackArtistLink::create(session, trackA1.get(), artistA.get(), TrackArtistLinkType::Artist, true) };
|
auto link{ session.create<TrackArtistLink>(trackA1.get(), artistA.get(), TrackArtistLinkType::Artist, true) };
|
||||||
link.modify()->setArtistName("ArtistA");
|
link.modify()->setArtistName("ArtistA");
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
auto link{ TrackArtistLink::create(session, trackA2.get(), artistA.get(), TrackArtistLinkType::Artist, true) };
|
auto link{ session.create<TrackArtistLink>(trackA2.get(), artistA.get(), TrackArtistLinkType::Artist, true) };
|
||||||
link.modify()->setArtistName("AlternateArtistA");
|
link.modify()->setArtistName("AlternateArtistA");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto link{ TrackArtistLink::create(session, trackB1.get(), artistB.get(), TrackArtistLinkType::Artist, true) };
|
auto link{ session.create<TrackArtistLink>(trackB1.get(), artistB.get(), TrackArtistLinkType::Artist, true) };
|
||||||
link.modify()->setArtistName("ArtistB");
|
link.modify()->setArtistName("ArtistB");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ add_executable(test-database
|
|||||||
RatedRelease.cpp
|
RatedRelease.cpp
|
||||||
RatedTrack.cpp
|
RatedTrack.cpp
|
||||||
Release.cpp
|
Release.cpp
|
||||||
|
ReleaseArtistLink.cpp
|
||||||
ScanSettings.cpp
|
ScanSettings.cpp
|
||||||
StarredArtist.cpp
|
StarredArtist.cpp
|
||||||
StarredRelease.cpp
|
StarredRelease.cpp
|
||||||
|
|||||||
@@ -445,7 +445,7 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) };
|
auto trackArtistLink{ session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist) };
|
||||||
cluster1.get().modify()->addTrack(track.get());
|
cluster1.get().modify()->addTrack(track.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,8 +505,8 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Mixer);
|
||||||
cluster.get().modify()->addTrack(track.get());
|
cluster.get().modify()->addTrack(track.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,7 +544,7 @@ namespace lms::db::tests
|
|||||||
tracks.emplace_back(session);
|
tracks.emplace_back(session);
|
||||||
|
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(tracks.back().get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
|
|
||||||
for (auto& cluster : clusters)
|
for (auto& cluster : clusters)
|
||||||
cluster.get().modify()->addTrack(tracks.back().get());
|
cluster.get().modify()->addTrack(tracks.back().get());
|
||||||
@@ -654,7 +654,7 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
track.get().modify()->setRelease(release.get());
|
track.get().modify()->setRelease(release.get());
|
||||||
cluster.get().modify()->addTrack(track.get());
|
cluster.get().modify()->addTrack(track.get());
|
||||||
}
|
}
|
||||||
@@ -675,11 +675,11 @@ namespace lms::db::tests
|
|||||||
ASSERT_EQ(artists.results.size(), 1);
|
ASSERT_EQ(artists.results.size(), 1);
|
||||||
EXPECT_EQ(artists.results.front(), artist.getId());
|
EXPECT_EQ(artists.results.front(), artist.getId());
|
||||||
|
|
||||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())) };
|
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId())) };
|
||||||
ASSERT_EQ(releases.results.size(), 1);
|
ASSERT_EQ(releases.results.size(), 1);
|
||||||
EXPECT_EQ(releases.results.front(), release.getId());
|
EXPECT_EQ(releases.results.front(), release.getId());
|
||||||
|
|
||||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster.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);
|
ASSERT_EQ(releases.results.size(), 1);
|
||||||
EXPECT_EQ(releases.results.front(), release.getId());
|
EXPECT_EQ(releases.results.front(), release.getId());
|
||||||
}
|
}
|
||||||
@@ -697,7 +697,7 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) };
|
auto trackArtistLink{ session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist) };
|
||||||
track.get().modify()->setRelease(release.get());
|
track.get().modify()->setRelease(release.get());
|
||||||
cluster1.get().modify()->addTrack(track.get());
|
cluster1.get().modify()->addTrack(track.get());
|
||||||
cluster2.get().modify()->addTrack(track.get());
|
cluster2.get().modify()->addTrack(track.get());
|
||||||
@@ -706,11 +706,11 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())) };
|
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId())) };
|
||||||
ASSERT_EQ(releases.results.size(), 1);
|
ASSERT_EQ(releases.results.size(), 1);
|
||||||
EXPECT_EQ(releases.results.front(), release.getId());
|
EXPECT_EQ(releases.results.front(), release.getId());
|
||||||
|
|
||||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()).setFilters(Filters{}.setClusters(std::initializer_list<ClusterId>{ cluster1.getId(), cluster2.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);
|
ASSERT_EQ(releases.results.size(), 1);
|
||||||
EXPECT_EQ(releases.results.front(), release.getId());
|
EXPECT_EQ(releases.results.front(), release.getId());
|
||||||
}
|
}
|
||||||
@@ -822,10 +822,10 @@ namespace lms::db::tests
|
|||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
if (i < 5)
|
if (i < 5)
|
||||||
TrackArtistLink::create(session, tracks.back().get(), artist1.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(tracks.back().get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
TrackArtistLink::create(session, tracks.back().get(), artist2.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(tracks.back().get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||||
cluster2.get().modify()->addTrack(tracks.back().get());
|
cluster2.get().modify()->addTrack(tracks.back().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -835,7 +835,7 @@ namespace lms::db::tests
|
|||||||
tracks.emplace_back(session);
|
tracks.emplace_back(session);
|
||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
TrackArtistLink::create(session, tracks.back().get(), artist3.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(tracks.back().get(), artist3.get(), TrackArtistLinkType::Artist);
|
||||||
cluster2.get().modify()->addTrack(tracks.back().get());
|
cluster2.get().modify()->addTrack(tracks.back().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -855,12 +855,12 @@ namespace lms::db::tests
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto artists{ artist1->findSimilarArtistIds({ TrackArtistLinkType::ReleaseArtist }) };
|
auto artists{ artist1->findSimilarArtistIds({ TrackArtistLinkType::Lyricist }) };
|
||||||
EXPECT_EQ(artists.results.size(), 0);
|
EXPECT_EQ(artists.results.size(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto artists{ artist1->findSimilarArtistIds({ TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }) };
|
auto artists{ artist1->findSimilarArtistIds({ TrackArtistLinkType::Artist, TrackArtistLinkType::Lyricist }) };
|
||||||
ASSERT_EQ(artists.results.size(), 1);
|
ASSERT_EQ(artists.results.size(), 1);
|
||||||
EXPECT_EQ(artists.results.front(), artist2.getId());
|
EXPECT_EQ(artists.results.front(), artist2.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ namespace lms::db::tests
|
|||||||
|
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
TrackArtistLink::create(session, tracks.back().get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(tracks.back().get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
tracks.back().get().modify()->setRelease(release.get());
|
tracks.back().get().modify()->setRelease(release.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())) };
|
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId())) };
|
||||||
ASSERT_EQ(releases.results.size(), 1);
|
ASSERT_EQ(releases.results.size(), 1);
|
||||||
EXPECT_EQ(releases.results.front(), release.getId());
|
EXPECT_EQ(releases.results.front(), release.getId());
|
||||||
|
|
||||||
@@ -67,18 +67,18 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
auto trackArtistLink{ TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist) };
|
auto trackArtistLink{ session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist) };
|
||||||
track.get().modify()->setRelease(release.get());
|
track.get().modify()->setRelease(release.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId())) };
|
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId())) };
|
||||||
ASSERT_EQ(releases.results.size(), 1);
|
ASSERT_EQ(releases.results.size(), 1);
|
||||||
EXPECT_EQ(releases.results.front(), release.getId());
|
EXPECT_EQ(releases.results.front(), release.getId());
|
||||||
|
|
||||||
auto artists{ release->getArtists() };
|
auto artists{ release->getTrackArtists() };
|
||||||
ASSERT_EQ(artists.size(), 1);
|
ASSERT_EQ(artists.size(), 1);
|
||||||
ASSERT_EQ(artists.front()->getId(), artist.getId());
|
ASSERT_EQ(artists.front()->getId(), artist.getId());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "database/objects/Listen.hpp"
|
#include "database/objects/Listen.hpp"
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
|
|
||||||
#include "Common.hpp"
|
#include "Common.hpp"
|
||||||
|
|
||||||
@@ -170,7 +171,7 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
TrackArtistLink::create(session, track2.get(), artist1.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track2.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -200,7 +201,7 @@ namespace lms::db::tests
|
|||||||
Listen::ArtistStatsFindParameters params;
|
Listen::ArtistStatsFindParameters params;
|
||||||
params.setUser(user->getId());
|
params.setUser(user->getId());
|
||||||
params.setScrobblingBackend(ScrobblingBackend::ListenBrainz);
|
params.setScrobblingBackend(ScrobblingBackend::ListenBrainz);
|
||||||
params.setLinkType(TrackArtistLinkType::Producer);
|
params.setTrackArtistLinkType(TrackArtistLinkType::Producer);
|
||||||
|
|
||||||
auto artists{ Listen::getTopArtists(session, params) };
|
auto artists{ Listen::getTopArtists(session, params) };
|
||||||
EXPECT_EQ(artists.results.size(), 0);
|
EXPECT_EQ(artists.results.size(), 0);
|
||||||
@@ -235,8 +236,8 @@ namespace lms::db::tests
|
|||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track2.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -304,7 +305,7 @@ namespace lms::db::tests
|
|||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -362,7 +363,7 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
track.get().modify()->setMediaLibrary(library.get());
|
track.get().modify()->setMediaLibrary(library.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,7 +696,7 @@ namespace lms::db::tests
|
|||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -859,7 +860,50 @@ namespace lms::db::tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DatabaseFixture, Listen_getRecentArtists)
|
TEST_F(DatabaseFixture, Listen_getRecentReleaseArtists)
|
||||||
|
{
|
||||||
|
ScopedTrack track{ session };
|
||||||
|
ScopedUser user{ session, "MyUser" };
|
||||||
|
ScopedArtist artist{ session, "MyArtist" };
|
||||||
|
ScopedRelease release{ session, "MyRelease" };
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
|
session.create<ReleaseArtistLink>(release.get(), artist.get(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
|
Listen::ArtistStatsFindParameters params;
|
||||||
|
params.setUser(user->getId());
|
||||||
|
params.setScrobblingBackend(ScrobblingBackend::Internal);
|
||||||
|
params.setReleaseArtistsOnly(true);
|
||||||
|
|
||||||
|
auto artists{ Listen::getRecentArtists(session, params) };
|
||||||
|
EXPECT_EQ(artists.results.size(), 0);
|
||||||
|
EXPECT_EQ(artists.moreResults, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wt::WDateTime dateTime{ Wt::WDate{ 2000, 1, 2 }, Wt::WTime{ 12, 0, 1 } };
|
||||||
|
ScopedListen listen1{ session, user.lockAndGet(), track.lockAndGet(), ScrobblingBackend::Internal, dateTime };
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
|
Listen::ArtistStatsFindParameters params;
|
||||||
|
params.setUser(user->getId());
|
||||||
|
params.setScrobblingBackend(ScrobblingBackend::Internal);
|
||||||
|
params.setReleaseArtistsOnly(true);
|
||||||
|
|
||||||
|
auto artists{ Listen::getRecentArtists(session, params) };
|
||||||
|
ASSERT_EQ(artists.results.size(), 1);
|
||||||
|
EXPECT_EQ(artists.results[0], artist->getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DatabaseFixture, Listen_getRecentTrackArtists)
|
||||||
{
|
{
|
||||||
ScopedTrack track{ session };
|
ScopedTrack track{ session };
|
||||||
ScopedUser user{ session, "MyUser" };
|
ScopedUser user{ session, "MyUser" };
|
||||||
@@ -867,7 +911,7 @@ namespace lms::db::tests
|
|||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -896,6 +940,20 @@ namespace lms::db::tests
|
|||||||
ASSERT_EQ(artists.results.size(), 1);
|
ASSERT_EQ(artists.results.size(), 1);
|
||||||
EXPECT_EQ(artists.results[0], artist->getId());
|
EXPECT_EQ(artists.results[0], artist->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
|
Listen::ArtistStatsFindParameters params;
|
||||||
|
params.setUser(user->getId());
|
||||||
|
params.setScrobblingBackend(ScrobblingBackend::Internal);
|
||||||
|
params.setReleaseArtistsOnly(true);
|
||||||
|
|
||||||
|
auto artists{ Listen::getRecentArtists(session, params) };
|
||||||
|
EXPECT_EQ(artists.results.size(), 0);
|
||||||
|
EXPECT_EQ(artists.moreResults, false);
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
@@ -912,7 +970,7 @@ namespace lms::db::tests
|
|||||||
Listen::ArtistStatsFindParameters params;
|
Listen::ArtistStatsFindParameters params;
|
||||||
params.setUser(user->getId());
|
params.setUser(user->getId());
|
||||||
params.setScrobblingBackend(ScrobblingBackend::Internal);
|
params.setScrobblingBackend(ScrobblingBackend::Internal);
|
||||||
params.setLinkType(TrackArtistLinkType::Producer);
|
params.setTrackArtistLinkType(TrackArtistLinkType::Producer);
|
||||||
|
|
||||||
auto artists{ Listen::getRecentArtists(session, params) };
|
auto artists{ Listen::getRecentArtists(session, params) };
|
||||||
EXPECT_EQ(artists.results.size(), 0);
|
EXPECT_EQ(artists.results.size(), 0);
|
||||||
@@ -935,7 +993,7 @@ namespace lms::db::tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DatabaseFixture, Listen_getRecentArtists_multi)
|
TEST_F(DatabaseFixture, Listen_getRecentTrackArtists_multi)
|
||||||
{
|
{
|
||||||
ScopedUser user{ session, "MyUser" };
|
ScopedUser user{ session, "MyUser" };
|
||||||
ScopedTrack track1{ session };
|
ScopedTrack track1{ session };
|
||||||
@@ -946,8 +1004,8 @@ namespace lms::db::tests
|
|||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
TrackArtistLink::create(session, track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track1.get(), artist1.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, track2.get(), artist2.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track2.get(), artist2.get(), TrackArtistLinkType::Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -1014,7 +1072,7 @@ namespace lms::db::tests
|
|||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -1056,7 +1114,7 @@ namespace lms::db::tests
|
|||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
track.get().modify()->setMediaLibrary(library.get());
|
track.get().modify()->setMediaLibrary(library.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
#include "database/objects/RatedArtist.hpp"
|
#include "database/objects/RatedArtist.hpp"
|
||||||
#include "database/objects/RatedRelease.hpp"
|
#include "database/objects/RatedRelease.hpp"
|
||||||
#include "database/objects/RatedTrack.hpp"
|
#include "database/objects/RatedTrack.hpp"
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
#include "database/objects/ScanSettings.hpp"
|
#include "database/objects/ScanSettings.hpp"
|
||||||
#include "database/objects/StarredArtist.hpp"
|
#include "database/objects/StarredArtist.hpp"
|
||||||
#include "database/objects/StarredRelease.hpp"
|
#include "database/objects/StarredRelease.hpp"
|
||||||
@@ -368,6 +369,7 @@ VALUES
|
|||||||
EXPECT_FALSE(RatedRelease::find(session, RatedReleaseId{}));
|
EXPECT_FALSE(RatedRelease::find(session, RatedReleaseId{}));
|
||||||
EXPECT_FALSE(RatedTrack::find(session, RatedTrackId{}));
|
EXPECT_FALSE(RatedTrack::find(session, RatedTrackId{}));
|
||||||
EXPECT_FALSE(Release::find(session, ReleaseId{}));
|
EXPECT_FALSE(Release::find(session, ReleaseId{}));
|
||||||
|
EXPECT_FALSE(ReleaseArtistLink::find(session, ReleaseArtistLinkId{}));
|
||||||
EXPECT_FALSE(ReleaseType::find(session, ReleaseTypeId{}));
|
EXPECT_FALSE(ReleaseType::find(session, ReleaseTypeId{}));
|
||||||
EXPECT_FALSE(ScanSettings::find(session, ScanSettingsId{}));
|
EXPECT_FALSE(ScanSettings::find(session, ScanSettingsId{}));
|
||||||
EXPECT_FALSE(StarredArtist::find(session, StarredArtistId{}));
|
EXPECT_FALSE(StarredArtist::find(session, StarredArtistId{}));
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
#include "database/objects/Artwork.hpp"
|
#include "database/objects/Artwork.hpp"
|
||||||
#include "database/objects/Image.hpp"
|
#include "database/objects/Image.hpp"
|
||||||
#include "database/objects/Medium.hpp"
|
#include "database/objects/Medium.hpp"
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
|
#include "database/objects/Types.hpp"
|
||||||
|
|
||||||
namespace lms::db::tests
|
namespace lms::db::tests
|
||||||
{
|
{
|
||||||
@@ -689,7 +691,7 @@ namespace lms::db::tests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DatabaseFixture, Release_artist)
|
TEST_F(DatabaseFixture, Release_trackArtist)
|
||||||
{
|
{
|
||||||
ScopedRelease release{ session, "MyRelease" };
|
ScopedRelease release{ session, "MyRelease" };
|
||||||
ScopedTrack track{ session };
|
ScopedTrack track{ session };
|
||||||
@@ -703,21 +705,21 @@ namespace lms::db::tests
|
|||||||
{
|
{
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })) };
|
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Artist })) };
|
||||||
EXPECT_EQ(releases.results.size(), 0);
|
EXPECT_EQ(releases.results.size(), 0);
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })), 0);
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Artist })), 0);
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId())), 0);
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist.getId())), 0);
|
||||||
|
|
||||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId(), { TrackArtistLinkType::Artist }));
|
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist2.getId(), { TrackArtistLinkType::Artist }));
|
||||||
EXPECT_EQ(releases.results.size(), 0);
|
EXPECT_EQ(releases.results.size(), 0);
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist2.getId(), { TrackArtistLinkType::Artist })), 0);
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist2.getId(), { TrackArtistLinkType::Artist })), 0);
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist2.getId())), 0);
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist2.getId())), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Artist);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::Producer);
|
session.create<TrackArtistLink>(track.get(), artist.get(), TrackArtistLinkType::Producer);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -725,83 +727,124 @@ namespace lms::db::tests
|
|||||||
|
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}), 1);
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}), 1);
|
||||||
|
|
||||||
auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })) };
|
auto releases{ Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Artist })) };
|
||||||
ASSERT_EQ(releases.results.size(), 1);
|
ASSERT_EQ(releases.results.size(), 1);
|
||||||
EXPECT_EQ(releases.results.front(), release.getId());
|
EXPECT_EQ(releases.results.front(), release.getId());
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist })), 1);
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Artist })), 1);
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Remixer })), 0);
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Remixer })), 0);
|
||||||
|
|
||||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist, TrackArtistLinkType::Mixer }));
|
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Artist, TrackArtistLinkType::Mixer }));
|
||||||
EXPECT_EQ(releases.results.size(), 1);
|
EXPECT_EQ(releases.results.size(), 1);
|
||||||
EXPECT_EQ(releases.results.front(), release.getId());
|
EXPECT_EQ(releases.results.front(), release.getId());
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Artist, TrackArtistLinkType::Mixer })), 1);
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Artist, TrackArtistLinkType::Mixer })), 1);
|
||||||
|
|
||||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId(), { TrackArtistLinkType::Artist }));
|
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist2.getId(), { TrackArtistLinkType::Artist }));
|
||||||
EXPECT_EQ(releases.results.size(), 0);
|
EXPECT_EQ(releases.results.size(), 0);
|
||||||
|
|
||||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist2.getId()));
|
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist2.getId()));
|
||||||
EXPECT_EQ(releases.results.size(), 0);
|
EXPECT_EQ(releases.results.size(), 0);
|
||||||
|
|
||||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist, TrackArtistLinkType::Artist }));
|
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Writer, TrackArtistLinkType::Artist }));
|
||||||
ASSERT_EQ(releases.results.size(), 1);
|
ASSERT_EQ(releases.results.size(), 1);
|
||||||
EXPECT_EQ(releases.results.front(), release.getId());
|
EXPECT_EQ(releases.results.front(), release.getId());
|
||||||
|
|
||||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId()));
|
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId()));
|
||||||
ASSERT_EQ(releases.results.size(), 1);
|
ASSERT_EQ(releases.results.size(), 1);
|
||||||
EXPECT_EQ(releases.results.front(), release.getId());
|
EXPECT_EQ(releases.results.front(), release.getId());
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId())), 1);
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist.getId())), 1);
|
||||||
|
EXPECT_EQ(release->getTrackArtists().size(), 1);
|
||||||
|
EXPECT_EQ(release->getTrackArtists(TrackArtistLinkType::Artist).size(), 1);
|
||||||
|
EXPECT_EQ(release->getTrackArtists(TrackArtistLinkType::Conductor).size(), 0);
|
||||||
|
|
||||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer }));
|
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Composer }));
|
||||||
EXPECT_EQ(releases.results.size(), 0);
|
EXPECT_EQ(releases.results.size(), 0);
|
||||||
|
|
||||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::Composer, TrackArtistLinkType::Mixer }));
|
releases = Release::findIds(session, Release::FindParameters{}.setTrackArtist(artist.getId(), { TrackArtistLinkType::Composer, TrackArtistLinkType::Mixer }));
|
||||||
EXPECT_EQ(releases.results.size(), 0);
|
|
||||||
|
|
||||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), {}, { TrackArtistLinkType::Artist }));
|
|
||||||
EXPECT_EQ(releases.results.size(), 0);
|
|
||||||
|
|
||||||
releases = Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), {}, { TrackArtistLinkType::Artist, TrackArtistLinkType::Composer }));
|
|
||||||
EXPECT_EQ(releases.results.size(), 0);
|
EXPECT_EQ(releases.results.size(), 0);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
TEST_F(DatabaseFixture, Release_releaseArtist)
|
|
||||||
{
|
|
||||||
ScopedRelease release{ session, "MyRelease" };
|
|
||||||
ScopedTrack track{ session };
|
|
||||||
ScopedArtist artist{ session, "MyArtist" };
|
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
const auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist })) };
|
{
|
||||||
|
bool visited{};
|
||||||
|
release->visitTrackArtistLinks(TrackArtistLinkType::Artist, [&](const db::TrackArtistLink::pointer& link) {
|
||||||
|
visited = true;
|
||||||
|
EXPECT_EQ(link->getArtistId(), artist.getId());
|
||||||
|
});
|
||||||
|
EXPECT_TRUE(visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
bool visited{};
|
||||||
|
release->visitTrackArtistLinks(TrackArtistLinkType::Conductor, [&](const db::TrackArtistLink::pointer&) {
|
||||||
|
visited = true;
|
||||||
|
});
|
||||||
|
EXPECT_FALSE(visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(DatabaseFixture, Release_artist)
|
||||||
|
{
|
||||||
|
ScopedRelease release{ session, "MyRelease" };
|
||||||
|
ScopedArtist artist1{ session, "MyArtist" };
|
||||||
|
ScopedArtist artist2{ session, "MyArtist" };
|
||||||
|
ScopedArtist artist3{ session, "MyArtist" };
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
|
const auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist1.getId())) };
|
||||||
EXPECT_EQ(releases.results.size(), 0);
|
EXPECT_EQ(releases.results.size(), 0);
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist })), 0);
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist1.getId())), 0);
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId())), 0);
|
EXPECT_EQ(release->getTrackArtists(TrackArtistLinkType::Conductor).size(), 0);
|
||||||
EXPECT_EQ(release->getArtists(TrackArtistLinkType::ReleaseArtist).size(), 0);
|
EXPECT_EQ(release->getArtistLinks().size(), 0);
|
||||||
EXPECT_EQ(release->getArtistIds(TrackArtistLinkType::ReleaseArtist).size(), 0);
|
|
||||||
|
bool visited{};
|
||||||
|
release->visitArtistLinks([&](const db::ReleaseArtistLink::pointer&) {
|
||||||
|
visited = true;
|
||||||
|
});
|
||||||
|
EXPECT_FALSE(visited);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createWriteTransaction() };
|
auto transaction{ session.createWriteTransaction() };
|
||||||
track.get().modify()->setRelease(release.get());
|
auto link1{ session.create<ReleaseArtistLink>(release.get(), artist1.get(), false) };
|
||||||
TrackArtistLink::create(session, track.get(), artist.get(), TrackArtistLinkType::ReleaseArtist);
|
auto link2{ session.create<ReleaseArtistLink>(release.get(), artist2.get(), true) };
|
||||||
|
link2.modify()->setArtistName("MyArtistAlternateName");
|
||||||
|
link2.modify()->setArtistSortName("MyArtistAlternateSortName");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
auto artists{ release->getArtists(TrackArtistLinkType::ReleaseArtist) };
|
const auto links{ release->getArtistLinks() };
|
||||||
ASSERT_EQ(artists.size(), 1);
|
ASSERT_EQ(links.size(), 2);
|
||||||
EXPECT_EQ(artists.front()->getId(), artist.getId());
|
EXPECT_EQ(links[0]->getArtistId(), artist1.getId());
|
||||||
|
EXPECT_EQ(links[0]->getArtistName(), "");
|
||||||
|
EXPECT_EQ(links[0]->getArtistSortName(), "");
|
||||||
|
EXPECT_EQ(links[1]->getArtistId(), artist2.getId());
|
||||||
|
EXPECT_EQ(links[1]->getArtistName(), "MyArtistAlternateName");
|
||||||
|
EXPECT_EQ(links[1]->getArtistSortName(), "MyArtistAlternateSortName");
|
||||||
|
EXPECT_EQ(links[1]->isArtistMBIDMatched(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
auto artists{ release->getArtistIds(TrackArtistLinkType::ReleaseArtist) };
|
std::vector<db::ReleaseArtistLink::pointer> links;
|
||||||
ASSERT_EQ(artists.size(), 1);
|
release->visitArtistLinks([&](const db::ReleaseArtistLink::pointer& artistLink) {
|
||||||
EXPECT_EQ(artists.front(), artist.getId());
|
links.push_back(artistLink);
|
||||||
|
});
|
||||||
|
ASSERT_EQ(links.size(), 2);
|
||||||
|
EXPECT_EQ(links[0]->getArtistId(), artist1.getId());
|
||||||
|
EXPECT_EQ(links[0]->getArtistName(), "");
|
||||||
|
EXPECT_EQ(links[0]->getArtistSortName(), "");
|
||||||
|
EXPECT_EQ(links[1]->getArtistId(), artist2.getId());
|
||||||
|
EXPECT_EQ(links[1]->getArtistName(), "MyArtistAlternateName");
|
||||||
|
EXPECT_EQ(links[1]->getArtistSortName(), "MyArtistAlternateSortName");
|
||||||
|
EXPECT_EQ(links[1]->isArtistMBIDMatched(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -809,13 +852,31 @@ namespace lms::db::tests
|
|||||||
|
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}), 1);
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}), 1);
|
||||||
|
|
||||||
const auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist })) };
|
const auto releases{ Release::findIds(session, Release::FindParameters{}.setArtist(artist1.getId())) };
|
||||||
ASSERT_EQ(releases.results.size(), 1);
|
ASSERT_EQ(releases.results.size(), 1);
|
||||||
EXPECT_EQ(releases.results.front(), release.getId());
|
EXPECT_EQ(releases.results.front(), release.getId());
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId(), { TrackArtistLinkType::ReleaseArtist })), 1);
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist1.getId())), 0);
|
||||||
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist.getId())), 1);
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist1.getId())), 1);
|
||||||
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setTrackArtist(artist2.getId())), 0);
|
||||||
|
EXPECT_EQ(Release::getCount(session, Release::FindParameters{}.setArtist(artist2.getId())), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
|
const auto artists{ release->getArtists() };
|
||||||
|
EXPECT_EQ(artists.size(), 2);
|
||||||
|
EXPECT_EQ(artists[0]->getId(), artist1.getId());
|
||||||
|
EXPECT_EQ(artists[1]->getId(), artist2.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
EXPECT_TRUE(release->hasArtist(artist1.getId()));
|
||||||
|
EXPECT_FALSE(release->hasArtist(artist3.getId()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(DatabaseFixture, Release_isCompilation)
|
TEST_F(DatabaseFixture, Release_isCompilation)
|
||||||
{
|
{
|
||||||
ScopedRelease release{ session, "MyRelease" };
|
ScopedRelease release{ session, "MyRelease" };
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 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 "Common.hpp"
|
||||||
|
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
|
|
||||||
|
namespace lms::db::tests
|
||||||
|
{
|
||||||
|
TEST_F(DatabaseFixture, ReleaseArtistLink)
|
||||||
|
{
|
||||||
|
ScopedRelease release{ session, "MyRelease" };
|
||||||
|
ScopedTrack track{ session };
|
||||||
|
ScopedArtist artist{ session, "MyArtist" };
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
|
ReleaseArtistLink::FindParameters params;
|
||||||
|
params.setRelease(release.getId());
|
||||||
|
|
||||||
|
bool visited{};
|
||||||
|
ReleaseArtistLink::find(session, params, [&](const ReleaseArtistLink::pointer&) {
|
||||||
|
visited = true;
|
||||||
|
});
|
||||||
|
EXPECT_FALSE(visited);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createWriteTransaction() };
|
||||||
|
|
||||||
|
track.get().modify()->setRelease(release.get());
|
||||||
|
session.create<ReleaseArtistLink>(release.get(), artist.get(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto transaction{ session.createReadTransaction() };
|
||||||
|
|
||||||
|
ReleaseArtistLink::FindParameters params;
|
||||||
|
params.setRelease(release.getId());
|
||||||
|
params.setMBIDMatched(false);
|
||||||
|
|
||||||
|
bool visited{};
|
||||||
|
ReleaseArtistLink::find(session, params, [&](const ReleaseArtistLink::pointer& link) {
|
||||||
|
visited = true;
|
||||||
|
EXPECT_EQ(link->getArtistId(), artist.getId());
|
||||||
|
});
|
||||||
|
EXPECT_TRUE(visited);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace lms::db::tests
|
||||||
@@ -100,7 +100,7 @@ namespace lms::feedback
|
|||||||
searchParams.setFilters(params.filters);
|
searchParams.setFilters(params.filters);
|
||||||
searchParams.setStarringUser(params.user, *backend);
|
searchParams.setStarringUser(params.user, *backend);
|
||||||
searchParams.setKeywords(params.keywords);
|
searchParams.setKeywords(params.keywords);
|
||||||
searchParams.setLinkType(params.linkType);
|
searchParams.setTrackArtistLinkType(params.trackArtistLinkType);
|
||||||
searchParams.setSortMethod(params.sortMethod);
|
searchParams.setSortMethod(params.sortMethod);
|
||||||
searchParams.setRange(params.range);
|
searchParams.setRange(params.range);
|
||||||
|
|
||||||
|
|||||||
@@ -81,12 +81,18 @@ namespace lms::feedback
|
|||||||
// Artists
|
// Artists
|
||||||
struct ArtistFindParameters : public FindParameters
|
struct ArtistFindParameters : public FindParameters
|
||||||
{
|
{
|
||||||
std::optional<db::TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type
|
std::optional<db::TrackArtistLinkType> trackArtistLinkType; // if set, only artists that have produced at least one track with this link type
|
||||||
db::ArtistSortMethod sortMethod{ db::ArtistSortMethod::None };
|
db::ArtistSortMethod sortMethod{ db::ArtistSortMethod::None };
|
||||||
|
std::optional<bool> releaseArtistsOnly;
|
||||||
|
|
||||||
ArtistFindParameters& setLinkType(std::optional<db::TrackArtistLinkType> _linkType)
|
ArtistFindParameters& setReleaseArtistsOnly(std::optional<bool> _releaseArtistsOnly)
|
||||||
{
|
{
|
||||||
linkType = _linkType;
|
releaseArtistsOnly = _releaseArtistsOnly;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ArtistFindParameters& setTrackArtistLinkType(std::optional<db::TrackArtistLinkType> _trackArtistLinkType)
|
||||||
|
{
|
||||||
|
trackArtistLinkType = _trackArtistLinkType;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
ArtistFindParameters& setSortMethod(db::ArtistSortMethod _sortMethod)
|
ArtistFindParameters& setSortMethod(db::ArtistSortMethod _sortMethod)
|
||||||
|
|||||||
@@ -38,6 +38,7 @@
|
|||||||
#include "database/objects/MediaLibrary.hpp"
|
#include "database/objects/MediaLibrary.hpp"
|
||||||
#include "database/objects/Medium.hpp"
|
#include "database/objects/Medium.hpp"
|
||||||
#include "database/objects/Release.hpp"
|
#include "database/objects/Release.hpp"
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
#include "database/objects/Track.hpp"
|
#include "database/objects/Track.hpp"
|
||||||
#include "database/objects/TrackArtistLink.hpp"
|
#include "database/objects/TrackArtistLink.hpp"
|
||||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||||
@@ -80,6 +81,20 @@ namespace lms::scanner
|
|||||||
createTrackArtistLinks(session, track, linkType, noRole, artists, allowArtistMBIDFallback);
|
createTrackArtistLinks(session, track, linkType, noRole, artists, allowArtistMBIDFallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void createReleaseArtistLinks(db::Session& session, const db::Release::pointer& dbRelease, std::span<const Artist> artists, helpers::AllowFallbackOnMBIDEntry allowArtistMBIDFallback)
|
||||||
|
{
|
||||||
|
for (const Artist& artist : artists)
|
||||||
|
{
|
||||||
|
db::Artist::pointer dbArtist{ helpers::getOrCreateArtist(session, artist, allowArtistMBIDFallback) };
|
||||||
|
|
||||||
|
const bool matchedUsingMbid{ artist.mbid.has_value() && dbArtist->getMBID() == artist.mbid };
|
||||||
|
db::ReleaseArtistLink::pointer link{ session.create<db::ReleaseArtistLink>(dbRelease, dbArtist, matchedUsingMbid) };
|
||||||
|
link.modify()->setArtistName(artist.name);
|
||||||
|
if (artist.sortName)
|
||||||
|
link.modify()->setArtistSortName(*artist.sortName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
db::ReleaseType::pointer getOrCreateReleaseType(db::Session& session, std::string_view name)
|
db::ReleaseType::pointer getOrCreateReleaseType(db::Session& session, std::string_view name)
|
||||||
{
|
{
|
||||||
db::ReleaseType::pointer releaseType{ db::ReleaseType::find(session, name) };
|
db::ReleaseType::pointer releaseType{ db::ReleaseType::find(session, name) };
|
||||||
@@ -107,7 +122,46 @@ namespace lms::scanner
|
|||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateReleaseIfNeeded(db::Session& session, db::Release::pointer dbRelease, const Release& release)
|
bool needUpdateReleaseArtists(const db::Release::pointer& dbRelease, const Release& release)
|
||||||
|
{
|
||||||
|
const std::vector<db::ReleaseArtistLink::pointer> dbArtistLinks{ dbRelease->getArtistLinks() };
|
||||||
|
|
||||||
|
if (dbArtistLinks.size() != release.artists.size())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// Must be in same order
|
||||||
|
for (std::size_t i{}; i < dbArtistLinks.size(); ++i)
|
||||||
|
{
|
||||||
|
const db::ReleaseArtistLink::pointer& dbArtistLink{ dbArtistLinks[i] };
|
||||||
|
const Artist& artist{ release.artists[i] };
|
||||||
|
|
||||||
|
if (dbArtistLink->getArtistName() != artist.name)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (dbArtistLink->getArtistSortName() != artist.sortName)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!dbArtistLink->isArtistMBIDMatched() && artist.mbid)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (dbArtistLink->isArtistMBIDMatched() && !artist.mbid)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (artist.mbid)
|
||||||
|
{
|
||||||
|
const db::Artist::pointer dbArtist{ dbArtistLink->getArtist() };
|
||||||
|
if (!dbArtist)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (dbArtistLink->getArtist()->getMBID() != artist.mbid)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateReleaseIfNeeded(db::Session& session, db::Release::pointer dbRelease, const Release& release, helpers::AllowFallbackOnMBIDEntry allowFallback)
|
||||||
{
|
{
|
||||||
if (dbRelease->getName() != release.name)
|
if (dbRelease->getName() != release.name)
|
||||||
dbRelease.modify()->setName(release.name);
|
dbRelease.modify()->setName(release.name);
|
||||||
@@ -143,6 +197,11 @@ namespace lms::scanner
|
|||||||
for (std::string_view label : release.labels)
|
for (std::string_view label : release.labels)
|
||||||
dbRelease.modify()->addLabel(getOrCreateLabel(session, label));
|
dbRelease.modify()->addLabel(getOrCreateLabel(session, label));
|
||||||
}
|
}
|
||||||
|
if (needUpdateReleaseArtists(dbRelease, release))
|
||||||
|
{
|
||||||
|
dbRelease.modify()->clearArtistLinks();
|
||||||
|
createReleaseArtistLinks(session, dbRelease, release.artists, allowFallback);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compare release level info
|
// Compare release level info
|
||||||
@@ -157,7 +216,7 @@ namespace lms::scanner
|
|||||||
&& dbCandidateRelease->getBarcode() == release.barcode;
|
&& dbCandidateRelease->getBarcode() == release.barcode;
|
||||||
}
|
}
|
||||||
|
|
||||||
db::Release::pointer getOrCreateRelease(db::Session& session, const Release& release, const db::Directory::pointer& currentDirectory)
|
db::Release::pointer getOrCreateRelease(db::Session& session, const Release& release, const db::Directory::pointer& currentDirectory, helpers::AllowFallbackOnMBIDEntry allowFallback)
|
||||||
{
|
{
|
||||||
db::Release::pointer dbRelease;
|
db::Release::pointer dbRelease;
|
||||||
|
|
||||||
@@ -224,7 +283,7 @@ namespace lms::scanner
|
|||||||
if (!dbRelease)
|
if (!dbRelease)
|
||||||
dbRelease = session.create<db::Release>(release.name);
|
dbRelease = session.create<db::Release>(release.name);
|
||||||
|
|
||||||
updateReleaseIfNeeded(session, dbRelease, release);
|
updateReleaseIfNeeded(session, dbRelease, release, allowFallback);
|
||||||
return dbRelease;
|
return dbRelease;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -671,13 +730,24 @@ namespace lms::scanner
|
|||||||
db::Directory::pointer directory{ utils::getOrCreateDirectory(dbSession, getFilePath().parent_path(), mediaLibrary) };
|
db::Directory::pointer directory{ utils::getOrCreateDirectory(dbSession, getFilePath().parent_path(), mediaLibrary) };
|
||||||
track.modify()->setDirectory(directory);
|
track.modify()->setDirectory(directory);
|
||||||
|
|
||||||
track.modify()->clearArtistLinks();
|
|
||||||
|
|
||||||
const helpers::AllowFallbackOnMBIDEntry allowFallback{ getScannerSettings().allowArtistMBIDFallback };
|
const helpers::AllowFallbackOnMBIDEntry allowFallback{ getScannerSettings().allowArtistMBIDFallback };
|
||||||
createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Artist, _file->track.artists, allowFallback);
|
|
||||||
if (_file->track.medium && _file->track.medium->release)
|
|
||||||
createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::ReleaseArtist, _file->track.medium->release->artists, allowFallback);
|
|
||||||
|
|
||||||
|
// For now, alway tie a medium to a release, and a release mst have at least one medium, even if no disc number is set
|
||||||
|
if (_file->track.medium && _file->track.medium->release)
|
||||||
|
{
|
||||||
|
db::Release::pointer release{ getOrCreateRelease(dbSession, *_file->track.medium->release, directory, allowFallback) };
|
||||||
|
assert(release);
|
||||||
|
track.modify()->setRelease(release);
|
||||||
|
track.modify()->setMedium(getOrCreateMedium(dbSession, *_file->track.medium, release));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
track.modify()->setRelease({});
|
||||||
|
track.modify()->setMedium({});
|
||||||
|
}
|
||||||
|
|
||||||
|
track.modify()->clearArtistLinks();
|
||||||
|
createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Artist, _file->track.artists, allowFallback);
|
||||||
createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Conductor, _file->track.conductorArtists, allowFallback);
|
createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Conductor, _file->track.conductorArtists, allowFallback);
|
||||||
createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Composer, _file->track.composerArtists, allowFallback);
|
createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Composer, _file->track.composerArtists, allowFallback);
|
||||||
createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Lyricist, _file->track.lyricistArtists, allowFallback);
|
createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Lyricist, _file->track.lyricistArtists, allowFallback);
|
||||||
@@ -688,19 +758,6 @@ namespace lms::scanner
|
|||||||
for (const auto& [role, performers] : _file->track.performerArtists)
|
for (const auto& [role, performers] : _file->track.performerArtists)
|
||||||
createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Performer, role, performers, allowFallback);
|
createTrackArtistLinks(dbSession, track, db::TrackArtistLinkType::Performer, role, performers, allowFallback);
|
||||||
|
|
||||||
// For now, alway tie a medium to a release, and a release mst have at least one medium, even if no disc number is set
|
|
||||||
if (_file->track.medium && _file->track.medium->release)
|
|
||||||
{
|
|
||||||
db::Release::pointer release{ getOrCreateRelease(dbSession, *_file->track.medium->release, directory) };
|
|
||||||
assert(release);
|
|
||||||
track.modify()->setRelease(release);
|
|
||||||
track.modify()->setMedium(getOrCreateMedium(dbSession, *_file->track.medium, release));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
track.modify()->setRelease({});
|
|
||||||
track.modify()->setMedium({});
|
|
||||||
}
|
|
||||||
track.modify()->setClusters(getOrCreateClusters(dbSession, _file->track));
|
track.modify()->setClusters(getOrCreateClusters(dbSession, _file->track));
|
||||||
track.modify()->setName(title);
|
track.modify()->setName(title);
|
||||||
track.modify()->setTrackNumber(_file->track.position);
|
track.modify()->setTrackNumber(_file->track.position);
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/objects/Artist.hpp"
|
#include "database/objects/Artist.hpp"
|
||||||
#include "database/objects/ArtistInfo.hpp"
|
#include "database/objects/ArtistInfo.hpp"
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
#include "database/objects/Track.hpp"
|
#include "database/objects/Track.hpp"
|
||||||
#include "database/objects/TrackArtistLink.hpp"
|
#include "database/objects/TrackArtistLink.hpp"
|
||||||
#include "database/objects/TrackList.hpp"
|
#include "database/objects/TrackList.hpp"
|
||||||
@@ -75,23 +76,46 @@ namespace lms::scanner
|
|||||||
artistInfo.modify()->setArtist(newArtist);
|
artistInfo.modify()->setArtist(newArtist);
|
||||||
}
|
}
|
||||||
|
|
||||||
db::TrackArtistLink::pointer getMostRecentMBIDArtistLink(db::Session& session, db::ArtistId artistId, std::optional<db::TrackArtistLinkType> linkType = std::nullopt)
|
struct ArtistReference
|
||||||
{
|
{
|
||||||
|
std::string name;
|
||||||
|
std::string sortName;
|
||||||
|
};
|
||||||
|
std::optional<ArtistReference> getMostRecentReleaseArtistReference(db::Session& session, db::ArtistId artistId)
|
||||||
|
{
|
||||||
|
std::optional<ArtistReference> ref;
|
||||||
|
|
||||||
|
db::ReleaseArtistLink::FindParameters params;
|
||||||
|
params.setArtist(artistId);
|
||||||
|
params.setSortMethod(db::ReleaseArtistLinkSortMethod::OriginalDateDesc);
|
||||||
|
params.setMBIDMatched(true);
|
||||||
|
params.setRange(db::Range{ .offset = 0, .size = 1 });
|
||||||
|
|
||||||
|
db::ReleaseArtistLink::pointer foundLink;
|
||||||
|
db::ReleaseArtistLink::find(session, params, [&](const db::ReleaseArtistLink::pointer& link) {
|
||||||
|
ref = ArtistReference{ .name = std::string{ link->getArtistName() }, .sortName = std::string{ link->getArtistSortName() } };
|
||||||
|
});
|
||||||
|
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ArtistReference> getMostRecentTrackArtistReference(db::Session& session, db::ArtistId artistId)
|
||||||
|
{
|
||||||
|
std::optional<ArtistReference> ref;
|
||||||
|
|
||||||
db::TrackArtistLink::FindParameters params;
|
db::TrackArtistLink::FindParameters params;
|
||||||
params.setArtist(artistId);
|
params.setArtist(artistId);
|
||||||
params.setLinkType(linkType);
|
|
||||||
params.setSortMethod(db::TrackArtistLinkSortMethod::OriginalDateDesc);
|
params.setSortMethod(db::TrackArtistLinkSortMethod::OriginalDateDesc);
|
||||||
params.setMBIDMatched(true);
|
params.setMBIDMatched(true);
|
||||||
params.setRange(db::Range{ .offset = 0, .size = 1 });
|
params.setRange(db::Range{ .offset = 0, .size = 1 });
|
||||||
|
|
||||||
db::TrackArtistLink::pointer foundLink;
|
db::TrackArtistLink::pointer foundLink;
|
||||||
db::TrackArtistLink::find(session, params, [&](const db::TrackArtistLink::pointer& link) {
|
db::TrackArtistLink::find(session, params, [&](const db::TrackArtistLink::pointer& link) {
|
||||||
foundLink = link;
|
ref = ArtistReference{ .name = std::string{ link->getArtistName() }, .sortName = std::string{ link->getArtistSortName() } };
|
||||||
});
|
});
|
||||||
|
|
||||||
return foundLink;
|
return ref;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
bool ScanStepArtistReconciliation::needProcess([[maybe_unused]] const ScanContext& context) const
|
bool ScanStepArtistReconciliation::needProcess([[maybe_unused]] const ScanContext& context) const
|
||||||
@@ -173,24 +197,24 @@ namespace lms::scanner
|
|||||||
if (hasArtistInfo)
|
if (hasArtistInfo)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
db::TrackArtistLink::pointer artistMostRecentLink{ getMostRecentMBIDArtistLink(session, artist->getId(), db::TrackArtistLinkType::ReleaseArtist) };
|
std::optional<ArtistReference> mostRecentArtistRef{ getMostRecentReleaseArtistReference(session, artist->getId()) };
|
||||||
if (!artistMostRecentLink)
|
if (!mostRecentArtistRef)
|
||||||
artistMostRecentLink = getMostRecentMBIDArtistLink(session, artist->getId());
|
mostRecentArtistRef = getMostRecentTrackArtistReference(session, artist->getId());
|
||||||
|
|
||||||
if (!artistMostRecentLink)
|
if (!mostRecentArtistRef)
|
||||||
{
|
{
|
||||||
LMS_LOG(DBUPDATER, DEBUG, "Unable to fix name discrepancy for artist " << artist << ": no link found!");
|
LMS_LOG(DBUPDATER, DEBUG, "Unable to fix name discrepancy for artist " << artist << ": no link found!");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (artistMostRecentLink->getArtistName() != artist->getName())
|
if (mostRecentArtistRef->name != artist->getName())
|
||||||
{
|
{
|
||||||
ArtistToUpdate& artistToUpdate{ artistsToUpdate.emplace_back() };
|
ArtistToUpdate& artistToUpdate{ artistsToUpdate.emplace_back() };
|
||||||
artistToUpdate.artist = artist;
|
artistToUpdate.artist = artist;
|
||||||
artistToUpdate.newName = artistMostRecentLink->getArtistName();
|
artistToUpdate.newName = mostRecentArtistRef->name;
|
||||||
artistToUpdate.newSortName = artistMostRecentLink->getArtistSortName();
|
artistToUpdate.newSortName = mostRecentArtistRef->sortName;
|
||||||
|
|
||||||
LMS_LOG(DBUPDATER, DEBUG, "Updating artist " << artist << " name to '" << artistToUpdate.newName << "' using most recent '" << db::trackArtistLinkTypeToString(artistMostRecentLink->getType()) << "' link reference");
|
LMS_LOG(DBUPDATER, DEBUG, "Updating artist " << artist << " name to '" << artistToUpdate.newName << "' using most recent artist link reference");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ namespace lms::scanner
|
|||||||
|
|
||||||
std::set<std::filesystem::path> releasePaths;
|
std::set<std::filesystem::path> releasePaths;
|
||||||
db::Directory::FindParameters params;
|
db::Directory::FindParameters params;
|
||||||
params.setArtist(artistId, { db::TrackArtistLinkType::ReleaseArtist });
|
params.setReleaseArtist(artistId);
|
||||||
|
|
||||||
db::Directory::find(session, params, [&](const db::Directory::pointer& directory) {
|
db::Directory::find(session, params, [&](const db::Directory::pointer& directory) {
|
||||||
releasePaths.insert(directory->getAbsolutePath());
|
releasePaths.insert(directory->getAbsolutePath());
|
||||||
@@ -176,7 +176,7 @@ namespace lms::scanner
|
|||||||
db::Artwork::pointer artwork;
|
db::Artwork::pointer artwork;
|
||||||
|
|
||||||
db::Release::FindParameters params;
|
db::Release::FindParameters params;
|
||||||
params.setArtist(artist->getId(), { db::TrackArtistLinkType::ReleaseArtist });
|
params.setArtist(artist->getId());
|
||||||
params.setSortMethod(db::ReleaseSortMethod::OriginalDate);
|
params.setSortMethod(db::ReleaseSortMethod::OriginalDate);
|
||||||
|
|
||||||
db::Release::find(session, params, [&](const db::Release::pointer& release) {
|
db::Release::find(session, params, [&](const db::Release::pointer& release) {
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ namespace lms::scrobbling
|
|||||||
|
|
||||||
db::Listen::ArtistStatsFindParameters convertToListenFindParameters(const ScrobblingService::ArtistFindParameters& params)
|
db::Listen::ArtistStatsFindParameters convertToListenFindParameters(const ScrobblingService::ArtistFindParameters& params)
|
||||||
{
|
{
|
||||||
return db::Listen::ArtistStatsFindParameters{ convertToListenFindParameters(static_cast<const ScrobblingService::FindParameters&>(params)), params.linkType };
|
db::Listen::ArtistStatsFindParameters listenFindParams{ convertToListenFindParameters(static_cast<const ScrobblingService::FindParameters&>(params)), params.linkType, params.releaseArtistsOnly };
|
||||||
|
return listenFindParams;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@
|
|||||||
#include "database/objects/Listen.hpp"
|
#include "database/objects/Listen.hpp"
|
||||||
#include "database/objects/Release.hpp"
|
#include "database/objects/Release.hpp"
|
||||||
#include "database/objects/Track.hpp"
|
#include "database/objects/Track.hpp"
|
||||||
|
#include "database/objects/TrackArtistLink.hpp"
|
||||||
#include "database/objects/User.hpp"
|
#include "database/objects/User.hpp"
|
||||||
#include "services/scrobbling/Exception.hpp"
|
#include "services/scrobbling/Exception.hpp"
|
||||||
|
|
||||||
@@ -46,6 +47,27 @@ namespace lms::scrobbling::listenBrainz
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
struct Artist
|
||||||
|
{
|
||||||
|
std::string name;
|
||||||
|
std::optional<core::UUID> mbid;
|
||||||
|
};
|
||||||
|
std::vector<Artist> getTrackArtists(const db::Track::pointer& track)
|
||||||
|
{
|
||||||
|
std::vector<Artist> artists;
|
||||||
|
|
||||||
|
for (const db::TrackArtistLink::pointer& trackArtistLink : track->getArtistLinks(db::TrackArtistLinkType::Artist))
|
||||||
|
{
|
||||||
|
const auto trackArtist{ trackArtistLink->getArtist() };
|
||||||
|
if (!trackArtist)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
artists.emplace_back(Artist{ std::string{ trackArtistLink->getArtistName() }, trackArtist->getMBID() });
|
||||||
|
}
|
||||||
|
|
||||||
|
return artists;
|
||||||
|
}
|
||||||
|
|
||||||
std::optional<Wt::Json::Object> listenToJsonPayload(db::Session& session, const scrobbling::Listen& listen, const Wt::WDateTime& timePoint)
|
std::optional<Wt::Json::Object> listenToJsonPayload(db::Session& session, const scrobbling::Listen& listen, const Wt::WDateTime& timePoint)
|
||||||
{
|
{
|
||||||
auto transaction{ session.createReadTransaction() };
|
auto transaction{ session.createReadTransaction() };
|
||||||
@@ -54,10 +76,7 @@ namespace lms::scrobbling::listenBrainz
|
|||||||
if (!track)
|
if (!track)
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
|
||||||
auto artists{ track->getArtists({ db::TrackArtistLinkType::Artist }) };
|
const std::vector<Artist> artists{ getTrackArtists(track) };
|
||||||
if (artists.empty())
|
|
||||||
artists = track->getArtists({ db::TrackArtistLinkType::ReleaseArtist });
|
|
||||||
|
|
||||||
if (artists.empty())
|
if (artists.empty())
|
||||||
{
|
{
|
||||||
LOG(DEBUG, "Track cannot be scrobbled since it does not have any artist");
|
LOG(DEBUG, "Track cannot be scrobbled since it does not have any artist");
|
||||||
@@ -77,10 +96,10 @@ namespace lms::scrobbling::listenBrainz
|
|||||||
|
|
||||||
{
|
{
|
||||||
Wt::Json::Array artistMBIDs;
|
Wt::Json::Array artistMBIDs;
|
||||||
for (const db::Artist::pointer& artist : artists)
|
for (const Artist& artist : artists)
|
||||||
{
|
{
|
||||||
if (auto MBID{ artist->getMBID() })
|
if (artist.mbid)
|
||||||
artistMBIDs.push_back(Wt::Json::Value{ std::string{ MBID->getAsString() } });
|
artistMBIDs.push_back(Wt::Json::Value{ std::string{ artist.mbid->getAsString() } });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!artistMBIDs.empty())
|
if (!artistMBIDs.empty())
|
||||||
|
|||||||
@@ -97,8 +97,14 @@ namespace lms::scrobbling
|
|||||||
{
|
{
|
||||||
std::optional<db::TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type
|
std::optional<db::TrackArtistLinkType> linkType; // if set, only artists that have produced at least one track with this link type
|
||||||
db::ArtistSortMethod sortMethod{ db::ArtistSortMethod::None };
|
db::ArtistSortMethod sortMethod{ db::ArtistSortMethod::None };
|
||||||
|
bool releaseArtistsOnly;
|
||||||
|
|
||||||
ArtistFindParameters& setLinkType(std::optional<db::TrackArtistLinkType> _linkType)
|
ArtistFindParameters& setReleaseArtistsOnly(bool _releaseArtistsOnly)
|
||||||
|
{
|
||||||
|
releaseArtistsOnly = _releaseArtistsOnly;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ArtistFindParameters& setTrackArtistLinkType(std::optional<db::TrackArtistLinkType> _linkType)
|
||||||
{
|
{
|
||||||
linkType = _linkType;
|
linkType = _linkType;
|
||||||
return *this;
|
return *this;
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ namespace lms::api::subsonic
|
|||||||
{
|
{
|
||||||
// API says: "Returns a random collection of songs from the given artist and similar artists"
|
// API says: "Returns a random collection of songs from the given artist and similar artists"
|
||||||
const std::size_t similarArtistCount{ count / 5 };
|
const std::size_t similarArtistCount{ count / 5 };
|
||||||
std::vector<ArtistId> artistIds{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(artistId, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, similarArtistCount) };
|
std::vector<ArtistId> artistIds{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(artistId, { TrackArtistLinkType::Artist }, similarArtistCount) };
|
||||||
artistIds.push_back(artistId);
|
artistIds.push_back(artistId);
|
||||||
|
|
||||||
const std::size_t meanTrackCountPerArtist{ (count / artistIds.size()) + 1 };
|
const std::size_t meanTrackCountPerArtist{ (count / artistIds.size()) + 1 };
|
||||||
@@ -392,16 +392,16 @@ namespace lms::api::subsonic
|
|||||||
case SubsonicArtistListMode::AllArtists:
|
case SubsonicArtistListMode::AllArtists:
|
||||||
break;
|
break;
|
||||||
case SubsonicArtistListMode::ReleaseArtists:
|
case SubsonicArtistListMode::ReleaseArtists:
|
||||||
parameters.setLinkType(TrackArtistLinkType::ReleaseArtist);
|
parameters.setReleaseArtistsOnly(true);
|
||||||
break;
|
break;
|
||||||
case SubsonicArtistListMode::TrackArtists:
|
case SubsonicArtistListMode::TrackArtists:
|
||||||
parameters.setLinkType(TrackArtistLinkType::Artist);
|
parameters.setTrackArtistLinkType(TrackArtistLinkType::Artist);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
parameters.filters.setMediaLibrary(mediaLibrary);
|
parameters.filters.setMediaLibrary(mediaLibrary);
|
||||||
|
|
||||||
// This endpoint does not scale: make sort lived transactions in order not to block the whole application
|
// This endpoint does not scale: make short lived transactions in order not to block the whole application
|
||||||
|
|
||||||
// first pass: dispatch the artists by first letter
|
// first pass: dispatch the artists by first letter
|
||||||
LMS_LOG(API_SUBSONIC, DEBUG, "GetArtists: fetching all artists...");
|
LMS_LOG(API_SUBSONIC, DEBUG, "GetArtists: fetching all artists...");
|
||||||
@@ -419,12 +419,7 @@ namespace lms::api::subsonic
|
|||||||
{
|
{
|
||||||
std::string_view sortName{ artist->getSortName() };
|
std::string_view sortName{ artist->getSortName() };
|
||||||
|
|
||||||
char sortChar;
|
const char sortChar{ (sortName.empty() || !std::isalpha(sortName[0])) ? '#' : static_cast<char>(std::toupper(sortName[0])) };
|
||||||
if (sortName.empty() || !std::isalpha(sortName[0]))
|
|
||||||
sortChar = '#';
|
|
||||||
else
|
|
||||||
sortChar = std::toupper(sortName[0]);
|
|
||||||
|
|
||||||
artistsSortedByFirstChar[sortChar].push_back(artist->getId());
|
artistsSortedByFirstChar[sortChar].push_back(artist->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -465,9 +460,18 @@ namespace lms::api::subsonic
|
|||||||
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
Response response{ Response::createOkResponse(context.getServerProtocolVersion()) };
|
||||||
Response::Node artistNode{ createArtistNode(context, artist) };
|
Response::Node artistNode{ createArtistNode(context, artist) };
|
||||||
|
|
||||||
const auto releases{ Release::find(context.getDbSession(), Release::FindParameters{}.setArtist(artist->getId())) };
|
auto addRelease{ [&](const Release::pointer& release) {
|
||||||
for (const Release::pointer& release : releases.results)
|
|
||||||
artistNode.addArrayChild("album", createAlbumNode(context, release, true /* id3 */));
|
artistNode.addArrayChild("album", createAlbumNode(context, release, true /* id3 */));
|
||||||
|
} };
|
||||||
|
|
||||||
|
Release::find(context.getDbSession(), Release::FindParameters{}.setArtist(artist->getId()), [&](const db::Release::pointer& release) {
|
||||||
|
addRelease(release);
|
||||||
|
});
|
||||||
|
|
||||||
|
Release::find(context.getDbSession(), Release::FindParameters{}.setTrackArtist(artist->getId()), [&](const db::Release::pointer& release) {
|
||||||
|
if (!release->hasArtist(id))
|
||||||
|
addRelease(release);
|
||||||
|
});
|
||||||
|
|
||||||
response.addNode("artist", std::move(artistNode));
|
response.addNode("artist", std::move(artistNode));
|
||||||
|
|
||||||
@@ -561,7 +565,7 @@ namespace lms::api::subsonic
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto similarArtistsId{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(id, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, count) };
|
auto similarArtistsId{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(id, { TrackArtistLinkType::Artist }, count) };
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ context.getDbSession().createReadTransaction() };
|
auto transaction{ context.getDbSession().createReadTransaction() };
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include "database/objects/Directory.hpp"
|
#include "database/objects/Directory.hpp"
|
||||||
#include "database/objects/Medium.hpp"
|
#include "database/objects/Medium.hpp"
|
||||||
#include "database/objects/Release.hpp"
|
#include "database/objects/Release.hpp"
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
#include "database/objects/Track.hpp"
|
#include "database/objects/Track.hpp"
|
||||||
#include "database/objects/User.hpp"
|
#include "database/objects/User.hpp"
|
||||||
#include "services/feedback/IFeedbackService.hpp"
|
#include "services/feedback/IFeedbackService.hpp"
|
||||||
@@ -99,23 +100,38 @@ namespace lms::api::subsonic
|
|||||||
else if (const auto year{ release->getYear() })
|
else if (const auto year{ release->getYear() })
|
||||||
albumNode.setAttribute("year", *year);
|
albumNode.setAttribute("year", *year);
|
||||||
|
|
||||||
auto artists{ release->getReleaseArtists() };
|
struct Artist
|
||||||
if (artists.empty())
|
|
||||||
artists = release->getArtists();
|
|
||||||
|
|
||||||
if (!artists.empty())
|
|
||||||
{
|
{
|
||||||
if (!release->getArtistDisplayName().empty())
|
std::string name;
|
||||||
albumNode.setAttribute("artist", release->getArtistDisplayName());
|
std::string id;
|
||||||
else
|
};
|
||||||
albumNode.setAttribute("artist", utils::joinArtistNames(artists));
|
std::optional<Artist> artist;
|
||||||
|
|
||||||
if (artists.size() == 1)
|
const auto artistLinks{ release->getArtistLinks() };
|
||||||
|
if (!artistLinks.empty())
|
||||||
|
{
|
||||||
|
artist = Artist{ .name = std::string{ release->getArtistDisplayName() }, .id = {} };
|
||||||
|
if (artistLinks.size() == 1)
|
||||||
|
artist->id = idToString(artistLinks.front()->getArtistId());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (const auto trackArtists{ release->getTrackArtists(TrackArtistLinkType::Artist) }; !trackArtists.empty())
|
||||||
{
|
{
|
||||||
albumNode.setAttribute("artistId", idToString(artists.front()->getId()));
|
if (trackArtists.size() > 1)
|
||||||
|
artist = Artist{ .name = "Various Artists", .id = {} };
|
||||||
|
else if (trackArtists.size() == 1)
|
||||||
|
artist = Artist{ .name = trackArtists.front()->getName(), .id = idToString(trackArtists.front()->getId()) };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (artist)
|
||||||
|
{
|
||||||
|
albumNode.setAttribute("artist", artist->name);
|
||||||
|
if (!artist->id.empty())
|
||||||
|
albumNode.setAttribute("artistId", artist->id);
|
||||||
|
}
|
||||||
|
|
||||||
albumNode.setAttribute("playCount", core::Service<scrobbling::IScrobblingService>::get()->getCount(context.getUser()->getId(), release->getId()));
|
albumNode.setAttribute("playCount", core::Service<scrobbling::IScrobblingService>::get()->getCount(context.getUser()->getId(), release->getId()));
|
||||||
|
|
||||||
// Report the first GENRE for this track
|
// Report the first GENRE for this track
|
||||||
@@ -182,16 +198,16 @@ namespace lms::api::subsonic
|
|||||||
if (id3)
|
if (id3)
|
||||||
{
|
{
|
||||||
albumNode.createEmptyArrayChild("artists");
|
albumNode.createEmptyArrayChild("artists");
|
||||||
for (const Artist::pointer& artist : release->getReleaseArtists())
|
for (const db::ReleaseArtistLink::pointer& artistLink : artistLinks)
|
||||||
albumNode.addArrayChild("artists", createArtistNode(artist));
|
albumNode.addArrayChild("artists", createMinimalArtistNode(artistLink));
|
||||||
|
|
||||||
albumNode.setAttribute("displayArtist", release->getArtistDisplayName());
|
albumNode.setAttribute("displayArtist", release->getArtistDisplayName());
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
albumNode.createEmptyArrayChild("albumArtists");
|
albumNode.createEmptyArrayChild("albumArtists");
|
||||||
for (const Artist::pointer& artist : release->getReleaseArtists())
|
for (const db::ReleaseArtistLink::pointer& artistLink : artistLinks)
|
||||||
albumNode.addArrayChild("albumArtists", createArtistNode(artist));
|
albumNode.addArrayChild("albumArtists", createMinimalArtistNode(artistLink));
|
||||||
|
|
||||||
albumNode.setAttribute("displayAlbumArtist", release->getArtistDisplayName());
|
albumNode.setAttribute("displayAlbumArtist", release->getArtistDisplayName());
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include "database/objects/Artist.hpp"
|
#include "database/objects/Artist.hpp"
|
||||||
#include "database/objects/Artwork.hpp"
|
#include "database/objects/Artwork.hpp"
|
||||||
#include "database/objects/Release.hpp"
|
#include "database/objects/Release.hpp"
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
#include "database/objects/TrackArtistLink.hpp"
|
#include "database/objects/TrackArtistLink.hpp"
|
||||||
#include "database/objects/User.hpp"
|
#include "database/objects/User.hpp"
|
||||||
#include "services/feedback/IFeedbackService.hpp"
|
#include "services/feedback/IFeedbackService.hpp"
|
||||||
@@ -66,18 +67,25 @@ namespace lms::api::subsonic
|
|||||||
{
|
{
|
||||||
LMS_SCOPED_TRACE_DETAILED("Subsonic", "CreateArtist");
|
LMS_SCOPED_TRACE_DETAILED("Subsonic", "CreateArtist");
|
||||||
|
|
||||||
Response::Node artistNode{ createArtistNode(artist) };
|
Response::Node artistNode{ createMinimalArtistNode(artist) };
|
||||||
|
|
||||||
artistNode.setAttribute("id", idToString(artist->getId()));
|
|
||||||
artistNode.setAttribute("name", artist->getName());
|
|
||||||
if (const auto artwork{ artist->getPreferredArtwork() })
|
if (const auto artwork{ artist->getPreferredArtwork() })
|
||||||
{
|
{
|
||||||
CoverArtId coverArtId{ artwork->getId(), artwork->getLastWrittenTime().toTime_t() };
|
CoverArtId coverArtId{ artwork->getId(), artwork->getLastWrittenTime().toTime_t() };
|
||||||
artistNode.setAttribute("coverArt", idToString(coverArtId));
|
artistNode.setAttribute("coverArt", idToString(coverArtId));
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::size_t count{ Release::getCount(context.getDbSession(), Release::FindParameters{}.setArtist(artist->getId())) };
|
{
|
||||||
artistNode.setAttribute("albumCount", count);
|
std::size_t count{ Release::getCount(context.getDbSession(), Release::FindParameters{}.setArtist(artist->getId())) };
|
||||||
|
|
||||||
|
// TODO: not very efficient
|
||||||
|
Release::find(context.getDbSession(), Release::FindParameters{}.setTrackArtist(artist->getId()), [&](const db::Release::pointer& release) {
|
||||||
|
if (!release->hasArtist(artist->getId()))
|
||||||
|
count++;
|
||||||
|
});
|
||||||
|
|
||||||
|
artistNode.setAttribute("albumCount", count);
|
||||||
|
}
|
||||||
|
|
||||||
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.getUser()->getId(), artist->getId()) }; dateTime.isValid())
|
if (const Wt::WDateTime dateTime{ core::Service<feedback::IFeedbackService>::get()->getStarredDateTime(context.getUser()->getId(), artist->getId()) }; dateTime.isValid())
|
||||||
artistNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime));
|
artistNode.setAttribute("starred", core::stringUtils::toISO8601String(dateTime));
|
||||||
@@ -107,7 +115,7 @@ namespace lms::api::subsonic
|
|||||||
return artistNode;
|
return artistNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
Response::Node createArtistNode(const Artist::pointer& artist)
|
Response::Node createMinimalArtistNode(const Artist::pointer& artist)
|
||||||
{
|
{
|
||||||
Response::Node artistNode;
|
Response::Node artistNode;
|
||||||
|
|
||||||
@@ -116,4 +124,24 @@ namespace lms::api::subsonic
|
|||||||
|
|
||||||
return artistNode;
|
return artistNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Response::Node createMinimalArtistNode(const db::ObjectPtr<db::ReleaseArtistLink>& artistLink)
|
||||||
|
{
|
||||||
|
Response::Node artistNode;
|
||||||
|
|
||||||
|
artistNode.setAttribute("id", idToString(artistLink->getArtistId()));
|
||||||
|
artistNode.setAttribute("name", artistLink->getArtistName());
|
||||||
|
|
||||||
|
return artistNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
Response::Node createMinimalArtistNode(const db::ObjectPtr<db::TrackArtistLink>& artistLink)
|
||||||
|
{
|
||||||
|
Response::Node artistNode;
|
||||||
|
|
||||||
|
artistNode.setAttribute("id", idToString(artistLink->getArtistId()));
|
||||||
|
artistNode.setAttribute("name", artistLink->getArtistName());
|
||||||
|
|
||||||
|
return artistNode;
|
||||||
|
}
|
||||||
} // namespace lms::api::subsonic
|
} // namespace lms::api::subsonic
|
||||||
@@ -30,6 +30,8 @@
|
|||||||
namespace lms::db
|
namespace lms::db
|
||||||
{
|
{
|
||||||
class Artist;
|
class Artist;
|
||||||
|
class ReleaseArtistLink;
|
||||||
|
class TrackArtistLink;
|
||||||
class User;
|
class User;
|
||||||
class Session;
|
class Session;
|
||||||
} // namespace lms::db
|
} // namespace lms::db
|
||||||
@@ -45,5 +47,7 @@ namespace lms::api::subsonic
|
|||||||
} // namespace utils
|
} // namespace utils
|
||||||
|
|
||||||
Response::Node createArtistNode(RequestContext& context, const db::ObjectPtr<db::Artist>& artist);
|
Response::Node createArtistNode(RequestContext& context, const db::ObjectPtr<db::Artist>& artist);
|
||||||
Response::Node createArtistNode(const db::ObjectPtr<db::Artist>& artist); // only minimal info
|
Response::Node createMinimalArtistNode(const db::ObjectPtr<db::Artist>& artist); // only minimal info
|
||||||
|
Response::Node createMinimalArtistNode(const db::ObjectPtr<db::ReleaseArtistLink>& artistLink); // only minimal info
|
||||||
|
Response::Node createMinimalArtistNode(const db::ObjectPtr<db::TrackArtistLink>& artistLink); // only minimal info
|
||||||
} // namespace lms::api::subsonic
|
} // namespace lms::api::subsonic
|
||||||
@@ -27,14 +27,14 @@
|
|||||||
|
|
||||||
namespace lms::api::subsonic
|
namespace lms::api::subsonic
|
||||||
{
|
{
|
||||||
Response::Node createContributorNode(const db::ObjectPtr<db::TrackArtistLink>& trackArtistLink, const db::ObjectPtr<db::Artist>& artist)
|
Response::Node createContributorNode(const db::ObjectPtr<db::TrackArtistLink>& trackArtistLink)
|
||||||
{
|
{
|
||||||
Response::Node contributorNode;
|
Response::Node contributorNode;
|
||||||
|
|
||||||
contributorNode.setAttribute("role", utils::toString(trackArtistLink->getType()));
|
contributorNode.setAttribute("role", utils::toString(trackArtistLink->getType()));
|
||||||
if (!trackArtistLink->getSubType().empty())
|
if (!trackArtistLink->getSubType().empty())
|
||||||
contributorNode.setAttribute("subRole", trackArtistLink->getSubType());
|
contributorNode.setAttribute("subRole", trackArtistLink->getSubType());
|
||||||
contributorNode.addChild("artist", createArtistNode(artist));
|
contributorNode.addChild("artist", createMinimalArtistNode(trackArtistLink));
|
||||||
|
|
||||||
return contributorNode;
|
return contributorNode;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,5 +31,5 @@ namespace lms::db
|
|||||||
|
|
||||||
namespace lms::api::subsonic
|
namespace lms::api::subsonic
|
||||||
{
|
{
|
||||||
Response::Node createContributorNode(const db::ObjectPtr<db::TrackArtistLink>& trackArtistLink, const db::ObjectPtr<db::Artist>& artist);
|
Response::Node createContributorNode(const db::ObjectPtr<db::TrackArtistLink>& trackArtistLink);
|
||||||
}
|
}
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "core/MimeTypes.hpp"
|
#include "core/MimeTypes.hpp"
|
||||||
#include "core/Service.hpp"
|
#include "core/Service.hpp"
|
||||||
#include "core/String.hpp"
|
#include "core/String.hpp"
|
||||||
|
|
||||||
#include "database/Types.hpp"
|
#include "database/Types.hpp"
|
||||||
#include "database/objects/Artist.hpp"
|
#include "database/objects/Artist.hpp"
|
||||||
#include "database/objects/Artwork.hpp"
|
#include "database/objects/Artwork.hpp"
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
#include "database/objects/MediaLibrary.hpp"
|
#include "database/objects/MediaLibrary.hpp"
|
||||||
#include "database/objects/Medium.hpp"
|
#include "database/objects/Medium.hpp"
|
||||||
#include "database/objects/Release.hpp"
|
#include "database/objects/Release.hpp"
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
#include "database/objects/Track.hpp"
|
#include "database/objects/Track.hpp"
|
||||||
#include "database/objects/TrackArtistLink.hpp"
|
#include "database/objects/TrackArtistLink.hpp"
|
||||||
#include "database/objects/User.hpp"
|
#include "database/objects/User.hpp"
|
||||||
@@ -198,19 +200,23 @@ namespace lms::api::subsonic
|
|||||||
trackResponse.createEmptyArrayChild("artists");
|
trackResponse.createEmptyArrayChild("artists");
|
||||||
trackResponse.createEmptyArrayChild("contributors");
|
trackResponse.createEmptyArrayChild("contributors");
|
||||||
|
|
||||||
db::TrackArtistLink::find(context.getDbSession(), track->getId(), [&](const db::TrackArtistLink::pointer& link, const db::Artist::pointer& artist) {
|
track->visitArtistLinks([&](const db::TrackArtistLink::pointer& artistLink) {
|
||||||
switch (link->getType())
|
switch (artistLink->getType())
|
||||||
{
|
{
|
||||||
case db::TrackArtistLinkType::Artist:
|
case db::TrackArtistLinkType::Artist:
|
||||||
trackResponse.addArrayChild("artists", createArtistNode(artist));
|
trackResponse.addArrayChild("artists", createMinimalArtistNode(artistLink));
|
||||||
break;
|
|
||||||
case db::TrackArtistLinkType::ReleaseArtist:
|
|
||||||
trackResponse.addArrayChild("albumArtists", createArtistNode(artist));
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
trackResponse.addArrayChild("contributors", createContributorNode(link, artist));
|
trackResponse.addArrayChild("contributors", createContributorNode(artistLink));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (release)
|
||||||
|
{
|
||||||
|
release->visitArtistLinks([&](const db::ReleaseArtistLink::pointer& artistLink) {
|
||||||
|
trackResponse.addArrayChild("albumArtists", createMinimalArtistNode(artistLink));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trackResponse.setAttribute("displayArtist", track->getArtistDisplayName());
|
trackResponse.setAttribute("displayArtist", track->getArtistDisplayName());
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ add_executable(lms
|
|||||||
ui/common/UUIDValidator.cpp
|
ui/common/UUIDValidator.cpp
|
||||||
ui/explore/ArtistCollector.cpp
|
ui/explore/ArtistCollector.cpp
|
||||||
ui/explore/ArtistListHelpers.cpp
|
ui/explore/ArtistListHelpers.cpp
|
||||||
|
ui/explore/ArtistType.cpp
|
||||||
ui/explore/ArtistView.cpp
|
ui/explore/ArtistView.cpp
|
||||||
ui/explore/ArtistsView.cpp
|
ui/explore/ArtistsView.cpp
|
||||||
ui/explore/DatabaseCollectorBase.cpp
|
ui/explore/DatabaseCollectorBase.cpp
|
||||||
|
|||||||
+11
-18
@@ -35,6 +35,7 @@
|
|||||||
#include "database/objects/Release.hpp"
|
#include "database/objects/Release.hpp"
|
||||||
#include "database/objects/Track.hpp"
|
#include "database/objects/Track.hpp"
|
||||||
#include "database/objects/TrackList.hpp"
|
#include "database/objects/TrackList.hpp"
|
||||||
|
#include "database/objects/Types.hpp"
|
||||||
#include "database/objects/User.hpp"
|
#include "database/objects/User.hpp"
|
||||||
|
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
@@ -194,7 +195,7 @@ namespace lms::ui
|
|||||||
_audioFileResource = std::make_unique<AudioFileResource>();
|
_audioFileResource = std::make_unique<AudioFileResource>();
|
||||||
|
|
||||||
_title = bindNew<Wt::WText>("title");
|
_title = bindNew<Wt::WText>("title");
|
||||||
_artist = bindNew<Wt::WAnchor>("artist");
|
_artists = bindNew<Wt::WContainerWidget>("artist");
|
||||||
_release = bindNew<Wt::WAnchor>("release");
|
_release = bindNew<Wt::WAnchor>("release");
|
||||||
_separator = bindNew<Wt::WText>("separator");
|
_separator = bindNew<Wt::WText>("separator");
|
||||||
_playQueue = bindNew<Wt::WPushButton>("playqueue-btn", Wt::WString::tr("Lms.MediaPlayer.template.playqueue-btn").arg(0), Wt::TextFormat::XHTML);
|
_playQueue = bindNew<Wt::WPushButton>("playqueue-btn", Wt::WString::tr("Lms.MediaPlayer.template.playqueue-btn").arg(0), Wt::TextFormat::XHTML);
|
||||||
@@ -237,10 +238,12 @@ namespace lms::ui
|
|||||||
if (!track)
|
if (!track)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
const auto release{ track->getRelease() };
|
||||||
|
|
||||||
const std::string transcodingResource{ _audioTranscodingResource->getUrl(trackId) };
|
const std::string transcodingResource{ _audioTranscodingResource->getUrl(trackId) };
|
||||||
const std::string nativeResource{ _audioFileResource->getUrl(trackId) };
|
const std::string nativeResource{ _audioFileResource->getUrl(trackId) };
|
||||||
|
|
||||||
const auto artists{ track->getArtists({ db::TrackArtistLinkType::Artist }) };
|
const auto artistDisplayInfo{ utils::computeArtistDisplayInfo(track, db::TrackArtistLinkType::Artist) };
|
||||||
|
|
||||||
oss
|
oss
|
||||||
<< "var params = {"
|
<< "var params = {"
|
||||||
@@ -250,8 +253,8 @@ namespace lms::ui
|
|||||||
<< " duration: " << std::chrono::duration_cast<std::chrono::duration<float>>(track->getDuration()).count() << ","
|
<< " duration: " << std::chrono::duration_cast<std::chrono::duration<float>>(track->getDuration()).count() << ","
|
||||||
<< " replayGain: " << replayGain << ","
|
<< " replayGain: " << replayGain << ","
|
||||||
<< " title: \"" << core::stringUtils::jsEscape(track->getName()) << "\","
|
<< " title: \"" << core::stringUtils::jsEscape(track->getName()) << "\","
|
||||||
<< " artist: \"" << (!artists.empty() ? core::stringUtils::jsEscape(track->getArtistDisplayName()) : "") << "\","
|
<< " artist: \"" << (!artistDisplayInfo.displayName.empty() ? core::stringUtils::jsEscape(track->getArtistDisplayName()) : "") << "\","
|
||||||
<< " release: \"" << (track->getRelease() ? core::stringUtils::jsEscape(track->getRelease()->getName()) : "") << "\",";
|
<< " release: \"" << (release ? core::stringUtils::jsEscape(release->getName()) : "") << "\",";
|
||||||
|
|
||||||
db::ArtworkId artworkId{ track->getPreferredMediaArtworkId() };
|
db::ArtworkId artworkId{ track->getPreferredMediaArtworkId() };
|
||||||
if (!artworkId.isValid())
|
if (!artworkId.isValid())
|
||||||
@@ -281,22 +284,12 @@ namespace lms::ui
|
|||||||
_title->setTextFormat(Wt::TextFormat::Plain);
|
_title->setTextFormat(Wt::TextFormat::Plain);
|
||||||
_title->setText(Wt::WString::fromUTF8(track->getName()));
|
_title->setText(Wt::WString::fromUTF8(track->getName()));
|
||||||
|
|
||||||
bool needSeparator{ true };
|
_artists->clear();
|
||||||
|
_artists->addWidget(utils::createArtistsAnchors(artistDisplayInfo));
|
||||||
|
|
||||||
if (!artists.empty())
|
bool needSeparator{ !artistDisplayInfo.displayName.empty() };
|
||||||
{
|
|
||||||
_artist->setTextFormat(Wt::TextFormat::Plain);
|
|
||||||
_artist->setText(Wt::WString::fromUTF8(artists.front()->getName()));
|
|
||||||
_artist->setLink(utils::createArtistLink(artists.front()));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_artist->setText("");
|
|
||||||
_artist->setLink({});
|
|
||||||
needSeparator = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const db::Release::pointer release{ track->getRelease() })
|
if (release)
|
||||||
{
|
{
|
||||||
_release->setTextFormat(Wt::TextFormat::Plain);
|
_release->setTextFormat(Wt::TextFormat::Plain);
|
||||||
_release->setText(Wt::WString::fromUTF8(std::string{ release->getName() }));
|
_release->setText(Wt::WString::fromUTF8(std::string{ release->getName() }));
|
||||||
|
|||||||
@@ -22,6 +22,7 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include <Wt/WAnchor.h>
|
#include <Wt/WAnchor.h>
|
||||||
|
#include <Wt/WContainerWidget.h>
|
||||||
#include <Wt/WJavaScript.h>
|
#include <Wt/WJavaScript.h>
|
||||||
#include <Wt/WPushButton.h>
|
#include <Wt/WPushButton.h>
|
||||||
#include <Wt/WTemplate.h>
|
#include <Wt/WTemplate.h>
|
||||||
@@ -126,7 +127,7 @@ namespace lms::ui
|
|||||||
Wt::WText* _title{};
|
Wt::WText* _title{};
|
||||||
Wt::WAnchor* _release{};
|
Wt::WAnchor* _release{};
|
||||||
Wt::WText* _separator{};
|
Wt::WText* _separator{};
|
||||||
Wt::WAnchor* _artist{};
|
Wt::WContainerWidget* _artists{};
|
||||||
Wt::WPushButton* _playQueue{};
|
Wt::WPushButton* _playQueue{};
|
||||||
};
|
};
|
||||||
} // namespace lms::ui
|
} // namespace lms::ui
|
||||||
|
|||||||
@@ -487,8 +487,8 @@ namespace lms::ui
|
|||||||
if (!artists.empty())
|
if (!artists.empty())
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-artists", true);
|
entry->setCondition("if-has-artists", true);
|
||||||
entry->bindWidget("artists", utils::createArtistAnchorList(artists));
|
entry->bindWidget("artists", utils::createArtistsAnchors(track, db::TrackArtistLinkType::Artist));
|
||||||
entry->bindWidget("artists-md", utils::createArtistAnchorList(artists));
|
entry->bindWidget("artists-md", utils::createArtistsAnchors(track, db::TrackArtistLinkType::Artist));
|
||||||
}
|
}
|
||||||
|
|
||||||
db::ArtworkId artworkId{ track->getPreferredMediaArtworkId() };
|
db::ArtworkId artworkId{ track->getPreferredMediaArtworkId() };
|
||||||
|
|||||||
@@ -457,13 +457,13 @@ namespace lms::ui
|
|||||||
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.name"), db::ReleaseSortMethod::Name);
|
_artistReleaseSortMethodModel->add(Wt::WString::tr("Lms.Settings.name"), db::ReleaseSortMethod::Name);
|
||||||
|
|
||||||
_artistRelationshipsModel = std::make_shared<ArtistRelationshipsModel>();
|
_artistRelationshipsModel = std::make_shared<ArtistRelationshipsModel>();
|
||||||
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-composer", 2), db::TrackArtistLinkType::Composer);
|
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.composer", 2), db::TrackArtistLinkType::Composer);
|
||||||
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-conductor", 2), db::TrackArtistLinkType::Conductor);
|
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.conductor", 2), db::TrackArtistLinkType::Conductor);
|
||||||
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-lyricist", 2), db::TrackArtistLinkType::Lyricist);
|
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.lyricist", 2), db::TrackArtistLinkType::Lyricist);
|
||||||
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-mixer", 2), db::TrackArtistLinkType::Mixer);
|
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.mixer", 2), db::TrackArtistLinkType::Mixer);
|
||||||
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-performer", 2), db::TrackArtistLinkType::Performer);
|
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.performer", 2), db::TrackArtistLinkType::Performer);
|
||||||
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-producer", 2), db::TrackArtistLinkType::Producer);
|
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.producer", 2), db::TrackArtistLinkType::Producer);
|
||||||
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.linktype-remixer", 2), db::TrackArtistLinkType::Remixer);
|
_artistRelationshipsModel->add(Wt::WString::trn("Lms.Explore.Artists.remixer", 2), db::TrackArtistLinkType::Remixer);
|
||||||
|
|
||||||
_transcodingModeModeModel = std::make_shared<TranscodingModeModel>();
|
_transcodingModeModeModel = std::make_shared<TranscodingModeModel>();
|
||||||
_transcodingModeModeModel->add(Wt::WString::tr("Lms.Settings.transcoding-mode.always"), MediaPlayer::Settings::Transcoding::Mode::Always);
|
_transcodingModeModeModel->add(Wt::WString::tr("Lms.Settings.transcoding-mode.always"), MediaPlayer::Settings::Transcoding::Mode::Always);
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ namespace lms::ui::state
|
|||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void writeValue(std::string_view item, std::optional<T> value)
|
void writeValue(std::string_view item, const std::optional<T>& value)
|
||||||
{
|
{
|
||||||
if (value.has_value())
|
if (value.has_value())
|
||||||
{
|
{
|
||||||
|
|||||||
+236
-106
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include "Utils.hpp"
|
#include "Utils.hpp"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
@@ -30,8 +31,10 @@
|
|||||||
#include "database/objects/Cluster.hpp"
|
#include "database/objects/Cluster.hpp"
|
||||||
#include "database/objects/Image.hpp"
|
#include "database/objects/Image.hpp"
|
||||||
#include "database/objects/Release.hpp"
|
#include "database/objects/Release.hpp"
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
#include "database/objects/ScanSettings.hpp"
|
#include "database/objects/ScanSettings.hpp"
|
||||||
#include "database/objects/Track.hpp"
|
#include "database/objects/Track.hpp"
|
||||||
|
#include "database/objects/TrackArtistLink.hpp"
|
||||||
#include "database/objects/TrackList.hpp"
|
#include "database/objects/TrackList.hpp"
|
||||||
|
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
@@ -51,6 +54,28 @@ namespace lms::ui::utils
|
|||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Wt::WLink createArtistLink(db::Artist::pointer artist)
|
||||||
|
{
|
||||||
|
if (const auto mbid{ artist->getMBID() })
|
||||||
|
return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/mbid/" + std::string{ mbid->getAsString() } };
|
||||||
|
else
|
||||||
|
return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString() };
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WAnchor> createArtistAnchor(db::Artist::pointer artist, std::string_view displayName, bool setText)
|
||||||
|
{
|
||||||
|
auto res{ std::make_unique<Wt::WAnchor>(createArtistLink(artist)) };
|
||||||
|
|
||||||
|
if (setText)
|
||||||
|
{
|
||||||
|
res->setTextFormat(Wt::TextFormat::Plain);
|
||||||
|
res->setText(Wt::WString::fromUTF8(std::string{ displayName }));
|
||||||
|
res->setToolTip(Wt::WString::fromUTF8(std::string{ displayName }), Wt::TextFormat::Plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
std::string durationToString(std::chrono::milliseconds msDuration)
|
std::string durationToString(std::chrono::milliseconds msDuration)
|
||||||
@@ -174,138 +199,38 @@ namespace lms::ui::utils
|
|||||||
return clusterContainer;
|
return clusterContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WContainerWidget> createArtistAnchorList(const std::vector<db::ArtistId>& artistIds, std::string_view cssAnchorClass)
|
std::unique_ptr<Wt::WContainerWidget> createArtistAnchorList(const std::vector<db::Artist::pointer>& artists, std::string_view cssAnchorClass)
|
||||||
{
|
{
|
||||||
using namespace db;
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WContainerWidget> artistContainer{ std::make_unique<Wt::WContainerWidget>() };
|
std::unique_ptr<Wt::WContainerWidget> artistContainer{ std::make_unique<Wt::WContainerWidget>() };
|
||||||
|
|
||||||
bool firstArtist{ true };
|
bool firstArtist{ true };
|
||||||
|
|
||||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
for (const auto& artist : artists)
|
||||||
for (const ArtistId artistId : artistIds)
|
|
||||||
{
|
{
|
||||||
const Artist::pointer artist{ Artist::find(LmsApp->getDbSession(), artistId) };
|
|
||||||
if (!artist)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!firstArtist)
|
if (!firstArtist)
|
||||||
artistContainer->addNew<Wt::WText>(" · ");
|
artistContainer->addNew<Wt::WText>(" · ");
|
||||||
|
firstArtist = false;
|
||||||
|
|
||||||
auto anchor{ createArtistAnchor(artist) };
|
auto anchor{ createArtistAnchor(artist) };
|
||||||
anchor->addStyleClass("text-decoration-none"); // hack
|
anchor->addStyleClass("text-decoration-none"); // hack
|
||||||
anchor->addStyleClass(std::string{ cssAnchorClass });
|
anchor->addStyleClass(std::string{ cssAnchorClass });
|
||||||
artistContainer->addWidget(std::move(anchor));
|
artistContainer->addWidget(std::move(anchor));
|
||||||
firstArtist = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return artistContainer;
|
return artistContainer;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WContainerWidget> createArtistDisplayNameWithAnchors(std::string_view displayName, const std::vector<db::ArtistId>& artistIds, std::string_view cssAnchorClass)
|
|
||||||
{
|
|
||||||
using namespace db;
|
|
||||||
|
|
||||||
std::size_t matchCount{};
|
|
||||||
std::string_view::size_type currentOffset{};
|
|
||||||
|
|
||||||
auto result{ std::make_unique<Wt::WContainerWidget>() };
|
|
||||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
|
||||||
|
|
||||||
// consider order is guaranteed + we will likely succeed
|
|
||||||
for (const ArtistId artistId : artistIds)
|
|
||||||
{
|
|
||||||
const Artist::pointer artist{ Artist::find(LmsApp->getDbSession(), artistId) };
|
|
||||||
if (!artist)
|
|
||||||
break;
|
|
||||||
|
|
||||||
const auto pos{ displayName.find(artist->getName(), currentOffset) };
|
|
||||||
if (pos == std::string_view::npos)
|
|
||||||
break;
|
|
||||||
|
|
||||||
assert(pos >= currentOffset);
|
|
||||||
if (pos != currentOffset)
|
|
||||||
result->addNew<Wt::WText>(std::string{ displayName.substr(currentOffset, pos - currentOffset) }, Wt::TextFormat::Plain);
|
|
||||||
|
|
||||||
auto anchor{ createArtistAnchor(artist) };
|
|
||||||
anchor->addStyleClass("text-decoration-none"); // hack
|
|
||||||
anchor->addStyleClass(std::string{ cssAnchorClass }); // hack
|
|
||||||
result->addWidget(std::move(anchor));
|
|
||||||
currentOffset = pos + artist->getName().size();
|
|
||||||
matchCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matchCount == artistIds.size())
|
|
||||||
{
|
|
||||||
const std::string_view remainingStr{ displayName.substr(currentOffset) };
|
|
||||||
if (!remainingStr.empty())
|
|
||||||
result->addNew<Wt::WText>(std::string{ remainingStr }, Wt::TextFormat::Plain);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
result = createArtistAnchorList(artistIds, cssAnchorClass);
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WContainerWidget> createArtistsAnchorsForRelease(db::ObjectPtr<db::Release> release, db::ArtistId omitIfMatchThisArtist, std::string_view cssAnchorClass)
|
|
||||||
{
|
|
||||||
using namespace db;
|
|
||||||
|
|
||||||
if (const std::vector<ArtistId> releaseArtists{ release->getArtistIds(TrackArtistLinkType::ReleaseArtist) }; !releaseArtists.empty())
|
|
||||||
{
|
|
||||||
if (releaseArtists.size() == 1 && releaseArtists.front() == omitIfMatchThisArtist)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
return createArtistDisplayNameWithAnchors(release->getArtistDisplayName(), releaseArtists, cssAnchorClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto artists{ release->getArtistIds(TrackArtistLinkType::Artist) };
|
|
||||||
if (artists.size() == 1)
|
|
||||||
{
|
|
||||||
if (artists.front() == omitIfMatchThisArtist)
|
|
||||||
return {};
|
|
||||||
|
|
||||||
return createArtistAnchorList({ artists.front() }, cssAnchorClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (artists.size() > 1)
|
|
||||||
{
|
|
||||||
auto res{ std::make_unique<Wt::WContainerWidget>() };
|
|
||||||
res->addNew<Wt::WText>(Wt::WString::tr("Lms.Explore.various-artists"));
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Wt::WLink createArtistLink(db::Artist::pointer artist)
|
|
||||||
{
|
|
||||||
if (const auto mbid{ artist->getMBID() })
|
|
||||||
return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/mbid/" + std::string{ mbid->getAsString() } };
|
|
||||||
else
|
|
||||||
return Wt::WLink{ Wt::LinkType::InternalPath, "/artist/" + artist->getId().toString() };
|
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WAnchor> createArtistAnchor(db::Artist::pointer artist, bool setText)
|
std::unique_ptr<Wt::WAnchor> createArtistAnchor(db::Artist::pointer artist, bool setText)
|
||||||
{
|
{
|
||||||
auto res = std::make_unique<Wt::WAnchor>(createArtistLink(artist));
|
return createArtistAnchor(artist, artist->getName(), setText);
|
||||||
|
|
||||||
if (setText)
|
|
||||||
{
|
|
||||||
res->setTextFormat(Wt::TextFormat::Plain);
|
|
||||||
res->setText(Wt::WString::fromUTF8(artist->getName()));
|
|
||||||
res->setToolTip(Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain);
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Wt::WLink createReleaseLink(db::Release::pointer release)
|
Wt::WLink createReleaseLink(db::Release::pointer release)
|
||||||
{
|
{
|
||||||
if (const auto mbid{ release->getMBID() })
|
if (const auto mbid{ release->getMBID() })
|
||||||
return Wt::WLink{ Wt::LinkType::InternalPath, "/release/mbid/" + std::string{ mbid->getAsString() } };
|
return Wt::WLink{ Wt::LinkType::InternalPath, "/release/mbid/" + std::string{ mbid->getAsString() } };
|
||||||
else
|
|
||||||
return Wt::WLink{ Wt::LinkType::InternalPath, "/release/" + release->getId().toString() };
|
return Wt::WLink{ Wt::LinkType::InternalPath, "/release/" + release->getId().toString() };
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WAnchor> createReleaseAnchor(db::Release::pointer release, bool setText)
|
std::unique_ptr<Wt::WAnchor> createReleaseAnchor(db::Release::pointer release, bool setText)
|
||||||
@@ -344,4 +269,209 @@ namespace lms::ui::utils
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArtistDisplayInfo computeArtistDisplayInfo(db::ObjectPtr<db::Release> release)
|
||||||
|
{
|
||||||
|
ArtistDisplayInfo res;
|
||||||
|
|
||||||
|
res.displayName = release->getArtistDisplayName();
|
||||||
|
release->visitArtistLinks([&res](const db::ObjectPtr<db::ReleaseArtistLink>& artistLink) {
|
||||||
|
res.entries.emplace_back(ArtistDisplayInfo::Entry{ .displayName = std::string{ artistLink->getArtistName() }, .artist = artistLink->getArtist() });
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no release artists, fallback on track artists, only if there are only 1 artist, otherwise just put various-artists
|
||||||
|
if (res.entries.empty())
|
||||||
|
{
|
||||||
|
res.displayName.clear();
|
||||||
|
|
||||||
|
const auto trackArtists{ release->getTrackArtists(db::TrackArtistLinkType::Artist) };
|
||||||
|
|
||||||
|
if (trackArtists.size() > 1)
|
||||||
|
res.displayName = Wt::WString::tr("Lms.Explore.various-artists").toUTF8();
|
||||||
|
else
|
||||||
|
res.entries.emplace_back(ArtistDisplayInfo::Entry{ .displayName = std::string{ trackArtists[0]->getName() }, .artist = trackArtists[0] });
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArtistDisplayInfo computeArtistDisplayInfo(db::ObjectPtr<db::Track> track, db::TrackArtistLinkType linkType)
|
||||||
|
{
|
||||||
|
ArtistDisplayInfo res;
|
||||||
|
|
||||||
|
res.displayName = track->getArtistDisplayName();
|
||||||
|
track->visitArtistLinks(linkType, [&res](const db::ObjectPtr<db::TrackArtistLink>& artistLink) {
|
||||||
|
res.entries.emplace_back(ArtistDisplayInfo::Entry{ .displayName = std::string{ artistLink->getArtistName() }, .artist = artistLink->getArtist() });
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WContainerWidget> createArtistsAnchors(const ArtistDisplayInfo& artistDisplayInfo, std::string_view cssAnchorClass)
|
||||||
|
{
|
||||||
|
auto result{ std::make_unique<Wt::WContainerWidget>() };
|
||||||
|
result->setInline(true); // TODO: use a template for that?
|
||||||
|
|
||||||
|
std::size_t matchCount{};
|
||||||
|
std::string_view::size_type currentOffset{};
|
||||||
|
|
||||||
|
if (artistDisplayInfo.entries.empty())
|
||||||
|
{
|
||||||
|
if (!artistDisplayInfo.displayName.empty())
|
||||||
|
result->addNew<Wt::WText>(std::string{ artistDisplayInfo.displayName }, Wt::TextFormat::Plain);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// consider order is guaranteed + we will likely succeed
|
||||||
|
for (const ArtistDisplayInfo::Entry& entry : artistDisplayInfo.entries)
|
||||||
|
{
|
||||||
|
const auto pos{ artistDisplayInfo.displayName.find(entry.displayName, currentOffset) };
|
||||||
|
if (pos == std::string_view::npos)
|
||||||
|
break;
|
||||||
|
|
||||||
|
assert(pos >= currentOffset);
|
||||||
|
if (pos != currentOffset)
|
||||||
|
result->addNew<Wt::WText>(std::string{ artistDisplayInfo.displayName.substr(currentOffset, pos - currentOffset) }, Wt::TextFormat::Plain);
|
||||||
|
|
||||||
|
auto anchor{ createArtistAnchor(entry.artist, entry.displayName, true) };
|
||||||
|
anchor->addStyleClass("text-decoration-none"); // hack
|
||||||
|
anchor->addStyleClass(std::string{ cssAnchorClass }); // hack
|
||||||
|
result->addWidget(std::move(anchor));
|
||||||
|
|
||||||
|
currentOffset = pos + entry.displayName.size();
|
||||||
|
matchCount += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matchCount == artistDisplayInfo.entries.size())
|
||||||
|
{
|
||||||
|
const std::string_view remainingStr{ artistDisplayInfo.displayName.substr(currentOffset) };
|
||||||
|
if (!remainingStr.empty())
|
||||||
|
result->addNew<Wt::WText>(std::string{ remainingStr }, Wt::TextFormat::Plain);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
std::vector<db::Artist::pointer> artists;
|
||||||
|
std::transform(std::cbegin(artistDisplayInfo.entries), std::cend(artistDisplayInfo.entries), std::back_inserter(artists), [](const ArtistDisplayInfo::Entry& entry) {
|
||||||
|
return entry.artist;
|
||||||
|
});
|
||||||
|
|
||||||
|
result = createArtistAnchorList(artists, cssAnchorClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WContainerWidget> createArtistsAnchors(db::ObjectPtr<db::Track> track, db::TrackArtistLinkType linkType, std::string_view cssAnchorClass)
|
||||||
|
{
|
||||||
|
const auto computeArtistDisplayInfo{ utils::computeArtistDisplayInfo(track, linkType) };
|
||||||
|
return utils::createArtistsAnchors(computeArtistDisplayInfo, cssAnchorClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WContainerWidget> createArtistsAnchors(db::ObjectPtr<db::Release> release, std::string_view cssAnchorClass)
|
||||||
|
{
|
||||||
|
const auto computeArtistDisplayInfo{ utils::computeArtistDisplayInfo(release) };
|
||||||
|
return utils::createArtistsAnchors(computeArtistDisplayInfo, cssAnchorClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<Wt::WString, std::vector<db::ObjectPtr<db::Artist>>> getArtistsByRole(db::ObjectPtr<db::Track> track, core::EnumSet<db::TrackArtistLinkType> artistLinkTypes)
|
||||||
|
{
|
||||||
|
std::map<Wt::WString, std::vector<db::Artist::pointer>> artistMap;
|
||||||
|
|
||||||
|
auto addArtists = [&](db::TrackArtistLinkType linkType, const char* type) {
|
||||||
|
if (!artistLinkTypes.empty() && !artistLinkTypes.contains(linkType))
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::vector<db::Artist::pointer> artists;
|
||||||
|
track->visitArtistLinks(linkType, [&](const auto& link) {
|
||||||
|
artists.push_back(link->getArtist());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (artists.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Wt::WString typeStr{ Wt::WString::trn(type, artists.size()) };
|
||||||
|
artistMap[typeStr] = std::move(artists);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<db::ObjectPtr<db::Artist>> rolelessPerformers;
|
||||||
|
auto addPerformerArtists = [&] {
|
||||||
|
if (!artistLinkTypes.empty() && !artistLinkTypes.contains(db::TrackArtistLinkType::Performer))
|
||||||
|
return;
|
||||||
|
|
||||||
|
track->visitArtistLinks(db::TrackArtistLinkType::Performer, [&](const auto& link) {
|
||||||
|
if (link->getSubType().empty())
|
||||||
|
rolelessPerformers.push_back(link->getArtist());
|
||||||
|
else
|
||||||
|
artistMap[std::string{ link->getSubType() }].push_back(link->getArtist());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
addArtists(db::TrackArtistLinkType::Composer, "Lms.Explore.Artists.composer");
|
||||||
|
addArtists(db::TrackArtistLinkType::Conductor, "Lms.Explore.Artists.conductor");
|
||||||
|
addArtists(db::TrackArtistLinkType::Lyricist, "Lms.Explore.Artists.lyricist");
|
||||||
|
addArtists(db::TrackArtistLinkType::Mixer, "Lms.Explore.Artists.mixer");
|
||||||
|
addArtists(db::TrackArtistLinkType::Remixer, "Lms.Explore.Artists.remixer");
|
||||||
|
addArtists(db::TrackArtistLinkType::Producer, "Lms.Explore.Artists.producer");
|
||||||
|
addPerformerArtists();
|
||||||
|
|
||||||
|
if (!rolelessPerformers.empty())
|
||||||
|
{
|
||||||
|
Wt::WString performersStr{ Wt::WString::trn("Lms.Explore.Artists.performer", rolelessPerformers.size()) };
|
||||||
|
artistMap[performersStr] = std::move(rolelessPerformers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return artistMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::map<Wt::WString, std::vector<db::ObjectPtr<db::Artist>>> getTrackArtistsByRole(db::ObjectPtr<db::Release> release)
|
||||||
|
{
|
||||||
|
std::map<Wt::WString, std::vector<db::Artist::pointer>> artistMap;
|
||||||
|
|
||||||
|
auto addArtists = [&](db::TrackArtistLinkType linkType, const char* type) {
|
||||||
|
std::vector<db::Artist::pointer> artists;
|
||||||
|
release->visitTrackArtists(linkType, [&](const auto& artist) {
|
||||||
|
artists.push_back(artist);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (artists.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Wt::WString typeStr{ Wt::WString::trn(type, artists.size()) };
|
||||||
|
artistMap[typeStr] = std::move(artists);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<db::ObjectPtr<db::Artist>> rolelessPerformers;
|
||||||
|
auto addPerformerArtists = [&] {
|
||||||
|
release->visitTrackArtistLinks(db::TrackArtistLinkType::Performer, [&](const db::TrackArtistLink::pointer& link) {
|
||||||
|
if (link->getSubType().empty())
|
||||||
|
{
|
||||||
|
if (std::find(std::cbegin(rolelessPerformers), std::cend(rolelessPerformers), link->getArtist()) == std::cend(rolelessPerformers))
|
||||||
|
rolelessPerformers.push_back(link->getArtist());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
auto& artists{ artistMap[std::string{ link->getSubType() }] };
|
||||||
|
if (std::find(std::cbegin(artists), std::cend(artists), link->getArtist()) == std::cend(artists))
|
||||||
|
artists.push_back(link->getArtist());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
addArtists(db::TrackArtistLinkType::Composer, "Lms.Explore.Artists.composer");
|
||||||
|
addArtists(db::TrackArtistLinkType::Conductor, "Lms.Explore.Artists.conductor");
|
||||||
|
addArtists(db::TrackArtistLinkType::Lyricist, "Lms.Explore.Artists.lyricist");
|
||||||
|
addArtists(db::TrackArtistLinkType::Mixer, "Lms.Explore.Artists.mixer");
|
||||||
|
addArtists(db::TrackArtistLinkType::Remixer, "Lms.Explore.Artists.remixer");
|
||||||
|
addArtists(db::TrackArtistLinkType::Producer, "Lms.Explore.Artists.producer");
|
||||||
|
addPerformerArtists();
|
||||||
|
|
||||||
|
if (!rolelessPerformers.empty())
|
||||||
|
{
|
||||||
|
Wt::WString performersStr{ Wt::WString::trn("Lms.Explore.Artists.performer", rolelessPerformers.size()) };
|
||||||
|
artistMap[performersStr] = std::move(rolelessPerformers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return artistMap;
|
||||||
|
}
|
||||||
} // namespace lms::ui::utils
|
} // namespace lms::ui::utils
|
||||||
|
|||||||
+29
-11
@@ -20,21 +20,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include <Wt/WContainerWidget.h>
|
#include <Wt/WContainerWidget.h>
|
||||||
#include <Wt/WImage.h>
|
#include <Wt/WImage.h>
|
||||||
#include <Wt/WInteractWidget.h>
|
#include <Wt/WInteractWidget.h>
|
||||||
#include <Wt/WString.h>
|
#include <Wt/WString.h>
|
||||||
|
|
||||||
#include "database/Object.hpp"
|
#include "core/EnumSet.hpp"
|
||||||
#include "database/objects/ArtistId.hpp"
|
|
||||||
#include "database/objects/ClusterId.hpp"
|
#include "database/Object.hpp"
|
||||||
#include "database/objects/ReleaseId.hpp"
|
#include "database/objects/ClusterId.hpp"
|
||||||
#include "database/objects/TrackId.hpp"
|
#include "database/objects/Types.hpp"
|
||||||
|
|
||||||
#include "database/Types.hpp"
|
|
||||||
#include "resource/ArtworkResource.hpp"
|
#include "resource/ArtworkResource.hpp"
|
||||||
|
|
||||||
namespace lms::db
|
namespace lms::db
|
||||||
@@ -64,13 +65,30 @@ namespace lms::ui::utils
|
|||||||
std::unique_ptr<Wt::WInteractWidget> createFilterCluster(db::ClusterId clusterId, bool canDelete = false);
|
std::unique_ptr<Wt::WInteractWidget> createFilterCluster(db::ClusterId clusterId, bool canDelete = false);
|
||||||
std::unique_ptr<Wt::WContainerWidget> createFilterClustersForTrack(db::ObjectPtr<db::Track> track, Filters& filters);
|
std::unique_ptr<Wt::WContainerWidget> createFilterClustersForTrack(db::ObjectPtr<db::Track> track, Filters& filters);
|
||||||
|
|
||||||
std::unique_ptr<Wt::WContainerWidget> createArtistAnchorList(const std::vector<db::ArtistId>& artistIds, std::string_view cssAnchorClass = "link-success");
|
|
||||||
std::unique_ptr<Wt::WContainerWidget> createArtistDisplayNameWithAnchors(std::string_view displayName, const std::vector<db::ArtistId>& artistIds, std::string_view cssAnchorClass = "link-success");
|
|
||||||
std::unique_ptr<Wt::WContainerWidget> createArtistsAnchorsForRelease(db::ObjectPtr<db::Release> release, db::ArtistId omitIfMatchThisArtist = {}, std::string_view cssAnchorClass = "link-success");
|
|
||||||
|
|
||||||
Wt::WLink createArtistLink(db::ObjectPtr<db::Artist> artist);
|
|
||||||
std::unique_ptr<Wt::WAnchor> createArtistAnchor(db::ObjectPtr<db::Artist> artist, bool setText = true);
|
std::unique_ptr<Wt::WAnchor> createArtistAnchor(db::ObjectPtr<db::Artist> artist, bool setText = true);
|
||||||
Wt::WLink createReleaseLink(db::ObjectPtr<db::Release> release);
|
Wt::WLink createReleaseLink(db::ObjectPtr<db::Release> release);
|
||||||
std::unique_ptr<Wt::WAnchor> createReleaseAnchor(db::ObjectPtr<db::Release> release, bool setText = true);
|
std::unique_ptr<Wt::WAnchor> createReleaseAnchor(db::ObjectPtr<db::Release> release, bool setText = true);
|
||||||
std::unique_ptr<Wt::WAnchor> createTrackListAnchor(db::ObjectPtr<db::TrackList> trackList, bool setText = true);
|
std::unique_ptr<Wt::WAnchor> createTrackListAnchor(db::ObjectPtr<db::TrackList> trackList, bool setText = true);
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WContainerWidget> createArtistAnchorList(const std::vector<db::ObjectPtr<db::Artist>>& artists, std::string_view cssAnchorClass = "link-success");
|
||||||
|
|
||||||
|
struct ArtistDisplayInfo
|
||||||
|
{
|
||||||
|
std::string displayName;
|
||||||
|
struct Entry
|
||||||
|
{
|
||||||
|
std::string displayName;
|
||||||
|
db::ObjectPtr<db::Artist> artist;
|
||||||
|
};
|
||||||
|
std::vector<Entry> entries;
|
||||||
|
};
|
||||||
|
ArtistDisplayInfo computeArtistDisplayInfo(db::ObjectPtr<db::Release> release);
|
||||||
|
ArtistDisplayInfo computeArtistDisplayInfo(db::ObjectPtr<db::Track> track, db::TrackArtistLinkType linkType);
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WContainerWidget> createArtistsAnchors(const ArtistDisplayInfo& artistDisplayInfo, std::string_view cssAnchorClass = "link-success");
|
||||||
|
std::unique_ptr<Wt::WContainerWidget> createArtistsAnchors(db::ObjectPtr<db::Track> track, db::TrackArtistLinkType linkType, std::string_view cssAnchorClass = "link-success");
|
||||||
|
std::unique_ptr<Wt::WContainerWidget> createArtistsAnchors(db::ObjectPtr<db::Release> release, std::string_view cssAnchorClass = "link-success");
|
||||||
|
|
||||||
|
std::map<Wt::WString, std::vector<db::ObjectPtr<db::Artist>>> getArtistsByRole(db::ObjectPtr<db::Track> track, core::EnumSet<db::TrackArtistLinkType> artistLinkTypes = {});
|
||||||
|
std::map<Wt::WString, std::vector<db::ObjectPtr<db::Artist>>> getTrackArtistsByRole(db::ObjectPtr<db::Release> release);
|
||||||
} // namespace lms::ui::utils
|
} // namespace lms::ui::utils
|
||||||
|
|||||||
@@ -20,14 +20,16 @@
|
|||||||
#include "ArtistCollector.hpp"
|
#include "ArtistCollector.hpp"
|
||||||
|
|
||||||
#include "core/Service.hpp"
|
#include "core/Service.hpp"
|
||||||
|
#include "core/Utils.hpp"
|
||||||
|
|
||||||
#include "database/Session.hpp"
|
#include "database/Session.hpp"
|
||||||
#include "database/objects/Artist.hpp"
|
#include "database/objects/Artist.hpp"
|
||||||
#include "database/objects/TrackList.hpp"
|
#include "database/objects/TrackList.hpp"
|
||||||
|
#include "database/objects/Types.hpp"
|
||||||
#include "database/objects/User.hpp"
|
#include "database/objects/User.hpp"
|
||||||
#include "services/feedback/IFeedbackService.hpp"
|
#include "services/feedback/IFeedbackService.hpp"
|
||||||
#include "services/scrobbling/IScrobblingService.hpp"
|
#include "services/scrobbling/IScrobblingService.hpp"
|
||||||
|
|
||||||
#include "Filters.hpp"
|
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
|
||||||
namespace lms::ui
|
namespace lms::ui
|
||||||
@@ -53,9 +55,14 @@ namespace lms::ui
|
|||||||
params.setFilters(getDbFilters());
|
params.setFilters(getDbFilters());
|
||||||
params.setUser(LmsApp->getUserId());
|
params.setUser(LmsApp->getUserId());
|
||||||
params.setKeywords(getSearchKeywords());
|
params.setKeywords(getSearchKeywords());
|
||||||
params.setLinkType(_linkType);
|
|
||||||
params.setSortMethod(db::ArtistSortMethod::StarredDateDesc);
|
params.setSortMethod(db::ArtistSortMethod::StarredDateDesc);
|
||||||
params.setRange(range);
|
params.setRange(range);
|
||||||
|
std::visit(core::utils::overloads{
|
||||||
|
[&](AllArtistsTag) {},
|
||||||
|
[&](ReleaseArtistsTag) { params.setReleaseArtistsOnly(true); },
|
||||||
|
[&](db::TrackArtistLinkType trackArtistLinkType) { params.setTrackArtistLinkType(trackArtistLinkType); } },
|
||||||
|
_artistType);
|
||||||
|
|
||||||
artists = feedbackService.findStarredArtists(params);
|
artists = feedbackService.findStarredArtists(params);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -66,8 +73,12 @@ namespace lms::ui
|
|||||||
params.setUser(LmsApp->getUserId());
|
params.setUser(LmsApp->getUserId());
|
||||||
params.setFilters(getDbFilters());
|
params.setFilters(getDbFilters());
|
||||||
params.setKeywords(getSearchKeywords());
|
params.setKeywords(getSearchKeywords());
|
||||||
params.setLinkType(_linkType);
|
|
||||||
params.setRange(range);
|
params.setRange(range);
|
||||||
|
std::visit(core::utils::overloads{
|
||||||
|
[&](AllArtistsTag) {},
|
||||||
|
[&](ReleaseArtistsTag) { params.setReleaseArtistsOnly(true); },
|
||||||
|
[&](db::TrackArtistLinkType trackArtistLinkType) { params.setTrackArtistLinkType(trackArtistLinkType); } },
|
||||||
|
_artistType);
|
||||||
|
|
||||||
artists = scrobblingService.getRecentArtists(params);
|
artists = scrobblingService.getRecentArtists(params);
|
||||||
break;
|
break;
|
||||||
@@ -79,8 +90,12 @@ namespace lms::ui
|
|||||||
params.setUser(LmsApp->getUserId());
|
params.setUser(LmsApp->getUserId());
|
||||||
params.setFilters(getDbFilters());
|
params.setFilters(getDbFilters());
|
||||||
params.setKeywords(getSearchKeywords());
|
params.setKeywords(getSearchKeywords());
|
||||||
params.setLinkType(_linkType);
|
|
||||||
params.setRange(range);
|
params.setRange(range);
|
||||||
|
std::visit(core::utils::overloads{
|
||||||
|
[&](AllArtistsTag) {},
|
||||||
|
[&](ReleaseArtistsTag) { params.setReleaseArtistsOnly(true); },
|
||||||
|
[&](db::TrackArtistLinkType trackArtistLinkType) { params.setTrackArtistLinkType(trackArtistLinkType); } },
|
||||||
|
_artistType);
|
||||||
|
|
||||||
artists = scrobblingService.getTopArtists(params);
|
artists = scrobblingService.getTopArtists(params);
|
||||||
break;
|
break;
|
||||||
@@ -91,9 +106,13 @@ namespace lms::ui
|
|||||||
db::Artist::FindParameters params;
|
db::Artist::FindParameters params;
|
||||||
params.setFilters(getDbFilters());
|
params.setFilters(getDbFilters());
|
||||||
params.setKeywords(getSearchKeywords());
|
params.setKeywords(getSearchKeywords());
|
||||||
params.setLinkType(_linkType);
|
|
||||||
params.setSortMethod(db::ArtistSortMethod::AddedDesc);
|
params.setSortMethod(db::ArtistSortMethod::AddedDesc);
|
||||||
params.setRange(range);
|
params.setRange(range);
|
||||||
|
std::visit(core::utils::overloads{
|
||||||
|
[&](AllArtistsTag) {},
|
||||||
|
[&](ReleaseArtistsTag) { params.setReleaseArtistsOnly(true); },
|
||||||
|
[&](db::TrackArtistLinkType trackArtistLinkType) { params.setTrackArtistLinkType(trackArtistLinkType); } },
|
||||||
|
_artistType);
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||||
@@ -107,9 +126,13 @@ namespace lms::ui
|
|||||||
db::Artist::FindParameters params;
|
db::Artist::FindParameters params;
|
||||||
params.setFilters(getDbFilters());
|
params.setFilters(getDbFilters());
|
||||||
params.setKeywords(getSearchKeywords());
|
params.setKeywords(getSearchKeywords());
|
||||||
params.setLinkType(_linkType);
|
|
||||||
params.setSortMethod(db::ArtistSortMethod::LastWrittenDesc);
|
params.setSortMethod(db::ArtistSortMethod::LastWrittenDesc);
|
||||||
params.setRange(range);
|
params.setRange(range);
|
||||||
|
std::visit(core::utils::overloads{
|
||||||
|
[&](AllArtistsTag) {},
|
||||||
|
[&](ReleaseArtistsTag) { params.setReleaseArtistsOnly(true); },
|
||||||
|
[&](db::TrackArtistLinkType trackArtistLinkType) { params.setTrackArtistLinkType(trackArtistLinkType); } },
|
||||||
|
_artistType);
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||||
@@ -123,9 +146,13 @@ namespace lms::ui
|
|||||||
db::Artist::FindParameters params;
|
db::Artist::FindParameters params;
|
||||||
params.setFilters(getDbFilters());
|
params.setFilters(getDbFilters());
|
||||||
params.setKeywords(getSearchKeywords());
|
params.setKeywords(getSearchKeywords());
|
||||||
params.setLinkType(_linkType);
|
|
||||||
params.setSortMethod(db::ArtistSortMethod::SortName);
|
params.setSortMethod(db::ArtistSortMethod::SortName);
|
||||||
params.setRange(range);
|
params.setRange(range);
|
||||||
|
std::visit(core::utils::overloads{
|
||||||
|
[&](AllArtistsTag) {},
|
||||||
|
[&](ReleaseArtistsTag) { params.setReleaseArtistsOnly(true); },
|
||||||
|
[&](db::TrackArtistLinkType trackArtistLinkType) { params.setTrackArtistLinkType(trackArtistLinkType); } },
|
||||||
|
_artistType);
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||||
@@ -150,9 +177,13 @@ namespace lms::ui
|
|||||||
db::Artist::FindParameters params;
|
db::Artist::FindParameters params;
|
||||||
params.setFilters(getDbFilters());
|
params.setFilters(getDbFilters());
|
||||||
params.setKeywords(getSearchKeywords());
|
params.setKeywords(getSearchKeywords());
|
||||||
params.setLinkType(_linkType);
|
|
||||||
params.setSortMethod(db::ArtistSortMethod::Random);
|
params.setSortMethod(db::ArtistSortMethod::Random);
|
||||||
params.setRange(db::Range{ 0, getMaxCount() });
|
params.setRange(db::Range{ 0, getMaxCount() });
|
||||||
|
std::visit(core::utils::overloads{
|
||||||
|
[&](AllArtistsTag) {},
|
||||||
|
[&](ReleaseArtistsTag) { params.setReleaseArtistsOnly(true); },
|
||||||
|
[&](db::TrackArtistLinkType trackArtistLinkType) { params.setTrackArtistLinkType(trackArtistLinkType); } },
|
||||||
|
_artistType);
|
||||||
|
|
||||||
{
|
{
|
||||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||||
|
|||||||
@@ -22,8 +22,8 @@
|
|||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
#include "database/objects/ArtistId.hpp"
|
#include "database/objects/ArtistId.hpp"
|
||||||
#include "database/objects/Types.hpp"
|
|
||||||
|
|
||||||
|
#include "ArtistType.hpp"
|
||||||
#include "DatabaseCollectorBase.hpp"
|
#include "DatabaseCollectorBase.hpp"
|
||||||
|
|
||||||
namespace lms::db
|
namespace lms::db
|
||||||
@@ -40,11 +40,11 @@ namespace lms::ui
|
|||||||
|
|
||||||
db::RangeResults<db::ArtistId> get(std::optional<db::Range> range = std::nullopt);
|
db::RangeResults<db::ArtistId> get(std::optional<db::Range> range = std::nullopt);
|
||||||
void reset() { _randomArtists.reset(); }
|
void reset() { _randomArtists.reset(); }
|
||||||
void setArtistLinkType(std::optional<db::TrackArtistLinkType> linkType) { _linkType = linkType; }
|
void setArtistType(ArtistType artistType) { _artistType = artistType; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
db::RangeResults<db::ArtistId> getRandomArtists(Range range);
|
db::RangeResults<db::ArtistId> getRandomArtists(Range range);
|
||||||
std::optional<db::RangeResults<db::ArtistId>> _randomArtists;
|
std::optional<db::RangeResults<db::ArtistId>> _randomArtists;
|
||||||
std::optional<db::TrackArtistLinkType> _linkType;
|
ArtistType _artistType;
|
||||||
};
|
};
|
||||||
} // namespace lms::ui
|
} // namespace lms::ui
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "ArtistType.hpp"
|
||||||
|
|
||||||
|
#include "core/Utils.hpp"
|
||||||
|
|
||||||
|
namespace lms::core::stringUtils
|
||||||
|
{
|
||||||
|
template<>
|
||||||
|
std::optional<lms::ui::ArtistType> readAs(std::string_view str)
|
||||||
|
{
|
||||||
|
if (str == "all")
|
||||||
|
return lms::ui::AllArtistsTag{};
|
||||||
|
if (str == "release")
|
||||||
|
return lms::ui::ReleaseArtistsTag{};
|
||||||
|
|
||||||
|
using UnderlyingType = std::underlying_type_t<lms::db::TrackArtistLinkType>;
|
||||||
|
std::optional<UnderlyingType> underlyingValue{ readAs<UnderlyingType>(str) };
|
||||||
|
if (!underlyingValue)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return lms::db::TrackArtistLinkType{ *underlyingValue };
|
||||||
|
}
|
||||||
|
} // namespace lms::core::stringUtils
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
std::string to_string(const lms::ui::ArtistType& artistType)
|
||||||
|
{
|
||||||
|
std::string res;
|
||||||
|
std::visit(lms::core::utils::overloads{
|
||||||
|
[&](lms::ui::AllArtistsTag) { res = "all"; },
|
||||||
|
[&](lms::ui::ReleaseArtistsTag) { res = "release"; },
|
||||||
|
[&](lms::db::TrackArtistLinkType linkType) { res = std::to_string(static_cast<std::underlying_type_t<lms::db::TrackArtistLinkType>>(linkType)); } },
|
||||||
|
artistType);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
} // namespace std
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#include "core/String.hpp"
|
||||||
|
|
||||||
|
#include "database/objects/Types.hpp"
|
||||||
|
|
||||||
|
namespace lms::ui
|
||||||
|
{
|
||||||
|
struct AllArtistsTag
|
||||||
|
{
|
||||||
|
bool operator==(const AllArtistsTag&) const = default;
|
||||||
|
};
|
||||||
|
struct ReleaseArtistsTag
|
||||||
|
{
|
||||||
|
bool operator==(const ReleaseArtistsTag&) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ArtistType = std::variant<AllArtistsTag, ReleaseArtistsTag, db::TrackArtistLinkType>;
|
||||||
|
|
||||||
|
} // namespace lms::ui
|
||||||
|
|
||||||
|
namespace lms::core::stringUtils
|
||||||
|
{
|
||||||
|
template<>
|
||||||
|
[[nodiscard]] std::optional<lms::ui::ArtistType> readAs(std::string_view str);
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
[[nodiscard]] std::string to_string(const lms::ui::ArtistType& artistType);
|
||||||
|
};
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2024 Emeric Poupon
|
||||||
|
*
|
||||||
|
* This file is part of LMS.
|
||||||
|
*
|
||||||
|
* LMS is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* LMS is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ArtistType.hpp"
|
||||||
|
#include "DropDownMenuSelector.hpp"
|
||||||
|
|
||||||
|
namespace lms::ui
|
||||||
|
{
|
||||||
|
class ArtistTypeSelector : public DropDownMenuSelector<ArtistType>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
ArtistTypeSelector(ArtistType defaultArtistType)
|
||||||
|
: DropDownMenuSelector{ Wt::WString::tr("Lms.Explore.Artists.template.artist-type-selector"), defaultArtistType }
|
||||||
|
{
|
||||||
|
bindItem("artist-type-all", Wt::WString::tr("Lms.Explore.Artists.all"), AllArtistsTag{});
|
||||||
|
bindItem("artist-type-release-artist", Wt::WString::trn("Lms.Explore.Artists.release-artist", 2), ReleaseArtistsTag{});
|
||||||
|
bindItem("artist-type-track-artist", Wt::WString::trn("Lms.Explore.Artists.track-artist", 2), db::TrackArtistLinkType::Artist);
|
||||||
|
bindItem("artist-type-composer", Wt::WString::trn("Lms.Explore.Artists.composer", 2), db::TrackArtistLinkType::Composer);
|
||||||
|
bindItem("artist-type-conductor", Wt::WString::trn("Lms.Explore.Artists.conductor", 2), db::TrackArtistLinkType::Conductor);
|
||||||
|
bindItem("artist-type-lyricist", Wt::WString::trn("Lms.Explore.Artists.lyricist", 2), db::TrackArtistLinkType::Lyricist);
|
||||||
|
bindItem("artist-type-mixer", Wt::WString::trn("Lms.Explore.Artists.mixer", 2), db::TrackArtistLinkType::Mixer);
|
||||||
|
bindItem("artist-type-performer", Wt::WString::trn("Lms.Explore.Artists.performer", 2), db::TrackArtistLinkType::Performer);
|
||||||
|
bindItem("artist-type-producer", Wt::WString::trn("Lms.Explore.Artists.producer", 2), db::TrackArtistLinkType::Producer);
|
||||||
|
bindItem("artist-type-remixer", Wt::WString::trn("Lms.Explore.Artists.remixer", 2), db::TrackArtistLinkType::Remixer);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace lms::ui
|
||||||
@@ -97,11 +97,12 @@ namespace lms::ui
|
|||||||
clear();
|
clear();
|
||||||
_artistId = {};
|
_artistId = {};
|
||||||
_trackContainer = nullptr;
|
_trackContainer = nullptr;
|
||||||
|
_artistName = {};
|
||||||
|
|
||||||
if (!artistId)
|
if (!artistId)
|
||||||
throw ArtistNotFoundException{};
|
throw ArtistNotFoundException{};
|
||||||
|
|
||||||
const auto similarArtistIds{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(*artistId, { db::TrackArtistLinkType::Artist, db::TrackArtistLinkType::ReleaseArtist }, 6) };
|
const auto similarArtistIds{ core::Service<recommendation::IRecommendationService>::get()->getSimilarArtists(*artistId, { db::TrackArtistLinkType::Artist }, 6) };
|
||||||
|
|
||||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||||
|
|
||||||
@@ -111,6 +112,7 @@ namespace lms::ui
|
|||||||
|
|
||||||
LmsApp->setTitle(artist->getName());
|
LmsApp->setTitle(artist->getName());
|
||||||
_artistId = *artistId;
|
_artistId = *artistId;
|
||||||
|
_artistName = artist->getName();
|
||||||
|
|
||||||
refreshArtwork(artist->getPreferredArtworkId());
|
refreshArtwork(artist->getPreferredArtworkId());
|
||||||
refreshArtistInfo();
|
refreshArtistInfo();
|
||||||
@@ -139,7 +141,7 @@ namespace lms::ui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bindString("name", Wt::WString::fromUTF8(artist->getName()), Wt::TextFormat::Plain);
|
bindString("name", Wt::WString::fromUTF8(_artistName), Wt::TextFormat::Plain);
|
||||||
|
|
||||||
bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)
|
bindNew<Wt::WPushButton>("play-btn", Wt::WString::tr("Lms.Explore.play"), Wt::TextFormat::XHTML)
|
||||||
->clicked()
|
->clicked()
|
||||||
@@ -230,20 +232,24 @@ namespace lms::ui
|
|||||||
_releaseContainers.clear();
|
_releaseContainers.clear();
|
||||||
|
|
||||||
db::Release::FindParameters params;
|
db::Release::FindParameters params;
|
||||||
params.setArtist(_artistId, { db::TrackArtistLinkType::ReleaseArtist }, {});
|
params.setArtist(_artistId);
|
||||||
params.setSortMethod(LmsApp->getUser()->getUIArtistReleaseSortMethod());
|
params.setSortMethod(LmsApp->getUser()->getUIArtistReleaseSortMethod());
|
||||||
|
|
||||||
const auto releases{ db::Release::findIds(LmsApp->getDbSession(), params) };
|
// first pass: gather all ids and sort by release type
|
||||||
if (!releases.results.empty())
|
bool hasReleaseWithDifferentName{};
|
||||||
{
|
db::Release::find(LmsApp->getDbSession(), params, [&](const db::Release::pointer& release) {
|
||||||
// first pass: gather all ids and sort by release type
|
ReleaseType releaseType{ parseReleaseType(release->getReleaseTypeNames()) };
|
||||||
for (const db::ReleaseId releaseId : releases.results)
|
_releaseContainers[releaseType].releases.push_back(release->getId());
|
||||||
{
|
|
||||||
const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), releaseId) };
|
|
||||||
|
|
||||||
ReleaseType releaseType{ parseReleaseType(release->getReleaseTypeNames()) };
|
if (release->getArtistDisplayName() != _artistName)
|
||||||
_releaseContainers[releaseType].releases.push_back(releaseId);
|
hasReleaseWithDifferentName = true;
|
||||||
}
|
});
|
||||||
|
|
||||||
|
if (!_releaseContainers.empty())
|
||||||
|
{
|
||||||
|
core::EnumSet displayOptions{ releaseListHelpers::DisplayOptions::ShowYearAndOriginalYear };
|
||||||
|
if (hasReleaseWithDifferentName)
|
||||||
|
displayOptions.insert(releaseListHelpers::DisplayOptions::ShowArtist);
|
||||||
|
|
||||||
// second pass: construct widgets
|
// second pass: construct widgets
|
||||||
Wt::WContainerWidget* releaseContainers{ bindNew<Wt::WContainerWidget>("release-containers") };
|
Wt::WContainerWidget* releaseContainers{ bindNew<Wt::WContainerWidget>("release-containers") };
|
||||||
@@ -253,8 +259,8 @@ namespace lms::ui
|
|||||||
|
|
||||||
releaseContainer->bindString("release-type", releaseHelpers::buildReleaseTypeString(releaseType));
|
releaseContainer->bindString("release-type", releaseHelpers::buildReleaseTypeString(releaseType));
|
||||||
releases.container = releaseContainer->bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
|
releases.container = releaseContainer->bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
|
||||||
releases.container->onRequestElements.connect(this, [this, &releases = releases] {
|
releases.container->onRequestElements.connect(this, [&, displayOptions] {
|
||||||
addSomeReleases(releases);
|
addSomeReleases(releases, displayOptions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,35 +272,24 @@ namespace lms::ui
|
|||||||
|
|
||||||
void Artist::refreshAppearsOnReleases()
|
void Artist::refreshAppearsOnReleases()
|
||||||
{
|
{
|
||||||
constexpr core::EnumSet<db::TrackArtistLinkType> types{
|
|
||||||
db::TrackArtistLinkType::Artist,
|
|
||||||
db::TrackArtistLinkType::Arranger,
|
|
||||||
db::TrackArtistLinkType::Composer,
|
|
||||||
db::TrackArtistLinkType::Conductor,
|
|
||||||
db::TrackArtistLinkType::Lyricist,
|
|
||||||
db::TrackArtistLinkType::Mixer,
|
|
||||||
db::TrackArtistLinkType::Performer,
|
|
||||||
db::TrackArtistLinkType::Producer,
|
|
||||||
db::TrackArtistLinkType::Remixer,
|
|
||||||
db::TrackArtistLinkType::Writer,
|
|
||||||
};
|
|
||||||
|
|
||||||
_appearsOnReleaseContainer = {};
|
_appearsOnReleaseContainer = {};
|
||||||
|
|
||||||
db::Release::FindParameters params;
|
db::Release::FindParameters params;
|
||||||
params.setFilters(_filters.getDbFilters());
|
params.setTrackArtist(_artistId);
|
||||||
params.setArtist(_artistId, types, { db::TrackArtistLinkType::ReleaseArtist });
|
|
||||||
params.setSortMethod(db::ReleaseSortMethod::OriginalDateDesc);
|
params.setSortMethod(db::ReleaseSortMethod::OriginalDateDesc);
|
||||||
|
|
||||||
const auto releases{ db::Release::findIds(LmsApp->getDbSession(), params) };
|
db::Release::find(LmsApp->getDbSession(), params, [this](const db::Release::pointer& release) {
|
||||||
if (!releases.results.empty())
|
if (!release->hasArtist(_artistId))
|
||||||
|
_appearsOnReleaseContainer.releases.push_back(release->getId());
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!_appearsOnReleaseContainer.releases.empty())
|
||||||
{
|
{
|
||||||
Wt::WTemplate* releaseContainer{ bindNew<Wt::WTemplate>("appears-on-releases", Wt::WString::tr("Lms.Explore.Artist.template.release-container")) };
|
Wt::WTemplate* releaseContainer{ bindNew<Wt::WTemplate>("appears-on-releases", Wt::WString::tr("Lms.Explore.Artist.template.release-container")) };
|
||||||
releaseContainer->bindString("release-type", Wt::WString::tr("Lms.Explore.Artist.appears-on"));
|
releaseContainer->bindString("release-type", Wt::WString::tr("Lms.Explore.Artist.appears-on"));
|
||||||
_appearsOnReleaseContainer.releases = releases.results;
|
|
||||||
_appearsOnReleaseContainer.container = releaseContainer->bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
|
_appearsOnReleaseContainer.container = releaseContainer->bindNew<InfiniteScrollingContainer>("releases", Wt::WString::tr("Lms.Explore.Releases.template.container"));
|
||||||
_appearsOnReleaseContainer.container->onRequestElements.connect(this, [this] {
|
_appearsOnReleaseContainer.container->onRequestElements.connect(this, [this] {
|
||||||
addSomeReleases(_appearsOnReleaseContainer);
|
addSomeReleases(_appearsOnReleaseContainer, { releaseListHelpers::DisplayOptions::ShowYearAndOriginalYear, releaseListHelpers::DisplayOptions::ShowArtist });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -343,7 +338,7 @@ namespace lms::ui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Artist::addSomeReleases(ReleaseContainer& releaseContainer)
|
void Artist::addSomeReleases(ReleaseContainer& releaseContainer, core::EnumSet<releaseListHelpers::DisplayOptions> displayOptions)
|
||||||
{
|
{
|
||||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||||
|
|
||||||
@@ -352,7 +347,7 @@ namespace lms::ui
|
|||||||
for (std::size_t i{ static_cast<std::size_t>(releaseContainer.container->getCount()) }; i < releaseContainer.releases.size(); ++i)
|
for (std::size_t i{ static_cast<std::size_t>(releaseContainer.container->getCount()) }; i < releaseContainer.releases.size(); ++i)
|
||||||
{
|
{
|
||||||
const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), releaseContainer.releases[i]) };
|
const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), releaseContainer.releases[i]) };
|
||||||
releaseContainer.container->add(releaseListHelpers::createEntryForArtist(release, artist));
|
releaseContainer.container->add(releaseListHelpers::createEntry(release, displayOptions));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -366,7 +361,6 @@ namespace lms::ui
|
|||||||
const db::Range range{ static_cast<std::size_t>(_trackContainer->getCount()), _tracksBatchSize };
|
const db::Range range{ static_cast<std::size_t>(_trackContainer->getCount()), _tracksBatchSize };
|
||||||
|
|
||||||
db::Track::FindParameters params;
|
db::Track::FindParameters params;
|
||||||
params.setFilters(_filters.getDbFilters());
|
|
||||||
params.setArtist(_artistId);
|
params.setArtist(_artistId);
|
||||||
params.setRange(range);
|
params.setRange(range);
|
||||||
params.setSortMethod(db::TrackSortMethod::Name);
|
params.setSortMethod(db::TrackSortMethod::Name);
|
||||||
@@ -390,5 +384,4 @@ namespace lms::ui
|
|||||||
|
|
||||||
return areTracksAdded;
|
return areTracksAdded;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace lms::ui
|
} // namespace lms::ui
|
||||||
|
|||||||
@@ -21,11 +21,14 @@
|
|||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
|
||||||
|
#include "core/EnumSet.hpp"
|
||||||
|
|
||||||
#include "database/Object.hpp"
|
#include "database/Object.hpp"
|
||||||
#include "database/objects/ArtistId.hpp"
|
#include "database/objects/ArtistId.hpp"
|
||||||
#include "database/objects/ArtworkId.hpp"
|
#include "database/objects/ArtworkId.hpp"
|
||||||
#include "database/objects/ReleaseId.hpp"
|
#include "database/objects/ReleaseId.hpp"
|
||||||
|
|
||||||
|
#include "ReleaseHelpers.hpp"
|
||||||
#include "ReleaseTypes.hpp"
|
#include "ReleaseTypes.hpp"
|
||||||
#include "common/Template.hpp"
|
#include "common/Template.hpp"
|
||||||
|
|
||||||
@@ -57,7 +60,7 @@ namespace lms::ui
|
|||||||
void refreshLinks(const db::ObjectPtr<db::Artist>& artist);
|
void refreshLinks(const db::ObjectPtr<db::Artist>& artist);
|
||||||
|
|
||||||
struct ReleaseContainer;
|
struct ReleaseContainer;
|
||||||
void addSomeReleases(ReleaseContainer& releaseContainer);
|
void addSomeReleases(ReleaseContainer& releaseContainer, core::EnumSet<releaseListHelpers::DisplayOptions> displayOptions);
|
||||||
bool addSomeNonReleaseTracks();
|
bool addSomeNonReleaseTracks();
|
||||||
static constexpr std::size_t _releasesBatchSize{ 6 };
|
static constexpr std::size_t _releasesBatchSize{ 6 };
|
||||||
static constexpr std::size_t _tracksBatchSize{ 6 };
|
static constexpr std::size_t _tracksBatchSize{ 6 };
|
||||||
@@ -76,5 +79,6 @@ namespace lms::ui
|
|||||||
ReleaseContainer _appearsOnReleaseContainer{};
|
ReleaseContainer _appearsOnReleaseContainer{};
|
||||||
InfiniteScrollingContainer* _trackContainer{};
|
InfiniteScrollingContainer* _trackContainer{};
|
||||||
db::ArtistId _artistId{};
|
db::ArtistId _artistId{};
|
||||||
|
std::string _artistName;
|
||||||
};
|
};
|
||||||
} // namespace lms::ui
|
} // namespace lms::ui
|
||||||
|
|||||||
@@ -27,12 +27,14 @@
|
|||||||
#include "database/objects/TrackArtistLink.hpp"
|
#include "database/objects/TrackArtistLink.hpp"
|
||||||
|
|
||||||
#include "ArtistListHelpers.hpp"
|
#include "ArtistListHelpers.hpp"
|
||||||
|
#include "ArtistTypeSelector.hpp"
|
||||||
#include "Filters.hpp"
|
#include "Filters.hpp"
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
#include "SortModeSelector.hpp"
|
#include "SortModeSelector.hpp"
|
||||||
#include "State.hpp"
|
#include "State.hpp"
|
||||||
#include "TrackArtistLinkTypeSelector.hpp"
|
|
||||||
#include "common/InfiniteScrollingContainer.hpp"
|
#include "common/InfiniteScrollingContainer.hpp"
|
||||||
|
#include "database/objects/Types.hpp"
|
||||||
|
#include "explore/ArtistType.hpp"
|
||||||
|
|
||||||
namespace lms::ui
|
namespace lms::ui
|
||||||
{
|
{
|
||||||
@@ -61,13 +63,13 @@ namespace lms::ui
|
|||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const std::optional<db::TrackArtistLinkType> linkType{ state::readValue<db::TrackArtistLinkType>("artists_link_type") };
|
ArtistType artistType{ state::readValue<ArtistType>("artists_type").value_or(_defaultArtistType) };
|
||||||
_artistCollector.setArtistLinkType(linkType);
|
_artistCollector.setArtistType(artistType);
|
||||||
|
|
||||||
TrackArtistLinkTypeSelector* linkTypeSelector{ bindNew<TrackArtistLinkTypeSelector>("link-type", linkType) };
|
ArtistTypeSelector* artistTypeSelector{ bindNew<ArtistTypeSelector>("artist-type", artistType) };
|
||||||
linkTypeSelector->itemSelected.connect([this](std::optional<db::TrackArtistLinkType> newLinkType) {
|
artistTypeSelector->itemSelected.connect([this](ArtistType newArtistType) {
|
||||||
state::writeValue<db::TrackArtistLinkType>("artists_link_type", newLinkType);
|
state::writeValue<ArtistType>("artists_type", newArtistType);
|
||||||
refreshView(newLinkType);
|
refreshView(newArtistType);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,9 +97,9 @@ namespace lms::ui
|
|||||||
refreshView();
|
refreshView();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Artists::refreshView(std::optional<db::TrackArtistLinkType> linkType)
|
void Artists::refreshView(ArtistType artistType)
|
||||||
{
|
{
|
||||||
_artistCollector.setArtistLinkType(linkType);
|
_artistCollector.setArtistType(artistType);
|
||||||
refreshView();
|
refreshView();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,14 +19,11 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include <Wt/WComboBox.h>
|
#include <Wt/WComboBox.h>
|
||||||
#include <Wt/WTemplate.h>
|
#include <Wt/WTemplate.h>
|
||||||
|
|
||||||
#include "database/Types.hpp"
|
|
||||||
|
|
||||||
#include "ArtistCollector.hpp"
|
#include "ArtistCollector.hpp"
|
||||||
|
#include "ArtistType.hpp"
|
||||||
#include "common/Template.hpp"
|
#include "common/Template.hpp"
|
||||||
|
|
||||||
namespace lms::ui
|
namespace lms::ui
|
||||||
@@ -42,7 +39,7 @@ namespace lms::ui
|
|||||||
private:
|
private:
|
||||||
void refreshView();
|
void refreshView();
|
||||||
void refreshView(ArtistCollector::Mode mode);
|
void refreshView(ArtistCollector::Mode mode);
|
||||||
void refreshView(std::optional<db::TrackArtistLinkType> linkType);
|
void refreshView(ArtistType artistType);
|
||||||
void refreshView(const Wt::WString& searchText);
|
void refreshView(const Wt::WString& searchText);
|
||||||
void addSome();
|
void addSome();
|
||||||
|
|
||||||
@@ -53,5 +50,6 @@ namespace lms::ui
|
|||||||
InfiniteScrollingContainer* _container{};
|
InfiniteScrollingContainer* _container{};
|
||||||
ArtistCollector _artistCollector;
|
ArtistCollector _artistCollector;
|
||||||
static constexpr ArtistCollector::Mode _defaultSortMode{ ArtistCollector::Mode::Random };
|
static constexpr ArtistCollector::Mode _defaultSortMode{ ArtistCollector::Mode::Random };
|
||||||
|
static constexpr ArtistType _defaultArtistType{ ReleaseArtistsTag{} };
|
||||||
};
|
};
|
||||||
} // namespace lms::ui
|
} // namespace lms::ui
|
||||||
|
|||||||
@@ -34,80 +34,53 @@
|
|||||||
|
|
||||||
namespace lms::ui::releaseListHelpers
|
namespace lms::ui::releaseListHelpers
|
||||||
{
|
{
|
||||||
namespace
|
std::unique_ptr<Wt::WTemplate> createEntry(const db::Release::pointer& release, core::EnumSet<DisplayOptions> options)
|
||||||
{
|
{
|
||||||
enum class ReleaseOptions
|
auto entry{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Releases.template.entry-grid")) };
|
||||||
|
|
||||||
|
entry->bindWidget("release-name", utils::createReleaseAnchor(release));
|
||||||
|
entry->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
|
|
||||||
{
|
{
|
||||||
ShowArtist,
|
Wt::WAnchor* anchor{ entry->bindWidget("cover", utils::createReleaseAnchor(release, false)) };
|
||||||
ShowYearAndOriginalYear,
|
|
||||||
ShowYear,
|
|
||||||
};
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WTemplate> createEntryInternal(const db::Release::pointer& release, const db::Artist::pointer& artist, core::EnumSet<ReleaseOptions> options)
|
std::unique_ptr<Wt::WImage> image;
|
||||||
{
|
if (release->getPreferredArtworkId().isValid())
|
||||||
auto entry{ std::make_unique<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Releases.template.entry-grid")) };
|
image = utils::createArtworkImage(release->getPreferredArtworkId(), ArtworkResource::DefaultArtworkType::Release, ArtworkResource::Size::Large);
|
||||||
|
else
|
||||||
|
image = utils::createDefaultArtworkImage(ArtworkResource::DefaultArtworkType::Release);
|
||||||
|
|
||||||
entry->bindWidget("release-name", utils::createReleaseAnchor(release));
|
image->addStyleClass("Lms-cover-release Lms-cover-anchor rounded"); // hack
|
||||||
entry->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
anchor->setImage(std::move(image));
|
||||||
|
|
||||||
{
|
|
||||||
Wt::WAnchor* anchor{ entry->bindWidget("cover", utils::createReleaseAnchor(release, false)) };
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WImage> image;
|
|
||||||
if (release->getPreferredArtworkId().isValid())
|
|
||||||
image = utils::createArtworkImage(release->getPreferredArtworkId(), ArtworkResource::DefaultArtworkType::Release, ArtworkResource::Size::Large);
|
|
||||||
else
|
|
||||||
image = utils::createDefaultArtworkImage(ArtworkResource::DefaultArtworkType::Release);
|
|
||||||
|
|
||||||
image->addStyleClass("Lms-cover-release Lms-cover-anchor rounded"); // hack
|
|
||||||
anchor->setImage(std::move(image));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.contains(ReleaseOptions::ShowArtist))
|
|
||||||
{
|
|
||||||
auto artistAnchors{ utils::createArtistsAnchorsForRelease(release, artist ? artist->getId() : db::ArtistId{}, "link-secondary") };
|
|
||||||
if (artistAnchors)
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-artist", true);
|
|
||||||
entry->bindWidget("artist-name", std::move(artistAnchors));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.contains(ReleaseOptions::ShowYearAndOriginalYear))
|
|
||||||
{
|
|
||||||
Wt::WString year{ releaseHelpers::buildReleaseYearString(release->getYear(), release->getOriginalYear()) };
|
|
||||||
if (!year.empty())
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-year", true);
|
|
||||||
entry->bindString("year", year, Wt::TextFormat::Plain);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (options.contains(ReleaseOptions::ShowYear))
|
|
||||||
{
|
|
||||||
if (release->getYear())
|
|
||||||
{
|
|
||||||
entry->setCondition("if-has-year", true);
|
|
||||||
entry->bindInt("year", *release->getYear());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return entry;
|
|
||||||
}
|
}
|
||||||
} // namespace
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WTemplate> createEntry(const db::Release::pointer& release)
|
if (options.contains(DisplayOptions::ShowArtist))
|
||||||
{
|
{
|
||||||
return createEntryInternal(release, db::Artist::pointer{}, core::EnumSet<ReleaseOptions>{ ReleaseOptions::ShowArtist });
|
utils::ArtistDisplayInfo artistDisplayInfo{ utils::computeArtistDisplayInfo(release) };
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WTemplate> createEntryForArtist(const db::Release::pointer& release, const db::Artist::pointer& artist)
|
entry->setCondition("if-has-artist", true);
|
||||||
{
|
entry->bindWidget("artist-name", utils::createArtistsAnchors(artistDisplayInfo, "link-secondary"));
|
||||||
return createEntryInternal(release, artist, core::EnumSet<ReleaseOptions>{ ReleaseOptions::ShowArtist, ReleaseOptions::ShowYearAndOriginalYear });
|
}
|
||||||
}
|
|
||||||
|
|
||||||
std::unique_ptr<Wt::WTemplate> createEntryForOtherVersions(const db::ObjectPtr<db::Release>& release)
|
if (options.contains(DisplayOptions::ShowYearAndOriginalYear))
|
||||||
{
|
{
|
||||||
return createEntryInternal(release, db::Artist::pointer{}, core::EnumSet<ReleaseOptions>{ ReleaseOptions::ShowYear });
|
Wt::WString year{ releaseHelpers::buildReleaseYearString(release->getYear(), release->getOriginalYear()) };
|
||||||
|
if (!year.empty())
|
||||||
|
{
|
||||||
|
entry->setCondition("if-has-year", true);
|
||||||
|
entry->bindString("year", year, Wt::TextFormat::Plain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (options.contains(DisplayOptions::ShowYear))
|
||||||
|
{
|
||||||
|
if (release->getYear())
|
||||||
|
{
|
||||||
|
entry->setCondition("if-has-year", true);
|
||||||
|
entry->bindInt("year", *release->getYear());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry;
|
||||||
}
|
}
|
||||||
} // namespace lms::ui::releaseListHelpers
|
} // namespace lms::ui::releaseListHelpers
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,14 @@ namespace lms::db
|
|||||||
|
|
||||||
namespace lms::ui::releaseListHelpers
|
namespace lms::ui::releaseListHelpers
|
||||||
{
|
{
|
||||||
std::unique_ptr<Wt::WTemplate> createEntry(const db::ObjectPtr<db::Release>& release);
|
enum class DisplayOptions
|
||||||
std::unique_ptr<Wt::WTemplate> createEntryForArtist(const db::ObjectPtr<db::Release>& release, const db::ObjectPtr<db::Artist>& artist);
|
{
|
||||||
std::unique_ptr<Wt::WTemplate> createEntryForOtherVersions(const db::ObjectPtr<db::Release>& release);
|
ShowArtist,
|
||||||
|
ShowYearAndOriginalYear,
|
||||||
|
ShowYear,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<Wt::WTemplate> createEntry(const db::ObjectPtr<db::Release>& release, core::EnumSet<DisplayOptions> options);
|
||||||
} // namespace lms::ui::releaseListHelpers
|
} // namespace lms::ui::releaseListHelpers
|
||||||
|
|
||||||
namespace lms::ui::releaseHelpers
|
namespace lms::ui::releaseHelpers
|
||||||
|
|||||||
@@ -78,53 +78,14 @@ namespace lms::ui
|
|||||||
releaseInfo->bindString("release-type", releaseHelpers::buildReleaseTypeString(parseReleaseType(releaseTypeNames)));
|
releaseInfo->bindString("release-type", releaseHelpers::buildReleaseTypeString(parseReleaseType(releaseTypeNames)));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::map<Wt::WString, std::set<db::ArtistId>> artistMap;
|
if (const auto artistsByRole{ utils::getTrackArtistsByRole(release) }; !artistsByRole.empty())
|
||||||
|
|
||||||
auto addArtists = [&](db::TrackArtistLinkType linkType, const char* type) {
|
|
||||||
db::Artist::FindParameters params;
|
|
||||||
params.setRelease(releaseId);
|
|
||||||
params.setLinkType(linkType);
|
|
||||||
const auto artistIds{ db::Artist::findIds(LmsApp->getDbSession(), params) };
|
|
||||||
if (artistIds.results.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
Wt::WString typeStr{ Wt::WString::trn(type, artistIds.results.size()) };
|
|
||||||
for (db::ArtistId artistId : artistIds.results)
|
|
||||||
artistMap[typeStr].insert(artistId);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto addPerformerArtists = [&] {
|
|
||||||
db::TrackArtistLink::FindParameters params;
|
|
||||||
params.setRelease(releaseId);
|
|
||||||
params.setLinkType(db::TrackArtistLinkType::Performer);
|
|
||||||
db::TrackArtistLink::find(LmsApp->getDbSession(), params, [&](const db::TrackArtistLink::pointer& link) {
|
|
||||||
artistMap[std::string{ link->getSubType() }].insert(link->getArtist()->getId());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
addArtists(db::TrackArtistLinkType::Composer, "Lms.Explore.Artists.linktype-composer");
|
|
||||||
addArtists(db::TrackArtistLinkType::Conductor, "Lms.Explore.Artists.linktype-conductor");
|
|
||||||
addArtists(db::TrackArtistLinkType::Lyricist, "Lms.Explore.Artists.linktype-lyricist");
|
|
||||||
addArtists(db::TrackArtistLinkType::Mixer, "Lms.Explore.Artists.linktype-mixer");
|
|
||||||
addArtists(db::TrackArtistLinkType::Remixer, "Lms.Explore.Artists.linktype-remixer");
|
|
||||||
addArtists(db::TrackArtistLinkType::Producer, "Lms.Explore.Artists.linktype-producer");
|
|
||||||
addPerformerArtists();
|
|
||||||
|
|
||||||
if (auto itRolelessPerformers{ artistMap.find("") }; itRolelessPerformers != std::cend(artistMap))
|
|
||||||
{
|
|
||||||
Wt::WString performersStr{ Wt::WString::trn("Lms.Explore.Artists.linktype-performer", itRolelessPerformers->second.size()) };
|
|
||||||
artistMap[performersStr] = std::move(itRolelessPerformers->second);
|
|
||||||
artistMap.erase(itRolelessPerformers);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!artistMap.empty())
|
|
||||||
{
|
{
|
||||||
releaseInfo->setCondition("if-has-artist", true);
|
releaseInfo->setCondition("if-has-artist", true);
|
||||||
Wt::WContainerWidget* artistTable{ releaseInfo->bindNew<Wt::WContainerWidget>("artist-table") };
|
Wt::WContainerWidget* artistTable{ releaseInfo->bindNew<Wt::WContainerWidget>("artist-table") };
|
||||||
|
|
||||||
for (const auto& [role, artistIds] : artistMap)
|
for (const auto& [role, artists] : artistsByRole)
|
||||||
{
|
{
|
||||||
std::unique_ptr<Wt::WContainerWidget> artistContainer{ utils::createArtistAnchorList(std::vector(std::cbegin(artistIds), std::cend(artistIds))) };
|
std::unique_ptr<Wt::WContainerWidget> artistContainer{ utils::createArtistAnchorList(artists) };
|
||||||
auto artistsEntry{ std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists")) };
|
auto artistsEntry{ std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists")) };
|
||||||
artistsEntry->bindString("type", role);
|
artistsEntry->bindString("type", role);
|
||||||
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
||||||
@@ -190,7 +151,7 @@ namespace lms::ui
|
|||||||
return core::stringUtils::readAs<db::ReleaseId::ValueType>(wApp->internalPathNextPart("/release/"));
|
return core::stringUtils::readAs<db::ReleaseId::ValueType>(wApp->internalPathNextPart("/release/"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void fillTrackArtistLinks(Wt::WTemplate* trackEntry, db::TrackId trackId)
|
void fillTrackArtistLinks(Wt::WTemplate* trackEntry, const db::Track::pointer& track)
|
||||||
{
|
{
|
||||||
const db::User::pointer user{ LmsApp->getUser() };
|
const db::User::pointer user{ LmsApp->getUser() };
|
||||||
if (!user->getUIEnableInlineArtistRelationships())
|
if (!user->getUIEnableInlineArtistRelationships())
|
||||||
@@ -200,36 +161,31 @@ namespace lms::ui
|
|||||||
if (inlineArtistRelationships.empty())
|
if (inlineArtistRelationships.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const std::map<Wt::WString, std::set<db::ArtistId>> artistsByRole{ TrackListHelpers::getArtistsByRole(trackId, inlineArtistRelationships) };
|
const std::map<Wt::WString, std::vector<db::Artist::pointer>> artistsByRole{ utils::getArtistsByRole(track, inlineArtistRelationships) };
|
||||||
if (artistsByRole.empty())
|
if (artistsByRole.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
trackEntry->setCondition("if-has-artist-links", true);
|
trackEntry->setCondition("if-has-artist-links", true);
|
||||||
Wt::WContainerWidget* artistLinksContainer = trackEntry->bindNew<Wt::WContainerWidget>("artist-links");
|
Wt::WContainerWidget* artistLinksContainer{ trackEntry->bindNew<Wt::WContainerWidget>("artist-links") };
|
||||||
|
|
||||||
for (const auto& [role, artists] : artistsByRole)
|
for (const auto& [role, artists] : artistsByRole)
|
||||||
{
|
{
|
||||||
Wt::WTemplate* artistLinkEntry{ artistLinksContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.artist-links-entry")) };
|
Wt::WTemplate* artistLinkEntry{ artistLinksContainer->addNew<Wt::WTemplate>(Wt::WString::tr("Lms.Explore.Release.template.artist-links-entry")) };
|
||||||
artistLinkEntry->bindString("role", role, Wt::TextFormat::Plain);
|
artistLinkEntry->bindString("role", role, Wt::TextFormat::Plain);
|
||||||
artistLinkEntry->bindWidget("anchors", utils::createArtistAnchorList(std::vector<db::ArtistId>(std::cbegin(artists), std::cend(artists))));
|
artistLinkEntry->bindWidget("anchors", utils::createArtistAnchorList(artists));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool shouldDisplayTrackArtists(db::ReleaseId releaseId)
|
bool shouldDisplayTrackArtists(const db::Release::pointer& release)
|
||||||
{
|
{
|
||||||
bool res{ true };
|
bool res{ true };
|
||||||
|
|
||||||
db::Artist::FindParameters params;
|
// TODO, just compare artist display names
|
||||||
params.setRelease(releaseId);
|
const auto trackArtists{ release->getTrackArtists(db::TrackArtistLinkType::Artist) };
|
||||||
params.setLinkType(db::TrackArtistLinkType::ReleaseArtist);
|
if (trackArtists.size() == 1)
|
||||||
auto releaseArtists{ db::Artist::findIds(LmsApp->getDbSession(), params) };
|
|
||||||
|
|
||||||
params.setLinkType(db::TrackArtistLinkType::Artist);
|
|
||||||
auto trackArtists{ db::Artist::findIds(LmsApp->getDbSession(), params) };
|
|
||||||
|
|
||||||
if (trackArtists.results.size() == 1)
|
|
||||||
{
|
{
|
||||||
if (releaseArtists.results.empty() || trackArtists.results == releaseArtists.results)
|
const auto releaseArtists{ release->getArtists() };
|
||||||
|
if (releaseArtists.empty() || trackArtists == releaseArtists)
|
||||||
res = false;
|
res = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,8 +365,7 @@ namespace lms::ui
|
|||||||
|
|
||||||
void Release::refreshReleaseArtists(const db::Release::pointer& release)
|
void Release::refreshReleaseArtists(const db::Release::pointer& release)
|
||||||
{
|
{
|
||||||
auto container{ utils::createArtistsAnchorsForRelease(release) };
|
if (auto container{ utils::createArtistsAnchors(release) })
|
||||||
if (container)
|
|
||||||
{
|
{
|
||||||
setCondition("if-has-release-artists", true);
|
setCondition("if-has-release-artists", true);
|
||||||
bindWidget("artists", std::move(container));
|
bindWidget("artists", std::move(container));
|
||||||
@@ -424,7 +379,7 @@ namespace lms::ui
|
|||||||
if (mediums.empty())
|
if (mediums.empty())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const bool displayTrackArtists{ shouldDisplayTrackArtists(_releaseId) };
|
const bool displayTrackArtists{ shouldDisplayTrackArtists(release) };
|
||||||
const auto totalDisc{ release->getTotalDisc() };
|
const auto totalDisc{ release->getTotalDisc() };
|
||||||
|
|
||||||
const bool createDiscs{ mediums.size() > 1
|
const bool createDiscs{ mediums.size() > 1
|
||||||
@@ -456,16 +411,16 @@ namespace lms::ui
|
|||||||
|
|
||||||
if (displayTrackArtists)
|
if (displayTrackArtists)
|
||||||
{
|
{
|
||||||
const auto artists{ track->getArtistIds({ db::TrackArtistLinkType::Artist }) };
|
const auto artistDisplayInfo{ utils::computeArtistDisplayInfo(track, db::TrackArtistLinkType::Artist) };
|
||||||
if (!artists.empty())
|
if (!artistDisplayInfo.entries.empty())
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-artists", true);
|
entry->setCondition("if-has-artists", true);
|
||||||
entry->bindWidget("artists", utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
entry->bindWidget("artists", utils::createArtistsAnchors(artistDisplayInfo));
|
||||||
entry->bindWidget("artists-md", utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
entry->bindWidget("artists-md", utils::createArtistsAnchors(artistDisplayInfo));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fillTrackArtistLinks(entry, track->getId());
|
fillTrackArtistLinks(entry, track);
|
||||||
|
|
||||||
auto trackNumber{ track->getTrackNumber() };
|
auto trackNumber{ track->getTrackNumber() };
|
||||||
if (trackNumber)
|
if (trackNumber)
|
||||||
@@ -628,7 +583,7 @@ namespace lms::ui
|
|||||||
if (!otherVersionRelease)
|
if (!otherVersionRelease)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
container->addWidget(releaseListHelpers::createEntryForOtherVersions(otherVersionRelease));
|
container->addWidget(releaseListHelpers::createEntry(otherVersionRelease, { releaseListHelpers::DisplayOptions::ShowYear }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,7 +601,7 @@ namespace lms::ui
|
|||||||
if (!similarRelease)
|
if (!similarRelease)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
similarReleasesContainer->addWidget(releaseListHelpers::createEntry(similarRelease));
|
similarReleasesContainer->addWidget(releaseListHelpers::createEntry(similarRelease, { releaseListHelpers::DisplayOptions::ShowArtist }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ namespace lms::ui
|
|||||||
for (const db::ReleaseId releaseId : releaseIds.results)
|
for (const db::ReleaseId releaseId : releaseIds.results)
|
||||||
{
|
{
|
||||||
if (const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), releaseId) })
|
if (const db::Release::pointer release{ db::Release::find(LmsApp->getDbSession(), releaseId) })
|
||||||
_container->add(releaseListHelpers::createEntry(release));
|
_container->add(releaseListHelpers::createEntry(release, { releaseListHelpers::DisplayOptions::ShowArtist }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2024 Emeric Poupon
|
|
||||||
*
|
|
||||||
* This file is part of LMS.
|
|
||||||
*
|
|
||||||
* LMS is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* LMS is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with LMS. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
|
|
||||||
#include "database/objects/Types.hpp"
|
|
||||||
|
|
||||||
#include "DropDownMenuSelector.hpp"
|
|
||||||
|
|
||||||
namespace lms::ui
|
|
||||||
{
|
|
||||||
class TrackArtistLinkTypeSelector : public DropDownMenuSelector<std::optional<db::TrackArtistLinkType>>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
TrackArtistLinkTypeSelector(std::optional<db::TrackArtistLinkType> defaultLinkType)
|
|
||||||
: DropDownMenuSelector{ Wt::WString::tr("Lms.Explore.Artists.template.track-artist-link-type-selector"), defaultLinkType }
|
|
||||||
{
|
|
||||||
bindItem("link-type-all", Wt::WString::tr("Lms.Explore.Artists.linktype-all"), std::nullopt);
|
|
||||||
bindItem("link-type-artist", Wt::WString::trn("Lms.Explore.Artists.linktype-artist", 2), db::TrackArtistLinkType::Artist);
|
|
||||||
bindItem("link-type-releaseartist", Wt::WString::trn("Lms.Explore.Artists.linktype-releaseartist", 2), db::TrackArtistLinkType::ReleaseArtist);
|
|
||||||
bindItem("link-type-composer", Wt::WString::trn("Lms.Explore.Artists.linktype-composer", 2), db::TrackArtistLinkType::Composer);
|
|
||||||
bindItem("link-type-conductor", Wt::WString::trn("Lms.Explore.Artists.linktype-conductor", 2), db::TrackArtistLinkType::Conductor);
|
|
||||||
bindItem("link-type-lyricist", Wt::WString::trn("Lms.Explore.Artists.linktype-lyricist", 2), db::TrackArtistLinkType::Lyricist);
|
|
||||||
bindItem("link-type-mixer", Wt::WString::trn("Lms.Explore.Artists.linktype-mixer", 2), db::TrackArtistLinkType::Mixer);
|
|
||||||
bindItem("link-type-performer", Wt::WString::trn("Lms.Explore.Artists.linktype-performer", 2), db::TrackArtistLinkType::Performer);
|
|
||||||
bindItem("link-type-producer", Wt::WString::trn("Lms.Explore.Artists.linktype-producer", 2), db::TrackArtistLinkType::Producer);
|
|
||||||
bindItem("link-type-remixer", Wt::WString::trn("Lms.Explore.Artists.linktype-remixer", 2), db::TrackArtistLinkType::Remixer);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace lms::ui
|
|
||||||
@@ -47,58 +47,6 @@
|
|||||||
|
|
||||||
namespace lms::ui::TrackListHelpers
|
namespace lms::ui::TrackListHelpers
|
||||||
{
|
{
|
||||||
std::map<Wt::WString, std::set<db::ArtistId>> getArtistsByRole(db::TrackId trackId, core::EnumSet<db::TrackArtistLinkType> artistLinkTypes)
|
|
||||||
{
|
|
||||||
std::map<Wt::WString, std::set<db::ArtistId>> artistMap;
|
|
||||||
|
|
||||||
auto addArtists = [&](db::TrackArtistLinkType linkType, const char* type) {
|
|
||||||
if (!artistLinkTypes.contains(linkType))
|
|
||||||
return;
|
|
||||||
|
|
||||||
db::Artist::FindParameters params;
|
|
||||||
params.setTrack(trackId);
|
|
||||||
params.setLinkType(linkType);
|
|
||||||
const auto artistIds{ db::Artist::findIds(LmsApp->getDbSession(), params) };
|
|
||||||
if (artistIds.results.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
Wt::WString typeStr{ Wt::WString::trn(type, artistIds.results.size()) };
|
|
||||||
|
|
||||||
for (db::ArtistId artistId : artistIds.results)
|
|
||||||
artistMap[typeStr].insert(artistId);
|
|
||||||
};
|
|
||||||
|
|
||||||
auto addPerformerArtists = [&] {
|
|
||||||
if (!artistLinkTypes.contains(db::TrackArtistLinkType::Performer))
|
|
||||||
return;
|
|
||||||
|
|
||||||
db::TrackArtistLink::FindParameters params;
|
|
||||||
params.setTrack(trackId);
|
|
||||||
params.setLinkType(db::TrackArtistLinkType::Performer);
|
|
||||||
|
|
||||||
db::TrackArtistLink::find(LmsApp->getDbSession(), params, [&](const db::TrackArtistLink::pointer& link) {
|
|
||||||
artistMap[std::string{ link->getSubType() }].insert(link->getArtist()->getId());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
addArtists(db::TrackArtistLinkType::Composer, "Lms.Explore.Artists.linktype-composer");
|
|
||||||
addArtists(db::TrackArtistLinkType::Conductor, "Lms.Explore.Artists.linktype-conductor");
|
|
||||||
addArtists(db::TrackArtistLinkType::Lyricist, "Lms.Explore.Artists.linktype-lyricist");
|
|
||||||
addArtists(db::TrackArtistLinkType::Mixer, "Lms.Explore.Artists.linktype-mixer");
|
|
||||||
addArtists(db::TrackArtistLinkType::Remixer, "Lms.Explore.Artists.linktype-remixer");
|
|
||||||
addArtists(db::TrackArtistLinkType::Producer, "Lms.Explore.Artists.linktype-producer");
|
|
||||||
addPerformerArtists();
|
|
||||||
|
|
||||||
if (auto itRolelessPerformers{ artistMap.find("") }; itRolelessPerformers != std::cend(artistMap))
|
|
||||||
{
|
|
||||||
Wt::WString performersStr{ Wt::WString::trn("Lms.Explore.Artists.linktype-performer", itRolelessPerformers->second.size()) };
|
|
||||||
artistMap[performersStr] = std::move(itRolelessPerformers->second);
|
|
||||||
artistMap.erase(itRolelessPerformers);
|
|
||||||
}
|
|
||||||
|
|
||||||
return artistMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
void showTrackInfoModal(db::TrackId trackId, Filters& filters)
|
void showTrackInfoModal(db::TrackId trackId, Filters& filters)
|
||||||
{
|
{
|
||||||
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
auto transaction{ LmsApp->getDbSession().createReadTransaction() };
|
||||||
@@ -111,18 +59,18 @@ namespace lms::ui::TrackListHelpers
|
|||||||
Wt::WWidget* trackInfoPtr{ trackInfo.get() };
|
Wt::WWidget* trackInfoPtr{ trackInfo.get() };
|
||||||
trackInfo->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
trackInfo->addFunction("tr", &Wt::WTemplate::Functions::tr);
|
||||||
|
|
||||||
std::map<Wt::WString, std::set<db::ArtistId>> artistMap{ getArtistsByRole(trackId) };
|
const auto artistsByRole{ utils::getArtistsByRole(track) };
|
||||||
if (!artistMap.empty())
|
if (!artistsByRole.empty())
|
||||||
{
|
{
|
||||||
trackInfo->setCondition("if-has-artist", true);
|
trackInfo->setCondition("if-has-artist", true);
|
||||||
Wt::WContainerWidget* artistTable{ trackInfo->bindNew<Wt::WContainerWidget>("artist-table") };
|
Wt::WContainerWidget* artistTable{ trackInfo->bindNew<Wt::WContainerWidget>("artist-table") };
|
||||||
|
|
||||||
for (const auto& [role, artistIds] : artistMap)
|
for (const auto& [role, artists] : artistsByRole)
|
||||||
{
|
{
|
||||||
std::unique_ptr<Wt::WContainerWidget> artistContainer{ utils::createArtistAnchorList(std::vector(std::cbegin(artistIds), std::cend(artistIds))) };
|
|
||||||
auto artistsEntry{ std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists")) };
|
auto artistsEntry{ std::make_unique<Template>(Wt::WString::tr("Lms.Explore.template.info.artists")) };
|
||||||
artistsEntry->bindString("type", role, Wt::TextFormat::Plain);
|
artistsEntry->bindString("type", role, Wt::TextFormat::Plain);
|
||||||
artistsEntry->bindWidget("artist-container", std::move(artistContainer));
|
artistsEntry->bindWidget("artist-container", utils::createArtistAnchorList(std::vector(std::cbegin(artists), std::cend(artists))));
|
||||||
|
|
||||||
artistTable->addWidget(std::move(artistsEntry));
|
artistTable->addWidget(std::move(artistsEntry));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -216,12 +164,12 @@ namespace lms::ui::TrackListHelpers
|
|||||||
const db::Release::pointer release{ track->getRelease() };
|
const db::Release::pointer release{ track->getRelease() };
|
||||||
const db::TrackId trackId{ track->getId() };
|
const db::TrackId trackId{ track->getId() };
|
||||||
|
|
||||||
const auto artists{ track->getArtistIds({ db::TrackArtistLinkType::Artist }) };
|
const auto artistDisplayInfo{ utils::computeArtistDisplayInfo(track, db::TrackArtistLinkType::Artist) };
|
||||||
if (!artists.empty())
|
if (!artistDisplayInfo.entries.empty())
|
||||||
{
|
{
|
||||||
entry->setCondition("if-has-artists", true);
|
entry->setCondition("if-has-artists", true);
|
||||||
entry->bindWidget("artists", utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
entry->bindWidget("artists", utils::createArtistsAnchors(artistDisplayInfo));
|
||||||
entry->bindWidget("artists-md", utils::createArtistDisplayNameWithAnchors(track->getArtistDisplayName(), artists));
|
entry->bindWidget("artists-md", utils::createArtistsAnchors(artistDisplayInfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<Wt::WImage> image;
|
std::unique_ptr<Wt::WImage> image;
|
||||||
|
|||||||
@@ -46,17 +46,6 @@ namespace lms::ui
|
|||||||
|
|
||||||
namespace lms::ui::TrackListHelpers
|
namespace lms::ui::TrackListHelpers
|
||||||
{
|
{
|
||||||
inline constexpr core::EnumSet<db::TrackArtistLinkType> AllArtistRoles{
|
|
||||||
db::TrackArtistLinkType::Composer,
|
|
||||||
db::TrackArtistLinkType::Conductor,
|
|
||||||
db::TrackArtistLinkType::Lyricist,
|
|
||||||
db::TrackArtistLinkType::Mixer,
|
|
||||||
db::TrackArtistLinkType::Remixer,
|
|
||||||
db::TrackArtistLinkType::Performer,
|
|
||||||
db::TrackArtistLinkType::Producer,
|
|
||||||
};
|
|
||||||
|
|
||||||
std::map<Wt::WString, std::set<db::ArtistId>> getArtistsByRole(db::TrackId trackId, core::EnumSet<db::TrackArtistLinkType> artistLinkTypes = AllArtistRoles);
|
|
||||||
void showTrackInfoModal(db::TrackId trackId, Filters& filters);
|
void showTrackInfoModal(db::TrackId trackId, Filters& filters);
|
||||||
void showTrackLyricsModal(db::TrackId trackId);
|
void showTrackLyricsModal(db::TrackId trackId);
|
||||||
std::unique_ptr<Wt::WWidget> createEntry(const db::ObjectPtr<db::Track>& track, PlayQueueController& playQueueController, Filters& filters);
|
std::unique_ptr<Wt::WWidget> createEntry(const db::ObjectPtr<db::Track>& track, PlayQueueController& playQueueController, Filters& filters);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
#include "LmsApplication.hpp"
|
#include "LmsApplication.hpp"
|
||||||
|
|
||||||
#define DL_RESOURCE_LOG(severity, message) LMS_LOG(UI, severity, "Download resource: " << message)
|
#define LMS_DL_RESOURCE_LOG(severity, message) LMS_LOG(UI, severity, "Download resource: " << message)
|
||||||
|
|
||||||
namespace lms::ui
|
namespace lms::ui
|
||||||
{
|
{
|
||||||
@@ -72,7 +72,7 @@ namespace lms::ui
|
|||||||
}
|
}
|
||||||
catch (zip::Exception& exception)
|
catch (zip::Exception& exception)
|
||||||
{
|
{
|
||||||
DL_RESOURCE_LOG(ERROR, "Zipper exception: " << exception.what());
|
LMS_DL_RESOURCE_LOG(ERROR, "Zipper exception: " << exception.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,9 +89,9 @@ namespace lms::ui
|
|||||||
|
|
||||||
std::vector<db::ObjectPtr<db::Artist>> artists;
|
std::vector<db::ObjectPtr<db::Artist>> artists;
|
||||||
|
|
||||||
artists = release->getReleaseArtists();
|
artists = release->getArtists();
|
||||||
if (artists.empty())
|
if (artists.empty())
|
||||||
artists = release->getArtists();
|
artists = release->getTrackArtists();
|
||||||
|
|
||||||
if (artists.size() > 1)
|
if (artists.size() > 1)
|
||||||
releaseArtistName = Wt::WString::tr("Lms.Explore.various-artists").toUTF8();
|
releaseArtistName = Wt::WString::tr("Lms.Explore.various-artists").toUTF8();
|
||||||
@@ -220,7 +220,7 @@ namespace lms::ui
|
|||||||
const db::Track::pointer track{ db::Track::find(LmsApp->getDbSession(), _trackId) };
|
const db::Track::pointer track{ db::Track::find(LmsApp->getDbSession(), _trackId) };
|
||||||
if (!track)
|
if (!track)
|
||||||
{
|
{
|
||||||
DL_RESOURCE_LOG(DEBUG, "Cannot find track");
|
LMS_DL_RESOURCE_LOG(DEBUG, "Cannot find track");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,3 +247,5 @@ namespace lms::ui
|
|||||||
return detail::createZipper(tracks.results);
|
return detail::createZipper(tracks.results);
|
||||||
}
|
}
|
||||||
} // namespace lms::ui
|
} // namespace lms::ui
|
||||||
|
|
||||||
|
#undef LMS_DL_RESOURCE_LOG
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
#include "database/objects/MediaLibrary.hpp"
|
#include "database/objects/MediaLibrary.hpp"
|
||||||
#include "database/objects/Medium.hpp"
|
#include "database/objects/Medium.hpp"
|
||||||
#include "database/objects/Release.hpp"
|
#include "database/objects/Release.hpp"
|
||||||
|
#include "database/objects/ReleaseArtistLink.hpp"
|
||||||
#include "database/objects/Track.hpp"
|
#include "database/objects/Track.hpp"
|
||||||
#include "database/objects/TrackArtistLink.hpp"
|
#include "database/objects/TrackArtistLink.hpp"
|
||||||
#include "database/objects/TrackEmbeddedImage.hpp"
|
#include "database/objects/TrackEmbeddedImage.hpp"
|
||||||
@@ -118,8 +119,8 @@ namespace lms
|
|||||||
if (mediaLibrary)
|
if (mediaLibrary)
|
||||||
track.modify()->setMediaLibrary(mediaLibrary);
|
track.modify()->setMediaLibrary(mediaLibrary);
|
||||||
|
|
||||||
TrackArtistLink::create(context.session, track, artist, TrackArtistLinkType::Artist);
|
context.session.create<TrackArtistLink>(track, artist, TrackArtistLinkType::Artist);
|
||||||
TrackArtistLink::create(context.session, track, artist, TrackArtistLinkType::ReleaseArtist);
|
context.session.create<ReleaseArtistLink>(release, artist, false);
|
||||||
|
|
||||||
if (!trackEmbeddedImages.empty())
|
if (!trackEmbeddedImages.empty())
|
||||||
context.session.create<TrackEmbeddedImageLink>(track, *core::random::pickRandom(trackEmbeddedImages));
|
context.session.create<TrackEmbeddedImageLink>(track, *core::random::pickRandom(trackEmbeddedImages));
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ namespace lms
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::cout << "Processing artist '" << artistToString(artistId) << "'" << std::endl;
|
std::cout << "Processing artist '" << artistToString(artistId) << "'" << std::endl;
|
||||||
for (ArtistId similarArtistId : recommendationService.getSimilarArtists(artistId, { TrackArtistLinkType::Artist, TrackArtistLinkType::ReleaseArtist }, maxSimilarityCount))
|
for (ArtistId similarArtistId : recommendationService.getSimilarArtists(artistId, { TrackArtistLinkType::Artist }, maxSimilarityCount))
|
||||||
{
|
{
|
||||||
std::cout << "\t- Similar artist '" << artistToString(similarArtistId) << "'" << std::endl;
|
std::cout << "\t- Similar artist '" << artistToString(similarArtistId) << "'" << std::endl;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user