diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 0f101729..13b291a0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,10 +1,9 @@ --- name: Bug report about: Create a report to help us improve -title: '' +title: "" labels: bug -assignees: '' - +assignees: "" --- **Describe the bug** @@ -20,8 +19,9 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **Environment** - - OS: - - Version: + +- OS: +- Version: **Additional context** Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index bbcbbe7d..2bc5d5f7 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,10 +1,9 @@ --- name: Feature request about: Suggest an idea for this project -title: '' -labels: '' -assignees: '' - +title: "" +labels: "" +assignees: "" --- **Is your feature request related to a problem? Please describe.** diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e9e0ab64..53763dd5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -158,7 +158,7 @@ jobs: - name: Checkout Repository uses: actions/checkout@v4 with: - fetch-depth: 0 # Get full history to count number of commits for package version + fetch-depth: 0 # Get full history to count number of commits for package version - name: Download Linux Binaries uses: actions/download-artifact@v4 diff --git a/.pkg/APPIMAGE/pkg2appimage-ingredients.yml b/.pkg/APPIMAGE/pkg2appimage-ingredients.yml index 86109e68..4870fd12 100644 --- a/.pkg/APPIMAGE/pkg2appimage-ingredients.yml +++ b/.pkg/APPIMAGE/pkg2appimage-ingredients.yml @@ -6,4 +6,4 @@ ingredients: - ../*.deb script: - mkdir -p /home/runner/work/psst/psst/.AppDir/ - - cp /home/runner/work/psst/psst/psst-gui/assets/logo_256.png /home/runner/work/psst/psst/.AppDir/psst.png \ No newline at end of file + - cp /home/runner/work/psst/psst/psst-gui/assets/logo_256.png /home/runner/work/psst/psst/.AppDir/psst.png diff --git a/.rustfmt.toml b/.rustfmt.toml index 4b8f37ac..47a41249 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1,2 +1,2 @@ imports_granularity = "Crate" -wrap_comments = true \ No newline at end of file +wrap_comments = true diff --git a/Cargo.lock b/Cargo.lock index 6f76de17..63438e79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "addr2line" @@ -56,7 +56,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", - "bitflags 2.6.0", + "bitflags 2.9.0", "cfg-if", "libc", ] @@ -127,19 +127,20 @@ dependencies = [ [[package]] name = "anstyle-wincon" -version = "3.0.6" +version = "3.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" +checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e" dependencies = [ "anstyle", + "once_cell", "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.94" +version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7" +checksum = "e16d2d3311acee920a9eb8d33b8cbc1787ce4a264e85f964c2404b969bdcd487" [[package]] name = "arbitrary" @@ -155,7 +156,7 @@ checksum = "0ae92a5119aa49cdbcf6b9f893fe4e1d98b04ccbf82ee0584ad948a44a734dea" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -201,7 +202,7 @@ dependencies = [ "async-task", "concurrent-queue", "fastrand 2.3.0", - "futures-lite 2.5.0", + "futures-lite 2.6.0", "slab", ] @@ -231,7 +232,7 @@ dependencies = [ "log", "parking", "polling 2.8.0", - "rustix 0.37.27", + "rustix 0.37.28", "slab", "socket2 0.4.10", "waker-fn", @@ -247,10 +248,10 @@ dependencies = [ "cfg-if", "concurrent-queue", "futures-io", - "futures-lite 2.5.0", + "futures-lite 2.6.0", "parking", "polling 3.7.4", - "rustix 0.38.42", + "rustix 0.38.44", "slab", "tracing", "windows-sys 0.59.0", @@ -271,7 +272,7 @@ version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18" dependencies = [ - "event-listener 5.3.1", + "event-listener 5.4.0", "event-listener-strategy", "pin-project-lite", ] @@ -289,7 +290,7 @@ dependencies = [ "cfg-if", "event-listener 3.1.0", "futures-lite 1.13.0", - "rustix 0.38.42", + "rustix 0.38.44", "windows-sys 0.48.0", ] @@ -301,7 +302,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -316,7 +317,7 @@ dependencies = [ "cfg-if", "futures-core", "futures-io", - "rustix 0.38.42", + "rustix 0.38.44", "signal-hook-registry", "slab", "windows-sys 0.59.0", @@ -330,13 +331,13 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-trait" -version = "0.1.83" +version = "0.1.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -369,6 +370,21 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "attohttpc" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e57d6e7a84f33ff3316e97af3180fe7f86597a6a60161c0be70c0e45f382620" +dependencies = [ + "flate2", + "http", + "log", + "native-tls", + "serde", + "serde_urlencoded", + "url", +] + [[package]] name = "audio_thread_priority" version = "0.32.0" @@ -405,9 +421,9 @@ dependencies = [ [[package]] name = "avif-serialize" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e335041290c43101ca215eed6f43ec437eb5a42125573f600fc3fa42b9bddd62" +checksum = "98922d6a4cfbcb08820c69d8eeccc05bb1f29bfa06b4f5b1dbfe9a868bd7608e" dependencies = [ "arrayvec", ] @@ -451,7 +467,7 @@ version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.0", "cexpr", "clang-sys", "itertools 0.13.0", @@ -460,7 +476,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -477,9 +493,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bitmaps" @@ -520,15 +536,15 @@ dependencies = [ "async-channel", "async-task", "futures-io", - "futures-lite 2.5.0", + "futures-lite 2.6.0", "piper", ] [[package]] name = "bstr" -version = "1.11.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "786a307d683a5bf92e6fd5fd69a7eb613751668d1d8d67d802846dfe367c62c8" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "regex-automata", @@ -537,21 +553,21 @@ dependencies = [ [[package]] name = "built" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c360505aed52b7ec96a3636c3f039d99103c37d1d9b4f7a8c743d3ea9ffcd03b" +checksum = "56ed6191a7e78c36abdb16ab65341eefd73d64d303fffccdbb00d51e4205967b" [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "bytemuck" -version = "1.20.0" +version = "1.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" +checksum = "b6b1fc10dbac614ebc03540c9dbd60e83887fda27794998c6528f1782047d540" [[package]] name = "byteorder" @@ -567,9 +583,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495" [[package]] name = "bytes" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "cairo-rs" @@ -598,9 +614,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.4" +version = "1.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf" +checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362" dependencies = [ "jobserver", "libc", @@ -640,9 +656,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.39" +version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "iana-time-zone", @@ -650,7 +666,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.52.6", + "windows-link", ] [[package]] @@ -676,9 +692,9 @@ dependencies = [ [[package]] name = "cmake" -version = "0.1.52" +version = "0.1.54" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c682c223677e0e5b6b7f63a64b9351844c3f1b1678a68b7ee617e30fb082620e" +checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" dependencies = [ "cc", ] @@ -851,9 +867,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.16" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] @@ -869,18 +885,18 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -897,15 +913,15 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" @@ -928,16 +944,16 @@ dependencies = [ [[package]] name = "cubeb" -version = "0.20.0" -source = "git+https://github.com/mozilla/cubeb-rs#0f685d93d51be7feaeb6ce5d4379afefb2a280a4" +version = "0.27.0" +source = "git+https://github.com/mozilla/cubeb-rs#51dc26862d6769eb8d46594de593a2dfe166265a" dependencies = [ "cubeb-core", ] [[package]] name = "cubeb-core" -version = "0.20.0" -source = "git+https://github.com/mozilla/cubeb-rs#0f685d93d51be7feaeb6ce5d4379afefb2a280a4" +version = "0.27.0" +source = "git+https://github.com/mozilla/cubeb-rs#51dc26862d6769eb8d46594de593a2dfe166265a" dependencies = [ "bitflags 1.3.2", "cc", @@ -946,8 +962,8 @@ dependencies = [ [[package]] name = "cubeb-sys" -version = "0.20.0" -source = "git+https://github.com/mozilla/cubeb-rs#0f685d93d51be7feaeb6ce5d4379afefb2a280a4" +version = "0.27.0" +source = "git+https://github.com/mozilla/cubeb-rs#51dc26862d6769eb8d46594de593a2dfe166265a" dependencies = [ "cmake", "pkg-config", @@ -971,9 +987,9 @@ dependencies = [ [[package]] name = "deranged" -version = "0.3.11" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +checksum = "9c9e6a11ca8224451684bc0d7d5a7adbf8f2fd6887261a1cfc3c0432f9d4068e" dependencies = [ "powerfmt", ] @@ -1056,7 +1072,7 @@ checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -1138,9 +1154,9 @@ dependencies = [ [[package]] name = "dwrote" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70182709525a3632b2ba96b6569225467b18ecb4a77f46d255f713a6bebf05fd" +checksum = "bfe1f192fcce01590bd8d839aca53ce0d11d803bf291b2a6c4ad925a8f0024be" dependencies = [ "lazy_static", "libc", @@ -1150,9 +1166,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "encoding_rs" @@ -1165,9 +1181,9 @@ dependencies = [ [[package]] name = "enumflags2" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d232db7f5956f3f14313dc2f87985c58bd2c695ce124c8cdd984e08e15ac133d" +checksum = "ba2f4b465f5318854c6f8dd686ede6c0a9dc67d4b1ac241cf0eb51521a309147" dependencies = [ "enumflags2_derive", "serde", @@ -1175,20 +1191,20 @@ dependencies = [ [[package]] name = "enumflags2_derive" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de0d48a183585823424a4ce1aa132d174a6a81bd540895822eb4c8373a8e49e8" +checksum = "fc4caf64a58d7a6d65ab00639b046ff54399a39f5f2554728895ace4b297cd79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] name = "env_filter" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" +checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0" dependencies = [ "log", "regex", @@ -1196,28 +1212,28 @@ dependencies = [ [[package]] name = "env_logger" -version = "0.11.5" +version = "0.11.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" +checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" dependencies = [ "anstream", "anstyle", "env_filter", - "humantime", + "jiff", "log", ] [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.10" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -1242,9 +1258,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.3.1" +version = "5.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +checksum = "3492acde4c3fc54c845eaab3eed8bd00c7a7d881f78bfc801e43a93dec1331ae" dependencies = [ "concurrent-queue", "parking", @@ -1253,11 +1269,11 @@ dependencies = [ [[package]] name = "event-listener-strategy" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3e4e0dd3673c1139bf041f3008816d9cf2946bbfac2945c09e523b8d7b05b2" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" dependencies = [ - "event-listener 5.3.1", + "event-listener 5.4.0", "pin-project-lite", ] @@ -1318,9 +1334,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.35" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" +checksum = "7ced92e76e966ca2fd84c8f7aa01a4aea65b0eb6648d72f7c8f3e2764a67fece" dependencies = [ "crc32fast", "miniz_oxide", @@ -1368,9 +1384,9 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foldhash" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foreign-types" @@ -1455,9 +1471,9 @@ dependencies = [ [[package]] name = "futures-lite" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cef40d21ae2c515b51041df9ed313ed21e572df340ea58a922a0aefe7e8891a1" +checksum = "f5edaec856126859abb19ed65f39e90fea3a9574b9707f13539acf4abf7eb532" dependencies = [ "fastrand 2.3.0", "futures-core", @@ -1474,7 +1490,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -1584,10 +1600,22 @@ dependencies = [ "cfg-if", "js-sys", "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + [[package]] name = "gif" version = "0.13.1" @@ -1654,7 +1682,7 @@ checksum = "53010ccb100b96a67bc32c0175f0ed1426b31b655d562898e57325f81c023ac0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -1668,7 +1696,7 @@ dependencies = [ "gix-utils", "itoa", "thiserror 1.0.69", - "winnow 0.6.20", + "winnow 0.6.26", ] [[package]] @@ -1689,32 +1717,32 @@ dependencies = [ "smallvec", "thiserror 1.0.69", "unicode-bom", - "winnow 0.6.20", + "winnow 0.6.26", ] [[package]] name = "gix-config-value" -version = "0.14.10" +version = "0.14.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49aaeef5d98390a3bcf9dbc6440b520b793d1bf3ed99317dc407b02be995b28e" +checksum = "8dc2c844c4cf141884678cabef736fd91dd73068b9146e6f004ba1a0457944b6" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.0", "bstr", "gix-path", "libc", - "thiserror 2.0.6", + "thiserror 2.0.12", ] [[package]] name = "gix-date" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "691142b1a34d18e8ed6e6114bc1a2736516c5ad60ef3aa9bd1b694886e3ca92d" +checksum = "daa30058ec7d3511fbc229e4f9e696a35abd07ec5b82e635eff864a2726217e4" dependencies = [ "bstr", "itoa", "jiff", - "thiserror 2.0.6", + "thiserror 2.0.12", ] [[package]] @@ -1749,7 +1777,7 @@ version = "0.16.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74908b4bbc0a0a40852737e5d7889f676f081e340d5451a16e5b4c50d592f111" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.0", "bstr", "gix-features", "gix-path", @@ -1792,20 +1820,20 @@ dependencies = [ "itoa", "smallvec", "thiserror 1.0.69", - "winnow 0.6.20", + "winnow 0.6.26", ] [[package]] name = "gix-path" -version = "0.10.13" +version = "0.10.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc292ef1a51e340aeb0e720800338c805975724c1dfbd243185452efd8645b7" +checksum = "f910668e2f6b2a55ff35a1f04df88a1a049f7b868507f4cbeeaa220eaba7be87" dependencies = [ "bstr", "gix-trace", "home", "once_cell", - "thiserror 2.0.6", + "thiserror 2.0.12", ] [[package]] @@ -1826,16 +1854,16 @@ dependencies = [ "gix-validate", "memmap2", "thiserror 1.0.69", - "winnow 0.6.20", + "winnow 0.6.26", ] [[package]] name = "gix-sec" -version = "0.10.10" +version = "0.10.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8b876ef997a955397809a2ec398d6a45b7a55b4918f2446344330f778d14fd6" +checksum = "47aeb0f13de9ef2f3033f5ff218de30f44db827ac9f1286f9ef050aacddd5888" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.0", "gix-path", "libc", "windows-sys 0.52.0", @@ -1856,15 +1884,15 @@ dependencies = [ [[package]] name = "gix-trace" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04bdde120c29f1fc23a24d3e115aeeea3d60d8e65bab92cc5f9d90d9302eb952" +checksum = "7c396a2036920c69695f760a65e7f2677267ccf483f25046977d87e4cb2665f7" [[package]] name = "gix-utils" -version = "0.1.13" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba427e3e9599508ed98a6ddf8ed05493db114564e338e41f6a996d2e4790335f" +checksum = "ff08f24e03ac8916c478c8419d7d3c33393da9bb41fa4c24455d5406aeefd35f" dependencies = [ "fastrand 2.3.0", "unicode-normalization", @@ -1872,12 +1900,12 @@ dependencies = [ [[package]] name = "gix-validate" -version = "0.9.2" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd520d09f9f585b34b32aba1d0b36ada89ab7fefb54a8ca3fe37fc482a750937" +checksum = "34b5f1253109da6c79ed7cf6e1e38437080bb6d704c76af14c93e2f255234084" dependencies = [ "bstr", - "thiserror 2.0.6", + "thiserror 2.0.12", ] [[package]] @@ -1929,9 +1957,9 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "gobject-sys" @@ -2020,9 +2048,9 @@ dependencies = [ [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", @@ -2089,11 +2117,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -2107,7 +2135,7 @@ dependencies = [ "markup5ever", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -2134,9 +2162,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.5" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "httpdate" @@ -2144,17 +2172,11 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" -version = "0.14.31" +version = "0.14.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" +checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" dependencies = [ "bytes", "futures-channel", @@ -2167,7 +2189,7 @@ dependencies = [ "httpdate", "itoa", "pin-project-lite", - "socket2 0.5.8", + "socket2 0.5.9", "tokio", "tower-service", "tracing", @@ -2190,16 +2212,17 @@ dependencies = [ [[package]] name = "iana-time-zone" -version = "0.1.61" +version = "0.1.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "b0c919e5debc312ad217002b8048a17b7d83f80703865bbfcfebb0458b0b27d8" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.61.0", ] [[package]] @@ -2252,9 +2275,9 @@ dependencies = [ [[package]] name = "icu_locid_transform_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" [[package]] name = "icu_normalizer" @@ -2276,9 +2299,9 @@ dependencies = [ [[package]] name = "icu_normalizer_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" [[package]] name = "icu_properties" @@ -2297,9 +2320,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" [[package]] name = "icu_provider" @@ -2326,7 +2349,7 @@ checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -2381,9 +2404,9 @@ dependencies = [ [[package]] name = "image" -version = "0.25.5" +version = "0.25.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6f44aed642f18953a158afeb30206f4d50da59fbc66ecb53c66488de73563b" +checksum = "db35664ce6b9810857a38a906215e75a9c879f0696556a39f59c62829710251a" dependencies = [ "bytemuck", "byteorder-lite", @@ -2404,9 +2427,9 @@ dependencies = [ [[package]] name = "image-webp" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e031e8e3d94711a9ccb5d6ea357439ef3dcbed361798bd4071dc4d9793fbe22f" +checksum = "b77d01e822461baa8409e156015a1d91735549f0f2c17691bd2d996bef238f7f" dependencies = [ "byteorder-lite", "quick-error", @@ -2420,9 +2443,9 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408" [[package]] name = "indexmap" -version = "2.7.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f" +checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown", @@ -2430,9 +2453,9 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] @@ -2457,7 +2480,7 @@ checksum = "c34819042dc3d3971c46c2190835914dfbe0c3c13f61449b2997f4e9722dfa60" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -2492,9 +2515,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.10.1" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "is-docker" @@ -2541,31 +2564,47 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.1.15" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db69f08d4fb10524cacdb074c10b296299d71274ddbc830a8ee65666867002e9" +checksum = "e5ad87c89110f55e4cd4dc2893a9790820206729eaf221555f742d540b0724a0" dependencies = [ + "jiff-static", "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde", "windows-sys 0.59.0", ] +[[package]] +name = "jiff-static" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d076d5b64a7e2fe6f0743f02c43ca4a6725c0f904203bfe276a5b3e793103605" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "jiff-tzdb" -version = "0.1.1" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91335e575850c5c4c673b9bd467b0e025f164ca59d0564f69d0c2ee0ffad4653" +checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" [[package]] name = "jiff-tzdb-platform" -version = "0.1.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9835f0060a626fe59f160437bc725491a6af23133ea906500027d1bd2f8f4329" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" dependencies = [ "jiff-tzdb", ] @@ -2594,10 +2633,11 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.32" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a" dependencies = [ + "getrandom 0.3.2", "libc", ] @@ -2609,9 +2649,9 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0" [[package]] name = "js-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", @@ -2650,9 +2690,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.168" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libdbus-sys" @@ -2665,9 +2705,9 @@ dependencies = [ [[package]] name = "libfuzzer-sys" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa" +checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75" dependencies = [ "arbitrary", "cc", @@ -2689,7 +2729,7 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.0", "libc", ] @@ -2710,15 +2750,21 @@ checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "linux-raw-sys" -version = "0.4.14" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" [[package]] name = "litemap" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" [[package]] name = "lock_api" @@ -2732,9 +2778,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.22" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "loop9" @@ -2829,6 +2875,12 @@ dependencies = [ "rayon", ] +[[package]] +name = "md5" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" + [[package]] name = "memchr" version = "2.7.4" @@ -2876,9 +2928,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.8.0" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +checksum = "3be647b768db090acb35d5ec5db2b0e1f1de11133ca123b9eacf5137868f892a" dependencies = [ "adler2", "simd-adler32", @@ -2891,17 +2943,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", - "wasi", + "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2076a31b7010b17a38c01907c45b945e8f11495ee4dd588309718901b1f7a5b7" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.0", "jni-sys", "log", "ndk-sys", @@ -2993,7 +3062,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -3050,10 +3119,10 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 3.2.0", + "proc-macro-crate 3.3.0", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -3073,7 +3142,7 @@ checksum = "c38841cdd844847e3e7c8d29cef9dcfed8877f8f56f9071f77843ecf3baf937f" dependencies = [ "base64 0.13.1", "chrono", - "getrandom", + "getrandom 0.2.15", "http", "rand", "reqwest", @@ -3096,9 +3165,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.5" +version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" +checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] @@ -3128,21 +3197,65 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.20.2" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "open" -version = "5.3.1" +version = "5.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ecd52f0b8d15c40ce4820aa251ed5de032e5d91fab27f7db2f40d42a8bdf69c" +checksum = "e2483562e62ea94312f3576a7aca397306df7990b8d89033e18766744377ef95" dependencies = [ "is-wsl", "libc", "pathdiff", ] +[[package]] +name = "openssl" +version = "0.10.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fedfea7d58a1f73118430a55da6a286e7b044961736ce96a16a17068ea25e5da" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -3267,57 +3380,38 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" dependencies = [ - "phf_shared 0.11.2", + "phf_shared", ] [[package]] name = "phf_codegen" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" -dependencies = [ - "phf_generator 0.11.2", - "phf_shared 0.11.2", -] - -[[package]] -name = "phf_generator" -version = "0.10.0" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a" dependencies = [ - "phf_shared 0.10.0", - "rand", + "phf_generator", + "phf_shared", ] [[package]] name = "phf_generator" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" dependencies = [ - "phf_shared 0.11.2", + "phf_shared", "rand", ] [[package]] name = "phf_shared" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" -dependencies = [ - "siphasher", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" +version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] @@ -3411,9 +3505,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" @@ -3434,9 +3528,9 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "platform-dirs" @@ -3449,9 +3543,9 @@ dependencies = [ [[package]] name = "png" -version = "0.17.15" +version = "0.17.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" dependencies = [ "bitflags 1.3.2", "crc32fast", @@ -3486,7 +3580,7 @@ dependencies = [ "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", - "rustix 0.38.42", + "rustix 0.38.44", "tracing", "windows-sys 0.59.0", ] @@ -3497,6 +3591,21 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +[[package]] +name = "portable-atomic" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" + +[[package]] +name = "portable-atomic-util" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" +dependencies = [ + "portable-atomic", +] + [[package]] name = "powerfmt" version = "0.2.0" @@ -3505,9 +3614,9 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] @@ -3530,11 +3639,11 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "3.2.0" +version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35" dependencies = [ - "toml_edit 0.22.22", + "toml_edit 0.22.24", ] [[package]] @@ -3563,9 +3672,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.92" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] @@ -3592,7 +3701,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a65f2e60fbf1063868558d69c6beacf412dc755f9fc020f514b7955fc914fe30" dependencies = [ "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -3630,6 +3739,7 @@ dependencies = [ "rand", "rangemap", "rb", + "rustfm-scrobble-proxy", "serde", "serde_json", "sha-1", @@ -3653,7 +3763,7 @@ dependencies = [ "druid-enums", "druid-shell", "env_logger", - "image 0.25.5", + "image 0.25.6", "itertools 0.13.0", "log", "lru", @@ -3665,6 +3775,7 @@ dependencies = [ "rand", "raw-window-handle", "regex", + "rustfm-scrobble-proxy", "sanitize_html", "serde", "serde_json", @@ -3710,13 +3821,19 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.37" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + [[package]] name = "rand" version = "0.8.5" @@ -3744,7 +3861,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.15", ] [[package]] @@ -3799,9 +3916,9 @@ dependencies = [ [[package]] name = "ravif" -version = "0.11.11" +version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2413fd96bd0ea5cdeeb37eaf446a22e6ed7b981d792828721e74ded1980a45c6" +checksum = "d6a5f31fcf7500f9401fea858ea4ab5525c99f2322cfcee732c0e6c74208c0c6" dependencies = [ "avif-serialize", "imgref", @@ -3846,11 +3963,11 @@ checksum = "56cf8381505b60ae18a4097f1d0be093287ca3bf4fbb23d36ac5ad3bba335daa" [[package]] name = "redox_syscall" -version = "0.5.8" +version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.0", ] [[package]] @@ -3859,7 +3976,7 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ - "getrandom", + "getrandom 0.2.15", "libredox", "thiserror 1.0.69", ] @@ -3942,15 +4059,14 @@ checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" [[package]] name = "ring" -version = "0.17.8" +version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom", + "getrandom 0.2.15", "libc", - "spin", "untrusted", "windows-sys 0.52.0", ] @@ -3976,11 +4092,24 @@ dependencies = [ "semver", ] +[[package]] +name = "rustfm-scrobble-proxy" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe604e5881fb980b570695795440cdba1abdbe13d1cc4858923ce11b4fc912be" +dependencies = [ + "attohttpc", + "md5", + "serde", + "serde_json", + "wrapped-vec", +] + [[package]] name = "rustix" -version = "0.37.27" +version = "0.37.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fea8ca367a3a01fe35e6943c400addf443c0f57670e6ec51196f71a4b8762dd2" +checksum = "519165d378b97752ca44bbe15047d5d3409e875f39327546b42ac81d7e18c1b6" dependencies = [ "bitflags 1.3.2", "errno", @@ -3992,14 +4121,27 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.42" +version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.9.0", "errno", "libc", - "linux-raw-sys 0.4.14", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.4", "windows-sys 0.59.0", ] @@ -4017,15 +4159,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.20" +version = "0.23.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5065c3f250cbd332cd894be57c40fa52387247659b14a2d6041d121547903b1b" +checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.102.8", + "rustls-webpki 0.103.1", "subtle", "zeroize", ] @@ -4041,9 +4183,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2bf47e6ff922db3825eb750c4e2ff784c6ff8fb9e13046ef6a1d1c5401b0b37" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" @@ -4057,20 +4199,26 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.102.8" +version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -4093,6 +4241,15 @@ dependencies = [ "regex", ] +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scopeguard" version = "1.2.0" @@ -4109,52 +4266,75 @@ dependencies = [ "untrusted", ] +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e14e4d63b804dc0c7ec4a1e52bcb63f02c7ac94476755aa579edac21e01f915d" dependencies = [ - "self_cell 1.1.0", + "self_cell 1.2.0", ] [[package]] name = "self_cell" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2fdfc24bc566f839a2da4c4295b82db7d25a24253867d5c64355abb5799bdbe" +checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749" [[package]] name = "semver" -version = "1.0.24" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cb6eb87a131f756572d7fb904f6e7b68633f09cca868c5df1c4b8d1a694bbba" +checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "serde" -version = "1.0.216" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.216" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] name = "serde_json" -version = "1.0.133" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -4164,9 +4344,9 @@ dependencies = [ [[package]] name = "serde_path_to_error" -version = "0.1.16" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" +checksum = "59fab13f937fa393d08645bf3a84bdfe86e296747b506ada67bb15f10f218b2a" dependencies = [ "itoa", "serde", @@ -4174,13 +4354,13 @@ dependencies = [ [[package]] name = "serde_repr" -version = "0.1.19" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -4293,9 +4473,9 @@ dependencies = [ [[package]] name = "siphasher" -version = "0.3.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" +checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "sized-chunks" @@ -4318,9 +4498,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" [[package]] name = "socket2" @@ -4334,9 +4514,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -4371,12 +4551,6 @@ dependencies = [ "zvariant", ] -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -4391,26 +4565,25 @@ checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "string_cache" -version = "0.8.7" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", - "once_cell", "parking_lot", - "phf_shared 0.10.0", + "phf_shared", "precomputed-hash", "serde", ] [[package]] name = "string_cache_codegen" -version = "0.5.2" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" dependencies = [ - "phf_generator 0.10.0", - "phf_shared 0.10.0", + "phf_generator", + "phf_shared", "proc-macro2", "quote", ] @@ -4518,9 +4691,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.90" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -4541,7 +4714,7 @@ checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -4574,7 +4747,7 @@ dependencies = [ "cfg-expr", "heck 0.5.0", "pkg-config", - "toml 0.8.19", + "toml 0.8.20", "version-compare", ] @@ -4586,14 +4759,14 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" -version = "3.14.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28cce251fcbc87fac86a866eeb0d6c2d536fc16d06f184bb61aeae11aa4cee0c" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" dependencies = [ - "cfg-if", "fastrand 2.3.0", + "getrandom 0.3.2", "once_cell", - "rustix 0.38.42", + "rustix 1.0.5", "windows-sys 0.59.0", ] @@ -4619,11 +4792,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.6" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.6", + "thiserror-impl 2.0.12", ] [[package]] @@ -4634,18 +4807,18 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] name = "thiserror-impl" -version = "2.0.6" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -4680,9 +4853,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.37" +version = "0.3.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" +checksum = "8a7619e19bc266e0f9c5e6686659d394bc57973859340060a69221e57dbc0c40" dependencies = [ "deranged", "itoa", @@ -4697,9 +4870,9 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.2" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +checksum = "c9e9a38711f559d9e3ce1cdb06dd7c5b8ea546bc90052da6d06bb76da74bb07c" [[package]] name = "time-humanize" @@ -4709,9 +4882,9 @@ checksum = "3e32d019b4f7c100bcd5494e40a27119d45b71fba2b07a4684153129279a4647" [[package]] name = "time-macros" -version = "0.2.19" +version = "0.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" +checksum = "3526739392ec93fd8b359c8e98514cb3e8e021beb4e5f597b00a0221f8ed8a49" dependencies = [ "num-conv", "time-core", @@ -4729,9 +4902,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] @@ -4744,16 +4917,16 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.42.0" +version = "1.44.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" +checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" dependencies = [ "backtrace", "bytes", "libc", "mio", "pin-project-lite", - "socket2 0.5.8", + "socket2 0.5.9", "windows-sys 0.52.0", ] @@ -4769,9 +4942,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.13" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" dependencies = [ "bytes", "futures-core", @@ -4791,14 +4964,14 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit 0.22.22", + "toml_edit 0.22.24", ] [[package]] @@ -4823,15 +4996,15 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", - "winnow 0.6.20", + "winnow 0.7.6", ] [[package]] @@ -4859,7 +5032,7 @@ checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] @@ -4911,9 +5084,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "uds_windows" @@ -5003,9 +5176,9 @@ checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" [[package]] name = "unicode-ident" -version = "1.0.14" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" @@ -5038,13 +5211,13 @@ dependencies = [ "flate2", "log", "once_cell", - "rustls 0.23.20", + "rustls 0.23.26", "rustls-pki-types", "serde", "serde_json", "socks", "url", - "webpki-roots 0.26.7", + "webpki-roots 0.26.8", ] [[package]] @@ -5100,6 +5273,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.0" @@ -5143,36 +5322,46 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.49" +version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", @@ -5183,9 +5372,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -5193,28 +5382,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.99" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.76" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -5228,9 +5420,9 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "0.26.7" +version = "0.26.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" +checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" dependencies = [ "rustls-pki-types", ] @@ -5301,15 +5493,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-core" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-core" version = "0.54.0" @@ -5326,13 +5509,26 @@ version = "0.58.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" dependencies = [ - "windows-implement", - "windows-interface", + "windows-implement 0.58.0", + "windows-interface 0.58.0", "windows-result 0.2.0", - "windows-strings", + "windows-strings 0.1.0", "windows-targets 0.52.6", ] +[[package]] +name = "windows-core" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +dependencies = [ + "windows-implement 0.60.0", + "windows-interface 0.59.1", + "windows-link", + "windows-result 0.3.2", + "windows-strings 0.4.0", +] + [[package]] name = "windows-implement" version = "0.58.0" @@ -5341,7 +5537,18 @@ checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] @@ -5352,9 +5559,26 @@ checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "windows-link" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" + [[package]] name = "windows-result" version = "0.1.2" @@ -5373,6 +5597,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-result" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-strings" version = "0.1.0" @@ -5383,6 +5616,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-strings" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.45.0" @@ -5608,9 +5850,18 @@ dependencies = [ [[package]] name = "winnow" -version = "0.6.20" +version = "0.6.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "1e90edd2ac1aa278a5c4599b1d89cf03074b610800f866d4026dc199d7929a28" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ "memchr", ] @@ -5643,6 +5894,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "wrapped-vec" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85e08702c1e919669e1e90213c9c75ea4bb689d0f3970347e2b37c04600b4e5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "write16" version = "1.0.0" @@ -5702,7 +5973,7 @@ checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", "synstructure", ] @@ -5774,43 +6045,42 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.35" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ - "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.35" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] name = "zerofrom" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", "synstructure", ] @@ -5839,7 +6109,7 @@ checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.90", + "syn 2.0.100", ] [[package]] diff --git a/README.md b/README.md index 22118b27..926188d1 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ Contributions are welcome! GitHub Actions automatically creates builds when new commits are pushed to the `main` branch. You can download the prebuilt binaries for x86_64 Windows, Linux (Ubuntu), and macOS. -| Platform | -| ------------------------------------------------------------------------------------------------------------------- | +| Platform | +| ----------------------------------------------------------------------------------------------------------------- | | [Linux (x86_64)](https://nightly.link/jpochyla/psst/workflows/build/main/psst-gui-x86_64-unknown-linux-gnu.zip) | | [Linux (aarch64)](https://nightly.link/jpochyla/psst/workflows/build/main/psst-gui-aarch64-unknown-linux-gnu.zip) | | [Debian Package (amd64)](https://nightly.link/jpochyla/psst/workflows/build/main/psst-deb-amd64.zip) | diff --git a/psst-core/Cargo.toml b/psst-core/Cargo.toml index 1cb9cfd4..a537b9e5 100644 --- a/psst-core/Cargo.toml +++ b/psst-core/Cargo.toml @@ -11,6 +11,7 @@ time = { version = "0.3.36", features = ["local-offset"] } [dependencies] psst-protocol = { path = "../psst-protocol" } +rustfm-scrobble-proxy = "2.0.0" # Common byteorder = { version = "1.5.0" } diff --git a/psst-core/src/error.rs b/psst-core/src/error.rs index 8a16e0c6..d22664dc 100644 --- a/psst-core/src/error.rs +++ b/psst-core/src/error.rs @@ -1,3 +1,4 @@ +use std::sync::mpsc::RecvTimeoutError; use std::{error, fmt, io}; #[derive(Debug)] @@ -13,9 +14,14 @@ pub enum Error { AudioDecodingError(Box), AudioOutputError(Box), AudioProbeError(Box), + ScrobblerError(Box), ResamplingError(i32), + ConfigError(String), IoError(io::Error), SendError, + RecvTimeoutError(RecvTimeoutError), + JoinError, + OAuthError(String), } impl error::Error for Error {} @@ -45,13 +51,18 @@ impl fmt::Display for Error { Self::ResamplingError(code) => { write!(f, "Resampling failed with error code {}", code) } + Self::ConfigError(msg) => write!(f, "Configuration error: {}", msg), Self::JsonError(err) | Self::AudioFetchingError(err) | Self::AudioDecodingError(err) | Self::AudioOutputError(err) + | Self::ScrobblerError(err) | Self::AudioProbeError(err) => err.fmt(f), Self::IoError(err) => err.fmt(f), Self::SendError => write!(f, "Failed to send into a channel"), + Self::RecvTimeoutError(err) => write!(f, "Channel receive timeout: {}", err), + Self::JoinError => write!(f, "Failed to join thread"), + Self::OAuthError(msg) => write!(f, "OAuth error: {}", msg), } } } @@ -67,3 +78,9 @@ impl From> for Error { Error::SendError } } + +impl From for Error { + fn from(err: RecvTimeoutError) -> Self { + Error::RecvTimeoutError(err) + } +} diff --git a/psst-core/src/lastfm.rs b/psst-core/src/lastfm.rs new file mode 100644 index 00000000..b646733c --- /dev/null +++ b/psst-core/src/lastfm.rs @@ -0,0 +1,95 @@ +use crate::error::Error; +use crate::oauth::listen_for_callback_parameter; +use rustfm_scrobble_proxy::{responses::SessionResponse, Scrobble, Scrobbler, ScrobblerError}; +use std::{net::SocketAddr, time::Duration}; +use url::Url; + +pub struct LastFmClient; + +impl LastFmClient { + /// Report a track as "now playing" to Last.fm using an existing Scrobbler instance. + pub fn now_playing_song( + scrobbler: &Scrobbler, // Requires an authenticated Scrobbler + artist: &str, + title: &str, + album: Option<&str>, + ) -> Result<(), Error> { + let song = Scrobble::new(artist, title, album); + scrobbler + .now_playing(&song) + .map(|_| ()) + .map_err(Error::from) + } + + /// Scrobble a finished track to Last.fm using an existing Scrobbler instance. + pub fn scrobble_song( + scrobbler: &Scrobbler, // Requires an authenticated Scrobbler + artist: &str, + title: &str, + album: Option<&str>, + ) -> Result<(), Error> { + let song = Scrobble::new(artist, title, album); + scrobbler.scrobble(&song).map(|_| ()).map_err(Error::from) + } + + /// Creates an authenticated Last.fm Scrobbler instance with provided credentials. + /// Note: This assumes the session_key is valid. Validity is checked on first API call. + pub fn create_scrobbler( + api_key: Option<&str>, + api_secret: Option<&str>, + session_key: Option<&str>, + ) -> Result { + let (Some(api_key), Some(api_secret), Some(session_key)) = + (api_key, api_secret, session_key) + else { + log::warn!("Missing Last.fm API key, secret, or session key for scrobbler creation."); + return Err(Error::ConfigError( + "Missing Last.fm API key, secret, or session key.".to_string(), + )); + }; + + let mut scrobbler = Scrobbler::new(api_key, api_secret); + // Associate the session key with the scrobbler instance. + scrobbler.authenticate_with_session_key(session_key); + log::info!("Scrobbler instance created with session key (validity checked on first use)."); + Ok(scrobbler) + } +} + +impl From for Error { + fn from(value: ScrobblerError) -> Self { + Self::ScrobblerError(Box::new(value)) + } +} + +/// Generate a Last.fm authentication URL +pub fn generate_lastfm_auth_url( + api_key: &str, + callback_url: &str, +) -> Result { + let base = "http://www.last.fm/api/auth/"; + let url = Url::parse_with_params(base, &[("api_key", api_key), ("cb", callback_url)])?; + Ok(url.to_string()) +} + +/// Exchange a token for a Last.fm session key +pub fn exchange_token_for_session( + api_key: &str, + api_secret: &str, + token: &str, +) -> Result { + let mut scrobbler = Scrobbler::new(api_key, api_secret); + scrobbler + .authenticate_with_token(token) // Uses auth.getSession API call internally + .map(|response: SessionResponse| response.key) // Extract the session key string + .map_err(Error::from) // Map ScrobblerError to crate::error::Error +} + +/// Listen for a Last.fm token from the callback +pub fn get_lastfm_token_listener( + socket_address: SocketAddr, + timeout: Duration, +) -> Result { + // Use the shared listener function, specifying "token" as the parameter + listen_for_callback_parameter(socket_address, timeout, "token") +} diff --git a/psst-core/src/lib.rs b/psst-core/src/lib.rs index 2faa3174..d2e0dad2 100644 --- a/psst-core/src/lib.rs +++ b/psst-core/src/lib.rs @@ -13,10 +13,11 @@ pub mod cdn; pub mod connection; pub mod error; pub mod item_id; +pub mod lastfm; pub mod metadata; +pub mod oauth; pub mod player; pub mod session; pub mod util; -pub mod oauth; pub use psst_protocol as protocol; diff --git a/psst-core/src/oauth.rs b/psst-core/src/oauth.rs index 314c0d08..a6b64a4f 100644 --- a/psst-core/src/oauth.rs +++ b/psst-core/src/oauth.rs @@ -1,3 +1,4 @@ +use crate::error::Error; use oauth2::{ basic::BasicClient, reqwest::http_client, AuthUrl, AuthorizationCode, ClientId, CsrfToken, PkceCodeChallenge, PkceCodeVerifier, RedirectUrl, Scope, TokenResponse, TokenUrl, @@ -11,61 +12,144 @@ use std::{ }; use url::Url; -pub fn get_authcode_listener( +pub fn listen_for_callback_parameter( socket_address: SocketAddr, timeout: Duration, -) -> Result { - log::info!("starting OAuth listener on {:?}", socket_address); - let listener = TcpListener::bind(socket_address) - .map_err(|e| format!("Failed to bind to address: {}", e))?; - log::info!("listener bound successfully"); + parameter_name: &'static str, +) -> Result { + log::info!( + "starting callback listener for '{}' on {:?}", + parameter_name, + socket_address + ); + + // Create a simpler, linear flow + // 1. Bind the listener + let listener = match TcpListener::bind(socket_address) { + Ok(l) => { + log::info!("listener bound successfully"); + l + } + Err(e) => { + log::error!("Failed to bind listener: {}", e); + return Err(Error::IoError(e)); + } + }; - let (tx, rx) = mpsc::channel(); + // 2. Set up the channel for communication + let (tx, rx) = mpsc::channel::>(); + // 3. Spawn the thread let handle = std::thread::spawn(move || { if let Ok((mut stream, _)) = listener.accept() { - handle_connection(&mut stream, &tx); + handle_callback_connection(&mut stream, &tx, parameter_name); + } else { + log::error!("Failed to accept connection on callback listener"); + let _ = tx.send(Err(Error::IoError(std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to accept connection", + )))); } }); - let result = rx - .recv_timeout(timeout) - .map_err(|_| "Timed out waiting for authorization code".to_string())?; + // 4. Wait for the result with timeout + let result = match rx.recv_timeout(timeout) { + Ok(r) => r, + Err(e) => { + log::error!("Timed out or channel error: {}", e); + return Err(Error::from(e)); + } + }; - handle - .join() - .map_err(|_| "Failed to join server thread".to_string())?; + // 5. Wait for thread completion + if handle.join().is_err() { + log::warn!("Thread join failed, but continuing with result"); + } + // 6. Return the result result } -fn handle_connection(stream: &mut TcpStream, tx: &mpsc::Sender>) { +/// Handles an incoming TCP connection for a generic OAuth callback. +fn handle_callback_connection( + stream: &mut TcpStream, + tx: &mpsc::Sender>, + parameter_name: &'static str, +) { let mut reader = BufReader::new(&mut *stream); let mut request_line = String::new(); if reader.read_line(&mut request_line).is_ok() { - if let Some(code) = extract_code_from_request(&request_line) { - send_success_response(stream); - let _ = tx.send(Ok(code)); - } else { - let _ = tx.send(Err("Failed to extract code from request".to_string())); + match extract_parameter_from_request(&request_line, parameter_name) { + Some(value) => { + log::info!("Received callback parameter '{}'.", parameter_name); + send_success_response(stream); + let _ = tx.send(Ok(value)); + } + None => { + let err_msg = format!( + "Failed to extract parameter '{}' from request: {}", + parameter_name, request_line + ); + log::error!("{}", err_msg); + let _ = tx.send(Err(Error::OAuthError(err_msg))); + } } + } else { + log::error!("Failed to read request line from callback."); + let _ = tx.send(Err(Error::IoError(std::io::Error::new( + std::io::ErrorKind::Other, + "Failed to read request line", + )))); } } -fn extract_code_from_request(request_line: &str) -> Option { - request_line.split_whitespace().nth(1).and_then(|path| { - Url::parse(&format!("http://localhost{}", path)) - .ok()? - .query_pairs() - .find(|(key, _)| key == "code") - .map(|(_, code)| AuthorizationCode::new(code.into_owned())) - }) +/// Extracts a specified query parameter from an HTTP request line. +fn extract_parameter_from_request(request_line: &str, parameter_name: &str) -> Option { + request_line + .split_whitespace() + .nth(1) + .and_then(|path| Url::parse(&format!("http://localhost{}", path)).ok()) + .and_then(|url| { + url.query_pairs() + .find(|(key, _)| key == parameter_name) + .map(|(_, value)| value.into_owned()) + }) +} + +pub fn get_authcode_listener( + socket_address: SocketAddr, + timeout: Duration, +) -> Result { + listen_for_callback_parameter(socket_address, timeout, "code").map(AuthorizationCode::new) } -fn send_success_response(stream: &mut TcpStream) { - let response = - "HTTP/1.1 200 OK\r\n\r\nYou can close this window now."; +pub fn send_success_response(stream: &mut TcpStream) { + let response = "HTTP/1.1 200 OK\r\n\r\n\ + \ + \ + \ + \ + \ +
Successfully authenticated! You can close this window now.
\ + \ + "; let _ = stream.write_all(response.as_bytes()); } diff --git a/psst-gui/Cargo.toml b/psst-gui/Cargo.toml index 7221b642..eb1d8154 100644 --- a/psst-gui/Cargo.toml +++ b/psst-gui/Cargo.toml @@ -53,6 +53,7 @@ souvlaki = { version = "0.7.3", default-features = false, features = [ "use_zbus", ] } sanitize_html = "0.8.1" +rustfm-scrobble-proxy = "2.0.0" [target.'cfg(windows)'.build-dependencies] winres = { version = "0.1.12" } image = { version = "0.25.4" } diff --git a/psst-gui/src/controller/playback.rs b/psst-gui/src/controller/playback.rs index bd01edee..279ee36f 100644 --- a/psst-gui/src/controller/playback.rs +++ b/psst-gui/src/controller/playback.rs @@ -13,9 +13,11 @@ use psst_core::{ audio::{normalize::NormalizationLevel, output::DefaultAudioOutput}, cache::Cache, cdn::Cdn, + lastfm::LastFmClient, player::{item::PlaybackItem, PlaybackConfig, Player, PlayerCommand, PlayerEvent}, session::SessionService, }; +use rustfm_scrobble_proxy::Scrobbler; use souvlaki::{ MediaControlEvent, MediaControls, MediaMetadata, MediaPlayback, MediaPosition, PlatformConfig, }; @@ -24,8 +26,8 @@ use crate::{ cmd, data::Nav, data::{ - AppState, Config, NowPlaying, Playback, PlaybackOrigin, PlaybackState, QueueBehavior, - QueueEntry, + AppState, Config, NowPlaying, Playable, Playback, PlaybackOrigin, PlaybackState, + QueueBehavior, QueueEntry, }, ui::lyrics, }; @@ -35,6 +37,8 @@ pub struct PlaybackController { thread: Option>, output: Option, media_controls: Option, + has_scrobbled: bool, + scrobbler: Option, } impl PlaybackController { @@ -44,6 +48,8 @@ impl PlaybackController { thread: None, output: None, media_controls: None, + has_scrobbled: false, + scrobbler: None, } } @@ -228,6 +234,59 @@ impl PlaybackController { } } + fn report_now_playing(&mut self, playback: &Playback) { + if let Some(now_playing) = playback.now_playing.as_ref() { + if let Playable::Track(track) = &now_playing.item { + if let Some(scrobbler) = &self.scrobbler { + let artist = track.artist_name(); + let title = track.name.clone(); + let album = track.album.clone(); + + if let Err(e) = LastFmClient::now_playing_song( + scrobbler, + artist.as_ref(), + title.as_ref(), + album.as_ref().map(|a| a.name.as_ref()), + ) { + log::warn!("Failed to report 'Now Playing' to Last.fm: {}", e); + } else { + log::info!("Reported 'Now Playing' to Last.fm: {} - {}", artist, title); + } + } else { + log::debug!("Last.fm not configured, skipping now_playing report."); + } + } + } + } + + fn report_scrobble(&mut self, playback: &Playback) { + if let Some(now_playing) = playback.now_playing.as_ref() { + if let Playable::Track(track) = &now_playing.item { + if now_playing.progress >= track.duration / 2 && !self.has_scrobbled { + if let Some(scrobbler) = &self.scrobbler { + let artist = track.artist_name(); + let title = track.name.clone(); + let album = track.album.clone(); + + if let Err(e) = LastFmClient::scrobble_song( + scrobbler, + artist.as_ref(), + title.as_ref(), + album.as_ref().map(|a| a.name.as_ref()), + ) { + log::warn!("Failed to scrobble track to Last.fm: {}", e); + } else { + log::info!("Scrobbled track to Last.fm: {} - {}", artist, title); + self.has_scrobbled = true; + } + } else { + log::debug!("Last.fm not configured, skipping scrobble."); + } + } + } + } + } + fn play(&mut self, items: &Vector, position: usize) { let playback_items = items.iter().map(|queued| PlaybackItem { item_id: queued.item.id(), @@ -289,7 +348,7 @@ impl PlaybackController { } else { now_playing.progress.saturating_sub(seek_duration) } - .min(now_playing.item.duration()); // Safeguard to not exceed the track duration. + .min(now_playing.item.duration()); self.seek(seek_position); } @@ -316,7 +375,7 @@ impl PlaybackController { })); } - fn update_lyrics(&self, ctx: &mut EventCtx, data: &AppState, now_playing: &NowPlaying) { + fn update_lyrics(&mut self, ctx: &mut EventCtx, data: &AppState, now_playing: &NowPlaying) { if matches!(data.nav, Nav::Lyrics) { ctx.submit_command(lyrics::SHOW_LYRICS.with(now_playing.clone())); } @@ -339,7 +398,6 @@ where Event::Command(cmd) if cmd.is(cmd::SET_FOCUS) => { ctx.request_focus(); } - // Player events. Event::Command(cmd) if cmd.is(cmd::PLAYBACK_LOADING) => { let item = cmd.get_unchecked(cmd::PLAYBACK_LOADING); @@ -355,6 +413,10 @@ where Event::Command(cmd) if cmd.is(cmd::PLAYBACK_PLAYING) => { let (item, progress) = cmd.get_unchecked(cmd::PLAYBACK_PLAYING); + // Song has changed, so we reset the has_scrobbled value + self.has_scrobbled = false; + self.report_now_playing(&data.playback); + if let Some(queued) = data.queued_entry(*item) { data.start_playback(queued.item, queued.origin, progress.to_owned()); self.update_media_control_playback(&data.playback); @@ -370,6 +432,8 @@ where Event::Command(cmd) if cmd.is(cmd::PLAYBACK_PROGRESS) => { let progress = cmd.get_unchecked(cmd::PLAYBACK_PROGRESS); data.progress_playback(progress.to_owned()); + + self.report_scrobble(&data.playback); self.update_media_control_playback(&data.playback); ctx.set_handled(); } @@ -392,7 +456,6 @@ where self.update_media_control_playback(&data.playback); ctx.set_handled(); } - // Playback actions. Event::Command(cmd) if cmd.is(cmd::PLAY_TRACKS) => { let payload = cmd.get_unchecked(cmd::PLAY_TRACKS); data.playback.queue = payload @@ -403,6 +466,7 @@ where item: item.to_owned(), }) .collect(); + self.play(&data.playback.queue, payload.position); ctx.set_handled(); } @@ -453,7 +517,6 @@ where Event::Command(cmd) if cmd.is(cmd::SKIP_TO_POSITION) => { let location = cmd.get_unchecked(cmd::SKIP_TO_POSITION); self.seek(Duration::from_millis(*location)); - ctx.set_handled(); } // Keyboard shortcuts. @@ -485,7 +548,6 @@ where data.playback.volume = (data.playback.volume - 0.1).max(0.0); ctx.set_handled(); } - // _ => child.event(ctx, event, data, env), } } @@ -536,6 +598,45 @@ where if !old_data.playback.volume.same(&data.playback.volume) { self.set_volume(data.playback.volume); } + + let lastfm_changed = old_data.config.lastfm_api_key != data.config.lastfm_api_key + || old_data.config.lastfm_api_secret != data.config.lastfm_api_secret + || old_data.config.lastfm_session_key != data.config.lastfm_session_key + || old_data.config.lastfm_enable != data.config.lastfm_enable; + + if lastfm_changed { + self.scrobbler = if data.config.lastfm_enable { + if let (Some(api_key), Some(api_secret), Some(session_key)) = ( + data.config.lastfm_api_key.as_deref(), + data.config.lastfm_api_secret.as_deref(), + data.config.lastfm_session_key.as_deref(), + ) { + match LastFmClient::create_scrobbler( + Some(api_key), + Some(api_secret), + Some(session_key), + ) { + Ok(scrobbler) => { + log::info!("Last.fm Scrobbler instance created/updated."); + Some(scrobbler) + } + Err(e) => { + log::warn!("Failed to create/update Last.fm Scrobbler instance: {}", e); + None + } + } + } else { + log::info!( + "Last.fm credentials incomplete or removed, clearing Scrobbler instance." + ); + None + } + } else { + log::info!("Last.fm scrobbling is disabled, clearing Scrobbler instance."); + None + }; + } + child.update(ctx, old_data, data, env); } } diff --git a/psst-gui/src/data/config.rs b/psst-gui/src/data/config.rs index bc6b6b4a..99010ce0 100644 --- a/psst-gui/src/data/config.rs +++ b/psst-gui/src/data/config.rs @@ -26,12 +26,15 @@ pub struct Preferences { pub active: PreferencesTab, pub cache_size: Promise, pub auth: Authentication, + pub lastfm_auth_result: Option, } impl Preferences { pub fn reset(&mut self) { self.cache_size.clear(); self.auth.result.clear(); + self.auth.lastfm_api_key_input.clear(); + self.auth.lastfm_api_secret_input.clear(); } pub fn measure_cache_usage() -> Option { @@ -53,9 +56,24 @@ pub struct Authentication { pub password: String, pub access_token: String, pub result: Promise<(), (), String>, + #[data(ignore)] + pub lastfm_api_key_input: String, + #[data(ignore)] + pub lastfm_api_secret_input: String, } impl Authentication { + pub fn new() -> Self { + Self { + username: String::new(), + password: String::new(), + access_token: String::new(), + result: Promise::Empty, + lastfm_api_key_input: String::new(), + lastfm_api_secret_input: String::new(), + } + } + pub fn session_config(&self) -> SessionConfig { SessionConfig { login_creds: if !self.access_token.is_empty() { @@ -102,6 +120,10 @@ pub struct Config { pub sort_criteria: SortCriteria, pub paginated_limit: usize, pub seek_duration: usize, + pub lastfm_session_key: Option, + pub lastfm_api_key: Option, + pub lastfm_api_secret: Option, + pub lastfm_enable: bool, } impl Default for Config { @@ -120,6 +142,10 @@ impl Default for Config { sort_criteria: Default::default(), paginated_limit: 500, seek_duration: 10, + lastfm_session_key: None, + lastfm_api_key: None, + lastfm_api_secret: None, + lastfm_enable: false, } } } @@ -183,7 +209,7 @@ impl Config { } pub fn store_credentials(&mut self, credentials: Credentials) { - self.credentials.replace(credentials); + self.credentials = Some(credentials); } pub fn clear_credentials(&mut self) { diff --git a/psst-gui/src/data/mod.rs b/psst-gui/src/data/mod.rs index 83769014..e4bdf3fe 100644 --- a/psst-gui/src/data/mod.rs +++ b/psst-gui/src/data/mod.rs @@ -119,13 +119,9 @@ impl AppState { config, preferences: Preferences { active: PreferencesTab::General, - auth: Authentication { - username: String::new(), - password: String::new(), - access_token: String::new(), - result: Promise::Empty, - }, cache_size: Promise::Empty, + auth: Authentication::new(), + lastfm_auth_result: None, }, playback, added_queue: Vector::new(), @@ -487,6 +483,18 @@ impl Library { } } +impl Default for Library { + fn default() -> Self { + Library { + user_profile: Promise::Empty, + playlists: Promise::Empty, + saved_albums: Promise::Empty, + saved_tracks: Promise::Empty, + saved_shows: Promise::Empty, + } + } +} + #[derive(Clone, Default, Data, Lens)] pub struct SavedTracks { pub tracks: Vector>, diff --git a/psst-gui/src/delegate.rs b/psst-gui/src/delegate.rs index 16369905..164b2f7d 100644 --- a/psst-gui/src/delegate.rs +++ b/psst-gui/src/delegate.rs @@ -55,43 +55,37 @@ impl Delegate { this } - fn show_main(&mut self, config: &Config, ctx: &mut DelegateCtx) { - match self.main_window { - Some(id) => { - ctx.submit_command(commands::SHOW_WINDOW.to(id)); - } - None => { - let window = ui::main_window(config); - self.main_window.replace(window.id); - ctx.new_window(window); - } + fn show_or_create_window( + window_id_option: &mut Option, + create_window_fn: F, + ctx: &mut DelegateCtx, + ) where + F: FnOnce() -> WindowDesc, + { + if let Some(id) = window_id_option { + ctx.submit_command(commands::SHOW_WINDOW.to(*id)); + } else { + let window = create_window_fn(); + *window_id_option = Some(window.id); + ctx.new_window(window); } } + fn show_main(&mut self, config: &Config, ctx: &mut DelegateCtx) { + let config_clone = config.clone(); + Self::show_or_create_window( + &mut self.main_window, + || ui::main_window(&config_clone), + ctx, + ); + } + fn show_account_setup(&mut self, ctx: &mut DelegateCtx) { - match self.preferences_window { - Some(id) => { - ctx.submit_command(commands::SHOW_WINDOW.to(id)); - } - None => { - let window = ui::account_setup_window(); - self.preferences_window.replace(window.id); - ctx.new_window(window); - } - } + Self::show_or_create_window(&mut self.preferences_window, ui::account_setup_window, ctx); } fn show_preferences(&mut self, ctx: &mut DelegateCtx) { - match self.preferences_window { - Some(id) => { - ctx.submit_command(commands::SHOW_WINDOW.to(id)); - } - None => { - let window = ui::preferences_window(); - self.preferences_window.replace(window.id); - ctx.new_window(window); - } - } + Self::show_or_create_window(&mut self.preferences_window, ui::preferences_window, ctx); } fn close_all_windows(&mut self, ctx: &mut DelegateCtx) { @@ -133,16 +127,7 @@ impl Delegate { } fn show_artwork(&mut self, ctx: &mut DelegateCtx) { - match self.artwork_window { - Some(id) => { - ctx.submit_command(commands::SHOW_WINDOW.to(id)); - } - None => { - let window = ui::artwork_window(); - self.artwork_window.replace(window.id); - ctx.new_window(window); - } - } + Self::show_or_create_window(&mut self.artwork_window, ui::artwork_window, ctx); } } @@ -280,7 +265,6 @@ impl AppDelegate for Delegate { ) -> Option { if self.main_window == Some(window_id) { if let Event::WindowSize(size) = event { - // This is a little hacky, but without it, the window will slowly get smaller each time the application is opened. if !self.size_updated { self.size_updated = true; } else { diff --git a/psst-gui/src/main.rs b/psst-gui/src/main.rs index 8832a3ac..f53a23ff 100644 --- a/psst-gui/src/main.rs +++ b/psst-gui/src/main.rs @@ -31,9 +31,11 @@ fn main() { ) .init(); + // Load configuration let config = Config::load().unwrap_or_default(); + let paginated_limit = config.paginated_limit; - let state = AppState::default_with_config(config); + let state = AppState::default_with_config(config.clone()); WebApi::new( state.session.clone(), Config::proxy().as_deref(), @@ -63,4 +65,6 @@ fn main() { .delegate(delegate) .launch(state) .expect("Application launch"); + + config.save(); } diff --git a/psst-gui/src/ui/preferences.rs b/psst-gui/src/ui/preferences.rs index 27548342..b74fd7f8 100644 --- a/psst-gui/src/ui/preferences.rs +++ b/psst-gui/src/ui/preferences.rs @@ -1,5 +1,6 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use std::thread::{self, JoinHandle}; +use std::time::Duration; use crate::{ cmd, @@ -7,23 +8,46 @@ use crate::{ AppState, AudioQuality, Authentication, Config, Preferences, PreferencesTab, Promise, SliderScrollScale, Theme, }, - webapi::WebApi, widget::{icons, Async, Border, Checkbox, MyWidgetExt}, }; use druid::{ - commands, text::ParseFormatter, widget::{ Button, Controller, CrossAxisAlignment, Flex, Label, LineBreaking, MainAxisAlignment, RadioGroup, SizedBox, Slider, TextBox, ViewSwitcher, }, - Color, Data, Env, Event, EventCtx, Insets, LensExt, LifeCycle, LifeCycleCtx, Selector, Widget, - WidgetExt, + Color, Data, Env, Event, EventCtx, Insets, Lens, LensExt, LifeCycle, LifeCycleCtx, Selector, + Widget, WidgetExt, }; -use psst_core::{connection::Credentials, oauth, session::SessionConfig}; +use psst_core::{connection::Credentials, lastfm, oauth, session::SessionConfig}; use super::{icons::SvgIcon, theme}; +// Helper function for creating a labeled input row +fn make_input_row( + label_text: &'static str, + placeholder_text: &'static str, + lens: L, +) -> impl Widget +where + L: Lens + 'static, +{ + Flex::row() + .cross_axis_alignment(CrossAxisAlignment::Center) + .with_child( + SizedBox::new(Label::new(label_text)) + .width(theme::grid(12.0)) + .align_left(), + ) + .with_flex_child( + TextBox::new() + .with_placeholder(placeholder_text) + .lens(lens) + .fix_width(theme::grid(30.0)), + 1.0, + ) +} + pub fn account_setup_widget() -> impl Widget { Flex::column() .must_fill_main_axis(true) @@ -264,25 +288,41 @@ fn account_tab_widget(tab: AccountTab) -> impl Widget { if matches!(tab, AccountTab::InPreferences) { col = col - .with_child(Label::new("Credentials").with_font(theme::UI_FONT_MEDIUM)) + .with_child(Label::new("Spotify Account").with_font(theme::UI_FONT_MEDIUM)) .with_spacer(theme::grid(2.0)); } + // Spotify Login/Logout button col = col - .with_child( - Button::new("Log in with Spotify").on_click(|ctx, _data: &mut AppState, _| { - ctx.submit_command(Authenticate::REQUEST); - }), - ) + .with_child(ViewSwitcher::new( + |data: &AppState, _| data.config.has_credentials(), + |is_logged_in, _, _| { + if *is_logged_in { + Button::new("Log Out") + .on_left_click(|ctx, _, _, _| { + ctx.submit_command(cmd::LOG_OUT); + }) + .boxed() + } else { + Button::new("Log in with Spotify") + .on_click(|ctx, _data: &mut AppState, _| { + ctx.submit_command(Authenticate::SPOTIFY_REQUEST); + }) + .boxed() + } + }, + )) .with_spacer(theme::grid(1.0)) .with_child( Async::new( || Label::new("Logging in...").with_text_size(theme::TEXT_SIZE_SMALL), - || Label::new("").with_text_size(theme::TEXT_SIZE_SMALL), + // Spotify Success Arm: Show nothing + || SizedBox::empty().boxed(), || { + // Error arm remains the same Label::dynamic(|err: &String, _| err.to_owned()) .with_text_size(theme::TEXT_SIZE_SMALL) - .with_text_color(theme::RED) + .with_text_color(druid::Color::RED) }, ) .lens( @@ -293,29 +333,234 @@ fn account_tab_widget(tab: AccountTab) -> impl Widget { ); if matches!(tab, AccountTab::InPreferences) { - col = col.with_child(Button::new("Log Out").on_left_click(|ctx, _, _, _| { - ctx.submit_command(cmd::LOG_OUT); - })) + col = col + .with_spacer(theme::grid(2.0)) + .with_child(Label::new("Last.fm Account").with_font(theme::UI_FONT_MEDIUM)) + .with_spacer(theme::grid(1.0)) + .with_child( + Label::new("Connect your Last.fm account to scrobble tracks you listen to.") + .with_text_color(theme::PLACEHOLDER_COLOR) + .with_line_break_mode(LineBreaking::WordWrap), + ) + .with_spacer(theme::grid(2.0)) + .with_child(ViewSwitcher::new( + |data: &AppState, _| data.config.lastfm_session_key.is_some(), + |connected, _, _| { + if *connected { + // --- Connected View --- + Flex::column() + .cross_axis_alignment(CrossAxisAlignment::Start) + .with_child( + Flex::row() + .with_child( + Checkbox::new("Toggle scrobbling") + .lens(AppState::config.then(Config::lastfm_enable)) + .padding((0.0, 0.0, theme::grid(1.0), 0.0)), + ) + .with_child(Button::new("Disconnect").on_click( + |_ctx, data: &mut AppState, _| { + data.config.lastfm_session_key = None; + data.preferences.lastfm_auth_result = None; + data.config.lastfm_api_key = None; + data.config.lastfm_api_secret = None; + data.config.save(); + }, + )), + ) + .boxed() + } else { + // --- Disconnected View --- + Flex::column() + .cross_axis_alignment(CrossAxisAlignment::Start) + .with_child(make_input_row( + "API Key:", + "Enter your Last.fm API Key", + AppState::preferences + .then(Preferences::auth) + .then(Authentication::lastfm_api_key_input), + )) + .with_default_spacer() + .with_child(make_input_row( + "API Secret:", + "Enter your Last.fm API Secret", + AppState::preferences + .then(Preferences::auth) + .then(Authentication::lastfm_api_secret_input), + )) + .with_spacer(theme::grid(2.0)) + .with_child( + Flex::row() // Put buttons in a row + .with_child( + Button::new("Connect Last.fm Account").on_click( + |ctx, data: &mut AppState, _| { + // Check directly from config + if data.config.lastfm_api_key.is_some() + && data.config.lastfm_api_secret.is_some() + { + ctx.submit_command( + Authenticate::LASTFM_REQUEST, + ); + } else { + data.preferences.lastfm_auth_result = Some( + "API Key and Secret required." + .to_string(), + ); + } + }, + ), + ) + .with_spacer(theme::grid(1.0)) + .with_child(Button::new("Request API Key").on_click( + |_, _, _| { + open::that( + "https://www.last.fm/api/account/create", + ) + .ok(); + }, + )), + ) + .with_spacer(theme::grid(1.0)) + // Last.fm Status label + .with_child(ViewSwitcher::new( + |data: &AppState, _| { + data.preferences + .lastfm_auth_result + .clone() + .unwrap_or_default() + }, + |msg: &String, _, _| { + // Only show label if there's an error or connecting message + if msg.is_empty() || msg.starts_with("Success") { + SizedBox::empty().boxed() + } else { + Label::new(msg.clone()) + .with_text_color(if msg.starts_with("Connect") { + druid::Color::GRAY + } else { + druid::Color::RED + }) + .boxed() + } + }, + )) + .boxed() + } + }, + )); } - col.controller(Authenticate::new(tab)) } -struct Authenticate { +pub struct Authenticate { tab: AccountTab, - thread: Option>, + spotify_thread: Option>, + lastfm_thread: Option>, } impl Authenticate { fn new(tab: AccountTab) -> Self { - Self { tab, thread: None } + Self { + tab, + spotify_thread: None, + lastfm_thread: None, + } + } + + // Helper function to spawn authentication threads + fn spawn_auth_thread( + ctx: &mut EventCtx, + auth_logic: impl FnOnce() -> Result + Send + 'static, + response_selector: Selector>, + existing_handle: Option>, + ) -> Option> { + // Clean up previous thread if any + if let Some(_handle) = existing_handle { + // Consider if joining is necessary/desirable + } + + let window_id = ctx.window_id(); + let event_sink = ctx.get_external_handle(); + + let thread = thread::spawn(move || { + let result = auth_logic(); + event_sink + .submit_command(response_selector, result, window_id) + .unwrap(); + }); + Some(thread) + } + + // Helper method to simplify Spotify authentication flow + fn start_spotify_auth(&mut self, ctx: &mut EventCtx, data: &mut AppState) { + // Set authentication to in-progress state + data.preferences.auth.result.defer_default(); + + // Generate auth URL and store PKCE verifier + let (auth_url, pkce_verifier) = oauth::generate_auth_url(8888); + let config = data.preferences.auth.session_config(); // Keep config local + + // Spawn authentication thread + self.spotify_thread = Authenticate::spawn_auth_thread( + ctx, + move || { + // Listen for authorization code + let code = oauth::get_authcode_listener( + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8888), + Duration::from_secs(300), + ) + .map_err(|e| e.to_string())?; + + // Exchange code for access token + let token = oauth::exchange_code_for_token(8888, code, pkce_verifier); + + // Try to authenticate with token, with retries + let mut retries = 3; + while retries > 0 { + match Authentication::authenticate_and_get_credentials(SessionConfig { + login_creds: Credentials::from_access_token(token.clone()), + ..config.clone() + }) { + Ok(credentials) => return Ok(credentials), + Err(e) if retries > 1 => { + log::warn!("authentication failed, retrying: {:?}", e); + retries -= 1; + } + Err(e) => return Err(e), + } + } + Err("Authentication retries exceeded".to_string()) + }, + Self::SPOTIFY_RESPONSE, + self.spotify_thread.take(), + ); + + // Open browser with authorization URL + if open::that(&auth_url).is_err() { + data.error_alert("Failed to open browser"); + // Resolve the promise with an error immediately + data.preferences + .auth + .result + .reject((), "Failed to open browser".to_string()); + } } } impl Authenticate { - const REQUEST: Selector = Selector::new("app.preferences.authenticate-request"); - const RESPONSE: Selector> = - Selector::new("app.preferences.authenticate-response"); + pub const SPOTIFY_REQUEST: Selector = + Selector::new("app.preferences.spotify.authenticate-request"); + pub const SPOTIFY_RESPONSE: Selector> = + Selector::new("app.preferences.spotify.authenticate-response"); + + // Selector for initializing fields + pub const INITIALIZE_LASTFM_FIELDS: Selector = + Selector::new("app.preferences.lastfm.initialize-fields"); + + // Last.fm selectors + pub const LASTFM_REQUEST: Selector = + Selector::new("app.preferences.lastfm.authenticate-request"); + pub const LASTFM_RESPONSE: Selector> = + Selector::new("app.preferences.lastfm.authenticate-response"); } impl> Controller for Authenticate { @@ -328,103 +573,120 @@ impl> Controller for Authenticate { env: &Env, ) { match event { - Event::Command(cmd) if cmd.is(Self::REQUEST) => { - data.preferences.auth.result.defer_default(); + Event::Command(cmd) if cmd.is(Self::SPOTIFY_REQUEST) => { + self.start_spotify_auth(ctx, data); + ctx.set_handled(); + } + Event::Command(cmd) if cmd.is(Self::INITIALIZE_LASTFM_FIELDS) => { + data.preferences.auth.lastfm_api_key_input = + data.config.lastfm_api_key.clone().unwrap_or_default(); + data.preferences.auth.lastfm_api_secret_input = + data.config.lastfm_api_secret.clone().unwrap_or_default(); + ctx.set_handled(); + } + Event::Command(cmd) if cmd.is(cmd::LOG_OUT) => { + data.config.clear_credentials(); + data.config.save(); + data.session.shutdown(); + ctx.submit_command(cmd::CLOSE_ALL_WINDOWS); + ctx.submit_command(cmd::SHOW_ACCOUNT_SETUP); + ctx.set_handled(); + } + Event::Command(cmd) if cmd.is(Self::LASTFM_REQUEST) => { + // Use the temporary input fields from preferences state. + let api_key = data.preferences.auth.lastfm_api_key_input.clone(); + let api_secret = data.preferences.auth.lastfm_api_secret_input.clone(); + + if api_key.is_empty() || api_secret.is_empty() { + data.preferences.lastfm_auth_result = + Some("API Key and Secret required.".to_string()); + ctx.set_handled(); + return; + } - let (auth_url, pkce_verifier) = oauth::generate_auth_url(8888); - let config = data.preferences.auth.session_config(); - let widget_id = ctx.widget_id(); - let event_sink = ctx.get_external_handle(); - let thread = thread::spawn(move || { - match oauth::get_authcode_listener( - SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 8888), - std::time::Duration::from_secs(300), - ) { - Ok(code) => { - let token = oauth::exchange_code_for_token(8888, code, pkce_verifier); - let mut retries = 3; - while retries > 0 { - let response = Authentication::authenticate_and_get_credentials( - SessionConfig { - login_creds: Credentials::from_access_token(token.clone()), - ..config.clone() - }, - ); - match response { - Ok(credentials) => { - event_sink - .submit_command( - Self::RESPONSE, - Ok(credentials), - widget_id, - ) - .unwrap(); - return; - } - Err(e) if retries > 1 => { - log::warn!("authentication failed, retrying: {:?}", e); - retries -= 1; - } - Err(e) => { - event_sink - .submit_command(Self::RESPONSE, Err(e), widget_id) - .unwrap(); - return; - } - } - } - } - Err(e) => { - event_sink - .submit_command(Self::RESPONSE, Err(e), widget_id) - .unwrap(); + data.preferences.lastfm_auth_result = Some("Connecting...".to_string()); + let port = 8889; + let callback_url = format!("http://127.0.0.1:{}/lastfm_callback", port); + let socket_addr = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port); + + match lastfm::generate_lastfm_auth_url(&api_key, &callback_url) { + Ok(auth_url) => { + self.lastfm_thread = Authenticate::spawn_auth_thread( + ctx, + move || { + let token = lastfm::get_lastfm_token_listener( + socket_addr, + Duration::from_secs(300), + ) + .map_err(|e| e.to_string())?; + log::info!("Received Last.fm token, exchanging..."); + lastfm::exchange_token_for_session(&api_key, &api_secret, &token) + .map_err(|e| format!("Token exchange failed: {}", e)) + }, + Self::LASTFM_RESPONSE, + self.lastfm_thread.take(), + ); + + if open::that(&auth_url).is_err() { + data.preferences.lastfm_auth_result = + Some("Failed to open browser.".to_string()); + // No promise to reject here, just update the status message } } - }); - self.thread.replace(thread); - ctx.set_handled(); - - if open::that(&auth_url).is_err() { - data.error_alert("Failed to open browser"); + Err(e) => { + data.preferences.lastfm_auth_result = + Some(format!("Failed to create auth URL: {}", e)); + } } + ctx.set_handled(); } - Event::Command(cmd) if cmd.is(Self::RESPONSE) => { - self.thread.take(); - - let result = cmd - .get_unchecked(Self::RESPONSE) - .to_owned() - .map(|credentials| { - let username = credentials.username.clone().unwrap_or_default(); - WebApi::global().load_local_tracks(&username); - data.config.store_credentials(credentials); + Event::Command(cmd) if cmd.is(Self::SPOTIFY_RESPONSE) => { + let result = cmd.get_unchecked(Self::SPOTIFY_RESPONSE); + match result { + Ok(credentials) => { + // Update session config with the new credentials + data.session.update_config(SessionConfig { + login_creds: credentials.clone(), + proxy_url: Config::proxy(), + }); + data.config.store_credentials(credentials.clone()); data.config.save(); - }); - let is_ok = result.is_ok(); - - data.preferences.auth.result.resolve_or_reject((), result); - - if is_ok { - match &self.tab { - AccountTab::FirstSetup => { + data.preferences.auth.result.resolve((), ()); + // Handle UI flow based on tab type + if matches!(self.tab, AccountTab::FirstSetup) { + ctx.submit_command(cmd::CLOSE_ALL_WINDOWS); ctx.submit_command(cmd::SHOW_MAIN); - ctx.submit_command(commands::CLOSE_WINDOW); - } - AccountTab::InPreferences => { - ctx.submit_command(cmd::SESSION_CONNECT); } } + Err(err) => { + data.preferences.auth.result.reject((), err.clone()); + } } - data.preferences.auth.access_token.clear(); - + self.spotify_thread.take(); ctx.set_handled(); } - Event::Command(cmd) if cmd.is(cmd::LOG_OUT) => { - data.config.clear_credentials(); - data.config.save(); - data.session.shutdown(); - ctx.submit_command(cmd::CLOSE_ALL_WINDOWS); - ctx.submit_command(cmd::SHOW_ACCOUNT_SETUP); + Event::Command(cmd) if cmd.is(Self::LASTFM_RESPONSE) => { + let result = cmd.get_unchecked(Self::LASTFM_RESPONSE); + match result { + Ok(session_key) => { + // On success, store the validated key/secret in config and save. + data.config.lastfm_api_key = + Some(data.preferences.auth.lastfm_api_key_input.clone()); + data.config.lastfm_api_secret = + Some(data.preferences.auth.lastfm_api_secret_input.clone()); + data.config.lastfm_session_key = Some(session_key.clone()); + data.config.save(); + + log::info!("Last.fm session key stored successfully."); + + data.preferences.lastfm_auth_result = + Some("Success! Last.fm connected.".to_string()); + } + Err(err) => { + data.preferences.lastfm_auth_result = Some(err.clone()); + } + } + self.lastfm_thread.take(); ctx.set_handled(); } _ => { @@ -432,6 +694,20 @@ impl> Controller for Authenticate { } } } + + fn lifecycle( + &mut self, + child: &mut W, + ctx: &mut LifeCycleCtx, + event: &LifeCycle, + data: &AppState, + env: &Env, + ) { + if let LifeCycle::WidgetAdded = event { + ctx.submit_command(Self::INITIALIZE_LASTFM_FIELDS); + } + child.lifecycle(ctx, event, data, env); + } } fn cache_tab_widget() -> impl Widget { diff --git a/psst-gui/src/widget/icons.rs b/psst-gui/src/widget/icons.rs index cc8d9eb5..bfeba75b 100644 --- a/psst-gui/src/widget/icons.rs +++ b/psst-gui/src/widget/icons.rs @@ -120,6 +120,7 @@ pub static HEART: SvgIcon = SvgIcon { svg_size: Size::new(22.0, 22.0), op: PaintOp::Fill, }; + // SF Pro Regular - person.crop.circle pub static ARTIST: SvgIcon = SvgIcon { svg_path: "M10.9912 19.7422C15.9746 19.7422 20.0879 15.6289 20.0879 10.6543C20.0879 5.67969 15.9658 1.56641 10.9824 1.56641C6.00781 1.56641 1.90332 5.67969 1.90332 10.6543C1.90332 15.6289 6.0166 19.7422 10.9912 19.7422ZM10.9912 13.6953C8.5127 13.6953 6.58789 14.583 5.65625 15.6025C4.46094 14.3105 3.73145 12.5703 3.73145 10.6543C3.73145 6.62012 6.95703 3.38574 10.9824 3.38574C15.0166 3.38574 18.2598 6.62012 18.2686 10.6543C18.2686 12.5703 17.5391 14.3105 16.335 15.6113C15.4033 14.583 13.4785 13.6953 10.9912 13.6953ZM10.9912 12.2539C12.6963 12.2715 14.0234 10.8125 14.0234 8.93164C14.0234 7.15625 12.6875 5.6709 10.9912 5.6709C9.30371 5.6709 7.95898 7.15625 7.96777 8.93164C7.97656 10.8125 9.29492 12.2451 10.9912 12.2539Z", @@ -170,6 +171,13 @@ pub static CIRCLE_CHECK: SvgIcon = SvgIcon { op: PaintOp::Fill, }; +// LastFM Logo: +// pub static LASTFM: SvgIcon = SvgIcon { +// svg_path: "M2.519 7.88C3.62 6.7 5.282 6 7.5 6c0.95 0 1.763 0.182 2.454 0.544 0.694 0.364 1.208 0.88 1.598 1.462 0.668 0.996 1.016 2.27 1.316 3.371l0.097 0.356c0.352 1.269 0.695 2.31 1.33 3.058C14.867 15.468 15.77 16 17.5 16c0.433 0 1.435 -0.078 2.29 -0.382 0.917 -0.325 1.21 -0.718 1.21 -1.118 0 -0.217 -0.075 -0.412 -0.558 -0.665 -0.507 -0.266 -1.205 -0.45 -2.073 -0.677l-0.123 -0.033c-0.848 -0.223 -1.868 -0.497 -2.67 -0.981C14.713 11.622 14 10.788 14 9.5c0 -0.884 0.526 -1.766 1.272 -2.391C16.05 6.456 17.154 6 18.5 6c2.828 0 4.185 1.616 4.47 2.757l-1.94 0.486C20.982 9.05 20.472 8 18.5 8c-0.883 0 -1.53 0.294 -1.943 0.641 -0.448 0.375 -0.557 0.743 -0.557 0.859 0 0.397 0.163 0.661 0.61 0.932 0.512 0.31 1.242 0.522 2.145 0.759l0.2 0.052c0.784 0.205 1.696 0.444 2.415 0.82 0.83 0.435 1.63 1.18 1.63 2.437 0 1.762 -1.457 2.619 -2.54 3.003 -1.145 0.406 -2.393 0.497 -2.96 0.497 -2.216 0 -3.716 -0.718 -4.732 -1.916 -0.955 -1.127 -1.389 -2.586 -1.73 -3.817l-0.086 -0.313c-0.325 -1.18 -0.586 -2.127 -1.061 -2.835a2.34 2.34 0 0 0 -0.865 -0.804C8.674 8.131 8.19 8 7.5 8c-1.782 0 -2.87 0.55 -3.519 1.245C3.318 9.955 3 10.94 3 12c0 0.925 0.472 1.933 1.27 2.73C5.067 15.527 6.075 16 7 16c0.888 0 1.566 -0.148 2.039 -0.317a3.32 3.32 0 0 0 0.55 -0.25 1.685 1.685 0 0 0 0.204 -0.14l1.414 1.414C10.64 17.276 9.19 18 7 18c-1.575 0 -3.067 -0.777 -4.145 -1.855C1.778 15.067 1 13.575 1 12c0 -1.44 0.432 -2.956 1.519 -4.12Z", +// svg_size: Size::new(22.0, 22.0), +// op: PaintOp::Fill, +// }; + #[derive(Copy, Clone)] pub enum PaintOp { Fill, diff --git a/psst-protocol/build.sh b/psst-protocol/build.sh index 978748e4..82f55603 100755 --- a/psst-protocol/build.sh +++ b/psst-protocol/build.sh @@ -1,10 +1,10 @@ #!/bin/sh pb-rs \ - --dont_use_cow \ - --output_directory src \ - "proto/authentication.proto" \ - "proto/keyexchange.proto" \ - "proto/mercury.proto" \ - "proto/metadata.proto" -rm src/mod.rs \ No newline at end of file + --dont_use_cow \ + --output_directory src \ + "proto/authentication.proto" \ + "proto/keyexchange.proto" \ + "proto/mercury.proto" \ + "proto/metadata.proto" +rm src/mod.rs