Ajoute la source pmoparadise au mediaserver

This commit is contained in:
2025-10-17 12:01:36 +02:00
parent 622c1d952e
commit 218e527e24
6 changed files with 204 additions and 23 deletions

View File

@@ -23,6 +23,7 @@ serde_json = "1.0"
axum = { version = "0.8", optional = true }
utoipa = { version = "5.3", optional = true }
pmoqobuz = { path = "../pmoqobuz", optional = true }
pmoparadise = { path = "../pmoparadise", optional = true }
pmoconfig = { path = "../pmoconfig", optional = true }
[features]
@@ -31,3 +32,5 @@ default = []
api = ["dep:axum", "dep:utoipa"]
# Feature pour activer le support Qobuz configuré
qobuz = ["dep:pmoqobuz", "dep:pmoconfig", "pmoqobuz/cache"]
# Feature pour activer le support Radio Paradise
paradise = ["dep:pmoparadise"]

View File

@@ -14,6 +14,10 @@ pub enum SourceInitError {
#[error("Failed to initialize Qobuz: {0}")]
QobuzError(String),
#[cfg(feature = "paradise")]
#[error("Failed to initialize Radio Paradise: {0}")]
ParadiseError(String),
#[error("Configuration error: {0}")]
ConfigError(String),
@@ -46,7 +50,7 @@ pub type Result<T> = std::result::Result<T, SourceInitError>;
/// ```
#[async_trait::async_trait]
pub trait SourcesExt {
/// Enregistre la source Qobuz depuis la configuration
/// Enregistre la source Qobuz
///
/// Cette méthode lit les credentials Qobuz depuis `pmoconfig` et crée
/// automatiquement un `QobuzSource` avec cache activé.
@@ -71,10 +75,10 @@ pub trait SourcesExt {
/// # Examples
///
/// ```ignore
/// server.register_qobuz_from_config().await?;
/// server.register_qobuz().await?;
/// ```
#[cfg(feature = "qobuz")]
async fn register_qobuz_from_config(&mut self) -> Result<()>;
async fn register_qobuz(&mut self) -> Result<()>;
/// Enregistre la source Qobuz avec des credentials explicites
///
@@ -86,19 +90,38 @@ pub trait SourcesExt {
/// # Examples
///
/// ```ignore
/// server.register_qobuz("user@example.com", "password").await?;
/// server.register_qobuz_with_credentials("user@example.com", "password").await?;
/// ```
#[cfg(feature = "qobuz")]
async fn register_qobuz(&mut self, username: &str, password: &str) -> Result<()>;
async fn register_qobuz_with_credentials(&mut self, username: &str, password: &str) -> Result<()>;
/// Enregistre la source Radio Paradise
///
/// Cette méthode crée automatiquement un `RadioParadiseSource` avec cache activé.
/// Radio Paradise ne nécessite pas d'authentification.
///
/// # Erreurs
///
/// Retourne une erreur si :
/// - La connexion au client Radio Paradise échoue
/// - La feature "paradise" n'est pas activée
///
/// # Examples
///
/// ```ignore
/// server.register_paradise().await?;
/// ```
#[cfg(feature = "paradise")]
async fn register_paradise(&mut self) -> Result<()>;
}
#[async_trait::async_trait]
impl SourcesExt for Server {
#[cfg(feature = "qobuz")]
async fn register_qobuz_from_config(&mut self) -> Result<()> {
async fn register_qobuz(&mut self) -> Result<()> {
use pmoqobuz::{QobuzClient, QobuzSource};
tracing::info!("Initializing Qobuz source from configuration...");
tracing::info!("Initializing Qobuz source...");
// Créer le client depuis la config
let client = QobuzClient::from_config()
@@ -122,7 +145,7 @@ impl SourcesExt for Server {
}
#[cfg(feature = "qobuz")]
async fn register_qobuz(&mut self, username: &str, password: &str) -> Result<()> {
async fn register_qobuz_with_credentials(&mut self, username: &str, password: &str) -> Result<()> {
use pmoqobuz::{QobuzClient, QobuzSource};
tracing::info!("Initializing Qobuz source with explicit credentials...");
@@ -147,12 +170,34 @@ impl SourcesExt for Server {
Ok(())
}
}
// Placeholder pour d'autres sources
// TODO: Ajouter Radio Paradise lorsque la crate sera disponible
// #[cfg(feature = "radioparadise")]
// async fn register_radioparadise_from_config(&mut self) -> Result<()> { ... }
#[cfg(feature = "paradise")]
async fn register_paradise(&mut self) -> Result<()> {
use pmoparadise::{RadioParadiseClient, RadioParadiseSource};
tracing::info!("Initializing Radio Paradise source...");
// Créer le client (Radio Paradise ne nécessite pas d'authentification)
let client = RadioParadiseClient::new()
.await
.map_err(|e| SourceInitError::ParadiseError(format!("Failed to create client: {}", e)))?;
// Récupérer l'URL de base du serveur depuis la config
let config = pmoconfig::get_config();
let port = config.get_http_port();
let base_url = format!("http://localhost:{}", port);
// Créer la source avec capacité FIFO par défaut
let source = RadioParadiseSource::new_default(client, &base_url);
// Enregistrer la source
self.register_music_source(Arc::new(source)).await;
tracing::info!("✅ Radio Paradise source registered successfully");
Ok(())
}
}
#[cfg(test)]
mod tests {

View File

@@ -65,6 +65,15 @@ pub struct QobuzCredentials {
pub password: Option<String>,
}
/// Paramètres pour Radio Paradise (actuellement vide, mais peut être étendu)
#[cfg(feature = "paradise")]
#[derive(Debug, Deserialize, utoipa::ToSchema)]
pub struct ParadiseParams {
/// Capacité FIFO (optionnelle, 50 par défaut)
#[serde(default)]
pub fifo_capacity: Option<usize>,
}
/// Réponse d'enregistrement de source
#[derive(Debug, Serialize, utoipa::ToSchema)]
pub struct SourceRegisteredResponse {
@@ -226,6 +235,65 @@ async fn register_qobuz(Json(creds): Json<QobuzCredentials>) -> impl IntoRespons
.into_response()
}
/// Enregistre une source Radio Paradise
///
/// Enregistre une nouvelle source Radio Paradise (ne nécessite pas d'authentification).
#[cfg(feature = "paradise")]
#[utoipa::path(
post,
path = "/sources/paradise",
request_body = ParadiseParams,
responses(
(status = 201, description = "Source enregistrée", body = SourceRegisteredResponse),
(status = 400, description = "Erreur d'enregistrement", body = ErrorResponse),
),
tag = "sources"
)]
async fn register_paradise(Json(params): Json<ParadiseParams>) -> impl IntoResponse {
use pmoparadise::{RadioParadiseClient, RadioParadiseSource};
let registry = get_source_registry().await;
// Créer le client (Radio Paradise ne nécessite pas d'auth)
let client = match RadioParadiseClient::new().await {
Ok(c) => c,
Err(e) => {
return (
StatusCode::BAD_REQUEST,
Json(ErrorResponse {
error: format!("Failed to create Radio Paradise client: {}", e),
}),
)
.into_response();
}
};
// Récupérer l'URL de base du serveur depuis la config
let config = pmoconfig::get_config();
let port = config.get_http_port();
let base_url = format!("http://localhost:{}", port);
// Créer et enregistrer la source
let source = if let Some(capacity) = params.fifo_capacity {
Arc::new(RadioParadiseSource::new(client, &base_url, capacity))
} else {
Arc::new(RadioParadiseSource::new_default(client, &base_url))
};
let source_id = source.as_ref().id().to_string();
registry.register(source).await;
(
StatusCode::CREATED,
Json(SourceRegisteredResponse {
message: "Radio Paradise source registered successfully".to_string(),
source_id,
}),
)
.into_response()
}
/// Désenregistre une source musicale
///
/// Supprime une source du registre par son ID.
@@ -281,19 +349,52 @@ async fn unregister_source(Path(id): Path<String>) -> impl IntoResponse {
pub fn sources_api_router() -> Router {
let mut router = Router::new()
.route("/sources", get(list_sources))
.route("/sources/:id", get(get_source))
.route("/sources/:id", delete(unregister_source));
.route("/sources/{id}", get(get_source))
.route("/sources/{id}", delete(unregister_source));
#[cfg(feature = "qobuz")]
{
router = router.route("/sources/qobuz", post(register_qobuz));
}
#[cfg(feature = "paradise")]
{
router = router.route("/sources/paradise", post(register_paradise));
}
router
}
/// Structure pour la documentation OpenAPI
#[cfg(feature = "qobuz")]
/// Structure pour la documentation OpenAPI (Qobuz + Paradise)
#[cfg(all(feature = "qobuz", feature = "paradise"))]
#[derive(utoipa::OpenApi)]
#[openapi(
paths(
list_sources,
get_source,
unregister_source,
register_qobuz,
register_paradise,
),
components(
schemas(
SourceInfo,
SourceCapabilitiesInfo,
SourcesList,
SourceRegisteredResponse,
ErrorResponse,
QobuzCredentials,
ParadiseParams,
)
),
tags(
(name = "sources", description = "Gestion des sources musicales")
)
)]
pub struct SourcesApiDoc;
/// Structure pour la documentation OpenAPI (Qobuz uniquement)
#[cfg(all(feature = "qobuz", not(feature = "paradise")))]
#[derive(utoipa::OpenApi)]
#[openapi(
paths(
@@ -318,8 +419,34 @@ pub fn sources_api_router() -> Router {
)]
pub struct SourcesApiDoc;
/// Structure pour la documentation OpenAPI (sans Qobuz)
#[cfg(not(feature = "qobuz"))]
/// Structure pour la documentation OpenAPI (Paradise uniquement)
#[cfg(all(feature = "paradise", not(feature = "qobuz")))]
#[derive(utoipa::OpenApi)]
#[openapi(
paths(
list_sources,
get_source,
unregister_source,
register_paradise,
),
components(
schemas(
SourceInfo,
SourceCapabilitiesInfo,
SourcesList,
SourceRegisteredResponse,
ErrorResponse,
ParadiseParams,
)
),
tags(
(name = "sources", description = "Gestion des sources musicales")
)
)]
pub struct SourcesApiDoc;
/// Structure pour la documentation OpenAPI (sans sources spécifiques)
#[cfg(not(any(feature = "qobuz", feature = "paradise")))]
#[derive(utoipa::OpenApi)]
#[openapi(
paths(