Rockbox open source high quality audio player as a Music Player Daemon
mpris rockbox mpd libadwaita audio rust zig deno
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #12 from tsirysndr/rockbox-library

feat: implement new rockbox music library manager

authored by

Tsiry Sandratraina and committed by
GitHub
538cc6f4 9a1232d7

+2497 -175
+553 -1
Cargo.lock
··· 387 387 checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" 388 388 389 389 [[package]] 390 + name = "android-tzdata" 391 + version = "0.1.1" 392 + source = "registry+https://github.com/rust-lang/crates.io-index" 393 + checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" 394 + 395 + [[package]] 390 396 name = "android_system_properties" 391 397 version = "0.1.5" 392 398 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 679 685 ] 680 686 681 687 [[package]] 688 + name = "atoi" 689 + version = "2.0.0" 690 + source = "registry+https://github.com/rust-lang/crates.io-index" 691 + checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" 692 + dependencies = [ 693 + "num-traits", 694 + ] 695 + 696 + [[package]] 682 697 name = "atomic-waker" 683 698 version = "1.1.2" 684 699 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 753 768 ] 754 769 755 770 [[package]] 771 + name = "base-x" 772 + version = "0.2.11" 773 + source = "registry+https://github.com/rust-lang/crates.io-index" 774 + checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" 775 + 776 + [[package]] 756 777 name = "base16ct" 757 778 version = "0.2.0" 758 779 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 763 784 version = "0.5.1" 764 785 source = "registry+https://github.com/rust-lang/crates.io-index" 765 786 checksum = "022dfe9eb35f19ebbcb51e0b40a5ab759f46ad60cadf7297e0bd085afb50e076" 787 + 788 + [[package]] 789 + name = "base36" 790 + version = "0.0.1" 791 + source = "registry+https://github.com/rust-lang/crates.io-index" 792 + checksum = "b9c26bddc1271f7112e5ec797e8eeba6de2de211c1488e506b9500196dbf77c5" 793 + dependencies = [ 794 + "base-x", 795 + "failure", 796 + ] 766 797 767 798 [[package]] 768 799 name = "base64" ··· 1030 1061 source = "registry+https://github.com/rust-lang/crates.io-index" 1031 1062 checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" 1032 1063 dependencies = [ 1064 + "android-tzdata", 1065 + "iana-time-zone", 1066 + "js-sys", 1033 1067 "num-traits", 1034 1068 "serde", 1069 + "wasm-bindgen", 1070 + "windows-targets 0.52.6", 1035 1071 ] 1036 1072 1037 1073 [[package]] ··· 1188 1224 ] 1189 1225 1190 1226 [[package]] 1227 + name = "crc" 1228 + version = "3.2.1" 1229 + source = "registry+https://github.com/rust-lang/crates.io-index" 1230 + checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" 1231 + dependencies = [ 1232 + "crc-catalog", 1233 + ] 1234 + 1235 + [[package]] 1236 + name = "crc-catalog" 1237 + version = "2.4.0" 1238 + source = "registry+https://github.com/rust-lang/crates.io-index" 1239 + checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" 1240 + 1241 + [[package]] 1191 1242 name = "crc32fast" 1192 1243 version = "1.4.2" 1193 1244 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1225 1276 ] 1226 1277 1227 1278 [[package]] 1279 + name = "crossbeam-queue" 1280 + version = "0.3.11" 1281 + source = "registry+https://github.com/rust-lang/crates.io-index" 1282 + checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" 1283 + dependencies = [ 1284 + "crossbeam-utils", 1285 + ] 1286 + 1287 + [[package]] 1228 1288 name = "crossbeam-utils" 1229 1289 version = "0.8.20" 1230 1290 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1263 1323 ] 1264 1324 1265 1325 [[package]] 1326 + name = "cuid" 1327 + version = "1.3.3" 1328 + source = "registry+https://github.com/rust-lang/crates.io-index" 1329 + checksum = "d7fe01ebbba358b9af4850d1a2b16d45f765137398e34134643790f19dc935a0" 1330 + dependencies = [ 1331 + "base36", 1332 + "cuid-util", 1333 + "cuid2", 1334 + "hostname 0.4.0", 1335 + "num", 1336 + "once_cell", 1337 + "rand", 1338 + "uuid", 1339 + ] 1340 + 1341 + [[package]] 1342 + name = "cuid-util" 1343 + version = "0.1.1" 1344 + source = "registry+https://github.com/rust-lang/crates.io-index" 1345 + checksum = "1d59a706635108a7e8eaae7ec8e6154504fafa4a415ef38690d94fccea051757" 1346 + 1347 + [[package]] 1348 + name = "cuid2" 1349 + version = "0.1.3" 1350 + source = "registry+https://github.com/rust-lang/crates.io-index" 1351 + checksum = "50e281dc36864ea88fae2ec4e21eb280e8239487acb1ddc59b528b0afa7997bd" 1352 + dependencies = [ 1353 + "cuid-util", 1354 + "getrandom", 1355 + "num", 1356 + "rand", 1357 + "sha3", 1358 + ] 1359 + 1360 + [[package]] 1266 1361 name = "curve25519-dalek" 1267 1362 version = "4.1.3" 1268 1363 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2389 2484 ] 2390 2485 2391 2486 [[package]] 2487 + name = "dotenvy" 2488 + version = "0.15.7" 2489 + source = "registry+https://github.com/rust-lang/crates.io-index" 2490 + checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" 2491 + 2492 + [[package]] 2392 2493 name = "dprint-swc-ext" 2393 2494 version = "0.18.0" 2394 2495 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2505 2606 version = "1.13.0" 2506 2607 source = "registry+https://github.com/rust-lang/crates.io-index" 2507 2608 checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 2609 + dependencies = [ 2610 + "serde", 2611 + ] 2508 2612 2509 2613 [[package]] 2510 2614 name = "elliptic-curve" ··· 2607 2711 checksum = "31ae425815400e5ed474178a7a22e275a9687086a12ca63ec793ff292d8fdae8" 2608 2712 2609 2713 [[package]] 2714 + name = "etcetera" 2715 + version = "0.8.0" 2716 + source = "registry+https://github.com/rust-lang/crates.io-index" 2717 + checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" 2718 + dependencies = [ 2719 + "cfg-if", 2720 + "home", 2721 + "windows-sys 0.48.0", 2722 + ] 2723 + 2724 + [[package]] 2610 2725 name = "event-listener" 2611 2726 version = "5.3.1" 2612 2727 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2628 2743 ] 2629 2744 2630 2745 [[package]] 2746 + name = "failure" 2747 + version = "0.1.8" 2748 + source = "registry+https://github.com/rust-lang/crates.io-index" 2749 + checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" 2750 + dependencies = [ 2751 + "backtrace", 2752 + "failure_derive", 2753 + ] 2754 + 2755 + [[package]] 2756 + name = "failure_derive" 2757 + version = "0.1.8" 2758 + source = "registry+https://github.com/rust-lang/crates.io-index" 2759 + checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" 2760 + dependencies = [ 2761 + "proc-macro2", 2762 + "quote", 2763 + "syn 1.0.109", 2764 + "synstructure 0.12.6", 2765 + ] 2766 + 2767 + [[package]] 2631 2768 name = "fallible-iterator" 2632 2769 version = "0.3.0" 2633 2770 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2757 2894 ] 2758 2895 2759 2896 [[package]] 2897 + name = "flume" 2898 + version = "0.11.0" 2899 + source = "registry+https://github.com/rust-lang/crates.io-index" 2900 + checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" 2901 + dependencies = [ 2902 + "futures-core", 2903 + "futures-sink", 2904 + "spin", 2905 + ] 2906 + 2907 + [[package]] 2760 2908 name = "fnv" 2761 2909 version = "1.0.7" 2762 2910 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 2891 3039 "futures-core", 2892 3040 "futures-task", 2893 3041 "futures-util", 3042 + ] 3043 + 3044 + [[package]] 3045 + name = "futures-intrusive" 3046 + version = "0.5.0" 3047 + source = "registry+https://github.com/rust-lang/crates.io-index" 3048 + checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" 3049 + dependencies = [ 3050 + "futures-core", 3051 + "lock_api", 3052 + "parking_lot", 2894 3053 ] 2895 3054 2896 3055 [[package]] ··· 3239 3398 ] 3240 3399 3241 3400 [[package]] 3401 + name = "hostname" 3402 + version = "0.4.0" 3403 + source = "registry+https://github.com/rust-lang/crates.io-index" 3404 + checksum = "f9c7c7c8ac16c798734b8a24560c1362120597c40d5e1459f09498f8f6c8f2ba" 3405 + dependencies = [ 3406 + "cfg-if", 3407 + "libc", 3408 + "windows", 3409 + ] 3410 + 3411 + [[package]] 3242 3412 name = "hstr" 3243 3413 version = "0.2.12" 3244 3414 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3417 3587 ] 3418 3588 3419 3589 [[package]] 3590 + name = "iana-time-zone" 3591 + version = "0.1.61" 3592 + source = "registry+https://github.com/rust-lang/crates.io-index" 3593 + checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" 3594 + dependencies = [ 3595 + "android_system_properties", 3596 + "core-foundation-sys", 3597 + "iana-time-zone-haiku", 3598 + "js-sys", 3599 + "wasm-bindgen", 3600 + "windows-core", 3601 + ] 3602 + 3603 + [[package]] 3604 + name = "iana-time-zone-haiku" 3605 + version = "0.1.2" 3606 + source = "registry+https://github.com/rust-lang/crates.io-index" 3607 + checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" 3608 + dependencies = [ 3609 + "cc", 3610 + ] 3611 + 3612 + [[package]] 3420 3613 name = "ident_case" 3421 3614 version = "1.0.1" 3422 3615 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3928 4121 ] 3929 4122 3930 4123 [[package]] 4124 + name = "lofty" 4125 + version = "0.21.1" 4126 + source = "registry+https://github.com/rust-lang/crates.io-index" 4127 + checksum = "c8bc4717ff10833a623b009e9254ae8667c7a59edc3cfb01c37aeeef4b6d54a7" 4128 + dependencies = [ 4129 + "byteorder", 4130 + "data-encoding", 4131 + "flate2", 4132 + "lofty_attr", 4133 + "log", 4134 + "ogg_pager", 4135 + "paste", 4136 + ] 4137 + 4138 + [[package]] 4139 + name = "lofty_attr" 4140 + version = "0.11.0" 4141 + source = "registry+https://github.com/rust-lang/crates.io-index" 4142 + checksum = "28bd4b9d8a5af74808932492521cdd272019b056f75fcc70056bd2c09fceb550" 4143 + dependencies = [ 4144 + "proc-macro2", 4145 + "quote", 4146 + "syn 2.0.77", 4147 + ] 4148 + 4149 + [[package]] 3931 4150 name = "log" 3932 4151 version = "0.4.22" 3933 4152 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3981 4200 dependencies = [ 3982 4201 "digest", 3983 4202 ] 4203 + 4204 + [[package]] 4205 + name = "md5" 4206 + version = "0.7.0" 4207 + source = "registry+https://github.com/rust-lang/crates.io-index" 4208 + checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" 3984 4209 3985 4210 [[package]] 3986 4211 name = "memchr" ··· 4278 4503 ] 4279 4504 4280 4505 [[package]] 4506 + name = "num" 4507 + version = "0.4.3" 4508 + source = "registry+https://github.com/rust-lang/crates.io-index" 4509 + checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" 4510 + dependencies = [ 4511 + "num-bigint", 4512 + "num-complex", 4513 + "num-integer", 4514 + "num-iter", 4515 + "num-rational", 4516 + "num-traits", 4517 + ] 4518 + 4519 + [[package]] 4281 4520 name = "num-bigint" 4282 4521 version = "0.4.6" 4283 4522 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4308 4547 ] 4309 4548 4310 4549 [[package]] 4550 + name = "num-complex" 4551 + version = "0.4.6" 4552 + source = "registry+https://github.com/rust-lang/crates.io-index" 4553 + checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" 4554 + dependencies = [ 4555 + "num-traits", 4556 + ] 4557 + 4558 + [[package]] 4311 4559 name = "num-conv" 4312 4560 version = "0.1.0" 4313 4561 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4334 4582 ] 4335 4583 4336 4584 [[package]] 4585 + name = "num-rational" 4586 + version = "0.4.2" 4587 + source = "registry+https://github.com/rust-lang/crates.io-index" 4588 + checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" 4589 + dependencies = [ 4590 + "num-bigint", 4591 + "num-integer", 4592 + "num-traits", 4593 + ] 4594 + 4595 + [[package]] 4337 4596 name = "num-traits" 4338 4597 version = "0.2.19" 4339 4598 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4369 4628 checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" 4370 4629 dependencies = [ 4371 4630 "memchr", 4631 + ] 4632 + 4633 + [[package]] 4634 + name = "ogg_pager" 4635 + version = "0.6.1" 4636 + source = "registry+https://github.com/rust-lang/crates.io-index" 4637 + checksum = "87b0bef808533c5890ab77279538212efdbbbd9aa4ef1ccdfcfbf77a42f7e6fa" 4638 + dependencies = [ 4639 + "byteorder", 4372 4640 ] 4373 4641 4374 4642 [[package]] ··· 5290 5558 source = "registry+https://github.com/rust-lang/crates.io-index" 5291 5559 checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" 5292 5560 dependencies = [ 5293 - "hostname", 5561 + "hostname 0.3.1", 5294 5562 "quick-error", 5295 5563 ] 5296 5564 ··· 5334 5602 dependencies = [ 5335 5603 "clap", 5336 5604 "owo-colors 4.1.0", 5605 + "rockbox-library", 5606 + "tokio", 5337 5607 ] 5338 5608 5339 5609 [[package]] ··· 5352 5622 "deno_webidl", 5353 5623 "deno_websocket", 5354 5624 "reqwest", 5625 + "rockbox-library", 5355 5626 "rockbox-sys", 5627 + "sqlx", 5356 5628 "tokio", 5357 5629 ] 5358 5630 ··· 5367 5639 "async-graphql-actix-web", 5368 5640 "owo-colors 4.1.0", 5369 5641 "reqwest", 5642 + "rockbox-library", 5370 5643 "rockbox-sys", 5371 5644 "serde", 5645 + "sqlx", 5646 + "tokio", 5647 + ] 5648 + 5649 + [[package]] 5650 + name = "rockbox-library" 5651 + version = "0.1.0" 5652 + dependencies = [ 5653 + "anyhow", 5654 + "chrono", 5655 + "cuid", 5656 + "futures", 5657 + "lofty", 5658 + "md5", 5659 + "owo-colors 4.1.0", 5660 + "rockbox-sys", 5661 + "serde", 5662 + "sqlx", 5372 5663 "tokio", 5373 5664 ] 5374 5665 ··· 5379 5670 "owo-colors 5.0.0", 5380 5671 "prost 0.13.2", 5381 5672 "reqwest", 5673 + "rockbox-library", 5382 5674 "rockbox-sys", 5383 5675 "serde", 5676 + "sqlx", 5384 5677 "tokio", 5385 5678 "tonic", 5386 5679 "tonic-build", ··· 5970 6263 version = "1.13.2" 5971 6264 source = "registry+https://github.com/rust-lang/crates.io-index" 5972 6265 checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 6266 + dependencies = [ 6267 + "serde", 6268 + ] 5973 6269 5974 6270 [[package]] 5975 6271 name = "smartstring" ··· 6016 6312 version = "0.9.8" 6017 6313 source = "registry+https://github.com/rust-lang/crates.io-index" 6018 6314 checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 6315 + dependencies = [ 6316 + "lock_api", 6317 + ] 6019 6318 6020 6319 [[package]] 6021 6320 name = "spirv" ··· 6037 6336 ] 6038 6337 6039 6338 [[package]] 6339 + name = "sqlformat" 6340 + version = "0.2.6" 6341 + source = "registry+https://github.com/rust-lang/crates.io-index" 6342 + checksum = "7bba3a93db0cc4f7bdece8bb09e77e2e785c20bfebf79eb8340ed80708048790" 6343 + dependencies = [ 6344 + "nom 7.1.3", 6345 + "unicode_categories", 6346 + ] 6347 + 6348 + [[package]] 6349 + name = "sqlx" 6350 + version = "0.8.2" 6351 + source = "registry+https://github.com/rust-lang/crates.io-index" 6352 + checksum = "93334716a037193fac19df402f8571269c84a00852f6a7066b5d2616dcd64d3e" 6353 + dependencies = [ 6354 + "sqlx-core", 6355 + "sqlx-macros", 6356 + "sqlx-mysql", 6357 + "sqlx-postgres", 6358 + "sqlx-sqlite", 6359 + ] 6360 + 6361 + [[package]] 6362 + name = "sqlx-core" 6363 + version = "0.8.2" 6364 + source = "registry+https://github.com/rust-lang/crates.io-index" 6365 + checksum = "d4d8060b456358185f7d50c55d9b5066ad956956fddec42ee2e8567134a8936e" 6366 + dependencies = [ 6367 + "atoi", 6368 + "byteorder", 6369 + "bytes", 6370 + "chrono", 6371 + "crc", 6372 + "crossbeam-queue", 6373 + "either", 6374 + "event-listener", 6375 + "futures-channel", 6376 + "futures-core", 6377 + "futures-intrusive", 6378 + "futures-io", 6379 + "futures-util", 6380 + "hashbrown 0.14.5", 6381 + "hashlink", 6382 + "hex", 6383 + "indexmap 2.5.0", 6384 + "log", 6385 + "memchr", 6386 + "once_cell", 6387 + "paste", 6388 + "percent-encoding", 6389 + "rustls", 6390 + "rustls-pemfile", 6391 + "serde", 6392 + "serde_json", 6393 + "sha2", 6394 + "smallvec", 6395 + "sqlformat", 6396 + "thiserror", 6397 + "tokio", 6398 + "tokio-stream", 6399 + "tracing", 6400 + "url", 6401 + "webpki-roots", 6402 + ] 6403 + 6404 + [[package]] 6405 + name = "sqlx-macros" 6406 + version = "0.8.2" 6407 + source = "registry+https://github.com/rust-lang/crates.io-index" 6408 + checksum = "cac0692bcc9de3b073e8d747391827297e075c7710ff6276d9f7a1f3d58c6657" 6409 + dependencies = [ 6410 + "proc-macro2", 6411 + "quote", 6412 + "sqlx-core", 6413 + "sqlx-macros-core", 6414 + "syn 2.0.77", 6415 + ] 6416 + 6417 + [[package]] 6418 + name = "sqlx-macros-core" 6419 + version = "0.8.2" 6420 + source = "registry+https://github.com/rust-lang/crates.io-index" 6421 + checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" 6422 + dependencies = [ 6423 + "dotenvy", 6424 + "either", 6425 + "heck 0.5.0", 6426 + "hex", 6427 + "once_cell", 6428 + "proc-macro2", 6429 + "quote", 6430 + "serde", 6431 + "serde_json", 6432 + "sha2", 6433 + "sqlx-core", 6434 + "sqlx-mysql", 6435 + "sqlx-postgres", 6436 + "sqlx-sqlite", 6437 + "syn 2.0.77", 6438 + "tempfile", 6439 + "tokio", 6440 + "url", 6441 + ] 6442 + 6443 + [[package]] 6444 + name = "sqlx-mysql" 6445 + version = "0.8.2" 6446 + source = "registry+https://github.com/rust-lang/crates.io-index" 6447 + checksum = "64bb4714269afa44aef2755150a0fc19d756fb580a67db8885608cf02f47d06a" 6448 + dependencies = [ 6449 + "atoi", 6450 + "base64 0.22.1", 6451 + "bitflags 2.6.0", 6452 + "byteorder", 6453 + "bytes", 6454 + "chrono", 6455 + "crc", 6456 + "digest", 6457 + "dotenvy", 6458 + "either", 6459 + "futures-channel", 6460 + "futures-core", 6461 + "futures-io", 6462 + "futures-util", 6463 + "generic-array", 6464 + "hex", 6465 + "hkdf", 6466 + "hmac", 6467 + "itoa", 6468 + "log", 6469 + "md-5", 6470 + "memchr", 6471 + "once_cell", 6472 + "percent-encoding", 6473 + "rand", 6474 + "rsa", 6475 + "serde", 6476 + "sha1", 6477 + "sha2", 6478 + "smallvec", 6479 + "sqlx-core", 6480 + "stringprep", 6481 + "thiserror", 6482 + "tracing", 6483 + "whoami", 6484 + ] 6485 + 6486 + [[package]] 6487 + name = "sqlx-postgres" 6488 + version = "0.8.2" 6489 + source = "registry+https://github.com/rust-lang/crates.io-index" 6490 + checksum = "6fa91a732d854c5d7726349bb4bb879bb9478993ceb764247660aee25f67c2f8" 6491 + dependencies = [ 6492 + "atoi", 6493 + "base64 0.22.1", 6494 + "bitflags 2.6.0", 6495 + "byteorder", 6496 + "chrono", 6497 + "crc", 6498 + "dotenvy", 6499 + "etcetera", 6500 + "futures-channel", 6501 + "futures-core", 6502 + "futures-io", 6503 + "futures-util", 6504 + "hex", 6505 + "hkdf", 6506 + "hmac", 6507 + "home", 6508 + "itoa", 6509 + "log", 6510 + "md-5", 6511 + "memchr", 6512 + "once_cell", 6513 + "rand", 6514 + "serde", 6515 + "serde_json", 6516 + "sha2", 6517 + "smallvec", 6518 + "sqlx-core", 6519 + "stringprep", 6520 + "thiserror", 6521 + "tracing", 6522 + "whoami", 6523 + ] 6524 + 6525 + [[package]] 6526 + name = "sqlx-sqlite" 6527 + version = "0.8.2" 6528 + source = "registry+https://github.com/rust-lang/crates.io-index" 6529 + checksum = "d5b2cf34a45953bfd3daaf3db0f7a7878ab9b7a6b91b422d24a7a9e4c857b680" 6530 + dependencies = [ 6531 + "atoi", 6532 + "chrono", 6533 + "flume", 6534 + "futures-channel", 6535 + "futures-core", 6536 + "futures-executor", 6537 + "futures-intrusive", 6538 + "futures-util", 6539 + "libsqlite3-sys", 6540 + "log", 6541 + "percent-encoding", 6542 + "serde", 6543 + "serde_urlencoded", 6544 + "sqlx-core", 6545 + "tracing", 6546 + "url", 6547 + ] 6548 + 6549 + [[package]] 6040 6550 name = "stable_deref_trait" 6041 6551 version = "1.2.0" 6042 6552 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 6077 6587 "quote", 6078 6588 "swc_macros_common", 6079 6589 "syn 2.0.77", 6590 + ] 6591 + 6592 + [[package]] 6593 + name = "stringprep" 6594 + version = "0.1.5" 6595 + source = "registry+https://github.com/rust-lang/crates.io-index" 6596 + checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" 6597 + dependencies = [ 6598 + "unicode-bidi", 6599 + "unicode-normalization", 6600 + "unicode-properties", 6080 6601 ] 6081 6602 6082 6603 [[package]] ··· 7102 7623 ] 7103 7624 7104 7625 [[package]] 7626 + name = "unicode-properties" 7627 + version = "0.1.2" 7628 + source = "registry+https://github.com/rust-lang/crates.io-index" 7629 + checksum = "52ea75f83c0137a9b98608359a5f1af8144876eb67bcb1ce837368e906a9f524" 7630 + 7631 + [[package]] 7105 7632 name = "unicode-segmentation" 7106 7633 version = "1.12.0" 7107 7634 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7118 7645 version = "0.2.5" 7119 7646 source = "registry+https://github.com/rust-lang/crates.io-index" 7120 7647 checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" 7648 + 7649 + [[package]] 7650 + name = "unicode_categories" 7651 + version = "0.1.1" 7652 + source = "registry+https://github.com/rust-lang/crates.io-index" 7653 + checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" 7121 7654 7122 7655 [[package]] 7123 7656 name = "universal-hash" ··· 7514 8047 version = "0.4.0" 7515 8048 source = "registry+https://github.com/rust-lang/crates.io-index" 7516 8049 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 8050 + 8051 + [[package]] 8052 + name = "windows" 8053 + version = "0.52.0" 8054 + source = "registry+https://github.com/rust-lang/crates.io-index" 8055 + checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" 8056 + dependencies = [ 8057 + "windows-core", 8058 + "windows-targets 0.52.6", 8059 + ] 8060 + 8061 + [[package]] 8062 + name = "windows-core" 8063 + version = "0.52.0" 8064 + source = "registry+https://github.com/rust-lang/crates.io-index" 8065 + checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" 8066 + dependencies = [ 8067 + "windows-targets 0.52.6", 8068 + ] 7517 8069 7518 8070 [[package]] 7519 8071 name = "windows-registry"
+17
README.md
··· 69 69 70 70 ![architecture](./docs/rockbox-server-architecture.jpg) 71 71 72 + ## 📚 GraphQL API 73 + 74 + Open [http://localhost:6062/graphiql](http://localhost:6062/graphiql) in your browser. 75 + 76 + <p style="margin-top: 20px; margin-bottom: 20px;"> 77 + <img src="./docs/graphql.png" width="100%" /> 78 + </p> 79 + 80 + ## 📚 gRPC API 81 + 82 + [https://buf.build/tsiry/rockboxapis/docs/main:rockbox.v1alpha1](https://buf.build/tsiry/rockboxapis/docs/main:rockbox.v1alpha1) 83 + 84 + Try Rockbox gRPC API using [Buf Studio](https://buf.build/studio/tsiry/rockboxapis/rockbox.v1alpha1.LibraryService/GetAlbums?target=http%3A%2F%2Flocalhost%3A6061&selectedProtocol=grpc-web). 85 + 86 + <p style="margin-top: 20px; margin-bottom: 20px;"> 87 + <img src="./docs/grpc.png" width="100%" /> 88 + </p>
+2
crates/cli/Cargo.toml
··· 9 9 [dependencies] 10 10 clap = "4.5.17" 11 11 owo-colors = "4.1.0" 12 + rockbox-library = {path = "../library"} 13 + tokio = {version = "1.36.0", features = ["full"]}
+23 -1
crates/cli/src/lib.rs
··· 1 1 use clap::Command; 2 2 use owo_colors::OwoColorize; 3 - use std::ffi::CStr; 3 + use rockbox_library::audio_scan::scan_audio_files; 4 + use rockbox_library::create_connection_pool; 5 + use std::{env, ffi::CStr}; 6 + use std::{fs, thread}; 4 7 5 8 #[macro_export] 6 9 macro_rules! cast_ptr { ··· 46 49 let cli = Command::new("rockbox").version(VERSION).about(&banner); 47 50 48 51 cli.get_matches_from(args); 52 + 53 + thread::spawn(move || { 54 + let home = env::var("HOME").unwrap(); 55 + 56 + match fs::create_dir_all(format!("{}/Music", home)) { 57 + Ok(_) => {} 58 + Err(e) => { 59 + eprintln!("Failed to create Music directory: {}", e); 60 + } 61 + } 62 + 63 + let path = env::var("ROCKBOX_LIBRARY").unwrap_or(format!("{}/Music", home)); 64 + let rt = tokio::runtime::Runtime::new().unwrap(); 65 + rt.block_on(async { 66 + let pool = create_connection_pool().await?; 67 + scan_audio_files(pool, path.into()).await 68 + }) 69 + .unwrap(); 70 + }); 49 71 50 72 return 0; 51 73 }
+2
crates/ext/Cargo.toml
··· 16 16 deno_webidl = "0.168.0" 17 17 deno_websocket = "0.173.0" 18 18 reqwest = {version = "0.12.7", features = ["rustls-tls", "json"], default-features = false} 19 + rockbox-library = {path = "../library"} 19 20 rockbox-sys = {path = "../sys"} 21 + sqlx = {version = "0.8.2", features = ["runtime-tokio", "tls-rustls", "sqlite", "chrono", "derive", "macros"]} 20 22 tokio = {version = "1.36.0", features = ["full"]} 21 23 22 24 [build-dependencies]
+1
crates/ext/build.rs
··· 81 81 esm_entry_point = "ext:rockbox/src/bootstrap.js", 82 82 esm = [ 83 83 "src/browse/browse.js", 84 + "src/library/library.js", 84 85 "src/playback/playback.js", 85 86 "src/playlist/playlist.js", 86 87 "src/settings/settings.js",
+3
crates/ext/example.ts
··· 7 7 console.log(await rb.playlist.getCurrent()); 8 8 console.log(await rb.playlist.amount()); 9 9 console.log(await rb.browse.tree.getEntries("/")); 10 + console.log(await rb.library.album.getAlbums()); 11 + console.log(await rb.library.artist.getArtists()); 12 + console.log(await rb.library.track.getTracks());
+1
crates/ext/src/bootstrap.js
··· 1 1 import "ext:deno_console/01_console.js"; 2 2 import "ext:rockbox/src/browse/browse.js"; 3 + import "ext:rockbox/src/library/library.js"; 3 4 import "ext:rockbox/src/playback/playback.js"; 4 5 import "ext:rockbox/src/playlist/playlist.js"; 5 6 import "ext:rockbox/src/settings/settings.js";
+3
crates/ext/src/lib.rs
··· 5 5 use deno_ast::{MediaType, ParseParams}; 6 6 use deno_core::{error::AnyError, ModuleLoadResponse, ModuleSourceCode}; 7 7 use deno_net::NetPermissions; 8 + use library::rb_library; 8 9 use playback::rb_playback; 9 10 use playlist::rb_playlist; 10 11 use settings::rb_settings; ··· 13 14 14 15 pub mod browse; 15 16 pub mod dir; 17 + pub mod library; 16 18 pub mod playback; 17 19 pub mod playlist; 18 20 pub mod settings; ··· 161 163 startup_snapshot: Some(RUNTIME_SNAPSHOT), 162 164 extensions: vec![ 163 165 rb_browse::init_ops(), 166 + rb_library::init_ops(), 164 167 rb_playback::init_ops(), 165 168 rb_playlist::init_ops(), 166 169 rb_settings::init_ops(),
+18
crates/ext/src/library/library.js
··· 1 + const { ops } = Deno.core; 2 + 3 + const library = { 4 + album: { 5 + getAlbum: (id) => ops.op_get_album(id), 6 + getAlbums: () => ops.op_get_albums(), 7 + }, 8 + artist: { 9 + getArtist: (id) => ops.op_get_artist(id), 10 + getArtists: () => ops.op_get_artists(), 11 + }, 12 + track: { 13 + getTrack: (id) => ops.op_get_track(id), 14 + getTracks: () => ops.op_get_tracks(), 15 + }, 16 + }; 17 + 18 + globalThis.rb = { ...globalThis.rb, library };
+67
crates/ext/src/library/mod.rs
··· 1 + use deno_core::{error::AnyError, extension, op2}; 2 + use rockbox_library::{ 3 + create_connection_pool, 4 + entity::{album::Album, artist::Artist, track::Track}, 5 + repo, 6 + }; 7 + 8 + extension!( 9 + rb_library, 10 + ops = [ 11 + op_get_albums, 12 + op_get_artists, 13 + op_get_tracks, 14 + op_get_album, 15 + op_get_artist, 16 + op_get_track, 17 + ], 18 + esm = ["src/library/library.js"], 19 + ); 20 + 21 + #[op2(async)] 22 + #[serde] 23 + pub async fn op_get_albums() -> Result<Vec<Album>, AnyError> { 24 + let pool = create_connection_pool().await?; 25 + let albums = repo::album::all(pool).await?; 26 + Ok(albums) 27 + } 28 + 29 + #[op2(async)] 30 + #[serde] 31 + pub async fn op_get_artists() -> Result<Vec<Artist>, AnyError> { 32 + let pool = create_connection_pool().await?; 33 + let artists = repo::artist::all(pool).await?; 34 + Ok(artists) 35 + } 36 + 37 + #[op2(async)] 38 + #[serde] 39 + pub async fn op_get_tracks() -> Result<Vec<Track>, AnyError> { 40 + let pool = create_connection_pool().await?; 41 + let tracks = repo::track::all(pool).await?; 42 + Ok(tracks) 43 + } 44 + 45 + #[op2(async)] 46 + #[serde] 47 + pub async fn op_get_album(#[string] id: String) -> Result<Option<Album>, AnyError> { 48 + let pool = create_connection_pool().await?; 49 + let album = repo::album::find(pool, &id).await?; 50 + Ok(album) 51 + } 52 + 53 + #[op2(async)] 54 + #[serde] 55 + pub async fn op_get_artist(#[string] id: String) -> Result<Option<Artist>, AnyError> { 56 + let pool = create_connection_pool().await?; 57 + let artist = repo::artist::find(pool, &id).await?; 58 + Ok(artist) 59 + } 60 + 61 + #[op2(async)] 62 + #[serde] 63 + pub async fn op_get_track(#[string] id: String) -> Result<Option<Track>, AnyError> { 64 + let pool = create_connection_pool().await?; 65 + let track = repo::track::find(pool, &id).await?; 66 + Ok(track) 67 + }
+2
crates/graphql/Cargo.toml
··· 11 11 async-graphql-actix-web = "7.0.9" 12 12 owo-colors = "4.1.0" 13 13 reqwest = {version = "0.12.7", features = ["rustls-tls", "json"], default-features = false} 14 + rockbox-library = {path = "../library"} 14 15 rockbox-sys = {path = "../sys"} 15 16 serde = "1.0.210" 17 + sqlx = {version = "0.8.2", features = ["runtime-tokio", "tls-rustls", "sqlite", "chrono", "derive", "macros"]} 16 18 tokio = {version = "1.36.0", features = ["full"]}
-16
crates/graphql/src/schema/browse.rs
··· 7 7 8 8 #[Object] 9 9 impl BrowseQuery { 10 - async fn browse_id3(&self) -> String { 11 - "browse id3".to_string() 12 - } 13 - 14 - async fn tree_get_context(&self) -> String { 15 - "tree get context".to_string() 16 - } 17 - 18 10 async fn tree_get_entries(&self, ctx: &Context<'_>, path: String) -> Result<Vec<Entry>, Error> { 19 11 let client = ctx.data::<reqwest::Client>().unwrap(); 20 12 let url = format!("{}/tree_entries?q={}", rockbox_url(), path); 21 13 let response = client.get(&url).send().await?; 22 14 let response = response.json::<Vec<Entry>>().await?; 23 15 Ok(response) 24 - } 25 - 26 - async fn tree_get_entry_at(&self, ctx: &Context<'_>) -> Result<Entry, Error> { 27 - todo!() 28 - } 29 - 30 - async fn rockbox_browse(&self) -> String { 31 - "rockbox browse".to_string() 32 16 } 33 17 }
+49
crates/graphql/src/schema/library.rs
··· 1 + use async_graphql::*; 2 + use rockbox_library::repo; 3 + use sqlx::{Pool, Sqlite}; 4 + 5 + use crate::schema::objects::track::Track; 6 + 7 + use super::objects::{album::Album, artist::Artist}; 8 + 9 + #[derive(Default)] 10 + pub struct LibraryQuery; 11 + 12 + #[Object] 13 + impl LibraryQuery { 14 + async fn albums(&self, ctx: &Context<'_>) -> Result<Vec<Album>, Error> { 15 + let pool = ctx.data::<Pool<Sqlite>>()?; 16 + let results = repo::album::all(pool.clone()).await?; 17 + Ok(results.into_iter().map(Into::into).collect()) 18 + } 19 + 20 + async fn artists(&self, ctx: &Context<'_>) -> Result<Vec<Artist>, Error> { 21 + let pool = ctx.data::<Pool<Sqlite>>()?; 22 + let results = repo::artist::all(pool.clone()).await?; 23 + Ok(results.into_iter().map(Into::into).collect()) 24 + } 25 + 26 + async fn tracks(&self, ctx: &Context<'_>) -> Result<Vec<Track>, Error> { 27 + let pool = ctx.data::<Pool<Sqlite>>()?; 28 + let results = repo::track::all(pool.clone()).await?; 29 + Ok(results.into_iter().map(Into::into).collect()) 30 + } 31 + 32 + async fn album(&self, ctx: &Context<'_>, id: String) -> Result<Option<Album>, Error> { 33 + let pool = ctx.data::<Pool<Sqlite>>()?; 34 + let results = repo::album::find(pool.clone(), &id).await?; 35 + Ok(results.map(Into::into)) 36 + } 37 + 38 + async fn artist(&self, ctx: &Context<'_>, id: String) -> Result<Option<Artist>, Error> { 39 + let pool = ctx.data::<Pool<Sqlite>>()?; 40 + let results = repo::artist::find(pool.clone(), &id).await?; 41 + Ok(results.map(Into::into)) 42 + } 43 + 44 + async fn track(&self, ctx: &Context<'_>, id: String) -> Result<Option<Track>, Error> { 45 + let pool = ctx.data::<Pool<Sqlite>>()?; 46 + let results = repo::track::find(pool.clone(), &id).await?; 47 + Ok(results.map(Into::into)) 48 + } 49 + }
+3
crates/graphql/src/schema/mod.rs
··· 1 1 use async_graphql::MergedObject; 2 2 use browse::BrowseQuery; 3 + use library::LibraryQuery; 3 4 use playback::{PlaybackMutation, PlaybackQuery}; 4 5 use playlist::{PlaylistMutation, PlaylistQuery}; 5 6 use settings::SettingsQuery; ··· 7 8 use system::SystemQuery; 8 9 9 10 pub mod browse; 11 + pub mod library; 10 12 pub mod metadata; 11 13 pub mod objects; 12 14 pub mod playback; ··· 19 21 #[derive(MergedObject, Default)] 20 22 pub struct Query( 21 23 BrowseQuery, 24 + LibraryQuery, 22 25 PlaybackQuery, 23 26 PlaylistQuery, 24 27 SoundQuery,
+58
crates/graphql/src/schema/objects/album.rs
··· 1 + use async_graphql::*; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(Default, Clone, Serialize, Deserialize)] 5 + pub struct Album { 6 + pub id: String, 7 + pub title: String, 8 + pub artist: String, 9 + pub year: u32, 10 + pub year_string: String, 11 + pub album_art: Option<String>, 12 + pub md5: String, 13 + } 14 + 15 + #[Object] 16 + impl Album { 17 + async fn id(&self) -> &str { 18 + &self.id 19 + } 20 + 21 + async fn title(&self) -> &str { 22 + &self.title 23 + } 24 + 25 + async fn artist(&self) -> &str { 26 + &self.artist 27 + } 28 + 29 + async fn year(&self) -> i32 { 30 + self.year as i32 31 + } 32 + 33 + async fn year_string(&self) -> &str { 34 + &self.year_string 35 + } 36 + 37 + async fn album_art(&self) -> Option<&str> { 38 + self.album_art.as_deref() 39 + } 40 + 41 + async fn md5(&self) -> &str { 42 + &self.md5 43 + } 44 + } 45 + 46 + impl From<rockbox_library::entity::album::Album> for Album { 47 + fn from(album: rockbox_library::entity::album::Album) -> Self { 48 + Self { 49 + id: album.id, 50 + title: album.title, 51 + artist: album.artist, 52 + year: album.year, 53 + year_string: album.year_string, 54 + album_art: album.album_art, 55 + md5: album.md5, 56 + } 57 + } 58 + }
+40
crates/graphql/src/schema/objects/artist.rs
··· 1 + use async_graphql::*; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(Default, Clone, Serialize, Deserialize)] 5 + pub struct Artist { 6 + pub id: String, 7 + pub name: String, 8 + pub bio: Option<String>, 9 + pub image: Option<String>, 10 + } 11 + 12 + #[Object] 13 + impl Artist { 14 + async fn id(&self) -> &str { 15 + &self.id 16 + } 17 + 18 + async fn name(&self) -> &str { 19 + &self.name 20 + } 21 + 22 + async fn bio(&self) -> Option<&str> { 23 + self.bio.as_deref() 24 + } 25 + 26 + async fn image(&self) -> Option<&str> { 27 + self.image.as_deref() 28 + } 29 + } 30 + 31 + impl From<rockbox_library::entity::artist::Artist> for Artist { 32 + fn from(artist: rockbox_library::entity::artist::Artist) -> Self { 33 + Self { 34 + id: artist.id, 35 + name: artist.name, 36 + bio: artist.bio, 37 + image: artist.image, 38 + } 39 + } 40 + }
+2
crates/graphql/src/schema/objects/mod.rs
··· 1 + pub mod album; 2 + pub mod artist; 1 3 pub mod compressor_settings; 2 4 pub mod entry; 3 5 pub mod eq_band_setting;
+48
crates/graphql/src/schema/objects/track.rs
··· 4 4 5 5 #[derive(Default, Clone, Serialize)] 6 6 pub struct Track { 7 + pub id: Option<String>, 7 8 pub title: String, 8 9 pub artist: String, 9 10 pub album: String, ··· 25 26 pub length: u64, 26 27 pub elapsed: u64, 27 28 pub path: String, 29 + pub album_id: Option<String>, 30 + pub artist_id: Option<String>, 31 + pub genre_id: Option<String>, 28 32 } 29 33 30 34 #[Object] 31 35 impl Track { 36 + async fn id(&self) -> Option<&str> { 37 + self.id.as_deref() 38 + } 39 + 32 40 async fn title(&self) -> &str { 33 41 &self.title 34 42 } ··· 112 120 async fn path(&self) -> &str { 113 121 &self.path 114 122 } 123 + 124 + async fn album_id(&self) -> Option<&str> { 125 + self.album_id.as_deref() 126 + } 127 + 128 + async fn artist_id(&self) -> Option<&str> { 129 + self.artist_id.as_deref() 130 + } 131 + 132 + async fn genre_id(&self) -> Option<&str> { 133 + self.genre_id.as_deref() 134 + } 115 135 } 116 136 117 137 impl From<Mp3Entry> for Track { ··· 160 180 length, 161 181 elapsed, 162 182 path, 183 + ..Default::default() 184 + } 185 + } 186 + } 187 + 188 + impl From<rockbox_library::entity::track::Track> for Track { 189 + fn from(track: rockbox_library::entity::track::Track) -> Self { 190 + Self { 191 + id: Some(track.id), 192 + title: track.title, 193 + artist: track.artist, 194 + album: track.album, 195 + genre: track.genre.unwrap_or_default(), 196 + year_string: track.year_string.unwrap_or_default(), 197 + composer: track.composer, 198 + album_artist: track.album_artist, 199 + discnum: track.disc_number as i32, 200 + tracknum: track.track_number.unwrap_or_default() as i32, 201 + year: track.year.unwrap_or_default() as i32, 202 + bitrate: track.bitrate, 203 + frequency: track.frequency as u64, 204 + filesize: track.filesize as u64, 205 + length: track.length as u64, 206 + artist_id: Some(track.artist_id), 207 + album_id: Some(track.album_id), 208 + genre_id: Some(track.genre_id), 209 + path: track.path, 210 + ..Default::default() 163 211 } 164 212 } 165 213 }
+6 -1
crates/graphql/src/server.rs
··· 6 6 web::{self, Data}, 7 7 App, HttpRequest, HttpResponse, HttpServer, Result, 8 8 }; 9 + use anyhow::Error; 9 10 use async_graphql::{http::GraphiQLSource, EmptySubscription, Schema}; 10 11 use async_graphql_actix_web::{GraphQLRequest, GraphQLResponse}; 11 12 use owo_colors::OwoColorize; 13 + use rockbox_library::create_connection_pool; 12 14 use rockbox_sys::events::RockboxCommand; 13 15 14 16 use crate::{ ··· 46 48 )) 47 49 } 48 50 49 - pub async fn start(cmd_tx: Arc<Mutex<Sender<RockboxCommand>>>) -> std::io::Result<()> { 51 + pub async fn start(cmd_tx: Arc<Mutex<Sender<RockboxCommand>>>) -> Result<(), Error> { 50 52 let client = reqwest::Client::new(); 53 + let pool = create_connection_pool().await?; 51 54 let schema = Schema::build( 52 55 Query::default(), 53 56 Mutation::default(), ··· 55 58 ) 56 59 .data(cmd_tx) 57 60 .data(client) 61 + .data(pool) 58 62 .finish(); 59 63 let graphql_port = std::env::var("ROCKBOX_GRAPHQL_PORT").unwrap_or("6062".to_string()); 60 64 let addr = format!("{}:{}", "0.0.0.0", graphql_port); ··· 76 80 .bind(addr)? 77 81 .run() 78 82 .await 83 + .map_err(Error::new) 79 84 }
+17
crates/library/Cargo.toml
··· 1 + [package] 2 + edition = "2021" 3 + name = "rockbox-library" 4 + version = "0.1.0" 5 + 6 + [dependencies] 7 + anyhow = "1.0.89" 8 + chrono = {version = "0.4.38", features = ["serde"]} 9 + cuid = "1.3.3" 10 + futures = "0.3.30" 11 + lofty = "0.21.1" 12 + md5 = "0.7.0" 13 + owo-colors = "4.1.0" 14 + rockbox-sys = {path = "../sys"} 15 + serde = "1.0.210" 16 + sqlx = {version = "0.8.2", features = ["runtime-tokio", "tls-rustls", "sqlite", "chrono", "derive", "macros"]} 17 + tokio = {version = "1.36.0", features = ["full"]}
+93
crates/library/migrations/20240923093823_create_tables.sql
··· 1 + -- Add migration script here 2 + CREATE TABLE IF NOT EXISTS album ( 3 + id VARCHAR(255) PRIMARY KEY, 4 + title VARCHAR(255) NOT NULL, 5 + artist VARCHAR(255) NOT NULL, 6 + year INT, 7 + year_string VARCHAR(255), 8 + album_art VARCHAR(255), 9 + md5 VARCHAR(255) NOT NULL UNIQUE 10 + ); 11 + 12 + CREATE TABLE IF NOT EXISTS track ( 13 + id VARCHAR(255) PRIMARY KEY, 14 + path VARCHAR(255) NOT NULL UNIQUE, 15 + title VARCHAR(255) NOT NULL, 16 + artist VARCHAR(255) NOT NULL, 17 + album VARCHAR(255) NOT NULL, 18 + album_artist VARCHAR(255) NOT NULL, 19 + bitrate INT NOT NULL, 20 + composer VARCHAR(255) NOT NULL, 21 + disc_number INT NOT NULL, 22 + filesize INT NOT NULL, 23 + frequency INT NOT NULL, 24 + length INT NOT NULL, 25 + track_number INT, 26 + year INT, 27 + year_string VARCHAR(255), 28 + genre VARCHAR(255), 29 + md5 VARCHAR(255) NOT NULL UNIQUE, 30 + album_art VARCHAR(255), 31 + artist_id VARCHAR(255) NOT NULL, 32 + album_id VARCHAR(255) NOT NULL, 33 + genre_id VARCHAR(255), 34 + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 35 + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 36 + ); 37 + 38 + CREATE TABLE IF NOT EXISTS artist ( 39 + id VARCHAR(255) PRIMARY KEY, 40 + name VARCHAR(255) NOT NULL UNIQUE, 41 + bio TEXT, 42 + image VARCHAR(255) 43 + ); 44 + 45 + CREATE TABLE IF NOT EXISTS genre ( 46 + id VARCHAR(255) PRIMARY KEY, 47 + name VARCHAR(255) NOT NULL, 48 + description TEXT, 49 + image VARCHAR(255) 50 + ); 51 + 52 + CREATE TABLE IF NOT EXISTS playlist ( 53 + id VARCHAR(255) PRIMARY KEY, 54 + name VARCHAR(255) NOT NULL, 55 + description TEXT, 56 + image VARCHAR(255), 57 + folder_id VARCHAR(255), 58 + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 59 + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 60 + ); 61 + 62 + CREATE TABLE IF NOT EXISTS playlist_tracks ( 63 + id VARCHAR(255) PRIMARY KEY, 64 + playlist_id VARCHAR(255) NOT NULL, 65 + track_id VARCHAR(255) NOT NULL, 66 + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 67 + ); 68 + 69 + CREATE TABLE IF NOT EXISTS folder ( 70 + id VARCHAR(255) PRIMARY KEY, 71 + name VARCHAR(255) NOT NULL, 72 + parent_id VARCHAR(255), 73 + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, 74 + updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 75 + ); 76 + 77 + CREATE TABLE IF NOT EXISTS favourites ( 78 + id VARCHAR(255) PRIMARY KEY, 79 + track_id VARCHAR(255) NOT NULL, 80 + created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP 81 + ); 82 + 83 + CREATE TABLE IF NOT EXISTS artist_tracks ( 84 + id VARCHAR(255) PRIMARY KEY, 85 + artist_id VARCHAR(255) NOT NULL, 86 + track_id VARCHAR(255) NOT NULL 87 + ); 88 + 89 + CREATE TABLE IF NOT EXISTS album_tracks ( 90 + id VARCHAR(255) PRIMARY KEY, 91 + album_id VARCHAR(255) NOT NULL, 92 + track_id VARCHAR(255) NOT NULL 93 + );
+80
crates/library/src/album_art.rs
··· 1 + use std::io::Write; 2 + 3 + use anyhow::Error; 4 + use lofty::{file::TaggedFileExt, probe::Probe, tag::Accessor}; 5 + 6 + pub fn extract_and_save_album_cover(track_path: &str) -> Result<Option<String>, Error> { 7 + let tagged_file = match Probe::open(track_path) 8 + .expect("ERROR: Bad path provided!") 9 + .read() 10 + { 11 + Ok(tagged_file) => tagged_file, 12 + Err(e) => { 13 + println!("Error opening file: {}", e); 14 + return Ok(None); 15 + } 16 + }; 17 + 18 + let primary_tag = tagged_file.primary_tag(); 19 + let tag = match primary_tag { 20 + Some(tag) => tag, 21 + None => { 22 + println!("No tag found in file: {}", track_path); 23 + return Ok(None); 24 + } 25 + }; 26 + 27 + let pictures = tag.pictures(); 28 + if pictures.len() > 0 { 29 + let home = std::env::var("HOME")?; 30 + let covers_path = format!("{}/.config/rockbox.org/covers", home); 31 + std::fs::create_dir_all(&covers_path)?; 32 + let picture = &pictures[0]; 33 + 34 + if tag.album().is_none() { 35 + println!("No album found in file: {}", track_path); 36 + return Ok(None); 37 + } 38 + 39 + let album = md5::compute(tag.album().unwrap().as_bytes()); 40 + let filename = format!("{}/{:x}", covers_path, album); 41 + match picture.mime_type() { 42 + Some(lofty::picture::MimeType::Jpeg) => { 43 + let filename = format!("{}.jpg", filename); 44 + let mut file = std::fs::File::create(filename)?; 45 + file.write_all(picture.data())?; 46 + Ok(Some(format!("{:x}.jpg", album))) 47 + } 48 + Some(lofty::picture::MimeType::Png) => { 49 + let filename = format!("{}.png", filename); 50 + let mut file = std::fs::File::create(filename)?; 51 + file.write_all(picture.data())?; 52 + Ok(Some(format!("{:x}.png", album))) 53 + } 54 + Some(lofty::picture::MimeType::Bmp) => { 55 + let filename = format!("{}.bmp", filename); 56 + let mut file = std::fs::File::create(filename)?; 57 + file.write_all(picture.data())?; 58 + Ok(Some(format!("{:x}.bmp", album))) 59 + } 60 + Some(lofty::picture::MimeType::Gif) => { 61 + let filename = format!("{}.gif", filename); 62 + let mut file = std::fs::File::create(filename)?; 63 + file.write_all(picture.data())?; 64 + Ok(Some(format!("{:x}.gif", album))) 65 + } 66 + Some(lofty::picture::MimeType::Tiff) => { 67 + let filename = format!("{}.tiff", filename); 68 + let mut file = std::fs::File::create(filename)?; 69 + file.write_all(picture.data())?; 70 + Ok(Some(format!("{:x}.tiff", album))) 71 + } 72 + _ => { 73 + println!("Unsupported picture format"); 74 + Ok(None) 75 + } 76 + } 77 + } else { 78 + Ok(None) 79 + } 80 + }
+143
crates/library/src/audio_scan.rs
··· 1 + use crate::album_art::extract_and_save_album_cover; 2 + use crate::entity::album::Album; 3 + use crate::entity::artist::Artist; 4 + use crate::{entity::track::Track, repo}; 5 + use anyhow::Error; 6 + use chrono::Utc; 7 + use futures::future::BoxFuture; 8 + use futures::stream::{FuturesUnordered, StreamExt}; 9 + use owo_colors::OwoColorize; 10 + use rockbox_sys as rb; 11 + use sqlx::{Pool, Sqlite}; 12 + use std::path::PathBuf; 13 + use tokio::fs; 14 + 15 + const AUDIO_EXTENSIONS: [&str; 17] = [ 16 + "mp3", "ogg", "flac", "m4a", "aac", "mp4", "alac", "wav", "wv", "mpc", "aiff", "ac3", "opus", 17 + "spx", "sid", "ape", "wma", 18 + ]; 19 + 20 + pub fn scan_audio_files( 21 + pool: Pool<Sqlite>, 22 + audio_dir: PathBuf, 23 + ) -> BoxFuture<'static, Result<Vec<PathBuf>, Error>> { 24 + Box::pin(async move { 25 + let mut result = Vec::new(); 26 + let mut dir = fs::read_dir(audio_dir).await?; 27 + let mut futures = FuturesUnordered::new(); 28 + while let Some(entry) = dir.next_entry().await? { 29 + let path = entry.path(); 30 + if path.is_dir() { 31 + println!("{} {:?}", "Scanning".bright_green(), path); 32 + let dir_path = path.clone(); 33 + let cloned_pool = pool.clone(); 34 + futures.push(tokio::spawn(async move { 35 + scan_audio_files(cloned_pool, dir_path).await 36 + })); 37 + } else if path.is_file() { 38 + let path = path.to_str().unwrap(); 39 + if !AUDIO_EXTENSIONS 40 + .into_iter() 41 + .any(|ext| path.ends_with(&format!(".{}", ext))) 42 + { 43 + continue; 44 + } 45 + let filename = path.split('/').last().unwrap(); 46 + let dir = path.replace(filename, ""); 47 + println!( 48 + "{} {}{}", 49 + "Found".bright_green(), 50 + dir, 51 + filename.bright_yellow() 52 + ); 53 + let entry = rb::metadata::get_metadata(-1, path); 54 + 55 + let track_hash = format!("{:x}", md5::compute(entry.path.as_bytes())); 56 + let artist_id = cuid::cuid1()?; 57 + let album_id = cuid::cuid1()?; 58 + let album_md5 = format!( 59 + "{:x}", 60 + md5::compute( 61 + format!("{}{}{}", entry.albumartist, entry.album, entry.year).as_bytes() 62 + ) 63 + ); 64 + 65 + repo::track::save( 66 + pool.clone(), 67 + Track { 68 + id: cuid::cuid1()?, 69 + path: entry.path.clone(), 70 + title: entry.title, 71 + artist: entry.artist.clone(), 72 + album: entry.album.clone(), 73 + genre: match entry.genre_string.as_str() { 74 + "" => None, 75 + _ => Some(entry.genre_string), 76 + }, 77 + year: Some(entry.year as u32), 78 + track_number: Some(entry.tracknum as u32), 79 + disc_number: entry.discnum as u32, 80 + year_string: Some(entry.year_string.clone()), 81 + composer: entry.composer, 82 + album_artist: entry.albumartist.clone(), 83 + bitrate: entry.bitrate, 84 + frequency: entry.frequency as u32, 85 + filesize: entry.filesize as u32, 86 + length: entry.length as u32, 87 + md5: track_hash, 88 + created_at: Utc::now(), 89 + updated_at: Utc::now(), 90 + artist_id: artist_id.clone(), 91 + album_id: album_id.clone(), 92 + ..Default::default() 93 + }, 94 + ) 95 + .await?; 96 + 97 + let album_art = extract_and_save_album_cover(&entry.path)?; 98 + repo::album::save( 99 + pool.clone(), 100 + Album { 101 + id: album_id, 102 + title: entry.album, 103 + artist: match entry.albumartist.is_empty() { 104 + true => entry.artist.clone(), 105 + false => entry.albumartist.clone(), 106 + }, 107 + year: entry.year as u32, 108 + year_string: entry.year_string, 109 + album_art, 110 + md5: album_md5, 111 + }, 112 + ) 113 + .await?; 114 + 115 + repo::artist::save( 116 + pool.clone(), 117 + Artist { 118 + id: artist_id, 119 + name: match entry.albumartist.is_empty() { 120 + true => entry.artist.clone(), 121 + false => entry.albumartist.clone(), 122 + }, 123 + bio: None, 124 + image: None, 125 + }, 126 + ) 127 + .await?; 128 + 129 + let path = path.into(); 130 + result.push(path); 131 + } 132 + } 133 + 134 + while let Some(Ok(sub_result)) = futures.next().await { 135 + match sub_result { 136 + Ok(paths) => result.extend(paths), 137 + Err(e) => return Err(e), 138 + } 139 + } 140 + 141 + Ok(result) 142 + }) 143 + }
+12
crates/library/src/entity/album.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + 3 + #[derive(sqlx::FromRow, Default, Serialize, Deserialize)] 4 + pub struct Album { 5 + pub id: String, 6 + pub title: String, 7 + pub artist: String, 8 + pub year: u32, 9 + pub year_string: String, 10 + pub album_art: Option<String>, 11 + pub md5: String, 12 + }
+6
crates/library/src/entity/album_tracks.rs
··· 1 + #[derive(sqlx::FromRow, Default)] 2 + pub struct AlbumTracks { 3 + pub id: String, 4 + pub album_id: String, 5 + pub track_id: String, 6 + }
+9
crates/library/src/entity/artist.rs
··· 1 + use serde::{Deserialize, Serialize}; 2 + 3 + #[derive(sqlx::FromRow, Default, Serialize, Deserialize)] 4 + pub struct Artist { 5 + pub id: String, 6 + pub name: String, 7 + pub bio: Option<String>, 8 + pub image: Option<String>, 9 + }
+6
crates/library/src/entity/artist_tracks.rs
··· 1 + #[derive(sqlx::FromRow, Default)] 2 + pub struct ArtistTracks { 3 + pub id: String, 4 + pub artist_id: String, 5 + pub track_id: String, 6 + }
+6
crates/library/src/entity/favourites.rs
··· 1 + #[derive(sqlx::FromRow, Default)] 2 + pub struct Favourites { 3 + pub id: String, 4 + pub track_id: String, 5 + pub created_at: i64, 6 + }
+13
crates/library/src/entity/folder.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(sqlx::FromRow, Default, Serialize, Deserialize)] 5 + pub struct Folder { 6 + pub id: String, 7 + pub name: String, 8 + pub parent_id: Option<String>, 9 + #[serde(with = "chrono::serde::ts_seconds")] 10 + pub created_at: DateTime<Utc>, 11 + #[serde(with = "chrono::serde::ts_seconds")] 12 + pub updated_at: DateTime<Utc>, 13 + }
+7
crates/library/src/entity/genre.rs
··· 1 + #[derive(sqlx::FromRow, Default)] 2 + pub struct Genre { 3 + pub id: String, 4 + pub name: String, 5 + pub description: Option<String>, 6 + pub image: Option<String>, 7 + }
+10
crates/library/src/entity/mod.rs
··· 1 + pub mod album; 2 + pub mod album_tracks; 3 + pub mod artist; 4 + pub mod artist_tracks; 5 + pub mod favourites; 6 + pub mod folder; 7 + pub mod genre; 8 + pub mod playlist; 9 + pub mod playlist_tracks; 10 + pub mod track;
+15
crates/library/src/entity/playlist.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(sqlx::FromRow, Default, Serialize, Deserialize)] 5 + pub struct Playlist { 6 + pub id: String, 7 + pub name: String, 8 + pub image: Option<String>, 9 + pub description: Option<String>, 10 + pub folder_id: Option<String>, 11 + #[serde(with = "chrono::serde::ts_seconds")] 12 + pub created_at: DateTime<Utc>, 13 + #[serde(with = "chrono::serde::ts_seconds")] 14 + pub updated_at: DateTime<Utc>, 15 + }
+6
crates/library/src/entity/playlist_tracks.rs
··· 1 + #[derive(sqlx::FromRow, Default)] 2 + pub struct PlaylistTracks { 3 + pub id: String, 4 + pub playlist_id: String, 5 + pub track_id: String, 6 + }
+31
crates/library/src/entity/track.rs
··· 1 + use chrono::{DateTime, Utc}; 2 + use serde::{Deserialize, Serialize}; 3 + 4 + #[derive(sqlx::FromRow, Default, Serialize, Deserialize)] 5 + pub struct Track { 6 + pub id: String, 7 + pub path: String, 8 + pub title: String, 9 + pub artist: String, 10 + pub album: String, 11 + pub album_artist: String, 12 + pub bitrate: u32, 13 + pub composer: String, 14 + pub disc_number: u32, 15 + pub filesize: u32, 16 + pub frequency: u32, 17 + pub length: u32, 18 + pub track_number: Option<u32>, 19 + pub year: Option<u32>, 20 + pub year_string: Option<String>, 21 + pub genre: Option<String>, 22 + pub md5: String, 23 + pub album_art: Option<String>, 24 + pub artist_id: String, 25 + pub album_id: String, 26 + pub genre_id: String, 27 + #[serde(with = "chrono::serde::ts_seconds")] 28 + pub created_at: DateTime<Utc>, 29 + #[serde(with = "chrono::serde::ts_seconds")] 30 + pub updated_at: DateTime<Utc>, 31 + }
+21
crates/library/src/lib.rs
··· 1 + use std::env; 2 + 3 + use sqlx::{sqlite::SqliteConnectOptions, Error, Executor, Pool, Sqlite, SqlitePool}; 4 + 5 + pub mod album_art; 6 + pub mod audio_scan; 7 + pub mod entity; 8 + pub mod repo; 9 + 10 + pub async fn create_connection_pool() -> Result<Pool<Sqlite>, Error> { 11 + let db_url = env::var("DATABASE_URL").unwrap_or(":memory:".to_string()); 12 + let options = SqliteConnectOptions::new() 13 + .filename(db_url) 14 + .create_if_missing(true); 15 + let pool = SqlitePool::connect_with(options).await?; 16 + pool.execute(include_str!( 17 + "../migrations/20240923093823_create_tables.sql" 18 + )) 19 + .await?; 20 + Ok(pool) 21 + }
+70
crates/library/src/repo/album.rs
··· 1 + use crate::entity::album::Album; 2 + use sqlx::{Pool, Sqlite}; 3 + 4 + pub async fn save(pool: Pool<Sqlite>, album: Album) -> Result<(), sqlx::Error> { 5 + match sqlx::query( 6 + r#" 7 + INSERT INTO album ( 8 + id, 9 + title, 10 + artist, 11 + year, 12 + year_string, 13 + album_art, 14 + md5 15 + ) 16 + VALUES ($1, $2, $3, $4, $5, $6, $7) 17 + "#, 18 + ) 19 + .bind(&album.id) 20 + .bind(&album.title) 21 + .bind(&album.artist) 22 + .bind(album.year) 23 + .bind(&album.year_string) 24 + .bind(&album.album_art) 25 + .bind(&album.md5) 26 + .execute(&pool) 27 + .await 28 + { 29 + Ok(_) => {} 30 + Err(_e) => { 31 + // eprintln!("Error saving album: {:?}", e); 32 + } 33 + } 34 + Ok(()) 35 + } 36 + 37 + pub async fn find(pool: Pool<Sqlite>, id: &str) -> Result<Option<Album>, sqlx::Error> { 38 + match sqlx::query_as::<_, Album>( 39 + r#" 40 + SELECT * FROM album WHERE id = $1 41 + "#, 42 + ) 43 + .bind(id) 44 + .fetch_optional(&pool) 45 + .await 46 + { 47 + Ok(album) => Ok(album), 48 + Err(e) => { 49 + eprintln!("Error finding album: {:?}", e); 50 + Err(e) 51 + } 52 + } 53 + } 54 + 55 + pub async fn all(pool: Pool<Sqlite>) -> Result<Vec<Album>, sqlx::Error> { 56 + match sqlx::query_as::<_, Album>( 57 + r#" 58 + SELECT * FROM album 59 + "#, 60 + ) 61 + .fetch_all(&pool) 62 + .await 63 + { 64 + Ok(albums) => Ok(albums), 65 + Err(e) => { 66 + eprintln!("Error finding albums: {:?}", e); 67 + Err(e) 68 + } 69 + } 70 + }
+6
crates/library/src/repo/album_tracks.rs
··· 1 + use crate::entity::album_tracks::AlbumTracks; 2 + use sqlx::{Pool, Sqlite}; 3 + 4 + pub async fn save(pool: Pool<Sqlite>, album_track: AlbumTracks) {} 5 + 6 + pub async fn find(pool: Pool<Sqlite>) {}
+64
crates/library/src/repo/artist.rs
··· 1 + use crate::entity::artist::Artist; 2 + use sqlx::{Error, Pool, Sqlite}; 3 + 4 + pub async fn save(pool: Pool<Sqlite>, artist: Artist) -> Result<(), Error> { 5 + match sqlx::query( 6 + r#" 7 + INSERT INTO artist ( 8 + id, 9 + name, 10 + bio, 11 + image 12 + ) 13 + VALUES ($1, $2, $3, $4) 14 + "#, 15 + ) 16 + .bind(&artist.id) 17 + .bind(&artist.name) 18 + .bind(&artist.bio) 19 + .bind(&artist.image) 20 + .execute(&pool) 21 + .await 22 + { 23 + Ok(_) => {} 24 + Err(_e) => { 25 + // eprintln!("Error saving artist: {:?}", e); 26 + } 27 + } 28 + Ok(()) 29 + } 30 + 31 + pub async fn find(pool: Pool<Sqlite>, id: &str) -> Result<Option<Artist>, Error> { 32 + match sqlx::query_as::<_, Artist>( 33 + r#" 34 + SELECT * FROM artist WHERE id = $1 35 + "#, 36 + ) 37 + .bind(id) 38 + .fetch_optional(&pool) 39 + .await 40 + { 41 + Ok(artist) => Ok(artist), 42 + Err(e) => { 43 + eprintln!("Error finding artist: {:?}", e); 44 + Err(e) 45 + } 46 + } 47 + } 48 + 49 + pub async fn all(pool: Pool<Sqlite>) -> Result<Vec<Artist>, Error> { 50 + match sqlx::query_as::<_, Artist>( 51 + r#" 52 + SELECT * FROM artist 53 + "#, 54 + ) 55 + .fetch_all(&pool) 56 + .await 57 + { 58 + Ok(artists) => Ok(artists), 59 + Err(e) => { 60 + eprintln!("Error finding artists: {:?}", e); 61 + Err(e) 62 + } 63 + } 64 + }
+6
crates/library/src/repo/artist_tracks.rs
··· 1 + use sqlx::{Pool, Sqlite}; 2 + use crate::entity::artist_tracks::ArtistTracks; 3 + 4 + pub async fn save(pool: Pool<Sqlite>, artist_tracks: ArtistTracks) {} 5 + 6 + pub async fn all(pool: Pool<Sqlite>) {}
+6
crates/library/src/repo/favourites.rs
··· 1 + use crate::entity::favourites::Favourites; 2 + use sqlx::{Pool, Sqlite}; 3 + 4 + pub async fn save(pool: Pool<Sqlite>, favourite: Favourites) {} 5 + 6 + pub async fn all(pool: Pool<Sqlite>) {}
+8
crates/library/src/repo/folder.rs
··· 1 + use crate::entity::folder::Folder; 2 + use sqlx::{Pool, Sqlite}; 3 + 4 + pub async fn save(pool: Pool<Sqlite>, folder: Folder) {} 5 + 6 + pub async fn find(pool: Pool<Sqlite>) {} 7 + 8 + pub async fn all(pool: Pool<Sqlite>) {}
crates/library/src/repo/genre.rs

This is a binary file and will not be displayed.

+10
crates/library/src/repo/mod.rs
··· 1 + pub mod album; 2 + pub mod album_tracks; 3 + pub mod artist; 4 + pub mod artist_tracks; 5 + pub mod favourites; 6 + pub mod folder; 7 + pub mod genre; 8 + pub mod playlist; 9 + pub mod playlist_tracks; 10 + pub mod track;
+8
crates/library/src/repo/playlist.rs
··· 1 + use crate::entity::playlist::Playlist; 2 + use sqlx::{Pool, Sqlite}; 3 + 4 + pub async fn save(pool: Pool<Sqlite>, playlist: Playlist) {} 5 + 6 + pub async fn find(pool: Pool<Sqlite>) {} 7 + 8 + pub async fn all(pool: Pool<Sqlite>) {}
+6
crates/library/src/repo/playlist_tracks.rs
··· 1 + use crate::entity::playlist_tracks::PlaylistTracks; 2 + use sqlx::{Pool, Sqlite}; 3 + 4 + pub async fn save(pool: Pool<Sqlite>, playlist_track: PlaylistTracks) {} 5 + 6 + pub async fn find(pool: Pool<Sqlite>) {}
+85
crates/library/src/repo/track.rs
··· 1 + use crate::entity::track::Track; 2 + use sqlx::{Error, Pool, Sqlite}; 3 + 4 + pub async fn save(pool: Pool<Sqlite>, track: Track) -> Result<(), Error> { 5 + match sqlx::query( 6 + r#" 7 + INSERT INTO track ( 8 + id, 9 + path, 10 + title, 11 + artist, 12 + album, 13 + genre, 14 + year, 15 + track_number, 16 + disc_number, 17 + year_string, 18 + composer, 19 + album_artist, 20 + bitrate, 21 + frequency, 22 + filesize, 23 + length, 24 + md5, 25 + created_at, 26 + updated_at, 27 + artist_id, 28 + album_id 29 + ) 30 + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) 31 + "#, 32 + ) 33 + .bind(&track.id) 34 + .bind(&track.path) 35 + .bind(&track.title) 36 + .bind(&track.artist) 37 + .bind(&track.album) 38 + .bind(track.genre) 39 + .bind(track.year) 40 + .bind(track.track_number) 41 + .bind(track.disc_number) 42 + .bind(&track.year_string) 43 + .bind(&track.composer) 44 + .bind(&track.album_artist) 45 + .bind(track.bitrate) 46 + .bind(track.frequency) 47 + .bind(track.filesize) 48 + .bind(track.length) 49 + .bind(&track.md5) 50 + .bind(track.created_at) 51 + .bind(track.updated_at) 52 + .bind(&track.artist_id) 53 + .bind(&track.album_id) 54 + .execute(&pool) 55 + .await { 56 + Ok(_) => {} 57 + Err(_e) => { 58 + // eprintln!("Error saving track: {:?}", e); 59 + } 60 + } 61 + Ok(()) 62 + } 63 + 64 + pub async fn find(pool: Pool<Sqlite>, id: &str) -> Result<Option<Track>, Error> { 65 + let result: Option<Track> = sqlx::query_as("SELECT * FROM track WHERE id = $1") 66 + .bind(id) 67 + .fetch_optional(&pool) 68 + .await?; 69 + Ok(result) 70 + } 71 + 72 + pub async fn find_by_md5(pool: Pool<Sqlite>, md5: &str) -> Result<Option<Track>, Error> { 73 + let result: Option<Track> = sqlx::query_as("SELECT * FROM track WHERE md5 = $1") 74 + .bind(md5) 75 + .fetch_optional(&pool) 76 + .await?; 77 + Ok(result) 78 + } 79 + 80 + pub async fn all(pool: Pool<Sqlite>) -> Result<Vec<Track>, Error> { 81 + let result: Vec<Track> = sqlx::query_as("SELECT * FROM track") 82 + .fetch_all(&pool) 83 + .await?; 84 + Ok(result) 85 + }
+2
crates/rpc/Cargo.toml
··· 7 7 owo-colors = "5.0.0" 8 8 prost = "0.13.2" 9 9 reqwest = {version = "0.12.7", features = ["rustls-tls", "json"], default-features = false} 10 + rockbox-library = {path = "../library"} 10 11 rockbox-sys = {path = "../sys"} 11 12 serde = "1.0.210" 13 + sqlx = {version = "0.8.2", features = ["runtime-tokio", "tls-rustls", "sqlite", "chrono", "derive", "macros"]} 12 14 tokio = {version = "1.36.0", features = ["full"]} 13 15 tonic = "0.12.2" 14 16 tonic-reflection = "0.12.2"
+1
crates/rpc/build.rs
··· 5 5 .compile( 6 6 &[ 7 7 "proto/rockbox/v1alpha1/browse.proto", 8 + "proto/rockbox/v1alpha1/library.proto", 8 9 "proto/rockbox/v1alpha1/metadata.proto", 9 10 "proto/rockbox/v1alpha1/playback.proto", 10 11 "proto/rockbox/v1alpha1/playlist.proto",
+9 -24
crates/rpc/proto/rockbox/v1alpha1/browse.proto
··· 2 2 3 3 package rockbox.v1alpha1; 4 4 5 - message RockboxBrowseRequest { 6 - } 5 + message RockboxBrowseRequest {} 7 6 8 - message RockboxBrowseResponse { 9 - } 7 + message RockboxBrowseResponse {} 10 8 11 - message TreeGetContextRequest { 12 - } 9 + message TreeGetContextRequest {} 13 10 14 - message TreeGetContextResponse { 15 - } 11 + message TreeGetContextResponse {} 16 12 17 13 message TreeGetEntriesRequest { 18 14 string path = 1; ··· 29 25 repeated Entry entries = 1; 30 26 } 31 27 28 + message TreeGetEntryAtRequest {} 32 29 33 - message TreeGetEntryAtRequest { 34 - } 30 + message TreeGetEntryAtResponse {} 35 31 36 - message TreeGetEntryAtResponse { 37 - } 38 - 39 - message BrowseId3Request { 40 - 41 - } 42 - 43 - message BrowseId3Response { 32 + message BrowseId3Request {} 44 33 45 - } 34 + message BrowseId3Response {} 46 35 47 36 service BrowseService { 48 - rpc RockboxBrowse(RockboxBrowseRequest) returns (RockboxBrowseResponse); 49 - rpc TreeGetContext(TreeGetContextRequest) returns (TreeGetContextResponse); 50 37 rpc TreeGetEntries(TreeGetEntriesRequest) returns (TreeGetEntriesResponse); 51 - rpc TreeGetEntryAt(TreeGetEntryAtRequest) returns (TreeGetEntryAtResponse); 52 - rpc BrowseId3(BrowseId3Request) returns (BrowseId3Response); 53 - } 38 + }
+97
crates/rpc/proto/rockbox/v1alpha1/library.proto
··· 1 + syntax = "proto3"; 2 + 3 + package rockbox.v1alpha1; 4 + 5 + message Track { 6 + string id = 1; 7 + string path = 2; 8 + string title = 3; 9 + string artist = 4; 10 + string album = 5; 11 + string album_artist = 6; 12 + uint32 bitrate = 7; 13 + string composer = 8; 14 + uint32 disc_number = 9; 15 + uint32 filesize = 10; 16 + uint32 frequency = 11; 17 + uint32 length = 12; 18 + uint32 track_number = 13; 19 + uint32 year = 14; 20 + string year_string = 15; 21 + string genre = 16; 22 + string md5 = 17; 23 + optional string album_art = 18; 24 + optional string artist_id = 19; 25 + optional string album_id = 20; 26 + optional string genre_id = 21; 27 + string created_at = 22; 28 + string updated_at = 23; 29 + } 30 + 31 + message Artist { 32 + string id = 1; 33 + string name = 2; 34 + optional string bio = 3; 35 + optional string image = 4; 36 + } 37 + 38 + message Album { 39 + string id = 1; 40 + string title = 2; 41 + string artist = 3; 42 + uint32 year = 4; 43 + string year_string = 5; 44 + optional string album_art = 6; 45 + string md5 = 7; 46 + } 47 + 48 + message GetAlbumRequest { 49 + string id = 1; 50 + } 51 + 52 + message GetAlbumResponse { 53 + optional Album album = 1; 54 + } 55 + 56 + message GetArtistRequest { 57 + string id = 1; 58 + } 59 + 60 + message GetArtistResponse { 61 + optional Artist artist = 1; 62 + } 63 + 64 + message GetTrackRequest { 65 + string id = 1; 66 + } 67 + 68 + message GetTrackResponse { 69 + optional Track track = 1; 70 + } 71 + 72 + message GetAlbumsRequest {} 73 + 74 + message GetAlbumsResponse { 75 + repeated Album albums = 1; 76 + } 77 + 78 + message GetArtistsRequest {} 79 + 80 + message GetArtistsResponse { 81 + repeated Artist artists = 1; 82 + } 83 + 84 + message GetTracksRequest {} 85 + 86 + message GetTracksResponse { 87 + repeated Track tracks = 1; 88 + } 89 + 90 + service LibraryService { 91 + rpc GetAlbums(GetAlbumsRequest) returns (GetAlbumsResponse); 92 + rpc GetArtists(GetArtistsRequest) returns (GetArtistsResponse); 93 + rpc GetTracks(GetTracksRequest) returns (GetTracksResponse); 94 + rpc GetAlbum(GetAlbumRequest) returns (GetAlbumResponse); 95 + rpc GetArtist(GetArtistRequest) returns (GetArtistResponse); 96 + rpc GetTrack(GetTrackRequest) returns (GetTrackResponse); 97 + }
+585 -102
crates/rpc/src/api/rockbox.v1alpha1.rs
··· 121 121 self.inner = self.inner.max_encoding_message_size(limit); 122 122 self 123 123 } 124 - pub async fn rockbox_browse( 124 + pub async fn tree_get_entries( 125 125 &mut self, 126 - request: impl tonic::IntoRequest<super::RockboxBrowseRequest>, 126 + request: impl tonic::IntoRequest<super::TreeGetEntriesRequest>, 127 127 ) -> std::result::Result< 128 - tonic::Response<super::RockboxBrowseResponse>, 128 + tonic::Response<super::TreeGetEntriesResponse>, 129 129 tonic::Status, 130 130 > { 131 131 self.inner ··· 139 139 })?; 140 140 let codec = tonic::codec::ProstCodec::default(); 141 141 let path = http::uri::PathAndQuery::from_static( 142 - "/rockbox.v1alpha1.BrowseService/RockboxBrowse", 142 + "/rockbox.v1alpha1.BrowseService/TreeGetEntries", 143 143 ); 144 144 let mut req = request.into_request(); 145 145 req.extensions_mut() 146 146 .insert( 147 - GrpcMethod::new("rockbox.v1alpha1.BrowseService", "RockboxBrowse"), 147 + GrpcMethod::new("rockbox.v1alpha1.BrowseService", "TreeGetEntries"), 148 148 ); 149 149 self.inner.unary(req, path, codec).await 150 150 } 151 - pub async fn tree_get_context( 151 + } 152 + } 153 + /// Generated server implementations. 154 + pub mod browse_service_server { 155 + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] 156 + use tonic::codegen::*; 157 + /// Generated trait containing gRPC methods that should be implemented for use with BrowseServiceServer. 158 + #[async_trait] 159 + pub trait BrowseService: std::marker::Send + std::marker::Sync + 'static { 160 + async fn tree_get_entries( 161 + &self, 162 + request: tonic::Request<super::TreeGetEntriesRequest>, 163 + ) -> std::result::Result< 164 + tonic::Response<super::TreeGetEntriesResponse>, 165 + tonic::Status, 166 + >; 167 + } 168 + #[derive(Debug)] 169 + pub struct BrowseServiceServer<T> { 170 + inner: Arc<T>, 171 + accept_compression_encodings: EnabledCompressionEncodings, 172 + send_compression_encodings: EnabledCompressionEncodings, 173 + max_decoding_message_size: Option<usize>, 174 + max_encoding_message_size: Option<usize>, 175 + } 176 + impl<T> BrowseServiceServer<T> { 177 + pub fn new(inner: T) -> Self { 178 + Self::from_arc(Arc::new(inner)) 179 + } 180 + pub fn from_arc(inner: Arc<T>) -> Self { 181 + Self { 182 + inner, 183 + accept_compression_encodings: Default::default(), 184 + send_compression_encodings: Default::default(), 185 + max_decoding_message_size: None, 186 + max_encoding_message_size: None, 187 + } 188 + } 189 + pub fn with_interceptor<F>( 190 + inner: T, 191 + interceptor: F, 192 + ) -> InterceptedService<Self, F> 193 + where 194 + F: tonic::service::Interceptor, 195 + { 196 + InterceptedService::new(Self::new(inner), interceptor) 197 + } 198 + /// Enable decompressing requests with the given encoding. 199 + #[must_use] 200 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 201 + self.accept_compression_encodings.enable(encoding); 202 + self 203 + } 204 + /// Compress responses with the given encoding, if the client supports it. 205 + #[must_use] 206 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 207 + self.send_compression_encodings.enable(encoding); 208 + self 209 + } 210 + /// Limits the maximum size of a decoded message. 211 + /// 212 + /// Default: `4MB` 213 + #[must_use] 214 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 215 + self.max_decoding_message_size = Some(limit); 216 + self 217 + } 218 + /// Limits the maximum size of an encoded message. 219 + /// 220 + /// Default: `usize::MAX` 221 + #[must_use] 222 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 223 + self.max_encoding_message_size = Some(limit); 224 + self 225 + } 226 + } 227 + impl<T, B> tonic::codegen::Service<http::Request<B>> for BrowseServiceServer<T> 228 + where 229 + T: BrowseService, 230 + B: Body + std::marker::Send + 'static, 231 + B::Error: Into<StdError> + std::marker::Send + 'static, 232 + { 233 + type Response = http::Response<tonic::body::BoxBody>; 234 + type Error = std::convert::Infallible; 235 + type Future = BoxFuture<Self::Response, Self::Error>; 236 + fn poll_ready( 237 + &mut self, 238 + _cx: &mut Context<'_>, 239 + ) -> Poll<std::result::Result<(), Self::Error>> { 240 + Poll::Ready(Ok(())) 241 + } 242 + fn call(&mut self, req: http::Request<B>) -> Self::Future { 243 + match req.uri().path() { 244 + "/rockbox.v1alpha1.BrowseService/TreeGetEntries" => { 245 + #[allow(non_camel_case_types)] 246 + struct TreeGetEntriesSvc<T: BrowseService>(pub Arc<T>); 247 + impl< 248 + T: BrowseService, 249 + > tonic::server::UnaryService<super::TreeGetEntriesRequest> 250 + for TreeGetEntriesSvc<T> { 251 + type Response = super::TreeGetEntriesResponse; 252 + type Future = BoxFuture< 253 + tonic::Response<Self::Response>, 254 + tonic::Status, 255 + >; 256 + fn call( 257 + &mut self, 258 + request: tonic::Request<super::TreeGetEntriesRequest>, 259 + ) -> Self::Future { 260 + let inner = Arc::clone(&self.0); 261 + let fut = async move { 262 + <T as BrowseService>::tree_get_entries(&inner, request) 263 + .await 264 + }; 265 + Box::pin(fut) 266 + } 267 + } 268 + let accept_compression_encodings = self.accept_compression_encodings; 269 + let send_compression_encodings = self.send_compression_encodings; 270 + let max_decoding_message_size = self.max_decoding_message_size; 271 + let max_encoding_message_size = self.max_encoding_message_size; 272 + let inner = self.inner.clone(); 273 + let fut = async move { 274 + let method = TreeGetEntriesSvc(inner); 275 + let codec = tonic::codec::ProstCodec::default(); 276 + let mut grpc = tonic::server::Grpc::new(codec) 277 + .apply_compression_config( 278 + accept_compression_encodings, 279 + send_compression_encodings, 280 + ) 281 + .apply_max_message_size_config( 282 + max_decoding_message_size, 283 + max_encoding_message_size, 284 + ); 285 + let res = grpc.unary(method, req).await; 286 + Ok(res) 287 + }; 288 + Box::pin(fut) 289 + } 290 + _ => { 291 + Box::pin(async move { 292 + Ok( 293 + http::Response::builder() 294 + .status(200) 295 + .header("grpc-status", tonic::Code::Unimplemented as i32) 296 + .header( 297 + http::header::CONTENT_TYPE, 298 + tonic::metadata::GRPC_CONTENT_TYPE, 299 + ) 300 + .body(empty_body()) 301 + .unwrap(), 302 + ) 303 + }) 304 + } 305 + } 306 + } 307 + } 308 + impl<T> Clone for BrowseServiceServer<T> { 309 + fn clone(&self) -> Self { 310 + let inner = self.inner.clone(); 311 + Self { 312 + inner, 313 + accept_compression_encodings: self.accept_compression_encodings, 314 + send_compression_encodings: self.send_compression_encodings, 315 + max_decoding_message_size: self.max_decoding_message_size, 316 + max_encoding_message_size: self.max_encoding_message_size, 317 + } 318 + } 319 + } 320 + /// Generated gRPC service name 321 + pub const SERVICE_NAME: &str = "rockbox.v1alpha1.BrowseService"; 322 + impl<T> tonic::server::NamedService for BrowseServiceServer<T> { 323 + const NAME: &'static str = SERVICE_NAME; 324 + } 325 + } 326 + #[derive(Clone, PartialEq, ::prost::Message)] 327 + pub struct Track { 328 + #[prost(string, tag = "1")] 329 + pub id: ::prost::alloc::string::String, 330 + #[prost(string, tag = "2")] 331 + pub path: ::prost::alloc::string::String, 332 + #[prost(string, tag = "3")] 333 + pub title: ::prost::alloc::string::String, 334 + #[prost(string, tag = "4")] 335 + pub artist: ::prost::alloc::string::String, 336 + #[prost(string, tag = "5")] 337 + pub album: ::prost::alloc::string::String, 338 + #[prost(string, tag = "6")] 339 + pub album_artist: ::prost::alloc::string::String, 340 + #[prost(uint32, tag = "7")] 341 + pub bitrate: u32, 342 + #[prost(string, tag = "8")] 343 + pub composer: ::prost::alloc::string::String, 344 + #[prost(uint32, tag = "9")] 345 + pub disc_number: u32, 346 + #[prost(uint32, tag = "10")] 347 + pub filesize: u32, 348 + #[prost(uint32, tag = "11")] 349 + pub frequency: u32, 350 + #[prost(uint32, tag = "12")] 351 + pub length: u32, 352 + #[prost(uint32, tag = "13")] 353 + pub track_number: u32, 354 + #[prost(uint32, tag = "14")] 355 + pub year: u32, 356 + #[prost(string, tag = "15")] 357 + pub year_string: ::prost::alloc::string::String, 358 + #[prost(string, tag = "16")] 359 + pub genre: ::prost::alloc::string::String, 360 + #[prost(string, tag = "17")] 361 + pub md5: ::prost::alloc::string::String, 362 + #[prost(string, optional, tag = "18")] 363 + pub album_art: ::core::option::Option<::prost::alloc::string::String>, 364 + #[prost(string, optional, tag = "19")] 365 + pub artist_id: ::core::option::Option<::prost::alloc::string::String>, 366 + #[prost(string, optional, tag = "20")] 367 + pub album_id: ::core::option::Option<::prost::alloc::string::String>, 368 + #[prost(string, optional, tag = "21")] 369 + pub genre_id: ::core::option::Option<::prost::alloc::string::String>, 370 + #[prost(string, tag = "22")] 371 + pub created_at: ::prost::alloc::string::String, 372 + #[prost(string, tag = "23")] 373 + pub updated_at: ::prost::alloc::string::String, 374 + } 375 + #[derive(Clone, PartialEq, ::prost::Message)] 376 + pub struct Artist { 377 + #[prost(string, tag = "1")] 378 + pub id: ::prost::alloc::string::String, 379 + #[prost(string, tag = "2")] 380 + pub name: ::prost::alloc::string::String, 381 + #[prost(string, optional, tag = "3")] 382 + pub bio: ::core::option::Option<::prost::alloc::string::String>, 383 + #[prost(string, optional, tag = "4")] 384 + pub image: ::core::option::Option<::prost::alloc::string::String>, 385 + } 386 + #[derive(Clone, PartialEq, ::prost::Message)] 387 + pub struct Album { 388 + #[prost(string, tag = "1")] 389 + pub id: ::prost::alloc::string::String, 390 + #[prost(string, tag = "2")] 391 + pub title: ::prost::alloc::string::String, 392 + #[prost(string, tag = "3")] 393 + pub artist: ::prost::alloc::string::String, 394 + #[prost(uint32, tag = "4")] 395 + pub year: u32, 396 + #[prost(string, tag = "5")] 397 + pub year_string: ::prost::alloc::string::String, 398 + #[prost(string, optional, tag = "6")] 399 + pub album_art: ::core::option::Option<::prost::alloc::string::String>, 400 + #[prost(string, tag = "7")] 401 + pub md5: ::prost::alloc::string::String, 402 + } 403 + #[derive(Clone, PartialEq, ::prost::Message)] 404 + pub struct GetAlbumRequest { 405 + #[prost(string, tag = "1")] 406 + pub id: ::prost::alloc::string::String, 407 + } 408 + #[derive(Clone, PartialEq, ::prost::Message)] 409 + pub struct GetAlbumResponse { 410 + #[prost(message, optional, tag = "1")] 411 + pub album: ::core::option::Option<Album>, 412 + } 413 + #[derive(Clone, PartialEq, ::prost::Message)] 414 + pub struct GetArtistRequest { 415 + #[prost(string, tag = "1")] 416 + pub id: ::prost::alloc::string::String, 417 + } 418 + #[derive(Clone, PartialEq, ::prost::Message)] 419 + pub struct GetArtistResponse { 420 + #[prost(message, optional, tag = "1")] 421 + pub artist: ::core::option::Option<Artist>, 422 + } 423 + #[derive(Clone, PartialEq, ::prost::Message)] 424 + pub struct GetTrackRequest { 425 + #[prost(string, tag = "1")] 426 + pub id: ::prost::alloc::string::String, 427 + } 428 + #[derive(Clone, PartialEq, ::prost::Message)] 429 + pub struct GetTrackResponse { 430 + #[prost(message, optional, tag = "1")] 431 + pub track: ::core::option::Option<Track>, 432 + } 433 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 434 + pub struct GetAlbumsRequest {} 435 + #[derive(Clone, PartialEq, ::prost::Message)] 436 + pub struct GetAlbumsResponse { 437 + #[prost(message, repeated, tag = "1")] 438 + pub albums: ::prost::alloc::vec::Vec<Album>, 439 + } 440 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 441 + pub struct GetArtistsRequest {} 442 + #[derive(Clone, PartialEq, ::prost::Message)] 443 + pub struct GetArtistsResponse { 444 + #[prost(message, repeated, tag = "1")] 445 + pub artists: ::prost::alloc::vec::Vec<Artist>, 446 + } 447 + #[derive(Clone, Copy, PartialEq, ::prost::Message)] 448 + pub struct GetTracksRequest {} 449 + #[derive(Clone, PartialEq, ::prost::Message)] 450 + pub struct GetTracksResponse { 451 + #[prost(message, repeated, tag = "1")] 452 + pub tracks: ::prost::alloc::vec::Vec<Track>, 453 + } 454 + /// Generated client implementations. 455 + pub mod library_service_client { 456 + #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] 457 + use tonic::codegen::*; 458 + use tonic::codegen::http::Uri; 459 + #[derive(Debug, Clone)] 460 + pub struct LibraryServiceClient<T> { 461 + inner: tonic::client::Grpc<T>, 462 + } 463 + impl LibraryServiceClient<tonic::transport::Channel> { 464 + /// Attempt to create a new client by connecting to a given endpoint. 465 + pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error> 466 + where 467 + D: TryInto<tonic::transport::Endpoint>, 468 + D::Error: Into<StdError>, 469 + { 470 + let conn = tonic::transport::Endpoint::new(dst)?.connect().await?; 471 + Ok(Self::new(conn)) 472 + } 473 + } 474 + impl<T> LibraryServiceClient<T> 475 + where 476 + T: tonic::client::GrpcService<tonic::body::BoxBody>, 477 + T::Error: Into<StdError>, 478 + T::ResponseBody: Body<Data = Bytes> + std::marker::Send + 'static, 479 + <T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send, 480 + { 481 + pub fn new(inner: T) -> Self { 482 + let inner = tonic::client::Grpc::new(inner); 483 + Self { inner } 484 + } 485 + pub fn with_origin(inner: T, origin: Uri) -> Self { 486 + let inner = tonic::client::Grpc::with_origin(inner, origin); 487 + Self { inner } 488 + } 489 + pub fn with_interceptor<F>( 490 + inner: T, 491 + interceptor: F, 492 + ) -> LibraryServiceClient<InterceptedService<T, F>> 493 + where 494 + F: tonic::service::Interceptor, 495 + T::ResponseBody: Default, 496 + T: tonic::codegen::Service< 497 + http::Request<tonic::body::BoxBody>, 498 + Response = http::Response< 499 + <T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody, 500 + >, 501 + >, 502 + <T as tonic::codegen::Service< 503 + http::Request<tonic::body::BoxBody>, 504 + >>::Error: Into<StdError> + std::marker::Send + std::marker::Sync, 505 + { 506 + LibraryServiceClient::new(InterceptedService::new(inner, interceptor)) 507 + } 508 + /// Compress requests with the given encoding. 509 + /// 510 + /// This requires the server to support it otherwise it might respond with an 511 + /// error. 512 + #[must_use] 513 + pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self { 514 + self.inner = self.inner.send_compressed(encoding); 515 + self 516 + } 517 + /// Enable decompressing responses. 518 + #[must_use] 519 + pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self { 520 + self.inner = self.inner.accept_compressed(encoding); 521 + self 522 + } 523 + /// Limits the maximum size of a decoded message. 524 + /// 525 + /// Default: `4MB` 526 + #[must_use] 527 + pub fn max_decoding_message_size(mut self, limit: usize) -> Self { 528 + self.inner = self.inner.max_decoding_message_size(limit); 529 + self 530 + } 531 + /// Limits the maximum size of an encoded message. 532 + /// 533 + /// Default: `usize::MAX` 534 + #[must_use] 535 + pub fn max_encoding_message_size(mut self, limit: usize) -> Self { 536 + self.inner = self.inner.max_encoding_message_size(limit); 537 + self 538 + } 539 + pub async fn get_albums( 152 540 &mut self, 153 - request: impl tonic::IntoRequest<super::TreeGetContextRequest>, 541 + request: impl tonic::IntoRequest<super::GetAlbumsRequest>, 154 542 ) -> std::result::Result< 155 - tonic::Response<super::TreeGetContextResponse>, 543 + tonic::Response<super::GetAlbumsResponse>, 156 544 tonic::Status, 157 545 > { 158 546 self.inner ··· 166 554 })?; 167 555 let codec = tonic::codec::ProstCodec::default(); 168 556 let path = http::uri::PathAndQuery::from_static( 169 - "/rockbox.v1alpha1.BrowseService/TreeGetContext", 557 + "/rockbox.v1alpha1.LibraryService/GetAlbums", 170 558 ); 171 559 let mut req = request.into_request(); 172 560 req.extensions_mut() 173 - .insert( 174 - GrpcMethod::new("rockbox.v1alpha1.BrowseService", "TreeGetContext"), 175 - ); 561 + .insert(GrpcMethod::new("rockbox.v1alpha1.LibraryService", "GetAlbums")); 176 562 self.inner.unary(req, path, codec).await 177 563 } 178 - pub async fn tree_get_entries( 564 + pub async fn get_artists( 179 565 &mut self, 180 - request: impl tonic::IntoRequest<super::TreeGetEntriesRequest>, 566 + request: impl tonic::IntoRequest<super::GetArtistsRequest>, 181 567 ) -> std::result::Result< 182 - tonic::Response<super::TreeGetEntriesResponse>, 568 + tonic::Response<super::GetArtistsResponse>, 183 569 tonic::Status, 184 570 > { 185 571 self.inner ··· 193 579 })?; 194 580 let codec = tonic::codec::ProstCodec::default(); 195 581 let path = http::uri::PathAndQuery::from_static( 196 - "/rockbox.v1alpha1.BrowseService/TreeGetEntries", 582 + "/rockbox.v1alpha1.LibraryService/GetArtists", 197 583 ); 198 584 let mut req = request.into_request(); 199 585 req.extensions_mut() 200 586 .insert( 201 - GrpcMethod::new("rockbox.v1alpha1.BrowseService", "TreeGetEntries"), 587 + GrpcMethod::new("rockbox.v1alpha1.LibraryService", "GetArtists"), 202 588 ); 203 589 self.inner.unary(req, path, codec).await 204 590 } 205 - pub async fn tree_get_entry_at( 591 + pub async fn get_tracks( 206 592 &mut self, 207 - request: impl tonic::IntoRequest<super::TreeGetEntryAtRequest>, 593 + request: impl tonic::IntoRequest<super::GetTracksRequest>, 208 594 ) -> std::result::Result< 209 - tonic::Response<super::TreeGetEntryAtResponse>, 595 + tonic::Response<super::GetTracksResponse>, 210 596 tonic::Status, 211 597 > { 212 598 self.inner ··· 220 606 })?; 221 607 let codec = tonic::codec::ProstCodec::default(); 222 608 let path = http::uri::PathAndQuery::from_static( 223 - "/rockbox.v1alpha1.BrowseService/TreeGetEntryAt", 609 + "/rockbox.v1alpha1.LibraryService/GetTracks", 224 610 ); 225 611 let mut req = request.into_request(); 226 612 req.extensions_mut() 227 - .insert( 228 - GrpcMethod::new("rockbox.v1alpha1.BrowseService", "TreeGetEntryAt"), 229 - ); 613 + .insert(GrpcMethod::new("rockbox.v1alpha1.LibraryService", "GetTracks")); 614 + self.inner.unary(req, path, codec).await 615 + } 616 + pub async fn get_album( 617 + &mut self, 618 + request: impl tonic::IntoRequest<super::GetAlbumRequest>, 619 + ) -> std::result::Result< 620 + tonic::Response<super::GetAlbumResponse>, 621 + tonic::Status, 622 + > { 623 + self.inner 624 + .ready() 625 + .await 626 + .map_err(|e| { 627 + tonic::Status::new( 628 + tonic::Code::Unknown, 629 + format!("Service was not ready: {}", e.into()), 630 + ) 631 + })?; 632 + let codec = tonic::codec::ProstCodec::default(); 633 + let path = http::uri::PathAndQuery::from_static( 634 + "/rockbox.v1alpha1.LibraryService/GetAlbum", 635 + ); 636 + let mut req = request.into_request(); 637 + req.extensions_mut() 638 + .insert(GrpcMethod::new("rockbox.v1alpha1.LibraryService", "GetAlbum")); 230 639 self.inner.unary(req, path, codec).await 231 640 } 232 - pub async fn browse_id3( 641 + pub async fn get_artist( 233 642 &mut self, 234 - request: impl tonic::IntoRequest<super::BrowseId3Request>, 643 + request: impl tonic::IntoRequest<super::GetArtistRequest>, 235 644 ) -> std::result::Result< 236 - tonic::Response<super::BrowseId3Response>, 645 + tonic::Response<super::GetArtistResponse>, 237 646 tonic::Status, 238 647 > { 239 648 self.inner ··· 247 656 })?; 248 657 let codec = tonic::codec::ProstCodec::default(); 249 658 let path = http::uri::PathAndQuery::from_static( 250 - "/rockbox.v1alpha1.BrowseService/BrowseId3", 659 + "/rockbox.v1alpha1.LibraryService/GetArtist", 251 660 ); 252 661 let mut req = request.into_request(); 253 662 req.extensions_mut() 254 - .insert(GrpcMethod::new("rockbox.v1alpha1.BrowseService", "BrowseId3")); 663 + .insert(GrpcMethod::new("rockbox.v1alpha1.LibraryService", "GetArtist")); 664 + self.inner.unary(req, path, codec).await 665 + } 666 + pub async fn get_track( 667 + &mut self, 668 + request: impl tonic::IntoRequest<super::GetTrackRequest>, 669 + ) -> std::result::Result< 670 + tonic::Response<super::GetTrackResponse>, 671 + tonic::Status, 672 + > { 673 + self.inner 674 + .ready() 675 + .await 676 + .map_err(|e| { 677 + tonic::Status::new( 678 + tonic::Code::Unknown, 679 + format!("Service was not ready: {}", e.into()), 680 + ) 681 + })?; 682 + let codec = tonic::codec::ProstCodec::default(); 683 + let path = http::uri::PathAndQuery::from_static( 684 + "/rockbox.v1alpha1.LibraryService/GetTrack", 685 + ); 686 + let mut req = request.into_request(); 687 + req.extensions_mut() 688 + .insert(GrpcMethod::new("rockbox.v1alpha1.LibraryService", "GetTrack")); 255 689 self.inner.unary(req, path, codec).await 256 690 } 257 691 } 258 692 } 259 693 /// Generated server implementations. 260 - pub mod browse_service_server { 694 + pub mod library_service_server { 261 695 #![allow(unused_variables, dead_code, missing_docs, clippy::let_unit_value)] 262 696 use tonic::codegen::*; 263 - /// Generated trait containing gRPC methods that should be implemented for use with BrowseServiceServer. 697 + /// Generated trait containing gRPC methods that should be implemented for use with LibraryServiceServer. 264 698 #[async_trait] 265 - pub trait BrowseService: std::marker::Send + std::marker::Sync + 'static { 266 - async fn rockbox_browse( 699 + pub trait LibraryService: std::marker::Send + std::marker::Sync + 'static { 700 + async fn get_albums( 701 + &self, 702 + request: tonic::Request<super::GetAlbumsRequest>, 703 + ) -> std::result::Result< 704 + tonic::Response<super::GetAlbumsResponse>, 705 + tonic::Status, 706 + >; 707 + async fn get_artists( 267 708 &self, 268 - request: tonic::Request<super::RockboxBrowseRequest>, 709 + request: tonic::Request<super::GetArtistsRequest>, 269 710 ) -> std::result::Result< 270 - tonic::Response<super::RockboxBrowseResponse>, 711 + tonic::Response<super::GetArtistsResponse>, 271 712 tonic::Status, 272 713 >; 273 - async fn tree_get_context( 714 + async fn get_tracks( 274 715 &self, 275 - request: tonic::Request<super::TreeGetContextRequest>, 716 + request: tonic::Request<super::GetTracksRequest>, 276 717 ) -> std::result::Result< 277 - tonic::Response<super::TreeGetContextResponse>, 718 + tonic::Response<super::GetTracksResponse>, 278 719 tonic::Status, 279 720 >; 280 - async fn tree_get_entries( 721 + async fn get_album( 281 722 &self, 282 - request: tonic::Request<super::TreeGetEntriesRequest>, 723 + request: tonic::Request<super::GetAlbumRequest>, 283 724 ) -> std::result::Result< 284 - tonic::Response<super::TreeGetEntriesResponse>, 725 + tonic::Response<super::GetAlbumResponse>, 285 726 tonic::Status, 286 727 >; 287 - async fn tree_get_entry_at( 728 + async fn get_artist( 288 729 &self, 289 - request: tonic::Request<super::TreeGetEntryAtRequest>, 730 + request: tonic::Request<super::GetArtistRequest>, 290 731 ) -> std::result::Result< 291 - tonic::Response<super::TreeGetEntryAtResponse>, 732 + tonic::Response<super::GetArtistResponse>, 292 733 tonic::Status, 293 734 >; 294 - async fn browse_id3( 735 + async fn get_track( 295 736 &self, 296 - request: tonic::Request<super::BrowseId3Request>, 737 + request: tonic::Request<super::GetTrackRequest>, 297 738 ) -> std::result::Result< 298 - tonic::Response<super::BrowseId3Response>, 739 + tonic::Response<super::GetTrackResponse>, 299 740 tonic::Status, 300 741 >; 301 742 } 302 743 #[derive(Debug)] 303 - pub struct BrowseServiceServer<T> { 744 + pub struct LibraryServiceServer<T> { 304 745 inner: Arc<T>, 305 746 accept_compression_encodings: EnabledCompressionEncodings, 306 747 send_compression_encodings: EnabledCompressionEncodings, 307 748 max_decoding_message_size: Option<usize>, 308 749 max_encoding_message_size: Option<usize>, 309 750 } 310 - impl<T> BrowseServiceServer<T> { 751 + impl<T> LibraryServiceServer<T> { 311 752 pub fn new(inner: T) -> Self { 312 753 Self::from_arc(Arc::new(inner)) 313 754 } ··· 358 799 self 359 800 } 360 801 } 361 - impl<T, B> tonic::codegen::Service<http::Request<B>> for BrowseServiceServer<T> 802 + impl<T, B> tonic::codegen::Service<http::Request<B>> for LibraryServiceServer<T> 362 803 where 363 - T: BrowseService, 804 + T: LibraryService, 364 805 B: Body + std::marker::Send + 'static, 365 806 B::Error: Into<StdError> + std::marker::Send + 'static, 366 807 { ··· 375 816 } 376 817 fn call(&mut self, req: http::Request<B>) -> Self::Future { 377 818 match req.uri().path() { 378 - "/rockbox.v1alpha1.BrowseService/RockboxBrowse" => { 819 + "/rockbox.v1alpha1.LibraryService/GetAlbums" => { 379 820 #[allow(non_camel_case_types)] 380 - struct RockboxBrowseSvc<T: BrowseService>(pub Arc<T>); 821 + struct GetAlbumsSvc<T: LibraryService>(pub Arc<T>); 381 822 impl< 382 - T: BrowseService, 383 - > tonic::server::UnaryService<super::RockboxBrowseRequest> 384 - for RockboxBrowseSvc<T> { 385 - type Response = super::RockboxBrowseResponse; 823 + T: LibraryService, 824 + > tonic::server::UnaryService<super::GetAlbumsRequest> 825 + for GetAlbumsSvc<T> { 826 + type Response = super::GetAlbumsResponse; 386 827 type Future = BoxFuture< 387 828 tonic::Response<Self::Response>, 388 829 tonic::Status, 389 830 >; 390 831 fn call( 391 832 &mut self, 392 - request: tonic::Request<super::RockboxBrowseRequest>, 833 + request: tonic::Request<super::GetAlbumsRequest>, 834 + ) -> Self::Future { 835 + let inner = Arc::clone(&self.0); 836 + let fut = async move { 837 + <T as LibraryService>::get_albums(&inner, request).await 838 + }; 839 + Box::pin(fut) 840 + } 841 + } 842 + let accept_compression_encodings = self.accept_compression_encodings; 843 + let send_compression_encodings = self.send_compression_encodings; 844 + let max_decoding_message_size = self.max_decoding_message_size; 845 + let max_encoding_message_size = self.max_encoding_message_size; 846 + let inner = self.inner.clone(); 847 + let fut = async move { 848 + let method = GetAlbumsSvc(inner); 849 + let codec = tonic::codec::ProstCodec::default(); 850 + let mut grpc = tonic::server::Grpc::new(codec) 851 + .apply_compression_config( 852 + accept_compression_encodings, 853 + send_compression_encodings, 854 + ) 855 + .apply_max_message_size_config( 856 + max_decoding_message_size, 857 + max_encoding_message_size, 858 + ); 859 + let res = grpc.unary(method, req).await; 860 + Ok(res) 861 + }; 862 + Box::pin(fut) 863 + } 864 + "/rockbox.v1alpha1.LibraryService/GetArtists" => { 865 + #[allow(non_camel_case_types)] 866 + struct GetArtistsSvc<T: LibraryService>(pub Arc<T>); 867 + impl< 868 + T: LibraryService, 869 + > tonic::server::UnaryService<super::GetArtistsRequest> 870 + for GetArtistsSvc<T> { 871 + type Response = super::GetArtistsResponse; 872 + type Future = BoxFuture< 873 + tonic::Response<Self::Response>, 874 + tonic::Status, 875 + >; 876 + fn call( 877 + &mut self, 878 + request: tonic::Request<super::GetArtistsRequest>, 393 879 ) -> Self::Future { 394 880 let inner = Arc::clone(&self.0); 395 881 let fut = async move { 396 - <T as BrowseService>::rockbox_browse(&inner, request).await 882 + <T as LibraryService>::get_artists(&inner, request).await 397 883 }; 398 884 Box::pin(fut) 399 885 } ··· 404 890 let max_encoding_message_size = self.max_encoding_message_size; 405 891 let inner = self.inner.clone(); 406 892 let fut = async move { 407 - let method = RockboxBrowseSvc(inner); 893 + let method = GetArtistsSvc(inner); 408 894 let codec = tonic::codec::ProstCodec::default(); 409 895 let mut grpc = tonic::server::Grpc::new(codec) 410 896 .apply_compression_config( ··· 420 906 }; 421 907 Box::pin(fut) 422 908 } 423 - "/rockbox.v1alpha1.BrowseService/TreeGetContext" => { 909 + "/rockbox.v1alpha1.LibraryService/GetTracks" => { 424 910 #[allow(non_camel_case_types)] 425 - struct TreeGetContextSvc<T: BrowseService>(pub Arc<T>); 911 + struct GetTracksSvc<T: LibraryService>(pub Arc<T>); 426 912 impl< 427 - T: BrowseService, 428 - > tonic::server::UnaryService<super::TreeGetContextRequest> 429 - for TreeGetContextSvc<T> { 430 - type Response = super::TreeGetContextResponse; 913 + T: LibraryService, 914 + > tonic::server::UnaryService<super::GetTracksRequest> 915 + for GetTracksSvc<T> { 916 + type Response = super::GetTracksResponse; 431 917 type Future = BoxFuture< 432 918 tonic::Response<Self::Response>, 433 919 tonic::Status, 434 920 >; 435 921 fn call( 436 922 &mut self, 437 - request: tonic::Request<super::TreeGetContextRequest>, 923 + request: tonic::Request<super::GetTracksRequest>, 438 924 ) -> Self::Future { 439 925 let inner = Arc::clone(&self.0); 440 926 let fut = async move { 441 - <T as BrowseService>::tree_get_context(&inner, request) 442 - .await 927 + <T as LibraryService>::get_tracks(&inner, request).await 443 928 }; 444 929 Box::pin(fut) 445 930 } ··· 450 935 let max_encoding_message_size = self.max_encoding_message_size; 451 936 let inner = self.inner.clone(); 452 937 let fut = async move { 453 - let method = TreeGetContextSvc(inner); 938 + let method = GetTracksSvc(inner); 454 939 let codec = tonic::codec::ProstCodec::default(); 455 940 let mut grpc = tonic::server::Grpc::new(codec) 456 941 .apply_compression_config( ··· 466 951 }; 467 952 Box::pin(fut) 468 953 } 469 - "/rockbox.v1alpha1.BrowseService/TreeGetEntries" => { 954 + "/rockbox.v1alpha1.LibraryService/GetAlbum" => { 470 955 #[allow(non_camel_case_types)] 471 - struct TreeGetEntriesSvc<T: BrowseService>(pub Arc<T>); 956 + struct GetAlbumSvc<T: LibraryService>(pub Arc<T>); 472 957 impl< 473 - T: BrowseService, 474 - > tonic::server::UnaryService<super::TreeGetEntriesRequest> 475 - for TreeGetEntriesSvc<T> { 476 - type Response = super::TreeGetEntriesResponse; 958 + T: LibraryService, 959 + > tonic::server::UnaryService<super::GetAlbumRequest> 960 + for GetAlbumSvc<T> { 961 + type Response = super::GetAlbumResponse; 477 962 type Future = BoxFuture< 478 963 tonic::Response<Self::Response>, 479 964 tonic::Status, 480 965 >; 481 966 fn call( 482 967 &mut self, 483 - request: tonic::Request<super::TreeGetEntriesRequest>, 968 + request: tonic::Request<super::GetAlbumRequest>, 484 969 ) -> Self::Future { 485 970 let inner = Arc::clone(&self.0); 486 971 let fut = async move { 487 - <T as BrowseService>::tree_get_entries(&inner, request) 488 - .await 972 + <T as LibraryService>::get_album(&inner, request).await 489 973 }; 490 974 Box::pin(fut) 491 975 } ··· 496 980 let max_encoding_message_size = self.max_encoding_message_size; 497 981 let inner = self.inner.clone(); 498 982 let fut = async move { 499 - let method = TreeGetEntriesSvc(inner); 983 + let method = GetAlbumSvc(inner); 500 984 let codec = tonic::codec::ProstCodec::default(); 501 985 let mut grpc = tonic::server::Grpc::new(codec) 502 986 .apply_compression_config( ··· 512 996 }; 513 997 Box::pin(fut) 514 998 } 515 - "/rockbox.v1alpha1.BrowseService/TreeGetEntryAt" => { 999 + "/rockbox.v1alpha1.LibraryService/GetArtist" => { 516 1000 #[allow(non_camel_case_types)] 517 - struct TreeGetEntryAtSvc<T: BrowseService>(pub Arc<T>); 1001 + struct GetArtistSvc<T: LibraryService>(pub Arc<T>); 518 1002 impl< 519 - T: BrowseService, 520 - > tonic::server::UnaryService<super::TreeGetEntryAtRequest> 521 - for TreeGetEntryAtSvc<T> { 522 - type Response = super::TreeGetEntryAtResponse; 1003 + T: LibraryService, 1004 + > tonic::server::UnaryService<super::GetArtistRequest> 1005 + for GetArtistSvc<T> { 1006 + type Response = super::GetArtistResponse; 523 1007 type Future = BoxFuture< 524 1008 tonic::Response<Self::Response>, 525 1009 tonic::Status, 526 1010 >; 527 1011 fn call( 528 1012 &mut self, 529 - request: tonic::Request<super::TreeGetEntryAtRequest>, 1013 + request: tonic::Request<super::GetArtistRequest>, 530 1014 ) -> Self::Future { 531 1015 let inner = Arc::clone(&self.0); 532 1016 let fut = async move { 533 - <T as BrowseService>::tree_get_entry_at(&inner, request) 534 - .await 1017 + <T as LibraryService>::get_artist(&inner, request).await 535 1018 }; 536 1019 Box::pin(fut) 537 1020 } ··· 542 1025 let max_encoding_message_size = self.max_encoding_message_size; 543 1026 let inner = self.inner.clone(); 544 1027 let fut = async move { 545 - let method = TreeGetEntryAtSvc(inner); 1028 + let method = GetArtistSvc(inner); 546 1029 let codec = tonic::codec::ProstCodec::default(); 547 1030 let mut grpc = tonic::server::Grpc::new(codec) 548 1031 .apply_compression_config( ··· 558 1041 }; 559 1042 Box::pin(fut) 560 1043 } 561 - "/rockbox.v1alpha1.BrowseService/BrowseId3" => { 1044 + "/rockbox.v1alpha1.LibraryService/GetTrack" => { 562 1045 #[allow(non_camel_case_types)] 563 - struct BrowseId3Svc<T: BrowseService>(pub Arc<T>); 1046 + struct GetTrackSvc<T: LibraryService>(pub Arc<T>); 564 1047 impl< 565 - T: BrowseService, 566 - > tonic::server::UnaryService<super::BrowseId3Request> 567 - for BrowseId3Svc<T> { 568 - type Response = super::BrowseId3Response; 1048 + T: LibraryService, 1049 + > tonic::server::UnaryService<super::GetTrackRequest> 1050 + for GetTrackSvc<T> { 1051 + type Response = super::GetTrackResponse; 569 1052 type Future = BoxFuture< 570 1053 tonic::Response<Self::Response>, 571 1054 tonic::Status, 572 1055 >; 573 1056 fn call( 574 1057 &mut self, 575 - request: tonic::Request<super::BrowseId3Request>, 1058 + request: tonic::Request<super::GetTrackRequest>, 576 1059 ) -> Self::Future { 577 1060 let inner = Arc::clone(&self.0); 578 1061 let fut = async move { 579 - <T as BrowseService>::browse_id3(&inner, request).await 1062 + <T as LibraryService>::get_track(&inner, request).await 580 1063 }; 581 1064 Box::pin(fut) 582 1065 } ··· 587 1070 let max_encoding_message_size = self.max_encoding_message_size; 588 1071 let inner = self.inner.clone(); 589 1072 let fut = async move { 590 - let method = BrowseId3Svc(inner); 1073 + let method = GetTrackSvc(inner); 591 1074 let codec = tonic::codec::ProstCodec::default(); 592 1075 let mut grpc = tonic::server::Grpc::new(codec) 593 1076 .apply_compression_config( ··· 621 1104 } 622 1105 } 623 1106 } 624 - impl<T> Clone for BrowseServiceServer<T> { 1107 + impl<T> Clone for LibraryServiceServer<T> { 625 1108 fn clone(&self) -> Self { 626 1109 let inner = self.inner.clone(); 627 1110 Self { ··· 634 1117 } 635 1118 } 636 1119 /// Generated gRPC service name 637 - pub const SERVICE_NAME: &str = "rockbox.v1alpha1.BrowseService"; 638 - impl<T> tonic::server::NamedService for BrowseServiceServer<T> { 1120 + pub const SERVICE_NAME: &str = "rockbox.v1alpha1.LibraryService"; 1121 + impl<T> tonic::server::NamedService for LibraryServiceServer<T> { 639 1122 const NAME: &'static str = SERVICE_NAME; 640 1123 } 641 1124 }
crates/rpc/src/api/rockbox_descriptor.bin

This is a binary file and will not be displayed.

-28
crates/rpc/src/browse.rs
··· 17 17 18 18 #[tonic::async_trait] 19 19 impl BrowseService for Browse { 20 - async fn rockbox_browse( 21 - &self, 22 - request: tonic::Request<RockboxBrowseRequest>, 23 - ) -> Result<tonic::Response<RockboxBrowseResponse>, tonic::Status> { 24 - Ok(tonic::Response::new(RockboxBrowseResponse::default())) 25 - } 26 - 27 - async fn tree_get_context( 28 - &self, 29 - request: tonic::Request<TreeGetContextRequest>, 30 - ) -> Result<tonic::Response<TreeGetContextResponse>, tonic::Status> { 31 - Ok(tonic::Response::new(TreeGetContextResponse::default())) 32 - } 33 - 34 20 async fn tree_get_entries( 35 21 &self, 36 22 request: tonic::Request<TreeGetEntriesRequest>, ··· 52 38 .map(|entry| Entry::from(entry)) 53 39 .collect::<Vec<Entry>>(); 54 40 Ok(tonic::Response::new(TreeGetEntriesResponse { entries })) 55 - } 56 - 57 - async fn tree_get_entry_at( 58 - &self, 59 - request: tonic::Request<TreeGetEntryAtRequest>, 60 - ) -> Result<tonic::Response<TreeGetEntryAtResponse>, tonic::Status> { 61 - Ok(tonic::Response::new(TreeGetEntryAtResponse::default())) 62 - } 63 - 64 - async fn browse_id3( 65 - &self, 66 - request: tonic::Request<BrowseId3Request>, 67 - ) -> Result<tonic::Response<BrowseId3Response>, tonic::Status> { 68 - Ok(tonic::Response::new(BrowseId3Response::default())) 69 41 } 70 42 }
+58 -2
crates/rpc/src/lib.rs
··· 1 1 pub mod browse; 2 + pub mod library; 2 3 pub mod metadata; 3 4 pub mod playback; 4 5 pub mod playlist; ··· 17 18 user_settings::{CompressorSettings, EqBandSetting, ReplaygainSettings, UserSettings}, 18 19 }; 19 20 use v1alpha1::{ 20 - CurrentTrackResponse, Entry, GetGlobalSettingsResponse, GetGlobalStatusResponse, 21 - NextTrackResponse, 21 + Album, Artist, CurrentTrackResponse, Entry, GetGlobalSettingsResponse, 22 + GetGlobalStatusResponse, NextTrackResponse, Track, 22 23 }; 23 24 24 25 #[path = "rockbox.v1alpha1.rs"] ··· 650 651 attr, 651 652 time_write, 652 653 customaction, 654 + } 655 + } 656 + } 657 + 658 + impl From<rockbox_library::entity::artist::Artist> for Artist { 659 + fn from(artist: rockbox_library::entity::artist::Artist) -> Self { 660 + Self { 661 + id: artist.id, 662 + name: artist.name, 663 + bio: artist.bio, 664 + image: artist.image, 665 + } 666 + } 667 + } 668 + 669 + impl From<rockbox_library::entity::album::Album> for Album { 670 + fn from(album: rockbox_library::entity::album::Album) -> Self { 671 + Self { 672 + id: album.id, 673 + title: album.title, 674 + artist: album.artist, 675 + year: album.year, 676 + year_string: album.year_string, 677 + album_art: album.album_art, 678 + md5: album.md5, 679 + } 680 + } 681 + } 682 + 683 + impl From<rockbox_library::entity::track::Track> for Track { 684 + fn from(track: rockbox_library::entity::track::Track) -> Self { 685 + Self { 686 + id: track.id, 687 + path: track.path, 688 + title: track.title, 689 + artist: track.artist, 690 + album: track.album, 691 + album_artist: track.album_artist, 692 + bitrate: track.bitrate, 693 + composer: track.composer, 694 + disc_number: track.disc_number, 695 + filesize: track.filesize, 696 + frequency: track.frequency, 697 + length: track.length, 698 + track_number: track.track_number.unwrap_or_default(), 699 + year: track.year.unwrap_or_default(), 700 + year_string: track.year_string.unwrap_or_default(), 701 + genre: track.genre.unwrap_or_default(), 702 + md5: track.md5, 703 + album_art: track.album_art, 704 + artist_id: Some(track.artist_id), 705 + album_id: Some(track.album_id), 706 + genre_id: Some(track.genre_id), 707 + created_at: track.created_at.to_rfc3339(), 708 + updated_at: track.updated_at.to_rfc3339(), 653 709 } 654 710 } 655 711 }
+96
crates/rpc/src/library.rs
··· 1 + use rockbox_library::repo; 2 + use sqlx::Sqlite; 3 + 4 + use crate::api::rockbox::v1alpha1::{ 5 + library_service_server::LibraryService, GetAlbumRequest, GetAlbumResponse, GetAlbumsRequest, 6 + GetAlbumsResponse, GetArtistRequest, GetArtistResponse, GetArtistsRequest, GetArtistsResponse, 7 + GetTrackRequest, GetTrackResponse, GetTracksRequest, GetTracksResponse, 8 + }; 9 + 10 + pub struct Library { 11 + pool: sqlx::Pool<Sqlite>, 12 + } 13 + 14 + impl Library { 15 + pub fn new(pool: sqlx::Pool<Sqlite>) -> Self { 16 + Self { pool } 17 + } 18 + } 19 + 20 + #[tonic::async_trait] 21 + impl LibraryService for Library { 22 + async fn get_albums( 23 + &self, 24 + _request: tonic::Request<GetAlbumsRequest>, 25 + ) -> Result<tonic::Response<GetAlbumsResponse>, tonic::Status> { 26 + let albums = repo::album::all(self.pool.clone()) 27 + .await 28 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 29 + Ok(tonic::Response::new(GetAlbumsResponse { 30 + albums: albums.into_iter().map(|a| a.into()).collect(), 31 + })) 32 + } 33 + 34 + async fn get_artists( 35 + &self, 36 + _request: tonic::Request<GetArtistsRequest>, 37 + ) -> Result<tonic::Response<GetArtistsResponse>, tonic::Status> { 38 + let artists = repo::artist::all(self.pool.clone()) 39 + .await 40 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 41 + Ok(tonic::Response::new(GetArtistsResponse { 42 + artists: artists.into_iter().map(|a| a.into()).collect(), 43 + })) 44 + } 45 + 46 + async fn get_tracks( 47 + &self, 48 + _request: tonic::Request<GetTracksRequest>, 49 + ) -> Result<tonic::Response<GetTracksResponse>, tonic::Status> { 50 + let tracks = repo::track::all(self.pool.clone()) 51 + .await 52 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 53 + Ok(tonic::Response::new(GetTracksResponse { 54 + tracks: tracks.into_iter().map(|t| t.into()).collect(), 55 + })) 56 + } 57 + 58 + async fn get_album( 59 + &self, 60 + request: tonic::Request<GetAlbumRequest>, 61 + ) -> Result<tonic::Response<GetAlbumResponse>, tonic::Status> { 62 + let params = request.into_inner(); 63 + let album = repo::album::find(self.pool.clone(), &params.id) 64 + .await 65 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 66 + Ok(tonic::Response::new(GetAlbumResponse { 67 + album: album.map(|a| a.into()), 68 + })) 69 + } 70 + 71 + async fn get_artist( 72 + &self, 73 + request: tonic::Request<GetArtistRequest>, 74 + ) -> Result<tonic::Response<GetArtistResponse>, tonic::Status> { 75 + let params = request.into_inner(); 76 + let artist = repo::artist::find(self.pool.clone(), &params.id) 77 + .await 78 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 79 + Ok(tonic::Response::new(GetArtistResponse { 80 + artist: artist.map(|a| a.into()), 81 + })) 82 + } 83 + 84 + async fn get_track( 85 + &self, 86 + request: tonic::Request<GetTrackRequest>, 87 + ) -> Result<tonic::Response<GetTrackResponse>, tonic::Status> { 88 + let params = request.into_inner(); 89 + let track = repo::track::find(self.pool.clone(), &params.id) 90 + .await 91 + .map_err(|e| tonic::Status::internal(e.to_string()))?; 92 + Ok(tonic::Response::new(GetTrackResponse { 93 + track: track.map(|t| t.into()), 94 + })) 95 + } 96 + }
+7
crates/rpc/src/server.rs
··· 3 3 use std::sync::{Arc, Mutex}; 4 4 5 5 use crate::api::rockbox::v1alpha1::browse_service_server::BrowseServiceServer; 6 + use crate::api::rockbox::v1alpha1::library_service_server::LibraryServiceServer; 6 7 use crate::api::rockbox::v1alpha1::playback_service_server::PlaybackServiceServer; 7 8 use crate::api::rockbox::v1alpha1::playlist_service_server::PlaylistServiceServer; 8 9 use crate::api::rockbox::v1alpha1::settings_service_server::SettingsServiceServer; 9 10 use crate::api::rockbox::v1alpha1::sound_service_server::SoundServiceServer; 10 11 use crate::api::rockbox::FILE_DESCRIPTOR_SET; 11 12 use crate::browse::Browse; 13 + use crate::library::Library; 12 14 use crate::playback::Playback; 13 15 use crate::playlist::Playlist; 14 16 use crate::settings::Settings; 15 17 use crate::sound::Sound; 16 18 use crate::system::System; 17 19 use owo_colors::OwoColorize; 20 + use rockbox_library::create_connection_pool; 18 21 use rockbox_sys::events::RockboxCommand; 19 22 use tonic::transport::Server; 20 23 ··· 37 40 ); 38 41 39 42 let client = reqwest::Client::new(); 43 + let pool = create_connection_pool().await?; 40 44 41 45 Server::builder() 42 46 .accept_http1(true) ··· 45 49 .register_encoded_file_descriptor_set(FILE_DESCRIPTOR_SET) 46 50 .build_v1alpha()?, 47 51 ) 52 + .add_service(tonic_web::enable(LibraryServiceServer::new( 53 + Library::new(pool), 54 + ))) 48 55 .add_service(tonic_web::enable(PlaylistServiceServer::new( 49 56 Playlist::new(cmd_tx.clone(), client.clone()), 50 57 )))
docs/graphql.png

This is a binary file and will not be displayed.

docs/grpc.png

This is a binary file and will not be displayed.