diff --git a/Cargo.lock b/Cargo.lock index 470dd324..5813a707 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,47 +23,45 @@ checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" [[package]] name = "aes" -version = "0.3.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54eb1d8fe354e5fc611daf4f2ea97dd45a765f4f1e4512306ec183ae2e8f20c9" +checksum = "884391ef1066acaa41e766ba8f596341b96e93ce34f9a43e7d24bf0a0eaf0561" dependencies = [ "aes-soft", "aesni", - "block-cipher-trait", + "cipher", ] [[package]] name = "aes-ctr" -version = "0.3.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e5b0458ea3beae0d1d8c0f3946564f8e10f90646cf78c06b4351052058d1ee" +checksum = "7729c3cde54d67063be556aeac75a81330d802f0259500ca40cb52967f975763" dependencies = [ "aes-soft", "aesni", + "cipher", "ctr", - "stream-cipher", ] [[package]] name = "aes-soft" -version = "0.3.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfd7e7ae3f9a1fb5c03b389fc6bb9a51400d0c13053f0dca698c832bfd893a0d" +checksum = "be14c7498ea50828a38d0e24a765ed2effe92a705885b57d029cd67d45744072" dependencies = [ - "block-cipher-trait", - "byteorder", - "opaque-debug", + "cipher", + "opaque-debug 0.3.0", ] [[package]] name = "aesni" -version = "0.6.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f70a6b5f971e473091ab7cfb5ffac6cde81666c4556751d8d5620ead8abf100" +checksum = "ea2e11f5e94c2f7d386164cc2aa1f97823fed6f259e486940a71c174dd01b0ce" dependencies = [ - "block-cipher-trait", - "opaque-debug", - "stream-cipher", + "cipher", + "opaque-debug 0.3.0", ] [[package]] @@ -123,14 +121,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" [[package]] -name = "atty" -version = "0.2.14" +name = "async-stream" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "3670df70cbc01729f901f94c887814b3c68db038aad1329a418bae178bc5295c" dependencies = [ - "hermit-abi", - "libc", - "winapi 0.3.9", + "async-stream-impl", + "futures-core", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3548b8efc9f8e8a5a0a2808c5bd8451a9031b9e5b879a79590304ae928b0a70" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3a45e77e34375a7923b1e8febb049bb011f064714a8e17a1a616fef01da13d" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -169,12 +188,6 @@ dependencies = [ "safemem", ] -[[package]] -name = "base64" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" - [[package]] name = "base64" version = "0.13.0" @@ -236,26 +249,7 @@ dependencies = [ "block-padding", "byte-tools", "byteorder", - "generic-array", -] - -[[package]] -name = "block-cipher-trait" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774" -dependencies = [ - "generic-array", -] - -[[package]] -name = "block-modes" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31aa8410095e39fdb732909fb5730a48d5bd7c2e3cd76bd1b07b3dbea130c529" -dependencies = [ - "block-cipher-trait", - "block-padding", + "generic-array 0.12.3", ] [[package]] @@ -269,9 +263,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.5.0" +version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f07aa6688c702439a1be0307b6a94dffe1168569e45b9500c1372bc580740d59" +checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "byte-tools" @@ -285,23 +279,6 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" -[[package]] -name = "bytes" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" -dependencies = [ - "byteorder", - "either", - "iovec", -] - -[[package]] -name = "bytes" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" - [[package]] name = "bytes" version = "1.0.1" @@ -351,7 +328,7 @@ dependencies = [ "num-integer", "num-traits", "time 0.1.43", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -360,6 +337,15 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7477065d45a8fe57167bf3cf8bcd3729b54cfcb81cca49bda2d038ea89ae82ca" +[[package]] +name = "cipher" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +dependencies = [ + "generic-array 0.14.4", +] + [[package]] name = "clang-sys" version = "1.0.3" @@ -399,7 +385,7 @@ version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc4369b5e4c0cddf64ad8981c0111e7df4f7078f4d6ba98fb31f2e17c4c57b7e" dependencies = [ - "bytes 1.0.1", + "bytes", "memchr", ] @@ -416,7 +402,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784ad0fbab4f3e9cef09f20e0aea6000ae08d2cb98ac4c0abc53df18803d702f" dependencies = [ "percent-encoding 2.1.0", - "time 0.2.25", + "time 0.2.24", "version_check", ] @@ -432,7 +418,7 @@ dependencies = [ "publicsuffix", "serde", "serde_json", - "time 0.2.25", + "time 0.2.24", "url 2.2.0", ] @@ -479,11 +465,11 @@ dependencies = [ "ndk-glue", "nix 0.15.0", "oboe", - "parking_lot 0.11.1", + "parking_lot", "stdweb 0.1.3", "thiserror", "web-sys", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -495,91 +481,23 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crossbeam-deque" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils 0.7.2", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" -dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "crossbeam-utils 0.7.2", - "lazy_static", - "maybe-uninit", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" -dependencies = [ - "crossbeam-utils 0.6.6", -] - -[[package]] -name = "crossbeam-queue" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" -dependencies = [ - "cfg-if 0.1.10", - "crossbeam-utils 0.7.2", - "maybe-uninit", -] - -[[package]] -name = "crossbeam-utils" -version = "0.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" -dependencies = [ - "cfg-if 0.1.10", - "lazy_static", -] - -[[package]] -name = "crossbeam-utils" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" -dependencies = [ - "autocfg", - "cfg-if 0.1.10", - "lazy_static", -] - [[package]] name = "crypto-mac" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4434400df11d95d556bac068ddfedd482915eb18fe8bea89bc80b6e4b1c179e5" dependencies = [ - "generic-array", + "generic-array 0.12.3", "subtle", ] [[package]] name = "ctr" -version = "0.3.2" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "022cd691704491df67d25d006fe8eca083098253c4d43516c2206479c58c6736" +checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" dependencies = [ - "block-cipher-trait", - "stream-cipher", + "cipher", ] [[package]] @@ -619,9 +537,9 @@ dependencies = [ [[package]] name = "derivative" -version = "2.2.0" +version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +checksum = "eaed5874effa6cde088c644ddcdcb4ffd1511391c5be4fdd7a5ccd02c7e4a183" dependencies = [ "proc-macro2", "quote", @@ -634,7 +552,7 @@ version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" dependencies = [ - "generic-array", + "generic-array 0.12.3", ] [[package]] @@ -643,34 +561,12 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0" -[[package]] -name = "dns-sd" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d748509dea20228f63ba519bf142ce2593396386125b01f5b0d6412dab972087" -dependencies = [ - "libc", - "pkg-config", -] - [[package]] name = "either" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" -[[package]] -name = "env_logger" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26ecb66b4bdca6c1409b40fb255eefc2bd4f6d135dab3c3124f80ffa2a9661e" -dependencies = [ - "atty", - "humantime", - "log", - "termcolor", -] - [[package]] name = "error-chain" version = "0.12.4" @@ -707,7 +603,7 @@ dependencies = [ "cfg-if 1.0.0", "libc", "redox_syscall 0.2.4", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -732,28 +628,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -[[package]] -name = "fuchsia-zircon" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" -dependencies = [ - "bitflags 1.2.1", - "fuchsia-zircon-sys", -] - -[[package]] -name = "fuchsia-zircon-sys" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" - -[[package]] -name = "futures" -version = "0.1.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c7e4c2612746b0df8fed4ce0c69156021b704c9aefa360311c04e6e9e002eed" - [[package]] name = "futures" version = "0.3.12" @@ -785,16 +659,6 @@ version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65" -[[package]] -name = "futures-cpupool" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" -dependencies = [ - "futures 0.1.30", - "num_cpus", -] - [[package]] name = "futures-executor" version = "0.3.12" @@ -852,7 +716,7 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.4", + "pin-project-lite", "pin-utils", "proc-macro-hack", "proc-macro-nested", @@ -875,12 +739,13 @@ dependencies = [ ] [[package]] -name = "getopts" -version = "0.2.21" +name = "generic-array" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ - "unicode-width", + "typenum", + "version_check", ] [[package]] @@ -1073,37 +938,19 @@ dependencies = [ [[package]] name = "h2" -version = "0.1.26" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" +checksum = "6b67e66362108efccd8ac053abafc8b7a8d86a37e6e48fc4f6f7485eb5e9e6a5" dependencies = [ - "byteorder", - "bytes 0.4.12", - "fnv", - "futures 0.1.30", - "http 0.1.21", - "indexmap", - "log", - "slab", - "string", - "tokio-io", -] - -[[package]] -name = "h2" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e4728fd124914ad25e99e3d15a9361a879f6620f63cb56bbb08f95abb97a535" -dependencies = [ - "bytes 0.5.6", + "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http 0.2.3", + "http", "indexmap", "slab", - "tokio 0.2.24", + "tokio", "tokio-util", "tracing", "tracing-futures", @@ -1133,12 +980,6 @@ dependencies = [ "libc", ] -[[package]] -name = "hex" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" - [[package]] name = "hmac" version = "0.7.1" @@ -1149,59 +990,25 @@ dependencies = [ "digest", ] -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi 0.3.9", -] - -[[package]] -name = "http" -version = "0.1.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" -dependencies = [ - "bytes 0.4.12", - "fnv", - "itoa", -] - [[package]] name = "http" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" dependencies = [ - "bytes 1.0.1", + "bytes", "fnv", "itoa", ] [[package]] name = "http-body" -version = "0.1.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" +checksum = "2861bd27ee074e5ee891e8b539837a9430012e249d7f0ca2d795650f579c1994" dependencies = [ - "bytes 0.4.12", - "futures 0.1.30", - "http 0.1.21", - "tokio-buf", -] - -[[package]] -name = "http-body" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b" -dependencies = [ - "bytes 0.5.6", - "http 0.2.3", + "bytes", + "http", ] [[package]] @@ -1216,79 +1023,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" -version = "0.12.35" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbe6ed1438e1f8ad955a4701e9a944938e9519f6888d12d8558b645e247d5f6" +checksum = "12219dc884514cb4a6a03737f4413c0e01c23a1b059b0156004b23f1e19dccbe" dependencies = [ - "bytes 0.4.12", - "futures 0.1.30", - "futures-cpupool", - "h2 0.1.26", - "http 0.1.21", - "http-body 0.1.0", - "httparse", - "iovec", - "itoa", - "log", - "net2", - "rustc_version", - "time 0.1.43", - "tokio 0.1.22", - "tokio-buf", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "tokio-tcp", - "tokio-threadpool", - "tokio-timer", - "want 0.2.0", -] - -[[package]] -name = "hyper" -version = "0.13.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ad767baac13b44d4529fcf58ba2cd0995e36e7b435bc5b039de6f47e880dbf" -dependencies = [ - "bytes 0.5.6", + "bytes", "futures-channel", "futures-core", "futures-util", - "h2 0.2.7", - "http 0.2.3", - "http-body 0.3.1", + "h2", + "http", + "http-body", "httparse", "httpdate", "itoa", "pin-project 1.0.4", "socket2", - "tokio 0.2.24", + "tokio", "tower-service", "tracing", - "want 0.3.0", -] - -[[package]] -name = "hyper-proxy" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cf120ed868e8e0cd22279cc8196c8db126884a5dbb01e0f528018048efd8fee" -dependencies = [ - "bytes 0.5.6", - "futures 0.3.12", - "http 0.2.3", - "hyper 0.13.9", - "tokio 0.2.24", - "tower-service", - "typed-headers", + "want", ] [[package]] @@ -1319,27 +1075,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "if-addrs" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28538916eb3f3976311f5dfbe67b5362d0add1293d0a9cad17debf86f8e3aa48" -dependencies = [ - "if-addrs-sys", - "libc", - "winapi 0.3.9", -] - -[[package]] -name = "if-addrs-sys" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de74b9dd780476e837e5eb5ab7c88b49ed304126e412030a0adba99c8efe79ea" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "indexmap" version = "1.6.1" @@ -1359,15 +1094,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "iovec" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" -dependencies = [ - "libc", -] - [[package]] name = "itertools" version = "0.9.0" @@ -1449,16 +1175,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "kernel32-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "lazy_static" version = "1.4.0" @@ -1473,13 +1189,13 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "lewton" -version = "0.9.4" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d542c1a317036c45c2aa1cf10cc9d403ca91eb2d333ef1a4917e5cb10628bd0" +checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030" dependencies = [ "byteorder", "ogg", - "smallvec 0.6.14", + "tinyvec", ] [[package]] @@ -1513,25 +1229,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "351a32417a12d5f7e82c368a66781e307834dae04c6ce0cd4456d52989229883" dependencies = [ "cfg-if 1.0.0", - "winapi 0.3.9", -] - -[[package]] -name = "libmdns" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d8582c174736c53633bc482ac709b24527c018356c3dc6d8e25a788b06b394e" -dependencies = [ - "byteorder", - "futures 0.1.30", - "hostname", - "if-addrs", - "log", - "multimap", - "net2", - "quick-error", - "rand 0.7.3", - "tokio-core", + "winapi", ] [[package]] @@ -1545,7 +1243,7 @@ dependencies = [ "libpulse-sys", "num-derive", "num-traits", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1579,35 +1277,18 @@ dependencies = [ "num-derive", "num-traits", "pkg-config", - "winapi 0.3.9", + "winapi", ] [[package]] name = "librespot" version = "0.1.3" dependencies = [ - "base64 0.13.0", - "env_logger", - "futures 0.1.30", - "getopts", - "hex", "librespot-audio", - "librespot-connect", "librespot-core", "librespot-metadata", "librespot-playback", "librespot-protocol", - "log", - "num-bigint", - "protobuf", - "rand 0.7.3", - "rpassword", - "sha-1", - "tokio 0.2.24", - "tokio-io", - "tokio-process", - "tokio-signal", - "url 1.7.2", ] [[package]] @@ -1617,46 +1298,19 @@ dependencies = [ "aes-ctr", "bit-set", "byteorder", - "bytes 0.4.12", - "futures 0.3.12", + "bytes", + "futures", "lewton", "librespot-core", "librespot-tremor", "log", "num-bigint", "num-traits", + "pin-project-lite", "tempfile", - "tokio 0.2.24", "vorbis", ] -[[package]] -name = "librespot-connect" -version = "0.1.3" -dependencies = [ - "aes-ctr", - "base64 0.13.0", - "block-modes", - "dns-sd", - "futures 0.1.30", - "hmac", - "hyper 0.12.35", - "libmdns", - "librespot-core", - "librespot-playback", - "librespot-protocol", - "log", - "num-bigint", - "protobuf", - "rand 0.7.3", - "serde", - "serde_derive", - "serde_json", - "sha-1", - "tokio 0.1.22", - "url 1.7.2", -] - [[package]] name = "librespot-core" version = "0.1.3" @@ -1664,20 +1318,19 @@ dependencies = [ "aes", "base64 0.13.0", "byteorder", - "bytes 0.5.6", - "error-chain", - "futures 0.3.12", + "bytes", + "futures", "hmac", "httparse", - "hyper 0.13.9", - "hyper-proxy", - "lazy_static", + "hyper", "librespot-protocol", "log", "num-bigint", "num-integer", "num-traits", + "once_cell", "pbkdf2", + "pin-project-lite", "protobuf", "rand 0.7.3", "serde", @@ -1685,8 +1338,7 @@ dependencies = [ "serde_json", "sha-1", "shannon", - "thiserror", - "tokio 0.2.24", + "tokio", "tokio-util", "url 1.7.2", "uuid", @@ -1697,8 +1349,9 @@ dependencies = [ name = "librespot-metadata" version = "0.1.3" dependencies = [ + "async-trait", "byteorder", - "futures 0.1.30", + "futures", "librespot-core", "librespot-protocol", "linear-map", @@ -1713,7 +1366,7 @@ dependencies = [ "alsa 0.2.2", "byteorder", "cpal", - "futures 0.1.30", + "futures", "glib", "gstreamer", "gstreamer-app", @@ -1760,15 +1413,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" -[[package]] -name = "lock_api" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75" -dependencies = [ - "scopeguard", -] - [[package]] name = "lock_api" version = "0.4.2" @@ -1796,45 +1440,18 @@ dependencies = [ "libc", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" -[[package]] -name = "maybe-uninit" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" - [[package]] name = "memchr" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" -[[package]] -name = "memoffset" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "043175f069eda7b85febe4a74abbaeff828d9f8b448515d3151a14a3542811aa" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - [[package]] name = "miniz_oxide" version = "0.4.3" @@ -1847,56 +1464,15 @@ dependencies = [ [[package]] name = "mio" -version = "0.6.23" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4afd66f5b91bf2a3bc13fad0e21caedac168ca4c707504e75585648ae80e4cc4" +checksum = "e50ae3f04d169fcc9bde0b547d1c205219b7157e07ded9c5aff03e0637cb3ed7" dependencies = [ - "cfg-if 0.1.10", - "fuchsia-zircon", - "fuchsia-zircon-sys", - "iovec", - "kernel32-sys", "libc", "log", - "miow 0.2.2", - "net2", - "slab", - "winapi 0.2.8", -] - -[[package]] -name = "mio-named-pipes" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" -dependencies = [ - "log", - "mio", - "miow 0.3.6", - "winapi 0.3.9", -] - -[[package]] -name = "mio-uds" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" -dependencies = [ - "iovec", - "libc", - "mio", -] - -[[package]] -name = "miow" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebd808424166322d4a38da87083bfddd3ac4c131334ed55856112eb06d46944d" -dependencies = [ - "kernel32-sys", - "net2", - "winapi 0.2.8", - "ws2_32-sys", + "miow", + "ntapi", + "winapi", ] [[package]] @@ -1906,7 +1482,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a33c1b55807fbed163481b5ba66db4b2fa6cde694a5027be10fb724206c5897" dependencies = [ "socket2", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -1915,15 +1491,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0419348c027fa7be448d2ae7ea0e4e04c2334c31dc4e74ab29f00a2a7ca69204" -[[package]] -name = "multimap" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1255076139a83bb467426e7f8d0134968a8118844faa755985e077cf31850333" -dependencies = [ - "serde", -] - [[package]] name = "ndk" version = "0.2.1" @@ -1969,17 +1536,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c44922cb3dbb1c70b5e5f443d63b64363a898564d739ba5198e3a9138442868d" -[[package]] -name = "net2" -version = "0.2.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "winapi 0.3.9", -] - [[package]] name = "nix" version = "0.9.0" @@ -2015,6 +1571,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "ntapi" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44" +dependencies = [ + "winapi", +] + [[package]] name = "num-bigint" version = "0.3.1" @@ -2130,9 +1695,9 @@ dependencies = [ [[package]] name = "ogg" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e571c3517af9e1729d4c63571a27edd660ade0667973bfc74a67c660c2b651" +checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e" dependencies = [ "byteorder", ] @@ -2161,15 +1726,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" [[package]] -name = "parking_lot" -version = "0.9.0" +name = "opaque-debug" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" -dependencies = [ - "lock_api 0.3.4", - "parking_lot_core 0.6.2", - "rustc_version", -] +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "parking_lot" @@ -2178,23 +1738,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" dependencies = [ "instant", - "lock_api 0.4.2", - "parking_lot_core 0.8.2", -] - -[[package]] -name = "parking_lot_core" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" -dependencies = [ - "cfg-if 0.1.10", - "cloudabi", - "libc", - "redox_syscall 0.1.57", - "rustc_version", - "smallvec 0.6.14", - "winapi 0.3.9", + "lock_api", + "parking_lot_core", ] [[package]] @@ -2207,8 +1752,8 @@ dependencies = [ "instant", "libc", "redox_syscall 0.1.57", - "smallvec 1.6.1", - "winapi 0.3.9", + "smallvec", + "winapi", ] [[package]] @@ -2290,12 +1835,6 @@ dependencies = [ "syn", ] -[[package]] -name = "pin-project-lite" -version = "0.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c917123afa01924fc84bb20c4c03f004d9c38e5127e3c039bbf7f4b9c76a2f6b" - [[package]] name = "pin-project-lite" version = "0.2.4" @@ -2448,12 +1987,6 @@ dependencies = [ "percent-encoding 2.1.0", ] -[[package]] -name = "quick-error" -version = "1.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" - [[package]] name = "quote" version = "1.0.8" @@ -2473,7 +2006,7 @@ dependencies = [ "fuchsia-cprng", "libc", "rand_core 0.3.1", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2608,7 +2141,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2623,7 +2156,7 @@ dependencies = [ "spin", "untrusted", "web-sys", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2641,16 +2174,6 @@ dependencies = [ "cpal", ] -[[package]] -name = "rpassword" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffc936cf8a7ea60c58f030fd36a612a48f440610214dc54bc36431f9ea0c3efb" -dependencies = [ - "libc", - "winapi 0.3.9", -] - [[package]] name = "rustc-demangle" version = "0.1.18" @@ -2706,12 +2229,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "scoped-tls" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28" - [[package]] name = "scopeguard" version = "1.1.0" @@ -2768,18 +2285,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.122" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "974ef1bd2ad8a507599b336595454081ff68a9599b4890af7643c0c0ed73a62c" +checksum = "166b2349061381baf54a58e4b13c89369feb0ef2eaa57198899e2312aac30aab" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.122" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8dee1f300f838c8ac340ecb0112b3ac472464fa67e87292bdb3dfc9c49128e17" +checksum = "0ca2a8cb5805ce9e3b95435e3765b7b553cecc762d938d409434338386cb5775" dependencies = [ "proc-macro2", "quote", @@ -2806,7 +2323,7 @@ dependencies = [ "block-buffer", "digest", "fake-simd", - "opaque-debug", + "opaque-debug 0.2.3", ] [[package]] @@ -2824,7 +2341,7 @@ dependencies = [ "block-buffer", "digest", "fake-simd", - "opaque-debug", + "opaque-debug 0.2.3", ] [[package]] @@ -2848,30 +2365,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" -[[package]] -name = "signal-hook-registry" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" -dependencies = [ - "libc", -] - [[package]] name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" -[[package]] -name = "smallvec" -version = "0.6.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" -dependencies = [ - "maybe-uninit", -] - [[package]] name = "smallvec" version = "1.6.1" @@ -2886,7 +2385,7 @@ checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e" dependencies = [ "cfg-if 1.0.0", "libc", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -2959,24 +2458,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" -[[package]] -name = "stream-cipher" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8131256a5896cabcf5eb04f4d6dacbe1aefda854b0d9896e09cb58829ec5638c" -dependencies = [ - "generic-array", -] - -[[package]] -name = "string" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" -dependencies = [ - "bytes 0.4.12", -] - [[package]] name = "strsim" version = "0.9.3" @@ -3009,9 +2490,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee" [[package]] name = "syn" -version = "1.0.60" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" dependencies = [ "proc-macro2", "quote", @@ -3047,12 +2528,13 @@ dependencies = [ [[package]] name = "tar" -version = "0.4.31" +version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69b4283cf44997cad75fd635aa70e16f3317248c4c8dfd96ad134d15d4d34db" +checksum = "489997b7557e9a43e192c527face4feacc78bfbe6eed67fd55c4c9e381cba290" dependencies = [ "filetime", "libc", + "redox_syscall 0.1.57", "xattr", ] @@ -3067,16 +2549,7 @@ dependencies = [ "rand 0.8.2", "redox_syscall 0.2.4", "remove_dir_all", - "winapi 0.3.9", -] - -[[package]] -name = "termcolor" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" -dependencies = [ - "winapi-util", + "winapi", ] [[package]] @@ -3106,14 +2579,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" dependencies = [ "libc", - "winapi 0.3.9", + "winapi", ] [[package]] name = "time" -version = "0.2.25" +version = "0.2.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1195b046942c221454c2539395f85413b33383a067449d78aab2b7b052a142f7" +checksum = "273d3ed44dca264b0d6b3665e8d48fb515042d42466fad93d2a45b90ec4058f7" dependencies = [ "const_fn", "libc", @@ -3121,7 +2594,7 @@ dependencies = [ "stdweb 0.4.20", "time-macros", "version_check", - "winapi 0.3.9", + "winapi", ] [[package]] @@ -3164,140 +2637,25 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "0.1.22" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" +checksum = "8efab2086f17abcddb8f756117665c958feee6b2e39974c2f1600592ab3a4195" dependencies = [ - "bytes 0.4.12", - "futures 0.1.30", - "mio", - "num_cpus", - "tokio-codec", - "tokio-current-thread", - "tokio-executor", - "tokio-fs", - "tokio-io", - "tokio-reactor", - "tokio-sync", - "tokio-tcp", - "tokio-threadpool", - "tokio-timer", - "tokio-udp", - "tokio-uds", -] - -[[package]] -name = "tokio" -version = "0.2.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099837d3464c16a808060bb3f02263b412f6fafcb5d01c533d309985fbeebe48" -dependencies = [ - "bytes 0.5.6", - "fnv", - "futures-core", - "iovec", - "lazy_static", + "autocfg", + "bytes", "libc", "memchr", "mio", - "mio-named-pipes", - "mio-uds", "num_cpus", - "pin-project-lite 0.1.11", - "signal-hook-registry", - "slab", + "pin-project-lite", "tokio-macros", - "winapi 0.3.9", -] - -[[package]] -name = "tokio-buf" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" -dependencies = [ - "bytes 0.4.12", - "either", - "futures 0.1.30", -] - -[[package]] -name = "tokio-codec" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25b2998660ba0e70d18684de5d06b70b70a3a747469af9dea7618cc59e75976b" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.30", - "tokio-io", -] - -[[package]] -name = "tokio-core" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87b1395334443abca552f63d4f61d0486f12377c2ba8b368e523f89e828cffd4" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.30", - "iovec", - "log", - "mio", - "scoped-tls", - "tokio 0.1.22", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "tokio-timer", -] - -[[package]] -name = "tokio-current-thread" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1de0e32a83f131e002238d7ccde18211c0a5397f60cbfffcb112868c2e0e20e" -dependencies = [ - "futures 0.1.30", - "tokio-executor", -] - -[[package]] -name = "tokio-executor" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb2d1b8f4548dbf5e1f7818512e9c406860678f29c300cdf0ebac72d1a3a1671" -dependencies = [ - "crossbeam-utils 0.7.2", - "futures 0.1.30", -] - -[[package]] -name = "tokio-fs" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "297a1206e0ca6302a0eed35b700d292b275256f596e2f3fea7729d5e629b6ff4" -dependencies = [ - "futures 0.1.30", - "tokio-io", - "tokio-threadpool", -] - -[[package]] -name = "tokio-io" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.30", - "log", ] [[package]] name = "tokio-macros" -version = "0.2.6" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" +checksum = "42517d2975ca3114b22a16192634e8241dc5cc1f130be194645970cc1c371494" dependencies = [ "proc-macro2", "quote", @@ -3305,159 +2663,30 @@ dependencies = [ ] [[package]] -name = "tokio-process" -version = "0.2.5" +name = "tokio-stream" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "382d90f43fa31caebe5d3bc6cfd854963394fff3b8cb59d5146607aaae7e7e43" +checksum = "76066865172052eb8796c686f0b441a93df8b08d40a950b062ffb9a426f00edd" dependencies = [ - "crossbeam-queue 0.1.2", - "futures 0.1.30", - "lazy_static", - "libc", - "log", - "mio", - "mio-named-pipes", - "tokio-io", - "tokio-reactor", - "tokio-signal", - "winapi 0.3.9", -] - -[[package]] -name = "tokio-reactor" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09bc590ec4ba8ba87652da2068d150dcada2cfa2e07faae270a5e0409aa51351" -dependencies = [ - "crossbeam-utils 0.7.2", - "futures 0.1.30", - "lazy_static", - "log", - "mio", - "num_cpus", - "parking_lot 0.9.0", - "slab", - "tokio-executor", - "tokio-io", - "tokio-sync", -] - -[[package]] -name = "tokio-signal" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c34c6e548f101053321cba3da7cbb87a610b85555884c41b07da2eb91aff12" -dependencies = [ - "futures 0.1.30", - "libc", - "mio", - "mio-uds", - "signal-hook-registry", - "tokio-executor", - "tokio-io", - "tokio-reactor", - "winapi 0.3.9", -] - -[[package]] -name = "tokio-sync" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edfe50152bc8164fcc456dab7891fa9bf8beaf01c5ee7e1dd43a397c3cf87dee" -dependencies = [ - "fnv", - "futures 0.1.30", -] - -[[package]] -name = "tokio-tcp" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98df18ed66e3b72e742f185882a9e201892407957e45fbff8da17ae7a7c51f72" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.30", - "iovec", - "mio", - "tokio-io", - "tokio-reactor", -] - -[[package]] -name = "tokio-threadpool" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89" -dependencies = [ - "crossbeam-deque", - "crossbeam-queue 0.2.3", - "crossbeam-utils 0.7.2", - "futures 0.1.30", - "lazy_static", - "log", - "num_cpus", - "slab", - "tokio-executor", -] - -[[package]] -name = "tokio-timer" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93044f2d313c95ff1cb7809ce9a7a05735b012288a888b62d4434fd58c94f296" -dependencies = [ - "crossbeam-utils 0.7.2", - "futures 0.1.30", - "slab", - "tokio-executor", -] - -[[package]] -name = "tokio-udp" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2a0b10e610b39c38b031a2fcab08e4b82f16ece36504988dcbd81dbba650d82" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.30", - "log", - "mio", - "tokio-codec", - "tokio-io", - "tokio-reactor", -] - -[[package]] -name = "tokio-uds" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab57a4ac4111c8c9dbcf70779f6fc8bc35ae4b2454809febac840ad19bd7e4e0" -dependencies = [ - "bytes 0.4.12", - "futures 0.1.30", - "iovec", - "libc", - "log", - "mio", - "mio-uds", - "tokio-codec", - "tokio-io", - "tokio-reactor", + "futures-core", + "pin-project-lite", + "tokio", ] [[package]] name = "tokio-util" -version = "0.3.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" +checksum = "feb971a26599ffd28066d387f109746df178eff14d5ea1e235015c5601967a4b" dependencies = [ - "bytes 0.5.6", + "async-stream", + "bytes", "futures-core", - "futures-io", "futures-sink", "log", - "pin-project-lite 0.1.11", - "tokio 0.2.24", + "pin-project-lite", + "tokio", + "tokio-stream", ] [[package]] @@ -3471,9 +2700,9 @@ dependencies = [ [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "e987b6bf443f4b5b3b6f38704195592cca41c5bb7aedd3c3693c7081f8289860" [[package]] name = "tracing" @@ -3482,8 +2711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f47026cdc4080c07e49b37087de021820269d996f581aac150ef9e5583eefe3" dependencies = [ "cfg-if 1.0.0", - "log", - "pin-project-lite 0.2.4", + "pin-project-lite", "tracing-core", ] @@ -3512,19 +2740,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" -[[package]] -name = "typed-headers" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3179a61e9eccceead5f1574fd173cf2e162ac42638b9bf214c6ad0baf7efa24a" -dependencies = [ - "base64 0.11.0", - "bytes 0.5.6", - "chrono", - "http 0.2.3", - "mime", -] - [[package]] name = "typenum" version = "1.12.0" @@ -3555,12 +2770,6 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" -[[package]] -name = "unicode-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" - [[package]] name = "unicode-xid" version = "0.2.1" @@ -3705,21 +2914,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" dependencies = [ "same-file", - "winapi 0.3.9", + "winapi", "winapi-util", ] -[[package]] -name = "want" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" -dependencies = [ - "futures 0.1.30", - "log", - "try-lock", -] - [[package]] name = "want" version = "0.3.0" @@ -3825,12 +3023,6 @@ dependencies = [ "webpki", ] -[[package]] -name = "winapi" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" - [[package]] name = "winapi" version = "0.3.9" @@ -3841,12 +3033,6 @@ dependencies = [ "winapi-x86_64-pc-windows-gnu", ] -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" - [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" @@ -3859,7 +3045,7 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "winapi 0.3.9", + "winapi", ] [[package]] @@ -3868,16 +3054,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "ws2_32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" -dependencies = [ - "winapi 0.2.8", - "winapi-build", -] - [[package]] name = "xattr" version = "0.2.2" diff --git a/Cargo.toml b/Cargo.toml index cba117d5..a7ef8ed4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,49 +15,35 @@ edition = "2018" name = "librespot" path = "src/lib.rs" -[[bin]] -name = "librespot" -path = "src/main.rs" -doc = false +# [[bin]] +# name = "librespot" +# path = "src/main.rs" +# doc = false [dependencies.librespot-audio] path = "audio" version = "0.1.3" -[dependencies.librespot-connect] -path = "connect" -version = "0.1.3" + +# [dependencies.librespot-connect] +# path = "connect" +# version = "0.1.3" + [dependencies.librespot-core] path = "core" version = "0.1.3" + [dependencies.librespot-metadata] path = "metadata" version = "0.1.3" + [dependencies.librespot-playback] path = "playback" version = "0.1.3" + [dependencies.librespot-protocol] path = "protocol" version = "0.1.3" -[dependencies] -base64 = "0.13" -env_logger = {version = "0.8", default-features = false, features = ["termcolor","humantime","atty"]} -futures = "0.1" -getopts = "0.2" -log = "0.4" -num-bigint = "0.3" -protobuf = "~2.14.0" -rand = "0.7" -rpassword = "5.0" -# tokio = "0.1" -tokio = { version = "0.2", features = ["rt-core"] } -tokio-io = "0.1" -tokio-process = "0.2" -tokio-signal = "0.2" -url = "1.7" -sha-1 = "0.8" -hex = "0.4" - [features] alsa-backend = ["librespot-playback/alsa-backend"] portaudio-backend = ["librespot-playback/portaudio-backend"] @@ -70,7 +56,7 @@ gstreamer-backend = ["librespot-playback/gstreamer-backend"] with-tremor = ["librespot-audio/with-tremor"] with-vorbis = ["librespot-audio/with-vorbis"] -with-dns-sd = ["librespot-connect/with-dns-sd"] +# with-dns-sd = ["librespot-connect/with-dns-sd"] default = ["librespot-playback/rodio-backend"] diff --git a/audio/Cargo.toml b/audio/Cargo.toml index 5f6a9426..5e950cdc 100644 --- a/audio/Cargo.toml +++ b/audio/Cargo.toml @@ -11,17 +11,17 @@ path = "../core" version = "0.1.3" [dependencies] +aes-ctr = "0.6" bit-set = "0.5" -byteorder = "1.3" -bytes = "0.4" +byteorder = "1.4" +bytes = "1.0" futures = "0.3" -tokio = { version = "0.2", features = ["full"] } # Temp "rt-core", "sync" -lewton = "0.9" +lewton = "0.10" log = "0.4" num-bigint = "0.3" num-traits = "0.2" +pin-project-lite = "0.2.4" tempfile = "3.1" -aes-ctr = "0.3" librespot-tremor = { version = "0.1.0", optional = true } vorbis = { version ="0.0.14", optional = true } diff --git a/audio/src/decrypt.rs b/audio/src/decrypt.rs index 818eb34e..616ef4f6 100644 --- a/audio/src/decrypt.rs +++ b/audio/src/decrypt.rs @@ -1,7 +1,7 @@ use std::io; -use aes_ctr::stream_cipher::generic_array::GenericArray; -use aes_ctr::stream_cipher::{NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek}; +use aes_ctr::cipher::generic_array::GenericArray; +use aes_ctr::cipher::{NewStreamCipher, SyncStreamCipher, SyncStreamCipherSeek}; use aes_ctr::Aes128Ctr; use librespot_core::audio_key::AudioKey; diff --git a/audio/src/fetch.rs b/audio/src/fetch.rs index 5ed4ccd1..51dddc6b 100644 --- a/audio/src/fetch.rs +++ b/audio/src/fetch.rs @@ -1,30 +1,31 @@ use crate::range_set::{Range, RangeSet}; use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; use bytes::Bytes; -use std::cmp::{max, min}; +use futures::{ + channel::{mpsc, oneshot}, + future, +}; +use futures::{Future, Stream, StreamExt, TryFutureExt, TryStreamExt}; + use std::fs; use std::io::{self, Read, Seek, SeekFrom, Write}; use std::sync::{Arc, Condvar, Mutex}; +use std::task::Poll; use std::time::{Duration, Instant}; +use std::{ + cmp::{max, min}, + pin::Pin, + task::Context, +}; use tempfile::NamedTempFile; +use futures::channel::mpsc::unbounded; use librespot_core::channel::{Channel, ChannelData, ChannelError, ChannelHeaders}; use librespot_core::session::Session; use librespot_core::spotify_id::FileId; use std::sync::atomic; use std::sync::atomic::AtomicUsize; -use futures::{ - channel::{mpsc, mpsc::unbounded, oneshot}, - ready, Future, Stream, -}; -use std::{ - pin::Pin, - task::{Context, Poll}, -}; - -use tokio::task; - const MINIMUM_DOWNLOAD_SIZE: usize = 1024 * 16; // The minimum size of a block that is requested from the Spotify servers in one request. // This is the block size that is typically requested while doing a seek() on a file. @@ -95,22 +96,6 @@ pub enum AudioFile { Streaming(AudioFileStreaming), } -pub enum AudioFileOpen { - Cached(Option), - Streaming(AudioFileOpenStreaming), -} - -pub struct AudioFileOpenStreaming { - session: Session, - initial_data_rx: Option, - initial_data_length: Option, - initial_request_sent_time: Instant, - headers: ChannelHeaders, - file_id: FileId, - complete_tx: Option>, - streaming_data_rate: usize, -} - enum StreamLoaderCommand { Fetch(Range), // signal the stream loader to fetch a range of the file RandomAccessMode(), // optimise download strategy for random access @@ -127,45 +112,36 @@ pub struct StreamLoaderController { impl StreamLoaderController { pub fn len(&self) -> usize { - return self.file_size; + self.file_size + } + + pub fn is_empty(&self) -> bool { + self.file_size == 0 } pub fn range_available(&self, range: Range) -> bool { if let Some(ref shared) = self.stream_shared { let download_status = shared.download_status.lock().unwrap(); - if range.length + range.length <= download_status .downloaded .contained_length_from_value(range.start) - { - return true; - } else { - return false; - } } else { - if range.length <= self.len() - range.start { - return true; - } else { - return false; - } + range.length <= self.len() - range.start } } pub fn range_to_end_available(&self) -> bool { - if let Some(ref shared) = self.stream_shared { + self.stream_shared.as_ref().map_or(true, |shared| { let read_position = shared.read_position.load(atomic::Ordering::Relaxed); self.range_available(Range::new(read_position, self.len() - read_position)) - } else { - true - } + }) } pub fn ping_time_ms(&self) -> usize { - if let Some(ref shared) = self.stream_shared { - return shared.ping_time_ms.load(atomic::Ordering::Relaxed); - } else { - return 0; - } + self.stream_shared.as_ref().map_or(0, |shared| { + shared.ping_time_ms.load(atomic::Ordering::Relaxed) + }) } fn send_stream_loader_command(&mut self, command: StreamLoaderCommand) { @@ -223,27 +199,23 @@ impl StreamLoaderController { } pub fn fetch_next(&mut self, length: usize) { - let range: Range = if let Some(ref shared) = self.stream_shared { - Range { + if let Some(ref shared) = self.stream_shared { + let range = Range { start: shared.read_position.load(atomic::Ordering::Relaxed), length: length, - } - } else { - return; - }; - self.fetch(range); + }; + self.fetch(range) + } } pub fn fetch_next_blocking(&mut self, length: usize) { - let range: Range = if let Some(ref shared) = self.stream_shared { - Range { + if let Some(ref shared) = self.stream_shared { + let range = Range { start: shared.read_position.load(atomic::Ordering::Relaxed), length: length, - } - } else { - return; - }; - self.fetch_blocking(range); + }; + self.fetch_blocking(range); + } } pub fn set_random_access_mode(&mut self) { @@ -295,110 +267,16 @@ struct AudioFileShared { read_position: AtomicUsize, } -impl AudioFileOpenStreaming { - fn finish(&mut self, size: usize) -> AudioFileStreaming { - let shared = Arc::new(AudioFileShared { - file_id: self.file_id, - file_size: size, - stream_data_rate: self.streaming_data_rate, - cond: Condvar::new(), - download_status: Mutex::new(AudioFileDownloadStatus { - requested: RangeSet::new(), - downloaded: RangeSet::new(), - }), - download_strategy: Mutex::new(DownloadStrategy::RandomAccess()), // start with random access mode until someone tells us otherwise - number_of_open_requests: AtomicUsize::new(0), - ping_time_ms: AtomicUsize::new(0), - read_position: AtomicUsize::new(0), - }); - - let mut write_file = NamedTempFile::new().unwrap(); - write_file.as_file().set_len(size as u64).unwrap(); - write_file.seek(SeekFrom::Start(0)).unwrap(); - - let read_file = write_file.reopen().unwrap(); - - let initial_data_rx = self.initial_data_rx.take().unwrap(); - let initial_data_length = self.initial_data_length.take().unwrap(); - let complete_tx = self.complete_tx.take().unwrap(); - //let (seek_tx, seek_rx) = mpsc::unbounded(); - let (stream_loader_command_tx, stream_loader_command_rx) = - mpsc::unbounded::(); - - let fetcher = AudioFileFetch::new( - self.session.clone(), - shared.clone(), - initial_data_rx, - self.initial_request_sent_time, - initial_data_length, - write_file, - stream_loader_command_rx, - complete_tx, - ); - self.session.spawn(fetcher); - // tokio::spawn(move |_| fetcher); - - AudioFileStreaming { - read_file: read_file, - - position: 0, - //seek: seek_tx, - stream_loader_command_tx: stream_loader_command_tx, - - shared: shared, - } - } -} - -impl Future for AudioFileOpen { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - match *self { - AudioFileOpen::Streaming(ref mut open) => { - let file = ready!(open.poll()); - Poll::Ready(Ok(AudioFile::Streaming(file))) - } - AudioFileOpen::Cached(ref mut file) => { - let file = file.take().unwrap(); - Poll::Ready(Ok(AudioFile::Cached(file))) - } - } - } -} - -impl Future for AudioFileOpenStreaming { - type Output = Result; - - fn poll( - self: Pin<&mut Self>, - cx: &mut Context, - ) -> Poll> { - loop { - let (id, data) = ready!(self.headers.poll()).unwrap(); - - if id == 0x3 { - let size = BigEndian::read_u32(&data) as usize * 4; - let file = self.finish(size); - - return Poll::Ready(Ok(file)); - } - } - } -} - impl AudioFile { - pub fn open( + pub async fn open( session: &Session, file_id: FileId, bytes_per_second: usize, play_from_beginning: bool, - ) -> AudioFileOpen { - let cache = session.cache().cloned(); - - if let Some(file) = cache.as_ref().and_then(|cache| cache.file(file_id)) { + ) -> Result { + if let Some(file) = session.cache().and_then(|cache| cache.file(file_id)) { debug!("File {} already in cache", file_id); - return AudioFileOpen::Cached(Some(file)); + return Ok(AudioFile::Cached(file)); } debug!("Downloading file {}", file_id); @@ -420,56 +298,112 @@ impl AudioFile { } let (headers, data) = request_range(session, file_id, 0, initial_data_length).split(); - let open = AudioFileOpenStreaming { - session: session.clone(), - file_id: file_id, - - headers: headers, - initial_data_rx: Some(data), - initial_data_length: Some(initial_data_length), - initial_request_sent_time: Instant::now(), - - complete_tx: Some(complete_tx), - streaming_data_rate: bytes_per_second, - }; - - let session_ = session.clone(); - session.spawn( - complete_rx - .map(move |mut file| { - if let Some(cache) = session_.cache() { - cache.save_file(file_id, &mut file); - debug!("File {} complete, saving to cache", file_id); - } else { - debug!("File {} complete", file_id); - } - }) - .or_else(|oneshot::Canceled| Ok(())), + let streaming = AudioFileStreaming::open( + session.clone(), + data, + initial_data_length, + Instant::now(), + headers, + file_id, + complete_tx, + bytes_per_second, ); - return AudioFileOpen::Streaming(open); + let session_ = session.clone(); + session.spawn(complete_rx.map_ok(move |mut file| { + if let Some(cache) = session_.cache() { + cache.save_file(file_id, &mut file); + debug!("File {} complete, saving to cache", file_id); + } else { + debug!("File {} complete", file_id); + } + })); + + Ok(AudioFile::Streaming(streaming.await?)) } pub fn get_stream_loader_controller(&self) -> StreamLoaderController { match self { - AudioFile::Streaming(ref stream) => { - return StreamLoaderController { - channel_tx: Some(stream.stream_loader_command_tx.clone()), - stream_shared: Some(stream.shared.clone()), - file_size: stream.shared.file_size, - }; - } - AudioFile::Cached(ref file) => { - return StreamLoaderController { - channel_tx: None, - stream_shared: None, - file_size: file.metadata().unwrap().len() as usize, - }; - } + AudioFile::Streaming(ref stream) => StreamLoaderController { + channel_tx: Some(stream.stream_loader_command_tx.clone()), + stream_shared: Some(stream.shared.clone()), + file_size: stream.shared.file_size, + }, + AudioFile::Cached(ref file) => StreamLoaderController { + channel_tx: None, + stream_shared: None, + file_size: file.metadata().unwrap().len() as usize, + }, } } } +impl AudioFileStreaming { + pub async fn open( + session: Session, + initial_data_rx: ChannelData, + initial_data_length: usize, + initial_request_sent_time: Instant, + headers: ChannelHeaders, + file_id: FileId, + complete_tx: oneshot::Sender, + streaming_data_rate: usize, + ) -> Result { + let (_, data) = headers + .try_filter(|(id, _)| future::ready(*id == 0x3)) + .next() + .await + .unwrap()?; + + let size = BigEndian::read_u32(&data) as usize * 4; + + let shared = Arc::new(AudioFileShared { + file_id: file_id, + file_size: size, + stream_data_rate: streaming_data_rate, + cond: Condvar::new(), + download_status: Mutex::new(AudioFileDownloadStatus { + requested: RangeSet::new(), + downloaded: RangeSet::new(), + }), + download_strategy: Mutex::new(DownloadStrategy::RandomAccess()), // start with random access mode until someone tells us otherwise + number_of_open_requests: AtomicUsize::new(0), + ping_time_ms: AtomicUsize::new(0), + read_position: AtomicUsize::new(0), + }); + + let mut write_file = NamedTempFile::new().unwrap(); + write_file.as_file().set_len(size as u64).unwrap(); + write_file.seek(SeekFrom::Start(0)).unwrap(); + + let read_file = write_file.reopen().unwrap(); + + //let (seek_tx, seek_rx) = mpsc::unbounded(); + let (stream_loader_command_tx, stream_loader_command_rx) = + mpsc::unbounded::(); + + let fetcher = AudioFileFetch::new( + session.clone(), + shared.clone(), + initial_data_rx, + initial_request_sent_time, + initial_data_length, + write_file, + stream_loader_command_rx, + complete_tx, + ); + + session.spawn(fetcher); + Ok(AudioFileStreaming { + read_file: read_file, + position: 0, + //seek: seek_tx, + stream_loader_command_tx: stream_loader_command_tx, + shared: shared, + }) + } +} + fn request_range(session: &Session, file: FileId, offset: usize, length: usize) -> Channel { assert!( offset % 4 == 0, @@ -511,143 +445,267 @@ enum ReceivedData { Data(PartialFileData), } -struct AudioFileFetchDataReceiver { +async fn audio_file_fetch_receive_data( shared: Arc, file_data_tx: mpsc::UnboundedSender, data_rx: ChannelData, initial_data_offset: usize, initial_request_length: usize, - data_offset: usize, - request_length: usize, - request_sent_time: Option, - measure_ping_time: bool, -} + request_sent_time: Instant, +) { + let mut data_offset = initial_data_offset; + let mut request_length = initial_request_length; + let mut measure_ping_time = shared + .number_of_open_requests + .load(atomic::Ordering::SeqCst) + == 0; -impl AudioFileFetchDataReceiver { - fn new( - shared: Arc, - file_data_tx: mpsc::UnboundedSender, - data_rx: ChannelData, - data_offset: usize, - request_length: usize, - request_sent_time: Instant, - ) -> AudioFileFetchDataReceiver { - let measure_ping_time = shared - .number_of_open_requests - .load(atomic::Ordering::SeqCst) - == 0; + shared + .number_of_open_requests + .fetch_add(1, atomic::Ordering::SeqCst); - shared - .number_of_open_requests - .fetch_add(1, atomic::Ordering::SeqCst); + enum TryFoldErr { + ChannelError, + FinishEarly, + } - AudioFileFetchDataReceiver { - shared: shared, - data_rx: data_rx, - file_data_tx: file_data_tx, - initial_data_offset: data_offset, - initial_request_length: request_length, - data_offset: data_offset, - request_length: request_length, - request_sent_time: Some(request_sent_time), - measure_ping_time: measure_ping_time, - } + let result = data_rx + .map_err(|_| TryFoldErr::ChannelError) + .try_for_each(|data| { + if measure_ping_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 + { + duration_ms = (MAXIMUM_ASSUMED_PING_TIME_SECONDS * 1000.0) as u64; + } else { + duration_ms = duration.as_millis() as u64; + } + let _ = file_data_tx + .unbounded_send(ReceivedData::ResponseTimeMs(duration_ms as usize)); + measure_ping_time = false; + } + let data_size = data.len(); + let _ = file_data_tx + .unbounded_send(ReceivedData::Data(PartialFileData { + offset: data_offset, + data: data, + })); + data_offset += data_size; + if request_length < data_size { + warn!("Data receiver for range {} (+{}) received more data from server than requested.", initial_data_offset, initial_request_length); + request_length = 0; + } else { + request_length -= data_size; + } + + future::ready(if request_length == 0 { + Err(TryFoldErr::FinishEarly) + } else { + Ok(()) + }) + }) + .await; + + if request_length > 0 { + let missing_range = Range::new(data_offset, request_length); + + let mut download_status = shared.download_status.lock().unwrap(); + download_status.requested.subtract_range(&missing_range); + shared.cond.notify_all(); + } + + shared + .number_of_open_requests + .fetch_sub(1, atomic::Ordering::SeqCst); + + if let Err(TryFoldErr::ChannelError) = result { + warn!( + "Error from channel for data receiver for range {} (+{}).", + initial_data_offset, initial_request_length + ); + } else if request_length > 0 { + warn!( + "Data receiver for range {} (+{}) received less data from server than requested.", + initial_data_offset, initial_request_length + ); } } +/* +async fn audio_file_fetch( + session: Session, + shared: Arc, + initial_data_rx: ChannelData, + initial_request_sent_time: Instant, + initial_data_length: usize, -impl AudioFileFetchDataReceiver { - fn finish(&mut self) { - if self.request_length > 0 { - let missing_range = Range::new(self.data_offset, self.request_length); + output: NamedTempFile, + stream_loader_command_rx: mpsc::UnboundedReceiver, + complete_tx: oneshot::Sender, +) { + let (file_data_tx, file_data_rx) = unbounded::(); - let mut download_status = self.shared.download_status.lock().unwrap(); - download_status.requested.subtract_range(&missing_range); - self.shared.cond.notify_all(); - } + let requested_range = Range::new(0, initial_data_length); + let mut download_status = shared.download_status.lock().unwrap(); + download_status.requested.add_range(&requested_range); - self.shared - .number_of_open_requests - .fetch_sub(1, atomic::Ordering::SeqCst); - } -} + session.spawn(audio_file_fetch_receive_data( + shared.clone(), + file_data_tx.clone(), + initial_data_rx, + 0, + initial_data_length, + initial_request_sent_time, + )); -impl Future for AudioFileFetchDataReceiver { - type Output = (); + let mut network_response_times_ms: Vec::new(); - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { - loop { - match self.data_rx.poll() { - Poll::Ready(Some(data)) => { - if self.measure_ping_time { - 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 - { - duration_ms = (MAXIMUM_ASSUMED_PING_TIME_SECONDS * 1000.0) as u64; - } else { - duration_ms = duration.as_millis() as u64; - } - let _ = self - .file_data_tx - .unbounded_send(ReceivedData::ResponseTimeMs(duration_ms as usize)); - self.measure_ping_time = false; - } - } - let data_size = data.len(); - let _ = self - .file_data_tx - .unbounded_send(ReceivedData::Data(PartialFileData { - offset: self.data_offset, - data: data, - })); - self.data_offset += data_size; - if self.request_length < data_size { - warn!("Data receiver for range {} (+{}) received more data from server than requested.", self.initial_data_offset, self.initial_request_length); - self.request_length = 0; - } else { - self.request_length -= data_size; - } - if self.request_length == 0 { - self.finish(); - return Poll::Ready(()); - } + let f1 = file_data_rx.map(|x| Ok::<_, ()>(x)).try_for_each(|x| { + match x { + ReceivedData::ResponseTimeMs(response_time_ms) => { + trace!("Ping time estimated as: {} ms.", response_time_ms); + + // record the response time + network_response_times_ms.push(response_time_ms); + + // prune old response times. Keep at most three. + while network_response_times_ms.len() > 3 { + network_response_times_ms.remove(0); } - Poll::Ready(None) => { - if self.request_length > 0 { - warn!("Data receiver for range {} (+{}) received less data from server than requested.", self.initial_data_offset, self.initial_request_length); + + // stats::median is experimental. So we calculate the median of up to three ourselves. + let ping_time_ms: usize = match network_response_times_ms.len() { + 1 => network_response_times_ms[0] as usize, + 2 => { + ((network_response_times_ms[0] + network_response_times_ms[1]) / 2) as usize } - self.finish(); - return Poll::Ready(()); + 3 => { + let mut times = network_response_times_ms.clone(); + times.sort(); + times[1] + } + _ => unreachable!(), + }; + + // store our new estimate for everyone to see + shared + .ping_time_ms + .store(ping_time_ms, atomic::Ordering::Relaxed); + } + ReceivedData::Data(data) => { + output + .as_mut() + .unwrap() + .seek(SeekFrom::Start(data.offset as u64)) + .unwrap(); + output + .as_mut() + .unwrap() + .write_all(data.data.as_ref()) + .unwrap(); + + let mut full = false; + + { + let mut download_status = shared.download_status.lock().unwrap(); + + let received_range = Range::new(data.offset, data.data.len()); + download_status.downloaded.add_range(&received_range); + shared.cond.notify_all(); + + if download_status.downloaded.contained_length_from_value(0) + >= shared.file_size + { + full = true; + } + + drop(download_status); } - Poll::Pending => return Poll::Pending, - Err(ChannelError) => { - warn!( - "Error from channel for data receiver for range {} (+{}).", - self.initial_data_offset, self.initial_request_length - ); + + if full { self.finish(); - return Poll::Ready(()); + return future::ready(Err(())); } } } + future::ready(Ok(())) + }); + + let f2 = stream_loader_command_rx.map(Ok::<_, ()>).try_for_each(|x| { + match cmd { + StreamLoaderCommand::Fetch(request) => { + self.download_range(request.start, request.length); + } + StreamLoaderCommand::RandomAccessMode() => { + *(shared.download_strategy.lock().unwrap()) = DownloadStrategy::RandomAccess(); + } + StreamLoaderCommand::StreamMode() => { + *(shared.download_strategy.lock().unwrap()) = DownloadStrategy::Streaming(); + } + StreamLoaderCommand::Close() => return future::ready(Err(())), + } + Ok(()) + }); + + let f3 = future::poll_fn(|_| { + if let DownloadStrategy::Streaming() = self.get_download_strategy() { + let number_of_open_requests = 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 = shared.download_status.lock().unwrap(); + download_status + .requested + .minus(&download_status.downloaded) + .len() + }; + + let ping_time_seconds = + 0.001 * shared.ping_time_ms.load(atomic::Ordering::Relaxed) as f64; + let download_rate = session.channel().get_download_rate_estimate(); + + let desired_pending_bytes = max( + (PREFETCH_THRESHOLD_FACTOR * ping_time_seconds * shared.stream_data_rate as f64) + as usize, + (FAST_PREFETCH_THRESHOLD_FACTOR * ping_time_seconds * download_rate as f64) + as usize, + ); + + if bytes_pending < desired_pending_bytes { + self.pre_fetch_more_data( + desired_pending_bytes - bytes_pending, + max_requests_to_send, + ); + } + } + } + Poll::Pending + }); + future::select_all(vec![f1, f2, f3]).await +}*/ + +pin_project! { + struct AudioFileFetch { + session: Session, + shared: Arc, + output: Option, + + file_data_tx: mpsc::UnboundedSender, + #[pin] + file_data_rx: mpsc::UnboundedReceiver, + + #[pin] + stream_loader_command_rx: mpsc::UnboundedReceiver, + complete_tx: Option>, + network_response_times_ms: Vec, } } -struct AudioFileFetch { - session: Session, - shared: Arc, - output: Option, - - file_data_tx: mpsc::UnboundedSender, - file_data_rx: mpsc::UnboundedReceiver, - - stream_loader_command_rx: mpsc::UnboundedReceiver, - complete_tx: Option>, - network_response_times_ms: Vec, -} - impl AudioFileFetch { fn new( session: Session, @@ -668,17 +726,14 @@ impl AudioFileFetch { download_status.requested.add_range(&requested_range); } - let initial_data_receiver = AudioFileFetchDataReceiver::new( + session.spawn(audio_file_fetch_receive_data( shared.clone(), file_data_tx.clone(), initial_data_rx, 0, initial_data_length, initial_request_sent_time, - ); - - session.spawn(initial_data_receiver); - // tokio::spawn(move |_| initial_data_receiver); + )); AudioFileFetch { session: session, @@ -708,7 +763,7 @@ impl AudioFileFetch { return; } - if length <= 0 { + if length == 0 { return; } @@ -744,17 +799,14 @@ impl AudioFileFetch { download_status.requested.add_range(range); - let receiver = AudioFileFetchDataReceiver::new( + self.session.spawn(audio_file_fetch_receive_data( self.shared.clone(), self.file_data_tx.clone(), data, range.start, range.length, Instant::now(), - ); - - self.session.spawn(receiver); - // tokio::spawn(move |_| receiver); + )); } } @@ -802,9 +854,9 @@ impl AudioFileFetch { } } - fn poll_file_data_rx(&mut self) -> Poll<()> { + fn poll_file_data_rx(&mut self, cx: &mut Context<'_>) -> Poll<()> { loop { - match self.file_data_rx.poll() { + match Pin::new(&mut self.file_data_rx).poll_next(cx) { Poll::Ready(None) => return Poll::Ready(()), Poll::Ready(Some(ReceivedData::ResponseTimeMs(response_time_ms))) => { trace!("Ping time estimated as: {} ms.", response_time_ms); @@ -827,7 +879,7 @@ impl AudioFileFetch { } 3 => { let mut times = self.network_response_times_ms.clone(); - times.sort(); + times.sort_unstable(); times[1] } _ => unreachable!(), @@ -874,30 +926,29 @@ impl AudioFileFetch { } } Poll::Pending => return Poll::Pending, - // Err(()) => unreachable!(), } } } - fn poll_stream_loader_command_rx(&mut self) -> Poll<()> { + fn poll_stream_loader_command_rx(&mut self, cx: &mut Context<'_>) -> Poll<()> { loop { - match self.stream_loader_command_rx.poll() { + match Pin::new(&mut self.stream_loader_command_rx).poll_next(cx) { Poll::Ready(None) => return Poll::Ready(()), - - Poll::Ready(Some(StreamLoaderCommand::Fetch(request))) => { - self.download_range(request.start, request.length); - } - Poll::Ready(Some(StreamLoaderCommand::RandomAccessMode())) => { - *(self.shared.download_strategy.lock().unwrap()) = - DownloadStrategy::RandomAccess(); - } - Poll::Ready(Some(StreamLoaderCommand::StreamMode())) => { - *(self.shared.download_strategy.lock().unwrap()) = - DownloadStrategy::Streaming(); - } - Poll::Ready(Some(StreamLoaderCommand::Close())) => return Poll::Ready(()), + Poll::Ready(Some(cmd)) => match cmd { + StreamLoaderCommand::Fetch(request) => { + self.download_range(request.start, request.length); + } + StreamLoaderCommand::RandomAccessMode() => { + *(self.shared.download_strategy.lock().unwrap()) = + DownloadStrategy::RandomAccess(); + } + StreamLoaderCommand::StreamMode() => { + *(self.shared.download_strategy.lock().unwrap()) = + DownloadStrategy::Streaming(); + } + StreamLoaderCommand::Close() => return Poll::Ready(()), + }, Poll::Pending => return Poll::Pending, - // Err(()) => unreachable!(), } } } @@ -910,21 +961,16 @@ impl AudioFileFetch { let _ = complete_tx.send(output); } } - impl Future for AudioFileFetch { type Output = (); - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<()> { - match self.poll_stream_loader_command_rx() { - Poll::Pending => (), - Poll::Ready(_) => return Poll::Ready(()), - // Err(()) => unreachable!(), + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { + if let Poll::Ready(()) = self.poll_stream_loader_command_rx(cx) { + return Poll::Ready(()); } - match self.poll_file_data_rx() { - Poll::Pending => (), - Poll::Ready(_) => return Poll::Ready(()), - // Err(()) => unreachable!(), + if let Poll::Ready(()) = self.poll_file_data_rx(cx) { + return Poll::Ready(()); } if let DownloadStrategy::Streaming() = self.get_download_strategy() { @@ -964,8 +1010,7 @@ impl Future for AudioFileFetch { } } } - - return Poll::Pending; + Poll::Pending } } @@ -1005,9 +1050,9 @@ impl Read for AudioFileStreaming { ranges_to_request.subtract_range_set(&download_status.downloaded); ranges_to_request.subtract_range_set(&download_status.requested); - for range in ranges_to_request.iter() { + for &range in ranges_to_request.iter() { self.stream_loader_command_tx - .unbounded_send(StreamLoaderCommand::Fetch(range.clone())) + .unbounded_send(StreamLoaderCommand::Fetch(range)) .unwrap(); } @@ -1054,7 +1099,7 @@ impl Read for AudioFileStreaming { .read_position .store(self.position as usize, atomic::Ordering::Relaxed); - return Ok(read_len); + Ok(read_len) } } diff --git a/audio/src/lib.rs b/audio/src/lib.rs index 3e13c079..1be1ba88 100644 --- a/audio/src/lib.rs +++ b/audio/src/lib.rs @@ -1,12 +1,15 @@ -#[macro_use] -extern crate futures; +#![allow(clippy::unused_io_amount)] + #[macro_use] extern crate log; +#[macro_use] +extern crate pin_project_lite; extern crate aes_ctr; extern crate bit_set; extern crate byteorder; extern crate bytes; +extern crate futures; extern crate num_bigint; extern crate num_traits; extern crate tempfile; @@ -24,7 +27,7 @@ mod libvorbis_decoder; mod range_set; pub use decrypt::AudioDecrypt; -pub use fetch::{AudioFile, AudioFileOpen, StreamLoaderController}; +pub use fetch::{AudioFile, StreamLoaderController}; pub use fetch::{ READ_AHEAD_BEFORE_PLAYBACK_ROUNDTRIPS, READ_AHEAD_BEFORE_PLAYBACK_SECONDS, READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS, diff --git a/audio/src/range_set.rs b/audio/src/range_set.rs index 44955388..8712dfd4 100644 --- a/audio/src/range_set.rs +++ b/audio/src/range_set.rs @@ -54,11 +54,7 @@ impl RangeSet { } pub fn len(&self) -> usize { - let mut result = 0; - for range in self.ranges.iter() { - result += range.length; - } - return result; + self.ranges.iter().map(|r| r.length).fold(0, std::ops::Add::add) } pub fn get_range(&self, index: usize) -> Range { @@ -98,12 +94,12 @@ impl RangeSet { return false; } } - return true; + true } pub fn add_range(&mut self, range: &Range) { - if range.length <= 0 { - // the interval is empty or invalid -> nothing to do. + if range.length == 0 { + // the interval is empty -> nothing to do. return; } @@ -111,7 +107,7 @@ impl RangeSet { // the new range is clear of any ranges we already iterated over. if range.end() < self.ranges[index].start { // 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()); + self.ranges.insert(index, *range); return; } else if range.start <= self.ranges[index].end() && self.ranges[index].start <= range.end() @@ -119,7 +115,7 @@ impl RangeSet { // 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. - let mut new_range = range.clone(); + let mut new_range = *range; while index < self.ranges.len() && self.ranges[index].start <= new_range.end() { let new_end = max(new_range.end(), self.ranges[index].end()); @@ -134,7 +130,7 @@ impl RangeSet { } // the new range is after everything else -> just add it - self.ranges.push(range.clone()); + self.ranges.push(*range); } #[allow(dead_code)] @@ -152,7 +148,7 @@ impl RangeSet { } pub fn subtract_range(&mut self, range: &Range) { - if range.length <= 0 { + if range.length == 0 { return; } diff --git a/core/Cargo.toml b/core/Cargo.toml index 077efe0a..c092c04d 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -13,37 +13,36 @@ path = "../protocol" version = "0.1.3" [dependencies] +aes = "0.6" base64 = "0.13" -thiserror = "1.0" -byteorder = "1.3" -bytes = "0.5" -error-chain = { version = "0.12", default_features = false } -futures = {version = "0.3",features =["unstable","bilock"]} +byteorder = "1.4" +bytes = "1.0" +futures = { version = "0.3", features = ["bilock", "unstable"] } +hmac = "0.7" httparse = "1.3" -hyper = "0.13" -hyper-proxy = { version = "0.6", default_features = false } -lazy_static = "1.3" +hyper = { version = "0.14", features = ["client", "tcp", "http1", "http2"] } log = "0.4" num-bigint = "0.3" num-integer = "0.1" num-traits = "0.2" +once_cell = "1.5.2" +pbkdf2 = "0.3" +pin-project-lite = "0.2.4" protobuf = "~2.14.0" rand = "0.7" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" +sha-1 = "~0.8" shannon = "0.2.0" -tokio = {version = "0.2", features = ["full","io-util","tcp"]} # io-util -tokio-util = {version = "0.3", features = ["compat","codec"]} -# tokio-codec = "0.1" -# tokio-io = "0.1" +tokio = { version = "1.0", features = ["io-util", "rt-multi-thread"] } +tokio-util = { version = "0.6", features = ["codec"] } url = "1.7" uuid = { version = "0.8", features = ["v4"] } -sha-1 = "0.8" -hmac = "0.7" -pbkdf2 = "0.3" -aes = "0.3" [build-dependencies] rand = "0.7" vergen = "3.0.4" + +[dev-dependencies] +tokio = {version = "1.0", features = ["macros"] } \ No newline at end of file diff --git a/core/src/apresolve.rs b/core/src/apresolve.rs index cf301788..07c2958f 100644 --- a/core/src/apresolve.rs +++ b/core/src/apresolve.rs @@ -1,83 +1,69 @@ const AP_FALLBACK: &'static str = "ap.spotify.com:443"; const APRESOLVE_ENDPOINT: &'static str = "http://apresolve.spotify.com/"; -use hyper::client::HttpConnector; -use hyper::{self, Body, Client, Request, Uri}; -use hyper_proxy::{Intercept, Proxy, ProxyConnector}; -use serde_json; -use std::error; -use std::str::FromStr; +use hyper::{Body, Client, Method, Request, Uri}; +use std::error::Error; use url::Url; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct APResolveData { ap_list: Vec, } -type Result = std::result::Result>; -async fn apresolve(proxy: &Option, ap_port: &Option) -> Result { - let url = Uri::from_str(APRESOLVE_ENDPOINT)?; //.expect("invalid AP resolve URL"); - let use_proxy = proxy.is_some(); +async fn apresolve(proxy: &Option, ap_port: &Option) -> Result> { + let port = ap_port.unwrap_or(443); - let mut req = Request::get(&url).body(Body::empty())?; - let response = match *proxy { - Some(ref val) => { - let proxy_url = Uri::from_str(val.as_str()).expect("invalid http proxy"); - let proxy = Proxy::new(Intercept::All, proxy_url); + let req = Request::builder() + .method(Method::GET) + .uri( + APRESOLVE_ENDPOINT + .parse::() + .expect("invalid AP resolve URL"), + ) + .body(Body::empty())?; + + let client = if proxy.is_some() { + todo!("proxies not yet supported") + /*let proxy = { + let proxy_url = val.as_str().parse().expect("invalid http proxy"); + let mut proxy = Proxy::new(Intercept::All, proxy_url); let connector = HttpConnector::new(); let proxy_connector = ProxyConnector::from_proxy_unsecured(connector, proxy); - if let Some(headers) = proxy_connector.http_headers(&url) { - req.headers_mut().extend(headers.clone().into_iter()); - } - let client = Client::builder().build(proxy_connector); - client.request(req) - } - _ => { - let client = Client::new(); - client.request(req) - } - } - .await?; + proxy_connector + }; - let body = hyper::body::to_bytes(response.into_body()).await?; - let body = String::from_utf8(body.to_vec())?; - let data = serde_json::from_str::(&body)?; - - let ap = { - let mut aps = data.ap_list.iter().filter(|ap| { - if let Some(p) = ap_port { - Uri::from_str(ap) - .ok() - .map_or(false, |uri| uri.port_u16().map_or(false, |port| &port == p)) - } else if use_proxy { - // It is unlikely that the proxy will accept CONNECT on anything other than 443. - Uri::from_str(ap).ok().map_or(false, |uri| { - uri.port_u16().map_or(false, |port| port == 443) - }) - } else { - true - } - }); - - let ap = aps.next().ok_or("empty AP List")?; - Ok(ap.clone()) + if let Some(headers) = proxy.http_headers(&APRESOLVE_ENDPOINT.parse().unwrap()) { + req.headers_mut().extend(headers.clone()); + }; + Client::builder().build(proxy)*/ + } else { + Client::new() }; - ap + let response = client.request(req).await?; + + let body = hyper::body::to_bytes(response.into_body()).await?; + let data: APResolveData = serde_json::from_slice(body.as_ref())?; + + let ap = if ap_port.is_some() || proxy.is_some() { + data.ap_list.into_iter().find_map(|ap| { + if ap.parse::().ok()?.port()? == port { + Some(ap) + } else { + None + } + }) + } else { + data.ap_list.into_iter().next() + } + .ok_or("empty AP List")?; + Ok(ap) } -pub(crate) async fn apresolve_or_fallback( - proxy: &Option, - ap_port: &Option, -) -> Result { - // match apresolve.await { - // Ok(ap) - // } - let ap = apresolve(proxy, ap_port).await.or_else(|e| { - warn!("Failed to resolve Access Point: {:?}", e); +pub async fn apresolve_or_fallback(proxy: &Option, ap_port: &Option) -> String { + apresolve(proxy, ap_port).await.unwrap_or_else(|e| { + warn!("Failed to resolve Access Point: {}", e); warn!("Using fallback \"{}\"", AP_FALLBACK); - Ok(AP_FALLBACK.into()) - }); - - ap + AP_FALLBACK.into() + }) } diff --git a/core/src/audio_key.rs b/core/src/audio_key.rs index 976361d0..b9f0c232 100644 --- a/core/src/audio_key.rs +++ b/core/src/audio_key.rs @@ -3,7 +3,6 @@ use bytes::Bytes; use futures::channel::oneshot; use std::collections::HashMap; use std::io::Write; -use thiserror::Error; use crate::spotify_id::{FileId, SpotifyId}; use crate::util::SeqGenerator; @@ -11,13 +10,8 @@ use crate::util::SeqGenerator; #[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)] pub struct AudioKey(pub [u8; 16]); -#[derive(Error, Debug)] -pub enum AudioKeyError { - #[error("AudioKey sender disconnected")] - Cancelled(#[from] oneshot::Canceled), - #[error("Unknown server response: `{0:?}`")] - UnknownResponse(Vec), -} +#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone)] +pub struct AudioKeyError; component! { AudioKeyManager : AudioKeyManagerInner { @@ -45,11 +39,9 @@ impl AudioKeyManager { data.as_ref()[0], data.as_ref()[1] ); - let _ = sender.send(Err(AudioKeyError::UnknownResponse( - data.as_ref()[..1].to_vec(), - ))); + let _ = sender.send(Err(AudioKeyError)); } - _ => warn!("Unexpected audioKey response: 0x{:x?} {:?}", cmd, data), + _ => (), } } } @@ -64,7 +56,7 @@ impl AudioKeyManager { }); self.send_key_request(seq, track, file); - rx.await? + rx.await.map_err(|_| AudioKeyError)? } fn send_key_request(&self, seq: u32, track: SpotifyId, file: FileId) { diff --git a/core/src/authentication.rs b/core/src/authentication.rs index 36cbd439..dd39fd85 100644 --- a/core/src/authentication.rs +++ b/core/src/authentication.rs @@ -1,11 +1,9 @@ use aes::Aes192; -use base64; +use aes::NewBlockCipher; use byteorder::{BigEndian, ByteOrder}; use hmac::Hmac; 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}; @@ -76,9 +74,9 @@ impl Credentials { // decrypt data using ECB mode without padding let blob = { - use aes::block_cipher_trait::generic_array::typenum::Unsigned; - use aes::block_cipher_trait::generic_array::GenericArray; - use aes::block_cipher_trait::BlockCipher; + use aes::cipher::generic_array::typenum::Unsigned; + use aes::cipher::generic_array::GenericArray; + use aes::cipher::BlockCipher; let mut data = base64::decode(encrypted_blob).unwrap(); let cipher = Aes192::new(GenericArray::from_slice(&key)); diff --git a/core/src/channel.rs b/core/src/channel.rs index f789bfe4..7ada05d5 100644 --- a/core/src/channel.rs +++ b/core/src/channel.rs @@ -1,16 +1,15 @@ use byteorder::{BigEndian, ByteOrder}; use bytes::Bytes; -use std::collections::HashMap; -use std::time::Instant; - -use crate::util::SeqGenerator; - -use futures::{channel::mpsc, lock::BiLock, Stream}; +use futures::{channel::mpsc, lock::BiLock, Stream, StreamExt}; use std::{ + collections::HashMap, pin::Pin, task::{Context, Poll}, + time::Instant, }; +use crate::util::SeqGenerator; + component! { ChannelManager : ChannelManagerInner { sequence: SeqGenerator = SeqGenerator::new(0), @@ -105,12 +104,10 @@ impl ChannelManager { } impl Channel { - fn recv_packet(&mut self) -> Poll> { - let (cmd, packet) = match self.receiver.poll() { - Poll::Ready(Ok(Some(t))) => t, - Poll::Ready(Ok(t)) => return Err(ChannelError), // The channel has been closed. + fn recv_packet(&mut self, cx: &mut Context<'_>) -> Poll> { + let (cmd, packet) = match self.receiver.poll_next_unpin(cx) { Poll::Pending => return Poll::Pending, - Err(()) => unreachable!(), + Poll::Ready(o) => o.ok_or(ChannelError)?, }; if cmd == 0xa { @@ -119,7 +116,7 @@ impl Channel { self.state = ChannelState::Closed; - Err(ChannelError) + Poll::Ready(Err(ChannelError)) } else { Poll::Ready(Ok(packet)) } @@ -133,15 +130,19 @@ impl Channel { } impl Stream for Channel { - type Item = Result, ChannelError>; + type Item = Result; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { match self.state.clone() { ChannelState::Closed => panic!("Polling already terminated channel"), ChannelState::Header(mut data) => { if data.len() == 0 { - data = ready!(self.recv_packet()); + data = match self.recv_packet(cx) { + Poll::Ready(Ok(x)) => x, + Poll::Ready(Err(x)) => return Poll::Ready(Some(Err(x))), + Poll::Pending => return Poll::Pending, + }; } let length = BigEndian::read_u16(data.split_to(2).as_ref()) as usize; @@ -155,19 +156,23 @@ impl Stream for Channel { self.state = ChannelState::Header(data); let event = ChannelEvent::Header(header_id, header_data); - return Poll::Ready(Ok(Some(event))); + return Poll::Ready(Some(Ok(event))); } } ChannelState::Data => { - let data = ready!(self.recv_packet()); + let data = match self.recv_packet(cx) { + Poll::Ready(Ok(x)) => x, + Poll::Ready(Err(x)) => return Poll::Ready(Some(Err(x))), + Poll::Pending => return Poll::Pending, + }; if data.len() == 0 { self.receiver.close(); self.state = ChannelState::Closed; - return Poll::Ready(Ok(None)); + return Poll::Ready(None); } else { let event = ChannelEvent::Data(data); - return Poll::Ready(Ok(Some(event))); + return Poll::Ready(Some(Ok(event))); } } } @@ -176,36 +181,45 @@ impl Stream for Channel { } impl Stream for ChannelData { - type Item = Result, ChannelError>; + type Item = Result; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let mut channel = match self.0.poll_lock() { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut channel = match self.0.poll_lock(cx) { Poll::Ready(c) => c, Poll::Pending => return Poll::Pending, }; loop { - match ready!(channel.poll()) { + let x = match channel.poll_next_unpin(cx) { + Poll::Ready(x) => x.transpose()?, + Poll::Pending => return Poll::Pending, + }; + match x { Some(ChannelEvent::Header(..)) => (), - Some(ChannelEvent::Data(data)) => return Poll::Ready(Ok(Some(data))), - None => return Poll::Ready(Ok(None)), + Some(ChannelEvent::Data(data)) => return Poll::Ready(Some(Ok(data))), + None => return Poll::Ready(None), } } } } impl Stream for ChannelHeaders { - type Item = Result)>, ChannelError>; + type Item = Result<(u8, Vec), ChannelError>; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - let mut channel = match self.0.poll_lock() { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut channel = match self.0.poll_lock(cx) { Poll::Ready(c) => c, Poll::Pending => return Poll::Pending, }; - match ready!(channel.poll()) { - Some(ChannelEvent::Header(id, data)) => Poll::Ready(Ok(Some((id, data)))), - Some(ChannelEvent::Data(..)) | None => Poll::Ready(Ok(None)), + let x = match channel.poll_next_unpin(cx) { + Poll::Ready(x) => x.transpose()?, + Poll::Pending => return Poll::Pending, + }; + + match x { + Some(ChannelEvent::Header(id, data)) => Poll::Ready(Some(Ok((id, data)))), + Some(ChannelEvent::Data(..)) | None => Poll::Ready(None), } } } diff --git a/core/src/component.rs b/core/src/component.rs index 50ab7b37..a761c455 100644 --- a/core/src/component.rs +++ b/core/src/component.rs @@ -35,29 +35,3 @@ macro_rules! component { } } } - -use std::cell::UnsafeCell; -use std::sync::Mutex; - -pub(crate) struct Lazy(Mutex, UnsafeCell>); -unsafe impl Sync for Lazy {} -unsafe impl Send for Lazy {} - -#[cfg_attr(feature = "cargo-clippy", allow(mutex_atomic))] -impl Lazy { - pub(crate) fn new() -> Lazy { - Lazy(Mutex::new(false), UnsafeCell::new(None)) - } - - pub(crate) fn get T>(&self, f: F) -> &T { - let mut inner = self.0.lock().unwrap(); - if !*inner { - unsafe { - *self.1.get() = Some(f()); - } - *inner = true; - } - - unsafe { &*self.1.get() }.as_ref().unwrap() - } -} diff --git a/core/src/connection/codec.rs b/core/src/connection/codec.rs index 1417a5c4..ead07b6e 100644 --- a/core/src/connection/codec.rs +++ b/core/src/connection/codec.rs @@ -35,11 +35,10 @@ impl APCodec { } } -type APCodecItem = (u8, Vec); -impl Encoder for APCodec { +impl Encoder<(u8, Vec)> for APCodec { type Error = io::Error; - fn encode(&mut self, item: APCodecItem, buf: &mut BytesMut) -> io::Result<()> { + fn encode(&mut self, item: (u8, Vec), buf: &mut BytesMut) -> io::Result<()> { let (cmd, payload) = item; let offset = buf.len(); diff --git a/core/src/connection/handshake.rs b/core/src/connection/handshake.rs index 39bce7c4..3810fc96 100644 --- a/core/src/connection/handshake.rs +++ b/core/src/connection/handshake.rs @@ -1,42 +1,25 @@ -use super::codec::APCodec; -use crate::{ - diffie_hellman::DHLocalKeys, - protocol, - protocol::keyexchange::{APResponseMessage, ClientHello, ClientResponsePlaintext}, - util, -}; - +use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; use hmac::{Hmac, Mac}; use protobuf::{self, Message}; use rand::thread_rng; use sha1::Sha1; -use std::{io, marker::Unpin}; +use std::io; use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; use tokio_util::codec::{Decoder, Framed}; -// struct handshake { -// keys: DHLocalKeys, -// connection: T, -// accumulator: Vec, -// } +use super::codec::APCodec; +use crate::diffie_hellman::DHLocalKeys; +use crate::protocol; +use crate::protocol::keyexchange::{APResponseMessage, ClientHello, ClientResponsePlaintext}; +use crate::util; pub async fn handshake( mut connection: T, -) -> Result, io::Error> { +) -> io::Result> { let local_keys = DHLocalKeys::random(&mut thread_rng()); - // Send ClientHello - let client_hello: Vec = client_hello(local_keys.public_key()).await?; - connection.write_all(&client_hello).await?; - - // Receive APResponseMessage - let size = connection.read_u32().await?; - let mut buffer = Vec::with_capacity(size as usize - 4); - let bytes = connection.read_buf(&mut buffer).await?; - let message = protobuf::parse_from_bytes::(&buffer[..bytes])?; - - let mut accumulator = client_hello.clone(); - accumulator.extend_from_slice(&size.to_be_bytes()); - accumulator.extend_from_slice(&buffer); + let gc = local_keys.public_key(); + let mut accumulator = client_hello(&mut connection, gc).await?; + let message: APResponseMessage = recv_packet(&mut connection, &mut accumulator).await?; let remote_key = message .get_challenge() .get_login_crypto_challenge() @@ -44,28 +27,19 @@ pub async fn handshake( .get_gs() .to_owned(); - // Solve the challenge let shared_secret = local_keys.shared_secret(&remote_key); let (challenge, send_key, recv_key) = compute_keys(&shared_secret, &accumulator); let codec = APCodec::new(&send_key, &recv_key); - let buffer: Vec = client_response(challenge).await?; - connection.write_all(&buffer).await?; - let framed = codec.framed(connection); - Ok(framed) + client_response(&mut connection, challenge).await?; + + Ok(codec.framed(connection)) } -// async fn recv_packet( -// mut connection: T, -// ) -> Result<(Message, &Vec), io::Error> { -// let size = connection.read_u32().await?; -// let mut buffer = Vec::with_capacity(size as usize - 4); -// let bytes = connection.read_buf(&mut buffer).await?; -// let proto = protobuf::parse_from_bytes(&buffer[..bytes])?; -// Ok(proto) -// } - -async fn client_hello(gc: Vec) -> Result, io::Error> { +async fn client_hello(connection: &mut T, gc: Vec) -> io::Result> +where + T: AsyncWrite + Unpin, +{ let mut packet = ClientHello::new(); packet .mut_build_info() @@ -73,7 +47,7 @@ async fn client_hello(gc: Vec) -> Result, io::Error> { packet .mut_build_info() .set_platform(protocol::keyexchange::Platform::PLATFORM_LINUX_X86); - packet.mut_build_info().set_version(109_800_078); + packet.mut_build_info().set_version(109800078); packet .mut_cryptosuites_supported() .push(protocol::keyexchange::Cryptosuite::CRYPTO_SUITE_SHANNON); @@ -88,15 +62,19 @@ async fn client_hello(gc: Vec) -> Result, io::Error> { packet.set_client_nonce(util::rand_vec(&mut thread_rng(), 0x10)); packet.set_padding(vec![0x1e]); + let mut buffer = vec![0, 4]; let size = 2 + 4 + packet.compute_size(); - let mut buffer = Vec::with_capacity(size as usize); - buffer.extend(&[0, 4]); - buffer.write_u32(size).await?; - buffer.extend(packet.write_to_bytes()?); + as WriteBytesExt>::write_u32::(&mut buffer, size).unwrap(); + packet.write_to_vec(&mut buffer).unwrap(); + + connection.write_all(&buffer[..]).await?; Ok(buffer) } -async fn client_response(challenge: Vec) -> Result, io::Error> { +async fn client_response(connection: &mut T, challenge: Vec) -> io::Result<()> +where + T: AsyncWrite + Unpin, +{ let mut packet = ClientResponsePlaintext::new(); packet .mut_login_crypto_response() @@ -105,14 +83,37 @@ async fn client_response(challenge: Vec) -> Result, io::Error> { packet.mut_pow_response(); packet.mut_crypto_response(); - // let mut buffer = vec![]; + let mut buffer = vec![]; let size = 4 + packet.compute_size(); - let mut buffer = Vec::with_capacity(size as usize); - buffer.write_u32(size).await?; - // This seems to reallocate - // packet.write_to_vec(&mut buffer)?; - buffer.extend(packet.write_to_bytes()?); - Ok(buffer) + as WriteBytesExt>::write_u32::(&mut buffer, size).unwrap(); + packet.write_to_vec(&mut buffer).unwrap(); + + connection.write_all(&buffer[..]).await?; + Ok(()) +} + +async fn recv_packet(connection: &mut T, acc: &mut Vec) -> io::Result +where + T: AsyncRead + Unpin, + M: Message, +{ + let header = read_into_accumulator(connection, 4, acc).await?; + let size = BigEndian::read_u32(header) as usize; + let data = read_into_accumulator(connection, size - 4, acc).await?; + let message = protobuf::parse_from_bytes(data).unwrap(); + Ok(message) +} + +async fn read_into_accumulator<'a, T: AsyncRead + Unpin>( + connection: &mut T, + size: usize, + acc: &'a mut Vec, +) -> io::Result<&'a mut [u8]> { + let offset = acc.len(); + acc.resize(offset + size, 0); + + connection.read_exact(&mut acc[offset..]).await?; + Ok(&mut acc[offset..]) } fn compute_keys(shared_secret: &[u8], packets: &[u8]) -> (Vec, Vec, Vec) { diff --git a/core/src/connection/mod.rs b/core/src/connection/mod.rs index 6540f4f5..eba64070 100644 --- a/core/src/connection/mod.rs +++ b/core/src/connection/mod.rs @@ -1,63 +1,56 @@ mod codec; mod handshake; -pub use self::{codec::APCodec, handshake::handshake}; -use crate::{authentication::Credentials, version}; +pub use self::codec::APCodec; +pub use self::handshake::handshake; use futures::{SinkExt, StreamExt}; use protobuf::{self, Message}; -use std::{io, net::ToSocketAddrs}; +use std::io; +use std::net::ToSocketAddrs; use tokio::net::TcpStream; use tokio_util::codec::Framed; use url::Url; -// use crate::proxytunnel; +use crate::authentication::Credentials; +use crate::version; + +use crate::proxytunnel; pub type Transport = Framed; -pub async fn connect(addr: String, proxy: &Option) -> Result { - let (addr, connect_url): (_, Option) = match *proxy { - Some(ref url) => { - info!("Using proxy \"{}\"", url); - - let mut iter = url.to_socket_addrs()?; - let socket_addr = iter.next().ok_or_else(|| { +pub async fn connect(addr: String, proxy: &Option) -> io::Result { + let socket = if let Some(proxy) = proxy { + info!("Using proxy \"{}\"", proxy); + let socket_addr = proxy.to_socket_addrs().and_then(|mut iter| { + iter.next().ok_or_else(|| { io::Error::new( io::ErrorKind::NotFound, "Can't resolve proxy server address", ) - })?; - (socket_addr, Some(addr)) - } - None => { - let mut iter = addr.to_socket_addrs()?; - let socket_addr = iter.next().ok_or_else(|| { + }) + })?; + let socket = TcpStream::connect(&socket_addr).await?; + proxytunnel::connect(socket, &addr).await? + } else { + let socket_addr = addr.to_socket_addrs().and_then(|mut iter| { + iter.next().ok_or_else(|| { io::Error::new(io::ErrorKind::NotFound, "Can't resolve server address") - })?; - (socket_addr, None) - } + }) + })?; + TcpStream::connect(&socket_addr).await? }; - let connection = TcpStream::connect(&addr).await?; - if let Some(connect_url) = connect_url { - unimplemented!() - // let connection = proxytunnel::connect(connection, &connect_url).await?; - // let connection = handshake(connection).await?; - // Ok(connection) - } else { - handshake(connection).await - } + handshake(socket).await } pub async fn authenticate( - mut transport: Transport, + transport: &mut Transport, credentials: Credentials, - device_id: String, -) -> Result<(Transport, Credentials), io::Error> { - use crate::protocol::{ - authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os}, - keyexchange::APLoginFailed, - }; + device_id: &str, +) -> io::Result { + use crate::protocol::authentication::{APWelcome, ClientResponseEncrypted, CpuFamily, Os}; + use crate::protocol::keyexchange::APLoginFailed; let mut packet = ClientResponseEncrypted::new(); packet @@ -80,19 +73,18 @@ pub async fn authenticate( version::short_sha(), version::build_id() )); - packet.mut_system_info().set_device_id(device_id); + packet + .mut_system_info() + .set_device_id(device_id.to_string()); packet.set_version_string(version::version_string()); - let cmd: u8 = 0xab; + let cmd = 0xab; let data = packet.write_to_bytes().unwrap(); transport.send((cmd, data)).await?; - - let packet = transport.next().await; - - // TODO: Don't panic? - match packet { - Some(Ok((0xac, data))) => { + let (cmd, data) = transport.next().await.expect("EOF")?; + match cmd { + 0xac => { let welcome_data: APWelcome = protobuf::parse_from_bytes(data.as_ref()).unwrap(); let reusable_credentials = Credentials { @@ -101,10 +93,10 @@ pub async fn authenticate( auth_data: welcome_data.get_reusable_auth_credentials().to_owned(), }; - Ok((transport, reusable_credentials)) + Ok(reusable_credentials) } - Some(Ok((0xad, data))) => { + 0xad => { let error_data: APLoginFailed = protobuf::parse_from_bytes(data.as_ref()).unwrap(); panic!( "Authentication failed with reason: {:?}", @@ -112,8 +104,6 @@ pub async fn authenticate( ) } - Some(Ok((cmd, _))) => panic!("Unexpected packet {:?}", cmd), - Some(err @ Err(_)) => panic!("Packet error: {:?}", err), - None => panic!("EOF"), + _ => panic!("Unexpected packet {:?}", cmd), } } diff --git a/core/src/diffie_hellman.rs b/core/src/diffie_hellman.rs index dec34a3b..358901be 100644 --- a/core/src/diffie_hellman.rs +++ b/core/src/diffie_hellman.rs @@ -1,12 +1,12 @@ use num_bigint::BigUint; -use num_traits::FromPrimitive; +use once_cell::sync::Lazy; use rand::Rng; 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(&[ +pub static DH_GENERATOR: Lazy = Lazy::new(|| BigUint::from_bytes_be(&[0x02])); +pub static DH_PRIME: Lazy = Lazy::new(|| { + 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, @@ -14,8 +14,8 @@ lazy_static! { 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, - ]); -} + ]) +}); pub struct DHLocalKeys { private_key: BigUint, diff --git a/core/src/keymaster.rs b/core/src/keymaster.rs index c7be11b0..87b3f1e3 100644 --- a/core/src/keymaster.rs +++ b/core/src/keymaster.rs @@ -1,8 +1,4 @@ -// use futures::Future; -use serde_json; - -use crate::mercury::MercuryError; -use crate::session::Session; +use crate::{mercury::MercuryError, session::Session}; #[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] @@ -22,13 +18,7 @@ pub async fn get_token( "hm://keymaster/token/authenticated?client_id={}&scope={}", client_id, scopes ); - - // Box::new(session.mercury().get(url).map(move |response| { - session.mercury().get(url).await.map(move |response| { - let data = response.payload.first().expect("Empty payload"); - let data = String::from_utf8(data.clone()).unwrap(); - let token: Token = serde_json::from_str(&data).unwrap(); - - token - }) + let response = session.mercury().get(url).await?; + let data = response.payload.first().expect("Empty payload"); + serde_json::from_slice(data.as_ref()).map_err(|_| MercuryError) } diff --git a/core/src/lib.rs b/core/src/lib.rs index 2d50ec70..a15aa7a2 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,27 +1,23 @@ -#![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))] +#![allow(clippy::unused_io_amount)] -// #[macro_use] -// extern crate error_chain; -#[macro_use] -extern crate futures; -#[macro_use] -extern crate lazy_static; #[macro_use] extern crate log; #[macro_use] extern crate serde_derive; - +#[macro_use] +extern crate pin_project_lite; extern crate aes; extern crate base64; extern crate byteorder; extern crate bytes; +extern crate futures; 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 once_cell; extern crate pbkdf2; extern crate protobuf; extern crate rand; @@ -29,9 +25,8 @@ extern crate serde; extern crate serde_json; extern crate sha1; extern crate shannon; -extern crate tokio; -// extern crate tokio_codec; -// extern crate tokio_io; +pub extern crate tokio; +extern crate tokio_util; extern crate url; extern crate uuid; @@ -39,7 +34,8 @@ extern crate librespot_protocol as protocol; #[macro_use] mod component; -mod apresolve; + +pub mod apresolve; pub mod audio_key; pub mod authentication; pub mod cache; @@ -49,7 +45,7 @@ pub mod connection; pub mod diffie_hellman; pub mod keymaster; pub mod mercury; -pub mod proxytunnel; +mod proxytunnel; pub mod session; pub mod spotify_id; pub mod util; diff --git a/core/src/mercury/mod.rs b/core/src/mercury/mod.rs index 7a80f44d..72360c97 100644 --- a/core/src/mercury/mod.rs +++ b/core/src/mercury/mod.rs @@ -1,18 +1,14 @@ use crate::protocol; use crate::util::url_encode; +use crate::util::SeqGenerator; use byteorder::{BigEndian, ByteOrder}; use bytes::Bytes; -use protobuf; -use std::collections::HashMap; -use std::mem; - use futures::{ channel::{mpsc, oneshot}, - Future, FutureExt, + Future, }; -use std::task::Poll; - -use crate::util::SeqGenerator; +use std::{collections::HashMap, task::Poll}; +use std::{mem, pin::Pin, task::Context}; mod types; pub use self::types::*; @@ -35,16 +31,21 @@ pub struct MercuryPending { callback: Option>>, } -pub struct MercuryFuture(oneshot::Receiver>); +pin_project! { + pub struct MercuryFuture { + #[pin] + receiver: oneshot::Receiver> + } +} + impl Future for MercuryFuture { type Output = Result; - fn poll(&mut self) -> Poll { - match self.0.poll() { - Poll::Ready(Ok(Ok(value))) => Poll::Ready(Ok(value)), - Poll::Ready(Ok(Err(err))) => Err(err), + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.project().receiver.poll(cx) { + Poll::Ready(Ok(x)) => Poll::Ready(x), + Poll::Ready(Err(_)) => Poll::Ready(Err(MercuryError)), Poll::Pending => Poll::Pending, - Err(oneshot::Canceled) => Err(MercuryError), } } } @@ -76,7 +77,7 @@ impl MercuryManager { let data = req.encode(&seq); self.session().send_packet(cmd, data); - MercuryFuture(rx) + MercuryFuture { receiver: rx } } pub fn get>(&self, uri: T) -> MercuryFuture { @@ -106,40 +107,41 @@ impl MercuryManager { uri: T, ) -> Result, MercuryError> { let uri = uri.into(); - let request = self.request(MercuryRequest { - method: MercuryMethod::SUB, - uri: uri.clone(), - content_type: None, - payload: Vec::new(), - }); + let response = self + .request(MercuryRequest { + method: MercuryMethod::SUB, + uri: uri.clone(), + content_type: None, + payload: Vec::new(), + }) + .await?; + + let (tx, rx) = mpsc::unbounded(); let manager = self.clone(); - request.await.map(move |response| { - let (tx, rx) = mpsc::unbounded(); - manager.lock(move |inner| { - 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(); + manager.lock(move |inner| { + if !inner.invalid { + debug!("subscribed uri={} count={}", uri, response.payload.len()); + if !response.payload.is_empty() { + // 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())); - } - } else { - // New subscription protocol, watch the requested URI - inner.subscriptions.push((uri, tx)); + inner.subscriptions.push((sub_uri, tx.clone())); } + } else { + // New subscription protocol, watch the requested URI + inner.subscriptions.push((uri, tx)); } - }); + } + }); - rx - }) + Ok(rx) } pub(crate) fn dispatch(&self, cmd: u8, mut data: Bytes) { @@ -195,7 +197,7 @@ impl MercuryManager { let header: protocol::mercury::Header = protobuf::parse_from_bytes(&header_data).unwrap(); let response = MercuryResponse { - uri: url_encode(header.get_uri()).to_owned(), + uri: url_encode(header.get_uri()), status_code: header.get_status_code(), payload: pending.parts, }; diff --git a/core/src/mercury/sender.rs b/core/src/mercury/sender.rs index f406a52b..860c2f33 100644 --- a/core/src/mercury/sender.rs +++ b/core/src/mercury/sender.rs @@ -1,11 +1,7 @@ -use futures::{Future, Sink}; -use std::collections::VecDeque; +use futures::Sink; +use std::{collections::VecDeque, pin::Pin, task::Context}; use super::*; -use std::{ - pin::Pin, - task::{Context, Poll}, -}; pub struct MercurySender { mercury: MercuryManager, @@ -34,25 +30,38 @@ impl Clone for MercurySender { } } -type SinkItem = Vec; -impl Sink for MercurySender { +impl Sink> for MercurySender { type Error = MercuryError; - fn start_send(self: Pin<&mut Self>, item: SinkItem) -> Result<(), Self::Error> { - let task = self.mercury.send(self.uri.clone(), item); - self.pending.push_back(task); + fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll_flush(cx) + } + + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { match self.pending.front_mut() { Some(task) => { - ready!(task.poll()); + match Pin::new(task).poll(cx) { + Poll::Ready(Err(x)) => return Poll::Ready(Err(x)), + Poll::Pending => return Poll::Pending, + _ => (), + }; + } + None => { + return Poll::Ready(Ok(())); } - None => return Poll::Ready(Ok(())), } self.pending.pop_front(); } } + + fn start_send(mut self: Pin<&mut Self>, item: Vec) -> Result<(), Self::Error> { + let task = self.mercury.send(self.uri.clone(), item); + self.pending.push_back(task); + Ok(()) + } } diff --git a/core/src/proxytunnel.rs b/core/src/proxytunnel.rs index fbc17f63..508de7f8 100644 --- a/core/src/proxytunnel.rs +++ b/core/src/proxytunnel.rs @@ -1,124 +1,45 @@ use std::io; -use std::str::FromStr; -use httparse; use hyper::Uri; -// use tokio_io::io::{read, write_all, Read, Window, WriteAll}; -// use tokio_io::{AsyncRead, AsyncWrite}; +use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; -use futures::{ - io::{Read, Window, WriteAll}, - AsyncRead, AsyncWrite, Future, -}; -use std::{ - pin::Pin, - task::{Context, Poll}, -}; -// use tokio::io::{AsyncReadExt, AsyncWriteExt}; - -pub struct ProxyTunnel<'a, T> { - state: ProxyState<'a, T>, -} - -enum ProxyState<'a, T> { - ProxyConnect(WriteAll<'a, T>), - ProxyResponse(Read<'a, T>), -} - -pub fn connect<'a, T: AsyncRead + AsyncWrite>( - connection: T, +pub async fn connect( + mut connection: T, connect_url: &str, -) -> ProxyTunnel<'a, T> { - let proxy = proxy_connect(connection, connect_url); - ProxyTunnel { - state: ProxyState::ProxyConnect(proxy), - } -} - -impl<'a, T: AsyncRead + AsyncWrite> Future for ProxyTunnel<'a, T> { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { - use self::ProxyState::*; - loop { - self.state = match self.state { - ProxyConnect(ref mut write) => { - let (connection, mut accumulator) = ready!(write.poll()); - - let capacity = accumulator.capacity(); - accumulator.resize(capacity, 0); - let window = Window::new(accumulator); - - // let read = read(connection, window); - // ProxyResponse(read) - ProxyResponse(connection.read(window)) - } - - ProxyResponse(ref mut read_f) => { - let (connection, mut window, bytes_read) = ready!(read_f.poll()); - - if bytes_read == 0 { - return Err(io::Error::new(io::ErrorKind::Other, "Early EOF from proxy")); - } - - let data_end = window.start() + bytes_read; - - let buf = window.get_ref()[0..data_end].to_vec(); - let mut headers = [httparse::EMPTY_HEADER; 16]; - 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.to_string())); - } - }; - - if status.is_complete() { - if let Some(code) = response.code { - if code == 200 { - // Proxy says all is well - return Poll::Ready(connection); - } else { - let reason = response.reason.unwrap_or("no reason"); - let msg = format!("Proxy responded with {}: {}", code, reason); - - return Err(io::Error::new(io::ErrorKind::Other, msg)); - } - } else { - return Err(io::Error::new( - io::ErrorKind::Other, - "Malformed response from proxy", - )); - } - } else { - if data_end >= window.end() { - // Allocate some more buffer space - let newsize = data_end + 100; - window.get_mut().resize(newsize, 0); - window.set_end(newsize); - } - // We did not get a full header - window.set_start(data_end); - // let read = read(connection, window); - // ProxyResponse(read) - ProxyResponse(connection.read(window)) - } - } - } - } - } -} - -fn proxy_connect(connection: T, connect_url: &str) -> WriteAll { - let uri = Uri::from_str(connect_url).unwrap(); - let buffer = format!( +) -> io::Result { + let uri = connect_url.parse::().unwrap(); + let mut buffer = format!( "CONNECT {0}:{1} HTTP/1.1\r\n\ \r\n", - uri.host().expect(&format!("No host in {}", uri)), - uri.port_u16().expect(&format!("No port in {}", uri)) + uri.host().unwrap_or_else(|| panic!("No host in {}", uri)), + uri.port().unwrap_or_else(|| panic!("No port in {}", uri)) ) .into_bytes(); + connection.write_all(buffer.as_ref()).await?; - // write_all(connection, buffer) - connection.write_all(buffer) + buffer.clear(); + connection.read_to_end(&mut buffer).await?; + if buffer.is_empty() { + return Err(io::Error::new(io::ErrorKind::Other, "Early EOF from proxy")); + } + + let mut headers = [httparse::EMPTY_HEADER; 16]; + let mut response = httparse::Response::new(&mut headers); + + response + .parse(&buffer[..]) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err.to_string()))?; + + match response.code { + Some(200) => Ok(connection), // Proxy says all is well + Some(code) => { + let reason = response.reason.unwrap_or("no reason"); + let msg = format!("Proxy responded with {}: {}", code, reason); + Err(io::Error::new(io::ErrorKind::Other, msg)) + } + None => Err(io::Error::new( + io::ErrorKind::Other, + "Malformed response from proxy", + )), + } } diff --git a/core/src/session.rs b/core/src/session.rs index 9a8df2b7..2def4085 100644 --- a/core/src/session.rs +++ b/core/src/session.rs @@ -1,32 +1,23 @@ -use std::io; use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex, RwLock, Weak}; +use std::sync::{Arc, RwLock, Weak}; +use std::task::Poll; use std::time::{SystemTime, UNIX_EPOCH}; +use std::{io, pin::Pin, task::Context}; + +use once_cell::sync::OnceCell; use byteorder::{BigEndian, ByteOrder}; use bytes::Bytes; -// use futures::sync::mpsc; -// use futures::{Async, Future, IntoFuture, Poll, Stream}; -// use tokio::runtime::{current_thread, current_thread::Handle}; - -// use futures::future::{IntoFuture, Remote}; -use futures::{channel::mpsc, future, Future, Stream, StreamExt, TryFutureExt}; -use std::{ - pin::Pin, - task::{Context, Poll}, -}; - -use tokio::runtime::Handle; +use futures::{channel::mpsc, Future, FutureExt, StreamExt, TryStream, TryStreamExt}; use crate::apresolve::apresolve_or_fallback; -// use crate::audio_key::AudioKeyManager; +use crate::audio_key::AudioKeyManager; use crate::authentication::Credentials; use crate::cache::Cache; -// use crate::channel::ChannelManager; -// use crate::component::Lazy; +use crate::channel::ChannelManager; use crate::config::SessionConfig; use crate::connection; -// use crate::mercury::MercuryManager; +use crate::mercury::MercuryManager; struct SessionData { country: String, @@ -39,13 +30,13 @@ struct SessionInternal { config: SessionConfig, data: RwLock, - tx_connection: mpsc::UnboundedSender)>>, + tx_connection: mpsc::UnboundedSender<(u8, Vec)>, - // audio_key: Lazy, - // channel: Lazy, - // mercury: Lazy, + audio_key: OnceCell, + channel: OnceCell, + mercury: OnceCell, cache: Option>, - handle: Mutex, + session_id: usize, } @@ -54,63 +45,35 @@ static SESSION_COUNTER: AtomicUsize = AtomicUsize::new(0); #[derive(Clone)] pub struct Session(Arc); -// TODO: Define better errors! -type Result = std::result::Result>; - impl Session { pub async fn connect( config: SessionConfig, credentials: Credentials, cache: Option, - handle: Handle, - ) -> Result { - let access_point_addr = - apresolve_or_fallback::(&config.proxy, &config.ap_port).await?; + ) -> io::Result { + let ap = apresolve_or_fallback(&config.proxy, &config.ap_port).await; - let proxy = config.proxy.clone(); - info!("Connecting to AP \"{}\"", access_point_addr); - let connection = connection::connect(access_point_addr, &proxy); + info!("Connecting to AP \"{}\"", ap); + let mut conn = connection::connect(ap, &config.proxy).await?; - let device_id = config.device_id.clone(); - let authentication = connection.and_then(move |connection| { - connection::authenticate(connection, credentials, device_id) - }); + let reusable_credentials = + connection::authenticate(&mut conn, credentials, &config.device_id).await?; + info!("Authenticated as \"{}\" !", reusable_credentials.username); + if let Some(cache) = &cache { + cache.save_credentials(&reusable_credentials); + } - let result = match authentication.await { - Ok((transport, reusable_credentials)) => { - info!("Authenticated as \"{}\" !", reusable_credentials.username); - if let Some(ref cache) = cache { - cache.save_credentials(&reusable_credentials); - } + let session = Session::create(conn, config, cache, reusable_credentials.username); - let (session, tasks) = Session::create( - &handle, - transport, - config, - cache, - reusable_credentials.username.clone(), - ); - - tokio::task::spawn_local(async move { tasks }); - - Ok(session) - } - Err(e) => { - error!("Unable to Connect"); - Err(e.into()) - } - }; - - result + Ok(session) } fn create( - handle: &Handle, transport: connection::Transport, config: SessionConfig, cache: Option, username: String, - ) -> (Session, Box, Result<()>)>>) { + ) -> Session { let (sink, stream) = transport.split(); let (sender_tx, sender_rx) = mpsc::unbounded(); @@ -119,7 +82,7 @@ impl Session { debug!("new Session[{}]", session_id); let session = Session(Arc::new(SessionInternal { - config, + config: config, data: RwLock::new(SessionData { country: String::new(), canonical_username: username, @@ -131,73 +94,51 @@ impl Session { cache: cache.map(Arc::new), - // audio_key: Lazy::new(), - // channel: Lazy::new(), - // mercury: Lazy::new(), - handle: Mutex::new(handle.clone()), - session_id, + audio_key: OnceCell::new(), + channel: OnceCell::new(), + mercury: OnceCell::new(), + + session_id: session_id, })); - let sender_task = sender_rx - .forward(sink) - .map_err(|e| -> Box { Box::new(e) }); - + let sender_task = sender_rx.map(Ok::<_, io::Error>).forward(sink); let receiver_task = DispatchTask(stream, session.weak()); - let task = Box::new(future::join(receiver_task, sender_task)); - - (session, task) + let task = + futures::future::join(sender_task, receiver_task).map(|_| io::Result::<_>::Ok(())); + tokio::spawn(task); + session } - // pub fn audio_key(&self) -> &AudioKeyManager { - // self.0.audio_key.get(|| AudioKeyManager::new(self.weak())) - // } + pub fn audio_key(&self) -> &AudioKeyManager { + self.0 + .audio_key + .get_or_init(|| AudioKeyManager::new(self.weak())) + } - // pub fn channel(&self) -> &ChannelManager { - // self.0.channel.get(|| ChannelManager::new(self.weak())) - // } + pub fn channel(&self) -> &ChannelManager { + self.0 + .channel + .get_or_init(|| ChannelManager::new(self.weak())) + } - // pub fn mercury(&self) -> &MercuryManager { - // self.0.mercury.get(|| MercuryManager::new(self.weak())) - // } + pub fn mercury(&self) -> &MercuryManager { + self.0 + .mercury + .get_or_init(|| MercuryManager::new(self.weak())) + } pub fn time_delta(&self) -> i64 { self.0.data.read().unwrap().time_delta } - // Spawn a future directly - // pub fn spawn(&self, f: F) - // where - // F: Future + Send + 'static, - // { - // let handle = self.0.handle.lock().unwrap(); - // let spawn_res = handle.spawn(f); - // match spawn_res { - // Ok(_) => (), - // Err(e) => error!("Session SpawnErr {:?}", e), - // } - // } - - // pub fn spawn(&self, f: F) - // where - // F: FnOnce() -> R + Send + 'static, - // R: Future + Send + 'static, - // { - // // This fails when called from a different thread - // // current_thread::spawn(future::lazy(|| f())); - // - // // These fail when the Future doesn't implement Send - // let handle = self.0.handle.lock().unwrap(); - // let spawn_res = handle.spawn(lazy(|| f())); - // - // // let mut te = current_thread::TaskExecutor::current(); - // // let spawn_res = te.spawn_local(Box::new(future::lazy(|| f()))); - // - // match spawn_res { - // Ok(_) => (), - // Err(e) => error!("Session SpawnErr {:?}", e), - // } - // } + pub fn spawn(&self, task: T) + where + T: Future + Send + 'static, + T::Output: Send + 'static, + { + tokio::spawn(task); + } fn debug_info(&self) { debug!( @@ -208,7 +149,7 @@ impl Session { ); } - // #[cfg_attr(feature = "cargo-clippy", allow(match_same_arms))] + #[allow(clippy::match_same_arms)] fn dispatch(&self, cmd: u8, data: Bytes) { match cmd { 0x4 => { @@ -231,18 +172,15 @@ impl Session { self.0.data.write().unwrap().country = country; } - // 0x9 | 0xa => self.channel().dispatch(cmd, data), - // 0xd | 0xe => self.audio_key().dispatch(cmd, data), - // 0xb2..=0xb6 => self.mercury().dispatch(cmd, data), - _ => trace!("Unknown dispatch cmd :{:?} {:?}", cmd, data), + 0x9 | 0xa => self.channel().dispatch(cmd, data), + 0xd | 0xe => self.audio_key().dispatch(cmd, data), + 0xb2..=0xb6 => self.mercury().dispatch(cmd, data), + _ => (), } } pub fn send_packet(&self, cmd: u8, data: Vec) { - self.0 - .tx_connection - .unbounded_send(Ok((cmd, data))) - .unwrap(); + self.0.tx_connection.unbounded_send((cmd, data)).unwrap(); } pub fn cache(&self) -> Option<&Arc> { @@ -276,8 +214,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(); + self.mercury().shutdown(); + self.channel().shutdown(); } pub fn is_invalid(&self) -> bool { @@ -306,35 +244,36 @@ impl Drop for SessionInternal { struct DispatchTask(S, SessionWeak) where - S: Stream> + Unpin; + S: TryStream + Unpin; -impl>> Future for DispatchTask +impl Future for DispatchTask where - S: Stream> + Unpin, + S: TryStream + Unpin, + ::Ok: std::fmt::Debug, { - type Output = Result<()>; + type Output = Result<(), S::Error>; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let session = match self.1.try_upgrade() { Some(session) => session, None => return Poll::Ready(Ok(())), }; loop { - let (cmd, data) = match Pin::new(&mut self.0).poll_next(cx) { + let (cmd, data) = match self.0.try_poll_next_unpin(cx) { Poll::Ready(Some(Ok(t))) => t, - Poll::Ready(Some(Err(e))) => { - warn!("Server Connectioned errored"); - session.shutdown(); - return Poll::Ready(Err(Box::new(e))); - } Poll::Ready(None) => { warn!("Connection to server closed."); session.shutdown(); return Poll::Ready(Ok(())); } + Poll::Ready(Some(Err(e))) => { + session.shutdown(); + return Poll::Ready(Err(e)); + } Poll::Pending => return Poll::Pending, }; + session.dispatch(cmd, data); } } @@ -342,7 +281,7 @@ where impl Drop for DispatchTask where - S: Stream> + Unpin, + S: TryStream + Unpin, { fn drop(&mut self) { debug!("drop Dispatch"); diff --git a/core/tests/connect.rs b/core/tests/connect.rs index abc75a0b..44d418a1 100644 --- a/core/tests/connect.rs +++ b/core/tests/connect.rs @@ -1,45 +1,32 @@ -use futures::future::TryFutureExt; use librespot_core::*; -use tokio::runtime; #[cfg(test)] mod tests { use super::*; // Test AP Resolve use apresolve::apresolve_or_fallback; - #[test] - fn test_ap_resolve() { - let mut rt = runtime::Runtime::new().unwrap(); - let ap = rt.block_on(apresolve_or_fallback(&None, &Some(80))); + #[tokio::test] + async fn test_ap_resolve() { + let ap = apresolve_or_fallback(&None, &None).await; println!("AP: {:?}", ap); } // Test connect use authentication::Credentials; use config::SessionConfig; - use connection; - #[test] - fn test_connection() { + #[tokio::test] + async fn test_connection() -> Result<(), Box> { println!("Running connection test"); - let mut rt = runtime::Runtime::new().unwrap(); - let access_point_addr = rt.block_on(apresolve_or_fallback(&None, &None)).unwrap(); + let ap = apresolve_or_fallback(&None, &None).await; let credentials = Credentials::with_password(String::from("test"), String::from("test")); let session_config = SessionConfig::default(); let proxy = None; - println!("Connecting to AP \"{}\"", access_point_addr); - let connection = connection::connect(access_point_addr, &proxy); - - let device_id = session_config.device_id.clone(); - let authentication = connection.and_then(move |connection| { - connection::authenticate(connection, credentials, device_id) - }); - match rt.block_on(authentication) { - Ok((_transport, reusable_credentials)) => { - println!("Authenticated as \"{}\" !", reusable_credentials.username) - } - // TODO assert that we get BadCredentials once we don't panic - Err(e) => println!("ConnectError: {:?}", e), - } + println!("Connecting to AP \"{}\"", ap); + let mut connection = connection::connect(ap, &proxy).await?; + let rc = connection::authenticate(&mut connection, credentials, &session_config.device_id) + .await?; + println!("Authenticated as \"{}\"", rc.username); + Ok(()) } } diff --git a/metadata/Cargo.toml b/metadata/Cargo.toml index 8f3b4c93..f5e61237 100644 --- a/metadata/Cargo.toml +++ b/metadata/Cargo.toml @@ -8,8 +8,9 @@ repository = "https://github.com/librespot-org/librespot" edition = "2018" [dependencies] +async-trait = "0.1" byteorder = "1.3" -futures = "0.1" +futures = "0.3" linear-map = "1.2" protobuf = "~2.14.0" log = "0.4" diff --git a/metadata/src/lib.rs b/metadata/src/lib.rs index 8bd422ce..f71bae95 100644 --- a/metadata/src/lib.rs +++ b/metadata/src/lib.rs @@ -1,6 +1,11 @@ +#![allow(clippy::unused_io_amount)] +#![allow(clippy::redundant_field_names)] #[macro_use] extern crate log; +#[macro_use] +extern crate async_trait; + extern crate byteorder; extern crate futures; extern crate linear_map; @@ -11,8 +16,6 @@ extern crate librespot_protocol as protocol; pub mod cover; -use futures::future; -use futures::Future; use linear_map::LinearMap; use librespot_core::mercury::MercuryError; @@ -69,81 +72,67 @@ pub struct AudioItem { } impl AudioItem { - pub fn get_audio_item( - session: &Session, - id: SpotifyId, - ) -> Box> { + pub async fn get_audio_item(session: &Session, id: SpotifyId) -> Result { match id.audio_type { - SpotifyAudioType::Track => Track::get_audio_item(session, id), - SpotifyAudioType::Podcast => Episode::get_audio_item(session, id), - SpotifyAudioType::NonPlayable => { - Box::new(future::err::(MercuryError)) - } + SpotifyAudioType::Track => Track::get_audio_item(session, id).await, + SpotifyAudioType::Podcast => Episode::get_audio_item(session, id).await, + SpotifyAudioType::NonPlayable => Err(MercuryError), } } } +#[async_trait] trait AudioFiles { - fn get_audio_item( - session: &Session, - id: SpotifyId, - ) -> Box>; + async fn get_audio_item(session: &Session, id: SpotifyId) -> Result; } +#[async_trait] impl AudioFiles for Track { - fn get_audio_item( - session: &Session, - id: SpotifyId, - ) -> Box> { - Box::new(Self::get(session, id).and_then(move |item| { - Ok(AudioItem { - id: id, - uri: format!("spotify:track:{}", id.to_base62()), - files: item.files, - name: item.name, - duration: item.duration, - available: item.available, - alternatives: Some(item.alternatives), - }) - })) + async fn get_audio_item(session: &Session, id: SpotifyId) -> Result { + let item = Self::get(session, id).await?; + Ok(AudioItem { + id: id, + uri: format!("spotify:track:{}", id.to_base62()), + files: item.files, + name: item.name, + duration: item.duration, + available: item.available, + alternatives: Some(item.alternatives), + }) } } +#[async_trait] impl AudioFiles for Episode { - fn get_audio_item( - session: &Session, - id: SpotifyId, - ) -> Box> { - Box::new(Self::get(session, id).and_then(move |item| { - Ok(AudioItem { - id: id, - uri: format!("spotify:episode:{}", id.to_base62()), - files: item.files, - name: item.name, - duration: item.duration, - available: item.available, - alternatives: None, - }) - })) + async fn get_audio_item(session: &Session, id: SpotifyId) -> Result { + let item = Self::get(session, id).await?; + + Ok(AudioItem { + id: id, + uri: format!("spotify:episode:{}", id.to_base62()), + files: item.files, + name: item.name, + duration: item.duration, + available: item.available, + alternatives: None, + }) } } + +#[async_trait] pub trait Metadata: Send + Sized + 'static { type Message: protobuf::Message; fn request_url(id: SpotifyId) -> String; fn parse(msg: &Self::Message, session: &Session) -> Self; - fn get(session: &Session, id: SpotifyId) -> Box> { + async fn get(session: &Session, id: SpotifyId) -> Result { let uri = Self::request_url(id); - let request = session.mercury().get(uri); + let response = session.mercury().get(uri).await?; + let data = response.payload.first().expect("Empty payload"); + let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap(); - let session = session.clone(); - Box::new(request.and_then(move |response| { - let data = response.payload.first().expect("Empty payload"); - let msg: Self::Message = protobuf::parse_from_bytes(data).unwrap(); - - Ok(Self::parse(&msg, &session)) - })) + Ok(Self::parse(&msg, &session)) } } diff --git a/playback/Cargo.toml b/playback/Cargo.toml index edd0951f..10451851 100644 --- a/playback/Cargo.toml +++ b/playback/Cargo.toml @@ -18,9 +18,9 @@ path = "../metadata" version = "0.1.3" [dependencies] -futures = "0.1" +futures = "0.3" log = "0.4" -byteorder = "1.3" +byteorder = "1.4" shell-words = "1.0.0" alsa = { version = "0.2", optional = true } diff --git a/playback/src/audio_backend/mod.rs b/playback/src/audio_backend/mod.rs index a9840d42..21ee3c05 100644 --- a/playback/src/audio_backend/mod.rs +++ b/playback/src/audio_backend/mod.rs @@ -10,7 +10,9 @@ pub trait Sink { fn write(&mut self, data: &[i16]) -> io::Result<()>; } -fn mk_sink(device: Option) -> Box { +pub type SinkBuilder = fn(Option) -> Box; + +fn mk_sink(device: Option) -> Box { Box::new(S::open(device)) } @@ -54,7 +56,7 @@ use self::pipe::StdoutSink; mod subprocess; use self::subprocess::SubprocessSink; -pub const BACKENDS: &'static [(&'static str, fn(Option) -> Box)] = &[ +pub const BACKENDS: &'static [(&'static str, SinkBuilder)] = &[ #[cfg(feature = "alsa-backend")] ("alsa", mk_sink::), #[cfg(feature = "portaudio-backend")] @@ -73,7 +75,7 @@ pub const BACKENDS: &'static [(&'static str, fn(Option) -> Box ("subprocess", mk_sink::), ]; -pub fn find(name: Option) -> Option) -> Box> { +pub fn find(name: Option) -> Option { if let Some(name) = name { BACKENDS .iter() diff --git a/playback/src/audio_backend/pipe.rs b/playback/src/audio_backend/pipe.rs index 2adafe11..02b8faf5 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 { diff --git a/playback/src/player.rs b/playback/src/player.rs index 125184a0..ff0fba24 100644 --- a/playback/src/player.rs +++ b/playback/src/player.rs @@ -1,20 +1,3 @@ -use byteorder::{LittleEndian, ReadBytesExt}; -use futures; -use futures::{future, Async, Future, Poll, Stream}; -use std; -use std::borrow::Cow; -use std::cmp::max; -use std::io::{Read, Result, Seek, SeekFrom}; -use std::mem; -use std::thread; -use std::time::{Duration, Instant}; - -use crate::config::{Bitrate, PlayerConfig}; -use librespot_core::session::Session; -use librespot_core::spotify_id::SpotifyId; - -use librespot_core::util::SeqGenerator; - use crate::audio::{AudioDecrypt, AudioFile, StreamLoaderController}; use crate::audio::{VorbisDecoder, VorbisPacket}; use crate::audio::{ @@ -22,14 +5,34 @@ use crate::audio::{ READ_AHEAD_DURING_PLAYBACK_ROUNDTRIPS, READ_AHEAD_DURING_PLAYBACK_SECONDS, }; use crate::audio_backend::Sink; +use crate::config::{Bitrate, PlayerConfig}; +use crate::librespot_core::tokio; use crate::metadata::{AudioItem, FileFormat}; use crate::mixer::AudioFilter; +use librespot_core::session::Session; +use librespot_core::spotify_id::SpotifyId; +use librespot_core::util::SeqGenerator; + +use byteorder::{LittleEndian, ReadBytesExt}; +use futures::{ + channel::{mpsc, oneshot}, + future, Future, Stream, StreamExt, +}; +use std::io::{Read, Seek, SeekFrom}; +use std::mem; +use std::time::{Duration, Instant}; +use std::{borrow::Cow, io}; +use std::{ + cmp::max, + pin::Pin, + task::{Context, Poll}, +}; const PRELOAD_NEXT_TRACK_BEFORE_END_DURATION_MS: u32 = 30000; pub struct Player { - commands: Option>, - thread_handle: Option>, + commands: Option>, + task_handle: Option>, play_request_id_generator: SeqGenerator, } @@ -45,15 +48,15 @@ pub type SinkEventCallback = Box; struct PlayerInternal { session: Session, config: PlayerConfig, - commands: futures::sync::mpsc::UnboundedReceiver, + commands: mpsc::UnboundedReceiver, state: PlayerState, preload: PlayerPreload, - sink: Box, + sink: Box, sink_status: SinkStatus, sink_event_callback: Option, audio_filter: Option>, - event_senders: Vec>, + event_senders: Vec>, } enum PlayerCommand { @@ -70,7 +73,7 @@ enum PlayerCommand { Pause, Stop, Seek(u32), - AddEventSender(futures::sync::mpsc::UnboundedSender), + AddEventSender(mpsc::UnboundedSender), SetSinkEventCallback(Option), EmitVolumeSetEvent(u16), } @@ -182,7 +185,7 @@ impl PlayerEvent { } } -pub type PlayerEventChannel = futures::sync::mpsc::UnboundedReceiver; +pub type PlayerEventChannel = mpsc::UnboundedReceiver; #[derive(Clone, Copy, Debug)] struct NormalisationData { @@ -193,7 +196,7 @@ struct NormalisationData { } impl NormalisationData { - fn parse_from_file(mut file: T) -> Result { + fn parse_from_file(mut file: T) -> io::Result { const SPOTIFY_NORMALIZATION_HEADER_START_OFFSET: u64 = 144; file.seek(SeekFrom::Start(SPOTIFY_NORMALIZATION_HEADER_START_OFFSET)) .unwrap(); @@ -239,38 +242,38 @@ impl Player { sink_builder: F, ) -> (Player, PlayerEventChannel) where - F: FnOnce() -> Box + Send + 'static, + F: FnOnce() -> Box + Send + 'static, { - let (cmd_tx, cmd_rx) = futures::sync::mpsc::unbounded(); - let (event_sender, event_receiver) = futures::sync::mpsc::unbounded(); + let (cmd_tx, cmd_rx) = mpsc::unbounded(); + let (event_sender, event_receiver) = mpsc::unbounded(); - let handle = thread::spawn(move || { - debug!("new Player[{}]", session.session_id()); + debug!("new Player[{}]", session.session_id()); - let internal = PlayerInternal { - session: session, - config: config, - commands: cmd_rx, + let internal = PlayerInternal { + session: session, + config: config, + commands: cmd_rx, - state: PlayerState::Stopped, - preload: PlayerPreload::None, - sink: sink_builder(), - sink_status: SinkStatus::Closed, - sink_event_callback: None, - audio_filter: audio_filter, - event_senders: [event_sender].to_vec(), - }; + state: PlayerState::Stopped, + preload: PlayerPreload::None, + sink: sink_builder(), + sink_status: SinkStatus::Closed, + sink_event_callback: None, + audio_filter: audio_filter, + event_senders: [event_sender].to_vec(), + }; - // While PlayerInternal is written as a future, it still contains blocking code. - // It must be run by using wait() in a dedicated thread. - let _ = internal.wait(); + // While PlayerInternal is written as a future, it still contains blocking code. + // It must be run by using wait() in a dedicated thread. + let handle = tokio::spawn(async move { + internal.await; debug!("PlayerInternal thread finished."); }); ( Player { commands: Some(cmd_tx), - thread_handle: Some(handle), + task_handle: Some(handle), play_request_id_generator: SeqGenerator::new(0), }, event_receiver, @@ -314,22 +317,21 @@ impl Player { } pub fn get_player_event_channel(&self) -> PlayerEventChannel { - let (event_sender, event_receiver) = futures::sync::mpsc::unbounded(); + let (event_sender, event_receiver) = mpsc::unbounded(); self.command(PlayerCommand::AddEventSender(event_sender)); event_receiver } - pub fn get_end_of_track_future(&self) -> Box> { - let result = self - .get_player_event_channel() - .filter(|event| match event { - PlayerEvent::EndOfTrack { .. } | PlayerEvent::Stopped { .. } => true, - _ => false, + pub async fn get_end_of_track_future(&self) { + self.get_player_event_channel() + .filter(|event| { + future::ready(matches!( + event, + PlayerEvent::EndOfTrack { .. } | PlayerEvent::Stopped { .. } + )) }) - .into_future() - .map_err(|_| ()) - .map(|_| ()); - Box::new(result) + .for_each(|_| future::ready(())) + .await } pub fn set_sink_event_callback(&self, callback: Option) { @@ -345,11 +347,13 @@ impl Drop for Player { fn drop(&mut self) { debug!("Shutting down player thread ..."); self.commands = None; - if let Some(handle) = self.thread_handle.take() { - match handle.join() { - Ok(_) => (), - Err(_) => error!("Player thread panicked!"), - } + if let Some(handle) = self.task_handle.take() { + tokio::spawn(async { + match handle.await { + Ok(_) => (), + Err(_) => error!("Player thread panicked!"), + } + }); } } } @@ -367,11 +371,11 @@ enum PlayerPreload { None, Loading { track_id: SpotifyId, - loader: Box>, + loader: Pin> + Send>>, }, Ready { track_id: SpotifyId, - loaded_track: PlayerLoadedTrackData, + loaded_track: Box, }, } @@ -383,7 +387,7 @@ enum PlayerState { track_id: SpotifyId, play_request_id: u64, start_playback: bool, - loader: Box>, + loader: Pin> + Send>>, }, Paused { track_id: SpotifyId, @@ -428,23 +432,15 @@ impl PlayerState { #[allow(dead_code)] fn is_stopped(&self) -> bool { - use self::PlayerState::*; - match *self { - Stopped => true, - _ => false, - } + matches!(self, Self::Stopped) } fn is_loading(&self) -> bool { - use self::PlayerState::*; - match *self { - Loading { .. } => true, - _ => false, - } + matches!(self, Self::Loading { .. }) } fn decoder(&mut self) -> Option<&mut Decoder> { - use self::PlayerState::*; + use PlayerState::*; match *self { Stopped | EndOfTrack { .. } | Loading { .. } => None, Paused { @@ -573,22 +569,23 @@ struct PlayerTrackLoader { } impl PlayerTrackLoader { - fn find_available_alternative<'a>(&self, audio: &'a AudioItem) -> Option> { + async fn find_available_alternative<'a, 'b>( + &'a self, + audio: &'b AudioItem, + ) -> Option> { if audio.available { Some(Cow::Borrowed(audio)) + } else if let Some(alternatives) = &audio.alternatives { + let alternatives = alternatives + .iter() + .map(|alt_id| AudioItem::get_audio_item(&self.session, *alt_id)); + let alternatives = future::try_join_all(alternatives).await.unwrap(); + alternatives + .into_iter() + .find(|alt| alt.available) + .map(Cow::Owned) } else { - if let Some(alternatives) = &audio.alternatives { - let alternatives = alternatives - .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) - } else { - None - } + None } } @@ -611,8 +608,12 @@ impl PlayerTrackLoader { } } - fn load_track(&self, spotify_id: SpotifyId, position_ms: u32) -> Option { - let audio = match AudioItem::get_audio_item(&self.session, spotify_id).wait() { + async fn load_track( + &self, + spotify_id: SpotifyId, + position_ms: u32, + ) -> Option { + let audio = match AudioItem::get_audio_item(&self.session, spotify_id).await { Ok(audio) => audio, Err(_) => { error!("Unable to load audio item."); @@ -622,7 +623,7 @@ impl PlayerTrackLoader { info!("Loading <{}> with Spotify URI <{}>", audio.name, audio.uri); - let audio = match self.find_available_alternative(&audio) { + let audio = match self.find_available_alternative(&audio).await { Some(audio) => audio, None => { warn!("<{}> is not available", audio.uri); @@ -675,7 +676,7 @@ impl PlayerTrackLoader { play_from_beginning, ); - let encrypted_file = match encrypted_file.wait() { + let encrypted_file = match encrypted_file.await { Ok(encrypted_file) => encrypted_file, Err(_) => { error!("Unable to load encrypted file."); @@ -693,7 +694,7 @@ impl PlayerTrackLoader { stream_loader_controller.set_random_access_mode(); } - let key = match key.wait() { + let key = match key.await { Ok(key) => key, Err(_) => { error!("Unable to load decryption key"); @@ -709,7 +710,7 @@ impl PlayerTrackLoader { } Err(_) => { warn!("Unable to extract normalisation data, using default value."); - 1.0 as f32 + 1.0_f32 } }; @@ -738,10 +739,9 @@ impl PlayerTrackLoader { } impl Future for PlayerInternal { - type Item = (); - type Error = (); + type Output = (); - fn poll(&mut self) -> Poll<(), ()> { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { // While this is written as a future, it still contains blocking code. // It must be run on its own thread. @@ -749,14 +749,13 @@ impl Future for PlayerInternal { let mut all_futures_completed_or_not_ready = true; // process commands that were sent to us - let cmd = match self.commands.poll() { - Ok(Async::Ready(None)) => return Ok(Async::Ready(())), // client has disconnected - shut down. - Ok(Async::Ready(Some(cmd))) => { + let cmd = match Pin::new(&mut self.commands).poll_next(cx) { + Poll::Ready(None) => return Poll::Ready(()), // client has disconnected - shut down. + Poll::Ready(Some(cmd)) => { all_futures_completed_or_not_ready = false; Some(cmd) } - Ok(Async::NotReady) => None, - Err(_) => None, + _ => None, }; if let Some(cmd) = cmd { @@ -771,8 +770,8 @@ impl Future for PlayerInternal { play_request_id, } = self.state { - match loader.poll() { - Ok(Async::Ready(loaded_track)) => { + match loader.as_mut().poll(cx) { + Poll::Ready(Ok(loaded_track)) => { self.start_playback( track_id, play_request_id, @@ -783,8 +782,7 @@ impl Future for PlayerInternal { panic!("The state wasn't changed by start_playback()"); } } - Ok(Async::NotReady) => (), - Err(_) => { + Poll::Ready(Err(_)) => { warn!("Unable to load <{:?}>\nSkipping to next track", track_id); assert!(self.state.is_loading()); self.send_event(PlayerEvent::EndOfTrack { @@ -792,6 +790,7 @@ impl Future for PlayerInternal { play_request_id, }) } + Poll::Pending => (), } } @@ -801,16 +800,15 @@ impl Future for PlayerInternal { track_id, } = self.preload { - match loader.poll() { - Ok(Async::Ready(loaded_track)) => { + match loader.as_mut().poll(cx) { + Poll::Ready(Ok(loaded_track)) => { self.send_event(PlayerEvent::Preloading { track_id }); self.preload = PlayerPreload::Ready { track_id, - loaded_track, + loaded_track: Box::new(loaded_track), }; } - Ok(Async::NotReady) => (), - Err(_) => { + Poll::Ready(Err(_)) => { debug!("Unable to preload {:?}", track_id); self.preload = PlayerPreload::None; // Let Spirc know that the track was unavailable. @@ -827,6 +825,7 @@ impl Future for PlayerInternal { }); } } + Poll::Pending => (), } } @@ -847,8 +846,7 @@ impl Future for PlayerInternal { let packet = decoder.next_packet().expect("Vorbis error"); if let Some(ref packet) = packet { - *stream_position_pcm = - *stream_position_pcm + (packet.data().len() / 2) as u64; + *stream_position_pcm += (packet.data().len() / 2) as u64; let stream_position_millis = Self::position_pcm_to_ms(*stream_position_pcm); let notify_about_position = match *reported_nominal_start_time { @@ -858,11 +856,7 @@ impl Future for PlayerInternal { let lag = (Instant::now() - reported_nominal_start_time).as_millis() as i64 - stream_position_millis as i64; - if lag > 1000 { - true - } else { - false - } + lag > 1000 } }; if notify_about_position { @@ -918,11 +912,11 @@ impl Future for PlayerInternal { } if self.session.is_invalid() { - return Ok(Async::Ready(())); + return Poll::Ready(()); } if (!self.state.is_playing()) && all_futures_completed_or_not_ready { - return Ok(Async::NotReady); + return Poll::Pending; } } } @@ -1061,12 +1055,14 @@ impl PlayerInternal { fn handle_packet(&mut self, packet: Option, normalisation_factor: f32) { match packet { Some(mut packet) => { - if packet.data().len() > 0 { + if !packet.data().is_empty() { if let Some(ref editor) = self.audio_filter { editor.modify_stream(&mut packet.data_mut()) }; - if self.config.normalisation && normalisation_factor != 1.0 { + if self.config.normalisation + && (normalisation_factor - 1.0).abs() < f32::EPSILON + { for x in packet.data_mut().iter_mut() { *x = (*x as f32 * normalisation_factor) as i16; } @@ -1214,10 +1210,9 @@ impl PlayerInternal { loaded_track .stream_loader_controller .set_random_access_mode(); - let _ = loaded_track.decoder.seek(position_ms as i64); // This may be blocking. - // But most likely the track is fully - // loaded already because we played - // to the end of it. + let _ = tokio::task::block_in_place(|| { + loaded_track.decoder.seek(position_ms as i64) + }); loaded_track.stream_loader_controller.set_stream_mode(); loaded_track.stream_position_pcm = Self::position_ms_to_pcm(position_ms); } @@ -1250,7 +1245,7 @@ impl PlayerInternal { // we can use the current decoder. Ensure it's at the correct position. if Self::position_ms_to_pcm(position_ms) != *stream_position_pcm { stream_loader_controller.set_random_access_mode(); - let _ = decoder.seek(position_ms as i64); // This may be blocking. + let _ = tokio::task::block_in_place(|| decoder.seek(position_ms as i64)); stream_loader_controller.set_stream_mode(); *stream_position_pcm = Self::position_ms_to_pcm(position_ms); } @@ -1318,10 +1313,12 @@ impl PlayerInternal { loaded_track .stream_loader_controller .set_random_access_mode(); - let _ = loaded_track.decoder.seek(position_ms as i64); // This may be blocking + let _ = tokio::task::block_in_place(|| { + loaded_track.decoder.seek(position_ms as i64) + }); loaded_track.stream_loader_controller.set_stream_mode(); } - self.start_playback(track_id, play_request_id, loaded_track, play); + self.start_playback(track_id, play_request_id, *loaded_track, play); return; } else { unreachable!(); @@ -1363,9 +1360,7 @@ impl PlayerInternal { self.preload = PlayerPreload::None; // If we don't have a loader yet, create one from scratch. - let loader = loader - .or_else(|| Some(self.load_track(track_id, position_ms))) - .unwrap(); + let loader = loader.unwrap_or_else(|| Box::pin(self.load_track(track_id, position_ms))); // Set ourselves to a loading state. self.state = PlayerState::Loading { @@ -1420,7 +1415,10 @@ impl PlayerInternal { // schedule the preload of the current track if desired. if preload_track { let loader = self.load_track(track_id, 0); - self.preload = PlayerPreload::Loading { track_id, loader } + self.preload = PlayerPreload::Loading { + track_id, + loader: Box::pin(loader), + } } } @@ -1532,34 +1530,33 @@ impl PlayerInternal { } } - fn load_track( + pub fn load_track( &self, spotify_id: SpotifyId, position_ms: u32, - ) -> Box> { + ) -> impl Future> + Send + 'static { // This method creates a future that returns the loaded stream and associated info. // Ideally all work should be done using asynchronous code. However, seek() on the // audio stream is implemented in a blocking fashion. Thus, we can't turn it into future // easily. Instead we spawn a thread to do the work and return a one-shot channel as the // future to work with. - let loader = PlayerTrackLoader { - session: self.session.clone(), - config: self.config.clone(), - }; + let session = self.session.clone(); + let config = self.config.clone(); - let (result_tx, result_rx) = futures::sync::oneshot::channel(); + async move { + let loader = PlayerTrackLoader { session, config }; - std::thread::spawn(move || { - loader - .load_track(spotify_id, position_ms) - .and_then(move |data| { + let (result_tx, result_rx) = oneshot::channel(); + + tokio::spawn(async move { + if let Some(data) = loader.load_track(spotify_id, position_ms).await { let _ = result_tx.send(data); - Some(()) - }); - }); + } + }); - Box::new(result_rx.map_err(|_| ())) + result_rx.await.map_err(|_| ()) + } } fn preload_data_before_playback(&mut self) { @@ -1585,7 +1582,9 @@ impl PlayerInternal { * bytes_per_second as f64) as usize, (READ_AHEAD_BEFORE_PLAYBACK_SECONDS * bytes_per_second as f64) as usize, ); - stream_loader_controller.fetch_next_blocking(wait_for_data_length); + tokio::task::block_in_place(|| { + stream_loader_controller.fetch_next_blocking(wait_for_data_length) + }); } } } @@ -1689,13 +1688,13 @@ impl Subfile { } impl Read for Subfile { - fn read(&mut self, buf: &mut [u8]) -> Result { + fn read(&mut self, buf: &mut [u8]) -> io::Result { self.stream.read(buf) } } impl Seek for Subfile { - fn seek(&mut self, mut pos: SeekFrom) -> Result { + fn seek(&mut self, mut pos: SeekFrom) -> io::Result { pos = match pos { SeekFrom::Start(offset) => SeekFrom::Start(offset + self.offset), x => x, diff --git a/src/lib.rs b/src/lib.rs index 610062e2..4304e187 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,7 @@ #![crate_name = "librespot"] -#![cfg_attr(feature = "cargo-clippy", allow(unused_io_amount))] pub extern crate librespot_audio as audio; -pub extern crate librespot_connect as connect; +// pub extern crate librespot_connect as connect; pub extern crate librespot_core as core; pub extern crate librespot_metadata as metadata; pub extern crate librespot_playback as playback; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 6673310b..00000000 --- a/src/main.rs +++ /dev/null @@ -1,623 +0,0 @@ -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}; -use std::mem; -use std::path::PathBuf; -use std::process::exit; -use std::str::FromStr; -use std::time::Instant; -use tokio_io::IoStream; -use url::Url; - -use librespot::core::authentication::{get_credentials, Credentials}; -use librespot::core::cache::Cache; -use librespot::core::config::{ConnectConfig, DeviceType, SessionConfig, VolumeCtrl}; -use librespot::core::session::Session; -use librespot::core::version; - -use librespot::connect::discovery::{discovery, DiscoveryStream}; -use librespot::connect::spirc::{Spirc, SpircTask}; -use librespot::playback::audio_backend::{self, Sink, BACKENDS}; -use librespot::playback::config::{Bitrate, PlayerConfig}; -use librespot::playback::mixer::{self, Mixer, MixerConfig}; -use librespot::playback::player::{Player, PlayerEvent}; - -use tokio::runtime::{ - current_thread, - current_thread::{Handle, Runtime}, -}; - -mod player_event_handler; -use crate::player_event_handler::{emit_sink_event, run_program_on_events}; - -fn device_id(name: &str) -> String { - hex::encode(Sha1::digest(name.as_bytes())) -} - -fn usage(program: &str, opts: &getopts::Options) -> String { - let brief = format!("Usage: {} [options]", program); - opts.usage(&brief) -} - -fn setup_logging(verbose: bool) { - let mut builder = env_logger::Builder::new(); - match env::var("RUST_LOG") { - Ok(config) => { - builder.parse_filters(&config); - builder.init(); - - if verbose { - warn!("`--verbose` flag overidden by `RUST_LOG` environment variable"); - } - } - Err(_) => { - if verbose { - builder.parse_filters("libmdns=info,librespot=trace"); - } else { - builder.parse_filters("libmdns=info,librespot=info"); - } - builder.init(); - } - } -} - -fn list_backends() { - println!("Available Backends : "); - for (&(name, _), idx) in BACKENDS.iter().zip(0..) { - if idx == 0 { - println!("- {} (default)", name); - } else { - println!("- {}", name); - } - } -} - -#[derive(Clone)] -struct Setup { - backend: fn(Option) -> Box, - device: Option, - - mixer: fn(Option) -> Box, - - cache: Option, - player_config: PlayerConfig, - session_config: SessionConfig, - connect_config: ConnectConfig, - mixer_config: MixerConfig, - credentials: Option, - enable_discovery: bool, - zeroconf_port: u16, - player_event_program: Option, - emit_sink_events: bool, -} - -fn setup(args: &[String]) -> Setup { - let mut opts = getopts::Options::new(); - opts.optopt( - "c", - "cache", - "Path to a directory where files will be cached.", - "CACHE", - ).optopt( - "", - "system-cache", - "Path to a directory where system files (credentials, volume) will be cached. Can be different from cache option value", - "SYTEMCACHE", - ).optflag("", "disable-audio-cache", "Disable caching of the audio data.") - .reqopt("n", "name", "Device name", "NAME") - .optopt("", "device-type", "Displayed device type", "DEVICE_TYPE") - .optopt( - "b", - "bitrate", - "Bitrate (96, 160 or 320). Defaults to 160", - "BITRATE", - ) - .optopt( - "", - "onevent", - "Run PROGRAM when playback is about to begin.", - "PROGRAM", - ) - .optflag("", "emit-sink-events", "Run program set by --onevent before sink is opened and after it is closed.") - .optflag("v", "verbose", "Enable verbose output") - .optopt("u", "username", "Username to sign in with", "USERNAME") - .optopt("p", "password", "Password", "PASSWORD") - .optopt("", "proxy", "HTTP proxy to use when connecting", "PROXY") - .optopt("", "ap-port", "Connect to AP with specified port. If no AP with that port are present fallback AP will be used. Available ports are usually 80, 443 and 4070", "AP_PORT") - .optflag("", "disable-discovery", "Disable discovery mode") - .optopt( - "", - "backend", - "Audio backend to use. Use '?' to list options", - "BACKEND", - ) - .optopt( - "", - "device", - "Audio device to use. Use '?' to list options if using portaudio or alsa", - "DEVICE", - ) - .optopt("", "mixer", "Mixer to use (alsa or softvol)", "MIXER") - .optopt( - "m", - "mixer-name", - "Alsa mixer name, e.g \"PCM\" or \"Master\". Defaults to 'PCM'", - "MIXER_NAME", - ) - .optopt( - "", - "mixer-card", - "Alsa mixer card, e.g \"hw:0\" or similar from `aplay -l`. Defaults to 'default' ", - "MIXER_CARD", - ) - .optopt( - "", - "mixer-index", - "Alsa mixer index, Index of the cards mixer. Defaults to 0", - "MIXER_INDEX", - ) - .optflag( - "", - "mixer-linear-volume", - "Disable alsa's mapped volume scale (cubic). Default false", - ) - .optopt( - "", - "initial-volume", - "Initial volume in %, once connected (must be from 0 to 100)", - "VOLUME", - ) - .optopt( - "", - "zeroconf-port", - "The port the internal server advertised over zeroconf uses.", - "ZEROCONF_PORT", - ) - .optflag( - "", - "enable-volume-normalisation", - "Play all tracks at the same volume", - ) - .optopt( - "", - "normalisation-pregain", - "Pregain (dB) applied by volume normalisation", - "PREGAIN", - ) - .optopt( - "", - "volume-ctrl", - "Volume control type - [linear, log, fixed]. Default is logarithmic", - "VOLUME_CTRL" - ) - .optflag( - "", - "autoplay", - "autoplay similar songs when your music ends.", - ) - .optflag( - "", - "disable-gapless", - "disable gapless playback.", - ); - - let matches = match opts.parse(&args[1..]) { - Ok(m) => m, - Err(f) => { - writeln!( - stderr(), - "error: {}\n{}", - f.to_string(), - usage(&args[0], &opts) - ) - .unwrap(); - exit(1); - } - }; - - let verbose = matches.opt_present("verbose"); - setup_logging(verbose); - - info!( - "librespot {} ({}). Built on {}. Build ID: {}", - version::short_sha(), - version::commit_date(), - version::short_now(), - version::build_id() - ); - - let backend_name = matches.opt_str("backend"); - if backend_name == Some("?".into()) { - list_backends(); - exit(0); - } - - let backend = audio_backend::find(backend_name).expect("Invalid backend"); - - let device = matches.opt_str("device"); - if device == Some("?".into()) { - backend(device); - exit(0); - } - - let mixer_name = matches.opt_str("mixer"); - 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")), - mixer: matches.opt_str("mixer-name").unwrap_or(String::from("PCM")), - index: matches - .opt_str("mixer-index") - .map(|index| index.parse::().unwrap()) - .unwrap_or(0), - mapped_volume: !matches.opt_present("mixer-linear-volume"), - }; - - let cache = matches.opt_str("c").map(|cache_path| { - let use_audio_cache = !matches.opt_present("disable-audio-cache"); - let system_cache_directory = matches - .opt_str("system-cache") - .unwrap_or(String::from(cache_path.clone())); - - Cache::new( - PathBuf::from(cache_path), - PathBuf::from(system_cache_directory), - use_audio_cache, - ) - }); - - let initial_volume = matches - .opt_str("initial-volume") - .map(|volume| { - let volume = volume.parse::().unwrap(); - if volume > 100 { - panic!("Initial volume must be in the range 0-100"); - } - (volume as i32 * 0xFFFF / 100) as u16 - }) - .or_else(|| cache.as_ref().and_then(Cache::volume)) - .unwrap_or(0x8000); - - let zeroconf_port = matches - .opt_str("zeroconf-port") - .map(|port| port.parse::().unwrap()) - .unwrap_or(0); - - let name = matches.opt_str("name").unwrap(); - - let credentials = { - let cached_credentials = cache.as_ref().and_then(Cache::credentials); - - let password = |username: &String| -> String { - write!(stderr(), "Password for {}: ", username).unwrap(); - stderr().flush().unwrap(); - rpassword::read_password().unwrap() - }; - - get_credentials( - matches.opt_str("username"), - matches.opt_str("password"), - cached_credentials, - password, - ) - }; - - let session_config = { - let device_id = device_id(&name); - - SessionConfig { - user_agent: version::version_string(), - device_id: device_id, - proxy: matches.opt_str("proxy").or(std::env::var("http_proxy").ok()).map( - |s| { - match Url::parse(&s) { - Ok(url) => { - if url.host().is_none() || url.port_or_known_default().is_none() { - panic!("Invalid proxy url, only urls on the format \"http://host:port\" are allowed"); - } - - if url.scheme() != "http" { - panic!("Only unsecure http:// proxies are supported"); - } - url - }, - Err(err) => panic!("Invalid proxy url: {}, only urls on the format \"http://host:port\" are allowed", err) - } - }, - ), - ap_port: matches - .opt_str("ap-port") - .map(|port| port.parse::().expect("Invalid port")), - } - }; - - let player_config = { - let bitrate = matches - .opt_str("b") - .as_ref() - .map(|bitrate| Bitrate::from_str(bitrate).expect("Invalid bitrate")) - .unwrap_or(Bitrate::default()); - PlayerConfig { - bitrate: bitrate, - gapless: !matches.opt_present("disable-gapless"), - normalisation: matches.opt_present("enable-volume-normalisation"), - normalisation_pregain: matches - .opt_str("normalisation-pregain") - .map(|pregain| pregain.parse::().expect("Invalid pregain float value")) - .unwrap_or(PlayerConfig::default().normalisation_pregain), - } - }; - - let connect_config = { - let device_type = matches - .opt_str("device-type") - .as_ref() - .map(|device_type| DeviceType::from_str(device_type).expect("Invalid device type")) - .unwrap_or(DeviceType::default()); - - let volume_ctrl = matches - .opt_str("volume-ctrl") - .as_ref() - .map(|volume_ctrl| VolumeCtrl::from_str(volume_ctrl).expect("Invalid volume ctrl type")) - .unwrap_or(VolumeCtrl::default()); - - ConnectConfig { - name: name, - device_type: device_type, - volume: initial_volume, - volume_ctrl: volume_ctrl, - autoplay: matches.opt_present("autoplay"), - } - }; - - let enable_discovery = !matches.opt_present("disable-discovery"); - - Setup { - backend: backend, - cache: cache, - session_config: session_config, - player_config: player_config, - connect_config: connect_config, - credentials: credentials, - device: device, - enable_discovery: enable_discovery, - zeroconf_port: zeroconf_port, - mixer: mixer, - mixer_config: mixer_config, - player_event_program: matches.opt_str("onevent"), - emit_sink_events: matches.opt_present("emit-sink-events"), - } -} - -struct Main { - cache: Option, - player_config: PlayerConfig, - session_config: SessionConfig, - connect_config: ConnectConfig, - backend: fn(Option) -> Box, - device: Option, - mixer: fn(Option) -> Box, - mixer_config: MixerConfig, - handle: Handle, - discovery: Option, - signal: IoStream<()>, - - spirc: Option, - spirc_task: Option, - connect: Box>, - - shutdown: bool, - last_credentials: Option, - auto_connect_times: Vec, - - player_event_channel: Option>, - player_event_program: Option, - emit_sink_events: bool, -} - -impl Main { - fn new(handle: Handle, setup: Setup) -> Main { - let mut task = Main { - handle: handle, - cache: setup.cache, - session_config: setup.session_config, - player_config: setup.player_config, - connect_config: setup.connect_config, - backend: setup.backend, - device: setup.device, - mixer: setup.mixer, - mixer_config: setup.mixer_config, - - connect: Box::new(futures::future::empty()), - discovery: None, - 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, - player_event_program: setup.player_event_program, - emit_sink_events: setup.emit_sink_events, - }; - - // if setup.enable_discovery { - // let config = task.connect_config.clone(); - // let device_id = task.session_config.device_id.clone(); - // - // task.discovery = Some(discovery(config, device_id, setup.zeroconf_port).unwrap()); - // } - - if let Some(credentials) = setup.credentials { - task.credentials(credentials); - } - - task - } - - fn credentials(&mut self, credentials: Credentials) { - self.last_credentials = Some(credentials.clone()); - let config = self.session_config.clone(); - let handle = self.handle.clone(); - let connection = Session::connect(config, credentials, self.cache.clone(), handle); - - self.connect = connection; - self.spirc = None; - let task = mem::replace(&mut self.spirc_task, None); - if let Some(task) = task { - current_thread::spawn(Box::new(task)); - } - } -} - -impl Future for Main { - type Item = (); - type Error = (); - - fn poll(&mut self) -> Poll<(), ()> { - loop { - let mut progress = false; - - 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; - } - - 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) - }); - - if self.emit_sink_events { - if let Some(player_event_program) = &self.player_event_program { - let player_event_program = player_event_program.clone(); - player.set_sink_event_callback(Some(Box::new(move |sink_status| { - emit_sink_event(sink_status, &player_event_program) - }))); - } - } - - 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; - } - 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() { - trace!("Ctrl-C received"); - if !self.shutdown { - if let Some(ref spirc) = self.spirc { - spirc.shutdown(); - } else { - return Ok(Async::Ready(())); - } - self.shutdown = true; - } else { - return Ok(Async::Ready(())); - } - - 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 { - 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); - } - } - } - - if let Some(ref mut player_event_channel) = self.player_event_channel { - if let Async::Ready(Some(event)) = player_event_channel.poll().unwrap() { - progress = true; - if let Some(ref program) = self.player_event_program { - if let Some(child) = run_program_on_events(event, program) { - let child = child - .expect("program failed to start") - .map(|status| { - if !status.success() { - error!("child exited with status {:?}", status.code()); - } - }) - .map_err(|e| error!("failed to wait on child process: {}", e)); - - current_thread::spawn(child); - } - } - } - } - - if !progress { - return Ok(Async::NotReady); - } - } - } -} - -fn main() { - if env::var("RUST_BACKTRACE").is_err() { - env::set_var("RUST_BACKTRACE", "full") - } - - let args: Vec = std::env::args().collect(); - - let mut runtime = Runtime::new().unwrap(); - let handle = runtime.handle(); - runtime.block_on(Main::new(handle, setup(&args))).unwrap(); - runtime.run().unwrap(); - // current_thread::block_on_all(Main::new(setup(&args))).unwrap() -}