Merge pull request '⬆️ migration mdns →mddns-sd' (#92) from push-kmzstwrlymsr into main
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m33s

Reviewed-on: #92
This commit was merged in pull request #92.
This commit is contained in:
2026-04-09 15:50:56 +02:00
12 changed files with 580 additions and 495 deletions

View File

@@ -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 145195) |
| `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 145195) à remplacer intégralement
```rust
// Thread de découverte mDNS pour Chromecast
let registry_for_mdns = Arc::clone(&registry);
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(&registry);
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 39179) — ~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::<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 39179)
### Conserver sans modification
- `build_renderer_info` (lignes 182234)
- `extract_host_from_location` / `extract_port_from_location` (lignes 239258)
- Les tests (lignes 260287)
---
## É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

373
Cargo.lock generated
View File

@@ -4,7 +4,7 @@ version = 4
[[package]] [[package]]
name = "PMOMusic" name = "PMOMusic"
version = "0.3.41" version = "0.3.42"
dependencies = [ dependencies = [
"axum 0.8.7", "axum 0.8.7",
"console-subscriber", "console-subscriber",
@@ -49,7 +49,7 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"cipher", "cipher",
"cpufeatures", "cpufeatures",
] ]
@@ -100,7 +100,7 @@ checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43"
dependencies = [ dependencies = [
"alsa-sys", "alsa-sys",
"bitflags 2.10.0", "bitflags 2.10.0",
"cfg-if 1.0.4", "cfg-if",
"libc", "libc",
] ]
@@ -176,27 +176,6 @@ dependencies = [
"syn 2.0.110", "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]] [[package]]
name = "async-channel" name = "async-channel"
version = "2.5.0" version = "2.5.0"
@@ -234,21 +213,6 @@ dependencies = [
"futures-lite", "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]] [[package]]
name = "async-io" name = "async-io"
version = "2.6.0" version = "2.6.0"
@@ -256,7 +220,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"cfg-if 1.0.4", "cfg-if",
"concurrent-queue", "concurrent-queue",
"futures-io", "futures-io",
"futures-lite", "futures-lite",
@@ -273,7 +237,7 @@ version = "3.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc" checksum = "5fd03604047cee9b6ce9de9f70c6cd540a0520c813cbd49bae61f33ab80ed1dc"
dependencies = [ dependencies = [
"event-listener 5.4.1", "event-listener",
"event-listener-strategy", "event-listener-strategy",
"pin-project-lite", "pin-project-lite",
] ]
@@ -295,14 +259,14 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75" checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
dependencies = [ dependencies = [
"async-channel 2.5.0", "async-channel",
"async-io", "async-io",
"async-lock", "async-lock",
"async-signal", "async-signal",
"async-task", "async-task",
"blocking", "blocking",
"cfg-if 1.0.4", "cfg-if",
"event-listener 5.4.1", "event-listener",
"futures-lite", "futures-lite",
"rustix 1.1.2", "rustix 1.1.2",
] ]
@@ -327,7 +291,7 @@ dependencies = [
"async-io", "async-io",
"async-lock", "async-lock",
"atomic-waker", "atomic-waker",
"cfg-if 1.0.4", "cfg-if",
"futures-core", "futures-core",
"futures-io", "futures-io",
"rustix 1.1.2", "rustix 1.1.2",
@@ -336,66 +300,17 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "async-stream" name = "async-stream"
version = "0.3.6" version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476"
dependencies = [ dependencies = [
"async-stream-impl 0.3.6", "async-stream-impl",
"futures-core", "futures-core",
"pin-project-lite", "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]] [[package]]
name = "async-stream-impl" name = "async-stream-impl"
version = "0.3.6" version = "0.3.6"
@@ -682,7 +597,7 @@ dependencies = [
"portable-atomic", "portable-atomic",
"portable-atomic-util", "portable-atomic-util",
"serde", "serde",
"spin", "spin 0.10.0",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
] ]
@@ -815,7 +730,7 @@ version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
dependencies = [ dependencies = [
"async-channel 2.5.0", "async-channel",
"async-task", "async-task",
"futures-io", "futures-io",
"futures-lite", "futures-lite",
@@ -930,12 +845,6 @@ dependencies = [
"target-lexicon", "target-lexicon",
] ]
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.4" version = "1.0.4"
@@ -1030,7 +939,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f" checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
dependencies = [ dependencies = [
"castaway", "castaway",
"cfg-if 1.0.4", "cfg-if",
"itoa", "itoa",
"ryu", "ryu",
"static_assertions", "static_assertions",
@@ -1202,7 +1111,7 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
] ]
[[package]] [[package]]
@@ -1458,16 +1367,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c9c272297e804878a2a4b707cfcfc6d2328b5bb936944613b4fdf2b9269afdfd" 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]] [[package]]
name = "document-features" name = "document-features"
version = "0.2.12" version = "0.2.12"
@@ -1522,7 +1421,7 @@ version = "0.8.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
] ]
[[package]] [[package]]
@@ -1562,20 +1461,6 @@ dependencies = [
"typeid", "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]] [[package]]
name = "errno" name = "errno"
version = "0.3.14" version = "0.3.14"
@@ -1586,12 +1471,6 @@ dependencies = [
"windows-sys 0.61.2", "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]] [[package]]
name = "event-listener" name = "event-listener"
version = "5.4.1" version = "5.4.1"
@@ -1609,7 +1488,7 @@ version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93"
dependencies = [ dependencies = [
"event-listener 5.4.1", "event-listener",
"pin-project-lite", "pin-project-lite",
] ]
@@ -1734,6 +1613,17 @@ dependencies = [
"miniz_oxide", "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]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@@ -1966,7 +1856,7 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"libc", "libc",
"wasi", "wasi",
] ]
@@ -1977,7 +1867,7 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"libc", "libc",
"r-efi", "r-efi",
"wasip2", "wasip2",
@@ -2018,18 +1908,6 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" 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]] [[package]]
name = "h2" name = "h2"
version = "0.4.12" version = "0.4.12"
@@ -2055,7 +1933,7 @@ version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"crunchy", "crunchy",
"zerocopy", "zerocopy",
] ]
@@ -2452,6 +2330,16 @@ dependencies = [
"icu_properties", "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]] [[package]]
name = "image" name = "image"
version = "0.25.8" version = "0.25.8"
@@ -2483,7 +2371,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3" checksum = "525e9ff3e1a4be2fbea1fdf0e98686a6d98b4d8f937e1bf7402245af1909e8c3"
dependencies = [ dependencies = [
"byteorder-lite", "byteorder-lite",
"quick-error 2.0.1", "quick-error",
] ]
[[package]] [[package]]
@@ -2599,7 +2487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97"
dependencies = [ dependencies = [
"cesu8", "cesu8",
"cfg-if 1.0.4", "cfg-if",
"combine", "combine",
"jni-sys", "jni-sys",
"log", "log",
@@ -2634,15 +2522,6 @@ dependencies = [
"wasm-bindgen", "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]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@@ -2698,7 +2577,7 @@ version = "0.8.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"windows-link", "windows-link",
] ]
@@ -2815,9 +2694,6 @@ name = "log"
version = "0.4.28" version = "0.4.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
dependencies = [
"value-bag",
]
[[package]] [[package]]
name = "loop9" name = "loop9"
@@ -2904,7 +2780,7 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"rayon", "rayon",
] ]
@@ -2914,24 +2790,23 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"digest", "digest",
] ]
[[package]] [[package]]
name = "mdns" name = "mdns-sd"
version = "3.0.0" version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c769962ac75a6ea437f0922b27834bcccd4c013d591383a16ae5731e3ef0f3f3" checksum = "451927183d65d600e52b4e877a1251e051576f84fa01e5b4a50b450dfaaa537c"
dependencies = [ dependencies = [
"async-std", "fastrand",
"async-stream 0.2.1", "flume",
"dns-parser", "if-addrs",
"err-derive",
"futures-core",
"futures-util",
"log", "log",
"net2", "mio 1.1.0",
"socket-pktinfo",
"socket2 0.6.1",
] ]
[[package]] [[package]]
@@ -3011,6 +2886,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873" checksum = "69d83b0086dc8ecf3ce9ae2874b2d1290252e2a30720bea58a5c6639b0092873"
dependencies = [ dependencies = [
"libc", "libc",
"log",
"wasi", "wasi",
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
@@ -3050,7 +2926,7 @@ dependencies = [
"crossbeam-epoch", "crossbeam-epoch",
"crossbeam-utils", "crossbeam-utils",
"equivalent", "equivalent",
"event-listener 5.4.1", "event-listener",
"futures-util", "futures-util",
"parking_lot", "parking_lot",
"portable-atomic", "portable-atomic",
@@ -3116,17 +2992,6 @@ dependencies = [
"jni-sys", "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]] [[package]]
name = "netlink-packet-core" name = "netlink-packet-core"
version = "0.7.0" version = "0.7.0"
@@ -3207,7 +3072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"cfg-if 1.0.4", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
] ]
@@ -3573,7 +3438,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328"
dependencies = [ dependencies = [
"bitflags 2.10.0", "bitflags 2.10.0",
"cfg-if 1.0.4", "cfg-if",
"foreign-types", "foreign-types",
"libc", "libc",
"once_cell", "once_cell",
@@ -3664,7 +3529,7 @@ version = "0.9.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"libc", "libc",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
@@ -3927,15 +3792,13 @@ name = "pmocontrol"
version = "0.3.0" version = "0.3.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-std", "async-stream",
"async-stream 0.3.6",
"async-trait", "async-trait",
"axum 0.8.7", "axum 0.8.7",
"chrono", "chrono",
"crossbeam-channel", "crossbeam-channel",
"crossterm", "crossterm",
"futures-util", "mdns-sd",
"mdns",
"percent-encoding", "percent-encoding",
"pmocovers", "pmocovers",
"pmodidl", "pmodidl",
@@ -4095,7 +3958,7 @@ name = "pmoparadise"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-stream 0.3.6", "async-stream",
"async-trait", "async-trait",
"axum 0.8.7", "axum 0.8.7",
"bytes", "bytes",
@@ -4140,7 +4003,7 @@ name = "pmoplaylist"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-stream 0.3.6", "async-stream",
"axum 0.8.7", "axum 0.8.7",
"chrono", "chrono",
"once_cell", "once_cell",
@@ -4234,7 +4097,7 @@ name = "pmoserver"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-stream 0.3.6", "async-stream",
"axum 0.8.7", "axum 0.8.7",
"axum-server", "axum-server",
"futures", "futures",
@@ -4376,7 +4239,7 @@ version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"concurrent-queue", "concurrent-queue",
"hermit-abi", "hermit-abi",
"pin-project-lite", "pin-project-lite",
@@ -4390,7 +4253,7 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"cpufeatures", "cpufeatures",
"opaque-debug", "opaque-debug",
"universal-hash", "universal-hash",
@@ -4460,30 +4323,6 @@ dependencies = [
"toml_edit 0.23.7", "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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.103" version = "1.0.103"
@@ -4629,12 +4468,6 @@ dependencies = [
"bytemuck", "bytemuck",
] ]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]] [[package]]
name = "quick-error" name = "quick-error"
version = "2.0.1" version = "2.0.1"
@@ -4757,7 +4590,7 @@ dependencies = [
"av1-grain", "av1-grain",
"bitstream-io", "bitstream-io",
"built", "built",
"cfg-if 1.0.4", "cfg-if",
"interpolate_name", "interpolate_name",
"itertools 0.12.1", "itertools 0.12.1",
"libc", "libc",
@@ -4789,7 +4622,7 @@ dependencies = [
"avif-serialize", "avif-serialize",
"imgref", "imgref",
"loop9", "loop9",
"quick-error 2.0.1", "quick-error",
"rav1e", "rav1e",
"rayon", "rayon",
"rgb", "rgb",
@@ -4923,7 +4756,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if 1.0.4", "cfg-if",
"getrandom 0.2.16", "getrandom 0.2.16",
"libc", "libc",
"untrusted", "untrusted",
@@ -5303,7 +5136,7 @@ version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"cpufeatures", "cpufeatures",
"digest", "digest",
] ]
@@ -5314,7 +5147,7 @@ version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"cpufeatures", "cpufeatures",
"digest", "digest",
] ]
@@ -5420,7 +5253,7 @@ version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f" checksum = "a33bd3e260892199c3ccfc487c88b2da2265080acb316cd920da72fdfd7c599f"
dependencies = [ dependencies = [
"async-channel 2.5.0", "async-channel",
"async-executor", "async-executor",
"async-fs", "async-fs",
"async-io", "async-io",
@@ -5440,6 +5273,17 @@ dependencies = [
"serde", "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]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.10" version = "0.5.10"
@@ -5471,6 +5315,15 @@ dependencies = [
"libsoxr-sys", "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]] [[package]]
name = "spin" name = "spin"
version = "0.10.0" version = "0.10.0"
@@ -5781,18 +5634,6 @@ dependencies = [
"futures-core", "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]] [[package]]
name = "synstructure" name = "synstructure"
version = "0.13.2" version = "0.13.2"
@@ -5810,7 +5651,7 @@ version = "0.30.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
"ntapi", "ntapi",
@@ -5935,7 +5776,7 @@ version = "1.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
] ]
[[package]] [[package]]
@@ -5947,7 +5788,7 @@ dependencies = [
"fax", "fax",
"flate2", "flate2",
"half", "half",
"quick-error 2.0.1", "quick-error",
"weezl", "weezl",
"zune-jpeg", "zune-jpeg",
] ]
@@ -6075,7 +5916,7 @@ version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7" checksum = "2468baabc3311435b55dd935f702f42cd1b8abb7e754fb7dfb16bd36aa88f9f7"
dependencies = [ dependencies = [
"async-stream 0.3.6", "async-stream",
"bytes", "bytes",
"futures-core", "futures-core",
"tokio", "tokio",
@@ -6177,7 +6018,7 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52"
dependencies = [ dependencies = [
"async-stream 0.3.6", "async-stream",
"async-trait", "async-trait",
"axum 0.7.9", "axum 0.7.9",
"base64 0.22.1", "base64 0.22.1",
@@ -6426,12 +6267,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254" checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "unicode-xid"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853"
[[package]] [[package]]
name = "universal-hash" name = "universal-hash"
version = "0.5.1" version = "0.5.1"
@@ -6617,12 +6452,6 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "value-bag"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0"
[[package]] [[package]]
name = "variadics_please" name = "variadics_please"
version = "1.1.0" version = "1.1.0"
@@ -6692,7 +6521,7 @@ version = "0.2.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60" checksum = "da95793dfc411fbbd93f5be7715b0578ec61fe87cb1a42b12eb625caa5c5ea60"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"once_cell", "once_cell",
"rustversion", "rustversion",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@@ -6705,7 +6534,7 @@ version = "0.4.55"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0" checksum = "551f88106c6d5e7ccc7cd9a16f312dd3b5d36ea8b4954304657d5dfba115d4a0"
dependencies = [ dependencies = [
"cfg-if 1.0.4", "cfg-if",
"js-sys", "js-sys",
"once_cell", "once_cell",
"wasm-bindgen", "wasm-bindgen",
@@ -7368,7 +7197,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.110", "syn 2.0.110",
"synstructure 0.13.2", "synstructure",
] ]
[[package]] [[package]]
@@ -7409,7 +7238,7 @@ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.110", "syn 2.0.110",
"synstructure 0.13.2", "synstructure",
] ]
[[package]] [[package]]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "PMOMusic" name = "PMOMusic"
version = "0.3.41" version = "0.3.42"
edition = "2024" edition = "2024"
[dependencies] [dependencies]

View File

@@ -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) // LAZY PK SUPPORT: Index sur lazy_pk pour lookups rapides (lazy_pk → real pk)
conn.execute( conn.execute(
"CREATE INDEX IF NOT EXISTS idx_asset_lazy_pk ON asset (lazy_pk)", "CREATE INDEX IF NOT EXISTS idx_asset_lazy_pk ON asset (lazy_pk)",

View File

@@ -20,9 +20,7 @@ ratatui = { version = "0.26", default-features = false, features = ["crossterm"]
crossterm = "0.27" crossterm = "0.27"
rust_cast = "0.19" rust_cast = "0.19"
rustls = { version = "0.23", features = ["aws-lc-rs"] } rustls = { version = "0.23", features = ["aws-lc-rs"] }
mdns = "3.0" mdns-sd = "0.19"
async-std = "1.12"
futures-util = "0.3"
smol = "2.0" smol = "2.0"
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true } serde_json = { workspace = true }

View File

@@ -13,7 +13,7 @@ use tracing::{debug, error, info, warn};
use crate::discovery::manager::UDNRegistry; use crate::discovery::manager::UDNRegistry;
use crate::errors::ControlPointError; use crate::errors::ControlPointError;
use crate::events::{MediaServerEventBus, RendererEventBus}; 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::media_server_events::spawn_media_server_event_runtime;
use crate::model::{MediaServerEvent, RendererEvent}; use crate::model::{MediaServerEvent, RendererEvent};
use crate::model::{PlaybackState, TrackMetadata}; use crate::model::{PlaybackState, TrackMetadata};
@@ -147,51 +147,52 @@ impl ControlPoint {
let udn_cache_for_mdns = Arc::clone(&udn_cache); let udn_cache_for_mdns = Arc::clone(&udn_cache);
thread::spawn(move || { thread::spawn(move || {
use crate::discovery::ChromecastDiscoveryManager; 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 = let mut discovery_manager =
ChromecastDiscoveryManager::new(registry_for_mdns, udn_cache_for_mdns); ChromecastDiscoveryManager::new(registry_for_mdns, udn_cache_for_mdns);
debug!("Starting mDNS discovery thread for Chromecast devices"); 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 let daemon = match ServiceDaemon::new() {
async_std::task::block_on(async { Ok(d) => d,
// Create mDNS discovery stream with 15 second query interval Err(e) => {
// (shorter interval for faster initial discovery) error!("Failed to create mDNS daemon: {}", e);
match mdns::discover::all(SERVICE_NAME, Duration::from_secs(15)) { return;
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 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. // 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; binding.auto_play_on_refresh = auto_play;
renderer.set_playlist_binding(Some(binding)); renderer.set_playlist_binding(Some(binding));
let callback: Option<Box<dyn FnOnce(&DeviceId) -> Result<(), ControlPointError> + Send + 'static>> = let callback: Option<
if auto_play { Box<dyn FnOnce(&DeviceId) -> Result<(), ControlPointError> + Send + 'static>,
let reg = Arc::clone(&self.registry); > = if auto_play {
Some(Box::new(move |rid: &DeviceId| { let reg = Arc::clone(&self.registry);
let renderer = reg.read().unwrap().get_renderer(rid).ok_or_else(|| Some(Box::new(move |rid: &DeviceId| {
ControlPointError::ControlPoint(format!("Renderer {} not found", rid.0)) let renderer = reg.read().unwrap().get_renderer(rid).ok_or_else(|| {
)?; ControlPointError::ControlPoint(format!("Renderer {} not found", rid.0))
renderer.play_current_from_queue() })?;
})) renderer.play_current_from_queue()
} else { }))
None } else {
}; None
let _ = schedule_queue_refresh_for( };
&self.registry, let _ =
renderer_id, schedule_queue_refresh_for(&self.registry, renderer_id, &self.event_bus, callback);
&self.event_bus,
callback,
);
return Ok(()); return Ok(());
} }
@@ -1240,22 +1238,23 @@ impl ControlPoint {
// Note: BindingChanged event is emitted automatically by MusicRenderer::set_playlist_binding() // 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) // For initial attach with auto_play, force playback start (don't check if idle)
let callback: Option<Box<dyn FnOnce(&DeviceId) -> Result<(), ControlPointError> + Send + 'static>> = let callback: Option<
if auto_play { Box<dyn FnOnce(&DeviceId) -> Result<(), ControlPointError> + Send + 'static>,
let reg = Arc::clone(&self.registry); > = if auto_play {
Some(Box::new(move |rid: &DeviceId| { let reg = Arc::clone(&self.registry);
debug!( Some(Box::new(move |rid: &DeviceId| {
renderer = rid.0.as_str(), debug!(
"Attach callback: forcing playback start (not checking if idle)" 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)) let renderer = reg.read().unwrap().get_renderer(rid).ok_or_else(|| {
)?; ControlPointError::ControlPoint(format!("Renderer {} not found", rid.0))
renderer.play_current_from_queue() })?;
})) renderer.play_current_from_queue()
} else { }))
None } else {
}; None
};
let _ = schedule_queue_refresh_for(&self.registry, renderer_id, &self.event_bus, callback); let _ = schedule_queue_refresh_for(&self.registry, renderer_id, &self.event_bus, callback);
Ok(()) Ok(())
@@ -1806,7 +1805,9 @@ fn schedule_queue_refresh_for(
registry: &Arc<RwLock<DeviceRegistry>>, registry: &Arc<RwLock<DeviceRegistry>>,
renderer_id: &DeviceId, renderer_id: &DeviceId,
event_bus: &RendererEventBus, event_bus: &RendererEventBus,
after_refresh: Option<Box<dyn FnOnce(&DeviceId) -> Result<(), ControlPointError> + Send + 'static>>, after_refresh: Option<
Box<dyn FnOnce(&DeviceId) -> Result<(), ControlPointError> + Send + 'static>,
>,
) -> SyncScheduleOutcome { ) -> SyncScheduleOutcome {
// Step 1: Get renderer from registry // Step 1: Get renderer from registry
let renderer = { let renderer = {
@@ -1895,7 +1896,9 @@ fn schedule_queue_refresh_for(
}; };
match music_server { match music_server {
Some(s) => fetch_queue_items_for(&s, &container_id_clone), 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(),
)),
} }
}); });

View File

@@ -5,14 +5,15 @@
//! This module handles the discovery of Chromecast devices and registers them //! This module handles the discovery of Chromecast devices and registers them
//! directly into the `DeviceRegistry`. //! directly into the `DeviceRegistry`.
use std::collections::HashMap;
use std::net::IpAddr;
use std::sync::{Arc, Mutex, RwLock}; use std::sync::{Arc, Mutex, RwLock};
use crate::DeviceId; use mdns_sd::ResolvedService;
use crate::DeviceRegistry; use mdns_sd::ServiceInfo;
use crate::discovery::manager::UDNRegistry; use crate::discovery::manager::UDNRegistry;
use crate::model::{RendererCapabilities, RendererInfo, RendererProtocol}; use crate::model::{RendererCapabilities, RendererInfo, RendererProtocol};
use crate::DeviceId;
use crate::DeviceRegistry;
use tracing::{debug, warn}; use tracing::{debug, warn};
/// Gestionnaire des événements mDNS pour Chromecast. /// 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 /// `ServiceInfo` arrive pré-assemblé : plus besoin de jointure manuelle
/// Chromecast et les enregistre directement dans le registre. /// des enregistrements PTR / A / SRV / TXT.
pub fn handle_mdns_response(&mut self, response: mdns::Response) { pub fn handle_service_resolved(&mut self, info: &ResolvedService) {
// Extract basic information from the mDNS response let fullname = info.get_fullname().to_string();
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;
}
};
debug!("Processing mDNS response for service: {}", service_name); debug!("Processing resolved Chromecast service: {}", fullname);
// Extract IP addresses let host = match info
let addresses: Vec<IpAddr> = response .get_addresses()
.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
.iter() .iter()
.find(|addr| matches!(addr, IpAddr::V4(_))) .find(|a| a.is_ipv4())
.or_else(|| addresses.first()) .or_else(|| info.get_addresses().iter().next())
{ {
Some(addr) => addr.to_string(), Some(addr) => addr.to_string(),
None => { None => {
warn!("Could not extract host from addresses"); warn!("No IP address for Chromecast service: {}", fullname);
return; return;
} }
}; };
// Extract port from SRV record let port = info.get_port();
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
// Extract TXT records for additional metadata let uuid = info
let txt_records: HashMap<String, String> = response .get_property_val_str("id")
.records() .unwrap_or_default()
.filter_map(|r| { .to_string();
if let mdns::RecordKind::TXT(ref data) = r.kind { let uuid = if uuid.is_empty() {
Some(data.clone()) format!("chromecast-{}-{}", host, port)
} else { } else {
None uuid
} };
})
.flat_map(|data| {
// data is Vec<String>, 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();
// Extract metadata from TXT records let model = info.get_property_val_str("md").map(|s| s.to_string());
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());
// Extract friendly name from TXT record "fn" if available let friendly_name = info
// Otherwise, extract from service instance name (PTR record) .get_property_val_str("fn")
let friendly_name = txt_records.get("fn").cloned().unwrap_or_else(|| { .map(|s| s.to_string())
// Fallback: extract from service name, removing the UUID suffix if present .unwrap_or_else(|| {
service_name fullname
.split("._googlecast._tcp.local") .split("._googlecast._tcp.local")
.next() .next()
.unwrap_or("Unknown Chromecast") .unwrap_or("Unknown Chromecast")
.split('-') .split('-')
.take_while(|part| part.len() != 32) // Skip 32-char hex UUID .take_while(|part| part.len() != 32)
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("-") .join("-")
.trim() .trim()
.to_string() .to_string()
}); });
debug!( debug!(
"Discovered Chromecast: {} at {}:{} (UUID: {}, Model: {:?})", "Discovered Chromecast: {} at {}:{} (UUID: {}, Model: {:?})",
friendly_name, host, port, uuid, model friendly_name, host, port, uuid, model
); );
// Build UDN and check cache
let udn = format!("uuid:{}", uuid); 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) { if !UDNRegistry::should_fetch(self.udn_cache.clone(), &udn, default_max_age) {
debug!("Chromecast {} recently seen, skipping", udn); debug!("Chromecast {} recently seen, skipping", udn);
return; return;
} }
// Create RendererInfo for the registry
let renderer_info = build_renderer_info( let renderer_info = build_renderer_info(
&uuid, &uuid,
&friendly_name, &friendly_name,
&host, &host,
port, port,
model.as_deref(), model.as_deref(),
manufacturer.as_deref(), Some("Google Inc."),
); );
// Register the renderer
self.device_registry self.device_registry
.write() .write()
.expect("DeviceRegistry mutex lock failed") .expect("DeviceRegistry mutex lock failed")

View File

@@ -402,7 +402,7 @@ unsafe fn setup_metadata(
use libflac_sys::*; use libflac_sys::*;
// Create a Vorbis Comment block // 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() { if meta.is_null() {
return Err(FlacError::LibFlacInit( return Err(FlacError::LibFlacInit(
"Failed to create metadata block".into(), "Failed to create metadata block".into(),
@@ -420,12 +420,14 @@ unsafe fn setup_metadata(
FlacError::LibFlacInit("Failed to create CString for field value".into()) FlacError::LibFlacInit("Failed to create CString for field value".into())
})?; })?;
let mut entry: FLAC__StreamMetadata_VorbisComment_Entry = std::mem::zeroed(); let mut entry: FLAC__StreamMetadata_VorbisComment_Entry = unsafe { std::mem::zeroed() };
let success = FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair( let success = unsafe {
&mut entry as *mut _, FLAC__metadata_object_vorbiscomment_entry_from_name_value_pair(
c_field_name.as_ptr(), &mut entry as *mut _,
c_value.as_ptr(), c_field_name.as_ptr(),
); c_value.as_ptr(),
)
};
if success == 0 { if success == 0 {
return Err(FlacError::LibFlacInit(format!( return Err(FlacError::LibFlacInit(format!(
@@ -435,7 +437,7 @@ unsafe fn setup_metadata(
} }
let append_success = 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 { if append_success == 0 {
return Err(FlacError::LibFlacInit(format!( return Err(FlacError::LibFlacInit(format!(
@@ -476,7 +478,7 @@ unsafe fn setup_metadata(
// Set the metadata on the encoder // Set the metadata on the encoder
let mut metadata_array = [meta]; 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 { if set_success == 0 {
return Err(FlacError::LibFlacInit( return Err(FlacError::LibFlacInit(
@@ -666,8 +668,8 @@ unsafe extern "C" fn write_callback(
_current_frame: u32, _current_frame: u32,
client_data: *mut c_void, client_data: *mut c_void,
) -> libflac_sys::FLAC__StreamEncoderWriteStatus { ) -> libflac_sys::FLAC__StreamEncoderWriteStatus {
let state = &mut *(client_data as *mut EncoderClientState); let state = unsafe { &mut *(client_data as *mut EncoderClientState) };
let slice = std::slice::from_raw_parts(buffer, bytes); let slice = unsafe { std::slice::from_raw_parts(buffer, bytes) };
match state.tx.blocking_send(Ok(slice.to_vec())) { match state.tx.blocking_send(Ok(slice.to_vec())) {
Ok(_) => libflac_sys::FLAC__STREAM_ENCODER_WRITE_STATUS_OK, Ok(_) => libflac_sys::FLAC__STREAM_ENCODER_WRITE_STATUS_OK,
Err(_) => { Err(_) => {

View File

@@ -83,7 +83,7 @@ pub mod paradise_streaming;
pub use content_handler::ContentHandler; pub use content_handler::ContentHandler;
pub use device::MEDIA_SERVER; pub use device::MEDIA_SERVER;
pub use device_ext::MediaServerDeviceExt; 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 source_registry::SourceRegistry;
pub use sources::{SourceInitError, SourcesExt}; pub use sources::{SourceInitError, SourcesExt};

View File

@@ -14,23 +14,6 @@ use std::sync::Arc;
// Réexporter le trait de base de pmosource // Réexporter le trait de base de pmosource
pub use pmosource::MusicSourceExt; 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<Arc<dyn MusicSource>> {
pmosource::api::list_all_sources().await
}
/// Trait d'extension pour le serveur MediaServer UPnP /// Trait d'extension pour le serveur MediaServer UPnP
/// ///
/// Ce trait ajoute des méthodes spécifiques au MediaServer UPnP. /// Ce trait ajoute des méthodes spécifiques au MediaServer UPnP.

View File

@@ -273,13 +273,13 @@ pub fn init_logging() -> LogState {
Ok(value) => { Ok(value) => {
let trimmed = value.trim(); let trimmed = value.trim();
if let Some(level) = string_to_level(trimmed) { if let Some(level) = string_to_level(trimmed) {
let filter = let filter = build_filter_with_noise_suppressions(trimmed);
EnvFilter::try_new(trimmed).unwrap_or_else(|_| EnvFilter::new("trace"));
(filter, level, format!("RUST_LOG ({})", trimmed)) (filter, level, format!("RUST_LOG ({})", trimmed))
} else { } else {
let filter = build_filter_with_noise_suppressions(trimmed);
match EnvFilter::try_new(trimmed) { match EnvFilter::try_new(trimmed) {
Ok(filter) => { Ok(raw) => {
let level_hint = filter let level_hint = raw
.max_level_hint() .max_level_hint()
.and_then(levelfilter_to_level) .and_then(levelfilter_to_level)
.unwrap_or(Level::TRACE); .unwrap_or(Level::TRACE);
@@ -295,7 +295,7 @@ pub fn init_logging() -> LogState {
.ok() .ok()
.and_then(|cfg| string_to_level(cfg.trim())) .and_then(|cfg| string_to_level(cfg.trim()))
.unwrap_or(Level::TRACE); .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()) (filter, cfg_level, "config".to_string())
} }
} }
@@ -307,7 +307,7 @@ pub fn init_logging() -> LogState {
.ok() .ok()
.and_then(|cfg| string_to_level(cfg.trim())) .and_then(|cfg| string_to_level(cfg.trim()))
.unwrap_or(Level::TRACE); .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()) (filter, cfg_level, "config".to_string())
} }
}; };
@@ -446,6 +446,27 @@ pub async fn log_setup_post(
.into_response() .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<Level> { fn string_to_level(s: &str) -> Option<Level> {
match s.to_uppercase().as_str() { match s.to_uppercase().as_str() {
"ERROR" => Some(Level::ERROR), "ERROR" => Some(Level::ERROR),

View File

@@ -1 +1 @@
0.3.41 0.3.42