diff options
Diffstat (limited to '')
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | AGENTS.md | 55 | ||||
| -rw-r--r-- | Cargo.lock | 3135 | ||||
| -rw-r--r-- | Cargo.toml | 21 | ||||
| -rw-r--r-- | PLAN.md | 24 | ||||
| -rw-r--r-- | TODO.md | 51 | ||||
| -rw-r--r-- | src/audio.rs | 418 | ||||
| -rw-r--r-- | src/cli.rs | 186 | ||||
| -rw-r--r-- | src/config.rs | 180 | ||||
| -rw-r--r-- | src/daemon.rs | 574 | ||||
| -rw-r--r-- | src/database.rs | 259 | ||||
| -rw-r--r-- | src/main.rs | 18 | ||||
| -rw-r--r-- | src/playback.rs | 131 | ||||
| -rw-r--r-- | src/scrobbler.rs | 75 |
14 files changed, 5128 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore | |||
| @@ -0,0 +1 @@ | |||
| /target | |||
diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..31f1205 --- /dev/null +++ b/AGENTS.md | |||
| @@ -0,0 +1,55 @@ | |||
| 1 | # Agent Instructions for rsplayer | ||
| 2 | |||
| 3 | ## Overview | ||
| 4 | Rust CLI music player daemon for GNU/Linux with shell autocompletion, SQLite library database, Unix socket IPC, and Last.fm/ListenBrainz scrobbling. | ||
| 5 | |||
| 6 | ## Required Verification Commands | ||
| 7 | Run these before completing any task: | ||
| 8 | - Lint: `cargo clippy -- -D warnings` | ||
| 9 | - Format check: `cargo fmt --check` | ||
| 10 | - Test: `cargo test` | ||
| 11 | - Build: `cargo build` | ||
| 12 | |||
| 13 | ## Development Workflow | ||
| 14 | - Use `rsp restart` or `rsplayer restart` to kill any running daemon and start a new one (useful during development) | ||
| 15 | - Daemon runs in background; restart command handles cleanup automatically | ||
| 16 | - After making changes to audio/playback/daemon code, always restart the daemon to test changes | ||
| 17 | |||
| 18 | ## Code Conventions | ||
| 19 | - Use `directories` crate to resolve XDG paths (respect `XDG_CONFIG_HOME` and `XDG_RUNTIME_DIR` environment variables) | ||
| 20 | - Follow standard Rust style (enforced by `cargo fmt`) | ||
| 21 | - Never commit changes unless explicitly requested by the user | ||
| 22 | - Update shell completions when adding/modifying CLI subcommands | ||
| 23 | - Use `rand` crate for shuffle/randomization features (added in v0.9) | ||
| 24 | |||
| 25 | ## Task-Specific Guidelines | ||
| 26 | - **CLI changes**: Update clap subcommands (including `rsp scan` and `rsp settings`) and regenerate shell completion scripts | ||
| 27 | - **Database changes**: Update SQLite schema and `rsp scan` logic | ||
| 28 | - **Playback changes**: Modify daemon audio engine or queue management code | ||
| 29 | - Audio engine uses cpal for output and symphonia for decoding | ||
| 30 | - Queue system implemented in daemon.rs with `Queue` struct | ||
| 31 | - Sample buffering implemented to prevent buffer underruns - uses device default sample rate, not file's sample rate | ||
| 32 | - Pause/resume uses cpal stream `pause()`/`play()` methods | ||
| 33 | - **Scrobbling changes**: Update Last.fm/ListenBrainz client implementation | ||
| 34 | - **Config/Settings changes**: Update config schema, `rsp settings` subcommands, and scan interval/auto-scan logic | ||
| 35 | |||
| 36 | ## Completed Features (Phase 2) | ||
| 37 | - [x] Unix socket daemon IPC | ||
| 38 | - [x] Audio engine with play/pause/stop/volume | ||
| 39 | - [x] Queue system with in-memory tracking and auto-advance | ||
| 40 | - [x] Shuffle mode: `none`, `all`, and `album` (structure ready) implemented | ||
| 41 | - [x] Next/previous track commands with queue navigation | ||
| 42 | - [x] Resume CLI subcommand | ||
| 43 | - [x] Directory playback - queues all audio files in folder recursively | ||
| 44 | - [x] Fixed audio buffer underrun issues by using device default sample rate | ||
| 45 | - [x] Queue display with pager support (`rsplayer queue`) | ||
| 46 | - [x] Auto-play next track when current track ends | ||
| 47 | |||
| 48 | ## Remaining Work | ||
| 49 | - Album shuffle mode (`ShuffleMode::Album`) | ||
| 50 | - Database integration for queue persistence | ||
| 51 | - `rsplayer queue` command for queue management | ||
| 52 | - Scrobbling (Last.fm/ListenBrainz) | ||
| 53 | - Database scanning and schema | ||
| 54 | - Shell completion scripts | ||
| 55 | - Systemd service unit | ||
diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1baedf3 --- /dev/null +++ b/Cargo.lock | |||
| @@ -0,0 +1,3135 @@ | |||
| 1 | # This file is automatically @generated by Cargo. | ||
| 2 | # It is not intended for manual editing. | ||
| 3 | version = 4 | ||
| 4 | |||
| 5 | [[package]] | ||
| 6 | name = "alsa" | ||
| 7 | version = "0.11.0" | ||
| 8 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 9 | checksum = "812947049edcd670a82cd5c73c3661d2e58468577ba8489de58e1a73c04cbd5d" | ||
| 10 | dependencies = [ | ||
| 11 | "alsa-sys", | ||
| 12 | "bitflags 2.11.1", | ||
| 13 | "cfg-if", | ||
| 14 | "libc", | ||
| 15 | ] | ||
| 16 | |||
| 17 | [[package]] | ||
| 18 | name = "alsa-sys" | ||
| 19 | version = "0.4.0" | ||
| 20 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 21 | checksum = "ad7569085a265dd3f607ebecce7458eaab2132a84393534c95b18dcbc3f31e04" | ||
| 22 | dependencies = [ | ||
| 23 | "libc", | ||
| 24 | "pkg-config", | ||
| 25 | ] | ||
| 26 | |||
| 27 | [[package]] | ||
| 28 | name = "android_system_properties" | ||
| 29 | version = "0.1.5" | ||
| 30 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 31 | checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" | ||
| 32 | dependencies = [ | ||
| 33 | "libc", | ||
| 34 | ] | ||
| 35 | |||
| 36 | [[package]] | ||
| 37 | name = "anstream" | ||
| 38 | version = "1.0.0" | ||
| 39 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 40 | checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" | ||
| 41 | dependencies = [ | ||
| 42 | "anstyle", | ||
| 43 | "anstyle-parse", | ||
| 44 | "anstyle-query", | ||
| 45 | "anstyle-wincon", | ||
| 46 | "colorchoice", | ||
| 47 | "is_terminal_polyfill", | ||
| 48 | "utf8parse", | ||
| 49 | ] | ||
| 50 | |||
| 51 | [[package]] | ||
| 52 | name = "anstyle" | ||
| 53 | version = "1.0.14" | ||
| 54 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 55 | checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" | ||
| 56 | |||
| 57 | [[package]] | ||
| 58 | name = "anstyle-parse" | ||
| 59 | version = "1.0.0" | ||
| 60 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 61 | checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" | ||
| 62 | dependencies = [ | ||
| 63 | "utf8parse", | ||
| 64 | ] | ||
| 65 | |||
| 66 | [[package]] | ||
| 67 | name = "anstyle-query" | ||
| 68 | version = "1.1.5" | ||
| 69 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 70 | checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" | ||
| 71 | dependencies = [ | ||
| 72 | "windows-sys 0.61.2", | ||
| 73 | ] | ||
| 74 | |||
| 75 | [[package]] | ||
| 76 | name = "anstyle-wincon" | ||
| 77 | version = "3.0.11" | ||
| 78 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 79 | checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" | ||
| 80 | dependencies = [ | ||
| 81 | "anstyle", | ||
| 82 | "once_cell_polyfill", | ||
| 83 | "windows-sys 0.61.2", | ||
| 84 | ] | ||
| 85 | |||
| 86 | [[package]] | ||
| 87 | name = "anyhow" | ||
| 88 | version = "1.0.102" | ||
| 89 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 90 | checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" | ||
| 91 | |||
| 92 | [[package]] | ||
| 93 | name = "arrayvec" | ||
| 94 | version = "0.7.6" | ||
| 95 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 96 | checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" | ||
| 97 | |||
| 98 | [[package]] | ||
| 99 | name = "atomic-waker" | ||
| 100 | version = "1.1.2" | ||
| 101 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 102 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" | ||
| 103 | |||
| 104 | [[package]] | ||
| 105 | name = "autocfg" | ||
| 106 | version = "1.5.0" | ||
| 107 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 108 | checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" | ||
| 109 | |||
| 110 | [[package]] | ||
| 111 | name = "aws-lc-rs" | ||
| 112 | version = "1.16.3" | ||
| 113 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 114 | checksum = "0ec6fb3fe69024a75fa7e1bfb48aa6cf59706a101658ea01bfd33b2b248a038f" | ||
| 115 | dependencies = [ | ||
| 116 | "aws-lc-sys", | ||
| 117 | "zeroize", | ||
| 118 | ] | ||
| 119 | |||
| 120 | [[package]] | ||
| 121 | name = "aws-lc-sys" | ||
| 122 | version = "0.40.0" | ||
| 123 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 124 | checksum = "f50037ee5e1e41e7b8f9d161680a725bd1626cb6f8c7e901f91f942850852fe7" | ||
| 125 | dependencies = [ | ||
| 126 | "cc", | ||
| 127 | "cmake", | ||
| 128 | "dunce", | ||
| 129 | "fs_extra", | ||
| 130 | ] | ||
| 131 | |||
| 132 | [[package]] | ||
| 133 | name = "base64" | ||
| 134 | version = "0.22.1" | ||
| 135 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 136 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" | ||
| 137 | |||
| 138 | [[package]] | ||
| 139 | name = "bitflags" | ||
| 140 | version = "1.3.2" | ||
| 141 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 142 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" | ||
| 143 | |||
| 144 | [[package]] | ||
| 145 | name = "bitflags" | ||
| 146 | version = "2.11.1" | ||
| 147 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 148 | checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" | ||
| 149 | |||
| 150 | [[package]] | ||
| 151 | name = "block2" | ||
| 152 | version = "0.6.2" | ||
| 153 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 154 | checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" | ||
| 155 | dependencies = [ | ||
| 156 | "objc2", | ||
| 157 | ] | ||
| 158 | |||
| 159 | [[package]] | ||
| 160 | name = "bumpalo" | ||
| 161 | version = "3.20.2" | ||
| 162 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 163 | checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" | ||
| 164 | |||
| 165 | [[package]] | ||
| 166 | name = "bytemuck" | ||
| 167 | version = "1.25.0" | ||
| 168 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 169 | checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" | ||
| 170 | |||
| 171 | [[package]] | ||
| 172 | name = "bytes" | ||
| 173 | version = "1.11.1" | ||
| 174 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 175 | checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" | ||
| 176 | |||
| 177 | [[package]] | ||
| 178 | name = "cc" | ||
| 179 | version = "1.2.61" | ||
| 180 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 181 | checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" | ||
| 182 | dependencies = [ | ||
| 183 | "find-msvc-tools", | ||
| 184 | "jobserver", | ||
| 185 | "libc", | ||
| 186 | "shlex", | ||
| 187 | ] | ||
| 188 | |||
| 189 | [[package]] | ||
| 190 | name = "cesu8" | ||
| 191 | version = "1.1.0" | ||
| 192 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 193 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" | ||
| 194 | |||
| 195 | [[package]] | ||
| 196 | name = "cfg-if" | ||
| 197 | version = "1.0.4" | ||
| 198 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 199 | checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" | ||
| 200 | |||
| 201 | [[package]] | ||
| 202 | name = "cfg_aliases" | ||
| 203 | version = "0.2.1" | ||
| 204 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 205 | checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" | ||
| 206 | |||
| 207 | [[package]] | ||
| 208 | name = "chacha20" | ||
| 209 | version = "0.10.0" | ||
| 210 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 211 | checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" | ||
| 212 | dependencies = [ | ||
| 213 | "cfg-if", | ||
| 214 | "cpufeatures", | ||
| 215 | "rand_core 0.10.1", | ||
| 216 | ] | ||
| 217 | |||
| 218 | [[package]] | ||
| 219 | name = "chrono" | ||
| 220 | version = "0.4.44" | ||
| 221 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 222 | checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" | ||
| 223 | dependencies = [ | ||
| 224 | "iana-time-zone", | ||
| 225 | "js-sys", | ||
| 226 | "num-traits", | ||
| 227 | "wasm-bindgen", | ||
| 228 | "windows-link", | ||
| 229 | ] | ||
| 230 | |||
| 231 | [[package]] | ||
| 232 | name = "clap" | ||
| 233 | version = "4.6.1" | ||
| 234 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 235 | checksum = "1ddb117e43bbf7dacf0a4190fef4d345b9bad68dfc649cb349e7d17d28428e51" | ||
| 236 | dependencies = [ | ||
| 237 | "clap_builder", | ||
| 238 | "clap_derive", | ||
| 239 | ] | ||
| 240 | |||
| 241 | [[package]] | ||
| 242 | name = "clap_builder" | ||
| 243 | version = "4.6.0" | ||
| 244 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 245 | checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" | ||
| 246 | dependencies = [ | ||
| 247 | "anstream", | ||
| 248 | "anstyle", | ||
| 249 | "clap_lex", | ||
| 250 | "strsim", | ||
| 251 | ] | ||
| 252 | |||
| 253 | [[package]] | ||
| 254 | name = "clap_derive" | ||
| 255 | version = "4.6.1" | ||
| 256 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 257 | checksum = "f2ce8604710f6733aa641a2b3731eaa1e8b3d9973d5e3565da11800813f997a9" | ||
| 258 | dependencies = [ | ||
| 259 | "heck", | ||
| 260 | "proc-macro2", | ||
| 261 | "quote", | ||
| 262 | "syn", | ||
| 263 | ] | ||
| 264 | |||
| 265 | [[package]] | ||
| 266 | name = "clap_lex" | ||
| 267 | version = "1.1.0" | ||
| 268 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 269 | checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" | ||
| 270 | |||
| 271 | [[package]] | ||
| 272 | name = "cmake" | ||
| 273 | version = "0.1.58" | ||
| 274 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 275 | checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678" | ||
| 276 | dependencies = [ | ||
| 277 | "cc", | ||
| 278 | ] | ||
| 279 | |||
| 280 | [[package]] | ||
| 281 | name = "colorchoice" | ||
| 282 | version = "1.0.5" | ||
| 283 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 284 | checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" | ||
| 285 | |||
| 286 | [[package]] | ||
| 287 | name = "combine" | ||
| 288 | version = "4.6.7" | ||
| 289 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 290 | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" | ||
| 291 | dependencies = [ | ||
| 292 | "bytes", | ||
| 293 | "memchr", | ||
| 294 | ] | ||
| 295 | |||
| 296 | [[package]] | ||
| 297 | name = "core-foundation" | ||
| 298 | version = "0.9.4" | ||
| 299 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 300 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" | ||
| 301 | dependencies = [ | ||
| 302 | "core-foundation-sys", | ||
| 303 | "libc", | ||
| 304 | ] | ||
| 305 | |||
| 306 | [[package]] | ||
| 307 | name = "core-foundation" | ||
| 308 | version = "0.10.1" | ||
| 309 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 310 | checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" | ||
| 311 | dependencies = [ | ||
| 312 | "core-foundation-sys", | ||
| 313 | "libc", | ||
| 314 | ] | ||
| 315 | |||
| 316 | [[package]] | ||
| 317 | name = "core-foundation-sys" | ||
| 318 | version = "0.8.7" | ||
| 319 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 320 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" | ||
| 321 | |||
| 322 | [[package]] | ||
| 323 | name = "coreaudio-rs" | ||
| 324 | version = "0.14.2" | ||
| 325 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 326 | checksum = "7d5d7dca3ebcf65a035582c9ad4385371a9d9ee6537474d2a278f4e1e475bb58" | ||
| 327 | dependencies = [ | ||
| 328 | "bitflags 2.11.1", | ||
| 329 | "libc", | ||
| 330 | "objc2-audio-toolbox", | ||
| 331 | "objc2-core-audio", | ||
| 332 | "objc2-core-audio-types", | ||
| 333 | "objc2-core-foundation", | ||
| 334 | ] | ||
| 335 | |||
| 336 | [[package]] | ||
| 337 | name = "cpal" | ||
| 338 | version = "0.17.3" | ||
| 339 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 340 | checksum = "d8942da362c0f0d895d7cac616263f2f9424edc5687364dfd1d25ef7eba506d7" | ||
| 341 | dependencies = [ | ||
| 342 | "alsa", | ||
| 343 | "coreaudio-rs", | ||
| 344 | "dasp_sample", | ||
| 345 | "jni 0.21.1", | ||
| 346 | "js-sys", | ||
| 347 | "libc", | ||
| 348 | "mach2", | ||
| 349 | "ndk", | ||
| 350 | "ndk-context", | ||
| 351 | "num-derive", | ||
| 352 | "num-traits", | ||
| 353 | "objc2", | ||
| 354 | "objc2-audio-toolbox", | ||
| 355 | "objc2-avf-audio", | ||
| 356 | "objc2-core-audio", | ||
| 357 | "objc2-core-audio-types", | ||
| 358 | "objc2-core-foundation", | ||
| 359 | "objc2-foundation", | ||
| 360 | "wasm-bindgen", | ||
| 361 | "wasm-bindgen-futures", | ||
| 362 | "web-sys", | ||
| 363 | "windows", | ||
| 364 | ] | ||
| 365 | |||
| 366 | [[package]] | ||
| 367 | name = "cpufeatures" | ||
| 368 | version = "0.3.0" | ||
| 369 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 370 | checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" | ||
| 371 | dependencies = [ | ||
| 372 | "libc", | ||
| 373 | ] | ||
| 374 | |||
| 375 | [[package]] | ||
| 376 | name = "ctrlc" | ||
| 377 | version = "3.5.2" | ||
| 378 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 379 | checksum = "e0b1fab2ae45819af2d0731d60f2afe17227ebb1a1538a236da84c93e9a60162" | ||
| 380 | dependencies = [ | ||
| 381 | "dispatch2", | ||
| 382 | "nix", | ||
| 383 | "windows-sys 0.61.2", | ||
| 384 | ] | ||
| 385 | |||
| 386 | [[package]] | ||
| 387 | name = "dasp_sample" | ||
| 388 | version = "0.11.0" | ||
| 389 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 390 | checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" | ||
| 391 | |||
| 392 | [[package]] | ||
| 393 | name = "directories" | ||
| 394 | version = "6.0.0" | ||
| 395 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 396 | checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" | ||
| 397 | dependencies = [ | ||
| 398 | "dirs-sys", | ||
| 399 | ] | ||
| 400 | |||
| 401 | [[package]] | ||
| 402 | name = "dirs" | ||
| 403 | version = "6.0.0" | ||
| 404 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 405 | checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" | ||
| 406 | dependencies = [ | ||
| 407 | "dirs-sys", | ||
| 408 | ] | ||
| 409 | |||
| 410 | [[package]] | ||
| 411 | name = "dirs-sys" | ||
| 412 | version = "0.5.0" | ||
| 413 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 414 | checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" | ||
| 415 | dependencies = [ | ||
| 416 | "libc", | ||
| 417 | "option-ext", | ||
| 418 | "redox_users", | ||
| 419 | "windows-sys 0.61.2", | ||
| 420 | ] | ||
| 421 | |||
| 422 | [[package]] | ||
| 423 | name = "dispatch2" | ||
| 424 | version = "0.3.1" | ||
| 425 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 426 | checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" | ||
| 427 | dependencies = [ | ||
| 428 | "bitflags 2.11.1", | ||
| 429 | "block2", | ||
| 430 | "libc", | ||
| 431 | "objc2", | ||
| 432 | ] | ||
| 433 | |||
| 434 | [[package]] | ||
| 435 | name = "displaydoc" | ||
| 436 | version = "0.2.5" | ||
| 437 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 438 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" | ||
| 439 | dependencies = [ | ||
| 440 | "proc-macro2", | ||
| 441 | "quote", | ||
| 442 | "syn", | ||
| 443 | ] | ||
| 444 | |||
| 445 | [[package]] | ||
| 446 | name = "dunce" | ||
| 447 | version = "1.0.5" | ||
| 448 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 449 | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" | ||
| 450 | |||
| 451 | [[package]] | ||
| 452 | name = "encoding_rs" | ||
| 453 | version = "0.8.35" | ||
| 454 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 455 | checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" | ||
| 456 | dependencies = [ | ||
| 457 | "cfg-if", | ||
| 458 | ] | ||
| 459 | |||
| 460 | [[package]] | ||
| 461 | name = "equivalent" | ||
| 462 | version = "1.0.2" | ||
| 463 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 464 | checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" | ||
| 465 | |||
| 466 | [[package]] | ||
| 467 | name = "errno" | ||
| 468 | version = "0.3.14" | ||
| 469 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 470 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" | ||
| 471 | dependencies = [ | ||
| 472 | "libc", | ||
| 473 | "windows-sys 0.61.2", | ||
| 474 | ] | ||
| 475 | |||
| 476 | [[package]] | ||
| 477 | name = "extended" | ||
| 478 | version = "0.1.0" | ||
| 479 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 480 | checksum = "af9673d8203fcb076b19dfd17e38b3d4ae9f44959416ea532ce72415a6020365" | ||
| 481 | |||
| 482 | [[package]] | ||
| 483 | name = "fallible-iterator" | ||
| 484 | version = "0.3.0" | ||
| 485 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 486 | checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" | ||
| 487 | |||
| 488 | [[package]] | ||
| 489 | name = "fallible-streaming-iterator" | ||
| 490 | version = "0.1.9" | ||
| 491 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 492 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" | ||
| 493 | |||
| 494 | [[package]] | ||
| 495 | name = "find-msvc-tools" | ||
| 496 | version = "0.1.9" | ||
| 497 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 498 | checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" | ||
| 499 | |||
| 500 | [[package]] | ||
| 501 | name = "fnv" | ||
| 502 | version = "1.0.7" | ||
| 503 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 504 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" | ||
| 505 | |||
| 506 | [[package]] | ||
| 507 | name = "foldhash" | ||
| 508 | version = "0.1.5" | ||
| 509 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 510 | checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" | ||
| 511 | |||
| 512 | [[package]] | ||
| 513 | name = "foldhash" | ||
| 514 | version = "0.2.0" | ||
| 515 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 516 | checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" | ||
| 517 | |||
| 518 | [[package]] | ||
| 519 | name = "form_urlencoded" | ||
| 520 | version = "1.2.2" | ||
| 521 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 522 | checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" | ||
| 523 | dependencies = [ | ||
| 524 | "percent-encoding", | ||
| 525 | ] | ||
| 526 | |||
| 527 | [[package]] | ||
| 528 | name = "fs_extra" | ||
| 529 | version = "1.3.0" | ||
| 530 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 531 | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" | ||
| 532 | |||
| 533 | [[package]] | ||
| 534 | name = "futures-channel" | ||
| 535 | version = "0.3.32" | ||
| 536 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 537 | checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" | ||
| 538 | dependencies = [ | ||
| 539 | "futures-core", | ||
| 540 | ] | ||
| 541 | |||
| 542 | [[package]] | ||
| 543 | name = "futures-core" | ||
| 544 | version = "0.3.32" | ||
| 545 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 546 | checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" | ||
| 547 | |||
| 548 | [[package]] | ||
| 549 | name = "futures-sink" | ||
| 550 | version = "0.3.32" | ||
| 551 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 552 | checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" | ||
| 553 | |||
| 554 | [[package]] | ||
| 555 | name = "futures-task" | ||
| 556 | version = "0.3.32" | ||
| 557 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 558 | checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" | ||
| 559 | |||
| 560 | [[package]] | ||
| 561 | name = "futures-util" | ||
| 562 | version = "0.3.32" | ||
| 563 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 564 | checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" | ||
| 565 | dependencies = [ | ||
| 566 | "futures-core", | ||
| 567 | "futures-task", | ||
| 568 | "pin-project-lite", | ||
| 569 | "slab", | ||
| 570 | ] | ||
| 571 | |||
| 572 | [[package]] | ||
| 573 | name = "getrandom" | ||
| 574 | version = "0.2.17" | ||
| 575 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 576 | checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" | ||
| 577 | dependencies = [ | ||
| 578 | "cfg-if", | ||
| 579 | "js-sys", | ||
| 580 | "libc", | ||
| 581 | "wasi", | ||
| 582 | "wasm-bindgen", | ||
| 583 | ] | ||
| 584 | |||
| 585 | [[package]] | ||
| 586 | name = "getrandom" | ||
| 587 | version = "0.3.4" | ||
| 588 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 589 | checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" | ||
| 590 | dependencies = [ | ||
| 591 | "cfg-if", | ||
| 592 | "js-sys", | ||
| 593 | "libc", | ||
| 594 | "r-efi 5.3.0", | ||
| 595 | "wasip2", | ||
| 596 | "wasm-bindgen", | ||
| 597 | ] | ||
| 598 | |||
| 599 | [[package]] | ||
| 600 | name = "getrandom" | ||
| 601 | version = "0.4.2" | ||
| 602 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 603 | checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" | ||
| 604 | dependencies = [ | ||
| 605 | "cfg-if", | ||
| 606 | "libc", | ||
| 607 | "r-efi 6.0.0", | ||
| 608 | "rand_core 0.10.1", | ||
| 609 | "wasip2", | ||
| 610 | "wasip3", | ||
| 611 | ] | ||
| 612 | |||
| 613 | [[package]] | ||
| 614 | name = "h2" | ||
| 615 | version = "0.4.14" | ||
| 616 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 617 | checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" | ||
| 618 | dependencies = [ | ||
| 619 | "atomic-waker", | ||
| 620 | "bytes", | ||
| 621 | "fnv", | ||
| 622 | "futures-core", | ||
| 623 | "futures-sink", | ||
| 624 | "http", | ||
| 625 | "indexmap", | ||
| 626 | "slab", | ||
| 627 | "tokio", | ||
| 628 | "tokio-util", | ||
| 629 | "tracing", | ||
| 630 | ] | ||
| 631 | |||
| 632 | [[package]] | ||
| 633 | name = "hashbrown" | ||
| 634 | version = "0.15.5" | ||
| 635 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 636 | checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" | ||
| 637 | dependencies = [ | ||
| 638 | "foldhash 0.1.5", | ||
| 639 | ] | ||
| 640 | |||
| 641 | [[package]] | ||
| 642 | name = "hashbrown" | ||
| 643 | version = "0.16.1" | ||
| 644 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 645 | checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" | ||
| 646 | dependencies = [ | ||
| 647 | "foldhash 0.2.0", | ||
| 648 | ] | ||
| 649 | |||
| 650 | [[package]] | ||
| 651 | name = "hashbrown" | ||
| 652 | version = "0.17.0" | ||
| 653 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 654 | checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" | ||
| 655 | |||
| 656 | [[package]] | ||
| 657 | name = "hashlink" | ||
| 658 | version = "0.11.0" | ||
| 659 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 660 | checksum = "ea0b22561a9c04a7cb1a302c013e0259cd3b4bb619f145b32f72b8b4bcbed230" | ||
| 661 | dependencies = [ | ||
| 662 | "hashbrown 0.16.1", | ||
| 663 | ] | ||
| 664 | |||
| 665 | [[package]] | ||
| 666 | name = "heck" | ||
| 667 | version = "0.5.0" | ||
| 668 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 669 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" | ||
| 670 | |||
| 671 | [[package]] | ||
| 672 | name = "http" | ||
| 673 | version = "1.4.0" | ||
| 674 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 675 | checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" | ||
| 676 | dependencies = [ | ||
| 677 | "bytes", | ||
| 678 | "itoa", | ||
| 679 | ] | ||
| 680 | |||
| 681 | [[package]] | ||
| 682 | name = "http-body" | ||
| 683 | version = "1.0.1" | ||
| 684 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 685 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" | ||
| 686 | dependencies = [ | ||
| 687 | "bytes", | ||
| 688 | "http", | ||
| 689 | ] | ||
| 690 | |||
| 691 | [[package]] | ||
| 692 | name = "http-body-util" | ||
| 693 | version = "0.1.3" | ||
| 694 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 695 | checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" | ||
| 696 | dependencies = [ | ||
| 697 | "bytes", | ||
| 698 | "futures-core", | ||
| 699 | "http", | ||
| 700 | "http-body", | ||
| 701 | "pin-project-lite", | ||
| 702 | ] | ||
| 703 | |||
| 704 | [[package]] | ||
| 705 | name = "httparse" | ||
| 706 | version = "1.10.1" | ||
| 707 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 708 | checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" | ||
| 709 | |||
| 710 | [[package]] | ||
| 711 | name = "hyper" | ||
| 712 | version = "1.9.0" | ||
| 713 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 714 | checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" | ||
| 715 | dependencies = [ | ||
| 716 | "atomic-waker", | ||
| 717 | "bytes", | ||
| 718 | "futures-channel", | ||
| 719 | "futures-core", | ||
| 720 | "h2", | ||
| 721 | "http", | ||
| 722 | "http-body", | ||
| 723 | "httparse", | ||
| 724 | "itoa", | ||
| 725 | "pin-project-lite", | ||
| 726 | "smallvec", | ||
| 727 | "tokio", | ||
| 728 | "want", | ||
| 729 | ] | ||
| 730 | |||
| 731 | [[package]] | ||
| 732 | name = "hyper-rustls" | ||
| 733 | version = "0.27.9" | ||
| 734 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 735 | checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" | ||
| 736 | dependencies = [ | ||
| 737 | "http", | ||
| 738 | "hyper", | ||
| 739 | "hyper-util", | ||
| 740 | "rustls", | ||
| 741 | "tokio", | ||
| 742 | "tokio-rustls", | ||
| 743 | "tower-service", | ||
| 744 | ] | ||
| 745 | |||
| 746 | [[package]] | ||
| 747 | name = "hyper-util" | ||
| 748 | version = "0.1.20" | ||
| 749 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 750 | checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" | ||
| 751 | dependencies = [ | ||
| 752 | "base64", | ||
| 753 | "bytes", | ||
| 754 | "futures-channel", | ||
| 755 | "futures-util", | ||
| 756 | "http", | ||
| 757 | "http-body", | ||
| 758 | "hyper", | ||
| 759 | "ipnet", | ||
| 760 | "libc", | ||
| 761 | "percent-encoding", | ||
| 762 | "pin-project-lite", | ||
| 763 | "socket2", | ||
| 764 | "system-configuration", | ||
| 765 | "tokio", | ||
| 766 | "tower-service", | ||
| 767 | "tracing", | ||
| 768 | "windows-registry", | ||
| 769 | ] | ||
| 770 | |||
| 771 | [[package]] | ||
| 772 | name = "iana-time-zone" | ||
| 773 | version = "0.1.65" | ||
| 774 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 775 | checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" | ||
| 776 | dependencies = [ | ||
| 777 | "android_system_properties", | ||
| 778 | "core-foundation-sys", | ||
| 779 | "iana-time-zone-haiku", | ||
| 780 | "js-sys", | ||
| 781 | "log", | ||
| 782 | "wasm-bindgen", | ||
| 783 | "windows-core", | ||
| 784 | ] | ||
| 785 | |||
| 786 | [[package]] | ||
| 787 | name = "iana-time-zone-haiku" | ||
| 788 | version = "0.1.2" | ||
| 789 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 790 | checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" | ||
| 791 | dependencies = [ | ||
| 792 | "cc", | ||
| 793 | ] | ||
| 794 | |||
| 795 | [[package]] | ||
| 796 | name = "icu_collections" | ||
| 797 | version = "2.2.0" | ||
| 798 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 799 | checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" | ||
| 800 | dependencies = [ | ||
| 801 | "displaydoc", | ||
| 802 | "potential_utf", | ||
| 803 | "utf8_iter", | ||
| 804 | "yoke", | ||
| 805 | "zerofrom", | ||
| 806 | "zerovec", | ||
| 807 | ] | ||
| 808 | |||
| 809 | [[package]] | ||
| 810 | name = "icu_locale_core" | ||
| 811 | version = "2.2.0" | ||
| 812 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 813 | checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" | ||
| 814 | dependencies = [ | ||
| 815 | "displaydoc", | ||
| 816 | "litemap", | ||
| 817 | "tinystr", | ||
| 818 | "writeable", | ||
| 819 | "zerovec", | ||
| 820 | ] | ||
| 821 | |||
| 822 | [[package]] | ||
| 823 | name = "icu_normalizer" | ||
| 824 | version = "2.2.0" | ||
| 825 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 826 | checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" | ||
| 827 | dependencies = [ | ||
| 828 | "icu_collections", | ||
| 829 | "icu_normalizer_data", | ||
| 830 | "icu_properties", | ||
| 831 | "icu_provider", | ||
| 832 | "smallvec", | ||
| 833 | "zerovec", | ||
| 834 | ] | ||
| 835 | |||
| 836 | [[package]] | ||
| 837 | name = "icu_normalizer_data" | ||
| 838 | version = "2.2.0" | ||
| 839 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 840 | checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" | ||
| 841 | |||
| 842 | [[package]] | ||
| 843 | name = "icu_properties" | ||
| 844 | version = "2.2.0" | ||
| 845 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 846 | checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" | ||
| 847 | dependencies = [ | ||
| 848 | "icu_collections", | ||
| 849 | "icu_locale_core", | ||
| 850 | "icu_properties_data", | ||
| 851 | "icu_provider", | ||
| 852 | "zerotrie", | ||
| 853 | "zerovec", | ||
| 854 | ] | ||
| 855 | |||
| 856 | [[package]] | ||
| 857 | name = "icu_properties_data" | ||
| 858 | version = "2.2.0" | ||
| 859 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 860 | checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" | ||
| 861 | |||
| 862 | [[package]] | ||
| 863 | name = "icu_provider" | ||
| 864 | version = "2.2.0" | ||
| 865 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 866 | checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" | ||
| 867 | dependencies = [ | ||
| 868 | "displaydoc", | ||
| 869 | "icu_locale_core", | ||
| 870 | "writeable", | ||
| 871 | "yoke", | ||
| 872 | "zerofrom", | ||
| 873 | "zerotrie", | ||
| 874 | "zerovec", | ||
| 875 | ] | ||
| 876 | |||
| 877 | [[package]] | ||
| 878 | name = "id-arena" | ||
| 879 | version = "2.3.0" | ||
| 880 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 881 | checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" | ||
| 882 | |||
| 883 | [[package]] | ||
| 884 | name = "idna" | ||
| 885 | version = "1.1.0" | ||
| 886 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 887 | checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" | ||
| 888 | dependencies = [ | ||
| 889 | "idna_adapter", | ||
| 890 | "smallvec", | ||
| 891 | "utf8_iter", | ||
| 892 | ] | ||
| 893 | |||
| 894 | [[package]] | ||
| 895 | name = "idna_adapter" | ||
| 896 | version = "1.2.2" | ||
| 897 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 898 | checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" | ||
| 899 | dependencies = [ | ||
| 900 | "icu_normalizer", | ||
| 901 | "icu_properties", | ||
| 902 | ] | ||
| 903 | |||
| 904 | [[package]] | ||
| 905 | name = "indexmap" | ||
| 906 | version = "2.14.0" | ||
| 907 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 908 | checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" | ||
| 909 | dependencies = [ | ||
| 910 | "equivalent", | ||
| 911 | "hashbrown 0.17.0", | ||
| 912 | "serde", | ||
| 913 | "serde_core", | ||
| 914 | ] | ||
| 915 | |||
| 916 | [[package]] | ||
| 917 | name = "ipnet" | ||
| 918 | version = "2.12.0" | ||
| 919 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 920 | checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" | ||
| 921 | |||
| 922 | [[package]] | ||
| 923 | name = "iri-string" | ||
| 924 | version = "0.7.12" | ||
| 925 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 926 | checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" | ||
| 927 | dependencies = [ | ||
| 928 | "memchr", | ||
| 929 | "serde", | ||
| 930 | ] | ||
| 931 | |||
| 932 | [[package]] | ||
| 933 | name = "is_terminal_polyfill" | ||
| 934 | version = "1.70.2" | ||
| 935 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 936 | checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" | ||
| 937 | |||
| 938 | [[package]] | ||
| 939 | name = "itoa" | ||
| 940 | version = "1.0.18" | ||
| 941 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 942 | checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" | ||
| 943 | |||
| 944 | [[package]] | ||
| 945 | name = "jni" | ||
| 946 | version = "0.21.1" | ||
| 947 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 948 | checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" | ||
| 949 | dependencies = [ | ||
| 950 | "cesu8", | ||
| 951 | "cfg-if", | ||
| 952 | "combine", | ||
| 953 | "jni-sys 0.3.1", | ||
| 954 | "log", | ||
| 955 | "thiserror 1.0.69", | ||
| 956 | "walkdir", | ||
| 957 | "windows-sys 0.45.0", | ||
| 958 | ] | ||
| 959 | |||
| 960 | [[package]] | ||
| 961 | name = "jni" | ||
| 962 | version = "0.22.4" | ||
| 963 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 964 | checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" | ||
| 965 | dependencies = [ | ||
| 966 | "cfg-if", | ||
| 967 | "combine", | ||
| 968 | "jni-macros", | ||
| 969 | "jni-sys 0.4.1", | ||
| 970 | "log", | ||
| 971 | "simd_cesu8", | ||
| 972 | "thiserror 2.0.18", | ||
| 973 | "walkdir", | ||
| 974 | "windows-link", | ||
| 975 | ] | ||
| 976 | |||
| 977 | [[package]] | ||
| 978 | name = "jni-macros" | ||
| 979 | version = "0.22.4" | ||
| 980 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 981 | checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" | ||
| 982 | dependencies = [ | ||
| 983 | "proc-macro2", | ||
| 984 | "quote", | ||
| 985 | "rustc_version", | ||
| 986 | "simd_cesu8", | ||
| 987 | "syn", | ||
| 988 | ] | ||
| 989 | |||
| 990 | [[package]] | ||
| 991 | name = "jni-sys" | ||
| 992 | version = "0.3.1" | ||
| 993 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 994 | checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" | ||
| 995 | dependencies = [ | ||
| 996 | "jni-sys 0.4.1", | ||
| 997 | ] | ||
| 998 | |||
| 999 | [[package]] | ||
| 1000 | name = "jni-sys" | ||
| 1001 | version = "0.4.1" | ||
| 1002 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1003 | checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" | ||
| 1004 | dependencies = [ | ||
| 1005 | "jni-sys-macros", | ||
| 1006 | ] | ||
| 1007 | |||
| 1008 | [[package]] | ||
| 1009 | name = "jni-sys-macros" | ||
| 1010 | version = "0.4.1" | ||
| 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1012 | checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" | ||
| 1013 | dependencies = [ | ||
| 1014 | "quote", | ||
| 1015 | "syn", | ||
| 1016 | ] | ||
| 1017 | |||
| 1018 | [[package]] | ||
| 1019 | name = "jobserver" | ||
| 1020 | version = "0.1.34" | ||
| 1021 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1022 | checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" | ||
| 1023 | dependencies = [ | ||
| 1024 | "getrandom 0.3.4", | ||
| 1025 | "libc", | ||
| 1026 | ] | ||
| 1027 | |||
| 1028 | [[package]] | ||
| 1029 | name = "js-sys" | ||
| 1030 | version = "0.3.97" | ||
| 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1032 | checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" | ||
| 1033 | dependencies = [ | ||
| 1034 | "cfg-if", | ||
| 1035 | "futures-util", | ||
| 1036 | "once_cell", | ||
| 1037 | "wasm-bindgen", | ||
| 1038 | ] | ||
| 1039 | |||
| 1040 | [[package]] | ||
| 1041 | name = "lazy_static" | ||
| 1042 | version = "1.5.0" | ||
| 1043 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1044 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" | ||
| 1045 | |||
| 1046 | [[package]] | ||
| 1047 | name = "leb128fmt" | ||
| 1048 | version = "0.1.0" | ||
| 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1050 | checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" | ||
| 1051 | |||
| 1052 | [[package]] | ||
| 1053 | name = "libc" | ||
| 1054 | version = "0.2.186" | ||
| 1055 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1056 | checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" | ||
| 1057 | |||
| 1058 | [[package]] | ||
| 1059 | name = "libredox" | ||
| 1060 | version = "0.1.16" | ||
| 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1062 | checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" | ||
| 1063 | dependencies = [ | ||
| 1064 | "libc", | ||
| 1065 | ] | ||
| 1066 | |||
| 1067 | [[package]] | ||
| 1068 | name = "libsqlite3-sys" | ||
| 1069 | version = "0.37.0" | ||
| 1070 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1071 | checksum = "b1f111c8c41e7c61a49cd34e44c7619462967221a6443b0ec299e0ac30cfb9b1" | ||
| 1072 | dependencies = [ | ||
| 1073 | "cc", | ||
| 1074 | "pkg-config", | ||
| 1075 | "vcpkg", | ||
| 1076 | ] | ||
| 1077 | |||
| 1078 | [[package]] | ||
| 1079 | name = "litemap" | ||
| 1080 | version = "0.8.2" | ||
| 1081 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1082 | checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" | ||
| 1083 | |||
| 1084 | [[package]] | ||
| 1085 | name = "lock_api" | ||
| 1086 | version = "0.4.14" | ||
| 1087 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1088 | checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" | ||
| 1089 | dependencies = [ | ||
| 1090 | "scopeguard", | ||
| 1091 | ] | ||
| 1092 | |||
| 1093 | [[package]] | ||
| 1094 | name = "log" | ||
| 1095 | version = "0.4.29" | ||
| 1096 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1097 | checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" | ||
| 1098 | |||
| 1099 | [[package]] | ||
| 1100 | name = "lru-slab" | ||
| 1101 | version = "0.1.2" | ||
| 1102 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1103 | checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154" | ||
| 1104 | |||
| 1105 | [[package]] | ||
| 1106 | name = "mach2" | ||
| 1107 | version = "0.5.0" | ||
| 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1109 | checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea" | ||
| 1110 | dependencies = [ | ||
| 1111 | "libc", | ||
| 1112 | ] | ||
| 1113 | |||
| 1114 | [[package]] | ||
| 1115 | name = "memchr" | ||
| 1116 | version = "2.8.0" | ||
| 1117 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1118 | checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" | ||
| 1119 | |||
| 1120 | [[package]] | ||
| 1121 | name = "mime" | ||
| 1122 | version = "0.3.17" | ||
| 1123 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1124 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" | ||
| 1125 | |||
| 1126 | [[package]] | ||
| 1127 | name = "mio" | ||
| 1128 | version = "1.2.0" | ||
| 1129 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1130 | checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" | ||
| 1131 | dependencies = [ | ||
| 1132 | "libc", | ||
| 1133 | "wasi", | ||
| 1134 | "windows-sys 0.61.2", | ||
| 1135 | ] | ||
| 1136 | |||
| 1137 | [[package]] | ||
| 1138 | name = "ndk" | ||
| 1139 | version = "0.9.0" | ||
| 1140 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1141 | checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" | ||
| 1142 | dependencies = [ | ||
| 1143 | "bitflags 2.11.1", | ||
| 1144 | "jni-sys 0.3.1", | ||
| 1145 | "log", | ||
| 1146 | "ndk-sys", | ||
| 1147 | "num_enum", | ||
| 1148 | "thiserror 1.0.69", | ||
| 1149 | ] | ||
| 1150 | |||
| 1151 | [[package]] | ||
| 1152 | name = "ndk-context" | ||
| 1153 | version = "0.1.1" | ||
| 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1155 | checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" | ||
| 1156 | |||
| 1157 | [[package]] | ||
| 1158 | name = "ndk-sys" | ||
| 1159 | version = "0.6.0+11769913" | ||
| 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1161 | checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" | ||
| 1162 | dependencies = [ | ||
| 1163 | "jni-sys 0.3.1", | ||
| 1164 | ] | ||
| 1165 | |||
| 1166 | [[package]] | ||
| 1167 | name = "nix" | ||
| 1168 | version = "0.31.2" | ||
| 1169 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1170 | checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" | ||
| 1171 | dependencies = [ | ||
| 1172 | "bitflags 2.11.1", | ||
| 1173 | "cfg-if", | ||
| 1174 | "cfg_aliases", | ||
| 1175 | "libc", | ||
| 1176 | ] | ||
| 1177 | |||
| 1178 | [[package]] | ||
| 1179 | name = "num-derive" | ||
| 1180 | version = "0.4.2" | ||
| 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1182 | checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202" | ||
| 1183 | dependencies = [ | ||
| 1184 | "proc-macro2", | ||
| 1185 | "quote", | ||
| 1186 | "syn", | ||
| 1187 | ] | ||
| 1188 | |||
| 1189 | [[package]] | ||
| 1190 | name = "num-traits" | ||
| 1191 | version = "0.2.19" | ||
| 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1193 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" | ||
| 1194 | dependencies = [ | ||
| 1195 | "autocfg", | ||
| 1196 | ] | ||
| 1197 | |||
| 1198 | [[package]] | ||
| 1199 | name = "num_enum" | ||
| 1200 | version = "0.7.6" | ||
| 1201 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1202 | checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" | ||
| 1203 | dependencies = [ | ||
| 1204 | "num_enum_derive", | ||
| 1205 | "rustversion", | ||
| 1206 | ] | ||
| 1207 | |||
| 1208 | [[package]] | ||
| 1209 | name = "num_enum_derive" | ||
| 1210 | version = "0.7.6" | ||
| 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1212 | checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" | ||
| 1213 | dependencies = [ | ||
| 1214 | "proc-macro-crate", | ||
| 1215 | "proc-macro2", | ||
| 1216 | "quote", | ||
| 1217 | "syn", | ||
| 1218 | ] | ||
| 1219 | |||
| 1220 | [[package]] | ||
| 1221 | name = "objc2" | ||
| 1222 | version = "0.6.4" | ||
| 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1224 | checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" | ||
| 1225 | dependencies = [ | ||
| 1226 | "objc2-encode", | ||
| 1227 | ] | ||
| 1228 | |||
| 1229 | [[package]] | ||
| 1230 | name = "objc2-audio-toolbox" | ||
| 1231 | version = "0.3.2" | ||
| 1232 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1233 | checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08" | ||
| 1234 | dependencies = [ | ||
| 1235 | "bitflags 2.11.1", | ||
| 1236 | "libc", | ||
| 1237 | "objc2", | ||
| 1238 | "objc2-core-audio", | ||
| 1239 | "objc2-core-audio-types", | ||
| 1240 | "objc2-core-foundation", | ||
| 1241 | "objc2-foundation", | ||
| 1242 | ] | ||
| 1243 | |||
| 1244 | [[package]] | ||
| 1245 | name = "objc2-avf-audio" | ||
| 1246 | version = "0.3.2" | ||
| 1247 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1248 | checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be" | ||
| 1249 | dependencies = [ | ||
| 1250 | "objc2", | ||
| 1251 | "objc2-foundation", | ||
| 1252 | ] | ||
| 1253 | |||
| 1254 | [[package]] | ||
| 1255 | name = "objc2-core-audio" | ||
| 1256 | version = "0.3.2" | ||
| 1257 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1258 | checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2" | ||
| 1259 | dependencies = [ | ||
| 1260 | "dispatch2", | ||
| 1261 | "objc2", | ||
| 1262 | "objc2-core-audio-types", | ||
| 1263 | "objc2-core-foundation", | ||
| 1264 | "objc2-foundation", | ||
| 1265 | ] | ||
| 1266 | |||
| 1267 | [[package]] | ||
| 1268 | name = "objc2-core-audio-types" | ||
| 1269 | version = "0.3.2" | ||
| 1270 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1271 | checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c" | ||
| 1272 | dependencies = [ | ||
| 1273 | "bitflags 2.11.1", | ||
| 1274 | "objc2", | ||
| 1275 | ] | ||
| 1276 | |||
| 1277 | [[package]] | ||
| 1278 | name = "objc2-core-foundation" | ||
| 1279 | version = "0.3.2" | ||
| 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1281 | checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" | ||
| 1282 | dependencies = [ | ||
| 1283 | "bitflags 2.11.1", | ||
| 1284 | "block2", | ||
| 1285 | "dispatch2", | ||
| 1286 | "libc", | ||
| 1287 | "objc2", | ||
| 1288 | ] | ||
| 1289 | |||
| 1290 | [[package]] | ||
| 1291 | name = "objc2-encode" | ||
| 1292 | version = "4.1.0" | ||
| 1293 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1294 | checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" | ||
| 1295 | |||
| 1296 | [[package]] | ||
| 1297 | name = "objc2-foundation" | ||
| 1298 | version = "0.3.2" | ||
| 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1300 | checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" | ||
| 1301 | dependencies = [ | ||
| 1302 | "bitflags 2.11.1", | ||
| 1303 | "block2", | ||
| 1304 | "libc", | ||
| 1305 | "objc2", | ||
| 1306 | "objc2-core-foundation", | ||
| 1307 | ] | ||
| 1308 | |||
| 1309 | [[package]] | ||
| 1310 | name = "once_cell" | ||
| 1311 | version = "1.21.4" | ||
| 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1313 | checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" | ||
| 1314 | |||
| 1315 | [[package]] | ||
| 1316 | name = "once_cell_polyfill" | ||
| 1317 | version = "1.70.2" | ||
| 1318 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1319 | checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" | ||
| 1320 | |||
| 1321 | [[package]] | ||
| 1322 | name = "openssl-probe" | ||
| 1323 | version = "0.2.1" | ||
| 1324 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1325 | checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" | ||
| 1326 | |||
| 1327 | [[package]] | ||
| 1328 | name = "option-ext" | ||
| 1329 | version = "0.2.0" | ||
| 1330 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1331 | checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" | ||
| 1332 | |||
| 1333 | [[package]] | ||
| 1334 | name = "parking_lot" | ||
| 1335 | version = "0.12.5" | ||
| 1336 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1337 | checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" | ||
| 1338 | dependencies = [ | ||
| 1339 | "lock_api", | ||
| 1340 | "parking_lot_core", | ||
| 1341 | ] | ||
| 1342 | |||
| 1343 | [[package]] | ||
| 1344 | name = "parking_lot_core" | ||
| 1345 | version = "0.9.12" | ||
| 1346 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1347 | checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" | ||
| 1348 | dependencies = [ | ||
| 1349 | "cfg-if", | ||
| 1350 | "libc", | ||
| 1351 | "redox_syscall", | ||
| 1352 | "smallvec", | ||
| 1353 | "windows-link", | ||
| 1354 | ] | ||
| 1355 | |||
| 1356 | [[package]] | ||
| 1357 | name = "percent-encoding" | ||
| 1358 | version = "2.3.2" | ||
| 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1360 | checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" | ||
| 1361 | |||
| 1362 | [[package]] | ||
| 1363 | name = "pin-project-lite" | ||
| 1364 | version = "0.2.17" | ||
| 1365 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1366 | checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" | ||
| 1367 | |||
| 1368 | [[package]] | ||
| 1369 | name = "pkg-config" | ||
| 1370 | version = "0.3.33" | ||
| 1371 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1372 | checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" | ||
| 1373 | |||
| 1374 | [[package]] | ||
| 1375 | name = "potential_utf" | ||
| 1376 | version = "0.1.5" | ||
| 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1378 | checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" | ||
| 1379 | dependencies = [ | ||
| 1380 | "zerovec", | ||
| 1381 | ] | ||
| 1382 | |||
| 1383 | [[package]] | ||
| 1384 | name = "ppv-lite86" | ||
| 1385 | version = "0.2.21" | ||
| 1386 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1387 | checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" | ||
| 1388 | dependencies = [ | ||
| 1389 | "zerocopy", | ||
| 1390 | ] | ||
| 1391 | |||
| 1392 | [[package]] | ||
| 1393 | name = "prettyplease" | ||
| 1394 | version = "0.2.37" | ||
| 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1396 | checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" | ||
| 1397 | dependencies = [ | ||
| 1398 | "proc-macro2", | ||
| 1399 | "syn", | ||
| 1400 | ] | ||
| 1401 | |||
| 1402 | [[package]] | ||
| 1403 | name = "proc-macro-crate" | ||
| 1404 | version = "3.5.0" | ||
| 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1406 | checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" | ||
| 1407 | dependencies = [ | ||
| 1408 | "toml_edit", | ||
| 1409 | ] | ||
| 1410 | |||
| 1411 | [[package]] | ||
| 1412 | name = "proc-macro2" | ||
| 1413 | version = "1.0.106" | ||
| 1414 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1415 | checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" | ||
| 1416 | dependencies = [ | ||
| 1417 | "unicode-ident", | ||
| 1418 | ] | ||
| 1419 | |||
| 1420 | [[package]] | ||
| 1421 | name = "quinn" | ||
| 1422 | version = "0.11.9" | ||
| 1423 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1424 | checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" | ||
| 1425 | dependencies = [ | ||
| 1426 | "bytes", | ||
| 1427 | "cfg_aliases", | ||
| 1428 | "pin-project-lite", | ||
| 1429 | "quinn-proto", | ||
| 1430 | "quinn-udp", | ||
| 1431 | "rustc-hash", | ||
| 1432 | "rustls", | ||
| 1433 | "socket2", | ||
| 1434 | "thiserror 2.0.18", | ||
| 1435 | "tokio", | ||
| 1436 | "tracing", | ||
| 1437 | "web-time", | ||
| 1438 | ] | ||
| 1439 | |||
| 1440 | [[package]] | ||
| 1441 | name = "quinn-proto" | ||
| 1442 | version = "0.11.14" | ||
| 1443 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1444 | checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098" | ||
| 1445 | dependencies = [ | ||
| 1446 | "aws-lc-rs", | ||
| 1447 | "bytes", | ||
| 1448 | "getrandom 0.3.4", | ||
| 1449 | "lru-slab", | ||
| 1450 | "rand 0.9.4", | ||
| 1451 | "ring", | ||
| 1452 | "rustc-hash", | ||
| 1453 | "rustls", | ||
| 1454 | "rustls-pki-types", | ||
| 1455 | "slab", | ||
| 1456 | "thiserror 2.0.18", | ||
| 1457 | "tinyvec", | ||
| 1458 | "tracing", | ||
| 1459 | "web-time", | ||
| 1460 | ] | ||
| 1461 | |||
| 1462 | [[package]] | ||
| 1463 | name = "quinn-udp" | ||
| 1464 | version = "0.5.14" | ||
| 1465 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1466 | checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd" | ||
| 1467 | dependencies = [ | ||
| 1468 | "cfg_aliases", | ||
| 1469 | "libc", | ||
| 1470 | "once_cell", | ||
| 1471 | "socket2", | ||
| 1472 | "tracing", | ||
| 1473 | "windows-sys 0.52.0", | ||
| 1474 | ] | ||
| 1475 | |||
| 1476 | [[package]] | ||
| 1477 | name = "quote" | ||
| 1478 | version = "1.0.45" | ||
| 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1480 | checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" | ||
| 1481 | dependencies = [ | ||
| 1482 | "proc-macro2", | ||
| 1483 | ] | ||
| 1484 | |||
| 1485 | [[package]] | ||
| 1486 | name = "r-efi" | ||
| 1487 | version = "5.3.0" | ||
| 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1489 | checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" | ||
| 1490 | |||
| 1491 | [[package]] | ||
| 1492 | name = "r-efi" | ||
| 1493 | version = "6.0.0" | ||
| 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1495 | checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" | ||
| 1496 | |||
| 1497 | [[package]] | ||
| 1498 | name = "rand" | ||
| 1499 | version = "0.9.4" | ||
| 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1501 | checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" | ||
| 1502 | dependencies = [ | ||
| 1503 | "rand_chacha", | ||
| 1504 | "rand_core 0.9.5", | ||
| 1505 | ] | ||
| 1506 | |||
| 1507 | [[package]] | ||
| 1508 | name = "rand" | ||
| 1509 | version = "0.10.1" | ||
| 1510 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1511 | checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" | ||
| 1512 | dependencies = [ | ||
| 1513 | "chacha20", | ||
| 1514 | "getrandom 0.4.2", | ||
| 1515 | "rand_core 0.10.1", | ||
| 1516 | ] | ||
| 1517 | |||
| 1518 | [[package]] | ||
| 1519 | name = "rand_chacha" | ||
| 1520 | version = "0.9.0" | ||
| 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1522 | checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" | ||
| 1523 | dependencies = [ | ||
| 1524 | "ppv-lite86", | ||
| 1525 | "rand_core 0.9.5", | ||
| 1526 | ] | ||
| 1527 | |||
| 1528 | [[package]] | ||
| 1529 | name = "rand_core" | ||
| 1530 | version = "0.9.5" | ||
| 1531 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1532 | checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" | ||
| 1533 | dependencies = [ | ||
| 1534 | "getrandom 0.3.4", | ||
| 1535 | ] | ||
| 1536 | |||
| 1537 | [[package]] | ||
| 1538 | name = "rand_core" | ||
| 1539 | version = "0.10.1" | ||
| 1540 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1541 | checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" | ||
| 1542 | |||
| 1543 | [[package]] | ||
| 1544 | name = "redox_syscall" | ||
| 1545 | version = "0.5.18" | ||
| 1546 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1547 | checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" | ||
| 1548 | dependencies = [ | ||
| 1549 | "bitflags 2.11.1", | ||
| 1550 | ] | ||
| 1551 | |||
| 1552 | [[package]] | ||
| 1553 | name = "redox_users" | ||
| 1554 | version = "0.5.2" | ||
| 1555 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1556 | checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" | ||
| 1557 | dependencies = [ | ||
| 1558 | "getrandom 0.2.17", | ||
| 1559 | "libredox", | ||
| 1560 | "thiserror 2.0.18", | ||
| 1561 | ] | ||
| 1562 | |||
| 1563 | [[package]] | ||
| 1564 | name = "reqwest" | ||
| 1565 | version = "0.13.3" | ||
| 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1567 | checksum = "62e0021ea2c22aed41653bc7e1419abb2c97e038ff2c33d0e1309e49a97deec0" | ||
| 1568 | dependencies = [ | ||
| 1569 | "base64", | ||
| 1570 | "bytes", | ||
| 1571 | "encoding_rs", | ||
| 1572 | "futures-core", | ||
| 1573 | "h2", | ||
| 1574 | "http", | ||
| 1575 | "http-body", | ||
| 1576 | "http-body-util", | ||
| 1577 | "hyper", | ||
| 1578 | "hyper-rustls", | ||
| 1579 | "hyper-util", | ||
| 1580 | "js-sys", | ||
| 1581 | "log", | ||
| 1582 | "mime", | ||
| 1583 | "percent-encoding", | ||
| 1584 | "pin-project-lite", | ||
| 1585 | "quinn", | ||
| 1586 | "rustls", | ||
| 1587 | "rustls-pki-types", | ||
| 1588 | "rustls-platform-verifier", | ||
| 1589 | "serde", | ||
| 1590 | "serde_json", | ||
| 1591 | "sync_wrapper", | ||
| 1592 | "tokio", | ||
| 1593 | "tokio-rustls", | ||
| 1594 | "tower", | ||
| 1595 | "tower-http", | ||
| 1596 | "tower-service", | ||
| 1597 | "url", | ||
| 1598 | "wasm-bindgen", | ||
| 1599 | "wasm-bindgen-futures", | ||
| 1600 | "web-sys", | ||
| 1601 | ] | ||
| 1602 | |||
| 1603 | [[package]] | ||
| 1604 | name = "ring" | ||
| 1605 | version = "0.17.14" | ||
| 1606 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1607 | checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" | ||
| 1608 | dependencies = [ | ||
| 1609 | "cc", | ||
| 1610 | "cfg-if", | ||
| 1611 | "getrandom 0.2.17", | ||
| 1612 | "libc", | ||
| 1613 | "untrusted", | ||
| 1614 | "windows-sys 0.52.0", | ||
| 1615 | ] | ||
| 1616 | |||
| 1617 | [[package]] | ||
| 1618 | name = "rsp" | ||
| 1619 | version = "0.1.0" | ||
| 1620 | dependencies = [ | ||
| 1621 | "chrono", | ||
| 1622 | "clap", | ||
| 1623 | "cpal", | ||
| 1624 | "ctrlc", | ||
| 1625 | "directories", | ||
| 1626 | "dirs", | ||
| 1627 | "libc", | ||
| 1628 | "rand 0.10.1", | ||
| 1629 | "reqwest", | ||
| 1630 | "rusqlite", | ||
| 1631 | "serde", | ||
| 1632 | "serde_json", | ||
| 1633 | "symphonia", | ||
| 1634 | "tokio", | ||
| 1635 | "toml", | ||
| 1636 | ] | ||
| 1637 | |||
| 1638 | [[package]] | ||
| 1639 | name = "rsqlite-vfs" | ||
| 1640 | version = "0.1.0" | ||
| 1641 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1642 | checksum = "a8a1f2315036ef6b1fbacd1972e8ee7688030b0a2121edfc2a6550febd41574d" | ||
| 1643 | dependencies = [ | ||
| 1644 | "hashbrown 0.16.1", | ||
| 1645 | "thiserror 2.0.18", | ||
| 1646 | ] | ||
| 1647 | |||
| 1648 | [[package]] | ||
| 1649 | name = "rusqlite" | ||
| 1650 | version = "0.39.0" | ||
| 1651 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1652 | checksum = "a0d2b0146dd9661bf67bb107c0bb2a55064d556eeb3fc314151b957f313bcd4e" | ||
| 1653 | dependencies = [ | ||
| 1654 | "bitflags 2.11.1", | ||
| 1655 | "fallible-iterator", | ||
| 1656 | "fallible-streaming-iterator", | ||
| 1657 | "hashlink", | ||
| 1658 | "libsqlite3-sys", | ||
| 1659 | "smallvec", | ||
| 1660 | "sqlite-wasm-rs", | ||
| 1661 | ] | ||
| 1662 | |||
| 1663 | [[package]] | ||
| 1664 | name = "rustc-hash" | ||
| 1665 | version = "2.1.2" | ||
| 1666 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1667 | checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" | ||
| 1668 | |||
| 1669 | [[package]] | ||
| 1670 | name = "rustc_version" | ||
| 1671 | version = "0.4.1" | ||
| 1672 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1673 | checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" | ||
| 1674 | dependencies = [ | ||
| 1675 | "semver", | ||
| 1676 | ] | ||
| 1677 | |||
| 1678 | [[package]] | ||
| 1679 | name = "rustls" | ||
| 1680 | version = "0.23.40" | ||
| 1681 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1682 | checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" | ||
| 1683 | dependencies = [ | ||
| 1684 | "aws-lc-rs", | ||
| 1685 | "once_cell", | ||
| 1686 | "rustls-pki-types", | ||
| 1687 | "rustls-webpki", | ||
| 1688 | "subtle", | ||
| 1689 | "zeroize", | ||
| 1690 | ] | ||
| 1691 | |||
| 1692 | [[package]] | ||
| 1693 | name = "rustls-native-certs" | ||
| 1694 | version = "0.8.3" | ||
| 1695 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1696 | checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" | ||
| 1697 | dependencies = [ | ||
| 1698 | "openssl-probe", | ||
| 1699 | "rustls-pki-types", | ||
| 1700 | "schannel", | ||
| 1701 | "security-framework", | ||
| 1702 | ] | ||
| 1703 | |||
| 1704 | [[package]] | ||
| 1705 | name = "rustls-pki-types" | ||
| 1706 | version = "1.14.1" | ||
| 1707 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1708 | checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" | ||
| 1709 | dependencies = [ | ||
| 1710 | "web-time", | ||
| 1711 | "zeroize", | ||
| 1712 | ] | ||
| 1713 | |||
| 1714 | [[package]] | ||
| 1715 | name = "rustls-platform-verifier" | ||
| 1716 | version = "0.7.0" | ||
| 1717 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1718 | checksum = "26d1e2536ce4f35f4846aa13bff16bd0ff40157cdb14cc056c7b14ba41233ba0" | ||
| 1719 | dependencies = [ | ||
| 1720 | "core-foundation 0.10.1", | ||
| 1721 | "core-foundation-sys", | ||
| 1722 | "jni 0.22.4", | ||
| 1723 | "log", | ||
| 1724 | "once_cell", | ||
| 1725 | "rustls", | ||
| 1726 | "rustls-native-certs", | ||
| 1727 | "rustls-platform-verifier-android", | ||
| 1728 | "rustls-webpki", | ||
| 1729 | "security-framework", | ||
| 1730 | "security-framework-sys", | ||
| 1731 | "webpki-root-certs", | ||
| 1732 | "windows-sys 0.61.2", | ||
| 1733 | ] | ||
| 1734 | |||
| 1735 | [[package]] | ||
| 1736 | name = "rustls-platform-verifier-android" | ||
| 1737 | version = "0.1.1" | ||
| 1738 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1739 | checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" | ||
| 1740 | |||
| 1741 | [[package]] | ||
| 1742 | name = "rustls-webpki" | ||
| 1743 | version = "0.103.13" | ||
| 1744 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1745 | checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" | ||
| 1746 | dependencies = [ | ||
| 1747 | "aws-lc-rs", | ||
| 1748 | "ring", | ||
| 1749 | "rustls-pki-types", | ||
| 1750 | "untrusted", | ||
| 1751 | ] | ||
| 1752 | |||
| 1753 | [[package]] | ||
| 1754 | name = "rustversion" | ||
| 1755 | version = "1.0.22" | ||
| 1756 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1757 | checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" | ||
| 1758 | |||
| 1759 | [[package]] | ||
| 1760 | name = "same-file" | ||
| 1761 | version = "1.0.6" | ||
| 1762 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1763 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" | ||
| 1764 | dependencies = [ | ||
| 1765 | "winapi-util", | ||
| 1766 | ] | ||
| 1767 | |||
| 1768 | [[package]] | ||
| 1769 | name = "schannel" | ||
| 1770 | version = "0.1.29" | ||
| 1771 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1772 | checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" | ||
| 1773 | dependencies = [ | ||
| 1774 | "windows-sys 0.61.2", | ||
| 1775 | ] | ||
| 1776 | |||
| 1777 | [[package]] | ||
| 1778 | name = "scopeguard" | ||
| 1779 | version = "1.2.0" | ||
| 1780 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1781 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" | ||
| 1782 | |||
| 1783 | [[package]] | ||
| 1784 | name = "security-framework" | ||
| 1785 | version = "3.7.0" | ||
| 1786 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1787 | checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" | ||
| 1788 | dependencies = [ | ||
| 1789 | "bitflags 2.11.1", | ||
| 1790 | "core-foundation 0.10.1", | ||
| 1791 | "core-foundation-sys", | ||
| 1792 | "libc", | ||
| 1793 | "security-framework-sys", | ||
| 1794 | ] | ||
| 1795 | |||
| 1796 | [[package]] | ||
| 1797 | name = "security-framework-sys" | ||
| 1798 | version = "2.17.0" | ||
| 1799 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1800 | checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" | ||
| 1801 | dependencies = [ | ||
| 1802 | "core-foundation-sys", | ||
| 1803 | "libc", | ||
| 1804 | ] | ||
| 1805 | |||
| 1806 | [[package]] | ||
| 1807 | name = "semver" | ||
| 1808 | version = "1.0.28" | ||
| 1809 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1810 | checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" | ||
| 1811 | |||
| 1812 | [[package]] | ||
| 1813 | name = "serde" | ||
| 1814 | version = "1.0.228" | ||
| 1815 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1816 | checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" | ||
| 1817 | dependencies = [ | ||
| 1818 | "serde_core", | ||
| 1819 | "serde_derive", | ||
| 1820 | ] | ||
| 1821 | |||
| 1822 | [[package]] | ||
| 1823 | name = "serde_core" | ||
| 1824 | version = "1.0.228" | ||
| 1825 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1826 | checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" | ||
| 1827 | dependencies = [ | ||
| 1828 | "serde_derive", | ||
| 1829 | ] | ||
| 1830 | |||
| 1831 | [[package]] | ||
| 1832 | name = "serde_derive" | ||
| 1833 | version = "1.0.228" | ||
| 1834 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1835 | checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" | ||
| 1836 | dependencies = [ | ||
| 1837 | "proc-macro2", | ||
| 1838 | "quote", | ||
| 1839 | "syn", | ||
| 1840 | ] | ||
| 1841 | |||
| 1842 | [[package]] | ||
| 1843 | name = "serde_json" | ||
| 1844 | version = "1.0.149" | ||
| 1845 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1846 | checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" | ||
| 1847 | dependencies = [ | ||
| 1848 | "itoa", | ||
| 1849 | "memchr", | ||
| 1850 | "serde", | ||
| 1851 | "serde_core", | ||
| 1852 | "zmij", | ||
| 1853 | ] | ||
| 1854 | |||
| 1855 | [[package]] | ||
| 1856 | name = "serde_spanned" | ||
| 1857 | version = "1.1.1" | ||
| 1858 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1859 | checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" | ||
| 1860 | dependencies = [ | ||
| 1861 | "serde_core", | ||
| 1862 | ] | ||
| 1863 | |||
| 1864 | [[package]] | ||
| 1865 | name = "shlex" | ||
| 1866 | version = "1.3.0" | ||
| 1867 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1868 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" | ||
| 1869 | |||
| 1870 | [[package]] | ||
| 1871 | name = "signal-hook-registry" | ||
| 1872 | version = "1.4.8" | ||
| 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1874 | checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" | ||
| 1875 | dependencies = [ | ||
| 1876 | "errno", | ||
| 1877 | "libc", | ||
| 1878 | ] | ||
| 1879 | |||
| 1880 | [[package]] | ||
| 1881 | name = "simd_cesu8" | ||
| 1882 | version = "1.1.1" | ||
| 1883 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1884 | checksum = "94f90157bb87cddf702797c5dadfa0be7d266cdf49e22da2fcaa32eff75b2c33" | ||
| 1885 | dependencies = [ | ||
| 1886 | "rustc_version", | ||
| 1887 | "simdutf8", | ||
| 1888 | ] | ||
| 1889 | |||
| 1890 | [[package]] | ||
| 1891 | name = "simdutf8" | ||
| 1892 | version = "0.1.5" | ||
| 1893 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1894 | checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" | ||
| 1895 | |||
| 1896 | [[package]] | ||
| 1897 | name = "slab" | ||
| 1898 | version = "0.4.12" | ||
| 1899 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1900 | checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" | ||
| 1901 | |||
| 1902 | [[package]] | ||
| 1903 | name = "smallvec" | ||
| 1904 | version = "1.15.1" | ||
| 1905 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1906 | checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" | ||
| 1907 | |||
| 1908 | [[package]] | ||
| 1909 | name = "socket2" | ||
| 1910 | version = "0.6.3" | ||
| 1911 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1912 | checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" | ||
| 1913 | dependencies = [ | ||
| 1914 | "libc", | ||
| 1915 | "windows-sys 0.61.2", | ||
| 1916 | ] | ||
| 1917 | |||
| 1918 | [[package]] | ||
| 1919 | name = "sqlite-wasm-rs" | ||
| 1920 | version = "0.5.3" | ||
| 1921 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1922 | checksum = "1b2c760607300407ddeaee518acf28c795661b7108c75421303dbefb237d3a36" | ||
| 1923 | dependencies = [ | ||
| 1924 | "cc", | ||
| 1925 | "js-sys", | ||
| 1926 | "rsqlite-vfs", | ||
| 1927 | "wasm-bindgen", | ||
| 1928 | ] | ||
| 1929 | |||
| 1930 | [[package]] | ||
| 1931 | name = "stable_deref_trait" | ||
| 1932 | version = "1.2.1" | ||
| 1933 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1934 | checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" | ||
| 1935 | |||
| 1936 | [[package]] | ||
| 1937 | name = "strsim" | ||
| 1938 | version = "0.11.1" | ||
| 1939 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1940 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" | ||
| 1941 | |||
| 1942 | [[package]] | ||
| 1943 | name = "subtle" | ||
| 1944 | version = "2.6.1" | ||
| 1945 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1946 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" | ||
| 1947 | |||
| 1948 | [[package]] | ||
| 1949 | name = "symphonia" | ||
| 1950 | version = "0.5.5" | ||
| 1951 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1952 | checksum = "5773a4c030a19d9bfaa090f49746ff35c75dfddfa700df7a5939d5e076a57039" | ||
| 1953 | dependencies = [ | ||
| 1954 | "lazy_static", | ||
| 1955 | "symphonia-bundle-flac", | ||
| 1956 | "symphonia-bundle-mp3", | ||
| 1957 | "symphonia-codec-aac", | ||
| 1958 | "symphonia-codec-adpcm", | ||
| 1959 | "symphonia-codec-alac", | ||
| 1960 | "symphonia-codec-pcm", | ||
| 1961 | "symphonia-codec-vorbis", | ||
| 1962 | "symphonia-core", | ||
| 1963 | "symphonia-format-caf", | ||
| 1964 | "symphonia-format-isomp4", | ||
| 1965 | "symphonia-format-mkv", | ||
| 1966 | "symphonia-format-ogg", | ||
| 1967 | "symphonia-format-riff", | ||
| 1968 | "symphonia-metadata", | ||
| 1969 | ] | ||
| 1970 | |||
| 1971 | [[package]] | ||
| 1972 | name = "symphonia-bundle-flac" | ||
| 1973 | version = "0.5.5" | ||
| 1974 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1975 | checksum = "c91565e180aea25d9b80a910c546802526ffd0072d0b8974e3ebe59b686c9976" | ||
| 1976 | dependencies = [ | ||
| 1977 | "log", | ||
| 1978 | "symphonia-core", | ||
| 1979 | "symphonia-metadata", | ||
| 1980 | "symphonia-utils-xiph", | ||
| 1981 | ] | ||
| 1982 | |||
| 1983 | [[package]] | ||
| 1984 | name = "symphonia-bundle-mp3" | ||
| 1985 | version = "0.5.5" | ||
| 1986 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1987 | checksum = "4872dd6bb56bf5eac799e3e957aa1981086c3e613b27e0ac23b176054f7c57ed" | ||
| 1988 | dependencies = [ | ||
| 1989 | "lazy_static", | ||
| 1990 | "log", | ||
| 1991 | "symphonia-core", | ||
| 1992 | "symphonia-metadata", | ||
| 1993 | ] | ||
| 1994 | |||
| 1995 | [[package]] | ||
| 1996 | name = "symphonia-codec-aac" | ||
| 1997 | version = "0.5.5" | ||
| 1998 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 1999 | checksum = "4c263845aa86881416849c1729a54c7f55164f8b96111dba59de46849e73a790" | ||
| 2000 | dependencies = [ | ||
| 2001 | "lazy_static", | ||
| 2002 | "log", | ||
| 2003 | "symphonia-core", | ||
| 2004 | ] | ||
| 2005 | |||
| 2006 | [[package]] | ||
| 2007 | name = "symphonia-codec-adpcm" | ||
| 2008 | version = "0.5.5" | ||
| 2009 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2010 | checksum = "2dddc50e2bbea4cfe027441eece77c46b9f319748605ab8f3443350129ddd07f" | ||
| 2011 | dependencies = [ | ||
| 2012 | "log", | ||
| 2013 | "symphonia-core", | ||
| 2014 | ] | ||
| 2015 | |||
| 2016 | [[package]] | ||
| 2017 | name = "symphonia-codec-alac" | ||
| 2018 | version = "0.5.5" | ||
| 2019 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2020 | checksum = "8413fa754942ac16a73634c9dfd1500ed5c61430956b33728567f667fdd393ab" | ||
| 2021 | dependencies = [ | ||
| 2022 | "log", | ||
| 2023 | "symphonia-core", | ||
| 2024 | ] | ||
| 2025 | |||
| 2026 | [[package]] | ||
| 2027 | name = "symphonia-codec-pcm" | ||
| 2028 | version = "0.5.5" | ||
| 2029 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2030 | checksum = "4e89d716c01541ad3ebe7c91ce4c8d38a7cf266a3f7b2f090b108fb0cb031d95" | ||
| 2031 | dependencies = [ | ||
| 2032 | "log", | ||
| 2033 | "symphonia-core", | ||
| 2034 | ] | ||
| 2035 | |||
| 2036 | [[package]] | ||
| 2037 | name = "symphonia-codec-vorbis" | ||
| 2038 | version = "0.5.5" | ||
| 2039 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2040 | checksum = "f025837c309cd69ffef572750b4a2257b59552c5399a5e49707cc5b1b85d1c73" | ||
| 2041 | dependencies = [ | ||
| 2042 | "log", | ||
| 2043 | "symphonia-core", | ||
| 2044 | "symphonia-utils-xiph", | ||
| 2045 | ] | ||
| 2046 | |||
| 2047 | [[package]] | ||
| 2048 | name = "symphonia-core" | ||
| 2049 | version = "0.5.5" | ||
| 2050 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2051 | checksum = "ea00cc4f79b7f6bb7ff87eddc065a1066f3a43fe1875979056672c9ef948c2af" | ||
| 2052 | dependencies = [ | ||
| 2053 | "arrayvec", | ||
| 2054 | "bitflags 1.3.2", | ||
| 2055 | "bytemuck", | ||
| 2056 | "lazy_static", | ||
| 2057 | "log", | ||
| 2058 | ] | ||
| 2059 | |||
| 2060 | [[package]] | ||
| 2061 | name = "symphonia-format-caf" | ||
| 2062 | version = "0.5.5" | ||
| 2063 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2064 | checksum = "b8faf379316b6b6e6bbc274d00e7a592e0d63ff1a7e182ce8ba25e24edd3d096" | ||
| 2065 | dependencies = [ | ||
| 2066 | "log", | ||
| 2067 | "symphonia-core", | ||
| 2068 | "symphonia-metadata", | ||
| 2069 | ] | ||
| 2070 | |||
| 2071 | [[package]] | ||
| 2072 | name = "symphonia-format-isomp4" | ||
| 2073 | version = "0.5.5" | ||
| 2074 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2075 | checksum = "243739585d11f81daf8dac8d9f3d18cc7898f6c09a259675fc364b382c30e0a5" | ||
| 2076 | dependencies = [ | ||
| 2077 | "encoding_rs", | ||
| 2078 | "log", | ||
| 2079 | "symphonia-core", | ||
| 2080 | "symphonia-metadata", | ||
| 2081 | "symphonia-utils-xiph", | ||
| 2082 | ] | ||
| 2083 | |||
| 2084 | [[package]] | ||
| 2085 | name = "symphonia-format-mkv" | ||
| 2086 | version = "0.5.5" | ||
| 2087 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2088 | checksum = "122d786d2c43a49beb6f397551b4a050d8229eaa54c7ddf9ee4b98899b8742d0" | ||
| 2089 | dependencies = [ | ||
| 2090 | "lazy_static", | ||
| 2091 | "log", | ||
| 2092 | "symphonia-core", | ||
| 2093 | "symphonia-metadata", | ||
| 2094 | "symphonia-utils-xiph", | ||
| 2095 | ] | ||
| 2096 | |||
| 2097 | [[package]] | ||
| 2098 | name = "symphonia-format-ogg" | ||
| 2099 | version = "0.5.5" | ||
| 2100 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2101 | checksum = "2b4955c67c1ed3aa8ae8428d04ca8397fbef6a19b2b051e73b5da8b1435639cb" | ||
| 2102 | dependencies = [ | ||
| 2103 | "log", | ||
| 2104 | "symphonia-core", | ||
| 2105 | "symphonia-metadata", | ||
| 2106 | "symphonia-utils-xiph", | ||
| 2107 | ] | ||
| 2108 | |||
| 2109 | [[package]] | ||
| 2110 | name = "symphonia-format-riff" | ||
| 2111 | version = "0.5.5" | ||
| 2112 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2113 | checksum = "c2d7c3df0e7d94efb68401d81906eae73c02b40d5ec1a141962c592d0f11a96f" | ||
| 2114 | dependencies = [ | ||
| 2115 | "extended", | ||
| 2116 | "log", | ||
| 2117 | "symphonia-core", | ||
| 2118 | "symphonia-metadata", | ||
| 2119 | ] | ||
| 2120 | |||
| 2121 | [[package]] | ||
| 2122 | name = "symphonia-metadata" | ||
| 2123 | version = "0.5.5" | ||
| 2124 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2125 | checksum = "36306ff42b9ffe6e5afc99d49e121e0bd62fe79b9db7b9681d48e29fa19e6b16" | ||
| 2126 | dependencies = [ | ||
| 2127 | "encoding_rs", | ||
| 2128 | "lazy_static", | ||
| 2129 | "log", | ||
| 2130 | "symphonia-core", | ||
| 2131 | ] | ||
| 2132 | |||
| 2133 | [[package]] | ||
| 2134 | name = "symphonia-utils-xiph" | ||
| 2135 | version = "0.5.5" | ||
| 2136 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2137 | checksum = "ee27c85ab799a338446b68eec77abf42e1a6f1bb490656e121c6e27bfbab9f16" | ||
| 2138 | dependencies = [ | ||
| 2139 | "symphonia-core", | ||
| 2140 | "symphonia-metadata", | ||
| 2141 | ] | ||
| 2142 | |||
| 2143 | [[package]] | ||
| 2144 | name = "syn" | ||
| 2145 | version = "2.0.117" | ||
| 2146 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2147 | checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" | ||
| 2148 | dependencies = [ | ||
| 2149 | "proc-macro2", | ||
| 2150 | "quote", | ||
| 2151 | "unicode-ident", | ||
| 2152 | ] | ||
| 2153 | |||
| 2154 | [[package]] | ||
| 2155 | name = "sync_wrapper" | ||
| 2156 | version = "1.0.2" | ||
| 2157 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2158 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" | ||
| 2159 | dependencies = [ | ||
| 2160 | "futures-core", | ||
| 2161 | ] | ||
| 2162 | |||
| 2163 | [[package]] | ||
| 2164 | name = "synstructure" | ||
| 2165 | version = "0.13.2" | ||
| 2166 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2167 | checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" | ||
| 2168 | dependencies = [ | ||
| 2169 | "proc-macro2", | ||
| 2170 | "quote", | ||
| 2171 | "syn", | ||
| 2172 | ] | ||
| 2173 | |||
| 2174 | [[package]] | ||
| 2175 | name = "system-configuration" | ||
| 2176 | version = "0.7.0" | ||
| 2177 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2178 | checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" | ||
| 2179 | dependencies = [ | ||
| 2180 | "bitflags 2.11.1", | ||
| 2181 | "core-foundation 0.9.4", | ||
| 2182 | "system-configuration-sys", | ||
| 2183 | ] | ||
| 2184 | |||
| 2185 | [[package]] | ||
| 2186 | name = "system-configuration-sys" | ||
| 2187 | version = "0.6.0" | ||
| 2188 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2189 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" | ||
| 2190 | dependencies = [ | ||
| 2191 | "core-foundation-sys", | ||
| 2192 | "libc", | ||
| 2193 | ] | ||
| 2194 | |||
| 2195 | [[package]] | ||
| 2196 | name = "thiserror" | ||
| 2197 | version = "1.0.69" | ||
| 2198 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2199 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" | ||
| 2200 | dependencies = [ | ||
| 2201 | "thiserror-impl 1.0.69", | ||
| 2202 | ] | ||
| 2203 | |||
| 2204 | [[package]] | ||
| 2205 | name = "thiserror" | ||
| 2206 | version = "2.0.18" | ||
| 2207 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2208 | checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" | ||
| 2209 | dependencies = [ | ||
| 2210 | "thiserror-impl 2.0.18", | ||
| 2211 | ] | ||
| 2212 | |||
| 2213 | [[package]] | ||
| 2214 | name = "thiserror-impl" | ||
| 2215 | version = "1.0.69" | ||
| 2216 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2217 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" | ||
| 2218 | dependencies = [ | ||
| 2219 | "proc-macro2", | ||
| 2220 | "quote", | ||
| 2221 | "syn", | ||
| 2222 | ] | ||
| 2223 | |||
| 2224 | [[package]] | ||
| 2225 | name = "thiserror-impl" | ||
| 2226 | version = "2.0.18" | ||
| 2227 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2228 | checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" | ||
| 2229 | dependencies = [ | ||
| 2230 | "proc-macro2", | ||
| 2231 | "quote", | ||
| 2232 | "syn", | ||
| 2233 | ] | ||
| 2234 | |||
| 2235 | [[package]] | ||
| 2236 | name = "tinystr" | ||
| 2237 | version = "0.8.3" | ||
| 2238 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2239 | checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" | ||
| 2240 | dependencies = [ | ||
| 2241 | "displaydoc", | ||
| 2242 | "zerovec", | ||
| 2243 | ] | ||
| 2244 | |||
| 2245 | [[package]] | ||
| 2246 | name = "tinyvec" | ||
| 2247 | version = "1.11.0" | ||
| 2248 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2249 | checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" | ||
| 2250 | dependencies = [ | ||
| 2251 | "tinyvec_macros", | ||
| 2252 | ] | ||
| 2253 | |||
| 2254 | [[package]] | ||
| 2255 | name = "tinyvec_macros" | ||
| 2256 | version = "0.1.1" | ||
| 2257 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2258 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" | ||
| 2259 | |||
| 2260 | [[package]] | ||
| 2261 | name = "tokio" | ||
| 2262 | version = "1.52.2" | ||
| 2263 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2264 | checksum = "110a78583f19d5cdb2c5ccf321d1290344e71313c6c37d43520d386027d18386" | ||
| 2265 | dependencies = [ | ||
| 2266 | "bytes", | ||
| 2267 | "libc", | ||
| 2268 | "mio", | ||
| 2269 | "parking_lot", | ||
| 2270 | "pin-project-lite", | ||
| 2271 | "signal-hook-registry", | ||
| 2272 | "socket2", | ||
| 2273 | "tokio-macros", | ||
| 2274 | "windows-sys 0.61.2", | ||
| 2275 | ] | ||
| 2276 | |||
| 2277 | [[package]] | ||
| 2278 | name = "tokio-macros" | ||
| 2279 | version = "2.7.0" | ||
| 2280 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2281 | checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" | ||
| 2282 | dependencies = [ | ||
| 2283 | "proc-macro2", | ||
| 2284 | "quote", | ||
| 2285 | "syn", | ||
| 2286 | ] | ||
| 2287 | |||
| 2288 | [[package]] | ||
| 2289 | name = "tokio-rustls" | ||
| 2290 | version = "0.26.4" | ||
| 2291 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2292 | checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" | ||
| 2293 | dependencies = [ | ||
| 2294 | "rustls", | ||
| 2295 | "tokio", | ||
| 2296 | ] | ||
| 2297 | |||
| 2298 | [[package]] | ||
| 2299 | name = "tokio-util" | ||
| 2300 | version = "0.7.18" | ||
| 2301 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2302 | checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" | ||
| 2303 | dependencies = [ | ||
| 2304 | "bytes", | ||
| 2305 | "futures-core", | ||
| 2306 | "futures-sink", | ||
| 2307 | "pin-project-lite", | ||
| 2308 | "tokio", | ||
| 2309 | ] | ||
| 2310 | |||
| 2311 | [[package]] | ||
| 2312 | name = "toml" | ||
| 2313 | version = "1.1.2+spec-1.1.0" | ||
| 2314 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2315 | checksum = "81f3d15e84cbcd896376e6730314d59fb5a87f31e4b038454184435cd57defee" | ||
| 2316 | dependencies = [ | ||
| 2317 | "indexmap", | ||
| 2318 | "serde_core", | ||
| 2319 | "serde_spanned", | ||
| 2320 | "toml_datetime", | ||
| 2321 | "toml_parser", | ||
| 2322 | "toml_writer", | ||
| 2323 | "winnow", | ||
| 2324 | ] | ||
| 2325 | |||
| 2326 | [[package]] | ||
| 2327 | name = "toml_datetime" | ||
| 2328 | version = "1.1.1+spec-1.1.0" | ||
| 2329 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2330 | checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" | ||
| 2331 | dependencies = [ | ||
| 2332 | "serde_core", | ||
| 2333 | ] | ||
| 2334 | |||
| 2335 | [[package]] | ||
| 2336 | name = "toml_edit" | ||
| 2337 | version = "0.25.11+spec-1.1.0" | ||
| 2338 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2339 | checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" | ||
| 2340 | dependencies = [ | ||
| 2341 | "indexmap", | ||
| 2342 | "toml_datetime", | ||
| 2343 | "toml_parser", | ||
| 2344 | "winnow", | ||
| 2345 | ] | ||
| 2346 | |||
| 2347 | [[package]] | ||
| 2348 | name = "toml_parser" | ||
| 2349 | version = "1.1.2+spec-1.1.0" | ||
| 2350 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2351 | checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" | ||
| 2352 | dependencies = [ | ||
| 2353 | "winnow", | ||
| 2354 | ] | ||
| 2355 | |||
| 2356 | [[package]] | ||
| 2357 | name = "toml_writer" | ||
| 2358 | version = "1.1.1+spec-1.1.0" | ||
| 2359 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2360 | checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" | ||
| 2361 | |||
| 2362 | [[package]] | ||
| 2363 | name = "tower" | ||
| 2364 | version = "0.5.3" | ||
| 2365 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2366 | checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" | ||
| 2367 | dependencies = [ | ||
| 2368 | "futures-core", | ||
| 2369 | "futures-util", | ||
| 2370 | "pin-project-lite", | ||
| 2371 | "sync_wrapper", | ||
| 2372 | "tokio", | ||
| 2373 | "tower-layer", | ||
| 2374 | "tower-service", | ||
| 2375 | ] | ||
| 2376 | |||
| 2377 | [[package]] | ||
| 2378 | name = "tower-http" | ||
| 2379 | version = "0.6.8" | ||
| 2380 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2381 | checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" | ||
| 2382 | dependencies = [ | ||
| 2383 | "bitflags 2.11.1", | ||
| 2384 | "bytes", | ||
| 2385 | "futures-util", | ||
| 2386 | "http", | ||
| 2387 | "http-body", | ||
| 2388 | "iri-string", | ||
| 2389 | "pin-project-lite", | ||
| 2390 | "tower", | ||
| 2391 | "tower-layer", | ||
| 2392 | "tower-service", | ||
| 2393 | ] | ||
| 2394 | |||
| 2395 | [[package]] | ||
| 2396 | name = "tower-layer" | ||
| 2397 | version = "0.3.3" | ||
| 2398 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2399 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" | ||
| 2400 | |||
| 2401 | [[package]] | ||
| 2402 | name = "tower-service" | ||
| 2403 | version = "0.3.3" | ||
| 2404 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2405 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" | ||
| 2406 | |||
| 2407 | [[package]] | ||
| 2408 | name = "tracing" | ||
| 2409 | version = "0.1.44" | ||
| 2410 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2411 | checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" | ||
| 2412 | dependencies = [ | ||
| 2413 | "pin-project-lite", | ||
| 2414 | "tracing-core", | ||
| 2415 | ] | ||
| 2416 | |||
| 2417 | [[package]] | ||
| 2418 | name = "tracing-core" | ||
| 2419 | version = "0.1.36" | ||
| 2420 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2421 | checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" | ||
| 2422 | dependencies = [ | ||
| 2423 | "once_cell", | ||
| 2424 | ] | ||
| 2425 | |||
| 2426 | [[package]] | ||
| 2427 | name = "try-lock" | ||
| 2428 | version = "0.2.5" | ||
| 2429 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2430 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" | ||
| 2431 | |||
| 2432 | [[package]] | ||
| 2433 | name = "unicode-ident" | ||
| 2434 | version = "1.0.24" | ||
| 2435 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2436 | checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" | ||
| 2437 | |||
| 2438 | [[package]] | ||
| 2439 | name = "unicode-xid" | ||
| 2440 | version = "0.2.6" | ||
| 2441 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2442 | checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" | ||
| 2443 | |||
| 2444 | [[package]] | ||
| 2445 | name = "untrusted" | ||
| 2446 | version = "0.9.0" | ||
| 2447 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2448 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" | ||
| 2449 | |||
| 2450 | [[package]] | ||
| 2451 | name = "url" | ||
| 2452 | version = "2.5.8" | ||
| 2453 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2454 | checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" | ||
| 2455 | dependencies = [ | ||
| 2456 | "form_urlencoded", | ||
| 2457 | "idna", | ||
| 2458 | "percent-encoding", | ||
| 2459 | "serde", | ||
| 2460 | ] | ||
| 2461 | |||
| 2462 | [[package]] | ||
| 2463 | name = "utf8_iter" | ||
| 2464 | version = "1.0.4" | ||
| 2465 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2466 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" | ||
| 2467 | |||
| 2468 | [[package]] | ||
| 2469 | name = "utf8parse" | ||
| 2470 | version = "0.2.2" | ||
| 2471 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2472 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" | ||
| 2473 | |||
| 2474 | [[package]] | ||
| 2475 | name = "vcpkg" | ||
| 2476 | version = "0.2.15" | ||
| 2477 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2478 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" | ||
| 2479 | |||
| 2480 | [[package]] | ||
| 2481 | name = "walkdir" | ||
| 2482 | version = "2.5.0" | ||
| 2483 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2484 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" | ||
| 2485 | dependencies = [ | ||
| 2486 | "same-file", | ||
| 2487 | "winapi-util", | ||
| 2488 | ] | ||
| 2489 | |||
| 2490 | [[package]] | ||
| 2491 | name = "want" | ||
| 2492 | version = "0.3.1" | ||
| 2493 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2494 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" | ||
| 2495 | dependencies = [ | ||
| 2496 | "try-lock", | ||
| 2497 | ] | ||
| 2498 | |||
| 2499 | [[package]] | ||
| 2500 | name = "wasi" | ||
| 2501 | version = "0.11.1+wasi-snapshot-preview1" | ||
| 2502 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2503 | checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" | ||
| 2504 | |||
| 2505 | [[package]] | ||
| 2506 | name = "wasip2" | ||
| 2507 | version = "1.0.3+wasi-0.2.9" | ||
| 2508 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2509 | checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" | ||
| 2510 | dependencies = [ | ||
| 2511 | "wit-bindgen 0.57.1", | ||
| 2512 | ] | ||
| 2513 | |||
| 2514 | [[package]] | ||
| 2515 | name = "wasip3" | ||
| 2516 | version = "0.4.0+wasi-0.3.0-rc-2026-01-06" | ||
| 2517 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2518 | checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" | ||
| 2519 | dependencies = [ | ||
| 2520 | "wit-bindgen 0.51.0", | ||
| 2521 | ] | ||
| 2522 | |||
| 2523 | [[package]] | ||
| 2524 | name = "wasm-bindgen" | ||
| 2525 | version = "0.2.120" | ||
| 2526 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2527 | checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" | ||
| 2528 | dependencies = [ | ||
| 2529 | "cfg-if", | ||
| 2530 | "once_cell", | ||
| 2531 | "rustversion", | ||
| 2532 | "wasm-bindgen-macro", | ||
| 2533 | "wasm-bindgen-shared", | ||
| 2534 | ] | ||
| 2535 | |||
| 2536 | [[package]] | ||
| 2537 | name = "wasm-bindgen-futures" | ||
| 2538 | version = "0.4.70" | ||
| 2539 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2540 | checksum = "af934872acec734c2d80e6617bbb5ff4f12b052dd8e6332b0817bce889516084" | ||
| 2541 | dependencies = [ | ||
| 2542 | "js-sys", | ||
| 2543 | "wasm-bindgen", | ||
| 2544 | ] | ||
| 2545 | |||
| 2546 | [[package]] | ||
| 2547 | name = "wasm-bindgen-macro" | ||
| 2548 | version = "0.2.120" | ||
| 2549 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2550 | checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" | ||
| 2551 | dependencies = [ | ||
| 2552 | "quote", | ||
| 2553 | "wasm-bindgen-macro-support", | ||
| 2554 | ] | ||
| 2555 | |||
| 2556 | [[package]] | ||
| 2557 | name = "wasm-bindgen-macro-support" | ||
| 2558 | version = "0.2.120" | ||
| 2559 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2560 | checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" | ||
| 2561 | dependencies = [ | ||
| 2562 | "bumpalo", | ||
| 2563 | "proc-macro2", | ||
| 2564 | "quote", | ||
| 2565 | "syn", | ||
| 2566 | "wasm-bindgen-shared", | ||
| 2567 | ] | ||
| 2568 | |||
| 2569 | [[package]] | ||
| 2570 | name = "wasm-bindgen-shared" | ||
| 2571 | version = "0.2.120" | ||
| 2572 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2573 | checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" | ||
| 2574 | dependencies = [ | ||
| 2575 | "unicode-ident", | ||
| 2576 | ] | ||
| 2577 | |||
| 2578 | [[package]] | ||
| 2579 | name = "wasm-encoder" | ||
| 2580 | version = "0.244.0" | ||
| 2581 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2582 | checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" | ||
| 2583 | dependencies = [ | ||
| 2584 | "leb128fmt", | ||
| 2585 | "wasmparser", | ||
| 2586 | ] | ||
| 2587 | |||
| 2588 | [[package]] | ||
| 2589 | name = "wasm-metadata" | ||
| 2590 | version = "0.244.0" | ||
| 2591 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2592 | checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" | ||
| 2593 | dependencies = [ | ||
| 2594 | "anyhow", | ||
| 2595 | "indexmap", | ||
| 2596 | "wasm-encoder", | ||
| 2597 | "wasmparser", | ||
| 2598 | ] | ||
| 2599 | |||
| 2600 | [[package]] | ||
| 2601 | name = "wasmparser" | ||
| 2602 | version = "0.244.0" | ||
| 2603 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2604 | checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" | ||
| 2605 | dependencies = [ | ||
| 2606 | "bitflags 2.11.1", | ||
| 2607 | "hashbrown 0.15.5", | ||
| 2608 | "indexmap", | ||
| 2609 | "semver", | ||
| 2610 | ] | ||
| 2611 | |||
| 2612 | [[package]] | ||
| 2613 | name = "web-sys" | ||
| 2614 | version = "0.3.97" | ||
| 2615 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2616 | checksum = "2eadbac71025cd7b0834f20d1fe8472e8495821b4e9801eb0a60bd1f19827602" | ||
| 2617 | dependencies = [ | ||
| 2618 | "js-sys", | ||
| 2619 | "wasm-bindgen", | ||
| 2620 | ] | ||
| 2621 | |||
| 2622 | [[package]] | ||
| 2623 | name = "web-time" | ||
| 2624 | version = "1.1.0" | ||
| 2625 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2626 | checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" | ||
| 2627 | dependencies = [ | ||
| 2628 | "js-sys", | ||
| 2629 | "wasm-bindgen", | ||
| 2630 | ] | ||
| 2631 | |||
| 2632 | [[package]] | ||
| 2633 | name = "webpki-root-certs" | ||
| 2634 | version = "1.0.7" | ||
| 2635 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2636 | checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" | ||
| 2637 | dependencies = [ | ||
| 2638 | "rustls-pki-types", | ||
| 2639 | ] | ||
| 2640 | |||
| 2641 | [[package]] | ||
| 2642 | name = "winapi-util" | ||
| 2643 | version = "0.1.11" | ||
| 2644 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2645 | checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" | ||
| 2646 | dependencies = [ | ||
| 2647 | "windows-sys 0.61.2", | ||
| 2648 | ] | ||
| 2649 | |||
| 2650 | [[package]] | ||
| 2651 | name = "windows" | ||
| 2652 | version = "0.62.2" | ||
| 2653 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2654 | checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" | ||
| 2655 | dependencies = [ | ||
| 2656 | "windows-collections", | ||
| 2657 | "windows-core", | ||
| 2658 | "windows-future", | ||
| 2659 | "windows-numerics", | ||
| 2660 | ] | ||
| 2661 | |||
| 2662 | [[package]] | ||
| 2663 | name = "windows-collections" | ||
| 2664 | version = "0.3.2" | ||
| 2665 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2666 | checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" | ||
| 2667 | dependencies = [ | ||
| 2668 | "windows-core", | ||
| 2669 | ] | ||
| 2670 | |||
| 2671 | [[package]] | ||
| 2672 | name = "windows-core" | ||
| 2673 | version = "0.62.2" | ||
| 2674 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2675 | checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" | ||
| 2676 | dependencies = [ | ||
| 2677 | "windows-implement", | ||
| 2678 | "windows-interface", | ||
| 2679 | "windows-link", | ||
| 2680 | "windows-result", | ||
| 2681 | "windows-strings", | ||
| 2682 | ] | ||
| 2683 | |||
| 2684 | [[package]] | ||
| 2685 | name = "windows-future" | ||
| 2686 | version = "0.3.2" | ||
| 2687 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2688 | checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" | ||
| 2689 | dependencies = [ | ||
| 2690 | "windows-core", | ||
| 2691 | "windows-link", | ||
| 2692 | "windows-threading", | ||
| 2693 | ] | ||
| 2694 | |||
| 2695 | [[package]] | ||
| 2696 | name = "windows-implement" | ||
| 2697 | version = "0.60.2" | ||
| 2698 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2699 | checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" | ||
| 2700 | dependencies = [ | ||
| 2701 | "proc-macro2", | ||
| 2702 | "quote", | ||
| 2703 | "syn", | ||
| 2704 | ] | ||
| 2705 | |||
| 2706 | [[package]] | ||
| 2707 | name = "windows-interface" | ||
| 2708 | version = "0.59.3" | ||
| 2709 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2710 | checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" | ||
| 2711 | dependencies = [ | ||
| 2712 | "proc-macro2", | ||
| 2713 | "quote", | ||
| 2714 | "syn", | ||
| 2715 | ] | ||
| 2716 | |||
| 2717 | [[package]] | ||
| 2718 | name = "windows-link" | ||
| 2719 | version = "0.2.1" | ||
| 2720 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2721 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" | ||
| 2722 | |||
| 2723 | [[package]] | ||
| 2724 | name = "windows-numerics" | ||
| 2725 | version = "0.3.1" | ||
| 2726 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2727 | checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" | ||
| 2728 | dependencies = [ | ||
| 2729 | "windows-core", | ||
| 2730 | "windows-link", | ||
| 2731 | ] | ||
| 2732 | |||
| 2733 | [[package]] | ||
| 2734 | name = "windows-registry" | ||
| 2735 | version = "0.6.1" | ||
| 2736 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2737 | checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" | ||
| 2738 | dependencies = [ | ||
| 2739 | "windows-link", | ||
| 2740 | "windows-result", | ||
| 2741 | "windows-strings", | ||
| 2742 | ] | ||
| 2743 | |||
| 2744 | [[package]] | ||
| 2745 | name = "windows-result" | ||
| 2746 | version = "0.4.1" | ||
| 2747 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2748 | checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" | ||
| 2749 | dependencies = [ | ||
| 2750 | "windows-link", | ||
| 2751 | ] | ||
| 2752 | |||
| 2753 | [[package]] | ||
| 2754 | name = "windows-strings" | ||
| 2755 | version = "0.5.1" | ||
| 2756 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2757 | checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" | ||
| 2758 | dependencies = [ | ||
| 2759 | "windows-link", | ||
| 2760 | ] | ||
| 2761 | |||
| 2762 | [[package]] | ||
| 2763 | name = "windows-sys" | ||
| 2764 | version = "0.45.0" | ||
| 2765 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2766 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" | ||
| 2767 | dependencies = [ | ||
| 2768 | "windows-targets 0.42.2", | ||
| 2769 | ] | ||
| 2770 | |||
| 2771 | [[package]] | ||
| 2772 | name = "windows-sys" | ||
| 2773 | version = "0.52.0" | ||
| 2774 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2775 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" | ||
| 2776 | dependencies = [ | ||
| 2777 | "windows-targets 0.52.6", | ||
| 2778 | ] | ||
| 2779 | |||
| 2780 | [[package]] | ||
| 2781 | name = "windows-sys" | ||
| 2782 | version = "0.61.2" | ||
| 2783 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2784 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" | ||
| 2785 | dependencies = [ | ||
| 2786 | "windows-link", | ||
| 2787 | ] | ||
| 2788 | |||
| 2789 | [[package]] | ||
| 2790 | name = "windows-targets" | ||
| 2791 | version = "0.42.2" | ||
| 2792 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2793 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" | ||
| 2794 | dependencies = [ | ||
| 2795 | "windows_aarch64_gnullvm 0.42.2", | ||
| 2796 | "windows_aarch64_msvc 0.42.2", | ||
| 2797 | "windows_i686_gnu 0.42.2", | ||
| 2798 | "windows_i686_msvc 0.42.2", | ||
| 2799 | "windows_x86_64_gnu 0.42.2", | ||
| 2800 | "windows_x86_64_gnullvm 0.42.2", | ||
| 2801 | "windows_x86_64_msvc 0.42.2", | ||
| 2802 | ] | ||
| 2803 | |||
| 2804 | [[package]] | ||
| 2805 | name = "windows-targets" | ||
| 2806 | version = "0.52.6" | ||
| 2807 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2808 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" | ||
| 2809 | dependencies = [ | ||
| 2810 | "windows_aarch64_gnullvm 0.52.6", | ||
| 2811 | "windows_aarch64_msvc 0.52.6", | ||
| 2812 | "windows_i686_gnu 0.52.6", | ||
| 2813 | "windows_i686_gnullvm", | ||
| 2814 | "windows_i686_msvc 0.52.6", | ||
| 2815 | "windows_x86_64_gnu 0.52.6", | ||
| 2816 | "windows_x86_64_gnullvm 0.52.6", | ||
| 2817 | "windows_x86_64_msvc 0.52.6", | ||
| 2818 | ] | ||
| 2819 | |||
| 2820 | [[package]] | ||
| 2821 | name = "windows-threading" | ||
| 2822 | version = "0.2.1" | ||
| 2823 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2824 | checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" | ||
| 2825 | dependencies = [ | ||
| 2826 | "windows-link", | ||
| 2827 | ] | ||
| 2828 | |||
| 2829 | [[package]] | ||
| 2830 | name = "windows_aarch64_gnullvm" | ||
| 2831 | version = "0.42.2" | ||
| 2832 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2833 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" | ||
| 2834 | |||
| 2835 | [[package]] | ||
| 2836 | name = "windows_aarch64_gnullvm" | ||
| 2837 | version = "0.52.6" | ||
| 2838 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2839 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" | ||
| 2840 | |||
| 2841 | [[package]] | ||
| 2842 | name = "windows_aarch64_msvc" | ||
| 2843 | version = "0.42.2" | ||
| 2844 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2845 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" | ||
| 2846 | |||
| 2847 | [[package]] | ||
| 2848 | name = "windows_aarch64_msvc" | ||
| 2849 | version = "0.52.6" | ||
| 2850 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2851 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" | ||
| 2852 | |||
| 2853 | [[package]] | ||
| 2854 | name = "windows_i686_gnu" | ||
| 2855 | version = "0.42.2" | ||
| 2856 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2857 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" | ||
| 2858 | |||
| 2859 | [[package]] | ||
| 2860 | name = "windows_i686_gnu" | ||
| 2861 | version = "0.52.6" | ||
| 2862 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2863 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" | ||
| 2864 | |||
| 2865 | [[package]] | ||
| 2866 | name = "windows_i686_gnullvm" | ||
| 2867 | version = "0.52.6" | ||
| 2868 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2869 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" | ||
| 2870 | |||
| 2871 | [[package]] | ||
| 2872 | name = "windows_i686_msvc" | ||
| 2873 | version = "0.42.2" | ||
| 2874 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2875 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" | ||
| 2876 | |||
| 2877 | [[package]] | ||
| 2878 | name = "windows_i686_msvc" | ||
| 2879 | version = "0.52.6" | ||
| 2880 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2881 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" | ||
| 2882 | |||
| 2883 | [[package]] | ||
| 2884 | name = "windows_x86_64_gnu" | ||
| 2885 | version = "0.42.2" | ||
| 2886 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2887 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" | ||
| 2888 | |||
| 2889 | [[package]] | ||
| 2890 | name = "windows_x86_64_gnu" | ||
| 2891 | version = "0.52.6" | ||
| 2892 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2893 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" | ||
| 2894 | |||
| 2895 | [[package]] | ||
| 2896 | name = "windows_x86_64_gnullvm" | ||
| 2897 | version = "0.42.2" | ||
| 2898 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2899 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" | ||
| 2900 | |||
| 2901 | [[package]] | ||
| 2902 | name = "windows_x86_64_gnullvm" | ||
| 2903 | version = "0.52.6" | ||
| 2904 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2905 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" | ||
| 2906 | |||
| 2907 | [[package]] | ||
| 2908 | name = "windows_x86_64_msvc" | ||
| 2909 | version = "0.42.2" | ||
| 2910 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2911 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" | ||
| 2912 | |||
| 2913 | [[package]] | ||
| 2914 | name = "windows_x86_64_msvc" | ||
| 2915 | version = "0.52.6" | ||
| 2916 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2917 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" | ||
| 2918 | |||
| 2919 | [[package]] | ||
| 2920 | name = "winnow" | ||
| 2921 | version = "1.0.2" | ||
| 2922 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2923 | checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" | ||
| 2924 | dependencies = [ | ||
| 2925 | "memchr", | ||
| 2926 | ] | ||
| 2927 | |||
| 2928 | [[package]] | ||
| 2929 | name = "wit-bindgen" | ||
| 2930 | version = "0.51.0" | ||
| 2931 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2932 | checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" | ||
| 2933 | dependencies = [ | ||
| 2934 | "wit-bindgen-rust-macro", | ||
| 2935 | ] | ||
| 2936 | |||
| 2937 | [[package]] | ||
| 2938 | name = "wit-bindgen" | ||
| 2939 | version = "0.57.1" | ||
| 2940 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2941 | checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" | ||
| 2942 | |||
| 2943 | [[package]] | ||
| 2944 | name = "wit-bindgen-core" | ||
| 2945 | version = "0.51.0" | ||
| 2946 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2947 | checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" | ||
| 2948 | dependencies = [ | ||
| 2949 | "anyhow", | ||
| 2950 | "heck", | ||
| 2951 | "wit-parser", | ||
| 2952 | ] | ||
| 2953 | |||
| 2954 | [[package]] | ||
| 2955 | name = "wit-bindgen-rust" | ||
| 2956 | version = "0.51.0" | ||
| 2957 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2958 | checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" | ||
| 2959 | dependencies = [ | ||
| 2960 | "anyhow", | ||
| 2961 | "heck", | ||
| 2962 | "indexmap", | ||
| 2963 | "prettyplease", | ||
| 2964 | "syn", | ||
| 2965 | "wasm-metadata", | ||
| 2966 | "wit-bindgen-core", | ||
| 2967 | "wit-component", | ||
| 2968 | ] | ||
| 2969 | |||
| 2970 | [[package]] | ||
| 2971 | name = "wit-bindgen-rust-macro" | ||
| 2972 | version = "0.51.0" | ||
| 2973 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2974 | checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" | ||
| 2975 | dependencies = [ | ||
| 2976 | "anyhow", | ||
| 2977 | "prettyplease", | ||
| 2978 | "proc-macro2", | ||
| 2979 | "quote", | ||
| 2980 | "syn", | ||
| 2981 | "wit-bindgen-core", | ||
| 2982 | "wit-bindgen-rust", | ||
| 2983 | ] | ||
| 2984 | |||
| 2985 | [[package]] | ||
| 2986 | name = "wit-component" | ||
| 2987 | version = "0.244.0" | ||
| 2988 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 2989 | checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" | ||
| 2990 | dependencies = [ | ||
| 2991 | "anyhow", | ||
| 2992 | "bitflags 2.11.1", | ||
| 2993 | "indexmap", | ||
| 2994 | "log", | ||
| 2995 | "serde", | ||
| 2996 | "serde_derive", | ||
| 2997 | "serde_json", | ||
| 2998 | "wasm-encoder", | ||
| 2999 | "wasm-metadata", | ||
| 3000 | "wasmparser", | ||
| 3001 | "wit-parser", | ||
| 3002 | ] | ||
| 3003 | |||
| 3004 | [[package]] | ||
| 3005 | name = "wit-parser" | ||
| 3006 | version = "0.244.0" | ||
| 3007 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3008 | checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" | ||
| 3009 | dependencies = [ | ||
| 3010 | "anyhow", | ||
| 3011 | "id-arena", | ||
| 3012 | "indexmap", | ||
| 3013 | "log", | ||
| 3014 | "semver", | ||
| 3015 | "serde", | ||
| 3016 | "serde_derive", | ||
| 3017 | "serde_json", | ||
| 3018 | "unicode-xid", | ||
| 3019 | "wasmparser", | ||
| 3020 | ] | ||
| 3021 | |||
| 3022 | [[package]] | ||
| 3023 | name = "writeable" | ||
| 3024 | version = "0.6.3" | ||
| 3025 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3026 | checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" | ||
| 3027 | |||
| 3028 | [[package]] | ||
| 3029 | name = "yoke" | ||
| 3030 | version = "0.8.2" | ||
| 3031 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3032 | checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" | ||
| 3033 | dependencies = [ | ||
| 3034 | "stable_deref_trait", | ||
| 3035 | "yoke-derive", | ||
| 3036 | "zerofrom", | ||
| 3037 | ] | ||
| 3038 | |||
| 3039 | [[package]] | ||
| 3040 | name = "yoke-derive" | ||
| 3041 | version = "0.8.2" | ||
| 3042 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3043 | checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" | ||
| 3044 | dependencies = [ | ||
| 3045 | "proc-macro2", | ||
| 3046 | "quote", | ||
| 3047 | "syn", | ||
| 3048 | "synstructure", | ||
| 3049 | ] | ||
| 3050 | |||
| 3051 | [[package]] | ||
| 3052 | name = "zerocopy" | ||
| 3053 | version = "0.8.48" | ||
| 3054 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3055 | checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" | ||
| 3056 | dependencies = [ | ||
| 3057 | "zerocopy-derive", | ||
| 3058 | ] | ||
| 3059 | |||
| 3060 | [[package]] | ||
| 3061 | name = "zerocopy-derive" | ||
| 3062 | version = "0.8.48" | ||
| 3063 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3064 | checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" | ||
| 3065 | dependencies = [ | ||
| 3066 | "proc-macro2", | ||
| 3067 | "quote", | ||
| 3068 | "syn", | ||
| 3069 | ] | ||
| 3070 | |||
| 3071 | [[package]] | ||
| 3072 | name = "zerofrom" | ||
| 3073 | version = "0.1.7" | ||
| 3074 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3075 | checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" | ||
| 3076 | dependencies = [ | ||
| 3077 | "zerofrom-derive", | ||
| 3078 | ] | ||
| 3079 | |||
| 3080 | [[package]] | ||
| 3081 | name = "zerofrom-derive" | ||
| 3082 | version = "0.1.7" | ||
| 3083 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3084 | checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" | ||
| 3085 | dependencies = [ | ||
| 3086 | "proc-macro2", | ||
| 3087 | "quote", | ||
| 3088 | "syn", | ||
| 3089 | "synstructure", | ||
| 3090 | ] | ||
| 3091 | |||
| 3092 | [[package]] | ||
| 3093 | name = "zeroize" | ||
| 3094 | version = "1.8.2" | ||
| 3095 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3096 | checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" | ||
| 3097 | |||
| 3098 | [[package]] | ||
| 3099 | name = "zerotrie" | ||
| 3100 | version = "0.2.4" | ||
| 3101 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3102 | checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" | ||
| 3103 | dependencies = [ | ||
| 3104 | "displaydoc", | ||
| 3105 | "yoke", | ||
| 3106 | "zerofrom", | ||
| 3107 | ] | ||
| 3108 | |||
| 3109 | [[package]] | ||
| 3110 | name = "zerovec" | ||
| 3111 | version = "0.11.6" | ||
| 3112 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3113 | checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" | ||
| 3114 | dependencies = [ | ||
| 3115 | "yoke", | ||
| 3116 | "zerofrom", | ||
| 3117 | "zerovec-derive", | ||
| 3118 | ] | ||
| 3119 | |||
| 3120 | [[package]] | ||
| 3121 | name = "zerovec-derive" | ||
| 3122 | version = "0.11.3" | ||
| 3123 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3124 | checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" | ||
| 3125 | dependencies = [ | ||
| 3126 | "proc-macro2", | ||
| 3127 | "quote", | ||
| 3128 | "syn", | ||
| 3129 | ] | ||
| 3130 | |||
| 3131 | [[package]] | ||
| 3132 | name = "zmij" | ||
| 3133 | version = "1.0.21" | ||
| 3134 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 3135 | checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" | ||
diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0ceee6f --- /dev/null +++ b/Cargo.toml | |||
| @@ -0,0 +1,21 @@ | |||
| 1 | [package] | ||
| 2 | name = "rsp" | ||
| 3 | version = "0.1.0" | ||
| 4 | edition = "2021" | ||
| 5 | |||
| 6 | [dependencies] | ||
| 7 | clap = { version = "4.6.1", features = ["derive", "string"] } | ||
| 8 | rusqlite = { version = "0.39", features = ["bundled"] } | ||
| 9 | directories = "6.0" | ||
| 10 | serde = { version = "1.0.228", features = ["derive"] } | ||
| 11 | toml = "1.1.2" | ||
| 12 | tokio = { version = "1.52.2", features = ["full"] } | ||
| 13 | reqwest = { version = "0.13.3", features = ["json"] } | ||
| 14 | symphonia = { version = "0.5.5", features = ["all"] } | ||
| 15 | cpal = "0.17.3" | ||
| 16 | chrono = "0.4.44" | ||
| 17 | serde_json = "1.0.149" | ||
| 18 | dirs = "6.0" | ||
| 19 | libc = "0.2.186" | ||
| 20 | ctrlc = "3.5.2" | ||
| 21 | rand = "0.10.1" | ||
| @@ -0,0 +1,24 @@ | |||
| 1 | # rsplayer Project Plan | ||
| 2 | |||
| 3 | ## Phase 1: Setup & Core | ||
| 4 | 1. Initialize Rust project, add dependencies: `clap` (CLI/autocompletion), `rusqlite` (SQLite), `symphonia`/`cpal` (audio), `directories` (XDG paths), `serde`/`toml` (config), `tokio` (daemon async), `reqwest` (scrobbling). | ||
| 5 | 2. Config stored at `$XDG_CONFIG_HOME/rsplayer/config.toml`: `music_paths` (array of directories to scan), `scan_interval` (optional periodic rescan interval), scrobbling credentials, daemon socket path. Daemon auto-scans configured paths on first run if database is empty/missing. | ||
| 6 | 3. SQLite library schema: `artists`, `albums`, `tracks` tables. Implement `rsp scan` to scan all paths in config's `music_paths`, populate/update database. Support `--force` full rescan, `--paths` override config paths. Auto-scan triggers on first run (if DB empty) and periodically per `scan_interval` config. | ||
| 7 | |||
| 8 | ## Phase 2: Daemon | ||
| 9 | 1. Unix socket daemon (path: `$XDG_RUNTIME_DIR/rsplayer/daemon.sock`) for IPC. | ||
| 10 | 2. Audio engine: Symphonia decode + CPAL output, support play/pause/stop/volume/seek. | ||
| 11 | 3. Queue: in-memory, `last` (append to end, default), `now` (play immediately, ending current song playback), `next` (insert after current). | ||
| 12 | 4. Shuffle modes: `none` (linear playback), `all` (shuffle all tracks), `album` (shuffle within album). | ||
| 13 | 5. Scrobbling: Last.fm and ListenBrainz support via configured credentials. | ||
| 14 | |||
| 15 | ## Phase 3: CLI & Completion | ||
| 16 | 1. Clap-based CLI with all subcommands. Generate shell completions for bash/zsh/fish, suggest database artists/albums/tracks, file paths, and config keys/values. | ||
| 17 | 2. `rsp status`: show player state, current track, progress, volume, next 2-3 queue items. | ||
| 18 | 3. `rsp queue`: no arguments display current queue; with arguments add items (default `last` behavior). | ||
| 19 | 4. `rsp settings` subcommands: `get`, `set`, `list`, `unset` config values (music paths, scrobbling credentials, scan interval, etc.) without manual file editing. | ||
| 20 | |||
| 21 | ## Phase 4: Polish | ||
| 22 | 1. Direct file/folder playback: prioritize database metadata if path matches known artist/album. | ||
| 23 | 2. Logging, systemd service unit, unit tests. | ||
| 24 | 3. Periodic rescan logic per config's `scan_interval`. | ||
| @@ -0,0 +1,51 @@ | |||
| 1 | # TODO | ||
| 2 | |||
| 3 | ## Completed ✓ | ||
| 4 | |||
| 5 | - [x] **Fix SampleRate access in audio.rs:104** - `config.sample_rate().0` is invalid because `cpal::SampleRate` is a type alias for `u32`, not a tuple struct. | ||
| 6 | |||
| 7 | - [x] **Implement previous track logic** (`src/playback.rs:99`) - Implemented basic previous() that restarts current track if playing/paused. | ||
| 8 | |||
| 9 | - [x] **Remove unused std::thread import** (`src/playback.rs:2`) - Removed commented out import. | ||
| 10 | |||
| 11 | - [x] **Run clippy** - All linting checks pass. | ||
| 12 | |||
| 13 | - [x] **Run tests** - All tests pass (no tests exist yet). | ||
| 14 | |||
| 15 | - [x] **Check formatting** - Code formatting is correct. | ||
| 16 | |||
| 17 | - [x] **Implement resume functionality** - Implemented pause/resume using cpal stream pause()/play() methods. | ||
| 18 | |||
| 19 | - [x] **Fix audio playback speed and buffer underrun** - Implemented proper sample buffering between callbacks to prevent audio skipping and buffer underrun errors. | ||
| 20 | |||
| 21 | - [x] **Add Resume CLI subcommand** - Added `Resume` subcommand to CLI so users can resume playback after pause. | ||
| 22 | |||
| 23 | - [x] **Implement shuffle mode** - Added shuffle on/off/album functionality with queue management in daemon. Supports `rsplayer shuffle on/off/album` commands. | ||
| 24 | |||
| 25 | - [x] **Implement next/previous commands** - Next and previous track commands now work with the queue system. | ||
| 26 | |||
| 27 | - [x] **Queue entire directory** - When playing a folder, all audio files are now queued instead of just the first one. | ||
| 28 | |||
| 29 | - [x] **Auto-play next track** - Daemon automatically plays next track when current one ends (using `poll_track_ended()`). | ||
| 30 | |||
| 31 | - [x] **Queue display with pager** - `rsplayer queue` shows all queued tracks with pager support for long lists. | ||
| 32 | |||
| 33 | - [x] **Album shuffle mode structure** - `ShuffleMode::Album` added to enum and queue logic (behaves like `All` until database ready). | ||
| 34 | |||
| 35 | ## Potential Improvements | ||
| 36 | |||
| 37 | - [ ] **Add error handling for audio playback** - Consider more robust error handling in the audio thread. | ||
| 38 | |||
| 39 | - [ ] **Add unit tests for Player struct** - The `Player` struct in `playback.rs` has no corresponding tests. | ||
| 40 | |||
| 41 | - [ ] **Persist queue state** - Save queue to disk so it survives daemon restarts. | ||
| 42 | |||
| 43 | - [ ] **Album shuffle mode (database)** - Implement proper `ShuffleMode::Album` when database is ready (shuffle tracks within same album/folder). | ||
| 44 | |||
| 45 | - [ ] **Scrobbling (Last.fm/ListenBrainz)** - Add scrobbling support via configured credentials. | ||
| 46 | |||
| 47 | - [ ] **Database integration** - Scan music paths, populate SQLite database, use for metadata. | ||
| 48 | |||
| 49 | - [ ] **Shell completion scripts** - Generate bash/zsh/fish completions using clap. | ||
| 50 | |||
| 51 | - [ ] **Systemd service unit** - Create systemd service file for auto-start. | ||
diff --git a/src/audio.rs b/src/audio.rs new file mode 100644 index 0000000..86c88f7 --- /dev/null +++ b/src/audio.rs | |||
| @@ -0,0 +1,418 @@ | |||
| 1 | use std::path::PathBuf; | ||
| 2 | use std::sync::atomic::{AtomicBool, Ordering}; | ||
| 3 | use std::sync::mpsc::{self, Receiver, Sender}; | ||
| 4 | use std::sync::{Arc, Mutex}; | ||
| 5 | use std::thread; | ||
| 6 | |||
| 7 | #[allow(dead_code)] | ||
| 8 | pub enum AudioCommand { | ||
| 9 | Play(PathBuf), | ||
| 10 | Pause, | ||
| 11 | Resume, | ||
| 12 | Stop, | ||
| 13 | SetVolume(f32), | ||
| 14 | } | ||
| 15 | |||
| 16 | pub struct AudioEngine { | ||
| 17 | command_tx: Sender<AudioCommand>, | ||
| 18 | is_playing: Arc<AtomicBool>, | ||
| 19 | current_track: Arc<Mutex<Option<std::path::PathBuf>>>, | ||
| 20 | track_ended: Arc<AtomicBool>, | ||
| 21 | } | ||
| 22 | |||
| 23 | impl AudioEngine { | ||
| 24 | pub fn new() -> Self { | ||
| 25 | let (tx, rx) = mpsc::channel(); | ||
| 26 | let is_playing = Arc::new(AtomicBool::new(false)); | ||
| 27 | let is_playing_clone = Arc::clone(&is_playing); | ||
| 28 | let current_track = Arc::new(Mutex::new(None)); | ||
| 29 | let current_track_clone = Arc::clone(¤t_track); | ||
| 30 | let track_ended = Arc::new(AtomicBool::new(false)); | ||
| 31 | let track_ended_clone = Arc::clone(&track_ended); | ||
| 32 | |||
| 33 | let volume = Arc::new(Mutex::new(1.0f32)); | ||
| 34 | let volume_clone = Arc::clone(&volume); | ||
| 35 | |||
| 36 | thread::spawn(move || { | ||
| 37 | audio_thread( | ||
| 38 | rx, | ||
| 39 | is_playing_clone, | ||
| 40 | current_track_clone, | ||
| 41 | volume_clone, | ||
| 42 | track_ended_clone, | ||
| 43 | ); | ||
| 44 | }); | ||
| 45 | |||
| 46 | AudioEngine { | ||
| 47 | command_tx: tx, | ||
| 48 | is_playing, | ||
| 49 | current_track, | ||
| 50 | track_ended, | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | pub fn play(&self, path: PathBuf) { | ||
| 55 | self.track_ended.store(false, Ordering::SeqCst); | ||
| 56 | if let Ok(mut track) = self.current_track.lock() { | ||
| 57 | *track = Some(path.clone()); | ||
| 58 | } | ||
| 59 | let _ = self.command_tx.send(AudioCommand::Play(path)); | ||
| 60 | } | ||
| 61 | |||
| 62 | pub fn pause(&self) { | ||
| 63 | let _ = self.command_tx.send(AudioCommand::Pause); | ||
| 64 | } | ||
| 65 | |||
| 66 | pub fn resume(&self) { | ||
| 67 | let _ = self.command_tx.send(AudioCommand::Resume); | ||
| 68 | } | ||
| 69 | |||
| 70 | pub fn stop(&self) { | ||
| 71 | let _ = self.command_tx.send(AudioCommand::Stop); | ||
| 72 | } | ||
| 73 | |||
| 74 | pub fn set_volume(&self, volume: f32) { | ||
| 75 | let _ = self.command_tx.send(AudioCommand::SetVolume(volume)); | ||
| 76 | } | ||
| 77 | |||
| 78 | pub fn is_playing(&self) -> bool { | ||
| 79 | self.is_playing.load(Ordering::SeqCst) | ||
| 80 | } | ||
| 81 | |||
| 82 | pub fn current_track(&self) -> Option<std::path::PathBuf> { | ||
| 83 | self.current_track.lock().ok().and_then(|g| g.clone()) | ||
| 84 | } | ||
| 85 | |||
| 86 | pub fn poll_track_ended(&self) -> bool { | ||
| 87 | self.track_ended.swap(false, Ordering::SeqCst) | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | struct AudioData { | ||
| 92 | format: Box<dyn symphonia::core::formats::FormatReader>, | ||
| 93 | decoder: Box<dyn symphonia::core::codecs::Decoder>, | ||
| 94 | sample_rate: u32, | ||
| 95 | channels: u16, | ||
| 96 | sample_buffer: Vec<f32>, | ||
| 97 | buffer_pos: usize, | ||
| 98 | resample_ratio: Option<f64>, | ||
| 99 | } | ||
| 100 | |||
| 101 | fn resample_linear(input: &[f32], ratio: f64, channels: usize) -> Vec<f32> { | ||
| 102 | if ratio == 1.0 { | ||
| 103 | return input.to_vec(); | ||
| 104 | } | ||
| 105 | let input_frames = input.len() / channels; | ||
| 106 | let output_frames = (input_frames as f64 * ratio).ceil() as usize; | ||
| 107 | let mut output = Vec::with_capacity(output_frames * channels); | ||
| 108 | for i in 0..output_frames { | ||
| 109 | let input_pos = i as f64 / ratio; | ||
| 110 | let idx = input_pos.floor() as usize; | ||
| 111 | let frac = input_pos.fract() as f32; | ||
| 112 | for ch in 0..channels { | ||
| 113 | let cur = if idx * channels + ch < input.len() { | ||
| 114 | input[idx * channels + ch] | ||
| 115 | } else { | ||
| 116 | 0.0 | ||
| 117 | }; | ||
| 118 | let next = if (idx + 1) * channels + ch < input.len() { | ||
| 119 | input[(idx + 1) * channels + ch] | ||
| 120 | } else { | ||
| 121 | cur | ||
| 122 | }; | ||
| 123 | output.push(cur + frac * (next - cur)); | ||
| 124 | } | ||
| 125 | } | ||
| 126 | output | ||
| 127 | } | ||
| 128 | |||
| 129 | fn audio_thread( | ||
| 130 | rx: Receiver<AudioCommand>, | ||
| 131 | is_playing: Arc<AtomicBool>, | ||
| 132 | current_track: Arc<Mutex<Option<std::path::PathBuf>>>, | ||
| 133 | volume: Arc<Mutex<f32>>, | ||
| 134 | track_ended: Arc<AtomicBool>, | ||
| 135 | ) { | ||
| 136 | use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; | ||
| 137 | |||
| 138 | let host = cpal::default_host(); | ||
| 139 | let device = match host.default_output_device() { | ||
| 140 | Some(d) => d, | ||
| 141 | None => { | ||
| 142 | eprintln!("No output device available"); | ||
| 143 | return; | ||
| 144 | } | ||
| 145 | }; | ||
| 146 | |||
| 147 | let config = match device.default_output_config() { | ||
| 148 | Ok(c) => c, | ||
| 149 | Err(e) => { | ||
| 150 | eprintln!("Failed to get output config: {}", e); | ||
| 151 | return; | ||
| 152 | } | ||
| 153 | }; | ||
| 154 | |||
| 155 | let device_sample_rate = config.sample_rate(); | ||
| 156 | let channels = config.channels(); | ||
| 157 | eprintln!( | ||
| 158 | "Default output: {} Hz, {} channels", | ||
| 159 | device_sample_rate, channels | ||
| 160 | ); | ||
| 161 | |||
| 162 | let mut current_stream: Option<cpal::Stream> = None; | ||
| 163 | let audio_data: Arc<Mutex<Option<AudioData>>> = Arc::new(Mutex::new(None)); | ||
| 164 | |||
| 165 | while let Ok(cmd) = rx.recv() { | ||
| 166 | match cmd { | ||
| 167 | AudioCommand::Play(path) => { | ||
| 168 | is_playing.store(true, Ordering::SeqCst); | ||
| 169 | if let Ok(mut track) = current_track.lock() { | ||
| 170 | *track = Some(path.clone()); | ||
| 171 | } | ||
| 172 | |||
| 173 | // Drop the current stream before creating a new one | ||
| 174 | current_stream = None; | ||
| 175 | |||
| 176 | match open_audio_file(&path) { | ||
| 177 | Ok(mut data) => { | ||
| 178 | eprintln!("File: {} Hz, {} channels", data.sample_rate, data.channels); | ||
| 179 | |||
| 180 | if data.sample_rate != device_sample_rate { | ||
| 181 | eprintln!( | ||
| 182 | "Resampling from {} Hz to {} Hz", | ||
| 183 | data.sample_rate, device_sample_rate | ||
| 184 | ); | ||
| 185 | data.resample_ratio = | ||
| 186 | Some(device_sample_rate as f64 / data.sample_rate as f64); | ||
| 187 | } | ||
| 188 | |||
| 189 | let stream_config: cpal::StreamConfig = | ||
| 190 | device.default_output_config().unwrap().into(); | ||
| 191 | |||
| 192 | let audio_data_clone = Arc::clone(&audio_data); | ||
| 193 | let vol = Arc::clone(&volume); | ||
| 194 | let ended = Arc::clone(&track_ended); | ||
| 195 | let is_playing_clone = Arc::clone(&is_playing); | ||
| 196 | |||
| 197 | if let Ok(mut d) = audio_data.lock() { | ||
| 198 | *d = Some(data); | ||
| 199 | } | ||
| 200 | |||
| 201 | match device.build_output_stream( | ||
| 202 | &stream_config, | ||
| 203 | move |data: &mut [f32], _: &cpal::OutputCallbackInfo| { | ||
| 204 | let vol = vol.lock().map(|v| *v).unwrap_or(1.0); | ||
| 205 | |||
| 206 | if let Ok(mut audio_data) = audio_data_clone.lock() { | ||
| 207 | if let Some(ref mut state) = *audio_data { | ||
| 208 | let channels = state.channels as usize; | ||
| 209 | let mut offset = 0usize; | ||
| 210 | |||
| 211 | // Keep decoding until we fill the buffer or run out of data | ||
| 212 | while offset < data.len() { | ||
| 213 | // First, copy any buffered samples | ||
| 214 | if state.buffer_pos < state.sample_buffer.len() { | ||
| 215 | let remaining = | ||
| 216 | &state.sample_buffer[state.buffer_pos..]; | ||
| 217 | let to_copy = | ||
| 218 | remaining.len().min(data.len() - offset); | ||
| 219 | data[offset..offset + to_copy] | ||
| 220 | .copy_from_slice(&remaining[..to_copy]); | ||
| 221 | state.buffer_pos += to_copy; | ||
| 222 | offset += to_copy; | ||
| 223 | |||
| 224 | // If we've consumed all buffered samples, clear the buffer | ||
| 225 | if state.buffer_pos >= state.sample_buffer.len() { | ||
| 226 | state.sample_buffer.clear(); | ||
| 227 | state.buffer_pos = 0; | ||
| 228 | } | ||
| 229 | continue; // Try to fill more of the buffer | ||
| 230 | } | ||
| 231 | |||
| 232 | // Decode more data | ||
| 233 | match state.format.next_packet() { | ||
| 234 | Ok(packet) => { | ||
| 235 | match state.decoder.decode(&packet) { | ||
| 236 | Ok(decoded) => { | ||
| 237 | let spec = *decoded.spec(); | ||
| 238 | let mut sample_buf = | ||
| 239 | symphonia::core::audio::SampleBuffer::< | ||
| 240 | f32, | ||
| 241 | >::new( | ||
| 242 | decoded.capacity() as u64, spec | ||
| 243 | ); | ||
| 244 | sample_buf | ||
| 245 | .copy_interleaved_ref(decoded); | ||
| 246 | let samples = sample_buf.samples(); | ||
| 247 | |||
| 248 | // Resample if needed | ||
| 249 | let resampled = if let Some(ratio) = | ||
| 250 | state.resample_ratio | ||
| 251 | { | ||
| 252 | resample_linear( | ||
| 253 | samples, ratio, channels, | ||
| 254 | ) | ||
| 255 | } else { | ||
| 256 | samples.to_vec() | ||
| 257 | }; | ||
| 258 | |||
| 259 | // Copy what we can to output | ||
| 260 | let to_copy = resampled | ||
| 261 | .len() | ||
| 262 | .min(data.len() - offset); | ||
| 263 | data[offset..offset + to_copy] | ||
| 264 | .copy_from_slice( | ||
| 265 | &resampled[..to_copy], | ||
| 266 | ); | ||
| 267 | offset += to_copy; | ||
| 268 | |||
| 269 | // Buffer the rest | ||
| 270 | if to_copy < resampled.len() { | ||
| 271 | state.sample_buffer.clear(); | ||
| 272 | state | ||
| 273 | .sample_buffer | ||
| 274 | .extend_from_slice( | ||
| 275 | &resampled[to_copy..], | ||
| 276 | ); | ||
| 277 | state.buffer_pos = 0; | ||
| 278 | } | ||
| 279 | } | ||
| 280 | Err(_) => break, | ||
| 281 | } | ||
| 282 | } | ||
| 283 | Err(_) => break, | ||
| 284 | } | ||
| 285 | } | ||
| 286 | |||
| 287 | // Apply volume | ||
| 288 | for sample in data.iter_mut().take(offset) { | ||
| 289 | *sample *= vol; | ||
| 290 | } | ||
| 291 | |||
| 292 | // Fill remaining with silence | ||
| 293 | for sample in data.iter_mut().skip(offset) { | ||
| 294 | *sample = 0.0; | ||
| 295 | } | ||
| 296 | |||
| 297 | // If no more data and offset is 0, track ended | ||
| 298 | if offset == 0 && state.sample_buffer.is_empty() { | ||
| 299 | ended.store(true, Ordering::SeqCst); | ||
| 300 | is_playing_clone.store(false, Ordering::SeqCst); | ||
| 301 | // Clear audio data so we don't keep triggering ended | ||
| 302 | if let Ok(mut audio_data) = audio_data_clone.lock() { | ||
| 303 | *audio_data = None; | ||
| 304 | } | ||
| 305 | } | ||
| 306 | } else { | ||
| 307 | for sample in data.iter_mut() { | ||
| 308 | *sample = 0.0; | ||
| 309 | } | ||
| 310 | } | ||
| 311 | } | ||
| 312 | }, | ||
| 313 | |err| { | ||
| 314 | eprintln!("Stream error: {}", err); | ||
| 315 | }, | ||
| 316 | None, | ||
| 317 | ) { | ||
| 318 | Ok(stream) => { | ||
| 319 | if let Err(e) = stream.play() { | ||
| 320 | eprintln!("Failed to start stream: {}", e); | ||
| 321 | is_playing.store(false, Ordering::SeqCst); | ||
| 322 | track_ended.store(true, Ordering::SeqCst); | ||
| 323 | } else { | ||
| 324 | current_stream = Some(stream); | ||
| 325 | } | ||
| 326 | } | ||
| 327 | Err(e) => { | ||
| 328 | eprintln!("Failed to create stream: {}", e); | ||
| 329 | is_playing.store(false, Ordering::SeqCst); | ||
| 330 | track_ended.store(true, Ordering::SeqCst); | ||
| 331 | } | ||
| 332 | } | ||
| 333 | } | ||
| 334 | Err(e) => { | ||
| 335 | eprintln!("Failed to open audio: {}", e); | ||
| 336 | is_playing.store(false, Ordering::SeqCst); | ||
| 337 | track_ended.store(true, Ordering::SeqCst); | ||
| 338 | } | ||
| 339 | } | ||
| 340 | } | ||
| 341 | AudioCommand::Pause => { | ||
| 342 | if let Some(ref stream) = current_stream { | ||
| 343 | if let Err(e) = stream.pause() { | ||
| 344 | eprintln!("Failed to pause stream: {}", e); | ||
| 345 | } | ||
| 346 | is_playing.store(false, Ordering::SeqCst); | ||
| 347 | } | ||
| 348 | } | ||
| 349 | AudioCommand::Resume => { | ||
| 350 | if let Some(ref stream) = current_stream { | ||
| 351 | if let Err(e) = stream.play() { | ||
| 352 | eprintln!("Failed to resume stream: {}", e); | ||
| 353 | } else { | ||
| 354 | is_playing.store(true, Ordering::SeqCst); | ||
| 355 | } | ||
| 356 | } | ||
| 357 | } | ||
| 358 | AudioCommand::Stop => { | ||
| 359 | current_stream = None; | ||
| 360 | if let Ok(mut d) = audio_data.lock() { | ||
| 361 | *d = None; | ||
| 362 | } | ||
| 363 | is_playing.store(false, Ordering::SeqCst); | ||
| 364 | if let Ok(mut track) = current_track.lock() { | ||
| 365 | *track = None; | ||
| 366 | } | ||
| 367 | } | ||
| 368 | AudioCommand::SetVolume(vol) => { | ||
| 369 | if let Ok(mut volume_ref) = volume.lock() { | ||
| 370 | *volume_ref = vol; | ||
| 371 | } | ||
| 372 | } | ||
| 373 | } | ||
| 374 | } | ||
| 375 | } | ||
| 376 | |||
| 377 | fn open_audio_file(path: &PathBuf) -> Result<AudioData, Box<dyn std::error::Error>> { | ||
| 378 | use symphonia::core::audio::Channels; | ||
| 379 | use symphonia::core::io::MediaSourceStream; | ||
| 380 | |||
| 381 | let file = std::fs::File::open(path)?; | ||
| 382 | let mss = MediaSourceStream::new(Box::new(file), Default::default()); | ||
| 383 | |||
| 384 | let hint = symphonia::core::probe::Hint::new(); | ||
| 385 | let probed = symphonia::default::get_probe().format( | ||
| 386 | &hint, | ||
| 387 | mss, | ||
| 388 | &Default::default(), | ||
| 389 | &Default::default(), | ||
| 390 | )?; | ||
| 391 | |||
| 392 | let format = probed.format; | ||
| 393 | let track = format | ||
| 394 | .tracks() | ||
| 395 | .iter() | ||
| 396 | .find(|t| t.codec_params.sample_rate.is_some()) | ||
| 397 | .ok_or("No suitable track found")?; | ||
| 398 | |||
| 399 | let sample_rate = track.codec_params.sample_rate.unwrap_or(44100); | ||
| 400 | let channels = track | ||
| 401 | .codec_params | ||
| 402 | .channels | ||
| 403 | .unwrap_or(Channels::FRONT_LEFT | Channels::FRONT_RIGHT) | ||
| 404 | .count() as u16; | ||
| 405 | |||
| 406 | let decoder = | ||
| 407 | symphonia::default::get_codecs().make(&track.codec_params, &Default::default())?; | ||
| 408 | |||
| 409 | Ok(AudioData { | ||
| 410 | format, | ||
| 411 | decoder, | ||
| 412 | sample_rate, | ||
| 413 | channels, | ||
| 414 | sample_buffer: Vec::new(), | ||
| 415 | buffer_pos: 0, | ||
| 416 | resample_ratio: None, | ||
| 417 | }) | ||
| 418 | } | ||
diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..13ef67c --- /dev/null +++ b/src/cli.rs | |||
| @@ -0,0 +1,186 @@ | |||
| 1 | use crate::config::Config; | ||
| 2 | use crate::database::Database; | ||
| 3 | use clap::{Parser, Subcommand}; | ||
| 4 | use std::path::PathBuf; | ||
| 5 | |||
| 6 | #[derive(Parser)] | ||
| 7 | #[command(name = "rsp")] | ||
| 8 | #[command(about = "Rust music player daemon", long_about = None)] | ||
| 9 | pub struct Cli { | ||
| 10 | #[command(subcommand)] | ||
| 11 | pub command: Commands, | ||
| 12 | } | ||
| 13 | |||
| 14 | #[derive(Subcommand)] | ||
| 15 | pub enum Commands { | ||
| 16 | /// Start the daemon | ||
| 17 | Start, | ||
| 18 | /// Stop the daemon | ||
| 19 | Stop, | ||
| 20 | /// Restart the daemon (kill existing and start new) | ||
| 21 | Restart, | ||
| 22 | /// Play a track, album, or artist | ||
| 23 | Play { | ||
| 24 | #[arg(value_name = "QUERY")] | ||
| 25 | query: Option<String>, | ||
| 26 | }, | ||
| 27 | /// Pause playback | ||
| 28 | Pause, | ||
| 29 | /// Resume playback | ||
| 30 | Resume, | ||
| 31 | /// Stop playback | ||
| 32 | StopPlayback, | ||
| 33 | /// Skip to next track | ||
| 34 | Next, | ||
| 35 | /// Skip to previous track | ||
| 36 | Previous, | ||
| 37 | /// Set volume (0-100) | ||
| 38 | Volume { | ||
| 39 | #[arg(value_name = "LEVEL")] | ||
| 40 | level: Option<u8>, | ||
| 41 | }, | ||
| 42 | /// Shuffle mode | ||
| 43 | Shuffle { | ||
| 44 | #[arg(value_name = "MODE")] | ||
| 45 | mode: Option<String>, | ||
| 46 | }, | ||
| 47 | /// Queue management | ||
| 48 | Queue { | ||
| 49 | #[arg(value_name = "POSITION")] | ||
| 50 | position: Option<String>, | ||
| 51 | #[arg(value_name = "QUERY")] | ||
| 52 | query: Option<String>, | ||
| 53 | }, | ||
| 54 | /// Show player status | ||
| 55 | Status, | ||
| 56 | /// Scan music paths to populate database | ||
| 57 | Scan { | ||
| 58 | #[arg(long)] | ||
| 59 | force: bool, | ||
| 60 | #[arg(long, value_name = "PATHS")] | ||
| 61 | paths: Option<Vec<PathBuf>>, | ||
| 62 | }, | ||
| 63 | /// Settings management | ||
| 64 | Settings { | ||
| 65 | #[command(subcommand)] | ||
| 66 | command: SettingsCommands, | ||
| 67 | }, | ||
| 68 | } | ||
| 69 | |||
| 70 | #[derive(Subcommand)] | ||
| 71 | pub enum SettingsCommands { | ||
| 72 | /// Get a setting value | ||
| 73 | Get { | ||
| 74 | #[arg(value_name = "KEY")] | ||
| 75 | key: String, | ||
| 76 | }, | ||
| 77 | /// Set a setting value | ||
| 78 | Set { | ||
| 79 | #[arg(value_name = "KEY")] | ||
| 80 | key: String, | ||
| 81 | #[arg(value_name = "VALUE")] | ||
| 82 | value: String, | ||
| 83 | }, | ||
| 84 | /// List all settings | ||
| 85 | List, | ||
| 86 | /// Unset a setting | ||
| 87 | Unset { | ||
| 88 | #[arg(value_name = "KEY")] | ||
| 89 | key: String, | ||
| 90 | }, | ||
| 91 | } | ||
| 92 | |||
| 93 | impl Cli { | ||
| 94 | pub fn run(&self) -> Result<(), Box<dyn std::error::Error>> { | ||
| 95 | match &self.command { | ||
| 96 | Commands::Start => { | ||
| 97 | crate::daemon::start_daemon()?; | ||
| 98 | } | ||
| 99 | Commands::Stop => { | ||
| 100 | crate::daemon::stop_daemon()?; | ||
| 101 | } | ||
| 102 | Commands::Restart => { | ||
| 103 | crate::daemon::restart_daemon()?; | ||
| 104 | } | ||
| 105 | Commands::Play { query } => { | ||
| 106 | if let Some(q) = query { | ||
| 107 | crate::daemon::send_command(format!("play {}", q))?; | ||
| 108 | } else { | ||
| 109 | crate::daemon::send_command("play".to_string())?; | ||
| 110 | } | ||
| 111 | } | ||
| 112 | Commands::Pause => { | ||
| 113 | crate::daemon::send_command("pause".to_string())?; | ||
| 114 | } | ||
| 115 | Commands::Resume => { | ||
| 116 | crate::daemon::send_command("resume".to_string())?; | ||
| 117 | } | ||
| 118 | Commands::StopPlayback => { | ||
| 119 | crate::daemon::send_command("stop".to_string())?; | ||
| 120 | } | ||
| 121 | Commands::Next => { | ||
| 122 | crate::daemon::send_command("next".to_string())?; | ||
| 123 | } | ||
| 124 | Commands::Previous => { | ||
| 125 | crate::daemon::send_command("previous".to_string())?; | ||
| 126 | } | ||
| 127 | Commands::Volume { level } => { | ||
| 128 | if let Some(l) = level { | ||
| 129 | crate::daemon::send_command(format!("volume {}", l))?; | ||
| 130 | } else { | ||
| 131 | crate::daemon::send_command("volume".to_string())?; | ||
| 132 | } | ||
| 133 | } | ||
| 134 | Commands::Shuffle { mode } => { | ||
| 135 | if let Some(m) = mode { | ||
| 136 | crate::daemon::send_command(format!("shuffle {}", m))?; | ||
| 137 | } else { | ||
| 138 | crate::daemon::send_command("shuffle".to_string())?; | ||
| 139 | } | ||
| 140 | } | ||
| 141 | Commands::Queue { position, query } => match (position, query) { | ||
| 142 | (None, None) => { | ||
| 143 | crate::daemon::send_command("queue".to_string())?; | ||
| 144 | } | ||
| 145 | (pos, Some(q)) => { | ||
| 146 | let pos = pos.as_deref().unwrap_or("last"); | ||
| 147 | crate::daemon::send_command(format!("queue {} {}", pos, q))?; | ||
| 148 | } | ||
| 149 | _ => {} | ||
| 150 | }, | ||
| 151 | Commands::Status => { | ||
| 152 | crate::daemon::send_command("status".to_string())?; | ||
| 153 | } | ||
| 154 | Commands::Scan { force, paths } => { | ||
| 155 | let config = Config::load()?; | ||
| 156 | let db = Database::open(&config)?; | ||
| 157 | if let Some(custom_paths) = paths { | ||
| 158 | db.scan_paths(custom_paths, *force)?; | ||
| 159 | } else { | ||
| 160 | db.scan_paths(&config.music_paths, *force)?; | ||
| 161 | } | ||
| 162 | println!("Scan complete"); | ||
| 163 | } | ||
| 164 | Commands::Settings { command } => { | ||
| 165 | let mut config = Config::load()?; | ||
| 166 | match command { | ||
| 167 | SettingsCommands::Get { key } => { | ||
| 168 | println!("{}", config.get(key)?); | ||
| 169 | } | ||
| 170 | SettingsCommands::Set { key, value } => { | ||
| 171 | config.set(key, value)?; | ||
| 172 | config.save()?; | ||
| 173 | } | ||
| 174 | SettingsCommands::List => { | ||
| 175 | config.list(); | ||
| 176 | } | ||
| 177 | SettingsCommands::Unset { key } => { | ||
| 178 | config.unset(key)?; | ||
| 179 | config.save()?; | ||
| 180 | } | ||
| 181 | } | ||
| 182 | } | ||
| 183 | } | ||
| 184 | Ok(()) | ||
| 185 | } | ||
| 186 | } | ||
diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..cda59f4 --- /dev/null +++ b/src/config.rs | |||
| @@ -0,0 +1,180 @@ | |||
| 1 | use directories::ProjectDirs; | ||
| 2 | use serde::{Deserialize, Serialize}; | ||
| 3 | use std::fs; | ||
| 4 | use std::path::PathBuf; | ||
| 5 | |||
| 6 | #[derive(Debug, Serialize, Deserialize, Default)] | ||
| 7 | pub struct Config { | ||
| 8 | #[serde(skip)] | ||
| 9 | pub config_path: PathBuf, | ||
| 10 | pub music_paths: Vec<PathBuf>, | ||
| 11 | pub scan_interval: Option<u64>, | ||
| 12 | pub socket_path: Option<PathBuf>, | ||
| 13 | pub lastfm_api_key: Option<String>, | ||
| 14 | pub lastfm_api_secret: Option<String>, | ||
| 15 | pub lastfm_session_key: Option<String>, | ||
| 16 | pub listenbrainz_token: Option<String>, | ||
| 17 | pub volume: Option<u8>, | ||
| 18 | pub shuffle: Option<String>, | ||
| 19 | } | ||
| 20 | |||
| 21 | impl Config { | ||
| 22 | pub fn load() -> Result<Self, Box<dyn std::error::Error>> { | ||
| 23 | let proj_dirs = ProjectDirs::from("com", "rsplayer", "rsplayer") | ||
| 24 | .ok_or("Failed to get project directories")?; | ||
| 25 | |||
| 26 | let config_dir = if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") { | ||
| 27 | PathBuf::from(xdg).join("rsplayer") | ||
| 28 | } else { | ||
| 29 | proj_dirs.config_dir().to_path_buf() | ||
| 30 | }; | ||
| 31 | |||
| 32 | let config_path = config_dir.join("config.toml"); | ||
| 33 | |||
| 34 | if !config_path.exists() { | ||
| 35 | fs::create_dir_all(&config_dir)?; | ||
| 36 | let default = Self { | ||
| 37 | config_path: config_path.clone(), | ||
| 38 | music_paths: vec![dirs::audio_dir().unwrap_or_else(|| PathBuf::from("~/Music"))], | ||
| 39 | scan_interval: Some(3600), | ||
| 40 | socket_path: None, | ||
| 41 | ..Default::default() | ||
| 42 | }; | ||
| 43 | default.save()?; | ||
| 44 | return Ok(default); | ||
| 45 | } | ||
| 46 | |||
| 47 | let content = fs::read_to_string(&config_path)?; | ||
| 48 | let mut config: Self = toml::from_str(&content)?; | ||
| 49 | config.config_path = config_path; | ||
| 50 | Ok(config) | ||
| 51 | } | ||
| 52 | |||
| 53 | pub fn save(&self) -> Result<(), Box<dyn std::error::Error>> { | ||
| 54 | let content = toml::to_string_pretty(self)?; | ||
| 55 | if let Some(parent) = self.config_path.parent() { | ||
| 56 | fs::create_dir_all(parent)?; | ||
| 57 | } | ||
| 58 | fs::write(&self.config_path, content)?; | ||
| 59 | Ok(()) | ||
| 60 | } | ||
| 61 | |||
| 62 | pub fn get(&self, key: &str) -> Result<String, Box<dyn std::error::Error>> { | ||
| 63 | match key { | ||
| 64 | "music_paths" => Ok(self | ||
| 65 | .music_paths | ||
| 66 | .iter() | ||
| 67 | .map(|p| p.display().to_string()) | ||
| 68 | .collect::<Vec<_>>() | ||
| 69 | .join(", ")), | ||
| 70 | "scan_interval" => Ok(self | ||
| 71 | .scan_interval | ||
| 72 | .map(|i| i.to_string()) | ||
| 73 | .unwrap_or_else(|| "not set".to_string())), | ||
| 74 | "socket_path" => Ok(self | ||
| 75 | .socket_path | ||
| 76 | .as_ref() | ||
| 77 | .map(|p| p.display().to_string()) | ||
| 78 | .unwrap_or_else(|| "default".to_string())), | ||
| 79 | "lastfm_api_key" => Ok(self | ||
| 80 | .lastfm_api_key | ||
| 81 | .clone() | ||
| 82 | .unwrap_or_else(|| "not set".to_string())), | ||
| 83 | "lastfm_api_secret" => Ok(self | ||
| 84 | .lastfm_api_secret | ||
| 85 | .clone() | ||
| 86 | .unwrap_or_else(|| "not set".to_string())), | ||
| 87 | "lastfm_session_key" => Ok(self | ||
| 88 | .lastfm_session_key | ||
| 89 | .clone() | ||
| 90 | .unwrap_or_else(|| "not set".to_string())), | ||
| 91 | "listenbrainz_token" => Ok(self | ||
| 92 | .listenbrainz_token | ||
| 93 | .clone() | ||
| 94 | .unwrap_or_else(|| "not set".to_string())), | ||
| 95 | "volume" => Ok(self | ||
| 96 | .volume | ||
| 97 | .map(|v| v.to_string()) | ||
| 98 | .unwrap_or_else(|| "not set".to_string())), | ||
| 99 | "shuffle" => Ok(self.shuffle.clone().unwrap_or_else(|| "off".to_string())), | ||
| 100 | _ => Err(format!("Unknown setting: {}", key).into()), | ||
| 101 | } | ||
| 102 | } | ||
| 103 | |||
| 104 | pub fn set(&mut self, key: &str, value: &str) -> Result<(), Box<dyn std::error::Error>> { | ||
| 105 | match key { | ||
| 106 | "music_paths" => { | ||
| 107 | self.music_paths = value.split(',').map(|p| PathBuf::from(p.trim())).collect(); | ||
| 108 | } | ||
| 109 | "scan_interval" => { | ||
| 110 | self.scan_interval = Some(value.parse()?); | ||
| 111 | } | ||
| 112 | "socket_path" => { | ||
| 113 | self.socket_path = Some(PathBuf::from(value)); | ||
| 114 | } | ||
| 115 | "lastfm_api_key" => { | ||
| 116 | self.lastfm_api_key = Some(value.to_string()); | ||
| 117 | } | ||
| 118 | "lastfm_api_secret" => { | ||
| 119 | self.lastfm_api_secret = Some(value.to_string()); | ||
| 120 | } | ||
| 121 | "lastfm_session_key" => { | ||
| 122 | self.lastfm_session_key = Some(value.to_string()); | ||
| 123 | } | ||
| 124 | "listenbrainz_token" => { | ||
| 125 | self.listenbrainz_token = Some(value.to_string()); | ||
| 126 | } | ||
| 127 | "volume" => { | ||
| 128 | self.volume = Some(value.parse()?); | ||
| 129 | } | ||
| 130 | "shuffle" => { | ||
| 131 | if value == "on" || value == "all" || value == "album" { | ||
| 132 | self.shuffle = Some(value.to_string()); | ||
| 133 | } else if value == "off" || value == "none" { | ||
| 134 | self.shuffle = None; | ||
| 135 | } else { | ||
| 136 | return Err(format!("Invalid shuffle value: {}", value).into()); | ||
| 137 | } | ||
| 138 | } | ||
| 139 | _ => return Err(format!("Unknown setting: {}", key).into()), | ||
| 140 | } | ||
| 141 | Ok(()) | ||
| 142 | } | ||
| 143 | |||
| 144 | pub fn list(&self) { | ||
| 145 | println!("music_paths = {:?}", self.music_paths); | ||
| 146 | println!("scan_interval = {:?}", self.scan_interval); | ||
| 147 | println!("socket_path = {:?}", self.socket_path); | ||
| 148 | println!("lastfm_api_key = {:?}", self.lastfm_api_key); | ||
| 149 | println!("lastfm_api_secret = {:?}", self.lastfm_api_secret); | ||
| 150 | println!("lastfm_session_key = {:?}", self.lastfm_session_key); | ||
| 151 | println!("listenbrainz_token = {:?}", self.listenbrainz_token); | ||
| 152 | println!("volume = {:?}", self.volume); | ||
| 153 | println!("shuffle = {:?}", self.shuffle); | ||
| 154 | } | ||
| 155 | |||
| 156 | pub fn unset(&mut self, key: &str) -> Result<(), Box<dyn std::error::Error>> { | ||
| 157 | match key { | ||
| 158 | "music_paths" => self.music_paths.clear(), | ||
| 159 | "scan_interval" => self.scan_interval = None, | ||
| 160 | "socket_path" => self.socket_path = None, | ||
| 161 | "lastfm_api_key" => self.lastfm_api_key = None, | ||
| 162 | "lastfm_api_secret" => self.lastfm_api_secret = None, | ||
| 163 | "lastfm_session_key" => self.lastfm_session_key = None, | ||
| 164 | "listenbrainz_token" => self.listenbrainz_token = None, | ||
| 165 | "volume" => self.volume = None, | ||
| 166 | "shuffle" => self.shuffle = None, | ||
| 167 | _ => return Err(format!("Unknown setting: {}", key).into()), | ||
| 168 | } | ||
| 169 | Ok(()) | ||
| 170 | } | ||
| 171 | |||
| 172 | pub fn get_socket_path(&self) -> PathBuf { | ||
| 173 | self.socket_path.clone().unwrap_or_else(|| { | ||
| 174 | let runtime_dir = std::env::var("XDG_RUNTIME_DIR") | ||
| 175 | .map(PathBuf::from) | ||
| 176 | .unwrap_or_else(|_| PathBuf::from("/tmp")); | ||
| 177 | runtime_dir.join("rsplayer").join("daemon.sock") | ||
| 178 | }) | ||
| 179 | } | ||
| 180 | } | ||
diff --git a/src/daemon.rs b/src/daemon.rs new file mode 100644 index 0000000..8ac3a89 --- /dev/null +++ b/src/daemon.rs | |||
| @@ -0,0 +1,574 @@ | |||
| 1 | use crate::audio::AudioEngine; | ||
| 2 | use crate::config::Config; | ||
| 3 | use std::io::{Read, Write}; | ||
| 4 | use std::os::unix::net::{UnixListener, UnixStream}; | ||
| 5 | use std::path::{Path, PathBuf}; | ||
| 6 | use std::sync::atomic::{AtomicBool, Ordering}; | ||
| 7 | use std::sync::{Arc, Mutex}; | ||
| 8 | use std::thread; | ||
| 9 | use std::time::Duration; | ||
| 10 | |||
| 11 | #[derive(Debug, Clone)] | ||
| 12 | enum ShuffleMode { | ||
| 13 | None, | ||
| 14 | All, | ||
| 15 | Album, | ||
| 16 | } | ||
| 17 | |||
| 18 | struct Queue { | ||
| 19 | tracks: Vec<PathBuf>, | ||
| 20 | position: usize, | ||
| 21 | shuffle: ShuffleMode, | ||
| 22 | shuffled_indices: Vec<usize>, | ||
| 23 | } | ||
| 24 | |||
| 25 | impl Queue { | ||
| 26 | fn new() -> Self { | ||
| 27 | Queue { | ||
| 28 | tracks: Vec::new(), | ||
| 29 | position: 0, | ||
| 30 | shuffle: ShuffleMode::None, | ||
| 31 | shuffled_indices: Vec::new(), | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | fn add_track(&mut self, path: PathBuf) { | ||
| 36 | self.tracks.push(path); | ||
| 37 | self.update_shuffle_indices(); | ||
| 38 | } | ||
| 39 | |||
| 40 | fn set_shuffle(&mut self, mode: ShuffleMode) { | ||
| 41 | self.shuffle = mode.clone(); | ||
| 42 | self.update_shuffle_indices(); | ||
| 43 | } | ||
| 44 | |||
| 45 | fn update_shuffle_indices(&mut self) { | ||
| 46 | self.shuffled_indices = (0..self.tracks.len()).collect(); | ||
| 47 | if let ShuffleMode::All = self.shuffle { | ||
| 48 | use rand::seq::SliceRandom; | ||
| 49 | let mut rng = rand::rng(); | ||
| 50 | self.shuffled_indices.shuffle(&mut rng); | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | fn current(&self) -> Option<&PathBuf> { | ||
| 55 | if self.tracks.is_empty() { | ||
| 56 | None | ||
| 57 | } else if let ShuffleMode::All = self.shuffle { | ||
| 58 | self.shuffled_indices | ||
| 59 | .get(self.position) | ||
| 60 | .and_then(|&idx| self.tracks.get(idx)) | ||
| 61 | } else if let ShuffleMode::Album = self.shuffle { | ||
| 62 | // TODO: When database is ready, only shuffle tracks in same album/folder | ||
| 63 | // For now, behave like All | ||
| 64 | self.shuffled_indices | ||
| 65 | .get(self.position) | ||
| 66 | .and_then(|&idx| self.tracks.get(idx)) | ||
| 67 | } else { | ||
| 68 | self.tracks.get(self.position) | ||
| 69 | } | ||
| 70 | } | ||
| 71 | |||
| 72 | fn next(&mut self) -> Option<PathBuf> { | ||
| 73 | let max_pos = self.tracks.len().saturating_sub(1); | ||
| 74 | if self.position < max_pos { | ||
| 75 | self.position += 1; | ||
| 76 | self.current().cloned() | ||
| 77 | } else { | ||
| 78 | None | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | fn previous(&mut self) -> Option<PathBuf> { | ||
| 83 | if self.position > 0 { | ||
| 84 | self.position -= 1; | ||
| 85 | self.current().cloned() | ||
| 86 | } else { | ||
| 87 | None | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | pub fn start_daemon() -> Result<(), Box<dyn std::error::Error>> { | ||
| 93 | let config = Config::load()?; | ||
| 94 | let socket_path = config.get_socket_path(); | ||
| 95 | let pid_path = get_pid_path(&config); | ||
| 96 | |||
| 97 | // Check if daemon is already running | ||
| 98 | if let Ok(pid) = check_running_daemon(&pid_path) { | ||
| 99 | return Err(format!("Daemon already running with PID {}", pid).into()); | ||
| 100 | } | ||
| 101 | |||
| 102 | // Clean up stale socket file if it exists | ||
| 103 | if socket_path.exists() { | ||
| 104 | std::fs::remove_file(&socket_path).ok(); | ||
| 105 | } | ||
| 106 | |||
| 107 | // Create runtime directory | ||
| 108 | if let Some(parent) = socket_path.parent() { | ||
| 109 | std::fs::create_dir_all(parent)?; | ||
| 110 | } | ||
| 111 | if let Some(parent) = pid_path.parent() { | ||
| 112 | std::fs::create_dir_all(parent)?; | ||
| 113 | } | ||
| 114 | |||
| 115 | // Write PID file | ||
| 116 | let pid = std::process::id(); | ||
| 117 | std::fs::write(&pid_path, pid.to_string())?; | ||
| 118 | println!("Daemon started with PID {}", pid); | ||
| 119 | |||
| 120 | // Shared shutdown flag - can be triggered by signal or command | ||
| 121 | let shutdown_flag = Arc::new(AtomicBool::new(false)); | ||
| 122 | let shutdown_flag_signal = Arc::clone(&shutdown_flag); | ||
| 123 | |||
| 124 | // Handle SIGTERM and SIGINT | ||
| 125 | ctrlc::set_handler(move || { | ||
| 126 | println!("Received shutdown signal"); | ||
| 127 | shutdown_flag_signal.store(true, Ordering::SeqCst); | ||
| 128 | })?; | ||
| 129 | |||
| 130 | let listener = UnixListener::bind(&socket_path)?; | ||
| 131 | println!("Listening on {:?}", socket_path); | ||
| 132 | |||
| 133 | let audio_engine = Arc::new(AudioEngine::new()); | ||
| 134 | let queue = Arc::new(Mutex::new(Queue::new())); | ||
| 135 | |||
| 136 | // Load and apply saved settings | ||
| 137 | let config = Config::load().unwrap_or_default(); | ||
| 138 | if let Some(vol) = config.volume { | ||
| 139 | audio_engine.set_volume(vol as f32 / 100.0); | ||
| 140 | } | ||
| 141 | if let Some(ref shuffle_str) = config.shuffle { | ||
| 142 | if let Ok(mut q) = queue.lock() { | ||
| 143 | match shuffle_str.as_str() { | ||
| 144 | "on" | "all" => q.set_shuffle(ShuffleMode::All), | ||
| 145 | "album" => q.set_shuffle(ShuffleMode::Album), | ||
| 146 | _ => {} | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
| 150 | |||
| 151 | // Accept connections until shutdown | ||
| 152 | listener.set_nonblocking(true)?; | ||
| 153 | loop { | ||
| 154 | // Check for shutdown signal | ||
| 155 | if shutdown_flag.load(Ordering::SeqCst) { | ||
| 156 | break; | ||
| 157 | } | ||
| 158 | |||
| 159 | // Check if track ended and play next | ||
| 160 | if audio_engine.poll_track_ended() { | ||
| 161 | if let Ok(mut q) = queue.lock() { | ||
| 162 | if let Some(next_track) = q.next() { | ||
| 163 | audio_engine.play(next_track); | ||
| 164 | } else { | ||
| 165 | audio_engine.stop(); | ||
| 166 | } | ||
| 167 | } | ||
| 168 | } | ||
| 169 | |||
| 170 | match listener.accept() { | ||
| 171 | Ok((stream, _)) => { | ||
| 172 | let audio_clone = Arc::clone(&audio_engine); | ||
| 173 | let shutdown_flag_clone = Arc::clone(&shutdown_flag); | ||
| 174 | let queue_clone = Arc::clone(&queue); | ||
| 175 | thread::spawn(move || { | ||
| 176 | handle_client(stream, audio_clone, shutdown_flag_clone, queue_clone); | ||
| 177 | }); | ||
| 178 | } | ||
| 179 | Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { | ||
| 180 | thread::sleep(Duration::from_millis(50)); | ||
| 181 | continue; | ||
| 182 | } | ||
| 183 | Err(e) => eprintln!("Connection failed: {}", e), | ||
| 184 | } | ||
| 185 | |||
| 186 | thread::sleep(Duration::from_millis(50)); | ||
| 187 | } | ||
| 188 | |||
| 189 | // Cleanup | ||
| 190 | cleanup_daemon(&socket_path, &pid_path); | ||
| 191 | println!("Daemon stopped"); | ||
| 192 | Ok(()) | ||
| 193 | } | ||
| 194 | |||
| 195 | pub fn stop_daemon() -> Result<(), Box<dyn std::error::Error>> { | ||
| 196 | let config = Config::load()?; | ||
| 197 | let pid_path = get_pid_path(&config); | ||
| 198 | |||
| 199 | match check_running_daemon(&pid_path) { | ||
| 200 | Ok(pid) => { | ||
| 201 | // Try to send stop command first for graceful shutdown | ||
| 202 | if let Ok(()) = send_command_raw("stop_daemon".to_string()) { | ||
| 203 | // Wait for daemon to stop | ||
| 204 | for _ in 0..50 { | ||
| 205 | thread::sleep(Duration::from_millis(100)); | ||
| 206 | if !is_process_running(pid) { | ||
| 207 | break; | ||
| 208 | } | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | // Force kill if still running | ||
| 213 | if is_process_running(pid) { | ||
| 214 | unsafe { | ||
| 215 | libc::kill(pid as i32, libc::SIGTERM); | ||
| 216 | } | ||
| 217 | thread::sleep(Duration::from_millis(500)); | ||
| 218 | |||
| 219 | // Force kill if still alive | ||
| 220 | if is_process_running(pid) { | ||
| 221 | unsafe { | ||
| 222 | libc::kill(pid as i32, libc::SIGKILL); | ||
| 223 | } | ||
| 224 | } | ||
| 225 | } | ||
| 226 | |||
| 227 | // Clean up files | ||
| 228 | let socket_path = config.get_socket_path(); | ||
| 229 | std::fs::remove_file(&socket_path).ok(); | ||
| 230 | std::fs::remove_file(&pid_path).ok(); | ||
| 231 | |||
| 232 | println!("Daemon stopped"); | ||
| 233 | Ok(()) | ||
| 234 | } | ||
| 235 | Err(e) => Err(format!("Daemon not running: {}", e).into()), | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | pub fn restart_daemon() -> Result<(), Box<dyn std::error::Error>> { | ||
| 240 | // Stop existing daemon | ||
| 241 | if let Ok(()) = stop_daemon() { | ||
| 242 | // Wait a bit for cleanup | ||
| 243 | thread::sleep(Duration::from_millis(500)); | ||
| 244 | } | ||
| 245 | |||
| 246 | // Start new daemon in background | ||
| 247 | let current_exe = std::env::current_exe()?; | ||
| 248 | std::process::Command::new(current_exe) | ||
| 249 | .arg("start") | ||
| 250 | .spawn()?; | ||
| 251 | |||
| 252 | println!("Daemon restarted"); | ||
| 253 | Ok(()) | ||
| 254 | } | ||
| 255 | |||
| 256 | pub fn send_command(cmd: String) -> Result<(), Box<dyn std::error::Error>> { | ||
| 257 | send_command_raw(cmd)?; | ||
| 258 | Ok(()) | ||
| 259 | } | ||
| 260 | |||
| 261 | fn send_command_raw(cmd: String) -> Result<(), Box<dyn std::error::Error>> { | ||
| 262 | let config = Config::load()?; | ||
| 263 | let socket_path = config.get_socket_path(); | ||
| 264 | |||
| 265 | let mut stream = UnixStream::connect(socket_path)?; | ||
| 266 | stream.write_all(cmd.as_bytes())?; | ||
| 267 | |||
| 268 | let mut response = String::new(); | ||
| 269 | stream.read_to_string(&mut response)?; | ||
| 270 | print!("{}", response); | ||
| 271 | |||
| 272 | Ok(()) | ||
| 273 | } | ||
| 274 | |||
| 275 | fn handle_client( | ||
| 276 | mut stream: UnixStream, | ||
| 277 | audio: Arc<AudioEngine>, | ||
| 278 | shutdown_flag: Arc<AtomicBool>, | ||
| 279 | queue: Arc<Mutex<Queue>>, | ||
| 280 | ) { | ||
| 281 | let mut buffer = [0; 1024]; | ||
| 282 | if let Ok(size) = stream.read(&mut buffer) { | ||
| 283 | let cmd = String::from_utf8_lossy(&buffer[..size]); | ||
| 284 | let response = process_command(cmd.trim(), &audio, &shutdown_flag, &queue); | ||
| 285 | let _ = stream.write_all(response.as_bytes()); | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | fn process_command( | ||
| 290 | cmd: &str, | ||
| 291 | audio: &AudioEngine, | ||
| 292 | shutdown_flag: &Arc<AtomicBool>, | ||
| 293 | queue: &Arc<Mutex<Queue>>, | ||
| 294 | ) -> String { | ||
| 295 | match cmd { | ||
| 296 | "status" => { | ||
| 297 | if audio.is_playing() { | ||
| 298 | if let Some(track) = audio.current_track() { | ||
| 299 | format!("Playing\nTrack: {}\n", track.display()) | ||
| 300 | } else { | ||
| 301 | "Playing\n".to_string() | ||
| 302 | } | ||
| 303 | } else { | ||
| 304 | "Stopped\n".to_string() | ||
| 305 | } | ||
| 306 | } | ||
| 307 | "pause" => { | ||
| 308 | audio.pause(); | ||
| 309 | "Paused\n".to_string() | ||
| 310 | } | ||
| 311 | "resume" => { | ||
| 312 | audio.resume(); | ||
| 313 | "Resumed\n".to_string() | ||
| 314 | } | ||
| 315 | "stop" => { | ||
| 316 | audio.stop(); | ||
| 317 | // Clear queue on stop | ||
| 318 | if let Ok(mut q) = queue.lock() { | ||
| 319 | q.tracks.clear(); | ||
| 320 | q.position = 0; | ||
| 321 | } | ||
| 322 | "Stopped\n".to_string() | ||
| 323 | } | ||
| 324 | "stop_daemon" => { | ||
| 325 | // Signal the daemon to shut down | ||
| 326 | shutdown_flag.store(true, Ordering::SeqCst); | ||
| 327 | "Shutting down\n".to_string() | ||
| 328 | } | ||
| 329 | "next" => { | ||
| 330 | if let Ok(mut q) = queue.lock() { | ||
| 331 | if let Some(next_track) = q.next() { | ||
| 332 | audio.play(next_track); | ||
| 333 | "Playing next track\n".to_string() | ||
| 334 | } else { | ||
| 335 | audio.stop(); | ||
| 336 | "Queue empty, stopped\n".to_string() | ||
| 337 | } | ||
| 338 | } else { | ||
| 339 | "Error accessing queue\n".to_string() | ||
| 340 | } | ||
| 341 | } | ||
| 342 | "previous" => { | ||
| 343 | if let Ok(mut q) = queue.lock() { | ||
| 344 | if let Some(prev_track) = q.previous() { | ||
| 345 | audio.play(prev_track); | ||
| 346 | "Playing previous track\n".to_string() | ||
| 347 | } else { | ||
| 348 | "No previous track\n".to_string() | ||
| 349 | } | ||
| 350 | } else { | ||
| 351 | "Error accessing queue\n".to_string() | ||
| 352 | } | ||
| 353 | } | ||
| 354 | "shuffle" => { | ||
| 355 | if let Ok(q) = queue.lock() { | ||
| 356 | match q.shuffle { | ||
| 357 | ShuffleMode::None => "Shuffle: off\n".to_string(), | ||
| 358 | ShuffleMode::All => "Shuffle: on (all)\n".to_string(), | ||
| 359 | ShuffleMode::Album => "Shuffle: on (album)\n".to_string(), | ||
| 360 | } | ||
| 361 | } else { | ||
| 362 | "Error accessing queue\n".to_string() | ||
| 363 | } | ||
| 364 | } | ||
| 365 | _ if cmd.starts_with("shuffle ") => { | ||
| 366 | let mode_str = cmd[8..].trim(); | ||
| 367 | if let Ok(mut q) = queue.lock() { | ||
| 368 | let result = if mode_str == "on" || mode_str == "all" { | ||
| 369 | q.set_shuffle(ShuffleMode::All); | ||
| 370 | "Shuffle: on (all)\n".to_string() | ||
| 371 | } else if mode_str == "album" { | ||
| 372 | q.set_shuffle(ShuffleMode::Album); | ||
| 373 | "Shuffle: on (album)\n".to_string() | ||
| 374 | } else if mode_str == "off" || mode_str == "none" { | ||
| 375 | q.set_shuffle(ShuffleMode::None); | ||
| 376 | "Shuffle: off\n".to_string() | ||
| 377 | } else { | ||
| 378 | "Usage: shuffle [on|off|album]\n".to_string() | ||
| 379 | }; | ||
| 380 | // Save to config | ||
| 381 | if let Ok(mut config) = Config::load() { | ||
| 382 | if mode_str == "off" || mode_str == "none" { | ||
| 383 | config.shuffle = None; | ||
| 384 | } else { | ||
| 385 | config.shuffle = Some(mode_str.to_string()); | ||
| 386 | } | ||
| 387 | let _ = config.save(); | ||
| 388 | } | ||
| 389 | result | ||
| 390 | } else { | ||
| 391 | "Error accessing queue\n".to_string() | ||
| 392 | } | ||
| 393 | } | ||
| 394 | _ if cmd.starts_with("play ") => { | ||
| 395 | let path_str = &cmd[5..].trim(); | ||
| 396 | let path = std::path::PathBuf::from(path_str); | ||
| 397 | if path.is_file() { | ||
| 398 | // Add to queue and play | ||
| 399 | if let Ok(mut q) = queue.lock() { | ||
| 400 | q.add_track(path.clone()); | ||
| 401 | } | ||
| 402 | audio.play(path); | ||
| 403 | format!("Playing: {}\n", path_str) | ||
| 404 | } else if path.is_dir() { | ||
| 405 | // Find all audio files in directory | ||
| 406 | match find_audio_in_dir(&path) { | ||
| 407 | Some(files) if !files.is_empty() => { | ||
| 408 | if let Ok(mut q) = queue.lock() { | ||
| 409 | for file in &files { | ||
| 410 | q.add_track(file.clone()); | ||
| 411 | } | ||
| 412 | // If shuffle is on, shuffled_indices is already set | ||
| 413 | // Play the first track from the shuffled list | ||
| 414 | if let Some(track) = q.current().cloned() { | ||
| 415 | audio.play(track); | ||
| 416 | } | ||
| 417 | } | ||
| 418 | format!("Queued {} tracks from: {}\n", files.len(), path_str) | ||
| 419 | } | ||
| 420 | _ => format!("Error: No audio files found in '{}'\n", path_str), | ||
| 421 | } | ||
| 422 | } else { | ||
| 423 | format!("File not found: {}\n", path_str) | ||
| 424 | } | ||
| 425 | } | ||
| 426 | _ if cmd.starts_with("volume ") => { | ||
| 427 | if let Ok(level) = cmd[7..].trim().parse::<u8>() { | ||
| 428 | audio.set_volume(level as f32 / 100.0); | ||
| 429 | // Save to config | ||
| 430 | if let Ok(mut config) = Config::load() { | ||
| 431 | config.volume = Some(level); | ||
| 432 | let _ = config.save(); | ||
| 433 | } | ||
| 434 | format!("Volume set to {}\n", level) | ||
| 435 | } else { | ||
| 436 | "Usage: volume <0-100>\n".to_string() | ||
| 437 | } | ||
| 438 | } | ||
| 439 | _ if cmd.starts_with("shuffle ") => { | ||
| 440 | let mode_str = cmd[8..].trim(); | ||
| 441 | if let Ok(mut q) = queue.lock() { | ||
| 442 | if mode_str == "on" || mode_str == "all" { | ||
| 443 | q.set_shuffle(ShuffleMode::All); | ||
| 444 | "Shuffle: on\n".to_string() | ||
| 445 | } else if mode_str == "off" || mode_str == "none" { | ||
| 446 | q.set_shuffle(ShuffleMode::None); | ||
| 447 | "Shuffle: off\n".to_string() | ||
| 448 | } else { | ||
| 449 | "Usage: shuffle [on|off]\n".to_string() | ||
| 450 | } | ||
| 451 | } else { | ||
| 452 | "Error accessing queue\n".to_string() | ||
| 453 | } | ||
| 454 | } | ||
| 455 | _ if cmd.starts_with("queue") => { | ||
| 456 | if let Ok(q) = queue.lock() { | ||
| 457 | if q.tracks.is_empty() { | ||
| 458 | "Queue is empty\n".to_string() | ||
| 459 | } else { | ||
| 460 | let mut output = String::new(); | ||
| 461 | let current_position = q.position; | ||
| 462 | // Use shuffled_indices for display order if shuffle is on | ||
| 463 | let indices: Vec<usize> = if let ShuffleMode::None = q.shuffle { | ||
| 464 | (0..q.tracks.len()).collect() | ||
| 465 | } else { | ||
| 466 | q.shuffled_indices.clone() | ||
| 467 | }; | ||
| 468 | for (display_idx, &track_idx) in indices.iter().enumerate() { | ||
| 469 | if let Some(track) = q.tracks.get(track_idx) { | ||
| 470 | if display_idx == current_position { | ||
| 471 | output.push_str(&format!("> {} (playing)\n", track.display())); | ||
| 472 | } else { | ||
| 473 | output.push_str(&format!(" {}\n", track.display())); | ||
| 474 | } | ||
| 475 | } | ||
| 476 | } | ||
| 477 | // Use pager if output is long (more than 24 lines) | ||
| 478 | if output.lines().count() > 24 { | ||
| 479 | use std::process::{Command, Stdio}; | ||
| 480 | let pager = Command::new("less").arg("-R").stdin(Stdio::piped()).spawn(); | ||
| 481 | if let Ok(mut child) = pager { | ||
| 482 | use std::io::Write; | ||
| 483 | let _ = child.stdin.as_mut().unwrap().write_all(output.as_bytes()); | ||
| 484 | let _ = child.wait(); | ||
| 485 | "".to_string() | ||
| 486 | } else { | ||
| 487 | output | ||
| 488 | } | ||
| 489 | } else { | ||
| 490 | output | ||
| 491 | } | ||
| 492 | } | ||
| 493 | } else { | ||
| 494 | "Error accessing queue\n".to_string() | ||
| 495 | } | ||
| 496 | } | ||
| 497 | _ => "Unknown command\n".to_string(), | ||
| 498 | } | ||
| 499 | } | ||
| 500 | |||
| 501 | fn find_audio_in_dir(dir: &Path) -> Option<Vec<std::path::PathBuf>> { | ||
| 502 | use std::ffi::OsStr; | ||
| 503 | |||
| 504 | let mut results = Vec::new(); | ||
| 505 | |||
| 506 | if let Ok(entries) = std::fs::read_dir(dir) { | ||
| 507 | let mut entries: Vec<_> = entries.filter_map(|e| e.ok()).collect(); | ||
| 508 | entries.sort_by_key(|e| e.file_name()); | ||
| 509 | |||
| 510 | for entry in entries { | ||
| 511 | let path = entry.path(); | ||
| 512 | if path.is_file() { | ||
| 513 | if let Some(ext) = path.extension().and_then(OsStr::to_str) { | ||
| 514 | if matches!( | ||
| 515 | ext.to_lowercase().as_str(), | ||
| 516 | "mp3" | "flac" | "ogg" | "m4a" | "wav" | "opus" | ||
| 517 | ) { | ||
| 518 | results.push(path); | ||
| 519 | } | ||
| 520 | } | ||
| 521 | } else if path.is_dir() { | ||
| 522 | if let Some(mut found) = find_audio_in_dir(&path) { | ||
| 523 | results.append(&mut found); | ||
| 524 | } | ||
| 525 | } | ||
| 526 | } | ||
| 527 | } | ||
| 528 | |||
| 529 | if results.is_empty() { | ||
| 530 | None | ||
| 531 | } else { | ||
| 532 | Some(results) | ||
| 533 | } | ||
| 534 | } | ||
| 535 | |||
| 536 | /// Get the PID file path for the daemon | ||
| 537 | fn get_pid_path(_config: &Config) -> std::path::PathBuf { | ||
| 538 | let runtime_dir = std::env::var("XDG_RUNTIME_DIR") | ||
| 539 | .map(std::path::PathBuf::from) | ||
| 540 | .unwrap_or_else(|_| std::path::PathBuf::from("/tmp")); | ||
| 541 | runtime_dir.join("rsplayer").join("rsplayer.pid") | ||
| 542 | } | ||
| 543 | |||
| 544 | /// Check if a process with the given PID is running | ||
| 545 | fn is_process_running(pid: u32) -> bool { | ||
| 546 | unsafe { | ||
| 547 | // send signal 0 to check if process exists | ||
| 548 | libc::kill(pid as i32, 0) == 0 | ||
| 549 | } | ||
| 550 | } | ||
| 551 | |||
| 552 | /// Check if daemon is running by reading PID file | ||
| 553 | fn check_running_daemon(pid_path: &std::path::Path) -> Result<u32, Box<dyn std::error::Error>> { | ||
| 554 | if !pid_path.exists() { | ||
| 555 | return Err("PID file not found".into()); | ||
| 556 | } | ||
| 557 | |||
| 558 | let contents = std::fs::read_to_string(pid_path)?; | ||
| 559 | let pid = contents.trim().parse::<u32>()?; | ||
| 560 | |||
| 561 | if is_process_running(pid) { | ||
| 562 | Ok(pid) | ||
| 563 | } else { | ||
| 564 | // Stale PID file, clean it up | ||
| 565 | std::fs::remove_file(pid_path).ok(); | ||
| 566 | Err("Stale PID file, daemon not running".into()) | ||
| 567 | } | ||
| 568 | } | ||
| 569 | |||
| 570 | /// Clean up daemon resources (socket and PID file) | ||
| 571 | fn cleanup_daemon(socket_path: &std::path::Path, pid_path: &std::path::Path) { | ||
| 572 | std::fs::remove_file(socket_path).ok(); | ||
| 573 | std::fs::remove_file(pid_path).ok(); | ||
| 574 | } | ||
diff --git a/src/database.rs b/src/database.rs new file mode 100644 index 0000000..9d1d5f2 --- /dev/null +++ b/src/database.rs | |||
| @@ -0,0 +1,259 @@ | |||
| 1 | use directories::ProjectDirs; | ||
| 2 | use rusqlite::{Connection, Result as SqlResult}; | ||
| 3 | use std::fs; | ||
| 4 | use std::path::{Path, PathBuf}; | ||
| 5 | use std::time::SystemTime; | ||
| 6 | |||
| 7 | #[derive(Debug, Clone)] | ||
| 8 | #[allow(dead_code)] | ||
| 9 | pub struct Artist { | ||
| 10 | pub id: i64, | ||
| 11 | pub name: String, | ||
| 12 | } | ||
| 13 | |||
| 14 | #[derive(Debug, Clone)] | ||
| 15 | #[allow(dead_code)] | ||
| 16 | pub struct Album { | ||
| 17 | pub id: i64, | ||
| 18 | pub artist_id: i64, | ||
| 19 | pub title: String, | ||
| 20 | } | ||
| 21 | |||
| 22 | #[derive(Debug, Clone)] | ||
| 23 | #[allow(dead_code)] | ||
| 24 | pub struct Track { | ||
| 25 | pub id: i64, | ||
| 26 | pub album_id: i64, | ||
| 27 | pub title: String, | ||
| 28 | pub path: PathBuf, | ||
| 29 | pub duration: Option<i64>, | ||
| 30 | pub track_number: Option<i32>, | ||
| 31 | pub artist_name: Option<String>, | ||
| 32 | pub album_title: Option<String>, | ||
| 33 | } | ||
| 34 | |||
| 35 | pub struct Database { | ||
| 36 | conn: Connection, | ||
| 37 | } | ||
| 38 | |||
| 39 | #[allow(dead_code)] | ||
| 40 | impl Database { | ||
| 41 | pub fn open(_config: &crate::config::Config) -> SqlResult<Self> { | ||
| 42 | let proj_dirs = ProjectDirs::from("com", "rsplayer", "rsplayer").ok_or_else(|| { | ||
| 43 | rusqlite::Error::InvalidPath("Failed to get project directories".into()) | ||
| 44 | })?; | ||
| 45 | |||
| 46 | let data_dir = if let Ok(xdg) = std::env::var("XDG_CONFIG_HOME") { | ||
| 47 | PathBuf::from(xdg).join("rsplayer") | ||
| 48 | } else { | ||
| 49 | proj_dirs.data_dir().to_path_buf() | ||
| 50 | }; | ||
| 51 | |||
| 52 | fs::create_dir_all(&data_dir).ok(); | ||
| 53 | let db_path = data_dir.join("library.db"); | ||
| 54 | |||
| 55 | let conn = Connection::open(&db_path)?; | ||
| 56 | let db = Database { conn }; | ||
| 57 | db.init_schema()?; | ||
| 58 | Ok(db) | ||
| 59 | } | ||
| 60 | |||
| 61 | fn init_schema(&self) -> SqlResult<()> { | ||
| 62 | self.conn.execute( | ||
| 63 | "CREATE TABLE IF NOT EXISTS artists ( | ||
| 64 | id INTEGER PRIMARY KEY, | ||
| 65 | name TEXT UNIQUE NOT NULL | ||
| 66 | )", | ||
| 67 | [], | ||
| 68 | )?; | ||
| 69 | |||
| 70 | self.conn.execute( | ||
| 71 | "CREATE TABLE IF NOT EXISTS albums ( | ||
| 72 | id INTEGER PRIMARY KEY, | ||
| 73 | artist_id INTEGER NOT NULL, | ||
| 74 | title TEXT NOT NULL, | ||
| 75 | FOREIGN KEY (artist_id) REFERENCES artists(id), | ||
| 76 | UNIQUE(artist_id, title) | ||
| 77 | )", | ||
| 78 | [], | ||
| 79 | )?; | ||
| 80 | |||
| 81 | self.conn.execute( | ||
| 82 | "CREATE TABLE IF NOT EXISTS tracks ( | ||
| 83 | id INTEGER PRIMARY KEY, | ||
| 84 | album_id INTEGER NOT NULL, | ||
| 85 | title TEXT NOT NULL, | ||
| 86 | path TEXT UNIQUE NOT NULL, | ||
| 87 | duration INTEGER, | ||
| 88 | track_number INTEGER, | ||
| 89 | last_scanned INTEGER, | ||
| 90 | FOREIGN KEY (album_id) REFERENCES albums(id) | ||
| 91 | )", | ||
| 92 | [], | ||
| 93 | )?; | ||
| 94 | |||
| 95 | Ok(()) | ||
| 96 | } | ||
| 97 | |||
| 98 | pub fn scan_paths(&self, paths: &[PathBuf], force: bool) -> SqlResult<()> { | ||
| 99 | for path in paths { | ||
| 100 | self.scan_directory(path, force)?; | ||
| 101 | } | ||
| 102 | Ok(()) | ||
| 103 | } | ||
| 104 | |||
| 105 | fn scan_directory(&self, dir: &Path, force: bool) -> SqlResult<()> { | ||
| 106 | if !dir.exists() { | ||
| 107 | return Ok(()); | ||
| 108 | } | ||
| 109 | |||
| 110 | let now = SystemTime::now() | ||
| 111 | .duration_since(SystemTime::UNIX_EPOCH) | ||
| 112 | .unwrap() | ||
| 113 | .as_secs() as i64; | ||
| 114 | |||
| 115 | self.scan_recursive(dir, now, force)?; | ||
| 116 | self.cleanup_old_tracks(now)?; | ||
| 117 | Ok(()) | ||
| 118 | } | ||
| 119 | |||
| 120 | fn scan_recursive(&self, dir: &Path, scan_time: i64, force: bool) -> SqlResult<()> { | ||
| 121 | let entries = fs::read_dir(dir).ok(); | ||
| 122 | if let Some(entries) = entries { | ||
| 123 | for entry in entries.flatten() { | ||
| 124 | let path = entry.path(); | ||
| 125 | if path.is_dir() { | ||
| 126 | self.scan_recursive(&path, scan_time, force)?; | ||
| 127 | } else if is_audio_file(&path) { | ||
| 128 | self.process_audio_file(&path, scan_time, force)?; | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
| 132 | Ok(()) | ||
| 133 | } | ||
| 134 | |||
| 135 | fn process_audio_file(&self, path: &Path, scan_time: i64, force: bool) -> SqlResult<()> { | ||
| 136 | let path_str = path.to_string_lossy().to_string(); | ||
| 137 | |||
| 138 | if !force { | ||
| 139 | let count: i64 = self | ||
| 140 | .conn | ||
| 141 | .query_row( | ||
| 142 | "SELECT COUNT(*) FROM tracks WHERE path = ? AND last_scanned = ?", | ||
| 143 | rusqlite::params![path_str, scan_time], | ||
| 144 | |row| row.get(0), | ||
| 145 | ) | ||
| 146 | .unwrap_or(0); | ||
| 147 | if count > 0 { | ||
| 148 | return Ok(()); | ||
| 149 | } | ||
| 150 | } | ||
| 151 | |||
| 152 | let metadata = extract_metadata(path); | ||
| 153 | let (artist_name, album_title, track_title, track_number, duration) = metadata; | ||
| 154 | let duration_i64 = duration.map(|d| d as i64); | ||
| 155 | |||
| 156 | let artist_id = self.get_or_create_artist(&artist_name)?; | ||
| 157 | let album_id = self.get_or_create_album(artist_id, &album_title)?; | ||
| 158 | |||
| 159 | self.conn.execute( | ||
| 160 | "INSERT OR REPLACE INTO tracks (album_id, title, path, duration, track_number, last_scanned) | ||
| 161 | VALUES (?, ?, ?, ?, ?, ?)", | ||
| 162 | rusqlite::params![album_id, track_title, path_str, duration_i64, track_number, scan_time], | ||
| 163 | )?; | ||
| 164 | |||
| 165 | Ok(()) | ||
| 166 | } | ||
| 167 | |||
| 168 | fn get_or_create_artist(&self, name: &str) -> SqlResult<i64> { | ||
| 169 | self.conn | ||
| 170 | .execute("INSERT OR IGNORE INTO artists (name) VALUES (?)", [name])?; | ||
| 171 | self.conn | ||
| 172 | .query_row("SELECT id FROM artists WHERE name = ?", [name], |row| { | ||
| 173 | row.get(0) | ||
| 174 | }) | ||
| 175 | } | ||
| 176 | |||
| 177 | fn get_or_create_album(&self, artist_id: i64, title: &str) -> SqlResult<i64> { | ||
| 178 | self.conn.execute( | ||
| 179 | "INSERT OR IGNORE INTO albums (artist_id, title) VALUES (?, ?)", | ||
| 180 | rusqlite::params![artist_id, title], | ||
| 181 | )?; | ||
| 182 | self.conn.query_row( | ||
| 183 | "SELECT id FROM albums WHERE artist_id = ? AND title = ?", | ||
| 184 | rusqlite::params![artist_id, title], | ||
| 185 | |row| row.get(0), | ||
| 186 | ) | ||
| 187 | } | ||
| 188 | |||
| 189 | fn cleanup_old_tracks(&self, scan_time: i64) -> SqlResult<()> { | ||
| 190 | self.conn | ||
| 191 | .execute("DELETE FROM tracks WHERE last_scanned < ?", [scan_time])?; | ||
| 192 | Ok(()) | ||
| 193 | } | ||
| 194 | |||
| 195 | pub fn search_tracks(&self, query: &str) -> SqlResult<Vec<Track>> { | ||
| 196 | let mut stmt = self.conn.prepare( | ||
| 197 | "SELECT t.id, t.album_id, t.title, t.path, t.duration, t.track_number, ar.name, a.title | ||
| 198 | FROM tracks t | ||
| 199 | JOIN albums a ON t.album_id = a.id | ||
| 200 | JOIN artists ar ON a.artist_id = ar.id | ||
| 201 | WHERE t.title LIKE ? OR a.title LIKE ? OR ar.name LIKE ? | ||
| 202 | ORDER BY ar.name, a.title, t.track_number", | ||
| 203 | )?; | ||
| 204 | |||
| 205 | let search = format!("%{}%", query); | ||
| 206 | let rows = stmt.query_map([&search, &search, &search], |row| { | ||
| 207 | Ok(Track { | ||
| 208 | id: row.get(0)?, | ||
| 209 | album_id: row.get(1)?, | ||
| 210 | title: row.get(2)?, | ||
| 211 | path: PathBuf::from(row.get::<_, String>(3)?), | ||
| 212 | duration: row.get(4)?, | ||
| 213 | track_number: row.get(5)?, | ||
| 214 | artist_name: Some(row.get(6)?), | ||
| 215 | album_title: Some(row.get(7)?), | ||
| 216 | }) | ||
| 217 | })?; | ||
| 218 | |||
| 219 | rows.collect() | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | fn is_audio_file(path: &Path) -> bool { | ||
| 224 | path.extension() | ||
| 225 | .and_then(|ext| ext.to_str()) | ||
| 226 | .map(|ext| { | ||
| 227 | matches!( | ||
| 228 | ext.to_lowercase().as_str(), | ||
| 229 | "mp3" | "flac" | "ogg" | "m4a" | "wav" | "opus" | ||
| 230 | ) | ||
| 231 | }) | ||
| 232 | .unwrap_or(false) | ||
| 233 | } | ||
| 234 | |||
| 235 | fn extract_metadata(path: &Path) -> (String, String, String, Option<i32>, Option<u64>) { | ||
| 236 | let file_stem = path | ||
| 237 | .file_stem() | ||
| 238 | .and_then(|s| s.to_str()) | ||
| 239 | .unwrap_or("Unknown"); | ||
| 240 | let parent = path | ||
| 241 | .parent() | ||
| 242 | .and_then(|p| p.file_name()) | ||
| 243 | .and_then(|s| s.to_str()) | ||
| 244 | .unwrap_or("Unknown"); | ||
| 245 | let grandparent = path | ||
| 246 | .parent() | ||
| 247 | .and_then(|p| p.parent()) | ||
| 248 | .and_then(|p| p.file_name()) | ||
| 249 | .and_then(|s| s.to_str()) | ||
| 250 | .unwrap_or("Unknown"); | ||
| 251 | |||
| 252 | ( | ||
| 253 | grandparent.to_string(), | ||
| 254 | parent.to_string(), | ||
| 255 | file_stem.to_string(), | ||
| 256 | None, | ||
| 257 | None, | ||
| 258 | ) | ||
| 259 | } | ||
diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2b88c44 --- /dev/null +++ b/src/main.rs | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | mod audio; | ||
| 2 | mod cli; | ||
| 3 | mod config; | ||
| 4 | mod daemon; | ||
| 5 | mod database; | ||
| 6 | mod playback; | ||
| 7 | mod scrobbler; | ||
| 8 | |||
| 9 | use clap::Parser; | ||
| 10 | use cli::Cli; | ||
| 11 | |||
| 12 | fn main() { | ||
| 13 | let cli = Cli::parse(); | ||
| 14 | if let Err(e) = cli.run() { | ||
| 15 | eprintln!("Error: {}", e); | ||
| 16 | std::process::exit(1); | ||
| 17 | } | ||
| 18 | } | ||
diff --git a/src/playback.rs b/src/playback.rs new file mode 100644 index 0000000..76c0f44 --- /dev/null +++ b/src/playback.rs | |||
| @@ -0,0 +1,131 @@ | |||
| 1 | use std::path::PathBuf; | ||
| 2 | |||
| 3 | #[derive(Debug, Clone, PartialEq)] | ||
| 4 | #[allow(dead_code)] | ||
| 5 | pub enum PlaybackState { | ||
| 6 | Stopped, | ||
| 7 | Playing, | ||
| 8 | Paused, | ||
| 9 | } | ||
| 10 | |||
| 11 | #[derive(Debug, Clone)] | ||
| 12 | #[allow(dead_code)] | ||
| 13 | pub struct Track { | ||
| 14 | pub path: PathBuf, | ||
| 15 | pub title: String, | ||
| 16 | pub artist: String, | ||
| 17 | pub album: String, | ||
| 18 | pub duration: Option<u64>, | ||
| 19 | } | ||
| 20 | |||
| 21 | #[derive(Debug, Clone)] | ||
| 22 | #[allow(dead_code)] | ||
| 23 | pub enum ShuffleMode { | ||
| 24 | None, | ||
| 25 | All, | ||
| 26 | Album, | ||
| 27 | } | ||
| 28 | |||
| 29 | #[allow(dead_code)] | ||
| 30 | pub struct Player { | ||
| 31 | state: PlaybackState, | ||
| 32 | current_track: Option<Track>, | ||
| 33 | queue: Vec<Track>, | ||
| 34 | volume: u8, | ||
| 35 | shuffle: ShuffleMode, | ||
| 36 | } | ||
| 37 | |||
| 38 | #[allow(dead_code)] | ||
| 39 | impl Player { | ||
| 40 | pub fn new() -> Self { | ||
| 41 | Player { | ||
| 42 | state: PlaybackState::Stopped, | ||
| 43 | current_track: None, | ||
| 44 | queue: Vec::new(), | ||
| 45 | volume: 70, | ||
| 46 | shuffle: ShuffleMode::None, | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | pub fn state(&self) -> &PlaybackState { | ||
| 51 | &self.state | ||
| 52 | } | ||
| 53 | |||
| 54 | pub fn volume(&self) -> u8 { | ||
| 55 | self.volume | ||
| 56 | } | ||
| 57 | |||
| 58 | pub fn play(&mut self) { | ||
| 59 | if self.state == PlaybackState::Paused { | ||
| 60 | self.resume(); | ||
| 61 | } else if self.current_track.is_none() && !self.queue.is_empty() { | ||
| 62 | self.current_track = Some(self.queue.remove(0)); | ||
| 63 | self.state = PlaybackState::Playing; | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | pub fn play_track(&mut self, track: Track) { | ||
| 68 | self.current_track = Some(track); | ||
| 69 | self.state = PlaybackState::Playing; | ||
| 70 | } | ||
| 71 | |||
| 72 | pub fn pause(&mut self) { | ||
| 73 | if self.state == PlaybackState::Playing { | ||
| 74 | self.state = PlaybackState::Paused; | ||
| 75 | } | ||
| 76 | } | ||
| 77 | |||
| 78 | pub fn resume(&mut self) { | ||
| 79 | if self.state == PlaybackState::Paused { | ||
| 80 | self.state = PlaybackState::Playing; | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | pub fn stop(&mut self) { | ||
| 85 | self.state = PlaybackState::Stopped; | ||
| 86 | self.current_track = None; | ||
| 87 | } | ||
| 88 | |||
| 89 | pub fn next(&mut self) { | ||
| 90 | if !self.queue.is_empty() { | ||
| 91 | self.current_track = Some(self.queue.remove(0)); | ||
| 92 | } else { | ||
| 93 | self.stop(); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | pub fn previous(&mut self) { | ||
| 98 | if (self.state == PlaybackState::Playing || self.state == PlaybackState::Paused) | ||
| 99 | && self.current_track.is_some() | ||
| 100 | { | ||
| 101 | self.state = PlaybackState::Playing; | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | pub fn set_volume(&mut self, level: u8) { | ||
| 106 | self.volume = level.min(100); | ||
| 107 | } | ||
| 108 | |||
| 109 | pub fn set_shuffle(&mut self, mode: ShuffleMode) { | ||
| 110 | self.shuffle = mode; | ||
| 111 | } | ||
| 112 | |||
| 113 | pub fn queue_track(&mut self, track: Track, position: &str) { | ||
| 114 | match position { | ||
| 115 | "now" => self.queue.insert(0, track), | ||
| 116 | "next" => { | ||
| 117 | let idx = 1.min(self.queue.len()); | ||
| 118 | self.queue.insert(idx, track); | ||
| 119 | } | ||
| 120 | _ => self.queue.push(track), | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | pub fn dequeue_next(&mut self) -> Option<Track> { | ||
| 125 | if !self.queue.is_empty() { | ||
| 126 | Some(self.queue.remove(0)) | ||
| 127 | } else { | ||
| 128 | None | ||
| 129 | } | ||
| 130 | } | ||
| 131 | } | ||
diff --git a/src/scrobbler.rs b/src/scrobbler.rs new file mode 100644 index 0000000..09a7f55 --- /dev/null +++ b/src/scrobbler.rs | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | use reqwest::Client; | ||
| 2 | |||
| 3 | #[allow(dead_code)] | ||
| 4 | pub struct Scrobbler { | ||
| 5 | lastfm_api_key: Option<String>, | ||
| 6 | lastfm_api_secret: Option<String>, | ||
| 7 | lastfm_session_key: Option<String>, | ||
| 8 | listenbrainz_token: Option<String>, | ||
| 9 | client: Client, | ||
| 10 | } | ||
| 11 | |||
| 12 | #[allow(dead_code)] | ||
| 13 | impl Scrobbler { | ||
| 14 | pub fn new( | ||
| 15 | lastfm_api_key: Option<String>, | ||
| 16 | lastfm_api_secret: Option<String>, | ||
| 17 | lastfm_session_key: Option<String>, | ||
| 18 | listenbrainz_token: Option<String>, | ||
| 19 | ) -> Self { | ||
| 20 | Scrobbler { | ||
| 21 | lastfm_api_key, | ||
| 22 | lastfm_api_secret, | ||
| 23 | lastfm_session_key, | ||
| 24 | listenbrainz_token, | ||
| 25 | client: Client::new(), | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | pub fn now_playing(&self, artist: &str, track: &str, album: Option<&str>) { | ||
| 30 | if let Some(api_key) = &self.lastfm_api_key { | ||
| 31 | let _ = self.send_lastfm_now_playing(api_key, artist, track, album); | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | pub fn scrobble(&self, artist: &str, track: &str, album: Option<&str>, timestamp: u64) { | ||
| 36 | if let Some(api_key) = &self.lastfm_api_key { | ||
| 37 | let _ = self.send_lastfm_scrobble(api_key, artist, track, album, timestamp); | ||
| 38 | } | ||
| 39 | if let Some(token) = &self.listenbrainz_token { | ||
| 40 | let _ = self.send_listenbrainz_scrobble(token, artist, track, album, timestamp); | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | fn send_lastfm_now_playing( | ||
| 45 | &self, | ||
| 46 | _api_key: &str, | ||
| 47 | _artist: &str, | ||
| 48 | _track: &str, | ||
| 49 | _album: Option<&str>, | ||
| 50 | ) -> Result<(), Box<dyn std::error::Error>> { | ||
| 51 | Ok(()) | ||
| 52 | } | ||
| 53 | |||
| 54 | fn send_lastfm_scrobble( | ||
| 55 | &self, | ||
| 56 | _api_key: &str, | ||
| 57 | _artist: &str, | ||
| 58 | _track: &str, | ||
| 59 | _album: Option<&str>, | ||
| 60 | _timestamp: u64, | ||
| 61 | ) -> Result<(), Box<dyn std::error::Error>> { | ||
| 62 | Ok(()) | ||
| 63 | } | ||
| 64 | |||
| 65 | fn send_listenbrainz_scrobble( | ||
| 66 | &self, | ||
| 67 | _token: &str, | ||
| 68 | _artist: &str, | ||
| 69 | _track: &str, | ||
| 70 | _album: Option<&str>, | ||
| 71 | _timestamp: u64, | ||
| 72 | ) -> Result<(), Box<dyn std::error::Error>> { | ||
| 73 | Ok(()) | ||
| 74 | } | ||
| 75 | } | ||
