Començons un Mediarenderer
This commit is contained in:
@@ -5,6 +5,7 @@ edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
pmoconfig = { path = "../pmoconfig" }
|
||||
pmodidl = { path = "../pmodidl"}
|
||||
|
||||
url = "2.5.7"
|
||||
uuid = "1.18.1"
|
||||
@@ -34,3 +35,5 @@ anyhow = "1.0"
|
||||
utoipa = { version = "5.4.0", features = ["axum_extras"] }
|
||||
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
|
||||
validator = { version = "0.20.0", features = ["derive"] }
|
||||
bevy_reflect = "0.17.1"
|
||||
bevy_reflect_derive = "0.17.1"
|
||||
|
||||
197
pmoupnp/src/actions/macros.rs
Normal file
197
pmoupnp/src/actions/macros.rs
Normal file
@@ -0,0 +1,197 @@
|
||||
/// Macro pour définir facilement des actions UPnP
|
||||
///
|
||||
/// # Syntaxe
|
||||
///
|
||||
/// ```ignore
|
||||
/// define_action! {
|
||||
/// pub static ACTION_NAME = "ActionName" {
|
||||
/// in "ParamName" => VARIABLE_REF,
|
||||
/// in "OtherParam" => OTHER_VAR,
|
||||
/// out "ResultParam" => RESULT_VAR,
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! define_action {
|
||||
// Variante sans arguments
|
||||
(pub static $name:ident = $action_name:literal) => {
|
||||
pub static $name: once_cell::sync::Lazy<$crate::actions::Action> =
|
||||
once_cell::sync::Lazy::new(|| {
|
||||
$crate::actions::Action::new($action_name.to_string())
|
||||
});
|
||||
};
|
||||
|
||||
// Variante avec arguments
|
||||
(pub static $name:ident = $action_name:literal {
|
||||
$(
|
||||
$direction:ident $arg_name:literal => $var_ref:expr
|
||||
),* $(,)?
|
||||
}) => {
|
||||
pub static $name: once_cell::sync::Lazy<$crate::actions::Action> =
|
||||
once_cell::sync::Lazy::new(|| {
|
||||
let mut ac = $crate::actions::Action::new($action_name.to_string());
|
||||
|
||||
$(
|
||||
ac.add_argument(
|
||||
define_action!(@arg $direction $arg_name, $var_ref)
|
||||
);
|
||||
)*
|
||||
|
||||
ac
|
||||
});
|
||||
};
|
||||
|
||||
// Helpers internes pour créer les arguments
|
||||
(@arg in $name:literal, $var:expr) => {
|
||||
$crate::actions::Argument::new_in($name.to_string(), $var.clone())
|
||||
};
|
||||
|
||||
(@arg out $name:literal, $var:expr) => {
|
||||
$crate::actions::Argument::new_out($name.to_string(), $var.clone())
|
||||
};
|
||||
}
|
||||
|
||||
// ============= Exemples d'utilisation =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod examples {
|
||||
use super::*;
|
||||
|
||||
// Utilisation originale (pour comparaison)
|
||||
mod original {
|
||||
use crate::mediarenderer::avtransport::variables::{
|
||||
A_ARG_TYPE_INSTANCE_ID,
|
||||
TRANSPORTPLAYSPEED
|
||||
};
|
||||
use crate::actions::{Action, Argument};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static PLAY: Lazy<Action> = Lazy::new(|| -> Action {
|
||||
let mut ac = Action::new("Play".to_string());
|
||||
|
||||
ac.add_argument(
|
||||
Argument::new_in("InstanceID".to_string(),
|
||||
A_ARG_TYPE_INSTANCE_ID.clone())
|
||||
);
|
||||
|
||||
ac.add_argument(
|
||||
Argument::new_in("Speed".to_string(),
|
||||
TRANSPORTPLAYSPEED.clone())
|
||||
);
|
||||
|
||||
ac
|
||||
});
|
||||
}
|
||||
|
||||
// Avec la macro - Version simple
|
||||
mod with_macro {
|
||||
use crate::mediarenderer::avtransport::variables::*;
|
||||
|
||||
define_action! {
|
||||
pub static PLAY = "Play" {
|
||||
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||
in "Speed" => TRANSPORTPLAYSPEED,
|
||||
}
|
||||
}
|
||||
|
||||
define_action! {
|
||||
pub static SETAVTRANSPORTURI = "SetAVTransportURI" {
|
||||
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||
in "CurrentURI" => AVTRANSPORTURI,
|
||||
in "CurrentURIMetaData" => AVTRANSPORTURIMETADATA,
|
||||
}
|
||||
}
|
||||
|
||||
define_action! {
|
||||
pub static STOP = "Stop" {
|
||||
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||
}
|
||||
}
|
||||
|
||||
// Action sans arguments
|
||||
define_action! {
|
||||
pub static PAUSE = "Pause"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// ============= Macro alternative encore plus concise =============
|
||||
|
||||
/// Macro pour définir plusieurs actions en une fois
|
||||
///
|
||||
/// # Syntaxe
|
||||
///
|
||||
/// ```ignore
|
||||
/// define_actions! {
|
||||
/// PLAY = "Play" {
|
||||
/// in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||
/// in "Speed" => TRANSPORTPLAYSPEED,
|
||||
/// }
|
||||
///
|
||||
/// STOP = "Stop" {
|
||||
/// in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! define_actions {
|
||||
(
|
||||
$(
|
||||
$name:ident = $action_name:literal {
|
||||
$(
|
||||
$direction:ident $arg_name:literal => $var_ref:expr
|
||||
),* $(,)?
|
||||
}
|
||||
)*
|
||||
) => {
|
||||
$(
|
||||
define_action! {
|
||||
pub static $name = $action_name {
|
||||
$($direction $arg_name => $var_ref),*
|
||||
}
|
||||
}
|
||||
)*
|
||||
};
|
||||
|
||||
// Support pour actions sans arguments
|
||||
(
|
||||
$(
|
||||
$name:ident = $action_name:literal
|
||||
)*
|
||||
) => {
|
||||
$(
|
||||
define_action! {
|
||||
pub static $name = $action_name
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
// ============= Exemple d'utilisation groupée =============
|
||||
|
||||
#[cfg(test)]
|
||||
mod grouped_example {
|
||||
use crate::mediarenderer::avtransport::variables::*;
|
||||
|
||||
define_actions! {
|
||||
PLAY = "Play" {
|
||||
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||
in "Speed" => TRANSPORTPLAYSPEED,
|
||||
}
|
||||
|
||||
STOP = "Stop" {
|
||||
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||
}
|
||||
|
||||
PAUSE = "Pause" {
|
||||
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||
}
|
||||
|
||||
SETAVTRANSPORTURI = "SetAVTransportURI" {
|
||||
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||
in "CurrentURI" => AVTRANSPORTURI,
|
||||
in "CurrentURIMetaData" => AVTRANSPORTURIMETADATA,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,15 @@ mod action_instance_set;
|
||||
mod argument;
|
||||
mod arg_set_methods;
|
||||
|
||||
mod macros;
|
||||
|
||||
|
||||
use std::collections::HashMap;
|
||||
use crate::{state_variables::StateVariable, UpnpObjectType};
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Action {
|
||||
pub struct Action {
|
||||
object: UpnpObjectType,
|
||||
arguments: ArgumentSet,
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ pub mod value_ranges;
|
||||
pub mod variable_types;
|
||||
pub mod actions;
|
||||
|
||||
pub mod mediarenderer;
|
||||
|
||||
pub use crate::object_trait::{UpnpXml,UpnpObject};
|
||||
|
||||
#[derive(Debug,Clone)]
|
||||
|
||||
8
pmoupnp/src/mediarenderer/avtransport/actions/mod.rs
Normal file
8
pmoupnp/src/mediarenderer/avtransport/actions/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
mod play;
|
||||
mod stop;
|
||||
mod setavtransporturi;
|
||||
|
||||
pub use play::PLAY;
|
||||
pub use stop::STOP;
|
||||
pub use setavtransporturi::SETAVTRANSPORTURI;
|
||||
|
||||
19
pmoupnp/src/mediarenderer/avtransport/actions/play.rs
Normal file
19
pmoupnp/src/mediarenderer/avtransport/actions/play.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
use crate::mediarenderer::avtransport::variables::{A_ARG_TYPE_INSTANCE_ID, TRANSPORTPLAYSPEED};
|
||||
use crate::actions::{Action, Argument};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static PLAY: Lazy<Action> = Lazy::new(|| -> Action {
|
||||
let mut ac = Action::new("Play".to_string());
|
||||
|
||||
ac.add_argument(
|
||||
Argument::new_in("InstanceID".to_string(),
|
||||
A_ARG_TYPE_INSTANCE_ID.clone())
|
||||
);
|
||||
|
||||
ac.add_argument(
|
||||
Argument::new_in("Speed".to_string(),
|
||||
TRANSPORTPLAYSPEED.clone())
|
||||
);
|
||||
|
||||
ac
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
use crate::mediarenderer::avtransport::variables::{A_ARG_TYPE_INSTANCE_ID, AVTRANSPORTURI, AVTRANSPORTURIMETADATA};
|
||||
use crate::define_action;
|
||||
|
||||
define_action! {
|
||||
pub static SETAVTRANSPORTURI = "SetAVTransportURI" {
|
||||
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||
in "CurrentURI" => AVTRANSPORTURI,
|
||||
in "CurrentURIMetaData" => AVTRANSPORTURIMETADATA,
|
||||
}
|
||||
}
|
||||
8
pmoupnp/src/mediarenderer/avtransport/actions/stop.rs
Normal file
8
pmoupnp/src/mediarenderer/avtransport/actions/stop.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use crate::mediarenderer::avtransport::variables::A_ARG_TYPE_INSTANCE_ID;
|
||||
use crate::define_action;
|
||||
|
||||
define_action! {
|
||||
pub static STOP = "Stop" {
|
||||
in "InstanceID" => A_ARG_TYPE_INSTANCE_ID,
|
||||
}
|
||||
}
|
||||
3
pmoupnp/src/mediarenderer/avtransport/mod.rs
Normal file
3
pmoupnp/src/mediarenderer/avtransport/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod variables;
|
||||
pub mod actions;
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
use crate::state_variables::StateVariable;
|
||||
use crate::variable_types::StateVarType;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static A_ARG_TYPE_INSTANCE_ID: Lazy<StateVariable> = Lazy::new(|| -> StateVariable {
|
||||
StateVariable::new(StateVarType::UI4, "A_ARG_TYPE_InstanceID".to_string())
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
use crate::state_variables::StateVariable;
|
||||
use crate::variable_types::StateVarType;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static A_ARG_TYPE_PLAY_SPEED: Lazy<StateVariable> = Lazy::new(|| -> StateVariable {
|
||||
StateVariable::new(StateVarType::String, "A_ARG_TYPE_PlaySpeed".to_string())
|
||||
});
|
||||
@@ -0,0 +1,7 @@
|
||||
use crate::state_variables::StateVariable;
|
||||
use crate::variable_types::StateVarType;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static AVTRANSPORTURI: Lazy<StateVariable> = Lazy::new(|| -> StateVariable {
|
||||
StateVariable::new(StateVarType::String, "AVTransportURI".to_string())
|
||||
});
|
||||
@@ -0,0 +1,33 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::state_variables::{StateVariable, StateVariableError};
|
||||
use crate::variable_types::StateVarType;
|
||||
use bevy_reflect::Reflect;
|
||||
use once_cell::sync::Lazy;
|
||||
use pmodidl::{DIDLLite, MediaMetadataParser};
|
||||
|
||||
// func _AVTransportURIMetaDataParser(value string) (interface{}, error) {
|
||||
// log.Debug("[avtransport] Parsing AVTransport)")
|
||||
// didl, err := pmodidl.Parse(value)
|
||||
// if err != nil {
|
||||
// return value, err
|
||||
// }
|
||||
|
||||
// return didl, nil
|
||||
// }
|
||||
|
||||
fn avtransporturimetadataparser(value: &str) -> Result<Box<dyn Reflect>, StateVariableError> {
|
||||
// Parse DIDL-Lite
|
||||
let didl = DIDLLite::parse(value)
|
||||
.map_err(|e| StateVariableError::ParseError(format!("Failed to parse DIDL-Lite: {}", e)))?;
|
||||
|
||||
// Retourne le résultat sous forme de Box<dyn Reflect>
|
||||
Ok(Box::new(didl) as Box<dyn Reflect>)
|
||||
}
|
||||
|
||||
pub static AVTRANSPORTURIMETADATA: Lazy<StateVariable> = Lazy::new(|| -> StateVariable {
|
||||
let mut sv = StateVariable::new(StateVarType::String, "AVTransportURIMetaData".to_string());
|
||||
|
||||
sv.set_value_parser(Arc::new(avtransporturimetadataparser)).expect("Failed to set parser");
|
||||
sv
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
use crate::state_variables::StateVariable;
|
||||
use crate::variable_types::StateVarType;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static CURRENTTRACKDURATION: Lazy<StateVariable> = Lazy::new(|| -> StateVariable {
|
||||
StateVariable::new(StateVarType::String, "CurrentTrackDuration".to_string())
|
||||
});
|
||||
|
||||
22
pmoupnp/src/mediarenderer/avtransport/variables/mod.rs
Normal file
22
pmoupnp/src/mediarenderer/avtransport/variables/mod.rs
Normal file
@@ -0,0 +1,22 @@
|
||||
mod a_arg_type_instanceid;
|
||||
mod a_arg_type_playspeed;
|
||||
mod avtransporturi;
|
||||
mod avtransporturimetadata;
|
||||
mod currenttrackduration;
|
||||
mod seekmode;
|
||||
mod transportplayspeed;
|
||||
mod transportstate;
|
||||
mod transportstatus;
|
||||
|
||||
pub use a_arg_type_instanceid::A_ARG_TYPE_INSTANCE_ID;
|
||||
pub use a_arg_type_playspeed::A_ARG_TYPE_PLAY_SPEED;
|
||||
pub use avtransporturi::AVTRANSPORTURI;
|
||||
pub use avtransporturimetadata::AVTRANSPORTURIMETADATA;
|
||||
pub use currenttrackduration::CURRENTTRACKDURATION;
|
||||
pub use seekmode::SEEKMODE;
|
||||
pub use transportplayspeed::TRANSPORTPLAYSPEED;
|
||||
pub use transportstate::TRANSPORTSTATE;
|
||||
pub use transportstatus::TRANSPORTSTATUS;
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
use crate::state_variables::StateVariable;
|
||||
use crate::variable_types::StateVarType;
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static SEEKMODE: Lazy<StateVariable> = Lazy::new(|| -> StateVariable {
|
||||
StateVariable::new(StateVarType::String, "SeekMode".to_string())
|
||||
});
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
use crate::state_variables::StateVariable;
|
||||
use crate::variable_types::{StateValue, StateVarType};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static TRANSPORTPLAYSPEED: Lazy<StateVariable> = Lazy::new(|| -> StateVariable {
|
||||
let mut sv = StateVariable::new(StateVarType::String, "TransportPlaySpeed".to_string());
|
||||
|
||||
sv.push_allowed_value(&StateValue::String("1".to_string())).expect("Cannot add allowed value");
|
||||
sv.set_default(&StateValue::String("1".to_string())).expect("Cannt set default value");
|
||||
|
||||
sv
|
||||
});
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
use crate::state_variables::StateVariable;
|
||||
use crate::variable_types::{StateValue, StateVarType};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static TRANSPORTSTATE: Lazy<StateVariable> = Lazy::new(|| -> StateVariable {
|
||||
let mut sv = StateVariable::new(StateVarType::String, "TransportState".to_string());
|
||||
|
||||
sv.push_allowed_value(&StateValue::String("NO_MEDIA_PRESENT".to_string())).expect("Cannot add allowed value");
|
||||
sv.extend_allowed_values(&[
|
||||
StateValue::String("STOPPED".to_string()),
|
||||
StateValue::String("PLAYING".to_string()),
|
||||
StateValue::String("RECORDING".to_string()),
|
||||
StateValue::String("TRANSITIONING".to_string()),
|
||||
StateValue::String("PAUSED_PLAYBACK".to_string()),
|
||||
StateValue::String("PAUSED_RECORDING".to_string()),
|
||||
StateValue::String("NO_MEDIA_PRESENT".to_string()),
|
||||
]).expect("Cannt set default value");
|
||||
|
||||
sv
|
||||
});
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
use crate::state_variables::StateVariable;
|
||||
use crate::variable_types::{StateValue, StateVarType};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
pub static TRANSPORTSTATUS: Lazy<StateVariable> = Lazy::new(|| -> StateVariable {
|
||||
let mut sv = StateVariable::new(StateVarType::String, "TransportStatus".to_string());
|
||||
|
||||
sv.push_allowed_value(&StateValue::String("OK".to_string()))
|
||||
.expect("Cannot add allowed value");
|
||||
sv.extend_allowed_values(&[
|
||||
StateValue::String("OK".to_string()),
|
||||
StateValue::String("ERROR_OCCURRED".to_string()),
|
||||
])
|
||||
.expect("Cannt set default value");
|
||||
|
||||
sv
|
||||
});
|
||||
1
pmoupnp/src/mediarenderer/mod.rs
Normal file
1
pmoupnp/src/mediarenderer/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod avtransport;
|
||||
@@ -24,8 +24,72 @@ pub trait UpnpXml {
|
||||
|
||||
xml_string
|
||||
}
|
||||
fn to_markdown(&self) -> String {
|
||||
let elem = self.to_xml_element();
|
||||
let mut md = String::new();
|
||||
|
||||
fn is_url(s: &str) -> bool {
|
||||
s.starts_with("http://") || s.starts_with("https://") || s.starts_with("urn:")
|
||||
}
|
||||
|
||||
fn is_image_url(s: &str) -> bool {
|
||||
let s = s.to_lowercase();
|
||||
s.ends_with(".png")
|
||||
|| s.ends_with(".jpg")
|
||||
|| s.ends_with(".jpeg")
|
||||
|| s.ends_with(".gif")
|
||||
|| s.ends_with(".svg")
|
||||
|| s.ends_with(".webp")
|
||||
}
|
||||
|
||||
fn format_value(v: &str) -> String {
|
||||
let v = v.trim().to_string(); // <-- clone du texte nettoyé
|
||||
if is_url(&v) {
|
||||
if is_image_url(&v) {
|
||||
format!("[{}]({})<br>", v, v, v)
|
||||
} else {
|
||||
format!("[{}]({})", v, v)
|
||||
}
|
||||
} else {
|
||||
format!("`{}`", v)
|
||||
}
|
||||
}
|
||||
|
||||
fn recurse(elem: &xmltree::Element, md: &mut String, depth: usize) {
|
||||
let indent = " ".repeat(depth);
|
||||
md.push_str(&format!("{}- **{}**", indent, elem.name));
|
||||
|
||||
// Attributs
|
||||
if !elem.attributes.is_empty() {
|
||||
let attrs: Vec<String> = elem
|
||||
.attributes
|
||||
.iter()
|
||||
.map(|(k, v)| format!("{}={}", k, format_value(v)))
|
||||
.collect();
|
||||
md.push_str(&format!(" ({})", attrs.join(", ")));
|
||||
}
|
||||
|
||||
// Texte (utilise get_text() maintenant)
|
||||
if let Some(text) = elem.get_text().map(|s| s.trim().to_string()).filter(|s| !s.is_empty()) {
|
||||
md.push_str(&format!(": {}", format_value(&text)));
|
||||
}
|
||||
|
||||
md.push('\n');
|
||||
|
||||
// Enfants
|
||||
for child in &elem.children {
|
||||
if let xmltree::XMLNode::Element(child_elem) = child {
|
||||
recurse(child_elem, md, depth + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
md.push_str("# UPnP XML (Markdown view)\n\n");
|
||||
recurse(&elem, &mut md, 0);
|
||||
md
|
||||
}
|
||||
}
|
||||
pub trait UpnpObject : UpnpXml {
|
||||
pub trait UpnpObject: UpnpXml {
|
||||
fn as_upnp_object_type(&self) -> &UpnpObjectType;
|
||||
|
||||
fn get_name(&self) -> &String {
|
||||
@@ -35,6 +99,4 @@ pub trait UpnpObject : UpnpXml {
|
||||
fn get_object_type(&self) -> &String {
|
||||
&self.as_upnp_object_type().object_type
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ use rust_embed::RustEmbed;
|
||||
use serde::Serialize;
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use tokio::{signal, sync::RwLock, task::JoinHandle};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing::{info, warn, debug, error};
|
||||
use utoipa::OpenApi;
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
@@ -150,16 +150,17 @@ impl Server {
|
||||
///
|
||||
/// # Exemple
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pmoupnp::server::Server;
|
||||
/// # use rust_embed::RustEmbed;
|
||||
/// ```ignore
|
||||
/// use pmoupnp::server::Server;
|
||||
/// use rust_embed::RustEmbed;
|
||||
///
|
||||
/// #[derive(RustEmbed, Clone)]
|
||||
/// #[folder = "static/"]
|
||||
/// struct Assets;
|
||||
///
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// # let mut server = Server::new("Test", "http://localhost:3000", 3000);
|
||||
/// let mut server = Server::new("Test", "http://localhost:3000", 3000);
|
||||
/// server.add_dir::<Assets>("/assets").await;
|
||||
/// // Les fichiers de static/ sont accessibles via /assets/*
|
||||
/// # }
|
||||
@@ -169,9 +170,9 @@ impl Server {
|
||||
E: RustEmbed + Clone + Send + Sync + 'static,
|
||||
{
|
||||
let serve = ServeEmbed::<E>::new();
|
||||
|
||||
|
||||
let mut r = self.router.write().await;
|
||||
|
||||
|
||||
if path == "/" {
|
||||
*r = std::mem::take(&mut *r).fallback_service(serve);
|
||||
} else {
|
||||
@@ -229,9 +230,9 @@ impl Server {
|
||||
axum_embed::FallbackBehavior::Ok,
|
||||
Some("index.html".to_string()),
|
||||
);
|
||||
|
||||
|
||||
let mut r = self.router.write().await;
|
||||
|
||||
|
||||
if path == "/" {
|
||||
*r = std::mem::take(&mut *r).fallback_service(serve);
|
||||
} else {
|
||||
@@ -288,17 +289,23 @@ impl Server {
|
||||
///
|
||||
/// # Exemple avec SSE
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # 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() } }
|
||||
/// # #[tokio::main]
|
||||
/// # async fn main() {
|
||||
/// # let mut server = Server::new("Test", "http://localhost:3000", 3000);
|
||||
/// ```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! {
|
||||
@@ -311,7 +318,6 @@ impl Server {
|
||||
///
|
||||
/// 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
|
||||
@@ -319,7 +325,9 @@ impl Server {
|
||||
T: 'static,
|
||||
S: Clone + Send + Sync + 'static,
|
||||
{
|
||||
let route = Router::new().route("/", get(handler)).with_state(state);
|
||||
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);
|
||||
@@ -398,9 +406,16 @@ impl Server {
|
||||
///
|
||||
/// # Exemple
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// ```ignore
|
||||
/// use utoipa::OpenApi;
|
||||
/// use axum::{Router, Json, routing::get};
|
||||
/// use serde::{Serialize, Deserialize};
|
||||
///
|
||||
/// #[derive(Serialize, Deserialize, utoipa::ToSchema)]
|
||||
/// struct User {
|
||||
/// id: u64,
|
||||
/// name: String,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(utoipa::OpenApi)]
|
||||
/// #[openapi(
|
||||
@@ -429,7 +444,8 @@ impl Server {
|
||||
*api_r = Some(api_router);
|
||||
|
||||
// Ajouter Swagger UI
|
||||
let swagger = SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", openapi);
|
||||
let swagger = SwaggerUi::new("/swagger-ui")
|
||||
.url("/api-docs/openapi.json", openapi);
|
||||
|
||||
let mut r = self.router.write().await;
|
||||
*r = std::mem::take(&mut *r).merge(swagger);
|
||||
@@ -453,10 +469,7 @@ impl Server {
|
||||
/// ```
|
||||
pub async fn start(&mut self) {
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], self.http_port));
|
||||
info!(
|
||||
"Server {} running at [http://{}:{}](http://{}:{})",
|
||||
self.name, self.base_url, self.http_port, self.base_url, self.http_port
|
||||
);
|
||||
info!("Server {} running at [http://{}:{}](http://{}:{})", 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;
|
||||
@@ -532,7 +545,7 @@ impl ServerBuilder {
|
||||
Self {
|
||||
name: "PMO-Music-Server".to_string(),
|
||||
base_url: config.get_base_url(),
|
||||
http_port: config.get_http_port(),
|
||||
http_port: config.get_http_port()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -550,4 +563,4 @@ impl ServerBuilder {
|
||||
pub fn build(self) -> Server {
|
||||
Server::new(self.name, self.base_url, self.http_port)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use std::{
|
||||
};
|
||||
|
||||
pub use crate::state_variables::variable_trait::UpnpVariable;
|
||||
use bevy_reflect::Reflect;
|
||||
use chrono::{DateTime, Utc};
|
||||
pub use errors::StateVariableError;
|
||||
|
||||
@@ -24,7 +25,7 @@ pub type StateConditionFunc = Arc<dyn Fn(&StateVarInstance) -> bool + Send + Syn
|
||||
|
||||
/// Type pour les fonctions de parsing de valeurs depuis des chaînes
|
||||
pub type StringValueParser =
|
||||
Arc<dyn Fn(&str) -> Result<StateValue, StateVariableError> + Send + Sync>;
|
||||
Arc<dyn Fn(&str) -> Result<Box<dyn Reflect>, StateVariableError> + Send + Sync>;
|
||||
|
||||
/// Type pour les fonctions de sérialisation de valeurs vers des chaînes
|
||||
pub type ValueSerializer =
|
||||
|
||||
@@ -295,7 +295,7 @@ impl StateVariable {
|
||||
self.description = description;
|
||||
}
|
||||
|
||||
pub fn set_default(&mut self, value: StateValue) -> Result<(), StateValueError> {
|
||||
pub fn set_default(&mut self, value: &StateValue) -> Result<(), StateValueError> {
|
||||
if self.as_state_var_type() != value.as_state_var_type() {
|
||||
return Err(StateValueError::TypeError(
|
||||
"value does not have the right type".to_string(),
|
||||
@@ -353,7 +353,7 @@ impl StateVariable {
|
||||
self.send_events = false;
|
||||
}
|
||||
|
||||
pub fn set_value_parseur(&mut self, parser: StringValueParser) -> Result<(), StateValueError> {
|
||||
pub fn set_value_parser(&mut self, parser: StringValueParser) -> Result<(), StateValueError> {
|
||||
if self.as_state_var_type() == StateVarType::String {
|
||||
self.parse = Some(parser);
|
||||
return Ok(());
|
||||
|
||||
@@ -30,6 +30,8 @@ mod values_from_uri;
|
||||
mod values_from_uuid;
|
||||
mod values_from_vec_u8;
|
||||
|
||||
mod values_from_str;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use chrono::{DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime};
|
||||
|
||||
21
pmoupnp/src/variable_types/values_from_str.rs
Normal file
21
pmoupnp/src/variable_types/values_from_str.rs
Normal file
@@ -0,0 +1,21 @@
|
||||
use std::convert::TryFrom;
|
||||
use url::Url;
|
||||
|
||||
use crate::variable_types::{StateValue, StateValueError};
|
||||
|
||||
impl TryFrom<&str> for StateValue {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(s: &str) -> Result<Self, StateValueError> {
|
||||
Ok(StateValue::String(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
// Conversion depuis String
|
||||
impl TryFrom<String> for StateValue {
|
||||
type Error = StateValueError;
|
||||
|
||||
fn try_from(s: String) -> Result<Self, StateValueError> {
|
||||
Ok(StateValue::String(s))
|
||||
}
|
||||
}
|
||||
@@ -400,6 +400,7 @@ button.active {
|
||||
.message {
|
||||
color: #d4d4d4;
|
||||
word-break: break-word;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
|
||||
Reference in New Issue
Block a user