amélioration de la webapp
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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")]
|
||||
|
||||
Reference in New Issue
Block a user