diff --git a/pmomediaserver/src/connectionmanager/actions/getcurrentconnectionids.rs b/pmomediaserver/src/connectionmanager/actions/getcurrentconnectionids.rs index 12e0ba94..d54b017a 100644 --- a/pmomediaserver/src/connectionmanager/actions/getcurrentconnectionids.rs +++ b/pmomediaserver/src/connectionmanager/actions/getcurrentconnectionids.rs @@ -2,7 +2,7 @@ use crate::connectionmanager::variables::CURRENTCONNECTIONIDS; use pmoupnp::define_action; define_action! { - pub static GETCURRENTCONNECTIONIDS = "GetCurrentConnectionIDs" { + pub static GETCURRENTCONNECTIONIDS = "GetCurrentConnectionIDs" stateless { out "ConnectionIDs" => CURRENTCONNECTIONIDS, } } diff --git a/pmomediaserver/src/connectionmanager/actions/getcurrentconnectioninfo.rs b/pmomediaserver/src/connectionmanager/actions/getcurrentconnectioninfo.rs index 817b15f9..cebc78da 100644 --- a/pmomediaserver/src/connectionmanager/actions/getcurrentconnectioninfo.rs +++ b/pmomediaserver/src/connectionmanager/actions/getcurrentconnectioninfo.rs @@ -5,7 +5,7 @@ use crate::connectionmanager::variables::{ use pmoupnp::define_action; define_action! { - pub static GETCURRENTCONNECTIONINFO = "GetCurrentConnectionInfo" { + pub static GETCURRENTCONNECTIONINFO = "GetCurrentConnectionInfo" stateless { in "ConnectionID" => A_ARG_TYPE_CONNECTIONID, out "RcsID" => A_ARG_TYPE_RCSID, out "AVTransportID" => A_ARG_TYPE_AVTRANSPORTID, diff --git a/pmomediaserver/src/connectionmanager/actions/getprotocolinfo.rs b/pmomediaserver/src/connectionmanager/actions/getprotocolinfo.rs index c9370ded..b2097b0c 100644 --- a/pmomediaserver/src/connectionmanager/actions/getprotocolinfo.rs +++ b/pmomediaserver/src/connectionmanager/actions/getprotocolinfo.rs @@ -2,7 +2,7 @@ use crate::connectionmanager::variables::{SOURCEPROTOCOLINFO, SINKPROTOCOLINFO}; use pmoupnp::define_action; define_action! { - pub static GETPROTOCOLINFO = "GetProtocolInfo" { + pub static GETPROTOCOLINFO = "GetProtocolInfo" stateless { out "Source" => SOURCEPROTOCOLINFO, out "Sink" => SINKPROTOCOLINFO, } diff --git a/pmomediaserver/src/contentdirectory/actions/browse.rs b/pmomediaserver/src/contentdirectory/actions/browse.rs index 419d0da4..e8ad1629 100644 --- a/pmomediaserver/src/contentdirectory/actions/browse.rs +++ b/pmomediaserver/src/contentdirectory/actions/browse.rs @@ -7,7 +7,7 @@ use pmoupnp::define_action; use crate::contentdirectory::handlers; define_action! { - pub static BROWSE = "Browse" { + pub static BROWSE = "Browse" stateless { in "ObjectID" => A_ARG_TYPE_OBJECTID, in "BrowseFlag" => A_ARG_TYPE_BROWSEFLAG, in "Filter" => A_ARG_TYPE_FILTER, diff --git a/pmomediaserver/src/contentdirectory/actions/getsearchcapabilities.rs b/pmomediaserver/src/contentdirectory/actions/getsearchcapabilities.rs index 6842cb73..fe73ba92 100644 --- a/pmomediaserver/src/contentdirectory/actions/getsearchcapabilities.rs +++ b/pmomediaserver/src/contentdirectory/actions/getsearchcapabilities.rs @@ -3,7 +3,7 @@ use pmoupnp::define_action; use crate::contentdirectory::handlers; define_action! { - pub static GETSEARCHCAPABILITIES = "GetSearchCapabilities" { + pub static GETSEARCHCAPABILITIES = "GetSearchCapabilities" stateless { out "SearchCaps" => SEARCHCAPABILITIES, } with handler handlers::get_search_capabilities_handler() diff --git a/pmomediaserver/src/contentdirectory/actions/getsortcapabilities.rs b/pmomediaserver/src/contentdirectory/actions/getsortcapabilities.rs index 0a30f7b9..944e7e24 100644 --- a/pmomediaserver/src/contentdirectory/actions/getsortcapabilities.rs +++ b/pmomediaserver/src/contentdirectory/actions/getsortcapabilities.rs @@ -3,7 +3,7 @@ use pmoupnp::define_action; use crate::contentdirectory::handlers; define_action! { - pub static GETSORTCAPABILITIES = "GetSortCapabilities" { + pub static GETSORTCAPABILITIES = "GetSortCapabilities" stateless { out "SortCaps" => SORTCAPABILITIES, } with handler handlers::get_sort_capabilities_handler() diff --git a/pmomediaserver/src/contentdirectory/actions/getsystemupdateid.rs b/pmomediaserver/src/contentdirectory/actions/getsystemupdateid.rs index b0d82eb2..a80f200e 100644 --- a/pmomediaserver/src/contentdirectory/actions/getsystemupdateid.rs +++ b/pmomediaserver/src/contentdirectory/actions/getsystemupdateid.rs @@ -3,7 +3,7 @@ use pmoupnp::define_action; use crate::contentdirectory::handlers; define_action! { - pub static GETSYSTEMUPDATEID = "GetSystemUpdateID" { + pub static GETSYSTEMUPDATEID = "GetSystemUpdateID" stateless { out "Id" => SYSTEMUPDATEID, } with handler handlers::get_system_update_id_handler() diff --git a/pmomediaserver/src/contentdirectory/actions/search.rs b/pmomediaserver/src/contentdirectory/actions/search.rs index 826a687a..cc7d81fa 100644 --- a/pmomediaserver/src/contentdirectory/actions/search.rs +++ b/pmomediaserver/src/contentdirectory/actions/search.rs @@ -7,7 +7,7 @@ use pmoupnp::define_action; use crate::contentdirectory::handlers; define_action! { - pub static SEARCH = "Search" { + pub static SEARCH = "Search" stateless { in "ContainerID" => A_ARG_TYPE_OBJECTID, in "SearchCriteria" => A_ARG_TYPE_SEARCHCRITERIA, in "Filter" => A_ARG_TYPE_FILTER, diff --git a/pmomediaserver/src/contentdirectory/handlers.rs b/pmomediaserver/src/contentdirectory/handlers.rs index bf9b5efb..71a63557 100644 --- a/pmomediaserver/src/contentdirectory/handlers.rs +++ b/pmomediaserver/src/contentdirectory/handlers.rs @@ -23,9 +23,8 @@ //! - [`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; -use pmoupnp::actions::{ActionHandler, ActionError}; -use pmoupnp::variable_types::StateValue; +use pmoupnp::{action_handler, get, set}; +use pmoupnp::actions::{ActionError, ActionHandler}; use crate::content_handler::ContentHandler; use tracing::{debug, error}; @@ -49,51 +48,18 @@ use tracing::{debug, error}; /// - `TotalMatches` : Nombre total d'éléments /// - `UpdateID` : ID de mise à jour pub fn browse_handler() -> ActionHandler { - action_handler!(|instance| { + action_handler!(|data| { + let mut data = data; debug!("📂 Browse handler called"); let handler = ContentHandler::new(); - // Extraire les arguments d'entrée - let object_id = match instance - .argument("ObjectID") - .and_then(|arg| arg.get_variable_instance()) - .ok_or_else(|| ActionError::ArgumentError("ObjectID not found".to_string()))? - .value() - { - StateValue::String(s) => s, - _ => return Err(ActionError::ArgumentError("ObjectID must be a string".to_string())), - }; - - let browse_flag = match instance - .argument("BrowseFlag") - .and_then(|arg| arg.get_variable_instance()) - .ok_or_else(|| ActionError::ArgumentError("BrowseFlag not found".to_string()))? - .value() - { - StateValue::String(s) => s, - _ => return Err(ActionError::ArgumentError("BrowseFlag must be a string".to_string())), - }; - - let starting_index = match instance - .argument("StartingIndex") - .and_then(|arg| arg.get_variable_instance()) - .ok_or_else(|| ActionError::ArgumentError("StartingIndex not found".to_string()))? - .value() - { - StateValue::UI4(n) => n, - _ => return Err(ActionError::ArgumentError("StartingIndex must be ui4".to_string())), - }; - - let requested_count = match instance - .argument("RequestedCount") - .and_then(|arg| arg.get_variable_instance()) - .ok_or_else(|| ActionError::ArgumentError("RequestedCount not found".to_string()))? - .value() - { - StateValue::UI4(n) => n, - _ => return Err(ActionError::ArgumentError("RequestedCount must be ui4".to_string())), - }; + let object_id: String = get!(&data, "ObjectID", String); + let browse_flag: String = get!(&data, "BrowseFlag", String); + let starting_index: u32 = get!(&data, "StartingIndex", u32); + let requested_count: u32 = get!(&data, "RequestedCount", u32); + let _filter: String = get!(&data, "Filter", String); + let _sort_criteria: String = get!(&data, "SortCriteria", String); // Appeler la logique métier let (didl, returned, total, update_id) = handler @@ -105,32 +71,13 @@ pub fn browse_handler() -> ActionHandler { })?; // Définir les arguments de sortie - if let Some(arg) = instance.argument("Result") { - if let Some(var) = arg.get_variable_instance() { - var.set_value(StateValue::String(didl)).await; - } - } - - if let Some(arg) = instance.argument("NumberReturned") { - if let Some(var) = arg.get_variable_instance() { - var.set_value(StateValue::UI4(returned)).await; - } - } - - if let Some(arg) = instance.argument("TotalMatches") { - if let Some(var) = arg.get_variable_instance() { - var.set_value(StateValue::UI4(total)).await; - } - } - - if let Some(arg) = instance.argument("UpdateID") { - if let Some(var) = arg.get_variable_instance() { - var.set_value(StateValue::UI4(update_id)).await; - } - } + set!(&mut data, "Result", didl); + set!(&mut data, "NumberReturned", returned); + set!(&mut data, "TotalMatches", total); + set!(&mut data, "UpdateID", update_id); debug!("✅ Browse completed: returned={}, total={}", returned, total); - Ok(()) + Ok(data) }) } @@ -154,30 +101,18 @@ pub fn browse_handler() -> ActionHandler { /// - `TotalMatches` : Total /// - `UpdateID` : ID de mise à jour pub fn search_handler() -> ActionHandler { - action_handler!(|instance| { + action_handler!(|data| { + let mut data = data; debug!("🔍 Search handler called"); let handler = ContentHandler::new(); - let container_id = match instance - .argument("ContainerID") - .and_then(|arg| arg.get_variable_instance()) - .ok_or_else(|| ActionError::ArgumentError("ContainerID not found".to_string()))? - .value() - { - StateValue::String(s) => s, - _ => return Err(ActionError::ArgumentError("ContainerID must be a string".to_string())), - }; - - let search_criteria = match instance - .argument("SearchCriteria") - .and_then(|arg| arg.get_variable_instance()) - .ok_or_else(|| ActionError::ArgumentError("SearchCriteria not found".to_string()))? - .value() - { - StateValue::String(s) => s, - _ => return Err(ActionError::ArgumentError("SearchCriteria must be a string".to_string())), - }; + let container_id: String = get!(&data, "ContainerID", String); + let search_criteria: String = get!(&data, "SearchCriteria", String); + let _filter: String = get!(&data, "Filter", String); + let _starting_index: u32 = get!(&data, "StartingIndex", u32); + let _requested_count: u32 = get!(&data, "RequestedCount", u32); + let _sort_criteria: String = get!(&data, "SortCriteria", String); let (didl, returned, total, update_id) = handler .search(&container_id, &search_criteria) @@ -188,32 +123,13 @@ pub fn search_handler() -> ActionHandler { })?; // Définir les sorties - if let Some(arg) = instance.argument("Result") { - if let Some(var) = arg.get_variable_instance() { - var.set_value(StateValue::String(didl)).await; - } - } - - if let Some(arg) = instance.argument("NumberReturned") { - if let Some(var) = arg.get_variable_instance() { - var.set_value(StateValue::UI4(returned)).await; - } - } - - if let Some(arg) = instance.argument("TotalMatches") { - if let Some(var) = arg.get_variable_instance() { - var.set_value(StateValue::UI4(total)).await; - } - } - - if let Some(arg) = instance.argument("UpdateID") { - if let Some(var) = arg.get_variable_instance() { - var.set_value(StateValue::UI4(update_id)).await; - } - } + set!(&mut data, "Result", didl); + set!(&mut data, "NumberReturned", returned); + set!(&mut data, "TotalMatches", total); + set!(&mut data, "UpdateID", update_id); debug!("✅ Search completed: returned={}, total={}", returned, total); - Ok(()) + Ok(data) }) } @@ -225,20 +141,17 @@ pub fn search_handler() -> ActionHandler { /// /// - `SearchCaps` : Chaîne de capacités séparées par virgules pub fn get_search_capabilities_handler() -> ActionHandler { - action_handler!(|instance| { + action_handler!(|data| { + let mut data = data; debug!("🔍 GetSearchCapabilities handler called"); let handler = ContentHandler::new(); let capabilities = handler.get_search_capabilities().await; - if let Some(arg) = instance.argument("SearchCaps") { - if let Some(var) = arg.get_variable_instance() { - var.set_value(StateValue::String(capabilities.clone())).await; - } - } + set!(&mut data, "SearchCaps", capabilities.clone()); debug!("✅ SearchCapabilities: {}", capabilities); - Ok(()) + Ok(data) }) } @@ -250,20 +163,17 @@ pub fn get_search_capabilities_handler() -> ActionHandler { /// /// - `SortCaps` : Chaîne de capacités séparées par virgules pub fn get_sort_capabilities_handler() -> ActionHandler { - action_handler!(|instance| { + action_handler!(|data| { + let mut data = data; debug!("📊 GetSortCapabilities handler called"); let handler = ContentHandler::new(); let capabilities = handler.get_sort_capabilities().await; - if let Some(arg) = instance.argument("SortCaps") { - if let Some(var) = arg.get_variable_instance() { - var.set_value(StateValue::String(capabilities.clone())).await; - } - } + set!(&mut data, "SortCaps", capabilities.clone()); debug!("✅ SortCapabilities: {}", capabilities); - Ok(()) + Ok(data) }) } @@ -276,20 +186,17 @@ pub fn get_sort_capabilities_handler() -> ActionHandler { /// /// - `Id` : ID de mise à jour (entier non signé) pub fn get_system_update_id_handler() -> ActionHandler { - action_handler!(|instance| { + action_handler!(|data| { + let mut data = data; debug!("🔄 GetSystemUpdateID handler called"); let handler = ContentHandler::new(); let update_id = handler.get_system_update_id().await; - if let Some(arg) = instance.argument("Id") { - if let Some(var) = arg.get_variable_instance() { - var.set_value(StateValue::UI4(update_id)).await; - } - } + set!(&mut data, "Id", update_id); debug!("✅ SystemUpdateID: {}", update_id); - Ok(()) + Ok(data) }) } diff --git a/pmomediaserver/src/contentdirectory/variables/a_arg_type_browseflag.rs b/pmomediaserver/src/contentdirectory/variables/a_arg_type_browseflag.rs index 8e6077ca..834ab4e1 100644 --- a/pmomediaserver/src/contentdirectory/variables/a_arg_type_browseflag.rs +++ b/pmomediaserver/src/contentdirectory/variables/a_arg_type_browseflag.rs @@ -2,8 +2,8 @@ use pmoupnp::define_variable; define_variable! { pub static A_ARG_TYPE_BROWSEFLAG: String = "A_ARG_TYPE_BrowseFlag" { - default: "BrowseDirectChildren", allowed: ["BrowseMetadata", "BrowseDirectChildren"], + default: "BrowseDirectChildren", evented: false, } } diff --git a/pmoupnp/src/actions/macros.rs b/pmoupnp/src/actions/macros.rs index 635fb32d..8ed93e84 100644 --- a/pmoupnp/src/actions/macros.rs +++ b/pmoupnp/src/actions/macros.rs @@ -104,24 +104,8 @@ /// - Initialisation paresseuse via `Lazy` (thread-safe) #[macro_export] macro_rules! define_action { - // Variante sans arguments avec options `stateless` et handler - (pub static $name:ident = $action_name:literal $(stateless)? $(with handler $handler:expr)?) => { - pub static $name: once_cell::sync::Lazy> = - once_cell::sync::Lazy::new(|| { - let mut ac = $crate::actions::Action::new($action_name.to_string()); - - define_action!(@maybe_stateless ac $(stateless)?); - - $( - ac.set_handler($handler); - )? - - std::sync::Arc::new(ac) - }); - }; - - // Variante avec arguments, options `stateless` et handler - (pub static $name:ident = $action_name:literal $(stateless)? { + // Variante stateless avec arguments + (pub static $name:ident = $action_name:literal stateless { $( $direction:ident $arg_name:literal => $var_ref:expr ),* $(,)? @@ -131,8 +115,7 @@ macro_rules! define_action { pub static $name: once_cell::sync::Lazy> = once_cell::sync::Lazy::new(|| { let mut ac = $crate::actions::Action::new($action_name.to_string()); - - define_action!(@maybe_stateless ac $(stateless)?); + ac.set_stateful(false); $( ac.add_argument( @@ -148,11 +131,64 @@ macro_rules! define_action { }); }; - (@maybe_stateless $ac:ident stateless) => { - $ac.set_stateful(false); + // Variante stateless sans arguments + (pub static $name:ident = $action_name:literal stateless + $(with handler $handler:expr)? + ) => { + pub static $name: once_cell::sync::Lazy> = + once_cell::sync::Lazy::new(|| { + let mut ac = $crate::actions::Action::new($action_name.to_string()); + ac.set_stateful(false); + + $( + ac.set_handler($handler); + )? + + std::sync::Arc::new(ac) + }); }; - (@maybe_stateless $ac:ident) => {}; + // Variante stateful (défaut) avec arguments + (pub static $name:ident = $action_name:literal { + $( + $direction:ident $arg_name:literal => $var_ref:expr + ),* $(,)? + } + $(with handler $handler:expr)? + ) => { + pub static $name: once_cell::sync::Lazy> = + once_cell::sync::Lazy::new(|| { + let mut ac = $crate::actions::Action::new($action_name.to_string()); + + $( + ac.add_argument( + define_action!(@arg $direction $arg_name, $var_ref) + ); + )* + + $( + ac.set_handler($handler); + )? + + std::sync::Arc::new(ac) + }); + }; + + // Variante stateful (défaut) sans arguments + (pub static $name:ident = $action_name:literal + $(with handler $handler:expr)? + ) => { + pub static $name: once_cell::sync::Lazy> = + once_cell::sync::Lazy::new(|| { + let mut ac = $crate::actions::Action::new($action_name.to_string()); + + $( + ac.set_handler($handler); + )? + + std::sync::Arc::new(ac) + }); + }; // Helper interne pour créer un argument d'entrée (@arg in $name:literal, $var:expr) => { diff --git a/pmoupnp/src/upnp_server.rs b/pmoupnp/src/upnp_server.rs index 220a3587..c3fb449d 100644 --- a/pmoupnp/src/upnp_server.rs +++ b/pmoupnp/src/upnp_server.rs @@ -386,7 +386,7 @@ impl UpnpServerExt for Server { async fn create_upnp_server() -> Result { use pmoserver::ServerBuilder; - use tracing::{info, warn}; + use tracing::{error, info, warn}; // 1. Créer le serveur depuis la config info!("🔧 Creating UPnP server from configuration..."); @@ -421,7 +421,19 @@ impl UpnpServerExt for Server { match server.init_ssdp() { Ok(_) => info!("✅ SSDP server initialized"), Err(e) => { - warn!("❌ SSDP initialization failed: {}", e); + let kind = e.kind(); + if kind == std::io::ErrorKind::AddrInUse { + error!( + "❌ SSDP initialization failed: port {} is already in use. \ + Check which process listens on UDP:{} (e.g. `lsof -nP -i UDP:{}`): {}", + crate::ssdp::SSDP_PORT, + crate::ssdp::SSDP_PORT, + crate::ssdp::SSDP_PORT, + e, + ); + } else { + error!("❌ SSDP initialization failed: {}", e); + } return Err(e.into()); } }