Revue de code et refactoring
This commit is contained in:
@@ -251,7 +251,7 @@ impl DeviceInstance {
|
||||
}
|
||||
|
||||
/// Enregistre toutes les URLs du device et de ses services dans le serveur.
|
||||
pub fn register_urls<'a, S: crate::UpnpServer + ?Sized>(&'a self, server: &'a mut S) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), DeviceError>> + 'a>> {
|
||||
pub fn register_urls<'a>(&'a self, server: &'a mut pmoserver::Server) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<(), DeviceError>> + 'a>> {
|
||||
Box::pin(async move {
|
||||
info!(
|
||||
"✅ Device description for {} available at: {}{}",
|
||||
@@ -330,4 +330,45 @@ impl DeviceInstance {
|
||||
xml,
|
||||
).into_response()
|
||||
}
|
||||
|
||||
/// Crée un SsdpDevice configuré pour ce device UPnP.
|
||||
///
|
||||
/// Cette méthode simplifie la création d'un device SSDP en configurant automatiquement :
|
||||
/// - L'UDN du device
|
||||
/// - Le type de device
|
||||
/// - La location (URL de description)
|
||||
/// - Le serveur (User-Agent avec OS/version détecté automatiquement)
|
||||
/// - Les types de notification pour tous les services
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `app_name` - Nom de l'application (ex: "PMOMusic")
|
||||
/// * `app_version` - Version de l'application (ex: "1.0")
|
||||
///
|
||||
/// # Exemple
|
||||
///
|
||||
/// ```ignore
|
||||
/// let renderer_instance = MEDIA_RENDERER.create_instance();
|
||||
/// let ssdp_device = renderer_instance.to_ssdp_device("PMOMusic", "1.0");
|
||||
/// ssdp_server.add_device(ssdp_device);
|
||||
/// ```
|
||||
pub fn to_ssdp_device(&self, app_name: &str, app_version: &str) -> crate::ssdp::SsdpDevice {
|
||||
let location = format!("{}{}", self.base_url(), self.description_route());
|
||||
let os_string = pmoutils::get_os_string();
|
||||
let server_string = format!("{} UPnP/1.1 {}/{}", os_string, app_name, app_version);
|
||||
|
||||
let mut ssdp_device = crate::ssdp::SsdpDevice::new(
|
||||
self.udn().to_string(),
|
||||
self.model.device_type(),
|
||||
location,
|
||||
server_string,
|
||||
);
|
||||
|
||||
// Ajouter les types de notification pour chaque service
|
||||
for service in self.services() {
|
||||
ssdp_device.add_notification_type(service.service_type());
|
||||
}
|
||||
|
||||
ssdp_device
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
//! Implémentation des traits UPnP pour Device.
|
||||
|
||||
use std::sync::Arc;
|
||||
use xmltree::{Element, XMLNode};
|
||||
|
||||
use crate::{
|
||||
devices::{Device, DeviceInstance},
|
||||
UpnpObject, UpnpModel,
|
||||
UpnpObject, UpnpModel, UpnpInstance,
|
||||
};
|
||||
|
||||
impl UpnpObject for Device {
|
||||
@@ -115,4 +116,19 @@ impl UpnpObject for Device {
|
||||
|
||||
impl UpnpModel for Device {
|
||||
type Instance = DeviceInstance;
|
||||
|
||||
/// Crée une instance du device avec ses services déjà instanciés.
|
||||
///
|
||||
/// Les services sont créés dans DeviceInstance::new(), cette méthode
|
||||
/// établit uniquement les liens bidirectionnels parent-enfant.
|
||||
fn create_instance(&self) -> Arc<DeviceInstance> {
|
||||
let instance = Arc::new(DeviceInstance::new(self));
|
||||
|
||||
// Établir le lien parent pour chaque service
|
||||
for service in instance.services() {
|
||||
service.set_device(Arc::clone(&instance));
|
||||
}
|
||||
|
||||
instance
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
mod object_trait;
|
||||
mod object_set;
|
||||
mod server;
|
||||
|
||||
pub mod actions;
|
||||
pub mod devices;
|
||||
pub mod mediarenderer;
|
||||
pub mod server;
|
||||
pub mod services;
|
||||
pub mod soap;
|
||||
pub mod ssdp;
|
||||
@@ -12,8 +12,7 @@ pub mod state_variables;
|
||||
pub mod value_ranges;
|
||||
pub mod variable_types;
|
||||
|
||||
// Re-exports
|
||||
pub use server::UpnpServer;
|
||||
|
||||
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
@@ -21,6 +20,7 @@ use std::{collections::HashMap, sync::Arc};
|
||||
use std::sync::RwLock;
|
||||
|
||||
pub use crate::object_trait::*;
|
||||
pub use crate::server::UpnpServer;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UpnpObjectType {
|
||||
|
||||
@@ -1,262 +1,22 @@
|
||||
//! Trait pour les serveurs UPnP
|
||||
//!
|
||||
//! Ce module définit le trait [`UpnpServer`] qui permet de connecter
|
||||
//! des devices UPnP à n'importe quelle implémentation de serveur web.
|
||||
//!
|
||||
//! ## Architecture
|
||||
//!
|
||||
//! Le trait `UpnpServer` définit une interface minimale permettant aux devices
|
||||
//! et services UPnP d'enregistrer leurs endpoints HTTP sans dépendre d'une
|
||||
//! implémentation de serveur spécifique.
|
||||
//!
|
||||
//! ## Séparation des responsabilités
|
||||
//!
|
||||
//! - **pmoupnp** : Définit le trait `UpnpServer` et l'utilise via des contraintes génériques
|
||||
//! - **pmoserver** : Fournit une implémentation concrète basée sur Axum
|
||||
//! - **Autres crates** : Peuvent fournir leurs propres implémentations (actix-web, warp, etc.)
|
||||
//!
|
||||
//! ## Exemple d'utilisation
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use pmoupnp::{UpnpServer, devices::{Device, DeviceInstance}};
|
||||
//! use std::sync::Arc;
|
||||
//!
|
||||
//! # async fn example<S: UpnpServer>(mut server: S) {
|
||||
//! // Créer un device
|
||||
//! let device = Device::new(
|
||||
//! "MyDevice".to_string(),
|
||||
//! "MyDeviceType".to_string(),
|
||||
//! "Friendly Name".to_string(),
|
||||
//! );
|
||||
//! let device_instance = Arc::new(DeviceInstance::new(&device));
|
||||
//!
|
||||
//! // Le device enregistre automatiquement ses routes UPnP
|
||||
//! device_instance.register_urls(&mut server).await;
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Implémentation
|
||||
//!
|
||||
//! Pour implémenter ce trait, votre serveur doit fournir trois méthodes
|
||||
//! pour enregistrer des handlers HTTP asynchrones :
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use pmoupnp::UpnpServer;
|
||||
//! use std::future::Future;
|
||||
//! use std::pin::Pin;
|
||||
//!
|
||||
//! struct MyServer {
|
||||
//! // votre implémentation
|
||||
//! }
|
||||
//!
|
||||
//! impl UpnpServer for MyServer {
|
||||
//! fn add_handler<F, Fut>(&mut self, path: &str, handler: F)
|
||||
//! -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
||||
//! where
|
||||
//! F: Fn() -> Fut + Send + Sync + 'static + Clone,
|
||||
//! Fut: Future<Output = pmoupnp::server::Response> + Send + 'static,
|
||||
//! {
|
||||
//! // Enregistrer le handler pour GET requests
|
||||
//! # todo!()
|
||||
//! }
|
||||
//!
|
||||
//! fn add_post_handler_with_state<S>(
|
||||
//! &mut self,
|
||||
//! path: &str,
|
||||
//! handler: fn(axum::extract::State<S>, String)
|
||||
//! -> Pin<Box<dyn Future<Output = pmoupnp::server::Response> + Send>>,
|
||||
//! state: S,
|
||||
//! ) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
||||
//! where
|
||||
//! S: Clone + Send + Sync + 'static,
|
||||
//! {
|
||||
//! // Enregistrer le handler pour POST avec body
|
||||
//! # todo!()
|
||||
//! }
|
||||
//!
|
||||
//! fn add_handler_with_state<S>(
|
||||
//! &mut self,
|
||||
//! path: &str,
|
||||
//! handler: fn(axum::extract::State<S>,
|
||||
//! pmoupnp::server::HeaderMap,
|
||||
//! pmoupnp::server::Request)
|
||||
//! -> Pin<Box<dyn Future<Output = pmoupnp::server::Response> + Send>>,
|
||||
//! state: S,
|
||||
//! ) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
||||
//! where
|
||||
//! S: Clone + Send + Sync + 'static,
|
||||
//! {
|
||||
//! // Enregistrer le handler avec accès complet à la requête
|
||||
//! # todo!()
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
use std::sync::Arc;
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use pmoserver::Server;
|
||||
|
||||
/// Type alias pour la réponse HTTP (basé sur Axum).
|
||||
///
|
||||
/// Utilisé pour éviter une dépendance directe sur axum dans les signatures de trait,
|
||||
/// tout en restant compatible avec les types Axum.
|
||||
pub type Response = axum::response::Response;
|
||||
use crate::devices::errors::DeviceError;
|
||||
use crate::devices::{Device, DeviceInstance};
|
||||
use crate::UpnpModel;
|
||||
|
||||
/// Type alias pour les en-têtes HTTP (basé sur Axum).
|
||||
pub type HeaderMap = axum::http::HeaderMap;
|
||||
pub trait UpnpServer {
|
||||
async fn register_device(&mut self, device: Arc<Device>) -> Result<Arc<DeviceInstance>,DeviceError> ;
|
||||
|
||||
/// Type alias pour la requête HTTP (basé sur Axum).
|
||||
pub type Request = axum::extract::Request<axum::body::Body>;
|
||||
|
||||
/// Trait pour les serveurs compatibles UPnP.
|
||||
///
|
||||
/// Ce trait définit l'interface minimale qu'un serveur web doit implémenter
|
||||
/// pour supporter l'enregistrement automatique des endpoints UPnP par les
|
||||
/// [`DeviceInstance`](crate::devices::DeviceInstance) et
|
||||
/// [`ServiceInstance`](crate::services::ServiceInstance).
|
||||
///
|
||||
/// ## Contraintes
|
||||
///
|
||||
/// - `Send + Sync` : Le serveur doit être partageable entre threads
|
||||
///
|
||||
/// ## Méthodes
|
||||
///
|
||||
/// Les trois méthodes permettent d'enregistrer différents types de handlers :
|
||||
///
|
||||
/// 1. **`add_handler`** : Handler GET simple sans état
|
||||
/// 2. **`add_post_handler_with_state`** : Handler POST avec état et body texte (pour SOAP)
|
||||
/// 3. **`add_handler_with_state`** : Handler générique avec accès complet (pour SUBSCRIBE/UNSUBSCRIBE)
|
||||
///
|
||||
/// ## Implémentations
|
||||
///
|
||||
/// - **pmoserver::Server** : Implémentation basée sur Axum (fournie par la crate `pmoserver`)
|
||||
pub trait UpnpServer: Send + Sync {
|
||||
/// Ajoute un handler GET pour un chemin donné.
|
||||
///
|
||||
/// Utilisé principalement pour servir les descripteurs XML des devices et services.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - Le chemin HTTP (ex: `/device/MediaRenderer/description.xml`)
|
||||
/// * `handler` - Une closure asynchrone qui génère la réponse
|
||||
///
|
||||
/// # Retour
|
||||
///
|
||||
/// Une future qui se résout quand le handler est enregistré.
|
||||
///
|
||||
/// # Exemple
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pmoupnp::UpnpServer;
|
||||
/// use axum::response::IntoResponse;
|
||||
///
|
||||
/// # async fn example<S: UpnpServer>(mut server: S) {
|
||||
/// server.add_handler("/description.xml", || async {
|
||||
/// "<?xml version=\"1.0\"?><root></root>".into_response()
|
||||
/// }).await;
|
||||
/// # }
|
||||
/// ```
|
||||
fn add_handler<F, Fut>(&mut self, path: &str, handler: F) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
||||
where
|
||||
F: Fn() -> Fut + Send + Sync + 'static + Clone,
|
||||
Fut: Future<Output = Response> + Send + 'static;
|
||||
|
||||
/// Ajoute un handler POST avec état pour un chemin donné.
|
||||
///
|
||||
/// Utilisé pour les endpoints de contrôle SOAP des services UPnP.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - Le chemin HTTP (ex: `/service/AVTransport/control`)
|
||||
/// * `handler` - Un pointeur de fonction qui traite la requête SOAP
|
||||
/// * `state` - L'état partagé (typiquement une `ServiceInstance`)
|
||||
///
|
||||
/// # Retour
|
||||
///
|
||||
/// Une future qui se résout quand le handler est enregistré.
|
||||
///
|
||||
/// # Exemple
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pmoupnp::{UpnpServer, server::Response};
|
||||
/// use axum::extract::State;
|
||||
/// use std::pin::Pin;
|
||||
/// use std::future::Future;
|
||||
///
|
||||
/// fn soap_handler(
|
||||
/// State(service): State<String>,
|
||||
/// body: String,
|
||||
/// ) -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
||||
/// Box::pin(async move {
|
||||
/// // Traiter la requête SOAP
|
||||
/// axum::response::Response::default()
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// # async fn example<S: UpnpServer>(mut server: S) {
|
||||
/// server.add_post_handler_with_state(
|
||||
/// "/control",
|
||||
/// soap_handler,
|
||||
/// "ServiceName".to_string(),
|
||||
/// ).await;
|
||||
/// # }
|
||||
/// ```
|
||||
fn add_post_handler_with_state<S>(
|
||||
&mut self,
|
||||
path: &str,
|
||||
handler: fn(axum::extract::State<S>, String) -> Pin<Box<dyn Future<Output = Response> + Send>>,
|
||||
state: S,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static;
|
||||
|
||||
/// Ajoute un handler avec état et accès complet à la requête.
|
||||
///
|
||||
/// Utilisé pour les endpoints d'événements (SUBSCRIBE/UNSUBSCRIBE) qui nécessitent
|
||||
/// un accès aux en-têtes HTTP et à la méthode HTTP.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `path` - Le chemin HTTP (ex: `/service/AVTransport/event`)
|
||||
/// * `handler` - Un pointeur de fonction avec accès complet à la requête
|
||||
/// * `state` - L'état partagé (typiquement une `ServiceInstance`)
|
||||
///
|
||||
/// # Retour
|
||||
///
|
||||
/// Une future qui se résout quand le handler est enregistré.
|
||||
///
|
||||
/// # Exemple
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pmoupnp::{UpnpServer, server::{Response, HeaderMap, Request}};
|
||||
/// use axum::extract::State;
|
||||
/// use std::pin::Pin;
|
||||
/// use std::future::Future;
|
||||
///
|
||||
/// fn event_handler(
|
||||
/// State(service): State<String>,
|
||||
/// headers: HeaderMap,
|
||||
/// req: Request,
|
||||
/// ) -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
||||
/// Box::pin(async move {
|
||||
/// // Traiter SUBSCRIBE/UNSUBSCRIBE
|
||||
/// axum::response::Response::default()
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// # async fn example<S: UpnpServer>(mut server: S) {
|
||||
/// server.add_handler_with_state(
|
||||
/// "/event",
|
||||
/// event_handler,
|
||||
/// "ServiceName".to_string(),
|
||||
/// ).await;
|
||||
/// # }
|
||||
/// ```
|
||||
fn add_handler_with_state<S>(
|
||||
&mut self,
|
||||
path: &str,
|
||||
handler: fn(axum::extract::State<S>, HeaderMap, Request) -> Pin<Box<dyn Future<Output = Response> + Send>>,
|
||||
state: S,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
||||
where
|
||||
S: Clone + Send + Sync + 'static;
|
||||
}
|
||||
|
||||
impl UpnpServer for Server {
|
||||
async fn register_device(&mut self, device: Arc<Device>) -> Result<Arc<DeviceInstance>,DeviceError> {
|
||||
let di = device.create_instance();
|
||||
|
||||
di.register_urls(self).await?;
|
||||
|
||||
Ok(di)
|
||||
}
|
||||
}
|
||||
@@ -304,7 +304,7 @@ impl ServiceInstance {
|
||||
/// # Errors
|
||||
///
|
||||
/// Retourne une erreur si l'enregistrement des routes échoue.
|
||||
pub async fn register_urls<S: crate::UpnpServer + ?Sized>(&self, server: &mut S) -> Result<(), ServiceError> {
|
||||
pub async fn register_urls(&self, server: &mut pmoserver::Server) -> Result<(), ServiceError> {
|
||||
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 server_url = device.as_ref().map(|d| d.base_url().to_string()).unwrap_or_default();
|
||||
|
||||
Reference in New Issue
Block a user