On applique le nouveau trait à PMOdIdl.

This commit is contained in:
2025-11-28 18:41:53 +01:00
parent f2dcd55549
commit c972182b3b
3 changed files with 202 additions and 1 deletions

15
Cargo.lock generated
View File

@@ -3173,10 +3173,12 @@ version = "0.1.0"
dependencies = [ dependencies = [
"bevy_reflect", "bevy_reflect",
"bevy_reflect_derive", "bevy_reflect_derive",
"pmoutils",
"quick-xml 0.38.4", "quick-xml 0.38.4",
"serde", "serde",
"utoipa", "utoipa",
"utoipa-swagger-ui", "utoipa-swagger-ui",
"xmltree 0.10.3",
] ]
[[package]] [[package]]
@@ -3229,6 +3231,7 @@ dependencies = [
"pmoserver", "pmoserver",
"pmosource", "pmosource",
"pmoupnp", "pmoupnp",
"pmoutils",
"quick-xml 0.38.4", "quick-xml 0.38.4",
"serde", "serde",
"serde_json", "serde_json",
@@ -3423,7 +3426,7 @@ dependencies = [
"url", "url",
"utoipa", "utoipa",
"uuid", "uuid",
"xmltree", "xmltree 0.11.0",
] ]
[[package]] [[package]]
@@ -3436,6 +3439,7 @@ dependencies = [
"quick-xml 0.38.4", "quick-xml 0.38.4",
"sysinfo", "sysinfo",
"users", "users",
"xmltree 0.10.3",
] ]
[[package]] [[package]]
@@ -5804,6 +5808,15 @@ version = "0.8.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f"
[[package]]
name = "xmltree"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb"
dependencies = [
"xml-rs",
]
[[package]] [[package]]
name = "xmltree" name = "xmltree"
version = "0.11.0" version = "0.11.0"

View File

@@ -10,3 +10,5 @@ utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
quick-xml = { version = "0.38.3", features = ["serialize"] } quick-xml = { version = "0.38.3", features = ["serialize"] }
bevy_reflect = "0.17.1" bevy_reflect = "0.17.1"
bevy_reflect_derive = "0.17.1" bevy_reflect_derive = "0.17.1"
pmoutils = { path = "../pmoutils" }
xmltree = "0.10"

View File

