Correction de ChatGPT

This commit is contained in:
2025-10-07 15:03:52 +02:00
parent 24be870e4a
commit 3154c11845
5 changed files with 114 additions and 227 deletions

View File

@@ -37,7 +37,7 @@ use axum::{
Json, Router, Json, Router,
}; };
use pmoserver::Server; use pmoserver::Server;
use tracing::{debug, info}; use tracing::{debug, info, warn};
use std::sync::Arc; use std::sync::Arc;
use utoipa::OpenApi; use utoipa::OpenApi;
@@ -52,11 +52,13 @@ async fn get_cover_image(
let path = req.uri().path(); let path = req.uri().path();
let parts: Vec<&str> = path.split('/').collect(); let parts: Vec<&str> = path.split('/').collect();
if parts.len() < 4 { warn!("{:?}",parts);
if parts.len() != 2 {
return (StatusCode::BAD_REQUEST, "Invalid path").into_response(); return (StatusCode::BAD_REQUEST, "Invalid path").into_response();
} }
let pk = parts[3]; let pk = parts[1];
match cache.get(pk).await { match cache.get(pk).await {
Ok(file_path) => { Ok(file_path) => {
@@ -83,12 +85,12 @@ async fn get_cover_variant(
let path = req.uri().path(); let path = req.uri().path();
let parts: Vec<&str> = path.split('/').collect(); let parts: Vec<&str> = path.split('/').collect();
if parts.len() < 5 { if parts.len() != 3 {
return (StatusCode::BAD_REQUEST, "Invalid path").into_response(); return (StatusCode::BAD_REQUEST, "Invalid path").into_response();
} }
let pk = parts[3]; let pk = parts[1];
let size = match parts[4].parse::<usize>() { let size = match parts[2].parse::<usize>() {
Ok(s) => s, Ok(s) => s,
Err(_) => return (StatusCode::BAD_REQUEST, "Invalid size").into_response(), Err(_) => return (StatusCode::BAD_REQUEST, "Invalid size").into_response(),
}; };

View File

@@ -16,13 +16,15 @@
use crate::logs::{LogState, LoggingOptions, init_logging, log_dump, log_sse}; use crate::logs::{LogState, LoggingOptions, init_logging, log_dump, log_sse};
use axum::handler::Handler; use axum::handler::Handler;
use axum::response::Redirect; use axum::response::Redirect;
use axum::routing::get; use axum::routing::{get, post};
use axum::{Json, Router}; use axum::{Json, Router};
use axum_embed::ServeEmbed; use axum_embed::ServeEmbed;
use pmoconfig::get_config; use pmoconfig::get_config;
use rust_embed::RustEmbed; use rust_embed::RustEmbed;
use serde::Serialize; use serde::Serialize;
use std::{net::SocketAddr, sync::Arc}; use std::future::Future;
use std::net::SocketAddr;
use std::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;
@@ -30,11 +32,8 @@ use utoipa_swagger_ui::SwaggerUi;
/// Info serveur sérialisable /// Info serveur sérialisable
#[derive(Clone, Serialize, utoipa::ToSchema)] #[derive(Clone, Serialize, utoipa::ToSchema)]
pub struct ServerInfo { pub struct ServerInfo {
/// Nom du serveur
pub name: String, pub name: String,
/// URL de base
pub base_url: String, pub base_url: String,
/// Port HTTP
pub http_port: u16, pub http_port: u16,
} }
@@ -80,8 +79,7 @@ impl Server {
let config = get_config(); let config = get_config();
let url = config.get_base_url(); let url = config.get_base_url();
let port = config.get_http_port(); let port = config.get_http_port();
Self::new("PMO-Music-Server", url, port)
return Self::new("PMO-Music-Server", url, port);
} }
/// Ajoute une route JSON dynamique /// Ajoute une route JSON dynamique
@@ -112,11 +110,10 @@ impl Server {
pub async fn add_route<F, Fut, T>(&mut self, path: &str, f: F) pub async fn add_route<F, Fut, T>(&mut self, path: &str, f: F)
where where
F: Fn() -> Fut + Send + Sync + 'static, F: Fn() -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = T> + Send + 'static, Fut: Future<Output = T> + Send + 'static,
T: Serialize + Send + 'static, T: Serialize + Send + 'static,
{ {
let f = Arc::new(f); let f = Arc::new(f);
let handler = { let handler = {
let f = f.clone(); let f = f.clone();
move || { move || {
@@ -128,76 +125,81 @@ impl Server {
let route = Router::new().route("/", get(handler)); let route = Router::new().route("/", get(handler));
let mut r = self.router.write().await; let mut r = self.router.write().await;
*r = std::mem::take(&mut *r).nest(path, route); *r = if path == "/" {
} std::mem::take(&mut *r).merge(route)
/// Add a new router safely:
/// - If `path` starts with '/', it is merged at root level.
/// - Otherwise, it is nested under the given subpath.
pub async fn add_router(&mut self, path: &str, route: Router) {
let mut r = self.router.write().await;
// Take current router without losing content
let current = std::mem::take(&mut *r);
let combined = if path.starts_with('/') {
// Absolute path => merge directly at root
tracing::debug!("Merging router at root path: {}", path);
current.merge(route)
} else { } else {
// Relative path => nest under the given path std::mem::take(&mut *r).nest(path, route)
let normalized = format!("/{}", path.trim_start_matches('/'));
tracing::debug!("Nesting router under: {}", normalized);
current.nest(&normalized, route)
}; };
*r = combined;
} }
/// Ajoute un répertoire de fichiers statiques /// Ajoute un handler Axum standard
/// pub async fn add_handler<H, T>(&mut self, path: &str, handler: H)
/// Sert des fichiers embarqués via `RustEmbed`. Les fichiers sont compilés where
/// dans le binaire à la compilation. H: Handler<T, ()> + Clone + 'static,
/// T: 'static,
/// # Arguments {
/// let route = Router::new().route("/", get(handler.clone()));
/// * `path` - Chemin où monter les fichiers statiques
/// let mut r = self.router.write().await;
/// # Type Parameter *r = if path == "/" {
/// std::mem::take(&mut *r).merge(route)
/// * `E` - Type RustEmbed définissant le répertoire à servir } else {
/// std::mem::take(&mut *r).nest(path, route)
/// # Exemple };
/// }
/// ```ignore
/// use pmoupnp::server::Server; /// Ajoute un handler POST avec état
/// use rust_embed::RustEmbed; pub async fn add_post_handler_with_state<H, T, S>(&mut self, path: &str, handler: H, state: S)
/// where
/// #[derive(RustEmbed, Clone)] H: Handler<T, S> + Clone + 'static,
/// #[folder = "static/"] T: 'static,
/// struct Assets; S: Clone + Send + Sync + 'static,
/// {
/// # #[tokio::main] let route = Router::new()
/// # async fn main() { .route("/", post(handler.clone()))
/// let mut server = Server::new("Test", "http://localhost:3000", 3000); .with_state(state.clone());
/// server.add_dir::<Assets>("/assets").await;
/// // Les fichiers de static/ sont accessibles via /assets/* let mut r = self.router.write().await;
/// # } *r = if path == "/" {
/// ``` std::mem::take(&mut *r).merge(route)
} else {
std::mem::take(&mut *r).nest(path, route)
};
}
/// Ajoute un handler avec état
pub async fn add_handler_with_state<H, T, S>(&mut self, path: &str, handler: H, state: S)
where
H: Handler<T, S> + Clone + 'static,
T: 'static,
S: Clone + Send + Sync + 'static,
{
let route = Router::new()
.route("/", get(handler.clone()))
.with_state(state.clone());
let mut r = self.router.write().await;
*r = if path == "/" {
std::mem::take(&mut *r).merge(route)
} else {
std::mem::take(&mut *r).nest(path, route)
};
}
/// Ajoute un répertoire statique
pub async fn add_dir<E>(&mut self, path: &str) pub async fn add_dir<E>(&mut self, path: &str)
where where
E: RustEmbed + Clone + Send + Sync + 'static, E: RustEmbed + Clone + Send + Sync + 'static,
{ {
let serve = ServeEmbed::<E>::new(); let serve = ServeEmbed::<E>::new();
let mut r = self.router.write().await; let mut r = self.router.write().await;
if path == "/" {
*r = std::mem::take(&mut *r).fallback_service(serve);
} else {
let route = Router::new().fallback_service(serve); let route = Router::new().fallback_service(serve);
*r = std::mem::take(&mut *r).nest(path, route); *r = if path == "/" {
} std::mem::take(&mut *r).merge(route)
} else {
std::mem::take(&mut *r).nest(path, route)
};
} }
/// Ajoute une Single Page Application (SPA) /// Ajoute une Single Page Application (SPA)
@@ -252,125 +254,12 @@ impl Server {
let mut r = self.router.write().await; let mut r = self.router.write().await;
if path == "/" {
*r = std::mem::take(&mut *r).fallback_service(serve);
} else {
let route = Router::new().fallback_service(serve); let route = Router::new().fallback_service(serve);
*r = std::mem::take(&mut *r).nest(path, route); *r = if path == "/" {
} std::mem::take(&mut *r).merge(route)
} } else {
std::mem::take(&mut *r).nest(path, route)
/// Ajoute un handler Axum personnalisé };
///
/// Pour des cas d'usage avancés nécessitant un contrôle complet sur le handler.
///
/// # Arguments
///
/// * `path` - Chemin de la route
/// * `handler` - Handler Axum
///
/// # Exemple
///
/// ```rust,no_run
/// # use pmoupnp::server::Server;
/// # use axum::response::Html;
/// # #[tokio::main]
/// # async fn main() {
/// # let mut server = Server::new("Test", "http://localhost:3000", 3000);
/// async fn custom_handler() -> Html<&'static str> {
/// Html("<h1>Custom Response</h1>")
/// }
///
/// server.add_handler("/custom", custom_handler).await;
/// # }
/// ```
pub async fn add_handler<H, T>(&mut self, path: &str, handler: H)
where
H: Handler<T, ()>,
T: 'static,
{
let route = Router::new().route("/", get(handler));
let mut r = self.router.write().await;
*r = std::mem::take(&mut *r).nest(path, route);
}
/// Ajoute un handler avec state (pour SSE, extracteurs, etc.)
///
/// Permet d'utiliser des extracteurs Axum comme `State`, `Query`, etc.
/// Idéal pour Server-Sent Events (SSE), WebSockets ou tout handler nécessitant un état partagé.
///
/// # Arguments
///
/// * `path` - Chemin de la route
/// * `handler` - Handler Axum avec extracteurs
/// * `state` - État partagé (doit être Clone + Send + Sync)
///
/// # Exemple avec SSE
///
/// ```ignore
/// use pmoupnp::server::Server;
/// use axum::extract::State;
/// use axum::response::sse::{Event, Sse, KeepAlive};
/// use tokio::sync::broadcast;
///
/// #[derive(Clone)]
/// struct LogState {
/// tx: broadcast::Sender<String>
/// }
///
/// impl LogState {
/// fn subscribe(&self) -> broadcast::Receiver<String> {
/// self.tx.subscribe()
/// }
/// }
///
/// async fn log_sse(State(state): State<LogState>) -> Sse<impl futures::Stream<Item = Result<Event, std::convert::Infallible>>> {
/// let mut rx = state.subscribe();
/// let stream = async_stream::stream! {
/// while let Ok(msg) = rx.recv().await {
/// yield Ok(Event::default().data(msg));
/// }
/// };
/// Sse::new(stream).keep_alive(KeepAlive::default())
/// }
///
/// let log_state = LogState { tx: broadcast::channel(100).0 };
/// server.add_handler_with_state("/logs", log_sse, log_state).await;
/// ```
pub async fn add_handler_with_state<H, T, S>(&mut self, path: &str, handler: H, state: S)
where
H: Handler<T, S>,
T: 'static,
S: Clone + Send + Sync + 'static,
{
let route = Router::new().route("/", get(handler)).with_state(state);
let mut r = self.router.write().await;
*r = std::mem::take(&mut *r).nest(path, route);
}
/// Ajoute un handler POST avec state
///
/// Similaire à `add_handler_with_state` mais pour les requêtes POST.
///
/// # Arguments
///
/// * `path` - Chemin de la route
/// * `handler` - Handler Axum pour POST
/// * `state` - État partagé
pub async fn add_post_handler_with_state<H, T, S>(&mut self, path: &str, handler: H, state: S)
where
H: Handler<T, S>,
T: 'static,
S: Clone + Send + Sync + 'static,
{
let route = Router::new()
.route("/", axum::routing::post(handler))
.with_state(state);
let mut r = self.router.write().await;
*r = std::mem::take(&mut *r).nest(path, route);
} }
/// Ajoute une redirection HTTP /// Ajoute une redirection HTTP
@@ -393,23 +282,20 @@ impl Server {
/// server.add_redirect("/", "/app").await; /// server.add_redirect("/", "/app").await;
/// # } /// # }
/// ``` /// ```
pub async fn add_redirect(&mut self, from: &str, to: &str) { pub async fn add_redirect(&mut self, from: &str, to: &str) {
let to = to.to_string(); let to = to.to_string();
let handler = move || { let make_handler = || {
let to = to.clone(); let target = to.clone();
async move { Redirect::permanent(&to) } get(move || async move { Redirect::permanent(&target) })
}; };
let mut r = self.router.write().await; let mut r = self.router.write().await;
*r = if from == "/" {
if from == "/" { std::mem::take(&mut *r).merge(Router::new().route("/", make_handler()))
// Pour la racine, utiliser merge au lieu de nest
let route = Router::new().route("/", get(handler));
*r = std::mem::take(&mut *r).merge(route);
} else { } else {
let route = Router::new().route("/", get(handler)); std::mem::take(&mut *r).nest(from, Router::new().route("/", make_handler()))
*r = std::mem::take(&mut *r).nest(from, route); };
}
} }
/// Ajoute une API documentée avec OpenAPI et Swagger UI /// Ajoute une API documentée avec OpenAPI et Swagger UI
@@ -481,21 +367,16 @@ impl Server {
/// - `/api/api1/users` et `/api/api2/products` sont accessibles via Axum. /// - `/api/api1/users` et `/api/api2/products` sont accessibles via Axum.
/// - `/swagger-ui/api1` et `/swagger-ui/api2` affichent la documentation Swagger correspondante. /// - `/swagger-ui/api1` et `/swagger-ui/api2` affichent la documentation Swagger correspondante.
/// - `/api-docs/api1.json` et `/api-docs/api2.json` fournissent les spécifications OpenAPI respectives. /// - `/api-docs/api1.json` et `/api-docs/api2.json` fournissent les spécifications OpenAPI respectives.
pub async fn add_openapi( pub async fn add_openapi(
&mut self, &mut self,
api_router: Router, api_router: Router,
openapi: utoipa::openapi::OpenApi, openapi: utoipa::openapi::OpenApi,
name: &str, // nom unique pour différencier Swagger et OpenAPI name: &str,
) { ) {
use axum::routing::get;
use utoipa_swagger_ui::SwaggerUi;
// Stocker le router API dans self.api_router si tu veux y accéder plus tard
let mut api_r = self.api_router.write().await; let mut api_r = self.api_router.write().await;
*api_r = Some(api_router.clone()); *api_r = Some(api_router.clone());
drop(api_r);
// Générer des chemins uniques pour Swagger UI et OpenAPI JSON
let swagger_path = format!("/swagger-ui/{}", name); let swagger_path = format!("/swagger-ui/{}", name);
let swagger_path_static: &'static str = Box::leak(swagger_path.into_boxed_str()); let swagger_path_static: &'static str = Box::leak(swagger_path.into_boxed_str());
@@ -504,16 +385,31 @@ impl Server {
let swagger = SwaggerUi::new(swagger_path_static).url(openapi_json_path_static, openapi); let swagger = SwaggerUi::new(swagger_path_static).url(openapi_json_path_static, openapi);
// Nester le router API sous /api/{name}
let base_path = format!("/api/{}", name); let base_path = format!("/api/{}", name);
let nested_router = Router::new().nest(&base_path, api_router); let nested_router = Router::new().nest(&base_path, api_router);
// Fusionner avec le router principal
let mut r = self.router.write().await; let mut r = self.router.write().await;
let mut combined = std::mem::take(&mut *r); *r = std::mem::take(&mut *r).merge(nested_router).merge(swagger);
combined = combined.merge(nested_router).merge(swagger); }
/// Ajoute un sous-router au serveur
///
/// - Si `path` est "/", merge directement au router principal
/// - Sinon, nest le router sous le chemin donné
pub async fn add_router(&mut self, path: &str, sub_router: Router) {
let mut r = self.router.write().await;
let combined = if path == "/" {
// Merge directement à la racine
r.clone().merge(sub_router)
} else {
// Sous-chemin => nest
let normalized = format!("/{}", path.trim_start_matches('/'));
r.clone().nest(&normalized, sub_router)
};
*r = combined; *r = combined;
} }
/// Démarre le serveur HTTP /// Démarre le serveur HTTP
/// ///
/// Lance le serveur sur le port configuré et met en place la gestion /// Lance le serveur sur le port configuré et met en place la gestion
@@ -537,16 +433,7 @@ impl Server {
self.name, self.base_url, self.http_port, self.base_url, self.http_port self.name, self.base_url, self.http_port, self.base_url, self.http_port
); );
// Merger le routeur API si présent
let api_router = self.api_router.read().await;
if let Some(api_r) = api_router.as_ref() {
let mut r = self.router.write().await;
*r = std::mem::take(&mut *r).nest("/api", api_r.clone());
}
drop(api_router);
let router = self.router.clone(); let router = self.router.clone();
let server_task = tokio::spawn(async move { let server_task = tokio::spawn(async move {
let r = router.read().await.clone(); let r = router.read().await.clone();
let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); let listener = tokio::net::TcpListener::bind(addr).await.unwrap();

View File

@@ -3,8 +3,6 @@ use std::sync::Arc;
use xmltree::{Element, XMLNode}; use xmltree::{Element, XMLNode};
use crate::actions::Action; use crate::actions::Action;
use crate::actions::Argument;
use crate::actions::ArgumentSet;
use crate::actions::ArgInstanceSet; use crate::actions::ArgInstanceSet;
use crate::actions::ActionInstance; use crate::actions::ActionInstance;
use crate::UpnpInstance; use crate::UpnpInstance;

View File

@@ -2,7 +2,7 @@ use std::{collections::HashMap, sync::Arc};
use std::sync::RwLock; use std::sync::RwLock;
use crate::{UpnpDeepClone, UpnpObjectSet, UpnpObjectSetError, UpnpSet, UpnpTypedObject}; use crate::{UpnpDeepClone, UpnpObjectSet, UpnpObjectSetError, UpnpTypedObject};
/// Implémentation du clonage profond pour `UpnpObjectSet`. /// Implémentation du clonage profond pour `UpnpObjectSet`.
/// ///

View File

@@ -5,7 +5,7 @@ use std::collections::HashMap;
use std::net::{SocketAddr, UdpSocket}; use std::net::{SocketAddr, UdpSocket};
use std::sync::{Arc, RwLock}; use std::sync::{Arc, RwLock};
use std::time::Duration; use std::time::Duration;
use tracing::{error, info, warn}; use tracing::{info, warn};
/// Serveur SSDP gérant les annonces et découvertes /// Serveur SSDP gérant les annonces et découvertes
pub struct SsdpServer { pub struct SsdpServer {