fin du travail précédent par chatgpt plus

This commit is contained in:
2025-10-19 10:06:04 +02:00
parent efa4855555
commit 2c814bd7c0
8 changed files with 222 additions and 318 deletions

View File

@@ -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>(())
//! })
//! }); //! });
//! ``` //! ```

View File

@@ -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 {
})); }));
} }
} }

View File

@@ -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

View File

@@ -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

View File

@@ -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
}
)?
)* )*
}; };
} }

View File

@@ -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.
/// ///

View File

@@ -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

View File

@@ -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.