Skip to main content

max / supernote-push

1.6 KB · 57 lines History Blame Raw
1 use std::time::{Duration, Instant};
2
3 use mdns_sd::{ServiceDaemon, ServiceEvent};
4
5 use crate::{Error, Result, client::Supernote};
6
7 const SERVICE: &str = "_http._tcp.local.";
8
9 pub fn discover(timeout: Duration) -> Result<Vec<Supernote>> {
10 let mdns = ServiceDaemon::new().map_err(Error::from_mdns)?;
11 let receiver = mdns.browse(SERVICE).map_err(Error::from_mdns)?;
12
13 let deadline = Instant::now() + timeout;
14 let mut found: Vec<Supernote> = Vec::new();
15
16 while let Some(remaining) = deadline.checked_duration_since(Instant::now()) {
17 let Ok(evt) = receiver.recv_timeout(remaining) else {
18 break;
19 };
20 let ServiceEvent::ServiceResolved(info) = evt else {
21 continue;
22 };
23
24 if !looks_like_supernote(info.get_fullname()) {
25 continue;
26 }
27 let Some(addr) = info.get_addresses().iter().next() else {
28 continue;
29 };
30 let host = addr.to_string();
31 if found.iter().any(|d| d.host() == host) {
32 continue;
33 }
34 found.push(Supernote::at(host).with_port(info.get_port()));
35 }
36
37 let _ = mdns.shutdown();
38 Ok(found)
39 }
40
41 fn looks_like_supernote(fullname: &str) -> bool {
42 let lower = fullname.to_lowercase();
43 lower.contains("supernote") || lower.contains("ratta")
44 }
45
46 #[cfg(test)]
47 mod tests {
48 use super::*;
49
50 #[test]
51 fn matches_supernote_service_names() {
52 assert!(looks_like_supernote("Supernote A6X._http._tcp.local."));
53 assert!(looks_like_supernote("ratta-nomad._http._tcp.local."));
54 assert!(!looks_like_supernote("printer._http._tcp.local."));
55 }
56 }
57