🏡 index : ~doyle/sonos-cli.git

author Jordan Doyle <jordan@doyle.la> 2020-02-23 20:27:19.0 +00:00:00
committer Jordan Doyle <jordan@doyle.la> 2020-02-23 20:27:19.0 +00:00:00
commit
ff0c600e2524255d33ab0e27ccd0a067358482f2 [patch]
tree
11824cafbf7e81929e55fd50418d7a676a1c0a7e
parent
0ed4c77dcd502f1475faf39bfe4c17da0b7daf02
download
ff0c600e2524255d33ab0e27ccd0a067358482f2.tar.gz

Move to futures-based sonos.rs and allow queue manipulation



Diff

 Cargo.lock       | 450 ++++++++++++++++++++++++++++++++++++--------------------
 Cargo.toml       |   4 +-
 src/discovery.rs | 108 +++++++++++++-
 src/main.rs      | 297 +++++++++++++------------------------
 src/util.rs      |  41 +++++-
 5 files changed, 555 insertions(+), 345 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index 4e04e14..3ec3b57 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -22,6 +22,44 @@ version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "arc-swap"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "async-std"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
 "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "kv-log-macro 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
 "memchr 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
 "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
 "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
 "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "async-task"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -58,15 +96,6 @@ dependencies = [

[[package]]
name = "base64"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "base64"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

@@ -81,11 +110,6 @@ version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "bytes"
version = "0.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -99,11 +123,6 @@ dependencies = [
]

[[package]]
name = "c_linked_list"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "cc"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -152,24 +171,59 @@ version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "dtoa"
version = "0.4.5"
name = "crossbeam-channel"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "encoding_rs"
version = "0.8.22"
name = "crossbeam-deque"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "crossbeam-epoch"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
 "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
 "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "error-chain"
version = "0.10.0"
name = "crossbeam-utils"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "backtrace 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)",
 "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "dtoa"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "encoding_rs"
version = "0.8.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
@@ -234,11 +288,26 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "futures"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "futures-channel"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
@@ -247,11 +316,32 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "futures-executor"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "futures-io"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "futures-macro"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
 "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
 "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "futures-sink"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -262,41 +352,34 @@ version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "futures-timer"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "futures-util"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "memchr 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
 "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
 "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "gcc"
version = "0.3.55"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "get_if_addrs"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "c_linked_list 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 "get_if_addrs-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "get_if_addrs-sys"
version = "0.1.1"
name = "genawaiter"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
@@ -369,24 +452,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "hyper"
version = "0.10.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
 "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
 "mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
 "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
 "traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "hyper"
version = "0.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
@@ -422,16 +487,6 @@ dependencies = [

[[package]]
name = "idna"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
 "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "idna"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
@@ -479,9 +534,12 @@ dependencies = [
]

[[package]]
name = "language-tags"
version = "0.2.2"
name = "kv-log-macro"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "lazy_static"
@@ -495,14 +553,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "log"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
@@ -515,16 +565,21 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "memchr"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "mime"
version = "0.2.6"
name = "memoffset"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
 "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
@@ -560,6 +615,27 @@ dependencies = [
]

[[package]]
name = "mio-named-pipes"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
 "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
 "miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "mio-uds"
version = "0.6.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "miow"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -571,6 +647,15 @@ dependencies = [
]

[[package]]
name = "miow"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)",
 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "native-tls"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -633,6 +718,11 @@ dependencies = [
]

[[package]]
name = "once_cell"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "openssl"
version = "0.10.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -664,11 +754,6 @@ dependencies = [

[[package]]
name = "percent-encoding"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

@@ -711,6 +796,21 @@ version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "proc-macro-hack"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
 "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "proc-macro-nested"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "proc-macro2"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -832,13 +932,16 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "ryu"
version = "1.0.2"
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "safemem"
version = "0.3.3"
name = "ryu"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
@@ -851,6 +954,11 @@ dependencies = [
]

