From 2c814bd7c0e4e92998b8b41c6fd5d9c84d6a0fd4 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sun, 19 Oct 2025 10:06:04 +0200 Subject: [PATCH] =?UTF-8?q?fin=20du=20travail=20pr=C3=A9c=C3=A9dent=20par?= =?UTF-8?q?=20chatgpt=20plus?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pmoupnp/src/actions/action_handler.rs | 14 +- pmoupnp/src/actions/action_instance.rs | 252 ++++++++++----------- pmoupnp/src/actions/action_methods.rs | 18 +- pmoupnp/src/actions/handler_helpers.rs | 46 +++- pmoupnp/src/actions/macros.rs | 166 ++------------ pmoupnp/src/actions/mod.rs | 2 +- pmoupnp/src/services/service_instance.rs | 41 ++-- pmoupnp/src/variable_types/reflect_impl.rs | 1 - 8 files changed, 222 insertions(+), 318 deletions(-) diff --git a/pmoupnp/src/actions/action_handler.rs b/pmoupnp/src/actions/action_handler.rs index 014d6e3b..7abd2360 100644 --- a/pmoupnp/src/actions/action_handler.rs +++ b/pmoupnp/src/actions/action_handler.rs @@ -25,17 +25,15 @@ //! use std::sync::Arc; //! //! // Créer un handler avec la macro -//! let handler = action_handler!(|instance, data| { +//! let handler = action_handler!(|mut data| { //! // Traiter les données -//! Ok::<(), ActionError>(()) +//! Ok::<_, ActionError>(data) //! }); //! //! // Ou manuellement -//! use pmoupnp::actions::{ActionData, ActionHandler, ActionInstance}; -//! let manual_handler: ActionHandler = Arc::new(|instance, data| { -//! Box::pin(async move { -//! Ok::<(), ActionError>(()) -//! }) +//! use pmoupnp::actions::{ActionData, ActionHandler}; +//! let manual_handler: ActionHandler = Arc::new(|data| { +//! Box::pin(async move { Ok::<_, ActionError>(data) }) //! }); //! ``` @@ -299,4 +297,4 @@ macro_rules! action_handler { Box::pin(async move $body) }) }; -} \ No newline at end of file +} diff --git a/pmoupnp/src/actions/action_instance.rs b/pmoupnp/src/actions/action_instance.rs index 225eb7ec..9b92e858 100644 --- a/pmoupnp/src/actions/action_instance.rs +++ b/pmoupnp/src/actions/action_instance.rs @@ -1,6 +1,9 @@ -use std::sync::Arc; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; -use tracing::{debug, trace}; +use bevy_reflect::Reflect; use xmltree::{Element, XMLNode}; use crate::{ @@ -16,6 +19,7 @@ use crate::actions::{ ActionInstance, ArgInstanceSet, }; +use crate::variable_types::StateValue; impl UpnpObject for ActionInstance { fn to_xml_element(&self) -> Element { @@ -130,156 +134,135 @@ impl ActionInstance { &self.arguments // ⬅️ Retourne les INSTANCES, pas les modèles ! } - /// Récupère les valeurs de tous les arguments de sortie (OUT). + /// Construit un [`ActionData`] initial à partir des variables d'état liées. /// - /// Cette méthode collecte automatiquement les valeurs actuelles de toutes - /// les variables d'état liées aux arguments OUT et les retourne dans un - /// [`ActionData`] indexé par le nom de chaque argument. - /// - /// # Returns - /// - /// Un [`ActionData`] contenant les paires (nom_argument, valeur_variable) pour - /// tous les arguments de sortie qui ont une variable d'instance liée. - /// - /// # Examples - /// - /// ```rust - /// # use pmoupnp::actions::{Action, ActionInstance}; - /// # use pmoupnp::UpnpInstance; - /// # use std::sync::Arc; - /// let action = Action::new("GetVolume".to_string()); - /// let instance = Arc::new(ActionInstance::new(&action)); - /// - /// // Récupérer automatiquement toutes les valeurs OUT - /// let output = instance.get_out_values(); - /// - /// // Afficher les résultats - /// for (arg_name, value) in output.iter() { - /// println!("{} = {:?}", arg_name, value); - /// } - /// ``` - /// - /// # Notes - /// - /// - Seuls les arguments marqués comme OUT sont inclus - /// - Les arguments sans variable d'instance liée sont ignorés - /// - Le nom de l'argument (pas le nom de la variable) est utilisé comme clé - /// - Cette méthode est utilisée par le handler par défaut - pub fn get_out_values(&self) -> ActionData { - use std::collections::HashMap; - use crate::UpnpTypedInstance; - - let mut result = HashMap::new(); + /// Chaque argument lié à une [`StateVarInstance`](crate::state_variables::StateVarInstance) + /// voit sa valeur courante convertie en [`Reflect`](bevy_reflect::Reflect) pour alimenter + /// le handler de l'action. + fn build_action_data(&self) -> ActionData { + let mut data = HashMap::new(); for arg_inst in self.arguments.all() { - let arg_model = arg_inst.as_ref().get_model(); - if arg_model.is_out() { - if let Some(var_inst) = arg_inst.get_variable_instance() { - result.insert(arg_inst.get_name().to_string(), var_inst.value()); + if let Some(var_inst) = arg_inst.get_variable_instance() { + let name = arg_inst.get_name().to_string(); + let reflect_value = var_inst.to_reflect(); + data.insert(name, reflect_value); + } + } + + data + } + + /// Fusionne les valeurs SOAP IN dans l'[`ActionData`] existant. + /// + /// Seuls les arguments marqués comme IN sont considérés. Les valeurs sont + /// converties depuis [`StateValue`] vers `Reflect` pour les handlers. + fn merge_soap_inputs( + &self, + action_data: &mut ActionData, + soap_data: &HashMap, + ) -> HashSet { + let mut updated = HashSet::new(); + + for (arg_name, state_value) in soap_data.iter() { + if let Some(arg_inst) = self.argument(arg_name) { + if arg_inst.get_model().is_in() { + action_data.insert(arg_name.clone(), state_value.to_reflect()); + updated.insert(arg_name.clone()); } } } - Arc::new(result) + updated } - /// Exécute l'action avec les données fournies. - /// - /// Cette méthode : - /// 1. Stocke les valeurs IN dans les variables liées - /// 2. Exécute le handler (qui peut accéder aux valeurs IN via les variables) - /// 3. Collecte automatiquement les valeurs OUT via [`get_out_values()`](Self::get_out_values) - /// 4. Retourne les résultats - /// - /// # Arguments - /// - /// * `data` - Données d'entrée de l'action (arguments IN) - /// - /// # Returns - /// - /// Un `Future` qui se résout en `Result` : - /// - `Ok(ActionData)` contenant les résultats (arguments OUT) si le handler réussit - /// - `Err(ActionError)` si le handler échoue - /// - /// # Errors - /// - /// Retourne une erreur si le handler retourne `Err(ActionError)`. - /// - /// # Fonctionnement - /// - /// 1. Pour chaque argument IN, la valeur fournie dans `data` est stockée dans la - /// variable d'état liée à cet argument - /// 2. Le handler est appelé avec l'instance (il peut lire les valeurs IN via - /// `argument.get_variable_instance().value()`) - /// 3. Le handler modifie les variables selon ses besoins et retourne `Ok(())` ou `Err(...)` - /// 4. Si le handler réussit, `run()` collecte automatiquement toutes les valeurs - /// des arguments marqués comme OUT - /// - /// # Examples - /// - /// ```rust,no_run - /// # use pmoupnp::actions::{Action, ActionData, ActionInstance}; - /// # use pmoupnp::UpnpInstance; - /// # use std::collections::HashMap; - /// # use std::sync::Arc; - /// # async fn example() { - /// let action = Action::new("SetVolume".to_string()); - /// let instance = Arc::new(ActionInstance::new(&action)); - /// - /// // Préparer les données d'entrée - /// let mut input = HashMap::new(); - /// input.insert("DesiredVolume".to_string(), - /// pmoupnp::variable_types::StateValue::UI2(50)); - /// let input_data = Arc::new(input); - /// - /// // Exécuter l'action - /// // 1. run() stocke DesiredVolume=50 dans la variable liée - /// // 2. Le handler lit la valeur et fait son travail - /// // 3. run() retourne automatiquement les valeurs OUT - /// match instance.run(input_data).await { - /// Ok(output_data) => { - /// // Traiter les résultats - /// for (key, value) in output_data.iter() { - /// println!("{} = {:?}", key, value); - /// } - /// } - /// Err(e) => { - /// eprintln!("Action failed: {:?}", e); - /// } - /// } - /// # } - /// ``` - /// - /// # Notes - /// - /// - Les valeurs IN sont automatiquement stockées avant l'appel du handler - /// - Le handler n'a plus besoin de recevoir les données en paramètre - /// - Le handler modifie les variables et retourne `Ok(())` ou `Err(ActionError)` - /// - `run()` collecte automatiquement les OUT si le handler retourne `Ok(())` - /// - L'instance doit être wrappée dans un `Arc` pour être passée au handler - pub async fn run(self: Arc, data: ActionData) -> Result { - // Stocker les valeurs IN dans les variables liées + /// Sauvegarde les arguments IN dans les variables d'état (mode stateful uniquement). + async fn save_inputs_to_state_variables( + &self, + action_data: &ActionData, + updated_keys: &HashSet, + ) -> Result<(), crate::actions::ActionError> { for arg_inst in self.arguments.all() { - let arg_model = arg_inst.as_ref().get_model(); - if arg_model.is_in() { - if let Some(value) = data.get(arg_inst.get_name()) { - if let Some(var_inst) = arg_inst.get_variable_instance() { - var_inst.set_value(value.clone()).await.ok(); - trace!(" IN {} = {:?}", arg_inst.get_name(), value); + if arg_inst.get_model().is_in() && updated_keys.contains(arg_inst.get_name()) { + if let Some(var_inst) = arg_inst.get_variable_instance() { + if let Some(reflect_value) = action_data.get(arg_inst.get_name()) { + let cloned = reflect_value + .as_ref() + .reflect_clone() + .map_err(|e| crate::actions::ActionError::ArgumentError(e.to_string()))?; + var_inst + .set_reflect_value(cloned) + .await + .map_err(|e| crate::actions::ActionError::SetError(e.to_string()))?; } } } } + Ok(()) + } + + /// Sauvegarde les arguments OUT dans les variables d'état (mode stateful uniquement). + async fn save_outputs_to_state_variables( + &self, + action_data: &ActionData, + ) -> Result<(), crate::actions::ActionError> { + for arg_inst in self.arguments.all() { + if arg_inst.get_model().is_out() { + if let Some(var_inst) = arg_inst.get_variable_instance() { + if let Some(reflect_value) = action_data.get(arg_inst.get_name()) { + let cloned = reflect_value + .as_ref() + .reflect_clone() + .map_err(|e| crate::actions::ActionError::SetError(e.to_string()))?; + var_inst + .set_reflect_value(cloned) + .await + .map_err(|e| crate::actions::ActionError::SetError(e.to_string()))?; + } + } + } + } + + Ok(()) + } + + /// Exécute l'action avec les données SOAP fournies. + /// + /// Workflow unifié : + /// 1. Construire l'[`ActionData`] initial depuis les `StateVarInstance` + /// 2. Fusionner les valeurs IN issues du SOAP + /// 3. Si l'action est stateful : sauvegarder les IN dans les `StateVarInstance` + /// 4. Exécuter le handler + /// 5. Si l'action est stateful : sauvegarder les OUT dans les `StateVarInstance` + /// 6. Retourner l'[`ActionData`] pour la réponse SOAP + pub async fn run( + self: Arc, + soap_data: Arc>, + ) -> Result { + // 1. Construire ActionData initial + let mut action_data = self.build_action_data(); + + // 2. Fusionner les valeurs SOAP IN + let updated_inputs = self.merge_soap_inputs(&mut action_data, &soap_data); + + // 3. Sauvegarder les IN si stateful + if self.is_stateful() { + self.save_inputs_to_state_variables(&action_data, &updated_inputs) + .await?; + } + + // 4. Exécuter le handler let handler = self.model.handler().clone(); - let instance_clone = self.clone(); + let result_data = handler(action_data).await?; - // Exécuter le handler (il peut maintenant lire les valeurs IN depuis les variables) - handler(instance_clone).await?; + // 5. Sauvegarder les OUT si stateful + if self.is_stateful() { + self.save_outputs_to_state_variables(&result_data).await?; + } - // Collecter automatiquement les valeurs OUT si succès - debug!("✅ Action '{}' completed successfully, collecting outputs", self.get_name()); - Ok(self.get_out_values()) + // 6. Retourner les données pour la réponse SOAP + Ok(result_data) } } @@ -310,4 +293,3 @@ mod tests { })); } } - diff --git a/pmoupnp/src/actions/action_methods.rs b/pmoupnp/src/actions/action_methods.rs index 30586710..571b5259 100644 --- a/pmoupnp/src/actions/action_methods.rs +++ b/pmoupnp/src/actions/action_methods.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use tracing::{debug, info, trace}; +use tracing::{info, trace}; use xmltree::{Element, XMLNode}; use crate::{ @@ -73,7 +73,11 @@ impl Action { // Logger les arguments for (key, value) in data.iter() { - trace!(" {} = {:?}", key, value); + trace!( + " {} = {}", + key, + crate::actions::reflect_to_string(value.as_ref()) + ); } // Retourner les données telles quelles @@ -141,9 +145,9 @@ impl Action { /// # use pmoupnp::action_handler; /// let mut action = Action::new("Play".to_string()); /// - /// let custom_handler = action_handler!(|instance, data| { + /// let custom_handler = action_handler!(|mut data| { /// // Logique personnalisée - /// Ok::<(), ActionError>(()) + /// Ok::<_, ActionError>(data) /// }); /// /// action.set_handler(custom_handler); @@ -184,6 +188,12 @@ impl Action { self.stateful = stateful; self } + + pub fn set_stateless(&mut self, stateless: bool) -> &mut Self { + self.stateful = !stateless; + self + } + /// Retourne `true` si l'action est stateful. /// diff --git a/pmoupnp/src/actions/handler_helpers.rs b/pmoupnp/src/actions/handler_helpers.rs index 9c85e06a..7cadadcc 100644 --- a/pmoupnp/src/actions/handler_helpers.rs +++ b/pmoupnp/src/actions/handler_helpers.rs @@ -35,7 +35,6 @@ use bevy_reflect::Reflect; use crate::actions::{ActionData, ActionError}; -use std::any::Any; /// Extrait une valeur typée depuis ActionData. /// @@ -58,7 +57,7 @@ use std::any::Any; /// /// # Errors /// -/// Retourne `ActionError::ArgumentNotFound` si : +/// Retourne `ActionError::ArgumentError` si : /// - La clé n'existe pas dans ActionData /// - La valeur ne peut pas être convertie vers le type `T` /// @@ -81,7 +80,10 @@ pub fn get_value( data.get(key) .and_then(|boxed| boxed.as_any().downcast_ref::()) .cloned() - .ok_or_else(|| ActionError::ArgumentNotFound(key.to_string())) + .ok_or_else(|| ActionError::ArgumentError(format!( + "Argument '{}' not found or type mismatch", + key + ))) } /// Insère une valeur dans ActionData. @@ -121,6 +123,44 @@ pub fn set_value( data.insert(key.into(), Box::new(value)); } +/// Convertit une valeur Reflect en chaîne lisible pour les logs/SOAP. +/// +/// Cette fonction réalise une tentative de conversion vers les types +/// primitifs les plus courants (String, entiers, flottants, bool). +/// Si aucune correspondance n'est trouvée, elle utilise `ReflectRef` +/// pour fournir une représentation `Debug` générique. +pub fn reflect_to_string(value: &dyn Reflect) -> String { + if let Some(v) = value.as_any().downcast_ref::() { + v.clone() + } else if let Some(v) = value.as_any().downcast_ref::<&str>() { + v.to_string() + } else if let Some(v) = value.as_any().downcast_ref::() { + v.to_string() + } else if let Some(v) = value.as_any().downcast_ref::() { + v.to_string() + } else if let Some(v) = value.as_any().downcast_ref::() { + v.to_string() + } else if let Some(v) = value.as_any().downcast_ref::() { + v.to_string() + } else if let Some(v) = value.as_any().downcast_ref::() { + v.to_string() + } else if let Some(v) = value.as_any().downcast_ref::() { + v.to_string() + } else if let Some(v) = value.as_any().downcast_ref::() { + v.to_string() + } else if let Some(v) = value.as_any().downcast_ref::() { + v.to_string() + } else if let Some(v) = value.as_any().downcast_ref::() { + v.to_string() + } else if let Some(v) = value.as_any().downcast_ref::() { + v.to_string() + } else if let Some(v) = value.as_any().downcast_ref::() { + if *v { "1".to_string() } else { "0".to_string() } + } else { + "".to_string() + } +} + /// Macro pour extraire facilement une valeur depuis ActionData. /// /// Cette macro simplifie l'utilisation de [`get_value`] en gérant diff --git a/pmoupnp/src/actions/macros.rs b/pmoupnp/src/actions/macros.rs index de7eabe3..635fb32d 100644 --- a/pmoupnp/src/actions/macros.rs +++ b/pmoupnp/src/actions/macros.rs @@ -1,107 +1,3 @@ -/// Macro pour créer facilement une action avec son handler. -/// -/// Cette macro simplifie la création d'actions dynamiques en combinant -/// la création de l'action et l'assignation du handler en une seule expression. -/// -/// # Syntaxe -/// -/// ## Action stateful (défaut) -/// -/// ```ignore -/// action!("ActionName", |data| { -/// // Handler code -/// Ok(data) -/// }) -/// ``` -/// -/// ## Action stateless -/// -/// ```ignore -/// action!("ActionName", stateless, |data| { -/// // Handler code -/// Ok(data) -/// }) -/// ``` -/// -/// # Arguments -/// -/// * `name` - Nom de l'action UPnP (chaîne littérale ou expression String) -/// * `stateless` - (Optionnel) Mot-clé pour marquer l'action comme stateless -/// * `|data| { ... }` - Closure du handler (voir [`action_handler!`](crate::action_handler)) -/// -/// # Type de retour -/// -/// Retourne une `Action` configurée avec le handler spécifié. -/// -/// # Examples -/// -/// ## Action stateless simple -/// -/// ```ignore -/// use pmoupnp::{action, get, set}; -/// -/// let convert = action!("ConvertTemp", stateless, |mut data| { -/// let celsius: f64 = get!(data, "Celsius", f64); -/// let fahrenheit = celsius * 9.0 / 5.0 + 32.0; -/// set!(data, "Fahrenheit", fahrenheit); -/// Ok(data) -/// }); -/// ``` -/// -/// ## Action stateful (défaut) -/// -/// ```ignore -/// use pmoupnp::{action, get, set}; -/// -/// let play = action!("Play", |mut data| { -/// let speed: String = get!(data, "Speed", String); -/// set!(data, "TransportState", "PLAYING".to_string()); -/// Ok(data) -/// }); -/// ``` -/// -/// ## Avec capture de contexte -/// -/// ```ignore -/// use pmoupnp::{action, get, set}; -/// use std::sync::Arc; -/// use tokio::sync::Mutex; -/// -/// let state = Arc::new(Mutex::new(PlayerState::Stopped)); -/// let state_clone = state.clone(); -/// -/// let play = action!("Play", |mut data| { -/// let mut player = state_clone.lock().await; -/// *player = PlayerState::Playing; -/// set!(data, "TransportState", "PLAYING".to_string()); -/// Ok(data) -/// }); -/// ``` -/// -/// # Notes -/// -/// - Actions stateful (défaut) : mettent à jour les StateVarInstance (notifications) -/// - Actions stateless : pas de mise à jour des StateVarInstance (performances) -/// - Le handler capture les variables par `move` -/// - Utilisez [`get!`](crate::get) et [`set!`](crate::set) pour manipuler facilement les données -#[macro_export] -macro_rules! action { - // Action stateful (défaut) - ($name:expr, |$data:ident| $body:block) => {{ - let mut action = $crate::actions::Action::new($name.to_string()); - action.set_handler($crate::action_handler!(|$data| $body)); - action - }}; - - // Action stateless - ($name:expr, stateless, |$data:ident| $body:block) => {{ - let mut action = $crate::actions::Action::new($name.to_string()); - action.set_stateful(false); - action.set_handler($crate::action_handler!(|$data| $body)); - action - }}; -} - /// Macro pour définir facilement une action UPnP. /// /// Cette macro simplifie la création d'actions UPnP statiques en générant @@ -208,12 +104,14 @@ macro_rules! action { /// - Initialisation paresseuse via `Lazy` (thread-safe) #[macro_export] macro_rules! define_action { - // Variante sans arguments avec handler optionnel - (pub static $name:ident = $action_name:literal $(with handler $handler:expr)?) => { + // 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); )? @@ -222,8 +120,8 @@ macro_rules! define_action { }); }; - // Variante avec arguments et handler optionnel - (pub static $name:ident = $action_name:literal { + // Variante avec arguments, options `stateless` et handler + (pub static $name:ident = $action_name:literal $(stateless)? { $( $direction:ident $arg_name:literal => $var_ref:expr ),* $(,)? @@ -234,6 +132,8 @@ macro_rules! define_action { once_cell::sync::Lazy::new(|| { let mut ac = $crate::actions::Action::new($action_name.to_string()); + define_action!(@maybe_stateless ac $(stateless)?); + $( ac.add_argument( define_action!(@arg $direction $arg_name, $var_ref) @@ -247,6 +147,12 @@ macro_rules! define_action { std::sync::Arc::new(ac) }); }; + + (@maybe_stateless $ac:ident stateless) => { + $ac.set_stateful(false); + }; + + (@maybe_stateless $ac:ident) => {}; // Helper interne pour créer un argument d'entrée (@arg in $name:literal, $var:expr) => { @@ -362,29 +268,10 @@ macro_rules! define_action { /// - La macro se développe en plusieurs appels à [`define_action!`] #[macro_export] macro_rules! define_actions { - // Variante avec arguments pour chaque action ( $( - $name:ident = $action_name:literal { - $( - $direction:ident $arg_name:literal => $var_ref:expr - ),* $(,)? - } - )* - ) => { - $( - define_action! { - pub static $name = $action_name { - $($direction $arg_name => $var_ref),* - } - } - )* - }; - - // Variante mixte : actions avec et sans arguments - ( - $( - $name:ident = $action_name:literal $({ + $name:ident = $action_name:literal $(stateless)? + $({ $( $direction:ident $arg_name:literal => $var_ref:expr ),* $(,)? @@ -392,20 +279,15 @@ macro_rules! define_actions { )* ) => { $( - $( - define_action! { - pub static $name = $action_name { + define_action! { + pub static $name = $action_name + $(stateless)? + $( + { $($direction $arg_name => $var_ref),* } - } - )? - $( - // Cas sans accolades (action sans arguments) - #[allow(unused)] - define_action! { - pub static $name = $action_name - } - )? + )? + } )* }; -} \ No newline at end of file +} diff --git a/pmoupnp/src/actions/mod.rs b/pmoupnp/src/actions/mod.rs index 3e75053d..2d63aab9 100644 --- a/pmoupnp/src/actions/mod.rs +++ b/pmoupnp/src/actions/mod.rs @@ -21,7 +21,7 @@ use std::sync::{Arc, RwLock}; pub use errors::ActionError; pub use action_handler::{ActionData, ActionFuture, ActionHandler}; -pub use handler_helpers::{get_value, set_value}; +pub use handler_helpers::{get_value, reflect_to_string, set_value}; /// Action UPnP. /// diff --git a/pmoupnp/src/services/service_instance.rs b/pmoupnp/src/services/service_instance.rs index b9cc4624..ab0126db 100644 --- a/pmoupnp/src/services/service_instance.rs +++ b/pmoupnp/src/services/service_instance.rs @@ -37,8 +37,6 @@ use axum::{ use bevy_reflect::Reflect; use std::{ collections::HashMap, - future::Future, - pin::Pin, sync::{Arc, Mutex, RwLock}, time::Duration, }; @@ -973,7 +971,6 @@ impl ServiceInstance { /// /// Le résultat est déjà échappé XML-safe selon les normes UPnP. fn reflect_to_string(value: &dyn Reflect) -> String { - use std::any::Any; use bevy_reflect::ReflectRef; // Essayer de downcaster vers des types primitifs courants @@ -1290,21 +1287,8 @@ async fn control_handler(State(instance): State>, body: Str } }; - // Convertir les arguments SOAP (String) en ActionData (StateValue) - // D'abord, initialiser tous les arguments IN avec leurs valeurs par défaut - let mut action_data = HashMap::new(); - for arg_inst in action_instance.arguments_set().all() { - let arg_model = arg_inst.as_ref().get_model(); - if arg_model.is_in() { - if let Some(var_inst) = arg_inst.get_variable_instance() { - // Utiliser la valeur par défaut de la variable - let default_value = var_inst.value(); - action_data.insert(arg_inst.get_name().to_string(), default_value); - } - } - } - - // Puis, écraser avec les valeurs fournies dans le SOAP + // Convertir les arguments SOAP (String) en StateValue + let mut soap_values = HashMap::new(); for (arg_name, arg_value) in soap_action.args { debug!("🔍 Processing SOAP arg: {} = '{}'", arg_name, arg_value); // Trouver l'argument correspondant pour obtenir son type @@ -1315,7 +1299,7 @@ async fn control_handler(State(instance): State>, body: Str match StateValue::from_string(&arg_value, &var_model.as_state_var_type()) { Ok(value) => { debug!("✅ Parsed {} = {:?}", arg_name, value); - action_data.insert(arg_name, value); + soap_values.insert(arg_name, value); } Err(e) => { error!("❌ Failed to parse argument '{}': {:?}", arg_name, e); @@ -1336,15 +1320,24 @@ async fn control_handler(State(instance): State>, body: Str } } - let action_data = Arc::new(action_data); + let soap_values = Arc::new(soap_values); // Exécuter l'action - match action_instance.run(action_data).await { + let action_instance_for_run = Arc::clone(&action_instance); + + match action_instance_for_run.run(soap_values).await { Ok(output_data) => { - // Convertir les StateValue en String pour SOAP + // Convertir ActionData (Reflect) → HashMap pour SOAP let mut soap_values = HashMap::new(); - for (key, value) in output_data.iter() { - soap_values.insert(key.clone(), value.to_string()); + + for arg_inst in action_instance.arguments_set().all() { + let arg_model = arg_inst.as_ref().get_model(); + if arg_model.is_out() { + if let Some(reflect_value) = output_data.get(arg_inst.get_name()) { + let soap_string = ServiceInstance::reflect_to_string(reflect_value.as_ref()); + soap_values.insert(arg_inst.get_name().to_string(), soap_string); + } + } } // Construire la réponse SOAP diff --git a/pmoupnp/src/variable_types/reflect_impl.rs b/pmoupnp/src/variable_types/reflect_impl.rs index 55944d2b..b29d77bd 100644 --- a/pmoupnp/src/variable_types/reflect_impl.rs +++ b/pmoupnp/src/variable_types/reflect_impl.rs @@ -6,7 +6,6 @@ use bevy_reflect::Reflect; use crate::variable_types::{StateValue, StateValueError, StateVarType}; -use std::any::Any; impl StateValue { /// Convertit la StateValue en une valeur Reflect.