From ea6dd03ac073818b5961942a578bf32277700594 Mon Sep 17 00:00:00 2001 From: Eric Coissac Date: Sun, 26 Oct 2025 23:49:20 +0100 Subject: [PATCH] Je ne sais pas trop --- .DS_Store | Bin 12292 -> 10244 bytes .gitignore | 5 ++- pmodidl/src/lib.rs | 3 ++ pmomediaserver/src/content_handler.rs | 41 ++++++++++++++++++----- pmomediaserver/src/source_registry.rs | 1 + pmoparadise/src/source.rs | 2 ++ pmoplaylist/src/lib.rs | 1 + pmoqobuz/src/didl.rs | 2 ++ pmoqobuz/src/source.rs | 2 ++ pmosource/src/api.rs | 3 ++ pmosource/src/lib.rs | 1 + pmoupnp/src/services/service_instance.rs | 9 +++-- pmoupnp/src/soap/builder.rs | 15 ++++----- pmoupnp/src/soap/fault.rs | 1 + pmoupnp/src/soap/mod.rs | 4 +-- 15 files changed, 66 insertions(+), 24 deletions(-) diff --git a/.DS_Store b/.DS_Store index 6f6e8ab5a26c65fd955e63beca276db4886aa0cf..a6136655f5bed994ff482494c8f7fd60434136c4 100644 GIT binary patch delta 138 zcmZokXbF&DU|?W$DortDU{C-uIe-{M3-C-V6q~3gIoUvmMH0woPzPd0pg03V2}1=# ziDyoJa#GI5i)QQ-ALMRk=V0MrRNrhOc%5(K0~JO=W{?h`LEJ#X6=c}Pi{F_i^Xup` YGE6q%QRZNRSfnvIMQ7$_O)+L>0OxobjQ{`u literal 12292 zcmeHNU2Ggz6+UO2#4}?%nS`Xt8WL}9N<*+6|0hmby8cN^Qdv%52O2G8zn zc6MzyR;mb&~5@^WA&Tch5O@?pYHNk5NBpcQoJ$qZ~%T zwLw%M7o|!b*>%$O4-`BS_$?>~byF#V&(i|s(cUsyR3>kErd)K~;-bLx^t>W)$&oOS zFpw~iFpw~iFmU?}1Z$`oTkmhbp#+gIkTCF`V?e}*G>WJ*L4D1yW$3_3e8P+O;uBsT zxW+y}K1&C6CaACZwS)pAp{RW*2U`sG;dq`F;;1u0ea)|ZII#~W4tnNbhr+UV;L=i@ zIOEsJIbk4Spq_yb+SFDO!p5CUF}S-4fpkQrU!-wu3pvDJspRSPcJMvg`)ekuObIw zf?8rKKXW_!hI0m7vUWztL-;XsL#Z-VPkQ2r5{uoBV;Bzq< zL0?!&6*3kQHy%dfAyu;&lYc8F-vD`xH!7uwq)xciX0*8q9SdmL!Sw>#EVJD*-@X33g|<;^Y-)UWcKupy*WWihgkArU-t~|1 zced-liI%Zt_on}k5Mt$pcrN5!XJzl&THkPIx^HlBos_}Bqp=;zXMHQ!G!-IJB{x;m z@s`K@Bu7JQP0iocQa+mAl$xE>=jZIaTb!}4SPRAu4>_NcxIJCAmXYh3&AU#uQ1ljz zo%yVjuR7U^HR3oz*qf_Zs}*rI@44m5m=s(vjH+i%TadGtt#L>Lx=XY)RjSx-(R;)y zd#F1yJ-&L{bIp79w}0p(J%h&{7(Y4r@f$7McNjZ&>H0j9N5zW$m~EBMuGnVf(z&dc zw~D4+T%29DJv--EXNuW^wUZ|_?Z#bu_qC*2Qy-|kibGVRL!GPU?8{ZV^0@5dKwC<` zU}Auit|@vkjptQOzUgR7rOt~tV`U3xQ&r{Tdk?1cGal46vVmY{m#&{#Le|UhFa$C1 z-ab8b*2(59$1pV?HgrT!%`W>|8xIE$kKd=K=JIZ#02RHR+!;L1jHFT*Mb8siuQFA# ziWt?8g6>Z|m`crim$GGRY6Xcm<7dJ1Q+O`t4Q~YOglSjYGG0nb+ES-g^_*yO#567Q zM$6uPcTLX-gkcw#ZKV1z8Ggpk_>HQn9hpP^yhv`f-Tuw`S)}<>)J_9*ANG^y@a?}s zpQA6*v-B-`j$Wjn(9h{*dWBx4*XZ~3CwiU!N`I$+(A!L`@h)!TySbe^xRa0aFdySF z{urO)hk1g}@;qPQNBJTza)Dhg@nyckpW-L^)BF^FfxpaO#!=mh zCk4q<{js-zaN#7`AR{(h*0HCN1Yb zW{bk4QmyaT_iyH9XWDsSg~d;q2^4EG-H=EK~}eLTST^G7+uBQW9#p5!S$&C`4yMtqSM zILn{pC3d*TE4&IrUgN7UZ*eunZ|!N6PoM~ymr%rkYYD+g t{v3~5{v3~5{v3}w_)3$4nE-RmukjYg|NI{TlJ$SHFX?3c|36y){|T&=Z^Qrq diff --git a/.gitignore b/.gitignore index a0b3cab9..6f9e14a2 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,8 @@ **/*.o **/*.o.d **/*.a +**/*.aif +**/*.aiff xxx /dcai/ **/.pmomusic.yml @@ -15,7 +17,7 @@ xxx **/.pmomusic_audio/** /.pmomusic .DS_Store -/target/ +target /.pmomusic_covers /.pmomusic_audio/** C/src/soxr-0.1.3/Release/tests @@ -28,3 +30,4 @@ all.txt pmo_src.txt upmpdcli/ /*.xml +test_upnp \ No newline at end of file diff --git a/pmodidl/src/lib.rs b/pmodidl/src/lib.rs index edd6b123..a85d5e0f 100644 --- a/pmodidl/src/lib.rs +++ b/pmodidl/src/lib.rs @@ -125,6 +125,9 @@ pub struct Container { #[serde(rename = "@childCount", skip_serializing_if = "Option::is_none")] pub child_count: Option, + #[serde(rename = "@searchable", skip_serializing_if = "Option::is_none")] + pub searchable: Option, + #[serde(rename = "dc:title", alias = "title")] pub title: String, diff --git a/pmomediaserver/src/content_handler.rs b/pmomediaserver/src/content_handler.rs index f1ea8f2a..b17e420b 100644 --- a/pmomediaserver/src/content_handler.rs +++ b/pmomediaserver/src/content_handler.rs @@ -28,7 +28,9 @@ fn to_didl_lite(containers: &[Container], items: &[pmodidl::Item]) -> Result{}", body)) } /// Handler pour le service ContentDirectory @@ -97,8 +99,8 @@ impl ContentHandler { if object_id == "0" { // Retourner le container racine let root = self.build_root_container().await; - let didl = to_didl_lite(&[root], &[])?; - Ok((didl, 1, 1, 0)) + let didl = to_didl_lite(&[root], &[])?; + Ok((didl, 1, 1, 1)) } else { // Essayer de trouver l'objet dans les sources // Vérifier si c'est un container racine d'une source @@ -219,6 +221,8 @@ impl ContentHandler { .root_container() .await .map_err(|e| format!("Failed to get root container: {}", e))?; + let mut container = container; + container.searchable = container.searchable.or_else(|| Some("1".to_string())); containers.push(container); } @@ -236,7 +240,14 @@ impl ContentHandler { let returned = paginated.len(); let didl = to_didl_lite(&paginated, &[])?; - Ok((didl, returned as u32, total as u32, 0)) + // Aggregate update IDs across all sources + let mut combined_id = 0u32; + for source in list_all_sources().await { + combined_id = combined_id.wrapping_add(source.update_id().await); + } + let update_id = combined_id.max(1); + + Ok((didl, returned as u32, total as u32, update_id)) } /// Browse le container racine d'une source spécifique @@ -317,6 +328,7 @@ impl ContentHandler { parent_id: "-1".to_string(), restricted: Some("1".to_string()), child_count: Some(child_count.to_string()), + searchable: Some("1".to_string()), title: "PMOMusic".to_string(), class: "object.container".to_string(), containers: vec![], @@ -367,7 +379,19 @@ impl ContentHandler { let total = (all_containers.len() + all_items.len()) as u32; let didl = to_didl_lite(&all_containers, &all_items)?; - Ok((didl, total, total, 0)) + // Compute a global update ID from active sources, ensure it starts at 1 + let update_id = if total > 0 { + let sources = list_all_sources().await; + let mut combined_id = 0u32; + for source in sources { + combined_id = combined_id.wrapping_add(source.update_id().await); + } + combined_id.max(1) + } else { + 1 + }; + + Ok((didl, total, total, update_id)) } /// Retourne les capacités de recherche @@ -392,7 +416,7 @@ impl ContentHandler { combined_id = combined_id.wrapping_add(source.update_id().await); } - combined_id + combined_id.max(1) } } @@ -419,9 +443,10 @@ mod tests { let result = handler.browse("0", "BrowseDirectChildren", 0, 0).await; assert!(result.is_ok()); - let (didl, returned, total, _) = result.unwrap(); + let (didl, returned, total, update_id) = result.unwrap(); assert_eq!(returned, 0); assert_eq!(total, 0); + assert_eq!(update_id, 1); assert!(didl.contains("DIDL-Lite")); } @@ -429,6 +454,6 @@ mod tests { async fn test_get_system_update_id() { let handler = ContentHandler::new(); let update_id = handler.get_system_update_id().await; - assert_eq!(update_id, 0); // No sources registered + assert_eq!(update_id, 1); // No sources registered -> minimum 1 } } diff --git a/pmomediaserver/src/source_registry.rs b/pmomediaserver/src/source_registry.rs index bf272701..8fa19042 100644 --- a/pmomediaserver/src/source_registry.rs +++ b/pmomediaserver/src/source_registry.rs @@ -249,6 +249,7 @@ mod tests { parent_id: "0".to_string(), restricted: Some("1".to_string()), child_count: Some("0".to_string()), + searchable: Some("1".to_string()), title: self.name.clone(), class: "object.container".to_string(), containers: vec![], diff --git a/pmoparadise/src/source.rs b/pmoparadise/src/source.rs index 99c69616..d6b6e8cb 100644 --- a/pmoparadise/src/source.rs +++ b/pmoparadise/src/source.rs @@ -245,6 +245,7 @@ impl RadioParadiseSource { parent_id: "0".to_string(), restricted: Some("1".to_string()), child_count: Some(ALL_CHANNELS.len().to_string()), + searchable: Some("1".to_string()), title: "Radio Paradise".to_string(), class: "object.container".to_string(), containers: vec![], @@ -262,6 +263,7 @@ impl RadioParadiseSource { parent_id: "radio-paradise".to_string(), restricted: Some("1".to_string()), child_count: Some(len.to_string()), + searchable: Some("1".to_string()), title: descriptor.display_name.to_string(), class: "object.container.playlistContainer".to_string(), containers: vec![], diff --git a/pmoplaylist/src/lib.rs b/pmoplaylist/src/lib.rs index 46824514..f7a924b0 100644 --- a/pmoplaylist/src/lib.rs +++ b/pmoplaylist/src/lib.rs @@ -504,6 +504,7 @@ impl FifoPlaylist { parent_id: parent_id.into(), restricted: Some("1".to_string()), child_count: Some(inner.queue.len().to_string()), + searchable: Some("1".to_string()), title: inner.title.clone(), class: "object.container.playlistContainer".to_string(), containers: vec![], diff --git a/pmoqobuz/src/didl.rs b/pmoqobuz/src/didl.rs index a06f9c16..2b8866c5 100644 --- a/pmoqobuz/src/didl.rs +++ b/pmoqobuz/src/didl.rs @@ -37,6 +37,7 @@ impl ToDIDL for Album { parent_id: parent_id.to_string(), restricted: Some("1".to_string()), child_count: self.tracks_count.map(|c| c.to_string()), + searchable: Some("1".to_string()), title: self.formatted_title(), class: "object.container.album.musicAlbum".to_string(), containers: Vec::new(), @@ -134,6 +135,7 @@ impl ToDIDL for Playlist { parent_id: parent_id.to_string(), restricted: Some("1".to_string()), child_count: self.tracks_count.map(|c| c.to_string()), + searchable: Some("1".to_string()), title: self.name.clone(), class: "object.container.playlistContainer".to_string(), containers: Vec::new(), diff --git a/pmoqobuz/src/source.rs b/pmoqobuz/src/source.rs index ec45db48..6bcdffcb 100644 --- a/pmoqobuz/src/source.rs +++ b/pmoqobuz/src/source.rs @@ -275,6 +275,7 @@ impl MusicSource for QobuzSource { parent_id: "0".to_string(), restricted: Some("1".to_string()), child_count: Some("2".to_string()), // Favorites + Search (simplified) + searchable: Some("1".to_string()), title: "Qobuz".to_string(), class: "object.container".to_string(), containers: vec![ @@ -284,6 +285,7 @@ impl MusicSource for QobuzSource { parent_id: "qobuz".to_string(), restricted: Some("1".to_string()), child_count: None, // Will be determined when browsed + searchable: Some("1".to_string()), title: "My Favorites".to_string(), class: "object.container".to_string(), containers: vec![], diff --git a/pmosource/src/api.rs b/pmosource/src/api.rs index 2ca70758..68202dd8 100644 --- a/pmosource/src/api.rs +++ b/pmosource/src/api.rs @@ -134,6 +134,8 @@ pub struct SourceRootContainer { pub class: String, /// Nombre d'enfants pub child_count: Option, + /// Indique si le container est searchable ("1" ou "0") + pub searchable: Option, } /// Message d'erreur @@ -639,6 +641,7 @@ async fn get_source_root(Path(id): Path) -> impl IntoResponse { title: container.title, class: container.class, child_count: container.child_count, + searchable: container.searchable, }; (StatusCode::OK, Json(root)).into_response() } diff --git a/pmosource/src/lib.rs b/pmosource/src/lib.rs index 6d159567..3e0cfa01 100644 --- a/pmosource/src/lib.rs +++ b/pmosource/src/lib.rs @@ -962,6 +962,7 @@ mod tests { parent_id: "0".to_string(), restricted: Some("1".to_string()), child_count: Some("0".to_string()), + searchable: Some("1".to_string()), title: "Test Source".to_string(), class: "object.container".to_string(), containers: vec![], diff --git a/pmoupnp/src/services/service_instance.rs b/pmoupnp/src/services/service_instance.rs index 6b6fda9f..5686b9aa 100644 --- a/pmoupnp/src/services/service_instance.rs +++ b/pmoupnp/src/services/service_instance.rs @@ -979,8 +979,7 @@ impl ServiceInstance { // Essayer de downcaster vers des types primitifs courants if let Some(v) = value.as_any().downcast_ref::() { - // Échapper les caractères XML spéciaux - return escape(v).to_string(); + return v.clone(); } else if let Some(v) = value.as_any().downcast_ref::() { return v.to_string(); } else if let Some(v) = value.as_any().downcast_ref::() { @@ -1000,7 +999,7 @@ impl ServiceInstance { } else if let Some(v) = value.as_any().downcast_ref::() { return if *v { "1" } else { "0" }.to_string(); } else if let Some(v) = value.as_any().downcast_ref::() { - return escape(&v.to_string()).to_string(); + return v.to_string(); } // Pour les structures complexes, essayer de sérialiser avec bevy_reflect @@ -1345,7 +1344,7 @@ async fn control_handler(State(instance): State>, body: Str match action_instance_for_run.run(soap_values).await { Ok(output_data) => { // Convertir ActionData (Reflect) → HashMap pour SOAP - let mut soap_values = HashMap::new(); + let mut soap_values: Vec<(String, String)> = Vec::new(); for arg_inst in action_instance.arguments_set().all() { let arg_model = arg_inst.as_ref().get_model(); @@ -1353,7 +1352,7 @@ async fn control_handler(State(instance): State>, body: Str if let Some(reflect_value) = output_data.get(arg_inst.get_name()) { let soap_string = ServiceInstance::reflect_to_string(reflect_value.as_ref()); - soap_values.insert(arg_inst.get_name().to_string(), soap_string); + soap_values.push((arg_inst.get_name().to_string(), soap_string)); } } } diff --git a/pmoupnp/src/soap/builder.rs b/pmoupnp/src/soap/builder.rs index 3b0a7ca8..6c760f6a 100644 --- a/pmoupnp/src/soap/builder.rs +++ b/pmoupnp/src/soap/builder.rs @@ -1,6 +1,5 @@ //! Construction de réponses SOAP -use std::collections::HashMap; use xmltree::{Element, XMLNode}; /// Construit une réponse SOAP UPnP @@ -17,13 +16,12 @@ use xmltree::{Element, XMLNode}; pub fn build_soap_response( service_urn: &str, action: &str, - values: HashMap, + values: Vec<(String, String)>, ) -> Result { // Construire l'élément de réponse // Format: - let response_name = format!("{}Response", action); + let response_name = format!("u:{}Response", action); let mut response_elem = Element::new(&response_name); - response_elem.namespace = Some(service_urn.to_string()); response_elem .attributes .insert("xmlns:u".to_string(), service_urn.to_string()); @@ -54,6 +52,7 @@ pub fn build_soap_response( // Sérialiser en XML let mut buf = Vec::new(); let config = xmltree::EmitterConfig::new() + .write_document_declaration(true) .perform_indent(true) .indent_string(" "); envelope.write_with_config(&mut buf, config)?; @@ -67,9 +66,9 @@ mod tests { #[test] fn test_build_response() { - let mut values = HashMap::new(); - values.insert("Track".to_string(), "5".to_string()); - values.insert("TrackDuration".to_string(), "00:03:45".to_string()); + let mut values = Vec::new(); + values.push(("Track".to_string(), "5".to_string())); + values.push(("TrackDuration".to_string(), "00:03:45".to_string())); let xml = build_soap_response( "urn:schemas-upnp-org:service:AVTransport:1", @@ -86,7 +85,7 @@ mod tests { #[test] fn test_build_empty_response() { - let values = HashMap::new(); + let values = Vec::new(); let xml = build_soap_response("urn:schemas-upnp-org:service:AVTransport:1", "Stop", values) .unwrap(); diff --git a/pmoupnp/src/soap/fault.rs b/pmoupnp/src/soap/fault.rs index 917bbe85..3689b483 100644 --- a/pmoupnp/src/soap/fault.rs +++ b/pmoupnp/src/soap/fault.rs @@ -129,6 +129,7 @@ pub fn build_soap_fault( // Sérialiser let mut buf = Vec::new(); let config = xmltree::EmitterConfig::new() + .write_document_declaration(true) .perform_indent(true) .indent_string(" "); envelope.write_with_config(&mut buf, config)?; diff --git a/pmoupnp/src/soap/mod.rs b/pmoupnp/src/soap/mod.rs index a34107d5..c2f7a63f 100644 --- a/pmoupnp/src/soap/mod.rs +++ b/pmoupnp/src/soap/mod.rs @@ -39,8 +39,8 @@ //! assert_eq!(action.args.get("InstanceID"), Some(&"0".to_string())); //! //! // Construire une réponse -//! let mut values = std::collections::HashMap::new(); -//! values.insert("CurrentTrack".to_string(), "5".to_string()); +//! let mut values = Vec::new(); +//! values.push(("CurrentTrack".to_string(), "5".to_string())); //! let response = build_soap_response( //! "urn:schemas-upnp-org:service:AVTransport:1", //! "GetPositionInfo",