Compare commits
16 Commits
22e6fe31b3
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2194a8a9c0 | |||
| 2be5849251 | |||
| 8b9bf1afce | |||
| 47639dd34a | |||
| 3a2883507d | |||
| be170b40f0 | |||
| e69a4271cb | |||
| e192bd5e23 | |||
| a652ae330f | |||
| 634c147ee9 | |||
| e60c2d15d2 | |||
| c5149b3390 | |||
| d7b226025e | |||
| 05b2e11ab4 | |||
| e69b45ca24 | |||
| 94b8b15f80 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
||||
/target
|
||||
perf.data*
|
||||
heaptrack*
|
||||
|
||||
390
Cargo.lock
generated
390
Cargo.lock
generated
@@ -68,9 +68,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.93"
|
||||
version = "1.0.94"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
|
||||
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
||||
|
||||
[[package]]
|
||||
name = "arbitrary"
|
||||
@@ -89,6 +89,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.6"
|
||||
@@ -189,9 +195,9 @@ dependencies = [
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.2",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower 0.5.1",
|
||||
"tower 0.5.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -212,7 +218,7 @@ dependencies = [
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper 1.0.2",
|
||||
"sync_wrapper",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
@@ -282,12 +288,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
name = "blake3"
|
||||
version = "1.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"arrayref",
|
||||
"arrayvec",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"constant_time_eq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -322,15 +332,15 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.8.0"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
|
||||
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.1"
|
||||
version = "1.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
|
||||
checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
@@ -401,6 +411,12 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "constant_time_eq"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.7"
|
||||
@@ -450,15 +466,6 @@ dependencies = [
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
@@ -470,9 +477,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.5"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
@@ -489,9 +496,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.20"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
@@ -499,16 +506,6 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dasp_sample"
|
||||
version = "0.11.0"
|
||||
@@ -516,13 +513,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
name = "deadpool"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
checksum = "6541a3916932fe57768d4be0b1ffb5ec7cbf74ca8c903fdfd5c0fe8aa958f0ed"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
"deadpool-runtime",
|
||||
"num_cpus",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool-runtime"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b"
|
||||
dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool-sqlite"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "656f14fc1ab819c65f332045ea7cb38841bbe551f3b2bc7e3abefb559af4155c"
|
||||
dependencies = [
|
||||
"deadpool",
|
||||
"deadpool-sync",
|
||||
"rusqlite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "deadpool-sync"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524bc3df0d57e98ecd022e21ba31166c2625e7d3e5bcc4510efaeeab4abcab04"
|
||||
dependencies = [
|
||||
"deadpool-runtime",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -554,12 +581,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.9"
|
||||
version = "0.3.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
||||
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -597,15 +624,15 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.2.0"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "fdeflate"
|
||||
version = "0.3.6"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb"
|
||||
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||
dependencies = [
|
||||
"simd-adler32",
|
||||
]
|
||||
@@ -680,16 +707,6 @@ dependencies = [
|
||||
"pin-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.15"
|
||||
@@ -728,17 +745,17 @@ name = "groove"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"blake3",
|
||||
"deadpool",
|
||||
"deadpool-sqlite",
|
||||
"dotenvy",
|
||||
"hex",
|
||||
"home",
|
||||
"image",
|
||||
"prost",
|
||||
"r2d2",
|
||||
"r2d2_sqlite",
|
||||
"rayon",
|
||||
"rodio",
|
||||
"rusqlite",
|
||||
"sha2",
|
||||
"symphonia",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -762,7 +779,7 @@ dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap 2.6.0",
|
||||
"indexmap 2.7.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
@@ -796,9 +813,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.1"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
|
||||
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
@@ -844,9 +861,9 @@ checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.1.0"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
||||
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
@@ -878,9 +895,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-range-header"
|
||||
version = "0.4.1"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a"
|
||||
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
@@ -998,12 +1015,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.6.0"
|
||||
version = "2.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
|
||||
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.15.1",
|
||||
"hashbrown 0.15.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1037,9 +1054,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.13"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2"
|
||||
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||
|
||||
[[package]]
|
||||
name = "jni"
|
||||
@@ -1080,10 +1097,11 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.72"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
|
||||
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
@@ -1112,9 +1130,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.164"
|
||||
version = "0.2.168"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
||||
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
@@ -1128,9 +1146,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
version = "0.8.5"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.52.6",
|
||||
@@ -1253,11 +1271,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "1.0.2"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
|
||||
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"wasi",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -1370,6 +1387,16 @@ dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_enum"
|
||||
version = "0.7.3"
|
||||
@@ -1480,7 +1507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"indexmap 2.6.0",
|
||||
"indexmap 2.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1523,9 +1550,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
||||
|
||||
[[package]]
|
||||
name = "png"
|
||||
version = "0.17.14"
|
||||
version = "0.17.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0"
|
||||
checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crc32fast",
|
||||
@@ -1592,9 +1619,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost"
|
||||
version = "0.13.3"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f"
|
||||
checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"prost-derive",
|
||||
@@ -1602,11 +1629,10 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost-build"
|
||||
version = "0.13.3"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15"
|
||||
checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"heck",
|
||||
"itertools 0.13.0",
|
||||
"log",
|
||||
@@ -1623,9 +1649,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost-derive"
|
||||
version = "0.13.3"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5"
|
||||
checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"itertools 0.13.0",
|
||||
@@ -1636,9 +1662,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "prost-types"
|
||||
version = "0.13.3"
|
||||
version = "0.13.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670"
|
||||
checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc"
|
||||
dependencies = [
|
||||
"prost",
|
||||
]
|
||||
@@ -1667,28 +1693,6 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r2d2"
|
||||
version = "0.8.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93"
|
||||
dependencies = [
|
||||
"log",
|
||||
"parking_lot",
|
||||
"scheduled-thread-pool",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r2d2_sqlite"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb14dba8247a6a15b7fdbc7d389e2e6f03ee9f184f87117706d509c092dfe846"
|
||||
dependencies = [
|
||||
"r2d2",
|
||||
"rusqlite",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
@@ -1791,9 +1795,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.7"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
|
||||
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
]
|
||||
@@ -1874,15 +1878,15 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.38.41"
|
||||
version = "0.38.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
|
||||
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1906,15 +1910,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scheduled-thread-pool"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19"
|
||||
dependencies = [
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
@@ -1923,18 +1918,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.215"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
||||
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.215"
|
||||
version = "1.0.216"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
||||
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -1984,17 +1979,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
@@ -2042,9 +2026,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.5.7"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
|
||||
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
@@ -2198,21 +2182,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.89"
|
||||
version = "2.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
|
||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.2"
|
||||
@@ -2248,7 +2226,7 @@ dependencies = [
|
||||
"fastrand",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2299,9 +2277,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.41.1"
|
||||
version = "1.42.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
|
||||
checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"bytes",
|
||||
@@ -2328,9 +2306,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.16"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
|
||||
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
@@ -2339,9 +2317,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.12"
|
||||
version = "0.7.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
|
||||
checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
@@ -2377,7 +2355,7 @@ version = "0.22.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||
dependencies = [
|
||||
"indexmap 2.6.0",
|
||||
"indexmap 2.7.0",
|
||||
"serde",
|
||||
"serde_spanned",
|
||||
"toml_datetime",
|
||||
@@ -2470,14 +2448,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.1"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
|
||||
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper 0.1.2",
|
||||
"sync_wrapper",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
@@ -2539,9 +2517,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.40"
|
||||
version = "0.1.41"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
@@ -2551,9 +2529,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.27"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
||||
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2562,9 +2540,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.32"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
@@ -2575,20 +2553,11 @@ version = "0.2.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "2.7.0"
|
||||
version = "2.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
||||
dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
@@ -2596,16 +2565,6 @@ version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "v_frame"
|
||||
version = "0.3.8"
|
||||
@@ -2662,9 +2621,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.95"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
|
||||
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"once_cell",
|
||||
@@ -2673,13 +2632,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.95"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
|
||||
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
@@ -2688,21 +2646,22 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.45"
|
||||
version = "0.4.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
|
||||
checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"once_cell",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.95"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
|
||||
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -2710,9 +2669,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.95"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
|
||||
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2723,15 +2682,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.95"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
|
||||
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.72"
|
||||
version = "0.3.76"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
|
||||
checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
@@ -2759,7 +2718,7 @@ version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||
dependencies = [
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2809,6 +2768,15 @@ dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
@@ -2977,9 +2945,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zune-jpeg"
|
||||
version = "0.4.13"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768"
|
||||
checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
|
||||
dependencies = [
|
||||
"zune-core",
|
||||
]
|
||||
|
||||
@@ -5,17 +5,17 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
axum = "0.7.9"
|
||||
blake3 = "1.5.4"
|
||||
deadpool = "0.12.1"
|
||||
deadpool-sqlite = "0.9.0"
|
||||
dotenvy = "0.15.7"
|
||||
hex = "0.4.3"
|
||||
home = "0.5.9"
|
||||
image = "0.25.5"
|
||||
prost = "0.13.3"
|
||||
r2d2 = "0.8.10"
|
||||
r2d2_sqlite = { version = "0.25.0", features = ["bundled"] }
|
||||
rayon = "1.10.0"
|
||||
rodio = "0.20.1"
|
||||
rusqlite = { version = "0.32.1", features = ["bundled"] }
|
||||
sha2 = "0.10.8"
|
||||
symphonia = { version = "0.5.4", features = ["mp3"] }
|
||||
tokio = { version = "1.41.1", features = ["full"] }
|
||||
tokio-stream = "0.1.16"
|
||||
|
||||
5
README.md
Normal file
5
README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# groove
|
||||
An audio player for local files.
|
||||
|
||||
> [!NOTE]
|
||||
> This is merely the backend, for the frontend see [groove-web](https://github.com/4-0-9/groove-web).
|
||||
@@ -6,6 +6,12 @@ package library;
|
||||
|
||||
service Library {
|
||||
rpc ListTracks(google.protobuf.Empty) returns (TrackList);
|
||||
rpc ListPlaylists(google.protobuf.Empty) returns (ListPlaylistsResponse);
|
||||
rpc CreatePlaylist(CreatePlaylistRequest) returns (CreatePlaylistResponse);
|
||||
rpc DeletePlaylist(DeletePlaylistRequest) returns (google.protobuf.Empty);
|
||||
rpc AddTrackToPlaylist(AddTrackToPlaylistRequest) returns (TrackList);
|
||||
rpc RemoveTrackFromPlaylist(RemoveTrackFromPlaylistRequest) returns (TrackList);
|
||||
rpc SwapTracks(SwapTracksRequest) returns (TrackList);
|
||||
}
|
||||
|
||||
message TrackList {
|
||||
@@ -19,3 +25,41 @@ message Track {
|
||||
uint64 artist_id = 4;
|
||||
uint64 duration = 5;
|
||||
}
|
||||
|
||||
message Playlist {
|
||||
uint32 id = 1;
|
||||
string name = 2;
|
||||
repeated Track tracks = 3;
|
||||
}
|
||||
|
||||
message ListPlaylistsResponse {
|
||||
repeated Playlist playlists = 1;
|
||||
}
|
||||
|
||||
message CreatePlaylistRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message CreatePlaylistResponse {
|
||||
Playlist playlist = 1;
|
||||
}
|
||||
|
||||
message DeletePlaylistRequest {
|
||||
uint32 id = 1;
|
||||
}
|
||||
|
||||
message AddTrackToPlaylistRequest {
|
||||
uint32 playlist_id = 1;
|
||||
string track_hash = 2;
|
||||
}
|
||||
|
||||
message RemoveTrackFromPlaylistRequest {
|
||||
uint32 playlist_id = 1;
|
||||
uint32 track_rank = 2;
|
||||
}
|
||||
|
||||
message SwapTracksRequest {
|
||||
uint32 playlist_id = 1;
|
||||
uint32 a_rank = 2;
|
||||
uint32 b_rank = 3;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,9 @@ service Player {
|
||||
rpc SetVolume(SetVolumeRequest) returns (SetVolumeResponse);
|
||||
rpc PlayTrackNext(TrackRequest) returns (Queue);
|
||||
rpc AddTrackToQueue(TrackRequest) returns (Queue);
|
||||
rpc AddTracksToQueue(TracksRequest) returns (Queue);
|
||||
rpc PlayPlaylist(PlayPlaylistRequest) returns (PlayerStatus);
|
||||
rpc SwapQueueIndices(SwapQueueIndicesRequest) returns (Queue);
|
||||
rpc SkipTrack(google.protobuf.Empty) returns (PlayerStatus);
|
||||
rpc SkipToQueueIndex(SkipToQueueIndexRequest) returns (PlayerStatus);
|
||||
}
|
||||
@@ -63,3 +66,17 @@ message SetVolumeResponse {
|
||||
message SkipToQueueIndexRequest {
|
||||
uint32 index = 1;
|
||||
}
|
||||
|
||||
message SwapQueueIndicesRequest {
|
||||
uint32 a = 1;
|
||||
uint32 b = 2;
|
||||
}
|
||||
|
||||
message TracksRequest {
|
||||
repeated string tracks = 1;
|
||||
}
|
||||
|
||||
message PlayPlaylistRequest {
|
||||
uint32 id = 1;
|
||||
optional uint32 starting_rank = 2;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
use sha2::{self, Digest, Sha256};
|
||||
use blake3::hash;
|
||||
|
||||
pub fn generate_hash(content: impl AsRef<[u8]>) -> String {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(content);
|
||||
let result = hasher.finalize();
|
||||
pub fn generate_hash(content: &[u8]) -> String {
|
||||
let hash = hash(content);
|
||||
|
||||
let hex = hex::encode(result);
|
||||
|
||||
hex
|
||||
hash.to_string()
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{fs, path::Path, time::Instant};
|
||||
|
||||
use axum::Router;
|
||||
use image::EncodableLayout;
|
||||
use std::{fs, path::Path};
|
||||
use tower_http::services::ServeDir;
|
||||
use webp::Encoder;
|
||||
|
||||
@@ -40,9 +40,11 @@ pub fn get_all_cover_hashes() -> Vec<String> {
|
||||
hashes
|
||||
}
|
||||
|
||||
pub fn write_cover(hash: String, cover: CoverData) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let now = Instant::now();
|
||||
|
||||
pub fn write_cover(
|
||||
hash: &str,
|
||||
cover: &CoverData,
|
||||
base_path: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if cover.mime_type != "image/jpeg"
|
||||
&& cover.mime_type != "image/png"
|
||||
&& cover.mime_type != "image/webp"
|
||||
@@ -50,34 +52,21 @@ pub fn write_cover(hash: String, cover: CoverData) -> Result<(), Box<dyn std::er
|
||||
return Err(format!("Invalid cover MIME type: {}", cover.mime_type).into());
|
||||
}
|
||||
|
||||
let base_path = get_cover_base_path();
|
||||
|
||||
let path = Path::new(&base_path).join(format!("{hash}.webp"));
|
||||
|
||||
let dynamic_image = image::load_from_memory_with_format(
|
||||
&cover.bytes,
|
||||
match cover.mime_type.as_str() {
|
||||
"image/png" => image::ImageFormat::Png,
|
||||
"image/jpeg" => image::ImageFormat::Jpeg,
|
||||
"image/webp" => image::ImageFormat::WebP,
|
||||
_ => panic!("Invalid cover MIME type (this should never happen)"),
|
||||
},
|
||||
)?;
|
||||
let dynamic_image = image::load_from_memory(&cover.bytes)?;
|
||||
|
||||
let resized_image = if dynamic_image.width() > 640 || dynamic_image.height() > 640 {
|
||||
dynamic_image.resize_to_fill(640, 640, image::imageops::FilterType::Lanczos3)
|
||||
let a = dynamic_image.resize_to_fill(640, 640, image::imageops::FilterType::Lanczos3);
|
||||
drop(dynamic_image);
|
||||
a
|
||||
} else {
|
||||
dynamic_image
|
||||
};
|
||||
|
||||
let webp_encoder = Encoder::from_image(&resized_image)?;
|
||||
let encoded_image = webp_encoder.encode_lossless().to_vec();
|
||||
let webp = Encoder::from_image(&resized_image)?.encode_lossless();
|
||||
|
||||
fs::write(path, encoded_image)?;
|
||||
let path = Path::new(&base_path).join(format!("{hash}.webp"));
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
println!("Writing '{}' cover took {:.?}", hash, elapsed);
|
||||
fs::write(path, webp.as_bytes())?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use r2d2::PooledConnection;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use deadpool_sqlite::Pool;
|
||||
use rusqlite::Row;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -15,9 +14,10 @@ fn map_artist(row: &Row) -> Result<Artist, rusqlite::Error> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_artists(
|
||||
connection: &PooledConnection<SqliteConnectionManager>,
|
||||
) -> Result<Vec<Artist>, rusqlite::Error> {
|
||||
pub async fn get_artists(pool: &Pool) -> Result<Vec<Artist>, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = manager.lock().unwrap();
|
||||
|
||||
let mut statement = connection.prepare("SELECT id, name FROM artists")?;
|
||||
let rows = statement.query_map([], map_artist)?;
|
||||
|
||||
|
||||
@@ -1,25 +1,27 @@
|
||||
use deadpool_sqlite::{Config, Pool};
|
||||
|
||||
pub mod artists;
|
||||
pub mod paths;
|
||||
pub mod tracks;
|
||||
|
||||
use r2d2::{Pool, PooledConnection};
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
|
||||
pub fn establish_connection() -> Pool<SqliteConnectionManager> {
|
||||
pub fn establish_connection() -> Pool {
|
||||
dotenvy::dotenv().ok();
|
||||
|
||||
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||
|
||||
let manager = SqliteConnectionManager::file(database_url);
|
||||
let pool = Pool::new(manager).expect("Error creating SQLite pool");
|
||||
let cfg = Config::new(database_url);
|
||||
let pool = cfg
|
||||
.create_pool(deadpool::Runtime::Tokio1)
|
||||
.expect("Error creating SQLite pool");
|
||||
|
||||
pool
|
||||
}
|
||||
|
||||
pub fn initialize_database(
|
||||
connection: &PooledConnection<SqliteConnectionManager>,
|
||||
) -> Result<(), r2d2_sqlite::rusqlite::Error> {
|
||||
connection.execute(
|
||||
pub async fn initialize_database(pool: &Pool) -> Result<(), rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let conn = manager.lock().unwrap();
|
||||
|
||||
conn.execute(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS library_paths (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
@@ -29,7 +31,7 @@ pub fn initialize_database(
|
||||
[],
|
||||
)?;
|
||||
|
||||
connection.execute(
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS artists (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
name TEXT NOT NULL
|
||||
@@ -37,7 +39,17 @@ pub fn initialize_database(
|
||||
[],
|
||||
)?;
|
||||
|
||||
connection.execute(
|
||||
conn.execute(
|
||||
"CREATE TABLE IF NOT EXISTS albums (
|
||||
name TEXT NOT NULL,
|
||||
artist_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (name, artist_id),
|
||||
FOREIGN KEY (artist_id) REFERENCES artists (id) ON DELETE CASCADE
|
||||
)",
|
||||
[],
|
||||
)?;
|
||||
|
||||
conn.execute(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS tracks (
|
||||
hash TEXT PRIMARY KEY NOT NULL,
|
||||
@@ -53,5 +65,29 @@ pub fn initialize_database(
|
||||
[],
|
||||
)?;
|
||||
|
||||
conn.execute(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS playlists (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
name TEXT NOT NULL
|
||||
);
|
||||
",
|
||||
[],
|
||||
)?;
|
||||
|
||||
conn.execute(
|
||||
"
|
||||
CREATE TABLE IF NOT EXISTS playlist_tracks (
|
||||
playlist_id INTEGER NOT NULL,
|
||||
track_hash TEXT NOT NULL,
|
||||
rank INTEGER NOT NULL,
|
||||
FOREIGN KEY (playlist_id) REFERENCES playlists (id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (track_hash) REFERENCES tracks (hash) ON DELETE CASCADE,
|
||||
UNIQUE (playlist_id, rank) ON CONFLICT FAIL
|
||||
);
|
||||
",
|
||||
[],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use r2d2::PooledConnection;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use deadpool_sqlite::Pool;
|
||||
use rusqlite::Row;
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -17,9 +16,10 @@ fn map_library_path(row: &Row) -> Result<LibraryPath, rusqlite::Error> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_library_paths(
|
||||
connection: &PooledConnection<SqliteConnectionManager>,
|
||||
) -> Result<Vec<LibraryPath>, rusqlite::Error> {
|
||||
pub async fn get_library_paths(pool: &Pool) -> Result<Vec<LibraryPath>, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = manager.lock().unwrap();
|
||||
|
||||
let mut statement = connection.prepare("SELECT id, path FROM library_paths")?;
|
||||
let rows = statement.query_map([], map_library_path)?;
|
||||
|
||||
@@ -34,10 +34,10 @@ pub fn get_library_paths(
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
pub fn get_library_path(
|
||||
connection: &PooledConnection<SqliteConnectionManager>,
|
||||
id: u64,
|
||||
) -> Result<LibraryPath, rusqlite::Error> {
|
||||
pub async fn get_library_path(pool: &Pool, id: u64) -> Result<LibraryPath, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = manager.lock().unwrap();
|
||||
|
||||
Ok(connection.query_row(
|
||||
"SELECT id, path FROM library_paths WHERE id = ?1",
|
||||
[id],
|
||||
@@ -45,19 +45,22 @@ pub fn get_library_path(
|
||||
)?)
|
||||
}
|
||||
|
||||
pub fn insert_library_path(
|
||||
connection: &PooledConnection<SqliteConnectionManager>,
|
||||
pub async fn insert_library_path(
|
||||
pool: &Pool,
|
||||
path: LibraryPathInsertData,
|
||||
) -> Result<bool, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = manager.lock().unwrap();
|
||||
|
||||
let result = connection.execute("INSERT INTO library_paths (path) VALUES (?1)", [path])?;
|
||||
|
||||
Ok(result > 0)
|
||||
}
|
||||
|
||||
pub fn delete_library_path(
|
||||
connection: &PooledConnection<SqliteConnectionManager>,
|
||||
path_id: u64,
|
||||
) -> Result<bool, rusqlite::Error> {
|
||||
pub async fn delete_library_path(pool: &Pool, path_id: u64) -> Result<bool, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = manager.lock().unwrap();
|
||||
|
||||
let result = connection.execute("DELETE FROM library_paths WHERE id = ?1", [path_id])?;
|
||||
|
||||
Ok(result > 0)
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
path::{Path, PathBuf},
|
||||
thread::JoinHandle,
|
||||
};
|
||||
|
||||
use r2d2::PooledConnection;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use deadpool_sqlite::Pool;
|
||||
use rusqlite::{params, Row};
|
||||
|
||||
use crate::{
|
||||
covers::{get_all_cover_hashes, write_cover},
|
||||
covers::{get_all_cover_hashes, get_cover_base_path, write_cover},
|
||||
music::metadata::TrackMetadata,
|
||||
proto,
|
||||
proto::{self, library::Playlist},
|
||||
};
|
||||
|
||||
use super::artists::get_artists;
|
||||
@@ -59,9 +57,20 @@ fn map_track(row: &Row) -> Result<Track, rusqlite::Error> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_tracks(
|
||||
connection: &PooledConnection<SqliteConnectionManager>,
|
||||
) -> Result<Vec<Track>, rusqlite::Error> {
|
||||
/// Creates a playlist from a database row (tracks are empty and need to be filled afterwards)
|
||||
/// * `row`: The database row
|
||||
fn map_playlist(row: &Row) -> Result<Playlist, rusqlite::Error> {
|
||||
Ok(Playlist {
|
||||
id: row.get(0)?,
|
||||
name: row.get(1)?,
|
||||
tracks: Vec::new(),
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn get_tracks(pool: &Pool) -> Result<Vec<Track>, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = manager.lock().unwrap();
|
||||
|
||||
let mut statement = connection.prepare("SELECT t.hash, t.name, t.duration, t.artist_id, a.name AS artist_name FROM tracks t INNER JOIN artists a ON a.id = t.artist_id")?;
|
||||
let rows = statement.query_map([], map_track)?;
|
||||
|
||||
@@ -76,17 +85,34 @@ pub fn get_tracks(
|
||||
Ok(tracks)
|
||||
}
|
||||
|
||||
pub fn get_track(
|
||||
connection: &PooledConnection<SqliteConnectionManager>,
|
||||
hash: &str,
|
||||
) -> Result<Track, rusqlite::Error> {
|
||||
pub async fn get_track(pool: &Pool, hash: &str) -> Result<Track, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = &manager.lock().unwrap();
|
||||
|
||||
connection.query_row("SELECT t.hash, t.name, t.duration, t.artist_id, a.name AS artist_name FROM tracks t INNER JOIN artists a ON a.id = t.artist_id WHERE t.hash = ?1", [hash], map_track)
|
||||
}
|
||||
|
||||
pub fn get_track_full_path(
|
||||
connection: &PooledConnection<SqliteConnectionManager>,
|
||||
hash: &str,
|
||||
) -> Result<PathBuf, rusqlite::Error> {
|
||||
pub async fn get_specific_tracks(
|
||||
pool: &Pool,
|
||||
hashes: &Vec<String>,
|
||||
) -> Result<Vec<Track>, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = &manager.lock().unwrap();
|
||||
|
||||
let mut statement = connection.prepare("SELECT t.hash, t.name, t.duration, t.artist_id, a.name AS artist_name FROM tracks t INNER JOIN artists a ON a.id = t.artist_id WHERE t.hash = ?1")?;
|
||||
let mut tracks: Vec<Track> = Vec::new();
|
||||
|
||||
for hash in hashes {
|
||||
tracks.push(statement.query_row([hash], map_track)?);
|
||||
}
|
||||
|
||||
Ok(tracks)
|
||||
}
|
||||
|
||||
pub async fn get_track_full_path(pool: &Pool, hash: &str) -> Result<PathBuf, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = &manager.lock().unwrap();
|
||||
|
||||
let (relative_path, library_path): (String, String) = connection.query_row(
|
||||
"SELECT t.path, l.path AS library_root FROM tracks t INNER JOIN library_paths l ON t.library_path_id = l.id WHERE t.hash = ?1",
|
||||
[hash],
|
||||
@@ -99,14 +125,43 @@ pub fn get_track_full_path(
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
pub fn insert_tracks(
|
||||
mut connection: PooledConnection<SqliteConnectionManager>,
|
||||
pub async fn get_specific_tracks_full_path(
|
||||
pool: &Pool,
|
||||
hashes: &Vec<String>,
|
||||
) -> Result<Vec<PathBuf>, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = &manager.lock().unwrap();
|
||||
|
||||
let mut paths: Vec<PathBuf> = Vec::new();
|
||||
|
||||
let home = home::home_dir().unwrap();
|
||||
let home_dir = home.to_str().unwrap();
|
||||
|
||||
let mut statement = connection.prepare(
|
||||
"SELECT t.path, l.path AS library_root FROM tracks t INNER JOIN library_paths l ON t.library_path_id = l.id WHERE t.hash = ?1",
|
||||
)?;
|
||||
|
||||
for hash in hashes {
|
||||
let (relative_path, library_path): (String, String) =
|
||||
statement.query_row([hash], |r| Ok((r.get(0)?, r.get(1)?)))?;
|
||||
|
||||
let library_root = library_path.replace("~", home_dir);
|
||||
let path = Path::new(&library_root).join(&relative_path);
|
||||
|
||||
paths.push(path);
|
||||
}
|
||||
|
||||
Ok(paths)
|
||||
}
|
||||
|
||||
pub async fn insert_tracks(
|
||||
pool: &Pool,
|
||||
tracks: HashMap<String, TrackMetadata>,
|
||||
library_path_id: u64,
|
||||
) -> Result<(), rusqlite::Error> {
|
||||
let existing_covers = get_all_cover_hashes();
|
||||
|
||||
let artists = get_artists(&connection)?;
|
||||
let artists = get_artists(pool).await?;
|
||||
let mut artist_names_to_id: HashMap<String, u64> = HashMap::new();
|
||||
|
||||
for artist in artists {
|
||||
@@ -125,6 +180,9 @@ pub fn insert_tracks(
|
||||
new_artists.push(meta.artist_name.clone());
|
||||
}
|
||||
|
||||
let manager = pool.get().await.unwrap();
|
||||
let mut connection = manager.lock().unwrap();
|
||||
|
||||
// BEGIN TRANSACTION
|
||||
let tx = connection.transaction()?;
|
||||
|
||||
@@ -145,13 +203,36 @@ pub fn insert_tracks(
|
||||
// BEGIN TRANSACTION
|
||||
let tx = connection.transaction()?;
|
||||
|
||||
let mut cover_handles: Vec<JoinHandle<()>> = Vec::new();
|
||||
|
||||
{
|
||||
let mut statement =
|
||||
tx.prepare("INSERT OR REPLACE INTO tracks (hash, library_path_id, name, artist_id, path, duration) VALUES (?1, ?2, ?3, ?4, ?5, ?6)")?;
|
||||
let base_path = get_cover_base_path();
|
||||
|
||||
for (hash, meta) in tracks {
|
||||
std::thread::scope(|s| {
|
||||
for (hash, meta) in tracks.iter() {
|
||||
if let Some(ref cover) = &meta.cover {
|
||||
if !existing_covers.contains(&hash) {
|
||||
s.spawn(|| {
|
||||
let _ = write_cover(hash, cover, &base_path);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut statement =
|
||||
tx.prepare("
|
||||
INSERT INTO tracks
|
||||
(hash, library_path_id, name, artist_id, path, duration)
|
||||
VALUES
|
||||
(?1, ?2, ?3, ?4, ?5, ?6)
|
||||
ON CONFLICT(hash) DO UPDATE SET
|
||||
library_path_id = ?2,
|
||||
name = ?3,
|
||||
artist_id = ?4,
|
||||
path = ?5,
|
||||
duration = ?6
|
||||
")?;
|
||||
|
||||
for (hash, meta) in tracks.iter() {
|
||||
statement.execute(params![
|
||||
&hash,
|
||||
library_path_id,
|
||||
@@ -160,23 +241,161 @@ pub fn insert_tracks(
|
||||
meta.path,
|
||||
meta.total_seconds * 1000
|
||||
])?;
|
||||
|
||||
if let Some(cover) = meta.cover {
|
||||
if !existing_covers.contains(&hash) {
|
||||
cover_handles.push(std::thread::spawn(|| {
|
||||
let _ = write_cover(hash, cover);
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// COMMIT
|
||||
tx.commit()?;
|
||||
|
||||
for handle in cover_handles {
|
||||
let _ = handle.join();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_playlists(pool: &Pool) -> Result<Vec<Playlist>, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = manager.lock().unwrap();
|
||||
|
||||
let mut statement = connection.prepare("SELECT id, name FROM playlists")?;
|
||||
let rows = statement.query_map([], map_playlist)?;
|
||||
|
||||
let mut playlists: Vec<Playlist> = Vec::new();
|
||||
|
||||
let mut tracks_statement = connection.prepare("
|
||||
SELECT t.hash, t.name, t.duration, t.artist_id, a.name FROM playlist_tracks pt INNER JOIN tracks t ON pt.track_hash = t.hash INNER JOIN artists a ON t.artist_id = a.id WHERE pt.playlist_id = ?1
|
||||
")?;
|
||||
|
||||
for row in rows {
|
||||
if let Ok(mut row) = row {
|
||||
let track_rows: Vec<proto::library::Track> = tracks_statement
|
||||
.query_map([row.id], map_track)?
|
||||
.filter_map(|t| t.ok().map(|t| t.into()))
|
||||
.collect();
|
||||
row.tracks = track_rows;
|
||||
|
||||
playlists.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(playlists)
|
||||
}
|
||||
|
||||
pub async fn get_playlist(pool: &Pool, id: u32) -> Result<Playlist, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = manager.lock().unwrap();
|
||||
|
||||
let mut playlist = connection.query_row(
|
||||
"SELECT id, name FROM playlists WHERE id = ?1",
|
||||
[id],
|
||||
map_playlist,
|
||||
)?;
|
||||
|
||||
let mut tracks_statement = connection.prepare("
|
||||
SELECT t.hash, t.name, t.duration, t.artist_id, a.name FROM playlist_tracks pt INNER JOIN tracks t ON pt.track_hash = t.hash INNER JOIN artists a ON t.artist_id = a.id WHERE pt.playlist_id = ?1
|
||||
")?;
|
||||
|
||||
let track_rows: Vec<proto::library::Track> = tracks_statement
|
||||
.query_map([id], map_track)?
|
||||
.filter_map(|t| t.ok().map(|t| t.into()))
|
||||
.collect();
|
||||
playlist.tracks = track_rows;
|
||||
|
||||
Ok(playlist)
|
||||
}
|
||||
|
||||
pub async fn create_playlist(pool: &Pool, name: &str) -> Result<Playlist, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = manager.lock().unwrap();
|
||||
|
||||
let mut statement =
|
||||
connection.prepare("INSERT INTO playlists (name) VALUES (?1) RETURNING id, name")?;
|
||||
let playlist = statement.query_row([name], map_playlist)?;
|
||||
|
||||
Ok(playlist)
|
||||
}
|
||||
|
||||
pub async fn delete_playlist(pool: &Pool, id: u32) -> Result<bool, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = manager.lock().unwrap();
|
||||
|
||||
let mut statement = connection.prepare("DELETE FROM playlists WHERE id = ?1")?;
|
||||
let len = statement.execute([id])?;
|
||||
|
||||
Ok(len == 1)
|
||||
}
|
||||
|
||||
pub async fn add_track_to_playlist(
|
||||
pool: &Pool,
|
||||
playlist_id: u32,
|
||||
track_hash: &str,
|
||||
) -> Result<bool, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let connection = manager.lock().unwrap();
|
||||
|
||||
let mut statement = connection
|
||||
.prepare("INSERT INTO playlist_tracks (playlist_id, track_hash, rank) VALUES (?1, ?2, (SELECT COALESCE(MAX(rank) + 1, 0) FROM playlist_tracks WHERE playlist_id = ?1))")?;
|
||||
let len = statement.execute(params![playlist_id, track_hash])?;
|
||||
|
||||
Ok(len == 1)
|
||||
}
|
||||
|
||||
pub async fn remove_track_from_playlist(
|
||||
pool: &Pool,
|
||||
playlist_id: u32,
|
||||
track_rank: u32,
|
||||
) -> Result<bool, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let mut connection = manager.lock().unwrap();
|
||||
|
||||
let tx = connection.transaction()?;
|
||||
|
||||
let len = tx.execute(
|
||||
"DELETE FROM playlist_tracks WHERE playlist_id = ?1 AND rank = ?2",
|
||||
params![playlist_id, track_rank],
|
||||
)?;
|
||||
|
||||
tx.execute(
|
||||
"UPDATE playlist_tracks SET rank = - rank WHERE playlist_id = ?1 AND rank > ?2",
|
||||
[playlist_id, track_rank],
|
||||
)?;
|
||||
|
||||
tx.execute(
|
||||
"UPDATE playlist_tracks SET rank = - rank - 1 WHERE playlist_id = ?1 AND rank < 0",
|
||||
[playlist_id],
|
||||
)?;
|
||||
|
||||
if len != 1 {
|
||||
tx.rollback()?;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
tx.commit()?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
pub async fn swap_playlist_tracks(
|
||||
pool: &Pool,
|
||||
playlist_id: u32,
|
||||
a_rank: u32,
|
||||
b_rank: u32,
|
||||
) -> Result<bool, rusqlite::Error> {
|
||||
let manager = pool.get().await.unwrap();
|
||||
let mut connection = manager.lock().unwrap();
|
||||
|
||||
let tx = connection.transaction()?;
|
||||
|
||||
tx.execute(
|
||||
"UPDATE playlist_tracks SET rank = CASE WHEN rank = ?2 THEN - ?3 - 1 ELSE - ?2 - 1 END WHERE playlist_id = ?1 AND rank IN (?2, ?3)",
|
||||
[playlist_id, a_rank, b_rank],
|
||||
)?;
|
||||
|
||||
let len = tx.execute("UPDATE playlist_tracks SET rank = - rank - 1 WHERE playlist_id = ?1 AND rank IN (- ?2 - 1, - ?3 - 1)", [playlist_id, a_rank, b_rank])?;
|
||||
|
||||
if len != 2 {
|
||||
tx.rollback()?;
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
tx.commit()?;
|
||||
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
208
src/library.rs
208
src/library.rs
@@ -1,42 +1,42 @@
|
||||
use rayon::prelude::*;
|
||||
use std::{collections::HashMap, fs, path::PathBuf, time::Instant};
|
||||
use deadpool_sqlite::Pool;
|
||||
use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Instant};
|
||||
use tokio::{sync::Mutex, task::JoinSet};
|
||||
|
||||
use r2d2::{Pool, PooledConnection};
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use tonic::{Request, Response, Status};
|
||||
use walkdir::{DirEntry, WalkDir};
|
||||
|
||||
use crate::{
|
||||
checksum::generate_hash,
|
||||
database::tracks::{get_tracks, insert_tracks},
|
||||
database::tracks::{
|
||||
add_track_to_playlist, create_playlist, delete_playlist, get_playlist, get_playlists,
|
||||
get_tracks, insert_tracks, remove_track_from_playlist, swap_playlist_tracks,
|
||||
},
|
||||
music::metadata::{extract_track_data, TrackMetadata},
|
||||
proto::library::{library_server::Library, Track, TrackList},
|
||||
proto::library::{
|
||||
library_server::Library, AddTrackToPlaylistRequest, CreatePlaylistRequest,
|
||||
CreatePlaylistResponse, DeletePlaylistRequest, ListPlaylistsResponse,
|
||||
RemoveTrackFromPlaylistRequest, SwapTracksRequest, Track, TrackList,
|
||||
},
|
||||
state::GrooveState,
|
||||
};
|
||||
|
||||
pub struct LibraryService {
|
||||
#[allow(dead_code)]
|
||||
state: GrooveState,
|
||||
pool: Pool<SqliteConnectionManager>,
|
||||
pool: Pool,
|
||||
}
|
||||
|
||||
impl LibraryService {
|
||||
pub fn new(state: GrooveState, pool: Pool<SqliteConnectionManager>) -> Self {
|
||||
pub fn new(state: GrooveState, pool: Pool) -> Self {
|
||||
Self { state, pool }
|
||||
}
|
||||
}
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl Library for LibraryService {
|
||||
async fn list_tracks(
|
||||
&self,
|
||||
_request: tonic::Request<()>,
|
||||
) -> Result<tonic::Response<TrackList>, tonic::Status> {
|
||||
let Ok(db) = self.pool.get() else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let Ok(tracks) = get_tracks(&db) else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
async fn list_tracks(&self, _request: Request<()>) -> Result<Response<TrackList>, Status> {
|
||||
let Ok(tracks) = get_tracks(&self.pool).await else {
|
||||
return Err(Status::internal(""));
|
||||
};
|
||||
|
||||
let response = TrackList {
|
||||
@@ -52,15 +52,139 @@ impl Library for LibraryService {
|
||||
.collect::<Vec<Track>>(),
|
||||
};
|
||||
|
||||
Ok(tonic::Response::new(response))
|
||||
Ok(Response::new(response))
|
||||
}
|
||||
|
||||
async fn list_playlists(
|
||||
&self,
|
||||
_request: Request<()>,
|
||||
) -> Result<Response<ListPlaylistsResponse>, Status> {
|
||||
let Ok(playlists) = get_playlists(&self.pool).await else {
|
||||
return Err(Status::internal(""));
|
||||
};
|
||||
|
||||
let response = ListPlaylistsResponse { playlists };
|
||||
|
||||
Ok(Response::new(response))
|
||||
}
|
||||
|
||||
async fn create_playlist(
|
||||
&self,
|
||||
request: Request<CreatePlaylistRequest>,
|
||||
) -> Result<Response<CreatePlaylistResponse>, Status> {
|
||||
let input = request.get_ref();
|
||||
|
||||
let Ok(playlist) = create_playlist(&self.pool, &input.name).await else {
|
||||
return Err(Status::internal(""));
|
||||
};
|
||||
|
||||
let response = CreatePlaylistResponse {
|
||||
playlist: Some(playlist),
|
||||
};
|
||||
|
||||
Ok(Response::new(response))
|
||||
}
|
||||
|
||||
async fn delete_playlist(
|
||||
&self,
|
||||
request: Request<DeletePlaylistRequest>,
|
||||
) -> Result<Response<()>, Status> {
|
||||
let input = request.get_ref();
|
||||
|
||||
let Ok(success) = delete_playlist(&self.pool, input.id).await else {
|
||||
return Err(Status::internal(""));
|
||||
};
|
||||
|
||||
if !success {
|
||||
return Err(Status::internal(""));
|
||||
}
|
||||
|
||||
Ok(Response::new(()))
|
||||
}
|
||||
|
||||
async fn add_track_to_playlist(
|
||||
&self,
|
||||
request: Request<AddTrackToPlaylistRequest>,
|
||||
) -> Result<Response<TrackList>, Status> {
|
||||
let input = request.get_ref();
|
||||
|
||||
let Ok(success) =
|
||||
add_track_to_playlist(&self.pool, input.playlist_id, &input.track_hash).await
|
||||
else {
|
||||
return Err(Status::internal(""));
|
||||
};
|
||||
|
||||
if !success {
|
||||
return Err(Status::internal(""));
|
||||
}
|
||||
|
||||
let Ok(playlist) = get_playlist(&self.pool, input.playlist_id).await else {
|
||||
return Err(Status::internal(""));
|
||||
};
|
||||
|
||||
let response = TrackList {
|
||||
tracks: playlist.tracks,
|
||||
};
|
||||
|
||||
Ok(Response::new(response))
|
||||
}
|
||||
|
||||
async fn remove_track_from_playlist(
|
||||
&self,
|
||||
request: Request<RemoveTrackFromPlaylistRequest>,
|
||||
) -> Result<Response<TrackList>, Status> {
|
||||
let input = request.get_ref();
|
||||
|
||||
let Ok(success) =
|
||||
remove_track_from_playlist(&self.pool, input.playlist_id, input.track_rank).await
|
||||
else {
|
||||
return Err(Status::internal(""));
|
||||
};
|
||||
|
||||
if !success {
|
||||
return Err(Status::internal(""));
|
||||
}
|
||||
|
||||
let Ok(playlist) = get_playlist(&self.pool, input.playlist_id).await else {
|
||||
return Err(Status::internal(""));
|
||||
};
|
||||
|
||||
let response = TrackList {
|
||||
tracks: playlist.tracks,
|
||||
};
|
||||
|
||||
Ok(Response::new(response))
|
||||
}
|
||||
|
||||
async fn swap_tracks(
|
||||
&self,
|
||||
request: Request<SwapTracksRequest>,
|
||||
) -> Result<Response<TrackList>, Status> {
|
||||
let input = request.get_ref();
|
||||
|
||||
let Ok(success) =
|
||||
swap_playlist_tracks(&self.pool, input.playlist_id, input.a_rank, input.b_rank).await
|
||||
else {
|
||||
return Err(Status::internal(""));
|
||||
};
|
||||
|
||||
if !success {
|
||||
return Err(Status::internal(""));
|
||||
}
|
||||
|
||||
let Ok(playlist) = get_playlist(&self.pool, input.playlist_id).await else {
|
||||
return Err(Status::internal(""));
|
||||
};
|
||||
|
||||
let response = TrackList {
|
||||
tracks: playlist.tracks,
|
||||
};
|
||||
|
||||
Ok(Response::new(response))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn index_path(
|
||||
path: PathBuf,
|
||||
db: PooledConnection<SqliteConnectionManager>,
|
||||
path_id: u64,
|
||||
) -> Result<(), rusqlite::Error> {
|
||||
pub async fn index_path(path: PathBuf, db: &Pool, path_id: u64) -> Result<(), rusqlite::Error> {
|
||||
let home = home::home_dir().unwrap();
|
||||
|
||||
let correct_path = path.to_str().unwrap().replace("~", home.to_str().unwrap());
|
||||
@@ -73,45 +197,37 @@ pub fn index_path(
|
||||
.filter_map(Result::ok)
|
||||
.collect();
|
||||
|
||||
let hashmaps: Vec<HashMap<String, TrackMetadata>> = entries
|
||||
.into_par_iter()
|
||||
.fold(
|
||||
|| HashMap::new(),
|
||||
|mut acc: HashMap<String, TrackMetadata>, entry| {
|
||||
if entry.file_type().is_file()
|
||||
&& entry.path().extension().is_some_and(|ext| ext == "mp3")
|
||||
{
|
||||
let file_path = entry.path();
|
||||
let content = fs::read(file_path).unwrap();
|
||||
let tracks: Arc<Mutex<HashMap<String, TrackMetadata>>> = Arc::new(Mutex::new(HashMap::new()));
|
||||
let mut set = JoinSet::new();
|
||||
|
||||
for entry in entries.into_iter() {
|
||||
if entry.file_type().is_file() && entry.path().extension().is_some_and(|ext| ext == "mp3") {
|
||||
let tracks = tracks.clone();
|
||||
set.spawn(async move {
|
||||
let file_path = entry.path();
|
||||
if let Ok(content) = std::fs::read(file_path) {
|
||||
let hash = generate_hash(&content);
|
||||
|
||||
let relative_path =
|
||||
file_path.to_str().unwrap().to_string()[path_offset..].to_string();
|
||||
|
||||
if let Some(metadata) = extract_track_data(content, relative_path) {
|
||||
acc.insert(hash, metadata);
|
||||
tracks.lock().await.insert(hash, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
acc
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
||||
let mut tracks = HashMap::<String, TrackMetadata>::new();
|
||||
|
||||
for tracks_chunk in hashmaps {
|
||||
tracks.extend(tracks_chunk);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
set.join_all().await;
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
println!("indexing took {:.2?}", elapsed);
|
||||
|
||||
let now = Instant::now();
|
||||
|
||||
insert_tracks(db, tracks, path_id)?;
|
||||
insert_tracks(db, Arc::try_unwrap(tracks).unwrap().into_inner(), path_id).await?;
|
||||
|
||||
let elapsed = now.elapsed();
|
||||
|
||||
|
||||
15
src/main.rs
15
src/main.rs
@@ -21,6 +21,7 @@ pub mod player;
|
||||
pub mod settings;
|
||||
pub mod state;
|
||||
|
||||
use music::player::start_watching;
|
||||
use settings::SettingsService;
|
||||
|
||||
pub mod proto {
|
||||
@@ -39,9 +40,9 @@ pub mod proto {
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let pool = &mut establish_connection();
|
||||
|
||||
let connection = pool.get().unwrap();
|
||||
|
||||
initialize_database(&connection).expect("Error initializing database");
|
||||
initialize_database(&pool)
|
||||
.await
|
||||
.expect("Error initializing database");
|
||||
|
||||
let address = "[::1]:39993".parse()?;
|
||||
|
||||
@@ -52,19 +53,17 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let player = Arc::new(Mutex::new(AudioPlayer::new(sink)));
|
||||
let c_player = player.clone();
|
||||
|
||||
let (watch_handle, mut rx) = player.lock().await.start_watching();
|
||||
let (watch_handle, mut rx) = start_watching(player.clone());
|
||||
|
||||
let player_rx_handle = tokio::spawn(async move {
|
||||
while let Some(next) = rx.recv().await {
|
||||
let mut player = player.lock().await;
|
||||
match next {
|
||||
Some(queued_track) => {
|
||||
let _ = player
|
||||
.play_track(queued_track.track, queued_track.path, false)
|
||||
.await;
|
||||
let _ = player.play_track(queued_track.track, queued_track.path, false);
|
||||
}
|
||||
None => {
|
||||
player.clear().await;
|
||||
player.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,9 @@ pub struct CoverData {
|
||||
pub mime_type: String,
|
||||
}
|
||||
|
||||
const JPEG_SOI_AND_JFIF: &[u8; 5] = &[0xFF, 0xD8, 0xFF, 0xE0, 0x00];
|
||||
const JPEG_RAW_HEADER_SEGMENT: &[u8; 8] = &[0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01];
|
||||
|
||||
pub fn extract_track_data(file: Vec<u8>, path: String) -> Option<TrackMetadata> {
|
||||
let probe = symphonia::default::get_probe();
|
||||
|
||||
@@ -66,10 +69,10 @@ pub fn extract_track_data(file: Vec<u8>, path: String) -> Option<TrackMetadata>
|
||||
|
||||
for tag in current_metadata.tags() {
|
||||
match tag.key.as_str() {
|
||||
"TIT2" => title = Some(tag.value.to_string()),
|
||||
"TPE1" => artist = Some(tag.value.to_string()),
|
||||
"TPE2" => album_artist = Some(tag.value.to_string()),
|
||||
"TALB" => album = Some(tag.value.to_string()),
|
||||
"TIT2" | "TT2" => title = Some(tag.value.to_string()),
|
||||
"TPE1" | "TP1" => artist = Some(tag.value.to_string()),
|
||||
"TPE2" | "TP2" => album_artist = Some(tag.value.to_string()),
|
||||
"TALB" | "TAL" => album = Some(tag.value.to_string()),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
@@ -77,14 +80,33 @@ pub fn extract_track_data(file: Vec<u8>, path: String) -> Option<TrackMetadata>
|
||||
let mut cover: Option<CoverData> = None;
|
||||
|
||||
for visual in current_metadata.visuals() {
|
||||
let mime_type = visual.media_type.clone();
|
||||
let mut mime_type = visual.media_type.clone();
|
||||
let lower_mime_type = mime_type.to_lowercase();
|
||||
|
||||
if lower_mime_type == "png"
|
||||
|| lower_mime_type == "webp"
|
||||
|| lower_mime_type == "jpeg"
|
||||
|| lower_mime_type == "jpg"
|
||||
{
|
||||
mime_type = format!("image/{lower_mime_type}");
|
||||
}
|
||||
|
||||
if mime_type != "image/png" && mime_type != "image/jpeg" && mime_type != "image/webp" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let data = if visual.data.starts_with(JPEG_RAW_HEADER_SEGMENT) {
|
||||
let mut data = JPEG_SOI_AND_JFIF.to_vec();
|
||||
|
||||
data.append(&mut visual.data.to_vec());
|
||||
|
||||
data
|
||||
} else {
|
||||
visual.data.to_vec()
|
||||
};
|
||||
|
||||
cover = Some(CoverData {
|
||||
bytes: visual.data.to_vec(),
|
||||
bytes: data,
|
||||
mime_type,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use std::{
|
||||
collections::VecDeque,
|
||||
fs,
|
||||
io::{BufReader, Cursor},
|
||||
path::Path,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
@@ -21,9 +21,9 @@ use crate::{database::tracks::Track, proto::player::PlayerStatus};
|
||||
use super::queue::QueuedTrack;
|
||||
|
||||
pub struct AudioPlayer {
|
||||
sink: Arc<Mutex<Sink>>,
|
||||
currently_playing: Arc<Mutex<Option<Track>>>,
|
||||
queue: Arc<Mutex<VecDeque<QueuedTrack>>>,
|
||||
sink: Sink,
|
||||
currently_playing: Option<Track>,
|
||||
queue: VecDeque<QueuedTrack>,
|
||||
}
|
||||
|
||||
impl AudioPlayer {
|
||||
@@ -32,41 +32,13 @@ impl AudioPlayer {
|
||||
sink.set_volume(0.5);
|
||||
|
||||
Self {
|
||||
sink: Arc::new(Mutex::new(sink)),
|
||||
currently_playing: Arc::new(Mutex::new(None)),
|
||||
queue: Arc::new(Mutex::new(VecDeque::new())),
|
||||
sink,
|
||||
currently_playing: None,
|
||||
queue: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_watching(&mut self) -> (JoinHandle<()>, Receiver<Option<QueuedTrack>>) {
|
||||
let sink = self.sink.clone();
|
||||
let current_track = self.currently_playing.clone();
|
||||
let queue = self.queue.clone();
|
||||
|
||||
let (tx, rx) = mpsc::channel::<Option<QueuedTrack>>(128);
|
||||
|
||||
(
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
if let Some(current_track) = current_track.lock().await.as_mut() {
|
||||
let sink = sink.lock().await;
|
||||
if sink.empty()
|
||||
|| current_track.duration as u128 <= sink.get_pos().as_millis()
|
||||
{
|
||||
if let Err(_) = tx.send(queue.lock().await.pop_front()).await {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokio::time::sleep(Self::WATCH_SLEEP_TIME).await;
|
||||
}
|
||||
}),
|
||||
rx,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn play_track<P>(
|
||||
pub fn play_track<P>(
|
||||
&mut self,
|
||||
track: Track,
|
||||
path: P,
|
||||
@@ -75,43 +47,36 @@ impl AudioPlayer {
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
let sink = self.sink.lock().await;
|
||||
self.currently_playing = Some(track);
|
||||
|
||||
sink.clear();
|
||||
self.sink.clear();
|
||||
if clear_queue {
|
||||
self.queue.lock().await.clear();
|
||||
self.queue.clear();
|
||||
}
|
||||
|
||||
let file = BufReader::new(Cursor::new(fs::read(path)?));
|
||||
|
||||
let source = Decoder::new(file)?;
|
||||
|
||||
*self.currently_playing.lock().await = Some(track);
|
||||
sink.append(source);
|
||||
self.sink.append(source);
|
||||
|
||||
sink.play();
|
||||
self.sink.play();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn play_track_next(
|
||||
pub fn play_track_next(
|
||||
&mut self,
|
||||
track: Track,
|
||||
path: &Path,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let sink = self.sink.lock().await;
|
||||
|
||||
let mut queue = self.queue.lock().await;
|
||||
|
||||
if sink.empty() && queue.is_empty() {
|
||||
drop(queue);
|
||||
drop(sink);
|
||||
return self.play_track(track, path, false).await;
|
||||
if self.sink.empty() && self.queue.is_empty() {
|
||||
return self.play_track(track, path, false);
|
||||
}
|
||||
|
||||
let queued_track = QueuedTrack::new(track, path.to_path_buf());
|
||||
|
||||
queue.push_front(queued_track);
|
||||
self.queue.push_front(queued_track);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -121,124 +86,166 @@ impl AudioPlayer {
|
||||
track: Track,
|
||||
path: &Path,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let sink = self.sink.lock().await;
|
||||
let mut queue = self.queue.lock().await;
|
||||
|
||||
if sink.empty() && queue.is_empty() {
|
||||
drop(queue);
|
||||
drop(sink);
|
||||
return self.play_track(track, path, false).await;
|
||||
if self.sink.empty() && self.queue.is_empty() {
|
||||
return self.play_track(track, path, false);
|
||||
}
|
||||
|
||||
let queued_track = QueuedTrack::new(track, path.to_path_buf());
|
||||
|
||||
queue.push_back(queued_track);
|
||||
self.queue.push_back(queued_track);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn skip_track(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut queue = self.queue.lock().await;
|
||||
|
||||
let Some(queued_track) = queue.pop_front() else {
|
||||
drop(queue);
|
||||
self.clear().await;
|
||||
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
drop(queue);
|
||||
|
||||
self.play_track(queued_track.track, queued_track.path, false)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn skip_to_queue_index(
|
||||
pub async fn add_tracks_to_queue(
|
||||
&mut self,
|
||||
index: usize,
|
||||
tracks: Vec<(Track, PathBuf)>,
|
||||
clear_queue: bool,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut queue = self.queue.lock().await;
|
||||
if clear_queue {
|
||||
self.clear();
|
||||
}
|
||||
|
||||
let Some(queued_track) = queue.remove(index) else {
|
||||
drop(queue);
|
||||
self.clear().await;
|
||||
for (track, path) in tracks {
|
||||
self.queue.push_back(QueuedTrack { track, path });
|
||||
}
|
||||
|
||||
if !self.sink.empty() || self.queue.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(queued_track) = self.queue.pop_front() {
|
||||
self.play_track(queued_track.track, queued_track.path, false)?;
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn skip_track(&mut self) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let Some(queued_track) = self.queue.pop_front() else {
|
||||
self.clear();
|
||||
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
drop(queue);
|
||||
self.play_track(queued_track.track, queued_track.path, false)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn resume(&self) {
|
||||
self.sink.lock().await.play();
|
||||
pub fn skip_to_queue_index(&mut self, index: usize) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let Some(queued_track) = self.queue.remove(index) else {
|
||||
self.clear();
|
||||
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
self.play_track(queued_track.track, queued_track.path, false)
|
||||
}
|
||||
|
||||
pub async fn pause(&self) {
|
||||
self.sink.lock().await.pause();
|
||||
pub fn swap_queue_indices(&mut self, a: usize, b: usize) -> bool {
|
||||
let len = self.queue.len();
|
||||
|
||||
if a >= len || b >= len {
|
||||
return false;
|
||||
}
|
||||
|
||||
self.queue.swap(a, b);
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
pub fn resume(&self) {
|
||||
self.sink.play();
|
||||
}
|
||||
|
||||
pub fn pause(&self) {
|
||||
self.sink.pause()
|
||||
}
|
||||
|
||||
/// Toggles the player's pause state and returns the new value
|
||||
pub async fn toggle_pause(&self) -> bool {
|
||||
if self.is_paused().await {
|
||||
self.resume().await;
|
||||
pub fn toggle_pause(&self) -> bool {
|
||||
if self.is_paused() {
|
||||
self.resume();
|
||||
false
|
||||
} else {
|
||||
self.pause().await;
|
||||
self.pause();
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn volume(&self) -> f32 {
|
||||
self.sink.lock().await.volume()
|
||||
pub fn volume(&self) -> f32 {
|
||||
self.sink.volume()
|
||||
}
|
||||
|
||||
pub async fn set_volume(&self, value: f32) {
|
||||
self.sink.lock().await.set_volume(value);
|
||||
pub fn set_volume(&self, value: f32) {
|
||||
self.sink.set_volume(value);
|
||||
}
|
||||
|
||||
pub async fn position(&self) -> u128 {
|
||||
self.sink.lock().await.get_pos().as_millis()
|
||||
pub fn position(&self) -> u128 {
|
||||
self.sink.get_pos().as_millis()
|
||||
}
|
||||
|
||||
pub async fn set_position(&self, position: u64) {
|
||||
let _ = self
|
||||
.sink
|
||||
.lock()
|
||||
.await
|
||||
.try_seek(Duration::from_millis(position));
|
||||
pub fn set_position(&self, position: u64) {
|
||||
let _ = self.sink.try_seek(Duration::from_millis(position));
|
||||
}
|
||||
|
||||
pub async fn is_paused(&self) -> bool {
|
||||
self.sink.lock().await.is_paused()
|
||||
pub fn is_paused(&self) -> bool {
|
||||
self.sink.is_paused()
|
||||
}
|
||||
|
||||
pub async fn currently_playing(&self) -> Option<Track> {
|
||||
self.currently_playing.lock().await.clone()
|
||||
pub fn currently_playing(&self) -> Option<Track> {
|
||||
self.currently_playing.clone()
|
||||
}
|
||||
|
||||
pub async fn queue(&self) -> VecDeque<QueuedTrack> {
|
||||
self.queue.lock().await.clone()
|
||||
pub fn queue(&self) -> VecDeque<QueuedTrack> {
|
||||
self.queue.clone()
|
||||
}
|
||||
|
||||
pub async fn clear(&mut self) {
|
||||
self.queue.lock().await.clear();
|
||||
self.sink.lock().await.clear();
|
||||
*self.currently_playing.lock().await = None;
|
||||
pub fn clear(&mut self) {
|
||||
self.queue.clear();
|
||||
self.sink.clear();
|
||||
self.currently_playing = None;
|
||||
}
|
||||
|
||||
pub async fn get_snapshot(&self) -> StatusSnapshot {
|
||||
pub fn get_snapshot(&self) -> StatusSnapshot {
|
||||
StatusSnapshot {
|
||||
volume: self.volume().await,
|
||||
position: self.position().await,
|
||||
is_paused: self.is_paused().await,
|
||||
currently_playing: self.currently_playing().await,
|
||||
queue: self.queue().await,
|
||||
volume: self.volume(),
|
||||
position: self.position(),
|
||||
is_paused: self.is_paused(),
|
||||
currently_playing: self.currently_playing(),
|
||||
queue: self.queue(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_watching(
|
||||
player: Arc<Mutex<AudioPlayer>>,
|
||||
) -> (JoinHandle<()>, Receiver<Option<QueuedTrack>>) {
|
||||
let (tx, rx) = mpsc::channel::<Option<QueuedTrack>>(128);
|
||||
|
||||
(
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
{
|
||||
let mut player = player.lock().await;
|
||||
|
||||
if let Some(current_track) = player.currently_playing() {
|
||||
if player.sink.empty()
|
||||
|| current_track.duration as u128 <= player.position()
|
||||
{
|
||||
if let Err(_) = tx.send(player.queue.pop_front()).await {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokio::time::sleep(AudioPlayer::WATCH_SLEEP_TIME).await;
|
||||
}
|
||||
}),
|
||||
rx,
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct StatusSnapshot {
|
||||
pub volume: f32,
|
||||
|
||||
189
src/player.rs
189
src/player.rs
@@ -1,18 +1,21 @@
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
use std::{pin::Pin, time::Duration};
|
||||
use deadpool_sqlite::Pool;
|
||||
use std::{path::PathBuf, pin::Pin, time::Duration};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio_stream::{wrappers::ReceiverStream, Stream};
|
||||
|
||||
use crate::{
|
||||
database::tracks::{get_track, get_track_full_path},
|
||||
database::tracks::{
|
||||
get_playlist, get_specific_tracks, get_specific_tracks_full_path, get_track,
|
||||
get_track_full_path, Track,
|
||||
},
|
||||
music::queue::queue_to_track_vec,
|
||||
proto::{
|
||||
self,
|
||||
player::{
|
||||
player_server::Player, PauseState, PlayTrackResponse, PlayerStatus, Queue,
|
||||
SeekPositionRequest, SeekPositionResponse, SetVolumeRequest, SetVolumeResponse,
|
||||
SkipToQueueIndexRequest, TrackRequest,
|
||||
player_server::Player, PauseState, PlayPlaylistRequest, PlayTrackResponse,
|
||||
PlayerStatus, Queue, SeekPositionRequest, SeekPositionResponse, SetVolumeRequest,
|
||||
SetVolumeResponse, SkipToQueueIndexRequest, SwapQueueIndicesRequest, TrackRequest,
|
||||
TracksRequest,
|
||||
},
|
||||
},
|
||||
state::GrooveState,
|
||||
@@ -20,11 +23,11 @@ use crate::{
|
||||
|
||||
pub struct PlayerService {
|
||||
state: GrooveState,
|
||||
pool: Pool<SqliteConnectionManager>,
|
||||
pool: Pool,
|
||||
}
|
||||
|
||||
impl PlayerService {
|
||||
pub fn new(state: GrooveState, pool: Pool<SqliteConnectionManager>) -> Self {
|
||||
pub fn new(state: GrooveState, pool: Pool) -> Self {
|
||||
Self { state, pool }
|
||||
}
|
||||
}
|
||||
@@ -37,28 +40,21 @@ impl Player for PlayerService {
|
||||
&self,
|
||||
request: tonic::Request<TrackRequest>,
|
||||
) -> Result<tonic::Response<PlayTrackResponse>, tonic::Status> {
|
||||
let Ok(db) = self.pool.get() else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let input = request.get_ref();
|
||||
|
||||
let Ok(track) = get_track(&db, input.hash.as_str()) else {
|
||||
return Err(tonic::Status::not_found(""));
|
||||
let Ok(track) = get_track(&self.pool, input.hash.as_str()).await else {
|
||||
return Err(tonic::Status::not_found("Could not get track"));
|
||||
};
|
||||
|
||||
let Ok(track_path) = get_track_full_path(&db, input.hash.as_str()) else {
|
||||
return Err(tonic::Status::not_found(""));
|
||||
let Ok(track_path) = get_track_full_path(&self.pool, input.hash.as_str()).await else {
|
||||
return Err(tonic::Status::not_found("Could not get track file path"));
|
||||
};
|
||||
|
||||
let state = self.state.lock().await;
|
||||
|
||||
let _ = state
|
||||
.player
|
||||
.lock()
|
||||
.await
|
||||
.play_track(track.clone(), track_path, true)
|
||||
.await;
|
||||
let player = &mut state.player.lock().await;
|
||||
|
||||
let _ = player.play_track(track.clone(), track_path, true);
|
||||
|
||||
let response = PlayTrackResponse {
|
||||
track: Some(proto::library::Track::from(track.into())),
|
||||
@@ -74,7 +70,7 @@ impl Player for PlayerService {
|
||||
) -> Result<tonic::Response<PauseState>, tonic::Status> {
|
||||
let state = self.state.lock().await;
|
||||
|
||||
state.player.lock().await.resume().await;
|
||||
state.player.lock().await.resume();
|
||||
|
||||
let response = PauseState { is_paused: false };
|
||||
|
||||
@@ -87,7 +83,7 @@ impl Player for PlayerService {
|
||||
) -> Result<tonic::Response<PauseState>, tonic::Status> {
|
||||
let state = self.state.lock().await;
|
||||
|
||||
state.player.lock().await.pause().await;
|
||||
state.player.lock().await.pause();
|
||||
|
||||
let response = PauseState { is_paused: true };
|
||||
|
||||
@@ -100,7 +96,7 @@ impl Player for PlayerService {
|
||||
) -> Result<tonic::Response<PauseState>, tonic::Status> {
|
||||
let state = self.state.lock().await;
|
||||
|
||||
let is_paused = state.player.lock().await.toggle_pause().await;
|
||||
let is_paused = state.player.lock().await.toggle_pause();
|
||||
|
||||
let response = PauseState { is_paused };
|
||||
|
||||
@@ -114,7 +110,7 @@ impl Player for PlayerService {
|
||||
let input = request.get_ref();
|
||||
let state = self.state.lock().await;
|
||||
|
||||
state.player.lock().await.set_position(input.position).await;
|
||||
state.player.lock().await.set_position(input.position);
|
||||
|
||||
let response = SeekPositionResponse {
|
||||
position: input.position,
|
||||
@@ -130,7 +126,7 @@ impl Player for PlayerService {
|
||||
let input = request.get_ref();
|
||||
let state = self.state.lock().await;
|
||||
|
||||
state.player.lock().await.set_volume(input.volume).await;
|
||||
state.player.lock().await.set_volume(input.volume);
|
||||
|
||||
let response = SetVolumeResponse {
|
||||
volume: input.volume,
|
||||
@@ -159,7 +155,6 @@ impl Player for PlayerService {
|
||||
.lock()
|
||||
.await
|
||||
.get_snapshot()
|
||||
.await
|
||||
.into()))
|
||||
.await
|
||||
{
|
||||
@@ -181,17 +176,13 @@ impl Player for PlayerService {
|
||||
&self,
|
||||
request: tonic::Request<TrackRequest>,
|
||||
) -> Result<tonic::Response<Queue>, tonic::Status> {
|
||||
let Ok(db) = self.pool.get() else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let input = request.get_ref();
|
||||
|
||||
let Ok(track) = get_track(&db, input.hash.as_str()) else {
|
||||
let Ok(track) = get_track(&self.pool, input.hash.as_str()).await else {
|
||||
return Err(tonic::Status::not_found(""));
|
||||
};
|
||||
|
||||
let Ok(track_path) = get_track_full_path(&db, input.hash.as_str()) else {
|
||||
let Ok(track_path) = get_track_full_path(&self.pool, input.hash.as_str()).await else {
|
||||
return Err(tonic::Status::not_found(""));
|
||||
};
|
||||
|
||||
@@ -202,7 +193,7 @@ impl Player for PlayerService {
|
||||
return Err(tonic::Status::internal(""));
|
||||
}
|
||||
|
||||
let queue = player.queue().await;
|
||||
let queue = player.queue();
|
||||
|
||||
let response = Queue {
|
||||
tracks: queue_to_track_vec(queue),
|
||||
@@ -211,32 +202,100 @@ impl Player for PlayerService {
|
||||
Ok(tonic::Response::new(response))
|
||||
}
|
||||
|
||||
async fn play_track_next(
|
||||
async fn add_tracks_to_queue(
|
||||
&self,
|
||||
request: tonic::Request<TrackRequest>,
|
||||
request: tonic::Request<TracksRequest>,
|
||||
) -> Result<tonic::Response<Queue>, tonic::Status> {
|
||||
let Ok(db) = self.pool.get() else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let input = request.get_ref();
|
||||
|
||||
let Ok(track) = get_track(&db, input.hash.as_str()) else {
|
||||
let Ok(tracks) = get_specific_tracks(&self.pool, &input.tracks).await else {
|
||||
return Err(tonic::Status::not_found(""));
|
||||
};
|
||||
|
||||
let Ok(track_path) = get_track_full_path(&db, input.hash.as_str()) else {
|
||||
let Ok(track_paths) = get_specific_tracks_full_path(&self.pool, &input.tracks).await else {
|
||||
return Err(tonic::Status::not_found(""));
|
||||
};
|
||||
|
||||
let state = self.state.lock().await;
|
||||
let mut player = state.player.lock().await;
|
||||
|
||||
if let Err(_) = player.play_track_next(track, &track_path).await {
|
||||
let tracks_and_paths: Vec<(Track, PathBuf)> =
|
||||
tracks.into_iter().zip(track_paths.into_iter()).collect();
|
||||
|
||||
if let Err(_) = player.add_tracks_to_queue(tracks_and_paths, false).await {
|
||||
return Err(tonic::Status::internal(""));
|
||||
}
|
||||
|
||||
let queue = player.queue().await;
|
||||
let queue = player.queue();
|
||||
|
||||
let response = Queue {
|
||||
tracks: queue_to_track_vec(queue),
|
||||
};
|
||||
|
||||
Ok(tonic::Response::new(response))
|
||||
}
|
||||
|
||||
async fn play_playlist(
|
||||
&self,
|
||||
request: tonic::Request<PlayPlaylistRequest>,
|
||||
) -> Result<tonic::Response<PlayerStatus>, tonic::Status> {
|
||||
let input = request.get_ref();
|
||||
|
||||
let Ok(playlist) = get_playlist(&self.pool, input.id).await else {
|
||||
return Err(tonic::Status::not_found(""));
|
||||
};
|
||||
|
||||
let Ok(track_paths) = get_specific_tracks_full_path(
|
||||
&self.pool,
|
||||
&playlist.tracks.iter().map(|t| t.hash.clone()).collect(),
|
||||
)
|
||||
.await
|
||||
else {
|
||||
return Err(tonic::Status::not_found(""));
|
||||
};
|
||||
|
||||
let state = self.state.lock().await;
|
||||
let mut player = state.player.lock().await;
|
||||
|
||||
let tracks = playlist.tracks.iter().map(|t| t.clone().into());
|
||||
|
||||
let tracks_and_paths: Vec<(Track, PathBuf)> = tracks
|
||||
.into_iter()
|
||||
.zip(track_paths.into_iter())
|
||||
.skip(input.starting_rank.unwrap_or(0) as usize)
|
||||
.collect();
|
||||
|
||||
if let Err(_) = player.add_tracks_to_queue(tracks_and_paths, true).await {
|
||||
return Err(tonic::Status::internal(""));
|
||||
}
|
||||
|
||||
let response = player.get_snapshot().into();
|
||||
|
||||
Ok(tonic::Response::new(response))
|
||||
}
|
||||
|
||||
async fn play_track_next(
|
||||
&self,
|
||||
request: tonic::Request<TrackRequest>,
|
||||
) -> Result<tonic::Response<Queue>, tonic::Status> {
|
||||
let input = request.get_ref();
|
||||
|
||||
let Ok(track) = get_track(&self.pool, input.hash.as_str()).await else {
|
||||
return Err(tonic::Status::not_found(""));
|
||||
};
|
||||
|
||||
let Ok(track_path) = get_track_full_path(&self.pool, input.hash.as_str()).await else {
|
||||
return Err(tonic::Status::not_found(""));
|
||||
};
|
||||
|
||||
let state = self.state.lock().await;
|
||||
let mut player = state.player.lock().await;
|
||||
|
||||
if let Err(_) = player.play_track_next(track, &track_path) {
|
||||
return Err(tonic::Status::internal(""));
|
||||
}
|
||||
|
||||
let queue = player.queue();
|
||||
|
||||
let response = Queue {
|
||||
tracks: queue_to_track_vec(queue),
|
||||
@@ -253,11 +312,11 @@ impl Player for PlayerService {
|
||||
|
||||
let mut player = state.player.lock().await;
|
||||
|
||||
if let Err(_) = player.skip_track().await {
|
||||
if let Err(_) = player.skip_track() {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let response = player.get_snapshot().await.into();
|
||||
let response = player.get_snapshot().into();
|
||||
|
||||
Ok(tonic::Response::new(response))
|
||||
}
|
||||
@@ -275,11 +334,41 @@ impl Player for PlayerService {
|
||||
let state = self.state.lock().await;
|
||||
let mut player = state.player.lock().await;
|
||||
|
||||
if let Err(_) = player.skip_to_queue_index(index).await {
|
||||
if let Err(_) = player.skip_to_queue_index(index) {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let response = player.get_snapshot().await.into();
|
||||
let response = player.get_snapshot().into();
|
||||
|
||||
Ok(tonic::Response::new(response))
|
||||
}
|
||||
|
||||
async fn swap_queue_indices(
|
||||
&self,
|
||||
request: tonic::Request<SwapQueueIndicesRequest>,
|
||||
) -> Result<tonic::Response<Queue>, tonic::Status> {
|
||||
let input = request.get_ref();
|
||||
|
||||
let Ok(a) = input.a.try_into() else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let Ok(b) = input.b.try_into() else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let state = self.state.lock().await;
|
||||
let mut player = state.player.lock().await;
|
||||
|
||||
if !player.swap_queue_indices(a, b) {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let queue = player.queue();
|
||||
|
||||
let response = Queue {
|
||||
tracks: queue_to_track_vec(queue),
|
||||
};
|
||||
|
||||
Ok(tonic::Response::new(response))
|
||||
}
|
||||
|
||||
@@ -9,18 +9,17 @@ use crate::proto::settings::{
|
||||
};
|
||||
use crate::state::GrooveState;
|
||||
|
||||
use deadpool_sqlite::Pool;
|
||||
use proto::settings::{settings_server::Settings, LibraryPath, SettingsData};
|
||||
use r2d2::Pool;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
|
||||
pub struct SettingsService {
|
||||
#[allow(dead_code)]
|
||||
state: GrooveState,
|
||||
pool: Pool<SqliteConnectionManager>,
|
||||
pool: Pool,
|
||||
}
|
||||
|
||||
impl SettingsService {
|
||||
pub fn new(state: GrooveState, pool: Pool<SqliteConnectionManager>) -> Self {
|
||||
pub fn new(state: GrooveState, pool: Pool) -> Self {
|
||||
Self { state, pool }
|
||||
}
|
||||
}
|
||||
@@ -31,11 +30,7 @@ impl Settings for SettingsService {
|
||||
&self,
|
||||
_request: tonic::Request<()>,
|
||||
) -> Result<tonic::Response<SettingsData>, tonic::Status> {
|
||||
let Ok(db) = self.pool.get() else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let Ok(library_paths) = get_library_paths(&db) else {
|
||||
let Ok(library_paths) = get_library_paths(&self.pool).await else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
@@ -58,11 +53,7 @@ impl Settings for SettingsService {
|
||||
) -> Result<tonic::Response<AddPathResponse>, tonic::Status> {
|
||||
let input = request.into_inner();
|
||||
|
||||
let Ok(db) = self.pool.get() else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let Ok(insert_result) = insert_library_path(&db, input.path) else {
|
||||
let Ok(insert_result) = insert_library_path(&self.pool, input.path).await else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
@@ -81,11 +72,7 @@ impl Settings for SettingsService {
|
||||
) -> Result<tonic::Response<DeletePathResponse>, tonic::Status> {
|
||||
let input = request.into_inner();
|
||||
|
||||
let Ok(db) = self.pool.get() else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let Ok(delete_result) = delete_library_path(&db, input.id) else {
|
||||
let Ok(delete_result) = delete_library_path(&self.pool, input.id).await else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
@@ -104,15 +91,11 @@ impl Settings for SettingsService {
|
||||
) -> Result<tonic::Response<RefreshPathResponse>, tonic::Status> {
|
||||
let input = request.into_inner();
|
||||
|
||||
let Ok(db) = self.pool.get() else {
|
||||
return Err(tonic::Status::internal(""));
|
||||
};
|
||||
|
||||
let Ok(library_path) = get_library_path(&db, input.id) else {
|
||||
let Ok(library_path) = get_library_path(&self.pool, input.id).await else {
|
||||
return Err(tonic::Status::not_found(""));
|
||||
};
|
||||
|
||||
let _ = index_path(library_path.path.into(), db, library_path.id);
|
||||
let _ = index_path(library_path.path.into(), &self.pool, library_path.id).await;
|
||||
|
||||
let response = RefreshPathResponse {};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user