use std::net::IpAddr; use std::time::Duration; use sonos::Speaker; use failure::Fallible; use tokio::io::{self, AsyncWriteExt, AsyncReadExt}; use futures::future::try_join_all; pub async fn find_speaker_by_name(name: &str) -> Fallible { let mut speakers = discover(true, false).await?; let mut min = 100; speakers.sort_by(|a, b| { let a = strsim::damerau_levenshtein(&a.name, name); let b = strsim::damerau_levenshtein(&b.name, name); if a < min { min = a; } if b < min { min = b; } a.cmp(&b) }); if min > 5 { bail!("Couldn't find a speaker by that name"); } let speaker = speakers.remove(0); if min > 2 { let mut stdin = io::stdin(); let mut stdout = io::stdout(); stdout.write_all(format!("Couldn't find speaker '{}', did you mean {}? [Y/n] ", name, speaker.name).as_bytes()).await?; stdout.flush().await?; let input = stdin .read_u8() .await? as char; if input != 'y' && input != 'Y' { bail!("Couldn't find a speaker by that name"); } } Ok(speaker) } pub async fn discover(pretty: bool, invalidate: bool) -> Fallible> { use serde::Serialize; const CACHE_FILE_NAME: &str = "/tmp/sonos-cli-speakers"; if !invalidate { if let Ok(mut cache) = tokio::fs::File::open(CACHE_FILE_NAME).await { let mut contents: Vec = vec![]; cache.read_to_end(&mut contents).await?; let cache: Vec = serde_json::from_slice(contents.as_ref())?; return try_join_all(cache.into_iter().map(Speaker::from_ip)).await; } } if pretty { tokio::spawn(async { let mut stdout = io::stdout(); const TWO: &str = "\u{23F2}\u{FE0F} Give me 2 secs to discover your devices..."; const ONE: &str = "\u{23F2}\u{FE0F} Give me a sec to discover your devices..."; stdout.write_all(TWO.as_bytes()).await?; stdout.write_all(b"\r").await?; stdout.flush().await?; tokio::time::delay_for(Duration::from_millis(1000)).await; stdout.write_all(ONE.as_bytes()).await?; stdout.write_all(" ".repeat(TWO.len() - ONE.len()).as_bytes()).await?; stdout.write_all(b"\r").await?; stdout.flush().await?; tokio::time::delay_for(Duration::from_millis(999)).await; stdout.write_all(" ".repeat(TWO.len()).as_bytes()).await?; stdout.write_all(b"\r").await?; stdout.flush().await?; Ok::<(), failure::Error>(()) }); } let speakers = sonos::discover().await?; { // write IP addresses of all known speakers to cache let writer = std::fs::File::create(CACHE_FILE_NAME).unwrap(); let mut serializer = serde_json::Serializer::new(writer); speakers.iter() .map(|s| s.ip) .collect::>() .serialize(&mut serializer).unwrap(); } Ok(speakers) }