diff --git a/pmoupnp/errors.rs b/pmoupnp/errors.rs new file mode 100644 index 00000000..f34f65a0 --- /dev/null +++ b/pmoupnp/errors.rs @@ -0,0 +1,72 @@ +use thiserror::Error; + + + +#[derive(Error, Debug)] +pub enum StateVariableError { + #[error("Conversion error: {0}")] + ConversionError(String), + + #[error("Validation error: {0}")] + ValidationError(String), + + #[error("Range error: {0}")] + RangeError(String), + + #[error("Type error: {0}")] + TypeError(String), + + #[error("Parse error: {0}")] + ParseError(String), + + #[error("Event condition error: {0}")] + EventConditionError(String), + + #[error("Arithmetic error: {0}")] + ArithmeticError(String), + + #[error("Unknown error: {0}")] + Unknown(String), +} + +impl From for StateVariableError { + fn from(err: std::num::TryFromIntError) -> Self { + StateVariableError::ConversionError(format!("Integer conversion error: {}", err)) + } +} + +impl From for StateVariableError { + fn from(err: std::str::ParseBoolError) -> Self { + StateVariableError::ConversionError(format!("Boolean conversion error: {}", err)) + } +} + +impl From for StateVariableError { + fn from(err: uuid::Error) -> Self { + StateVariableError::ConversionError(format!("UUID conversion error: {}", err)) + } +} + +impl From for StateVariableError { + fn from(err: chrono::ParseError) -> Self { + StateVariableError::ConversionError(format!("Time conversion error: {}", err)) + } +} + +impl From for StateVariableError { + fn from(err: url::ParseError) -> Self { + StateVariableError::ConversionError(format!("URI conversion error: {}", err)) + } +} + +impl From for StateVariableError { + fn from(err: base64::DecodeError) -> Self { + StateVariableError::ConversionError(format!("Base64 conversion error: {}", err)) + } +} + +impl From for StateVariableError { + fn from(err: hex::FromHexError) -> Self { + StateVariableError::ConversionError(format!("Hex conversion error: {}", err)) + } +} diff --git a/pmoupnp/src/actions/action_instance.rs b/pmoupnp/src/actions/action_instance.rs new file mode 100644 index 00000000..a4de2140 --- /dev/null +++ b/pmoupnp/src/actions/action_instance.rs @@ -0,0 +1,51 @@ +use xmltree::{Element, XMLNode}; + +use crate::actions::Action; +use crate::actions::Argument; +use crate::actions::ArgumentSet; +use crate::actions::ActionInstance; +use crate::UpnpXml; +use crate::{UpnpObject, UpnpObjectType}; + +impl UpnpXml for ActionInstance { +fn to_xml_element(&self) -> Element { + let mut elem = Element::new("action"); + + // + let mut name_elem = Element::new("name"); + name_elem.children.push(XMLNode::Text(self.get_name().clone())); + elem.children.push(XMLNode::Element(name_elem)); + + // déplacer tous les enfants de args_elem dans un nouvel Element + let args_container = self.arguments_set().to_xml_element(); + elem.children.push(XMLNode::Element(args_container)); + + elem + } +} +impl UpnpObject for ActionInstance { + fn as_upnp_object_type(&self) -> &UpnpObjectType { + return &self.object; + } +} + +impl ActionInstance { + + pub fn new(action: &Action) -> Self { + Self { + object: UpnpObjectType { + name: action.get_name().clone(), + object_type: "ActionInstance".to_string(), + }, + model: action.clone(), + } + } + + pub fn arguments(&self, name: &str) -> Option<&Argument> { + self.model.arguments.get(name) + } + + pub fn arguments_set(&self) -> &ArgumentSet { + &self.model.arguments + } +} diff --git a/pmoupnp/src/actions/action_instance_set.rs b/pmoupnp/src/actions/action_instance_set.rs new file mode 100644 index 00000000..02583e3f --- /dev/null +++ b/pmoupnp/src/actions/action_instance_set.rs @@ -0,0 +1,49 @@ +use crate::{ + UpnpObject, UpnpXml, + actions::{ActionInstance, ActionInstanceSet}, +}; +use std::collections::HashMap; +use xmltree::{Element,XMLNode}; + +impl UpnpXml for ActionInstanceSet { + // Méthode pour convertir en XML (à implémenter avec une librairie XML) + fn to_xml_element(&self) -> Element { + let mut elem = Element::new("actionList"); + + for action in self.iter() { + let action_elem = action.to_xml_element(); // retourne un complet + elem.children.push(XMLNode::Element(action_elem)); + } + + elem + } +} + +impl ActionInstanceSet { + pub fn new() -> Self { + Self { + instances: HashMap::new(), + } + } + + pub fn insert(&mut self, instance: ActionInstance) { + self.instances.insert(instance.get_name().clone(), instance); + } + + pub fn contains(&self, name: &str) -> bool { + self.instances.contains_key(name) + } + + pub fn get(&self, name: &str) -> Option<&ActionInstance> { + self.instances.get(name) + } + + pub fn iter(&self) -> impl Iterator { + self.instances.values() + } + + pub fn all(&self) -> Vec<&ActionInstance> { + self.instances.values().collect() + } +} + diff --git a/pmoupnp/src/actions/action_methods.rs b/pmoupnp/src/actions/action_methods.rs new file mode 100644 index 00000000..4b9bd0ea --- /dev/null +++ b/pmoupnp/src/actions/action_methods.rs @@ -0,0 +1,51 @@ +use xmltree::{Element,XMLNode}; + +use crate::UpnpXml; +use crate::actions::Action; +use crate::actions::Argument; +use crate::actions::ArgumentSet; +use crate::{UpnpObject, UpnpObjectType}; + +impl UpnpXml for Action { + fn to_xml_element(&self) -> Element { + let mut action_elem = Element::new("action"); + + // + let mut name_elem = Element::new("name"); + name_elem + .children + .push(XMLNode::Text(self.get_name().clone())); + action_elem.children.push(XMLNode::Element(name_elem)); + + // + let args_elem = self.arguments.to_xml_element(); + action_elem.children.push(XMLNode::Element(args_elem)); + + action_elem + } +} +impl UpnpObject for Action { + fn as_upnp_object_type(&self) -> &UpnpObjectType { + return &self.object; + } +} + +impl Action { + pub fn new(name: String) -> Action { + Self { + object: UpnpObjectType { + name, + object_type: "Action".to_string(), + }, + arguments: ArgumentSet::new(), + } + } + + pub fn add_argument(&mut self, arg: Argument) { + self.arguments.insert(arg); + } + + pub fn arguments(&self) -> &ArgumentSet { + &self.arguments + } +} diff --git a/pmoupnp/src/actions/action_set_methods.rs b/pmoupnp/src/actions/action_set_methods.rs new file mode 100644 index 00000000..3113b1dc --- /dev/null +++ b/pmoupnp/src/actions/action_set_methods.rs @@ -0,0 +1,61 @@ +use std::collections::HashMap; +use xmltree::{Element, XMLNode}; + +use crate::actions::Action; +use crate::actions::ActionSet; +use crate::actions::errors::ActionError; +use crate::UpnpObject; +use crate::UpnpXml; + +impl UpnpXml for ActionSet { + fn to_xml_element(&self) -> Element { + let mut elem = Element::new("actionList"); + + for action in self.iter() { + let action_elem = action.to_xml_element(); // retourne un complet + elem.children.push(XMLNode::Element(action_elem)); + } + + elem + } + +} + +impl ActionSet { + pub fn new() -> Self { + Self { + actions: HashMap::new(), + } + } + + pub fn insert(&mut self, action: Action) -> Result<(), ActionError> { + let name = action.get_name(); + if self.actions.contains_key(name) { + return Err(ActionError::SetError( + format!("Action {} already exists", name) + )); + } + self.actions.insert(name.clone(), action); + Ok(()) + } + + pub fn insert_or_replace(&mut self, action: Action) { + self.actions.insert(action.get_name().clone(), action); + } + + pub fn contains(&self, name: &str) -> bool { + self.actions.contains_key(name) + } + + pub fn get(&self, name: &str) -> Option<&Action> { + self.actions.get(name) + } + + pub fn iter(&self) -> impl Iterator { + self.actions.values() + } + + pub fn all(&self) -> Vec<&Action> { + self.actions.values().collect() + } +} diff --git a/pmoupnp/src/actions/arg_set_methods.rs b/pmoupnp/src/actions/arg_set_methods.rs new file mode 100644 index 00000000..0212677f --- /dev/null +++ b/pmoupnp/src/actions/arg_set_methods.rs @@ -0,0 +1,52 @@ +use crate::UpnpObject; +use crate::{ + UpnpXml, + actions::{Argument, ArgumentSet}, +}; +use std::collections::HashMap; +use xmltree::Element; + +impl UpnpXml for ArgumentSet { + // Méthode pour convertir en XML (à implémenter avec une librairie XML) + fn to_xml_element(&self) -> Element { + let mut elem = Element::new("argumentList"); + + for arg in self.iter() { + let arg_elem = arg.to_xml_element(); // toujours un contenant 1 ou 2 + + // Pour InOut, on ajoute tous les enfants du généré + for child in arg_elem.children { + elem.children.push(child); + } + } + + elem + } +} +impl ArgumentSet { + pub fn new() -> Self { + Self { + arguments: HashMap::new(), + } + } + + pub fn insert(&mut self, arg: Argument) { + self.arguments.insert(arg.get_name().clone(), arg); + } + + pub fn contains(&self, name: &str) -> bool { + self.arguments.contains_key(name) + } + + pub fn get(&self, name: &str) -> Option<&Argument> { + self.arguments.get(name) + } + + pub fn iter(&self) -> impl Iterator { + self.arguments.values() + } + + pub fn all(&self) -> Vec<&Argument> { + self.arguments.values().collect() + } +} diff --git a/pmoupnp/src/actions/argument.rs b/pmoupnp/src/actions/argument.rs new file mode 100644 index 00000000..de75c244 --- /dev/null +++ b/pmoupnp/src/actions/argument.rs @@ -0,0 +1,108 @@ +use xmltree::{Element, XMLNode}; + +use crate::{ + UpnpObject, UpnpObjectType, UpnpXml, actions::Argument, state_variables::StateVariable, +}; + +impl UpnpXml for Argument { + fn to_xml_element(&self) -> Element { + let mut parent = Element::new("argumentList"); + + if self.is_in() && self.is_out() { + // InOut → deux arguments + parent.children.push(XMLNode::Element(make_argument_elem( + self.get_name(), + "in", + self.state_variable().get_name(), + ))); + parent.children.push(XMLNode::Element(make_argument_elem( + self.get_name(), + "out", + self.state_variable().get_name(), + ))); + } else { + // Cas simple + let direction = if self.is_in() { "in" } else { "out" }; + parent.children.push(XMLNode::Element(make_argument_elem( + self.get_name(), + direction, + self.state_variable().get_name(), + ))); + } + + parent + } +} + +impl UpnpObject for Argument { + fn as_upnp_object_type(&self) -> &UpnpObjectType { + return &self.object; + } +} + +impl Argument { + fn new(name: String, state_variable: StateVariable) -> Self { + Self { + object: UpnpObjectType { + name, + object_type: "Argument".to_string(), + }, + state_variable, + is_in: false, + is_out: false, + } + } + + pub fn new_in(name: String, state_variable: StateVariable) -> Self { + let mut arg = Self::new(name, state_variable); + arg.is_in = true; + arg + } + + pub fn new_out(name: String, state_variable: StateVariable) -> Self { + let mut arg = Self::new(name, state_variable); + arg.is_out = true; + arg + } + + pub fn new_in_out(name: String, state_variable: StateVariable) -> Self { + let mut arg = Self::new(name, state_variable); + arg.is_in = true; + arg.is_out = true; + arg + } + + pub fn state_variable(&self) -> &StateVariable { + &self.state_variable + } + + pub fn is_in(&self) -> bool { + self.is_in + } + + pub fn is_out(&self) -> bool { + self.is_out + } +} + +/// Fabrique un complet avec ses sous-éléments +fn make_argument_elem(name: &str, direction: &str, state_var_name: &str) -> Element { + let mut arg = Element::new("argument"); + + let mut name_elem = Element::new("name"); + name_elem.children.push(XMLNode::Text(name.to_string())); + + let mut dir_elem = Element::new("direction"); + dir_elem.children.push(XMLNode::Text(direction.to_string())); + + let mut rel_elem = Element::new("relatedStateVariable"); + rel_elem + .children + .push(XMLNode::Text(state_var_name.to_string())); + + arg.children.push(XMLNode::Element(name_elem)); + arg.children.push(XMLNode::Element(dir_elem)); + arg.children.push(XMLNode::Element(rel_elem)); + + arg +} diff --git a/pmoupnp/src/actions/errors.rs b/pmoupnp/src/actions/errors.rs new file mode 100644 index 00000000..c4ab8443 --- /dev/null +++ b/pmoupnp/src/actions/errors.rs @@ -0,0 +1,19 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ActionError { + #[error("Action error: {0}")] + GeneralError(String), + + #[error("Argument error: {0}")] + ArgumentError(String), + + #[error("Set operation error: {0}")] + SetError(String), +} + +impl From for ActionError { + fn from(err: std::io::Error) -> Self { + ActionError::GeneralError(format!("IO error: {}", err)) + } +} \ No newline at end of file diff --git a/pmoupnp/src/actions/mod.rs b/pmoupnp/src/actions/mod.rs new file mode 100644 index 00000000..9a765134 --- /dev/null +++ b/pmoupnp/src/actions/mod.rs @@ -0,0 +1,48 @@ +mod errors; + +mod action_methods; +mod action_instance; +mod action_set_methods; +mod action_instance_set; +mod argument; +mod arg_set_methods; + + +use std::collections::HashMap; +use crate::{state_variables::StateVariable, UpnpObjectType}; + + +#[derive(Debug, Clone)] +struct Action { + object: UpnpObjectType, + arguments: ArgumentSet, +} + +#[derive(Debug, Default, Clone)] +pub struct ActionSet { + actions: HashMap, +} + +#[derive(Debug, Clone)] +pub struct ActionInstance { + object: UpnpObjectType, + model: Action, +} + +#[derive(Debug, Default, Clone)] +pub struct ActionInstanceSet { + instances: HashMap, +} + +#[derive(Debug, Clone)] +pub struct Argument { + object: UpnpObjectType, + state_variable: StateVariable, + is_in: bool, + is_out: bool, +} + +#[derive(Debug, Default, Clone)] +pub struct ArgumentSet { + arguments: HashMap, +} diff --git a/pmoupnp/src/lib.rs b/pmoupnp/src/lib.rs index 63ce81fa..8a2480a6 100644 --- a/pmoupnp/src/lib.rs +++ b/pmoupnp/src/lib.rs @@ -4,10 +4,11 @@ pub mod server; pub mod state_variables; pub mod value_ranges; pub mod variable_types; +pub mod actions; -pub use crate::object_trait::UpnpObject; +pub use crate::object_trait::{UpnpXml,UpnpObject}; -#[derive(Clone)] +#[derive(Debug,Clone)] pub struct UpnpObjectType { name: String, object_type: String, diff --git a/pmoupnp/src/object_trait.rs b/pmoupnp/src/object_trait.rs index 1c59c3f7..c18dea2a 100644 --- a/pmoupnp/src/object_trait.rs +++ b/pmoupnp/src/object_trait.rs @@ -2,18 +2,8 @@ use xmltree::{Element, EmitterConfig}; use crate::UpnpObjectType; -pub trait UpnpObject { - fn as_upnp_object_type(&self) -> &UpnpObjectType; +pub trait UpnpXml { fn to_xml_element(&self) -> Element; - - fn get_name(&self) -> &String { - return &self.as_upnp_object_type().name; - } - - fn get_object_type(&self) -> &String { - &self.as_upnp_object_type().object_type - } - fn to_xml(&self) -> String { let elem = self.to_xml_element(); @@ -35,3 +25,16 @@ pub trait UpnpObject { xml_string } } +pub trait UpnpObject : UpnpXml { + fn as_upnp_object_type(&self) -> &UpnpObjectType; + + fn get_name(&self) -> &String { + return &self.as_upnp_object_type().name; + } + + fn get_object_type(&self) -> &String { + &self.as_upnp_object_type().object_type + } + + +} diff --git a/pmoupnp/src/state_variables/instance_methods.rs b/pmoupnp/src/state_variables/instance_methods.rs index 0e9093a6..b5d76ba8 100644 --- a/pmoupnp/src/state_variables/instance_methods.rs +++ b/pmoupnp/src/state_variables/instance_methods.rs @@ -3,6 +3,7 @@ use xmltree::Element; use crate::{ UpnpObject, UpnpObjectType, + object_trait::UpnpXml, state_variables::{StateVarInstance, StateVariable, UpnpVariable}, variable_types::StateValue, }; @@ -13,14 +14,16 @@ impl UpnpVariable for StateVarInstance { } } +impl UpnpXml for StateVarInstance { + fn to_xml_element(&self) -> Element { + self.get_definition().to_xml_element() + } +} + impl UpnpObject for StateVarInstance { fn as_upnp_object_type(&self) -> &UpnpObjectType { return &self.object; } - - fn to_xml_element(&self) -> Element { - self.get_definition().to_xml_element() - } } impl StateVarInstance { diff --git a/pmoupnp/src/state_variables/mod.rs b/pmoupnp/src/state_variables/mod.rs index f45fc51b..be971f7a 100644 --- a/pmoupnp/src/state_variables/mod.rs +++ b/pmoupnp/src/state_variables/mod.rs @@ -1,6 +1,7 @@ mod errors; mod instance_methods; mod variable_methods; +mod var_set_methods; mod variable_trait; use std::{ @@ -44,6 +45,11 @@ pub struct StateVariable { marshal: Option, } +#[derive(Debug, Default, Clone)] +pub struct StateVariableSet { + instances: HashMap, +} + pub struct StateVarInstance { object: UpnpObjectType, definition: StateVariable, diff --git a/pmoupnp/src/state_variables/var_set_methods.rs b/pmoupnp/src/state_variables/var_set_methods.rs new file mode 100644 index 00000000..85113128 --- /dev/null +++ b/pmoupnp/src/state_variables/var_set_methods.rs @@ -0,0 +1,34 @@ +use std::collections::HashMap; + +use crate::{state_variables::{StateVariable, StateVariableSet}, UpnpObject}; + +impl StateVariableSet { + pub fn new() -> Self { + Self { + instances: HashMap::new(), + } + } + + pub fn insert(&mut self, instance: StateVariable) { + self.instances.insert(instance.get_name().clone(), instance); + } + + pub fn contains(&self, name: &str) -> bool { + self.instances.contains_key(name) + } + + pub fn get(&self, name: &str) -> Option<&StateVariable> { + self.instances.get(name) + } + + pub fn iter(&self) -> impl Iterator { + self.instances.values() + } + + pub fn all(&self) -> Vec<&StateVariable> { + self.instances.values().collect() + } + + +} + diff --git a/pmoupnp/src/state_variables/variable_methods.rs b/pmoupnp/src/state_variables/variable_methods.rs index a78ccd0c..838709d5 100644 --- a/pmoupnp/src/state_variables/variable_methods.rs +++ b/pmoupnp/src/state_variables/variable_methods.rs @@ -1,25 +1,16 @@ use std::{ - collections::HashMap, - sync::{Arc, RwLock}, + collections::HashMap, fmt, sync::{Arc, RwLock} }; use xmltree::{Element, XMLNode}; use crate::{ - UpnpObject, UpnpObjectType, - state_variables::{ - StateConditionFunc, StateVariable, StringValueParser, ValueSerializer, - variable_trait::UpnpVariable, - }, - value_ranges::ValueRange, - variable_types::{StateValue, StateValueError, StateVarType, UpnpVarType}, + object_trait::UpnpXml, state_variables::{ + variable_trait::UpnpVariable, StateConditionFunc, StateVariable, StringValueParser, ValueSerializer + }, value_ranges::ValueRange, variable_types::{StateValue, StateValueError, StateVarType, UpnpVarType}, UpnpObject, UpnpObjectType }; -impl UpnpObject for StateVariable { - fn as_upnp_object_type(&self) -> &UpnpObjectType { - return &self.object; - } - +impl UpnpXml for StateVariable { fn to_xml_element(&self) -> Element { // Création de l'élément racine let mut root = Element::new("stateVariable"); @@ -91,6 +82,13 @@ impl UpnpObject for StateVariable { root } + +} + +impl UpnpObject for StateVariable { + fn as_upnp_object_type(&self) -> &UpnpObjectType { + return &self.object; + } } impl Clone for StateVariable { @@ -133,6 +131,37 @@ impl Clone for StateVariable { } } +impl fmt::Debug for StateVariable { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("StateVariable") + .field("object", &self.object) + .field("value_type", &self.value_type) + .field("step", &self.step) + .field("modifiable", &self.modifiable) + .field( + "event_conditions", + &format_args!( + "len={}", + self.event_conditions.read().map(|m| m.len()).unwrap_or(0) + ), + ) + .field("description", &self.description) + .field("default_value", &self.default_value) + .field("value_range", &self.value_range) + .field( + "allowed_values", + &format_args!( + "len={}", + self.allowed_values.read().map(|v| v.len()).unwrap_or(0) + ), + ) + .field("send_events", &self.send_events) + .field("parse", &self.parse.as_ref().map(|_| "Some(StringValueParser)").unwrap_or("None")) + .field("marshal", &self.marshal.as_ref().map(|_| "Some(ValueSerializer)").unwrap_or("None")) + .finish() + } +} + impl UpnpVarType for StateVariable { fn as_state_var_type(&self) -> StateVarType { self.value_type // utilise ton From<&StateValue> existant