On continue l'implementation de pmoupnp

This commit is contained in:
2025-10-01 15:44:52 +02:00
parent 7b6e133bba
commit f1fc39ef47
15 changed files with 618 additions and 31 deletions

72
pmoupnp/errors.rs Normal file
View File

@@ -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<std::num::TryFromIntError> for StateVariableError {
fn from(err: std::num::TryFromIntError) -> Self {
StateVariableError::ConversionError(format!("Integer conversion error: {}", err))
}
}
impl From<std::str::ParseBoolError> for StateVariableError {
fn from(err: std::str::ParseBoolError) -> Self {
StateVariableError::ConversionError(format!("Boolean conversion error: {}", err))
}
}
impl From<uuid::Error> for StateVariableError {
fn from(err: uuid::Error) -> Self {
StateVariableError::ConversionError(format!("UUID conversion error: {}", err))
}
}
impl From<chrono::ParseError> for StateVariableError {
fn from(err: chrono::ParseError) -> Self {
StateVariableError::ConversionError(format!("Time conversion error: {}", err))
}
}
impl From<url::ParseError> for StateVariableError {
fn from(err: url::ParseError) -> Self {
StateVariableError::ConversionError(format!("URI conversion error: {}", err))
}
}
impl From<base64::DecodeError> for StateVariableError {
fn from(err: base64::DecodeError) -> Self {
StateVariableError::ConversionError(format!("Base64 conversion error: {}", err))
}
}
impl From<hex::FromHexError> for StateVariableError {
fn from(err: hex::FromHexError) -> Self {
StateVariableError::ConversionError(format!("Hex conversion error: {}", err))
}
}

View File

@@ -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");
// <name>
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
}
}

View File

@@ -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 <action> 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<Item = &ActionInstance> {
self.instances.values()
}
pub fn all(&self) -> Vec<&ActionInstance> {
self.instances.values().collect()
}
}

View File

@@ -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");
// <name>
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));
// <argumentList>
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
}
}

View File

@@ -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 <action> 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<Item = &Action> {
self.actions.values()
}
pub fn all(&self) -> Vec<&Action> {
self.actions.values().collect()
}
}

View File

@@ -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 <argumentList> contenant 1 ou 2 <argument>
// Pour InOut, on ajoute tous les enfants du <argumentList> 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<Item = &Argument> {
self.arguments.values()
}
pub fn all(&self) -> Vec<&Argument> {
self.arguments.values().collect()
}
}

View File

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

View File

@@ -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<std::io::Error> for ActionError {
fn from(err: std::io::Error) -> Self {
ActionError::GeneralError(format!("IO error: {}", err))
}
}

View File

@@ -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<String, Action>,
}
#[derive(Debug, Clone)]
pub struct ActionInstance {
object: UpnpObjectType,
model: Action,
}
#[derive(Debug, Default, Clone)]
pub struct ActionInstanceSet {
instances: HashMap<String, ActionInstance>,
}
#[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<String, Argument>,
}

View File

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

View File

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

View File

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

View File

@@ -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<ValueSerializer>,
}
#[derive(Debug, Default, Clone)]
pub struct StateVariableSet {
instances: HashMap<String, StateVariable>,
}
pub struct StateVarInstance {
object: UpnpObjectType,
definition: StateVariable,

View File

@@ -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<Item = &StateVariable> {
self.instances.values()
}
pub fn all(&self) -> Vec<&StateVariable> {
self.instances.values().collect()
}
}

View File

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