From c2ac57d7275a3b4075bcde649759951dfaa010db Mon Sep 17 00:00:00 2001 From: Magnus Root Date: Sat, 25 Jul 2026 21:00:32 +0300 Subject: [PATCH] New smb --- .gitignore | 1 - Cargo.lock | 2105 ++++++++++++++++++++++++++++++++- Cargo.toml | 1 + README.en.md | 12 +- README.md | 21 +- crates/library/Cargo.toml | 1 + crates/library/src/lib.rs | 20 +- crates/library/src/scanner.rs | 36 +- crates/library/src/smb.rs | 45 +- crates/player/Cargo.toml | 1 + crates/player/src/decode.rs | 80 +- crates/smbfs/Cargo.toml | 11 + crates/smbfs/src/lib.rs | 319 +++++ crates/tui/Cargo.toml | 1 + crates/tui/src/app.rs | 135 ++- crates/tui/src/lib.rs | 1 + crates/tui/src/ui.rs | 80 +- 17 files changed, 2790 insertions(+), 80 deletions(-) create mode 100644 crates/smbfs/Cargo.toml create mode 100644 crates/smbfs/src/lib.rs diff --git a/.gitignore b/.gitignore index 96ef6c0..ea8c4bf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1 @@ /target -Cargo.lock diff --git a/Cargo.lock b/Cargo.lock index a355502..22869cf 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 = "adler2" @@ -8,6 +8,41 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.6.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac8202ab55fcbf46ca829833f347a82a2a4ce0596f0304ac322c2d100030cd56" +dependencies = [ + "crypto-common 0.2.0-rc.4", + "inout", +] + +[[package]] +name = "aes" +version = "0.9.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e713c57c2a2b19159e7be83b9194600d7e8eb3b7c2cd67e671adf47ce189a05" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.11.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0686ba04dc80c816104c96cd7782b748f6ad58c5dd4ee619ff3258cf68e83d54" +dependencies = [ + "aead", + "aes", + "cipher", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "aho-corasick" version = "1.1.4" @@ -60,24 +95,84 @@ version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +[[package]] +name = "array-init" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" + [[package]] name = "arrayvec" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" +[[package]] +name = "async-dnssd" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d49ffe175ab45bbfd74b548313d9d7cdfff27161a94b007b52eeeb5f9aaa15e" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-util", + "libc", + "log", + "pin-utils", + "pkg-config", + "tokio", + "winapi", +] + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "base16ct" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b59d472eab27ade8d770dcb11da7201c11234bef9f82ce7aa517be028d462b" + +[[package]] +name = "base16ct" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd307490d624467aa6f74b0eabb77633d1f758a7b25f12bceb0b22e08d9726f6" + [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + [[package]] name = "bindgen" version = "0.72.1" @@ -93,7 +188,31 @@ dependencies = [ "regex", "rustc-hash", "shlex 1.3.0", - "syn", + "syn 2.0.118", +] + +[[package]] +name = "binrw" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ad120d555272286c1017d25165ab8bd74806f13fc85b258484ec7e4ce75458f" +dependencies = [ + "array-init", + "binrw_derive", + "bytemuck", +] + +[[package]] +name = "binrw_derive" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6df92e0e9baae4dc82c7bad7715ca40c0a5c71539057bf2ea04a5c29c980410b" +dependencies = [ + "either", + "owo-colors", + "proc-macro2", + "quote", + "syn 2.0.118", ] [[package]] @@ -108,6 +227,33 @@ version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.11.0-rc.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9ef36a6fcdb072aa548f3da057640ec10859eb4e91ddf526ee648d50c76a949" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "block-padding" +version = "0.4.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e59c1aab3e6c5e56afe1b7e8650be9b5a791cb997bdea449194ae62e4bf8c73" +dependencies = [ + "hybrid-array", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -147,6 +293,15 @@ dependencies = [ "rustversion", ] +[[package]] +name = "cbc" +version = "0.2.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dbf9e5b071e9de872e32b73f485e8f644ff47c7011d95476733e7482ee3e5c3" +dependencies = [ + "cipher", +] + [[package]] name = "cc" version = "1.2.66" @@ -159,6 +314,18 @@ dependencies = [ "shlex 2.0.1", ] +[[package]] +name = "ccm" +version = "0.6.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a49680966b1cae2a239dd94d11bac423894ee4e00dacea1c50e8673e26a9829f" +dependencies = [ + "aead", + "cipher", + "ctr", + "subtle", +] + [[package]] name = "cesu8" version = "1.1.0" @@ -180,6 +347,23 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + [[package]] name = "chrono" version = "0.4.45" @@ -192,6 +376,17 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cipher" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e12a13eb01ded5d32ee9658d94f553a19e804204f2dc811df69ab4d9e0cb8c7" +dependencies = [ + "block-buffer 0.11.0-rc.5", + "crypto-common 0.2.0-rc.4", + "inout", +] + [[package]] name = "clang-sys" version = "1.8.1" @@ -203,6 +398,17 @@ dependencies = [ "libloading", ] +[[package]] +name = "cmac" +version = "0.8.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2fa99ca412f76b4656efeab943ef06f1ddd8036c31ca38f35188f173d0dc85e" +dependencies = [ + "cipher", + "dbl", + "digest 0.11.0-rc.3", +] + [[package]] name = "color_quant" version = "1.1.0" @@ -233,6 +439,22 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -279,7 +501,25 @@ dependencies = [ "wasm-bindgen", "wasm-bindgen-futures", "web-sys", - "windows", + "windows 0.54.0", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", ] [[package]] @@ -322,6 +562,97 @@ dependencies = [ "winapi", ] +[[package]] +name = "crypto-bigint" +version = "0.7.0-rc.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4113edbc9f68c0a64d5b911f803eb245d04bb812680fd56776411f69c670f3e0" +dependencies = [ + "hybrid-array", + "num-traits", + "rand_core 0.9.5", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "crypto-common" +version = "0.2.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8235645834fbc6832939736ce2f2d08192652269e11010a6240f61b908a1c6" +dependencies = [ + "hybrid-array", + "rand_core 0.9.5", +] + +[[package]] +name = "crypto-mac" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "crypto-primes" +version = "0.7.0-pre.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25f2523fbb68811c8710829417ad488086720a6349e337c38d12fa81e09e50bf" +dependencies = [ + "crypto-bigint", + "libm", + "rand_core 0.9.5", +] + +[[package]] +name = "ctr" +version = "0.10.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27e41d01c6f73b9330177f5cf782ae5b581b5f2c7840e298e0275ceee5001434" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "5.0.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f9200d1d13637f15a6acb71e758f64624048d85b31a5fdbfd8eca1e2687d0b7" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.11.0-rc.3", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "dasp_sample" version = "0.11.0" @@ -334,18 +665,136 @@ version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" +[[package]] +name = "dbl" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d7a944e61df464668c5f51f56cc667396a8821434273112948ea0b66e405d7" +dependencies = [ + "hybrid-array", +] + +[[package]] +name = "der" +version = "0.8.0-rc.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d8dd2f26c86b27a2a8ea2767ec7f9df7a89516e4794e54ac01ee618dda3aa4" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" + +[[package]] +name = "des" +version = "0.9.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f51594a70805988feb1c85495ddec0c2052e4fbe59d9c0bb7f94bfc164f4f90" +dependencies = [ + "cipher", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "crypto-common 0.1.7", +] + +[[package]] +name = "digest" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dac89f8a64533a9b0eaa73a68e424db0fb1fd6271c74cc0125336a05f090568d" +dependencies = [ + "block-buffer 0.11.0-rc.5", + "const-oid", + "crypto-common 0.2.0-rc.4", + "subtle", +] + [[package]] name = "dyn-clone" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "ecdsa" +version = "0.17.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ab355ec063f7a110eb627471058093aba00eb7f4e70afbd15e696b79d1077b" +dependencies = [ + "der", + "digest 0.11.0-rc.3", + "elliptic-curve", + "rfc6979", + "signature", + "spki", + "zeroize", +] + +[[package]] +name = "ed25519" +version = "3.0.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ef49c0b20c0ad088893ad2a790a29c06a012b3f05bcfc66661fd22a94b32129" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "3.0.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad207ed88a133091f83224265eac21109930db09bedcad05d5252f2af2de20a1" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.9.5", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "either" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +[[package]] +name = "elliptic-curve" +version = "0.14.0-rc.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e3be87c458d756141f3b6ee188828132743bf90c7d14843e2835d6443e5fb03" +dependencies = [ + "base16ct 0.3.0", + "crypto-bigint", + "digest 0.11.0-rc.3", + "ff", + "group", + "hkdf", + "hybrid-array", + "once_cell", + "pem-rfc7468", + "pkcs8", + "rand_core 0.9.5", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encoding_rs" version = "0.8.35" @@ -392,6 +841,22 @@ dependencies = [ "simd-adler32", ] +[[package]] +name = "ff" +version = "0.14.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d42dd26f5790eda47c1a2158ea4120e32c35ddc9a7743c98a292accc01b54ef3" +dependencies = [ + "rand_core 0.9.5", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64cd1e32ddd350061ae6edb1b082d7c54915b5c672c389143b9a63403a109f24" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -423,12 +888,71 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + [[package]] name = "futures-core" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4577ecaa3c4f96589d473f679a71b596316f6641bc350038b962a5daf0085d7a" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "futures-sink" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e34418ac499d6305c2fb5ad0ed2f6ac998c5f8ca209b4510f7f94242c647e307" + [[package]] name = "futures-task" version = "0.3.32" @@ -441,12 +965,27 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "slab", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.17" @@ -454,8 +993,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", ] [[package]] @@ -465,8 +1020,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" dependencies = [ "cfg-if", + "js-sys", "libc", - "r-efi", + "r-efi 6.0.0", + "rand_core 0.10.1", + "wasm-bindgen", +] + +[[package]] +name = "ghash" +version = "0.6.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f88107cb02ed63adcc4282942e60c4d09d80208d33b360ce7c729ce6dae1739" +dependencies = [ + "polyval", ] [[package]] @@ -485,6 +1052,17 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "group" +version = "0.14.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ff6a0b2dd4b981b1ae9e3e6830ab146771f3660d31d57bafd9018805a91b0f1" +dependencies = [ + "ff", + "rand_core 0.9.5", + "subtle", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -508,6 +1086,139 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.13.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8ef30358b03ca095a5b910547f4f8d4b9f163e4057669c5233ef595b1ecf008" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.13.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3fd4dc94c318c1ede8a2a48341c250d6ddecd3ba793da2820301a9f92417ad9" +dependencies = [ + "digest 0.11.0-rc.3", +] + +[[package]] +name = "http" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9f41fd6a08e4d4ec69df65976da761afd5ad5e58a9d4acb46bd1c953a9e3ff2" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hybrid-array" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "818356c5132c1fede50f837ca96afbe78ff42413047f4abb886217845e1b6c8c" +dependencies = [ + "subtle", + "typenum", + "zeroize", +] + +[[package]] +name = "hyper" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-native-certs", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.65" @@ -595,6 +1306,16 @@ dependencies = [ "hashbrown 0.17.1", ] +[[package]] +name = "inout" +version = "0.2.0-rc.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1603f76010ff924b616c8f44815a42eb10fb0b93d308b41deaa8da6d4251fd4b" +dependencies = [ + "block-padding", + "hybrid-array", +] + [[package]] name = "instability" version = "0.3.1" @@ -602,9 +1323,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8a8b1fa57a8c4b31ef130ac67b3e11b354c09e3a0453a24cade62756d52a6ee" dependencies = [ "quote", - "syn", + "syn 2.0.118", ] +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + [[package]] name = "itertools" version = "0.13.0" @@ -631,7 +1358,7 @@ dependencies = [ "combine", "jni-sys 0.3.1", "log", - "thiserror", + "thiserror 1.0.69", "walkdir", "windows-sys 0.45.0", ] @@ -661,7 +1388,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" dependencies = [ "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -685,6 +1412,24 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "kbkdf" +version = "0.1.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97e1f9b50adafd503518a832b24241dca1d319bfc27e9e708bcf220b0b25419" +dependencies = [ + "digest 0.11.0-rc.3", +] + +[[package]] +name = "keccak" +version = "0.2.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d546793a04a1d3049bd192856f804cfe96356e2cf36b54b4e575155babe9f41" +dependencies = [ + "cpufeatures 0.2.17", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -707,6 +1452,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + [[package]] name = "library" version = "0.1.0" @@ -717,9 +1468,10 @@ dependencies = [ "nucleo-matcher", "serde", "serde_json", + "smbfs", "tempfile", "textfix", - "thiserror", + "thiserror 1.0.69", "ureq", "walkdir", ] @@ -762,7 +1514,7 @@ checksum = "83e2dcdba64a01c3ce936b02401ab17b52db3c72720cf484c9df3387ca7517d3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -780,6 +1532,18 @@ dependencies = [ "hashbrown 0.15.5", ] +[[package]] +name = "lru-slab" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" + +[[package]] +name = "lz4_flex" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373f5eceeeab7925e0c1098212f2fbc4d416adec9d35051a6ab251e824c1854a" + [[package]] name = "mach2" version = "0.4.3" @@ -789,6 +1553,36 @@ dependencies = [ "libc", ] +[[package]] +name = "maybe-async" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "746873a384ad60adc5db74471dfaba74bd278afbdcfd81db93fafcdfc8b5ca0c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "md-5" +version = "0.11.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9ec86664728010f574d67ef01aec964e6f1299241a3402857c1a8a390a62478" +dependencies = [ + "cfg-if", + "digest 0.11.0-rc.3", +] + +[[package]] +name = "md4" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da5ac363534dce5fabf69949225e174fbf111a498bf0ff794c8ea1fba9f3dda" +dependencies = [ + "digest 0.10.7", +] + [[package]] name = "memchr" version = "2.8.3" @@ -823,6 +1617,27 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ndk" version = "0.8.0" @@ -834,7 +1649,7 @@ dependencies = [ "log", "ndk-sys", "num_enum", - "thiserror", + "thiserror 1.0.69", ] [[package]] @@ -889,6 +1704,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + [[package]] name = "num-derive" version = "0.4.2" @@ -897,7 +1718,7 @@ checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -937,7 +1758,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -972,12 +1793,75 @@ dependencies = [ "byteorder", ] +[[package]] +name = "oid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c19903c598813dba001b53beeae59bb77ad4892c5c1b9b3500ce4293a0d06c2" +dependencies = [ + "serde", +] + [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "owo-colors" +version = "4.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d211803b9b6b570f68772237e415a029d5a50c65d382910b879fb19d3271f94d" + +[[package]] +name = "p256" +version = "0.14.0-pre.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b374901df34ee468167a58e2a49e468cb059868479cafebeb804f6b855423d" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "sha2", +] + +[[package]] +name = "p384" +version = "0.14.0-pre.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "701032b3730df6b882496d6cee8221de0ce4bc11ddc64e6d89784aa5b8a6de30" +dependencies = [ + "ecdsa", + "elliptic-curve", + "fiat-crypto", + "primefield", + "primeorder", + "sha2", +] + +[[package]] +name = "p521" +version = "0.14.0-pre.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ba29c2906eb5c89a8c411c4f11243ee4e5517ee7d71d9a13fedc877a6057b1" +dependencies = [ + "base16ct 0.3.0", + "ecdsa", + "elliptic-curve", + "primefield", + "primeorder", + "rand_core 0.9.5", + "sha2", +] + [[package]] name = "parking_lot" version = "0.12.5" @@ -1007,18 +1891,193 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pastey" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35fb2e5f958ec131621fdd531e9fc186ed768cbe395337403ae56c17a74c68ec" + +[[package]] +name = "pbkdf2" +version = "0.13.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3fc18bb4460ac250ba6b75dfa7cf9d0b2273e3e623f660bd6ce2c3e902342e" +dependencies = [ + "digest 0.11.0-rc.3", + "hmac", + "sha1", +] + +[[package]] +name = "pem-rfc7468" +version = "1.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8e58fab693c712c0d4e88f8eb3087b6521d060bcaf76aeb20cb192d809115ba" +dependencies = [ + "base64ct", +] + [[package]] name = "percent-encoding" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" +[[package]] +name = "picky" +version = "7.0.0-rc.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cdc52be663aebd70d7006ae305c87eb32a2b836d6c2f26f7e384f845d80b621" +dependencies = [ + "base64", + "block-buffer 0.11.0-rc.5", + "block-padding", + "crypto-bigint", + "crypto-common 0.2.0-rc.4", + "crypto-primes", + "curve25519-dalek", + "der", + "digest 0.11.0-rc.3", + "ecdsa", + "ed25519", + "ed25519-dalek", + "elliptic-curve", + "ff", + "group", + "hex", + "hkdf", + "inout", + "keccak", + "md-5", + "p256", + "p384", + "p521", + "pem-rfc7468", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "pkcs1", + "pkcs8", + "primefield", + "primeorder", + "rand 0.9.5", + "rand_core 0.9.5", + "rfc6979", + "rsa", + "sec1", + "serde", + "sha1", + "sha2", + "sha3", + "signature", + "spki", + "thiserror 2.0.19", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "picky-asn1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ff038f9360b934342fb3c0a1d6e82c438a2624b51c3c6e3e6d7cf252b6f3ee3" +dependencies = [ + "oid", + "serde", + "serde_bytes", + "time", + "zeroize", +] + +[[package]] +name = "picky-asn1-der" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d413165e4bf7f808b9a27cbaba657657a2921f0965db833f488c4d4be96dcd2e" +dependencies = [ + "picky-asn1", + "serde", + "serde_bytes", +] + +[[package]] +name = "picky-asn1-x509" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c97cd14d567a17755910fa8718277baf39d08682a980b1b1a4b4da7d0bc61a04" +dependencies = [ + "base64", + "crypto-bigint", + "oid", + "picky-asn1", + "picky-asn1-der", + "serde", + "widestring", + "zeroize", +] + +[[package]] +name = "picky-krb" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed61c8d7448649c031ecae02afb10c679524c7a9af5fb0fbee466b3cc0d6df1" +dependencies = [ + "aes", + "block-buffer 0.11.0-rc.5", + "block-padding", + "byteorder", + "cbc", + "cipher", + "crypto-bigint", + "crypto-common 0.2.0-rc.4", + "des", + "digest 0.11.0-rc.3", + "hmac", + "inout", + "oid", + "pbkdf2", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "rand 0.9.5", + "serde", + "sha1", + "thiserror 2.0.19", + "uuid", +] + [[package]] name = "pin-project-lite" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs1" +version = "0.8.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986d2e952779af96ea048f160fd9194e1751b4faea78bcf3ceb456efe008088e" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.11.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93eac55f10aceed84769df670ea4a32d2ffad7399400d41ee1c13b1cd8e1b478" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" version = "0.3.33" @@ -1032,13 +2091,14 @@ dependencies = [ "anyhow", "cpal", "encoding_rs", - "rand", + "rand 0.8.6", "realfft", "ringbuf", "rubato", + "smbfs", "symphonia", "textfix", - "thiserror", + "thiserror 1.0.69", "ureq", ] @@ -1055,6 +2115,23 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "polyval" +version = "0.7.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffd40cc99d0fbb02b4b3771346b811df94194bc103983efa0203c8893755085" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "universal-hash", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.21" @@ -1073,6 +2150,28 @@ dependencies = [ "num-integer", ] +[[package]] +name = "primefield" +version = "0.14.0-pre.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fcd4a163053332fd93f39b81c133e96a98567660981654579c90a99062fbf5" +dependencies = [ + "crypto-bigint", + "ff", + "rand_core 0.9.5", + "subtle", + "zeroize", +] + +[[package]] +name = "primeorder" +version = "0.14.0-pre.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c36e8766fcd270fa9c665b9dc364f570695f5a59240949441b077a397f15b74" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -1091,6 +2190,62 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quinn" +version = "0.11.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.19", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4bfc015262b9df63c8845072ce59068853ff5872180c2ce2f13038b970e560" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand 0.10.2", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.19", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + [[package]] name = "quote" version = "1.0.46" @@ -1100,6 +2255,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" @@ -1113,8 +2274,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", +] + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core 0.10.1", ] [[package]] @@ -1124,7 +2306,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1136,6 +2328,30 @@ dependencies = [ "getrandom 0.2.17", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core 0.10.1", +] + [[package]] name = "ratatui" version = "0.28.1" @@ -1168,7 +2384,7 @@ dependencies = [ "dyn-clone", "icy_sixel", "image", - "rand", + "rand 0.8.6", "ratatui", "rustix", ] @@ -1220,6 +2436,56 @@ version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-native-certs", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "rfc6979" +version = "0.5.0-rc.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d369f9c4f79388704648e7bcb92749c0d6cf4397039293a9b747694fa4fb4bae" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ring" version = "0.17.14" @@ -1243,6 +2509,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "rsa" +version = "0.10.0-rc.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8955ab399f6426998fde6b76ae27233cce950705e758a6c17afd2f6d0e5d52" +dependencies = [ + "const-oid", + "crypto-bigint", + "crypto-primes", + "digest 0.11.0-rc.3", + "pkcs1", + "pkcs8", + "rand_core 0.9.5", + "sha1", + "signature", + "spki", + "subtle", + "zeroize", +] + [[package]] name = "rubato" version = "0.15.0" @@ -1261,6 +2547,15 @@ version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + [[package]] name = "rustfft" version = "6.4.1" @@ -1303,12 +2598,25 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + [[package]] name = "rustls-pki-types" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "764899a24af3980067ee14bc143654f297b22eaebfe3c7b6b211920a5a59b046" dependencies = [ + "web-time", "zeroize", ] @@ -1344,12 +2652,63 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.8.0-rc.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dff52f6118bc9f0ac974a54a639d499ac26a6cad7a6e39bc0990c19625e793b" +dependencies = [ + "base16ct 0.3.0", + "der", + "hybrid-array", + "subtle", + "zeroize", +] + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.13.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "serde" version = "1.0.228" @@ -1360,6 +2719,16 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -1377,7 +2746,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -1393,6 +2762,60 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serdect" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66cf8fedced2fcf12406bcb34223dffb92eaf34908ede12fed414c82b7f00b3e" +dependencies = [ + "base16ct 1.0.0", + "serde", +] + +[[package]] +name = "sha1" +version = "0.11.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e046edf639aa2e7afb285589e5405de2ef7e61d4b0ac1e30256e3eab911af9" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.11.0-rc.3", +] + +[[package]] +name = "sha2" +version = "0.11.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1e3878ab0f98e35b2df35fe53201d088299b41a6bb63e3e34dada2ac4abd924" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest 0.11.0-rc.3", +] + +[[package]] +name = "sha3" +version = "0.11.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2103ca0e6f4e9505eae906de5e5883e06fc3b2232fb5d6914890c7bbcb62f478" +dependencies = [ + "digest 0.11.0-rc.3", + "keccak", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1436,6 +2859,16 @@ dependencies = [ "libc", ] +[[package]] +name = "signature" +version = "3.0.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc280a6ff65c79fbd6622f64d7127f32b85563bca8c53cd2e9141d6744a9056d" +dependencies = [ + "digest 0.11.0-rc.3", + "rand_core 0.9.5", +] + [[package]] name = "simd-adler32" version = "0.3.9" @@ -1454,6 +2887,239 @@ version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +[[package]] +name = "smb" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c12ddf75029c02180237fa98948bd3dea72647c08bd7d1b5fa4923491764100" +dependencies = [ + "aead", + "aes", + "aes-gcm", + "binrw", + "ccm", + "cmac", + "crypto-common 0.2.0-rc.4", + "futures", + "futures-core", + "futures-util", + "hmac", + "kbkdf", + "log", + "lz4_flex", + "maybe-async", + "modular-bitfield", + "pastey", + "rand 0.8.6", + "sha2", + "smb-dtyp", + "smb-fscc", + "smb-msg", + "smb-rpc", + "smb-transport", + "sspi", + "thiserror 2.0.19", + "time", + "tokio", + "tokio-stream", + "tokio-util", + "url", +] + +[[package]] +name = "smb-dtyp" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11e11570063587382275297db37dd686360fcc109d22a8d655846dc93bf4a71a" +dependencies = [ + "binrw", + "modular-bitfield", + "pastey", + "rand 0.8.6", + "smb-dtyp-derive", + "time", +] + +[[package]] +name = "smb-dtyp-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12fcc3a1d02a6b5a78a0380adcc9218a65bbdee515d4f60b9c90566611ef5ec8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "smb-fscc" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff4559d37f9c3e6b56fffa1a359a6f151337b18b9d0218161488a4a7e15685" +dependencies = [ + "binrw", + "modular-bitfield", + "pastey", + "smb-dtyp", + "thiserror 2.0.19", + "time", +] + +[[package]] +name = "smb-msg" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87b761115473b9af471c06b28e1d26b16602c97ae315c8d325b2418dedb992bf" +dependencies = [ + "binrw", + "modular-bitfield", + "pastey", + "smb-dtyp", + "smb-fscc", + "smb-msg-derive", + "thiserror 2.0.19", + "time", +] + +[[package]] +name = "smb-msg-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae33ff90211b548f0ff248b511eada2623ba1693d388c363e48003a5a6be5991" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "smb-rpc" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352732178153ef2379552a09cd5319fc4ce49a0837576e87eb9ac36652330a46" +dependencies = [ + "binrw", + "maybe-async", + "modular-bitfield", + "pastey", + "smb-dtyp", + "thiserror 2.0.19", + "time", +] + +[[package]] +name = "smb-transport" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "383209d094590c95b748cf20e79e5216ec8cf458486c0253c3610dd2c36d435d" +dependencies = [ + "binrw", + "futures-core", + "futures-util", + "log", + "maybe-async", + "modular-bitfield", + "pastey", + "smb-dtyp", + "thiserror 2.0.19", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "smbfs" +version = "0.1.0" +dependencies = [ + "anyhow", + "futures-util", + "smb", + "smb-fscc", + "tokio", +] + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spki" +version = "0.8.0-rc.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8baeff88f34ed0691978ec34440140e1572b68c7dd4a495fd14a3dc1944daa80" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sspi" +version = "0.18.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f4823ee743a4a0cc2153eb640e28ff95b55ca25c88085b559bae59fb6c317a" +dependencies = [ + "async-dnssd", + "async-recursion", + "bitflags 2.13.0", + "block-buffer 0.11.0-rc.5", + "byteorder", + "cfg-if", + "crypto-bigint", + "crypto-common 0.2.0-rc.4", + "crypto-mac", + "crypto-primes", + "curve25519-dalek", + "der", + "digest 0.11.0-rc.3", + "ed25519-dalek", + "ff", + "futures", + "getrandom 0.3.4", + "group", + "hmac", + "md-5", + "md4", + "num-derive", + "num-traits", + "oid", + "p256", + "p384", + "p521", + "pem-rfc7468", + "picky", + "picky-asn1", + "picky-asn1-der", + "picky-asn1-x509", + "picky-krb", + "pkcs1", + "pkcs8", + "primefield", + "primeorder", + "rand 0.9.5", + "reqwest", + "rsa", + "rustls", + "serde", + "sha1", + "sha2", + "signature", + "spki", + "time", + "tokio", + "tracing", + "url", + "uuid", + "windows 0.62.2", + "windows-registry", + "zeroize", +] + [[package]] name = "static_assertions" version = "1.1.0" @@ -1485,7 +3151,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 2.0.118", ] [[package]] @@ -1689,6 +3355,17 @@ dependencies = [ "symphonia-metadata", ] +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.118" @@ -1700,6 +3377,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + [[package]] name = "tempfile" version = "3.14.0" @@ -1726,7 +3423,16 @@ version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl", + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl 2.0.19", ] [[package]] @@ -1737,7 +3443,49 @@ checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.3", +] + +[[package]] +name = "time" +version = "0.3.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1d5e639ff6bab73cb6885cc7e7b1de96c3f32c68ec55f3952614bec1092244" +dependencies = [ + "deranged", + "js-sys", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", ] [[package]] @@ -1755,6 +3503,66 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6328af13490e73a9b4694030fafd93f8c8c6a9dede33e821c3fc63eddf8042ba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3d06f0b082ba57c26b79407372e57cf2a1e28124f78e9479fe80322cf53420b" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -1785,6 +3593,82 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.0", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + [[package]] name = "transpose" version = "0.2.3" @@ -1795,6 +3679,12 @@ dependencies = [ "strength_reduce", ] +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + [[package]] name = "tui" version = "0.1.0" @@ -1807,8 +3697,15 @@ dependencies = [ "player", "ratatui", "ratatui-image", + "smbfs", ] +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + [[package]] name = "unicode-bidi" version = "0.3.18" @@ -1859,6 +3756,16 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" +[[package]] +name = "universal-hash" +version = "0.6.0-rc.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55be643b40a21558f44806b53ee9319595bc7ca6896372e4e08e5d7d83c9cd6" +dependencies = [ + "crypto-common 0.2.0-rc.4", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -1901,6 +3808,24 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "uuid" +version = "1.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf3923a6f5c4c6382e0b653c4117f48d631ea17f38ed86e2a828e6f7412f5239" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -1911,12 +3836,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.126" @@ -1959,7 +3902,7 @@ dependencies = [ "bumpalo", "proc-macro2", "quote", - "syn", + "syn 2.0.118", "wasm-bindgen-shared", ] @@ -1982,6 +3925,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "webpki-roots" version = "0.26.11" @@ -2006,6 +3959,12 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a28ac98ddc8b9274cb41bb4d9d4d5c425b6020c50c46f25559911905610b4a88" +[[package]] +name = "widestring" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" + [[package]] name = "winapi" version = "0.3.9" @@ -2047,6 +4006,27 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core 0.62.2", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core 0.62.2", +] + [[package]] name = "windows-core" version = "0.54.0" @@ -2070,6 +4050,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core 0.62.2", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -2078,7 +4069,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2089,7 +4080,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2098,6 +4089,27 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core 0.62.2", + "windows-link", +] + +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result 0.4.1", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -2192,6 +4204,15 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -2291,6 +4312,24 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "x25519-dalek" +version = "3.0.0-pre.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a45998121837fd8c92655d2334aa8f3e5ef0645cdfda5b321b13760c548fd55" +dependencies = [ + "curve25519-dalek", + "rand_core 0.9.5", + "serde", + "zeroize", +] + [[package]] name = "zerocopy" version = "0.8.54" @@ -2308,7 +4347,7 @@ checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.118", ] [[package]] @@ -2316,6 +4355,20 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c50655cbb0fe3fc43170059e702f1ce5e19b84cec58dc87b037a09935c2f328" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] [[package]] name = "zmij" diff --git a/Cargo.toml b/Cargo.toml index ea7ecb3..e4644f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,6 +6,7 @@ members = [ "crates/tui", "crates/nsm", "crates/textfix", + "crates/smbfs", ] [workspace.package] diff --git a/README.en.md b/README.en.md index 7707d83..1c80746 100644 --- a/README.en.md +++ b/README.en.md @@ -16,7 +16,7 @@ A terminal music player written in Rust — something like [cmus](https://cmus.g - **Fade-out at track end**: the last 4 seconds smoothly fade to silence — toggle with `F`, off by default - **Loudness normalization**: ReplayGain tag takes priority if present in the file, otherwise an adaptive leveler (AGC) adjusts volume on the fly — toggle with `G`, off by default - **Internet radio**: add your own stations by URL, a curated built-in list (SomaFM, Radio Paradise), station search via the open [Radio Browser API](https://api.radio-browser.info) (auto-fails over between several mirrors if one is down), ICY metadata ("now playing" straight from the stream) -- **SMB shares**: connect to network folders (including anonymous/guest access) via the system's gvfs — no SMB protocol implemented inside the player itself +- **SMB shares**: connect to network folders (including anonymous/guest access) — a pure-Rust SMB client (the `smb` crate), no system dependency on `gio`/gvfs - **Listening history**: asks on first launch whether to remember what you play; stats (artist/track/date) as a chart — hourly for today, daily for the last week/month; can be toggled on/off anytime, resettable with confirmation - **New releases**: checks for new albums/EPs/singles **from artists already in your library**, via the open [MusicBrainz](https://musicbrainz.org) database (no API keys) — not a global chart, but specifically "what's new from the artists you actually listen to" - **Keyboard layout independent**: hotkeys work the same on both English and Russian (or other Cyrillic) keyboard layouts @@ -51,7 +51,11 @@ xcode-select --install **Windows** *(untested — this project has only been built and tested on Linux)* — no separate audio dependency needed either (`cpal` uses WASAPI); a normal Rust install via [rustup](https://rustup.rs) (MSVC toolchain) should be enough. For cover art to render well you'll want a modern terminal with true color support — Windows Terminal is recommended; the image protocols (kitty/sixel/iTerm2) are unlikely to be supported there, so cover art will fall back to block-graphics rendering. -**The SMB feature (`c` in Library) only works on Linux** — it's built entirely on `gio`/gvfs (the GNOME stack), which doesn't exist on macOS or Windows. On those systems, trying to connect an SMB share through nsm will just return a clear error ("is gvfs installed?") rather than crash — network shares there are normally mounted through Finder/Explorer instead. +**The SMB feature (`c` in Library)** is implemented with its own pure-Rust SMB client (the [`smb`](https://crates.io/crates/smb) crate) — no dependency on the system's `gio`/gvfs stack or its per-distro quirks. Connecting and scanning a share run on a background thread — the interface doesn't freeze even on a slow network. + +**Requires Rust 1.85+ (edition 2024)** — at the time of writing, on Ubuntu/Debian this means separate versioned packages (`rustc-1.91`/`cargo-1.91` and similar) rather than the default (usually 1.75). If your `rustc` is older, either install versioned packages like these via `apt` if your distro has them, or use `rustup`. + +**Honest testing caveat**: the core SMB logic (connecting, recursive directory listing, reading files with accurate `seek`, decoding audio over SMB) was verified against a real Samba server and works. But the sandbox this was developed in has no real sound card — `cpal` (the audio library) reliably fails to start there, and in that specific state (plus an active audio-engine thread) a hang was found when listing an SMB directory. The root cause wasn't fully tracked down (something at the intersection of ALSA/cpal and tokio's reactor, specifically on a **failed** cpal device-open attempt) — it's possible this is specific to the no-sound-card condition and won't occur where cpal opens a stream successfully. The SMB connection now runs on a background thread regardless, so even if this hang does occur, the whole interface won't freeze — only that one connection attempt will. If you do hit this, let us know — with working audio to test against, it'll narrow things down a lot faster. No extra system dependencies are needed on any OS for internet radio (**SomaFM**, **Radio Paradise**, etc.) or MusicBrainz — TLS is handled by `rustls` (pure Rust, no OpenSSL). @@ -230,10 +234,10 @@ Toggle with `G`. If the file has a ReplayGain tag (`REPLAYGAIN_TRACK_GAIN` and e ## Known limitations -- **Only built and tested on Linux** — macOS/Windows builds haven't been verified; the SMB feature fundamentally won't work there (see above) +- **Only built and tested on Linux** — macOS/Windows builds haven't been verified (SMB is now pure Rust and in theory cross-platform, but untested outside Linux) - **Opus is not decoded** (see "Supported formats" above) - **Playlists don't store cue track ranges** — adding a cue track to a playlist saves the whole underlying file, not the specific virtual track - Minimum terminal size is 70×18; below that a warning is shown instead of a broken layout -- Connecting to SMB shares is a blocking call on the UI thread; the interface will briefly freeze during the operation (radio search and new-releases checks already run in the background and don't block) +- Connecting to SMB shares, radio search, and new-releases checks all run in the background and don't block the interface - Multi-file cue sheets (a rare case) are only supported using the first `FILE` entry in the sheet diff --git a/README.md b/README.md index 6ae65ad..5db6005 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ - **Затухание в конце трека**: последние 4 секунды перед концом плавно уводятся в тишину — включается клавишей `F`, по умолчанию выключено - **Выравнивание громкости**: тег ReplayGain в приоритете (если есть в файле), иначе — адаптивный левеллер (AGC), подстраивающий громкость на лету — клавиша `G`, по умолчанию выключено - **Интернет-радио**: свои станции по URL, встроенный список (SomaFM, Radio Paradise), поиск станций через открытый [Radio Browser API](https://api.radio-browser.info) (с автопереключением между несколькими зеркалами, если одно недоступно), ICY-метаданные ("сейчас играет" прямо из потока) -- **SMB-шары**: подключение к сетевым папкам (в т.ч. анонимно/гостевой доступ) через системный gvfs — без реализации протокола внутри плеера +- **SMB-шары**: подключение к сетевым папкам (в т.ч. анонимно/гостевой доступ) — свой SMB-клиент на чистом Rust (крейт `smb`), без системных зависимостей вроде `gio`/gvfs - **История прослушиваний**: при первом запуске спрашивает, запоминать ли прослушанное; статистика (артист/трек/дата) — диаграммой по часам за сегодня, по дням за неделю/месяц; включается/выключается в любой момент, сбрасывается с подтверждением - **Новые релизы**: проверка новых альбомов/EP/синглов **у артистов, уже имеющихся в твоей библиотеке**, через открытую базу [MusicBrainz](https://musicbrainz.org) (без ключей API) — не глобальный чарт, а именно "что вышло нового у тех, кого ты слушаешь" - **Раскладка**: хоткеи работают одинаково что на английской, что на русской раскладке @@ -51,20 +51,11 @@ xcode-select --install **Windows** *(не проверено — собиралось и тестировалось только на Linux)* — отдельных аудио-зависимостей тоже не нужно (`cpal` использует WASAPI), достаточно обычной установки Rust через [rustup](https://rustup.rs) (MSVC toolchain). Для нормального отображения обложки в терминале нужен современный эмулятор с поддержкой true color — рекомендуется Windows Terminal; протоколы отображения картинок (kitty/sixel/iTerm2) там, скорее всего, не поддерживаются, так что обложка будет через block-графику как fallback. -**SMB-фича (`c` в Library) работает только на Linux** — она целиком построена на `gio`/gvfs (GNOME-стек), которого нет ни на macOS, ни на Windows. На этих системах при попытке подключить SMB-шару через nsm просто вернётся понятная ошибка ("is gvfs installed?"), без краша — сетевые папки там штатно монтируются через Finder/Проводник, не через nsm. +**SMB-фича (`c` в Library)** реализована собственным SMB-клиентом на чистом Rust (крейт [`smb`](https://crates.io/crates/smb)) — не зависит от системного `gio`/gvfs и его особенностей на конкретном дистрибутиве. Подключение и сканирование шары идут в фоновом потоке — интерфейс не подвисает даже если сеть медленная. -**Если получаешь ошибку "volume doesn't implement mount" / "том не поддерживает монтирование"** — это gvfs-специфичная ошибка, при том что сама SMB-шара доступна и работает. Обычно значит, что не хватает именно **SMB-бэкенда gvfs** (сам `gio` может быть установлен как чужая зависимость, а конкретно SMB-плагин к нему — нет). Проверить и починить: -```bash -# проверить руками, минуя nsm -gio mount smb://сервер/шара +**Требует Rust 1.85+ (edition 2024)** — на момент написания в Ubuntu/Debian это отдельные версионированные пакеты (`rustc-1.91`/`cargo-1.91` и т.п.), а не то, что стоит по умолчанию (там обычно 1.75). Если у тебя старый `rustc` — либо поставь такие версионированные пакеты через `apt`, если они есть в твоём дистрибутиве, либо используй `rustup`. -# на ALT/Fedora-подобных — убедиться, что бэкенд установлен -sudo apt-get install gvfs-backends # (или свой пакетный менеджер) - -# проверить, что D-Bus сессия вообще есть (без неё gvfs не активируется) -echo $DBUS_SESSION_BUS_ADDRESS -``` -Если это не помогло — причина может быть глубже, в согласовании версии протокола SMB между клиентом и сервером (см. `journalctl` на предмет строк вида `smbXcli_negprot` от `gvfsd`); в этом случае поможет `client min protocol` в `/etc/samba/smb.conf`, как описано выше. +**Честная оговорка по тестированию**: сама SMB-логика (подключение, рекурсивный листинг папок, чтение файлов с точным `seek`, декодирование аудио через SMB) проверена на реальном Samba-сервере и работает. Но в песочнице, где это разрабатывалось, не было настоящей звуковой карты — `cpal` (аудио-библиотека) там гарантированно падает при старте, и в этом конкретном состоянии (плюс активный поток аудио-движка) обнаружилось зависание при листинге SMB-папки. Причина не докопана до конца (что-то на стыке ALSA/cpal и tokio-реактора при **неудачной** попытке cpal открыть устройство) — и не исключено, что это специфично именно для отсутствия звуковой карты, а не проявится там, где `cpal` открывает поток нормально. Само SMB-подключение теперь работает в фоновом потоке, так что даже если это зависание всё-таки проявится — интерфейс не замёрзнет целиком, зависнет только конкретная попытка подключения. Если у тебя это тоже случится — дай знать, будем разбираться дальше уже на работающем звуке, что сильно сузит круг подозреваемых. Для интернет-радио (**SomaFM**, **Radio Paradise** и т.д.) и MusicBrainz дополнительных системных зависимостей не нужно ни на одной ОС — TLS обеспечивает `rustls` (чистый Rust, без OpenSSL). @@ -245,9 +236,9 @@ crates/ - Минимальный размер терминала — 70×18; на меньшем показывается предупреждение вместо сломанного интерфейса -- **Собиралось и тестировалось только на Linux** — сборки под macOS/Windows не проверялись; SMB-фича на них принципиально не будет работать (см. выше) +- **Собиралось и тестировалось только на Linux** — сборки под macOS/Windows не проверялись (SMB теперь на чистом Rust и в теории кросс-платформенна, но не тестировалась вне Linux) - **Opus не декодируется** (см. раздел «Форматы») - **Плейлисты не хранят диапазоны cue-треков** — добавление cue-трека в плейлист сохраняет файл целиком, а не конкретный виртуальный трек -- Подключение к SMB — блокирующий вызов в UI-потоке; интерфейс на секунду-другую подвиснет во время операции (поиск радиостанций и проверка новых релизов уже работают в фоне и не блокируют) +- Подключение к SMB, поиск радиостанций и проверка новых релизов — все работают в фоне и не блокируют интерфейс - Мульти-файловые cue-листы (редкость) поддерживаются только по первому `FILE` в листе diff --git a/crates/library/Cargo.toml b/crates/library/Cargo.toml index f2d3484..c475ccd 100644 --- a/crates/library/Cargo.toml +++ b/crates/library/Cargo.toml @@ -14,6 +14,7 @@ nucleo-matcher = "0.3" ureq = { version = "2", features = ["json"] } chrono = { version = "0.4", default-features = false, features = ["clock", "serde"] } textfix = { path = "../textfix" } +smbfs = { path = "../smbfs" } [dev-dependencies] tempfile = "3" diff --git a/crates/library/src/lib.rs b/crates/library/src/lib.rs index 0712ebe..e75dfca 100644 --- a/crates/library/src/lib.rs +++ b/crates/library/src/lib.rs @@ -4,7 +4,7 @@ pub mod model; pub mod new_releases; pub mod playlist; pub mod radio; -mod scanner; +pub mod scanner; mod search; pub mod settings; pub mod smb; @@ -41,6 +41,22 @@ impl Library { /// не удваивало треки. pub fn add_root(&mut self, root: &Path) -> anyhow::Result { let new_tracks = scanner::scan(root)?; + Ok(self.merge_tracks(new_tracks)) + } + + /// То же самое, что `add_root`, но для SMB-шары (нужно, чтобы `smbfs::connect` для этой + /// server+share уже был вызван ранее). Свой Rust SMB-клиент, без системных зависимостей + /// вроде gio/gvfs. + pub fn add_smb_root(&mut self, server: &str, share: &str, subpath: &str) -> anyhow::Result { + let new_tracks = scanner::scan_smb(server, share, subpath)?; + Ok(self.merge_tracks(new_tracks)) + } + + /// Добавляет треки, не дублируя уже имеющиеся (по паре путь+cue_start — актуально и для + /// обычных файлов, и для cue-треков, и для SMB-путей, поскольку все они хранятся в + /// одном и том же Track.path). Публичный — используется и когда сканирование SMB-шары + /// делается в фоновом потоке (сеть), а слияние с уже загруженной библиотекой — на UI-потоке. + pub fn merge_tracks(&mut self, new_tracks: Vec) -> usize { let existing: std::collections::HashSet<(PathBuf, Option)> = self .tracks .iter() @@ -54,7 +70,7 @@ impl Library { added += 1; } } - Ok(added) + added } /// Полностью очищает библиотеку (не трогает файлы на диске — только текущий сеанс). diff --git a/crates/library/src/scanner.rs b/crates/library/src/scanner.rs index c57368a..0a48dd6 100644 --- a/crates/library/src/scanner.rs +++ b/crates/library/src/scanner.rs @@ -11,6 +11,27 @@ use crate::cue; use crate::model::{is_audio_file, is_cue_file, Track}; use textfix::fix_mojibake; +/// Рекурсивно обходит SMB-шару (уже должно быть подключено через `smbfs::connect`) и строит +/// те же `Track`, что и `scan()` для локальной папки — просто источник файлов другой. +/// `subpath` — путь внутри шары, с которого начинать (пусто — весь корень шары). +pub fn scan_smb(server: &str, share: &str, subpath: &str) -> anyhow::Result> { + let files = smbfs::list_dir_recursive(server, share, subpath)?; + let mut tracks = Vec::new(); + for rel_path in files { + let pseudo_path = smbfs::make_smb_path(server, share, &rel_path); + if !is_audio_file(&pseudo_path) { + continue; + } + let Ok(reader) = smbfs::open_read(server, share, &rel_path) else { + continue; // не удалось открыть конкретный файл — пропускаем, не валим весь скан + }; + if let Ok(track) = read_track_smb(&pseudo_path, reader) { + tracks.push(track); + } + } + Ok(tracks) +} + /// Рекурсивно обходит `root` и все вложенные папки. Сначала обрабатывает `.cue`-листы — /// каждый превращается в несколько "виртуальных" треков (диапазонов внутри одного /// физического файла); файл, на который ссылается успешно распарсенный cue, после этого @@ -99,10 +120,23 @@ pub fn scan(root: &Path) -> anyhow::Result> { Ok(tracks) } -/// Читает теги одного файла в модель Track. Отсутствующие поля заполняются +/// Читает теги одного локального файла в модель Track. Отсутствующие поля заполняются /// разумными значениями по умолчанию (имя файла как title, "Unknown Artist"/"Unknown Album"). pub fn read_track(path: &Path) -> anyhow::Result { let tagged = Probe::open(path)?.read()?; + track_from_tagged(path, &tagged) +} + +/// То же самое, но для файла на SMB-шаре — через уже открытый `smbfs::SmbFile` (Read+Seek), +/// а не через `std::fs::File`. `pseudo_path` — это `smb://server/share/...`, используется +/// только для fallback-имени и как итоговый Track.path (реальное чтение уже произошло через +/// переданный reader). +pub fn read_track_smb(pseudo_path: &Path, reader: smbfs::SmbFile) -> anyhow::Result { + let tagged = Probe::new(reader).guess_file_type()?.read()?; + track_from_tagged(pseudo_path, &tagged) +} + +fn track_from_tagged(path: &Path, tagged: &lofty::file::TaggedFile) -> anyhow::Result { let properties = tagged.properties(); let duration = Some(properties.duration()).filter(|d| *d != Duration::ZERO); diff --git a/crates/library/src/smb.rs b/crates/library/src/smb.rs index bd66f7d..0ffd42e 100644 --- a/crates/library/src/smb.rs +++ b/crates/library/src/smb.rs @@ -46,16 +46,27 @@ fn find_mount_path(base: &Path, server: &str, share: &str) -> anyhow::Result) -> String { + let server = server.trim().trim_matches('/'); + let share = share.trim().trim_matches('/'); + match username { + Some(u) => format!("smb://{u}@{server}/{share}"), + None => format!("smb://{server}/{share}"), + } +} + pub fn mount_share( server: &str, share: &str, username: Option<&str>, password: Option<&str>, ) -> anyhow::Result { - let url = match username { - Some(u) => format!("smb://{u}@{server}/{share}"), - None => format!("smb://{server}/{share}"), - }; + let server = server.trim().trim_matches('/'); + let share = share.trim().trim_matches('/'); + let url = build_smb_url(server, share, username); let mut child = Command::new("gio") .arg("mount") @@ -152,6 +163,32 @@ pub fn unmount_share(server: &str, share: &str) -> anyhow::Result<()> { mod tests { use super::*; + #[test] + fn strips_trailing_slash_from_share() { + // Именно этот случай вызывал "Invalid argument" у gio, хотя Dolphin(KIO) с тем же + // адресом работал нормально — gio строже разбирает завершающий слэш после шары. + assert_eq!( + build_smb_url("192.168.100.61", "media/", Some("guest")), + "smb://guest@192.168.100.61/media" + ); + } + + #[test] + fn strips_leading_and_trailing_whitespace_and_slashes() { + assert_eq!( + build_smb_url(" 192.168.100.61 ", "/media/", Some("guest")), + "smb://guest@192.168.100.61/media" + ); + } + + #[test] + fn anonymous_url_has_no_username_prefix() { + assert_eq!( + build_smb_url("192.168.100.61", "media", None), + "smb://192.168.100.61/media" + ); + } + #[test] fn finds_matching_mount_dir_by_server_and_share() { let dir = tempfile::tempdir().unwrap(); diff --git a/crates/player/Cargo.toml b/crates/player/Cargo.toml index 91e88f1..aa11de5 100644 --- a/crates/player/Cargo.toml +++ b/crates/player/Cargo.toml @@ -14,6 +14,7 @@ ringbuf = "0.3" ureq = "2" realfft = "3" textfix = { path = "../textfix" } +smbfs = { path = "../smbfs" } [dev-dependencies] encoding_rs = "0.8" diff --git a/crates/player/src/decode.rs b/crates/player/src/decode.rs index 971bc70..77d3fb1 100644 --- a/crates/player/src/decode.rs +++ b/crates/player/src/decode.rs @@ -1,5 +1,5 @@ use std::fs::File; -use std::io::Read; +use std::io::{Read, Seek, SeekFrom}; use std::path::Path; use std::sync::mpsc::Sender; use std::time::Duration; @@ -7,7 +7,7 @@ use std::time::Duration; use symphonia::core::audio::{SampleBuffer, SignalSpec}; use symphonia::core::codecs::{Decoder, DecoderOptions}; use symphonia::core::formats::{FormatOptions, FormatReader, SeekMode, SeekTo}; -use symphonia::core::io::{MediaSourceStream, ReadOnlySource}; +use symphonia::core::io::{MediaSource, MediaSourceStream, ReadOnlySource}; use symphonia::core::meta::MetadataOptions; use symphonia::core::probe::Hint; use symphonia::core::units::Time; @@ -15,6 +15,46 @@ use symphonia::core::units::Time; use crate::types::{PlayerEvent, TrackInfo}; use textfix::fix_mojibake; +/// Оборачивает `smbfs::SmbFile` (Read+Seek) в symphonia-совместимый `MediaSource` — +/// позволяет декодировать аудио прямо с SMB-шары точно так же, как с локального файла, +/// без единого системного вызова вроде gio/gvfs. +struct SmbMediaSource { + inner: smbfs::SmbFile, +} + +impl Read for SmbMediaSource { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + self.inner.read(buf) + } +} + +impl Seek for SmbMediaSource { + fn seek(&mut self, pos: SeekFrom) -> std::io::Result { + self.inner.seek(pos) + } +} + +impl MediaSource for SmbMediaSource { + fn is_seekable(&self) -> bool { + true // SMB read всегда по смещению — "перемотка" ничего не стоит + } + + fn byte_len(&self) -> Option { + Some(self.inner.len()) + } +} + +/// Строит symphonia-источник (Box) для локального файла или SMB-пути — +/// единая точка входа, дальше `open_track`/`read_tags` работают одинаково для обоих случаев. +fn open_media_source(path: &Path) -> anyhow::Result> { + if let Some(loc) = smbfs::parse_smb_path(path) { + let smb_file = smbfs::open_read(&loc.server, &loc.share, &loc.path)?; + Ok(Box::new(SmbMediaSource { inner: smb_file })) + } else { + Ok(Box::new(File::open(path)?)) + } +} + pub struct DecodedTrack { pub format: Box, pub decoder: Box, @@ -68,8 +108,8 @@ fn build_decoded(mss: MediaSourceStream, hint: Hint) -> anyhow::Result anyhow::Result { - let file = File::open(path)?; - let mss = MediaSourceStream::new(Box::new(file), Default::default()); + let source = open_media_source(path)?; + let mss = MediaSourceStream::new(source, Default::default()); let mut hint = Hint::new(); if let Some(ext) = path.extension().and_then(|e| e.to_str()) { @@ -255,8 +295,8 @@ pub fn next_packet_samples(track: &mut DecodedTrack) -> anyhow::Result anyhow::Result { use symphonia::core::meta::StandardTagKey; - let file = File::open(path)?; - let mss = MediaSourceStream::new(Box::new(file), Default::default()); + let source = open_media_source(path)?; + let mss = MediaSourceStream::new(source, Default::default()); let mut hint = Hint::new(); if let Some(ext) = path.extension().and_then(|e| e.to_str()) { hint.with_extension(ext); @@ -347,6 +387,34 @@ mod tests { assert_eq!(parse_stream_title(meta), None); } + /// Проверяет, что `open_track`/`next_packet_samples` реально декодируют аудио, читая его + /// байты со настоящей SMB-шары (через smbfs — чистый Rust клиент, без gio/gvfs), а не + /// только что теги читаются. Нужен локальный Samba-сервер с гостевой шарой `test`, + /// содержащей `Artist/Album/01.mp3` — поднимается вручную перед прогоном. Запускать + /// явно: `cargo test -- --ignored`. + #[test] + #[ignore] + fn open_track_decodes_real_audio_over_smb() { + smbfs::connect("127.0.0.1", "test", None, None) + .expect("connect failed — is the local samba test server running?"); + + let smb_path = smbfs::make_smb_path("127.0.0.1", "test", "Artist/Album/01.mp3"); + let mut track = open_track(&smb_path).expect("open_track over SMB failed"); + + let mut total_samples = 0usize; + let mut packets = 0usize; + while let Some(samples) = next_packet_samples(&mut track).expect("decode failed") { + total_samples += samples.len(); + packets += 1; + if packets >= 5 { + break; // достаточно для проверки — не декодируем весь файл + } + } + assert!(packets > 0, "should have decoded at least one packet"); + assert!(total_samples > 0, "should have produced actual audio samples"); + println!("decoded {packets} packets, {total_samples} samples total, over SMB"); + } + /// Регрессионный тест на реальный баг: кракозябры чинились в библиотеке (library::scanner), /// но не в TrackInfo, который показывается в "Now Playing" при реальном воспроизведении — /// потому что read_tags жила отдельно и не применяла fix_mojibake. Воспроизводит точный diff --git a/crates/smbfs/Cargo.toml b/crates/smbfs/Cargo.toml new file mode 100644 index 0000000..2ae941a --- /dev/null +++ b/crates/smbfs/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "smbfs" +version.workspace = true +edition = "2021" + +[dependencies] +smb = "0.11" +smb-fscc = "0.11" +tokio = { version = "1", features = ["rt-multi-thread", "macros", "sync"] } +futures-util = "0.3" +anyhow = "1" diff --git a/crates/smbfs/src/lib.rs b/crates/smbfs/src/lib.rs new file mode 100644 index 0000000..7d5b27e --- /dev/null +++ b/crates/smbfs/src/lib.rs @@ -0,0 +1,319 @@ +//! Чистый Rust SMB-клиент (без системных зависимостей вроде gio/gvfs) — используется и +//! `library` (для сканирования шары), и `player` (для чтения аудио при воспроизведении). +//! Общается с сетью через крейт `smb` (async, поверх tokio), но наружу отдаёт синхронный +//! blocking-friendly интерфейс, поскольку остальной nsm — синхронный/поточный. +//! +//! Пути к файлам на SMB представляются как "псевдо-пути" вида `smb://server/share/path` — +//! это позволяет переиспользовать Track.path/QueueItem.path (обычный PathBuf) везде в +//! проекте без изменения модели данных: `player::decode` и `library::scanner` сами проверяют, +//! является ли путь таким псевдо-путём, и если да — работают через этот модуль вместо +//! `std::fs::File`. + +use std::collections::HashMap; +use std::io::{self, Read, Seek, SeekFrom}; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex, OnceLock}; + +use futures_util::StreamExt; +use smb::resource::{Directory, FileCreateArgs}; +use smb::{Client, ClientConfig, UncPath}; +use smb_fscc::{FileAccessMask, FileIdBothDirectoryInformation, FileStandardInformation}; +use tokio::runtime::Runtime; + +const SCHEME: &str = "smb://"; + +/// Расположение файла/папки на SMB-шаре: сервер, имя шары, относительный путь внутри неё. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +pub struct SmbLocation { + pub server: String, + pub share: String, + /// Путь внутри шары, без ведущего слэша. Пусто для корня шары. + pub path: String, +} + +/// true, если это наш "псевдо-путь" вида smb://server/share/path. +pub fn is_smb_path(path: &Path) -> bool { + path.to_str().map(|s| s.starts_with(SCHEME)).unwrap_or(false) +} + +pub fn make_smb_path(server: &str, share: &str, relative: &str) -> PathBuf { + let relative = relative.trim_start_matches('/'); + if relative.is_empty() { + PathBuf::from(format!("{SCHEME}{server}/{share}")) + } else { + PathBuf::from(format!("{SCHEME}{server}/{share}/{relative}")) + } +} + +pub fn parse_smb_path(path: &Path) -> Option { + let s = path.to_str()?; + let rest = s.strip_prefix(SCHEME)?; + let mut parts = rest.splitn(3, '/'); + let server = parts.next()?.to_string(); + let share = parts.next()?.to_string(); + let path = parts.next().unwrap_or("").to_string(); + if server.is_empty() || share.is_empty() { + return None; + } + Some(SmbLocation { server, share, path }) +} + +// ---- Общий tokio runtime для всех blocking-вызовов ---- + +fn runtime() -> &'static Runtime { + static RT: OnceLock = OnceLock::new(); + RT.get_or_init(|| { + tokio::runtime::Builder::new_multi_thread() + .worker_threads(2) + .enable_all() + .build() + .expect("failed to start tokio runtime for smbfs") + }) +} + +// ---- Реестр подключений: (server, share) -> уже аутентифицированный клиент ---- + +struct Connection { + client: Client, + share_root: UncPath, +} + +fn registry() -> &'static Mutex>> { + static REG: OnceLock>>> = OnceLock::new(); + REG.get_or_init(|| Mutex::new(HashMap::new())) +} + +/// Подключается к SMB-шаре (или, если уже подключены к этой же server+share — переиспользует +/// существующее соединение) и регистрирует её в реестре. После этого `list_dir_recursive`/ +/// `open_read` для этой server+share будут работать без повторного логина. +pub fn connect( + server: &str, + share: &str, + username: Option<&str>, + password: Option<&str>, +) -> anyhow::Result<()> { + let key = (server.to_string(), share.to_string()); + if registry().lock().unwrap().contains_key(&key) { + return Ok(()); // уже подключены + } + + // Анонимный вход — явно логин "guest", как и в gio-варианте: часть серверов не + // принимает буквально пустой логин, ожидая общепринятое имя гостевого пользователя. + let user = username.unwrap_or("guest").to_string(); + let pass = password.unwrap_or("").to_string(); + let server_owned = server.to_string(); + let share_owned = share.to_string(); + + let connection = runtime().block_on(async move { + let mut config = ClientConfig::default(); + // Без этого флага гостевые/анонимные сессии падают с "signing is required" — сервер + // требует подписи сообщений, а анонимной сессии нечем их подписать. + config.connection.allow_unsigned_guest_access = true; + + let client = Client::new(config); + let share_root = UncPath::new(&server_owned)?.with_share(&share_owned)?; + client.share_connect(&share_root, &user, pass).await?; + anyhow::Ok(Connection { client, share_root }) + })?; + + registry().lock().unwrap().insert(key, Arc::new(connection)); + Ok(()) +} + +pub fn is_connected(server: &str, share: &str) -> bool { + registry() + .lock() + .unwrap() + .contains_key(&(server.to_string(), share.to_string())) +} + +fn get_connection(server: &str, share: &str) -> anyhow::Result> { + registry() + .lock() + .unwrap() + .get(&(server.to_string(), share.to_string())) + .cloned() + .ok_or_else(|| { + anyhow::anyhow!("not connected to smb://{server}/{share} — call connect() first") + }) +} + +/// Рекурсивно обходит шару (или подпапку внутри неё) и возвращает относительные пути всех +/// найденных файлов (не папок). +pub fn list_dir_recursive(server: &str, share: &str, subpath: &str) -> anyhow::Result> { + let conn = get_connection(server, share)?; + runtime().block_on(list_recursive_async(conn, subpath.to_string())) +} + +fn list_recursive_async( + conn: Arc, + subpath: String, +) -> std::pin::Pin>> + Send>> { + Box::pin(async move { + let dir_path = if subpath.is_empty() { + conn.share_root.clone() + } else { + conn.share_root.clone().with_path(&subpath) + }; + let args = + FileCreateArgs::make_open_existing(FileAccessMask::new().with_generic_read(true)); + let resource = conn.client.create_file(&dir_path, &args).await?; + let dir = Arc::new(resource.unwrap_dir()); + + let mut stream = Directory::query::(&dir, "*").await?; + let mut results = Vec::new(); + let mut subdirs = Vec::new(); + + while let Some(entry) = stream.next().await { + let entry = entry?; + let name = entry.file_name.to_string(); + if name == "." || name == ".." { + continue; + } + let rel = if subpath.is_empty() { + name.clone() + } else { + format!("{subpath}/{name}") + }; + if entry.file_attributes.directory() { + subdirs.push(rel); + } else { + results.push(rel); + } + } + drop(stream); + + for sub in subdirs { + let mut nested = list_recursive_async(conn.clone(), sub).await?; + results.append(&mut nested); + } + + Ok(results) + }) +} + +/// Открывает файл на шаре для чтения. Требует, чтобы `connect()` для этой server+share уже +/// был вызван ранее. +pub fn open_read(server: &str, share: &str, subpath: &str) -> anyhow::Result { + let conn = get_connection(server, share)?; + let subpath = subpath.to_string(); + + let (file, size) = runtime().block_on(async move { + let file_path = conn.share_root.clone().with_path(&subpath); + let args = + FileCreateArgs::make_open_existing(FileAccessMask::new().with_generic_read(true)); + let resource = conn.client.create_file(&file_path, &args).await?; + let file = resource.unwrap_file(); + let info: FileStandardInformation = file.query_info().await?; + anyhow::Ok((file, info.end_of_file)) + })?; + + Ok(SmbFile { file: Some(file), pos: 0, size }) +} + +/// Read+Seek обёртка над файлом на SMB-шаре — читается блоками через async API крейта `smb`, +/// но "перемотка" не требует реального сетевого запроса: SMB read всегда идёт по смещению, +/// так что seek — это просто обновление счётчика позиции на нашей стороне. +pub struct SmbFile { + file: Option, + pos: u64, + size: u64, +} + +impl SmbFile { + /// Полный размер файла в байтах — известен сразу после открытия (запрошен один раз + /// при `open_read`, не требует отдельного сетевого похода). + pub fn len(&self) -> u64 { + self.size + } + + pub fn is_empty(&self) -> bool { + self.size == 0 + } +} + +impl Read for SmbFile { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + let file = self + .file + .as_ref() + .ok_or_else(|| io::Error::new(io::ErrorKind::Other, "smb file already closed"))?; + let n = runtime() + .block_on(file.read_block(buf, self.pos, None, false)) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; + self.pos += n as u64; + Ok(n) + } +} + +impl Drop for SmbFile { + fn drop(&mut self) { + // У ResourceHandle крейта `smb` свой Drop, который асинхронно спавнит задачу + // закрытия хендла на сервере (tokio::spawn) — а это паникует с "no reactor running", + // если в текущем потоке в момент дропа нет активного tokio-контекста (у нас его нет, + // мы работаем через block_on только на время конкретных вызовов). Явно входим в + // контекст рантайма перед тем, как дать File реально задропаться. + if let Some(file) = self.file.take() { + let _guard = runtime().enter(); + drop(file); + } + } +} + +impl Seek for SmbFile { + fn seek(&mut self, pos: SeekFrom) -> io::Result { + let new_pos = match pos { + SeekFrom::Start(p) => p as i64, + SeekFrom::End(p) => self.size as i64 + p, + SeekFrom::Current(p) => self.pos as i64 + p, + }; + if new_pos < 0 { + return Err(io::Error::new( + io::ErrorKind::InvalidInput, + "seek to negative position", + )); + } + self.pos = new_pos as u64; + Ok(self.pos) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parses_smb_pseudo_path() { + let loc = parse_smb_path(Path::new("smb://192.168.1.1/media/Artist/song.mp3")).unwrap(); + assert_eq!(loc.server, "192.168.1.1"); + assert_eq!(loc.share, "media"); + assert_eq!(loc.path, "Artist/song.mp3"); + } + + #[test] + fn parses_smb_path_at_share_root() { + let loc = parse_smb_path(Path::new("smb://server/share")).unwrap(); + assert_eq!(loc.path, ""); + } + + #[test] + fn rejects_non_smb_paths() { + assert!(parse_smb_path(Path::new("/home/user/music/song.mp3")).is_none()); + } + + #[test] + fn round_trips_make_and_parse() { + let p = make_smb_path("192.168.1.1", "media", "Artist/Album/song.mp3"); + assert_eq!(p.to_str().unwrap(), "smb://192.168.1.1/media/Artist/Album/song.mp3"); + let loc = parse_smb_path(&p).unwrap(); + assert_eq!(loc.server, "192.168.1.1"); + assert_eq!(loc.share, "media"); + assert_eq!(loc.path, "Artist/Album/song.mp3"); + } + + #[test] + fn detects_smb_paths_correctly() { + assert!(is_smb_path(Path::new("smb://server/share/file.mp3"))); + assert!(!is_smb_path(Path::new("/local/path/file.mp3"))); + } +} diff --git a/crates/tui/Cargo.toml b/crates/tui/Cargo.toml index 413e44b..242d01d 100644 --- a/crates/tui/Cargo.toml +++ b/crates/tui/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true anyhow.workspace = true player = { path = "../player" } library = { path = "../library" } +smbfs = { path = "../smbfs" } ratatui = "0.28" crossterm = "0.28" ratatui-image = { version = "1", default-features = false, features = ["crossterm", "rustix"] } diff --git a/crates/tui/src/app.rs b/crates/tui/src/app.rs index 8e55bc5..e15824d 100644 --- a/crates/tui/src/app.rs +++ b/crates/tui/src/app.rs @@ -161,6 +161,20 @@ impl RightPanelView { } } +/// Обновляет экспоненциальное скользящее среднее басовой энергии на один новый отсчёт. +/// `alpha` — насколько быстро среднее "забывает" старые значения (0..1; больше = быстрее). +pub fn update_bass_ema(ema: f32, current: f32, alpha: f32) -> f32 { + ema * (1.0 - alpha) + current * alpha +} + +/// "Удар баса" — текущее значение заметно выше недавнего среднего, а не просто выше +/// произвольной фиксированной константы. Так реакция подстраивается под конкретный трек: +/// и тихая акустика, и громкий метал будут давать срабатывания на СВОИХ собственных ударах, +/// а не либо никогда, либо постоянно. +pub fn is_bass_hit(current: f32, ema: f32) -> bool { + current > ema * 1.3 + 0.02 +} + pub fn protocol_to_str(p: ProtocolType) -> &'static str { match p { ProtocolType::Halfblocks => "halfblocks", @@ -232,6 +246,10 @@ pub struct App { pub station_query: String, pub station_search_in_progress: bool, station_search_rx: Option>>>, + /// Фоновое подключение+сканирование SMB — сеть может быть медленной (или зависнуть), + /// не должна морозить весь интерфейс. Результат: (server, share, треки или ошибка). + smb_connect_rx: Option>)>>, + pub smb_connecting: bool, // --- Now playing / transport --- pub now_playing: Option, @@ -268,6 +286,11 @@ pub struct App { pub right_panel_view: RightPanelView, /// Текущие значения полос спектра (0..1), обновляются событиями от плеера. pub spectrum: Vec, + /// Скользящее среднее басовой энергии — для адаптивного "детектора удара баса" + /// (Noise Marines реагируют на ВСПЛЕСК относительно недавнего среднего, а не на + /// фиксированный порог, который у одних треков никогда не пробивается, а у других + /// пробит всегда). + pub bass_ema: f32, /// Если true — выбранный right_panel_view занимает весь экран. pub fullscreen: bool, @@ -356,6 +379,8 @@ impl App { station_query: String::new(), station_search_in_progress: false, station_search_rx: None, + smb_connect_rx: None, + smb_connecting: false, now_playing: None, speed: Speed::default(), shuffle: false, @@ -378,6 +403,7 @@ impl App { last_cover_bytes: None, right_panel_view: RightPanelView::Cover, spectrum: vec![0.0; player::SPECTRUM_BANDS], + bass_ema: 0.0, fullscreen: false, status_message: format!("nsm — loaded from {}", root.display()), }; @@ -455,6 +481,9 @@ impl App { /// "guest" — gvfs почти никогда не подключается по-настоящему без логина вообще, /// даже если шара разрешает гостевой доступ (`guest ok = yes` в конфиге Samba), /// а "guest" — общепринятое соглашение, которое такие шары понимают. + /// Запускает подключение+сканирование SMB-шары в фоновом потоке — сеть (или сам сервер) + /// может быть медленной или зависнуть, это не должно морозить весь интерфейс (как раньше, + /// когда это было синхронным вызовом прямо в обработчике клавиши). pub fn connect_smb_share(&mut self) { let server = self.smb_server.trim().to_string(); let share = self.smb_share.trim().to_string(); @@ -462,7 +491,7 @@ impl App { let password = (!self.smb_password.is_empty()).then(|| self.smb_password.clone()); let (username, password, anonymous) = if username_input.is_empty() { - (Some("guest".to_string()), None, true) + (None, None, true) // smbfs::connect сама подставляет "guest" для анонимного входа } else { (Some(username_input.to_string()), password, false) }; @@ -478,30 +507,47 @@ impl App { } else { format!("Connecting to smb://{server}/{share}…") }; + self.smb_connecting = true; + self.clear_smb_inputs(); - let mount_path = if let Some(path) = library::smb::find_existing_mount(&server, &share) { - Ok(path) - } else { - library::smb::mount_share(&server, &share, username.as_deref(), password.as_deref()) - .map(|info| info.mount_path) + let (tx, rx) = std::sync::mpsc::channel(); + self.smb_connect_rx = Some(rx); + std::thread::spawn(move || { + // Свой Rust SMB-клиент (smbfs) — не зависит от системного gio/gvfs и его багов. + // connect() сама переиспользует уже открытое соединение, если оно уже было для + // этой server+share (не переспрашивает пароль каждый раз). + let result = smbfs::connect(&server, &share, username.as_deref(), password.as_deref()) + .and_then(|()| library::scanner::scan_smb(&server, &share, "")); + let _ = tx.send((server, share, result)); + }); + } + + /// Опрашивает фоновое подключение SMB — вызывать каждый тик, как и остальные фоновые задачи. + pub fn poll_smb_connect(&mut self) { + let Some(rx) = self.smb_connect_rx.as_ref() else { + return; }; - - match mount_path { - Ok(path) => match self.library.add_root(&path) { - Ok(added) => { - self.status_message = - format!("Connected to \\\\{server}\\{share} — added {added} track(s)"); - self.rebuild_library_view(); - } - Err(e) => { - self.status_message = format!("Mounted, but scanning failed: {e}"); - } - }, - Err(e) => { + match rx.try_recv() { + Ok((server, share, Ok(tracks))) => { + let added = self.library.merge_tracks(tracks); + self.status_message = format!("Connected to \\\\{server}\\{share} — added {added} track(s)"); + self.rebuild_library_view(); + self.smb_connecting = false; + self.smb_connect_rx = None; + } + Ok((_, _, Err(e))) => { self.status_message = format!("SMB connect failed: {e}"); + self.smb_connecting = false; + self.smb_connect_rx = None; + } + Err(std::sync::mpsc::TryRecvError::Empty) => { + // ещё не готово — подождём следующего тика + } + Err(std::sync::mpsc::TryRecvError::Disconnected) => { + self.smb_connecting = false; + self.smb_connect_rx = None; } } - self.clear_smb_inputs(); } fn clear_smb_inputs(&mut self) { @@ -594,6 +640,14 @@ impl App { } } Spectrum(bands) => { + // Скользящее среднее по нескольким нижним полосам — та же "басовая энергия", + // что использует детектор удара в draw_noise_marines (см. там комментарий, + // почему усредняем несколько полос, а не берём одну). + let n = bands.len().min(7); + if n > 0 { + let bass_now = bands[..n].iter().sum::() / n as f32; + self.bass_ema = update_bass_ema(self.bass_ema, bass_now, 0.15); + } self.spectrum = bands; } QueueFinished => { @@ -1122,3 +1176,44 @@ impl App { self.fullscreen = !self.fullscreen; } } + +#[cfg(test)] +mod tests { + use super::*; + + /// Симулирует несколько "тихих" отсчётов (устанавливая ema), затем один резкий + /// всплеск — детектор должен среагировать на сам всплеск. + #[test] + fn detects_sudden_bass_hit_over_quiet_baseline() { + let mut ema = 0.0; + for _ in 0..20 { + ema = update_bass_ema(ema, 0.05, 0.15); // тихая "гитара" — база устаканивается низко + } + assert!(!is_bass_hit(0.05, ema), "ровный тихий уровень не должен считаться ударом"); + + // резкий удар баса поверх этой тихой базы + assert!(is_bass_hit(0.4, ema), "резкий всплеск над тихой базой должен сработать"); + } + + /// Трек, где бас стабильно громкий (тяжёлый метал) — после того, как ema устаканится + /// на этом уровне, ровный громкий бас НЕ должен постоянно триггерить "удар" на каждом + /// кадре — иначе десантники будут просто дёргаться без остановки, а не танцевать в такт. + #[test] + fn steady_loud_bass_does_not_trigger_constantly_once_settled() { + let mut ema = 0.0; + for _ in 0..50 { + ema = update_bass_ema(ema, 0.5, 0.15); + } + assert!( + !is_bass_hit(0.5, ema), + "устаканившийся ровный громкий бас не должен считаться 'ударом' каждый кадр" + ); + } + + #[test] + fn ema_moves_toward_target_but_not_instantly() { + let ema = update_bass_ema(0.0, 1.0, 0.15); + assert!(ema > 0.0 && ema < 1.0, "ema={ema}"); + assert!((ema - 0.15).abs() < 0.001); + } +} diff --git a/crates/tui/src/lib.rs b/crates/tui/src/lib.rs index 5b2dd6e..7bb03a6 100644 --- a/crates/tui/src/lib.rs +++ b/crates/tui/src/lib.rs @@ -80,6 +80,7 @@ fn event_loop( app.drain_player_events(); app.poll_new_releases(); app.poll_station_search(); + app.poll_smb_connect(); last_tick = Instant::now(); } diff --git a/crates/tui/src/ui.rs b/crates/tui/src/ui.rs index d2e0014..5197ba1 100644 --- a/crates/tui/src/ui.rs +++ b/crates/tui/src/ui.rs @@ -543,7 +543,8 @@ fn draw_now_playing(frame: &mut Frame, app: &mut App, area: Rect) { .constraints([ Constraint::Length(12), // обложка + инфо Constraint::Length(1), // транспорт (speed/shuffle/repeat) - Constraint::Min(0), // эквалайзер + Constraint::Percentage(50), // эквалайзер + Constraint::Percentage(50), // танцующие Шумовые Десантники ]) .split(area); @@ -580,6 +581,7 @@ fn draw_now_playing(frame: &mut Frame, app: &mut App, area: Rect) { draw_track_info(frame, app, top[1]); draw_transport(frame, app, chunks[1]); draw_eq(frame, app, chunks[2]); + draw_noise_marines(frame, app, chunks[3]); } fn draw_track_info(frame: &mut Frame, app: &App, area: Rect) { @@ -643,6 +645,82 @@ fn draw_transport(frame: &mut Frame, app: &App, area: Rect) { frame.render_widget(Paragraph::new(line), area); } +/// Пасхалка: Шумовые Десантники (Noise Marines) из Warhammer 40000 — культовая "музыкальная" +/// фракция Хаоса, поклоняющаяся Слаанешу через звук и звуковое оружие (Doom Siren/Blastmaster). +/// Поза синхронизирована с басом ТЕКУЩЕГО трека через уже существующий спектроанализатор — +/// буквально пляшут под музыку, а не просто крутят произвольную анимацию. +fn draw_noise_marines(frame: &mut Frame, app: &App, area: Rect) { + let block = Block::default() + .borders(Borders::ALL) + .title(" Noise Marines "); + let inner = block.inner(area); + frame.render_widget(block, area); + + // Басовая энергия усредняется по нескольким нижним полосам спектра, а не берётся из + // одной-единственной (полоса 0 при размере FFT-окна 1024 — это всего один узкий бин + // ~43Гц, слишком шумно и не отражает реальный бас трека). "Удар баса" детектируется + // относительно недавнего среднего (bass_ema), а не фиксированным порогом — так реакция + // подстраивается под конкретный трек, а не пробивается либо всегда, либо никогда. + let n = app.spectrum.len().min(7); + let bass_now = if n > 0 { + app.spectrum[..n].iter().sum::() / n as f32 + } else { + 0.0 + }; + let pose_a = crate::app::is_bass_hit(bass_now, app.bass_ema); + + // Тело и рог/сирена — в одной строке за раз, как в изначальной, подтверждённо рабочей + // версии (была проверена вживую на реальном терминале). Отдельные "гитарные" аксессуары + // с другими символами не прижились — рассинхронизировали ширину колонок в разных + // шрифтах/терминалах, поэтому вернулись к простому и надёжному. + const FRAME_A: [&str; 9] = [ + " ▄██▄ ", + " ◢█◉◉█◣ ", + " █▀▀▀▀█ ", + "▟████████▙ ╭─◯ ))) ", + " █▓▓▓▓█ ╱ ", + " █▓▓▓▓█ ╱ ", + " ▐▓▓▓▓▌ ╱ ", + " █ █ ", + " ▟█▙▟█▙ ", + ]; + const FRAME_B: [&str; 9] = [ + " ▄██▄ ", + " ◢█◉◉█◣ ", + " █▀▀▀▀█ ", + "▟████████▙ ", + " █▓▓▓▓█ ╲ ", + " █▓▓▓▓█ ╲ ", + " ▐▓▓▓▓▌╭─◯ ))) ", + " ▟█▙ █ ", + " █ ▟█▙ ", + ]; + let sprite = if pose_a { &FRAME_A } else { &FRAME_B }; + + const MARINE_WIDTH: usize = 24; + let count = ((inner.width as usize) / MARINE_WIDTH).clamp(1, 4); + + let lines: Vec = sprite + .iter() + .map(|row| { + let mut s = String::new(); + for _ in 0..count { + let visible_len = row.chars().count(); + s.push_str(row); + s.push_str(&" ".repeat(MARINE_WIDTH.saturating_sub(visible_len))); + } + Line::from(s) + }) + .collect(); + + frame.render_widget( + Paragraph::new(lines) + .style(Style::default().fg(Color::Magenta)) + .alignment(Alignment::Center), + inner, + ); +} + fn draw_eq(frame: &mut Frame, app: &App, area: Rect) { let block = Block::default() .borders(Borders::ALL)