Compare commits
18 Commits
8443635fda
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2194a8a9c0 | |||
| 2be5849251 | |||
| 8b9bf1afce | |||
| 47639dd34a | |||
| 3a2883507d | |||
| be170b40f0 | |||
| e69a4271cb | |||
| e192bd5e23 | |||
| a652ae330f | |||
| 634c147ee9 | |||
| e60c2d15d2 | |||
| c5149b3390 | |||
| d7b226025e | |||
| 05b2e11ab4 | |||
| e69b45ca24 | |||
| 94b8b15f80 | |||
| 22e6fe31b3 | |||
| c71d3128e2 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
|
perf.data*
|
||||||
|
heaptrack*
|
||||||
|
|||||||
390
Cargo.lock
generated
390
Cargo.lock
generated
@@ -68,9 +68,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyhow"
|
name = "anyhow"
|
||||||
version = "1.0.93"
|
version = "1.0.94"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
|
checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arbitrary"
|
name = "arbitrary"
|
||||||
@@ -89,6 +89,12 @@ dependencies = [
|
|||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "arrayref"
|
||||||
|
version = "0.3.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.7.6"
|
version = "0.7.6"
|
||||||
@@ -189,9 +195,9 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_path_to_error",
|
"serde_path_to_error",
|
||||||
"serde_urlencoded",
|
"serde_urlencoded",
|
||||||
"sync_wrapper 1.0.2",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower 0.5.1",
|
"tower 0.5.2",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -212,7 +218,7 @@ dependencies = [
|
|||||||
"mime",
|
"mime",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"rustversion",
|
"rustversion",
|
||||||
"sync_wrapper 1.0.2",
|
"sync_wrapper",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -282,12 +288,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
|
checksum = "6099cdc01846bc367c4e7dd630dc5966dccf36b652fae7a74e17b640411a91b2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block-buffer"
|
name = "blake3"
|
||||||
version = "0.10.4"
|
version = "1.5.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"generic-array",
|
"arrayref",
|
||||||
|
"arrayvec",
|
||||||
|
"cc",
|
||||||
|
"cfg-if",
|
||||||
|
"constant_time_eq",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -322,15 +332,15 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.8.0"
|
version = "1.9.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
|
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.2.1"
|
version = "1.2.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
|
checksum = "9157bbaa6b165880c27a4293a474c91cdcf265cc68cc829bf10be0964a391caf"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"jobserver",
|
"jobserver",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -401,6 +411,12 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "constant_time_eq"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
@@ -450,15 +466,6 @@ dependencies = [
|
|||||||
"windows",
|
"windows",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "cpufeatures"
|
|
||||||
version = "0.2.16"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3"
|
|
||||||
dependencies = [
|
|
||||||
"libc",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc32fast"
|
name = "crc32fast"
|
||||||
version = "1.4.2"
|
version = "1.4.2"
|
||||||
@@ -470,9 +477,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.5"
|
version = "0.8.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
|
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-epoch",
|
"crossbeam-epoch",
|
||||||
"crossbeam-utils",
|
"crossbeam-utils",
|
||||||
@@ -489,9 +496,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-utils"
|
name = "crossbeam-utils"
|
||||||
version = "0.8.20"
|
version = "0.8.21"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crunchy"
|
name = "crunchy"
|
||||||
@@ -499,16 +506,6 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
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]]
|
[[package]]
|
||||||
name = "dasp_sample"
|
name = "dasp_sample"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
@@ -516,13 +513,43 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "deadpool"
|
||||||
version = "0.10.7"
|
version = "0.12.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
checksum = "6541a3916932fe57768d4be0b1ffb5ec7cbf74ca8c903fdfd5c0fe8aa958f0ed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"block-buffer",
|
"deadpool-runtime",
|
||||||
"crypto-common",
|
"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]]
|
[[package]]
|
||||||
@@ -554,12 +581,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.3.9"
|
version = "0.3.10"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba"
|
checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -597,15 +624,15 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fastrand"
|
name = "fastrand"
|
||||||
version = "2.2.0"
|
version = "2.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "486f806e73c5707928240ddc295403b1b93c96a02038563881c4a2fd84b81ac4"
|
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fdeflate"
|
name = "fdeflate"
|
||||||
version = "0.3.6"
|
version = "0.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb"
|
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"simd-adler32",
|
"simd-adler32",
|
||||||
]
|
]
|
||||||
@@ -680,16 +707,6 @@ dependencies = [
|
|||||||
"pin-utils",
|
"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]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
@@ -728,17 +745,17 @@ name = "groove"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
|
"blake3",
|
||||||
|
"deadpool",
|
||||||
|
"deadpool-sqlite",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
"hex",
|
"hex",
|
||||||
"home",
|
"home",
|
||||||
"image",
|
"image",
|
||||||
"prost",
|
"prost",
|
||||||
"r2d2",
|
|
||||||
"r2d2_sqlite",
|
|
||||||
"rayon",
|
"rayon",
|
||||||
"rodio",
|
"rodio",
|
||||||
"rusqlite",
|
"rusqlite",
|
||||||
"sha2",
|
|
||||||
"symphonia",
|
"symphonia",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
@@ -762,7 +779,7 @@ dependencies = [
|
|||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"http",
|
"http",
|
||||||
"indexmap 2.6.0",
|
"indexmap 2.7.0",
|
||||||
"slab",
|
"slab",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-util",
|
"tokio-util",
|
||||||
@@ -796,9 +813,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.15.1"
|
version = "0.15.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
|
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashlink"
|
name = "hashlink"
|
||||||
@@ -844,9 +861,9 @@ checksum = "62adaabb884c94955b19907d60019f4e145d091c75345379e70d1ee696f7854f"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258"
|
checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@@ -878,9 +895,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http-range-header"
|
name = "http-range-header"
|
||||||
version = "0.4.1"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a"
|
checksum = "9171a2ea8a68358193d15dd5d70c1c10a2afc3e7e4c5bc92bc9f025cebd7359c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
@@ -998,12 +1015,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.6.0"
|
version = "2.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
|
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown 0.15.1",
|
"hashbrown 0.15.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1037,9 +1054,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "1.0.13"
|
version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2"
|
checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "jni"
|
name = "jni"
|
||||||
@@ -1080,10 +1097,11 @@ checksum = "f5d4a7da358eff58addd2877a45865158f0d78c911d43a5784ceb7bbf52833b0"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.72"
|
version = "0.3.76"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
|
checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -1112,9 +1130,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.164"
|
version = "0.2.168"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
|
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libfuzzer-sys"
|
name = "libfuzzer-sys"
|
||||||
@@ -1128,9 +1146,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libloading"
|
name = "libloading"
|
||||||
version = "0.8.5"
|
version = "0.8.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
@@ -1253,11 +1271,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "1.0.2"
|
version = "1.0.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec"
|
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"hermit-abi",
|
|
||||||
"libc",
|
"libc",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
@@ -1370,6 +1387,16 @@ dependencies = [
|
|||||||
"autocfg",
|
"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]]
|
[[package]]
|
||||||
name = "num_enum"
|
name = "num_enum"
|
||||||
version = "0.7.3"
|
version = "0.7.3"
|
||||||
@@ -1480,7 +1507,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"fixedbitset",
|
"fixedbitset",
|
||||||
"indexmap 2.6.0",
|
"indexmap 2.7.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1523,9 +1550,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "png"
|
name = "png"
|
||||||
version = "0.17.14"
|
version = "0.17.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0"
|
checksum = "b67582bd5b65bdff614270e2ea89a1cf15bef71245cc1e5f7ea126977144211d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 1.3.2",
|
"bitflags 1.3.2",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
@@ -1592,9 +1619,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost"
|
name = "prost"
|
||||||
version = "0.13.3"
|
version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b0487d90e047de87f984913713b85c601c05609aad5b0df4b4573fbf69aa13f"
|
checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"prost-derive",
|
"prost-derive",
|
||||||
@@ -1602,11 +1629,10 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-build"
|
name = "prost-build"
|
||||||
version = "0.13.3"
|
version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0c1318b19085f08681016926435853bbf7858f9c082d0999b80550ff5d9abe15"
|
checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
|
||||||
"heck",
|
"heck",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
"log",
|
"log",
|
||||||
@@ -1623,9 +1649,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-derive"
|
name = "prost-derive"
|
||||||
version = "0.13.3"
|
version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e9552f850d5f0964a4e4d0bf306459ac29323ddfbae05e35a7c0d35cb0803cc5"
|
checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"itertools 0.13.0",
|
"itertools 0.13.0",
|
||||||
@@ -1636,9 +1662,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost-types"
|
name = "prost-types"
|
||||||
version = "0.13.3"
|
version = "0.13.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4759aa0d3a6232fb8dbdb97b61de2c20047c68aca932c7ed76da9d788508d670"
|
checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"prost",
|
"prost",
|
||||||
]
|
]
|
||||||
@@ -1667,28 +1693,6 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"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]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.5"
|
version = "0.8.5"
|
||||||
@@ -1791,9 +1795,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.7"
|
version = "0.5.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f"
|
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
]
|
]
|
||||||
@@ -1874,15 +1878,15 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.41"
|
version = "0.38.42"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d7f649912bc1495e167a6edee79151c84b1bad49748cb4f1f1167f459f6224f6"
|
checksum = "f93dc38ecbab2eb790ff964bb77fa94faf256fd3e73285fd7ba0903b76bedb85"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.6.0",
|
"bitflags 2.6.0",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1906,15 +1910,6 @@ dependencies = [
|
|||||||
"winapi-util",
|
"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]]
|
[[package]]
|
||||||
name = "scopeguard"
|
name = "scopeguard"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
@@ -1923,18 +1918,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.215"
|
version = "1.0.216"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
|
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.215"
|
version = "1.0.216"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
|
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1984,17 +1979,6 @@ dependencies = [
|
|||||||
"serde",
|
"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]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
@@ -2042,9 +2026,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
version = "0.5.7"
|
version = "0.5.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
|
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
@@ -2198,21 +2182,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.89"
|
version = "2.0.90"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e"
|
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sync_wrapper"
|
|
||||||
version = "0.1.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sync_wrapper"
|
name = "sync_wrapper"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -2248,7 +2226,7 @@ dependencies = [
|
|||||||
"fastrand",
|
"fastrand",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"rustix",
|
"rustix",
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2299,9 +2277,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.41.1"
|
version = "1.42.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
|
checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -2328,9 +2306,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-stream"
|
name = "tokio-stream"
|
||||||
version = "0.1.16"
|
version = "0.1.17"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1"
|
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@@ -2339,9 +2317,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.12"
|
version = "0.7.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
|
checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
@@ -2377,7 +2355,7 @@ version = "0.22.22"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap 2.6.0",
|
"indexmap 2.7.0",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
@@ -2470,14 +2448,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tower"
|
name = "tower"
|
||||||
version = "0.5.1"
|
version = "0.5.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
|
checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"sync_wrapper 0.1.2",
|
"sync_wrapper",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tower-layer",
|
"tower-layer",
|
||||||
"tower-service",
|
"tower-service",
|
||||||
@@ -2539,9 +2517,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.40"
|
version = "0.1.41"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
@@ -2551,9 +2529,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.27"
|
version = "0.1.28"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
|
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2562,9 +2540,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-core"
|
name = "tracing-core"
|
||||||
version = "0.1.32"
|
version = "0.1.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
@@ -2575,20 +2553,11 @@ version = "0.2.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "typenum"
|
|
||||||
version = "1.17.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicase"
|
name = "unicase"
|
||||||
version = "2.7.0"
|
version = "2.8.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89"
|
checksum = "7e51b68083f157f853b6379db119d1c1be0e6e4dec98101079dec41f6f5cf6df"
|
||||||
dependencies = [
|
|
||||||
"version_check",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
@@ -2596,16 +2565,6 @@ version = "1.0.14"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
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]]
|
[[package]]
|
||||||
name = "v_frame"
|
name = "v_frame"
|
||||||
version = "0.3.8"
|
version = "0.3.8"
|
||||||
@@ -2662,9 +2621,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.95"
|
version = "0.2.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
|
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
@@ -2673,13 +2632,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.95"
|
version = "0.2.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
|
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
"once_cell",
|
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
@@ -2688,21 +2646,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-futures"
|
name = "wasm-bindgen-futures"
|
||||||
version = "0.4.45"
|
version = "0.4.49"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
|
checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
"once_cell",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.95"
|
version = "0.2.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
|
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -2710,9 +2669,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.95"
|
version = "0.2.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
|
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -2723,15 +2682,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.95"
|
version = "0.2.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
|
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "web-sys"
|
name = "web-sys"
|
||||||
version = "0.3.72"
|
version = "0.3.76"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112"
|
checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
@@ -2759,7 +2718,7 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2809,6 +2768,15 @@ dependencies = [
|
|||||||
"windows-targets 0.52.6",
|
"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]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.42.2"
|
version = "0.42.2"
|
||||||
@@ -2977,9 +2945,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "zune-jpeg"
|
name = "zune-jpeg"
|
||||||
version = "0.4.13"
|
version = "0.4.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768"
|
checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"zune-core",
|
"zune-core",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -5,17 +5,17 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
axum = "0.7.9"
|
axum = "0.7.9"
|
||||||
|
blake3 = "1.5.4"
|
||||||
|
deadpool = "0.12.1"
|
||||||
|
deadpool-sqlite = "0.9.0"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
home = "0.5.9"
|
home = "0.5.9"
|
||||||
image = "0.25.5"
|
image = "0.25.5"
|
||||||
prost = "0.13.3"
|
prost = "0.13.3"
|
||||||
r2d2 = "0.8.10"
|
|
||||||
r2d2_sqlite = { version = "0.25.0", features = ["bundled"] }
|
|
||||||
rayon = "1.10.0"
|
rayon = "1.10.0"
|
||||||
rodio = "0.20.1"
|
rodio = "0.20.1"
|
||||||
rusqlite = { version = "0.32.1", features = ["bundled"] }
|
rusqlite = { version = "0.32.1", features = ["bundled"] }
|
||||||
sha2 = "0.10.8"
|
|
||||||
symphonia = { version = "0.5.4", features = ["mp3"] }
|
symphonia = { version = "0.5.4", features = ["mp3"] }
|
||||||
tokio = { version = "1.41.1", features = ["full"] }
|
tokio = { version = "1.41.1", features = ["full"] }
|
||||||
tokio-stream = "0.1.16"
|
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 {
|
service Library {
|
||||||
rpc ListTracks(google.protobuf.Empty) returns (TrackList);
|
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 {
|
message TrackList {
|
||||||
@@ -19,3 +25,41 @@ message Track {
|
|||||||
uint64 artist_id = 4;
|
uint64 artist_id = 4;
|
||||||
uint64 duration = 5;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,16 +6,23 @@ import 'library.proto';
|
|||||||
package player;
|
package player;
|
||||||
|
|
||||||
service Player {
|
service Player {
|
||||||
rpc PlayTrack(PlayTrackRequest) returns (PlayTrackResponse);
|
rpc PlayTrack(TrackRequest) returns (PlayTrackResponse);
|
||||||
rpc ResumeTrack(google.protobuf.Empty) returns (PauseState);
|
rpc ResumeTrack(google.protobuf.Empty) returns (PauseState);
|
||||||
rpc PauseTrack(google.protobuf.Empty) returns (PauseState);
|
rpc PauseTrack(google.protobuf.Empty) returns (PauseState);
|
||||||
rpc TogglePause(google.protobuf.Empty) returns (PauseState);
|
rpc TogglePause(google.protobuf.Empty) returns (PauseState);
|
||||||
rpc GetStatus(google.protobuf.Empty) returns (stream PlayerStatus);
|
rpc GetStatus(google.protobuf.Empty) returns (stream PlayerStatus);
|
||||||
rpc SeekPosition(SeekPositionRequest) returns (SeekPositionResponse);
|
rpc SeekPosition(SeekPositionRequest) returns (SeekPositionResponse);
|
||||||
rpc SetVolume(SetVolumeRequest) returns (SetVolumeResponse);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
message PlayTrackRequest {
|
message TrackRequest {
|
||||||
string hash = 1;
|
string hash = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,6 +36,11 @@ message PlayerStatus {
|
|||||||
bool is_paused = 2;
|
bool is_paused = 2;
|
||||||
float volume = 3;
|
float volume = 3;
|
||||||
uint64 progress = 4;
|
uint64 progress = 4;
|
||||||
|
repeated library.Track queue = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message Queue {
|
||||||
|
repeated library.Track tracks = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PauseState {
|
message PauseState {
|
||||||
@@ -50,3 +62,21 @@ message SetVolumeRequest {
|
|||||||
message SetVolumeResponse {
|
message SetVolumeResponse {
|
||||||
float volume = 1;
|
float volume = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
pub fn generate_hash(content: &[u8]) -> String {
|
||||||
let mut hasher = Sha256::new();
|
let hash = hash(content);
|
||||||
hasher.update(content);
|
|
||||||
let result = hasher.finalize();
|
|
||||||
|
|
||||||
let hex = hex::encode(result);
|
hash.to_string()
|
||||||
|
|
||||||
hex
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use std::{fs, path::Path, time::Instant};
|
|
||||||
|
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
|
use image::EncodableLayout;
|
||||||
|
use std::{fs, path::Path};
|
||||||
use tower_http::services::ServeDir;
|
use tower_http::services::ServeDir;
|
||||||
use webp::Encoder;
|
use webp::Encoder;
|
||||||
|
|
||||||
@@ -40,9 +40,11 @@ pub fn get_all_cover_hashes() -> Vec<String> {
|
|||||||
hashes
|
hashes
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_cover(hash: String, cover: CoverData) -> Result<(), Box<dyn std::error::Error>> {
|
pub fn write_cover(
|
||||||
let now = Instant::now();
|
hash: &str,
|
||||||
|
cover: &CoverData,
|
||||||
|
base_path: &str,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
if cover.mime_type != "image/jpeg"
|
if cover.mime_type != "image/jpeg"
|
||||||
&& cover.mime_type != "image/png"
|
&& cover.mime_type != "image/png"
|
||||||
&& cover.mime_type != "image/webp"
|
&& 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());
|
return Err(format!("Invalid cover MIME type: {}", cover.mime_type).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let base_path = get_cover_base_path();
|
let dynamic_image = image::load_from_memory(&cover.bytes)?;
|
||||||
|
|
||||||
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 resized_image = if dynamic_image.width() > 640 || dynamic_image.height() > 640 {
|
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 {
|
} else {
|
||||||
dynamic_image
|
dynamic_image
|
||||||
};
|
};
|
||||||
|
|
||||||
let webp_encoder = Encoder::from_image(&resized_image)?;
|
let webp = Encoder::from_image(&resized_image)?.encode_lossless();
|
||||||
let encoded_image = webp_encoder.encode_lossless().to_vec();
|
|
||||||
|
|
||||||
fs::write(path, encoded_image)?;
|
let path = Path::new(&base_path).join(format!("{hash}.webp"));
|
||||||
|
|
||||||
let elapsed = now.elapsed();
|
fs::write(path, webp.as_bytes())?;
|
||||||
|
|
||||||
println!("Writing '{}' cover took {:.?}", hash, elapsed);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use r2d2::PooledConnection;
|
use deadpool_sqlite::Pool;
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
|
||||||
use rusqlite::Row;
|
use rusqlite::Row;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -15,9 +14,10 @@ fn map_artist(row: &Row) -> Result<Artist, rusqlite::Error> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_artists(
|
pub async fn get_artists(pool: &Pool) -> Result<Vec<Artist>, rusqlite::Error> {
|
||||||
connection: &PooledConnection<SqliteConnectionManager>,
|
let manager = pool.get().await.unwrap();
|
||||||
) -> Result<Vec<Artist>, rusqlite::Error> {
|
let connection = manager.lock().unwrap();
|
||||||
|
|
||||||
let mut statement = connection.prepare("SELECT id, name FROM artists")?;
|
let mut statement = connection.prepare("SELECT id, name FROM artists")?;
|
||||||
let rows = statement.query_map([], map_artist)?;
|
let rows = statement.query_map([], map_artist)?;
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
|
use deadpool_sqlite::{Config, Pool};
|
||||||
|
|
||||||
pub mod artists;
|
pub mod artists;
|
||||||
pub mod paths;
|
pub mod paths;
|
||||||
pub mod tracks;
|
pub mod tracks;
|
||||||
|
|
||||||
use r2d2::{Pool, PooledConnection};
|
pub fn establish_connection() -> Pool {
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
|
||||||
|
|
||||||
pub fn establish_connection() -> Pool<SqliteConnectionManager> {
|
|
||||||
dotenvy::dotenv().ok();
|
dotenvy::dotenv().ok();
|
||||||
|
|
||||||
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
let database_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
|
||||||
|
|
||||||
let manager = SqliteConnectionManager::file(database_url);
|
let cfg = Config::new(database_url);
|
||||||
let pool = Pool::new(manager).expect("Error creating SQLite pool");
|
let pool = cfg
|
||||||
|
.create_pool(deadpool::Runtime::Tokio1)
|
||||||
|
.expect("Error creating SQLite pool");
|
||||||
|
|
||||||
pool
|
pool
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn initialize_database(
|
pub async fn initialize_database(pool: &Pool) -> Result<(), rusqlite::Error> {
|
||||||
connection: &PooledConnection<SqliteConnectionManager>,
|
let manager = pool.get().await.unwrap();
|
||||||
) -> Result<(), r2d2_sqlite::rusqlite::Error> {
|
let conn = manager.lock().unwrap();
|
||||||
connection.execute(
|
|
||||||
|
conn.execute(
|
||||||
"
|
"
|
||||||
CREATE TABLE IF NOT EXISTS library_paths (
|
CREATE TABLE IF NOT EXISTS library_paths (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
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 (
|
"CREATE TABLE IF NOT EXISTS artists (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||||
name TEXT 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 (
|
CREATE TABLE IF NOT EXISTS tracks (
|
||||||
hash TEXT PRIMARY KEY NOT NULL,
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
use r2d2::PooledConnection;
|
use deadpool_sqlite::Pool;
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
|
||||||
use rusqlite::Row;
|
use rusqlite::Row;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -17,9 +16,10 @@ fn map_library_path(row: &Row) -> Result<LibraryPath, rusqlite::Error> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_library_paths(
|
pub async fn get_library_paths(pool: &Pool) -> Result<Vec<LibraryPath>, rusqlite::Error> {
|
||||||
connection: &PooledConnection<SqliteConnectionManager>,
|
let manager = pool.get().await.unwrap();
|
||||||
) -> Result<Vec<LibraryPath>, rusqlite::Error> {
|
let connection = manager.lock().unwrap();
|
||||||
|
|
||||||
let mut statement = connection.prepare("SELECT id, path FROM library_paths")?;
|
let mut statement = connection.prepare("SELECT id, path FROM library_paths")?;
|
||||||
let rows = statement.query_map([], map_library_path)?;
|
let rows = statement.query_map([], map_library_path)?;
|
||||||
|
|
||||||
@@ -34,10 +34,10 @@ pub fn get_library_paths(
|
|||||||
Ok(paths)
|
Ok(paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_library_path(
|
pub async fn get_library_path(pool: &Pool, id: u64) -> Result<LibraryPath, rusqlite::Error> {
|
||||||
connection: &PooledConnection<SqliteConnectionManager>,
|
let manager = pool.get().await.unwrap();
|
||||||
id: u64,
|
let connection = manager.lock().unwrap();
|
||||||
) -> Result<LibraryPath, rusqlite::Error> {
|
|
||||||
Ok(connection.query_row(
|
Ok(connection.query_row(
|
||||||
"SELECT id, path FROM library_paths WHERE id = ?1",
|
"SELECT id, path FROM library_paths WHERE id = ?1",
|
||||||
[id],
|
[id],
|
||||||
@@ -45,19 +45,22 @@ pub fn get_library_path(
|
|||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_library_path(
|
pub async fn insert_library_path(
|
||||||
connection: &PooledConnection<SqliteConnectionManager>,
|
pool: &Pool,
|
||||||
path: LibraryPathInsertData,
|
path: LibraryPathInsertData,
|
||||||
) -> Result<bool, rusqlite::Error> {
|
) -> 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])?;
|
let result = connection.execute("INSERT INTO library_paths (path) VALUES (?1)", [path])?;
|
||||||
|
|
||||||
Ok(result > 0)
|
Ok(result > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_library_path(
|
pub async fn delete_library_path(pool: &Pool, path_id: u64) -> Result<bool, rusqlite::Error> {
|
||||||
connection: &PooledConnection<SqliteConnectionManager>,
|
let manager = pool.get().await.unwrap();
|
||||||
path_id: u64,
|
let connection = manager.lock().unwrap();
|
||||||
) -> Result<bool, rusqlite::Error> {
|
|
||||||
let result = connection.execute("DELETE FROM library_paths WHERE id = ?1", [path_id])?;
|
let result = connection.execute("DELETE FROM library_paths WHERE id = ?1", [path_id])?;
|
||||||
|
|
||||||
Ok(result > 0)
|
Ok(result > 0)
|
||||||
|
|||||||
@@ -1,17 +1,15 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
thread::JoinHandle,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use r2d2::PooledConnection;
|
use deadpool_sqlite::Pool;
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
|
||||||
use rusqlite::{params, Row};
|
use rusqlite::{params, Row};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
covers::{get_all_cover_hashes, write_cover},
|
covers::{get_all_cover_hashes, get_cover_base_path, write_cover},
|
||||||
music::metadata::TrackMetadata,
|
music::metadata::TrackMetadata,
|
||||||
proto,
|
proto::{self, library::Playlist},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::artists::get_artists;
|
use super::artists::get_artists;
|
||||||
@@ -59,9 +57,20 @@ fn map_track(row: &Row) -> Result<Track, rusqlite::Error> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_tracks(
|
/// Creates a playlist from a database row (tracks are empty and need to be filled afterwards)
|
||||||
connection: &PooledConnection<SqliteConnectionManager>,
|
/// * `row`: The database row
|
||||||
) -> Result<Vec<Track>, rusqlite::Error> {
|
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 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)?;
|
let rows = statement.query_map([], map_track)?;
|
||||||
|
|
||||||
@@ -76,17 +85,34 @@ pub fn get_tracks(
|
|||||||
Ok(tracks)
|
Ok(tracks)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_track(
|
pub async fn get_track(pool: &Pool, hash: &str) -> Result<Track, rusqlite::Error> {
|
||||||
connection: &PooledConnection<SqliteConnectionManager>,
|
let manager = pool.get().await.unwrap();
|
||||||
hash: &str,
|
let connection = &manager.lock().unwrap();
|
||||||
) -> Result<Track, rusqlite::Error> {
|
|
||||||
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)
|
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(
|
pub async fn get_specific_tracks(
|
||||||
connection: &PooledConnection<SqliteConnectionManager>,
|
pool: &Pool,
|
||||||
hash: &str,
|
hashes: &Vec<String>,
|
||||||
) -> Result<PathBuf, rusqlite::Error> {
|
) -> 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(
|
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",
|
"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],
|
[hash],
|
||||||
@@ -99,14 +125,43 @@ pub fn get_track_full_path(
|
|||||||
Ok(path)
|
Ok(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_tracks(
|
pub async fn get_specific_tracks_full_path(
|
||||||
mut connection: PooledConnection<SqliteConnectionManager>,
|
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>,
|
tracks: HashMap<String, TrackMetadata>,
|
||||||
library_path_id: u64,
|
library_path_id: u64,
|
||||||
) -> Result<(), rusqlite::Error> {
|
) -> Result<(), rusqlite::Error> {
|
||||||
let existing_covers = get_all_cover_hashes();
|
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();
|
let mut artist_names_to_id: HashMap<String, u64> = HashMap::new();
|
||||||
|
|
||||||
for artist in artists {
|
for artist in artists {
|
||||||
@@ -125,6 +180,9 @@ pub fn insert_tracks(
|
|||||||
new_artists.push(meta.artist_name.clone());
|
new_artists.push(meta.artist_name.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let manager = pool.get().await.unwrap();
|
||||||
|
let mut connection = manager.lock().unwrap();
|
||||||
|
|
||||||
// BEGIN TRANSACTION
|
// BEGIN TRANSACTION
|
||||||
let tx = connection.transaction()?;
|
let tx = connection.transaction()?;
|
||||||
|
|
||||||
@@ -145,13 +203,36 @@ pub fn insert_tracks(
|
|||||||
// BEGIN TRANSACTION
|
// BEGIN TRANSACTION
|
||||||
let tx = connection.transaction()?;
|
let tx = connection.transaction()?;
|
||||||
|
|
||||||
let mut cover_handles: Vec<JoinHandle<()>> = Vec::new();
|
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut statement =
|
let base_path = get_cover_base_path();
|
||||||
tx.prepare("INSERT OR REPLACE INTO tracks (hash, library_path_id, name, artist_id, path, duration) VALUES (?1, ?2, ?3, ?4, ?5, ?6)")?;
|
|
||||||
|
|
||||||
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![
|
statement.execute(params![
|
||||||
&hash,
|
&hash,
|
||||||
library_path_id,
|
library_path_id,
|
||||||
@@ -160,23 +241,161 @@ pub fn insert_tracks(
|
|||||||
meta.path,
|
meta.path,
|
||||||
meta.total_seconds * 1000
|
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
|
// COMMIT
|
||||||
tx.commit()?;
|
tx.commit()?;
|
||||||
|
|
||||||
for handle in cover_handles {
|
|
||||||
let _ = handle.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
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)
|
||||||
|
}
|
||||||
|
|||||||
210
src/library.rs
210
src/library.rs
@@ -1,47 +1,47 @@
|
|||||||
use rayon::prelude::*;
|
use deadpool_sqlite::Pool;
|
||||||
use std::{collections::HashMap, fs, path::PathBuf, time::Instant};
|
use std::{collections::HashMap, path::PathBuf, sync::Arc, time::Instant};
|
||||||
|
use tokio::{sync::Mutex, task::JoinSet};
|
||||||
|
|
||||||
use r2d2::{Pool, PooledConnection};
|
use tonic::{Request, Response, Status};
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
|
||||||
use walkdir::{DirEntry, WalkDir};
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
checksum::generate_hash,
|
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},
|
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,
|
state::GrooveState,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct LibraryService {
|
pub struct LibraryService {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
state: GrooveState,
|
state: GrooveState,
|
||||||
pool: Pool<SqliteConnectionManager>,
|
pool: Pool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LibraryService {
|
impl LibraryService {
|
||||||
pub fn new(state: GrooveState, pool: Pool<SqliteConnectionManager>) -> Self {
|
pub fn new(state: GrooveState, pool: Pool) -> Self {
|
||||||
Self { state, pool }
|
Self { state, pool }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tonic::async_trait]
|
#[tonic::async_trait]
|
||||||
impl Library for LibraryService {
|
impl Library for LibraryService {
|
||||||
async fn list_tracks(
|
async fn list_tracks(&self, _request: Request<()>) -> Result<Response<TrackList>, Status> {
|
||||||
&self,
|
let Ok(tracks) = get_tracks(&self.pool).await else {
|
||||||
_request: tonic::Request<()>,
|
return Err(Status::internal(""));
|
||||||
) -> 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(""));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = TrackList {
|
let response = TrackList {
|
||||||
tracks: tracks
|
tracks: tracks
|
||||||
.iter()
|
.into_iter()
|
||||||
.map(|t| Track {
|
.map(|t| Track {
|
||||||
hash: t.hash.clone(),
|
hash: t.hash.clone(),
|
||||||
name: t.name.clone(),
|
name: t.name.clone(),
|
||||||
@@ -52,15 +52,139 @@ impl Library for LibraryService {
|
|||||||
.collect::<Vec<Track>>(),
|
.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(
|
pub async fn index_path(path: PathBuf, db: &Pool, path_id: u64) -> Result<(), rusqlite::Error> {
|
||||||
path: PathBuf,
|
|
||||||
db: PooledConnection<SqliteConnectionManager>,
|
|
||||||
path_id: u64,
|
|
||||||
) -> Result<(), rusqlite::Error> {
|
|
||||||
let home = home::home_dir().unwrap();
|
let home = home::home_dir().unwrap();
|
||||||
|
|
||||||
let correct_path = path.to_str().unwrap().replace("~", home.to_str().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)
|
.filter_map(Result::ok)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let hashmaps: Vec<HashMap<String, TrackMetadata>> = entries
|
let tracks: Arc<Mutex<HashMap<String, TrackMetadata>>> = Arc::new(Mutex::new(HashMap::new()));
|
||||||
.par_iter()
|
let mut set = JoinSet::new();
|
||||||
.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();
|
|
||||||
|
|
||||||
|
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 hash = generate_hash(&content);
|
||||||
|
|
||||||
let relative_path =
|
let relative_path =
|
||||||
file_path.to_str().unwrap().to_string()[path_offset..].to_string();
|
file_path.to_str().unwrap().to_string()[path_offset..].to_string();
|
||||||
|
|
||||||
if let Some(metadata) = extract_track_data(content, relative_path) {
|
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();
|
let elapsed = now.elapsed();
|
||||||
|
|
||||||
println!("indexing took {:.2?}", elapsed);
|
println!("indexing took {:.2?}", elapsed);
|
||||||
|
|
||||||
let now = Instant::now();
|
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();
|
let elapsed = now.elapsed();
|
||||||
|
|
||||||
|
|||||||
31
src/main.rs
31
src/main.rs
@@ -8,6 +8,7 @@ use proto::player::player_server::PlayerServer;
|
|||||||
use proto::settings::settings_server::SettingsServer;
|
use proto::settings::settings_server::SettingsServer;
|
||||||
use rodio::{OutputStream, Sink};
|
use rodio::{OutputStream, Sink};
|
||||||
use state::{GrooveState, GrooveStateData};
|
use state::{GrooveState, GrooveStateData};
|
||||||
|
use std::sync::Arc;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
use tonic::transport::Server;
|
use tonic::transport::Server;
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ pub mod player;
|
|||||||
pub mod settings;
|
pub mod settings;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
|
|
||||||
|
use music::player::start_watching;
|
||||||
use settings::SettingsService;
|
use settings::SettingsService;
|
||||||
|
|
||||||
pub mod proto {
|
pub mod proto {
|
||||||
@@ -38,9 +40,9 @@ pub mod proto {
|
|||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let pool = &mut establish_connection();
|
let pool = &mut establish_connection();
|
||||||
|
|
||||||
let connection = pool.get().unwrap();
|
initialize_database(&pool)
|
||||||
|
.await
|
||||||
initialize_database(&connection).expect("Error initializing database");
|
.expect("Error initializing database");
|
||||||
|
|
||||||
let address = "[::1]:39993".parse()?;
|
let address = "[::1]:39993".parse()?;
|
||||||
|
|
||||||
@@ -48,9 +50,26 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
OutputStream::try_default().expect("Error getting audio output stream");
|
OutputStream::try_default().expect("Error getting audio output stream");
|
||||||
let sink = Sink::try_new(&stream_handle).expect("Error getting audio sink");
|
let sink = Sink::try_new(&stream_handle).expect("Error getting audio sink");
|
||||||
|
|
||||||
let player = AudioPlayer::new(sink);
|
let player = Arc::new(Mutex::new(AudioPlayer::new(sink)));
|
||||||
|
let c_player = player.clone();
|
||||||
|
|
||||||
let state = GrooveState::new(Mutex::new(GrooveStateData::new(player)));
|
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);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
player.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let state = GrooveState::new(Mutex::new(GrooveStateData::new(c_player)));
|
||||||
|
|
||||||
let settings = SettingsService::new(state.clone(), pool.clone());
|
let settings = SettingsService::new(state.clone(), pool.clone());
|
||||||
let library = LibraryService::new(state.clone(), pool.clone());
|
let library = LibraryService::new(state.clone(), pool.clone());
|
||||||
@@ -72,6 +91,8 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let _ = cover_server_handle.await;
|
let _ = cover_server_handle.await;
|
||||||
|
let _ = watch_handle.await;
|
||||||
|
let _ = player_rx_handle.await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ pub struct CoverData {
|
|||||||
pub mime_type: String,
|
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> {
|
pub fn extract_track_data(file: Vec<u8>, path: String) -> Option<TrackMetadata> {
|
||||||
let probe = symphonia::default::get_probe();
|
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() {
|
for tag in current_metadata.tags() {
|
||||||
match tag.key.as_str() {
|
match tag.key.as_str() {
|
||||||
"TIT2" => title = Some(tag.value.to_string()),
|
"TIT2" | "TT2" => title = Some(tag.value.to_string()),
|
||||||
"TPE1" => artist = Some(tag.value.to_string()),
|
"TPE1" | "TP1" => artist = Some(tag.value.to_string()),
|
||||||
"TPE2" => album_artist = Some(tag.value.to_string()),
|
"TPE2" | "TP2" => album_artist = Some(tag.value.to_string()),
|
||||||
"TALB" => album = 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;
|
let mut cover: Option<CoverData> = None;
|
||||||
|
|
||||||
for visual in current_metadata.visuals() {
|
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" {
|
if mime_type != "image/png" && mime_type != "image/jpeg" && mime_type != "image/webp" {
|
||||||
continue;
|
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 {
|
cover = Some(CoverData {
|
||||||
bytes: visual.data.to_vec(),
|
bytes: data,
|
||||||
mime_type,
|
mime_type,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
|
pub mod queue;
|
||||||
|
|||||||
@@ -1,40 +1,63 @@
|
|||||||
use std::{
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
fs,
|
fs,
|
||||||
io::{BufReader, Cursor},
|
io::{BufReader, Cursor},
|
||||||
path::Path,
|
path::{Path, PathBuf},
|
||||||
|
sync::Arc,
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use rodio::{Decoder, Sink};
|
use rodio::{Decoder, Sink};
|
||||||
|
use tokio::{
|
||||||
|
sync::{
|
||||||
|
mpsc::{self, Receiver},
|
||||||
|
Mutex,
|
||||||
|
},
|
||||||
|
task::JoinHandle,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::{database::tracks::Track, proto::player::PlayerStatus};
|
use crate::{database::tracks::Track, proto::player::PlayerStatus};
|
||||||
|
|
||||||
|
use super::queue::QueuedTrack;
|
||||||
|
|
||||||
pub struct AudioPlayer {
|
pub struct AudioPlayer {
|
||||||
sink: Sink,
|
sink: Sink,
|
||||||
currently_playing: Option<Track>,
|
currently_playing: Option<Track>,
|
||||||
|
queue: VecDeque<QueuedTrack>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AudioPlayer {
|
impl AudioPlayer {
|
||||||
|
const WATCH_SLEEP_TIME: Duration = Duration::from_millis(20);
|
||||||
pub fn new(sink: Sink) -> Self {
|
pub fn new(sink: Sink) -> Self {
|
||||||
sink.set_volume(0.5);
|
sink.set_volume(0.5);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
sink,
|
sink,
|
||||||
currently_playing: None,
|
currently_playing: None,
|
||||||
|
queue: VecDeque::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn play_track<P>(&mut self, track: Track, path: P) -> Result<(), Box<dyn std::error::Error>>
|
pub fn play_track<P>(
|
||||||
|
&mut self,
|
||||||
|
track: Track,
|
||||||
|
path: P,
|
||||||
|
clear_queue: bool,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>>
|
||||||
where
|
where
|
||||||
P: AsRef<Path>,
|
P: AsRef<Path>,
|
||||||
{
|
{
|
||||||
|
self.currently_playing = Some(track);
|
||||||
|
|
||||||
self.sink.clear();
|
self.sink.clear();
|
||||||
|
if clear_queue {
|
||||||
|
self.queue.clear();
|
||||||
|
}
|
||||||
|
|
||||||
let file = BufReader::new(Cursor::new(fs::read(path)?));
|
let file = BufReader::new(Cursor::new(fs::read(path)?));
|
||||||
|
|
||||||
let source = Decoder::new(file)?;
|
let source = Decoder::new(file)?;
|
||||||
|
|
||||||
self.currently_playing = Some(track);
|
|
||||||
self.sink.append(source);
|
self.sink.append(source);
|
||||||
|
|
||||||
self.sink.play();
|
self.sink.play();
|
||||||
@@ -42,12 +65,100 @@ impl AudioPlayer {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn play_track_next(
|
||||||
|
&mut self,
|
||||||
|
track: Track,
|
||||||
|
path: &Path,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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());
|
||||||
|
|
||||||
|
self.queue.push_front(queued_track);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_to_queue(
|
||||||
|
&mut self,
|
||||||
|
track: Track,
|
||||||
|
path: &Path,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
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());
|
||||||
|
|
||||||
|
self.queue.push_back(queued_track);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn add_tracks_to_queue(
|
||||||
|
&mut self,
|
||||||
|
tracks: Vec<(Track, PathBuf)>,
|
||||||
|
clear_queue: bool,
|
||||||
|
) -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
if clear_queue {
|
||||||
|
self.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
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(());
|
||||||
|
};
|
||||||
|
|
||||||
|
self.play_track(queued_track.track, queued_track.path, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 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) {
|
pub fn resume(&self) {
|
||||||
self.sink.play();
|
self.sink.play();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pause(&self) {
|
pub fn pause(&self) {
|
||||||
self.sink.pause();
|
self.sink.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Toggles the player's pause state and returns the new value
|
/// Toggles the player's pause state and returns the new value
|
||||||
@@ -85,22 +196,63 @@ impl AudioPlayer {
|
|||||||
self.currently_playing.clone()
|
self.currently_playing.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn queue(&self) -> VecDeque<QueuedTrack> {
|
||||||
|
self.queue.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear(&mut self) {
|
||||||
|
self.queue.clear();
|
||||||
|
self.sink.clear();
|
||||||
|
self.currently_playing = None;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_snapshot(&self) -> StatusSnapshot {
|
pub fn get_snapshot(&self) -> StatusSnapshot {
|
||||||
StatusSnapshot {
|
StatusSnapshot {
|
||||||
volume: self.volume(),
|
volume: self.volume(),
|
||||||
position: self.position(),
|
position: self.position(),
|
||||||
is_paused: self.is_paused(),
|
is_paused: self.is_paused(),
|
||||||
currently_playing: self.currently_playing(),
|
currently_playing: self.currently_playing(),
|
||||||
|
queue: self.queue(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
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 struct StatusSnapshot {
|
||||||
pub volume: f32,
|
pub volume: f32,
|
||||||
pub position: u128,
|
pub position: u128,
|
||||||
pub is_paused: bool,
|
pub is_paused: bool,
|
||||||
pub currently_playing: Option<Track>,
|
pub currently_playing: Option<Track>,
|
||||||
|
pub queue: VecDeque<QueuedTrack>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<PlayerStatus> for StatusSnapshot {
|
impl Into<PlayerStatus> for StatusSnapshot {
|
||||||
@@ -110,6 +262,7 @@ impl Into<PlayerStatus> for StatusSnapshot {
|
|||||||
is_paused: self.is_paused,
|
is_paused: self.is_paused,
|
||||||
progress: self.position as u64,
|
progress: self.position as u64,
|
||||||
currently_playing: self.currently_playing.clone().map(|t| t.into()),
|
currently_playing: self.currently_playing.clone().map(|t| t.into()),
|
||||||
|
queue: Vec::from_iter(self.queue.into_iter().map(|t| t.track.into())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/music/queue.rs
Normal file
28
src/music/queue.rs
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
use std::{collections::VecDeque, path::PathBuf};
|
||||||
|
|
||||||
|
use crate::{database::tracks::Track, proto};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct QueuedTrack {
|
||||||
|
pub track: Track,
|
||||||
|
pub path: PathBuf,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueuedTrack {
|
||||||
|
pub fn new(track: Track, path: PathBuf) -> Self {
|
||||||
|
Self { track, path }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<QueuedTrack> for Track {
|
||||||
|
fn from(value: QueuedTrack) -> Track {
|
||||||
|
value.track
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn queue_to_track_vec(queue: VecDeque<QueuedTrack>) -> Vec<proto::library::Track> {
|
||||||
|
queue
|
||||||
|
.into_iter()
|
||||||
|
.map(|queued_track| Track::from(queued_track).into())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
269
src/player.rs
269
src/player.rs
@@ -1,16 +1,21 @@
|
|||||||
use r2d2::Pool;
|
use deadpool_sqlite::Pool;
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
use std::{path::PathBuf, pin::Pin, time::Duration};
|
||||||
use std::{pin::Pin, time::Duration};
|
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio_stream::{wrappers::ReceiverStream, Stream};
|
use tokio_stream::{wrappers::ReceiverStream, Stream};
|
||||||
|
|
||||||
use crate::{
|
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::{
|
proto::{
|
||||||
self,
|
self,
|
||||||
player::{
|
player::{
|
||||||
player_server::Player, PauseState, PlayTrackRequest, PlayTrackResponse, PlayerStatus,
|
player_server::Player, PauseState, PlayPlaylistRequest, PlayTrackResponse,
|
||||||
SeekPositionRequest, SeekPositionResponse, SetVolumeRequest, SetVolumeResponse,
|
PlayerStatus, Queue, SeekPositionRequest, SeekPositionResponse, SetVolumeRequest,
|
||||||
|
SetVolumeResponse, SkipToQueueIndexRequest, SwapQueueIndicesRequest, TrackRequest,
|
||||||
|
TracksRequest,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
state::GrooveState,
|
state::GrooveState,
|
||||||
@@ -18,11 +23,11 @@ use crate::{
|
|||||||
|
|
||||||
pub struct PlayerService {
|
pub struct PlayerService {
|
||||||
state: GrooveState,
|
state: GrooveState,
|
||||||
pool: Pool<SqliteConnectionManager>,
|
pool: Pool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayerService {
|
impl PlayerService {
|
||||||
pub fn new(state: GrooveState, pool: Pool<SqliteConnectionManager>) -> Self {
|
pub fn new(state: GrooveState, pool: Pool) -> Self {
|
||||||
Self { state, pool }
|
Self { state, pool }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,25 +38,23 @@ impl Player for PlayerService {
|
|||||||
|
|
||||||
async fn play_track(
|
async fn play_track(
|
||||||
&self,
|
&self,
|
||||||
request: tonic::Request<PlayTrackRequest>,
|
request: tonic::Request<TrackRequest>,
|
||||||
) -> Result<tonic::Response<PlayTrackResponse>, tonic::Status> {
|
) -> Result<tonic::Response<PlayTrackResponse>, tonic::Status> {
|
||||||
let Ok(db) = self.pool.get() else {
|
|
||||||
return Err(tonic::Status::internal(""));
|
|
||||||
};
|
|
||||||
|
|
||||||
let input = request.get_ref();
|
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(""));
|
return Err(tonic::Status::not_found("Could not get track"));
|
||||||
};
|
};
|
||||||
|
|
||||||
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(""));
|
return Err(tonic::Status::not_found("Could not get track file path"));
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut state = self.state.lock().await;
|
let state = self.state.lock().await;
|
||||||
|
|
||||||
let _ = state.player.play_track(track.clone(), track_path);
|
let player = &mut state.player.lock().await;
|
||||||
|
|
||||||
|
let _ = player.play_track(track.clone(), track_path, true);
|
||||||
|
|
||||||
let response = PlayTrackResponse {
|
let response = PlayTrackResponse {
|
||||||
track: Some(proto::library::Track::from(track.into())),
|
track: Some(proto::library::Track::from(track.into())),
|
||||||
@@ -63,11 +66,11 @@ impl Player for PlayerService {
|
|||||||
|
|
||||||
async fn resume_track(
|
async fn resume_track(
|
||||||
&self,
|
&self,
|
||||||
_request: tonic::Request<()>,
|
_: tonic::Request<()>,
|
||||||
) -> Result<tonic::Response<PauseState>, tonic::Status> {
|
) -> Result<tonic::Response<PauseState>, tonic::Status> {
|
||||||
let state = self.state.lock().await;
|
let state = self.state.lock().await;
|
||||||
|
|
||||||
state.player.resume();
|
state.player.lock().await.resume();
|
||||||
|
|
||||||
let response = PauseState { is_paused: false };
|
let response = PauseState { is_paused: false };
|
||||||
|
|
||||||
@@ -76,11 +79,11 @@ impl Player for PlayerService {
|
|||||||
|
|
||||||
async fn pause_track(
|
async fn pause_track(
|
||||||
&self,
|
&self,
|
||||||
_request: tonic::Request<()>,
|
_: tonic::Request<()>,
|
||||||
) -> Result<tonic::Response<PauseState>, tonic::Status> {
|
) -> Result<tonic::Response<PauseState>, tonic::Status> {
|
||||||
let state = self.state.lock().await;
|
let state = self.state.lock().await;
|
||||||
|
|
||||||
state.player.pause();
|
state.player.lock().await.pause();
|
||||||
|
|
||||||
let response = PauseState { is_paused: true };
|
let response = PauseState { is_paused: true };
|
||||||
|
|
||||||
@@ -89,11 +92,11 @@ impl Player for PlayerService {
|
|||||||
|
|
||||||
async fn toggle_pause(
|
async fn toggle_pause(
|
||||||
&self,
|
&self,
|
||||||
_request: tonic::Request<()>,
|
_: tonic::Request<()>,
|
||||||
) -> Result<tonic::Response<PauseState>, tonic::Status> {
|
) -> Result<tonic::Response<PauseState>, tonic::Status> {
|
||||||
let state = self.state.lock().await;
|
let state = self.state.lock().await;
|
||||||
|
|
||||||
let is_paused = state.player.toggle_pause();
|
let is_paused = state.player.lock().await.toggle_pause();
|
||||||
|
|
||||||
let response = PauseState { is_paused };
|
let response = PauseState { is_paused };
|
||||||
|
|
||||||
@@ -107,7 +110,7 @@ impl Player for PlayerService {
|
|||||||
let input = request.get_ref();
|
let input = request.get_ref();
|
||||||
let state = self.state.lock().await;
|
let state = self.state.lock().await;
|
||||||
|
|
||||||
state.player.set_position(input.position);
|
state.player.lock().await.set_position(input.position);
|
||||||
|
|
||||||
let response = SeekPositionResponse {
|
let response = SeekPositionResponse {
|
||||||
position: input.position,
|
position: input.position,
|
||||||
@@ -123,7 +126,7 @@ impl Player for PlayerService {
|
|||||||
let input = request.get_ref();
|
let input = request.get_ref();
|
||||||
let state = self.state.lock().await;
|
let state = self.state.lock().await;
|
||||||
|
|
||||||
state.player.set_volume(input.volume);
|
state.player.lock().await.set_volume(input.volume);
|
||||||
|
|
||||||
let response = SetVolumeResponse {
|
let response = SetVolumeResponse {
|
||||||
volume: input.volume,
|
volume: input.volume,
|
||||||
@@ -134,7 +137,7 @@ impl Player for PlayerService {
|
|||||||
|
|
||||||
async fn get_status(
|
async fn get_status(
|
||||||
&self,
|
&self,
|
||||||
_request: tonic::Request<()>,
|
_: tonic::Request<()>,
|
||||||
) -> Result<tonic::Response<Self::GetStatusStream>, tonic::Status> {
|
) -> Result<tonic::Response<Self::GetStatusStream>, tonic::Status> {
|
||||||
let state = self.state.clone();
|
let state = self.state.clone();
|
||||||
|
|
||||||
@@ -145,7 +148,14 @@ impl Player for PlayerService {
|
|||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
while !tx.is_closed() {
|
while !tx.is_closed() {
|
||||||
if let Err(_) = tx
|
if let Err(_) = tx
|
||||||
.send(Ok(state.lock().await.player.get_snapshot().into()))
|
.send(Ok(state
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.player
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.get_snapshot()
|
||||||
|
.into()))
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -161,4 +171,205 @@ impl Player for PlayerService {
|
|||||||
|
|
||||||
Ok(tonic::Response::new(response))
|
Ok(tonic::Response::new(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn add_track_to_queue(
|
||||||
|
&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.add_to_queue(track, &track_path).await {
|
||||||
|
return Err(tonic::Status::internal(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
let queue = player.queue();
|
||||||
|
|
||||||
|
let response = Queue {
|
||||||
|
tracks: queue_to_track_vec(queue),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(tonic::Response::new(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_tracks_to_queue(
|
||||||
|
&self,
|
||||||
|
request: tonic::Request<TracksRequest>,
|
||||||
|
) -> Result<tonic::Response<Queue>, tonic::Status> {
|
||||||
|
let input = request.get_ref();
|
||||||
|
|
||||||
|
let Ok(tracks) = get_specific_tracks(&self.pool, &input.tracks).await else {
|
||||||
|
return Err(tonic::Status::not_found(""));
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(tonic::Response::new(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn skip_track(
|
||||||
|
&self,
|
||||||
|
_: tonic::Request<()>,
|
||||||
|
) -> Result<tonic::Response<PlayerStatus>, tonic::Status> {
|
||||||
|
let state = self.state.lock().await;
|
||||||
|
|
||||||
|
let mut player = state.player.lock().await;
|
||||||
|
|
||||||
|
if let Err(_) = player.skip_track() {
|
||||||
|
return Err(tonic::Status::internal(""));
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = player.get_snapshot().into();
|
||||||
|
|
||||||
|
Ok(tonic::Response::new(response))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn skip_to_queue_index(
|
||||||
|
&self,
|
||||||
|
request: tonic::Request<SkipToQueueIndexRequest>,
|
||||||
|
) -> Result<tonic::Response<PlayerStatus>, tonic::Status> {
|
||||||
|
let input = request.get_ref();
|
||||||
|
|
||||||
|
let Ok(index) = input.index.try_into() else {
|
||||||
|
return Err(tonic::Status::internal(""));
|
||||||
|
};
|
||||||
|
|
||||||
|
let state = self.state.lock().await;
|
||||||
|
let mut player = state.player.lock().await;
|
||||||
|
|
||||||
|
if let Err(_) = player.skip_to_queue_index(index) {
|
||||||
|
return Err(tonic::Status::internal(""));
|
||||||
|
};
|
||||||
|
|
||||||
|
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 crate::state::GrooveState;
|
||||||
|
|
||||||
|
use deadpool_sqlite::Pool;
|
||||||
use proto::settings::{settings_server::Settings, LibraryPath, SettingsData};
|
use proto::settings::{settings_server::Settings, LibraryPath, SettingsData};
|
||||||
use r2d2::Pool;
|
|
||||||
use r2d2_sqlite::SqliteConnectionManager;
|
|
||||||
|
|
||||||
pub struct SettingsService {
|
pub struct SettingsService {
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
state: GrooveState,
|
state: GrooveState,
|
||||||
pool: Pool<SqliteConnectionManager>,
|
pool: Pool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SettingsService {
|
impl SettingsService {
|
||||||
pub fn new(state: GrooveState, pool: Pool<SqliteConnectionManager>) -> Self {
|
pub fn new(state: GrooveState, pool: Pool) -> Self {
|
||||||
Self { state, pool }
|
Self { state, pool }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,11 +30,7 @@ impl Settings for SettingsService {
|
|||||||
&self,
|
&self,
|
||||||
_request: tonic::Request<()>,
|
_request: tonic::Request<()>,
|
||||||
) -> Result<tonic::Response<SettingsData>, tonic::Status> {
|
) -> Result<tonic::Response<SettingsData>, tonic::Status> {
|
||||||
let Ok(db) = self.pool.get() else {
|
let Ok(library_paths) = get_library_paths(&self.pool).await else {
|
||||||
return Err(tonic::Status::internal(""));
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(library_paths) = get_library_paths(&db) else {
|
|
||||||
return Err(tonic::Status::internal(""));
|
return Err(tonic::Status::internal(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -58,11 +53,7 @@ impl Settings for SettingsService {
|
|||||||
) -> Result<tonic::Response<AddPathResponse>, tonic::Status> {
|
) -> Result<tonic::Response<AddPathResponse>, tonic::Status> {
|
||||||
let input = request.into_inner();
|
let input = request.into_inner();
|
||||||
|
|
||||||
let Ok(db) = self.pool.get() else {
|
let Ok(insert_result) = insert_library_path(&self.pool, input.path).await else {
|
||||||
return Err(tonic::Status::internal(""));
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(insert_result) = insert_library_path(&db, input.path) else {
|
|
||||||
return Err(tonic::Status::internal(""));
|
return Err(tonic::Status::internal(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -81,11 +72,7 @@ impl Settings for SettingsService {
|
|||||||
) -> Result<tonic::Response<DeletePathResponse>, tonic::Status> {
|
) -> Result<tonic::Response<DeletePathResponse>, tonic::Status> {
|
||||||
let input = request.into_inner();
|
let input = request.into_inner();
|
||||||
|
|
||||||
let Ok(db) = self.pool.get() else {
|
let Ok(delete_result) = delete_library_path(&self.pool, input.id).await else {
|
||||||
return Err(tonic::Status::internal(""));
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(delete_result) = delete_library_path(&db, input.id) else {
|
|
||||||
return Err(tonic::Status::internal(""));
|
return Err(tonic::Status::internal(""));
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -104,15 +91,11 @@ impl Settings for SettingsService {
|
|||||||
) -> Result<tonic::Response<RefreshPathResponse>, tonic::Status> {
|
) -> Result<tonic::Response<RefreshPathResponse>, tonic::Status> {
|
||||||
let input = request.into_inner();
|
let input = request.into_inner();
|
||||||
|
|
||||||
let Ok(db) = self.pool.get() else {
|
let Ok(library_path) = get_library_path(&self.pool, input.id).await else {
|
||||||
return Err(tonic::Status::internal(""));
|
|
||||||
};
|
|
||||||
|
|
||||||
let Ok(library_path) = get_library_path(&db, input.id) else {
|
|
||||||
return Err(tonic::Status::not_found(""));
|
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 {};
|
let response = RefreshPathResponse {};
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ use crate::music::player::AudioPlayer;
|
|||||||
pub type GrooveState = Arc<Mutex<GrooveStateData>>;
|
pub type GrooveState = Arc<Mutex<GrooveStateData>>;
|
||||||
|
|
||||||
pub struct GrooveStateData {
|
pub struct GrooveStateData {
|
||||||
pub player: AudioPlayer,
|
pub player: Arc<Mutex<AudioPlayer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GrooveStateData {
|
impl GrooveStateData {
|
||||||
pub fn new(player: AudioPlayer) -> Self {
|
pub fn new(player: Arc<Mutex<AudioPlayer>>) -> Self {
|
||||||
Self { player }
|
Self { player }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user