diff --git a/Blackboard/Todo/mdns_sd_migration.md b/Blackboard/Todo/mdns_sd_migration.md new file mode 100644 index 00000000..2bacaf06 --- /dev/null +++ b/Blackboard/Todo/mdns_sd_migration.md @@ -0,0 +1,306 @@ +# Migration `mdns` → `mdns-sd` + +## Contexte + +La crate `mdns 3.0.0` (dernière version, 2021, abandonnée) s'appuie sur `dns-parser 0.8.0` +qui rejette les labels DNS non-ASCII avec `LabelIsNotAscii`. Les appareils Apple utilisent +U+2019 (RIGHT SINGLE QUOTATION MARK) dans leurs noms (ex : "Sophie's MacBook Air"), ce qui +spamme les logs en WARN à chaque paquet mDNS reçu. + +`mdns-sd 0.19` (avril 2026, activement maintenu) gère l'UTF-8 correctement, fournit une API +de plus haut niveau (service pré-assemblé), et ne nécessite pas `async-std`. + +## Périmètre + +Deux fichiers à modifier, un fichier à nettoyer : + +| Fichier | Rôle | +|---|---| +| `pmocontrol/Cargo.toml` | Dépendances | +| `pmocontrol/src/control_point.rs` | Thread de découverte mDNS (lignes 145–195) | +| `pmocontrol/src/discovery/chromecast_discovery.rs` | Parsing des réponses mDNS | +| `pmoserver/src/logs/mod.rs` | Filtre de bruit `mdns=error` devenu inutile | + +--- + +## Étape 1 — `pmocontrol/Cargo.toml` + +### Supprimer +```toml +mdns = "3.0" +``` + +Vérifier si `async-std` et `futures-util` sont utilisés **ailleurs** que dans le thread mDNS. +D'après l'analyse : +- `async_std` : uniquement `control_point.rs:161` → **supprimer** +- `futures-util` : uniquement `control_point.rs:150,167` → **supprimer** + +### Ajouter +```toml +mdns-sd = "0.19" +``` + +--- + +## Étape 2 — `pmocontrol/src/control_point.rs` + +### Code actuel (lignes 145–195) à remplacer intégralement + +```rust +// Thread de découverte mDNS pour Chromecast +let registry_for_mdns = Arc::clone(®istry); +let udn_cache_for_mdns = Arc::clone(&udn_cache); +thread::spawn(move || { + use crate::discovery::ChromecastDiscoveryManager; + use futures_util::StreamExt; + + let mut discovery_manager = + ChromecastDiscoveryManager::new(registry_for_mdns, udn_cache_for_mdns); + + debug!("Starting mDNS discovery thread for Chromecast devices"); + + const SERVICE_NAME: &str = "_googlecast._tcp.local"; + + async_std::task::block_on(async { + match mdns::discover::all(SERVICE_NAME, Duration::from_secs(15)) { + Ok(discovery) => { + let stream = discovery.listen(); + futures_util::pin_mut!(stream); + debug!("mDNS discovery stream started for Chromecast devices"); + while let Some(result) = stream.next().await { + match result { + Ok(response) => { + debug!("Received mDNS response with {} records", response.records().count()); + discovery_manager.handle_mdns_response(response); + } + Err(e) => { warn!("mDNS discovery error: {}", e); } + } + } + warn!("mDNS discovery stream ended unexpectedly"); + } + Err(e) => { error!("Failed to start mDNS discovery: {}", e); } + } + }); +}); +``` + +### Nouveau code + +```rust +// Thread de découverte mDNS pour Chromecast +let registry_for_mdns = Arc::clone(®istry); +let udn_cache_for_mdns = Arc::clone(&udn_cache); +thread::spawn(move || { + use crate::discovery::ChromecastDiscoveryManager; + use mdns_sd::{ServiceDaemon, ServiceEvent}; + + let mut discovery_manager = + ChromecastDiscoveryManager::new(registry_for_mdns, udn_cache_for_mdns); + + debug!("Starting mDNS discovery thread for Chromecast devices"); + + // Note: mdns-sd requires the trailing dot in the service type + const SERVICE_TYPE: &str = "_googlecast._tcp.local."; + + let daemon = match ServiceDaemon::new() { + Ok(d) => d, + Err(e) => { + error!("Failed to create mDNS daemon: {}", e); + return; + } + }; + + let receiver = match daemon.browse(SERVICE_TYPE) { + Ok(r) => r, + Err(e) => { + error!("Failed to start mDNS browse for {}: {}", SERVICE_TYPE, e); + return; + } + }; + + debug!("mDNS discovery started for Chromecast devices"); + + while let Ok(event) = receiver.recv() { + match event { + ServiceEvent::ServiceResolved(info) => { + debug!( + fullname = info.get_fullname(), + host = info.get_hostname(), + port = info.get_port(), + "mDNS Chromecast service resolved" + ); + discovery_manager.handle_service_resolved(&info); + } + ServiceEvent::ServiceRemoved(_service_type, fullname) => { + debug!(fullname = %fullname, "mDNS Chromecast service removed"); + // Pas de retrait actif du registre : le timeout habituel s'en charge + } + _ => {} + } + } + + warn!("mDNS discovery receiver closed"); +}); +``` + +### Points d'attention +- Le point final `.` dans `"_googlecast._tcp.local."` est **obligatoire** pour `mdns-sd`. +- `receiver.recv()` est bloquant synchrone — pas besoin d'async runtime. +- `ServiceDaemon` gère son propre thread interne ; inutile de relancer manuellement. + +--- + +## Étape 3 — `pmocontrol/src/discovery/chromecast_discovery.rs` + +### Supprimer l'import `mdns` + +```rust +// Supprimer ces uses implicites via le type dans la signature +use std::collections::HashMap; // <- plus nécessaire si on passe par TxtProperties +``` + +### Ajouter l'import `mdns-sd` + +```rust +use mdns_sd::ServiceInfo; +``` + +### Remplacer `handle_mdns_response` par `handle_service_resolved` + +#### Code actuel (lignes 39–179) — ~80 lignes de parsing manuel + +Toute la logique d'extraction PTR / A / AAAA / SRV / TXT disparaît. + +#### Nouveau code + +```rust +/// Traite un service Chromecast résolu par mDNS-SD. +/// +/// `ServiceInfo` arrive pré-assemblé : plus besoin de jointure manuelle +/// des enregistrements PTR / A / SRV / TXT. +pub fn handle_service_resolved(&mut self, info: &ServiceInfo) { + let fullname = info.get_fullname().to_string(); + + debug!("Processing resolved Chromecast service: {}", fullname); + + // Adresse IP : préférer IPv4 + let host = match info + .get_addresses() + .iter() + .find(|a| a.is_ipv4()) + .or_else(|| info.get_addresses().iter().next()) + { + Some(addr) => addr.to_string(), + None => { + warn!("No IP address for Chromecast service: {}", fullname); + return; + } + }; + + let port = info.get_port(); + + // TXT records — API directe par clé + let uuid = info + .get_property_val_str("id") + .unwrap_or_default() + .to_string(); + let uuid = if uuid.is_empty() { + format!("chromecast-{}-{}", host, port) + } else { + uuid + }; + + let model = info.get_property_val_str("md").map(|s| s.to_string()); + + let friendly_name = info + .get_property_val_str("fn") + .map(|s| s.to_string()) + .unwrap_or_else(|| { + // Fallback : extraire depuis le fullname, supprimer le suffixe de service + fullname + .split("._googlecast._tcp.local") + .next() + .unwrap_or("Unknown Chromecast") + .split('-') + .take_while(|part| part.len() != 32) + .collect::>() + .join("-") + .trim() + .to_string() + }); + + debug!( + "Discovered Chromecast: {} at {}:{} (UUID: {}, Model: {:?})", + friendly_name, host, port, uuid, model + ); + + let udn = format!("uuid:{}", uuid); + let default_max_age = 1800u64; + + if !UDNRegistry::should_fetch(self.udn_cache.clone(), &udn, default_max_age) { + debug!("Chromecast {} recently seen, skipping", udn); + return; + } + + let renderer_info = build_renderer_info( + &uuid, + &friendly_name, + &host, + port, + model.as_deref(), + Some("Google Inc."), + ); + + self.device_registry + .write() + .expect("DeviceRegistry mutex lock failed") + .push_renderer(&renderer_info, default_max_age as u32); +} +``` + +### Supprimer +- L'import `use std::collections::HashMap` (plus utilisé) +- Tout le bloc `handle_mdns_response` (lignes 39–179) + +### Conserver sans modification +- `build_renderer_info` (lignes 182–234) +- `extract_host_from_location` / `extract_port_from_location` (lignes 239–258) +- Les tests (lignes 260–287) + +--- + +## Étape 4 — `pmoserver/src/logs/mod.rs` + +Le filtre de bruit `mdns=error` injecté dans `build_filter_with_noise_suppressions` n'est +plus nécessaire. Deux options : + +**Option A (recommandée)** — Supprimer l'entrée du tableau : +```rust +const NOISE_FILTERS: &[(&str, &str)] = &[ + // ("mdns", "mdns=error"), // supprimé : migration vers mdns-sd +]; +``` +Ou supprimer `build_filter_with_noise_suppressions` entièrement si aucun autre bruit n'est +à filtrer, et revenir à `EnvFilter::try_new(base)` direct. + +**Option B** — Laisser en place. La directive `mdns=error` ne cause aucun dommage si la +crate `mdns` n'est plus dans le build (elle sera simplement ignorée). + +--- + +## Résumé des diffs + +| Fichier | Lignes supprimées | Lignes ajoutées | +|---|---|---| +| `Cargo.toml` | `mdns`, `async-std`, `futures-util` | `mdns-sd` | +| `control_point.rs` | ~50 (async block) | ~35 (sync recv loop) | +| `chromecast_discovery.rs` | ~80 (parsing manuel) | ~50 (lecture ServiceInfo) | +| `logs/mod.rs` | ~5 (filtre bruit) | 0 | + +## Vérification + +Après implémentation : +1. `cargo check -p pmocontrol` sans erreurs ni `use of undeclared crate mdns` +2. `cargo check -p pmoserver` sans erreurs +3. Tester la découverte d'un Chromecast en réseau local +4. Vérifier l'absence de `LabelIsNotAscii` dans les logs avec des appareils Apple présents diff --git a/Cargo.lock b/Cargo.lock index f874fddc..9a26616a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,7 +4,7 @@ version = 4 [[package]] name = "PMOMusic" -version = "0.3.41" +version = "0.3.42" dependencies = [ "axum 0.8.7", "console-subscriber", @@ -49,7 +49,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "cipher", "cpufeatures", ] @@ -100,7 +100,7 @@ checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", "bitflags 2.10.0", - "cfg-if 1.0.4", + "cfg-if", "libc", ] @@ -176,27 +176,6 @@ dependencies = [ "syn 2.0.110", ] -[[package]] -name = "async-attributes" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" -dependencies = [ - "quote", - "syn 1.0.109", -] - -[[package]] -name = "async-channel" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" -dependencies = [ - "concurrent-queue", - "event-listener 2.5.3", - "futures-core", -] - [[package]] name = "async-channel" version = "2.5.0" @@ -234,21 +213,6 @@ dependencies = [ "futures-lite", ] -[[package]] -name = "async-global-executor" -version = "2.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" -dependencies = [ - "async-channel 2.5.0", - "async-executor", - "async-io", - "async-lock", - "blocking", - "futures-lite", - "once_cell", -] - [[package]] name = "async-io" version = "2.6.0" @@ -256,7 +220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" dependencies = [ "autocfg", - "cfg-if 1.0.4", + "cfg-if", "concurrent-queue", "futures-io", "futures-lite", @@ -273,7 +237,7 @@ version = "3.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" dependencies = [ - "event-listener 5.4.1", + "event-listener", "event-listener-strategy", "pin-project-lite", ] @@ -295,14 +259,14 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" dependencies = [ - "async-channel 2.5.0", + "async-channel", "async-io", "async-lock", "async-signal", "async-task", "blocking", - "cfg-if 1.0.4", - "event-listener 5.4.1", + "cfg-if", + "event-listener", "futures-lite", "rustix 1.1.2", ] @@ -327,7 +291,7 @@ dependencies = [ "async-io", "async-lock", "atomic-waker", - "cfg-if 1.0.4", + "cfg-if", "futures-core", "futures-io", "rustix 1.1.2", @@ -336,66 +300,17 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "async-std" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" -dependencies = [ - "async-attributes", - "async-channel 1.9.0", - "async-global-executor", - "async-io", - "async-lock", - "async-process", - "crossbeam-utils", - "futures-channel", - "futures-core", - "futures-io", - "futures-lite", - "gloo-timers", - "kv-log-macro", - "log", - "memchr", - "once_cell", - "pin-project-lite", - "pin-utils", - "slab", - "wasm-bindgen-futures", -] - -[[package]] -name = "async-stream" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22068c0c19514942eefcfd4daf8976ef1aad84e61539f95cd200c35202f80af5" -dependencies = [ - "async-stream-impl 0.2.1", - "futures-core", -] - [[package]] name = "async-stream" version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" dependencies = [ - "async-stream-impl 0.3.6", + "async-stream-impl", "futures-core", "pin-project-lite", ] -[[package]] -name = "async-stream-impl" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f9db3b38af870bf7e5cc649167533b493928e50744e2c30ae350230b414670" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "async-stream-impl" version = "0.3.6" @@ -682,7 +597,7 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde", - "spin", + "spin 0.10.0", "wasm-bindgen", "wasm-bindgen-futures", ] @@ -815,7 +730,7 @@ version = "1.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" dependencies = [ - "async-channel 2.5.0", + "async-channel", "async-task", "futures-io", "futures-lite", @@ -930,12 +845,6 @@ dependencies = [ "target-lexicon", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.4" @@ -1030,7 +939,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" dependencies = [ "castaway", - "cfg-if 1.0.4", + "cfg-if", "itoa", "ryu", "static_assertions", @@ -1202,7 +1111,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", ] [[package]] @@ -1458,16 +1367,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" -[[package]] -name = "dns-parser" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4d33be9473d06f75f58220f71f7a9317aca647dc061dbd3c361b0bef505fbea" -dependencies = [ - "byteorder", - "quick-error 1.2.3", -] - [[package]] name = "document-features" version = "0.2.12" @@ -1522,7 +1421,7 @@ version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", ] [[package]] @@ -1562,20 +1461,6 @@ dependencies = [ "typeid", ] -[[package]] -name = "err-derive" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22deed3a8124cff5fa835713fa105621e43bbdc46690c3a6b68328a012d350d4" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "rustversion", - "syn 1.0.109", - "synstructure 0.12.6", -] - [[package]] name = "errno" version = "0.3.14" @@ -1586,12 +1471,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "event-listener" -version = "2.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" - [[package]] name = "event-listener" version = "5.4.1" @@ -1609,7 +1488,7 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.4.1", + "event-listener", "pin-project-lite", ] @@ -1734,6 +1613,17 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin 0.9.8", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1966,7 +1856,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "libc", "wasi", ] @@ -1977,7 +1867,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "libc", "r-efi", "wasip2", @@ -2018,18 +1908,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "h2" version = "0.4.12" @@ -2055,7 +1933,7 @@ version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "crunchy", "zerocopy", ] @@ -2452,6 +2330,16 @@ dependencies = [ "icu_properties", ] +[[package]] +name = "if-addrs" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "image" version = "0.25.8" @@ -2483,7 +2371,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" dependencies = [ "byteorder-lite", - "quick-error 2.0.1", + "quick-error", ] [[package]] @@ -2599,7 +2487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", - "cfg-if 1.0.4", + "cfg-if", "combine", "jni-sys", "log", @@ -2634,15 +2522,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kv-log-macro" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" -dependencies = [ - "log", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -2698,7 +2577,7 @@ version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "windows-link", ] @@ -2815,9 +2694,6 @@ name = "log" version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" -dependencies = [ - "value-bag", -] [[package]] name = "loop9" @@ -2904,7 +2780,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "rayon", ] @@ -2914,24 +2790,23 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "digest", ] [[package]] -name = "mdns" -version = "3.0.0" +name = "mdns-sd" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c769962ac75a6ea437f0922b27834bcccd4c013d591383a16ae5731e3ef0f3f3" +checksum = "451927183d65d600e52b4e877a1251e051576f84fa01e5b4a50b450dfaaa537c" dependencies = [ - "async-std", - "async-stream 0.2.1", - "dns-parser", - "err-derive", - "futures-core", - "futures-util", + "fastrand", + "flume", + "if-addrs", "log", - "net2", + "mio 1.1.0", + "socket-pktinfo", + "socket2 0.6.1", ] [[package]] @@ -3011,6 +2886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" dependencies = [ "libc", + "log", "wasi", "windows-sys 0.61.2", ] @@ -3050,7 +2926,7 @@ dependencies = [ "crossbeam-epoch", "crossbeam-utils", "equivalent", - "event-listener 5.4.1", + "event-listener", "futures-util", "parking_lot", "portable-atomic", @@ -3116,17 +2992,6 @@ dependencies = [ "jni-sys", ] -[[package]] -name = "net2" -version = "0.2.39" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b13b648036a2339d06de780866fbdfda0dde886de7b3af2ddeba8b14f4ee34ac" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.9", -] - [[package]] name = "netlink-packet-core" version = "0.7.0" @@ -3207,7 +3072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags 2.10.0", - "cfg-if 1.0.4", + "cfg-if", "cfg_aliases", "libc", ] @@ -3573,7 +3438,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" dependencies = [ "bitflags 2.10.0", - "cfg-if 1.0.4", + "cfg-if", "foreign-types", "libc", "once_cell", @@ -3664,7 +3529,7 @@ version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "libc", "redox_syscall", "smallvec", @@ -3927,15 +3792,13 @@ name = "pmocontrol" version = "0.3.0" dependencies = [ "anyhow", - "async-std", - "async-stream 0.3.6", + "async-stream", "async-trait", "axum 0.8.7", "chrono", "crossbeam-channel", "crossterm", - "futures-util", - "mdns", + "mdns-sd", "percent-encoding", "pmocovers", "pmodidl", @@ -4095,7 +3958,7 @@ name = "pmoparadise" version = "0.1.0" dependencies = [ "anyhow", - "async-stream 0.3.6", + "async-stream", "async-trait", "axum 0.8.7", "bytes", @@ -4140,7 +4003,7 @@ name = "pmoplaylist" version = "0.1.0" dependencies = [ "anyhow", - "async-stream 0.3.6", + "async-stream", "axum 0.8.7", "chrono", "once_cell", @@ -4234,7 +4097,7 @@ name = "pmoserver" version = "0.1.0" dependencies = [ "anyhow", - "async-stream 0.3.6", + "async-stream", "axum 0.8.7", "axum-server", "futures", @@ -4376,7 +4239,7 @@ version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "concurrent-queue", "hermit-abi", "pin-project-lite", @@ -4390,7 +4253,7 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "cpufeatures", "opaque-debug", "universal-hash", @@ -4460,30 +4323,6 @@ dependencies = [ "toml_edit 0.23.7", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.109", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro2" version = "1.0.103" @@ -4629,12 +4468,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quick-error" version = "2.0.1" @@ -4757,7 +4590,7 @@ dependencies = [ "av1-grain", "bitstream-io", "built", - "cfg-if 1.0.4", + "cfg-if", "interpolate_name", "itertools 0.12.1", "libc", @@ -4789,7 +4622,7 @@ dependencies = [ "avif-serialize", "imgref", "loop9", - "quick-error 2.0.1", + "quick-error", "rav1e", "rayon", "rgb", @@ -4923,7 +4756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", - "cfg-if 1.0.4", + "cfg-if", "getrandom 0.2.16", "libc", "untrusted", @@ -5303,7 +5136,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "cpufeatures", "digest", ] @@ -5314,7 +5147,7 @@ version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "cpufeatures", "digest", ] @@ -5420,7 +5253,7 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" dependencies = [ - "async-channel 2.5.0", + "async-channel", "async-executor", "async-fs", "async-io", @@ -5440,6 +5273,17 @@ dependencies = [ "serde", ] +[[package]] +name = "socket-pktinfo" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "927136cc2ae6a1b0e66ac6b1210902b75c3f726db004a73bc18686dcd0dcd22f" +dependencies = [ + "libc", + "socket2 0.6.1", + "windows-sys 0.60.2", +] + [[package]] name = "socket2" version = "0.5.10" @@ -5471,6 +5315,15 @@ dependencies = [ "libsoxr-sys", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "spin" version = "0.10.0" @@ -5781,18 +5634,6 @@ dependencies = [ "futures-core", ] -[[package]] -name = "synstructure" -version = "0.12.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", - "unicode-xid", -] - [[package]] name = "synstructure" version = "0.13.2" @@ -5810,7 +5651,7 @@ version = "0.30.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "core-foundation-sys", "libc", "ntapi", @@ -5935,7 +5776,7 @@ version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", ] [[package]] @@ -5947,7 +5788,7 @@ dependencies = [ "fax", "flate2", "half", - "quick-error 2.0.1", + "quick-error", "weezl", "zune-jpeg", ] @@ -6075,7 +5916,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" dependencies = [ - "async-stream 0.3.6", + "async-stream", "bytes", "futures-core", "tokio", @@ -6177,7 +6018,7 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" dependencies = [ - "async-stream 0.3.6", + "async-stream", "async-trait", "axum 0.7.9", "base64 0.22.1", @@ -6426,12 +6267,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "universal-hash" version = "0.5.1" @@ -6617,12 +6452,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" -[[package]] -name = "value-bag" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" - [[package]] name = "variadics_please" version = "1.1.0" @@ -6692,7 +6521,7 @@ version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", @@ -6705,7 +6534,7 @@ version = "0.4.55" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" dependencies = [ - "cfg-if 1.0.4", + "cfg-if", "js-sys", "once_cell", "wasm-bindgen", @@ -7368,7 +7197,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.110", - "synstructure 0.13.2", + "synstructure", ] [[package]] @@ -7409,7 +7238,7 @@ dependencies = [ "proc-macro2", "quote", "syn 2.0.110", - "synstructure 0.13.2", + "synstructure", ] [[package]] diff --git a/PMOMusic/Cargo.toml b/PMOMusic/Cargo.toml index bc890477..2e016542 100644 --- a/PMOMusic/Cargo.toml +++ b/PMOMusic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "PMOMusic" -version = "0.3.41" +version = "0.3.42" edition = "2024" [dependencies] diff --git a/pmocache/src/db.rs b/pmocache/src/db.rs index 9832f38c..3f7b0081 100644 --- a/pmocache/src/db.rs +++ b/pmocache/src/db.rs @@ -203,6 +203,14 @@ impl DB { [], )?; + // Index sur metadata(key, value) pour rendre get_pk_by_origin_url efficace. + // Sans cet index, la requête WHERE key = 'origin_url' AND value = ? fait + // un full scan car la PRIMARY KEY est (pk, key). + conn.execute( + "CREATE INDEX IF NOT EXISTS idx_metadata_key_value ON metadata (key, value)", + [], + )?; + // LAZY PK SUPPORT: Index sur lazy_pk pour lookups rapides (lazy_pk → real pk) conn.execute( "CREATE INDEX IF NOT EXISTS idx_asset_lazy_pk ON asset (lazy_pk)", diff --git a/pmocontrol/Cargo.toml b/pmocontrol/Cargo.toml index fd7ec5c2..dd5b8fe4 100644 --- a/pmocontrol/Cargo.toml +++ b/pmocontrol/Cargo.toml @@ -20,9 +20,7 @@ ratatui = { version = "0.26", default-features = false, features = ["crossterm"] crossterm = "0.27" rust_cast = "0.19" rustls = { version = "0.23", features = ["aws-lc-rs"] } -mdns = "3.0" -async-std = "1.12" -futures-util = "0.3" +mdns-sd = "0.19" smol = "2.0" serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/pmocontrol/src/control_point.rs b/pmocontrol/src/control_point.rs index 01220e08..42d0f9b8 100644 --- a/pmocontrol/src/control_point.rs +++ b/pmocontrol/src/control_point.rs @@ -13,7 +13,7 @@ use tracing::{debug, error, info, warn}; use crate::discovery::manager::UDNRegistry; use crate::errors::ControlPointError; use crate::events::{MediaServerEventBus, RendererEventBus}; -use crate::media_server::{MediaBrowser, MusicServer, playback_item_from_entry}; +use crate::media_server::{playback_item_from_entry, MediaBrowser, MusicServer}; use crate::media_server_events::spawn_media_server_event_runtime; use crate::model::{MediaServerEvent, RendererEvent}; use crate::model::{PlaybackState, TrackMetadata}; @@ -147,51 +147,52 @@ impl ControlPoint { let udn_cache_for_mdns = Arc::clone(&udn_cache); thread::spawn(move || { use crate::discovery::ChromecastDiscoveryManager; - use futures_util::StreamExt; + use mdns_sd::{ServiceDaemon, ServiceEvent}; - // Créer le gestionnaire de découverte UPNP let mut discovery_manager = ChromecastDiscoveryManager::new(registry_for_mdns, udn_cache_for_mdns); debug!("Starting mDNS discovery thread for Chromecast devices"); - const SERVICE_NAME: &str = "_googlecast._tcp.local"; + const SERVICE_TYPE: &str = "_googlecast._tcp.local."; - // Run async discovery in a blocking task - async_std::task::block_on(async { - // Create mDNS discovery stream with 15 second query interval - // (shorter interval for faster initial discovery) - match mdns::discover::all(SERVICE_NAME, Duration::from_secs(15)) { - Ok(discovery) => { - let stream = discovery.listen(); - futures_util::pin_mut!(stream); - - debug!("mDNS discovery stream started for Chromecast devices"); - - // Listen to mDNS responses - while let Some(result) = stream.next().await { - match result { - Ok(response) => { - debug!( - "Received mDNS response with {} records", - response.records().count() - ); - - discovery_manager.handle_mdns_response(response); - } - Err(e) => { - warn!("mDNS discovery error: {}", e); - } - } - } - - warn!("mDNS discovery stream ended unexpectedly"); - } - Err(e) => { - error!("Failed to start mDNS discovery: {}", e); - } + let daemon = match ServiceDaemon::new() { + Ok(d) => d, + Err(e) => { + error!("Failed to create mDNS daemon: {}", e); + return; } - }); + }; + + let receiver = match daemon.browse(SERVICE_TYPE) { + Ok(r) => r, + Err(e) => { + error!("Failed to start mDNS browse for {}: {}", SERVICE_TYPE, e); + return; + } + }; + + debug!("mDNS discovery started for Chromecast devices"); + + while let Ok(event) = receiver.recv() { + match event { + ServiceEvent::ServiceResolved(info) => { + debug!( + fullname = info.get_fullname(), + host = info.get_hostname(), + port = info.get_port(), + "mDNS Chromecast service resolved" + ); + discovery_manager.handle_service_resolved(&info); + } + ServiceEvent::ServiceRemoved(_service_type, fullname) => { + debug!(fullname = %fullname, "mDNS Chromecast service removed"); + } + _ => {} + } + } + + warn!("mDNS discovery receiver closed"); }); // Note: Renderer polling is now handled by each MusicRenderer's own watcher thread. @@ -1169,24 +1170,21 @@ impl ControlPoint { binding.auto_play_on_refresh = auto_play; renderer.set_playlist_binding(Some(binding)); - let callback: Option Result<(), ControlPointError> + Send + 'static>> = - if auto_play { - let reg = Arc::clone(&self.registry); - Some(Box::new(move |rid: &DeviceId| { - let renderer = reg.read().unwrap().get_renderer(rid).ok_or_else(|| - ControlPointError::ControlPoint(format!("Renderer {} not found", rid.0)) - )?; - renderer.play_current_from_queue() - })) - } else { - None - }; - let _ = schedule_queue_refresh_for( - &self.registry, - renderer_id, - &self.event_bus, - callback, - ); + let callback: Option< + Box Result<(), ControlPointError> + Send + 'static>, + > = if auto_play { + let reg = Arc::clone(&self.registry); + Some(Box::new(move |rid: &DeviceId| { + let renderer = reg.read().unwrap().get_renderer(rid).ok_or_else(|| { + ControlPointError::ControlPoint(format!("Renderer {} not found", rid.0)) + })?; + renderer.play_current_from_queue() + })) + } else { + None + }; + let _ = + schedule_queue_refresh_for(&self.registry, renderer_id, &self.event_bus, callback); return Ok(()); } @@ -1240,22 +1238,23 @@ impl ControlPoint { // Note: BindingChanged event is emitted automatically by MusicRenderer::set_playlist_binding() // For initial attach with auto_play, force playback start (don't check if idle) - let callback: Option Result<(), ControlPointError> + Send + 'static>> = - if auto_play { - let reg = Arc::clone(&self.registry); - Some(Box::new(move |rid: &DeviceId| { - debug!( - renderer = rid.0.as_str(), - "Attach callback: forcing playback start (not checking if idle)" - ); - let renderer = reg.read().unwrap().get_renderer(rid).ok_or_else(|| - ControlPointError::ControlPoint(format!("Renderer {} not found", rid.0)) - )?; - renderer.play_current_from_queue() - })) - } else { - None - }; + let callback: Option< + Box Result<(), ControlPointError> + Send + 'static>, + > = if auto_play { + let reg = Arc::clone(&self.registry); + Some(Box::new(move |rid: &DeviceId| { + debug!( + renderer = rid.0.as_str(), + "Attach callback: forcing playback start (not checking if idle)" + ); + let renderer = reg.read().unwrap().get_renderer(rid).ok_or_else(|| { + ControlPointError::ControlPoint(format!("Renderer {} not found", rid.0)) + })?; + renderer.play_current_from_queue() + })) + } else { + None + }; let _ = schedule_queue_refresh_for(&self.registry, renderer_id, &self.event_bus, callback); Ok(()) @@ -1806,7 +1805,9 @@ fn schedule_queue_refresh_for( registry: &Arc>, renderer_id: &DeviceId, event_bus: &RendererEventBus, - after_refresh: Option Result<(), ControlPointError> + Send + 'static>>, + after_refresh: Option< + Box Result<(), ControlPointError> + Send + 'static>, + >, ) -> SyncScheduleOutcome { // Step 1: Get renderer from registry let renderer = { @@ -1895,7 +1896,9 @@ fn schedule_queue_refresh_for( }; match music_server { Some(s) => fetch_queue_items_for(&s, &container_id_clone), - None => Err(ControlPointError::MediaServerError("Server not found".to_string())), + None => Err(ControlPointError::MediaServerError( + "Server not found".to_string(), + )), } }); diff --git a/pmocontrol/src/discovery/chromecast_discovery.rs b/pmocontrol/src/discovery/chromecast_discovery.rs index 673443c1..c9c1f8ef 100644 --- a/pmocontrol/src/discovery/chromecast_discovery.rs +++ b/pmocontrol/src/discovery/chromecast_discovery.rs @@ -5,14 +5,15 @@ //! This module handles the discovery of Chromecast devices and registers them //! directly into the `DeviceRegistry`. -use std::collections::HashMap; -use std::net::IpAddr; use std::sync::{Arc, Mutex, RwLock}; -use crate::DeviceId; -use crate::DeviceRegistry; +use mdns_sd::ResolvedService; +use mdns_sd::ServiceInfo; + use crate::discovery::manager::UDNRegistry; use crate::model::{RendererCapabilities, RendererInfo, RendererProtocol}; +use crate::DeviceId; +use crate::DeviceRegistry; use tracing::{debug, warn}; /// Gestionnaire des événements mDNS pour Chromecast. @@ -32,146 +33,80 @@ impl ChromecastDiscoveryManager { } } - /// Traite une réponse mDNS pour un appareil Chromecast. + /// Traite un service Chromecast résolu par mDNS-SD. /// - /// Cette fonction parse les réponses de service discovery mDNS pour les appareils - /// Chromecast et les enregistre directement dans le registre. - pub fn handle_mdns_response(&mut self, response: mdns::Response) { - // Extract basic information from the mDNS response - let service_name = match response.records().find_map(|r| { - if let mdns::RecordKind::PTR(ref name) = r.kind { - Some(name.clone()) - } else { - None - } - }) { - Some(name) => name, - None => { - warn!("No PTR record found in mDNS response"); - return; - } - }; + /// `ServiceInfo` arrive pré-assemblé : plus besoin de jointure manuelle + /// des enregistrements PTR / A / SRV / TXT. + pub fn handle_service_resolved(&mut self, info: &ResolvedService) { + let fullname = info.get_fullname().to_string(); - debug!("Processing mDNS response for service: {}", service_name); + debug!("Processing resolved Chromecast service: {}", fullname); - // Extract IP addresses - let addresses: Vec = response - .records() - .filter_map(|r| match r.kind { - mdns::RecordKind::A(addr) => Some(IpAddr::V4(addr)), - mdns::RecordKind::AAAA(addr) => Some(IpAddr::V6(addr)), - _ => None, - }) - .collect(); - - if addresses.is_empty() { - warn!( - "No IP address found for Chromecast device: {}", - service_name - ); - return; - } - - // Prefer IPv4 addresses - let host = match addresses + let host = match info + .get_addresses() .iter() - .find(|addr| matches!(addr, IpAddr::V4(_))) - .or_else(|| addresses.first()) + .find(|a| a.is_ipv4()) + .or_else(|| info.get_addresses().iter().next()) { Some(addr) => addr.to_string(), None => { - warn!("Could not extract host from addresses"); + warn!("No IP address for Chromecast service: {}", fullname); return; } }; - // Extract port from SRV record - let port = response - .records() - .find_map(|r| { - if let mdns::RecordKind::SRV { port, .. } = r.kind { - Some(port) - } else { - None - } - }) - .unwrap_or(8009); // Default Chromecast port + let port = info.get_port(); - // Extract TXT records for additional metadata - let txt_records: HashMap = response - .records() - .filter_map(|r| { - if let mdns::RecordKind::TXT(ref data) = r.kind { - Some(data.clone()) - } else { - None - } - }) - .flat_map(|data| { - // data is Vec, each string is "key=value" - data.into_iter().filter_map(|s| { - let parts: Vec<&str> = s.splitn(2, '=').collect(); - if parts.len() == 2 { - Some((parts[0].to_string(), parts[1].to_string())) - } else { - None - } - }) - }) - .collect(); + let uuid = info + .get_property_val_str("id") + .unwrap_or_default() + .to_string(); + let uuid = if uuid.is_empty() { + format!("chromecast-{}-{}", host, port) + } else { + uuid + }; - // Extract metadata from TXT records - let model = txt_records.get("md").cloned(); - let uuid = txt_records - .get("id") - .cloned() - .unwrap_or_else(|| format!("chromecast-{}-{}", host, port)); - let manufacturer = Some("Google Inc.".to_string()); + let model = info.get_property_val_str("md").map(|s| s.to_string()); - // Extract friendly name from TXT record "fn" if available - // Otherwise, extract from service instance name (PTR record) - let friendly_name = txt_records.get("fn").cloned().unwrap_or_else(|| { - // Fallback: extract from service name, removing the UUID suffix if present - service_name - .split("._googlecast._tcp.local") - .next() - .unwrap_or("Unknown Chromecast") - .split('-') - .take_while(|part| part.len() != 32) // Skip 32-char hex UUID - .collect::>() - .join("-") - .trim() - .to_string() - }); + let friendly_name = info + .get_property_val_str("fn") + .map(|s| s.to_string()) + .unwrap_or_else(|| { + fullname + .split("._googlecast._tcp.local") + .next() + .unwrap_or("Unknown Chromecast") + .split('-') + .take_while(|part| part.len() != 32) + .collect::>() + .join("-") + .trim() + .to_string() + }); debug!( "Discovered Chromecast: {} at {}:{} (UUID: {}, Model: {:?})", friendly_name, host, port, uuid, model ); - // Build UDN and check cache let udn = format!("uuid:{}", uuid); + let default_max_age = 1800u64; - // Pour Chromecast, on utilise un max_age par défaut car mDNS n'a pas ce concept - let default_max_age = 1800u64; // 30 minutes - - // Check cache to avoid redundant updates if !UDNRegistry::should_fetch(self.udn_cache.clone(), &udn, default_max_age) { debug!("Chromecast {} recently seen, skipping", udn); return; } - // Create RendererInfo for the registry let renderer_info = build_renderer_info( &uuid, &friendly_name, &host, port, model.as_deref(), - manufacturer.as_deref(), + Some("Google Inc."), ); - // Register the renderer self.device_registry .write() .expect("DeviceRegistry mutex lock failed") diff --git a/pmoflac/src/encoder.rs b/pmoflac/src/encoder.rs index b1c35e57..5960bdca 100755 --- a/pmoflac/src/encoder.rs +++ b/pmoflac/src/encoder.rs @@ -402,7 +402,7 @@ unsafe fn setup_metadata( use libflac_sys::*; // Create a Vorbis Comment block - let meta = FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT); + let meta = unsafe { FLAC__metadata_object_new(FLAC__METADATA_TYPE_VORBIS_COMMENT) }; if meta.is_null() { return Err(FlacError::LibFlacInit( "Failed to create metadata block".into(), @@ -420,12 +420,14 @@ unsafe fn setup_metadata( FlacError::LibFlacInit("Failed to create CString for field value".into()) })?; - let mut entry: FLAC__StreamMetadata_VorbisComment_Entry = std::mem::zeroed(); - let success = FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair( - &mut entry as *mut _, - c_field_name.as_ptr(), - c_value.as_ptr(), - ); + let mut entry: FLAC__StreamMetadata_VorbisComment_Entry = unsafe { std::mem::zeroed() }; + let success = unsafe { + FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair( + &mut entry as *mut _, + c_field_name.as_ptr(), + c_value.as_ptr(), + ) + }; if success == 0 { return Err(FlacError::LibFlacInit(format!( @@ -435,7 +437,7 @@ unsafe fn setup_metadata( } let append_success = - FLAC__metadata_object_vorbiscomment_append_comment(meta, entry, 0 /* copy */); + unsafe { FLAC__metadata_object_vorbiscomment_append_comment(meta, entry, 0 /* copy */) }; if append_success == 0 { return Err(FlacError::LibFlacInit(format!( @@ -476,7 +478,7 @@ unsafe fn setup_metadata( // Set the metadata on the encoder let mut metadata_array = [meta]; - let set_success = FLAC__stream_encoder_set_metadata(encoder, metadata_array.as_mut_ptr(), 1); + let set_success = unsafe { FLAC__stream_encoder_set_metadata(encoder, metadata_array.as_mut_ptr(), 1) }; if set_success == 0 { return Err(FlacError::LibFlacInit( @@ -666,8 +668,8 @@ unsafe extern "C" fn write_callback( _current_frame: u32, client_data: *mut c_void, ) -> libflac_sys::FLAC__StreamEncoderWriteStatus { - let state = &mut *(client_data as *mut EncoderClientState); - let slice = std::slice::from_raw_parts(buffer, bytes); + let state = unsafe { &mut *(client_data as *mut EncoderClientState) }; + let slice = unsafe { std::slice::from_raw_parts(buffer, bytes) }; match state.tx.blocking_send(Ok(slice.to_vec())) { Ok(_) => libflac_sys::FLAC__STREAM_ENCODER_WRITE_STATUS_OK, Err(_) => { diff --git a/pmomediaserver/src/lib.rs b/pmomediaserver/src/lib.rs index a47669d9..fa578ecd 100644 --- a/pmomediaserver/src/lib.rs +++ b/pmomediaserver/src/lib.rs @@ -83,7 +83,7 @@ pub mod paradise_streaming; pub use content_handler::ContentHandler; pub use device::MEDIA_SERVER; pub use device_ext::MediaServerDeviceExt; -pub use server_ext::{MediaServerExt, MusicSourceExt, get_source_registry}; +pub use server_ext::{MediaServerExt, MusicSourceExt}; pub use source_registry::SourceRegistry; pub use sources::{SourceInitError, SourcesExt}; diff --git a/pmomediaserver/src/server_ext.rs b/pmomediaserver/src/server_ext.rs index 025aa227..00f63614 100644 --- a/pmomediaserver/src/server_ext.rs +++ b/pmomediaserver/src/server_ext.rs @@ -14,23 +14,6 @@ use std::sync::Arc; // Réexporter le trait de base de pmosource pub use pmosource::MusicSourceExt; -/// Récupère le registre global de sources (délègue à pmosource) -/// -/// # Examples -/// -/// ```ignore -/// use pmomediaserver::server_ext::get_source_registry; -/// -/// let sources = pmosource::api::list_all_sources().await; -/// ``` -#[deprecated( - since = "0.2.0", - note = "Use pmosource::api::list_all_sources() directly" -)] -pub async fn get_source_registry() -> Vec> { - pmosource::api::list_all_sources().await -} - /// Trait d'extension pour le serveur MediaServer UPnP /// /// Ce trait ajoute des méthodes spécifiques au MediaServer UPnP. diff --git a/pmoserver/src/logs/mod.rs b/pmoserver/src/logs/mod.rs index cfb82fab..9bd962de 100644 --- a/pmoserver/src/logs/mod.rs +++ b/pmoserver/src/logs/mod.rs @@ -273,13 +273,13 @@ pub fn init_logging() -> LogState { Ok(value) => { let trimmed = value.trim(); if let Some(level) = string_to_level(trimmed) { - let filter = - EnvFilter::try_new(trimmed).unwrap_or_else(|_| EnvFilter::new("trace")); + let filter = build_filter_with_noise_suppressions(trimmed); (filter, level, format!("RUST_LOG ({})", trimmed)) } else { + let filter = build_filter_with_noise_suppressions(trimmed); match EnvFilter::try_new(trimmed) { - Ok(filter) => { - let level_hint = filter + Ok(raw) => { + let level_hint = raw .max_level_hint() .and_then(levelfilter_to_level) .unwrap_or(Level::TRACE); @@ -295,7 +295,7 @@ pub fn init_logging() -> LogState { .ok() .and_then(|cfg| string_to_level(cfg.trim())) .unwrap_or(Level::TRACE); - let filter = EnvFilter::new(level_to_string(cfg_level)); + let filter = build_filter_with_noise_suppressions(&level_to_string(cfg_level)); (filter, cfg_level, "config".to_string()) } } @@ -307,7 +307,7 @@ pub fn init_logging() -> LogState { .ok() .and_then(|cfg| string_to_level(cfg.trim())) .unwrap_or(Level::TRACE); - let filter = EnvFilter::new(level_to_string(cfg_level)); + let filter = build_filter_with_noise_suppressions(&level_to_string(cfg_level)); (filter, cfg_level, "config".to_string()) } }; @@ -446,6 +446,27 @@ pub async fn log_setup_post( .into_response() } +/// Construit un `EnvFilter` en ajoutant les suppressions de bruit connues +/// si la directive correspondante n'est pas déjà présente dans `base`. +fn build_filter_with_noise_suppressions(base: &str) -> EnvFilter { + const NOISE_FILTERS: &[(&str, &str)] = &[]; + + let mut directives = base.to_string(); + for (prefix, directive) in NOISE_FILTERS { + // N'ajouter que si l'utilisateur n'a pas déjà configuré cette cible + let already_set = base.split(',').any(|part| { + let part = part.trim(); + part == *prefix || part.starts_with(&format!("{}=", prefix)) + }); + if !already_set { + directives.push(','); + directives.push_str(directive); + } + } + + EnvFilter::try_new(&directives).unwrap_or_else(|_| EnvFilter::new(base)) +} + fn string_to_level(s: &str) -> Option { match s.to_uppercase().as_str() { "ERROR" => Some(Level::ERROR), diff --git a/version.txt b/version.txt index b463c010..0bdfd66f 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.3.41 +0.3.42