[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "security-framework"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -870,6 +978,19 @@ dependencies = [
]

[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "serde"
version = "1.0.104"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -906,6 +1027,15 @@ dependencies = [
]

[[package]]
name = "signal-hook-registry"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "slab"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -916,16 +1046,27 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "socket2"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "sonos"
version = "0.1.4"
source = "git+https://github.com/w4/sonos.rs#7f16481f3ef41c1df80b0d44bbb33a9a50bf7305"
version = "0.2.0"
dependencies = [
 "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
 "regex 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
 "ssdp 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "ssdp-client 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "xmltree 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
]

@@ -937,12 +1078,14 @@ dependencies = [
 "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
 "fern 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
 "serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
 "serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
 "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
 "sonos 0.1.4 (git+https://github.com/w4/sonos.rs)",
 "sonos 0.2.0",
 "strsim 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
@@ -951,16 +1094,13 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "ssdp"
version = "0.7.0"
name = "ssdp-client"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "get_if_addrs 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
 "hyper 0.10.16 (registry+https://github.com/rust-lang/crates.io-index)",
 "log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
 "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
 "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
 "async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "genawaiter 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
@@ -1040,13 +1180,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
 "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
 "memchr 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
 "mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
 "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
 "num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
 "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "tokio-macros 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
 "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "tokio-macros"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)",
 "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
 "syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
@@ -1077,29 +1234,11 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "traitobject"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "try-lock"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "typeable"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "unicase"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1140,16 +1279,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"

[[package]]
name = "url"
version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
 "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
 "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
 "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
]

[[package]]
name = "url"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
@@ -1352,27 +1481,30 @@ dependencies = [
"checksum aho-corasick 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)" = "743ad5a418686aad3b87fd14c43badd828cf26e214a00f92a384291cf22e1811"
"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
"checksum anyhow 1.0.26 (registry+https://github.com/rust-lang/crates.io-index)" = "7825f6833612eb2414095684fcf6c635becf3ce97fe48cf6421321e93bfbd53c"
"checksum arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff"
"checksum async-std 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "538ecb01eb64eecd772087e5b6f7540cbc917f047727339a472dafed2185b267"
"checksum async-task 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0ac2c016b079e771204030951c366db398864f5026f84a44dafb0ff20f02085d"
"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
"checksum backtrace 0.3.44 (registry+https://github.com/rust-lang/crates.io-index)" = "e4036b9bf40f3cf16aba72a3d65e8a520fc4bafcdc7079aea8f848c58c5b5536"
"checksum backtrace-sys 0.1.32 (registry+https://github.com/rust-lang/crates.io-index)" = "5d6575f128516de27e3ce99689419835fce9643a9b215a14d2b5b685be018491"
"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
"checksum bumpalo 3.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742"
"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
"checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
"checksum c_linked_list 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4964518bd3b4a8190e832886cdc0da9794f12e8e6c1613a9e90ff331c4c8724b"
"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
"checksum chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "31850b4a4d6bae316f7a09e691c944c28299298837edc0a03f755618c23cbc01"
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d"
"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b"
"checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
"checksum crossbeam-deque 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
"checksum crossbeam-epoch 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
"checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
"checksum encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8d03faa7fe0c1431609dfad7bbe827af30f82e1e2ae6f7ee4fca6bd764bc28"
"checksum error-chain 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d9435d864e017c3c6afeac1654189b06cdb491cf2ff73dbf0d73b0f292f42ff8"
"checksum failure 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f8273f13c977665c5db7eb2b99ae520952fe5ac831ae4cd09d80c4c7042b5ed9"
"checksum failure_derive 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08"
"checksum fern 0.5.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e69ab0d5aca163e388c3a49d284fed6c3d0810700e77c5ae2756a50ec1a4daaa"
@@ -1381,15 +1513,17 @@ dependencies = [
"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
"checksum futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780"
"checksum futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8"
"checksum futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a"
"checksum futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba"
"checksum futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6"
"checksum futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7"
"checksum futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6"
"checksum futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27"
"checksum futures-timer 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a1de7508b218029b0f01662ed8f61b1c964b3ae99d6f25462d0f55a595109df6"
"checksum futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5"
"checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2"
"checksum get_if_addrs 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "abddb55a898d32925f3148bd281174a68eeb68bbfd9a5938a57b18f506ee4ef7"
"checksum get_if_addrs-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0d04f9fb746cf36b191c00f3ede8bde9c8e64f9f4b05ae2694a9ccf5e3f5ab48"
"checksum genawaiter 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1236259ce812baf9e1d1e316724e0fec341ce788f038aa543f813dfe78e12d32"
"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
"checksum h2 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9433d71e471c1736fd5a61b671fc0b148d7a2992f666c958d03cd8feb3b88d1"
"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
@@ -1397,38 +1531,39 @@ dependencies = [
"checksum http 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b708cc7f06493459026f53b9a61a7a121a5d1ec6238dee58ea4941132b30156b"
"checksum http-body 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
"checksum hyper 0.10.16 (registry+https://github.com/rust-lang/crates.io-index)" = "0a0652d9a2609a968c14be1a9ea00bf4b1d64e2e1f53a1b51b6fff3a6e829273"
"checksum hyper 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fa1c527bbc634be72aa7ba31e4e4def9bbb020f5416916279b7c705cd838893e"
"checksum hyper-tls 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3adcd308402b9553630734e9c36b77a7e48b3821251ca2493e8cd596763aafaa"
"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e"
"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
"checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
"checksum js-sys 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "7889c7c36282151f6bf465be4700359318aef36baa951462382eae49e9577cf9"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
"checksum kv-log-macro 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c54d9f465d530a752e6ebdc217e081a7a614b48cb200f6f0aee21ba6bc9aabb"
"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
"checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
"checksum memchr 2.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "53445de381a1f436797497c61d851644d0e8e88e6140f22872ad33a704933978"
"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
"checksum memoffset 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
"checksum mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
"checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599"
"checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f"
"checksum mio-named-pipes 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3"
"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
"checksum miow 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "396aa0f2003d7df8395cb93e09871561ccc3e785f0acb369170e8cc74ddf9226"
"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e"
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
"checksum num_cpus 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
"checksum once_cell 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b"
"checksum openssl 0.10.28 (registry+https://github.com/rust-lang/crates.io-index)" = "973293749822d7dd6370d6da1e523b0d1db19f06c459134c658b2a4261378b52"
"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
"checksum openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)" = "1024c0a59774200a555087a6da3f253a9095a5f344e353b212ac4c8b8e450986"
"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831"
"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
"checksum pin-project 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7804a463a8d9572f13453c516a5faea534a2403d7ced2f0c7e100eeff072772c"
"checksum pin-project-internal 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "385322a45f2ecf3410c68d2a549a4a2685e8051d0f278e39743ff4e451cb9b3f"
@@ -1436,6 +1571,8 @@ dependencies = [
"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
"checksum proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
"checksum proc-macro2 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3acb317c6ff86a4e579dfa00fc5e6cca91ecbb4e7eb2df0468805b674eb88548"
"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe"
"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
@@ -1448,20 +1585,24 @@ dependencies = [
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum reqwest 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0e798e19e258bf6c30a304622e3e9ac820e483b06a1857a026e1f109b113fe4"
"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
"checksum safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
"checksum schannel 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "507a9e6e8ffe0a4e0ebb9a10293e62fdf7657c06f1b8bb07a8fcf697d2abf295"
"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
"checksum security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df"
"checksum security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895"
"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
"checksum serde 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "414115f25f818d7dfccec8ee535d76949ae78584fc4f79a6f45a904bf8ab4449"
"checksum serde_derive 1.0.104 (registry+https://github.com/rust-lang/crates.io-index)" = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64"
"checksum serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)" = "9371ade75d4c2d6cb154141b9752cf3781ec9c05e0e5cf35060e1e70ee7b9c25"
"checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
"checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41"
"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
"checksum smallvec 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5c2fb2ec9bcd216a5b0d0ccf31ab17b5ed1d627960edff65bbe95d3ce221cefc"
"checksum sonos 0.1.4 (git+https://github.com/w4/sonos.rs)" = "<none>"
"checksum socket2 0.3.11 (registry+https://github.com/rust-lang/crates.io-index)" = "e8b74de517221a2cb01a53349cf54182acdc31a074727d3079068448c0676d85"
"checksum sourcefile 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4bf77cb82ba8453b42b6ae1d692e4cdc92f9a47beaf89a847c8be83f4e328ad3"
"checksum ssdp 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8c020eda46782b417016b689d4d409c8d1b3b4f98bbd51ff5003bb1b0a9041fa"
"checksum ssdp-client 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04cf55128834903a7de193c5d9b26c0e035f068f98780f4f711354f94a80bab7"
"checksum strsim 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
"checksum syn 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "af6f3550d8dff9ef7dc34d384ac6f107e5d31c8f57d9f28e0081503f547ac8f5"
@@ -1471,20 +1612,17 @@ dependencies = [
"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
"checksum tokio 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8fdd17989496f49cdc57978c96f0c9fe5e4a58a8bddc6813c449a4624f6a030b"
"checksum tokio-macros 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f4b1e7ed7d5d4c2af3d999904b0eebe76544897cdbfb2b9684bed2174ab20f7c"
"checksum tokio-tls 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7bde02a3a5291395f59b06ec6945a3077602fac2b07eeeaf0dee2122f3619828"
"checksum tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930"
"checksum tower-service 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860"
"checksum traitobject 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079"
"checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382"
"checksum typeable 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1410f6f91f21d1612654e7cc69193b0334f909dcf2c790c4826254fbb86f8887"
"checksum unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33"
"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
"checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4"
"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
diff --git a/Cargo.toml b/Cargo.toml
index 8c67b32..1565142 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,6 +2,7 @@
name = "sonos-cli"
version = "0.1.0"
authors = ["Jordan Doyle <jordan@doyle.la>"]
edition = "2018"

[dependencies]
sonos = { git = "https://github.com/w4/sonos.rs" }
@@ -12,6 +13,9 @@ chrono = ""

failure = ""

tokio = { version = "0.2", features = ["full"] }
futures = "0.3"

serde = ""
serde_derive = ""
serde_json = ""
diff --git a/src/discovery.rs b/src/discovery.rs
new file mode 100644
index 0000000..b3a2e25
--- /dev/null
+++ b/src/discovery.rs
@@ -0,0 +1,108 @@
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<Speaker> {
    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<Vec<Speaker>> {
    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<u8> = vec![];
            cache.read_to_end(&mut contents).await?;

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

    Ok(speakers)
}
diff --git a/src/main.rs b/src/main.rs
index 085f347..c621f79 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,23 +1,18 @@
#![feature(iter_rfold)]
#[macro_use]
extern crate clap;
#[macro_use] extern crate clap;
#[macro_use] extern crate log;
#[macro_use] extern crate serde_derive;
#[macro_use] extern crate failure;

extern crate sonos;

#[macro_use]
extern crate log;
extern crate fern;

#[macro_use]
extern crate serde_derive;
extern crate serde;
extern crate serde_json;

extern crate strsim;
#[macro_use] mod util;
mod discovery;

use std::time::Duration;
use std::net::IpAddr;

use sonos::Speaker;

use failure::Fallible;

fn argparse<'a, 'b>() -> clap::App<'a, 'b> {
    use clap::{App, AppSettings, Arg, SubCommand};

@@ -34,10 +29,21 @@ fn argparse<'a, 'b>() -> clap::App<'a, 'b> {
                .takes_value(true))
        .arg(Arg::with_name("json")
                .help("Return back JSON serialised responses for programmatic use of the CLI"))
        .subcommand(SubCommand::with_name("track").about("Show the current track information"))
        .subcommand(SubCommand::with_name("next").about("Skip to the next track"))
        .subcommand(SubCommand::with_name("previous").about("Go back to the last track"))
        .subcommand(SubCommand::with_name("info").about("Shows information about the speaker"))
        .subcommand(
            SubCommand::with_name("track")
                .about("Commands to manipulate the tracklist")
                .subcommand(SubCommand::with_name("next").about("Skip to the next track"))
                .subcommand(SubCommand::with_name("prev").about("Go back to the last track"))
                .subcommand(SubCommand::with_name("list").about("Get the list of tracks in the queue"))
                .subcommand(
                    SubCommand::with_name("play")
                        .about("Play a given track")
                        .subcommand(SubCommand::with_name("tv").about("Set the current speaker's input to the SPDIF"))
                        .subcommand(SubCommand::with_name("line-in").about("Set the current speaker's input to the line-in"))
                        .arg(Arg::with_name("uri").help("Queue position to skip to or a Sonos URI to play").index(1).conflicts_with_all(&["tv", "line-in"]))
                )
        )
        .subcommand(SubCommand::with_name("seek").about("Seek to a specific timestamp on the current track")
                        .arg(Arg::with_name("TIMESTAMP")
                                .help("hh:mm:ss/mm:ss")
@@ -51,135 +57,69 @@ fn argparse<'a, 'b>() -> clap::App<'a, 'b> {
                        .arg(Arg::with_name("invalidate").help("Detect new speakers and room arrangements")))
}

fn setup_logger() -> Result<(), fern::InitError> {
    fern::Dispatch::new()
        .format(|out, message, record| {
            out.finish(format_args!(
                "{}",
                message
            ))
        })
        .level(log::LevelFilter::Info)
        .chain(std::io::stdout())
        .apply()?;
    Ok(())
}

fn duration_to_hms(d: std::time::Duration) -> String {
    let mut s = String::new();

    const SECS_IN_MIN: u64 = 60;
    const MINS_IN_HOUR: u64 = 60;
    const SECS_IN_HOUR: u64 = SECS_IN_MIN * MINS_IN_HOUR;

    let hours = d.as_secs() / SECS_IN_HOUR;
    if hours > 0 {
        s.push_str(&format!("{:02}:", hours));
    }

    s.push_str(&format!("{:02}:", d.as_secs() % SECS_IN_HOUR / SECS_IN_MIN));
    s.push_str(&format!("{:02}", d.as_secs() % SECS_IN_MIN));

    s
}

fn main() {
#[tokio::main]
async fn main() -> Fallible<()> {
    let args = argparse().get_matches();

    setup_logger().expect("logger");
    util::setup_logger()?;

    let controller = args.value_of("controller").expect("controller");
    let speaker = if let Ok(ip) = controller.parse::<IpAddr>() {
        Speaker::from_ip(ip).expect("speaker")
    } else {
        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
    let speaker = match controller.parse::<IpAddr>() {
        Ok(ip) => Speaker::from_ip(ip).await?,
        Err(_) => discovery::find_speaker_by_name(controller).await?,
    };

    match args.subcommand() {
        ("track", _) => {
            let t = Track::new(&speaker).expect("track");
            info!("{}", if args.is_present("json") {
                serde_json::to_string(&t).expect("serialise track")
            } else {
                t.to_string()
            })
        },
        ("next", _) => speaker.next().expect("next"),
        ("previous", _) => speaker.previous().expect("prev"),
        ("info", _) => {
            let i = Info::new(&speaker);
            info!("{}", if args.is_present("json") {
                serde_json::to_string(&i).expect("serialise info")
            } else {
                i.to_string()
            })
        },
        ("volume", Some(sub)) => {
            if let Some(volume) = sub.value_of("VOLUME") {
                speaker.set_volume(volume.parse().unwrap());
            } else {
                info!("{}", Volume::new(speaker.volume().unwrap(), speaker.muted().unwrap()));
        ("track", Some(subargs)) => {
            match subargs.subcommand() {
                ("next", _) => speaker.queue().next().await?,
                ("prev", _) => speaker.queue().previous().await?,
                ("list", _) => print_struct!(args, &TrackList::new(&speaker).await?),
                ("play", Some(play_subargs)) => match play_subargs.subcommand_name() {
                    Some("tv") => speaker.play_tv().await?,
                    Some("line-in") => speaker.play_line_in().await?,
                    _ => {
                        let uri = play_subargs.value_of("uri")
                            .filter(|s| !s.is_empty())
                            .ok_or_else(|| format_err!("Must pass [tv], [line-in] or a URI to the play command"))?;

                        if let Ok(pos) = uri.parse::<u64>() {
                            speaker.queue().skip_to(&pos).await?
                        } else {
                            speaker.play_track(uri).await?
                        }
                    },
                },
                _ => print_struct!(args, &Track::new(&speaker).await?)
            }
        },
        ("info", _) => print_struct!(args, &Info::new(&speaker)),
        ("volume", Some(sub)) => match sub.value_of("VOLUME") {
            Some(volume) => speaker.set_volume(volume.parse()?).await?,
            None => print_struct!(args, &Volume::new(&speaker).await?),
        },
        ("seek", Some(sub)) => {
            let a = sub.value_of("TIMESTAMP").expect("timestamp");

            let mut multiplier = 1;

            let secs = a.split(":").collect::<Vec<&str>>().iter().rfold(0, |curr, iter_val| {
                let section_value = iter_val.parse::<u64>().expect("Can't parse int") * multiplier;
                let section_value = iter_val.parse::<u64>().expect("can't parse int") * multiplier;
                multiplier *= 60;
                curr + section_value
            });

            let duration = std::time::Duration::new(secs, 0);
            let duration = Duration::new(secs, 0);

            speaker.seek(&duration).expect("couldn't seek");
            speaker.seek(&duration).await?;
        },
        ("rooms", Some(sub)) => {
            let devices = discover(true, sub.is_present("invalidate"));
            let devices = discovery::discover(true, sub.is_present("invalidate")).await?;

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

            for device in devices {
                let coordinator = device.coordinator().unwrap();
                let coordinator = device.coordinator().await?;

                let mut room = rooms.entry(coordinator).or_insert(Vec::new());
                room.push(device);
@@ -197,60 +137,46 @@ fn main() {
            panic!();
        }
    }
}

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));
    Ok(())
}

            print!("{}\r", " ".repeat(TWO.len()));
            stdout().flush().unwrap();
        });
#[derive(Serialize, Deserialize, Debug)]
struct TrackListItem {
    pub position: u64,
    pub title: String,
    pub artist: String,
    pub album: String,
    pub duration: Duration
}
#[derive(Serialize, Deserialize, Debug)]
struct TrackList(Vec<TrackListItem>);
impl TrackList {
    pub async fn new(speaker: &Speaker) -> Fallible<Self> {
        Ok(Self(
            speaker.queue().list().await?
                .into_iter()
                .map(|v| TrackListItem {
                    position: v.position,
                    title: v.title,
                    artist: v.artist,
                    album: v.album,
                    duration: v.duration
                })
                .collect()
        ))
    }

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

    {
        // 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::<Vec<IpAddr>>()
            .serialize(&mut serializer).unwrap();
}
impl std::fmt::Display for TrackList {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        Ok(for item in &self.0 {
            writeln!(f, "{}: {} - {} ({})",
                   item.position,
                   item.artist,
                   item.title,
                   util::duration_to_hms(item.duration))?
        })
    }

    speakers
}

#[derive(Serialize, Deserialize, Debug)]
@@ -258,13 +184,12 @@ struct Track {
    pub title: String,
    pub artist: String,
    pub album: Option<String>,
    pub running_time: std::time::Duration,
    pub duration: std::time::Duration
    pub running_time: Duration,
    pub duration: Duration
}

impl Track {
    pub fn new(speaker: &Speaker) -> Result<Track, failure::Error> {
        let track = speaker.track()?;
    pub async fn new(speaker: &Speaker) -> Fallible<Track> {
        let track = speaker.track().await?;

        Ok(Self {
            title: track.title,
@@ -275,7 +200,6 @@ impl Track {
        })
    }
}

impl std::fmt::Display for Track {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        writeln!(f, "\u{1F3A4}  {}", self.artist)?;
@@ -285,8 +209,8 @@ impl std::fmt::Display for Track {
            writeln!(f, "\u{1F4BF}  {}", album)?;
        }

        let running_time = duration_to_hms(self.running_time);
        let duration = duration_to_hms(self.duration);
        let running_time = util::duration_to_hms(self.running_time);
        let duration = util::duration_to_hms(self.duration);

        write!(f, "\u{23F1}\u{FE0F}  {}/{}", running_time, duration)?;

@@ -301,16 +225,14 @@ struct Volume {
    volume: u8,
    muted: bool,
}

impl Volume {
    pub fn new(vol: u8, muted: bool) -> Volume {
        Self {
            volume: vol,
            muted,
        }
    pub async fn new(speaker: &Speaker) -> Result<Volume, failure::Error> {
        Ok(Self {
            volume: speaker.volume().await?,
            muted: speaker.muted().await?,
        })
    }
}

impl std::fmt::Display for Volume {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        const MAX_VOLUME: usize = 100;
@@ -341,7 +263,6 @@ struct Info {
    pub name: String,
    pub uuid: String,
}

impl Info {
    pub fn new(speaker: &Speaker) -> Info {
        Info {
@@ -356,8 +277,6 @@ impl Info {
        }
    }
}


impl std::fmt::Display for Info {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        writeln!(f, "\u{1F508}  {}", self.name)?;
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..72368d7
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,41 @@
macro_rules! print_struct {
    ($args:ident, $struc:expr) => {{
        info!("{}", if $args.is_present("json") {
            serde_json::to_string($struc)?
        } else {
            $struc.to_string()
        })
    }}
}

pub fn duration_to_hms(d: std::time::Duration) -> String {
    let mut s = String::new();

    const SECS_IN_MIN: u64 = 60;
    const MINS_IN_HOUR: u64 = 60;
    const SECS_IN_HOUR: u64 = SECS_IN_MIN * MINS_IN_HOUR;

    let hours = d.as_secs() / SECS_IN_HOUR;
    if hours > 0 {
        s.push_str(&format!("{:02}:", hours));
    }

    s.push_str(&format!("{:02}:", d.as_secs() % SECS_IN_HOUR / SECS_IN_MIN));
    s.push_str(&format!("{:02}", d.as_secs() % SECS_IN_MIN));

    s
}

pub fn setup_logger() -> Result<(), fern::InitError> {
    fern::Dispatch::new()
        .format(|out, message, _record| {
            out.finish(format_args!(
                "{}",
                message
            ))
        })
        .level(log::LevelFilter::Info)
        .chain(std::io::stdout())
        .apply()?;
    Ok(())
}
\ No newline at end of file