From 015cc69a31c594f0f7a929f567f0e8e211d060d8 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Thu, 16 Oct 2025 21:06:29 +0200 Subject: [PATCH] =?UTF-8?q?mise=20=C3=A0=20jours=20des=20handlers=20d'acti?= =?UTF-8?q?ons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pmoupnp/src/actions/action_handler.rs | 81 +++++++++++---------- pmoupnp/src/actions/action_instance.rs | 44 ++++++++--- pmoupnp/src/actions/action_methods.rs | 17 ++--- pmoupnp/src/actions/arg_instance_methods.rs | 4 +- pmoupnp/src/actions/mod.rs | 8 ++ 5 files changed, 95 insertions(+), 59 deletions(-) diff --git a/pmoupnp/src/actions/action_handler.rs b/pmoupnp/src/actions/action_handler.rs index 66a2be67..a73e8d1e 100644 --- a/pmoupnp/src/actions/action_handler.rs +++ b/pmoupnp/src/actions/action_handler.rs @@ -113,25 +113,27 @@ pub type ActionFuture = Pin, ActionData) -> ActionFuture +/// Fn(Arc) -> ActionFuture /// ``` /// /// Prend : /// - [`Arc`](crate::actions::ActionInstance) : L'instance de l'action avec accès aux variables liées -/// - [`ActionData`] : Les données d'entrée (arguments IN) +/// qui contiennent déjà les valeurs des arguments IN /// /// Retourne un [`ActionFuture`] qui se résout en `Result<(), ActionError>`. /// /// # Responsabilités /// /// Le handler est responsable de : -/// - Lire les arguments d'entrée depuis `data` +/// - Lire les arguments d'entrée depuis les variables liées à l'instance /// - Exécuter la logique métier /// - Modifier les variables d'instance selon les besoins /// - Retourner `Ok(())` en cas de succès ou `Err(ActionError)` en cas d'erreur /// /// La méthode [`ActionInstance::run()`](crate::actions::ActionInstance::run) s'occupe -/// automatiquement de collecter les valeurs OUT si le handler retourne `Ok(())`. +/// automatiquement de : +/// 1. Stocker les valeurs IN dans les variables liées avant d'appeler le handler +/// 2. Collecter les valeurs OUT si le handler retourne `Ok(())` /// /// # Traits requis /// @@ -147,8 +149,8 @@ pub type ActionFuture = Pin(()) /// }); /// ``` @@ -156,10 +158,10 @@ pub type ActionFuture = Pin(()) @@ -174,7 +176,7 @@ pub type ActionFuture = Pin, ActionData) -> ActionFuture + Send + Sync>; +pub type ActionHandler = Arc) -> ActionFuture + Send + Sync>; /// Macro pour créer facilement un ActionHandler. /// @@ -184,16 +186,16 @@ pub type ActionHandler = Arc, ActionD /// # Syntaxe /// /// ```ignore -/// action_handler!(|instance, data| { +/// action_handler!(|instance| { /// // votre logique async (automatiquement dans un bloc async move) -/// data +/// // Les valeurs IN sont déjà disponibles dans les variables liées /// }) /// ``` /// /// # Arguments /// /// - `instance` : Paramètre de type `Arc<`[`ActionInstance`](crate::actions::ActionInstance)`>` - L'instance de l'action -/// - `data` : Paramètre de type [`ActionData`] (Arc>) - Les données d'entrée +/// avec les valeurs IN déjà stockées dans les variables liées /// - Le corps du bloc peut contenir du code asynchrone (`.await`) /// /// # Type de retour @@ -208,56 +210,61 @@ pub type ActionHandler = Arc, ActionD /// use pmoupnp::action_handler; /// /// // Handler minimal - run() collectera automatiquement les OUT -/// let handler = action_handler!(|instance, data| { +/// let handler = action_handler!(|instance| { /// Ok(()) // Succès, pas d'erreur /// }); /// ``` /// -/// ## Exemple 2 : Handler qui modifie une variable avec gestion d'erreur +/// ## Exemple 2 : Handler qui lit et modifie des variables /// /// ```ignore /// use pmoupnp::action_handler; /// use pmoupnp::actions::ActionError; /// -/// let handler = action_handler!(|instance, data| { -/// // Lire un argument d'entrée -/// let volume = data.get("DesiredVolume") -/// .ok_or_else(|| ActionError::MissingArgument("DesiredVolume".to_string()))?; -/// -/// // Modifier la variable d'instance -/// let arg = instance.argument("CurrentVolume") -/// .ok_or_else(|| ActionError::ArgumentNotFound("CurrentVolume".to_string()))?; +/// let handler = action_handler!(|instance| { +/// // Lire un argument d'entrée depuis la variable liée +/// let arg = instance.argument("DesiredVolume") +/// .ok_or_else(|| ActionError::ArgumentNotFound("DesiredVolume".to_string()))?; /// /// let var = arg.get_variable_instance() /// .ok_or_else(|| ActionError::VariableNotBound)?; /// -/// var.set_value(volume.clone()); +/// let volume = var.value(); +/// +/// // Modifier une autre variable d'instance +/// let current_volume = instance.argument("CurrentVolume") +/// .ok_or_else(|| ActionError::ArgumentNotFound("CurrentVolume".to_string()))? +/// .get_variable_instance() +/// .ok_or_else(|| ActionError::VariableNotBound)?; +/// +/// current_volume.set_value(volume); /// /// Ok(()) // Succès - run() collectera CurrentVolume dans les OUT /// }); /// ``` /// -/// ## Exemple 3 : Handler avec logique métier asynchrone et gestion d'erreur +/// ## Exemple 3 : Handler avec logique métier asynchrone /// /// ```ignore /// use pmoupnp::action_handler; /// use pmoupnp::actions::ActionError; /// -/// let handler = action_handler!(|instance, data| { +/// let handler = action_handler!(|instance| { +/// // Lire les paramètres depuis les variables liées +/// let uri_var = instance.argument("CurrentURI") +/// .and_then(|a| a.get_variable_instance()) +/// .ok_or_else(|| ActionError::VariableNotBound)?; +/// +/// let uri = uri_var.value(); +/// /// // Appel asynchrone à un service externe -/// let response = external_service::fetch_data().await +/// let response = external_service::fetch_metadata(&uri).await /// .map_err(|e| ActionError::ExternalError(e.to_string()))?; /// /// // Mettre à jour les variables selon la réponse -/// if let Some(arg) = instance.argument("Status") { +/// if let Some(arg) = instance.argument("Metadata") { /// if let Some(var) = arg.get_variable_instance() { -/// var.set_value(StateValue::String(response.status)); -/// } -/// } -/// -/// if let Some(arg) = instance.argument("Message") { -/// if let Some(var) = arg.get_variable_instance() { -/// var.set_value(StateValue::String(response.message)); +/// var.set_value(StateValue::String(response.metadata)); /// } /// } /// @@ -276,7 +283,7 @@ pub type ActionHandler = Arc, ActionD /// // Contexte partagé (ex: état d'un lecteur média) /// let player_state = Arc::new(Mutex::new(PlayerState::Stopped)); /// -/// let handler = action_handler!(|instance, data| { +/// let handler = action_handler!(|instance| { /// // Vérifier l'état actuel /// { /// let state = player_state.lock().await; @@ -309,8 +316,8 @@ pub type ActionHandler = Arc, ActionD /// - Le résultat est automatiquement boxé et arcé #[macro_export] macro_rules! action_handler { - (|$instance:ident, $data:ident| $body:block) => { - std::sync::Arc::new(|$instance: std::sync::Arc<$crate::actions::ActionInstance>, $data: $crate::actions::ActionData| { + (|$instance:ident| $body:block) => { + std::sync::Arc::new(|$instance: std::sync::Arc<$crate::actions::ActionInstance>| { Box::pin(async move $body) }) }; diff --git a/pmoupnp/src/actions/action_instance.rs b/pmoupnp/src/actions/action_instance.rs index 0581be48..65e5cb78 100644 --- a/pmoupnp/src/actions/action_instance.rs +++ b/pmoupnp/src/actions/action_instance.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use tracing::debug; +use tracing::{debug, trace}; use xmltree::{Element, XMLNode}; use crate::{ @@ -165,9 +165,10 @@ impl ActionInstance { /// Exécute l'action avec les données fournies. /// /// Cette méthode : - /// 1. Exécute le handler avec les données d'entrée - /// 2. Collecte automatiquement les valeurs OUT via [`get_out_values()`](Self::get_out_values) - /// 3. Retourne les résultats + /// 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 /// @@ -185,9 +186,13 @@ impl ActionInstance { /// /// # Fonctionnement /// - /// Le handler n'a pas besoin de retourner les valeurs OUT - il modifie simplement - /// les variables d'instance et retourne `Ok(())`. La méthode `run()` collecte automatiquement - /// toutes les valeurs des arguments marqués comme OUT si le handler réussit. + /// 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 /// @@ -206,8 +211,10 @@ impl ActionInstance { /// pmoupnp::variable_types::StateValue::UI2(50)); /// let input_data = Arc::new(input); /// - /// // Exécuter l'action - le handler modifie CurrentVolume - /// // run() retourne automatiquement CurrentVolume dans les OUT + /// // 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 @@ -224,15 +231,30 @@ impl ActionInstance { /// /// # 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 + 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()); + trace!(" IN {} = {:?}", arg_inst.get_name(), value); + } + } + } + } + let handler = self.model.handler().clone(); let instance_clone = self.clone(); - // Exécuter le handler - handler(instance_clone, data).await?; + // Exécuter le handler (il peut maintenant lire les valeurs IN depuis les variables) + handler(instance_clone).await?; // Collecter automatiquement les valeurs OUT si succès debug!("✅ Action '{}' completed successfully, collecting outputs", self.get_name()); diff --git a/pmoupnp/src/actions/action_methods.rs b/pmoupnp/src/actions/action_methods.rs index 1f579ef4..6e442112 100644 --- a/pmoupnp/src/actions/action_methods.rs +++ b/pmoupnp/src/actions/action_methods.rs @@ -1,4 +1,3 @@ -use std::collections::HashMap; use std::sync::Arc; use tracing::{debug, trace}; @@ -11,11 +10,9 @@ use crate::{ UpnpObjectSetError, UpnpObjectType, UpnpTyped, - UpnpTypedInstance, }; use crate::actions::{ Action, - ActionData, ActionHandler, ActionInstance, Argument, @@ -56,7 +53,9 @@ impl Action { /// /// Ce handler logge simplement l'appel et les arguments d'entrée. /// La méthode [`ActionInstance::run()`](crate::actions::ActionInstance::run) s'occupe - /// automatiquement de collecter les valeurs OUT après l'exécution. + /// automatiquement de : + /// 1. Stocker les valeurs IN dans les variables liées avant d'appeler le handler + /// 2. Collecter les valeurs OUT après l'exécution /// /// # Returns /// @@ -65,7 +64,7 @@ impl Action { /// # Comportement /// /// - Logge le nom de l'action - /// - Logge les arguments IN avec leurs valeurs + /// - Logge les arguments IN avec leurs valeurs (lues depuis les variables liées) /// - Ne fait aucune modification (handler passif) /// /// # Note @@ -73,17 +72,17 @@ impl Action { /// Ce handler est automatiquement assigné lors de la création d'une action. /// Il peut être remplacé via [`set_handler`](Self::set_handler). fn default_handler() -> ActionHandler { - action_handler!(|instance, data| { + action_handler!(|instance| { use crate::UpnpTypedInstance; debug!("🎬 Action '{}' called", instance.get_name()); - // Logger les arguments d'entrée + // Logger les arguments d'entrée (déjà stockés dans les variables par run()) for arg_inst in instance.arguments_set().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()) { - trace!(" IN {} = {:?}", arg_inst.get_name(), value); + if let Some(var_inst) = arg_inst.get_variable_instance() { + trace!(" IN {} = {:?}", arg_inst.get_name(), var_inst.value()); } } } diff --git a/pmoupnp/src/actions/arg_instance_methods.rs b/pmoupnp/src/actions/arg_instance_methods.rs index 7ad75e16..0316b6a6 100644 --- a/pmoupnp/src/actions/arg_instance_methods.rs +++ b/pmoupnp/src/actions/arg_instance_methods.rs @@ -137,10 +137,10 @@ impl UpnpInstance for ArgumentInstance { name: from.get_name().clone(), object_type: "ArgumentInstance".to_string(), }, - + // Clone du modèle pour référence future model: from.clone(), - + // Initialisation à None - sera lié plus tard via bind_variable() // Arc> permet la modification thread-safe post-construction variable_instance: Arc::new(RwLock::new(None)), diff --git a/pmoupnp/src/actions/mod.rs b/pmoupnp/src/actions/mod.rs index 6de16cd4..d8c3623c 100644 --- a/pmoupnp/src/actions/mod.rs +++ b/pmoupnp/src/actions/mod.rs @@ -106,6 +106,7 @@ pub type ArgumentSet = UpnpObjectSet; /// 1. **Création** : Instanciation via [`UpnpInstance::new`] avec `variable_instance = None` /// 2. **Liaison** : Association à une [`StateVarInstance`] via [`bind_variable`](Self::bind_variable) /// 3. **Utilisation** : Accès à la valeur runtime via [`get_variable_instance`](Self::get_variable_instance) +/// 4. **Exécution** : Les valeurs IN sont stockées dans les variables liées lors de l'appel à `run()` /// /// # Pourquoi `variable_instance` est optionnel ? /// @@ -114,6 +115,13 @@ pub type ArgumentSet = UpnpObjectSet; /// - Les `ActionInstance` sont créées **avant** que toutes les variables soient disponibles /// - La validation des dépendances se fait en deux phases /// +/// # Stockage des valeurs IN +/// +/// Lors de l'exécution d'une action, les valeurs des arguments IN sont automatiquement +/// stockées dans les `StateVarInstance` liées. Les handlers peuvent ensuite y accéder +/// via `argument.get_variable_instance().value()` sans avoir besoin de recevoir les +/// valeurs en paramètre. +/// /// # Thread-safety /// /// Le champ `variable_instance` est protégé par un `RwLock` pour permettre :