fin du travail précédent par chatgpt plus
This commit is contained in:
@@ -25,17 +25,15 @@
|
|||||||
//! use std::sync::Arc;
|
//! use std::sync::Arc;
|
||||||
//!
|
//!
|
||||||
//! // Créer un handler avec la macro
|
//! // Créer un handler avec la macro
|
||||||
//! let handler = action_handler!(|instance, data| {
|
//! let handler = action_handler!(|mut data| {
|
||||||
//! // Traiter les données
|
//! // Traiter les données
|
||||||
//! Ok::<(), ActionError>(())
|
//! Ok::<_, ActionError>(data)
|
||||||
//! });
|
//! });
|
||||||
//!
|
//!
|
||||||
//! // Ou manuellement
|
//! // Ou manuellement
|
||||||
//! use pmoupnp::actions::{ActionData, ActionHandler, ActionInstance};
|
//! use pmoupnp::actions::{ActionData, ActionHandler};
|
||||||
//! let manual_handler: ActionHandler = Arc::new(|instance, data| {
|
//! let manual_handler: ActionHandler = Arc::new(|data| {
|
||||||
//! Box::pin(async move {
|
//! Box::pin(async move { Ok::<_, ActionError>(data) })
|
||||||
//! Ok::<(), ActionError>(())
|
|
||||||
//! })
|
|
||||||
//! });
|
//! });
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
|
|||||||
@@ -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 xmltree::{Element, XMLNode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -16,6 +19,7 @@ use crate::actions::{
|
|||||||
ActionInstance,
|
ActionInstance,
|
||||||
ArgInstanceSet,
|
ArgInstanceSet,
|
||||||
};
|
};
|
||||||
|
use crate::variable_types::StateValue;
|
||||||
|
|
||||||
impl UpnpObject for ActionInstance {
|
impl UpnpObject for ActionInstance {
|
||||||
fn to_xml_element(&self) -> Element {
|
fn to_xml_element(&self) -> Element {
|
||||||
@@ -130,156 +134,135 @@ impl ActionInstance {
|
|||||||
&self.arguments // ⬅️ Retourne les INSTANCES, pas les modèles !
|
&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
|
/// Chaque argument lié à une [`StateVarInstance`](crate::state_variables::StateVarInstance)
|
||||||
/// les variables d'état liées aux arguments OUT et les retourne dans un
|
/// voit sa valeur courante convertie en [`Reflect`](bevy_reflect::Reflect) pour alimenter
|
||||||
/// [`ActionData`] indexé par le nom de chaque argument.
|
/// le handler de l'action.
|
||||||
///
|
fn build_action_data(&self) -> ActionData {
|
||||||
/// # Returns
|
let mut data = HashMap::new();
|
||||||
///
|
|
||||||
/// 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();
|
|
||||||
|
|
||||||
for arg_inst in self.arguments.all() {
|
for arg_inst in self.arguments.all() {
|
||||||
let arg_model = arg_inst.as_ref().get_model();
|
if let Some(var_inst) = arg_inst.get_variable_instance() {
|
||||||
if arg_model.is_out() {
|
let name = arg_inst.get_name().to_string();
|
||||||
if let Some(var_inst) = arg_inst.get_variable_instance() {
|
let reflect_value = var_inst.to_reflect();
|
||||||
result.insert(arg_inst.get_name().to_string(), var_inst.value());
|
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<String, StateValue>,
|
||||||
|
) -> HashSet<String> {
|
||||||
|
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.
|
/// Sauvegarde les arguments IN dans les variables d'état (mode stateful uniquement).
|
||||||
///
|
async fn save_inputs_to_state_variables(
|
||||||
/// Cette méthode :
|
&self,
|
||||||
/// 1. Stocke les valeurs IN dans les variables liées
|
action_data: &ActionData,
|
||||||
/// 2. Exécute le handler (qui peut accéder aux valeurs IN via les variables)
|
updated_keys: &HashSet<String>,
|
||||||
/// 3. Collecte automatiquement les valeurs OUT via [`get_out_values()`](Self::get_out_values)
|
) -> Result<(), crate::actions::ActionError> {
|
||||||
/// 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<ActionData, ActionError>` :
|
|
||||||
/// - `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<Self>, data: ActionData) -> Result<ActionData, crate::actions::ActionError> {
|
|
||||||
// Stocker les valeurs IN dans les variables liées
|
|
||||||
for arg_inst in self.arguments.all() {
|
for arg_inst in self.arguments.all() {
|
||||||
let arg_model = arg_inst.as_ref().get_model();
|
if arg_inst.get_model().is_in() && updated_keys.contains(arg_inst.get_name()) {
|
||||||
if arg_model.is_in() {
|
if let Some(var_inst) = arg_inst.get_variable_instance() {
|
||||||
if let Some(value) = data.get(arg_inst.get_name()) {
|
if let Some(reflect_value) = action_data.get(arg_inst.get_name()) {
|
||||||
if let Some(var_inst) = arg_inst.get_variable_instance() {
|
let cloned = reflect_value
|
||||||
var_inst.set_value(value.clone()).await.ok();
|
.as_ref()
|
||||||
trace!(" IN {} = {:?}", arg_inst.get_name(), value);
|
.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<Self>,
|
||||||
|
soap_data: Arc<HashMap<String, StateValue>>,
|
||||||
|
) -> Result<ActionData, crate::actions::ActionError> {
|
||||||
|
// 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 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)
|
// 5. Sauvegarder les OUT si stateful
|
||||||
handler(instance_clone).await?;
|
if self.is_stateful() {
|
||||||
|
self.save_outputs_to_state_variables(&result_data).await?;
|
||||||
|
}
|
||||||
|
|
||||||
// Collecter automatiquement les valeurs OUT si succès
|
// 6. Retourner les données pour la réponse SOAP
|
||||||
debug!("✅ Action '{}' completed successfully, collecting outputs", self.get_name());
|
Ok(result_data)
|
||||||
Ok(self.get_out_values())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,4 +293,3 @@ mod tests {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use tracing::{debug, info, trace};
|
use tracing::{info, trace};
|
||||||
use xmltree::{Element, XMLNode};
|
use xmltree::{Element, XMLNode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@@ -73,7 +73,11 @@ impl Action {
|
|||||||
|
|
||||||
// Logger les arguments
|
// Logger les arguments
|
||||||
for (key, value) in data.iter() {
|
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
|
// Retourner les données telles quelles
|
||||||
@@ -141,9 +145,9 @@ impl Action {
|
|||||||
/// # use pmoupnp::action_handler;
|
/// # use pmoupnp::action_handler;
|
||||||
/// let mut action = Action::new("Play".to_string());
|
/// 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
|
/// // Logique personnalisée
|
||||||
/// Ok::<(), ActionError>(())
|
/// Ok::<_, ActionError>(data)
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// action.set_handler(custom_handler);
|
/// action.set_handler(custom_handler);
|
||||||
@@ -185,6 +189,12 @@ impl Action {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_stateless(&mut self, stateless: bool) -> &mut Self {
|
||||||
|
self.stateful = !stateless;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Retourne `true` si l'action est stateful.
|
/// Retourne `true` si l'action est stateful.
|
||||||
///
|
///
|
||||||
/// # Returns
|
/// # Returns
|
||||||
|
|||||||
@@ -35,7 +35,6 @@
|
|||||||
|
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
use crate::actions::{ActionData, ActionError};
|
use crate::actions::{ActionData, ActionError};
|
||||||
use std::any::Any;
|
|
||||||
|
|
||||||
/// Extrait une valeur typée depuis ActionData.
|
/// Extrait une valeur typée depuis ActionData.
|
||||||
///
|
///
|
||||||
@@ -58,7 +57,7 @@ use std::any::Any;
|
|||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Retourne `ActionError::ArgumentNotFound` si :
|
/// Retourne `ActionError::ArgumentError` si :
|
||||||
/// - La clé n'existe pas dans ActionData
|
/// - La clé n'existe pas dans ActionData
|
||||||
/// - La valeur ne peut pas être convertie vers le type `T`
|
/// - La valeur ne peut pas être convertie vers le type `T`
|
||||||
///
|
///
|
||||||
@@ -81,7 +80,10 @@ pub fn get_value<T: Reflect + Clone>(
|
|||||||
data.get(key)
|
data.get(key)
|
||||||
.and_then(|boxed| boxed.as_any().downcast_ref::<T>())
|
.and_then(|boxed| boxed.as_any().downcast_ref::<T>())
|
||||||
.cloned()
|
.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.
|
/// Insère une valeur dans ActionData.
|
||||||
@@ -121,6 +123,44 @@ pub fn set_value<T: Reflect + 'static>(
|
|||||||
data.insert(key.into(), Box::new(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::<String>() {
|
||||||
|
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::<u8>() {
|
||||||
|
v.to_string()
|
||||||
|
} else if let Some(v) = value.as_any().downcast_ref::<u16>() {
|
||||||
|
v.to_string()
|
||||||
|
} else if let Some(v) = value.as_any().downcast_ref::<u32>() {
|
||||||
|
v.to_string()
|
||||||
|
} else if let Some(v) = value.as_any().downcast_ref::<u64>() {
|
||||||
|
v.to_string()
|
||||||
|
} else if let Some(v) = value.as_any().downcast_ref::<i8>() {
|
||||||
|
v.to_string()
|
||||||
|
} else if let Some(v) = value.as_any().downcast_ref::<i16>() {
|
||||||
|
v.to_string()
|
||||||
|
} else if let Some(v) = value.as_any().downcast_ref::<i32>() {
|
||||||
|
v.to_string()
|
||||||
|
} else if let Some(v) = value.as_any().downcast_ref::<i64>() {
|
||||||
|
v.to_string()
|
||||||
|
} else if let Some(v) = value.as_any().downcast_ref::<f32>() {
|
||||||
|
v.to_string()
|
||||||
|
} else if let Some(v) = value.as_any().downcast_ref::<f64>() {
|
||||||
|
v.to_string()
|
||||||
|
} else if let Some(v) = value.as_any().downcast_ref::<bool>() {
|
||||||
|
if *v { "1".to_string() } else { "0".to_string() }
|
||||||
|
} else {
|
||||||
|
"<unsupported Reflect>".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Macro pour extraire facilement une valeur depuis ActionData.
|
/// Macro pour extraire facilement une valeur depuis ActionData.
|
||||||
///
|
///
|
||||||
/// Cette macro simplifie l'utilisation de [`get_value`] en gérant
|
/// Cette macro simplifie l'utilisation de [`get_value`] en gérant
|
||||||
|
|||||||
@@ -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.
|
/// Macro pour définir facilement une action UPnP.
|
||||||
///
|
///
|
||||||
/// Cette macro simplifie la création d'actions UPnP statiques en générant
|
/// 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)
|
/// - Initialisation paresseuse via `Lazy` (thread-safe)
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! define_action {
|
macro_rules! define_action {
|
||||||
// Variante sans arguments avec handler optionnel
|
// Variante sans arguments avec options `stateless` et handler
|
||||||
(pub static $name:ident = $action_name:literal $(with handler $handler:expr)?) => {
|
(pub static $name:ident = $action_name:literal $(stateless)? $(with handler $handler:expr)?) => {
|
||||||
pub static $name: once_cell::sync::Lazy<std::sync::Arc<$crate::actions::Action>> =
|
pub static $name: once_cell::sync::Lazy<std::sync::Arc<$crate::actions::Action>> =
|
||||||
once_cell::sync::Lazy::new(|| {
|
once_cell::sync::Lazy::new(|| {
|
||||||
let mut ac = $crate::actions::Action::new($action_name.to_string());
|
let mut ac = $crate::actions::Action::new($action_name.to_string());
|
||||||
|
|
||||||
|
define_action!(@maybe_stateless ac $(stateless)?);
|
||||||
|
|
||||||
$(
|
$(
|
||||||
ac.set_handler($handler);
|
ac.set_handler($handler);
|
||||||
)?
|
)?
|
||||||
@@ -222,8 +120,8 @@ macro_rules! define_action {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Variante avec arguments et handler optionnel
|
// Variante avec arguments, options `stateless` et handler
|
||||||
(pub static $name:ident = $action_name:literal {
|
(pub static $name:ident = $action_name:literal $(stateless)? {
|
||||||
$(
|
$(
|
||||||
$direction:ident $arg_name:literal => $var_ref:expr
|
$direction:ident $arg_name:literal => $var_ref:expr
|
||||||
),* $(,)?
|
),* $(,)?
|
||||||
@@ -234,6 +132,8 @@ macro_rules! define_action {
|
|||||||
once_cell::sync::Lazy::new(|| {
|
once_cell::sync::Lazy::new(|| {
|
||||||
let mut ac = $crate::actions::Action::new($action_name.to_string());
|
let mut ac = $crate::actions::Action::new($action_name.to_string());
|
||||||
|
|
||||||
|
define_action!(@maybe_stateless ac $(stateless)?);
|
||||||
|
|
||||||
$(
|
$(
|
||||||
ac.add_argument(
|
ac.add_argument(
|
||||||
define_action!(@arg $direction $arg_name, $var_ref)
|
define_action!(@arg $direction $arg_name, $var_ref)
|
||||||
@@ -248,6 +148,12 @@ macro_rules! define_action {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
(@maybe_stateless $ac:ident stateless) => {
|
||||||
|
$ac.set_stateful(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
(@maybe_stateless $ac:ident) => {};
|
||||||
|
|
||||||
// Helper interne pour créer un argument d'entrée
|
// Helper interne pour créer un argument d'entrée
|
||||||
(@arg in $name:literal, $var:expr) => {
|
(@arg in $name:literal, $var:expr) => {
|
||||||
std::sync::Arc::new(
|
std::sync::Arc::new(
|
||||||
@@ -362,29 +268,10 @@ macro_rules! define_action {
|
|||||||
/// - La macro se développe en plusieurs appels à [`define_action!`]
|
/// - La macro se développe en plusieurs appels à [`define_action!`]
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! define_actions {
|
macro_rules! define_actions {
|
||||||
// Variante avec arguments pour chaque action
|
|
||||||
(
|
(
|
||||||
$(
|
$(
|
||||||
$name:ident = $action_name:literal {
|
$name:ident = $action_name:literal $(stateless)?
|
||||||
$(
|
$({
|
||||||
$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 $({
|
|
||||||
$(
|
$(
|
||||||
$direction:ident $arg_name:literal => $var_ref:expr
|
$direction:ident $arg_name:literal => $var_ref:expr
|
||||||
),* $(,)?
|
),* $(,)?
|
||||||
@@ -392,20 +279,15 @@ macro_rules! define_actions {
|
|||||||
)*
|
)*
|
||||||
) => {
|
) => {
|
||||||
$(
|
$(
|
||||||
$(
|
define_action! {
|
||||||
define_action! {
|
pub static $name = $action_name
|
||||||
pub static $name = $action_name {
|
$(stateless)?
|
||||||
|
$(
|
||||||
|
{
|
||||||
$($direction $arg_name => $var_ref),*
|
$($direction $arg_name => $var_ref),*
|
||||||
}
|
}
|
||||||
}
|
)?
|
||||||
)?
|
}
|
||||||
$(
|
|
||||||
// Cas sans accolades (action sans arguments)
|
|
||||||
#[allow(unused)]
|
|
||||||
define_action! {
|
|
||||||
pub static $name = $action_name
|
|
||||||
}
|
|
||||||
)?
|
|
||||||
)*
|
)*
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -21,7 +21,7 @@ use std::sync::{Arc, RwLock};
|
|||||||
|
|
||||||
pub use errors::ActionError;
|
pub use errors::ActionError;
|
||||||
pub use action_handler::{ActionData, ActionFuture, ActionHandler};
|
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.
|
/// Action UPnP.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -37,8 +37,6 @@ use axum::{
|
|||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
future::Future,
|
|
||||||
pin::Pin,
|
|
||||||
sync::{Arc, Mutex, RwLock},
|
sync::{Arc, Mutex, RwLock},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
@@ -973,7 +971,6 @@ impl ServiceInstance {
|
|||||||
///
|
///
|
||||||
/// Le résultat est déjà échappé XML-safe selon les normes UPnP.
|
/// Le résultat est déjà échappé XML-safe selon les normes UPnP.
|
||||||
fn reflect_to_string(value: &dyn Reflect) -> String {
|
fn reflect_to_string(value: &dyn Reflect) -> String {
|
||||||
use std::any::Any;
|
|
||||||
use bevy_reflect::ReflectRef;
|
use bevy_reflect::ReflectRef;
|
||||||
|
|
||||||
// Essayer de downcaster vers des types primitifs courants
|
// Essayer de downcaster vers des types primitifs courants
|
||||||
@@ -1290,21 +1287,8 @@ async fn control_handler(State(instance): State<Arc<ServiceInstance>>, body: Str
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Convertir les arguments SOAP (String) en ActionData (StateValue)
|
// Convertir les arguments SOAP (String) en StateValue
|
||||||
// D'abord, initialiser tous les arguments IN avec leurs valeurs par défaut
|
let mut soap_values = HashMap::new();
|
||||||
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
|
|
||||||
for (arg_name, arg_value) in soap_action.args {
|
for (arg_name, arg_value) in soap_action.args {
|
||||||
debug!("🔍 Processing SOAP arg: {} = '{}'", arg_name, arg_value);
|
debug!("🔍 Processing SOAP arg: {} = '{}'", arg_name, arg_value);
|
||||||
// Trouver l'argument correspondant pour obtenir son type
|
// Trouver l'argument correspondant pour obtenir son type
|
||||||
@@ -1315,7 +1299,7 @@ async fn control_handler(State(instance): State<Arc<ServiceInstance>>, body: Str
|
|||||||
match StateValue::from_string(&arg_value, &var_model.as_state_var_type()) {
|
match StateValue::from_string(&arg_value, &var_model.as_state_var_type()) {
|
||||||
Ok(value) => {
|
Ok(value) => {
|
||||||
debug!("✅ Parsed {} = {:?}", arg_name, value);
|
debug!("✅ Parsed {} = {:?}", arg_name, value);
|
||||||
action_data.insert(arg_name, value);
|
soap_values.insert(arg_name, value);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("❌ Failed to parse argument '{}': {:?}", arg_name, e);
|
error!("❌ Failed to parse argument '{}': {:?}", arg_name, e);
|
||||||
@@ -1336,15 +1320,24 @@ async fn control_handler(State(instance): State<Arc<ServiceInstance>>, body: Str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let action_data = Arc::new(action_data);
|
let soap_values = Arc::new(soap_values);
|
||||||
|
|
||||||
// Exécuter l'action
|
// 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) => {
|
Ok(output_data) => {
|
||||||
// Convertir les StateValue en String pour SOAP
|
// Convertir ActionData (Reflect) → HashMap<String, String> pour SOAP
|
||||||
let mut soap_values = HashMap::new();
|
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
|
// Construire la réponse SOAP
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
use crate::variable_types::{StateValue, StateValueError, StateVarType};
|
use crate::variable_types::{StateValue, StateValueError, StateVarType};
|
||||||
use std::any::Any;
|
|
||||||
|
|
||||||
impl StateValue {
|
impl StateValue {
|
||||||
/// Convertit la StateValue en une valeur Reflect.
|
/// Convertit la StateValue en une valeur Reflect.
|
||||||
|
|||||||
Reference in New Issue
Block a user