Gros refactoring

This commit is contained in:
2025-10-02 18:39:32 +02:00
parent 40c003cca4
commit ad24ee57f8
42 changed files with 5903 additions and 461 deletions

View File

@@ -1,59 +1,116 @@
use std::fmt;
use chrono::{DateTime, Utc};
use tokio::sync::RwLock;
use xmltree::Element;
use crate::{
UpnpObject, UpnpObjectType,
object_trait::UpnpXml,
state_variables::{StateVarInstance, StateVariable, UpnpVariable},
variable_types::StateValue,
object_trait::{UpnpInstance, UpnpObject}, state_variables::{StateVarInstance, StateVariable, UpnpVariable}, variable_types::{StateValue, StateValueError, UpnpVarType}, UpnpObjectType, UpnpTyped, UpnpTypedInstance, UpnpTypedObject
};
impl UpnpVariable for StateVarInstance {
fn get_definition(&self) -> &StateVariable {
return &self.definition;
}
}
impl UpnpXml for StateVarInstance {
fn to_xml_element(&self) -> Element {
self.get_definition().to_xml_element()
return &self.model;
}
}
impl UpnpObject for StateVarInstance {
fn as_upnp_object_type(&self) -> &UpnpObjectType {
return &self.object;
async fn to_xml_element(&self) -> Element {
self.get_definition().to_xml_element().await
}
}
impl StateVarInstance {
pub fn new(from: &StateVariable) -> Self {
impl UpnpVarType for StateVarInstance {
fn as_state_var_type(&self) -> crate::variable_types::StateVarType {
self.get_definition().as_state_var_type()
}
}
impl UpnpInstance for StateVarInstance {
type Model = StateVariable;
fn new(from: &StateVariable) -> Self {
Self {
object: UpnpObjectType {
name: from.object.name.clone(),
object_type: "StateVarInstance".to_string(),
},
definition: from.clone(),
value: from.get_default(),
old_value: from.get_default(),
last_modified: Utc::now(),
last_notification: Utc::now(),
model: from.clone(),
value: RwLock::new(from.get_default()),
old_value: RwLock::new(from.get_default()),
last_modified: RwLock::new(Utc::now()),
last_notification: RwLock::new(Utc::now()),
}
}
pub fn set_value(&mut self, new_value: StateValue) {
self.old_value = self.value.clone();
self.value = new_value;
self.last_modified = Utc::now(); // mise à jour automatique
}
}
impl UpnpTyped for StateVarInstance {
fn as_upnp_object_type(&self) -> &UpnpObjectType {
return &self.object;
}
}
impl UpnpTypedInstance for StateVarInstance {
fn get_model(&self) -> &Self::Model {
&self.model
}
}
impl fmt::Debug for StateVarInstance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StateVarInstance")
.field("object", &self.object)
.field("model", &self.model)
.field("value", &self.value)
.field("old_value", &self.old_value)
.field("last_modified", &self.last_modified)
.field("last_notification", &self.last_notification)
.finish()
}
}
impl Clone for StateVarInstance {
fn clone(&self) -> Self {
Self {
object: self.object.clone(),
model: self.model.clone(),
value: RwLock::new(self.value.blocking_read().clone()),
old_value: RwLock::new(self.old_value.blocking_read().clone()),
last_modified: RwLock::new(self.last_modified.blocking_read().clone()),
last_notification: RwLock::new(self.last_notification.blocking_read().clone()),
}
}
}
impl StateVarInstance {
pub async fn set_value(&self, new_value: StateValue) -> Result<(), StateValueError> {
// Validation du type
if self.as_state_var_type() != new_value.as_state_var_type() {
return Err(StateValueError::TypeError(
"Value type mismatch".to_string()
));
}
// Mise à jour avec les locks
let mut old_val = self.old_value.write().await;
let mut val = self.value.write().await;
let mut modified = self.last_modified.write().await;
*old_val = val.clone();
*val = new_value;
*modified = Utc::now();
Ok(())
}
/// Accès à la valeur
pub fn value(&self) -> &StateValue {
&self.value
pub fn value(&self) -> StateValue {
self.value.blocking_read().clone()
}
/// Accès au timestamp
pub fn last_modified(&self) -> DateTime<Utc> {
self.last_modified
self.last_modified.blocking_read().clone()
}
}

View File

@@ -2,22 +2,22 @@ mod errors;
mod instance_methods;
mod variable_methods;
mod var_set_methods;
mod var_inst_set_methods;
mod variable_trait;
use std::{
collections::HashMap,
sync::{Arc, RwLock},
sync::Arc,
};
pub use crate::state_variables::variable_trait::UpnpVariable;
use bevy_reflect::Reflect;
use chrono::{DateTime, Utc};
pub use errors::StateVariableError;
use tokio::sync::RwLock;
use crate::{
UpnpObjectType,
value_ranges::ValueRange,
variable_types::{StateValue, StateVarType},
value_ranges::ValueRange, variable_types::{StateValue, StateVarType}, UpnpObjectSet, UpnpObjectType, UpnpSet
};
/// Type pour les fonctions de condition d'événement
@@ -46,16 +46,16 @@ pub struct StateVariable {
marshal: Option<ValueSerializer>,
}
#[derive(Debug, Default, Clone)]
pub struct StateVariableSet {
instances: HashMap<String, StateVariable>,
}
pub type StateVariableSet = UpnpObjectSet<StateVariable>;
pub struct StateVarInstance {
object: UpnpObjectType,
definition: StateVariable,
value: StateValue,
old_value: StateValue,
last_modified: DateTime<Utc>,
last_notification: DateTime<Utc>,
model: StateVariable,
value: RwLock<StateValue>,
old_value: RwLock<StateValue>,
last_modified: RwLock<DateTime<Utc>>,
last_notification: RwLock<DateTime<Utc>>,
}
pub type StateVarInstanceSet = UpnpObjectSet<StateVarInstance>;

View File

@@ -0,0 +1,33 @@
use std::collections::HashMap;
use tokio::sync::RwLock;
use xmltree::{Element, XMLNode};
use crate::{state_variables::{StateVarInstance, StateVarInstanceSet, StateVariableSet}, UpnInstanceSet, UpnpObject, UpnpTyped};
use crate::UpnpInstance;
impl UpnpObject for StateVarInstanceSet {
async fn to_xml_element(&self) -> Element {
let mut elem = Element::new("serviceStateTable");
for state_var in self.all().await {
let state_var_elem = state_var.to_xml_element().await; // retourne un <stateVariable> complet
elem.children.push(XMLNode::Element(state_var_elem));
}
elem
}
}
impl UpnpInstance for StateVarInstanceSet {
type Model = StateVariableSet;
fn new(_: &StateVariableSet) -> Self {
Self { objects: RwLock::new(HashMap::new()) }
}
}

View File

@@ -1,34 +1,25 @@
use std::collections::HashMap;
use xmltree::{Element, XMLNode};
use crate::{state_variables::{StateVariable, StateVariableSet}, UpnpObject};
use crate::{object_trait::UpnpModel, state_variables::{StateVarInstanceSet, StateVariableSet}, UpnInstanceSet, UpnpObject};
impl StateVariableSet {
pub fn new() -> Self {
Self {
instances: HashMap::new(),
impl UpnpObject for StateVariableSet {
async fn to_xml_element(&self) -> Element {
let mut elem = Element::new("serviceStateTable");
for state_var in self.get_all() {
let state_var_elem = state_var.to_xml_element().await; // retourne un <stateVariable> complet
elem.children.push(XMLNode::Element(state_var_elem));
}
}
pub fn insert(&mut self, instance: StateVariable) {
self.instances.insert(instance.get_name().clone(), instance);
elem
}
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()
}
}
impl UpnpModel for StateVariableSet {
type Instance = StateVarInstanceSet;
}

View File

@@ -1,17 +1,37 @@
use std::{
collections::HashMap, fmt, sync::{Arc, RwLock}
collections::HashMap,
fmt,
sync::Arc,
};
use tokio::sync::RwLock;
use xmltree::{Element, XMLNode};
use crate::{
object_trait::UpnpXml, state_variables::{
variable_trait::UpnpVariable, StateConditionFunc, StateVariable, StringValueParser, ValueSerializer
}, value_ranges::ValueRange, variable_types::{StateValue, StateValueError, StateVarType, UpnpVarType}, UpnpObject, UpnpObjectType
UpnpObjectType, UpnpTyped,
object_trait::{UpnpModel, UpnpObject},
state_variables::{
StateConditionFunc, StateVarInstance, StateVariable, StringValueParser, ValueSerializer,
variable_trait::UpnpVariable,
},
value_ranges::ValueRange,
variable_types::{StateValue, StateValueError, StateVarType, UpnpVarType},
};
impl UpnpXml for StateVariable {
fn to_xml_element(&self) -> Element {
impl UpnpTyped for StateVariable {
fn as_upnp_object_type(&self) -> &UpnpObjectType {
&self.object
}
}
impl UpnpVarType for StateVariable {
fn as_state_var_type(&self) -> StateVarType {
self.value_type.as_state_var_type() // utilise ton From<&StateValue> existant
}
}
impl UpnpObject for StateVariable {
async fn to_xml_element(&self) -> Element {
// Création de l'élément racine <stateVariable>
let mut root = Element::new("stateVariable");
root.attributes.insert(
@@ -39,16 +59,15 @@ impl UpnpXml for StateVariable {
}
// <allowedValueList> si défini
if let Ok(av) = self.allowed_values.read() {
if !av.is_empty() {
let mut list_elem = Element::new("allowedValueList");
for val in av.iter() {
let mut val_elem = Element::new("allowedValue");
val_elem.children.push(XMLNode::Text(val.to_string()));
list_elem.children.push(XMLNode::Element(val_elem));
}
root.children.push(XMLNode::Element(list_elem));
let av = self.allowed_values.read().await;
if !av.is_empty() {
let mut list_elem = Element::new("allowedValueList");
for val in av.iter() {
let mut val_elem = Element::new("allowedValue");
val_elem.children.push(XMLNode::Text(val.to_string()));
list_elem.children.push(XMLNode::Element(val_elem));
}
root.children.push(XMLNode::Element(list_elem));
}
// <allowedValueRange> si défini
@@ -82,13 +101,10 @@ impl UpnpXml for StateVariable {
root
}
}
impl UpnpObject for StateVariable {
fn as_upnp_object_type(&self) -> &UpnpObjectType {
return &self.object;
}
impl UpnpModel for StateVariable {
type Instance = StateVarInstance;
}
impl Clone for StateVariable {
@@ -98,8 +114,7 @@ impl Clone for StateVariable {
// si le lock est "poisoned" on panic - tu peux adapter la gestion si tu veux
let guard = self
.event_conditions
.read()
.expect("RwLock poisoned during clone");
.blocking_read();
// nécessite que Key: Clone, Value: Clone
Arc::new(RwLock::new(guard.clone()))
};
@@ -107,8 +122,7 @@ impl Clone for StateVariable {
let allowed_values_clone = {
let guard = self
.allowed_values
.read()
.expect("RwLock poisoned during clone");
.blocking_read();
Arc::new(RwLock::new(guard.clone()))
};
@@ -142,7 +156,7 @@ impl fmt::Debug for StateVariable {
"event_conditions",
&format_args!(
"len={}",
self.event_conditions.read().map(|m| m.len()).unwrap_or(0)
self.event_conditions.blocking_read().len()
),
)
.field("description", &self.description)
@@ -152,22 +166,30 @@ impl fmt::Debug for StateVariable {
"allowed_values",
&format_args!(
"len={}",
self.allowed_values.read().map(|v| v.len()).unwrap_or(0)
self.allowed_values.blocking_read().len()
),
)
.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"))
.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
}
}
impl UpnpVariable for StateVariable {
fn get_definition(&self) -> &StateVariable {
return self;
@@ -276,18 +298,18 @@ impl StateVariable {
pub fn add_event_condition(&self, name: String, func: StateConditionFunc) {
// on lock en écriture
let mut guard = self.event_conditions.write().unwrap();
let mut guard = self.event_conditions.blocking_write();
guard.insert(name, func);
// le lock est automatiquement relâché ici (RAII)
}
pub fn remove_event_condition(&self, name: &str) {
let mut guard = self.event_conditions.write().unwrap();
let mut guard = self.event_conditions.blocking_write();
guard.remove(name);
}
pub fn clear_event_conditions(&mut self) {
let mut guard = self.event_conditions.write().unwrap();
let mut guard = self.event_conditions.blocking_write();
guard.clear()
}
@@ -312,8 +334,7 @@ impl StateVariable {
pub fn extend_allowed_values(&mut self, values: &[StateValue]) -> Result<(), StateValueError> {
let mut av = self
.allowed_values
.write()
.map_err(|_| StateValueError::TypeError("Lock poisoned".to_string()))?;
.blocking_write();
for v in values {
if self.as_state_var_type() == v.as_state_var_type() {
@@ -331,8 +352,7 @@ impl StateVariable {
pub fn push_allowed_value(&mut self, value: &StateValue) -> Result<(), StateValueError> {
let mut av = self
.allowed_values
.write()
.map_err(|_| StateValueError::TypeError("Lock poisoned".to_string()))?;
.blocking_write();
if self.as_state_var_type() == value.as_state_var_type() {
av.push(value.clone());

View File

@@ -3,46 +3,199 @@ use crate::{
variable_types::{StateValue, UpnpVarType},
};
/// Trait pour accéder aux propriétés et contraintes d'une variable UPnP.
///
/// Ce trait fournit une interface uniforme pour interroger les métadonnées,
/// contraintes et comportements d'une variable UPnP, qu'il s'agisse d'une
/// définition ([`StateVariable`]) ou d'une instance ([`StateVarInstance`]).
///
/// # Architecture
///
/// Le trait utilise le pattern "trait avec implémentation par défaut" :
/// - Une seule méthode requise : [`get_definition`](Self::get_definition)
/// - Toutes les autres méthodes sont implémentées par défaut en déléguant à la définition
///
/// Cela permet une interface cohérente entre modèles et instances sans duplication de code.
///
/// # Hiérarchie
///
/// ```text
/// UpnpVariable
/// ├─> StateVariable (get_definition() retourne self)
/// └─> StateVarInstance (get_definition() retourne self.definition)
/// ```
///
/// # Examples
///
/// ```ignore
/// fn display_variable_info<V: UpnpVariable>(var: &V) {
/// println!("Variable: {}", var.get_definition().get_name());
///
/// if var.has_default() {
/// println!("Default: {:?}", var.get_default());
/// }
///
/// if var.has_range() {
/// println!("Has range constraints");
/// }
///
/// if var.has_allowed_values() {
/// println!("Has allowed values list");
/// }
/// }
/// ```
pub trait UpnpVariable {
/// Retourne une référence vers la définition de la variable.
///
/// Cette méthode est la base de toutes les autres méthodes du trait.
///
/// # Implementation
///
/// - Pour [`StateVariable`] : retourne `self`
/// - Pour [`StateVarInstance`] : retourne `self.definition`
fn get_definition(&self) -> &StateVariable;
/// Indique si la variable a un pas (step) défini.
///
/// Le pas définit l'incrément minimal entre deux valeurs valides pour
/// les types numériques.
///
/// # Returns
///
/// `true` si un pas est défini, `false` sinon.
///
/// # Examples
///
/// ```ignore
/// if var.has_step() {
/// println!("Step: {:?}", var.get_step());
/// }
/// ```
fn has_step(&self) -> bool {
return self.get_definition().step.is_some();
self.get_definition().step.is_some()
}
/// Retourne le pas (step) de la variable s'il est défini.
///
/// # Returns
///
/// - `Some(StateValue)` si un pas est défini
/// - `None` sinon
///
/// # See also
///
/// - [`has_step`](Self::has_step) pour tester l'existence
fn get_step(&self) -> Option<StateValue> {
return self.get_definition().step.clone();
self.get_definition().step.clone()
}
/// Indique si la variable a une plage de valeurs (range) définie.
///
/// La plage définit les valeurs minimale et maximale acceptables.
///
/// # Returns
///
/// `true` si une plage est définie, `false` sinon.
fn has_range(&self) -> bool {
return self.get_definition().value_range.is_some();
self.get_definition().value_range.is_some()
}
/// Indique si la variable est modifiable.
///
/// Une variable non modifiable est en lecture seule.
///
/// # Returns
///
/// `true` si la variable peut être modifiée, `false` sinon.
fn is_modifiable(&self) -> bool {
return self.get_definition().modifiable;
self.get_definition().modifiable
}
/// Indique si la variable a des conditions d'événement définies.
///
/// Les conditions d'événement déterminent quand des notifications
/// doivent être envoyées lors de changements de valeur.
///
/// # Returns
///
/// `true` si au moins une condition d'événement existe, `false` sinon.
///
/// # Note
///
/// Retourne `false` si le lock est empoisonné (poisoned).
fn has_event_conditions(&self) -> bool {
return self.get_definition().event_conditions.read().unwrap().len() > 0;
let guard = self.get_definition().event_conditions.blocking_read();
!guard.is_empty()
}
/// Vérifie si une condition d'événement spécifique existe.
///
/// # Arguments
///
/// * `name` - Le nom de la condition à rechercher
///
/// # Returns
///
/// `true` si la condition existe, `false` sinon.
///
/// # Note
///
/// Retourne `false` si le lock est empoisonné (poisoned).
fn has_event_condition(&self, name: &String) -> bool {
let guard = self.get_definition().event_conditions.read().unwrap();
return guard.contains_key(name);
let guard = self.get_definition().event_conditions.blocking_read();
guard.contains_key(name)
}
/// Indique si la variable a une description non vide.
///
/// # Returns
///
/// `true` si une description existe et n'est pas vide, `false` sinon.
fn has_description(&self) -> bool {
return !String::is_empty(&self.get_definition().description);
!self.get_definition().description.is_empty()
}
/// Retourne la description de la variable.
///
/// # Returns
///
/// La description sous forme de `String`. Peut être vide.
///
/// # See also
///
/// - [`has_description`](Self::has_description) pour tester si non vide
fn get_description(&self) -> String {
return self.get_definition().description.clone();
self.get_definition().description.clone()
}
/// Indique si la variable a une valeur par défaut définie explicitement.
///
/// # Returns
///
/// `true` si une valeur par défaut est explicitement définie, `false` sinon.
///
/// # Note
///
/// Même si cette méthode retourne `false`, [`get_default`](Self::get_default)
/// retournera toujours une valeur (la valeur par défaut du type).
fn has_default(&self) -> bool {
return self.get_definition().default_value.is_some();
self.get_definition().default_value.is_some()
}
/// Retourne la valeur par défaut de la variable.
///
/// # Returns
///
/// La valeur par défaut. Si aucune valeur par défaut n'est explicitement
/// définie, retourne la valeur par défaut du type de la variable
/// (ex: 0 pour les entiers, chaîne vide pour String, etc.).
///
/// # Examples
///
/// ```ignore
/// let default = var.get_default();
/// println!("Default value: {:?}", default);
/// ```
fn get_default(&self) -> StateValue {
self.get_definition()
.default_value
@@ -50,23 +203,99 @@ pub trait UpnpVariable {
.unwrap_or_else(|| self.get_definition().as_state_var_type().default_value())
}
/// Indique si la variable a une liste de valeurs autorisées.
///
/// Lorsqu'une liste de valeurs autorisées est définie, seules ces valeurs
/// sont acceptables pour la variable.
///
/// # Returns
///
/// `true` si une liste non vide de valeurs autorisées existe, `false` sinon.
///
/// # Note
///
/// Retourne `false` si le lock est empoisonné (poisoned).
fn has_allowed_values(&self) -> bool {
return self.get_definition().allowed_values.read().unwrap().len() > 0;
let guard = self.get_definition()
.allowed_values
.blocking_read();
!guard.is_empty()
}
fn is_an_allowed_values(&self, value: &StateValue) -> bool {
let guard = self.get_definition().allowed_values.read().unwrap();
return guard.contains(value);
/// Vérifie si une valeur fait partie des valeurs autorisées.
///
/// # Arguments
///
/// * `value` - La valeur à vérifier
///
/// # Returns
///
/// `true` si la valeur est dans la liste des valeurs autorisées, `false` sinon.
/// Retourne également `false` si aucune liste de valeurs autorisées n'est définie
/// ou si le lock est empoisonné.
///
/// # Examples
///
/// ```ignore
/// let value = StateValue::String("ON".to_string());
/// if var.is_an_allowed_value(&value) {
/// println!("Value is allowed");
/// }
/// ```
///
/// # Note
///
/// Si aucune liste de valeurs autorisées n'est définie, cette méthode
/// retourne `false`. Utilisez [`has_allowed_values`](Self::has_allowed_values)
/// pour distinguer "pas de liste" de "valeur non autorisée".
fn is_an_allowed_value(&self, value: &StateValue) -> bool {
let guard = self.get_definition()
.allowed_values
.blocking_read();
guard.contains(value)
}
/// Indique si la variable envoie des notifications d'événement.
///
/// Les notifications d'événement sont envoyées aux abonnés lorsque
/// la valeur de la variable change.
///
/// # Returns
///
/// `true` si les notifications sont activées, `false` sinon.
///
/// # See also
///
/// - [`has_event_conditions`](Self::has_event_conditions) pour vérifier
/// les conditions d'envoi d'événements
fn is_sending_notification(&self) -> bool {
self.get_definition().send_events
}
/// Indique si la variable a un parser de valeur personnalisé.
///
/// Un parser personnalisé est utilisé pour convertir des chaînes de
/// caractères en valeurs typées. Disponible uniquement pour les variables
/// de type String.
///
/// # Returns
///
/// `true` si un parser est défini, `false` sinon.
fn has_value_parser(&self) -> bool {
self.get_definition().parse.is_some()
}
/// Indique si la variable a un marshaler de valeur personnalisé.
///
/// Un marshaler personnalisé est utilisé pour sérialiser des valeurs
/// en chaînes de caractères. Disponible uniquement pour les variables
/// de type String.
///
/// # Returns
///
/// `true` si un marshaler est défini, `false` sinon.
fn has_value_marshaler(&self) -> bool {
self.get_definition().marshal.is_some()
}