diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1c5a03f1..b5db6817 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,7 @@ jobs: target: x86_64-unknown-linux-gnu - os: ubuntu-latest target: aarch64-unknown-linux-gnu - - os: macos-latest + - os: macOS-latest - os: windows-latest runs-on: ${{ matrix.os }} @@ -130,8 +130,9 @@ jobs: --icon "Psst.app" 150 160 \ --hide-extension "Psst.app" \ --app-drop-link 450 160 \ + --no-internet-enable \ "Psst.dmg" \ - "../target/release/bundle/osx/Psst.app" + "target/release/bundle/osx/Psst.app" working-directory: psst-gui - name: Upload macOS DMG diff --git a/Cargo.lock b/Cargo.lock index 4c63ef8b..1dc352e1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -23,7 +23,7 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cipher", "cpufeatures", ] @@ -57,7 +57,7 @@ checksum = "ed7572b7ba83a31e20d1b48970ee402d2e3e0537dcfe0a3ff4d6eb7508617d43" dependencies = [ "alsa-sys", "bitflags 2.9.0", - "cfg-if", + "cfg-if 1.0.0", "libc", ] @@ -227,7 +227,7 @@ checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock 2.8.0", "autocfg", - "cfg-if", + "cfg-if 1.0.0", "concurrent-queue", "futures-lite 1.13.0", "log", @@ -246,7 +246,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a2b323ccce0a1d90b449fd71f2a06ca7faa7c54c2751f06c9bd851fc061059" dependencies = [ "async-lock 3.4.0", - "cfg-if", + "cfg-if 1.0.0", "concurrent-queue", "futures-io", "futures-lite 2.6.0", @@ -288,7 +288,7 @@ dependencies = [ "async-lock 2.8.0", "async-signal", "blocking", - "cfg-if", + "cfg-if 1.0.0", "event-listener 3.1.0", "futures-lite 1.13.0", "rustix 0.38.44", @@ -315,7 +315,7 @@ dependencies = [ "async-io 2.4.0", "async-lock 3.4.0", "atomic-waker", - "cfg-if", + "cfg-if 1.0.0", "futures-core", "futures-io", "rustix 0.38.44", @@ -377,7 +377,7 @@ version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "716b7ed619475af650836aa562422e724b807201acd10046298d6efa22b558a3" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "dbus", "libc", "log", @@ -400,7 +400,7 @@ dependencies = [ "anyhow", "arrayvec", "log", - "nom", + "nom 7.1.3", "num-rational", "v_frame", ] @@ -421,7 +421,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6806a6321ec58106fea15becdad98371e28d92ccbc7c8f1b3b6dd724fe8f1002" dependencies = [ "addr2line", - "cfg-if", + "cfg-if 1.0.0", "libc", "miniz_oxide", "object", @@ -455,20 +455,22 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bindgen" -version = "0.70.1" +version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" +checksum = "99de13bb6361e01e493b3db7928085dcc474b7ba4f5481818e53a89d76b8393f" dependencies = [ - "bitflags 2.9.0", + "bitflags 1.3.2", "cexpr", + "cfg-if 0.1.10", "clang-sys", - "itertools 0.13.0", + "lazy_static", + "lazycell", + "peeking_take_while", "proc-macro2", "quote", "regex", "rustc-hash", - "shlex", - "syn 2.0.101", + "shlex 0.1.1", ] [[package]] @@ -612,7 +614,7 @@ checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" dependencies = [ "jobserver", "libc", - "shlex", + "shlex 1.3.0", ] [[package]] @@ -623,11 +625,11 @@ checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" [[package]] name = "cexpr" -version = "0.6.0" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +checksum = "fce5b5fb86b0c57c20c834c1b412fd09c77c8a59b9473f86272709e78874cd1d" dependencies = [ - "nom", + "nom 4.2.3", ] [[package]] @@ -651,6 +653,12 @@ dependencies = [ "target-lexicon", ] +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -690,9 +698,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.8.1" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +checksum = "81de550971c976f176130da4b2978d3b524eaa0fd9ac31f3ceb5ae1231fb4853" dependencies = [ "glob", "libc", @@ -775,7 +783,7 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "wasm-bindgen", ] @@ -793,7 +801,7 @@ checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951" dependencies = [ "percent-encoding", "time 0.2.27", - "version_check", + "version_check 0.9.5", ] [[package]] @@ -804,7 +812,7 @@ checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "percent-encoding", "time 0.3.41", - "version_check", + "version_check 0.9.5", ] [[package]] @@ -906,9 +914,9 @@ dependencies = [ [[package]] name = "coreaudio-sys" -version = "0.2.16" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce857aa0b77d77287acc1ac3e37a05a8c95a2af3647d23b15f263bdaeb7562b" +checksum = "17f73df0f29f4c3c374854f076c47dc018f19acaa63538880dba0937ad4fa8d7" dependencies = [ "bindgen", ] @@ -951,7 +959,7 @@ version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -1046,12 +1054,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" -[[package]] -name = "data-encoding" -version = "2.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a2330da5de22e8a3cb63252ce2abb30116bf5265e89c0e01bc17015ce30a476" - [[package]] name = "dbus" version = "0.6.5" @@ -1108,7 +1110,7 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf36e65a80337bea855cd4ef9b8401ffce06a7baedf2e85ec467b1ac3f6e82b6" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "dirs-sys-next", ] @@ -1220,7 +1222,7 @@ dependencies = [ "bitflags 1.3.2", "block", "cairo-rs", - "cfg-if", + "cfg-if 1.0.0", "cocoa", "core-graphics", "foreign-types", @@ -1268,7 +1270,7 @@ version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -1684,7 +1686,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", - "version_check", + "version_check 0.9.5", ] [[package]] @@ -1693,7 +1695,7 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", @@ -1706,7 +1708,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "r-efi", "wasi 0.14.2+wasi-0.2.4", @@ -1791,7 +1793,7 @@ dependencies = [ "gix-date", "gix-utils", "itoa", - "thiserror 2.0.16", + "thiserror 2.0.12", "winnow 0.7.9", ] @@ -1811,7 +1813,7 @@ dependencies = [ "memchr", "once_cell", "smallvec", - "thiserror 2.0.16", + "thiserror 2.0.12", "unicode-bom", "winnow 0.7.9", ] @@ -1826,7 +1828,7 @@ dependencies = [ "bstr", "gix-path", "libc", - "thiserror 2.0.16", + "thiserror 2.0.12", ] [[package]] @@ -1839,7 +1841,7 @@ dependencies = [ "itoa", "jiff", "smallvec", - "thiserror 2.0.16", + "thiserror 2.0.12", ] [[package]] @@ -1867,7 +1869,7 @@ dependencies = [ "gix-features", "gix-path", "gix-utils", - "thiserror 2.0.16", + "thiserror 2.0.12", ] [[package]] @@ -1891,7 +1893,7 @@ dependencies = [ "faster-hex", "gix-features", "sha1-checked", - "thiserror 2.0.16", + "thiserror 2.0.12", ] [[package]] @@ -1913,7 +1915,7 @@ checksum = "570f8b034659f256366dc90f1a24924902f20acccd6a15be96d44d1269e7a796" dependencies = [ "gix-tempfile", "gix-utils", - "thiserror 2.0.16", + "thiserror 2.0.12", ] [[package]] @@ -1933,7 +1935,7 @@ dependencies = [ "gix-validate", "itoa", "smallvec", - "thiserror 2.0.16", + "thiserror 2.0.12", "winnow 0.7.9", ] @@ -1948,7 +1950,7 @@ dependencies = [ "gix-validate", "home", "once_cell", - "thiserror 2.0.16", + "thiserror 2.0.12", ] [[package]] @@ -1968,7 +1970,7 @@ dependencies = [ "gix-utils", "gix-validate", "memmap2", - "thiserror 2.0.16", + "thiserror 2.0.12", "winnow 0.7.9", ] @@ -2020,7 +2022,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77b9e00cacde5b51388d28ed746c493b18a6add1f19b5e01d686b3b9ece66d4d" dependencies = [ "bstr", - "thiserror 2.0.16", + "thiserror 2.0.12", ] [[package]] @@ -2167,7 +2169,7 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "crunchy", ] @@ -2545,7 +2547,7 @@ dependencies = [ "serde", "sized-chunks", "typenum", - "version_check", + "version_check 0.9.5", ] [[package]] @@ -2635,7 +2637,7 @@ version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "wasm-bindgen", "web-sys", @@ -2722,15 +2724,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.14.0" @@ -2794,7 +2787,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" dependencies = [ "cesu8", - "cfg-if", + "cfg-if 1.0.0", "combine", "jni-sys", "log", @@ -2860,6 +2853,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "lebe" version = "0.5.2" @@ -2868,9 +2867,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libdbus-sys" @@ -2893,12 +2892,12 @@ dependencies = [ [[package]] name = "libloading" -version = "0.8.6" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" dependencies = [ - "cfg-if", - "windows-targets 0.52.6", + "cc", + "winapi", ] [[package]] @@ -2911,16 +2910,6 @@ dependencies = [ "libc", ] -[[package]] -name = "librespot-protocol" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "193cdc35135a01dde3f5c3f51de4597ca8b7bec8291c2f0c8ff55d64df25a2ed" -dependencies = [ - "protobuf", - "protobuf-codegen", -] - [[package]] name = "libsamplerate" version = "0.1.0" @@ -3061,7 +3050,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "rayon", ] @@ -3179,11 +3168,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" dependencies = [ "bitflags 1.3.2", - "cfg-if", + "cfg-if 1.0.0", "libc", "memoffset 0.7.1", ] +[[package]] +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +dependencies = [ + "memchr", + "version_check 0.1.5", +] + [[package]] name = "nom" version = "7.1.3" @@ -3200,15 +3199,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8" -[[package]] -name = "ntapi" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" -dependencies = [ - "winapi", -] - [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -3345,25 +3335,6 @@ dependencies = [ "malloc_buf", ] -[[package]] -name = "objc2-core-foundation" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166" -dependencies = [ - "bitflags 2.9.0", -] - -[[package]] -name = "objc2-io-kit" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a" -dependencies = [ - "libc", - "objc2-core-foundation", -] - [[package]] name = "object" version = "0.36.7" @@ -3510,7 +3481,7 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "libc", "redox_syscall", "smallvec", @@ -3529,6 +3500,12 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -3606,7 +3583,7 @@ checksum = "9dd8497cc0bcfecb1e14e027428c5e3eaf9af6e14761176e1212006d8bdba387" dependencies = [ "cairo-rs", "cairo-sys-rs", - "cfg-if", + "cfg-if 1.0.0", "core-graphics", "piet", "piet-cairo", @@ -3719,7 +3696,7 @@ checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", "bitflags 1.3.2", - "cfg-if", + "cfg-if 1.0.0", "concurrent-queue", "libc", "log", @@ -3733,7 +3710,7 @@ version = "3.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a604568c3202727d1507653cb121dbd627a58684eb09a820fd746bee38b4442f" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "concurrent-queue", "hermit-abi 0.4.0", "pin-project-lite", @@ -3813,7 +3790,7 @@ dependencies = [ "proc-macro2", "quote", "syn 1.0.109", - "version_check", + "version_check 0.9.5", ] [[package]] @@ -3824,7 +3801,7 @@ checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", - "version_check", + "version_check 0.9.5", ] [[package]] @@ -3871,57 +3848,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "protobuf" -version = "3.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65a1d4ddae7d8b5de68153b48f6aa3bba8cb002b243dbdbc55a5afbc98f99f4" -dependencies = [ - "once_cell", - "protobuf-support", - "thiserror 1.0.69", -] - -[[package]] -name = "protobuf-codegen" -version = "3.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d3976825c0014bbd2f3b34f0001876604fe87e0c86cd8fa54251530f1544ace" -dependencies = [ - "anyhow", - "once_cell", - "protobuf", - "protobuf-parse", - "regex", - "tempfile", - "thiserror 1.0.69", -] - -[[package]] -name = "protobuf-parse" -version = "3.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4aeaa1f2460f1d348eeaeed86aea999ce98c1bded6f089ff8514c9d9dbdc973" -dependencies = [ - "anyhow", - "indexmap", - "log", - "protobuf", - "protobuf-support", - "tempfile", - "thiserror 1.0.69", - "which", -] - -[[package]] -name = "protobuf-support" -version = "3.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e36c2f31e0a47f9280fb347ef5e461ffcd2c52dd520d8e216b52f93b0b0d7d6" -dependencies = [ - "thiserror 1.0.69", -] - [[package]] name = "psst-cli" version = "0.1.0" @@ -3942,18 +3868,16 @@ dependencies = [ "crossbeam-channel", "ctr", "cubeb", - "data-encoding", "git-version", "gix-config", "hmac", - "librespot-protocol", "libsamplerate", "log", "num-bigint", "num-traits", "oauth2", + "once_cell", "parking_lot", - "protobuf", "psst-protocol", "quick-protobuf", "rand 0.9.1", @@ -3966,7 +3890,6 @@ dependencies = [ "shannon", "socks", "symphonia", - "sysinfo", "tempfile", "time 0.3.41", "ureq 3.0.11", @@ -3989,6 +3912,7 @@ dependencies = [ "itertools 0.14.0", "log", "lru", + "once_cell", "open", "parking_lot", "platform-dirs", @@ -4006,6 +3930,7 @@ dependencies = [ "time-humanize", "ureq 3.0.11", "url", + "winapi", "winres", ] @@ -4160,7 +4085,7 @@ dependencies = [ "av1-grain", "bitstream-io", "built", - "cfg-if", + "cfg-if 1.0.0", "interpolate_name", "itertools 0.12.1", "libc", @@ -4258,7 +4183,7 @@ checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.16", "libredox", - "thiserror 2.0.16", + "thiserror 2.0.12", ] [[package]] @@ -4359,7 +4284,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", - "cfg-if", + "cfg-if 1.0.0", "getrandom 0.2.16", "libc", "untrusted 0.9.0", @@ -4707,7 +4632,7 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5058ada175748e33390e40e872bd0fe59a19f265d0158daa551c5a88a76009c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest", ] @@ -4727,7 +4652,7 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest", ] @@ -4754,7 +4679,7 @@ version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest", ] @@ -4777,6 +4702,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + [[package]] name = "shlex" version = "1.3.0" @@ -4906,7 +4837,7 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e113fb6f3de07a243d434a56ec6f186dfd51cb08448239fe7bcae73f87ff28ff" dependencies = [ - "version_check", + "version_check 0.9.5", ] [[package]] @@ -5118,20 +5049,6 @@ dependencies = [ "syn 2.0.101", ] -[[package]] -name = "sysinfo" -version = "0.35.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3ffa3e4ff2b324a57f7aeb3c349656c7b127c3c189520251a648102a92496e" -dependencies = [ - "libc", - "memchr", - "ntapi", - "objc2-core-foundation", - "objc2-io-kit", - "windows 0.61.1", -] - [[package]] name = "system-configuration" version = "0.5.1" @@ -5207,11 +5124,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.16" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ - "thiserror-impl 2.0.16", + "thiserror-impl 2.0.12", ] [[package]] @@ -5227,9 +5144,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.16" +version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", @@ -5242,7 +5159,7 @@ version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", ] @@ -5277,7 +5194,7 @@ dependencies = [ "standback", "stdweb", "time-macros 0.1.1", - "version_check", + "version_check 0.9.5", "winapi", ] @@ -5763,13 +5680,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.18.1" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f87b8aa10b915a06587d0dec516c282ff295b475d94abf425d62b57710070a2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" [[package]] name = "v_frame" @@ -5788,6 +5701,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + [[package]] name = "version_check" version = "0.9.5" @@ -5840,7 +5759,7 @@ version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "once_cell", "rustversion", "wasm-bindgen-macro", @@ -5866,7 +5785,7 @@ version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "once_cell", "wasm-bindgen", @@ -5976,18 +5895,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082" -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix 0.38.44", -] - [[package]] name = "winapi" version = "0.3.9" @@ -6396,7 +6303,7 @@ version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "windows-sys 0.48.0", ] diff --git a/psst-core/Cargo.toml b/psst-core/Cargo.toml index eb7d4c28..8f2fd440 100644 --- a/psst-core/Cargo.toml +++ b/psst-core/Cargo.toml @@ -13,12 +13,6 @@ time = { version = "0.3.36", features = ["local-offset"] } psst-protocol = { path = "../psst-protocol" } rustfm-scrobble = "1.1.1" -# Login5 dependencies -librespot-protocol = "0.7.1" -protobuf = "3" -sysinfo = "0.35.0" -data-encoding = "2.9" - # Common byteorder = { version = "1.5.0" } crossbeam-channel = { version = "0.5.13" } @@ -27,6 +21,7 @@ log = { version = "0.4.22" } num-bigint = { version = "0.4.6", features = ["rand"] } num-traits = { version = "0.2.19" } oauth2 = { version = "4.4.2" } +once_cell = { version = "1.20.2" } parking_lot = { version = "0.12.3" } quick-protobuf = { version = "0.8.1" } rand = { version = "0.9.1" } diff --git a/psst-core/build.rs b/psst-core/build.rs index d8957449..4e899af6 100644 --- a/psst-core/build.rs +++ b/psst-core/build.rs @@ -4,11 +4,11 @@ use time::OffsetDateTime; fn main() { let outdir = env::var("OUT_DIR").unwrap(); - let outfile = format!("{outdir}/build-time.txt"); + let outfile = format!("{}/build-time.txt", outdir); let mut fh = fs::File::create(outfile).unwrap(); let now = OffsetDateTime::now_local().unwrap_or_else(|_| OffsetDateTime::now_utc()); - write!(fh, r#""{now}""#).ok(); + write!(fh, r#""{}""#, now).ok(); let git_config = File::from_git_dir("../.git/".into()).expect("Git Config not found!"); // Get Git's 'Origin' URL @@ -37,7 +37,7 @@ fn main() { let trimmed_url = remote_url.trim_end_matches(".git"); remote_url.clone_from(&String::from(trimmed_url)); - let outfile = format!("{outdir}/remote-url.txt"); + let outfile = format!("{}/remote-url.txt", outdir); let mut file = fs::File::create(outfile).unwrap(); - write!(file, r#""{remote_url}""#).ok(); + write!(file, r#""{}""#, remote_url).ok(); } diff --git a/psst-core/src/actor.rs b/psst-core/src/actor.rs index a979db32..e53c88c9 100644 --- a/psst-core/src/actor.rs +++ b/psst-core/src/actor.rs @@ -50,7 +50,7 @@ pub trait Actor: Sized { act = match self.handle(msg) { Ok(act) => act, Err(err) => { - log::error!("error: {err}"); + log::error!("error: {}", err); break; } }; diff --git a/psst-core/src/audio/decode.rs b/psst-core/src/audio/decode.rs index abff2a38..543c3e30 100644 --- a/psst-core/src/audio/decode.rs +++ b/psst-core/src/audio/decode.rs @@ -116,7 +116,7 @@ impl AudioDecoder { return None; // End of this stream. } Err(err) => { - log::error!("format error: {err}"); + log::error!("format error: {}", err); return None; // We cannot recover from format errors, quit. } }; @@ -137,16 +137,16 @@ impl AudioDecoder { } Err(SymphoniaError::IoError(err)) => { // The packet failed to decode due to an IO error, skip the packet. - log::error!("io decode error: {err}"); + log::error!("io decode error: {}", err); continue; } Err(SymphoniaError::DecodeError(err)) => { // The packet failed to decode due to invalid data, skip the packet. - log::error!("decode error: {err}"); + log::error!("decode error: {}", err); continue; } Err(err) => { - log::error!("fatal decode error: {err}"); + log::error!("fatal decode error: {}", err); return None; } }; diff --git a/psst-core/src/audio/output/cpal.rs b/psst-core/src/audio/output/cpal.rs index b32dabf6..f674ed01 100644 --- a/psst-core/src/audio/output/cpal.rs +++ b/psst-core/src/audio/output/cpal.rs @@ -24,7 +24,7 @@ impl CpalOutput { .ok_or(cpal::DefaultStreamConfigError::DeviceNotAvailable)?; if let Ok(name) = device.name() { - log::info!("using audio device: {name:?}"); + log::info!("using audio device: {:?}", name); } // Get the default device config, so we know what sample format and sample rate @@ -159,14 +159,14 @@ impl Stream { state: CallbackState::Paused, }; - log::info!("opening output stream: {config:?}"); + log::info!("opening output stream: {:?}", config); let stream = device.build_output_stream( &config, move |output, _| { callback.write_samples(output); }, |err| { - log::error!("audio output error: {err}"); + log::error!("audio output error: {}", err); }, None, )?; @@ -187,14 +187,14 @@ impl Actor for Stream { StreamMsg::Pause => { log::debug!("pausing audio output stream"); if let Err(err) = self.stream.pause() { - log::error!("failed to stop stream: {err}"); + log::error!("failed to stop stream: {}", err); } Ok(Act::Continue) } StreamMsg::Resume => { log::debug!("resuming audio output stream"); if let Err(err) = self.stream.play() { - log::error!("failed to start stream: {err}"); + log::error!("failed to start stream: {}", err); } Ok(Act::Continue) } diff --git a/psst-core/src/cache.rs b/psst-core/src/cache.rs index 739253a4..71afdced 100644 --- a/psst-core/src/cache.rs +++ b/psst-core/src/cache.rs @@ -30,7 +30,7 @@ fn create_cache_dirs(base: &Path) -> io::Result<()> { impl Cache { pub fn new(base: PathBuf) -> Result { - log::info!("using cache: {base:?}"); + log::info!("using cache: {:?}", base); // Create the cache structure. create_cache_dirs(&base)?; @@ -65,7 +65,7 @@ impl Cache { } pub fn save_track(&self, item_id: ItemId, track: &Track) -> Result<(), Error> { - log::debug!("saving track to cache: {item_id:?}"); + log::debug!("saving track to cache: {:?}", item_id); fs::write(self.track_path(item_id), serialize_protobuf(track)?)?; Ok(()) } @@ -83,7 +83,7 @@ impl Cache { } pub fn save_episode(&self, item_id: ItemId, episode: &Episode) -> Result<(), Error> { - log::debug!("saving episode to cache: {item_id:?}"); + log::debug!("saving episode to cache: {:?}", item_id); fs::write(self.episode_path(item_id), serialize_protobuf(episode)?)?; Ok(()) } @@ -106,7 +106,7 @@ impl Cache { file_id: FileId, key: &AudioKey, ) -> Result<(), Error> { - log::debug!("saving audio key to cache: {item_id:?}:{file_id:?}"); + log::debug!("saving audio key to cache: {:?}:{:?}", item_id, file_id); fs::write(self.audio_key_path(item_id, file_id), key.0)?; Ok(()) } @@ -126,7 +126,7 @@ impl Cache { } pub fn save_audio_file(&self, file_id: FileId, from_path: PathBuf) -> Result<(), Error> { - log::debug!("saving audio file to cache: {file_id:?}"); + log::debug!("saving audio file to cache: {:?}", file_id); fs::copy(from_path, self.audio_file_path(file_id))?; Ok(()) } diff --git a/psst-core/src/cdn.rs b/psst-core/src/cdn.rs index 0d79a1cc..7a0cc596 100644 --- a/psst-core/src/cdn.rs +++ b/psst-core/src/cdn.rs @@ -9,17 +9,16 @@ use serde::Deserialize; use crate::{ error::Error, item_id::FileId, - session::{SessionService}, + session::{access_token::TokenProvider, SessionService}, util::default_ureq_agent_builder, }; -use crate::session::login5::Login5; pub type CdnHandle = Arc; pub struct Cdn { session: SessionService, agent: ureq::Agent, - login5: Login5, + token_provider: TokenProvider, } impl Cdn { @@ -28,7 +27,7 @@ impl Cdn { Ok(Arc::new(Self { session, agent: agent.into(), - login5: Login5::new(None, proxy_url), + token_provider: TokenProvider::new(), })) } @@ -37,7 +36,7 @@ impl Cdn { "https://api.spotify.com/v1/storage-resolve/files/audio/interactive/{}", id.to_base16() ); - let access_token = self.login5.get_access_token(&self.session)?; + let access_token = self.token_provider.get(&self.session)?; let response = self .agent .get(&locations_uri) @@ -45,7 +44,7 @@ impl Cdn { .query("product", "9") .query("platform", "39") .query("alt", "json") - .header("Authorization", &format!("Bearer {}", access_token.access_token)) + .header("Authorization", &format!("Bearer {}", access_token.token)) .call()?; #[derive(Deserialize)] @@ -122,7 +121,7 @@ impl From for Error { /// Constructs a Range header value for given offset and length. fn range_header(offfset: u64, length: u64) -> String { let last_byte = offfset + length - 1; // Offset of the last byte of the range is inclusive. - format!("bytes={offfset}-{last_byte}") + format!("bytes={}-{}", offfset, last_byte) } /// Parses a total content length from a Content-Range response header. diff --git a/psst-core/src/connection/mod.rs b/psst-core/src/connection/mod.rs index bd9daf5a..2067bd57 100644 --- a/psst-core/src/connection/mod.rs +++ b/psst-core/src/connection/mod.rs @@ -106,7 +106,7 @@ impl Transport { ap_list } Err(err) => { - log::error!("error while resolving APs, using fallback: {err:?}"); + log::error!("error while resolving APs, using fallback: {:?}", err); vec![AP_FALLBACK.into()] } } @@ -119,7 +119,7 @@ impl Transport { } let agent: ureq::Agent = default_ureq_agent_builder(proxy_url).build().into(); - log::info!("requesting AP list from {AP_RESOLVE_ENDPOINT}"); + log::info!("requesting AP list from {}", AP_RESOLVE_ENDPOINT); let data: APResolveData = agent .get(AP_RESOLVE_ENDPOINT) .call()? @@ -145,7 +145,7 @@ impl Transport { match Self::stream_through_proxy(ap, url) { Ok(s) => s, Err(e) => { - log::warn!("failed to connect to AP {ap} through proxy: {e:?}"); + log::warn!("failed to connect to AP {} through proxy: {:?}", ap, e); continue; } } @@ -153,15 +153,15 @@ impl Transport { match Self::stream_without_proxy(ap) { Ok(s) => s, Err(e) => { - log::warn!("failed to connect to AP {ap} without proxy: {e:?}"); + log::warn!("failed to connect to AP {} without proxy: {:?}", ap, e); continue; } } }; if let Err(err) = stream.set_write_timeout(Some(NET_IO_TIMEOUT)) { - log::warn!("failed to set TCP write timeout: {err:?}"); + log::warn!("failed to set TCP write timeout: {:?}", err); } - log::info!("successfully connected to AP: {ap}"); + log::info!("successfully connected to AP: {}", ap); return Self::exchange_keys(stream); } log::error!("failed to connect to any access point"); diff --git a/psst-core/src/error.rs b/psst-core/src/error.rs index 76a65979..d22664dc 100644 --- a/psst-core/src/error.rs +++ b/psst-core/src/error.rs @@ -10,8 +10,6 @@ pub enum Error { AuthFailed { code: i32 }, ConnectionFailed, JsonError(Box), - InvalidStateError(Box), - UnimplementedError(Box), AudioFetchingError(Box), AudioDecodingError(Box), AudioOutputError(Box), @@ -47,26 +45,24 @@ impl fmt::Display for Error { 15 => write!(f, "Authentication failed: extra verification required"), 16 => write!(f, "Authentication failed: invalid app key"), 17 => write!(f, "Authentication failed: application banned"), - _ => write!(f, "Authentication failed with error code {code}"), + _ => write!(f, "Authentication failed with error code {}", code), }, Self::ConnectionFailed => write!(f, "Failed to connect to any access point"), Self::ResamplingError(code) => { - write!(f, "Resampling failed with error code {code}") + write!(f, "Resampling failed with error code {}", code) } - Self::ConfigError(msg) => write!(f, "Configuration error: {msg}"), + 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::InvalidStateError(err) - | Self::UnimplementedError(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::RecvTimeoutError(err) => write!(f, "Channel receive timeout: {}", err), Self::JoinError => write!(f, "Failed to join thread"), - Self::OAuthError(msg) => write!(f, "OAuth error: {msg}"), + Self::OAuthError(msg) => write!(f, "OAuth error: {}", msg), } } } @@ -88,7 +84,3 @@ impl From for Error { Error::RecvTimeoutError(err) } } - -impl From for Error { - fn from(err: protobuf::Error) -> Self { Error::InvalidStateError(err.into()) } -} \ No newline at end of file diff --git a/psst-core/src/item_id.rs b/psst-core/src/item_id.rs index 3396fef6..73f76c25 100644 --- a/psst-core/src/item_id.rs +++ b/psst-core/src/item_id.rs @@ -1,14 +1,8 @@ -use std::{ - collections::HashMap, - convert::TryInto, - fmt, - ops::Deref, - path::PathBuf, - sync::{LazyLock, Mutex}, -}; - -static LOCAL_REGISTRY: LazyLock> = - LazyLock::new(|| Mutex::new(LocalItemRegistry::new())); +use once_cell::sync::Lazy; +use std::{collections::HashMap, convert::TryInto, fmt, ops::Deref, path::PathBuf, sync::Mutex}; + +static LOCAL_REGISTRY: Lazy> = + Lazy::new(|| Mutex::new(LocalItemRegistry::new())); // LocalItemRegistry allows generating IDs for local music files, so they can be // treated similarly to files hosted on Spotify's remote servers. IDs are @@ -117,8 +111,8 @@ impl ItemId { pub fn to_uri(&self) -> Option { let b64 = self.to_base62(); match self.id_type { - ItemIdType::Track => Some(format!("spotify:track:{b64}")), - ItemIdType::Podcast => Some(format!("spotify:podcast:{b64}")), + ItemIdType::Track => Some(format!("spotify:track:{}", b64)), + ItemIdType::Podcast => Some(format!("spotify:podcast:{}", b64)), // TODO: support adding local files to playlists ItemIdType::LocalFile => None, ItemIdType::Unknown => None, @@ -182,7 +176,7 @@ impl FileId { pub fn to_base16(&self) -> String { self.0 .iter() - .map(|b| format!("{b:02x}")) + .map(|b| format!("{:02x}", b)) .collect::>() .concat() } diff --git a/psst-core/src/lib.rs b/psst-core/src/lib.rs index 24f1196e..d2e0dad2 100644 --- a/psst-core/src/lib.rs +++ b/psst-core/src/lib.rs @@ -19,6 +19,5 @@ pub mod oauth; pub mod player; pub mod session; pub mod util; -pub mod system_info; pub use psst_protocol as protocol; diff --git a/psst-core/src/oauth.rs b/psst-core/src/oauth.rs index 202297c2..6aa9a10a 100644 --- a/psst-core/src/oauth.rs +++ b/psst-core/src/oauth.rs @@ -18,7 +18,9 @@ pub fn listen_for_callback_parameter( parameter_name: &'static str, ) -> Result { log::info!( - "starting callback listener for '{parameter_name}' on {socket_address:?}", + "starting callback listener for '{}' on {:?}", + parameter_name, + socket_address ); // Create a simpler, linear flow @@ -29,7 +31,7 @@ pub fn listen_for_callback_parameter( l } Err(e) => { - log::error!("Failed to bind listener: {e}"); + log::error!("Failed to bind listener: {}", e); return Err(Error::IoError(e)); } }; @@ -53,7 +55,7 @@ pub fn listen_for_callback_parameter( let result = match rx.recv_timeout(timeout) { Ok(r) => r, Err(e) => { - log::error!("Timed out or channel error: {e}"); + log::error!("Timed out or channel error: {}", e); return Err(Error::from(e)); } }; @@ -79,15 +81,16 @@ fn handle_callback_connection( if reader.read_line(&mut request_line).is_ok() { match extract_parameter_from_request(&request_line, parameter_name) { Some(value) => { - log::info!("received callback parameter '{parameter_name}'."); + 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 '{parameter_name}' from request: {request_line}", + "Failed to extract parameter '{}' from request: {}", + parameter_name, request_line ); - log::error!("{err_msg}"); + log::error!("{}", err_msg); let _ = tx.send(Err(Error::OAuthError(err_msg))); } } @@ -104,7 +107,7 @@ fn extract_parameter_from_request(request_line: &str, parameter_name: &str) -> O request_line .split_whitespace() .nth(1) - .and_then(|path| Url::parse(&format!("http://localhost{path}")).ok()) + .and_then(|path| Url::parse(&format!("http://localhost{}", path)).ok()) .and_then(|url| { url.query_pairs() .find(|(key, _)| key == parameter_name) diff --git a/psst-core/src/player/file.rs b/psst-core/src/player/file.rs index 46f5fbcb..65f4e8f6 100644 --- a/psst-core/src/player/file.rs +++ b/psst-core/src/player/file.rs @@ -261,12 +261,12 @@ impl StreamedFile { if writer.is_complete() && !cache.audio_file_path(file_id).exists() { // TODO: We should do this atomically. if let Err(err) = cache.save_audio_file(file_id, file_path) { - log::warn!("failed to save audio file to cache: {err:?}"); + log::warn!("failed to save audio file to cache: {:?}", err); } } } Err(err) => { - log::error!("failed to download: {err}"); + log::error!("failed to download: {}", err); // Range failed to download, remove it from the requested set. writer.mark_as_not_requested(offset, length); } @@ -281,11 +281,11 @@ impl StreamedFile { match req { StreamRequest::Preload { offset, length } => { if let Err(err) = download_range(offset, length) { - log::error!("failed to request audio range: {err:?}"); + log::error!("failed to request audio range: {:?}", err); } } StreamRequest::Blocked { offset } => { - log::info!("blocked at {offset}"); + log::info!("blocked at {}", offset); } } } diff --git a/psst-core/src/player/item.rs b/psst-core/src/player/item.rs index ebd76a6b..9b934b32 100644 --- a/psst-core/src/player/item.rs +++ b/psst-core/src/player/item.rs @@ -155,7 +155,7 @@ fn get_country_code(session: &SessionService, cache: &CacheHandle) -> Option { self.consecutive_loading_failures += 1; if self.consecutive_loading_failures < STOP_AFTER_CONSECUTIVE_LOADING_FAILURES { - log::error!("skipping, error while loading: {err}"); + log::error!("skipping, error while loading: {}", err); self.next(); } else { - log::error!("stopping, error while loading: {err}"); + log::error!("stopping, error while loading: {}", err); self.stop(); } } @@ -162,7 +162,7 @@ impl Player { self.preload = PreloadState::Preloaded { item, loaded_item }; } Err(err) => { - log::error!("failed to preload audio file, error while opening: {err}"); + log::error!("failed to preload audio file, error while opening: {}", err); self.preload = PreloadState::None; } }, diff --git a/psst-core/src/player/queue.rs b/psst-core/src/player/queue.rs index 1ebd3454..622440a7 100644 --- a/psst-core/src/player/queue.rs +++ b/psst-core/src/player/queue.rs @@ -2,15 +2,20 @@ use rand::prelude::SliceRandom; use super::PlaybackItem; -#[derive(Default, Debug)] +#[derive(Debug)] pub enum QueueBehavior { - #[default] Sequential, Random, LoopTrack, LoopAll, } +impl Default for QueueBehavior { + fn default() -> Self { + Self::Sequential + } +} + pub struct Queue { items: Vec, user_items: Vec, diff --git a/psst-core/src/player/worker.rs b/psst-core/src/player/worker.rs index 3af01e24..9102c6b2 100644 --- a/psst-core/src/player/worker.rs +++ b/psst-core/src/player/worker.rs @@ -290,7 +290,7 @@ impl Worker { if let Err(err) = audio_thread_priority::promote_current_thread_to_real_time(0, input.signal_spec().rate) { - log::warn!("failed to promote thread to audio priority: {err}"); + log::warn!("failed to promote thread to audio priority: {}", err); } Self { @@ -337,7 +337,7 @@ impl Worker { self.output.clear(); } Err(err) => { - log::error!("failed to seek: {err}"); + log::error!("failed to seek: {}", err); } } Ok(Act::Continue) diff --git a/psst-core/src/session/access_token.rs b/psst-core/src/session/access_token.rs index 384c452a..bd4dee61 100644 --- a/psst-core/src/session/access_token.rs +++ b/psst-core/src/session/access_token.rs @@ -42,7 +42,8 @@ impl AccessToken { } let token: MercuryAccessToken = session.connected()?.get_mercury_json(format!( - "hm://keymaster/token/authenticated?client_id={CLIENT_ID}&scope={ACCESS_SCOPES}", + "hm://keymaster/token/authenticated?client_id={}&scope={}", + CLIENT_ID, ACCESS_SCOPES ))?; Ok(Self { diff --git a/psst-core/src/session/audio_key.rs b/psst-core/src/session/audio_key.rs index b4d09bc8..17d36f1b 100644 --- a/psst-core/src/session/audio_key.rs +++ b/psst-core/src/session/audio_key.rs @@ -56,10 +56,10 @@ impl AudioKeyDispatcher { payload.read_exact(&mut key).unwrap(); if tx.send(Ok(AudioKey(key))).is_err() { - log::warn!("missing receiver for audio key, seq: {seq}"); + log::warn!("missing receiver for audio key, seq: {}", seq); } } else { - log::warn!("received unexpected audio key msg, seq: {seq}"); + log::warn!("received unexpected audio key msg, seq: {}", seq); } } @@ -70,10 +70,10 @@ impl AudioKeyDispatcher { if let Some(tx) = self.pending.remove(&seq) { log::error!("audio key error"); if tx.send(Err(Error::UnexpectedResponse)).is_err() { - log::warn!("missing receiver for audio key error, seq: {seq}"); + log::warn!("missing receiver for audio key error, seq: {}", seq); } } else { - log::warn!("received unknown audio key, seq: {seq}"); + log::warn!("received unknown audio key, seq: {}", seq); } } } diff --git a/psst-core/src/session/client_token.rs b/psst-core/src/session/client_token.rs deleted file mode 100644 index 5e9d440d..00000000 --- a/psst-core/src/session/client_token.rs +++ /dev/null @@ -1,252 +0,0 @@ -// Ported from librespot - -use crate::error::Error; -use crate::session::token::{Token}; -use crate::util::{default_ureq_agent_builder, solve_hash_cash}; -use data_encoding::HEXUPPER_PERMISSIVE; -use librespot_protocol::clienttoken_http::{ - ChallengeAnswer, ChallengeType, ClientTokenRequest, ClientTokenRequestType, - ClientTokenResponse, ClientTokenResponseType, -}; -use parking_lot::Mutex; -use protobuf::{Enum, Message}; -use std::time::{Duration, Instant}; -use crate::system_info::{CLIENT_ID, DEVICE_ID, OS, SPOTIFY_SEMANTIC_VERSION}; - -pub struct ClientTokenProvider { - token: Mutex>, - agent: ureq::Agent, -} - -impl ClientTokenProvider { - pub fn new(proxy_url: Option<&str>) -> Self { - Self { - token: Mutex::new(None), - agent: default_ureq_agent_builder(proxy_url).build().into(), - } - } - - fn request(&self, message: &M) -> Result, Error> { - let body = message.write_to_bytes()?; - - let mut response = self - .agent - .post("https://clienttoken.spotify.com/v1/clienttoken") - .header("Accept", "application/x-protobuf") - .send(body)?; - - let vec = response.body_mut().read_to_vec(); - Ok(vec?) - } - - fn request_new_token(&self) -> Result { - log::debug!("Requesting new token..."); - - let mut request = ClientTokenRequest::new(); - request.request_type = ClientTokenRequestType::REQUEST_CLIENT_DATA_REQUEST.into(); - - let client_data = request.mut_client_data(); - - client_data.client_version = SPOTIFY_SEMANTIC_VERSION.into(); - client_data.client_id = CLIENT_ID.into(); - - let connectivity_data = client_data.mut_connectivity_sdk_data(); - connectivity_data.device_id = DEVICE_ID.to_string(); - - let platform_data = connectivity_data - .platform_specific_data - .mut_or_insert_default(); - - let os_version = sysinfo::System::os_version().unwrap_or("0".into()); - let kernel_version = sysinfo::System::kernel_version().unwrap_or_else(|| String::from("0")); - - match OS { - "windows" => { - let os_version = os_version.parse::().unwrap_or(10.) as i32; - let kernel_version = kernel_version.parse::().unwrap_or(21370); - - let (pe, image_file) = match std::env::consts::ARCH { - "arm" => (448, 452), - "aarch64" => (43620, 452), - "x86_64" => (34404, 34404), - _ => (332, 332), // x86 - }; - - let windows_data = platform_data.mut_desktop_windows(); - windows_data.os_version = os_version; - windows_data.os_build = kernel_version; - windows_data.platform_id = 2; - windows_data.unknown_value_6 = 9; - windows_data.image_file_machine = image_file; - windows_data.pe_machine = pe; - windows_data.unknown_value_10 = true; - } - "ios" => { - let ios_data = platform_data.mut_ios(); - ios_data.user_interface_idiom = 0; - ios_data.target_iphone_simulator = false; - ios_data.hw_machine = "iPhone14,5".to_string(); - ios_data.system_version = os_version; - } - "android" => { - let android_data = platform_data.mut_android(); - android_data.android_version = os_version; - android_data.api_version = 31; - "Pixel".clone_into(&mut android_data.device_name); - "GF5KQ".clone_into(&mut android_data.model_str); - "Google".clone_into(&mut android_data.vendor); - } - "macos" => { - let macos_data = platform_data.mut_desktop_macos(); - macos_data.system_version = os_version; - macos_data.hw_model = "iMac21,1".to_string(); - macos_data.compiled_cpu_type = std::env::consts::ARCH.to_string(); - } - _ => { - let linux_data = platform_data.mut_desktop_linux(); - linux_data.system_name = "Linux".to_string(); - linux_data.system_release = kernel_version; - linux_data.system_version = os_version; - linux_data.hardware = std::env::consts::ARCH.to_string(); - } - } - - let mut response = self.request(&request)?; - let mut count = 0; - const MAX_TRIES: u8 = 3; - - let token_response = loop { - count += 1; - - let message = ClientTokenResponse::parse_from_bytes(&response)?; - - match ClientTokenResponseType::from_i32(message.response_type.value()) { - // depending on the platform, you're either given a token immediately - // or are presented a hash cash challenge to solve first - Some(ClientTokenResponseType::RESPONSE_GRANTED_TOKEN_RESPONSE) => { - log::debug!("Received a granted token"); - break message; - } - Some(ClientTokenResponseType::RESPONSE_CHALLENGES_RESPONSE) => { - log::debug!("Received a hash cash challenge, solving..."); - - let challenges = message.challenges().clone(); - let state = challenges.state; - if let Some(challenge) = challenges.challenges.first() { - let hash_cash_challenge = challenge.evaluate_hashcash_parameters(); - - let ctx = vec![]; - let prefix = HEXUPPER_PERMISSIVE - .decode(hash_cash_challenge.prefix.as_bytes()) - .map_err(|e| { - Error::InvalidStateError( - format!("Unable to decode hash cash challenge: {e}").into(), - ) - })?; - let length = hash_cash_challenge.length; - - let mut suffix = [0u8; 0x10]; - let answer = solve_hash_cash(&ctx, &prefix, length, &mut suffix); - - match answer { - Ok(_) => { - // the suffix must be in uppercase - let suffix = HEXUPPER_PERMISSIVE.encode(&suffix); - - let mut answer_message = ClientTokenRequest::new(); - answer_message.request_type = - ClientTokenRequestType::REQUEST_CHALLENGE_ANSWERS_REQUEST - .into(); - - let challenge_answers = answer_message.mut_challenge_answers(); - - let mut challenge_answer = ChallengeAnswer::new(); - challenge_answer.mut_hash_cash().suffix = suffix; - challenge_answer.ChallengeType = - ChallengeType::CHALLENGE_HASH_CASH.into(); - - challenge_answers.state = state.to_string(); - challenge_answers.answers.push(challenge_answer); - - log::trace!("Answering hash cash challenge"); - match self.request(&answer_message) { - Ok(token) => { - response = token; - continue; - } - Err(e) => { - log::trace!("Answer not accepted {count}/{MAX_TRIES}: {e}"); - } - } - } - Err(e) => log::trace!( - "Unable to solve hash cash challenge {count}/{MAX_TRIES}: {e}" - ), - } - - if count < MAX_TRIES { - response = self.request(&request)?; - } else { - return Err(Error::InvalidStateError( - format!("Unable to solve any of {MAX_TRIES} hash cash challenges") - .into(), - )); - } - } else { - return Err(Error::InvalidStateError("No challenges found".into())); - } - } - - Some(unknown) => { - return Err(Error::UnimplementedError( - format!("Unknown client token response type: {unknown:?}").into(), - )); - } - None => { - return Err(Error::InvalidStateError( - "No client token response type".into(), - )) - } - } - }; - - let granted_token = token_response.granted_token(); - let access_token = granted_token.token.to_owned(); - - Ok(Token { - access_token: access_token.clone(), - expires_in: Duration::from_secs( - granted_token - .refresh_after_seconds - .try_into() - .unwrap_or(7200), - ), - token_type: "client-token".to_string(), - scopes: granted_token - .domains - .iter() - .map(|d| d.domain.clone()) - .collect(), - timestamp: Instant::now(), - }) - } - - pub fn get(&self) -> Result { - // Check for cached token availability, else retrieve fresh token - let mut cur_token = self.token.lock(); - - if let Some(token) = &*cur_token { - if !token.is_expired() { - return Ok(token.access_token.clone()); - } - - *cur_token = None; - log::debug!("Client token expired"); - } - - let new_token = self.request_new_token()?; - - *cur_token = Some(new_token.clone()); - Ok(new_token.access_token) - } -} diff --git a/psst-core/src/session/login5.rs b/psst-core/src/session/login5.rs deleted file mode 100644 index baf89634..00000000 --- a/psst-core/src/session/login5.rs +++ /dev/null @@ -1,283 +0,0 @@ -// Ported from librespot - -use crate::error::Error; -use crate::session::client_token::ClientTokenProvider; -use crate::session::token::Token; -use crate::session::SessionService; -use crate::system_info::{CLIENT_ID, DEVICE_ID}; -use crate::util::{default_ureq_agent_builder, solve_hash_cash}; -use librespot_protocol::login5::login_response::Response; -use librespot_protocol::{ - client_info::ClientInfo, - credentials::StoredCredential, - hashcash::HashcashSolution, - login5::{ - login_request::Login_method, ChallengeSolution, LoginError, LoginRequest, LoginResponse, - }, -}; -use parking_lot::Mutex; -use protobuf::well_known_types::duration::Duration as ProtoDuration; -use protobuf::{Message, MessageField}; -use std::fmt::Formatter; -use std::time::{Duration, Instant}; -use std::{error, fmt, thread}; - -const MAX_LOGIN_TRIES: u8 = 3; -const LOGIN_TIMEOUT: Duration = Duration::from_secs(3); - -#[derive(Debug)] -pub enum ChallengeError { - Unsupported, - NoneFound, -} - -#[derive(Debug)] -enum Login5Error { - /// The server denied the request with a specific error code. - RequestDenied(LoginError), - /// The server issued a challenge that we could not solve. - Challenge(ChallengeError), - /// The operation could not be performed due to invalid local state. - InvalidState(String), - /// The login attempt failed after multiple retries. - RetriesExceeded(u8), - /// The server's response was malformed or missing expected fields. - MalformedResponse, -} - -impl error::Error for Login5Error {} - -impl fmt::Display for Login5Error { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - Login5Error::RequestDenied(e) => write!(f, "Login request was denied: {:?}", e), - Login5Error::Challenge(c) => match c { - ChallengeError::Unsupported => write!(f, "Login5 code challenge is not supported"), - ChallengeError::NoneFound => write!(f, "No challenges found in response"), - }, - Login5Error::InvalidState(s) => write!(f, "Invalid state: {}", s), - Login5Error::RetriesExceeded(n) => { - write!(f, "Couldn't successfully authenticate after {} times", n) - } - Login5Error::MalformedResponse => write!(f, "Missing response from login server"), - } - } -} - -impl From for Error { - fn from(err: Login5Error) -> Self { - match err { - Login5Error::RequestDenied(_) - | Login5Error::InvalidState(_) - | Login5Error::RetriesExceeded(_) - | Login5Error::MalformedResponse => Error::InvalidStateError(err.into()), - Login5Error::Challenge(_) => Error::UnimplementedError(err.into()), - } - } -} - -pub struct Login5 { - auth_token: Mutex>, - client_token_provider: ClientTokenProvider, - agent: ureq::Agent, -} - -impl Login5 { - /// Login5 instances can be used to cache and retrieve access tokens from stored credentials. - /// - /// # Arguments - /// - /// * `client_token_provider`: Can be optionally injected to control which client-id is - /// used for it. - /// - /// returns: Login5 - pub fn new( - client_token_provider: Option, - proxy_url: Option<&str>, - ) -> Self { - Self { - auth_token: Mutex::new(None), - client_token_provider: client_token_provider - .unwrap_or_else(|| ClientTokenProvider::new(proxy_url)), - agent: default_ureq_agent_builder(proxy_url).build().into(), - } - } - - fn request(&self, message: &LoginRequest) -> Result, Error> { - let client_token: String = self.client_token_provider.get()?; - let body = message.write_to_bytes()?; - - let mut response = self - .agent - .post("https://login5.spotify.com/v3/login") - .header("Accept", "application/x-protobuf") - .header("client-token", &client_token) - .send(body)?; - - let vec = response.body_mut().read_to_vec()?; - Ok(vec) - } - - fn request_new_token(&self, login: Login_method) -> Result { - let mut login_request = LoginRequest { - client_info: MessageField::some(ClientInfo { - client_id: String::from(CLIENT_ID), - device_id: String::from(DEVICE_ID), - special_fields: Default::default(), - }), - login_method: Some(login), - ..Default::default() - }; - - let mut response = self.request(&login_request)?; - let mut count = 0; - - loop { - count += 1; - - let mut message = LoginResponse::parse_from_bytes(&response)?; - match message.response.take() { - Some(Response::Ok(ok)) => { - let expiry_secs = ok.access_token_expires_in.try_into().unwrap_or(3600); - return Ok(Token { - access_token: ok.access_token, - expires_in: Duration::from_secs(expiry_secs), - token_type: "Bearer".to_string(), - scopes: vec![], - timestamp: Instant::now(), - }); - } - Some(Response::Error(err)) => match err.enum_value() { - Ok(LoginError::TIMEOUT) | Ok(LoginError::TOO_MANY_ATTEMPTS) => { - log::debug!("Too many login5 requests... timeout!"); - thread::sleep(LOGIN_TIMEOUT) - } - Ok(other) => { - log::debug!("Login5 request failed!"); - return Err(Login5Error::RequestDenied(other).into()); - } - Err(other) => { - log::warn!("Unknown login error: {}", other); - } - }, - Some(Response::Challenges(_)) => { - // handles the challenges, and updates the login context with the response - Self::handle_challenges(&mut login_request, message)?; - } - None => { - return Err(Login5Error::MalformedResponse.into()); - } - _ => { - log::warn!("Unhandled login response"); - } - } - - if count < MAX_LOGIN_TRIES { - response = self.request(&login_request)?; - } else { - return Err(Login5Error::RetriesExceeded(MAX_LOGIN_TRIES).into()); - } - } - } - - fn handle_challenges( - login_request: &mut LoginRequest, - message: LoginResponse, - ) -> Result<(), Error> { - let challenges = message.challenges(); - log::debug!( - "Received {} challenges, solving...", - challenges.challenges.len() - ); - - if challenges.challenges.is_empty() { - return Err(Login5Error::Challenge(ChallengeError::NoneFound).into()); - } - - for challenge in &challenges.challenges { - if challenge.has_code() || !challenge.has_hashcash() { - // We only solve hashcash challenges. - return Err(Login5Error::Challenge(ChallengeError::Unsupported).into()); - } - - let hash_cash_challenge = challenge.hashcash(); - - let mut suffix = [0u8; 0x10]; - let duration = solve_hash_cash( - &message.login_context, - &hash_cash_challenge.prefix, - hash_cash_challenge.length, - &mut suffix, - )?; - let (seconds, nanos) = (duration.as_secs() as i64, duration.subsec_nanos() as i32); - log::debug!("Solving hashcash took {seconds}s {nanos}ns"); - - let mut solution = ChallengeSolution::new(); - solution.set_hashcash(HashcashSolution { - suffix: Vec::from(suffix), - duration: MessageField::some(ProtoDuration { - seconds, - nanos, - ..Default::default() - }), - ..Default::default() - }); - - login_request - .challenge_solutions - .mut_or_insert_default() - .solutions - .push(solution); - } - - login_request.login_context = message.login_context; - - Ok(()) - } - - /// Retrieve an `access_token` via Login5. The token is either requested first (slow), or - /// retrieved from local cache (fast). - /// - /// This request will only work if the session already has valid credentials available. - /// The client-id of the credentials have to match the client-id used to retrieve - /// the client token (see also `Login5::new(...)`). For example, if you previously generated - /// stored credentials with an android client-id, they won't work within login5 using a desktop - /// client-id. - pub fn get_access_token(&self, session: &SessionService) -> Result { - let mut cur_token = self.auth_token.lock(); - - let login_creds = session.config.lock().as_ref().unwrap().login_creds.clone(); - let auth_data = login_creds.auth_data.clone(); - if auth_data.is_empty() { - return Err(Login5Error::InvalidState( - "Tried to acquire access token without stored credentials".to_string(), - ) - .into()); - } - - if let Some(auth_token) = &*cur_token { - if !auth_token.is_expired() { - return Ok(auth_token.clone()); - } - - *cur_token = None; - log::debug!("Auth token expired"); - } - - log::debug!("Requesting new auth token"); - - // Conversion from psst protocol structs to librespot protocol structs - let method = Login_method::StoredCredential(StoredCredential { - username: login_creds.username.clone().unwrap(), - data: auth_data, - ..Default::default() - }); - - let new_token = self.request_new_token(method)?; - - log::debug!("Successfully requested new auth token"); - - *cur_token = Some(new_token.clone()); - Ok(new_token) - } -} diff --git a/psst-core/src/session/mercury.rs b/psst-core/src/session/mercury.rs index cd66be92..d11a4b61 100644 --- a/psst-core/src/session/mercury.rs +++ b/psst-core/src/session/mercury.rs @@ -59,7 +59,7 @@ impl MercuryDispatcher { self.pending.insert(msg_seq, pending); } } else { - log::warn!("received unexpected mercury msg, seq: {msg_seq}"); + log::warn!("received unexpected mercury msg, seq: {}", msg_seq); } } } diff --git a/psst-core/src/session/mod.rs b/psst-core/src/session/mod.rs index fd6ece41..ee75c9d0 100644 --- a/psst-core/src/session/mod.rs +++ b/psst-core/src/session/mod.rs @@ -1,9 +1,6 @@ pub mod access_token; pub mod audio_key; pub mod mercury; -pub mod login5; -pub mod client_token; -pub mod token; use std::{ io, @@ -191,13 +188,13 @@ impl SessionWorker { pub fn join(self) { if let Err(err) = self.dispatching_thread.join() { - log::error!("session dispatching thread panicked: {err:?}"); + log::error!("session dispatching thread panicked: {:?}", err); } if let Err(err) = self.encoding_thread.join() { - log::error!("session encoding thread panicked: {err:?}"); + log::error!("session encoding thread panicked: {:?}", err); } if let Err(err) = self.decoding_thread.join() { - log::error!("session decoding thread panicked: {err:?}"); + log::error!("session decoding thread panicked: {:?}", err); } } @@ -380,12 +377,12 @@ fn dispatch_messages( log::debug!("ignored message: {:?}", msg.cmd); } DispatchCmd::DecoderError(err) => { - log::error!("connection error: {err:?}"); + log::error!("connection error: {:?}", err); let _ = stream.shutdown(Shutdown::Write); break; } DispatchCmd::EncoderError(err) => { - log::error!("connection error: {err:?}"); + log::error!("connection error: {:?}", err); let _ = stream.shutdown(Shutdown::Read); break; } diff --git a/psst-core/src/session/token.rs b/psst-core/src/session/token.rs deleted file mode 100644 index f19e62fa..00000000 --- a/psst-core/src/session/token.rs +++ /dev/null @@ -1,20 +0,0 @@ -// Ported from librespot - -use std::time::{Duration, Instant}; - -const EXPIRY_THRESHOLD: Duration = Duration::from_secs(10); - -#[derive(Clone, Debug)] -pub struct Token { - pub access_token: String, - pub expires_in: Duration, - pub token_type: String, - pub scopes: Vec, - pub timestamp: Instant, -} - -impl Token { - pub fn is_expired(&self) -> bool { - self.timestamp + (self.expires_in.saturating_sub(EXPIRY_THRESHOLD)) < Instant::now() - } -} \ No newline at end of file diff --git a/psst-core/src/system_info.rs b/psst-core/src/system_info.rs deleted file mode 100644 index d0dd8f6d..00000000 --- a/psst-core/src/system_info.rs +++ /dev/null @@ -1,12 +0,0 @@ -/// Operating System as given by the Rust standard library -pub const OS: &str = std::env::consts::OS; - -/// Device ID used for authentication procedures. -/// librespot opts for UUIDv4s instead -pub const DEVICE_ID: &str = "Psst"; - -/// Client ID for desktop keymaster client -pub const CLIENT_ID: &str = "65b708073fc0480ea92a077233ca87bd"; - -/// The semantic version of the Spotify desktop client -pub const SPOTIFY_SEMANTIC_VERSION: &str = "1.2.52.442"; \ No newline at end of file diff --git a/psst-core/src/util.rs b/psst-core/src/util.rs index 07de7b18..244f87a7 100644 --- a/psst-core/src/util.rs +++ b/psst-core/src/util.rs @@ -1,10 +1,9 @@ -use crate::error::Error; -use byteorder::{BigEndian, ByteOrder}; +use std::{io, io::SeekFrom, mem, time::Duration}; + use num_traits::{One, WrappingAdd}; use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; -use sha1::{Digest, Sha1}; -use std::time::Instant; -use std::{io, io::SeekFrom, mem, time::Duration}; + +use crate::error::Error; pub const NET_CONNECT_TIMEOUT: Duration = Duration::from_millis(8 * 1000); @@ -27,46 +26,6 @@ pub fn default_ureq_agent_builder( agent } -pub fn solve_hash_cash( - ctx: &[u8], - prefix: &[u8], - length: i32, - dst: &mut [u8], -) -> Result { - const TIMEOUT: Duration = Duration::from_secs(5); - // SHA-1 produces a 20-byte hash, we check the trailing 8 bytes. - const OFFSET_LEN: usize = 8; - const CHECK_OFFSET: usize = 20 - OFFSET_LEN; - - let now = Instant::now(); - let initial_digest = Sha1::digest(ctx); - let target = BigEndian::read_i64(&initial_digest[CHECK_OFFSET..]); - - let mut suffix = [0u8; 16]; - let mut counter = 0i64; - - while now.elapsed() < TIMEOUT { - suffix[..OFFSET_LEN].copy_from_slice(&target.wrapping_add(counter).to_be_bytes()); - suffix[OFFSET_LEN..].copy_from_slice(&counter.to_be_bytes()); - - let final_digest = Sha1::new() - .chain_update(prefix) - .chain_update(suffix) - .finalize(); - - if BigEndian::read_i64(&final_digest[CHECK_OFFSET..]).trailing_zeros() >= (length as u32) { - dst.copy_from_slice(&suffix); - return Ok(now.elapsed()); - } - - counter += 1; - } - - Err(Error::InvalidStateError( - format!("{TIMEOUT:?} expired").into(), - )) -} - #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct Sequence(T); diff --git a/psst-gui/Cargo.toml b/psst-gui/Cargo.toml index d8b79934..95179324 100644 --- a/psst-gui/Cargo.toml +++ b/psst-gui/Cargo.toml @@ -22,6 +22,7 @@ env_logger = { version = "0.11.8" } itertools = "0.14.0" log = { version = "0.4.27" } lru = "0.14.0" +once_cell = { version = "1.21.3" } parking_lot = { version = "0.12.3" } platform-dirs = { version = "0.3.0" } rand = { version = "0.9.1" } @@ -41,7 +42,6 @@ druid = { git = "https://github.com/jpochyla/druid", branch = "psst", features = "image", "jpeg", "png", - "webp", "serde", ] } druid-enums = { git = "https://github.com/jpochyla/druid-enums" } @@ -57,6 +57,9 @@ rustfm-scrobble = "1.1.1" winres = { version = "0.1.12" } image = { version = "0.25.6" } +[target.'cfg(windows)'.dependencies] +winapi = { version = "0.3", features = ["winuser", "shellapi", "commctrl", "libloaderapi", "processthreadsapi"] } + [package.metadata.bundle] name = "Psst" identifier = "com.jpochyla.psst" diff --git a/psst-gui/build.rs b/psst-gui/build.rs index db025eb4..1499033f 100644 --- a/psst-gui/build.rs +++ b/psst-gui/build.rs @@ -26,7 +26,7 @@ fn add_windows_icon() { .iter() .map(|s| { IcoFrame::as_png( - image::open(format!("assets/logo_{s}.png")) + image::open(format!("assets/logo_{}.png", s)) .unwrap() .as_bytes(), *s, diff --git a/psst-gui/src/cmd.rs b/psst-gui/src/cmd.rs index 9e897e79..8b4b24b9 100644 --- a/psst-gui/src/cmd.rs +++ b/psst-gui/src/cmd.rs @@ -17,6 +17,8 @@ pub const SHOW_MAIN: Selector = Selector::new("app.show-main"); pub const SHOW_ACCOUNT_SETUP: Selector = Selector::new("app.show-initial"); pub const CLOSE_ALL_WINDOWS: Selector = Selector::new("app.close-all-windows"); pub const QUIT_APP_WITH_SAVE: Selector = Selector::new("app.quit-with-save"); +pub const QUIT_APP_CLEAN: Selector = Selector::new("app.quit-clean"); +pub const TRAY_REMOVED: Selector = Selector::new("app.tray-removed"); pub const SET_FOCUS: Selector = Selector::new("app.set-focus"); pub const COPY: Selector = Selector::new("app.copy-to-clipboard"); pub const GO_TO_URL: Selector = Selector::new("app.go-to-url"); @@ -51,6 +53,7 @@ pub const PLAY_TRACKS: Selector = Selector::new("app.play-track pub const PLAY_PREVIOUS: Selector = Selector::new("app.play-previous"); pub const PLAY_PAUSE: Selector = Selector::new("app.play-pause"); pub const PLAY_RESUME: Selector = Selector::new("app.play-resume"); +pub const PLAY_PAUSE_TOGGLE: Selector = Selector::new("app.play-pause-toggle"); pub const PLAY_NEXT: Selector = Selector::new("app.play-next"); pub const PLAY_STOP: Selector = Selector::new("app.play-stop"); pub const ADD_TO_QUEUE: Selector<(QueueEntry, PlaybackItem)> = Selector::new("app.add-to-queue"); diff --git a/psst-gui/src/controller/nav.rs b/psst-gui/src/controller/nav.rs index ec46a7ba..bb026042 100644 --- a/psst-gui/src/controller/nav.rs +++ b/psst-gui/src/controller/nav.rs @@ -94,7 +94,7 @@ where self.load_route_data(ctx, data); } Event::Command(cmd) if cmd.is(cmd::NAVIGATE_REFRESH) => { - data.refresh_playlist(); + data.refresh(); ctx.set_handled(); self.load_route_data(ctx, data); } @@ -116,8 +116,8 @@ where ctx.set_handled(); self.load_route_data(ctx, data); } - Event::KeyDown(key) if key.mods.ctrl() && key.code == Code::KeyR => { - data.refresh_all(); + Event::KeyDown(key) if key.mods.meta() && key.code == Code::KeyR => { + data.refresh(); ctx.set_handled(); self.load_route_data(ctx, data); } diff --git a/psst-gui/src/controller/playback.rs b/psst-gui/src/controller/playback.rs index a8fb08e6..65e6f55e 100644 --- a/psst-gui/src/controller/playback.rs +++ b/psst-gui/src/controller/playback.rs @@ -56,7 +56,7 @@ fn init_scrobbler_instance(data: &AppState) -> Option { return Some(scr); } Err(e) => { - log::warn!("Failed to create/update Last.fm Scrobbler instance: {e}"); + log::warn!("Failed to create/update Last.fm Scrobbler instance: {}", e); } } } else { @@ -101,7 +101,7 @@ impl PlaybackController { ); self.media_controls = Self::create_media_controls(player.sender(), window) - .map_err(|err| log::error!("failed to connect to media control interface: {err:?}")) + .map_err(|err| log::error!("failed to connect to media control interface: {:?}", err)) .ok(); self.sender = Some(player.sender()); @@ -257,7 +257,7 @@ impl PlaybackController { fn send(&mut self, event: PlayerEvent) { if let Some(s) = &self.sender { s.send(event) - .map_err(|e| log::error!("error sending message: {e:?}")) + .map_err(|e| log::error!("error sending message: {:?}", e)) .ok(); } } @@ -276,9 +276,9 @@ impl PlaybackController { title.as_ref(), album.as_ref().map(|a| a.name.as_ref()), ) { - log::warn!("failed to report 'Now Playing' to Last.fm: {e}"); + log::warn!("failed to report 'Now Playing' to Last.fm: {}", e); } else { - log::info!("reported 'Now Playing' to Last.fm: {artist} - {title}"); + log::info!("reported 'Now Playing' to Last.fm: {} - {}", artist, title); } } else { log::debug!("Last.fm not configured, skipping now_playing report."); @@ -302,9 +302,9 @@ impl PlaybackController { title.as_ref(), album.as_ref().map(|a| a.name.as_ref()), ) { - log::warn!("failed to scrobble track to Last.fm: {e}"); + log::warn!("failed to scrobble track to Last.fm: {}", e); } else { - log::info!("scrobbled track to Last.fm: {artist} - {title}"); + log::info!("scrobbled track to Last.fm: {} - {}", artist, title); self.has_scrobbled = true; } } else { @@ -506,6 +506,10 @@ where self.resume(); ctx.set_handled(); } + Event::Command(cmd) if cmd.is(cmd::PLAY_PAUSE_TOGGLE) => { + self.pause_or_resume(); + ctx.set_handled(); + } Event::Command(cmd) if cmd.is(cmd::PLAY_PREVIOUS) => { self.previous(); ctx.set_handled(); diff --git a/psst-gui/src/data/album.rs b/psst-gui/src/data/album.rs index ed30d0fc..e9041736 100644 --- a/psst-gui/src/data/album.rs +++ b/psst-gui/src/data/album.rs @@ -109,16 +109,21 @@ impl AlbumLink { } } -#[derive(Clone, Debug, Data, Eq, PartialEq, Hash, Deserialize, Serialize, Default)] +#[derive(Clone, Debug, Data, Eq, PartialEq, Hash, Deserialize)] #[serde(rename_all = "snake_case")] pub enum AlbumType { - #[default] Album, Single, Compilation, AppearsOn, } +impl Default for AlbumType { + fn default() -> Self { + Self::Album + } +} + #[derive(Clone, Debug, Eq, PartialEq, Data, Deserialize)] #[serde(rename_all = "snake_case")] pub enum DatePrecision { diff --git a/psst-gui/src/data/config.rs b/psst-gui/src/data/config.rs index 639fb98c..d5527f99 100644 --- a/psst-gui/src/data/config.rs +++ b/psst-gui/src/data/config.rs @@ -116,6 +116,8 @@ pub struct Config { pub last_route: Option