@@ -3,8 +3,10 @@
//! Parser et utilitaires pour le format DIDL-Lite utilisé dans UPnP/DLNA. //! Parser et utilitaires pour le format DIDL-Lite utilisé dans UPnP/DLNA.
use bevy_reflect::Reflect; use bevy_reflect::Reflect;
use pmoutils::ToXmlElement;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::fmt::Write; use std::fmt::Write;
use xmltree::{Element, XMLNode};
// ============= Couche d'abstraction générique ============= // ============= Couche d'abstraction générique =============
@@ -274,6 +276,26 @@ impl Default for DIDLLite {
} }
impl DIDLLite { impl DIDLLite {
/// Applique les namespaces sur un élément xmltree.
fn set_namespaces(&self, elem: &mut Element) {
elem.attributes.insert("xmlns".into(), self.xmlns.clone());
if let Some(ref upnp) = self.xmlns_upnp {
elem.attributes.insert("xmlns:upnp".into(), upnp.clone());
}
if let Some(ref dc) = self.xmlns_dc {
elem.attributes.insert("xmlns:dc".into(), dc.clone());
}
if let Some(ref dlna) = self.xmlns_dlna {
elem.attributes.insert("xmlns:dlna".into(), dlna.clone());
}
if let Some(ref sec) = self.xmlns_sec {
elem.attributes.insert("xmlns:sec".into(), sec.clone());
}
if let Some(ref pv) = self.xmlns_pv {
elem.attributes.insert("xmlns:pv".into(), pv.clone());
}
}
/// Itère sur tous les containers de manière récursive /// Itère sur tous les containers de manière récursive
pub fn all_containers(&self) -> impl Iterator<Item = &Container> { pub fn all_containers(&self) -> impl Iterator<Item = &Container> {
AllContainersIter::new(&self.containers) AllContainersIter::new(&self.containers)
@@ -378,6 +400,19 @@ impl Container {
} }
impl Item { impl Item {
/// Formate la date pour satisfaire les clients stricts (YYYY-MM-DD). Si seule
/// l'année est fournie, on complète avec "-01-01".
fn normalized_date(&self) -> Option<String> {
self.date.as_ref().map(|d| {
let trimmed = d.trim();
if trimmed.len() == 4 && trimmed.chars().all(|c| c.is_ascii_digit()) {
format!("{}-01-01", trimmed)
} else {
trimmed.to_string()
}
})
}
/// Itère sur les ressources audio uniquement /// Itère sur les ressources audio uniquement
pub fn audio_resources(&self) -> impl Iterator<Item = &Resource> { pub fn audio_resources(&self) -> impl Iterator<Item = &Resource> {
self.resources self.resources
@@ -493,6 +528,157 @@ impl Item {
} }
} }
// ============= Implémentation ToXmlElement =============
fn text_element(name: &str, value: &str) -> Element {
let mut e = Element::new(name);
e.children.push(XMLNode::Text(value.to_string()));
e
}
impl ToXmlElement for DIDLLite {
fn to_xml_element(&self) -> Element {
let mut root = Element::new("DIDL-Lite");
self.set_namespaces(&mut root);
for c in &self.containers {
root.children.push(XMLNode::Element(c.to_xml_element()));
}
for i in &self.items {
root.children.push(XMLNode::Element(i.to_xml_element()));
}
root
}
}
impl ToXmlElement for Container {
fn to_xml_element(&self) -> Element {
let mut elem = Element::new("container");
elem.attributes.insert("id".into(), self.id.clone());
elem.attributes.insert("parentID".into(), self.parent_id.clone());
if let Some(ref r) = self.restricted {
elem.attributes.insert("restricted".into(), r.clone());
}
if let Some(ref cc) = self.child_count {
elem.attributes.insert("childCount".into(), cc.clone());
}
if let Some(ref searchable) = self.searchable {
elem.attributes.insert("searchable".into(), searchable.clone());
}
elem.children.push(XMLNode::Element(text_element("dc:title", &self.title)));
elem.children.push(XMLNode::Element(text_element("upnp:class", &self.class)));
for c in &self.containers {
elem.children.push(XMLNode::Element(c.to_xml_element()));
}
for i in &self.items {
elem.children.push(XMLNode::Element(i.to_xml_element()));
}
elem
}
}
impl ToXmlElement for Item {
fn to_xml_element(&self) -> Element {
let mut elem = Element::new("item");
elem.attributes.insert("id".into(), self.id.clone());
elem.attributes.insert("parentID".into(), self.parent_id.clone());
if let Some(ref r) = self.restricted {
elem.attributes.insert("restricted".into(), r.clone());
}
elem.children.push(XMLNode::Element(text_element("dc:title", &self.title)));
if let Some(ref c) = self.creator {
elem.children.push(XMLNode::Element(text_element("dc:creator", c)));
}
elem.children
.push(XMLNode::Element(text_element("upnp:class", &self.class)));
if let Some(ref artist) = self.artist {
elem.children
.push(XMLNode::Element(text_element("upnp:artist", artist)));
}
if let Some(ref album) = self.album {
elem.children
.push(XMLNode::Element(text_element("upnp:album", album)));
}
if let Some(ref genre) = self.genre {
elem.children
.push(XMLNode::Element(text_element("upnp:genre", genre)));
}
if let Some(ref art) = self.album_art {
elem.children
.push(XMLNode::Element(text_element("upnp:albumArtURI", art)));
}
if let Some(date) = self.normalized_date() {
elem.children
.push(XMLNode::Element(text_element("dc:date", &date)));
}
if let Some(ref track) = self.original_track_number {
elem.children.push(XMLNode::Element(text_element(
"upnp:originalTrackNumber",
track,
)));
}
for res in &self.resources {
elem.children.push(XMLNode::Element(res.to_xml_element()));
}
for desc in &self.descriptions {
elem.children.push(XMLNode::Element(desc.to_xml_element()));
}
elem
}
}
impl ToXmlElement for Resource {
fn to_xml_element(&self) -> Element {
let mut elem = Element::new("res");
elem.attributes
.insert("protocolInfo".into(), self.protocol_info.clone());
if let Some(ref bps) = self.bits_per_sample {
elem.attributes.insert("bitsPerSample".into(), bps.clone());
}
if let Some(ref freq) = self.sample_frequency {
elem.attributes.insert("sampleFrequency".into(), freq.clone());
}
if let Some(ref ch) = self.nr_audio_channels {
elem.attributes.insert("nrAudioChannels".into(), ch.clone());
}
if let Some(ref dur) = self.duration {
elem.attributes.insert("duration".into(), dur.clone());
}
elem.children.push(XMLNode::Text(self.url.clone()));
elem
}
}
impl ToXmlElement for Description {
fn to_xml_element(&self) -> Element {
let mut elem = Element::new("desc");
if let Some(ref id) = self.id {
elem.attributes.insert("id".into(), id.clone());
}
if let Some(ref ns) = self.namespace {
elem.attributes.insert("nameSpace".into(), ns.clone());
}
if let Some(ref gain) = self.track_gain {
elem.children
.push(XMLNode::Element(text_element("track_gain", gain)));
}
if let Some(ref peak) = self.track_peak {
elem.children
.push(XMLNode::Element(text_element("track_peak", peak)));
}
elem
}
}
// ============= Itérateurs personnalisés ============= // ============= Itérateurs personnalisés =============
struct AllContainersIter<'a> { struct AllContainersIter<'a> {