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

@@ -1,6 +1,6 @@
use crate::connectionmanager::variables::{
A_ARG_TYPE_CONNECTIONID, A_ARG_TYPE_RCSID, A_ARG_TYPE_AVTRANSPORTID,
A_ARG_TYPE_PROTOCOLINFO, A_ARG_TYPE_DIRECTION, A_ARG_TYPE_CONNECTIONSTATUS,
A_ARG_TYPE_AVTRANSPORTID, A_ARG_TYPE_CONNECTIONID, A_ARG_TYPE_CONNECTIONSTATUS,
A_ARG_TYPE_DIRECTION, A_ARG_TYPE_PROTOCOLINFO, A_ARG_TYPE_RCSID,
};
use pmoupnp::define_action;

View File

@@ -1,4 +1,4 @@
use crate::connectionmanager::variables::{SOURCEPROTOCOLINFO, SINKPROTOCOLINFO};
use crate::connectionmanager::variables::{SINKPROTOCOLINFO, SOURCEPROTOCOLINFO};
use pmoupnp::define_action;
define_action! {

View File

@@ -1,7 +1,7 @@
mod getprotocolinfo;
mod getcurrentconnectionids;
mod getcurrentconnectioninfo;
mod getprotocolinfo;
pub use getprotocolinfo::GETPROTOCOLINFO;
pub use getcurrentconnectionids::GETCURRENTCONNECTIONIDS;
pub use getcurrentconnectioninfo::GETCURRENTCONNECTIONINFO;
pub use getprotocolinfo::GETPROTOCOLINFO;

View File

@@ -65,14 +65,14 @@
use pmoupnp::define_service;
pub mod variables;
pub mod actions;
pub mod variables;
use actions::{GETCURRENTCONNECTIONIDS, GETCURRENTCONNECTIONINFO, GETPROTOCOLINFO};
use variables::{
A_ARG_TYPE_AVTRANSPORTID, A_ARG_TYPE_CONNECTIONID, A_ARG_TYPE_CONNECTIONSTATUS,
A_ARG_TYPE_DIRECTION, A_ARG_TYPE_PROTOCOLINFO, A_ARG_TYPE_RCSID,
CURRENTCONNECTIONIDS, SINKPROTOCOLINFO, SOURCEPROTOCOLINFO
A_ARG_TYPE_DIRECTION, A_ARG_TYPE_PROTOCOLINFO, A_ARG_TYPE_RCSID, CURRENTCONNECTIONIDS,
SINKPROTOCOLINFO, SOURCEPROTOCOLINFO,
};
// Service ConnectionManager:1 conforme à la spécification UPnP AV pour MediaServer

View File

@@ -1,19 +1,19 @@
mod a_arg_type_avtransportid;
mod a_arg_type_connectionid;
mod a_arg_type_connectionstatus;
mod a_arg_type_direction;
mod a_arg_type_protocolinfo;
mod a_arg_type_rcsid;
mod a_arg_type_avtransportid;
mod currentconnectionids;
mod sourceprotocolinfo;
mod sinkprotocolinfo;
mod sourceprotocolinfo;
pub use a_arg_type_avtransportid::A_ARG_TYPE_AVTRANSPORTID;
pub use a_arg_type_connectionid::A_ARG_TYPE_CONNECTIONID;
pub use a_arg_type_connectionstatus::A_ARG_TYPE_CONNECTIONSTATUS;
pub use a_arg_type_direction::A_ARG_TYPE_DIRECTION;
pub use a_arg_type_protocolinfo::A_ARG_TYPE_PROTOCOLINFO;
pub use a_arg_type_rcsid::A_ARG_TYPE_RCSID;
pub use a_arg_type_avtransportid::A_ARG_TYPE_AVTRANSPORTID;
pub use currentconnectionids::CURRENTCONNECTIONIDS;
pub use sourceprotocolinfo::SOURCEPROTOCOLINFO;
pub use sinkprotocolinfo::SINKPROTOCOLINFO;
pub use sourceprotocolinfo::SOURCEPROTOCOLINFO;

View File

@@ -10,8 +10,8 @@
//! - **Search** : Recherche dans les sources qui le supportent
//! - **Update ID** : Suivi des changements pour les notifications UPnP
use pmosource::api::{list_all_sources, get_source as get_source_from_registry};
use pmodidl::{Container, DIDLLite};
use pmosource::api::{get_source as get_source_from_registry, list_all_sources};
use pmosource::{BrowseResult, MusicSource};
use std::sync::Arc;
@@ -28,8 +28,7 @@ fn to_didl_lite(containers: &[Container], items: &[pmodidl::Item]) -> Result<Str
items: items.to_vec(),
};
quick_xml::se::to_string(&didl)
.map_err(|e| format!("Failed to serialize DIDL-Lite: {}", e))
quick_xml::se::to_string(&didl).map_err(|e| format!("Failed to serialize DIDL-Lite: {}", e))
}
/// Handler pour le service ContentDirectory
@@ -208,11 +207,7 @@ impl ContentHandler {
requested_count as usize
};
let paginated: Vec<Container> = containers
.into_iter()
.skip(start)
.take(count)
.collect();
let paginated: Vec<Container> = containers.into_iter().skip(start).take(count).collect();
let returned = paginated.len();
let didl = to_didl_lite(&paginated, &[])?;
@@ -278,11 +273,7 @@ impl ContentHandler {
// On commence dans les items
containers.clear();
let item_start = start - total_containers;
items = items
.into_iter()
.skip(item_start)
.take(count)
.collect();
items = items.into_iter().skip(item_start).take(count).collect();
}
let returned = (containers.len() + items.len()) as u32;

