🏡 index : ~doyle/sonos-cli.git

author Jordan Doyle <jordan@9t9t9.com> 2018-04-05 9:00:16.0 +00:00:00
committer Jordan Doyle <jordan@9t9t9.com> 2018-04-05 9:00:16.0 +00:00:00
commit
a4c1d854dedcebec1f33f84d5584f496bda8c2b6 [patch]
tree
b18d2243739d6ddfff36ed77d9e8dab84b9e0d48
parent
a501903506a14445ae4d3ff955a07a9bdfca9545
download
a4c1d854dedcebec1f33f84d5584f496bda8c2b6.tar.gz

Allow referencing rooms by name



Diff

 .gitignore  |   1 +-
 Cargo.lock  |   1 +-
 Cargo.toml  |   4 +-
 src/main.rs | 122 +++++++++++++++++++++++++++++++++++++++++++++++--------------
 4 files changed, 100 insertions(+), 28 deletions(-)

diff --git a/.gitignore b/.gitignore
index 2f7896d..ee31042 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
target/
cmake-build-debug/
diff --git a/Cargo.lock b/Cargo.lock
index 19af588..9440dd5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -896,6 +896,7 @@ dependencies = [
 "serde_derive 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
 "serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)",
 "sonos 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index ff4141e..9a94a34 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -4,7 +4,7 @@ version = "0.1.0"
authors = ["Jordan Doyle <jordan@9t9t9.com>"]

[dependencies]
sonos = { path = "../soncon/sonos.rs" }
sonos = ""
clap = ""
log = ""
fern = ""
@@ -13,3 +13,5 @@ chrono = ""
serde = ""
serde_derive = ""
serde_json = ""

strsim = ""
diff --git a/src/main.rs b/src/main.rs
index 5ca5f23..2ab5a7f 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -13,6 +13,8 @@ extern crate serde_derive;
extern crate serde;
extern crate serde_json;

extern crate strsim;

use std::net::IpAddr;
use sonos::Speaker;

@@ -45,7 +47,8 @@ fn argparse<'a, 'b>() -> clap::App<'a, 'b> {
                        .arg(Arg::with_name("VOLUME")
                                .help("Percent volume to set speaker to 0-100")
                                .index(1)))
        .subcommand(SubCommand::with_name("rooms").about("List all of your speakers"))
        .subcommand(SubCommand::with_name("rooms").about("List all of your speakers")
                        .arg(Arg::with_name("invalidate").help("Detect new speakers and room arrangements")))
}

fn setup_logger() -> Result<(), fern::InitError> {
@@ -89,10 +92,44 @@ fn main() {
    let speaker = if let Ok(ip) = controller.parse::<IpAddr>() {
        Speaker::from_ip(ip).expect("speaker")
    } else {
        // sonos::discover().unwrap().iter()
        //    .find(|d| d.name == controller)
        //    .unwrap()
        panic!("not implemented");
        let mut speakers = discover(true, false);

        let mut min = 100;

        speakers.sort_by(|a, b| {
            let a = strsim::damerau_levenshtein(&a.name, controller);
            let b = strsim::damerau_levenshtein(&b.name, controller);

            if a < min { min = a; }
            if b < min { min = b; }

            a.cmp(&b)
        });

        if min > 5 {
            panic!("Couldn't find a speaker by that name");
        }

        let speaker = speakers.remove(0);

        if min > 2 {
            use std::io::{Read, Write};
            print!("Couldn't find speaker '{}', did you mean {}? [Y/n] ", controller, speaker.name);
            std::io::stdout().flush();

            let input: char = std::io::stdin()
                .bytes()
                .next()
                .and_then(|result| result.ok())
                .map(|byte| byte as char)
                .unwrap();

            if input != 'y' && input != 'Y' {
                panic!();
            }
        }

        speaker
    };

    match args.subcommand() {
@@ -136,28 +173,8 @@ fn main() {

            speaker.seek(&duration).expect("couldn't seek");
        },
        ("rooms", _) => {
            std::thread::spawn(|| {
                use std::io::{Write, 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...";

                print!("{}\r", TWO);
                stdout().flush().unwrap();

                std::thread::sleep(std::time::Duration::from_millis(1000));

                print!("{}{}\r", ONE, " ".repeat(TWO.len() - ONE.len()));
                stdout().flush().unwrap();

                std::thread::sleep(std::time::Duration::from_millis(999));

                print!("{}\r", " ".repeat(TWO.len()));
                stdout().flush().unwrap();
            });

            let devices = sonos::discover().unwrap();
        ("rooms", Some(sub)) => {
            let devices = discover(true, sub.is_present("invalidate"));

            let mut rooms = std::collections::HashMap::new();

@@ -182,6 +199,57 @@ fn main() {
    }
}

pub fn discover(pretty: bool, invalidate: bool) -> Vec<sonos::Speaker> {
    use serde::Serialize;

    const CACHE_FILE_NAME: &str = "/tmp/sonos-cli-speakers";

    if !invalidate {
        if let Ok(cache) = std::fs::File::open(CACHE_FILE_NAME) {
            let cache: Vec<IpAddr> = serde_json::from_reader(std::io::BufReader::new(cache))
                                                .unwrap();

            return cache.iter()
                .map(|i| sonos::Speaker::from_ip(*i).unwrap())
                .collect();
        }
    }

    if pretty {
        std::thread::spawn(|| {
            use std::io::{Write, 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...";

            print!("{}\r", TWO);
            stdout().flush().unwrap();

            std::thread::sleep(std::time::Duration::from_millis(1000));

            print!("{}{}\r", ONE, " ".repeat(TWO.len() - ONE.len()));
            stdout().flush().unwrap();

            std::thread::sleep(std::time::Duration::from_millis(999));

            print!("{}\r", " ".repeat(TWO.len()));
            stdout().flush().unwrap();
        });
    }

    let speakers = sonos::discover().unwrap();

    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::<Vec<IpAddr>>()
        .serialize(&mut serializer).unwrap();

    speakers
}

#[derive(Serialize, Deserialize, Debug)]
struct Track {
    pub title: String,