fin du travail précédent par chatgpt plus
This commit is contained in:
@@ -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)
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<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.
|
||||
///
|
||||
/// 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<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
|
||||
/// 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<String>,
|
||||
) -> 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<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 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 {
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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<T: Reflect + Clone>(
|
||||
data.get(key)
|
||||
.and_then(|boxed| boxed.as_any().downcast_ref::<T>())
|
||||
.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<T: Reflect + 'static>(
|
||||
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.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// 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<std::sync::Arc<$crate::actions::Action>> =
|
||||
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
|
||||
}
|
||||
)?
|
||||
)?
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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<Arc<ServiceInstance>>, 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<Arc<ServiceInstance>>, 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<Arc<ServiceInstance>>, 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<String, String> 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user