- Remplacer la crate abandonnée `mdns` (3.0, non-UTF8) par activement maintenue et UTF‑compliant `mdns-sd` (0.19) - Supprimer les dépendances inutiles `async‑std`, `futures-util` (uniquement utilisées dans le thread mDNS) - Refactoriser `control_point.rs` : passer d’un bloc async bloquant à une boucle synchrone avec `ServiceDaemon.browse()` + récepteur blocant - Simplifier drastiquement le parsing dans `chromecast_discovery.rs` via l’API haut‑niveau de mdns-sd (`ServiceInfo`, `get_property_val_str`) → ~80 lignes remplacées par 52 - Ajouter index SQLite `idx_metadata_key_value` pour accélérer les requêtes par origin_url - Nettoyer exports inutiles dans `pmomediaserver::server_ext` - Supprimer filtre de bruit obsolète `mdns=error` dans les logs - Met à jour la version en 0.3.42
9.4 KiB
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
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: uniquementcontrol_point.rs:161→ supprimerfutures-util: uniquementcontrol_point.rs:150,167→ supprimer
Ajouter
mdns-sd = "0.19"
Étape 2 — pmocontrol/src/control_point.rs
Code actuel (lignes 145–195) à remplacer intégralement
// 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
// 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 pourmdns-sd. receiver.recv()est bloquant synchrone — pas besoin d'async runtime.ServiceDaemongère son propre thread interne ; inutile de relancer manuellement.
Étape 3 — pmocontrol/src/discovery/chromecast_discovery.rs
Supprimer l'import mdns
// 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
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
/// 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::<Vec<_>>()
.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 :
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 :
cargo check -p pmocontrolsans erreurs niuse of undeclared crate mdnscargo check -p pmoserversans erreurs- Tester la découverte d'un Chromecast en réseau local
- Vérifier l'absence de
LabelIsNotAsciidans les logs avec des appareils Apple présents