amélioration de la webapp

This commit is contained in:
2025-10-19 13:42:29 +02:00
parent 756ae7f82c
commit 208fe8be76
179 changed files with 2426 additions and 1685 deletions

View File

@@ -16,9 +16,9 @@
//! cargo run --example radio_paradise
//! ```
use pmosource::{async_trait, BrowseResult, MusicSource, MusicSourceError, Result};
use pmodidl::{Container, Item, Resource};
use pmoplaylist::{FifoPlaylist, Track};
use pmosource::{async_trait, BrowseResult, MusicSource, MusicSourceError, Result};
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
@@ -353,7 +353,10 @@ async fn main() -> anyhow::Result<()> {
println!("Source: {}", source.name());
println!("ID: {}", source.id());
println!("Supports FIFO: {}", source.supports_fifo());
println!("Default image size: {} bytes\n", source.default_image().len());
println!(
"Default image size: {} bytes\n",
source.default_image().len()
);
// Add some sample tracks
println!("Adding sample tracks...");
@@ -427,14 +430,13 @@ async fn main() -> anyhow::Result<()> {
// Track changes
println!("Change Tracking:");
println!(" Update ID: {}", source.update_id().await);
println!(
" Last Change: {:?}\n",
source.last_change().await.unwrap()
);
println!(" Last Change: {:?}\n", source.last_change().await.unwrap());
// Simulate caching a track
println!("Simulating cache for rp-001...");
source.cache_track("rp-001", "cached-abc123".to_string()).await?;
source
.cache_track("rp-001", "cached-abc123".to_string())
.await?;
let cached_uri = source.resolve_uri("rp-001").await?;
println!(" Cached URI: {}\n", cached_uri);

View File

@@ -22,7 +22,7 @@
#[cfg(feature = "server")]
use axum::{
extract::Path,
http::{StatusCode, header},
http::{header, StatusCode},
response::{IntoResponse, Response},
routing::{delete, get},
Json, Router,
@@ -313,21 +313,19 @@ async fn get_source_capabilities(Path(id): Path<String>) -> impl IntoResponse {
)]
async fn get_source_statistics(Path(id): Path<String>) -> impl IntoResponse {
match get_source(&id).await {
Some(source) => {
match source.statistics().await {
Ok(stats) => {
let stats_info: SourceStatisticsInfo = stats.into();
(StatusCode::OK, Json(stats_info)).into_response()
}
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to get statistics: {}", e),
}),
)
.into_response(),
Some(source) => match source.statistics().await {
Ok(stats) => {
let stats_info: SourceStatisticsInfo = stats.into();
(StatusCode::OK, Json(stats_info)).into_response()
}
}
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to get statistics: {}", e),
}),
)
.into_response(),
},
None => (
StatusCode::NOT_FOUND,
Json(ErrorResponse {
@@ -357,27 +355,25 @@ async fn get_source_statistics(Path(id): Path<String>) -> impl IntoResponse {
)]
async fn get_source_root(Path(id): Path<String>) -> impl IntoResponse {
match get_source(&id).await {
Some(source) => {
match source.root_container().await {
Ok(container) => {
let root = SourceRootContainer {
id: container.id,
parent_id: container.parent_id,
title: container.title,
class: container.class,
child_count: container.child_count,
};
(StatusCode::OK, Json(root)).into_response()
}
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to get root container: {}", e),
}),
)
.into_response(),
Some(source) => match source.root_container().await {
Ok(container) => {
let root = SourceRootContainer {
id: container.id,
parent_id: container.parent_id,
title: container.title,
class: container.class,
child_count: container.child_count,
};
(StatusCode::OK, Json(root)).into_response()
}
}
Err(e) => (
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to get root container: {}", e),
}),
)
.into_response(),
},
None => (
StatusCode::NOT_FOUND,
Json(ErrorResponse {

View File

@@ -18,12 +18,12 @@
//! └─ collection: "qobuz"
//! ```
use crate::{CacheStatus, MusicSourceError, Result};
use pmoaudiocache::{AudioMetadata, Cache as AudioCache};
use pmocovers::Cache as CoverCache;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;
use pmocovers::Cache as CoverCache;
use pmoaudiocache::{Cache as AudioCache, AudioMetadata};
use crate::{MusicSourceError, Result, CacheStatus};
/// Métadonnées d'une piste en cache
#[derive(Debug, Clone)]
@@ -76,15 +76,13 @@ impl SourceCacheManager {
/// Retourne une erreur si les caches ne sont pas encore initialisés dans le registre
#[cfg(feature = "server")]
pub fn from_registry(collection_id: String) -> Result<Self> {
let cover_cache = pmoupnp::cache_registry::get_cover_cache()
.ok_or_else(|| MusicSourceError::CacheError(
"Cover cache not initialized in registry".to_string()
))?;
let cover_cache = pmoupnp::cache_registry::get_cover_cache().ok_or_else(|| {
MusicSourceError::CacheError("Cover cache not initialized in registry".to_string())
})?;
let audio_cache = pmoupnp::cache_registry::get_audio_cache()
.ok_or_else(|| MusicSourceError::CacheError(
"Audio cache not initialized in registry".to_string()
))?;
let audio_cache = pmoupnp::cache_registry::get_audio_cache().ok_or_else(|| {
MusicSourceError::CacheError("Audio cache not initialized in registry".to_string())
})?;
Ok(Self {
track_cache: RwLock::new(HashMap::new()),
@@ -132,7 +130,7 @@ impl SourceCacheManager {
#[cfg(not(feature = "server"))]
{
return Err(MusicSourceError::CacheError(
"Server feature not enabled".to_string()
"Server feature not enabled".to_string(),
));
}
}
@@ -190,7 +188,7 @@ impl SourceCacheManager {
#[cfg(not(feature = "server"))]
{
Err(MusicSourceError::CacheError(
"Server feature not enabled - cannot build cover URL".to_string()
"Server feature not enabled - cannot build cover URL".to_string(),
))
}
}
@@ -207,11 +205,11 @@ impl SourceCacheManager {
/// # Returns
///
/// La clé primaire (pk) de la piste dans le cache
pub async fn cache_audio(&self, url: &str, _metadata: Option<AudioMetadata>)
-> Result<String> {
pub async fn cache_audio(&self, url: &str, _metadata: Option<AudioMetadata>) -> Result<String> {
// Note: Les métadonnées seront extraites automatiquement par le cache audio
// lors de la conversion FLAC
let pk = self.audio_cache
let pk = self
.audio_cache
.add_from_url(url, Some(&self.collection_id))
.await
.map_err(|e| MusicSourceError::CacheError(e.to_string()))?;
@@ -246,7 +244,8 @@ impl SourceCacheManager {
/// Obtenir les statistiques du cache pour cette source
pub async fn statistics(&self) -> CacheStatistics {
let cache = self.track_cache.read().await;
let cached_count = cache.values()
let cached_count = cache
.values()
.filter(|m| m.cached_audio_pk.is_some())
.count();

View File

@@ -693,7 +693,9 @@ pub trait MusicSource: Debug + Send + Sync {
/// ```
async fn cache_item(&self, object_id: &str) -> Result<CacheStatus> {
let _ = object_id;
Err(MusicSourceError::NotSupported("Caching not supported".to_string()))
Err(MusicSourceError::NotSupported(
"Caching not supported".to_string(),
))
}
/// Add an item to favorites
@@ -715,7 +717,9 @@ pub trait MusicSource: Debug + Send + Sync {
/// ```
async fn add_favorite(&self, object_id: &str) -> Result<()> {
let _ = object_id;
Err(MusicSourceError::NotSupported("Favorites not supported".to_string()))
Err(MusicSourceError::NotSupported(
"Favorites not supported".to_string(),
))
}
/// Remove an item from favorites
@@ -731,7 +735,9 @@ pub trait MusicSource: Debug + Send + Sync {
/// Returns `MusicSourceError::NotSupported` if favorites are not available.
async fn remove_favorite(&self, object_id: &str) -> Result<()> {
let _ = object_id;
Err(MusicSourceError::NotSupported("Favorites not supported".to_string()))
Err(MusicSourceError::NotSupported(
"Favorites not supported".to_string(),
))
}
/// Check if an item is in favorites
@@ -749,7 +755,9 @@ pub trait MusicSource: Debug + Send + Sync {
/// Returns `MusicSourceError::NotSupported` if favorites are not available.
async fn is_favorite(&self, object_id: &str) -> Result<bool> {
let _ = object_id;
Err(MusicSourceError::NotSupported("Favorites not supported".to_string()))
Err(MusicSourceError::NotSupported(
"Favorites not supported".to_string(),
))
}
/// Get user playlists
@@ -773,7 +781,9 @@ pub trait MusicSource: Debug + Send + Sync {
/// }
/// ```
async fn get_user_playlists(&self) -> Result<Vec<Container>> {
Err(MusicSourceError::NotSupported("Playlists not supported".to_string()))
Err(MusicSourceError::NotSupported(
"Playlists not supported".to_string(),
))
}
/// Add an item to a playlist
@@ -788,7 +798,9 @@ pub trait MusicSource: Debug + Send + Sync {
/// Returns `MusicSourceError::NotSupported` if playlists are not available.
async fn add_to_playlist(&self, playlist_id: &str, item_id: &str) -> Result<()> {
let _ = (playlist_id, item_id);
Err(MusicSourceError::NotSupported("Playlists not supported".to_string()))
Err(MusicSourceError::NotSupported(
"Playlists not supported".to_string(),
))
}
/// Get total item count for a container
@@ -907,7 +919,7 @@ pub use pmodidl;
pub use pmoplaylist;
// Re-export cache types
pub use cache::{TrackMetadata, SourceCacheManager, CacheStatistics};
pub use cache::{CacheStatistics, SourceCacheManager, TrackMetadata};
// Server extension modules (feature-gated)
#[cfg(feature = "server")]

View File

@@ -30,7 +30,10 @@
//! ```
#[cfg(feature = "server")]
use crate::api::{register_source, unregister_source, list_all_sources, get_source, create_sources_router, SourcesApiDoc};
use crate::api::{
create_sources_router, get_source, list_all_sources, register_source, unregister_source,
SourcesApiDoc,
};
#[cfg(feature = "server")]
use crate::pmoserver_ext::MusicSourceExt;
#[cfg(feature = "server")]