feat: fiddling with the bits
:3
This commit is contained in:
parent
4d826ed2a4
commit
7e1f504368
35 changed files with 874 additions and 573 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,3 +1,5 @@
|
|||
/target
|
||||
users.db*
|
||||
.en*
|
||||
|
||||
.rave-dev-db
|
||||
|
|
|
|||
358
Cargo.lock
generated
358
Cargo.lock
generated
|
|
@ -52,17 +52,6 @@ dependencies = [
|
|||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.8.3"
|
||||
|
|
@ -174,12 +163,6 @@ dependencies = [
|
|||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "async-attributes"
|
||||
version = "1.1.2"
|
||||
|
|
@ -388,17 +371,6 @@ version = "1.6.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bigdecimal"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6773ddc0eafc0e509fb60e48dff7f450f8e674a0686ae8605e8d9901bd5eefa"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
|
|
@ -414,18 +386,6 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
|
||||
dependencies = [
|
||||
"funty",
|
||||
"radium",
|
||||
"tap",
|
||||
"wyz",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
|
|
@ -451,51 +411,6 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "borsh"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b"
|
||||
dependencies = [
|
||||
"borsh-derive",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "borsh-derive"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7"
|
||||
dependencies = [
|
||||
"borsh-derive-internal",
|
||||
"borsh-schema-derive-internal",
|
||||
"proc-macro-crate 0.1.5",
|
||||
"proc-macro2",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "borsh-derive-internal"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "borsh-schema-derive-internal"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "3.4.0"
|
||||
|
|
@ -523,28 +438,6 @@ version = "3.14.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec"
|
||||
|
||||
[[package]]
|
||||
name = "bytecheck"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627"
|
||||
dependencies = [
|
||||
"bytecheck_derive",
|
||||
"ptr_meta",
|
||||
"simdutf8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytecheck_derive"
|
||||
version = "0.6.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
|
|
@ -581,7 +474,6 @@ dependencies = [
|
|||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
|
|
@ -746,6 +638,16 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.8"
|
||||
|
|
@ -1009,12 +911,6 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "funty"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.28"
|
||||
|
|
@ -1208,9 +1104,6 @@ name = "hashbrown"
|
|||
version = "0.12.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
dependencies = [
|
||||
"ahash 0.7.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
|
|
@ -1218,7 +1111,7 @@ version = "0.14.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
|
|
@ -1384,6 +1277,18 @@ dependencies = [
|
|||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "id3"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a85caa20f41eef2391fd1dd42b015d0eba17171cdb1a162246a7cc03cd73ab1e"
|
||||
dependencies = [
|
||||
"bitflags 2.4.0",
|
||||
"byteorder",
|
||||
"flate2",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
|
|
@ -1682,17 +1587,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.8.4"
|
||||
|
|
@ -1751,6 +1645,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.32.1"
|
||||
|
|
@ -1969,7 +1872,7 @@ version = "1.3.58"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2550a0bce7273b278894ef3ccc5a6869e7031b6870042f3cc6826ed9faa980a6"
|
||||
dependencies = [
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.38",
|
||||
|
|
@ -2026,7 +1929,7 @@ dependencies = [
|
|||
"http",
|
||||
"indexmap 2.0.2",
|
||||
"mime",
|
||||
"proc-macro-crate 1.3.1",
|
||||
"proc-macro-crate",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
|
|
@ -2078,15 +1981,6 @@ dependencies = [
|
|||
"indexmap 1.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785"
|
||||
dependencies = [
|
||||
"toml",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "1.3.1"
|
||||
|
|
@ -2130,26 +2024,6 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptr_meta"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1"
|
||||
dependencies = [
|
||||
"ptr_meta_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptr_meta_derive"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-xml"
|
||||
version = "0.30.0"
|
||||
|
|
@ -2169,12 +2043,6 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "radium"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
|
|
@ -2213,6 +2081,7 @@ dependencies = [
|
|||
"color-eyre",
|
||||
"entities",
|
||||
"futures-lite",
|
||||
"id3",
|
||||
"md5",
|
||||
"migration",
|
||||
"once_cell",
|
||||
|
|
@ -2225,6 +2094,7 @@ dependencies = [
|
|||
"time",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
"url",
|
||||
"url-escape",
|
||||
|
|
@ -2283,15 +2153,6 @@ version = "0.7.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||
|
||||
[[package]]
|
||||
name = "rend"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2571463863a6bd50c32f94402933f03457a3fbaf697a707c5be741e459f08fd"
|
||||
dependencies = [
|
||||
"bytecheck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rfc7239"
|
||||
version = "0.1.0"
|
||||
|
|
@ -2316,34 +2177,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rkyv"
|
||||
version = "0.7.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58"
|
||||
dependencies = [
|
||||
"bitvec",
|
||||
"bytecheck",
|
||||
"hashbrown 0.12.3",
|
||||
"ptr_meta",
|
||||
"rend",
|
||||
"rkyv_derive",
|
||||
"seahash",
|
||||
"tinyvec",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rkyv_derive"
|
||||
version = "0.7.42"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.9.2"
|
||||
|
|
@ -2366,22 +2199,6 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust_decimal"
|
||||
version = "1.32.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4c4216490d5a413bc6d10fa4742bd7d4955941d062c0ef873141d6b0e7b30fd"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"borsh",
|
||||
"bytes",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"rkyv",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.23"
|
||||
|
|
@ -2497,24 +2314,19 @@ checksum = "da5b2d70c255bc5cbe1d49f69c3c8eadae0fbbaeb18ee978edbf2f75775cb94d"
|
|||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"bigdecimal",
|
||||
"chrono",
|
||||
"futures",
|
||||
"log",
|
||||
"ouroboros",
|
||||
"rust_decimal",
|
||||
"sea-orm-macros",
|
||||
"sea-query",
|
||||
"sea-query-binder",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"strum",
|
||||
"thiserror",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2571,16 +2383,11 @@ version = "0.30.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb3e6bba153bb198646c8762c48414942a38db27d142e44735a133cabddcc820"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"chrono",
|
||||
"derivative",
|
||||
"inherent",
|
||||
"ordered-float",
|
||||
"rust_decimal",
|
||||
"sea-query-derive",
|
||||
"serde_json",
|
||||
"time",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2589,14 +2396,9 @@ version = "0.5.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36bbb68df92e820e4d5aeb17b4acd5cc8b5d18b2c36a4dd6f4626aabfa7ab1b9"
|
||||
dependencies = [
|
||||
"bigdecimal",
|
||||
"chrono",
|
||||
"rust_decimal",
|
||||
"sea-query",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"time",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2635,12 +2437,6 @@ dependencies = [
|
|||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "seahash"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.19"
|
||||
|
|
@ -2753,12 +2549,6 @@ dependencies = [
|
|||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "simdutf8"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
|
@ -2849,12 +2639,10 @@ version = "0.7.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8d6753e460c998bbd4cd8c6f0ed9a64346fcca0723d6e75e52fdc351c5d2169d"
|
||||
dependencies = [
|
||||
"ahash 0.8.3",
|
||||
"ahash",
|
||||
"atoi",
|
||||
"bigdecimal",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"crc",
|
||||
"crossbeam-queue",
|
||||
"dotenvy",
|
||||
|
|
@ -2873,7 +2661,6 @@ dependencies = [
|
|||
"once_cell",
|
||||
"paste",
|
||||
"percent-encoding",
|
||||
"rust_decimal",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
|
|
@ -2887,7 +2674,6 @@ dependencies = [
|
|||
"tokio-stream",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
|
|
@ -2938,11 +2724,9 @@ checksum = "864b869fdf56263f4c95c45483191ea0af340f9f3e3e7b4d57a61c7c87a970db"
|
|||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
"bigdecimal",
|
||||
"bitflags 2.4.0",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"crc",
|
||||
"digest",
|
||||
"dotenvy",
|
||||
|
|
@ -2963,7 +2747,6 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"rand",
|
||||
"rsa",
|
||||
"rust_decimal",
|
||||
"serde",
|
||||
"sha1",
|
||||
"sha2",
|
||||
|
|
@ -2973,7 +2756,6 @@ dependencies = [
|
|||
"thiserror",
|
||||
"time",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
|
|
@ -2985,10 +2767,8 @@ checksum = "eb7ae0e6a97fb3ba33b23ac2671a5ce6e3cabe003f451abd5a56e7951d975624"
|
|||
dependencies = [
|
||||
"atoi",
|
||||
"base64",
|
||||
"bigdecimal",
|
||||
"bitflags 2.4.0",
|
||||
"byteorder",
|
||||
"chrono",
|
||||
"crc",
|
||||
"dotenvy",
|
||||
"etcetera",
|
||||
|
|
@ -3004,10 +2784,8 @@ dependencies = [
|
|||
"log",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"num-bigint",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"rust_decimal",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
|
|
@ -3018,7 +2796,6 @@ dependencies = [
|
|||
"thiserror",
|
||||
"time",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
|
|
@ -3029,7 +2806,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d59dc83cf45d89c555a577694534fcd1b55c545a816c816ce51f20bbe56a4f3f"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"chrono",
|
||||
"flume",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
|
@ -3044,7 +2820,6 @@ dependencies = [
|
|||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3104,12 +2879,6 @@ dependencies = [
|
|||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tap"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.8.0"
|
||||
|
|
@ -3161,6 +2930,8 @@ checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe"
|
|||
dependencies = [
|
||||
"deranged",
|
||||
"itoa",
|
||||
"libc",
|
||||
"num_threads",
|
||||
"serde",
|
||||
"time-core",
|
||||
"time-macros",
|
||||
|
|
@ -3261,15 +3032,6 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "toml_datetime"
|
||||
version = "0.6.3"
|
||||
|
|
@ -3306,6 +3068,17 @@ dependencies = [
|
|||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-appender"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"time",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.26"
|
||||
|
|
@ -3348,6 +3121,16 @@ dependencies = [
|
|||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-serde"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.17"
|
||||
|
|
@ -3359,6 +3142,8 @@ dependencies = [
|
|||
"once_cell",
|
||||
"parking_lot",
|
||||
"regex",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sharded-slab",
|
||||
"smallvec",
|
||||
"thread_local",
|
||||
|
|
@ -3366,6 +3151,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"tracing-core",
|
||||
"tracing-log",
|
||||
"tracing-serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -3480,15 +3266,6 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79daa5ed5740825c40b389c5e50312b9c86df53fccd33f281df655642b43869d"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
|
|
@ -3731,15 +3508,6 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
||||
dependencies = [
|
||||
"tap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "zeroize"
|
||||
version = "1.6.0"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ resolver = "2"
|
|||
[workspace.dependencies]
|
||||
entities = { path = "entities" }
|
||||
migration = { path = "migration" }
|
||||
sea-orm = { version = "0.12", features = [
|
||||
sea-orm = { version = "0.12", default-features = false, features = [
|
||||
"sqlx-postgres",
|
||||
"runtime-tokio-rustls",
|
||||
"with-time",
|
||||
|
|
|
|||
|
|
@ -24,8 +24,6 @@ FROM gcr.io/distroless/static AS runtime
|
|||
|
||||
COPY --from=build /tmp/rave /rave
|
||||
|
||||
VOLUME [ "/storage", "/config" ]
|
||||
|
||||
ENV RUST_LOG=info
|
||||
|
||||
ENTRYPOINT ["/rave"]
|
||||
|
|
|
|||
|
|
@ -11,4 +11,4 @@ services:
|
|||
ports:
|
||||
- 12345:5432
|
||||
volumes:
|
||||
- /tmp/rave-dev-db:/var/lib/postgresql/data
|
||||
- ./.rave-dev-db:/var/lib/postgresql/data
|
||||
|
|
|
|||
|
|
@ -14,11 +14,15 @@ pub struct Model {
|
|||
pub song_count: i32,
|
||||
pub duration: i64,
|
||||
pub play_count: i64,
|
||||
pub created: DateTimeWithTimeZone,
|
||||
pub starred: Option<DateTimeWithTimeZone>,
|
||||
pub created: TimeDateTimeWithTimeZone,
|
||||
pub starred: Option<TimeDateTimeWithTimeZone>,
|
||||
pub year: Option<i32>,
|
||||
pub genre: Option<String>,
|
||||
pub music_folder_id: Option<i64>,
|
||||
pub genre_ids: Option<Vec<i64>>,
|
||||
pub played: TimeDateTimeWithTimeZone,
|
||||
pub user_rating: Option<i16>,
|
||||
pub artist_ids: Option<Vec<i64>>,
|
||||
pub original_release_date: Option<TimeDateTimeWithTimeZone>,
|
||||
pub music_folder_id: i64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
|
|
|||
|
|
@ -8,12 +8,14 @@ use serde::{Deserialize, Serialize};
|
|||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i64,
|
||||
#[sea_orm(unique)]
|
||||
pub name: String,
|
||||
pub cover_art_id: Option<i64>,
|
||||
pub artist_image_url: Option<String>,
|
||||
pub album_count: i32,
|
||||
pub starred: bool,
|
||||
pub music_brainz_id: String,
|
||||
pub sort_name: String,
|
||||
pub roles: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
|
|
|||
20
entities/src/genre.rs
Normal file
20
entities/src/genre.rs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.3
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "genre")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i64,
|
||||
#[sea_orm(unique)]
|
||||
pub name: String,
|
||||
pub song_count: i64,
|
||||
pub album_count: i64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
|
@ -5,6 +5,7 @@ pub mod prelude;
|
|||
pub mod album;
|
||||
pub mod artist;
|
||||
pub mod cover_art;
|
||||
pub mod genre;
|
||||
pub mod music_folder;
|
||||
pub mod track;
|
||||
pub mod user;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
pub use super::album::Entity as Album;
|
||||
pub use super::artist::Entity as Artist;
|
||||
pub use super::cover_art::Entity as CoverArt;
|
||||
pub use super::genre::Entity as Genre;
|
||||
pub use super::music_folder::Entity as MusicFolder;
|
||||
pub use super::track::Entity as Track;
|
||||
pub use super::user::Entity as User;
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ pub struct Model {
|
|||
pub artist_id: Option<i64>,
|
||||
pub is_dir: bool,
|
||||
pub cover_art_id: Option<i64>,
|
||||
pub created: DateTimeWithTimeZone,
|
||||
pub created: TimeDateTimeWithTimeZone,
|
||||
pub duration: i64,
|
||||
pub bit_rate: i64,
|
||||
pub bit_rate: Option<i64>,
|
||||
pub size: i64,
|
||||
pub suffix: String,
|
||||
pub content_type: String,
|
||||
|
|
|
|||
|
|
@ -12,6 +12,8 @@ pub struct Model {
|
|||
pub name: String,
|
||||
pub password: String,
|
||||
pub is_admin: bool,
|
||||
pub can_download: bool,
|
||||
pub scrobbling_enabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
pub use sea_orm_migration::prelude::*;
|
||||
|
||||
mod m20220101_000001_create_user;
|
||||
mod m20231009_181004_create_music_folder;
|
||||
mod m20231009_181104_create_cover_art;
|
||||
mod m20231009_181204_create_artist;
|
||||
mod m20231009_181346_create_album;
|
||||
mod m20231009_185712_create_track;
|
||||
mod m000001_create_user;
|
||||
mod m000002_create_music_folder;
|
||||
mod m000003_create_cover_art;
|
||||
mod m000004_create_artist;
|
||||
mod m000005_create_genre;
|
||||
mod m000006_create_album;
|
||||
mod m000007_create_track;
|
||||
|
||||
pub struct Migrator;
|
||||
|
||||
|
|
@ -13,12 +14,13 @@ pub struct Migrator;
|
|||
impl MigratorTrait for Migrator {
|
||||
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
|
||||
vec![
|
||||
Box::new(m20220101_000001_create_user::Migration),
|
||||
Box::new(m20231009_181004_create_music_folder::Migration),
|
||||
Box::new(m20231009_181104_create_cover_art::Migration),
|
||||
Box::new(m20231009_181204_create_artist::Migration),
|
||||
Box::new(m20231009_181346_create_album::Migration),
|
||||
Box::new(m20231009_185712_create_track::Migration),
|
||||
Box::new(m000001_create_user::Migration),
|
||||
Box::new(m000002_create_music_folder::Migration),
|
||||
Box::new(m000003_create_cover_art::Migration),
|
||||
Box::new(m000004_create_artist::Migration),
|
||||
Box::new(m000005_create_genre::Migration),
|
||||
Box::new(m000006_create_album::Migration),
|
||||
Box::new(m000007_create_track::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,6 +27,18 @@ impl MigrationTrait for Migration {
|
|||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(User::CanDownload)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(User::ScrobblingEnabled)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
|
@ -65,4 +77,6 @@ pub enum User {
|
|||
Name,
|
||||
Password,
|
||||
IsAdmin,
|
||||
CanDownload,
|
||||
ScrobblingEnabled,
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
use crate::m20231009_181104_create_cover_art::CoverArt;
|
||||
use crate::m000003_create_cover_art::CoverArt;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
|
@ -21,12 +21,7 @@ impl MigrationTrait for Migration {
|
|||
.auto_increment()
|
||||
.unique_key(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Artist::Name)
|
||||
.string()
|
||||
.not_null()
|
||||
.unique_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Artist::Name).string().not_null())
|
||||
.col(ColumnDef::new(Artist::CoverArtId).big_integer().null())
|
||||
.col(ColumnDef::new(Artist::ArtistImageUrl).string().null())
|
||||
.col(
|
||||
|
|
@ -41,6 +36,14 @@ impl MigrationTrait for Migration {
|
|||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Artist::MusicBrainzId)
|
||||
.string()
|
||||
.not_null()
|
||||
.default(""),
|
||||
)
|
||||
.col(ColumnDef::new(Artist::SortName).string().not_null())
|
||||
.col(ColumnDef::new(Artist::Roles).string().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
|
@ -76,4 +79,7 @@ pub enum Artist {
|
|||
ArtistImageUrl,
|
||||
AlbumCount,
|
||||
Starred,
|
||||
MusicBrainzId,
|
||||
SortName,
|
||||
Roles,
|
||||
}
|
||||
54
migration/src/m000005_create_genre.rs
Normal file
54
migration/src/m000005_create_genre.rs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Genre::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Genre::Id)
|
||||
.big_integer()
|
||||
.not_null()
|
||||
.primary_key()
|
||||
.auto_increment()
|
||||
.unique_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Genre::Name).string().not_null().unique_key())
|
||||
.col(
|
||||
ColumnDef::new(Genre::SongCount)
|
||||
.big_integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Genre::AlbumCount)
|
||||
.big_integer()
|
||||
.not_null()
|
||||
.default(0),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Genre::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
pub enum Genre {
|
||||
Table,
|
||||
Id,
|
||||
Name,
|
||||
SongCount,
|
||||
AlbumCount,
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
use crate::{
|
||||
m20231009_181004_create_music_folder::MusicFolder, m20231009_181104_create_cover_art::CoverArt,
|
||||
m20231009_181204_create_artist::Artist,
|
||||
m000002_create_music_folder::MusicFolder, m000003_create_cover_art::CoverArt,
|
||||
m000004_create_artist::Artist,
|
||||
};
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
|
|
@ -45,8 +45,32 @@ impl MigrationTrait for Migration {
|
|||
.null(),
|
||||
)
|
||||
.col(ColumnDef::new(Album::Year).integer().null())
|
||||
.col(ColumnDef::new(Album::Genre).string().null())
|
||||
.col(ColumnDef::new(Album::MusicFolderId).big_integer().null())
|
||||
.col(
|
||||
ColumnDef::new(Album::GenreIds)
|
||||
.array(ColumnType::BigInteger)
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Album::Played)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Album::UserRating).tiny_integer().null())
|
||||
.col(
|
||||
ColumnDef::new(Album::ArtistIds)
|
||||
.array(ColumnType::BigInteger)
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Album::OriginalReleaseDate)
|
||||
.timestamp_with_time_zone()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(Album::MusicFolderId)
|
||||
.big_integer()
|
||||
.not_null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
|
@ -110,6 +134,10 @@ pub enum Album {
|
|||
Created,
|
||||
Starred,
|
||||
Year,
|
||||
Genre,
|
||||
GenreIds,
|
||||
Played,
|
||||
UserRating,
|
||||
ArtistIds,
|
||||
OriginalReleaseDate,
|
||||
MusicFolderId,
|
||||
}
|
||||
|
|
@ -1,8 +1,7 @@
|
|||
use sea_orm_migration::prelude::*;
|
||||
|
||||
use crate::{
|
||||
m20231009_181104_create_cover_art::CoverArt, m20231009_181204_create_artist::Artist,
|
||||
m20231009_181346_create_album::Album,
|
||||
m000003_create_cover_art::CoverArt, m000004_create_artist::Artist, m000006_create_album::Album,
|
||||
};
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
|
|
@ -40,7 +39,7 @@ impl MigrationTrait for Migration {
|
|||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(Track::Duration).big_integer().not_null())
|
||||
.col(ColumnDef::new(Track::BitRate).big_integer().not_null())
|
||||
.col(ColumnDef::new(Track::BitRate).big_integer().null())
|
||||
.col(ColumnDef::new(Track::Size).big_integer().not_null())
|
||||
.col(ColumnDef::new(Track::Suffix).string().not_null())
|
||||
.col(ColumnDef::new(Track::ContentType).string().not_null())
|
||||
|
|
@ -22,7 +22,7 @@ poem-ext = "0.9.4"
|
|||
quick-xml = { version = "0.30.0", features = ["serialize"] }
|
||||
serde = { workspace = true }
|
||||
serde_json = "1.0.107"
|
||||
time = { workspace = true }
|
||||
time = { workspace = true, features = ["local-offset"] }
|
||||
tokio = { version = "1.32.0", features = ["full"] }
|
||||
tracing = { workspace = true }
|
||||
tracing-subscriber = { version = "0.3.17", features = [
|
||||
|
|
@ -30,6 +30,7 @@ tracing-subscriber = { version = "0.3.17", features = [
|
|||
"tracing",
|
||||
"parking_lot",
|
||||
"time",
|
||||
"json",
|
||||
] }
|
||||
url = { version = "2.4.1", features = ["serde"] }
|
||||
url-escape = "0.1.1"
|
||||
|
|
@ -38,3 +39,5 @@ entities = { workspace = true }
|
|||
migration = { workspace = true }
|
||||
once_cell = { version = "1.18.0", features = ["parking_lot"] }
|
||||
futures-lite = "1.13.0"
|
||||
id3 = { version = "1.8.0", features = ["tokio"] }
|
||||
tracing-appender = "0.2.2"
|
||||
|
|
|
|||
|
|
@ -15,7 +15,11 @@ use poem::{
|
|||
use poem_ext::db::DbTransactionMiddleware;
|
||||
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
|
||||
use tracing::info;
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
use tracing_appender::non_blocking::WorkerGuard;
|
||||
use tracing_subscriber::{
|
||||
fmt, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer,
|
||||
Registry,
|
||||
};
|
||||
|
||||
mod authentication;
|
||||
mod random_types;
|
||||
|
|
@ -30,7 +34,7 @@ const LISTEN: &str = "0.0.0.0:1234";
|
|||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
color_eyre::install()?;
|
||||
install_tracing()?;
|
||||
let _guards = install_tracing().await?;
|
||||
|
||||
let route = create_route();
|
||||
|
||||
|
|
@ -90,60 +94,30 @@ fn create_server() -> poem::Server<TcpListener<&'static str>, std::convert::Infa
|
|||
poem::Server::new(listener).name("rave")
|
||||
}
|
||||
|
||||
fn install_tracing() -> Result<()> {
|
||||
#[allow(clippy::unused_async)]
|
||||
async fn install_tracing() -> Result<[WorkerGuard; 1]> {
|
||||
let filter = {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(debug_assertions)] {
|
||||
std::env::var("RUST_LOG").unwrap_or_else(|_| "poem=trace,rave=debug".to_string())
|
||||
std::env::var("RUST_LOG").unwrap_or_else(|_| "info,rave=debug".to_string())
|
||||
} else {
|
||||
std::env::var("RUST_LOG").unwrap_or_else(|_| "poem=warn,rave=debug".to_string())
|
||||
std::env::var("RUST_LOG").unwrap_or_else(|_| "warn,rave=debug".to_string())
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let filter = EnvFilter::from(filter);
|
||||
|
||||
fmt()
|
||||
let (non_blocking, guard) = tracing_appender::non_blocking(std::io::stdout());
|
||||
|
||||
Registry::default()
|
||||
.with(
|
||||
fmt::layer()
|
||||
.pretty()
|
||||
.with_env_filter(filter)
|
||||
.try_init()
|
||||
.map_err(|v| color_eyre::eyre::eyre!("failed to install tracing: {v}"))?;
|
||||
.with_writer(non_blocking)
|
||||
.with_filter(filter),
|
||||
)
|
||||
.try_init()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use poem::{
|
||||
http::{Method, StatusCode},
|
||||
Request,
|
||||
};
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_hello_world() {
|
||||
let app = create_route();
|
||||
|
||||
let resp = app
|
||||
.call(Request::builder().method(Method::GET).uri_str("/").finish())
|
||||
.await;
|
||||
assert!(
|
||||
resp.is_ok(),
|
||||
"Failed to get response: {}",
|
||||
resp.expect_err("Failed to get response")
|
||||
);
|
||||
|
||||
let resp = resp.expect("Failed to get response");
|
||||
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
let body = resp.into_body().into_string().await;
|
||||
assert!(
|
||||
body.is_ok(),
|
||||
"Failed to get body: {}",
|
||||
body.expect_err("Failed to get body")
|
||||
);
|
||||
let body = body.expect("Failed to get body");
|
||||
assert_eq!(body, "Hello, world!");
|
||||
}
|
||||
Ok([guard])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,8 @@ pub async fn get_album(
|
|||
}
|
||||
};
|
||||
|
||||
SubsonicResponse::new_album(album, tracks)
|
||||
todo!()
|
||||
// SubsonicResponse::new_album(album, tracks)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize)]
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#![allow(clippy::unused_async)] // todo: remove
|
||||
|
||||
use entities::{
|
||||
album, artist,
|
||||
prelude::{Album, Artist},
|
||||
album, artist, genre,
|
||||
prelude::{Album, Artist, Genre},
|
||||
};
|
||||
use poem::{
|
||||
web::{Data, Query},
|
||||
|
|
@ -62,17 +62,19 @@ pub async fn get_album_list(
|
|||
SortType::ByGenre => get_album_list_by_genre(txn, params).await,
|
||||
};
|
||||
|
||||
match album_list {
|
||||
Ok(a) => {
|
||||
debug!("uri path: {}", req.uri().path());
|
||||
if req.uri().path().contains("getAlbumList2") {
|
||||
SubsonicResponse::new_album_list2(a)
|
||||
} else {
|
||||
SubsonicResponse::new_album_list(a)
|
||||
}
|
||||
}
|
||||
Err(e) => SubsonicResponse::new_error(e),
|
||||
}
|
||||
todo!()
|
||||
|
||||
// match album_list {
|
||||
// Ok(a) => {
|
||||
// debug!("uri path: {}", req.uri().path());
|
||||
// if req.uri().path().contains("getAlbumList2") {
|
||||
// SubsonicResponse::new_album_list2(a)
|
||||
// } else {
|
||||
// SubsonicResponse::new_album_list(a)
|
||||
// }
|
||||
// }
|
||||
// Err(e) => SubsonicResponse::new_error(e),
|
||||
// }
|
||||
}
|
||||
|
||||
#[allow(unused_variables)]
|
||||
|
|
@ -221,14 +223,39 @@ async fn get_album_list_by_genre(
|
|||
)));
|
||||
};
|
||||
|
||||
let genre_id = Genre::find()
|
||||
.filter(genre::Column::Name.eq(genre))
|
||||
.one(&*conn)
|
||||
.await;
|
||||
|
||||
let genre = match genre_id {
|
||||
Ok(Some(g)) => g,
|
||||
Ok(None) | Err(_) => return Err(Error::Generic(Some("Genre not found".to_string()))),
|
||||
};
|
||||
|
||||
let albums = Album::find()
|
||||
.filter(album::Column::Genre.is_not_null())
|
||||
.filter(album::Column::Genre.eq(genre))
|
||||
.filter(album::Column::GenreIds.is_not_null())
|
||||
.limit(params.size)
|
||||
.offset(params.offset)
|
||||
.all(&*conn)
|
||||
.await;
|
||||
|
||||
let albums = albums.map(|c| {
|
||||
c.into_iter()
|
||||
.filter_map(|c| {
|
||||
if c.genre_ids
|
||||
.clone()
|
||||
.expect("we have a genre")
|
||||
.contains(&genre.id)
|
||||
{
|
||||
Some(c)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
error_or!(albums)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,5 +24,5 @@ pub async fn get_music_folders(Data(txn): Data<&DbTxn>, auth: Authentication) ->
|
|||
return SubsonicResponse::new_error(Error::RequestedDataWasNotFound(None));
|
||||
};
|
||||
|
||||
SubsonicResponse::new_music_folders(folders)
|
||||
SubsonicResponse::new_music_folders(folders.into_iter().map(Into::into).collect())
|
||||
}
|
||||
|
|
|
|||
30
rave/src/rest/get_scan_status.rs
Normal file
30
rave/src/rest/get_scan_status.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use poem::web::Data;
|
||||
use poem_ext::db::DbTxn;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{
|
||||
authentication::Authentication,
|
||||
scan,
|
||||
subsonic::{Error, SubsonicResponse},
|
||||
utils::{self},
|
||||
};
|
||||
|
||||
#[poem::handler]
|
||||
pub async fn get_scan_status(Data(txn): Data<&DbTxn>, auth: Authentication) -> SubsonicResponse {
|
||||
let u = utils::verify_user(txn.clone(), auth).await;
|
||||
|
||||
match u {
|
||||
Ok(_) => {}
|
||||
Err(e) => return e,
|
||||
};
|
||||
|
||||
let status = scan::get_scan_status().await;
|
||||
|
||||
match status {
|
||||
Ok(status) => SubsonicResponse::new_scan_status(status.scanning, status.count),
|
||||
Err(e) => {
|
||||
warn!("Error getting scan status: {}", e);
|
||||
SubsonicResponse::new_error(Error::Generic(None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -14,6 +14,8 @@ mod get_album;
|
|||
mod stream;
|
||||
// rest/startScan
|
||||
mod start_scan;
|
||||
// rest/getScanStatus
|
||||
mod get_scan_status;
|
||||
|
||||
pub fn build() -> Box<dyn Endpoint<Output = poem::Response>> {
|
||||
Route::new()
|
||||
|
|
@ -25,5 +27,6 @@ pub fn build() -> Box<dyn Endpoint<Output = poem::Response>> {
|
|||
.at("/getAlbum", get_album::get_album)
|
||||
.at("/stream", stream::stream)
|
||||
.at("/startScan", start_scan::start_scan)
|
||||
.at("/getScanStatus", get_scan_status::get_scan_status)
|
||||
.boxed()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ pub async fn start_scan(Data(txn): Data<&DbTxn>, auth: Authentication) -> Subson
|
|||
}
|
||||
Err(e) => return e,
|
||||
}
|
||||
crate::scan::start_scan();
|
||||
crate::scan::start_scan().await;
|
||||
|
||||
let res = crate::scan::get_scan_status().await;
|
||||
|
||||
|
|
|
|||
375
rave/src/scan.rs
375
rave/src/scan.rs
|
|
@ -1,14 +1,31 @@
|
|||
use color_eyre::{Report, Result};
|
||||
use entities::{
|
||||
album, artist, genre, music_folder,
|
||||
prelude::{Album, Artist, Genre, MusicFolder, Track},
|
||||
track,
|
||||
};
|
||||
use futures_lite::StreamExt;
|
||||
use id3::{Tag, TagLike};
|
||||
use once_cell::sync::Lazy;
|
||||
use sea_orm::{ConnectOptions, Database, DatabaseTransaction, TransactionTrait};
|
||||
use std::{path::PathBuf, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
use tracing::warn;
|
||||
use sea_orm::{
|
||||
ActiveModelBehavior, ColumnTrait, ConnectOptions, Database, DatabaseTransaction, EntityTrait,
|
||||
QueryFilter, Set, TransactionTrait,
|
||||
};
|
||||
use std::{
|
||||
convert::Into,
|
||||
ops::ControlFlow,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use time::OffsetDateTime;
|
||||
use tokio::{fs::File, sync::RwLock};
|
||||
use tracing::{debug, info, warn};
|
||||
|
||||
mod walk;
|
||||
|
||||
pub fn start_scan() {
|
||||
pub async fn start_scan() {
|
||||
STATUS.write().await.scanning = true;
|
||||
|
||||
tokio::spawn(scan());
|
||||
}
|
||||
|
||||
|
|
@ -34,14 +51,11 @@ pub struct ScanStatus {
|
|||
}
|
||||
|
||||
async fn scan() {
|
||||
{
|
||||
let mut stat = STATUS.write().await;
|
||||
|
||||
stat.scanning = true;
|
||||
}
|
||||
|
||||
let url = std::env::var("DATABASE_URL").expect("DATABASE_URL not set");
|
||||
let conn = ConnectOptions::new(url);
|
||||
let mut conn = ConnectOptions::new(url);
|
||||
conn.max_connections(500)
|
||||
.min_connections(1)
|
||||
.sqlx_logging(true);
|
||||
let dbc = get_dbc(conn).await;
|
||||
|
||||
if dbc.is_none() {
|
||||
|
|
@ -50,60 +64,335 @@ async fn scan() {
|
|||
let dbc = dbc.expect("Failed to connect to database");
|
||||
|
||||
let root_dir = get_root_dir();
|
||||
let state = Arc::new(RwLock::new(ScanState::default()));
|
||||
|
||||
if create_root_music_folder(&dbc, &root_dir, state.clone()).await == ControlFlow::Break(()) {
|
||||
return;
|
||||
}
|
||||
|
||||
info!("Scanning {root_dir:?}");
|
||||
|
||||
let mut walk = walk::WalkDir::new(root_dir);
|
||||
|
||||
let mut count = 0;
|
||||
|
||||
while let Some(res) = walk.next().await {
|
||||
let Some(de) = check_dir_entry(res, &mut count).await else {
|
||||
let Some(de) = check_dir_entry(res).await else {
|
||||
continue;
|
||||
};
|
||||
|
||||
let Some(txn) = create_txn(&dbc, &mut count).await else {
|
||||
let Some(txn) = create_txn(&dbc).await else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if let Err(e) = handle_entry(&txn, de).await {
|
||||
warn!("Failed to handle directory entry: {e}");
|
||||
|
||||
{
|
||||
let mut write = STATUS.write().await;
|
||||
write.errors.push(Arc::new(e));
|
||||
}
|
||||
let _ = txn.rollback().await;
|
||||
|
||||
count += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let _ = txn.commit().await;
|
||||
tokio::spawn(do_entry(de, txn, state.clone()));
|
||||
}
|
||||
|
||||
{
|
||||
let mut stat = STATUS.write().await;
|
||||
|
||||
stat.scanning = false;
|
||||
stat.count = count;
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_entry(tx: &DatabaseTransaction, entry: walk::DirEntry) -> Result<()> {
|
||||
const VALID_EXTENSIONS: &[&str] = &["mp3"];
|
||||
|
||||
async fn do_entry(de: walk::DirEntry, txn: DatabaseTransaction, state: Arc<RwLock<ScanState>>) {
|
||||
if let Err(e) = handle_entry(&txn, de, state).await {
|
||||
let _ = txn.rollback().await;
|
||||
warn!("Failed to handle directory entry: {e}");
|
||||
|
||||
{
|
||||
let mut write = STATUS.write().await;
|
||||
write.errors.push(Arc::new(e));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let _ = txn.commit().await;
|
||||
{
|
||||
STATUS.write().await.count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
async fn handle_entry(
|
||||
tx: &DatabaseTransaction,
|
||||
entry: walk::DirEntry,
|
||||
state: Arc<RwLock<ScanState>>,
|
||||
) -> Result<()> {
|
||||
let path = entry.path();
|
||||
let path = path
|
||||
.to_str()
|
||||
.ok_or_else(|| Report::msg("Failed to convert path to string"))?;
|
||||
let file_type = entry.file_type().await?;
|
||||
let meta = entry.metadata().await?;
|
||||
|
||||
if !file_type.is_file() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let file_ext = path.extension();
|
||||
|
||||
let Some(ext) = file_ext else {
|
||||
warn!("Couldn't get file extension for {path:?}");
|
||||
|
||||
{
|
||||
STATUS
|
||||
.write()
|
||||
.await
|
||||
.errors
|
||||
.push(Arc::new(Report::msg(format!(
|
||||
"Couldn't get file extension for {path:?}"
|
||||
))));
|
||||
}
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let ext = ext.to_string_lossy();
|
||||
|
||||
let file_stem = path.file_stem();
|
||||
|
||||
let Some(stem) = file_stem else {
|
||||
warn!("Couldn't get file stem for {path:?}");
|
||||
|
||||
{
|
||||
STATUS
|
||||
.write()
|
||||
.await
|
||||
.errors
|
||||
.push(Arc::new(Report::msg(format!(
|
||||
"Couldn't get file stem for {path:?}"
|
||||
))));
|
||||
}
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let stem = stem.to_string_lossy();
|
||||
|
||||
if !VALID_EXTENSIONS.contains(&ext.as_ref()) {
|
||||
debug!("Skipping file with invalid extension: {path:?}");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let meta = { File::open(&path).await?.metadata().await? };
|
||||
|
||||
let current_album = { state.read().await.album_id };
|
||||
|
||||
let tag = Tag::async_read_from_path(&path).await?;
|
||||
|
||||
let artist = find_artist(tx, &tag).await?;
|
||||
|
||||
let album = find_album(
|
||||
tx,
|
||||
current_album,
|
||||
artist.as_ref().map(|c| c.id),
|
||||
&tag,
|
||||
state.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
let mut am = track::ActiveModel::new();
|
||||
|
||||
am.title = Set(tag.title().unwrap_or(&stem).to_string());
|
||||
am.album_id = Set(Some(album.id));
|
||||
am.artist_id = Set(artist.as_ref().map(|c| c.id));
|
||||
am.content_type = Set("audio/mpeg".to_string());
|
||||
am.suffix = Set("mp3".to_string());
|
||||
am.path = Set(path.to_string_lossy().to_string());
|
||||
am.is_dir = Set(false);
|
||||
am.is_video = Set(false);
|
||||
am.duration = Set(tag.duration().map(Into::into).unwrap_or_default());
|
||||
am.created = Set(OffsetDateTime::now_utc());
|
||||
am.size = Set(meta
|
||||
.len()
|
||||
.try_into()
|
||||
.expect("Failed to convert meta len to i64"));
|
||||
|
||||
let model = Track::insert(am).exec_with_returning(tx).await?;
|
||||
|
||||
debug!("Inserted track {:?}", model.id);
|
||||
|
||||
// TODO: figure out how to scan. steal from Gonic if we have to :3
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn create_txn(
|
||||
async fn find_album(
|
||||
tx: &DatabaseTransaction,
|
||||
current_album: Option<i64>,
|
||||
artist_id: Option<i64>,
|
||||
tag: &Tag,
|
||||
state: Arc<RwLock<ScanState>>,
|
||||
) -> Result<album::Model, Report> {
|
||||
if let Some(current_album) = current_album {
|
||||
let album = Album::find_by_id(current_album).one(tx).await?;
|
||||
|
||||
let Some(album) = album else {
|
||||
warn!("Couldn't find album with id {current_album}");
|
||||
|
||||
return Err(Report::msg(format!(
|
||||
"Couldn't find album with id {current_album}"
|
||||
)));
|
||||
};
|
||||
|
||||
Ok(album)
|
||||
} else {
|
||||
let mut am = album::ActiveModel::new();
|
||||
|
||||
if let Some(tag_album) = tag.album() {
|
||||
am.name = Set(tag_album.to_string());
|
||||
} else {
|
||||
am.name = Set("Unknown Album".to_string());
|
||||
}
|
||||
|
||||
am.music_folder_id = Set(state.read().await.music_folder_id);
|
||||
am.artist_id = Set(artist_id);
|
||||
am.year = Set(tag.year());
|
||||
let genre = tag.genre_parsed();
|
||||
if let Some(genre) = genre {
|
||||
let genre_id = find_or_create_genre(tx, genre.as_ref()).await?;
|
||||
am.genre_ids = Set(Some(vec![genre_id]));
|
||||
}
|
||||
am.song_count = Set(tag
|
||||
.total_tracks()
|
||||
.map(|v| i32::try_from(v).expect("Failed to convert total tracks to i32"))
|
||||
.unwrap_or_default());
|
||||
am.duration = Set(tag.duration().map(Into::into).unwrap_or_default());
|
||||
am.created = Set(OffsetDateTime::now_utc());
|
||||
|
||||
let model = Album::insert(am).exec_with_returning(tx).await;
|
||||
|
||||
let Ok(model) = model else {
|
||||
let err = model.expect_err("somehow not err");
|
||||
warn!("Failed to insert album {err}");
|
||||
|
||||
return Err(Report::new(err));
|
||||
};
|
||||
|
||||
Ok(model)
|
||||
}
|
||||
}
|
||||
|
||||
async fn find_or_create_genre(tx: &DatabaseTransaction, name: &str) -> Result<i64, Report> {
|
||||
let res = Genre::find()
|
||||
.filter(genre::Column::Name.eq(name))
|
||||
.one(tx)
|
||||
.await?;
|
||||
|
||||
if let Some(genre) = res {
|
||||
Ok(genre.id)
|
||||
} else {
|
||||
let am = genre::ActiveModel {
|
||||
name: Set(name.to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let model = Genre::insert(am).exec_with_returning(tx).await;
|
||||
|
||||
let Ok(model) = model else {
|
||||
let err = model.expect_err("somehow not err");
|
||||
warn!("Failed to insert genre {err}");
|
||||
|
||||
return Err(Report::new(err));
|
||||
};
|
||||
|
||||
Ok(model.id)
|
||||
}
|
||||
}
|
||||
|
||||
async fn find_artist(tx: &DatabaseTransaction, tag: &Tag) -> Result<Option<artist::Model>, Report> {
|
||||
let artist_to_search = match (tag.album_artist(), tag.artists()) {
|
||||
(Some(tag_artist), None) => Some(tag_artist.to_string()),
|
||||
(None, Some(tag_artists)) => Some(tag_artists.join(", ")),
|
||||
(Some(tag_artist), Some(tag_artists)) => {
|
||||
let mut artists = tag_artists.clone();
|
||||
artists.push(tag_artist);
|
||||
Some(artists.join(", "))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
match &artist_to_search {
|
||||
Some(artist_to_search) => {
|
||||
let attempt = Artist::find()
|
||||
.filter(artist::Column::Name.contains(artist_to_search))
|
||||
.one(tx)
|
||||
.await?;
|
||||
|
||||
if let Some(attempt) = attempt {
|
||||
Ok(Some(attempt))
|
||||
} else {
|
||||
let am = artist::ActiveModel {
|
||||
name: Set(artist_to_search.clone()),
|
||||
..Default::default()
|
||||
};
|
||||
let model = Artist::insert(am).exec_with_returning(tx).await;
|
||||
|
||||
let Ok(model) = model else {
|
||||
let err = model.expect_err("somehow not err");
|
||||
warn!("Failed to insert artist {err}");
|
||||
|
||||
return Err(Report::new(err));
|
||||
};
|
||||
|
||||
Ok(Some(model))
|
||||
}
|
||||
}
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ScanState {
|
||||
pub music_folder_id: i64,
|
||||
pub album_id: Option<i64>,
|
||||
}
|
||||
|
||||
async fn create_root_music_folder(
|
||||
dbc: &sea_orm::DatabaseConnection,
|
||||
count: &mut u64,
|
||||
) -> Option<DatabaseTransaction> {
|
||||
root_dir: &Path,
|
||||
state: Arc<RwLock<ScanState>>,
|
||||
) -> ControlFlow<()> {
|
||||
let txn = dbc.begin().await;
|
||||
let Ok(txn) = txn else {
|
||||
let err = txn.expect_err("somehow not err");
|
||||
warn!("Failed to start database transaction to add the root music folder {err}");
|
||||
|
||||
{
|
||||
let mut stat = STATUS.write().await;
|
||||
stat.scanning = false;
|
||||
stat.errors.push(Arc::new(Report::new(err)));
|
||||
}
|
||||
return ControlFlow::Break(());
|
||||
};
|
||||
debug!("created transaction");
|
||||
|
||||
let new_music_folder = music_folder::ActiveModel {
|
||||
name: Set(root_dir.to_string_lossy().to_string()),
|
||||
..Default::default()
|
||||
};
|
||||
debug!("created new music folder model");
|
||||
|
||||
let mf = MusicFolder::insert(new_music_folder).exec(&txn).await;
|
||||
|
||||
debug!("inserted new music folder model");
|
||||
|
||||
let Ok(mf) = mf else {
|
||||
let err = mf.expect_err("somehow not err");
|
||||
warn!("Failed to add the root music folder {err}");
|
||||
|
||||
{
|
||||
let mut stat = STATUS.write().await;
|
||||
stat.scanning = false;
|
||||
stat.errors.push(Arc::new(Report::new(err)));
|
||||
}
|
||||
|
||||
let _ = txn.rollback().await;
|
||||
return ControlFlow::Break(());
|
||||
};
|
||||
|
||||
let _ = txn.commit().await;
|
||||
|
||||
state.write().await.music_folder_id = mf.last_insert_id;
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
async fn create_txn(dbc: &sea_orm::DatabaseConnection) -> Option<DatabaseTransaction> {
|
||||
let txn = match dbc.begin().await {
|
||||
Ok(txn) => txn,
|
||||
Err(e) => {
|
||||
|
|
@ -114,7 +403,6 @@ async fn create_txn(
|
|||
write.errors.push(Arc::new(Report::new(e)));
|
||||
}
|
||||
|
||||
*count += 1;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
|
@ -123,7 +411,6 @@ async fn create_txn(
|
|||
|
||||
async fn check_dir_entry(
|
||||
res: std::result::Result<Arc<tokio::fs::DirEntry>, std::io::Error>,
|
||||
count: &mut u64,
|
||||
) -> Option<Arc<tokio::fs::DirEntry>> {
|
||||
let de = match res {
|
||||
Ok(de) => de,
|
||||
|
|
@ -135,7 +422,6 @@ async fn check_dir_entry(
|
|||
write.errors.push(Arc::new(Report::new(e)));
|
||||
}
|
||||
|
||||
*count += 1;
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
|
@ -164,3 +450,8 @@ fn get_root_dir() -> PathBuf {
|
|||
let root_dir = std::env::var("RAVE_STORAGE_DIR").expect("RAVE_STORAGE_DIR not set");
|
||||
PathBuf::from(root_dir)
|
||||
}
|
||||
|
||||
// fn get_cache_dir() -> PathBuf {
|
||||
// let cache_dir = std::env::var("RAVE_CACHE_DIR").expect("RAVE_CACHE_DIR not set");
|
||||
// PathBuf::from(cache_dir)
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -1,139 +1,7 @@
|
|||
use std::fmt::Display;
|
||||
|
||||
use entities::{album, music_folder, track};
|
||||
use poem::{http::StatusCode, IntoResponse, Response};
|
||||
use serde::{ser::SerializeStruct, Serialize};
|
||||
|
||||
use crate::authentication::VersionTriple;
|
||||
|
||||
impl IntoResponse for SubsonicResponse {
|
||||
fn into_response(self) -> poem::Response {
|
||||
let body = quick_xml::se::to_string(&self).expect("Failed to serialize response");
|
||||
Response::builder().status(StatusCode::OK).body(body)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename = "subsonic-response")]
|
||||
pub struct SubsonicResponse {
|
||||
#[serde(rename = "@xmlns")]
|
||||
pub xmlns: String,
|
||||
#[serde(rename = "@status")]
|
||||
pub status: ResponseStatus,
|
||||
#[serde(rename = "@version")]
|
||||
pub version: VersionTriple,
|
||||
#[serde(rename = "$value")]
|
||||
pub value: Box<SubResponseType>,
|
||||
}
|
||||
|
||||
impl SubsonicResponse {
|
||||
pub fn new(inner: SubResponseType) -> Self {
|
||||
Self {
|
||||
xmlns: "http://subsonic.org/restapi".to_string(),
|
||||
status: ResponseStatus::Ok,
|
||||
version: VersionTriple(1, 16, 1),
|
||||
value: Box::new(inner),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_music_folders(music_folders: Vec<music_folder::Model>) -> Self {
|
||||
Self::new(SubResponseType::MusicFolders { music_folders })
|
||||
}
|
||||
|
||||
pub fn new_album_list(albums: Vec<album::Model>) -> Self {
|
||||
Self::new(SubResponseType::AlbumList { albums })
|
||||
}
|
||||
|
||||
pub fn new_album_list2(albums: Vec<album::Model>) -> Self {
|
||||
Self::new(SubResponseType::AlbumList2 { albums })
|
||||
}
|
||||
|
||||
pub fn new_album(album: album::Model, songs: Vec<track::Model>) -> Self {
|
||||
Self::new(SubResponseType::Album { album, songs })
|
||||
}
|
||||
|
||||
pub fn new_empty() -> Self {
|
||||
Self::new(SubResponseType::Empty)
|
||||
}
|
||||
|
||||
pub fn new_error(inner: Error) -> Self {
|
||||
Self {
|
||||
xmlns: "http://subsonic.org/restapi".to_string(),
|
||||
status: ResponseStatus::Failed,
|
||||
version: VersionTriple(1, 16, 1),
|
||||
value: Box::new(SubResponseType::Error(inner)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_scan_status(scanning: bool, count: u64) -> Self {
|
||||
Self {
|
||||
xmlns: "http://subsonic.org/restapi".to_string(),
|
||||
status: ResponseStatus::Ok,
|
||||
version: VersionTriple(1, 16, 1),
|
||||
value: Box::new(SubResponseType::ScanStatus { scanning, count }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub enum SubResponseType {
|
||||
#[serde(rename = "musicFolders")]
|
||||
MusicFolders {
|
||||
#[serde(rename = "musicFolder")]
|
||||
music_folders: Vec<music_folder::Model>,
|
||||
},
|
||||
#[serde(rename = "error")]
|
||||
Error(Error),
|
||||
#[serde(rename = "license")]
|
||||
License {
|
||||
#[serde(rename = "valid")]
|
||||
valid: bool,
|
||||
},
|
||||
#[serde(rename = "albumList")]
|
||||
AlbumList {
|
||||
#[serde(rename = "album")]
|
||||
albums: Vec<album::Model>,
|
||||
},
|
||||
#[serde(rename = "albumList2")]
|
||||
AlbumList2 {
|
||||
#[serde(rename = "album")]
|
||||
albums: Vec<album::Model>,
|
||||
},
|
||||
#[serde(rename = "album")]
|
||||
Album {
|
||||
#[serde(flatten)]
|
||||
album: album::Model,
|
||||
#[serde(flatten)]
|
||||
songs: Vec<track::Model>,
|
||||
},
|
||||
#[serde(rename = "scanStatus")]
|
||||
ScanStatus {
|
||||
#[serde(rename = "scanning")]
|
||||
scanning: bool,
|
||||
#[serde(rename = "count")]
|
||||
count: u64,
|
||||
},
|
||||
Empty,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ResponseStatus {
|
||||
Ok,
|
||||
Failed,
|
||||
}
|
||||
|
||||
impl Serialize for ResponseStatus {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(match self {
|
||||
Self::Ok => "ok",
|
||||
Self::Failed => "failed",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(unused)]
|
||||
pub enum Error {
|
||||
140
rave/src/subsonic/mod.rs
Normal file
140
rave/src/subsonic/mod.rs
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
use poem::{http::StatusCode, IntoResponse, Response};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::authentication::VersionTriple;
|
||||
|
||||
mod error;
|
||||
|
||||
mod types;
|
||||
pub use error::Error;
|
||||
pub use types::album::Album;
|
||||
pub use types::music_folder::MusicFolder;
|
||||
pub use types::track::Track;
|
||||
|
||||
impl IntoResponse for SubsonicResponse {
|
||||
fn into_response(self) -> poem::Response {
|
||||
let body = quick_xml::se::to_string(&self).expect("Failed to serialize response");
|
||||
Response::builder().status(StatusCode::OK).body(body)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
#[serde(rename = "subsonic-response")]
|
||||
pub struct SubsonicResponse {
|
||||
#[serde(rename = "@xmlns")]
|
||||
pub xmlns: String,
|
||||
#[serde(rename = "@status")]
|
||||
pub status: ResponseStatus,
|
||||
#[serde(rename = "@version")]
|
||||
pub version: VersionTriple,
|
||||
#[serde(rename = "$value")]
|
||||
pub value: Box<SubResponseType>,
|
||||
}
|
||||
|
||||
impl SubsonicResponse {
|
||||
pub fn new(inner: SubResponseType) -> Self {
|
||||
Self {
|
||||
xmlns: "http://subsonic.org/restapi".to_string(),
|
||||
status: ResponseStatus::Ok,
|
||||
version: VersionTriple(1, 16, 1),
|
||||
value: Box::new(inner),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_music_folders(music_folders: Vec<MusicFolder>) -> Self {
|
||||
Self::new(SubResponseType::MusicFolders { music_folders })
|
||||
}
|
||||
|
||||
pub fn new_album_list(albums: Vec<Album>) -> Self {
|
||||
Self::new(SubResponseType::AlbumList { albums })
|
||||
}
|
||||
|
||||
pub fn new_album_list2(albums: Vec<Album>) -> Self {
|
||||
Self::new(SubResponseType::AlbumList2 { albums })
|
||||
}
|
||||
|
||||
pub fn new_album(album: Album, songs: Vec<Track>) -> Self {
|
||||
Self::new(SubResponseType::Album { album, songs })
|
||||
}
|
||||
|
||||
pub fn new_empty() -> Self {
|
||||
Self::new(SubResponseType::Empty)
|
||||
}
|
||||
|
||||
pub fn new_error(inner: Error) -> Self {
|
||||
Self {
|
||||
xmlns: "http://subsonic.org/restapi".to_string(),
|
||||
status: ResponseStatus::Failed,
|
||||
version: VersionTriple(1, 16, 1),
|
||||
value: Box::new(SubResponseType::Error(inner)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_scan_status(scanning: bool, count: u64) -> Self {
|
||||
Self {
|
||||
xmlns: "http://subsonic.org/restapi".to_string(),
|
||||
status: ResponseStatus::Ok,
|
||||
version: VersionTriple(1, 16, 1),
|
||||
value: Box::new(SubResponseType::ScanStatus { scanning, count }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub enum SubResponseType {
|
||||
#[serde(rename = "musicFolders")]
|
||||
MusicFolders {
|
||||
#[serde(rename = "musicFolder")]
|
||||
music_folders: Vec<MusicFolder>,
|
||||
},
|
||||
#[serde(rename = "error")]
|
||||
Error(error::Error),
|
||||
#[serde(rename = "license")]
|
||||
License {
|
||||
#[serde(rename = "valid")]
|
||||
valid: bool,
|
||||
},
|
||||
#[serde(rename = "albumList")]
|
||||
AlbumList {
|
||||
#[serde(rename = "album")]
|
||||
albums: Vec<Album>,
|
||||
},
|
||||
#[serde(rename = "albumList2")]
|
||||
AlbumList2 {
|
||||
#[serde(rename = "album")]
|
||||
albums: Vec<Album>,
|
||||
},
|
||||
#[serde(rename = "album")]
|
||||
Album {
|
||||
#[serde(flatten)]
|
||||
album: Album,
|
||||
#[serde(flatten)]
|
||||
songs: Vec<Track>,
|
||||
},
|
||||
#[serde(rename = "scanStatus")]
|
||||
ScanStatus {
|
||||
#[serde(rename = "scanning")]
|
||||
scanning: bool,
|
||||
#[serde(rename = "count")]
|
||||
count: u64,
|
||||
},
|
||||
Empty,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum ResponseStatus {
|
||||
Ok,
|
||||
Failed,
|
||||
}
|
||||
|
||||
impl Serialize for ResponseStatus {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
serializer.serialize_str(match self {
|
||||
Self::Ok => "ok",
|
||||
Self::Failed => "failed",
|
||||
})
|
||||
}
|
||||
}
|
||||
3
rave/src/subsonic/types.rs
Normal file
3
rave/src/subsonic/types.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub mod album;
|
||||
pub mod music_folder;
|
||||
pub mod track;
|
||||
27
rave/src/subsonic/types/album.rs
Normal file
27
rave/src/subsonic/types/album.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
// use entities::*;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Album {
|
||||
id: String,
|
||||
name: String,
|
||||
artist: Option<String>,
|
||||
#[serde(rename = "artistId")]
|
||||
artist_id: Option<String>,
|
||||
}
|
||||
|
||||
// impl Album {
|
||||
// pub fn new(
|
||||
// album: album::Model,
|
||||
// artists: Option<artist::Model>,
|
||||
// cover_art: Option<cover_art::Model>,
|
||||
// genres: Vec<genre::Model>,
|
||||
// ) -> Self {
|
||||
// Self {
|
||||
// id: album.id.to_string(),
|
||||
// name: album.name,
|
||||
// artist: artist.map(|a| a.name),
|
||||
// artist_id: artist.map(|a| format!("ar-{}", a.id)),
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
29
rave/src/subsonic/types/music_folder.rs
Normal file
29
rave/src/subsonic/types/music_folder.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
use entities::music_folder::Model;
|
||||
use serde::{ser::SerializeStruct, Serialize};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MusicFolder {
|
||||
pub(crate) id: i64,
|
||||
pub(crate) name: String,
|
||||
}
|
||||
|
||||
impl From<Model> for MusicFolder {
|
||||
fn from(value: Model) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
name: value.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for MusicFolder {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
let mut s = serializer.serialize_struct("MusicFolder", 2)?;
|
||||
s.serialize_field("id", &format!("mf-{}", self.id))?;
|
||||
s.serialize_field("name", &self.name)?;
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
4
rave/src/subsonic/types/track.rs
Normal file
4
rave/src/subsonic/types/track.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
use serde::Serialize;
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct Track {}
|
||||
Loading…
Reference in a new issue