Gerer les souscription aux variables
This commit is contained in:
@@ -298,7 +298,7 @@ pub trait WebAppExt {
|
|||||||
/// # Type Parameter
|
/// # Type Parameter
|
||||||
///
|
///
|
||||||
/// * `W` - Type RustEmbed contenant les fichiers de la webapp
|
/// * `W` - Type RustEmbed contenant les fichiers de la webapp
|
||||||
fn add_webapp<W>(&mut self, path: &str) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
async fn add_webapp<W>(&mut self, path: &str)
|
||||||
where
|
where
|
||||||
W: RustEmbed + Clone + Send + Sync + 'static;
|
W: RustEmbed + Clone + Send + Sync + 'static;
|
||||||
|
|
||||||
@@ -311,7 +311,7 @@ pub trait WebAppExt {
|
|||||||
/// # Type Parameter
|
/// # Type Parameter
|
||||||
///
|
///
|
||||||
/// * `W` - Type RustEmbed contenant les fichiers de la webapp
|
/// * `W` - Type RustEmbed contenant les fichiers de la webapp
|
||||||
fn add_webapp_with_redirect<W>(&mut self, path: &str) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
async fn add_webapp_with_redirect<W>(&mut self, path: &str)
|
||||||
where
|
where
|
||||||
W: RustEmbed + Clone + Send + Sync + 'static;
|
W: RustEmbed + Clone + Send + Sync + 'static;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,28 +30,24 @@
|
|||||||
use crate::WebAppExt;
|
use crate::WebAppExt;
|
||||||
use pmoserver::Server;
|
use pmoserver::Server;
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
impl WebAppExt for Server {
|
impl WebAppExt for Server {
|
||||||
fn add_webapp<W>(&mut self, path: &str) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
async fn add_webapp<W>(&mut self, path: &str)
|
||||||
where
|
where
|
||||||
W: RustEmbed + Clone + Send + Sync + 'static,
|
W: RustEmbed + Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let path = path.to_string();
|
let path = path.to_string();
|
||||||
Box::pin(async move {
|
|
||||||
self.add_spa::<W>(&path).await;
|
self.add_spa::<W>(&path).await;
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_webapp_with_redirect<W>(&mut self, path: &str) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
async fn add_webapp_with_redirect<W>(&mut self, path: &str)
|
||||||
where
|
where
|
||||||
W: RustEmbed + Clone + Send + Sync + 'static,
|
W: RustEmbed + Clone + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
let path = path.to_string();
|
let path = path.to_string();
|
||||||
Box::pin(async move {
|
|
||||||
self.add_spa::<W>(&path).await;
|
self.add_spa::<W>(&path).await;
|
||||||
self.add_redirect("/", &path).await;
|
self.add_redirect("/", &path).await;
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,28 @@
|
|||||||
//! Implémentation de ServiceInstance.
|
//! Implémentation de ServiceInstance.
|
||||||
|
|
||||||
use std::{
|
|
||||||
collections::HashMap,
|
|
||||||
sync::{Arc, Mutex, RwLock},
|
|
||||||
time::Duration,
|
|
||||||
pin::Pin,
|
|
||||||
future::Future,
|
|
||||||
};
|
|
||||||
use axum::{
|
use axum::{
|
||||||
|
body::Body,
|
||||||
extract::{Request, State},
|
extract::{Request, State},
|
||||||
http::{HeaderMap, StatusCode},
|
http::{HeaderMap, StatusCode},
|
||||||
response::{IntoResponse, Response},
|
response::{IntoResponse, Response},
|
||||||
body::Body,
|
};
|
||||||
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
|
future::Future,
|
||||||
|
pin::Pin,
|
||||||
|
sync::{Arc, Mutex, RwLock},
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
use tokio::time;
|
use tokio::time;
|
||||||
use tracing::{info, warn, error};
|
use tracing::{error, info, warn};
|
||||||
use xmltree::{Element, XMLNode, EmitterConfig};
|
use xmltree::{Element, EmitterConfig, XMLNode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
services::{Service, ServiceError},
|
UpnpInstance, UpnpObject, UpnpObjectType, UpnpTyped, UpnpTypedInstance,
|
||||||
actions::{ActionInstance, ActionInstanceSet},
|
actions::{ActionInstance, ActionInstanceSet},
|
||||||
state_variables::{StateVarInstance, StateVarInstanceSet, UpnpVariable},
|
|
||||||
devices::DeviceInstance,
|
devices::DeviceInstance,
|
||||||
UpnpObject, UpnpInstance, UpnpTyped, UpnpTypedInstance, UpnpObjectType,
|
services::{Service, ServiceError},
|
||||||
|
state_variables::{StateVarInstance, StateVarInstanceSet, UpnpVariable},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Méthodes HTTP pour les événements UPnP.
|
/// Méthodes HTTP pour les événements UPnP.
|
||||||
@@ -70,33 +70,32 @@ pub const METHOD_UNSUBSCRIBE: &str = "UNSUBSCRIBE";
|
|||||||
pub struct ServiceInstance {
|
pub struct ServiceInstance {
|
||||||
/// Métadonnées de l'objet
|
/// Métadonnées de l'objet
|
||||||
object: UpnpObjectType,
|
object: UpnpObjectType,
|
||||||
|
|
||||||
/// Référence vers le modèle
|
/// Référence vers le modèle
|
||||||
model: Arc<Service>,
|
model: Arc<Service>,
|
||||||
|
|
||||||
/// Identifiant du service
|
/// Identifiant du service
|
||||||
identifier: String,
|
identifier: String,
|
||||||
|
|
||||||
/// Device parent (optionnel) - utilisé via interior mutability
|
/// Device parent (optionnel) - utilisé via interior mutability
|
||||||
device: Arc<RwLock<Option<Arc<DeviceInstance>>>>,
|
device: Arc<RwLock<Option<Arc<DeviceInstance>>>>,
|
||||||
|
|
||||||
/// Variables d'état instanciées
|
/// Variables d'état instanciées
|
||||||
statevariables: StateVarInstanceSet,
|
statevariables: StateVarInstanceSet,
|
||||||
|
|
||||||
/// Actions instanciées
|
/// Actions instanciées
|
||||||
actions: ActionInstanceSet,
|
actions: ActionInstanceSet,
|
||||||
|
|
||||||
/// Abonnés aux événements (SID -> Callback URL)
|
/// Abonnés aux événements (SID -> Callback URL)
|
||||||
subscribers: Arc<RwLock<HashMap<String, String>>>,
|
subscribers: Arc<RwLock<HashMap<String, String>>>,
|
||||||
|
|
||||||
/// Buffer des changements en attente de notification
|
/// Buffer des changements en attente de notification
|
||||||
changed_buffer: Arc<Mutex<HashMap<String, String>>>,
|
changed_buffer: Arc<Mutex<HashMap<String, String>>>,
|
||||||
|
|
||||||
/// Compteurs de séquence par abonné
|
/// Compteurs de séquence par abonné
|
||||||
seqid: Arc<Mutex<HashMap<String, u32>>>,
|
seqid: Arc<Mutex<HashMap<String, u32>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl std::fmt::Debug for ServiceInstance {
|
impl std::fmt::Debug for ServiceInstance {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.debug_struct("ServiceInstance")
|
f.debug_struct("ServiceInstance")
|
||||||
@@ -132,14 +131,14 @@ impl UpnpInstance for ServiceInstance {
|
|||||||
for a in model.actions() {
|
for a in model.actions() {
|
||||||
// Vérifier que toutes les variables référencées existent
|
// Vérifier que toutes les variables référencées existent
|
||||||
let mut missing_vars = Vec::new();
|
let mut missing_vars = Vec::new();
|
||||||
|
|
||||||
for arg in a.arguments().all() {
|
for arg in a.arguments().all() {
|
||||||
let related_var_name = arg.state_variable().get_name();
|
let related_var_name = arg.state_variable().get_name();
|
||||||
if statevariables.get_by_name(related_var_name).is_none() {
|
if statevariables.get_by_name(related_var_name).is_none() {
|
||||||
missing_vars.push(related_var_name.to_string());
|
missing_vars.push(related_var_name.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !missing_vars.is_empty() {
|
if !missing_vars.is_empty() {
|
||||||
error!(
|
error!(
|
||||||
"Action '{}' references missing state variables: {:?}",
|
"Action '{}' references missing state variables: {:?}",
|
||||||
@@ -148,10 +147,10 @@ impl UpnpInstance for ServiceInstance {
|
|||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Créer l'instance d'action
|
// Créer l'instance d'action
|
||||||
let action_instance = Arc::new(ActionInstance::new(&*a));
|
let action_instance = Arc::new(ActionInstance::new(&*a));
|
||||||
|
|
||||||
// ✅ Phase 3 : ACTIVER le binding des arguments aux variables d'instance
|
// ✅ Phase 3 : ACTIVER le binding des arguments aux variables d'instance
|
||||||
for arg_instance in action_instance.arguments_set().all() {
|
for arg_instance in action_instance.arguments_set().all() {
|
||||||
let var_name = arg_instance.get_model().state_variable().get_name();
|
let var_name = arg_instance.get_model().state_variable().get_name();
|
||||||
@@ -160,7 +159,7 @@ impl UpnpInstance for ServiceInstance {
|
|||||||
arg_instance.bind_variable(var_instance);
|
arg_instance.bind_variable(var_instance);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = actions.insert(action_instance) {
|
if let Err(e) = actions.insert(action_instance) {
|
||||||
error!("Failed to insert action '{}': {:?}", a.get_name(), e);
|
error!("Failed to insert action '{}': {:?}", a.get_name(), e);
|
||||||
}
|
}
|
||||||
@@ -194,7 +193,9 @@ impl UpnpObject for ServiceInstance {
|
|||||||
let mut elem = Element::new("service");
|
let mut elem = Element::new("service");
|
||||||
|
|
||||||
let mut service_type = Element::new("serviceType");
|
let mut service_type = Element::new("serviceType");
|
||||||
service_type.children.push(XMLNode::Text(self.service_type()));
|
service_type
|
||||||
|
.children
|
||||||
|
.push(XMLNode::Text(self.service_type()));
|
||||||
elem.children.push(XMLNode::Element(service_type));
|
elem.children.push(XMLNode::Element(service_type));
|
||||||
|
|
||||||
let mut service_id = Element::new("serviceId");
|
let mut service_id = Element::new("serviceId");
|
||||||
@@ -206,11 +207,15 @@ impl UpnpObject for ServiceInstance {
|
|||||||
elem.children.push(XMLNode::Element(scpd_url));
|
elem.children.push(XMLNode::Element(scpd_url));
|
||||||
|
|
||||||
let mut control_url = Element::new("controlURL");
|
let mut control_url = Element::new("controlURL");
|
||||||
control_url.children.push(XMLNode::Text(self.control_route()));
|
control_url
|
||||||
|
.children
|
||||||
|
.push(XMLNode::Text(self.control_route()));
|
||||||
elem.children.push(XMLNode::Element(control_url));
|
elem.children.push(XMLNode::Element(control_url));
|
||||||
|
|
||||||
let mut event_sub_url = Element::new("eventSubURL");
|
let mut event_sub_url = Element::new("eventSubURL");
|
||||||
event_sub_url.children.push(XMLNode::Text(self.event_route()));
|
event_sub_url
|
||||||
|
.children
|
||||||
|
.push(XMLNode::Text(self.event_route()));
|
||||||
elem.children.push(XMLNode::Element(event_sub_url));
|
elem.children.push(XMLNode::Element(event_sub_url));
|
||||||
|
|
||||||
elem
|
elem
|
||||||
@@ -241,7 +246,7 @@ impl ServiceInstance {
|
|||||||
pub fn get_variable(&self, name: &str) -> Option<Arc<StateVarInstance>> {
|
pub fn get_variable(&self, name: &str) -> Option<Arc<StateVarInstance>> {
|
||||||
self.statevariables.get_by_name(name)
|
self.statevariables.get_by_name(name)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Raccourci pour obtenir une action par nom
|
/// Raccourci pour obtenir une action par nom
|
||||||
pub fn get_action(&self, name: &str) -> Option<Arc<ActionInstance>> {
|
pub fn get_action(&self, name: &str) -> Option<Arc<ActionInstance>> {
|
||||||
self.actions.get_by_name(name)
|
self.actions.get_by_name(name)
|
||||||
@@ -306,8 +311,14 @@ impl ServiceInstance {
|
|||||||
/// Retourne une erreur si l'enregistrement des routes échoue.
|
/// Retourne une erreur si l'enregistrement des routes échoue.
|
||||||
pub async fn register_urls(&self, server: &mut pmoserver::Server) -> Result<(), ServiceError> {
|
pub async fn register_urls(&self, server: &mut pmoserver::Server) -> Result<(), ServiceError> {
|
||||||
let device = self.device.read().unwrap();
|
let device = self.device.read().unwrap();
|
||||||
let device_name = device.as_ref().map(|d| d.get_name().clone()).unwrap_or_else(|| "unknown".to_string());
|
let device_name = device
|
||||||
let server_url = device.as_ref().map(|d| d.base_url().to_string()).unwrap_or_default();
|
.as_ref()
|
||||||
|
.map(|d| d.get_name().clone())
|
||||||
|
.unwrap_or_else(|| "unknown".to_string());
|
||||||
|
let server_url = device
|
||||||
|
.as_ref()
|
||||||
|
.map(|d| d.base_url().to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
drop(device);
|
drop(device);
|
||||||
|
|
||||||
info!(
|
info!(
|
||||||
@@ -320,26 +331,24 @@ impl ServiceInstance {
|
|||||||
|
|
||||||
// Handler SCPD
|
// Handler SCPD
|
||||||
let instance_scpd = self.clone();
|
let instance_scpd = self.clone();
|
||||||
server.add_handler(&self.scpd_route(), move || {
|
server
|
||||||
let instance = instance_scpd.clone();
|
.add_handler(&self.scpd_route(), move || {
|
||||||
async move { instance.scpd_handler().await }
|
let instance = instance_scpd.clone();
|
||||||
}).await;
|
async move { instance.scpd_handler().await }
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
// Handler control
|
// Handler control
|
||||||
let instance_control = self.clone();
|
let instance_control = self.clone();
|
||||||
server.add_post_handler_with_state(
|
server
|
||||||
&self.control_route(),
|
.add_post_handler_with_state(&self.control_route(), control_handler, instance_control)
|
||||||
control_handler,
|
.await;
|
||||||
instance_control,
|
|
||||||
).await;
|
|
||||||
|
|
||||||
// Handler événements
|
// Handler événements
|
||||||
let instance_event = self.clone();
|
let instance_event = self.clone();
|
||||||
server.add_handler_with_state(
|
server
|
||||||
&self.event_route(),
|
.add_handler_with_state(&self.event_route(), event_sub_handler, instance_event)
|
||||||
event_sub_handler,
|
.await;
|
||||||
instance_event,
|
|
||||||
).await;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -357,25 +366,23 @@ impl ServiceInstance {
|
|||||||
let mut major = Element::new("major");
|
let mut major = Element::new("major");
|
||||||
major.children.push(XMLNode::Text("1".to_string()));
|
major.children.push(XMLNode::Text("1".to_string()));
|
||||||
spec.children.push(XMLNode::Element(major));
|
spec.children.push(XMLNode::Element(major));
|
||||||
|
|
||||||
let mut minor = Element::new("minor");
|
let mut minor = Element::new("minor");
|
||||||
minor.children.push(XMLNode::Text("0".to_string()));
|
minor.children.push(XMLNode::Text("0".to_string()));
|
||||||
spec.children.push(XMLNode::Element(minor));
|
spec.children.push(XMLNode::Element(minor));
|
||||||
|
|
||||||
elem.children.push(XMLNode::Element(spec));
|
elem.children.push(XMLNode::Element(spec));
|
||||||
|
|
||||||
// actionList
|
// actionList
|
||||||
if !self.actions.all().is_empty() {
|
if !self.actions.all().is_empty() {
|
||||||
elem.children.push(XMLNode::Element(
|
elem.children
|
||||||
self.actions.to_xml_element()
|
.push(XMLNode::Element(self.actions.to_xml_element()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// serviceStateTable
|
// serviceStateTable
|
||||||
if !self.statevariables.all().is_empty() {
|
if !self.statevariables.all().is_empty() {
|
||||||
elem.children.push(XMLNode::Element(
|
elem.children
|
||||||
self.statevariables.to_xml_element()
|
.push(XMLNode::Element(self.statevariables.to_xml_element()));
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
elem
|
elem
|
||||||
@@ -384,11 +391,11 @@ impl ServiceInstance {
|
|||||||
/// Handler pour la description SCPD.
|
/// Handler pour la description SCPD.
|
||||||
async fn scpd_handler(&self) -> Response {
|
async fn scpd_handler(&self) -> Response {
|
||||||
let elem = self.scpd_element();
|
let elem = self.scpd_element();
|
||||||
|
|
||||||
let config = EmitterConfig::new()
|
let config = EmitterConfig::new()
|
||||||
.perform_indent(true)
|
.perform_indent(true)
|
||||||
.indent_string(" ");
|
.indent_string(" ");
|
||||||
|
|
||||||
let mut xml_output = Vec::new();
|
let mut xml_output = Vec::new();
|
||||||
if let Err(e) = elem.write_with_config(&mut xml_output, config) {
|
if let Err(e) = elem.write_with_config(&mut xml_output, config) {
|
||||||
error!("Failed to serialize SCPD XML: {}", e);
|
error!("Failed to serialize SCPD XML: {}", e);
|
||||||
@@ -399,9 +406,13 @@ impl ServiceInstance {
|
|||||||
|
|
||||||
(
|
(
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[(axum::http::header::CONTENT_TYPE, "text/xml; charset=\"utf-8\"")],
|
[(
|
||||||
|
axum::http::header::CONTENT_TYPE,
|
||||||
|
"text/xml; charset=\"utf-8\"",
|
||||||
|
)],
|
||||||
xml,
|
xml,
|
||||||
).into_response()
|
)
|
||||||
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ajoute un abonné aux événements.
|
/// Ajoute un abonné aux événements.
|
||||||
@@ -442,10 +453,14 @@ impl ServiceInstance {
|
|||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let callback = callback.trim().trim_matches(|c| c == '<' || c == '>');
|
let callback = callback.trim().trim_matches(|c| c == '<' || c == '>');
|
||||||
|
|
||||||
let mut body = r#"<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">"#.to_string();
|
let mut body =
|
||||||
|
r#"<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">"#.to_string();
|
||||||
for (name, val) in changed {
|
for (name, val) in changed {
|
||||||
body.push_str(&format!("<e:property><{0}>{1}</{0}></e:property>", name, val));
|
body.push_str(&format!(
|
||||||
|
"<e:property><{0}>{1}</{0}></e:property>",
|
||||||
|
name, val
|
||||||
|
));
|
||||||
}
|
}
|
||||||
body.push_str("</e:propertyset>");
|
body.push_str("</e:propertyset>");
|
||||||
|
|
||||||
@@ -462,7 +477,11 @@ impl ServiceInstance {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(resp) => {
|
Ok(resp) => {
|
||||||
info!("✅ Initial event sent to {}, status={}", callback, resp.status());
|
info!(
|
||||||
|
"✅ Initial event sent to {}, status={}",
|
||||||
|
callback,
|
||||||
|
resp.status()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to send initial event to {}: {}", callback, e);
|
error!("Failed to send initial event to {}: {}", callback, e);
|
||||||
@@ -507,13 +526,17 @@ impl ServiceInstance {
|
|||||||
for (sid, callback) in subscribers_copy {
|
for (sid, callback) in subscribers_copy {
|
||||||
let changed_clone = changed.clone();
|
let changed_clone = changed.clone();
|
||||||
let seq = self.next_seq(&sid);
|
let seq = self.next_seq(&sid);
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let callback = callback.trim().trim_matches(|c| c == '<' || c == '>');
|
let callback = callback.trim().trim_matches(|c| c == '<' || c == '>');
|
||||||
|
|
||||||
let mut body = r#"<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">"#.to_string();
|
let mut body =
|
||||||
|
r#"<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">"#.to_string();
|
||||||
for (name, val) in changed_clone {
|
for (name, val) in changed_clone {
|
||||||
body.push_str(&format!("<e:property><{0}>{1}</{0}></e:property>", name, val));
|
body.push_str(&format!(
|
||||||
|
"<e:property><{0}>{1}</{0}></e:property>",
|
||||||
|
name, val
|
||||||
|
));
|
||||||
}
|
}
|
||||||
body.push_str("</e:propertyset>");
|
body.push_str("</e:propertyset>");
|
||||||
|
|
||||||
@@ -551,7 +574,7 @@ impl ServiceInstance {
|
|||||||
/// Un handle vers la tâche tokio du notifier.
|
/// Un handle vers la tâche tokio du notifier.
|
||||||
pub fn start_notifier(&self, interval: Duration) -> tokio::task::JoinHandle<()> {
|
pub fn start_notifier(&self, interval: Duration) -> tokio::task::JoinHandle<()> {
|
||||||
let instance = self.clone();
|
let instance = self.clone();
|
||||||
|
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
let mut ticker = time::interval(interval);
|
let mut ticker = time::interval(interval);
|
||||||
info!("✅ Starting notifier every {:?}", interval);
|
info!("✅ Starting notifier every {:?}", interval);
|
||||||
@@ -565,18 +588,26 @@ impl ServiceInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Handler Axum pour les événements (SUBSCRIBE/UNSUBSCRIBE).
|
/// Handler Axum pour les événements (SUBSCRIBE/UNSUBSCRIBE).
|
||||||
fn event_sub_handler(
|
async fn event_sub_handler(
|
||||||
State(instance): State<ServiceInstance>,
|
State(instance): State<ServiceInstance>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
req: Request<Body>,
|
req: Request<Body>,
|
||||||
) -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
) -> Response {
|
||||||
Box::pin(async move {
|
|
||||||
info!("📡 Event Subscription request for {}", instance.get_name());
|
info!("📡 Event Subscription request for {}", instance.get_name());
|
||||||
|
|
||||||
let method = req.method().as_str();
|
let method = req.method().as_str();
|
||||||
let sid = headers.get("SID").and_then(|v| v.to_str().ok()).unwrap_or("");
|
let sid = headers
|
||||||
let timeout = headers.get("Timeout").and_then(|v| v.to_str().ok()).unwrap_or("");
|
.get("SID")
|
||||||
let callback = headers.get("Callback").and_then(|v| v.to_str().ok()).unwrap_or("");
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.unwrap_or("");
|
||||||
|
let timeout = headers
|
||||||
|
.get("Timeout")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.unwrap_or("");
|
||||||
|
let callback = headers
|
||||||
|
.get("Callback")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.unwrap_or("");
|
||||||
|
|
||||||
match method {
|
match method {
|
||||||
METHOD_SUBSCRIBE => {
|
METHOD_SUBSCRIBE => {
|
||||||
@@ -584,21 +615,26 @@ fn event_sub_handler(
|
|||||||
// Nouvelle souscription
|
// Nouvelle souscription
|
||||||
let new_sid = format!("uuid:{}", uuid::Uuid::new_v4());
|
let new_sid = format!("uuid:{}", uuid::Uuid::new_v4());
|
||||||
if !callback.is_empty() {
|
if !callback.is_empty() {
|
||||||
instance.add_subscriber(new_sid.clone(), callback.to_string()).await;
|
instance
|
||||||
|
.add_subscriber(new_sid.clone(), callback.to_string())
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
let timeout_val = if timeout.is_empty() {
|
let timeout_val = if timeout.is_empty() {
|
||||||
"Second-1800"
|
"Second-1800"
|
||||||
} else {
|
} else {
|
||||||
timeout
|
timeout
|
||||||
};
|
};
|
||||||
info!("🔒 New subscription: SID={}, Callback={}, Timeout={}", new_sid, callback, timeout_val);
|
info!(
|
||||||
|
"🔒 New subscription: SID={}, Callback={}, Timeout={}",
|
||||||
|
new_sid, callback, timeout_val
|
||||||
|
);
|
||||||
|
|
||||||
let sid_clone = new_sid.clone();
|
let sid_clone = new_sid.clone();
|
||||||
let instance_clone = instance.clone();
|
let instance_clone = instance.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
instance_clone.send_initial_event(sid_clone).await;
|
instance_clone.send_initial_event(sid_clone).await;
|
||||||
});
|
});
|
||||||
|
|
||||||
(new_sid, timeout_val.to_string())
|
(new_sid, timeout_val.to_string())
|
||||||
} else {
|
} else {
|
||||||
// Renouvellement
|
// Renouvellement
|
||||||
@@ -611,15 +647,16 @@ fn event_sub_handler(
|
|||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
axum::http::header::HeaderName::from_static("sid"),
|
axum::http::header::HeaderName::from_static("sid"),
|
||||||
axum::http::HeaderValue::from_str(&response_sid).unwrap()
|
axum::http::HeaderValue::from_str(&response_sid).unwrap(),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
axum::http::header::HeaderName::from_static("timeout"),
|
axum::http::header::HeaderName::from_static("timeout"),
|
||||||
axum::http::HeaderValue::from_str(&response_timeout).unwrap()
|
axum::http::HeaderValue::from_str(&response_timeout).unwrap(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
).into_response()
|
)
|
||||||
|
.into_response()
|
||||||
}
|
}
|
||||||
METHOD_UNSUBSCRIBE => {
|
METHOD_UNSUBSCRIBE => {
|
||||||
if !sid.is_empty() {
|
if !sid.is_empty() {
|
||||||
@@ -633,19 +670,14 @@ fn event_sub_handler(
|
|||||||
StatusCode::METHOD_NOT_ALLOWED.into_response()
|
StatusCode::METHOD_NOT_ALLOWED.into_response()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Handler Axum pour le contrôle SOAP.
|
/// Handler Axum pour le contrôle SOAP.
|
||||||
fn control_handler(
|
async fn control_handler(State(instance): State<ServiceInstance>, _body: String) -> Response {
|
||||||
State(instance): State<ServiceInstance>,
|
|
||||||
_body: String,
|
|
||||||
) -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
|
||||||
Box::pin(async move {
|
|
||||||
info!("📡 Control request for {}", instance.get_name());
|
info!("📡 Control request for {}", instance.get_name());
|
||||||
|
|
||||||
// TODO: Parser le SOAP et appeler l'action correspondante
|
// TODO: Parser le SOAP et appeler l'action correspondante
|
||||||
|
|
||||||
let response_xml = format!(
|
let response_xml = format!(
|
||||||
r#"<?xml version="1.0"?>
|
r#"<?xml version="1.0"?>
|
||||||
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
|
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
|
||||||
@@ -660,10 +692,13 @@ fn control_handler(
|
|||||||
|
|
||||||
(
|
(
|
||||||
StatusCode::OK,
|
StatusCode::OK,
|
||||||
[(axum::http::header::CONTENT_TYPE, "text/xml; charset=\"utf-8\"")],
|
[(
|
||||||
|
axum::http::header::CONTENT_TYPE,
|
||||||
|
"text/xml; charset=\"utf-8\"",
|
||||||
|
)],
|
||||||
response_xml,
|
response_xml,
|
||||||
).into_response()
|
)
|
||||||
})
|
.into_response()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@@ -675,7 +710,7 @@ mod tests {
|
|||||||
fn test_service_instance_creation() {
|
fn test_service_instance_creation() {
|
||||||
let service = Service::new("AVTransport".to_string());
|
let service = Service::new("AVTransport".to_string());
|
||||||
let instance = ServiceInstance::new(&service);
|
let instance = ServiceInstance::new(&service);
|
||||||
|
|
||||||
assert_eq!(instance.get_name(), "AVTransport");
|
assert_eq!(instance.get_name(), "AVTransport");
|
||||||
assert_eq!(instance.identifier(), "AVTransport");
|
assert_eq!(instance.identifier(), "AVTransport");
|
||||||
}
|
}
|
||||||
@@ -684,7 +719,7 @@ mod tests {
|
|||||||
fn test_service_urls() {
|
fn test_service_urls() {
|
||||||
let service = Service::new("AVTransport".to_string());
|
let service = Service::new("AVTransport".to_string());
|
||||||
let instance = ServiceInstance::new(&service);
|
let instance = ServiceInstance::new(&service);
|
||||||
|
|
||||||
assert_eq!(instance.route(), "/service/AVTransport");
|
assert_eq!(instance.route(), "/service/AVTransport");
|
||||||
assert_eq!(instance.control_route(), "/service/AVTransport/control");
|
assert_eq!(instance.control_route(), "/service/AVTransport/control");
|
||||||
assert_eq!(instance.event_route(), "/service/AVTransport/event");
|
assert_eq!(instance.event_route(), "/service/AVTransport/event");
|
||||||
@@ -696,10 +731,10 @@ mod tests {
|
|||||||
let mut service = Service::new("AVTransport".to_string());
|
let mut service = Service::new("AVTransport".to_string());
|
||||||
service.set_version(2).unwrap();
|
service.set_version(2).unwrap();
|
||||||
let instance = ServiceInstance::new(&service);
|
let instance = ServiceInstance::new(&service);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
instance.service_type(),
|
instance.service_type(),
|
||||||
"urn:schemas-upnp-org:service:AVTransport:2"
|
"urn:schemas-upnp-org:service:AVTransport:2"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user