Revue de code et refactoring
This commit is contained in:
81
Cargo.lock
generated
81
Cargo.lock
generated
@@ -469,6 +469,15 @@ dependencies = [
|
|||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "deranged"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a41953f86f8a05768a6cda24def994fd2f424b04ec5c719cf89989779f199071"
|
||||||
|
dependencies = [
|
||||||
|
"powerfmt",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "derive_arbitrary"
|
name = "derive_arbitrary"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
@@ -1323,6 +1332,12 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-conv"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.19"
|
version = "0.2.19"
|
||||||
@@ -1397,6 +1412,18 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "os_info"
|
||||||
|
version = "3.12.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d0e1ac5fde8d43c34139135df8ea9ee9465394b2d8d20f032d38998f64afffc3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"plist",
|
||||||
|
"serde",
|
||||||
|
"windows-sys 0.52.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.4"
|
version = "0.12.4"
|
||||||
@@ -1444,10 +1471,24 @@ version = "0.3.32"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "plist"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "740ebea15c5d1428f910cd1a5f52cebf8d25006245ed8ade92702f4943d91e07"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"indexmap",
|
||||||
|
"quick-xml",
|
||||||
|
"serde",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pmoapp"
|
name = "pmoapp"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"pmoserver",
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1489,7 +1530,6 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"pmoconfig",
|
"pmoconfig",
|
||||||
"pmoupnp",
|
|
||||||
"rust-embed",
|
"rust-embed",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -1515,6 +1555,7 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pmoconfig",
|
"pmoconfig",
|
||||||
"pmodidl",
|
"pmodidl",
|
||||||
|
"pmoserver",
|
||||||
"pmoutils",
|
"pmoutils",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1532,6 +1573,7 @@ name = "pmoutils"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"get_if_addrs",
|
"get_if_addrs",
|
||||||
|
"os_info",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1558,6 +1600,12 @@ dependencies = [
|
|||||||
"zerovec",
|
"zerovec",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "powerfmt"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.101"
|
version = "1.0.101"
|
||||||
@@ -2106,6 +2154,37 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d"
|
||||||
|
dependencies = [
|
||||||
|
"deranged",
|
||||||
|
"itoa",
|
||||||
|
"num-conv",
|
||||||
|
"powerfmt",
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3"
|
||||||
|
dependencies = [
|
||||||
|
"num-conv",
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tinystr"
|
name = "tinystr"
|
||||||
version = "0.8.1"
|
version = "0.8.1"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ edition = "2024"
|
|||||||
pmoconfig = { path = "../pmoconfig" }
|
pmoconfig = { path = "../pmoconfig" }
|
||||||
pmoupnp = { path = "../pmoupnp"}
|
pmoupnp = { path = "../pmoupnp"}
|
||||||
pmoserver = { path = "../pmoserver" }
|
pmoserver = { path = "../pmoserver" }
|
||||||
pmoapp = { path = "../pmoapp" }
|
pmoapp = { path = "../pmoapp", features = ["pmoserver"] }
|
||||||
|
|
||||||
|
|
||||||
tokio = { version = "1.35", features = ["rt-multi-thread", "macros", "sync", "time","signal"] }
|
tokio = { version = "1.35", features = ["rt-multi-thread", "macros", "sync", "time","signal"] }
|
||||||
|
|||||||
@@ -1,34 +1,24 @@
|
|||||||
use pmoupnp::{
|
use pmoupnp::{
|
||||||
mediarenderer::MEDIA_RENDERER,
|
mediarenderer::MEDIA_RENDERER,
|
||||||
ssdp::{SsdpDevice, SsdpServer},
|
ssdp::SsdpServer,
|
||||||
|
UpnpServer,
|
||||||
UpnpModel,
|
UpnpModel,
|
||||||
};
|
};
|
||||||
use pmoserver::{
|
use pmoserver::{
|
||||||
logs::{log_dump, log_sse, LogState, SseLayer},
|
logs::LoggingOptions,
|
||||||
ServerBuilder
|
ServerBuilder
|
||||||
};
|
};
|
||||||
use pmoapp::Webapp;
|
use pmoapp::{Webapp, WebAppExt};
|
||||||
use tracing_subscriber::Registry;
|
|
||||||
use tracing_subscriber::prelude::*;
|
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Initialiser le logging d'abord
|
|
||||||
let log_state = LogState::new(1000);
|
|
||||||
let subscriber = Registry::default()
|
|
||||||
.with(
|
|
||||||
tracing_subscriber::fmt::layer()
|
|
||||||
.with_target(true)
|
|
||||||
.with_level(true)
|
|
||||||
.with_ansi(true),
|
|
||||||
)
|
|
||||||
.with(SseLayer::new(log_state.clone()));
|
|
||||||
tracing::subscriber::set_global_default(subscriber).unwrap();
|
|
||||||
|
|
||||||
// Créer le serveur
|
// Créer le serveur
|
||||||
let mut server = ServerBuilder::new_configured().build();
|
let mut server = ServerBuilder::new_configured().build();
|
||||||
|
|
||||||
|
// Initialiser le logging et enregistrer les routes de logs
|
||||||
|
server.init_logging(LoggingOptions::default()).await;
|
||||||
|
|
||||||
// Routes de base
|
// Routes de base
|
||||||
server
|
server
|
||||||
.add_route("/info", || async {
|
.add_route("/info", || async {
|
||||||
@@ -36,33 +26,12 @@ async fn main() {
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
server.add_spa::<Webapp>("/app").await;
|
// Ajouter la webapp via le trait WebAppExt
|
||||||
|
info!("📡 Registering Web application...");
|
||||||
|
server.add_webapp_with_redirect::<Webapp>("/app").await;
|
||||||
|
|
||||||
// Routes de logging
|
info!("📡 Registering MediaRenderer...");
|
||||||
server
|
let renderer_instance = server.register_device(MEDIA_RENDERER.clone())
|
||||||
.add_handler_with_state("/log-sse", log_sse, log_state.clone())
|
|
||||||
.await;
|
|
||||||
server
|
|
||||||
.add_handler_with_state("/log-dump", log_dump, log_state.clone())
|
|
||||||
.await;
|
|
||||||
|
|
||||||
server.add_redirect("/", "/app").await;
|
|
||||||
|
|
||||||
// Créer et enregistrer le MediaRenderer
|
|
||||||
info!("🎵 Creating MediaRenderer instance...");
|
|
||||||
let renderer_instance = MEDIA_RENDERER.create_instance();
|
|
||||||
|
|
||||||
// Créer et ajouter les instances de services
|
|
||||||
for service in MEDIA_RENDERER.services().iter() {
|
|
||||||
let service_instance = service.create_instance();
|
|
||||||
renderer_instance
|
|
||||||
.add_service(service_instance)
|
|
||||||
.expect("Failed to add service to renderer");
|
|
||||||
}
|
|
||||||
|
|
||||||
info!("📡 Registering MediaRenderer routes...");
|
|
||||||
renderer_instance
|
|
||||||
.register_urls(&mut server)
|
|
||||||
.await
|
.await
|
||||||
.expect("Failed to register MediaRenderer routes");
|
.expect("Failed to register MediaRenderer routes");
|
||||||
|
|
||||||
@@ -76,25 +45,9 @@ async fn main() {
|
|||||||
let mut ssdp_server = SsdpServer::new();
|
let mut ssdp_server = SsdpServer::new();
|
||||||
ssdp_server.start().expect("Failed to start SSDP server");
|
ssdp_server.start().expect("Failed to start SSDP server");
|
||||||
|
|
||||||
// Créer le device SSDP pour le MediaRenderer
|
// Créer et enregistrer le device SSDP pour le MediaRenderer
|
||||||
let location = format!("{}{}",
|
let ssdp_device = renderer_instance
|
||||||
renderer_instance.base_url(),
|
.to_ssdp_device("PMOMusic", "1.0");
|
||||||
renderer_instance.description_route()
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut ssdp_device = SsdpDevice::new(
|
|
||||||
renderer_instance.udn().to_string(),
|
|
||||||
MEDIA_RENDERER.device_type(),
|
|
||||||
location,
|
|
||||||
format!("Linux/5.0 UPnP/1.1 PMOMusic/1.0"),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ajouter les types de notification pour chaque service
|
|
||||||
for service in renderer_instance.services() {
|
|
||||||
ssdp_device.add_notification_type(service.service_type());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enregistrer le device et envoyer les annonces SSDP
|
|
||||||
ssdp_server.add_device(ssdp_device);
|
ssdp_server.add_device(ssdp_device);
|
||||||
info!("✅ SSDP announcements sent for MediaRenderer");
|
info!("✅ SSDP announcements sent for MediaRenderer");
|
||||||
|
|
||||||
|
|||||||
@@ -5,3 +5,11 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rust-embed = "8.5.0"
|
rust-embed = "8.5.0"
|
||||||
|
|
||||||
|
[dependencies.pmoserver]
|
||||||
|
path = "../pmoserver"
|
||||||
|
optional = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
pmoserver = ["dep:pmoserver"]
|
||||||
|
|||||||
@@ -239,6 +239,8 @@
|
|||||||
//! - [Vite Documentation](https://vitejs.dev/)
|
//! - [Vite Documentation](https://vitejs.dev/)
|
||||||
|
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
/// Structure représentant l'application web embarquée.
|
/// Structure représentant l'application web embarquée.
|
||||||
///
|
///
|
||||||
@@ -248,16 +250,72 @@ use rust_embed::RustEmbed;
|
|||||||
/// ## Exemple
|
/// ## Exemple
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// use pmoapp::Webapp;
|
/// use pmoapp::{Webapp, WebAppExt};
|
||||||
/// use pmoserver::ServerBuilder;
|
/// use pmoserver::ServerBuilder;
|
||||||
///
|
///
|
||||||
/// # async fn example() {
|
/// # async fn example() {
|
||||||
/// let mut server = ServerBuilder::new("MyApp").build();
|
/// let mut server = ServerBuilder::new("MyApp").build();
|
||||||
///
|
///
|
||||||
/// // Ajouter la webapp comme SPA sur le chemin /app
|
/// // Ajouter la webapp via le trait WebAppExt
|
||||||
/// server.add_spa::<Webapp>("/app").await;
|
/// server.add_webapp::<Webapp>("/app").await;
|
||||||
/// # }
|
/// # }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(RustEmbed, Clone)]
|
#[derive(RustEmbed, Clone)]
|
||||||
#[folder = "webapp/dist"]
|
#[folder = "webapp/dist"]
|
||||||
pub struct Webapp;
|
pub struct Webapp;
|
||||||
|
|
||||||
|
/// Trait pour étendre un serveur HTTP avec des fonctionnalités webapp.
|
||||||
|
///
|
||||||
|
/// Ce trait permet à `pmoapp` d'ajouter des méthodes d'extension sur des types
|
||||||
|
/// de serveurs externes (comme `pmoserver::Server`) sans que ces crates dépendent de `pmoapp`.
|
||||||
|
///
|
||||||
|
/// # Architecture
|
||||||
|
///
|
||||||
|
/// Similaire au pattern utilisé par `pmoupnp` pour `UpnpServer`, ce trait permet
|
||||||
|
/// une extension propre et découplée :
|
||||||
|
///
|
||||||
|
/// - `pmoserver` définit un serveur HTTP générique
|
||||||
|
/// - `pmoapp` étend ce serveur avec des méthodes webapp via ce trait
|
||||||
|
/// - Le serveur n'a pas besoin de connaître `pmoapp`
|
||||||
|
///
|
||||||
|
/// # Exemple d'implémentation
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// impl WebAppExt for pmoserver::Server {
|
||||||
|
/// fn add_webapp<W: RustEmbed>(&mut self, path: &str) -> ... {
|
||||||
|
/// // Délègue à la méthode interne add_spa
|
||||||
|
/// self.add_spa::<W>(path)
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub trait WebAppExt {
|
||||||
|
/// Ajoute une Single Page Application au serveur.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `path` - Le chemin où monter la webapp (ex: "/app")
|
||||||
|
///
|
||||||
|
/// # Type Parameter
|
||||||
|
///
|
||||||
|
/// * `W` - Type RustEmbed contenant les fichiers de la webapp
|
||||||
|
fn add_webapp<W>(&mut self, path: &str) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
||||||
|
where
|
||||||
|
W: RustEmbed + Clone + Send + Sync + 'static;
|
||||||
|
|
||||||
|
/// Ajoute une webapp avec une redirection automatique depuis la racine.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `path` - Le chemin où monter la webapp (ex: "/app")
|
||||||
|
///
|
||||||
|
/// # Type Parameter
|
||||||
|
///
|
||||||
|
/// * `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 + '_>>
|
||||||
|
where
|
||||||
|
W: RustEmbed + Clone + Send + Sync + 'static;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implémentation du trait pour pmoserver::Server (feature-gated)
|
||||||
|
#[cfg(feature = "pmoserver")]
|
||||||
|
mod pmoserver_impl;
|
||||||
|
|||||||
57
pmoapp/src/pmoserver_impl.rs
Normal file
57
pmoapp/src/pmoserver_impl.rs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
//! Implémentation du trait WebAppExt pour le serveur pmoserver
|
||||||
|
//!
|
||||||
|
//! Ce module enrichit `pmoserver::Server` avec les fonctionnalités webapp en
|
||||||
|
//! implémentant le trait [`WebAppExt`](crate::WebAppExt). Cette implémentation
|
||||||
|
//! permet d'enregistrer facilement des webapps embarquées sur le serveur.
|
||||||
|
//!
|
||||||
|
//! ## Architecture
|
||||||
|
//!
|
||||||
|
//! `pmoapp` étend `pmoserver::Server` sans que `pmoserver` connaisse `pmoapp`.
|
||||||
|
//! C'est le pattern d'extension : `pmoapp` ajoute des fonctionnalités à un type
|
||||||
|
//! externe via un trait, similaire au pattern utilisé par `pmoupnp` pour `UpnpServer`.
|
||||||
|
//!
|
||||||
|
//! ## Exemple d'utilisation
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use pmoapp::{Webapp, WebAppExt};
|
||||||
|
//! use pmoserver::ServerBuilder;
|
||||||
|
//!
|
||||||
|
//! # async fn example() {
|
||||||
|
//! let mut server = ServerBuilder::new("MyApp").build();
|
||||||
|
//!
|
||||||
|
//! // Le trait WebAppExt est automatiquement disponible
|
||||||
|
//! server.add_webapp::<Webapp>("/app").await;
|
||||||
|
//!
|
||||||
|
//! // Ou avec redirection
|
||||||
|
//! server.add_webapp_with_redirect::<Webapp>("/app").await;
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
|
||||||
|
use crate::WebAppExt;
|
||||||
|
use pmoserver::Server;
|
||||||
|
use rust_embed::RustEmbed;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
impl WebAppExt for Server {
|
||||||
|
fn add_webapp<W>(&mut self, path: &str) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
||||||
|
where
|
||||||
|
W: RustEmbed + Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let path = path.to_string();
|
||||||
|
Box::pin(async move {
|
||||||
|
self.add_spa::<W>(&path).await;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_webapp_with_redirect<W>(&mut self, path: &str) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
||||||
|
where
|
||||||
|
W: RustEmbed + Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
let path = path.to_string();
|
||||||
|
Box::pin(async move {
|
||||||
|
self.add_spa::<W>(&path).await;
|
||||||
|
self.add_redirect("/", &path).await;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,7 +21,3 @@ axum-embed = "0.1.0"
|
|||||||
rust-embed = "8.7.2"
|
rust-embed = "8.7.2"
|
||||||
utoipa = { version = "5.4.0", features = ["axum_extras"] }
|
utoipa = { version = "5.4.0", features = ["axum_extras"] }
|
||||||
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
|
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
|
||||||
|
|
||||||
[dependencies.pmoupnp]
|
|
||||||
path = "../pmoupnp"
|
|
||||||
default-features = false
|
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
//!
|
//!
|
||||||
//! - [`server`] : Implémentation du serveur principal et du builder
|
//! - [`server`] : Implémentation du serveur principal et du builder
|
||||||
//! - [`logs`] : Système de logs SSE pour monitoring en temps réel
|
//! - [`logs`] : Système de logs SSE pour monitoring en temps réel
|
||||||
//! - `upnp_impl` : Implémentation du trait `pmoupnp::UpnpServer` (privé)
|
|
||||||
//!
|
//!
|
||||||
//! ## Exemple d'utilisation
|
//! ## Exemple d'utilisation
|
||||||
//!
|
//!
|
||||||
@@ -53,27 +52,25 @@
|
|||||||
//!
|
//!
|
||||||
//! ## Intégration UPnP
|
//! ## Intégration UPnP
|
||||||
//!
|
//!
|
||||||
//! Le serveur implémente automatiquement le trait `pmoupnp::UpnpServer`, permettant
|
//! Le serveur peut être étendu avec UPnP via le trait `pmoupnp::UpnpServer`.
|
||||||
//! de connecter des devices UPnP :
|
//! L'implémentation est fournie par `pmoupnp` (feature `pmoserver`), permettant
|
||||||
|
//! de connecter des devices UPnP sans que `pmoserver` dépende de `pmoupnp` :
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use pmoupnp::{UpnpServer, mediarenderer::device::MEDIA_RENDERER};
|
//! use pmoupnp::{UpnpServer, mediarenderer::MEDIA_RENDERER};
|
||||||
//! use pmoupnp::devices::DeviceInstance;
|
|
||||||
//! use pmoserver::ServerBuilder;
|
//! use pmoserver::ServerBuilder;
|
||||||
//! use std::sync::Arc;
|
|
||||||
//!
|
//!
|
||||||
//! # async fn example() {
|
//! # async fn example() {
|
||||||
//! let mut server = ServerBuilder::new("MediaRenderer").build();
|
//! let mut server = ServerBuilder::new("MediaRenderer").build();
|
||||||
//! let device = Arc::new(DeviceInstance::new(&MEDIA_RENDERER));
|
//! let device = MEDIA_RENDERER.create_instance();
|
||||||
//!
|
//!
|
||||||
//! // Le device enregistre automatiquement ses routes
|
//! // Le trait UpnpServer est automatiquement disponible (implémenté dans pmoupnp)
|
||||||
//! device.register_urls(&mut server).await;
|
//! device.register_urls(&mut server).await;
|
||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod logs;
|
pub mod logs;
|
||||||
mod upnp_impl;
|
|
||||||
|
|
||||||
pub use server::{Server, ServerBuilder, ServerInfo};
|
pub use server::{Server, ServerBuilder, ServerInfo};
|
||||||
pub use logs::{LogState, SseLayer, log_sse, log_dump};
|
pub use logs::{LogState, SseLayer, log_sse, log_dump, init_logging, LoggingOptions};
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ use axum::{
|
|||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tokio::sync::broadcast;
|
use tokio::sync::broadcast;
|
||||||
|
use tracing_subscriber::{Registry, layer::SubscriberExt};
|
||||||
|
|
||||||
/// Représente une entrée de log
|
/// Représente une entrée de log
|
||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
@@ -157,3 +158,60 @@ fn filter_entry(entry: &LogEntry, q: &LogQuery) -> bool {
|
|||||||
|
|
||||||
allowed
|
allowed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Options d'initialisation du système de logging
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct LoggingOptions {
|
||||||
|
/// Capacité du buffer circulaire (nombre d'entrées conservées)
|
||||||
|
pub buffer_capacity: usize,
|
||||||
|
/// Activer la sortie vers stderr/stdout
|
||||||
|
pub enable_console: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LoggingOptions {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
buffer_capacity: 1000,
|
||||||
|
enable_console: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initialise le système de logging avec SSE et optionnellement la console
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
/// * `options` - Options de configuration du logging
|
||||||
|
///
|
||||||
|
/// # Retourne
|
||||||
|
/// Le `LogState` qui peut être utilisé pour ajouter les routes de logging au serveur
|
||||||
|
///
|
||||||
|
/// # Exemple
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// use pmoserver::logs::{init_logging, LoggingOptions};
|
||||||
|
///
|
||||||
|
/// let log_state = init_logging(LoggingOptions {
|
||||||
|
/// buffer_capacity: 1000,
|
||||||
|
/// enable_console: true,
|
||||||
|
/// });
|
||||||
|
/// ```
|
||||||
|
pub fn init_logging(options: LoggingOptions) -> LogState {
|
||||||
|
let log_state = LogState::new(options.buffer_capacity);
|
||||||
|
|
||||||
|
let subscriber = Registry::default().with(SseLayer::new(log_state.clone()));
|
||||||
|
|
||||||
|
if options.enable_console {
|
||||||
|
let subscriber = subscriber.with(
|
||||||
|
tracing_subscriber::fmt::layer()
|
||||||
|
.with_target(true)
|
||||||
|
.with_level(true)
|
||||||
|
.with_ansi(true),
|
||||||
|
);
|
||||||
|
tracing::subscriber::set_global_default(subscriber)
|
||||||
|
.expect("Failed to set global default subscriber");
|
||||||
|
} else {
|
||||||
|
tracing::subscriber::set_global_default(subscriber)
|
||||||
|
.expect("Failed to set global default subscriber");
|
||||||
|
}
|
||||||
|
|
||||||
|
log_state
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ use std::{net::SocketAddr, sync::Arc};
|
|||||||
use tokio::{signal, sync::RwLock, task::JoinHandle};
|
use tokio::{signal, sync::RwLock, task::JoinHandle};
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
use utoipa_swagger_ui::SwaggerUi;
|
use utoipa_swagger_ui::SwaggerUi;
|
||||||
|
use crate::logs::{LogState, LoggingOptions, init_logging, log_sse, log_dump};
|
||||||
|
|
||||||
/// Info serveur sérialisable
|
/// Info serveur sérialisable
|
||||||
#[derive(Clone, Serialize, utoipa::ToSchema)]
|
#[derive(Clone, Serialize, utoipa::ToSchema)]
|
||||||
@@ -45,6 +46,7 @@ pub struct Server {
|
|||||||
router: Arc<RwLock<Router>>,
|
router: Arc<RwLock<Router>>,
|
||||||
api_router: Arc<RwLock<Option<Router>>>,
|
api_router: Arc<RwLock<Option<Router>>>,
|
||||||
join_handle: Option<JoinHandle<()>>,
|
join_handle: Option<JoinHandle<()>>,
|
||||||
|
log_state: Option<LogState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
@@ -70,6 +72,7 @@ impl Server {
|
|||||||
router: Arc::new(RwLock::new(Router::new())),
|
router: Arc::new(RwLock::new(Router::new())),
|
||||||
api_router: Arc::new(RwLock::new(None)),
|
api_router: Arc::new(RwLock::new(None)),
|
||||||
join_handle: None,
|
join_handle: None,
|
||||||
|
log_state: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -508,6 +511,45 @@ impl Server {
|
|||||||
http_port: self.http_port,
|
http_port: self.http_port,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Initialise le système de logging et enregistre les routes de logs
|
||||||
|
///
|
||||||
|
/// Cette méthode configure le système de tracing avec SSE et optionnellement la console,
|
||||||
|
/// puis enregistre automatiquement les routes `/log-sse` et `/log-dump`.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `options` - Options de configuration du logging
|
||||||
|
///
|
||||||
|
/// # Exemple
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// # use pmoserver::{ServerBuilder, logs::LoggingOptions};
|
||||||
|
/// # #[tokio::main]
|
||||||
|
/// # async fn main() {
|
||||||
|
/// let mut server = ServerBuilder::new_configured().build();
|
||||||
|
///
|
||||||
|
/// // Initialiser les logs avec console
|
||||||
|
/// server.init_logging(LoggingOptions::default()).await;
|
||||||
|
///
|
||||||
|
/// // Ou sans console
|
||||||
|
/// server.init_logging(LoggingOptions {
|
||||||
|
/// buffer_capacity: 1000,
|
||||||
|
/// enable_console: false,
|
||||||
|
/// }).await;
|
||||||
|
///
|
||||||
|
/// server.start().await;
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub async fn init_logging(&mut self, options: LoggingOptions) {
|
||||||
|
let log_state = init_logging(options);
|
||||||
|
|
||||||
|
// Enregistrer automatiquement les routes de logging
|
||||||
|
self.add_handler_with_state("/log-sse", log_sse, log_state.clone()).await;
|
||||||
|
self.add_handler_with_state("/log-dump", log_dump, log_state.clone()).await;
|
||||||
|
|
||||||
|
self.log_state = Some(log_state);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builder pattern
|
/// Builder pattern
|
||||||
|
|||||||
@@ -1,94 +0,0 @@
|
|||||||
//! Implémentation du trait UpnpServer pour le serveur pmoserver
|
|
||||||
//!
|
|
||||||
//! Ce module fournit l'implémentation du trait [`pmoupnp::UpnpServer`] pour
|
|
||||||
//! le [`Server`](crate::server::Server) de pmoserver, permettant aux devices
|
|
||||||
//! et services UPnP d'enregistrer automatiquement leurs endpoints HTTP.
|
|
||||||
//!
|
|
||||||
//! ## Architecture
|
|
||||||
//!
|
|
||||||
//! L'implémentation fait le pont entre :
|
|
||||||
//! - Les pointeurs de fonction du trait `UpnpServer` (agnostiques du framework web)
|
|
||||||
//! - Les handlers Axum (spécifiques à l'implémentation `pmoserver`)
|
|
||||||
//!
|
|
||||||
//! Chaque méthode du trait crée un wrapper qui :
|
|
||||||
//! 1. Convertit les pointeurs de fonction en closures compatibles Axum
|
|
||||||
//! 2. Délègue l'enregistrement aux méthodes internes du `Server`
|
|
||||||
//! 3. Retourne une future qui se résout une fois le handler enregistré
|
|
||||||
//!
|
|
||||||
//! ## Exemple d'utilisation
|
|
||||||
//!
|
|
||||||
//! ```rust,no_run
|
|
||||||
//! use pmoupnp::{UpnpServer, mediarenderer::device::MEDIA_RENDERER};
|
|
||||||
//! use pmoupnp::devices::DeviceInstance;
|
|
||||||
//! use pmoserver::ServerBuilder;
|
|
||||||
//! use std::sync::Arc;
|
|
||||||
//!
|
|
||||||
//! # async fn example() {
|
|
||||||
//! let mut server = ServerBuilder::new("MyRenderer").build();
|
|
||||||
//! let device = Arc::new(DeviceInstance::new(&MEDIA_RENDERER));
|
|
||||||
//!
|
|
||||||
//! // Le trait UpnpServer est automatiquement disponible
|
|
||||||
//! device.register_urls(&mut server).await;
|
|
||||||
//! # }
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
use crate::server::Server;
|
|
||||||
use pmoupnp::{UpnpServer, server::{Response, HeaderMap, Request}};
|
|
||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
|
||||||
use axum::extract::State;
|
|
||||||
|
|
||||||
impl UpnpServer for Server {
|
|
||||||
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,
|
|
||||||
{
|
|
||||||
let path = path.to_string();
|
|
||||||
Box::pin(async move {
|
|
||||||
Self::add_handler(self, &path, handler).await;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_post_handler_with_state<S>(
|
|
||||||
&mut self,
|
|
||||||
path: &str,
|
|
||||||
handler: fn(State<S>, String) -> Pin<Box<dyn Future<Output = Response> + Send>>,
|
|
||||||
state: S,
|
|
||||||
) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>
|
|
||||||
where
|
|
||||||
S: Clone + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
let path = path.to_string();
|
|
||||||
|
|
||||||
// Créer un wrapper qui convertit le fn pointer en handler Axum
|
|
||||||
let wrapper = move |State(s): State<S>, body: String| -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
|
||||||
handler(State(s), body)
|
|
||||||
};
|
|
||||||
|
|
||||||
Box::pin(async move {
|
|
||||||
Self::add_post_handler_with_state(self, &path, wrapper, state).await;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_handler_with_state<S>(
|
|
||||||
&mut self,
|
|
||||||
path: &str,
|
|
||||||
handler: fn(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,
|
|
||||||
{
|
|
||||||
let path = path.to_string();
|
|
||||||
|
|
||||||
// Créer un wrapper qui convertit le fn pointer en handler Axum
|
|
||||||
let wrapper = move |State(s): State<S>, headers: HeaderMap, req: Request| -> Pin<Box<dyn Future<Output = Response> + Send>> {
|
|
||||||
handler(State(s), headers, req)
|
|
||||||
};
|
|
||||||
|
|
||||||
Box::pin(async move {
|
|
||||||
Self::add_handler_with_state(self, &path, wrapper, state).await;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,6 +7,7 @@ edition = "2024"
|
|||||||
pmoconfig = { path = "../pmoconfig" }
|
pmoconfig = { path = "../pmoconfig" }
|
||||||
pmodidl = { path = "../pmodidl"}
|
pmodidl = { path = "../pmodidl"}
|
||||||
pmoutils = { path = "../pmoutils" }
|
pmoutils = { path = "../pmoutils" }
|
||||||
|
pmoserver = { path = "../pmoserver" }
|
||||||
|
|
||||||
url = "2.5.7"
|
url = "2.5.7"
|
||||||
uuid = "1.18.1"
|
uuid = "1.18.1"
|
||||||
|
|||||||
@@ -251,7 +251,7 @@ impl DeviceInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Enregistre toutes les URLs du device et de ses services dans le serveur.
|
/// 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 {
|
Box::pin(async move {
|
||||||
info!(
|
info!(
|
||||||
"✅ Device description for {} available at: {}{}",
|
"✅ Device description for {} available at: {}{}",
|
||||||
@@ -330,4 +330,45 @@ impl DeviceInstance {
|
|||||||
xml,
|
xml,
|
||||||
).into_response()
|
).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.
|
//! Implémentation des traits UPnP pour Device.
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
use xmltree::{Element, XMLNode};
|
use xmltree::{Element, XMLNode};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
devices::{Device, DeviceInstance},
|
devices::{Device, DeviceInstance},
|
||||||
UpnpObject, UpnpModel,
|
UpnpObject, UpnpModel, UpnpInstance,
|
||||||
};
|
};
|
||||||
|
|
||||||
impl UpnpObject for Device {
|
impl UpnpObject for Device {
|
||||||
@@ -115,4 +116,19 @@ impl UpnpObject for Device {
|
|||||||
|
|
||||||
impl UpnpModel for Device {
|
impl UpnpModel for Device {
|
||||||
type Instance = DeviceInstance;
|
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_trait;
|
||||||
mod object_set;
|
mod object_set;
|
||||||
|
mod server;
|
||||||
|
|
||||||
pub mod actions;
|
pub mod actions;
|
||||||
pub mod devices;
|
pub mod devices;
|
||||||
pub mod mediarenderer;
|
pub mod mediarenderer;
|
||||||
pub mod server;
|
|
||||||
pub mod services;
|
pub mod services;
|
||||||
pub mod soap;
|
pub mod soap;
|
||||||
pub mod ssdp;
|
pub mod ssdp;
|
||||||
@@ -12,8 +12,7 @@ pub mod state_variables;
|
|||||||
pub mod value_ranges;
|
pub mod value_ranges;
|
||||||
pub mod variable_types;
|
pub mod variable_types;
|
||||||
|
|
||||||
// Re-exports
|
|
||||||
pub use server::UpnpServer;
|
|
||||||
|
|
||||||
|
|
||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
@@ -21,6 +20,7 @@ use std::{collections::HashMap, sync::Arc};
|
|||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
pub use crate::object_trait::*;
|
pub use crate::object_trait::*;
|
||||||
|
pub use crate::server::UpnpServer;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct UpnpObjectType {
|
pub struct UpnpObjectType {
|
||||||
|
|||||||
@@ -1,262 +1,22 @@
|
|||||||
//! Trait pour les serveurs UPnP
|
use std::sync::Arc;
|
||||||
//!
|
|
||||||
//! 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::future::Future;
|
use pmoserver::Server;
|
||||||
use std::pin::Pin;
|
|
||||||
|
|
||||||
/// Type alias pour la réponse HTTP (basé sur Axum).
|
use crate::devices::errors::DeviceError;
|
||||||
///
|
use crate::devices::{Device, DeviceInstance};
|
||||||
/// Utilisé pour éviter une dépendance directe sur axum dans les signatures de trait,
|
use crate::UpnpModel;
|
||||||
/// tout en restant compatible avec les types Axum.
|
|
||||||
pub type Response = axum::response::Response;
|
|
||||||
|
|
||||||
/// Type alias pour les en-têtes HTTP (basé sur Axum).
|
pub trait UpnpServer {
|
||||||
pub type HeaderMap = axum::http::HeaderMap;
|
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
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// Retourne une erreur si l'enregistrement des routes échoue.
|
/// 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 = 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.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();
|
let server_url = device.as_ref().map(|d| d.base_url().to_string()).unwrap_or_default();
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
get_if_addrs = "0.5.3"
|
get_if_addrs = "0.5.3"
|
||||||
|
os_info = "3.8"
|
||||||
@@ -17,4 +17,36 @@
|
|||||||
/// ```
|
/// ```
|
||||||
mod ip_utils;
|
mod ip_utils;
|
||||||
|
|
||||||
pub use ip_utils::guess_local_ip;
|
pub use ip_utils::guess_local_ip;
|
||||||
|
|
||||||
|
/// Retourne une chaîne décrivant le système d'exploitation et sa version.
|
||||||
|
///
|
||||||
|
/// Utilise la crate `os_info` pour obtenir de manière portable et fiable
|
||||||
|
/// les informations sur le système d'exploitation courant.
|
||||||
|
///
|
||||||
|
/// # Format
|
||||||
|
/// - macOS: "macOS/15.1" ou "Mac OS/10.15.7"
|
||||||
|
/// - Linux: "Linux/6.5.0" ou "Ubuntu/22.04"
|
||||||
|
/// - Windows: "Windows/10.0.19045"
|
||||||
|
/// - Autre: "{OS}/Unknown"
|
||||||
|
///
|
||||||
|
/// # Exemples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use pmoutils::get_os_string;
|
||||||
|
///
|
||||||
|
/// let os = get_os_string();
|
||||||
|
/// println!("OS: {}", os); // Ex: "Linux/6.5.0"
|
||||||
|
/// ```
|
||||||
|
pub fn get_os_string() -> String {
|
||||||
|
let info = os_info::get();
|
||||||
|
let os_type = format!("{:?}", info.os_type());
|
||||||
|
|
||||||
|
// Obtenir la version si disponible
|
||||||
|
let version = info.version();
|
||||||
|
if version != &os_info::Version::Unknown {
|
||||||
|
format!("{}/{}", os_type, version)
|
||||||
|
} else {
|
||||||
|
format!("{}/Unknown", os_type)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user