Correction de petits bugs d'interface
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,173 +1,260 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from "vue";
|
||||||
import type { ContainerEntry } from '@/services/pmocontrol/types'
|
import type { ContainerEntry } from "@/services/pmocontrol/types";
|
||||||
import { Folder, Music } from 'lucide-vue-next'
|
import { Folder, Music } from "lucide-vue-next";
|
||||||
import ActionMenu from './ActionMenu.vue'
|
import ActionMenu from "./ActionMenu.vue";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
entry: ContainerEntry
|
entry: ContainerEntry;
|
||||||
serverId: string
|
serverId: string;
|
||||||
showActions?: boolean
|
showActions?: boolean;
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
browse: [containerId: string]
|
browse: [containerId: string];
|
||||||
playNow: [containerId: string, rendererId: string]
|
playNow: [containerId: string, rendererId: string];
|
||||||
addToQueue: [containerId: string, rendererId: string]
|
addToQueue: [containerId: string, rendererId: string];
|
||||||
}>()
|
}>();
|
||||||
|
|
||||||
const iconComponent = computed(() => {
|
const iconComponent = computed(() => {
|
||||||
const cls = props.entry.class.toLowerCase()
|
const cls = props.entry.class.toLowerCase();
|
||||||
if (cls.includes('playlist')) return Music
|
if (cls.includes("playlist")) return Music;
|
||||||
if (cls.includes('album')) return Music
|
if (cls.includes("album")) return Music;
|
||||||
return Folder
|
return Folder;
|
||||||
})
|
});
|
||||||
|
|
||||||
const containerType = computed(() => {
|
const containerType = computed(() => {
|
||||||
const cls = props.entry.class.toLowerCase()
|
const cls = props.entry.class.toLowerCase();
|
||||||
if (cls.includes('playlist')) return 'Playlist'
|
if (cls.includes("playlist")) return "Playlist";
|
||||||
if (cls.includes('album')) return 'Album'
|
if (cls.includes("album")) return "Album";
|
||||||
if (cls.includes('artist')) return 'Artiste'
|
if (cls.includes("artist")) return "Artiste";
|
||||||
if (cls.includes('genre')) return 'Genre'
|
if (cls.includes("genre")) return "Genre";
|
||||||
return 'Dossier'
|
return "Dossier";
|
||||||
})
|
});
|
||||||
|
|
||||||
|
const isPlayable = computed(() => {
|
||||||
|
const cls = props.entry.class.toLowerCase();
|
||||||
|
return cls.includes("playlist") || cls.includes("album");
|
||||||
|
});
|
||||||
|
|
||||||
function handleBrowse() {
|
function handleBrowse() {
|
||||||
emit('browse', props.entry.id)
|
emit("browse", props.entry.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePlayNow(rendererId: string) {
|
function handlePlayNow(rendererId: string) {
|
||||||
emit('playNow', props.entry.id, rendererId)
|
emit("playNow", props.entry.id, rendererId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleAddToQueue(rendererId: string) {
|
function handleAddToQueue(rendererId: string) {
|
||||||
emit('addToQueue', props.entry.id, rendererId)
|
emit("addToQueue", props.entry.id, rendererId);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleImageError(event: Event) {
|
||||||
|
const img = event.target as HTMLImageElement;
|
||||||
|
img.style.display = "none";
|
||||||
|
const placeholder = img.nextElementSibling;
|
||||||
|
if (placeholder && placeholder instanceof HTMLElement) {
|
||||||
|
placeholder.style.display = "flex";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="container-item">
|
<div class="container-item">
|
||||||
<!-- Main content (clickable) -->
|
<!-- Main content (clickable) -->
|
||||||
<button class="container-content" @click="handleBrowse">
|
<button class="container-content" @click="handleBrowse">
|
||||||
<div class="container-icon">
|
<!-- Cover avec icône de type en overlay -->
|
||||||
<component :is="iconComponent" :size="24" />
|
<div class="container-cover">
|
||||||
</div>
|
<img
|
||||||
<div class="container-metadata">
|
v-if="entry.album_art_uri"
|
||||||
<div class="container-title">{{ entry.title }}</div>
|
:src="entry.album_art_uri"
|
||||||
<div class="container-details">
|
:alt="entry.title"
|
||||||
<span class="container-type">{{ containerType }}</span>
|
class="cover-image"
|
||||||
<span v-if="entry.child_count !== null" class="container-count">
|
loading="lazy"
|
||||||
{{ entry.child_count }} élément{{ entry.child_count > 1 ? 's' : '' }}
|
@error="handleImageError"
|
||||||
</span>
|
/>
|
||||||
</div>
|
<div
|
||||||
</div>
|
class="cover-placeholder"
|
||||||
</button>
|
:style="{
|
||||||
|
display: entry.album_art_uri ? 'none' : 'flex',
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<component :is="iconComponent" :size="28" />
|
||||||
|
</div>
|
||||||
|
<!-- Petite icône de type dans le coin inférieur droit -->
|
||||||
|
<div v-if="isPlayable" class="type-badge">
|
||||||
|
<Folder :size="14" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Actions menu -->
|
<!-- Métadonnées -->
|
||||||
<div class="container-actions">
|
<div class="container-metadata">
|
||||||
<ActionMenu
|
<div class="container-title">{{ entry.title }}</div>
|
||||||
type="container"
|
<div class="container-details">
|
||||||
:entry-id="entry.id"
|
<span v-if="entry.artist" class="container-artist">{{
|
||||||
:server-id="serverId"
|
entry.artist
|
||||||
@play-now="handlePlayNow"
|
}}</span>
|
||||||
@add-to-queue="handleAddToQueue"
|
<span class="container-type">{{ containerType }}</span>
|
||||||
/>
|
<span
|
||||||
|
v-if="entry.child_count !== null"
|
||||||
|
class="container-count"
|
||||||
|
>
|
||||||
|
{{ entry.child_count }} élément{{
|
||||||
|
entry.child_count > 1 ? "s" : ""
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Actions menu -->
|
||||||
|
<div class="container-actions">
|
||||||
|
<ActionMenu
|
||||||
|
type="container"
|
||||||
|
:entry-id="entry.id"
|
||||||
|
:server-id="serverId"
|
||||||
|
@play-now="handlePlayNow"
|
||||||
|
@add-to-queue="handleAddToQueue"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.container-item {
|
.container-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-sm);
|
gap: var(--spacing-sm);
|
||||||
padding: var(--spacing-sm);
|
padding: var(--spacing-sm);
|
||||||
border-radius: var(--radius-md);
|
border-radius: var(--radius-md);
|
||||||
transition: background-color var(--transition-fast);
|
transition: background-color var(--transition-fast);
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-item:hover {
|
.container-item:hover {
|
||||||
background-color: var(--color-bg-secondary);
|
background-color: var(--color-bg-secondary);
|
||||||
border-color: var(--color-border);
|
border-color: var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-content {
|
.container-content {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--spacing-md);
|
gap: var(--spacing-md);
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-icon {
|
/* Cover avec image et icône de type */
|
||||||
flex-shrink: 0;
|
.container-cover {
|
||||||
width: 48px;
|
position: relative;
|
||||||
height: 48px;
|
flex-shrink: 0;
|
||||||
display: flex;
|
width: 64px;
|
||||||
align-items: center;
|
height: 64px;
|
||||||
justify-content: center;
|
border-radius: var(--radius-md);
|
||||||
background-color: var(--color-bg-tertiary);
|
overflow: hidden;
|
||||||
border-radius: var(--radius-sm);
|
background-color: var(--color-bg-tertiary);
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cover-image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cover-placeholder {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.type-badge {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 4px;
|
||||||
|
right: 4px;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Métadonnées */
|
||||||
.container-metadata {
|
.container-metadata {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-title {
|
.container-title {
|
||||||
font-size: var(--text-base);
|
font-size: var(--text-base);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
margin-bottom: var(--spacing-xs);
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-details {
|
.container-details {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--spacing-sm);
|
flex-wrap: wrap;
|
||||||
font-size: var(--text-sm);
|
gap: var(--spacing-xs) var(--spacing-sm);
|
||||||
color: var(--color-text-secondary);
|
font-size: var(--text-sm);
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container-artist {
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-type {
|
.container-type {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-count::before {
|
.container-count::before {
|
||||||
content: '•';
|
content: "•";
|
||||||
margin-right: var(--spacing-sm);
|
margin-right: var(--spacing-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.container-actions {
|
.container-actions {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon {
|
.btn-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 36px;
|
width: 36px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
background: none;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
color: var(--color-text-secondary);
|
color: var(--color-text-secondary);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all var(--transition-fast);
|
transition: all var(--transition-fast);
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon:hover {
|
.btn-icon:hover {
|
||||||
background-color: var(--color-bg-tertiary);
|
background-color: var(--color-bg-tertiary);
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@ export interface PlaylistSummary {
|
|||||||
persistent: boolean;
|
persistent: boolean;
|
||||||
cover_pk?: string | null;
|
cover_pk?: string | null;
|
||||||
cover_url?: string | null;
|
cover_url?: string | null;
|
||||||
|
artist?: string | null;
|
||||||
track_count: number;
|
track_count: number;
|
||||||
max_size?: number | null;
|
max_size?: number | null;
|
||||||
default_ttl_secs?: number | null;
|
default_ttl_secs?: number | null;
|
||||||
@@ -49,6 +50,7 @@ export interface UpdatePlaylistPayload {
|
|||||||
max_size?: number | null;
|
max_size?: number | null;
|
||||||
default_ttl_secs?: number | null;
|
default_ttl_secs?: number | null;
|
||||||
cover_pk?: string | null;
|
cover_pk?: string | null;
|
||||||
|
artist?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AddTracksPayload {
|
export interface AddTracksPayload {
|
||||||
@@ -99,7 +101,9 @@ export async function getPlaylistDetail(id: string): Promise<PlaylistDetail> {
|
|||||||
return parseJsonOrThrow(response);
|
return parseJsonOrThrow(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createPlaylist(body: CreatePlaylistPayload): Promise<PlaylistDetail> {
|
export async function createPlaylist(
|
||||||
|
body: CreatePlaylistPayload,
|
||||||
|
): Promise<PlaylistDetail> {
|
||||||
const response = await fetch("/api/playlists", {
|
const response = await fetch("/api/playlists", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -112,7 +116,7 @@ export async function createPlaylist(body: CreatePlaylistPayload): Promise<Playl
|
|||||||
|
|
||||||
export async function updatePlaylist(
|
export async function updatePlaylist(
|
||||||
id: string,
|
id: string,
|
||||||
body: UpdatePlaylistPayload
|
body: UpdatePlaylistPayload,
|
||||||
): Promise<PlaylistDetail> {
|
): Promise<PlaylistDetail> {
|
||||||
const response = await fetch(`/api/playlists/${encodeURIComponent(id)}`, {
|
const response = await fetch(`/api/playlists/${encodeURIComponent(id)}`, {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
@@ -133,31 +137,40 @@ export async function deletePlaylist(id: string): Promise<void> {
|
|||||||
|
|
||||||
export async function addTracksToPlaylist(
|
export async function addTracksToPlaylist(
|
||||||
id: string,
|
id: string,
|
||||||
payload: AddTracksPayload
|
payload: AddTracksPayload,
|
||||||
): Promise<PlaylistDetail> {
|
): Promise<PlaylistDetail> {
|
||||||
const response = await fetch(`/api/playlists/${encodeURIComponent(id)}/tracks`, {
|
const response = await fetch(
|
||||||
method: "POST",
|
`/api/playlists/${encodeURIComponent(id)}/tracks`,
|
||||||
headers: {
|
{
|
||||||
"Content-Type": "application/json",
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload),
|
||||||
},
|
},
|
||||||
body: JSON.stringify(payload),
|
);
|
||||||
});
|
|
||||||
return parseJsonOrThrow(response);
|
return parseJsonOrThrow(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function flushPlaylist(id: string): Promise<PlaylistDetail> {
|
export async function flushPlaylist(id: string): Promise<PlaylistDetail> {
|
||||||
const response = await fetch(`/api/playlists/${encodeURIComponent(id)}/tracks`, {
|
const response = await fetch(
|
||||||
method: "DELETE",
|
`/api/playlists/${encodeURIComponent(id)}/tracks`,
|
||||||
});
|
{
|
||||||
|
method: "DELETE",
|
||||||
|
},
|
||||||
|
);
|
||||||
return parseJsonOrThrow(response);
|
return parseJsonOrThrow(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function removeTrackFromPlaylist(id: string, cachePk: string): Promise<PlaylistDetail> {
|
export async function removeTrackFromPlaylist(
|
||||||
|
id: string,
|
||||||
|
cachePk: string,
|
||||||
|
): Promise<PlaylistDetail> {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`/api/playlists/${encodeURIComponent(id)}/tracks/${encodeURIComponent(cachePk)}`,
|
`/api/playlists/${encodeURIComponent(id)}/tracks/${encodeURIComponent(cachePk)}`,
|
||||||
{
|
{
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
return parseJsonOrThrow(response);
|
return parseJsonOrThrow(response);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,6 +141,20 @@ pub struct Container {
|
|||||||
#[serde(rename = "upnp:class", alias = "class", default)]
|
#[serde(rename = "upnp:class", alias = "class", default)]
|
||||||
pub class: String,
|
pub class: String,
|
||||||
|
|
||||||
|
#[serde(
|
||||||
|
rename = "upnp:artist",
|
||||||
|
alias = "artist",
|
||||||
|
skip_serializing_if = "Option::is_none"
|
||||||
|
)]
|
||||||
|
pub artist: Option<String>,
|
||||||
|
|
||||||
|
#[serde(
|
||||||
|
rename = "upnp:albumArtURI",
|
||||||
|
alias = "albumArtURI",
|
||||||
|
skip_serializing_if = "Option::is_none"
|
||||||
|
)]
|
||||||
|
pub album_art: Option<String>,
|
||||||
|
|
||||||
#[serde(rename = "container", default)]
|
#[serde(rename = "container", default)]
|
||||||
pub containers: Vec<Container>,
|
pub containers: Vec<Container>,
|
||||||
|
|
||||||
|
|||||||
@@ -474,6 +474,8 @@ impl ContentHandler {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "PMOMusic".to_string(),
|
title: "PMOMusic".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -252,6 +252,8 @@ mod tests {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: self.name.clone(),
|
title: self.name.clone(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -364,6 +364,8 @@ impl RadioParadiseSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: descriptor.display_name.to_string(),
|
title: descriptor.display_name.to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -379,6 +381,8 @@ impl RadioParadiseSource {
|
|||||||
searchable: Some("0".to_string()),
|
searchable: Some("0".to_string()),
|
||||||
title: format!("{} - Live Playlist", descriptor.display_name),
|
title: format!("{} - Live Playlist", descriptor.display_name),
|
||||||
class: "object.container.playlistContainer".to_string(),
|
class: "object.container.playlistContainer".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -435,6 +439,8 @@ impl RadioParadiseSource {
|
|||||||
title: format!("{} - History", descriptor.display_name),
|
title: format!("{} - History", descriptor.display_name),
|
||||||
// Expose l'historique comme une playlist jouable
|
// Expose l'historique comme une playlist jouable
|
||||||
class: "object.container.playlistContainer".to_string(),
|
class: "object.container.playlistContainer".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -650,6 +656,8 @@ impl MusicSource for RadioParadiseSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Radio Paradise".to_string(),
|
title: "Radio Paradise".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ pub struct PlaylistSummaryResponse {
|
|||||||
pub cover_pk: Option<String>,
|
pub cover_pk: Option<String>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub cover_url: Option<String>,
|
pub cover_url: Option<String>,
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub artist: Option<String>,
|
||||||
pub track_count: usize,
|
pub track_count: usize,
|
||||||
pub max_size: Option<usize>,
|
pub max_size: Option<usize>,
|
||||||
pub default_ttl_secs: Option<u64>,
|
pub default_ttl_secs: Option<u64>,
|
||||||
@@ -103,6 +105,8 @@ pub struct UpdatePlaylistRequest {
|
|||||||
pub default_ttl_secs: Option<Option<u64>>,
|
pub default_ttl_secs: Option<Option<u64>>,
|
||||||
/// Utiliser `null` explicite pour supprimer la cover, ou omettre pour ne pas modifier.
|
/// Utiliser `null` explicite pour supprimer la cover, ou omettre pour ne pas modifier.
|
||||||
pub cover_pk: Option<Option<String>>,
|
pub cover_pk: Option<Option<String>>,
|
||||||
|
/// Utiliser `null` explicite pour supprimer l'artiste, ou omettre pour ne pas modifier.
|
||||||
|
pub artist: Option<Option<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Requête pour ajouter des morceaux dans une playlist.
|
/// Requête pour ajouter des morceaux dans une playlist.
|
||||||
@@ -262,6 +266,7 @@ pub async fn update_playlist(
|
|||||||
max_size,
|
max_size,
|
||||||
default_ttl_secs,
|
default_ttl_secs,
|
||||||
cover_pk,
|
cover_pk,
|
||||||
|
artist,
|
||||||
} = req;
|
} = req;
|
||||||
|
|
||||||
let manager = crate::manager::PlaylistManager();
|
let manager = crate::manager::PlaylistManager();
|
||||||
@@ -289,6 +294,9 @@ pub async fn update_playlist(
|
|||||||
};
|
};
|
||||||
writer.set_cover_pk(normalized).await?;
|
writer.set_cover_pk(normalized).await?;
|
||||||
}
|
}
|
||||||
|
if let Some(artist) = artist {
|
||||||
|
writer.set_artist(artist).await?;
|
||||||
|
}
|
||||||
|
|
||||||
manager.playlist_snapshot(&playlist_id).await
|
manager.playlist_snapshot(&playlist_id).await
|
||||||
}
|
}
|
||||||
@@ -545,6 +553,7 @@ impl From<PlaylistOverview> for PlaylistSummaryResponse {
|
|||||||
persistent: value.persistent,
|
persistent: value.persistent,
|
||||||
cover_pk: cover_pk.clone(),
|
cover_pk: cover_pk.clone(),
|
||||||
cover_url: cover_pk.as_deref().map(cover_url_from_pk),
|
cover_url: cover_pk.as_deref().map(cover_url_from_pk),
|
||||||
|
artist: value.artist,
|
||||||
track_count: value.track_count,
|
track_count: value.track_count,
|
||||||
max_size: value.max_size,
|
max_size: value.max_size,
|
||||||
default_ttl_secs: value.default_ttl.map(|ttl| ttl.as_secs()),
|
default_ttl_secs: value.default_ttl.map(|ttl| ttl.as_secs()),
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ impl ReadHandle {
|
|||||||
let title = self.playlist.title().await;
|
let title = self.playlist.title().await;
|
||||||
let role = self.playlist.role().await;
|
let role = self.playlist.role().await;
|
||||||
let cover_pk = self.playlist.cover_pk().await;
|
let cover_pk = self.playlist.cover_pk().await;
|
||||||
|
let artist = self.playlist.artist().await;
|
||||||
let core = self.playlist.core.read().await;
|
let core = self.playlist.core.read().await;
|
||||||
let _ = persistence
|
let _ = persistence
|
||||||
.save_playlist(
|
.save_playlist(
|
||||||
@@ -76,6 +77,7 @@ impl ReadHandle {
|
|||||||
&title,
|
&title,
|
||||||
&role,
|
&role,
|
||||||
cover_pk.as_deref(),
|
cover_pk.as_deref(),
|
||||||
|
artist.as_deref(),
|
||||||
&core.config,
|
&core.config,
|
||||||
&core.tracks,
|
&core.tracks,
|
||||||
)
|
)
|
||||||
@@ -175,8 +177,13 @@ impl ReadHandle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let title = self.playlist.title().await;
|
let title = self.playlist.title().await;
|
||||||
|
let artist = self.playlist.artist().await;
|
||||||
|
let cover_pk = self.playlist.cover_pk().await;
|
||||||
let _remaining = self.remaining().await?;
|
let _remaining = self.remaining().await?;
|
||||||
|
|
||||||
|
// Convertir cover_pk en URL si présent
|
||||||
|
let album_art = cover_pk.map(|pk| format!("/cover/{}", pk));
|
||||||
|
|
||||||
Ok(Container {
|
Ok(Container {
|
||||||
id: self.playlist.id.clone(),
|
id: self.playlist.id.clone(),
|
||||||
parent_id: "0".to_string(),
|
parent_id: "0".to_string(),
|
||||||
@@ -185,6 +192,8 @@ impl ReadHandle {
|
|||||||
searchable: Some("0".to_string()),
|
searchable: Some("0".to_string()),
|
||||||
title,
|
title,
|
||||||
class: "object.container.playlistContainer".to_string(),
|
class: "object.container.playlistContainer".to_string(),
|
||||||
|
artist,
|
||||||
|
album_art,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -309,6 +309,23 @@ impl WriteHandle {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Met à jour l'artiste associé à la playlist.
|
||||||
|
pub async fn set_artist(&self, artist: Option<String>) -> Result<()> {
|
||||||
|
if !self.playlist.is_alive() {
|
||||||
|
return Err(crate::Error::PlaylistDeleted(self.playlist.id.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.playlist.set_artist(artist).await;
|
||||||
|
|
||||||
|
if self.playlist.persistent {
|
||||||
|
self.save_to_db().await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
crate::manager::PlaylistManager().notify_playlist_changed(&self.playlist.id);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Vérifie si la playlist contient déjà un pk
|
/// Vérifie si la playlist contient déjà un pk
|
||||||
pub async fn contains_pk(&self, cache_pk: &str) -> Result<bool> {
|
pub async fn contains_pk(&self, cache_pk: &str) -> Result<bool> {
|
||||||
if !self.playlist.is_alive() {
|
if !self.playlist.is_alive() {
|
||||||
@@ -549,6 +566,7 @@ impl WriteHandle {
|
|||||||
let tracks = &core.tracks;
|
let tracks = &core.tracks;
|
||||||
|
|
||||||
let cover_pk = self.playlist.cover_pk().await;
|
let cover_pk = self.playlist.cover_pk().await;
|
||||||
|
let artist = self.playlist.artist().await;
|
||||||
|
|
||||||
persistence
|
persistence
|
||||||
.save_playlist(
|
.save_playlist(
|
||||||
@@ -556,6 +574,7 @@ impl WriteHandle {
|
|||||||
&title,
|
&title,
|
||||||
&role,
|
&role,
|
||||||
cover_pk.as_deref(),
|
cover_pk.as_deref(),
|
||||||
|
artist.as_deref(),
|
||||||
config,
|
config,
|
||||||
tracks,
|
tracks,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ pub struct PlaylistOverview {
|
|||||||
pub role: PlaylistRole,
|
pub role: PlaylistRole,
|
||||||
pub persistent: bool,
|
pub persistent: bool,
|
||||||
pub cover_pk: Option<String>,
|
pub cover_pk: Option<String>,
|
||||||
|
pub artist: Option<String>,
|
||||||
pub track_count: usize,
|
pub track_count: usize,
|
||||||
pub max_size: Option<usize>,
|
pub max_size: Option<usize>,
|
||||||
pub default_ttl: Option<Duration>,
|
pub default_ttl: Option<Duration>,
|
||||||
@@ -205,6 +206,7 @@ impl PlaylistManager {
|
|||||||
let title = playlist.title().await;
|
let title = playlist.title().await;
|
||||||
let role = playlist.role().await;
|
let role = playlist.role().await;
|
||||||
let cover_pk = playlist.cover_pk().await;
|
let cover_pk = playlist.cover_pk().await;
|
||||||
|
let artist = playlist.artist().await;
|
||||||
let core = playlist.core.read().await;
|
let core = playlist.core.read().await;
|
||||||
persistence
|
persistence
|
||||||
.save_playlist(
|
.save_playlist(
|
||||||
@@ -212,6 +214,7 @@ impl PlaylistManager {
|
|||||||
&title,
|
&title,
|
||||||
&role,
|
&role,
|
||||||
cover_pk.as_deref(),
|
cover_pk.as_deref(),
|
||||||
|
artist.as_deref(),
|
||||||
&core.config,
|
&core.config,
|
||||||
&core.tracks,
|
&core.tracks,
|
||||||
)
|
)
|
||||||
@@ -519,7 +522,7 @@ impl PlaylistManager {
|
|||||||
|
|
||||||
// Pas en mémoire, essayer de charger depuis la DB
|
// Pas en mémoire, essayer de charger depuis la DB
|
||||||
if let Some(persistence) = &self.inner.persistence {
|
if let Some(persistence) = &self.inner.persistence {
|
||||||
if let Some((title, role, config, cover_pk, tracks)) =
|
if let Some((title, role, config, cover_pk, artist, tracks)) =
|
||||||
persistence.load_playlist(&id).await?
|
persistence.load_playlist(&id).await?
|
||||||
{
|
{
|
||||||
// Reconstruire la playlist
|
// Reconstruire la playlist
|
||||||
@@ -534,6 +537,11 @@ impl PlaylistManager {
|
|||||||
cover_pk,
|
cover_pk,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Restaurer l'artiste si présent
|
||||||
|
if let Some(artist_name) = artist {
|
||||||
|
playlist.set_artist(Some(artist_name)).await;
|
||||||
|
}
|
||||||
|
|
||||||
// Restaurer les tracks
|
// Restaurer les tracks
|
||||||
{
|
{
|
||||||
let mut core = playlist.core.write().await;
|
let mut core = playlist.core.write().await;
|
||||||
@@ -575,7 +583,7 @@ impl PlaylistManager {
|
|||||||
|
|
||||||
// Pas en m<>moire, essayer de ressusciter depuis la DB
|
// Pas en m<>moire, essayer de ressusciter depuis la DB
|
||||||
if let Some(persistence) = &self.inner.persistence {
|
if let Some(persistence) = &self.inner.persistence {
|
||||||
if let Some((title, role, config, cover_pk, tracks)) =
|
if let Some((title, role, config, cover_pk, artist, tracks)) =
|
||||||
persistence.load_playlist(id).await?
|
persistence.load_playlist(id).await?
|
||||||
{
|
{
|
||||||
// Reconstruire la playlist
|
// Reconstruire la playlist
|
||||||
@@ -590,6 +598,11 @@ impl PlaylistManager {
|
|||||||
cover_pk,
|
cover_pk,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// Restaurer l'artiste si présent
|
||||||
|
if let Some(artist_name) = artist {
|
||||||
|
playlist.set_artist(Some(artist_name)).await;
|
||||||
|
}
|
||||||
|
|
||||||
// Restaurer les tracks
|
// Restaurer les tracks
|
||||||
{
|
{
|
||||||
let mut core = playlist.core.write().await;
|
let mut core = playlist.core.write().await;
|
||||||
@@ -653,12 +666,15 @@ impl PlaylistManager {
|
|||||||
let track_count = core.len();
|
let track_count = core.len();
|
||||||
let config = core.config.clone();
|
let config = core.config.clone();
|
||||||
|
|
||||||
|
let artist = playlist.artist().await;
|
||||||
|
|
||||||
Ok(PlaylistOverview {
|
Ok(PlaylistOverview {
|
||||||
id: playlist.id.clone(),
|
id: playlist.id.clone(),
|
||||||
title,
|
title,
|
||||||
role,
|
role,
|
||||||
persistent,
|
persistent,
|
||||||
cover_pk,
|
cover_pk,
|
||||||
|
artist,
|
||||||
track_count,
|
track_count,
|
||||||
max_size: config.max_size,
|
max_size: config.max_size,
|
||||||
default_ttl: config.default_ttl,
|
default_ttl: config.default_ttl,
|
||||||
@@ -978,6 +994,7 @@ impl PlaylistManager {
|
|||||||
let title = playlist.title().await;
|
let title = playlist.title().await;
|
||||||
let role = playlist.role().await;
|
let role = playlist.role().await;
|
||||||
let cover_pk = playlist.cover_pk().await;
|
let cover_pk = playlist.cover_pk().await;
|
||||||
|
let artist = playlist.artist().await;
|
||||||
let core = playlist.core.read().await;
|
let core = playlist.core.read().await;
|
||||||
let _ = persistence
|
let _ = persistence
|
||||||
.save_playlist(
|
.save_playlist(
|
||||||
@@ -985,6 +1002,7 @@ impl PlaylistManager {
|
|||||||
&title,
|
&title,
|
||||||
&role,
|
&role,
|
||||||
cover_pk.as_deref(),
|
cover_pk.as_deref(),
|
||||||
|
artist.as_deref(),
|
||||||
&core.config,
|
&core.config,
|
||||||
&core.tracks,
|
&core.tracks,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ impl PersistenceManager {
|
|||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
role TEXT NOT NULL,
|
role TEXT NOT NULL,
|
||||||
cover_pk TEXT,
|
cover_pk TEXT,
|
||||||
|
artist TEXT,
|
||||||
max_size INTEGER,
|
max_size INTEGER,
|
||||||
default_ttl_secs INTEGER,
|
default_ttl_secs INTEGER,
|
||||||
created_at INTEGER NOT NULL,
|
created_at INTEGER NOT NULL,
|
||||||
@@ -86,6 +87,7 @@ impl PersistenceManager {
|
|||||||
title: &str,
|
title: &str,
|
||||||
role: &PlaylistRole,
|
role: &PlaylistRole,
|
||||||
cover_pk: Option<&str>,
|
cover_pk: Option<&str>,
|
||||||
|
artist: Option<&str>,
|
||||||
config: &PlaylistConfig,
|
config: &PlaylistConfig,
|
||||||
tracks: &VecDeque<Arc<Record>>,
|
tracks: &VecDeque<Arc<Record>>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
@@ -98,15 +100,16 @@ impl PersistenceManager {
|
|||||||
|
|
||||||
// Upsert playlist metadata
|
// Upsert playlist metadata
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"INSERT OR REPLACE INTO playlists (id, title, role, cover_pk, max_size, default_ttl_secs, created_at, last_modified)
|
"INSERT OR REPLACE INTO playlists (id, title, role, cover_pk, artist, max_size, default_ttl_secs, created_at, last_modified)
|
||||||
VALUES (?1, ?2, ?3, ?4, ?5, ?6,
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7,
|
||||||
COALESCE((SELECT created_at FROM playlists WHERE id = ?1), ?7),
|
COALESCE((SELECT created_at FROM playlists WHERE id = ?1), ?8),
|
||||||
?7)",
|
?8)",
|
||||||
params![
|
params![
|
||||||
id,
|
id,
|
||||||
title,
|
title,
|
||||||
role.as_str(),
|
role.as_str(),
|
||||||
cover_pk,
|
cover_pk,
|
||||||
|
artist,
|
||||||
config.max_size.map(|s| s as i64),
|
config.max_size.map(|s| s as i64),
|
||||||
config.default_ttl.map(|d| d.as_secs() as i64),
|
config.default_ttl.map(|d| d.as_secs() as i64),
|
||||||
now_nanos,
|
now_nanos,
|
||||||
@@ -150,6 +153,7 @@ impl PersistenceManager {
|
|||||||
PlaylistRole,
|
PlaylistRole,
|
||||||
PlaylistConfig,
|
PlaylistConfig,
|
||||||
Option<String>,
|
Option<String>,
|
||||||
|
Option<String>,
|
||||||
VecDeque<Arc<Record>>,
|
VecDeque<Arc<Record>>,
|
||||||
)>,
|
)>,
|
||||||
> {
|
> {
|
||||||
@@ -157,7 +161,7 @@ impl PersistenceManager {
|
|||||||
|
|
||||||
// Charger les métadonnées
|
// Charger les métadonnées
|
||||||
let mut stmt = conn.prepare(
|
let mut stmt = conn.prepare(
|
||||||
"SELECT title, role, cover_pk, max_size, default_ttl_secs FROM playlists WHERE id = ?1",
|
"SELECT title, role, cover_pk, artist, max_size, default_ttl_secs FROM playlists WHERE id = ?1",
|
||||||
)
|
)
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
crate::Error::PersistenceError(format!("Failed to prepare statement: {}", e))
|
crate::Error::PersistenceError(format!("Failed to prepare statement: {}", e))
|
||||||
@@ -167,8 +171,9 @@ impl PersistenceManager {
|
|||||||
let title: String = row.get(0)?;
|
let title: String = row.get(0)?;
|
||||||
let role_raw: String = row.get(1)?;
|
let role_raw: String = row.get(1)?;
|
||||||
let cover_pk: Option<String> = row.get(2)?;
|
let cover_pk: Option<String> = row.get(2)?;
|
||||||
let max_size: Option<i64> = row.get(3)?;
|
let artist: Option<String> = row.get(3)?;
|
||||||
let default_ttl_secs: Option<i64> = row.get(4)?;
|
let max_size: Option<i64> = row.get(4)?;
|
||||||
|
let default_ttl_secs: Option<i64> = row.get(5)?;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
title,
|
title,
|
||||||
@@ -179,10 +184,11 @@ impl PersistenceManager {
|
|||||||
default_ttl: default_ttl_secs.map(|s| Duration::from_secs(s as u64)),
|
default_ttl: default_ttl_secs.map(|s| Duration::from_secs(s as u64)),
|
||||||
},
|
},
|
||||||
cover_pk,
|
cover_pk,
|
||||||
|
artist,
|
||||||
))
|
))
|
||||||
});
|
});
|
||||||
|
|
||||||
let (title, role, config, cover_pk) = match result {
|
let (title, role, config, cover_pk, artist) = match result {
|
||||||
Ok(data) => data,
|
Ok(data) => data,
|
||||||
Err(rusqlite::Error::QueryReturnedNoRows) => return Ok(None),
|
Err(rusqlite::Error::QueryReturnedNoRows) => return Ok(None),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -225,7 +231,7 @@ impl PersistenceManager {
|
|||||||
tracks.push_back(Arc::new(record));
|
tracks.push_back(Arc::new(record));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some((title, role, config, cover_pk, tracks)))
|
Ok(Some((title, role, config, cover_pk, artist, tracks)))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Supprime une playlist
|
/// Supprime une playlist
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ pub struct Playlist {
|
|||||||
title: RwLock<String>,
|
title: RwLock<String>,
|
||||||
role: RwLock<PlaylistRole>,
|
role: RwLock<PlaylistRole>,
|
||||||
cover_pk: RwLock<Option<String>>,
|
cover_pk: RwLock<Option<String>>,
|
||||||
|
artist: RwLock<Option<String>>,
|
||||||
state: Arc<AtomicU8>,
|
state: Arc<AtomicU8>,
|
||||||
pub core: Arc<RwLock<PlaylistCore>>,
|
pub core: Arc<RwLock<PlaylistCore>>,
|
||||||
pub persistent: bool,
|
pub persistent: bool,
|
||||||
@@ -57,6 +58,7 @@ impl Playlist {
|
|||||||
title: RwLock::new(title),
|
title: RwLock::new(title),
|
||||||
role: RwLock::new(role),
|
role: RwLock::new(role),
|
||||||
cover_pk: RwLock::new(cover_pk),
|
cover_pk: RwLock::new(cover_pk),
|
||||||
|
artist: RwLock::new(None),
|
||||||
state: Arc::new(AtomicU8::new(PlaylistState::Active as u8)),
|
state: Arc::new(AtomicU8::new(PlaylistState::Active as u8)),
|
||||||
core: Arc::new(RwLock::new(PlaylistCore::new(config))),
|
core: Arc::new(RwLock::new(PlaylistCore::new(config))),
|
||||||
persistent,
|
persistent,
|
||||||
@@ -114,6 +116,17 @@ impl Playlist {
|
|||||||
self.touch().await;
|
self.touch().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retourne l'artiste associé à la playlist.
|
||||||
|
pub async fn artist(&self) -> Option<String> {
|
||||||
|
self.artist.read().await.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Modifie l'artiste de la playlist.
|
||||||
|
pub async fn set_artist(&self, value: Option<String>) {
|
||||||
|
*self.artist.write().await = value;
|
||||||
|
self.touch().await;
|
||||||
|
}
|
||||||
|
|
||||||
/// Timestamp du dernier changement
|
/// Timestamp du dernier changement
|
||||||
pub async fn last_change(&self) -> SystemTime {
|
pub async fn last_change(&self) -> SystemTime {
|
||||||
*self.last_change.read().await
|
*self.last_change.read().await
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ impl ToDIDL for Album {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: self.formatted_title(),
|
title: self.formatted_title(),
|
||||||
class: "object.container.album.musicAlbum".to_string(),
|
class: "object.container.album.musicAlbum".to_string(),
|
||||||
|
artist: Some(self.artist.name.clone()),
|
||||||
|
album_art: self.image_cached.clone().or_else(|| self.image.clone()),
|
||||||
containers: Vec::new(),
|
containers: Vec::new(),
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
})
|
})
|
||||||
@@ -138,6 +140,8 @@ impl ToDIDL for Playlist {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: self.name.clone(),
|
title: self.name.clone(),
|
||||||
class: "object.container.playlistContainer".to_string(),
|
class: "object.container.playlistContainer".to_string(),
|
||||||
|
album_art: self.image_cached.clone().or_else(|| self.image.clone()),
|
||||||
|
artist: self.owner.as_ref().map(|o| o.name.clone()),
|
||||||
containers: Vec::new(),
|
containers: Vec::new(),
|
||||||
items: Vec::new(),
|
items: Vec::new(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -568,7 +568,8 @@ impl QobuzSource {
|
|||||||
for mut item in items {
|
for mut item in items {
|
||||||
// Extraire cache_pk depuis l'URL du resource
|
// Extraire cache_pk depuis l'URL du resource
|
||||||
let cache_pk = if let Some(resource) = item.resources.first() {
|
let cache_pk = if let Some(resource) = item.resources.first() {
|
||||||
resource.url
|
resource
|
||||||
|
.url
|
||||||
.strip_prefix("/audio/flac/")
|
.strip_prefix("/audio/flac/")
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
} else {
|
} else {
|
||||||
@@ -577,7 +578,11 @@ impl QobuzSource {
|
|||||||
|
|
||||||
if let Some(pk) = cache_pk {
|
if let Some(pk) = cache_pk {
|
||||||
// Récupérer track_id depuis metadata
|
// Récupérer track_id depuis metadata
|
||||||
if let Ok(Some(track_id_value)) = self.inner.cache_manager.get_audio_metadata(&pk, "qobuz_track_id") {
|
if let Ok(Some(track_id_value)) = self
|
||||||
|
.inner
|
||||||
|
.cache_manager
|
||||||
|
.get_audio_metadata(&pk, "qobuz_track_id")
|
||||||
|
{
|
||||||
if let Some(track_id) = track_id_value.as_str() {
|
if let Some(track_id) = track_id_value.as_str() {
|
||||||
item.id = format!("qobuz:track:{}", track_id);
|
item.id = format!("qobuz:track:{}", track_id);
|
||||||
} else {
|
} else {
|
||||||
@@ -647,11 +652,7 @@ impl QobuzSource {
|
|||||||
|
|
||||||
// 2. Cache cover
|
// 2. Cache cover
|
||||||
let cover_pk = if let Some(ref image_url) = album.image {
|
let cover_pk = if let Some(ref image_url) = album.image {
|
||||||
self.inner
|
self.inner.cache_manager.cache_cover(image_url).await.ok()
|
||||||
.cache_manager
|
|
||||||
.cache_cover(image_url)
|
|
||||||
.await
|
|
||||||
.ok()
|
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@@ -685,6 +686,11 @@ impl QobuzSource {
|
|||||||
.await
|
.await
|
||||||
.map_err(|e| MusicSourceError::PlaylistError(e.to_string()))?;
|
.map_err(|e| MusicSourceError::PlaylistError(e.to_string()))?;
|
||||||
|
|
||||||
|
writer
|
||||||
|
.set_artist(Some(album.artist.name.clone()))
|
||||||
|
.await
|
||||||
|
.map_err(|e| MusicSourceError::PlaylistError(e.to_string()))?;
|
||||||
|
|
||||||
if let Some(pk) = cover_pk {
|
if let Some(pk) = cover_pk {
|
||||||
writer
|
writer
|
||||||
.set_cover_pk(Some(pk))
|
.set_cover_pk(Some(pk))
|
||||||
@@ -731,6 +737,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Discover Catalog".to_string(),
|
title: "Discover Catalog".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -746,6 +754,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Discover Genres".to_string(),
|
title: "Discover Genres".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -761,6 +771,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "My Music".to_string(),
|
title: "My Music".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -776,6 +788,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Albums".to_string(),
|
title: "Albums".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -791,6 +805,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Tracks".to_string(),
|
title: "Tracks".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -806,6 +822,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Artists".to_string(),
|
title: "Artists".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -821,6 +839,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Playlists".to_string(),
|
title: "Playlists".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -892,6 +912,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: artist.name.clone(),
|
title: artist.name.clone(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: Some(artist.name.clone()),
|
||||||
|
album_art: artist.image_cached.clone().or_else(|| artist.image.clone()),
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
})
|
})
|
||||||
@@ -942,6 +964,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: tag.display_name().to_string(),
|
title: tag.display_name().to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
});
|
});
|
||||||
@@ -959,6 +983,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Playlists".to_string(),
|
title: "Playlists".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -973,6 +999,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Albums (Ideal Discography)".to_string(),
|
title: "Albums (Ideal Discography)".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -987,6 +1015,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Albums (Qobuzissime)".to_string(),
|
title: "Albums (Qobuzissime)".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -1001,6 +1031,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Albums (New Releases)".to_string(),
|
title: "Albums (New Releases)".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -1015,6 +1047,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Artists".to_string(),
|
title: "Artists".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -1065,7 +1099,11 @@ impl QobuzSource {
|
|||||||
|
|
||||||
let containers: Vec<Container> = albums
|
let containers: Vec<Container> = albums
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|album| album.to_didl_container("qobuz:discover:albums:qobuzissime").ok())
|
.filter_map(|album| {
|
||||||
|
album
|
||||||
|
.to_didl_container("qobuz:discover:albums:qobuzissime")
|
||||||
|
.ok()
|
||||||
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Ok(BrowseResult::Containers(containers))
|
Ok(BrowseResult::Containers(containers))
|
||||||
@@ -1107,6 +1145,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: artist.name.clone(),
|
title: artist.name.clone(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: Some(artist.name.clone()),
|
||||||
|
album_art: artist.image_cached.clone().or_else(|| artist.image.clone()),
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
})
|
})
|
||||||
@@ -1156,6 +1196,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: genre.name.clone(),
|
title: genre.name.clone(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
})
|
})
|
||||||
@@ -1188,6 +1230,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "New Releases".to_string(),
|
title: "New Releases".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -1202,6 +1246,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Ideal Discography".to_string(),
|
title: "Ideal Discography".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -1216,6 +1262,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Qobuzissime".to_string(),
|
title: "Qobuzissime".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -1230,6 +1278,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Editor Picks".to_string(),
|
title: "Editor Picks".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -1244,6 +1294,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Press Awards".to_string(),
|
title: "Press Awards".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -1258,6 +1310,8 @@ impl QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Qobuz Playlists".to_string(),
|
title: "Qobuz Playlists".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
}
|
}
|
||||||
@@ -1392,19 +1446,29 @@ impl QobuzSource {
|
|||||||
["qobuz", "discover"] => ObjectIdType::DiscoverCatalog,
|
["qobuz", "discover"] => ObjectIdType::DiscoverCatalog,
|
||||||
["qobuz", "discover", "playlists"] => ObjectIdType::DiscoverPlaylists,
|
["qobuz", "discover", "playlists"] => ObjectIdType::DiscoverPlaylists,
|
||||||
["qobuz", "discover", "albums", "ideal"] => ObjectIdType::DiscoverAlbumsIdeal,
|
["qobuz", "discover", "albums", "ideal"] => ObjectIdType::DiscoverAlbumsIdeal,
|
||||||
["qobuz", "discover", "albums", "qobuzissime"] => ObjectIdType::DiscoverAlbumsQobuzissime,
|
["qobuz", "discover", "albums", "qobuzissime"] => {
|
||||||
|
ObjectIdType::DiscoverAlbumsQobuzissime
|
||||||
|
}
|
||||||
["qobuz", "discover", "albums", "new"] => ObjectIdType::DiscoverAlbumsNew,
|
["qobuz", "discover", "albums", "new"] => ObjectIdType::DiscoverAlbumsNew,
|
||||||
["qobuz", "discover", "artists"] => ObjectIdType::DiscoverArtists,
|
["qobuz", "discover", "artists"] => ObjectIdType::DiscoverArtists,
|
||||||
["qobuz", "discover", "playlists", tag] => ObjectIdType::DiscoverPlaylistsByTag(tag.to_string()),
|
["qobuz", "discover", "playlists", tag] => {
|
||||||
|
ObjectIdType::DiscoverPlaylistsByTag(tag.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
// Discover Genres
|
// Discover Genres
|
||||||
["qobuz", "genres"] => ObjectIdType::DiscoverGenres,
|
["qobuz", "genres"] => ObjectIdType::DiscoverGenres,
|
||||||
["qobuz", "genre", id] => ObjectIdType::GenreRoot(id.to_string()),
|
["qobuz", "genre", id] => ObjectIdType::GenreRoot(id.to_string()),
|
||||||
["qobuz", "genre", id, "new-releases"] => ObjectIdType::GenreNewReleases(id.to_string()),
|
["qobuz", "genre", id, "new-releases"] => {
|
||||||
|
ObjectIdType::GenreNewReleases(id.to_string())
|
||||||
|
}
|
||||||
["qobuz", "genre", id, "ideal"] => ObjectIdType::GenreIdealDiscography(id.to_string()),
|
["qobuz", "genre", id, "ideal"] => ObjectIdType::GenreIdealDiscography(id.to_string()),
|
||||||
["qobuz", "genre", id, "qobuzissime"] => ObjectIdType::GenreQobuzissime(id.to_string()),
|
["qobuz", "genre", id, "qobuzissime"] => ObjectIdType::GenreQobuzissime(id.to_string()),
|
||||||
["qobuz", "genre", id, "editor-picks"] => ObjectIdType::GenreEditorPicks(id.to_string()),
|
["qobuz", "genre", id, "editor-picks"] => {
|
||||||
["qobuz", "genre", id, "press-awards"] => ObjectIdType::GenrePressAwards(id.to_string()),
|
ObjectIdType::GenreEditorPicks(id.to_string())
|
||||||
|
}
|
||||||
|
["qobuz", "genre", id, "press-awards"] => {
|
||||||
|
ObjectIdType::GenrePressAwards(id.to_string())
|
||||||
|
}
|
||||||
["qobuz", "genre", id, "playlists"] => ObjectIdType::GenrePlaylists(id.to_string()),
|
["qobuz", "genre", id, "playlists"] => ObjectIdType::GenrePlaylists(id.to_string()),
|
||||||
|
|
||||||
// Favourites
|
// Favourites
|
||||||
@@ -1440,13 +1504,13 @@ enum ObjectIdType {
|
|||||||
|
|
||||||
// Discover Genres
|
// Discover Genres
|
||||||
DiscoverGenres,
|
DiscoverGenres,
|
||||||
GenreRoot(String), // genre_id
|
GenreRoot(String), // genre_id
|
||||||
GenreNewReleases(String), // genre_id
|
GenreNewReleases(String), // genre_id
|
||||||
GenreIdealDiscography(String), // genre_id
|
GenreIdealDiscography(String), // genre_id
|
||||||
GenreQobuzissime(String), // genre_id
|
GenreQobuzissime(String), // genre_id
|
||||||
GenreEditorPicks(String), // genre_id
|
GenreEditorPicks(String), // genre_id
|
||||||
GenrePressAwards(String), // genre_id
|
GenrePressAwards(String), // genre_id
|
||||||
GenrePlaylists(String), // genre_id
|
GenrePlaylists(String), // genre_id
|
||||||
|
|
||||||
// Favourites
|
// Favourites
|
||||||
Favourites,
|
Favourites,
|
||||||
@@ -1488,6 +1552,8 @@ impl MusicSource for QobuzSource {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Qobuz".to_string(),
|
title: "Qobuz".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![
|
containers: vec![
|
||||||
self.build_discover_catalog_container(),
|
self.build_discover_catalog_container(),
|
||||||
self.build_discover_genres_container(),
|
self.build_discover_genres_container(),
|
||||||
@@ -1509,16 +1575,22 @@ impl MusicSource for QobuzSource {
|
|||||||
ObjectIdType::DiscoverCatalog => self.browse_discover_catalog().await,
|
ObjectIdType::DiscoverCatalog => self.browse_discover_catalog().await,
|
||||||
ObjectIdType::DiscoverPlaylists => self.browse_discover_playlists().await,
|
ObjectIdType::DiscoverPlaylists => self.browse_discover_playlists().await,
|
||||||
ObjectIdType::DiscoverAlbumsIdeal => self.browse_discover_albums_ideal().await,
|
ObjectIdType::DiscoverAlbumsIdeal => self.browse_discover_albums_ideal().await,
|
||||||
ObjectIdType::DiscoverAlbumsQobuzissime => self.browse_discover_albums_qobuzissime().await,
|
ObjectIdType::DiscoverAlbumsQobuzissime => {
|
||||||
|
self.browse_discover_albums_qobuzissime().await
|
||||||
|
}
|
||||||
ObjectIdType::DiscoverAlbumsNew => self.browse_discover_albums_new().await,
|
ObjectIdType::DiscoverAlbumsNew => self.browse_discover_albums_new().await,
|
||||||
ObjectIdType::DiscoverArtists => self.browse_discover_artists().await,
|
ObjectIdType::DiscoverArtists => self.browse_discover_artists().await,
|
||||||
ObjectIdType::DiscoverPlaylistsByTag(tag) => self.browse_discover_playlists_tag(&tag).await,
|
ObjectIdType::DiscoverPlaylistsByTag(tag) => {
|
||||||
|
self.browse_discover_playlists_tag(&tag).await
|
||||||
|
}
|
||||||
|
|
||||||
// Discover Genres
|
// Discover Genres
|
||||||
ObjectIdType::DiscoverGenres => self.browse_discover_genres().await,
|
ObjectIdType::DiscoverGenres => self.browse_discover_genres().await,
|
||||||
ObjectIdType::GenreRoot(id) => self.browse_genre(&id).await,
|
ObjectIdType::GenreRoot(id) => self.browse_genre(&id).await,
|
||||||
ObjectIdType::GenreNewReleases(id) => self.browse_genre_new_releases(&id).await,
|
ObjectIdType::GenreNewReleases(id) => self.browse_genre_new_releases(&id).await,
|
||||||
ObjectIdType::GenreIdealDiscography(id) => self.browse_genre_ideal_discography(&id).await,
|
ObjectIdType::GenreIdealDiscography(id) => {
|
||||||
|
self.browse_genre_ideal_discography(&id).await
|
||||||
|
}
|
||||||
ObjectIdType::GenreQobuzissime(id) => self.browse_genre_qobuzissime(&id).await,
|
ObjectIdType::GenreQobuzissime(id) => self.browse_genre_qobuzissime(&id).await,
|
||||||
ObjectIdType::GenreEditorPicks(id) => self.browse_genre_editor_picks(&id).await,
|
ObjectIdType::GenreEditorPicks(id) => self.browse_genre_editor_picks(&id).await,
|
||||||
ObjectIdType::GenrePressAwards(id) => self.browse_genre_press_awards(&id).await,
|
ObjectIdType::GenrePressAwards(id) => self.browse_genre_press_awards(&id).await,
|
||||||
@@ -1952,11 +2024,7 @@ impl MusicSource for QobuzSource {
|
|||||||
.get_or_create_album_playlist_items(&album_id, usize::MAX)
|
.get_or_create_album_playlist_items(&album_id, usize::MAX)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let items: Vec<Item> = all_items
|
let items: Vec<Item> = all_items.into_iter().skip(offset).take(limit).collect();
|
||||||
.into_iter()
|
|
||||||
.skip(offset)
|
|
||||||
.take(limit)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(BrowseResult::Items(items))
|
Ok(BrowseResult::Items(items))
|
||||||
}
|
}
|
||||||
|
|||||||
2027
pmoqobuz/src/source.rs.bak
Normal file
2027
pmoqobuz/src/source.rs.bak
Normal file
File diff suppressed because it is too large
Load Diff
@@ -997,6 +997,8 @@ mod tests {
|
|||||||
searchable: Some("1".to_string()),
|
searchable: Some("1".to_string()),
|
||||||
title: "Test Source".to_string(),
|
title: "Test Source".to_string(),
|
||||||
class: "object.container".to_string(),
|
class: "object.container".to_string(),
|
||||||
|
artist: None,
|
||||||
|
album_art: None,
|
||||||
containers: vec![],
|
containers: vec![],
|
||||||
items: vec![],
|
items: vec![],
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user