diff --git a/.travis.yml b/.travis.yml index 182adfd..31b7bbe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,7 +15,8 @@ before_cache: - rm -rfv target/debug/deps/liblibrespot-* - rm -rfv target/debug/deps/librespot-* - rm -rfv target/debug/{librespot,liblibrespot}.d - - cargo clean -p librespot librespot-core librespot-connect librespot-audio librespot-metadata librespot-playback + - rm -rfv target/debug/incremental/{build_script_build,librespot,librespot_core,librespot_connect,librespot_audio,librespot_metadata,librespot_playback,librespot_player,librespot_protocol}-* + - cargo clean -p librespot -p librespot-core -p librespot-connect -p librespot-audio -p librespot-metadata -p librespot-playback addons: apt: @@ -28,12 +29,14 @@ addons: - libsdl2-dev before_script: + - rustup component add rustfmt - mkdir -p ~/.cargo - echo '[target.armv7-unknown-linux-gnueabihf]' > ~/.cargo/config - echo 'linker = "arm-linux-gnueabihf-gcc"' >> ~/.cargo/config - rustup target add armv7-unknown-linux-gnueabihf script: + - cargo fmt --all -- --check - cargo build --locked --no-default-features - cargo build --locked --examples - cargo build --locked --no-default-features --features "with-tremor" diff --git a/Cargo.lock b/Cargo.lock index 304089c..0ed0911 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -532,19 +532,6 @@ name = "futures" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "futures-channel" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "futures-cpupool" version = "0.1.8" @@ -554,46 +541,6 @@ dependencies = [ "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "futures-executor" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-macro" -version = "0.3.1" -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.6 (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.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "futures-task" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "futures-util" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (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" @@ -624,132 +571,11 @@ dependencies = [ "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "glib" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "glib-sys" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "glob" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "gobject-sys" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gstreamer" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "muldiv 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gstreamer-app" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-app-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-base 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gstreamer-app-sys" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-base-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gstreamer-base" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-base-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gstreamer-base-sys" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "gstreamer-sys" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", - "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "hex" version = "0.3.2" @@ -942,7 +768,7 @@ dependencies = [ [[package]] name = "librespot" -version = "0.1.0" +version = "0.1.1" dependencies = [ "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -950,12 +776,12 @@ dependencies = [ "getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)", - "librespot-audio 0.1.0", - "librespot-connect 0.1.0", - "librespot-core 0.1.0", - "librespot-metadata 0.1.0", - "librespot-playback 0.1.0", - "librespot-protocol 0.1.0", + "librespot-audio 0.1.1", + "librespot-connect 0.1.1", + "librespot-core 0.1.1", + "librespot-metadata 0.1.1", + "librespot-playback 0.1.1", + "librespot-protocol 0.1.1", "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -971,7 +797,7 @@ dependencies = [ [[package]] name = "librespot-audio" -version = "0.1.0" +version = "0.1.1" dependencies = [ "aes-ctr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "bit-set 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -979,7 +805,7 @@ dependencies = [ "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", "lewton 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)", - "librespot-core 0.1.0", + "librespot-core 0.1.1", "librespot-tremor 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", @@ -990,7 +816,7 @@ dependencies = [ [[package]] name = "librespot-connect" -version = "0.1.0" +version = "0.1.1" dependencies = [ "aes-ctr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1000,9 +826,9 @@ dependencies = [ "hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)", "hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)", "libmdns 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "librespot-core 0.1.0", - "librespot-playback 0.1.0", - "librespot-protocol 0.1.0", + "librespot-core 0.1.1", + "librespot-playback 0.1.1", + "librespot-protocol 0.1.1", "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1017,7 +843,7 @@ dependencies = [ [[package]] name = "librespot-core" -version = "0.1.0" +version = "0.1.1" dependencies = [ "aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1030,7 +856,7 @@ dependencies = [ "hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)", "hyper-proxy 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "librespot-protocol 0.1.0", + "librespot-protocol 0.1.1", "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "num-bigint 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1053,12 +879,12 @@ dependencies = [ [[package]] name = "librespot-metadata" -version = "0.1.0" +version = "0.1.1" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "librespot-core 0.1.0", - "librespot-protocol 0.1.0", + "librespot-core 0.1.1", + "librespot-protocol 0.1.1", "linear-map 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1066,31 +892,28 @@ dependencies = [ [[package]] name = "librespot-playback" -version = "0.1.0" +version = "0.1.1" dependencies = [ "alsa 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "cpal 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)", "futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)", - "glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", - "gstreamer-app 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", "jack 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.65 (registry+https://github.com/rust-lang/crates.io-index)", "libpulse-sys 0.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "librespot-audio 0.1.0", - "librespot-core 0.1.0", - "librespot-metadata 0.1.0", + "librespot-audio 0.1.1", + "librespot-core 0.1.1", + "librespot-metadata 0.1.1", "log 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)", "portaudio-rs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "rodio 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "sdl2 0.32.2 (registry+https://github.com/rust-lang/crates.io-index)", - "zerocopy 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "shell-words 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "librespot-protocol" -version = "0.1.0" +version = "0.1.1" dependencies = [ "protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "protobuf-codegen 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1238,11 +1061,6 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "muldiv" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "multimap" version = "0.4.0" @@ -1360,16 +1178,6 @@ dependencies = [ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "num-rational" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "autocfg 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "num-traits" version = "0.2.8" @@ -1438,26 +1246,6 @@ dependencies = [ "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "paste" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "paste-impl" -version = "0.1.6" -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.6 (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.11 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "pbkdf2" version = "0.3.0" @@ -1482,11 +1270,6 @@ name = "percent-encoding" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "pin-utils" -version = "0.1.0-alpha.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "pkg-config" version = "0.3.15" @@ -1516,21 +1299,6 @@ name = "ppv-lite86" version = "0.2.5" 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.6 (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.11 (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 = "0.2.3" @@ -1547,14 +1315,6 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "proc-macro2" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "protobuf" version = "2.8.1" @@ -1598,14 +1358,6 @@ dependencies = [ "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "quote" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "rand" version = "0.3.23" @@ -1974,6 +1726,11 @@ dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "shell-words" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "signal-hook" version = "0.1.10" @@ -2078,16 +1835,6 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "syn" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "synstructure" version = "0.10.2" @@ -2446,11 +2193,6 @@ name = "unicode-xid" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "unicode-xid" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "url" version = "1.7.2" @@ -2620,25 +2362,6 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "zerocopy" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "zerocopy-derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "zerocopy-derive" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.42 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [metadata] "checksum aes 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9" "checksum aes-ctr 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d2e5b0458ea3beae0d1d8c0f3946564f8e10f90646cf78c06b4351052058d1ee" @@ -2703,27 +2426,12 @@ dependencies = [ "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.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "45dc39533a6cae6da2b56da48edae506bb767ec07370f86f70fc062e9d435869" -"checksum futures-channel 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fcae98ca17d102fd8a3603727b9259fcf7fa4239b603d2142926189bc8999b86" -"checksum futures-core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "79564c427afefab1dfb3298535b21eda083ef7935b4f0ecbfcb121f0aec10866" "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" -"checksum futures-executor 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1e274736563f686a837a0568b478bdabfeaec2dca794b5649b04e2fe1627c231" -"checksum futures-macro 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "52e7c56c15537adb4f76d0b7a76ad131cb4d2f4f32d3b0bcabcbe1c7c5e87764" -"checksum futures-task 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0bae52d6b29cf440e298856fec3965ee6fa71b06aa7495178615953fd669e5f9" -"checksum futures-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d66274fb76985d3c62c886d1da7ac4c0903a8c9f754e8fe0f35a6a6cc39e76" "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum getopts 0.2.19 (registry+https://github.com/rust-lang/crates.io-index)" = "72327b15c228bfe31f1390f93dd5e9279587f0463836393c9df719ce62a3e450" "checksum getrandom 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "e65cce4e5084b14874c4e7097f38cab54f47ee554f9194673456ea379dcc4c55" -"checksum glib 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "121c502fc6895e62d2ce084e677d3289ccbdd7f56edd4ac9a5ab8bd95d4a8670" -"checksum glib-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "95856f3802f446c05feffa5e24859fe6a183a7cb849c8449afc35c86b1e316e2" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" -"checksum gobject-sys 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31d1a804f62034eccf370006ccaef3708a71c31d561fee88564abe71177553d9" -"checksum gstreamer 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "08d9ea04f6e746e90d979eaf5b55a9125fd159e58959f203a2f3fbc4b2a93b77" -"checksum gstreamer-app 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fe16616846b453c5d976cee9e3617ec4b098bd160d95883a455c43b4e31a3b56" -"checksum gstreamer-app-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bf869ce152c23bca5d761ab62146b47f750d0b28d4d499731857532897d48167" -"checksum gstreamer-base 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1eaec2ff20b049560762f83545d95b33fd1533512792f37a9a3f6800e45da42b" -"checksum gstreamer-base-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ba384f52174b3c586593fca32642680a9e67961fea9f4cd8419f678965023bed" -"checksum gstreamer-sys 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1d18da01b97d0ab5896acd5151e4c155acefd0e6c03c3dd24dd133ba054053db" "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" "checksum hmac 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dcb5e64cda4c23119ab41ba960d1e170a774c8e4b9d9e6a9bc18aabf5e59695" "checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" @@ -2760,7 +2468,6 @@ dependencies = [ "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 muldiv 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "451a9a05d2a32c566c897835e0ea95cf79ed2fdfe957924045a1721a36c9980f" "checksum multimap 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb04b9f127583ed176e163fb9ec6f3e793b87e21deedd5734a69386a18a0151" "checksum nalgebra 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8e12856109b5cb8e2934b5e45e4624839416e1c6c1f7d286711a7a66b79db29d" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" @@ -2773,7 +2480,6 @@ dependencies = [ "checksum num-complex 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "fcb0cf31fb3ff77e6d2a6ebd6800df7fdcd106f2ad89113c9130bcd07f93dffc" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e" -"checksum num-rational 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2885278d5fe2adc2f75ced642d52d879bffaceb5a2e0b1d4309ffdfb239b454" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" "checksum ogg 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d79f1db9148be9d0e174bb3ac890f6030fcb1ed947267c5a91ee4c91b5a91e15" @@ -2782,28 +2488,21 @@ dependencies = [ "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" "checksum parking_lot 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" "checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" -"checksum paste 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "423a519e1c6e828f1e73b720f9d9ed2fa643dce8a7737fb43235ce0b41eeaa49" -"checksum paste-impl 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5" "checksum pbkdf2 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "006c038a43a45995a9670da19e67600114740e8511d4333bf97a56e66a7542d9" "checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" -"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587" "checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af" "checksum portaudio-rs 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "fc0e6b38f00fae9dde9a9832a2b54405988c6dcaf2870e6f9551546b447bbd7f" "checksum portaudio-sys 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5194a4fa953b4ffd851c320ef6f0484cd7278cb7169ea9d6c433e49b23f7b7f5" "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" -"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 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cd07deb3c6d1d9ff827999c7f9b04cdfd66b1b17ae508e14fe47b620f2282ae0" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -"checksum proc-macro2 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9c9e470a8dc4aeae2dee2f335e8f533e2d4b347e1434e5671afc49b054592f27" "checksum protobuf 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "40361836defdd5871ff7e84096c6f6444af7fc157f8ef1789f54f147687caa20" "checksum protobuf-codegen 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "12c6abd78435445fc86898ebbd0521a68438063d4a73e23527b7134e6bf58b4a" "checksum protobuf-codegen-pure 2.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c1646acda5319f5b28b0bff4a484324df43ddae2c0f5a3f3e63c0b26095cd600" "checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" "checksum quote 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1eca14c727ad12702eb4b6bfb5a232287dcf8385cb8ca83a3eeaf6519c44c408" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -"checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" "checksum rand 0.3.23 (registry+https://github.com/rust-lang/crates.io-index)" = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" "checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" "checksum rand 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c618c47cd3ebd209790115ab837de41425723956ad3ce2e6a7f09890947cacb9" @@ -2847,6 +2546,7 @@ dependencies = [ "checksum sha-1 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "23962131a91661d643c98940b20fcaffe62d776a823247be80a48fcb8b6fce68" "checksum sha2 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b4d8bfd0e469f417657573d8451fb33d16cfe0989359b93baf3a1ffc639543d" "checksum shannon 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7ea5b41c9427b56caa7b808cb548a04fb50bb5b9e98590b53f28064ff4174561" +"checksum shell-words 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "39acde55a154c4cd3ae048ac78cc21c25f3a0145e44111b523279113dce0d94a" "checksum signal-hook 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4f61c4d59f3aaa9f61bba6450a9b80ba48362fd7d651689e7a10c453b1f6dc68" "checksum signal-hook-registry 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "913661ac8848a61e39684a3c3e7a7a14a4deec7f54b4976d0641e70dda3939b1" "checksum slab 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "17b4fcaed89ab08ef143da37bc52adbcc04d4a69014f4c1208d6b51f0c47bc23" @@ -2862,7 +2562,6 @@ dependencies = [ "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" "checksum subtle 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" "checksum syn 0.15.42 (registry+https://github.com/rust-lang/crates.io-index)" = "eadc09306ca51a40555dd6fc2b415538e9e18bc9f870e47b1a524a79fe2dcf5e" -"checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238" "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" "checksum take 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b157868d8ac1f56b64604539990685fa7611d8fa9e5476cf0c02cf34d32917c5" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" @@ -2896,7 +2595,6 @@ dependencies = [ "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" -"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 utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d50aa7650df78abf942826607c62468ce18d9019673d4a2ebe1865dbb96ffde" "checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" @@ -2918,5 +2616,3 @@ dependencies = [ "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" "checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba" "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -"checksum zerocopy 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "992b9b31f80fd4a167f903f879b8ca43d6716cc368ea01df90538baa2dd34056" -"checksum zerocopy-derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b090467ecd0624026e8a6405d343ac7382592530d54881330b3fc8e400280fa5" diff --git a/Cargo.toml b/Cargo.toml index 18f05ff..08edd6c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "librespot" -version = "0.1.0" +version = "0.1.1" authors = ["Librespot Org"] license = "MIT" description = "An open source client library for Spotify, with support for Spotify Connect" keywords = ["spotify"] repository = "https://github.com/librespot-org/librespot" readme = "README.md" +edition = "2018" [workspace] @@ -21,22 +22,22 @@ doc = false [dependencies.librespot-audio] path = "audio" -version = "0.1.0" +version = "0.1.1" [dependencies.librespot-connect] path = "connect" -version = "0.1.0" +version = "0.1.1" [dependencies.librespot-core] path = "core" -version = "0.1.0" +version = "0.1.1" [dependencies.librespot-metadata] path = "metadata" -version = "0.1.0" +version = "0.1.1" [dependencies.librespot-playback] path = "playback" -version = "0.1.0" +version = "0.1.1" [dependencies.librespot-protocol] path = "protocol" -version = "0.1.0" +version = "0.1.1" [dependencies] base64 = "0.10" diff --git a/README.md b/README.md index 0144bdd..c7dd05e 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,10 @@ [![Gitter chat](https://badges.gitter.im/librespot-org/librespot.png)](https://gitter.im/librespot-org/spotify-connect-resources) [![Crates.io](https://img.shields.io/crates/v/librespot.svg)](https://crates.io/crates/librespot) -Current maintainer is @awiouy folks. +Current maintainer is [@awiouy](https://github.com/awiouy) folks. # librespot -*librespot* is an open source client library for Spotify. It enables -applications to use Spotify's service, without using the official but -closed-source `libspotify`. Additionally, it will provide extra features -which are not available in the official library. +*librespot* is an open source client library for Spotify. It enables applications to use Spotify's service to control and play music via various backends, and to act as a Spotify Connect receiver. It is an alternative to the official and [now deprecated](https://pyspotify.mopidy.com/en/latest/#libspotify-s-deprecation) closed-source `libspotify`. Additionally, it will provide extra features which are not available in the official library. _Note: librespot only works with Spotify Premium. This will remain the case for the foreseeable future, as we are unlikely to work on implementing the features such as limited skips and adverts that would be required to make librespot compliant with free accounts._ @@ -31,7 +28,6 @@ There is some brief documentation on how the protocol works in the [docs](https: If you wish to learn more about how librespot works overall, the best way is to simply read the code, and ask any questions you have in our [Gitter Room](https://gitter.im/librespot-org/spotify-connect-resources). # Issues - If you run into a bug when using librespot, please search the existing issues before opening a new one. Chances are, we've encountered it before, and have provided a resolution. If not, please open a new one, and where possible, include the backtrace librespot generates on crashing, along with anything we can use to reproduce the issue, eg. the Spotify URI of the song that caused the crash. # Building diff --git a/audio/Cargo.toml b/audio/Cargo.toml index 6c485e2..c912156 100644 --- a/audio/Cargo.toml +++ b/audio/Cargo.toml @@ -1,13 +1,14 @@ [package] name = "librespot-audio" -version = "0.1.0" +version = "0.1.1" authors = ["Paul Lietar "] description="The audio fetching and processing logic for librespot" license="MIT" +edition = "2018" [dependencies.librespot-core] path = "../core" -version = "0.1.0" +version = "0.1.1" [dependencies] bit-set = "0.5" diff --git a/audio/src/decrypt.rs b/audio/src/decrypt.rs index d2697e7..818eb34 100644 --- a/audio/src/decrypt.rs +++ b/audio/src/decrypt.rs @@ -1,16 +1,13 @@ use std::io; -use aes_ctr::Aes128Ctr; -use aes_ctr::stream_cipher::{ - NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek -}; use aes_ctr::stream_cipher::generic_array::GenericArray; +use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek}; +use aes_ctr::Aes128Ctr; use librespot_core::audio_key::AudioKey; const AUDIO_AESIV: [u8; 16] = [ - 0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77, - 0xeb, 0xe8, 0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93, + 0x72, 0xe0, 0x67, 0xfb, 0xdd, 0xcb, 0xcf, 0x77, 0xeb, 0xe8, 0xbc, 0x64, 0x3f, 0x63, 0x0d, 0x93, ]; pub struct AudioDecrypt { @@ -30,7 +27,7 @@ impl AudioDecrypt { impl io::Read for AudioDecrypt { fn read(&mut self, output: &mut [u8]) -> io::Result { - let len = try!(self.reader.read(output)); + let len = self.reader.read(output)?; self.cipher.apply_keystream(&mut output[..len]); @@ -40,7 +37,7 @@ impl io::Read for AudioDecrypt { impl io::Seek for AudioDecrypt { fn seek(&mut self, pos: io::SeekFrom) -> io::Result { - let newpos = try!(self.reader.seek(pos)); + let newpos = self.reader.seek(pos)?; self.cipher.seek(newpos); diff --git a/audio/src/fetch.rs b/audio/src/fetch.rs index 53c9545..1ab6ce7 100644 --- a/audio/src/fetch.rs +++ b/audio/src/fetch.rs @@ -1,9 +1,9 @@ +use crate::range_set::{Range, RangeSet}; use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; use bytes::Bytes; use futures::sync::{mpsc, oneshot}; use futures::Stream; use futures::{Async, Future, Poll}; -use range_set::{Range, RangeSet}; use std::cmp::{max, min}; use std::fs; use std::io::{self, Read, Seek, SeekFrom, Write}; @@ -446,7 +446,7 @@ impl AudioFile { channel_tx: None, stream_shared: None, file_size: file.metadata().unwrap().len() as usize, - } + }; } } } @@ -514,7 +514,10 @@ impl AudioFileFetchDataReceiver { request_length: usize, request_sent_time: Instant, ) -> AudioFileFetchDataReceiver { - let measure_ping_time = shared.number_of_open_requests.load(atomic::Ordering::SeqCst) == 0; + let measure_ping_time = shared + .number_of_open_requests + .load(atomic::Ordering::SeqCst) + == 0; shared .number_of_open_requests @@ -562,7 +565,8 @@ impl Future for AudioFileFetchDataReceiver { if let Some(request_sent_time) = self.request_sent_time { let duration = Instant::now() - request_sent_time; let duration_ms: u64; - if 0.001 * (duration.as_millis() as f64) > MAXIMUM_ASSUMED_PING_TIME_SECONDS + if 0.001 * (duration.as_millis() as f64) + > MAXIMUM_ASSUMED_PING_TIME_SECONDS { duration_ms = (MAXIMUM_ASSUMED_PING_TIME_SECONDS * 1000.0) as u64; } else { @@ -714,8 +718,13 @@ impl AudioFileFetch { ranges_to_request.subtract_range_set(&download_status.requested); for range in ranges_to_request.iter() { - let (_headers, data) = - request_range(&self.session, self.shared.file_id, range.start, range.length).split(); + let (_headers, data) = request_range( + &self.session, + self.shared.file_id, + range.start, + range.length, + ) + .split(); download_status.requested.add_range(range); @@ -749,7 +758,10 @@ impl AudioFileFetch { // download data from after the current read position first let mut tail_end = RangeSet::new(); let read_position = self.shared.read_position.load(atomic::Ordering::Relaxed); - tail_end.add_range(&Range::new(read_position, self.shared.file_size - read_position)); + tail_end.add_range(&Range::new( + read_position, + self.shared.file_size - read_position, + )); let tail_end = tail_end.intersection(&missing_data); if !tail_end.is_empty() { @@ -794,8 +806,9 @@ impl AudioFileFetch { let ping_time_ms: usize = match self.network_response_times_ms.len() { 1 => self.network_response_times_ms[0] as usize, 2 => { - ((self.network_response_times_ms[0] + self.network_response_times_ms[1]) / 2) - as usize + ((self.network_response_times_ms[0] + + self.network_response_times_ms[1]) + / 2) as usize } 3 => { let mut times = self.network_response_times_ms.clone(); @@ -863,10 +876,12 @@ impl AudioFileFetch { self.download_range(request.start, request.length); } Ok(Async::Ready(Some(StreamLoaderCommand::RandomAccessMode()))) => { - *(self.shared.download_strategy.lock().unwrap()) = DownloadStrategy::RandomAccess(); + *(self.shared.download_strategy.lock().unwrap()) = + DownloadStrategy::RandomAccess(); } Ok(Async::Ready(Some(StreamLoaderCommand::StreamMode()))) => { - *(self.shared.download_strategy.lock().unwrap()) = DownloadStrategy::Streaming(); + *(self.shared.download_strategy.lock().unwrap()) = + DownloadStrategy::Streaming(); } Ok(Async::Ready(Some(StreamLoaderCommand::Close()))) => { return Ok(Async::Ready(())); @@ -908,15 +923,20 @@ impl Future for AudioFileFetch { } if let DownloadStrategy::Streaming() = self.get_download_strategy() { - let number_of_open_requests = - self.shared.number_of_open_requests.load(atomic::Ordering::SeqCst); + let number_of_open_requests = self + .shared + .number_of_open_requests + .load(atomic::Ordering::SeqCst); let max_requests_to_send = MAX_PREFETCH_REQUESTS - min(MAX_PREFETCH_REQUESTS, number_of_open_requests); if max_requests_to_send > 0 { let bytes_pending: usize = { let download_status = self.shared.download_status.lock().unwrap(); - download_status.requested.minus(&download_status.downloaded).len() + download_status + .requested + .minus(&download_status.downloaded) + .len() }; let ping_time_seconds = @@ -924,9 +944,11 @@ impl Future for AudioFileFetch { let download_rate = self.session.channel().get_download_rate_estimate(); let desired_pending_bytes = max( - (PREFETCH_THRESHOLD_FACTOR * ping_time_seconds * self.shared.stream_data_rate as f64) + (PREFETCH_THRESHOLD_FACTOR + * ping_time_seconds + * self.shared.stream_data_rate as f64) as usize, + (FAST_PREFETCH_THRESHOLD_FACTOR * ping_time_seconds * download_rate as f64) as usize, - (FAST_PREFETCH_THRESHOLD_FACTOR * ping_time_seconds * download_rate as f64) as usize, ); if bytes_pending < desired_pending_bytes { @@ -1003,13 +1025,15 @@ impl Read for AudioFileStreaming { .unwrap() .0; } - let available_length = download_status.downloaded.contained_length_from_value(offset); + let available_length = download_status + .downloaded + .contained_length_from_value(offset); assert!(available_length > 0); drop(download_status); self.position = self.read_file.seek(SeekFrom::Start(offset as u64)).unwrap(); let read_len = min(length, available_length); - let read_len = try!(self.read_file.read(&mut output[..read_len])); + let read_len = self.read_file.read(&mut output[..read_len])?; if download_message_printed { debug!( @@ -1031,7 +1055,7 @@ impl Read for AudioFileStreaming { impl Seek for AudioFileStreaming { fn seek(&mut self, pos: SeekFrom) -> io::Result { - self.position = try!(self.read_file.seek(pos)); + self.position = self.read_file.seek(pos)?; // Do not seek past EOF self.shared .read_position diff --git a/audio/src/lib.rs b/audio/src/lib.rs index 9a82f90..3e13c07 100644 --- a/audio/src/lib.rs +++ b/audio/src/lib.rs @@ -31,6 +31,6 @@ pub use fetch::{ }; #[cfg(not(any(feature = "with-tremor", feature = "with-vorbis")))] -pub use lewton_decoder::{VorbisDecoder, VorbisError, VorbisPacket}; +pub use crate::lewton_decoder::{VorbisDecoder, VorbisError, VorbisPacket}; #[cfg(any(feature = "with-tremor", feature = "with-vorbis"))] pub use libvorbis_decoder::{VorbisDecoder, VorbisError, VorbisPacket}; diff --git a/audio/src/libvorbis_decoder.rs b/audio/src/libvorbis_decoder.rs index eef1708..c219825 100644 --- a/audio/src/libvorbis_decoder.rs +++ b/audio/src/libvorbis_decoder.rs @@ -77,7 +77,7 @@ impl error::Error for VorbisError { error::Error::description(&self.0) } - fn cause(&self) -> Option<&error::Error> { - error::Error::cause(&self.0) + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + error::Error::source(&self.0) } } diff --git a/audio/src/range_set.rs b/audio/src/range_set.rs index 448c097..4495538 100644 --- a/audio/src/range_set.rs +++ b/audio/src/range_set.rs @@ -113,7 +113,8 @@ impl RangeSet { // the new range starts after anything we already passed and ends before the next range starts (they don't touch) -> insert it. self.ranges.insert(index, range.clone()); return; - } else if range.start <= self.ranges[index].end() && self.ranges[index].start <= range.end() + } else if range.start <= self.ranges[index].end() + && self.ranges[index].start <= range.end() { // the new range overlaps (or touches) the first range. They are to be merged. // In addition we might have to merge further ranges in as well. @@ -161,7 +162,9 @@ impl RangeSet { if range.end() <= self.ranges[index].start { // the remaining ranges are past the one to subtract. -> we're done. return; - } else if range.start <= self.ranges[index].start && self.ranges[index].start < range.end() { + } else if range.start <= self.ranges[index].start + && self.ranges[index].start < range.end() + { // the range to subtract started before the current range and reaches into the current range // -> we have to remove the beginning of the range or the entire range and do the same for following ranges. @@ -223,8 +226,14 @@ impl RangeSet { other_index += 1; } else { // the two intervals overlap. Add the union and advance the index of the one that ends first. - let new_start = max(self.ranges[self_index].start, other.ranges[other_index].start); - let new_end = min(self.ranges[self_index].end(), other.ranges[other_index].end()); + let new_start = max( + self.ranges[self_index].start, + other.ranges[other_index].start, + ); + let new_end = min( + self.ranges[self_index].end(), + other.ranges[other_index].end(), + ); assert!(new_start <= new_end); result.add_range(&Range::new(new_start, new_end - new_start)); if self.ranges[self_index].end() <= other.ranges[other_index].end() { diff --git a/connect/Cargo.toml b/connect/Cargo.toml index 270f605..25b4963 100644 --- a/connect/Cargo.toml +++ b/connect/Cargo.toml @@ -1,19 +1,20 @@ [package] name = "librespot-connect" -version = "0.1.0" +version = "0.1.1" authors = ["Paul Lietar "] description="The discovery and Spotify Connect logic for librespot" license="MIT" +edition = "2018" [dependencies.librespot-core] path = "../core" -version = "0.1.0" +version = "0.1.1" [dependencies.librespot-playback] path = "../playback" -version = "0.1.0" +version = "0.1.1" [dependencies.librespot-protocol] path = "../protocol" -version = "0.1.0" +version = "0.1.1" [dependencies] base64 = "0.10" diff --git a/connect/src/context.rs b/connect/src/context.rs index c59e389..5a94f6c 100644 --- a/connect/src/context.rs +++ b/connect/src/context.rs @@ -1,5 +1,5 @@ +use crate::protocol::spirc::TrackRef; use librespot_core::spotify_id::SpotifyId; -use protocol::spirc::TrackRef; use serde; @@ -69,7 +69,7 @@ fn deserialize_protobuf_TrackRef<'d, D>(de: D) -> Result, D::Error where D: serde::Deserializer<'d>, { - let v: Vec = try!(serde::Deserialize::deserialize(de)); + let v: Vec = serde::Deserialize::deserialize(de)?; let track_vec = v .iter() .map(|v| { diff --git a/connect/src/discovery.rs b/connect/src/discovery.rs index de575b1..9779e6f 100644 --- a/connect/src/discovery.rs +++ b/connect/src/discovery.rs @@ -1,13 +1,13 @@ -use base64; -use sha1::{Sha1, Digest}; -use hmac::{Hmac, Mac}; -use aes_ctr::Aes128Ctr; -use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher}; use aes_ctr::stream_cipher::generic_array::GenericArray; +use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher}; +use aes_ctr::Aes128Ctr; +use base64; use futures::sync::mpsc; use futures::{Future, Poll, Stream}; +use hmac::{Hmac, Mac}; use hyper::server::{Http, Request, Response, Service}; use hyper::{self, Get, Post, StatusCode}; +use sha1::{Digest, Sha1}; #[cfg(feature = "with-dns-sd")] use dns_sd::DNSService; @@ -114,21 +114,18 @@ impl Discovery { let base_key = &base_key[..16]; let checksum_key = { - let mut h = HmacSha1::new_varkey(base_key) - .expect("HMAC can take key of any size"); + let mut h = HmacSha1::new_varkey(base_key).expect("HMAC can take key of any size"); h.input(b"checksum"); h.result().code() }; let encryption_key = { - let mut h = HmacSha1::new_varkey(&base_key) - .expect("HMAC can take key of any size"); + let mut h = HmacSha1::new_varkey(&base_key).expect("HMAC can take key of any size"); h.input(b"encryption"); h.result().code() }; - let mut h = HmacSha1::new_varkey(&checksum_key) - .expect("HMAC can take key of any size"); + let mut h = HmacSha1::new_varkey(&checksum_key).expect("HMAC can take key of any size"); h.input(encrypted); if let Err(_) = h.verify(cksum) { warn!("Login error for user {:?}: MAC mismatch", username); @@ -139,7 +136,7 @@ impl Discovery { }); let body = result.to_string(); - return ::futures::finished(Response::new().with_body(body)) + return ::futures::finished(Response::new().with_body(body)); } let decrypted = { @@ -152,7 +149,8 @@ impl Discovery { String::from_utf8(data).unwrap() }; - let credentials = Credentials::with_blob(username.to_owned(), &decrypted, &self.0.device_id); + let credentials = + Credentials::with_blob(username.to_owned(), &decrypted, &self.0.device_id); self.0.tx.unbounded_send(credentials).unwrap(); @@ -175,7 +173,7 @@ impl Service for Discovery { type Request = Request; type Response = Response; type Error = hyper::Error; - type Future = Box>; + type Future = Box>; fn call(&self, request: Request) -> Self::Future { let mut params = BTreeMap::new(); @@ -194,17 +192,18 @@ impl Service for Discovery { body.fold(Vec::new(), |mut acc, chunk| { acc.extend_from_slice(chunk.as_ref()); Ok::<_, hyper::Error>(acc) - }).map(move |body| { - params.extend(url::form_urlencoded::parse(&body).into_owned()); - params - }) - .and_then( - move |params| match (method, params.get("action").map(AsRef::as_ref)) { - (Get, Some("getInfo")) => this.handle_get_info(¶ms), - (Post, Some("addUser")) => this.handle_add_user(¶ms), - _ => this.not_found(), - }, - ), + }) + .map(move |body| { + params.extend(url::form_urlencoded::parse(&body).into_owned()); + params + }) + .and_then(move |params| { + match (method, params.get("action").map(AsRef::as_ref)) { + (Get, Some("getInfo")) => this.handle_get_info(¶ms), + (Post, Some("addUser")) => this.handle_add_user(¶ms), + _ => this.not_found(), + } + }), ) } } @@ -235,7 +234,8 @@ pub fn discovery( &format!("0.0.0.0:{}", port).parse().unwrap(), &handle, move || Ok(discovery.clone()), - ).unwrap() + ) + .unwrap() }; let s_port = serve.incoming_ref().local_addr().port(); @@ -260,7 +260,8 @@ pub fn discovery( None, s_port, &["VERSION=1.0", "CPath=/"], - ).unwrap(); + ) + .unwrap(); #[cfg(not(feature = "with-dns-sd"))] let responder = libmdns::Responder::spawn(&handle)?; diff --git a/connect/src/lib.rs b/connect/src/lib.rs index d029058..118c85d 100644 --- a/connect/src/lib.rs +++ b/connect/src/lib.rs @@ -15,10 +15,10 @@ extern crate rand; extern crate tokio_core; extern crate url; -extern crate sha1; -extern crate hmac; extern crate aes_ctr; extern crate block_modes; +extern crate hmac; +extern crate sha1; #[cfg(feature = "with-dns-sd")] extern crate dns_sd; diff --git a/connect/src/spirc.rs b/connect/src/spirc.rs index 4fd4288..5147201 100644 --- a/connect/src/spirc.rs +++ b/connect/src/spirc.rs @@ -9,7 +9,11 @@ use rand; use rand::seq::SliceRandom; use serde_json; -use context::StationContext; +use crate::context::StationContext; +use crate::playback::mixer::Mixer; +use crate::playback::player::Player; +use crate::protocol; +use crate::protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State, TrackRef}; use librespot_core::config::ConnectConfig; use librespot_core::mercury::MercuryError; use librespot_core::session::Session; @@ -17,14 +21,10 @@ use librespot_core::spotify_id::{SpotifyAudioType, SpotifyId, SpotifyIdError}; use librespot_core::util::SeqGenerator; use librespot_core::version; use librespot_core::volume::Volume; -use playback::mixer::Mixer; -use playback::player::Player; -use protocol; -use protocol::spirc::{DeviceState, Frame, MessageType, PlayStatus, State, TrackRef}; pub struct SpircTask { player: Player, - mixer: Box, + mixer: Box, config: SpircTaskConfig, sequence: SeqGenerator, @@ -33,15 +33,15 @@ pub struct SpircTask { device: DeviceState, state: State, - subscription: Box>, - sender: Box>, + subscription: Box>, + sender: Box>, commands: mpsc::UnboundedReceiver, - end_of_track: Box>, + end_of_track: Box>, shutdown: bool, session: Session, - context_fut: Box>, - autoplay_fut: Box>, + context_fut: Box>, + autoplay_fut: Box>, context: Option, } @@ -221,7 +221,7 @@ impl Spirc { config: ConnectConfig, session: Session, player: Player, - mixer: Box, + mixer: Box, ) -> (Spirc, SpircTask) { debug!("new Spirc[{}]", session.session_id()); @@ -333,7 +333,11 @@ impl Future for SpircTask { progress = true; self.handle_frame(frame); } - Async::Ready(None) => panic!("subscription terminated"), + Async::Ready(None) => { + error!("subscription terminated"); + self.shutdown = true; + self.commands.close(); + } Async::NotReady => (), } @@ -526,7 +530,8 @@ impl SpircTask { if self.state.get_track().len() > 0 { let now = self.now_ms(); - self.state.set_position_ms(frame.get_state().get_position_ms()); + self.state + .set_position_ms(frame.get_state().get_position_ms()); self.state.set_position_measured_at(now as u64); let play = frame.get_state().get_status() == PlayStatus::kPlayStatusPlay; @@ -534,6 +539,8 @@ impl SpircTask { } else { info!("No more tracks left in queue"); self.state.set_status(PlayStatus::kPlayStatusStop); + self.player.stop(); + self.mixer.stop(); } self.notify(None); @@ -670,11 +677,14 @@ impl SpircTask { // Removes current track if it is queued // Returns the index of the next track let current_index = self.state.get_playing_track_index() as usize; - if self.state.get_track()[current_index].get_queued() { + if (current_index < self.state.get_track().len()) + && self.state.get_track()[current_index].get_queued() + { self.state.mut_track().remove(current_index); - return current_index; + current_index + } else { + current_index + 1 } - current_index + 1 } fn handle_next(&mut self) { @@ -689,7 +699,8 @@ impl SpircTask { tracks_len - new_index < CONTEXT_FETCH_THRESHOLD ); let context_uri = self.state.get_context_uri().to_owned(); - if (context_uri.starts_with("spotify:station:") || context_uri.starts_with("spotify:dailymix:")) + if (context_uri.starts_with("spotify:station:") + || context_uri.starts_with("spotify:dailymix:")) && ((self.state.get_track().len() as u32) - new_index) < CONTEXT_FETCH_THRESHOLD { self.context_fut = self.resolve_station(&context_uri); @@ -706,12 +717,21 @@ impl SpircTask { new_index = 0; // Loop around back to start continue_playing = self.state.get_repeat(); } - self.state.set_playing_track_index(new_index); - self.state.set_position_ms(0); - let now = self.now_ms(); - self.state.set_position_measured_at(now as u64); - self.load_track(continue_playing); + if tracks_len > 0 { + self.state.set_playing_track_index(new_index); + self.state.set_position_ms(0); + let now = self.now_ms(); + self.state.set_position_measured_at(now as u64); + + self.load_track(continue_playing); + } else { + info!("Not playing next track because there are no more tracks left in queue."); + self.state.set_playing_track_index(0); + self.state.set_status(PlayStatus::kPlayStatusStop); + self.player.stop(); + self.mixer.stop(); + } } fn handle_prev(&mut self) { @@ -785,28 +805,48 @@ impl SpircTask { self.state.get_position_ms() + diff as u32 } - fn resolve_station(&self, uri: &str) -> Box> { + fn resolve_station( + &self, + uri: &str, + ) -> Box> { let radio_uri = format!("hm://radio-apollo/v3/stations/{}", uri); self.resolve_uri(&radio_uri) } - fn resolve_autoplay_uri(&self, uri: &str) -> Box> { + fn resolve_autoplay_uri( + &self, + uri: &str, + ) -> Box> { let query_uri = format!("hm://autoplay-enabled/query?uri={}", uri); let request = self.session.mercury().get(query_uri); Box::new(request.and_then(move |response| { - let data = response.payload.first().expect("Empty autoplay uri").to_vec(); - let autoplay_uri = String::from_utf8(data).unwrap(); - - Ok(autoplay_uri) + if response.status_code == 200 { + let data = response + .payload + .first() + .expect("Empty autoplay uri") + .to_vec(); + let autoplay_uri = String::from_utf8(data).unwrap(); + Ok(autoplay_uri) + } else { + warn!("No autoplay_uri found"); + Err(MercuryError) + } })) } - fn resolve_uri(&self, uri: &str) -> Box> { + fn resolve_uri( + &self, + uri: &str, + ) -> Box> { let request = self.session.mercury().get(uri); Box::new(request.and_then(move |response| { - let data = response.payload.first().expect("Empty payload on context uri"); + let data = response + .payload + .first() + .expect("Empty payload on context uri"); let response: serde_json::Value = serde_json::from_slice(&data).unwrap(); Ok(response) @@ -824,7 +864,8 @@ impl SpircTask { track_vec.drain(0..head); } track_vec.extend_from_slice(&new_tracks); - self.state.set_track(protobuf::RepeatedField::from_vec(track_vec)); + self.state + .set_track(protobuf::RepeatedField::from_vec(track_vec)); // Update playing index if let Some(new_index) = self @@ -845,7 +886,9 @@ impl SpircTask { let context_uri = frame.get_state().get_context_uri().to_owned(); let tracks = frame.get_state().get_track(); debug!("Frame has {:?} tracks", tracks.len()); - if context_uri.starts_with("spotify:station:") || context_uri.starts_with("spotify:dailymix:") { + if context_uri.starts_with("spotify:station:") + || context_uri.starts_with("spotify:dailymix:") + { self.context_fut = self.resolve_station(&context_uri); } else if self.config.autoplay { info!("Fetching autoplay context uri"); @@ -856,8 +899,16 @@ impl SpircTask { self.state.set_playing_track_index(index); self.state.set_track(tracks.into_iter().cloned().collect()); self.state.set_context_uri(context_uri); - self.state.set_repeat(frame.get_state().get_repeat()); - self.state.set_shuffle(frame.get_state().get_shuffle()); + // has_shuffle/repeat seem to always be true in these replace msgs, + // but to replicate the behaviour of the Android client we have to + // ignore false values. + let state = frame.get_state(); + if state.get_repeat() { + self.state.set_repeat(true); + } + if state.get_shuffle() { + self.state.set_shuffle(true); + } } // should this be a method of SpotifyId directly? @@ -885,7 +936,8 @@ impl SpircTask { let track = { let mut track_ref = self.state.get_track()[index as usize].clone(); let mut track_id = self.get_spotify_id_for_track(&track_ref); - while track_id.is_err() || track_id.unwrap().audio_type == SpotifyAudioType::NonPlayable { + while track_id.is_err() || track_id.unwrap().audio_type == SpotifyAudioType::NonPlayable + { warn!( "Skipping track <{:?}> at position [{}] of {}", track_ref.get_uri(), diff --git a/core/Cargo.toml b/core/Cargo.toml index 908b907..e01c986 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -1,14 +1,15 @@ [package] name = "librespot-core" -version = "0.1.0" +version = "0.1.1" authors = ["Paul Lietar "] build = "build.rs" description="The core functionality provided by librespot" license="MIT" +edition = "2018" [dependencies.librespot-protocol] path = "../protocol" -version = "0.1.0" +version = "0.1.1" [dependencies] base64 = "0.10" diff --git a/core/src/apresolve.rs b/core/src/apresolve.rs index bcb7bfe..94d9424 100644 --- a/core/src/apresolve.rs +++ b/core/src/apresolve.rs @@ -10,7 +10,7 @@ use std::str::FromStr; use tokio_core::reactor::Handle; use url::Url; -error_chain!{} +error_chain! {} #[derive(Clone, Debug, Serialize, Deserialize)] pub struct APResolveData { @@ -21,7 +21,7 @@ fn apresolve( handle: &Handle, proxy: &Option, ap_port: &Option, -) -> Box> { +) -> Box> { let url = Uri::from_str(APRESOLVE_ENDPOINT).expect("invalid AP resolve URL"); let use_proxy = proxy.is_some(); @@ -52,19 +52,20 @@ fn apresolve( }) }); let body = body.then(|result| result.chain_err(|| "HTTP error")); - let body = body.and_then(|body| String::from_utf8(body).chain_err(|| "invalid UTF8 in response")); + let body = + body.and_then(|body| String::from_utf8(body).chain_err(|| "invalid UTF8 in response")); - let data = - body.and_then(|body| serde_json::from_str::(&body).chain_err(|| "invalid JSON")); + let data = body + .and_then(|body| serde_json::from_str::(&body).chain_err(|| "invalid JSON")); let p = ap_port.clone(); let ap = data.and_then(move |data| { let mut aps = data.ap_list.iter().filter(|ap| { if p.is_some() { - Uri::from_str(ap) - .ok() - .map_or(false, |uri| uri.port().map_or(false, |port| port == p.unwrap())) + Uri::from_str(ap).ok().map_or(false, |uri| { + uri.port().map_or(false, |port| port == p.unwrap()) + }) } else if use_proxy { // It is unlikely that the proxy will accept CONNECT on anything other than 443. Uri::from_str(ap) @@ -86,7 +87,7 @@ pub(crate) fn apresolve_or_fallback( handle: &Handle, proxy: &Option, ap_port: &Option, -) -> Box> +) -> Box> where E: 'static, { diff --git a/core/src/audio_key.rs b/core/src/audio_key.rs index 410cc2c..1e5310c 100644 --- a/core/src/audio_key.rs +++ b/core/src/audio_key.rs @@ -5,8 +5,8 @@ use futures::{Async, Future, Poll}; use std::collections::HashMap; use std::io::Write; -use spotify_id::{FileId, SpotifyId}; -use util::SeqGenerator; +use crate::spotify_id::{FileId, SpotifyId}; +use crate::util::SeqGenerator; #[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)] pub struct AudioKey(pub [u8; 16]); @@ -35,7 +35,11 @@ impl AudioKeyManager { let _ = sender.send(Ok(AudioKey(key))); } 0xe => { - warn!("error audio key {:x} {:x}", data.as_ref()[0], data.as_ref()[1]); + warn!( + "error audio key {:x} {:x}", + data.as_ref()[0], + data.as_ref()[1] + ); let _ = sender.send(Err(AudioKeyError)); } _ => (), diff --git a/core/src/authentication.rs b/core/src/authentication.rs index 07c014f..36cbd43 100644 --- a/core/src/authentication.rs +++ b/core/src/authentication.rs @@ -1,18 +1,18 @@ +use aes::Aes192; use base64; use byteorder::{BigEndian, ByteOrder}; -use aes::Aes192; use hmac::Hmac; -use sha1::{Sha1, Digest}; use pbkdf2::pbkdf2; use protobuf::ProtobufEnum; use serde; use serde_json; +use sha1::{Digest, Sha1}; use std::fs::File; use std::io::{self, Read, Write}; use std::ops::FnOnce; use std::path::Path; -use protocol::authentication::AuthenticationType; +use crate::protocol::authentication::AuthenticationType; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Credentials { @@ -40,24 +40,24 @@ impl Credentials { pub fn with_blob(username: String, encrypted_blob: &str, device_id: &str) -> Credentials { fn read_u8(stream: &mut R) -> io::Result { let mut data = [0u8]; - try!(stream.read_exact(&mut data)); + stream.read_exact(&mut data)?; Ok(data[0]) } fn read_int(stream: &mut R) -> io::Result { - let lo = try!(read_u8(stream)) as u32; + let lo = read_u8(stream)? as u32; if lo & 0x80 == 0 { return Ok(lo); } - let hi = try!(read_u8(stream)) as u32; + let hi = read_u8(stream)? as u32; Ok(lo & 0x7f | hi << 7) } fn read_bytes(stream: &mut R) -> io::Result> { - let length = try!(read_int(stream)); + let length = read_int(stream)?; let mut data = vec![0u8; length as usize]; - try!(stream.read_exact(&mut data)); + stream.read_exact(&mut data)?; Ok(data) } @@ -76,9 +76,9 @@ impl Credentials { // decrypt data using ECB mode without padding let blob = { - use aes::block_cipher_trait::BlockCipher; - use aes::block_cipher_trait::generic_array::GenericArray; use aes::block_cipher_trait::generic_array::typenum::Unsigned; + use aes::block_cipher_trait::generic_array::GenericArray; + use aes::block_cipher_trait::BlockCipher; let mut data = base64::decode(encrypted_blob).unwrap(); let cipher = Aes192::new(GenericArray::from_slice(&key)); @@ -148,7 +148,7 @@ where T: ProtobufEnum, D: serde::Deserializer<'de>, { - let v: i32 = try!(serde::Deserialize::deserialize(de)); + let v: i32 = serde::Deserialize::deserialize(de)?; T::from_i32(v).ok_or_else(|| serde::de::Error::custom("Invalid enum value")) } @@ -164,7 +164,7 @@ fn deserialize_base64<'de, D>(de: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { - let v: String = try!(serde::Deserialize::deserialize(de)); + let v: String = serde::Deserialize::deserialize(de)?; base64::decode(&v).map_err(|e| serde::de::Error::custom(e.to_string())) } @@ -181,9 +181,10 @@ pub fn get_credentials String>( Some(credentials.clone()) } - (Some(username), None, _) => { - Some(Credentials::with_password(username.clone(), prompt(&username))) - } + (Some(username), None, _) => Some(Credentials::with_password( + username.clone(), + prompt(&username), + )), (None, _, Some(credentials)) => Some(credentials), diff --git a/core/src/cache.rs b/core/src/cache.rs index 908ba29..9ee0ae1 100644 --- a/core/src/cache.rs +++ b/core/src/cache.rs @@ -5,9 +5,9 @@ use std::io::Read; use std::path::Path; use std::path::PathBuf; -use authentication::Credentials; -use spotify_id::FileId; -use volume::Volume; +use crate::authentication::Credentials; +use crate::spotify_id::FileId; +use crate::volume::Volume; #[derive(Clone)] pub struct Cache { @@ -80,7 +80,7 @@ impl Cache { File::open(self.file_path(file)).ok() } - pub fn save_file(&self, file: FileId, contents: &mut Read) { + pub fn save_file(&self, file: FileId, contents: &mut dyn Read) { if self.use_audio_cache { let path = self.file_path(file); diff --git a/core/src/channel.rs b/core/src/channel.rs index a4785eb..b614fac 100644 --- a/core/src/channel.rs +++ b/core/src/channel.rs @@ -5,7 +5,7 @@ use futures::{Async, Poll, Stream}; use std::collections::HashMap; use std::time::Instant; -use util::SeqGenerator; +use crate::util::SeqGenerator; component! { ChannelManager : ChannelManagerInner { @@ -14,6 +14,7 @@ component! { download_rate_estimate: usize = 0, download_measurement_start: Option = None, download_measurement_bytes: usize = 0, + invalid: bool = false, } } @@ -46,7 +47,9 @@ impl ChannelManager { let seq = self.lock(|inner| { let seq = inner.sequence.get(); - inner.channels.insert(seq, tx); + if !inner.invalid { + inner.channels.insert(seq, tx); + } seq }); @@ -87,12 +90,21 @@ impl ChannelManager { pub fn get_download_rate_estimate(&self) -> usize { return self.lock(|inner| inner.download_rate_estimate); } + + pub(crate) fn shutdown(&self) { + self.lock(|inner| { + inner.invalid = true; + // destroy the sending halves of the channels to signal everyone who is waiting for something. + inner.channels.clear(); + }); + } } impl Channel { fn recv_packet(&mut self) -> Poll { let (cmd, packet) = match self.receiver.poll() { - Ok(Async::Ready(t)) => t.expect("channel closed"), + Ok(Async::Ready(Some(t))) => t, + Ok(Async::Ready(None)) => return Err(ChannelError), // The channel has been closed. Ok(Async::NotReady) => return Ok(Async::NotReady), Err(()) => unreachable!(), }; diff --git a/core/src/config.rs b/core/src/config.rs index 7c05321..38be459 100644 --- a/core/src/config.rs +++ b/core/src/config.rs @@ -3,7 +3,7 @@ use std::str::FromStr; use url::Url; use uuid::Uuid; -use version; +use crate::version; #[derive(Clone, Debug)] pub struct SessionConfig { diff --git a/core/src/connection/codec.rs b/core/src/connection/codec.rs index 60634fc..fa4cd9d 100644 --- a/core/src/connection/codec.rs +++ b/core/src/connection/codec.rs @@ -88,7 +88,8 @@ impl Decoder for APCodec { let mut payload = buf.split_to(size + MAC_SIZE); - self.decode_cipher.decrypt(&mut payload.get_mut(..size).unwrap()); + self.decode_cipher + .decrypt(&mut payload.get_mut(..size).unwrap()); let mac = payload.split_off(size); self.decode_cipher.check_mac(mac.as_ref())?; diff --git a/core/src/connection/handshake.rs b/core/src/connection/handshake.rs index f291f13..220ab6e 100644 --- a/core/src/connection/handshake.rs +++ b/core/src/connection/handshake.rs @@ -1,9 +1,9 @@ use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; -use hmac::{Hmac, Mac}; -use sha1::Sha1; use futures::{Async, Future, Poll}; +use hmac::{Hmac, Mac}; use protobuf::{self, Message}; use rand::thread_rng; +use sha1::Sha1; use std::io::{self, Read}; use std::marker::PhantomData; use tokio_codec::{Decoder, Framed}; @@ -11,10 +11,10 @@ use tokio_io::io::{read_exact, write_all, ReadExact, Window, WriteAll}; use tokio_io::{AsyncRead, AsyncWrite}; use super::codec::APCodec; -use diffie_hellman::DHLocalKeys; -use protocol; -use protocol::keyexchange::{APResponseMessage, ClientHello, ClientResponsePlaintext}; -use util; +use crate::diffie_hellman::DHLocalKeys; +use crate::protocol; +use crate::protocol::keyexchange::{APResponseMessage, ClientHello, ClientResponsePlaintext}; +use crate::util; pub struct Handshake { keys: DHLocalKeys, @@ -62,7 +62,8 @@ impl Future for Handshake { .to_owned(); let shared_secret = self.keys.shared_secret(&remote_key); - let (challenge, send_key, recv_key) = compute_keys(&shared_secret, &accumulator); + let (challenge, send_key, recv_key) = + compute_keys(&shared_secret, &accumulator); let codec = APCodec::new(&send_key, &recv_key); let write = client_response(connection, challenge); @@ -92,7 +93,10 @@ fn client_hello(connection: T, gc: Vec) -> WriteAll (Vec, Vec, Vec< let mut data = Vec::with_capacity(0x64); for i in 1..6 { - let mut mac = HmacSha1::new_varkey(&shared_secret) - .expect("HMAC can take key of any size"); + let mut mac = HmacSha1::new_varkey(&shared_secret).expect("HMAC can take key of any size"); mac.input(packets); mac.input(&[i]); data.extend_from_slice(&mac.result().code()); } - let mut mac = HmacSha1::new_varkey(&data[..0x14]) - .expect("HMAC can take key of any size");; + let mut mac = HmacSha1::new_varkey(&data[..0x14]).expect("HMAC can take key of any size"); mac.input(packets); ( diff --git a/core/src/connection/mod.rs b/core/src/connection/mod.rs index 91b46c8..7249779 100644 --- a/core/src/connection/mod.rs +++ b/core/src/connection/mod.rs @@ -8,15 +8,15 @@ use futures::{Future, Sink, Stream}; use protobuf::{self, Message}; use std::io; use std::net::ToSocketAddrs; +use tokio_codec::Framed; use tokio_core::net::TcpStream; use tokio_core::reactor::Handle; -use tokio_codec::Framed; use url::Url; -use authentication::Credentials; -use version; +use crate::authentication::Credentials; +use crate::version; -use proxytunnel; +use crate::proxytunnel; pub type Transport = Framed; @@ -24,13 +24,31 @@ pub fn connect( addr: String, handle: &Handle, proxy: &Option, -) -> Box> { +) -> Box> { let (addr, connect_url) = match *proxy { Some(ref url) => { info!("Using proxy \"{}\"", url); - (url.to_socket_addrs().unwrap().next().unwrap(), Some(addr)) + match url.to_socket_addrs().and_then(|mut iter| { + iter.next().ok_or(io::Error::new( + io::ErrorKind::NotFound, + "Can't resolve proxy server address", + )) + }) { + Ok(socket_addr) => (socket_addr, Some(addr)), + Err(error) => return Box::new(futures::future::err(error)), + } + } + None => { + match addr.to_socket_addrs().and_then(|mut iter| { + iter.next().ok_or(io::Error::new( + io::ErrorKind::NotFound, + "Can't resolve server address", + )) + }) { + Ok(socket_addr) => (socket_addr, None), + Err(error) => return Box::new(futures::future::err(error)), + } } - None => (addr.to_socket_addrs().unwrap().next().unwrap(), None), }; let socket = TcpStream::connect(&addr, handle); @@ -48,23 +66,31 @@ pub fn authenticate( transport: Transport, credentials: Credentials, device_id: String, -) -> Box> { - use protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os}; - use protocol::keyexchange::APLoginFailed; +) -> Box> { + use crate::protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os}; + use crate::protocol::keyexchange::APLoginFailed; let mut packet = ClientResponseEncrypted::new(); - packet.mut_login_credentials().set_username(credentials.username); - packet.mut_login_credentials().set_typ(credentials.auth_type); + packet + .mut_login_credentials() + .set_username(credentials.username); + packet + .mut_login_credentials() + .set_typ(credentials.auth_type); packet .mut_login_credentials() .set_auth_data(credentials.auth_data); - packet.mut_system_info().set_cpu_family(CpuFamily::CPU_UNKNOWN); + packet + .mut_system_info() + .set_cpu_family(CpuFamily::CPU_UNKNOWN); packet.mut_system_info().set_os(Os::OS_UNKNOWN); - packet.mut_system_info().set_system_information_string(format!( - "librespot_{}_{}", - version::short_sha(), - version::build_id() - )); + packet + .mut_system_info() + .set_system_information_string(format!( + "librespot_{}_{}", + version::short_sha(), + version::build_id() + )); packet.mut_system_info().set_device_id(device_id); packet.set_version_string(version::version_string()); @@ -77,7 +103,8 @@ pub fn authenticate( .and_then(|transport| transport.into_future().map_err(|(err, _stream)| err)) .and_then(|(packet, transport)| match packet { Some((0xac, data)) => { - let welcome_data: APWelcome = protobuf::parse_from_bytes(data.as_ref()).unwrap(); + let welcome_data: APWelcome = + protobuf::parse_from_bytes(data.as_ref()).unwrap(); let reusable_credentials = Credentials { username: welcome_data.get_canonical_username().to_owned(), @@ -89,7 +116,8 @@ pub fn authenticate( } Some((0xad, data)) => { - let error_data: APLoginFailed = protobuf::parse_from_bytes(data.as_ref()).unwrap(); + let error_data: APLoginFailed = + protobuf::parse_from_bytes(data.as_ref()).unwrap(); panic!( "Authentication failed with reason: {:?}", error_data.get_error_code() diff --git a/core/src/diffie_hellman.rs b/core/src/diffie_hellman.rs index b8e4142..dec34a3 100644 --- a/core/src/diffie_hellman.rs +++ b/core/src/diffie_hellman.rs @@ -2,17 +2,18 @@ use num_bigint::BigUint; use num_traits::FromPrimitive; use rand::Rng; -use util; +use crate::util; lazy_static! { pub static ref DH_GENERATOR: BigUint = BigUint::from_u64(0x2).unwrap(); pub static ref DH_PRIME: BigUint = BigUint::from_bytes_be(&[ - 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, 0x34, - 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, 0xcc, 0x74, - 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, 0x34, 0x04, 0xdd, - 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, 0xf2, 0x5f, 0x14, 0x37, - 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5, 0x76, 0x62, 0x5e, 0x7e, 0xc6, - 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc9, 0x0f, 0xda, 0xa2, 0x21, 0x68, 0xc2, + 0x34, 0xc4, 0xc6, 0x62, 0x8b, 0x80, 0xdc, 0x1c, 0xd1, 0x29, 0x02, 0x4e, 0x08, 0x8a, 0x67, + 0xcc, 0x74, 0x02, 0x0b, 0xbe, 0xa6, 0x3b, 0x13, 0x9b, 0x22, 0x51, 0x4a, 0x08, 0x79, 0x8e, + 0x34, 0x04, 0xdd, 0xef, 0x95, 0x19, 0xb3, 0xcd, 0x3a, 0x43, 0x1b, 0x30, 0x2b, 0x0a, 0x6d, + 0xf2, 0x5f, 0x14, 0x37, 0x4f, 0xe1, 0x35, 0x6d, 0x6d, 0x51, 0xc2, 0x45, 0xe4, 0x85, 0xb5, + 0x76, 0x62, 0x5e, 0x7e, 0xc6, 0xf4, 0x4c, 0x42, 0xe9, 0xa6, 0x3a, 0x36, 0x20, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, ]); } @@ -39,7 +40,11 @@ impl DHLocalKeys { } pub fn shared_secret(&self, remote_key: &[u8]) -> Vec { - let shared_key = util::powm(&BigUint::from_bytes_be(remote_key), &self.private_key, &DH_PRIME); + let shared_key = util::powm( + &BigUint::from_bytes_be(remote_key), + &self.private_key, + &DH_PRIME, + ); shared_key.to_bytes_be() } } diff --git a/core/src/keymaster.rs b/core/src/keymaster.rs index 4d82ae0..f2d7b77 100644 --- a/core/src/keymaster.rs +++ b/core/src/keymaster.rs @@ -1,8 +1,8 @@ use futures::Future; use serde_json; -use mercury::MercuryError; -use session::Session; +use crate::mercury::MercuryError; +use crate::session::Session; #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] @@ -17,7 +17,7 @@ pub fn get_token( session: &Session, client_id: &str, scopes: &str, -) -> Box> { +) -> Box> { let url = format!( "hm://keymaster/token/authenticated?client_id={}&scope={}", client_id, scopes diff --git a/core/src/lib.rs b/core/src/lib.rs index e384d14..c65878c 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -11,29 +11,29 @@ extern crate log; #[macro_use] extern crate serde_derive; +extern crate aes; extern crate base64; extern crate byteorder; extern crate bytes; +extern crate hmac; extern crate httparse; extern crate hyper; extern crate hyper_proxy; extern crate num_bigint; extern crate num_integer; extern crate num_traits; +extern crate pbkdf2; extern crate protobuf; extern crate rand; extern crate serde; extern crate serde_json; +extern crate sha1; extern crate shannon; extern crate tokio_codec; extern crate tokio_core; extern crate tokio_io; extern crate url; extern crate uuid; -extern crate sha1; -extern crate hmac; -extern crate pbkdf2; -extern crate aes; extern crate librespot_protocol as protocol; diff --git a/core/src/mercury/mod.rs b/core/src/mercury/mod.rs index 0b69d8e..e167f9c 100644 --- a/core/src/mercury/mod.rs +++ b/core/src/mercury/mod.rs @@ -1,13 +1,13 @@ +use crate::protocol; use byteorder::{BigEndian, ByteOrder}; use bytes::Bytes; use futures::sync::{mpsc, oneshot}; use futures::{Async, Future, Poll}; use protobuf; -use protocol; use std::collections::HashMap; use std::mem; -use util::SeqGenerator; +use crate::util::SeqGenerator; mod types; pub use self::types::*; @@ -20,6 +20,7 @@ component! { sequence: SeqGenerator = SeqGenerator::new(0), pending: HashMap, MercuryPending> = HashMap::new(), subscriptions: Vec<(String, mpsc::UnboundedSender)> = Vec::new(), + invalid: bool = false, } } @@ -61,7 +62,11 @@ impl MercuryManager { }; let seq = self.next_seq(); - self.lock(|inner| inner.pending.insert(seq.clone(), pending)); + self.lock(|inner| { + if !inner.invalid { + inner.pending.insert(seq.clone(), pending); + } + }); let cmd = req.method.command(); let data = req.encode(&seq); @@ -95,7 +100,8 @@ impl MercuryManager { pub fn subscribe>( &self, uri: T, - ) -> Box, Error = MercuryError>> { + ) -> Box, Error = MercuryError>> + { let uri = uri.into(); let request = self.request(MercuryRequest { method: MercuryMethod::SUB, @@ -109,21 +115,23 @@ impl MercuryManager { let (tx, rx) = mpsc::unbounded(); manager.lock(move |inner| { - debug!("subscribed uri={} count={}", uri, response.payload.len()); - if response.payload.len() > 0 { - // Old subscription protocol, watch the provided list of URIs - for sub in response.payload { - let mut sub: protocol::pubsub::Subscription = - protobuf::parse_from_bytes(&sub).unwrap(); - let sub_uri = sub.take_uri(); + if !inner.invalid { + debug!("subscribed uri={} count={}", uri, response.payload.len()); + if response.payload.len() > 0 { + // Old subscription protocol, watch the provided list of URIs + for sub in response.payload { + let mut sub: protocol::pubsub::Subscription = + protobuf::parse_from_bytes(&sub).unwrap(); + let sub_uri = sub.take_uri(); - debug!("subscribed sub_uri={}", sub_uri); + debug!("subscribed sub_uri={}", sub_uri); - inner.subscriptions.push((sub_uri, tx.clone())); + inner.subscriptions.push((sub_uri, tx.clone())); + } + } else { + // New subscription protocol, watch the requested URI + inner.subscriptions.push((uri, tx)); } - } else { - // New subscription protocol, watch the requested URI - inner.subscriptions.push((uri, tx)); } }); @@ -222,4 +230,13 @@ impl MercuryManager { } } } + + pub(crate) fn shutdown(&self) { + self.lock(|inner| { + inner.invalid = true; + // destroy the sending halves of the channels to signal everyone who is waiting for something. + inner.pending.clear(); + inner.subscriptions.clear(); + }); + } } diff --git a/core/src/mercury/types.rs b/core/src/mercury/types.rs index 23f64c4..57cedce 100644 --- a/core/src/mercury/types.rs +++ b/core/src/mercury/types.rs @@ -2,7 +2,7 @@ use byteorder::{BigEndian, WriteBytesExt}; use protobuf::Message; use std::io::Write; -use protocol; +use crate::protocol; #[derive(Debug, PartialEq, Eq)] pub enum MercuryMethod { @@ -37,7 +37,8 @@ impl ToString for MercuryMethod { MercuryMethod::SUB => "SUB", MercuryMethod::UNSUB => "UNSUB", MercuryMethod::SEND => "SEND", - }.to_owned() + } + .to_owned() } } diff --git a/core/src/proxytunnel.rs b/core/src/proxytunnel.rs index 5e07db9..ea84bc8 100644 --- a/core/src/proxytunnel.rs +++ b/core/src/proxytunnel.rs @@ -57,7 +57,9 @@ impl Future for ProxyTunnel { let mut response = httparse::Response::new(&mut headers); let status = match response.parse(&buf) { Ok(status) => status, - Err(err) => return Err(io::Error::new(io::ErrorKind::Other, err.description())), + Err(err) => { + return Err(io::Error::new(io::ErrorKind::Other, err.description())); + } }; if status.is_complete() { @@ -102,7 +104,8 @@ fn proxy_connect(connection: T, connect_url: &str) -> WriteAll, handle: Handle, - ) -> Box> { - let access_point = apresolve_or_fallback::(&handle, &config.proxy, &config.ap_port); + ) -> Box> { + let access_point = + apresolve_or_fallback::(&handle, &config.proxy, &config.ap_port); let handle_ = handle.clone(); let proxy = config.proxy.clone(); @@ -64,8 +65,9 @@ impl Session { }); let device_id = config.device_id.clone(); - let authentication = connection - .and_then(move |connection| connection::authenticate(connection, credentials, device_id)); + let authentication = connection.and_then(move |connection| { + connection::authenticate(connection, credentials, device_id) + }); let result = authentication.map(move |(transport, reusable_credentials)| { info!("Authenticated as \"{}\" !", reusable_credentials.username); @@ -97,7 +99,7 @@ impl Session { config: SessionConfig, cache: Option, username: String, - ) -> (Session, Box>) { + ) -> (Session, Box>) { let (sink, stream) = transport.split(); let (sender_tx, sender_rx) = mpsc::unbounded(); @@ -133,7 +135,11 @@ impl Session { .map(|_| ()); let receiver_task = DispatchTask(stream, session.weak()); - let task = Box::new((receiver_task, sender_task).into_future().map(|((), ())| ())); + let task = Box::new( + (receiver_task, sender_task) + .into_future() + .map(|((), ())| ()), + ); (session, task) } @@ -197,7 +203,7 @@ impl Session { 0x9 | 0xa => self.channel().dispatch(cmd, data), 0xd | 0xe => self.audio_key().dispatch(cmd, data), - 0xb2...0xb6 => self.mercury().dispatch(cmd, data), + 0xb2..=0xb6 => self.mercury().dispatch(cmd, data), _ => (), } } @@ -237,6 +243,8 @@ impl Session { pub fn shutdown(&self) { debug!("Invalidating session[{}]", self.0.session_id); self.0.data.write().unwrap().invalid = true; + self.mercury().shutdown(); + self.channel().shutdown(); } pub fn is_invalid(&self) -> bool { @@ -283,13 +291,18 @@ where loop { let (cmd, data) = match self.0.poll() { - Ok(Async::Ready(t)) => t, + Ok(Async::Ready(Some(t))) => t, + Ok(Async::Ready(None)) => { + warn!("Connection to server closed."); + session.shutdown(); + return Ok(Async::Ready(())); + } Ok(Async::NotReady) => return Ok(Async::NotReady), Err(e) => { session.shutdown(); return Err(From::from(e)); } - }.expect("connection closed"); + }; session.dispatch(cmd, data); } diff --git a/core/src/spotify_id.rs b/core/src/spotify_id.rs index e6f0cdd..1a5fcd2 100644 --- a/core/src/spotify_id.rs +++ b/core/src/spotify_id.rs @@ -17,7 +17,8 @@ pub struct SpotifyId { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct SpotifyIdError; -const BASE62_DIGITS: &'static [u8] = b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; +const BASE62_DIGITS: &'static [u8] = + b"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; const BASE16_DIGITS: &'static [u8] = b"0123456789abcdef"; impl SpotifyId { diff --git a/core/src/util/mod.rs b/core/src/util/mod.rs index c91cac9..5c1e50f 100644 --- a/core/src/util/mod.rs +++ b/core/src/util/mod.rs @@ -6,7 +6,10 @@ use std::mem; use std::ops::{Mul, Rem, Shr}; pub fn rand_vec(rng: &mut G, size: usize) -> Vec { - ::std::iter::repeat(()).map(|()| rng.gen()).take(size).collect() + ::std::iter::repeat(()) + .map(|()| rng.gen()) + .take(size) + .collect() } pub fn powm(base: &BigUint, exp: &BigUint, modulus: &BigUint) -> BigUint { diff --git a/core/src/volume.rs b/core/src/volume.rs index 24a3d3f..6b456d1 100644 --- a/core/src/volume.rs +++ b/core/src/volume.rs @@ -21,7 +21,9 @@ impl Volume { // write volume to file fn save_to_writer(&self, writer: &mut W) { - writer.write_all(self.volume.to_string().as_bytes()).unwrap(); + writer + .write_all(self.volume.to_string().as_bytes()) + .unwrap(); } pub(crate) fn save_to_file>(&self, path: P) { diff --git a/examples/play.rs b/examples/play.rs index 87f6882..6888ebb 100644 --- a/examples/play.rs +++ b/examples/play.rs @@ -1,6 +1,3 @@ -extern crate librespot; -extern crate tokio_core; - use std::env; use tokio_core::reactor::Core; @@ -37,7 +34,9 @@ fn main() { .run(Session::connect(session_config, credentials, None, handle)) .unwrap(); - let (player, _) = Player::new(player_config, session.clone(), None, move || (backend)(None)); + let (player, _) = Player::new(player_config, session.clone(), None, move || { + (backend)(None) + }); println!("Playing..."); core.run(player.load(track, true, 0)).unwrap(); diff --git a/examples/playlist_tracks.rs b/examples/playlist_tracks.rs index 5d6aec5..fc288d1 100644 --- a/examples/playlist_tracks.rs +++ b/examples/playlist_tracks.rs @@ -1,11 +1,4 @@ -extern crate log; -extern crate env_logger; - -extern crate librespot; -extern crate tokio_core; -extern crate tokio_io; -extern crate futures; - +use env_logger; use std::env; use tokio_core::reactor::Core; @@ -13,7 +6,7 @@ use librespot::core::authentication::Credentials; use librespot::core::config::SessionConfig; use librespot::core::session::Session; use librespot::core::spotify_id::SpotifyId; -use librespot::metadata::{Metadata, Track, Playlist}; +use librespot::metadata::{Metadata, Playlist, Track}; fn main() { env_logger::init(); @@ -32,16 +25,16 @@ fn main() { let uri_split = args[3].split(":"); let uri_parts: Vec<&str> = uri_split.collect(); - println!("{}, {}, {}",uri_parts[0], uri_parts[1], uri_parts[2]); - + println!("{}, {}, {}", uri_parts[0], uri_parts[1], uri_parts[2]); + let plist_uri = SpotifyId::from_base62(uri_parts[2]).unwrap(); - + let session = core .run(Session::connect(session_config, credentials, None, handle)) .unwrap(); let plist = core.run(Playlist::get(&session, plist_uri)).unwrap(); - println!("{:?}",plist); + println!("{:?}", plist); for track_id in plist.tracks { let plist_track = core.run(Track::get(&session, track_id)).unwrap(); println!("track: {} ", plist_track.name); diff --git a/metadata/Cargo.toml b/metadata/Cargo.toml index 7502819..328ba06 100644 --- a/metadata/Cargo.toml +++ b/metadata/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "librespot-metadata" -version = "0.1.0" +version = "0.1.1" authors = ["Paul Lietar "] description="The metadata logic for librespot" license="MIT" +edition = "2018" [dependencies] byteorder = "1.3" @@ -14,7 +15,7 @@ log = "0.4" [dependencies.librespot-core] path = "../core" -version = "0.1.0" +version = "0.1.1" [dependencies.librespot-protocol] path = "../protocol" -version = "0.1.0" +version = "0.1.1" diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index 677e466..d4bd797 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -19,7 +19,7 @@ use librespot_core::mercury::MercuryError; use librespot_core::session::Session; use librespot_core::spotify_id::{FileId, SpotifyAudioType, SpotifyId}; -pub use protocol::metadata::AudioFile_Format as FileFormat; +pub use crate::protocol::metadata::AudioFile_Format as FileFormat; fn countrylist_contains(list: &str, country: &str) -> bool { list.chunks(2).any(|cc| cc == country) @@ -301,7 +301,6 @@ impl Metadata for Playlist { } fn parse(msg: &Self::Message, _: &Session) -> Self { - let tracks = msg .get_contents() .get_items() @@ -312,9 +311,13 @@ impl Metadata for Playlist { SpotifyId::from_base62(uri_parts[2]).unwrap() }) .collect::>(); - + if tracks.len() != msg.get_length() as usize { - warn!("Got {} tracks, but the playlist should contain {} tracks.", tracks.len(), msg.get_length()); + warn!( + "Got {} tracks, but the playlist should contain {} tracks.", + tracks.len(), + msg.get_length() + ); } Playlist { diff --git a/playback/Cargo.toml b/playback/Cargo.toml index 3d6fee6..b806d01 100644 --- a/playback/Cargo.toml +++ b/playback/Cargo.toml @@ -1,24 +1,26 @@ [package] name = "librespot-playback" -version = "0.1.0" +version = "0.1.1" authors = ["Sasha Hilton "] description="The audio playback logic for librespot" license="MIT" +edition = "2018" [dependencies.librespot-audio] path = "../audio" -version = "0.1.0" +version = "0.1.1" [dependencies.librespot-core] path = "../core" -version = "0.1.0" +version = "0.1.1" [dependencies.librespot-metadata] path = "../metadata" -version = "0.1.0" +version = "0.1.1" [dependencies] futures = "0.1" log = "0.4" byteorder = "1.3" +shell-words = "0.1.0" alsa = { version = "0.2.1", optional = true } portaudio-rs = { version = "0.3.0", optional = true } diff --git a/playback/src/audio_backend/alsa.rs b/playback/src/audio_backend/alsa.rs index 98e7c8f..8bdcb9d 100644 --- a/playback/src/audio_backend/alsa.rs +++ b/playback/src/audio_backend/alsa.rs @@ -64,7 +64,8 @@ impl Open for AlsaSink { } Some(device) => device, None => "default", - }.to_string(); + } + .to_string(); AlsaSink(None, name) } diff --git a/playback/src/audio_backend/jackaudio.rs b/playback/src/audio_backend/jackaudio.rs index 3b118f3..54cf8b4 100644 --- a/playback/src/audio_backend/jackaudio.rs +++ b/playback/src/audio_backend/jackaudio.rs @@ -1,7 +1,7 @@ use super::{Open, Sink}; use jack::prelude::{ - client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port, ProcessHandler, - ProcessScope, + client_options, AsyncClient, AudioOutPort, AudioOutSpec, Client, JackControl, Port, + ProcessHandler, ProcessScope, }; use std::io; use std::sync::mpsc::{sync_channel, Receiver, SyncSender}; @@ -45,9 +45,14 @@ impl Open for JackSink { info!("Using jack sink!"); let client_name = client_name.unwrap_or("librespot".to_string()); - let (client, _status) = Client::new(&client_name[..], client_options::NO_START_SERVER).unwrap(); - let ch_r = client.register_port("out_0", AudioOutSpec::default()).unwrap(); - let ch_l = client.register_port("out_1", AudioOutSpec::default()).unwrap(); + let (client, _status) = + Client::new(&client_name[..], client_options::NO_START_SERVER).unwrap(); + let ch_r = client + .register_port("out_0", AudioOutSpec::default()) + .unwrap(); + let ch_l = client + .register_port("out_1", AudioOutSpec::default()) + .unwrap(); // buffer for samples from librespot (~10ms) let (tx, rx) = sync_channel(2 * 1024 * 4); let jack_data = JackData { diff --git a/playback/src/audio_backend/mod.rs b/playback/src/audio_backend/mod.rs index 48f4f61..a9840d4 100644 --- a/playback/src/audio_backend/mod.rs +++ b/playback/src/audio_backend/mod.rs @@ -1,7 +1,7 @@ use std::io; pub trait Open { - fn open(Option) -> Self; + fn open(_: Option) -> Self; } pub trait Sink { @@ -10,7 +10,7 @@ pub trait Sink { fn write(&mut self, data: &[i16]) -> io::Result<()>; } -fn mk_sink(device: Option) -> Box { +fn mk_sink(device: Option) -> Box { Box::new(S::open(device)) } @@ -51,7 +51,10 @@ use self::sdl::SdlSink; mod pipe; use self::pipe::StdoutSink; -pub const BACKENDS: &'static [(&'static str, fn(Option) -> Box)] = &[ +mod subprocess; +use self::subprocess::SubprocessSink; + +pub const BACKENDS: &'static [(&'static str, fn(Option) -> Box)] = &[ #[cfg(feature = "alsa-backend")] ("alsa", mk_sink::), #[cfg(feature = "portaudio-backend")] @@ -67,9 +70,10 @@ pub const BACKENDS: &'static [(&'static str, fn(Option) -> Box)] = #[cfg(feature = "sdl-backend")] ("sdl", mk_sink::), ("pipe", mk_sink::), + ("subprocess", mk_sink::), ]; -pub fn find(name: Option) -> Option) -> Box> { +pub fn find(name: Option) -> Option) -> Box> { if let Some(name) = name { BACKENDS .iter() diff --git a/playback/src/audio_backend/pipe.rs b/playback/src/audio_backend/pipe.rs index 414e192..2adafe1 100644 --- a/playback/src/audio_backend/pipe.rs +++ b/playback/src/audio_backend/pipe.rs @@ -4,7 +4,7 @@ use std::io::{self, Write}; use std::mem; use std::slice; -pub struct StdoutSink(Box); +pub struct StdoutSink(Box); impl Open for StdoutSink { fn open(path: Option) -> StdoutSink { @@ -28,7 +28,10 @@ impl Sink for StdoutSink { fn write(&mut self, data: &[i16]) -> io::Result<()> { let data: &[u8] = unsafe { - slice::from_raw_parts(data.as_ptr() as *const u8, data.len() * mem::size_of::()) + slice::from_raw_parts( + data.as_ptr() as *const u8, + data.len() * mem::size_of::(), + ) }; self.0.write_all(data)?; diff --git a/playback/src/audio_backend/portaudio.rs b/playback/src/audio_backend/portaudio.rs index 19a0bf0..31397bf 100644 --- a/playback/src/audio_backend/portaudio.rs +++ b/playback/src/audio_backend/portaudio.rs @@ -11,7 +11,7 @@ pub struct PortAudioSink<'a>( StreamParameters, ); -fn output_devices() -> Box> { +fn output_devices() -> Box> { let count = portaudio_rs::device::get_count().unwrap(); let devices = (0..count) .filter_map(|idx| portaudio_rs::device::get_info(idx).map(|info| (idx, info))) @@ -51,7 +51,8 @@ impl<'a> Open for PortAudioSink<'a> { } Some(device) => find_output(device), None => get_default_output_index(), - }.expect("Could not find device"); + } + .expect("Could not find device"); let info = portaudio_rs::device::get_info(device_idx); let latency = match info { @@ -81,8 +82,9 @@ impl<'a> Sink for PortAudioSink<'a> { FRAMES_PER_BUFFER_UNSPECIFIED, StreamFlags::empty(), None, - ).unwrap(), - );; + ) + .unwrap(), + ); } self.0.as_mut().unwrap().start().unwrap(); diff --git a/playback/src/audio_backend/pulseaudio.rs b/playback/src/audio_backend/pulseaudio.rs index 88f6280..e844b0d 100644 --- a/playback/src/audio_backend/pulseaudio.rs +++ b/playback/src/audio_backend/pulseaudio.rs @@ -14,7 +14,11 @@ pub struct PulseAudioSink { desc: CString, } -fn call_pulseaudio(f: F, fail_check: FailCheck, kind: io::ErrorKind) -> io::Result +fn call_pulseaudio( + f: F, + fail_check: FailCheck, + kind: io::ErrorKind, +) -> io::Result where T: Copy, F: Fn(*mut libc::c_int) -> T, diff --git a/playback/src/audio_backend/rodio.rs b/playback/src/audio_backend/rodio.rs index c4b9c92..4ce09f6 100644 --- a/playback/src/audio_backend/rodio.rs +++ b/playback/src/audio_backend/rodio.rs @@ -1,8 +1,8 @@ use super::{Open, Sink}; -extern crate rodio; extern crate cpal; -use std::{io, thread, time}; +extern crate rodio; use std::process::exit; +use std::{io, thread, time}; pub struct RodioSink { rodio_sink: rodio::Sink, @@ -14,7 +14,7 @@ fn list_formats(ref device: &rodio::Device) { Err(e) => { warn!("Error getting default rodio::Sink format: {:?}", e); return; - }, + } }; let mut output_formats = match device.supported_output_formats() { @@ -22,13 +22,16 @@ fn list_formats(ref device: &rodio::Device) { Err(e) => { warn!("Error getting supported rodio::Sink formats: {:?}", e); return; - }, + } }; if output_formats.peek().is_some() { debug!(" Available formats:"); for format in output_formats { - let s = format!("{}ch, {:?}, min {:?}, max {:?}", format.channels, format.data_type, format.min_sample_rate, format.max_sample_rate); + let s = format!( + "{}ch, {:?}, min {:?}, max {:?}", + format.channels, format.data_type, format.min_sample_rate, format.max_sample_rate + ); if format == default_fmt { debug!(" (default) {}", s); } else { @@ -79,9 +82,7 @@ impl Open for RodioSink { } let sink = rodio::Sink::new(&rodio_device); - RodioSink { - rodio_sink: sink, - } + RodioSink { rodio_sink: sink } } } diff --git a/playback/src/audio_backend/subprocess.rs b/playback/src/audio_backend/subprocess.rs new file mode 100644 index 0000000..2af8836 --- /dev/null +++ b/playback/src/audio_backend/subprocess.rs @@ -0,0 +1,59 @@ +use super::{Open, Sink}; +use shell_words::split; +use std::io::{self, Write}; +use std::mem; +use std::process::{Child, Command, Stdio}; +use std::slice; + +pub struct SubprocessSink { + shell_command: String, + child: Option, +} + +impl Open for SubprocessSink { + fn open(shell_command: Option) -> SubprocessSink { + if let Some(shell_command) = shell_command { + SubprocessSink { + shell_command: shell_command, + child: None, + } + } else { + panic!("subprocess sink requires specifying a shell command"); + } + } +} + +impl Sink for SubprocessSink { + fn start(&mut self) -> io::Result<()> { + let args = split(&self.shell_command).unwrap(); + self.child = Some( + Command::new(&args[0]) + .args(&args[1..]) + .stdin(Stdio::piped()) + .spawn()?, + ); + Ok(()) + } + + fn stop(&mut self) -> io::Result<()> { + if let Some(child) = &mut self.child.take() { + child.kill()?; + child.wait()?; + } + Ok(()) + } + + fn write(&mut self, data: &[i16]) -> io::Result<()> { + let data: &[u8] = unsafe { + slice::from_raw_parts( + data.as_ptr() as *const u8, + data.len() * mem::size_of::(), + ) + }; + if let Some(child) = &mut self.child { + let child_stdin = child.stdin.as_mut().unwrap(); + child_stdin.write_all(data)?; + } + Ok(()) + } +} diff --git a/playback/src/lib.rs b/playback/src/lib.rs index a3a54ec..84441a8 100644 --- a/playback/src/lib.rs +++ b/playback/src/lib.rs @@ -3,6 +3,7 @@ extern crate log; extern crate byteorder; extern crate futures; +extern crate shell_words; #[cfg(feature = "alsa-backend")] extern crate alsa; diff --git a/playback/src/mixer/alsamixer.rs b/playback/src/mixer/alsamixer.rs index 5c77d47..a906c2e 100644 --- a/playback/src/mixer/alsamixer.rs +++ b/playback/src/mixer/alsamixer.rs @@ -10,13 +10,17 @@ pub struct AlsaMixer { } impl AlsaMixer { - fn map_volume(&self, set_volume: Option) -> Result<(u16), Box> { + fn map_volume(&self, set_volume: Option) -> Result<(u16), Box> { let mixer = alsa::mixer::Mixer::new(&self.config.card, false)?; let sid = alsa::mixer::SelemId::new(&*self.config.mixer, self.config.index); - let selem = mixer - .find_selem(&sid) - .expect(format!("Couldn't find simple mixer control for {}", self.config.mixer).as_str()); + let selem = mixer.find_selem(&sid).expect( + format!( + "Couldn't find simple mixer control for {}", + self.config.mixer + ) + .as_str(), + ); let (min, max) = selem.get_playback_volume_range(); let range = (max - min) as f64; @@ -72,7 +76,7 @@ impl Mixer for AlsaMixer { } } - fn get_audio_filter(&self) -> Option> { + fn get_audio_filter(&self) -> Option> { None } } diff --git a/playback/src/mixer/mod.rs b/playback/src/mixer/mod.rs index f19a866..4fc01b5 100644 --- a/playback/src/mixer/mod.rs +++ b/playback/src/mixer/mod.rs @@ -1,12 +1,12 @@ pub trait Mixer: Send { - fn open(Option) -> Self + fn open(_: Option) -> Self where Self: Sized; fn start(&self); fn stop(&self); fn set_volume(&self, volume: u16); fn volume(&self) -> u16; - fn get_audio_filter(&self) -> Option> { + fn get_audio_filter(&self) -> Option> { None } } @@ -28,10 +28,11 @@ pub struct MixerConfig { } impl Default for MixerConfig { - fn default() -> MixerConfig { MixerConfig { - card: String::from("default"), - mixer: String::from("PCM"), - index: 0, + fn default() -> MixerConfig { + MixerConfig { + card: String::from("default"), + mixer: String::from("PCM"), + index: 0, } } } @@ -39,11 +40,11 @@ impl Default for MixerConfig { pub mod softmixer; use self::softmixer::SoftMixer; -fn mk_sink(device: Option) -> Box { +fn mk_sink(device: Option) -> Box { Box::new(M::open(device)) } -pub fn find>(name: Option) -> Option) -> Box> { +pub fn find>(name: Option) -> Option) -> Box> { match name.as_ref().map(AsRef::as_ref) { None | Some("softvol") => Some(mk_sink::), #[cfg(feature = "alsa-backend")] diff --git a/playback/src/mixer/softmixer.rs b/playback/src/mixer/softmixer.rs index 4b96978..28e1cf5 100644 --- a/playback/src/mixer/softmixer.rs +++ b/playback/src/mixer/softmixer.rs @@ -23,7 +23,7 @@ impl Mixer for SoftMixer { fn set_volume(&self, volume: u16) { self.volume.store(volume as usize, Ordering::Relaxed); } - fn get_audio_filter(&self) -> Option> { + fn get_audio_filter(&self) -> Option> { Some(Box::new(SoftVolumeApplier { volume: self.volume.clone(), })) diff --git a/playback/src/player.rs b/playback/src/player.rs index a54a577..38ee00c 100644 --- a/playback/src/player.rs +++ b/playback/src/player.rs @@ -11,19 +11,19 @@ use std::sync::mpsc::{RecvError, RecvTimeoutError, TryRecvError}; use std::thread; use std::time::Duration; -use config::{Bitrate, PlayerConfig}; +use crate::config::{Bitrate, PlayerConfig}; use librespot_core::session::Session; use librespot_core::spotify_id::SpotifyId; -use audio::{AudioDecrypt, AudioFile, StreamLoaderController}; -use audio::{VorbisDecoder, VorbisPacket}; -use audio::{ +use crate::audio::{AudioDecrypt, AudioFile, StreamLoaderController}; +use crate::audio::{VorbisDecoder, VorbisPacket}; +use crate::audio::{ READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_BEFORE_PLAYBACK_SECONDS, READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS, }; -use audio_backend::Sink; -use metadata::{AudioItem, FileFormat}; -use mixer::AudioFilter; +use crate::audio_backend::Sink; +use crate::metadata::{AudioItem, FileFormat}; +use crate::mixer::AudioFilter; pub struct Player { commands: Option>, @@ -36,9 +36,9 @@ struct PlayerInternal { commands: std::sync::mpsc::Receiver, state: PlayerState, - sink: Box, + sink: Box, sink_running: bool, - audio_filter: Option>, + audio_filter: Option>, event_sender: futures::sync::mpsc::UnboundedSender, } @@ -98,8 +98,10 @@ impl NormalisationData { } fn get_factor(config: &PlayerConfig, data: NormalisationData) -> f32 { - let mut normalisation_factor = - f32::powf(10.0, (data.track_gain_db + config.normalisation_pregain) / 20.0); + let mut normalisation_factor = f32::powf( + 10.0, + (data.track_gain_db + config.normalisation_pregain) / 20.0, + ); if normalisation_factor * data.track_peak > 1.0 { warn!("Reducing normalisation factor to prevent clipping. Please add negative pregain to avoid."); @@ -117,11 +119,11 @@ impl Player { pub fn new( config: PlayerConfig, session: Session, - audio_filter: Option>, + audio_filter: Option>, sink_builder: F, ) -> (Player, PlayerEventChannel) where - F: FnOnce() -> Box + Send + 'static, + F: FnOnce() -> Box + Send + 'static, { let (cmd_tx, cmd_rx) = std::sync::mpsc::channel(); let (event_sender, event_receiver) = futures::sync::mpsc::unbounded(); @@ -238,7 +240,12 @@ impl PlayerState { use self::PlayerState::*; match *self { Stopped | EndOfTrack { .. } => None, - Paused { ref mut decoder, .. } | Playing { ref mut decoder, .. } => Some(decoder), + Paused { + ref mut decoder, .. + } + | Playing { + ref mut decoder, .. + } => Some(decoder), Invalid => panic!("invalid state"), } } @@ -518,7 +525,10 @@ impl PlayerInternal { if let Some(stream_loader_controller) = self.state.stream_loader_controller() { stream_loader_controller.set_stream_mode(); } - if let PlayerState::Playing { bytes_per_second, .. } = self.state { + if let PlayerState::Playing { + bytes_per_second, .. + } = self.state + { if let Some(stream_loader_controller) = self.state.stream_loader_controller() { // Request our read ahead range let request_data_length = max( @@ -592,7 +602,10 @@ impl PlayerInternal { .iter() .map(|alt_id| AudioItem::get_audio_item(&self.session, *alt_id)); let alternatives = future::join_all(alternatives).wait().unwrap(); - alternatives.into_iter().find(|alt| alt.available).map(Cow::Owned) + alternatives + .into_iter() + .find(|alt| alt.available) + .map(Cow::Owned) } else { None } @@ -623,9 +636,14 @@ impl PlayerInternal { spotify_id: SpotifyId, position: i64, ) -> Option<(Decoder, f32, StreamLoaderController, usize)> { - let audio = AudioItem::get_audio_item(&self.session, spotify_id) - .wait() - .unwrap(); + let audio = match AudioItem::get_audio_item(&self.session, spotify_id).wait() { + Ok(audio) => audio, + Err(_) => { + error!("Unable to load audio item."); + return None; + } + }; + info!("Loading <{}> with Spotify URI <{}>", audio.name, audio.uri); let audio = match self.find_available_alternative(&audio) { @@ -670,10 +688,20 @@ impl PlayerInternal { let play_from_beginning = position == 0; let key = self.session.audio_key().request(spotify_id, file_id); - let encrypted_file = - AudioFile::open(&self.session, file_id, bytes_per_second, play_from_beginning); + let encrypted_file = AudioFile::open( + &self.session, + file_id, + bytes_per_second, + play_from_beginning, + ); - let encrypted_file = encrypted_file.wait().unwrap(); + let encrypted_file = match encrypted_file.wait() { + Ok(encrypted_file) => encrypted_file, + Err(_) => { + error!("Unable to load encrypted file."); + return None; + } + }; let mut stream_loader_controller = encrypted_file.get_stream_loader_controller(); @@ -685,11 +713,20 @@ impl PlayerInternal { stream_loader_controller.set_random_access_mode(); } - let key = key.wait().unwrap(); + let key = match key.wait() { + Ok(key) => key, + Err(_) => { + error!("Unable to load decryption key"); + return None; + } + }; + let mut decrypted_file = AudioDecrypt::new(key, encrypted_file); let normalisation_factor = match NormalisationData::parse_from_file(&mut decrypted_file) { - Ok(normalisation_data) => NormalisationData::get_factor(&self.config, normalisation_data), + Ok(normalisation_data) => { + NormalisationData::get_factor(&self.config, normalisation_data) + } Err(_) => { warn!("Unable to extract normalisation data, using default value."); 1.0 as f32 @@ -768,7 +805,7 @@ impl Seek for Subfile { x => x, }; - let newpos = try!(self.stream.seek(pos)); + let newpos = self.stream.seek(pos)?; if newpos > self.offset { Ok(newpos - self.offset) } else { diff --git a/protocol/Cargo.toml b/protocol/Cargo.toml index 4c9a332..0960cfb 100644 --- a/protocol/Cargo.toml +++ b/protocol/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "librespot-protocol" -version = "0.1.0" +version = "0.1.1" authors = ["Paul LiĆ©tar "] build = "build.rs" description="The protobuf logic for communicating with Spotify servers" license="MIT" +edition = "2018" [dependencies] protobuf = "2.8.1" diff --git a/protocol/build.rs b/protocol/build.rs index c7f19ac..3ea25e7 100644 --- a/protocol/build.rs +++ b/protocol/build.rs @@ -1,14 +1,16 @@ extern crate protobuf_codegen; // Does the business extern crate protobuf_codegen_pure; // Helper function -use std::path::Path; use std::fs::{read_to_string, write}; +use std::path::Path; -use protobuf_codegen_pure::Customize; use protobuf_codegen_pure::parse_and_typecheck; +use protobuf_codegen_pure::Customize; fn main() { - let customizations = Customize { ..Default::default() }; + let customizations = Customize { + ..Default::default() + }; let lib_str = read_to_string("src/lib.rs").unwrap(); @@ -21,10 +23,9 @@ fn main() { let name; if line.starts_with("pub mod ") { - name = &line[8..len-1]; // Remove keywords and semi-colon - } - else { - name = &line[4..len-1]; // Remove keywords and semi-colon + name = &line[8..len - 1]; // Remove keywords and semi-colon + } else { + name = &line[4..len - 1]; // Remove keywords and semi-colon } // Build the paths to relevant files. @@ -44,11 +45,7 @@ fn main() { let p = parse_and_typecheck(&["proto"], &[src]).expect("protoc"); // But generate them with the protobuf-codegen crate directly. // Then we can keep the result in-memory. - let result = protobuf_codegen::gen( - &p.file_descriptors, - &p.relative_paths, - &customizations, - ); + let result = protobuf_codegen::gen(&p.file_descriptors, &p.relative_paths, &customizations); // Protoc result as a byte array. let new = &result.first().unwrap().content; // Convert to utf8 to compare with existing. diff --git a/rustfmt.toml b/rustfmt.toml index 627f7c4..25c1fc1 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,4 +1,3 @@ -max_width = 105 +# max_width = 105 reorder_imports = true -reorder_imports_in_group = true reorder_modules = true diff --git a/src/lib.rs b/src/lib.rs index f73db1a..610062e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,15 +1,6 @@ #![crate_name = "librespot"] #![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))] -extern crate base64; -extern crate futures; -extern crate hyper; -extern crate num_bigint; -extern crate protobuf; -extern crate rand; -extern crate tokio_core; -extern crate url; - pub extern crate librespot_audio as audio; pub extern crate librespot_connect as connect; pub extern crate librespot_core as core; diff --git a/src/main.rs b/src/main.rs index e193257..8ee3b0c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,6 @@ -extern crate env_logger; -extern crate futures; -extern crate getopts; -extern crate librespot; -#[macro_use] -extern crate log; -extern crate hex; -extern crate rpassword; -extern crate sha1; -extern crate tokio_core; -extern crate tokio_io; -extern crate tokio_process; -extern crate tokio_signal; -extern crate url; - use futures::sync::mpsc::UnboundedReceiver; use futures::{Async, Future, Poll, Stream}; +use log::{error, info, trace, warn}; use sha1::{Digest, Sha1}; use std::env; use std::io::{self, stderr, Write}; @@ -22,6 +8,7 @@ use std::mem; use std::path::PathBuf; use std::process::exit; use std::str::FromStr; +use std::time::Instant; use tokio_core::reactor::{Core, Handle}; use tokio_io::IoStream; use url::Url; @@ -40,7 +27,7 @@ use librespot::playback::mixer::{self, Mixer, MixerConfig}; use librespot::playback::player::{Player, PlayerEvent}; mod player_event_handler; -use player_event_handler::run_program_on_events; +use crate::player_event_handler::run_program_on_events; fn device_id(name: &str) -> String { hex::encode(Sha1::digest(name.as_bytes())) @@ -86,10 +73,10 @@ fn list_backends() { #[derive(Clone)] struct Setup { - backend: fn(Option) -> Box, + backend: fn(Option) -> Box, device: Option, - mixer: fn(Option) -> Box, + mixer: fn(Option) -> Box, cache: Option, player_config: PlayerConfig, @@ -198,7 +185,13 @@ fn setup(args: &[String]) -> Setup { let matches = match opts.parse(&args[1..]) { Ok(m) => m, Err(f) => { - writeln!(stderr(), "error: {}\n{}", f.to_string(), usage(&args[0], &opts)).unwrap(); + writeln!( + stderr(), + "error: {}\n{}", + f.to_string(), + usage(&args[0], &opts) + ) + .unwrap(); exit(1); } }; @@ -232,7 +225,9 @@ fn setup(args: &[String]) -> Setup { let mixer = mixer::find(mixer_name.as_ref()).expect("Invalid mixer"); let mixer_config = MixerConfig { - card: matches.opt_str("mixer-card").unwrap_or(String::from("default")), + card: matches + .opt_str("mixer-card") + .unwrap_or(String::from("default")), mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")), index: matches .opt_str("mixer-index") @@ -367,9 +362,9 @@ struct Main { player_config: PlayerConfig, session_config: SessionConfig, connect_config: ConnectConfig, - backend: fn(Option) -> Box, + backend: fn(Option) -> Box, device: Option, - mixer: fn(Option) -> Box, + mixer: fn(Option) -> Box, mixer_config: MixerConfig, handle: Handle, @@ -378,9 +373,11 @@ struct Main { spirc: Option, spirc_task: Option, - connect: Box>, + connect: Box>, shutdown: bool, + last_credentials: Option, + auto_connect_times: Vec, player_event_channel: Option>, player_event_program: Option, @@ -404,6 +401,8 @@ impl Main { spirc: None, spirc_task: None, shutdown: false, + last_credentials: None, + auto_connect_times: Vec::new(), signal: Box::new(tokio_signal::ctrl_c().flatten_stream()), player_event_channel: None, @@ -414,7 +413,8 @@ impl Main { let config = task.connect_config.clone(); let device_id = task.session_config.device_id.clone(); - task.discovery = Some(discovery(&handle, config, device_id, setup.zeroconf_port).unwrap()); + task.discovery = + Some(discovery(&handle, config, device_id, setup.zeroconf_port).unwrap()); } if let Some(credentials) = setup.credentials { @@ -425,6 +425,7 @@ impl Main { } fn credentials(&mut self, credentials: Credentials) { + self.last_credentials = Some(credentials.clone()); let config = self.session_config.clone(); let handle = self.handle.clone(); @@ -447,36 +448,46 @@ impl Future for Main { loop { let mut progress = false; - if let Some(Async::Ready(Some(creds))) = self.discovery.as_mut().map(|d| d.poll().unwrap()) { + if let Some(Async::Ready(Some(creds))) = + self.discovery.as_mut().map(|d| d.poll().unwrap()) + { if let Some(ref spirc) = self.spirc { spirc.shutdown(); } + self.auto_connect_times.clear(); self.credentials(creds); progress = true; } - if let Async::Ready(session) = self.connect.poll().unwrap() { - self.connect = Box::new(futures::future::empty()); - let mixer_config = self.mixer_config.clone(); - let mixer = (self.mixer)(Some(mixer_config)); - let player_config = self.player_config.clone(); - let connect_config = self.connect_config.clone(); + match self.connect.poll() { + Ok(Async::Ready(session)) => { + self.connect = Box::new(futures::future::empty()); + let mixer_config = self.mixer_config.clone(); + let mixer = (self.mixer)(Some(mixer_config)); + let player_config = self.player_config.clone(); + let connect_config = self.connect_config.clone(); - let audio_filter = mixer.get_audio_filter(); - let backend = self.backend; - let device = self.device.clone(); - let (player, event_channel) = - Player::new(player_config, session.clone(), audio_filter, move || { - (backend)(device) - }); + let audio_filter = mixer.get_audio_filter(); + let backend = self.backend; + let device = self.device.clone(); + let (player, event_channel) = + Player::new(player_config, session.clone(), audio_filter, move || { + (backend)(device) + }); - let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer); - self.spirc = Some(spirc); - self.spirc_task = Some(spirc_task); - self.player_event_channel = Some(event_channel); + let (spirc, spirc_task) = Spirc::new(connect_config, session, player, mixer); + self.spirc = Some(spirc); + self.spirc_task = Some(spirc_task); + self.player_event_channel = Some(event_channel); - progress = true; + progress = true; + } + Ok(Async::NotReady) => (), + Err(error) => { + error!("Could not connect to server: {}", error); + self.connect = Box::new(futures::future::empty()); + } } if let Async::Ready(Some(())) = self.signal.poll().unwrap() { @@ -495,12 +506,32 @@ impl Future for Main { progress = true; } + let mut drop_spirc_and_try_to_reconnect = false; if let Some(ref mut spirc_task) = self.spirc_task { if let Async::Ready(()) = spirc_task.poll().unwrap() { if self.shutdown { return Ok(Async::Ready(())); } else { - panic!("Spirc shut down unexpectedly"); + warn!("Spirc shut down unexpectedly"); + drop_spirc_and_try_to_reconnect = true; + } + progress = true; + } + } + if drop_spirc_and_try_to_reconnect { + self.spirc_task = None; + while (!self.auto_connect_times.is_empty()) + && ((Instant::now() - self.auto_connect_times[0]).as_secs() > 600) + { + let _ = self.auto_connect_times.remove(0); + } + + if let Some(credentials) = self.last_credentials.clone() { + if self.auto_connect_times.len() >= 5 { + warn!("Spirc shut down too often. Not reconnecting automatically."); + } else { + self.auto_connect_times.push(Instant::now()); + self.credentials(credentials); } } } diff --git a/src/player_event_handler.rs b/src/player_event_handler.rs index 1e682b9..03bae14 100644 --- a/src/player_event_handler.rs +++ b/src/player_event_handler.rs @@ -1,8 +1,9 @@ use librespot::playback::player::PlayerEvent; -use tokio_process::{Child, CommandExt}; +use log::info; use std::collections::HashMap; use std::io; use std::process::Command; +use tokio_process::{Child, CommandExt}; fn run_program(program: &str, env_vars: HashMap<&str, String>) -> io::Result { let mut v: Vec<&str> = program.split_whitespace().collect();