diff --git a/Cargo.lock b/Cargo.lock index 07e95e80..fa696ffc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,42 +162,51 @@ dependencies = [ [[package]] name = "anstream" -version = "0.2.6" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +checksum = "9e579a7752471abc2a8268df8b20005e3eadd975f585398f17efcfd8d4927371" dependencies = [ "anstyle", "anstyle-parse", + "anstyle-query", "anstyle-wincon", - "concolor-override", - "concolor-query", + "colorchoice", "is-terminal", "utf8parse", ] [[package]] name = "anstyle" -version = "0.3.5" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" [[package]] name = "anstyle-parse" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" dependencies = [ "utf8parse", ] +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + [[package]] name = "anstyle-wincon" -version = "0.2.0" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +checksum = "4bcd8291a340dd8ac70e18878bc4501dd7b4ff970cfa21c207d36ece51ea88fd" dependencies = [ "anstyle", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -226,9 +235,9 @@ checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" [[package]] name = "async-stream" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad445822218ce64be7a341abfb0b1ea43b5c23aa83902542a4542e78309d8e5e" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", @@ -237,13 +246,13 @@ dependencies = [ [[package]] name = "async-stream-impl" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] @@ -254,7 +263,7 @@ checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn 2.0.11", + "syn 2.0.15", ] [[package]] @@ -273,7 +282,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide", + "miniz_oxide 0.6.2", "object", "rustc-demangle", ] @@ -481,9 +490,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77ed9a53e5d4d9c573ae844bfac6872b159cb1d1585a83b29e7a64b7eef7332a" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", @@ -492,18 +501,20 @@ dependencies = [ [[package]] name = "clap" -version = "4.2.0" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6efb5f0a41b5ef5b50c5da28c07609c20091df0c1fc33d418fa2a7e693c2b624" +checksum = "956ac1f6381d8d82ab4684768f89c0ea3afe66925ceadb4eeb3fc452ffc55d62" dependencies = [ "clap_builder", + "clap_derive", + "once_cell", ] [[package]] name = "clap_builder" -version = "4.2.0" +version = "4.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "671fcaa5debda4b9a84aa7fde49c907c8986c0e6ab927e04217c9cb74e7c8bc9" +checksum = "84080e799e54cff944f4b4a4b0e71630b0e0443b25b985175c7dddc1a859b749" dependencies = [ "anstream", "anstyle", @@ -512,6 +523,18 @@ dependencies = [ "strsim", ] +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.15", +] + [[package]] name = "clap_lex" version = "0.4.1" @@ -565,6 +588,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "combine" version = "4.6.6" @@ -575,21 +604,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "concolor-override" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" - -[[package]] -name = "concolor-query" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" -dependencies = [ - "windows-sys 0.45.0", -] - [[package]] name = "config_parser2" version = "0.1.4" @@ -637,9 +651,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "core-graphics" @@ -731,9 +745,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.7" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -872,7 +886,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn 2.0.11", + "syn 2.0.15", ] [[package]] @@ -889,7 +903,7 @@ checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.11", + "syn 2.0.15", ] [[package]] @@ -1056,13 +1070,13 @@ dependencies = [ [[package]] name = "errno" -version = "0.2.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -1085,7 +1099,7 @@ dependencies = [ "flume", "half", "lebe", - "miniz_oxide", + "miniz_oxide 0.6.2", "rayon-core", "smallvec", "zune-inflate", @@ -1100,6 +1114,15 @@ dependencies = [ "instant", ] +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + [[package]] name = "fixedbitset" version = "0.4.2" @@ -1113,7 +1136,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" dependencies = [ "crc32fast", - "miniz_oxide", + "miniz_oxide 0.6.2", ] [[package]] @@ -1171,9 +1194,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531ac96c6ff5fd7c62263c5e3c67a603af4fcaee2e1a0ae5565ba3a11e69e549" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -1186,9 +1209,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "164713a5a0dcc3e7b4b1ed7d3b433cabc18025386f9339346e8daf15963cf7ac" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -1196,15 +1219,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86d7a0c1aa76363dac491de0ee99faf6941128376f1cf96f07db7603b7de69dd" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1997dd9df74cdac935c76252744c1ed5794fac083242ea4fe77ef3ed60ba0f83" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -1213,38 +1236,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d422fa3cbe3b40dca574ab087abb5bc98258ea57eea3fd6f1fa7162c778b91" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3eb14ed937631bd8b8b8977f2c198443447a8355b6e3ca599f38c975e5a963b6" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] name = "futures-sink" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec93083a4aecafb2a80a885c9de1f0ccae9dbd32c2bb54b0c3a65690e0b8d2f2" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd65540d33b37b16542a0438c12e6aeead10d4ac5d05bd3f805b8f35ab592879" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.27" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ef6b17e481503ec85211fed8f39d1970f128935ca1f814cd32ac4a6842e84ab" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -1270,9 +1293,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", "js-sys", @@ -1485,9 +1508,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.3.16" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d" +checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" dependencies = [ "bytes", "fnv", @@ -1658,9 +1681,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.25" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc5e554ff619822309ffd57d8734d77cd5ce6238bc956f037ea06c58238c9899" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -1710,16 +1733,16 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.54" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c17cc76786e99f8d2f055c11159e7f0091c42474dcc3189fbab96072e873e6d" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows 0.46.0", + "windows 0.48.0", ] [[package]] @@ -1801,13 +1824,13 @@ dependencies = [ [[package]] name = "io-lifetimes" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" dependencies = [ "hermit-abi 0.3.1", "libc", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1818,14 +1841,14 @@ checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "is-terminal" -version = "0.4.5" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8687c819457e979cc940d09cb16e42a1bf70aa6b60a549de6d3a62a0ee90c69e" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ "hermit-abi 0.3.1", "io-lifetimes", "rustix", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -1962,15 +1985,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.140" +version = "0.2.142" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" +checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" [[package]] name = "libdbus-sys" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f8d7ae751e1cb825c840ae5e682f59b098cdfd213c350ac268b61449a5f58a0" +checksum = "06085512b750d640299b79be4bad3d2fa90a9c00b1fd9e1b46364f66f0485c72" dependencies = [ "pkg-config", ] @@ -2241,9 +2264,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "9b085a4f2cde5781fc4b1717f2e86c62f5cda49de7ba99a7c2eae02b61c9064c" [[package]] name = "lock_api" @@ -2436,6 +2459,16 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "simd-adler32", +] + [[package]] name = "mio" version = "0.8.6" @@ -2758,9 +2791,9 @@ checksum = "df3b9834c1e95694a05a828b59f55fa2afec6288359cda67146126b3f90a55d7" [[package]] name = "objc2" -version = "0.3.0-beta.3.patch-leaks.2" +version = "0.3.0-beta.3.patch-leaks.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7d9bb2ee6b71d02b1b3554ed600d267ee9a2796acc9fa43fb7748e13fe072dd" +checksum = "7e01640f9f2cb1220bbe80325e179e532cb3379ebcd1bf2279d703c19fe3a468" dependencies = [ "block2", "objc-sys", @@ -2840,9 +2873,9 @@ checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "openssl" -version = "0.10.48" +version = "0.10.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518915b97df115dd36109bfa429a48b8f737bd05508cf9588977b599648926d2" +checksum = "7e30d8bc91859781f0a943411186324d580f2bbeb71b452fe91ae344806af3f1" dependencies = [ "bitflags", "cfg-if", @@ -2855,13 +2888,13 @@ dependencies = [ [[package]] name = "openssl-macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.15", ] [[package]] @@ -2872,11 +2905,10 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.83" +version = "0.9.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "666416d899cf077260dac8698d60a60b435a46d57e82acb1be3d0dad87284e5b" +checksum = "0d3d193fb1488ad46ffe3aaabc912cc931d02ee8518fe2959aea8ef52718b0c0" dependencies = [ - "autocfg", "cc", "libc", "pkg-config", @@ -2894,9 +2926,9 @@ dependencies = [ [[package]] name = "orbclient" -version = "0.3.43" +version = "0.3.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "974465c5e83cf9df05c1e4137b271d29035c902e39e5ad4c1939837e22160af8" +checksum = "0e9829e16c5e112e94efb5e2ad1fe17f8c1c99bb0fcdc8c65c44e935d904767d" dependencies = [ "cfg-if", "redox_syscall 0.2.16", @@ -3086,14 +3118,15 @@ checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] name = "png" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +checksum = "aaeebc51f9e7d2c150d3f3bfeb667f2aa985db5ef1e3d212847bdedb488beeaa" dependencies = [ "bitflags", "crc32fast", + "fdeflate", "flate2", - "miniz_oxide", + "miniz_oxide 0.7.1", ] [[package]] @@ -3181,9 +3214,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.54" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] @@ -3282,9 +3315,9 @@ dependencies = [ [[package]] name = "raw-window-handle" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f851a03551ceefd30132e447f07f96cb7011d6b658374f3aed847333adb5559" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "rayon" @@ -3500,9 +3533,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4a36c42d1873f9a77c53bde094f9664d9891bc604a45b4798fd2c389ed12e5b" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" @@ -3527,16 +3560,16 @@ dependencies = [ [[package]] name = "rustix" -version = "0.36.11" +version = "0.37.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db4165c9963ab29e422d6c26fbc1d37f15bace6b2810221f9d925023480fcf0e" +checksum = "f79bef90eb6d984c72722595b5b1348ab39275a5e5123faca6863bf07d75a4e0" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.45.0", + "windows-sys 0.48.0", ] [[package]] @@ -3589,9 +3622,9 @@ checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "sctk-adwaita" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc56402866c717f54e48b122eb93c69f709bc5a6359c403598992fd92f017931" +checksum = "cda4e97be1fd174ccc2aae81c8b694e803fa99b34e8fd0f057a9d70698e3ed09" dependencies = [ "ab_glyph", "log", @@ -3654,29 +3687,29 @@ checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" [[package]] name = "serde" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.159" +version = "1.0.160" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" dependencies = [ "proc-macro2", "quote", - "syn 2.0.11", + "syn 2.0.15", ] [[package]] name = "serde_json" -version = "1.0.95" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ "itoa", "ryu", @@ -3900,9 +3933,9 @@ dependencies = [ [[package]] name = "spin" -version = "0.9.7" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0959fd6f767df20b231736396e4f602171e00d95205676286e79d4a4eb67bef" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" dependencies = [ "lock_api", ] @@ -4055,9 +4088,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.11" +version = "2.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e3787bb71465627110e7d87ed4faaa36c1f61042ee67badb9e2ef173accc40" +checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" dependencies = [ "proc-macro2", "quote", @@ -4090,15 +4123,15 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.4.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95" +checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.2.16", + "redox_syscall 0.3.5", "rustix", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -4138,7 +4171,7 @@ checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.11", + "syn 2.0.15", ] [[package]] @@ -4266,7 +4299,7 @@ checksum = "61a573bdc87985e9d6ddeed1b3d864e8a302c847e40d647746df2f1de209d1ce" dependencies = [ "proc-macro2", "quote", - "syn 2.0.11", + "syn 2.0.15", ] [[package]] @@ -4507,9 +4540,9 @@ checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" [[package]] name = "uuid" -version = "1.3.0" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" +checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb" dependencies = [ "getrandom", ] @@ -4809,16 +4842,16 @@ version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", ] [[package]] name = "windows" -version = "0.46.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", ] [[package]] @@ -4827,12 +4860,12 @@ version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] @@ -4842,7 +4875,16 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets", + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", ] [[package]] @@ -4851,21 +4893,42 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.42.2", "windows_aarch64_msvc 0.42.2", "windows_i686_gnu 0.42.2", "windows_i686_msvc 0.42.2", "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.42.2", "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows-targets" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +dependencies = [ + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" + [[package]] name = "windows_aarch64_msvc" version = "0.39.0" @@ -4878,6 +4941,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" + [[package]] name = "windows_i686_gnu" version = "0.39.0" @@ -4890,6 +4959,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +[[package]] +name = "windows_i686_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" + [[package]] name = "windows_i686_msvc" version = "0.39.0" @@ -4902,6 +4977,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +[[package]] +name = "windows_i686_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" + [[package]] name = "windows_x86_64_gnu" version = "0.39.0" @@ -4914,12 +4995,24 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" + [[package]] name = "windows_x86_64_gnullvm" version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" + [[package]] name = "windows_x86_64_msvc" version = "0.39.0" @@ -4932,6 +5025,12 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + [[package]] name = "winit" version = "0.28.3" diff --git a/spotify_player/Cargo.toml b/spotify_player/Cargo.toml index b4fb4fd1..4fb9a4f6 100644 --- a/spotify_player/Cargo.toml +++ b/spotify_player/Cargo.toml @@ -11,7 +11,7 @@ readme = "../README.md" [dependencies] anyhow = "1.0.70" -clap = "4.2.0" +clap = { version = "4.2.0", features = ["derive"] } config_parser2 = "0.1.4" crossterm = "0.26.1" dirs-next = "2.0.0" diff --git a/spotify_player/src/cli/commands.rs b/spotify_player/src/cli/commands.rs new file mode 100644 index 00000000..46c951b8 --- /dev/null +++ b/spotify_player/src/cli/commands.rs @@ -0,0 +1,67 @@ +use clap::{builder::EnumValueParser, value_parser, Arg, Command}; + +use super::{ContextType, Key}; + +pub fn init_get_subcommand() -> Command { + Command::new("get") + .about("Get spotify data") + .subcommand_required(true) + .subcommand( + Command::new("key").about("Get data by key").arg( + Arg::new("key") + .value_parser(EnumValueParser::::new()) + .required(true), + ), + ) + .subcommand( + Command::new("context") + .about("Get context data") + .arg( + Arg::new("context_type") + .value_parser(EnumValueParser::::new()) + .required(true), + ) + .arg(Arg::new("context_id").required(true)), + ) +} + +fn init_playback_start_subcommand() -> Command { + Command::new("start") + .about("Start a context playback") + .arg( + Arg::new("context_type") + .value_parser(EnumValueParser::::new()) + .required(true), + ) + .arg(Arg::new("context_id").required(true)) +} + +pub fn init_playback_subcommand() -> Command { + Command::new("playback") + .about("Interact with the playback") + .subcommand_required(true) + .subcommand(init_playback_start_subcommand()) + .subcommand(Command::new("play-pause").about("Toggle between play and pause")) + .subcommand(Command::new("next").about("Skip to the next track")) + .subcommand(Command::new("previous").about("Skip to the previous track")) + .subcommand(Command::new("shuffle").about("Toggle the shuffle mode")) + .subcommand(Command::new("repeat").about("Cycle the repeat mode")) + .subcommand( + Command::new("volume") + .about("Set the volume percentage") + .arg( + Arg::new("percent") + .value_parser(value_parser!(u8).range(0..=100)) + .required(true), + ), + ) + .subcommand( + Command::new("seek") + .about("Seek by an offset milliseconds") + .arg( + Arg::new("position_offset_ms") + .value_parser(value_parser!(i32)) + .required(true), + ), + ) +} diff --git a/spotify_player/src/cli/handlers.rs b/spotify_player/src/cli/handlers.rs new file mode 100644 index 00000000..95d88adf --- /dev/null +++ b/spotify_player/src/cli/handlers.rs @@ -0,0 +1,102 @@ +use super::*; +use anyhow::Result; +use clap::ArgMatches; +use std::net::UdpSocket; + +fn receive_data(socket: &UdpSocket) -> Result> { + // read response from the server's socket, which can be splitted into + // smaller chunks of data + let mut data = Vec::new(); + let mut buf = [0; 4096]; + loop { + let (n_bytes, _) = socket.recv_from(&mut buf)?; + if n_bytes == 0 { + // end of chunk + break; + } + data.extend_from_slice(&buf[..n_bytes]); + } + + Ok(data) +} + +fn handle_get_subcommand(args: &ArgMatches, socket: UdpSocket) -> Result<()> { + let (cmd, args) = args.subcommand().expect("playback subcommand is required"); + + let request = match cmd { + "key" => { + let key = args + .get_one::("key") + .expect("key is required") + .to_owned(); + Request::Get(GetRequest::Key(key)) + } + "context" => { + let context_id = args + .get_one::("context_id") + .expect("context_id is required") + .to_owned(); + let context_type = args + .get_one::("context_type") + .expect("context_type is required") + .to_owned(); + Request::Get(GetRequest::Context(context_id, context_type)) + } + _ => unreachable!(), + }; + + socket.send(&serde_json::to_vec(&request)?)?; + let data = receive_data(&socket)?; + println!("{}", String::from_utf8_lossy(&data)); + + Ok(()) +} + +fn handle_playback_subcommand(args: &ArgMatches, socket: UdpSocket) -> Result<()> { + let (cmd, args) = args.subcommand().expect("playback subcommand is required"); + let command = match cmd { + "start" => { + let context_id = args + .get_one::("context_id") + .expect("context_id is required"); + let context_type = args + .get_one::("context_type") + .expect("context_type is required"); + Command::Start(context_id.to_owned(), context_type.to_owned()) + } + "play-pause" => Command::PlayPause, + "next" => Command::Next, + "previous" => Command::Previous, + "shuffle" => Command::Shuffle, + "repeat" => Command::Repeat, + "volume" => { + let percent = args + .get_one::("percent") + .expect("percent arg is required"); + Command::Volume(*percent) + } + "seek" => { + let position_offset_ms = args + .get_one::("position_offset_ms") + .expect("position_offset_ms is required"); + Command::Seek(*position_offset_ms) + } + _ => unreachable!(), + }; + + let request = Request::Playback(command); + socket.send(&serde_json::to_vec(&request)?)?; + + Ok(()) +} + +pub fn handle_cli_subcommand(cmd: &str, args: &ArgMatches, client_port: u16) -> Result<()> { + let socket = UdpSocket::bind("127.0.0.1:0")?; + socket.connect(("127.0.0.1", client_port))?; + + match cmd { + "get" => handle_get_subcommand(args, socket), + "playback" => handle_playback_subcommand(args, socket), + _ => unreachable!(), + } +} diff --git a/spotify_player/src/cli/mod.rs b/spotify_player/src/cli/mod.rs new file mode 100644 index 00000000..af006a53 --- /dev/null +++ b/spotify_player/src/cli/mod.rs @@ -0,0 +1,52 @@ +mod commands; +mod handlers; +mod socket; + +use serde::{Deserialize, Serialize}; + +pub use commands::{init_get_subcommand, init_playback_subcommand}; +pub use handlers::handle_cli_subcommand; +pub use socket::start_socket; + +#[derive(Debug, Serialize, Deserialize, clap::ValueEnum, Clone)] +pub enum Key { + Playback, + Devices, + UserPlaylists, + UserLikedTracks, + UserSavedAlbums, + UserFollowedArtists, + UserTopTracks, + Queue, +} + +#[derive(Debug, Serialize, Deserialize, clap::ValueEnum, Clone)] +pub enum ContextType { + Playlist, + Album, + Artist, +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum GetRequest { + Key(Key), + Context(String, ContextType), +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Command { + Start(String, ContextType), + PlayPause, + Next, + Previous, + Shuffle, + Repeat, + Volume(u8), + Seek(i32), +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum Request { + Get(GetRequest), + Playback(Command), +} diff --git a/spotify_player/src/cli/socket.rs b/spotify_player/src/cli/socket.rs new file mode 100644 index 00000000..b64b323b --- /dev/null +++ b/spotify_player/src/cli/socket.rs @@ -0,0 +1,218 @@ +use std::net::SocketAddr; + +use anyhow::Result; +use tokio::net::UdpSocket; + +use rspotify::{model::*, prelude::OAuthClient}; + +use crate::{ + cli::{ContextType, Request}, + client::Client, + state::SharedState, +}; + +use super::*; + +pub async fn start_socket(client: Client, state: SharedState) -> Result<()> { + let port = state.app_config.client_port; + tracing::info!("Starting a client socket at 127.0.0.1:{port}"); + + let socket = UdpSocket::bind(("127.0.0.1", port)).await?; + + // initialize the receive buffer to be 4096 bytes + let mut buf = [0; 4096]; + loop { + match socket.recv_from(&mut buf).await { + Err(err) => tracing::warn!("failed to receive from the socket: {err}"), + Ok((n_bytes, dest_addr)) => { + let request: Request = serde_json::from_slice(&buf[0..n_bytes])?; + tracing::info!("Handle socket request: {request:?}"); + handle_socket_request(&client, &state, request, &socket, dest_addr).await?; + } + } + } +} + +async fn send_data(data: Vec, socket: &UdpSocket, dest_addr: SocketAddr) -> Result<()> { + // as the result data can be large and may not be sent in a single UDP datagram, + // split it into smaller chunks + for chunk in data.chunks(4096) { + socket.send_to(chunk, dest_addr).await?; + } + // send an empty data at the end to indicate end of chunks + socket.send_to(&[], dest_addr).await?; + Ok(()) +} + +async fn handle_socket_request( + client: &Client, + state: &SharedState, + request: super::Request, + socket: &UdpSocket, + dest_addr: SocketAddr, +) -> Result<()> { + match request { + Request::Get(GetRequest::Key(key)) => { + let result = handle_get_key_request(client, key).await?; + send_data(result, socket, dest_addr).await?; + } + Request::Get(GetRequest::Context(context_id, context_type)) => { + let result = handle_get_context_request(client, context_id, context_type).await?; + send_data(result, socket, dest_addr).await?; + } + Request::Playback(command) => { + handle_playback_request(client, command).await?; + client.update_playback(state); + } + } + Ok(()) +} + +async fn handle_get_key_request(client: &Client, key: Key) -> Result> { + Ok(match key { + Key::Playback => { + let playback = client + .spotify + .current_playback(None, None::>) + .await?; + serde_json::to_vec(&playback)? + } + Key::Devices => { + let devices = client.spotify.device().await?; + serde_json::to_vec(&devices)? + } + Key::UserPlaylists => { + let playlists = client.current_user_playlists().await?; + serde_json::to_vec(&playlists)? + } + Key::UserLikedTracks => { + let tracks = client.current_user_saved_tracks().await?; + serde_json::to_vec(&tracks)? + } + Key::UserTopTracks => { + let tracks = client.current_user_top_tracks().await?; + serde_json::to_vec(&tracks)? + } + Key::UserSavedAlbums => { + let albums = client.current_user_saved_albums().await?; + serde_json::to_vec(&albums)? + } + Key::UserFollowedArtists => { + let artists = client.current_user_followed_artists().await?; + serde_json::to_vec(&artists)? + } + Key::Queue => { + let queue = client.spotify.current_user_queue().await?; + serde_json::to_vec(&queue)? + } + }) +} + +async fn handle_get_context_request( + client: &Client, + context_id: String, + context_type: ContextType, +) -> Result> { + let context = match context_type { + ContextType::Playlist => { + let id = PlaylistId::from_id(context_id)?; + client.playlist_context(id).await? + } + ContextType::Album => { + let id = AlbumId::from_id(context_id)?; + client.album_context(id).await? + } + ContextType::Artist => { + let id = ArtistId::from_id(context_id)?; + client.artist_context(id).await? + } + }; + + Ok(serde_json::to_vec(&context)?) +} + +async fn handle_playback_request(client: &Client, command: Command) -> Result<()> { + let playback = match client + .spotify + .current_playback(None, None::>) + .await? + { + Some(playback) => playback, + None => { + eprintln!("No playback found!"); + std::process::exit(1); + } + }; + let device_id = playback.device.id.as_deref(); + + match command { + Command::Start(context_id, context_type) => { + let context_id = match context_type { + ContextType::Playlist => PlayContextId::Playlist(PlaylistId::from_id(context_id)?), + ContextType::Album => PlayContextId::Album(AlbumId::from_id(context_id)?), + ContextType::Artist => PlayContextId::Artist(ArtistId::from_id(context_id)?), + }; + + client + .spotify + .start_context_playback(context_id, device_id, None, None) + .await?; + + // for some reasons, when starting a new playback, the integrated `spotify-player` + // client doesn't respect the initial shuffle state, so we need to manually update the state + client + .spotify + .shuffle(playback.shuffle_state, device_id) + .await? + } + Command::PlayPause => { + if playback.is_playing { + client.spotify.pause_playback(device_id).await?; + } else { + client.spotify.resume_playback(device_id, None).await?; + } + } + Command::Next => { + client.spotify.next_track(device_id).await?; + } + Command::Previous => { + client.spotify.previous_track(device_id).await?; + } + Command::Shuffle => { + client + .spotify + .shuffle(!playback.shuffle_state, device_id) + .await?; + } + Command::Repeat => { + let next_repeat_state = match playback.repeat_state { + RepeatState::Off => RepeatState::Track, + RepeatState::Track => RepeatState::Context, + RepeatState::Context => RepeatState::Off, + }; + + client.spotify.repeat(next_repeat_state, device_id).await?; + } + Command::Volume(percent) => { + client.spotify.volume(percent, device_id).await?; + } + Command::Seek(position_offset_ms) => { + let progress_ms = match playback.progress { + Some(progress) => progress.as_millis(), + None => { + eprintln!("Playback has no progress!"); + std::process::exit(1); + } + }; + client + .spotify + .seek_track( + (progress_ms as u32).saturating_add_signed(position_offset_ms), + device_id, + ) + .await?; + } + } + + Ok(()) +} diff --git a/spotify_player/src/client/mod.rs b/spotify_player/src/client/mod.rs index bb4cd4bc..d7e140df 100644 --- a/spotify_player/src/client/mod.rs +++ b/spotify_player/src/client/mod.rs @@ -22,7 +22,7 @@ use serde::Deserialize; /// The application's client #[derive(Clone)] pub struct Client { - spotify: Arc, + pub spotify: Arc, http: reqwest::Client, } @@ -372,7 +372,7 @@ impl Client { Ok(()) } - fn update_playback(&self, state: &SharedState) { + pub fn update_playback(&self, state: &SharedState) { // After handling a request that updates the player's playback, // update the playback state by making additional refresh requests. // @@ -928,7 +928,7 @@ impl Client { } /// gets a playlist context data - async fn playlist_context(&self, playlist_id: PlaylistId<'_>) -> Result { + pub async fn playlist_context(&self, playlist_id: PlaylistId<'_>) -> Result { let playlist_uri = playlist_id.uri(); tracing::info!("Get playlist context: {}", playlist_uri); @@ -955,7 +955,7 @@ impl Client { } /// gets an album context data - async fn album_context(&self, album_id: AlbumId<'_>) -> Result { + pub async fn album_context(&self, album_id: AlbumId<'_>) -> Result { let album_uri = album_id.uri(); tracing::info!("Get album context: {}", album_uri); @@ -985,7 +985,7 @@ impl Client { } /// gets an artist context data - async fn artist_context(&self, artist_id: ArtistId<'_>) -> Result { + pub async fn artist_context(&self, artist_id: ArtistId<'_>) -> Result { let artist_uri = artist_id.uri(); tracing::info!("Get artist context: {}", artist_uri); diff --git a/spotify_player/src/config/mod.rs b/spotify_player/src/config/mod.rs index d410591c..f78797b4 100644 --- a/spotify_player/src/config/mod.rs +++ b/spotify_player/src/config/mod.rs @@ -23,6 +23,8 @@ pub struct AppConfig { pub theme: String, pub client_id: String, + pub client_port: u16, + pub copy_command: Command, pub playback_format: String, @@ -130,6 +132,8 @@ impl Default for AppConfig { // official spotify web app's client id client_id: "65b708073fc0480ea92a077233ca87bd".to_string(), + client_port: 8080, + playback_format: String::from("{track} • {artists}\n{album}\n{metadata}"), #[cfg(feature = "notify")] notify_format: NotifyFormat { diff --git a/spotify_player/src/main.rs b/spotify_player/src/main.rs index f3a6b7bb..2b5bbf65 100644 --- a/spotify_player/src/main.rs +++ b/spotify_player/src/main.rs @@ -1,4 +1,5 @@ mod auth; +mod cli; mod client; mod command; mod config; @@ -21,6 +22,8 @@ fn init_app_cli_arguments() -> clap::ArgMatches { .version("0.13.1") .about("A command driven spotify player") .author("Thang Pham ") + .subcommand(cli::init_get_subcommand()) + .subcommand(cli::init_playback_subcommand()) .arg( clap::Arg::new("theme") .short('t') @@ -53,8 +56,6 @@ async fn init_spotify( client: &client::Client, state: &state::SharedState, ) -> Result<()> { - client.init_token().await?; - // if `streaming` feature is enabled, create a new streaming connection #[cfg(feature = "streaming")] if state.app_config.enable_streaming { @@ -116,44 +117,15 @@ fn init_logging(cache_folder: &std::path::Path) -> Result<()> { Ok(()) } -#[tokio::main] -async fn main() -> Result<()> { - // parse command line arguments - let args = init_app_cli_arguments(); - - // initialize the application's cache folder and config folder - let config_folder = match args.get_one::("config-folder") { - Some(path) => path.into(), - None => config::get_config_folder_path()?, - }; - let cache_folder = match args.get_one::("cache-folder") { - Some(path) => path.into(), - None => config::get_cache_folder_path()?, - }; - if !config_folder.exists() { - std::fs::create_dir_all(&config_folder)?; - } - let cache_audio_folder = cache_folder.join("audio"); - if !cache_audio_folder.exists() { - std::fs::create_dir_all(&cache_audio_folder)?; - } - let cache_image_folder = cache_folder.join("image"); - if !cache_image_folder.exists() { - std::fs::create_dir_all(&cache_image_folder)?; - } - +async fn start_app(state: state::SharedState, cache_folder: std::path::PathBuf) -> Result<()> { + // initialize the application's log init_logging(&cache_folder).context("failed to initialize application's logging")?; - // initialize the application state - let state = { - let mut state = state::State { - cache_folder, - ..state::State::default() - }; - // parse config options from the config files into application's state - state.parse_config_files(&config_folder, args.get_one::("theme"))?; - std::sync::Arc::new(state) - }; + // client channels + let (client_pub, client_sub) = flume::unbounded::(); + // streaming channels, which are used to notify a shutdown to running streaming connections + // upon creating a new connection. + let (streaming_pub, streaming_sub) = flume::unbounded::<()>(); // create a librespot session let session = auth::new_session( @@ -169,12 +141,7 @@ async fn main() -> Result<()> { state.app_config.device.clone(), state.app_config.client_id.clone(), ); - - // client channels - let (client_pub, client_sub) = flume::unbounded::(); - // streaming channels, which are used to notify a shutdown to running streaming connections - // upon creating a new connection. - let (streaming_pub, streaming_sub) = flume::unbounded::<()>(); + client.init_token().await?; // initialize Spotify-related stuff init_spotify(&client_pub, &streaming_sub, &client, &state) @@ -192,6 +159,16 @@ async fn main() -> Result<()> { // Spawn application's tasks + tokio::task::spawn({ + let client = client.clone(); + let state = state.clone(); + async move { + if let Err(err) = cli::start_socket(client, state).await { + tracing::warn!("Failed to run client socket for CLI: {err}"); + } + } + }); + // client event handler task tokio::task::spawn({ let state = state.clone(); @@ -267,3 +244,47 @@ async fn main() -> Result<()> { Ok(()) } } + +#[tokio::main] +async fn main() -> Result<()> { + // parse command line arguments + let args = init_app_cli_arguments(); + + // initialize the application's cache folder and config folder + let config_folder = match args.get_one::("config-folder") { + Some(path) => path.into(), + None => config::get_config_folder_path()?, + }; + if !config_folder.exists() { + std::fs::create_dir_all(&config_folder)?; + } + + let cache_folder = match args.get_one::("cache-folder") { + Some(path) => path.into(), + None => config::get_cache_folder_path()?, + }; + let cache_audio_folder = cache_folder.join("audio"); + if !cache_audio_folder.exists() { + std::fs::create_dir_all(&cache_audio_folder)?; + } + let cache_image_folder = cache_folder.join("image"); + if !cache_image_folder.exists() { + std::fs::create_dir_all(&cache_image_folder)?; + } + + // initialize the application state + let state = { + let mut state = state::State { + cache_folder: cache_folder.clone(), + ..state::State::default() + }; + // parse config options from the config files into application's state + state.parse_config_files(&config_folder, args.get_one::("theme"))?; + std::sync::Arc::new(state) + }; + + match args.subcommand() { + None => start_app(state, cache_folder).await, + Some((cmd, args)) => cli::handle_cli_subcommand(cmd, args, state.app_config.client_port), + } +} diff --git a/spotify_player/src/state/model.rs b/spotify_player/src/state/model.rs index cfb3b278..9bb154fa 100644 --- a/spotify_player/src/state/model.rs +++ b/spotify_player/src/state/model.rs @@ -2,8 +2,10 @@ pub use rspotify::model as rspotify_model; pub use rspotify::model::{AlbumId, ArtistId, Id, PlaylistId, TrackId, UserId}; use crate::utils::map_join; +use serde::Serialize; -#[derive(Clone, Debug)] +#[derive(Serialize, Clone, Debug)] +#[serde(untagged)] /// A Spotify context (playlist, album, artist) pub enum Context { Playlist { @@ -107,7 +109,7 @@ pub struct Device { pub name: String, } -#[derive(Debug, Clone)] +#[derive(Serialize, Debug, Clone)] /// A Spotify track pub struct Track { pub id: TrackId<'static>, @@ -115,10 +117,11 @@ pub struct Track { pub artists: Vec, pub album: Option, pub duration: std::time::Duration, + #[serde(skip)] pub added_at: u64, } -#[derive(Debug, Clone)] +#[derive(Serialize, Debug, Clone)] /// A Spotify album pub struct Album { pub id: AlbumId<'static>, @@ -127,14 +130,14 @@ pub struct Album { pub artists: Vec, } -#[derive(Debug, Clone)] +#[derive(Serialize, Debug, Clone)] /// A Spotify artist pub struct Artist { pub id: ArtistId<'static>, pub name: String, } -#[derive(Debug, Clone)] +#[derive(Serialize, Debug, Clone)] /// A Spotify playlist pub struct Playlist { pub id: PlaylistId<'static>,