View File

@@ -1,10 +1,9 @@
use crate::contentdirectory::handlers;
use crate::contentdirectory::variables::{
A_ARG_TYPE_OBJECTID, A_ARG_TYPE_BROWSEFLAG, A_ARG_TYPE_FILTER,
A_ARG_TYPE_SORTCRITERIA, A_ARG_TYPE_INDEX, A_ARG_TYPE_COUNT,
A_ARG_TYPE_RESULT, A_ARG_TYPE_UPDATEID,
A_ARG_TYPE_BROWSEFLAG, A_ARG_TYPE_COUNT, A_ARG_TYPE_FILTER, A_ARG_TYPE_INDEX,
A_ARG_TYPE_OBJECTID, A_ARG_TYPE_RESULT, A_ARG_TYPE_SORTCRITERIA, A_ARG_TYPE_UPDATEID,
};
use pmoupnp::define_action;
use crate::contentdirectory::handlers;
define_action! {
pub static BROWSE = "Browse" stateless {

View File

@@ -1,6 +1,6 @@
use crate::contentdirectory::handlers;
use crate::contentdirectory::variables::SEARCHCAPABILITIES;
use pmoupnp::define_action;
use crate::contentdirectory::handlers;
define_action! {
pub static GETSEARCHCAPABILITIES = "GetSearchCapabilities" stateless {

View File

@@ -1,6 +1,6 @@
use crate::contentdirectory::handlers;
use crate::contentdirectory::variables::SORTCAPABILITIES;
use pmoupnp::define_action;
use crate::contentdirectory::handlers;
define_action! {
pub static GETSORTCAPABILITIES = "GetSortCapabilities" stateless {

View File

@@ -1,6 +1,6 @@
use crate::contentdirectory::handlers;
use crate::contentdirectory::variables::SYSTEMUPDATEID;
use pmoupnp::define_action;
use crate::contentdirectory::handlers;
define_action! {
pub static GETSYSTEMUPDATEID = "GetSystemUpdateID" stateless {

View File

@@ -1,11 +1,11 @@
mod browse;
mod search;
mod getsearchcapabilities;
mod getsortcapabilities;
mod getsystemupdateid;
mod search;
pub use browse::BROWSE;
pub use search::SEARCH;
pub use getsearchcapabilities::GETSEARCHCAPABILITIES;
pub use getsortcapabilities::GETSORTCAPABILITIES;
pub use getsystemupdateid::GETSYSTEMUPDATEID;
pub use search::SEARCH;

View File

@@ -1,10 +1,9 @@
use crate::contentdirectory::handlers;
use crate::contentdirectory::variables::{
A_ARG_TYPE_OBJECTID, A_ARG_TYPE_SEARCHCRITERIA, A_ARG_TYPE_FILTER,
A_ARG_TYPE_SORTCRITERIA, A_ARG_TYPE_INDEX, A_ARG_TYPE_COUNT,
A_ARG_TYPE_RESULT, A_ARG_TYPE_UPDATEID,
A_ARG_TYPE_COUNT, A_ARG_TYPE_FILTER, A_ARG_TYPE_INDEX, A_ARG_TYPE_OBJECTID, A_ARG_TYPE_RESULT,
A_ARG_TYPE_SEARCHCRITERIA, A_ARG_TYPE_SORTCRITERIA, A_ARG_TYPE_UPDATEID,
};
use pmoupnp::define_action;
use crate::contentdirectory::handlers;
define_action! {
pub static SEARCH = "Search" stateless {

View File

@@ -23,9 +23,9 @@
//! - [`get_sort_capabilities_handler`] : Capacités de tri supportées
//! - [`get_system_update_id_handler`] : ID de mise à jour du système
use pmoupnp::{action_handler, get, set};
use pmoupnp::actions::{ActionError, ActionHandler};
use crate::content_handler::ContentHandler;
use pmoupnp::actions::{ActionError, ActionHandler};
use pmoupnp::{action_handler, get, set};
use tracing::{debug, error};
/// Handler pour l'action Browse.
@@ -76,7 +76,10 @@ pub fn browse_handler() -> ActionHandler {
set!(&mut data, "TotalMatches", total);
set!(&mut data, "UpdateID", update_id);
debug!("✅ Browse completed: returned={}, total={}", returned, total);
debug!(
"✅ Browse completed: returned={}, total={}",
returned, total
);
Ok(data)
})
}
@@ -128,7 +131,10 @@ pub fn search_handler() -> ActionHandler {
set!(&mut data, "TotalMatches", total);
set!(&mut data, "UpdateID", update_id);
debug!("✅ Search completed: returned={}, total={}", returned, total);
debug!(
"✅ Search completed: returned={}, total={}",
returned, total
);
Ok(data)
})
}

View File

@@ -74,18 +74,15 @@
use pmoupnp::define_service;
pub mod variables;
pub mod actions;
pub mod handlers;
pub mod variables;
use actions::{
BROWSE, SEARCH, GETSEARCHCAPABILITIES, GETSORTCAPABILITIES, GETSYSTEMUPDATEID
};
use actions::{BROWSE, GETSEARCHCAPABILITIES, GETSORTCAPABILITIES, GETSYSTEMUPDATEID, SEARCH};
use variables::{
A_ARG_TYPE_OBJECTID, A_ARG_TYPE_BROWSEFLAG, A_ARG_TYPE_FILTER,
A_ARG_TYPE_SORTCRITERIA, A_ARG_TYPE_INDEX, A_ARG_TYPE_COUNT,
A_ARG_TYPE_UPDATEID, A_ARG_TYPE_RESULT, A_ARG_TYPE_SEARCHCRITERIA,
SEARCHCAPABILITIES, SORTCAPABILITIES, SYSTEMUPDATEID
A_ARG_TYPE_BROWSEFLAG, A_ARG_TYPE_COUNT, A_ARG_TYPE_FILTER, A_ARG_TYPE_INDEX,
A_ARG_TYPE_OBJECTID, A_ARG_TYPE_RESULT, A_ARG_TYPE_SEARCHCRITERIA, A_ARG_TYPE_SORTCRITERIA,
A_ARG_TYPE_UPDATEID, SEARCHCAPABILITIES, SORTCAPABILITIES, SYSTEMUPDATEID,
};
// Service ContentDirectory:1 conforme à la spécification UPnP AV pour MediaServer

View File

@@ -1,25 +1,25 @@
mod a_arg_type_objectid;
mod a_arg_type_browseflag;
mod a_arg_type_filter;
mod a_arg_type_sortcriteria;
mod a_arg_type_index;
mod a_arg_type_count;
mod a_arg_type_updateid;
mod a_arg_type_filter;
mod a_arg_type_index;
mod a_arg_type_objectid;
mod a_arg_type_result;
mod a_arg_type_searchcriteria;
mod a_arg_type_sortcriteria;
mod a_arg_type_updateid;
mod searchcapabilities;
mod sortcapabilities;
mod systemupdateid;
pub use a_arg_type_objectid::A_ARG_TYPE_OBJECTID;
pub use a_arg_type_browseflag::A_ARG_TYPE_BROWSEFLAG;
pub use a_arg_type_filter::A_ARG_TYPE_FILTER;
pub use a_arg_type_sortcriteria::A_ARG_TYPE_SORTCRITERIA;
pub use a_arg_type_index::A_ARG_TYPE_INDEX;
pub use a_arg_type_count::A_ARG_TYPE_COUNT;
pub use a_arg_type_updateid::A_ARG_TYPE_UPDATEID;
pub use a_arg_type_filter::A_ARG_TYPE_FILTER;
pub use a_arg_type_index::A_ARG_TYPE_INDEX;
pub use a_arg_type_objectid::A_ARG_TYPE_OBJECTID;
pub use a_arg_type_result::A_ARG_TYPE_RESULT;
pub use a_arg_type_searchcriteria::A_ARG_TYPE_SEARCHCRITERIA;
pub use a_arg_type_sortcriteria::A_ARG_TYPE_SORTCRITERIA;
pub use a_arg_type_updateid::A_ARG_TYPE_UPDATEID;
pub use searchcapabilities::SEARCHCAPABILITIES;
pub use sortcapabilities::SORTCAPABILITIES;
pub use systemupdateid::SYSTEMUPDATEID;

View File

@@ -3,11 +3,8 @@
use once_cell::sync::Lazy;
use std::sync::Arc;
use crate::{connectionmanager::CONNECTIONMANAGER, contentdirectory::CONTENTDIRECTORY};
use pmoupnp::devices::Device;
use crate::{
contentdirectory::CONTENTDIRECTORY,
connectionmanager::CONNECTIONMANAGER,
};
/// Device MediaServer UPnP.
///
@@ -52,10 +49,12 @@ pub static MEDIA_SERVER: Lazy<Arc<Device>> = Lazy::new(|| {
device.set_udn_prefix("pmomusic".to_string());
// Ajouter les deux services obligatoires
device.add_service(Arc::clone(&CONTENTDIRECTORY))
device
.add_service(Arc::clone(&CONTENTDIRECTORY))
.expect("Failed to add ContentDirectory service");
device.add_service(Arc::clone(&CONNECTIONMANAGER))
device
.add_service(Arc::clone(&CONNECTIONMANAGER))
.expect("Failed to add ConnectionManager service");
Arc::new(device)

View File

@@ -63,23 +63,23 @@
//! server.register_qobuz_from_config().await?;
//! ```
pub mod contentdirectory;
pub mod connectionmanager;
pub mod device;
pub mod source_registry;
pub mod server_ext;
pub mod content_handler;
pub mod contentdirectory;
pub mod device;
pub mod server_ext;
pub mod source_registry;
pub mod sources;
// API REST pour l'enregistrement des sources (requires features qobuz/paradise)
#[cfg(any(feature = "qobuz", feature = "paradise"))]
pub mod sources_api;
pub use device::MEDIA_SERVER;
pub use source_registry::SourceRegistry;
pub use server_ext::{MediaServerExt, get_source_registry, MusicSourceExt};
pub use content_handler::ContentHandler;
pub use sources::{SourcesExt, SourceInitError};
pub use device::MEDIA_SERVER;
pub use server_ext::{MediaServerExt, MusicSourceExt, get_source_registry};
pub use source_registry::SourceRegistry;
pub use sources::{SourceInitError, SourcesExt};
// Re-export sources when features are enabled
#[cfg(feature = "qobuz")]

View File

@@ -7,8 +7,8 @@
//! méthodes spécifiques au MediaServer UPnP.
use async_trait::async_trait;
use pmosource::MusicSource;
use pmoserver::Server;
use pmosource::MusicSource;
use std::sync::Arc;
// Réexporter le trait de base de pmosource
@@ -23,7 +23,10 @@ pub use pmosource::MusicSourceExt;
///
/// let sources = pmosource::api::list_all_sources().await;
/// ```
#[deprecated(since = "0.2.0", note = "Use pmosource::api::list_all_sources() directly")]
#[deprecated(
since = "0.2.0",
note = "Use pmosource::api::list_all_sources() directly"
)]
pub async fn get_source_registry() -> Vec<Arc<dyn MusicSource>> {
pmosource::api::list_all_sources().await
}

View File

@@ -210,8 +210,8 @@ impl Default for SourceRegistry {
#[cfg(test)]
mod tests {
use super::*;
use pmosource::{MusicSource, Result, BrowseResult};
use pmodidl::{Container, Item};
use pmosource::{BrowseResult, MusicSource, Result};
use std::time::SystemTime;
#[derive(Debug)]
@@ -308,9 +308,15 @@ mod tests {
async fn test_list_all() {
let registry = SourceRegistry::new();
registry.register(Arc::new(TestSource::new("test-1", "Test 1"))).await;
registry.register(Arc::new(TestSource::new("test-2", "Test 2"))).await;
registry.register(Arc::new(TestSource::new("test-3", "Test 3"))).await;
registry
.register(Arc::new(TestSource::new("test-1", "Test 1")))
.await;
registry
.register(Arc::new(TestSource::new("test-2", "Test 2")))
.await;
registry
.register(Arc::new(TestSource::new("test-3", "Test 3")))
.await;
let sources = registry.list_all().await;
assert_eq!(sources.len(), 3);
@@ -321,17 +327,23 @@ mod tests {
let registry = SourceRegistry::new();
assert_eq!(registry.count().await, 0);
registry.register(Arc::new(TestSource::new("test-1", "Test 1"))).await;
registry
.register(Arc::new(TestSource::new("test-1", "Test 1")))
.await;
assert_eq!(registry.count().await, 1);
registry.register(Arc::new(TestSource::new("test-2", "Test 2"))).await;
registry
.register(Arc::new(TestSource::new("test-2", "Test 2")))
.await;
assert_eq!(registry.count().await, 2);
}
#[tokio::test]
async fn test_remove() {
let registry = SourceRegistry::new();
registry.register(Arc::new(TestSource::new("test-1", "Test 1"))).await;
registry
.register(Arc::new(TestSource::new("test-1", "Test 1")))
.await;
assert!(registry.contains("test-1").await);
assert!(registry.remove("test-1").await);
@@ -345,7 +357,9 @@ mod tests {
assert!(!registry.contains("test-1").await);
registry.register(Arc::new(TestSource::new("test-1", "Test 1"))).await;
registry
.register(Arc::new(TestSource::new("test-1", "Test 1")))
.await;
assert!(registry.contains("test-1").await);
assert!(!registry.contains("test-2").await);
@@ -355,11 +369,15 @@ mod tests {
async fn test_replace_source() {
let registry = SourceRegistry::new();
registry.register(Arc::new(TestSource::new("test-1", "Old Name"))).await;
registry
.register(Arc::new(TestSource::new("test-1", "Old Name")))
.await;
let old = registry.get("test-1").await.unwrap();
assert_eq!(old.name(), "Old Name");
registry.register(Arc::new(TestSource::new("test-1", "New Name"))).await;
registry
.register(Arc::new(TestSource::new("test-1", "New Name")))
.await;
let new = registry.get("test-1").await.unwrap();
assert_eq!(new.name(), "New Name");

View File

@@ -3,8 +3,8 @@
//! Ce module fournit des helpers pour créer et enregistrer facilement des sources
//! musicales préconfigurées à partir de la configuration système.
use pmosource::MusicSourceExt;
use pmoserver::Server;
use pmosource::MusicSourceExt;
use std::sync::Arc;
/// Erreur lors de l'initialisation d'une source
@@ -93,7 +93,11 @@ pub trait SourcesExt {
/// server.register_qobuz_with_credentials("user@example.com", "password").await?;
/// ```
#[cfg(feature = "qobuz")]
async fn register_qobuz_with_credentials(&mut self, username: &str, password: &str) -> Result<()>;
async fn register_qobuz_with_credentials(
&mut self,
username: &str,
password: &str,
) -> Result<()>;
/// Enregistre la source Radio Paradise
///
@@ -141,7 +145,11 @@ impl SourcesExt for Server {
}
#[cfg(feature = "qobuz")]
async fn register_qobuz_with_credentials(&mut self, username: &str, password: &str) -> Result<()> {
async fn register_qobuz_with_credentials(
&mut self,
username: &str,
password: &str,
) -> Result<()> {
use pmoqobuz::{QobuzClient, QobuzSource};
tracing::info!("Initializing Qobuz source with explicit credentials...");
@@ -165,18 +173,19 @@ impl SourcesExt for Server {
#[cfg(feature = "paradise")]
async fn register_paradise(&mut self) -> Result<()> {
use pmoparadise::{RadioParadiseClient, RadioParadiseSource, RadioParadiseExt};
use pmoparadise::{RadioParadiseClient, RadioParadiseExt, RadioParadiseSource};
tracing::info!("Initializing Radio Paradise source...");
// Créer le client (Radio Paradise ne nécessite pas d'authentification)
let client = RadioParadiseClient::new()
.await
.map_err(|e| SourceInitError::ParadiseError(format!("Failed to create client: {}", e)))?;
let client = RadioParadiseClient::new().await.map_err(|e| {
SourceInitError::ParadiseError(format!("Failed to create client: {}", e))
})?;
// Créer la source depuis le registry avec capacité FIFO par défaut
let source = RadioParadiseSource::from_registry_default(client)
.map_err(|e| SourceInitError::ParadiseError(format!("Failed to create source: {}", e)))?;
let source = RadioParadiseSource::from_registry_default(client).map_err(|e| {
SourceInitError::ParadiseError(format!("Failed to create source: {}", e))
})?;
// Enregistrer la source
// Note: La FIFO sera peuplée automatiquement lors du premier browse

View File

@@ -13,13 +13,7 @@
//! Ces endpoints sont définis ici plutôt que dans `pmosource` pour éviter les
//! dépendances circulaires (pmoqobuz et pmoparadise dépendent de pmosource).
use axum::{
extract::Json,
http::StatusCode,
response::IntoResponse,
routing::post,
Router,
};
use axum::{Router, extract::Json, http::StatusCode, response::IntoResponse, routing::post};
use pmosource::MusicSource;
use serde::{Deserialize, Serialize};
use std::sync::Arc;