Compare commits
No commits in common. "24e706cb1914435c1d7af363cf5d5e908f642889" and "4ff2d82bd303bba8f85552028ed6a5e53fd72ea4" have entirely different histories.
24e706cb19
...
4ff2d82bd3
26 changed files with 342 additions and 725 deletions
1
.env.dev
1
.env.dev
|
@ -1,2 +1 @@
|
||||||
DATABASE_URL="postgres://postgres:1234@localhost/tiraya"
|
DATABASE_URL="postgres://postgres:1234@localhost/tiraya"
|
||||||
RUST_LOG="tiraya_extractor=info"
|
|
||||||
|
|
200
Cargo.lock
generated
200
Cargo.lock
generated
|
@ -31,9 +31,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.1"
|
version = "1.0.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ea5d730647d4fadd988536d06fecce94b7b4f2a7efdae548f1cf4b63205518ab"
|
checksum = "0c378d78423fdad8089616f827526ee33c19f2fddbd5de1629152c9593ba4783"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -374,7 +374,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"strsim",
|
"strsim",
|
||||||
"syn 2.0.37",
|
"syn 2.0.36",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -396,7 +396,7 @@ checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"darling_core 0.20.3",
|
"darling_core 0.20.3",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.36",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -659,7 +659,7 @@ checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.36",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -719,12 +719,6 @@ version = "0.28.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
|
checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "glob"
|
|
||||||
version = "0.3.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.3.21"
|
version = "0.3.21"
|
||||||
|
@ -780,9 +774,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.3"
|
version = "0.3.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hex"
|
name = "hex"
|
||||||
|
@ -938,9 +932,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "insta"
|
name = "insta"
|
||||||
version = "1.32.0"
|
version = "1.31.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3e02c584f4595792d09509a94cdb92a3cef7592b1eb2d9877ee6f527062d0ea"
|
checksum = "a0770b0a3d4c70567f0d58331f3088b0e4c4f56c9b8d764efe654b4a5d46de3a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console",
|
"console",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
|
@ -1148,16 +1142,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "nu-ansi-term"
|
|
||||||
version = "0.46.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
|
||||||
dependencies = [
|
|
||||||
"overload",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-bigint-dig"
|
name = "num-bigint-dig"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
|
@ -1254,7 +1238,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.36",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1283,12 +1267,6 @@ dependencies = [
|
||||||
"range-ext",
|
"range-ext",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "overload"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
|
@ -1370,7 +1348,7 @@ dependencies = [
|
||||||
"pest_meta",
|
"pest_meta",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.36",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1419,7 +1397,7 @@ checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.36",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1488,9 +1466,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quick-xml"
|
name = "quick-xml"
|
||||||
version = "0.30.0"
|
version = "0.29.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eff6510e86862b57b210fd8cbe8ed3f0d7d600b9c2863cd4549a2e033c66e956"
|
checksum = "81b9228215d82c7b61490fec1de287136b5de6f5700f6e58ea9ad61a7964ca51"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -1591,12 +1569,6 @@ version = "0.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "relative-path"
|
|
||||||
version = "1.9.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reqwest"
|
name = "reqwest"
|
||||||
version = "0.11.20"
|
version = "0.11.20"
|
||||||
|
@ -1694,53 +1666,17 @@ dependencies = [
|
||||||
"zeroize",
|
"zeroize",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rstest"
|
|
||||||
version = "0.18.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199"
|
|
||||||
dependencies = [
|
|
||||||
"rstest_macros",
|
|
||||||
"rustc_version",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rstest_macros"
|
|
||||||
version = "0.18.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"glob",
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"regex",
|
|
||||||
"relative-path",
|
|
||||||
"rustc_version",
|
|
||||||
"syn 2.0.37",
|
|
||||||
"unicode-ident",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-demangle"
|
name = "rustc-demangle"
|
||||||
version = "0.1.23"
|
version = "0.1.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "rustc_version"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
|
||||||
dependencies = [
|
|
||||||
"semver",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.14"
|
version = "0.38.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "747c788e9ce8e92b12cd485c49ddf90723550b654b32508f979b71a7b1ecda4f"
|
checksum = "d7db8590df6dfcd144d22afd1b83b36c21a18d7cbc1dc4bb5295a8712e9eb662"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.0",
|
"bitflags 2.4.0",
|
||||||
"errno",
|
"errno",
|
||||||
|
@ -1771,9 +1707,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.101.6"
|
version = "0.101.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe"
|
checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
|
@ -1782,11 +1718,12 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustypipe"
|
name = "rustypipe"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://code.thetadev.de/ThetaDev/rustypipe.git#127596687b0f5a29be65adfe80c40059edc4cc50"
|
source = "git+https://code.thetadev.de/ThetaDev/rustypipe.git#abd3317a10535203adab0bda236c4ace66435b63"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.4",
|
"base64 0.21.4",
|
||||||
"fancy-regex",
|
"fancy-regex",
|
||||||
"futures",
|
"futures",
|
||||||
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"phf",
|
"phf",
|
||||||
"quick-js-dtp",
|
"quick-js-dtp",
|
||||||
|
@ -1802,7 +1739,6 @@ dependencies = [
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"time",
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
|
||||||
"url",
|
"url",
|
||||||
"urlencoding",
|
"urlencoding",
|
||||||
]
|
]
|
||||||
|
@ -1870,12 +1806,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "semver"
|
|
||||||
version = "1.0.18"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.188"
|
version = "1.0.188"
|
||||||
|
@ -1893,7 +1823,7 @@ checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.36",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1952,14 +1882,14 @@ dependencies = [
|
||||||
"darling 0.20.3",
|
"darling 0.20.3",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.36",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sha1"
|
name = "sha1"
|
||||||
version = "0.10.6"
|
version = "0.10.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
|
checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cpufeatures",
|
"cpufeatures",
|
||||||
|
@ -1977,15 +1907,6 @@ dependencies = [
|
||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sharded-slab"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signature"
|
name = "signature"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
|
@ -2025,9 +1946,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.11.1"
|
version = "1.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a"
|
checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
|
@ -2350,9 +2271,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.37"
|
version = "2.0.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
|
checksum = "91e02e55d62894af2a08aca894c6577281f76769ba47c94d5756bec8ac6e7373"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -2397,12 +2318,12 @@ name = "testbed"
|
||||||
version = "0.0.1"
|
version = "0.0.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
"env_logger",
|
||||||
"rustypipe",
|
"rustypipe",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tiraya-db",
|
"tiraya-db",
|
||||||
"tiraya-extractor",
|
"tiraya-extractor",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing-subscriber",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2422,17 +2343,7 @@ checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.36",
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "thread_local"
|
|
||||||
version = "1.1.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"once_cell",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2508,10 +2419,10 @@ dependencies = [
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"futures",
|
"futures",
|
||||||
"hex-literal",
|
"hex-literal",
|
||||||
|
"log",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"quick_cache",
|
"quick_cache",
|
||||||
"regex",
|
"regex",
|
||||||
"rstest",
|
|
||||||
"rustypipe",
|
"rustypipe",
|
||||||
"siphasher 1.0.0",
|
"siphasher 1.0.0",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
|
@ -2521,7 +2432,6 @@ dependencies = [
|
||||||
"time",
|
"time",
|
||||||
"tiraya-db",
|
"tiraya-db",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2549,7 +2459,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.36",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2575,9 +2485,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.7.9"
|
version = "0.7.8"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1d68074620f57a0b21594d9735eb2e98ab38b17f80d3fcb189fca266771ca60d"
|
checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
@ -2614,7 +2524,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.36",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2624,32 +2534,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
|
checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"valuable",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tracing-log"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
|
|
||||||
dependencies = [
|
|
||||||
"lazy_static",
|
|
||||||
"log",
|
|
||||||
"tracing-core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tracing-subscriber"
|
|
||||||
version = "0.3.17"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77"
|
|
||||||
dependencies = [
|
|
||||||
"nu-ansi-term",
|
|
||||||
"sharded-slab",
|
|
||||||
"smallvec",
|
|
||||||
"thread_local",
|
|
||||||
"tracing-core",
|
|
||||||
"tracing-log",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2742,12 +2626,6 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "valuable"
|
|
||||||
version = "0.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
@ -2806,7 +2684,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.36",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2840,7 +2718,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.37",
|
"syn 2.0.36",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
@ -2894,9 +2772,9 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-util"
|
name = "winapi-util"
|
||||||
version = "0.1.6"
|
version = "0.1.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
|
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
|
@ -17,8 +17,6 @@ thiserror = "1.0.36"
|
||||||
anyhow = "1.0.71"
|
anyhow = "1.0.71"
|
||||||
dotenvy = "0.15.7"
|
dotenvy = "0.15.7"
|
||||||
log = "0.4.17"
|
log = "0.4.17"
|
||||||
tracing = "0.1.37"
|
|
||||||
tracing-subscriber = "0.3.17"
|
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
path_macro = "1.0.0"
|
path_macro = "1.0.0"
|
||||||
hex-literal = "0.4.1"
|
hex-literal = "0.4.1"
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "select t.id, t.src_id, t.service as \"service: _\", t.name, t.duration,\n t.size, t.loudness, t.album_id, t.album_pos, t.ul_artists, t.isrc, t.description, t.created_at, t.updated_at,\n t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,\n jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)\n filter (where a.src_id is not null) as \"artists: _\"\nfrom tracks t\n left join artists_tracks art on art.track_id = t.id\n left join artists a on a.id = art.artist_id\n join track_aliases ta on ta.track_id=t.id\nwhere ta.src_id=$1 and ta.service=$2\ngroup by t.id",
|
"query": "select t.id, t.src_id, t.service as \"service: _\", t.name, t.duration, t.duration_ms,\n t.size, t.loudness, t.album_id, t.album_pos, t.ul_artists, t.isrc, t.description, t.created_at, t.updated_at,\n t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,\n jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)\n filter (where a.src_id is not null) as \"artists: _\"\nfrom tracks t\n left join artists_tracks art on art.track_id = t.id\n left join artists a on a.id = art.artist_id\nwhere t.src_id=$1 and t.service=$2\ngroup by t.id",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
|
@ -42,71 +42,76 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
|
"name": "duration_ms",
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 6,
|
||||||
"name": "size",
|
"name": "size",
|
||||||
"type_info": "Int8"
|
"type_info": "Int8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 6,
|
"ordinal": 7,
|
||||||
"name": "loudness",
|
"name": "loudness",
|
||||||
"type_info": "Float4"
|
"type_info": "Float4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 7,
|
"ordinal": 8,
|
||||||
"name": "album_id",
|
"name": "album_id",
|
||||||
"type_info": "Int4"
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 8,
|
"ordinal": 9,
|
||||||
"name": "album_pos",
|
"name": "album_pos",
|
||||||
"type_info": "Int2"
|
"type_info": "Int2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 9,
|
"ordinal": 10,
|
||||||
"name": "ul_artists",
|
"name": "ul_artists",
|
||||||
"type_info": "TextArray"
|
"type_info": "TextArray"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 10,
|
"ordinal": 11,
|
||||||
"name": "isrc",
|
"name": "isrc",
|
||||||
"type_info": "Varchar"
|
"type_info": "Varchar"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 11,
|
"ordinal": 12,
|
||||||
"name": "description",
|
"name": "description",
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 12,
|
"ordinal": 13,
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"type_info": "Timestamp"
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 13,
|
"ordinal": 14,
|
||||||
"name": "updated_at",
|
"name": "updated_at",
|
||||||
"type_info": "Timestamp"
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 14,
|
"ordinal": 15,
|
||||||
"name": "primary_track",
|
"name": "primary_track",
|
||||||
"type_info": "Bool"
|
"type_info": "Bool"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 15,
|
"ordinal": 16,
|
||||||
"name": "downloaded_at",
|
"name": "downloaded_at",
|
||||||
"type_info": "Timestamp"
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 16,
|
"ordinal": 17,
|
||||||
"name": "last_streamed_at",
|
"name": "last_streamed_at",
|
||||||
"type_info": "Timestamp"
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 17,
|
"ordinal": 18,
|
||||||
"name": "n_streams",
|
"name": "n_streams",
|
||||||
"type_info": "Int4"
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 18,
|
"ordinal": 19,
|
||||||
"name": "artists: _",
|
"name": "artists: _",
|
||||||
"type_info": "Jsonb"
|
"type_info": "Jsonb"
|
||||||
}
|
}
|
||||||
|
@ -135,6 +140,7 @@
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
@ -151,5 +157,5 @@
|
||||||
null
|
null
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "240c8386d19e97338971891b871bc2f5ed7a6edcdf6305d5a1bbbccc19793057"
|
"hash": "1075ed43809289b5b81d7cda88d66a270ff6085a0e280cc70e5a2a3c45fccf12"
|
||||||
}
|
}
|
46
crates/db/.sqlx/query-147c40380556124f815737fece63fa626ec7b9a214c371bf89feaa90824a629e.json
generated
Normal file
46
crates/db/.sqlx/query-147c40380556124f815737fece63fa626ec7b9a214c371bf89feaa90824a629e.json
generated
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "insert into tracks (src_id, service, name, duration, duration_ms,\n size, loudness, album_id, album_pos, ul_artists, isrc, description, primary_track)\nvalues ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)\non conflict (src_id, service) do update set\n name = excluded.name,\n duration = case when tracks.duration_ms and not excluded.duration_ms\n then tracks.duration else coalesce(excluded.duration, tracks.duration) end,\n duration_ms = excluded.duration_ms or tracks.duration_ms,\n size = coalesce(excluded.size, tracks.size),\n loudness = coalesce(excluded.loudness, tracks.loudness),\n album_id = excluded.album_id,\n album_pos = coalesce(excluded.album_pos, tracks.album_pos),\n ul_artists = coalesce(excluded.ul_artists, tracks.ul_artists),\n isrc = coalesce(excluded.isrc, tracks.isrc),\n description = coalesce(excluded.description, tracks.description),\n primary_track = coalesce(excluded.primary_track, tracks.primary_track)\nreturning id",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Int4"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
{
|
||||||
|
"Custom": {
|
||||||
|
"name": "music_service",
|
||||||
|
"kind": {
|
||||||
|
"Enum": [
|
||||||
|
"yt",
|
||||||
|
"ty",
|
||||||
|
"sp",
|
||||||
|
"mx"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Text",
|
||||||
|
"Int4",
|
||||||
|
"Bool",
|
||||||
|
"Int8",
|
||||||
|
"Float4",
|
||||||
|
"Int4",
|
||||||
|
"Int2",
|
||||||
|
"TextArray",
|
||||||
|
"Varchar",
|
||||||
|
"Text",
|
||||||
|
"Bool"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "147c40380556124f815737fece63fa626ec7b9a214c371bf89feaa90824a629e"
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "select t.id, t.src_id, t.service as \"service: _\", t.name, t.duration,\n t.size, t.loudness, t.album_id, t.album_pos, t.ul_artists, t.isrc, t.description, t.created_at, t.updated_at,\n t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,\n jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)\n filter (where a.src_id is not null) as \"artists: _\"\nfrom tracks t\n left join artists_tracks art on art.track_id = t.id\n left join artists a on a.id = art.artist_id\nwhere t.src_id=$1 and t.service=$2\ngroup by t.id",
|
"query": "select t.id, t.src_id, t.service as \"service: _\", t.name, t.duration, t.duration_ms,\n t.size, t.loudness, t.album_id, t.album_pos, t.ul_artists, t.isrc, t.description, t.created_at, t.updated_at,\n t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,\n jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)\n filter (where a.src_id is not null) as \"artists: _\"\nfrom tracks t\n left join artists_tracks art on art.track_id = t.id\n left join artists a on a.id = art.artist_id\n join track_aliases ta on ta.track_id=t.id\nwhere ta.src_id=$1 and ta.service=$2\ngroup by t.id",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
|
@ -42,71 +42,76 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
|
"name": "duration_ms",
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 6,
|
||||||
"name": "size",
|
"name": "size",
|
||||||
"type_info": "Int8"
|
"type_info": "Int8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 6,
|
"ordinal": 7,
|
||||||
"name": "loudness",
|
"name": "loudness",
|
||||||
"type_info": "Float4"
|
"type_info": "Float4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 7,
|
"ordinal": 8,
|
||||||
"name": "album_id",
|
"name": "album_id",
|
||||||
"type_info": "Int4"
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 8,
|
"ordinal": 9,
|
||||||
"name": "album_pos",
|
"name": "album_pos",
|
||||||
"type_info": "Int2"
|
"type_info": "Int2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 9,
|
"ordinal": 10,
|
||||||
"name": "ul_artists",
|
"name": "ul_artists",
|
||||||
"type_info": "TextArray"
|
"type_info": "TextArray"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 10,
|
"ordinal": 11,
|
||||||
"name": "isrc",
|
"name": "isrc",
|
||||||
"type_info": "Varchar"
|
"type_info": "Varchar"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 11,
|
"ordinal": 12,
|
||||||
"name": "description",
|
"name": "description",
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 12,
|
"ordinal": 13,
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"type_info": "Timestamp"
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 13,
|
"ordinal": 14,
|
||||||
"name": "updated_at",
|
"name": "updated_at",
|
||||||
"type_info": "Timestamp"
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 14,
|
"ordinal": 15,
|
||||||
"name": "primary_track",
|
"name": "primary_track",
|
||||||
"type_info": "Bool"
|
"type_info": "Bool"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 15,
|
"ordinal": 16,
|
||||||
"name": "downloaded_at",
|
"name": "downloaded_at",
|
||||||
"type_info": "Timestamp"
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 16,
|
"ordinal": 17,
|
||||||
"name": "last_streamed_at",
|
"name": "last_streamed_at",
|
||||||
"type_info": "Timestamp"
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 17,
|
"ordinal": 18,
|
||||||
"name": "n_streams",
|
"name": "n_streams",
|
||||||
"type_info": "Int4"
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 18,
|
"ordinal": 19,
|
||||||
"name": "artists: _",
|
"name": "artists: _",
|
||||||
"type_info": "Jsonb"
|
"type_info": "Jsonb"
|
||||||
}
|
}
|
||||||
|
@ -135,6 +140,7 @@
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
@ -151,5 +157,5 @@
|
||||||
null
|
null
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "b74d96a7a07be63547f6292beb8e2ed946f04184555c3092d1a502d2ba7622bb"
|
"hash": "22bd3df069a5e7fa8c7882a2b38c2d2e4be1b49a61ea77c4ac2770064cdc3323"
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"db_name": "PostgreSQL",
|
"db_name": "PostgreSQL",
|
||||||
"query": "select t.id, t.src_id, t.service as \"service: _\", t.name, t.duration,\n t.size, t.loudness, t.album_id, t.album_pos, t.ul_artists, t.isrc, t.description, t.created_at, t.updated_at,\n t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,\n jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)\n filter (where a.src_id is not null) as \"artists: _\"\nfrom tracks t\n left join artists_tracks art on art.track_id = t.id\n left join artists a on a.id = art.artist_id\nwhere t.id=$1\ngroup by t.id",
|
"query": "select t.id, t.src_id, t.service as \"service: _\", t.name, t.duration, t.duration_ms,\n t.size, t.loudness, t.album_id, t.album_pos, t.ul_artists, t.isrc, t.description, t.created_at, t.updated_at,\n t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,\n jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)\n filter (where a.src_id is not null) as \"artists: _\"\nfrom tracks t\n left join artists_tracks art on art.track_id = t.id\n left join artists a on a.id = art.artist_id\nwhere t.id=$1\ngroup by t.id",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
|
@ -42,71 +42,76 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 5,
|
"ordinal": 5,
|
||||||
|
"name": "duration_ms",
|
||||||
|
"type_info": "Bool"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 6,
|
||||||
"name": "size",
|
"name": "size",
|
||||||
"type_info": "Int8"
|
"type_info": "Int8"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 6,
|
"ordinal": 7,
|
||||||
"name": "loudness",
|
"name": "loudness",
|
||||||
"type_info": "Float4"
|
"type_info": "Float4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 7,
|
"ordinal": 8,
|
||||||
"name": "album_id",
|
"name": "album_id",
|
||||||
"type_info": "Int4"
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 8,
|
"ordinal": 9,
|
||||||
"name": "album_pos",
|
"name": "album_pos",
|
||||||
"type_info": "Int2"
|
"type_info": "Int2"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 9,
|
"ordinal": 10,
|
||||||
"name": "ul_artists",
|
"name": "ul_artists",
|
||||||
"type_info": "TextArray"
|
"type_info": "TextArray"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 10,
|
"ordinal": 11,
|
||||||
"name": "isrc",
|
"name": "isrc",
|
||||||
"type_info": "Varchar"
|
"type_info": "Varchar"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 11,
|
"ordinal": 12,
|
||||||
"name": "description",
|
"name": "description",
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 12,
|
"ordinal": 13,
|
||||||
"name": "created_at",
|
"name": "created_at",
|
||||||
"type_info": "Timestamp"
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 13,
|
"ordinal": 14,
|
||||||
"name": "updated_at",
|
"name": "updated_at",
|
||||||
"type_info": "Timestamp"
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 14,
|
"ordinal": 15,
|
||||||
"name": "primary_track",
|
"name": "primary_track",
|
||||||
"type_info": "Bool"
|
"type_info": "Bool"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 15,
|
"ordinal": 16,
|
||||||
"name": "downloaded_at",
|
"name": "downloaded_at",
|
||||||
"type_info": "Timestamp"
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 16,
|
"ordinal": 17,
|
||||||
"name": "last_streamed_at",
|
"name": "last_streamed_at",
|
||||||
"type_info": "Timestamp"
|
"type_info": "Timestamp"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 17,
|
"ordinal": 18,
|
||||||
"name": "n_streams",
|
"name": "n_streams",
|
||||||
"type_info": "Int4"
|
"type_info": "Int4"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"ordinal": 18,
|
"ordinal": 19,
|
||||||
"name": "artists: _",
|
"name": "artists: _",
|
||||||
"type_info": "Jsonb"
|
"type_info": "Jsonb"
|
||||||
}
|
}
|
||||||
|
@ -122,6 +127,7 @@
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
true,
|
true,
|
||||||
|
false,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
|
@ -138,5 +144,5 @@
|
||||||
null
|
null
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "ebd526014759ba1316f26ba42a0b66aef00cae6c86a6d5f4747a99414d42c2b1"
|
"hash": "dbb478aab35ec0b538845d633a84399984847f42b62fd980c022385a8c6e69e7"
|
||||||
}
|
}
|
|
@ -1,45 +0,0 @@
|
||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "insert into tracks (src_id, service, name, duration,\n size, loudness, album_id, album_pos, ul_artists, isrc, description, primary_track)\nvalues ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)\non conflict (src_id, service) do update set\n name = excluded.name,\n duration = coalesce(excluded.duration, tracks.duration),\n size = coalesce(excluded.size, tracks.size),\n loudness = coalesce(excluded.loudness, tracks.loudness),\n album_id = excluded.album_id,\n album_pos = coalesce(excluded.album_pos, tracks.album_pos),\n ul_artists = coalesce(excluded.ul_artists, tracks.ul_artists),\n isrc = coalesce(excluded.isrc, tracks.isrc),\n description = coalesce(excluded.description, tracks.description),\n primary_track = coalesce(excluded.primary_track, tracks.primary_track)\nreturning id",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"ordinal": 0,
|
|
||||||
"name": "id",
|
|
||||||
"type_info": "Int4"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Text",
|
|
||||||
{
|
|
||||||
"Custom": {
|
|
||||||
"name": "music_service",
|
|
||||||
"kind": {
|
|
||||||
"Enum": [
|
|
||||||
"yt",
|
|
||||||
"ty",
|
|
||||||
"sp",
|
|
||||||
"mx"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Text",
|
|
||||||
"Int4",
|
|
||||||
"Int8",
|
|
||||||
"Float4",
|
|
||||||
"Int4",
|
|
||||||
"Int2",
|
|
||||||
"TextArray",
|
|
||||||
"Varchar",
|
|
||||||
"Text",
|
|
||||||
"Bool"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "f3e06fbfd2fc3bff86b80c754109bbbd11d8c2decf4d72760c68a2f6d9d22c67"
|
|
||||||
}
|
|
|
@ -23,6 +23,7 @@ CREATE TABLE tracks (
|
||||||
service music_service NOT NULL,
|
service music_service NOT NULL,
|
||||||
name text NOT NULL,
|
name text NOT NULL,
|
||||||
duration integer,
|
duration integer,
|
||||||
|
duration_ms bool NOT NULL DEFAULT false,
|
||||||
size bigint,
|
size bigint,
|
||||||
loudness float4,
|
loudness float4,
|
||||||
album_pos smallint,
|
album_pos smallint,
|
||||||
|
@ -42,7 +43,8 @@ COMMENT ON COLUMN tracks.id IS E'Internal track ID';
|
||||||
COMMENT ON COLUMN tracks.src_id IS E'Track ID from the source';
|
COMMENT ON COLUMN tracks.src_id IS E'Track ID from the source';
|
||||||
COMMENT ON COLUMN tracks.service IS E'Service providing the track';
|
COMMENT ON COLUMN tracks.service IS E'Service providing the track';
|
||||||
COMMENT ON COLUMN tracks.name IS E'Track name';
|
COMMENT ON COLUMN tracks.name IS E'Track name';
|
||||||
COMMENT ON COLUMN tracks.duration IS E'Duration of the track in seconds';
|
COMMENT ON COLUMN tracks.duration IS E'Duration of the track in milliseconds';
|
||||||
|
COMMENT ON COLUMN tracks.duration_ms IS E'True if the duration is in millisecond resolution';
|
||||||
COMMENT ON COLUMN tracks.size IS E'File size in bytes';
|
COMMENT ON COLUMN tracks.size IS E'File size in bytes';
|
||||||
COMMENT ON COLUMN tracks.loudness IS E'Track loudness in dB';
|
COMMENT ON COLUMN tracks.loudness IS E'Track loudness in dB';
|
||||||
COMMENT ON COLUMN tracks.album_pos IS E'Position of the track in its album';
|
COMMENT ON COLUMN tracks.album_pos IS E'Position of the track in its album';
|
||||||
|
@ -329,7 +331,7 @@ CREATE TRIGGER albums_set_updated_at
|
||||||
EXECUTE PROCEDURE set_updated_at();
|
EXECUTE PROCEDURE set_updated_at();
|
||||||
|
|
||||||
CREATE TRIGGER tracks_set_updated_at
|
CREATE TRIGGER tracks_set_updated_at
|
||||||
BEFORE UPDATE OF id,src_id,service,name,duration,size,loudness,album_pos,album_id,ul_artists,isrc,description
|
BEFORE UPDATE OF id,src_id,service,name,duration,duration_ms,size,loudness,album_pos,album_id,ul_artists,isrc,description
|
||||||
ON tracks
|
ON tracks
|
||||||
FOR EACH ROW
|
FOR EACH ROW
|
||||||
EXECUTE PROCEDURE set_updated_at();
|
EXECUTE PROCEDURE set_updated_at();
|
||||||
|
|
|
@ -5,7 +5,7 @@ use time::{Date, PrimitiveDateTime};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
artist::{ArtistId, ArtistJsonb},
|
artist::{ArtistId, ArtistJsonb},
|
||||||
map_artists, AlbumType, DatePrecision, Id, MusicService, SrcId, SrcIdOwned, TrackSlim,
|
map_artists, AlbumType, Artist, DatePrecision, Id, MusicService, SrcId, SrcIdOwned, TrackSlim,
|
||||||
TrackSlimRow,
|
TrackSlimRow,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -254,10 +254,11 @@ group by b.id"#,
|
||||||
|
|
||||||
pub async fn add_artists(
|
pub async fn add_artists(
|
||||||
id: i32,
|
id: i32,
|
||||||
artists: &[i32],
|
artists: &[Id<'_>],
|
||||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<(), DatabaseError> {
|
) -> Result<(), DatabaseError> {
|
||||||
for artist_id in artists {
|
for artist_id in artists {
|
||||||
|
let artist_id = Artist::resolve_id(*artist_id, tx).await?;
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"insert into artists_albums (album_id, artist_id) values ($1, $2)
|
r#"insert into artists_albums (album_id, artist_id) values ($1, $2)
|
||||||
on conflict (album_id, artist_id) do nothing"#,
|
on conflict (album_id, artist_id) do nothing"#,
|
||||||
|
@ -272,7 +273,7 @@ on conflict (album_id, artist_id) do nothing"#,
|
||||||
|
|
||||||
pub async fn set_artists(
|
pub async fn set_artists(
|
||||||
id: i32,
|
id: i32,
|
||||||
artists: &[i32],
|
artists: &[Id<'_>],
|
||||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<(), DatabaseError> {
|
) -> Result<(), DatabaseError> {
|
||||||
sqlx::query!(r#"delete from artists_albums where album_id=$1"#, id)
|
sqlx::query!(r#"delete from artists_albums where album_id=$1"#, id)
|
||||||
|
@ -639,7 +640,7 @@ mod tests {
|
||||||
album_hash: Some(&hex!("badeaffe")),
|
album_hash: Some(&hex!("badeaffe")),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let album_artists = [ids::ARTIST_ID_LEA, ids::ARTIST_ID_CYRIL];
|
let album_artists = [ids::ARTIST_LEA, ids::ARTIST_CYRIL];
|
||||||
|
|
||||||
// Create
|
// Create
|
||||||
let mut c_tx = pool.begin().await.unwrap();
|
let mut c_tx = pool.begin().await.unwrap();
|
||||||
|
|
|
@ -7,7 +7,7 @@ expression: tracks_iwwus
|
||||||
src_id: "hWFarQmaQAQ",
|
src_id: "hWFarQmaQAQ",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Immer wenn wir uns sehn (\"Das schönste Mädchen der Welt\", Soundtrack)",
|
name: "Immer wenn wir uns sehn (\"Das schönste Mädchen der Welt\", Soundtrack)",
|
||||||
duration: Some(186),
|
duration: Some(186000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
|
|
@ -7,7 +7,7 @@ expression: tracks_vakuum
|
||||||
src_id: "2txScm52-QI",
|
src_id: "2txScm52-QI",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Die Segel sind gesetzt",
|
name: "Die Segel sind gesetzt",
|
||||||
duration: Some(186),
|
duration: Some(186000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -29,7 +29,7 @@ expression: tracks_vakuum
|
||||||
src_id: "oZKv47vyqQU",
|
src_id: "oZKv47vyqQU",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Monster",
|
name: "Monster",
|
||||||
duration: Some(224),
|
duration: Some(224000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -51,7 +51,7 @@ expression: tracks_vakuum
|
||||||
src_id: "7WXlMU9ItnA",
|
src_id: "7WXlMU9ItnA",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Dach",
|
name: "Dach",
|
||||||
duration: Some(231),
|
duration: Some(231000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -73,7 +73,7 @@ expression: tracks_vakuum
|
||||||
src_id: "ySChj_9rT5Y",
|
src_id: "ySChj_9rT5Y",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Kennst du das",
|
name: "Kennst du das",
|
||||||
duration: Some(191),
|
duration: Some(191000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -95,7 +95,7 @@ expression: tracks_vakuum
|
||||||
src_id: "revpIT2HiNs",
|
src_id: "revpIT2HiNs",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Wohin willst du",
|
name: "Wohin willst du",
|
||||||
duration: Some(255),
|
duration: Some(255000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -117,7 +117,7 @@ expression: tracks_vakuum
|
||||||
src_id: "LeEgBsYfjLU",
|
src_id: "LeEgBsYfjLU",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Vakuum",
|
name: "Vakuum",
|
||||||
duration: Some(220),
|
duration: Some(220000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -139,7 +139,7 @@ expression: tracks_vakuum
|
||||||
src_id: "-i5XjMkQN8M",
|
src_id: "-i5XjMkQN8M",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Melodie",
|
name: "Melodie",
|
||||||
duration: Some(251),
|
duration: Some(251000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -161,7 +161,7 @@ expression: tracks_vakuum
|
||||||
src_id: "DhlIZkoPsxg",
|
src_id: "DhlIZkoPsxg",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Du & Ich",
|
name: "Du & Ich",
|
||||||
duration: Some(238),
|
duration: Some(238000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -183,7 +183,7 @@ expression: tracks_vakuum
|
||||||
src_id: "LCoomBMOkgU",
|
src_id: "LCoomBMOkgU",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Schwerelos",
|
name: "Schwerelos",
|
||||||
duration: Some(198),
|
duration: Some(198000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -205,7 +205,7 @@ expression: tracks_vakuum
|
||||||
src_id: "c6Ot-Z3HEBo",
|
src_id: "c6Ot-Z3HEBo",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Lichtermeer",
|
name: "Lichtermeer",
|
||||||
duration: Some(164),
|
duration: Some(164000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -227,7 +227,7 @@ expression: tracks_vakuum
|
||||||
src_id: "ybm_4hQG0ok",
|
src_id: "ybm_4hQG0ok",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Nachtzug",
|
name: "Nachtzug",
|
||||||
duration: Some(290),
|
duration: Some(290000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -249,7 +249,7 @@ expression: tracks_vakuum
|
||||||
src_id: "DJKmtK5PmSY",
|
src_id: "DJKmtK5PmSY",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Rückenwind",
|
name: "Rückenwind",
|
||||||
duration: Some(263),
|
duration: Some(263000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
|
|
@ -7,7 +7,7 @@ expression: tracks
|
||||||
src_id: "LeEgBsYfjLU",
|
src_id: "LeEgBsYfjLU",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Vakuum",
|
name: "Vakuum",
|
||||||
duration: Some(220),
|
duration: Some(220000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -29,7 +29,7 @@ expression: tracks
|
||||||
src_id: "LCoomBMOkgU",
|
src_id: "LCoomBMOkgU",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Schwerelos",
|
name: "Schwerelos",
|
||||||
duration: Some(198),
|
duration: Some(198000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -51,7 +51,7 @@ expression: tracks
|
||||||
src_id: "c6Ot-Z3HEBo",
|
src_id: "c6Ot-Z3HEBo",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Lichtermeer",
|
name: "Lichtermeer",
|
||||||
duration: Some(164),
|
duration: Some(164000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -73,7 +73,7 @@ expression: tracks
|
||||||
src_id: "hWFarQmaQAQ",
|
src_id: "hWFarQmaQAQ",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Immer wenn wir uns sehn (\"Das schönste Mädchen der Welt\", Soundtrack)",
|
name: "Immer wenn wir uns sehn (\"Das schönste Mädchen der Welt\", Soundtrack)",
|
||||||
duration: Some(186),
|
duration: Some(186000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -99,7 +99,7 @@ expression: tracks
|
||||||
src_id: "2txScm52-QI",
|
src_id: "2txScm52-QI",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Die Segel sind gesetzt",
|
name: "Die Segel sind gesetzt",
|
||||||
duration: Some(186),
|
duration: Some(186000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
|
|
@ -7,7 +7,7 @@ expression: tracks
|
||||||
src_id: "2txScm52-QI",
|
src_id: "2txScm52-QI",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Die Segel sind gesetzt",
|
name: "Die Segel sind gesetzt",
|
||||||
duration: Some(186),
|
duration: Some(186000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -29,7 +29,7 @@ expression: tracks
|
||||||
src_id: "oZKv47vyqQU",
|
src_id: "oZKv47vyqQU",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Monster",
|
name: "Monster",
|
||||||
duration: Some(224),
|
duration: Some(224000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -51,7 +51,7 @@ expression: tracks
|
||||||
src_id: "7WXlMU9ItnA",
|
src_id: "7WXlMU9ItnA",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Dach",
|
name: "Dach",
|
||||||
duration: Some(231),
|
duration: Some(231000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -73,7 +73,7 @@ expression: tracks
|
||||||
src_id: "ySChj_9rT5Y",
|
src_id: "ySChj_9rT5Y",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Kennst du das",
|
name: "Kennst du das",
|
||||||
duration: Some(191),
|
duration: Some(191000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -95,7 +95,7 @@ expression: tracks
|
||||||
src_id: "revpIT2HiNs",
|
src_id: "revpIT2HiNs",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Wohin willst du",
|
name: "Wohin willst du",
|
||||||
duration: Some(255),
|
duration: Some(255000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -117,7 +117,7 @@ expression: tracks
|
||||||
src_id: "LeEgBsYfjLU",
|
src_id: "LeEgBsYfjLU",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Vakuum",
|
name: "Vakuum",
|
||||||
duration: Some(220),
|
duration: Some(220000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -139,7 +139,7 @@ expression: tracks
|
||||||
src_id: "-i5XjMkQN8M",
|
src_id: "-i5XjMkQN8M",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Melodie",
|
name: "Melodie",
|
||||||
duration: Some(251),
|
duration: Some(251000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -161,7 +161,7 @@ expression: tracks
|
||||||
src_id: "DhlIZkoPsxg",
|
src_id: "DhlIZkoPsxg",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Du & Ich",
|
name: "Du & Ich",
|
||||||
duration: Some(238),
|
duration: Some(238000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -183,7 +183,7 @@ expression: tracks
|
||||||
src_id: "LCoomBMOkgU",
|
src_id: "LCoomBMOkgU",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Schwerelos",
|
name: "Schwerelos",
|
||||||
duration: Some(198),
|
duration: Some(198000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -205,7 +205,7 @@ expression: tracks
|
||||||
src_id: "c6Ot-Z3HEBo",
|
src_id: "c6Ot-Z3HEBo",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Lichtermeer",
|
name: "Lichtermeer",
|
||||||
duration: Some(164),
|
duration: Some(164000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -227,7 +227,7 @@ expression: tracks
|
||||||
src_id: "ybm_4hQG0ok",
|
src_id: "ybm_4hQG0ok",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Nachtzug",
|
name: "Nachtzug",
|
||||||
duration: Some(290),
|
duration: Some(290000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -249,7 +249,7 @@ expression: tracks
|
||||||
src_id: "DJKmtK5PmSY",
|
src_id: "DJKmtK5PmSY",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Rückenwind",
|
name: "Rückenwind",
|
||||||
duration: Some(263),
|
duration: Some(263000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
@ -271,7 +271,7 @@ expression: tracks
|
||||||
src_id: "hWFarQmaQAQ",
|
src_id: "hWFarQmaQAQ",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Immer wenn wir uns sehn (\"Das schönste Mädchen der Welt\", Soundtrack)",
|
name: "Immer wenn wir uns sehn (\"Das schönste Mädchen der Welt\", Soundtrack)",
|
||||||
duration: Some(186),
|
duration: Some(186000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
|
|
@ -10,7 +10,7 @@ expression: tracks
|
||||||
src_id: "WSBUeFdXiSs",
|
src_id: "WSBUeFdXiSs",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Leicht",
|
name: "Leicht",
|
||||||
duration: Some(206),
|
duration: Some(206000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC-2mb3G26qV676d-iXbOTVQ"),
|
id: Some("yt:UC-2mb3G26qV676d-iXbOTVQ"),
|
||||||
|
@ -36,7 +36,7 @@ expression: tracks
|
||||||
src_id: "OCgE2GSL1Pk",
|
src_id: "OCgE2GSL1Pk",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Smoke Signals",
|
name: "Smoke Signals",
|
||||||
duration: Some(197),
|
duration: Some(197000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UCQ6yypykkyPLM5FVhOm4Eog"),
|
id: Some("yt:UCQ6yypykkyPLM5FVhOm4Eog"),
|
||||||
|
@ -62,7 +62,7 @@ expression: tracks
|
||||||
src_id: "6485PhOtHzY",
|
src_id: "6485PhOtHzY",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Lieblingsmensch",
|
name: "Lieblingsmensch",
|
||||||
duration: Some(190),
|
duration: Some(190000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UCIh4j8fXWf2U0ro0qnGU8Mg"),
|
id: Some("yt:UCIh4j8fXWf2U0ro0qnGU8Mg"),
|
||||||
|
|
|
@ -8,6 +8,7 @@ Track(
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "empty",
|
name: "empty",
|
||||||
duration: None,
|
duration: None,
|
||||||
|
duration_ms: false,
|
||||||
artists: [],
|
artists: [],
|
||||||
size: None,
|
size: None,
|
||||||
loudness: None,
|
loudness: None,
|
||||||
|
|
|
@ -7,7 +7,8 @@ Track(
|
||||||
src_id: "g0iRiJ_ck48",
|
src_id: "g0iRiJ_ck48",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Aulë und Yavanna",
|
name: "Aulë und Yavanna",
|
||||||
duration: Some(216),
|
duration: Some(216000),
|
||||||
|
duration_ms: false,
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
|
|
@ -6,7 +6,7 @@ TrackSlim(
|
||||||
src_id: "g0iRiJ_ck48",
|
src_id: "g0iRiJ_ck48",
|
||||||
service: yt,
|
service: yt,
|
||||||
name: "Aulë und Yavanna",
|
name: "Aulë und Yavanna",
|
||||||
duration: Some(216),
|
duration: Some(216000),
|
||||||
artists: [
|
artists: [
|
||||||
ArtistId(
|
ArtistId(
|
||||||
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
id: Some("yt:UC_MxOdawj_BStPs4CKBYD0Q"),
|
||||||
|
|
|
@ -6,7 +6,7 @@ use time::{Date, PrimitiveDateTime};
|
||||||
use super::{
|
use super::{
|
||||||
album::AlbumId,
|
album::AlbumId,
|
||||||
artist::{ArtistId, ArtistJsonb},
|
artist::{ArtistId, ArtistJsonb},
|
||||||
map_artists, AlbumType, Id, MusicService, SrcId, SrcIdOwned,
|
map_artists, AlbumType, Artist, Id, MusicService, SrcId, SrcIdOwned,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
error::{DatabaseError, OptionalRes},
|
error::{DatabaseError, OptionalRes},
|
||||||
|
@ -20,6 +20,7 @@ pub struct Track {
|
||||||
pub service: MusicService,
|
pub service: MusicService,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub duration: Option<i32>,
|
pub duration: Option<i32>,
|
||||||
|
pub duration_ms: bool,
|
||||||
pub artists: Vec<ArtistId>,
|
pub artists: Vec<ArtistId>,
|
||||||
pub size: Option<i64>,
|
pub size: Option<i64>,
|
||||||
pub loudness: Option<f32>,
|
pub loudness: Option<f32>,
|
||||||
|
@ -42,6 +43,7 @@ struct TrackRow {
|
||||||
service: MusicService,
|
service: MusicService,
|
||||||
name: String,
|
name: String,
|
||||||
duration: Option<i32>,
|
duration: Option<i32>,
|
||||||
|
duration_ms: bool,
|
||||||
size: Option<i64>,
|
size: Option<i64>,
|
||||||
loudness: Option<f32>,
|
loudness: Option<f32>,
|
||||||
album_id: i32,
|
album_id: i32,
|
||||||
|
@ -65,6 +67,7 @@ pub struct TrackNew<'a> {
|
||||||
pub service: MusicService,
|
pub service: MusicService,
|
||||||
pub name: &'a str,
|
pub name: &'a str,
|
||||||
pub duration: Option<i32>,
|
pub duration: Option<i32>,
|
||||||
|
pub duration_ms: bool,
|
||||||
pub size: Option<i64>,
|
pub size: Option<i64>,
|
||||||
pub loudness: Option<f32>,
|
pub loudness: Option<f32>,
|
||||||
pub album_id: i32,
|
pub album_id: i32,
|
||||||
|
@ -80,6 +83,7 @@ pub struct TrackNew<'a> {
|
||||||
pub struct TrackUpdate<'a> {
|
pub struct TrackUpdate<'a> {
|
||||||
pub name: Option<&'a str>,
|
pub name: Option<&'a str>,
|
||||||
pub duration: Option<Option<i32>>,
|
pub duration: Option<Option<i32>>,
|
||||||
|
pub duration_ms: Option<bool>,
|
||||||
pub size: Option<Option<i64>>,
|
pub size: Option<Option<i64>>,
|
||||||
pub loudness: Option<Option<f32>>,
|
pub loudness: Option<Option<f32>>,
|
||||||
pub album_id: Option<i32>,
|
pub album_id: Option<i32>,
|
||||||
|
@ -148,7 +152,7 @@ impl Track {
|
||||||
Id::Db(id) => {
|
Id::Db(id) => {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
TrackRow,
|
TrackRow,
|
||||||
r#"select t.id, t.src_id, t.service as "service: _", t.name, t.duration,
|
r#"select t.id, t.src_id, t.service as "service: _", t.name, t.duration, t.duration_ms,
|
||||||
t.size, t.loudness, t.album_id, t.album_pos, t.ul_artists, t.isrc, t.description, t.created_at, t.updated_at,
|
t.size, t.loudness, t.album_id, t.album_pos, t.ul_artists, t.isrc, t.description, t.created_at, t.updated_at,
|
||||||
t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,
|
t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,
|
||||||
jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)
|
jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)
|
||||||
|
@ -166,7 +170,7 @@ group by t.id"#,
|
||||||
Id::Src(src_id, srv) => {
|
Id::Src(src_id, srv) => {
|
||||||
let res = sqlx::query_as!(
|
let res = sqlx::query_as!(
|
||||||
TrackRow,
|
TrackRow,
|
||||||
r#"select t.id, t.src_id, t.service as "service: _", t.name, t.duration,
|
r#"select t.id, t.src_id, t.service as "service: _", t.name, t.duration, t.duration_ms,
|
||||||
t.size, t.loudness, t.album_id, t.album_pos, t.ul_artists, t.isrc, t.description, t.created_at, t.updated_at,
|
t.size, t.loudness, t.album_id, t.album_pos, t.ul_artists, t.isrc, t.description, t.created_at, t.updated_at,
|
||||||
t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,
|
t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,
|
||||||
jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)
|
jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)
|
||||||
|
@ -188,7 +192,7 @@ group by t.id"#,
|
||||||
None => {
|
None => {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
TrackRow,
|
TrackRow,
|
||||||
r#"select t.id, t.src_id, t.service as "service: _", t.name, t.duration,
|
r#"select t.id, t.src_id, t.service as "service: _", t.name, t.duration, t.duration_ms,
|
||||||
t.size, t.loudness, t.album_id, t.album_pos, t.ul_artists, t.isrc, t.description, t.created_at, t.updated_at,
|
t.size, t.loudness, t.album_id, t.album_pos, t.ul_artists, t.isrc, t.description, t.created_at, t.updated_at,
|
||||||
t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,
|
t.primary_track, t.downloaded_at, t.last_streamed_at, t.n_streams,
|
||||||
jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)
|
jsonb_agg(json_build_object('id', a.src_id, 'sv', a.service, 'n', a.name) order by art.seq)
|
||||||
|
@ -280,7 +284,7 @@ where ta.src_id=$1 and ta.service=$2"#,
|
||||||
|
|
||||||
pub async fn set_artists(
|
pub async fn set_artists(
|
||||||
id: i32,
|
id: i32,
|
||||||
artists: &[i32],
|
artists: &[Id<'_>],
|
||||||
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
tx: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<(), DatabaseError> {
|
) -> Result<(), DatabaseError> {
|
||||||
sqlx::query!(r#"delete from artists_tracks where track_id=$1"#, id)
|
sqlx::query!(r#"delete from artists_tracks where track_id=$1"#, id)
|
||||||
|
@ -288,6 +292,7 @@ where ta.src_id=$1 and ta.service=$2"#,
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for artist_id in artists {
|
for artist_id in artists {
|
||||||
|
let artist_id = Artist::resolve_id(*artist_id, tx).await?;
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
r#"insert into artists_tracks (track_id, artist_id) values ($1, $2)"#,
|
r#"insert into artists_tracks (track_id, artist_id) values ($1, $2)"#,
|
||||||
id,
|
id,
|
||||||
|
@ -347,12 +352,14 @@ impl TrackNew<'_> {
|
||||||
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
E: sqlx::Executor<'a, Database = sqlx::Postgres>,
|
||||||
{
|
{
|
||||||
let res = sqlx::query!(
|
let res = sqlx::query!(
|
||||||
r#"insert into tracks (src_id, service, name, duration,
|
r#"insert into tracks (src_id, service, name, duration, duration_ms,
|
||||||
size, loudness, album_id, album_pos, ul_artists, isrc, description, primary_track)
|
size, loudness, album_id, album_pos, ul_artists, isrc, description, primary_track)
|
||||||
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
values ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
||||||
on conflict (src_id, service) do update set
|
on conflict (src_id, service) do update set
|
||||||
name = excluded.name,
|
name = excluded.name,
|
||||||
duration = coalesce(excluded.duration, tracks.duration),
|
duration = case when tracks.duration_ms and not excluded.duration_ms
|
||||||
|
then tracks.duration else coalesce(excluded.duration, tracks.duration) end,
|
||||||
|
duration_ms = excluded.duration_ms or tracks.duration_ms,
|
||||||
size = coalesce(excluded.size, tracks.size),
|
size = coalesce(excluded.size, tracks.size),
|
||||||
loudness = coalesce(excluded.loudness, tracks.loudness),
|
loudness = coalesce(excluded.loudness, tracks.loudness),
|
||||||
album_id = excluded.album_id,
|
album_id = excluded.album_id,
|
||||||
|
@ -366,6 +373,7 @@ returning id"#,
|
||||||
self.service as MusicService,
|
self.service as MusicService,
|
||||||
self.name,
|
self.name,
|
||||||
self.duration,
|
self.duration,
|
||||||
|
self.duration_ms,
|
||||||
self.size,
|
self.size,
|
||||||
self.loudness,
|
self.loudness,
|
||||||
self.album_id,
|
self.album_id,
|
||||||
|
@ -402,6 +410,14 @@ impl TrackUpdate<'_> {
|
||||||
query.push_bind(duration);
|
query.push_bind(duration);
|
||||||
n += 1;
|
n += 1;
|
||||||
}
|
}
|
||||||
|
if let Some(duration_ms) = &self.duration_ms {
|
||||||
|
if n != 0 {
|
||||||
|
query.push(", ");
|
||||||
|
}
|
||||||
|
query.push("duration_ms=");
|
||||||
|
query.push_bind(duration_ms);
|
||||||
|
n += 1;
|
||||||
|
}
|
||||||
if let Some(size) = &self.size {
|
if let Some(size) = &self.size {
|
||||||
if n != 0 {
|
if n != 0 {
|
||||||
query.push(", ");
|
query.push(", ");
|
||||||
|
@ -583,6 +599,7 @@ impl From<TrackRow> for Track {
|
||||||
service: value.service,
|
service: value.service,
|
||||||
name: value.name,
|
name: value.name,
|
||||||
duration: value.duration,
|
duration: value.duration,
|
||||||
|
duration_ms: value.duration_ms,
|
||||||
artists: map_artists(value.artists, value.ul_artists),
|
artists: map_artists(value.artists, value.ul_artists),
|
||||||
size: value.size,
|
size: value.size,
|
||||||
loudness: value.loudness,
|
loudness: value.loudness,
|
||||||
|
@ -637,7 +654,8 @@ mod tests {
|
||||||
src_id: "g0iRiJ_ck48",
|
src_id: "g0iRiJ_ck48",
|
||||||
service: MusicService::YouTube,
|
service: MusicService::YouTube,
|
||||||
name: "Aulë und Yavanna",
|
name: "Aulë und Yavanna",
|
||||||
duration: Some(216),
|
duration: Some(216000),
|
||||||
|
duration_ms: false,
|
||||||
size: Some(3_439_414),
|
size: Some(3_439_414),
|
||||||
loudness: Some(6.1513805),
|
loudness: Some(6.1513805),
|
||||||
album_id: 1,
|
album_id: 1,
|
||||||
|
@ -647,7 +665,7 @@ mod tests {
|
||||||
description: Some("Hello World"),
|
description: Some("Hello World"),
|
||||||
primary_track: Some(true),
|
primary_track: Some(true),
|
||||||
};
|
};
|
||||||
let track_artists = [ids::ARTIST_ID_LEA, ids::ARTIST_ID_CYRIL];
|
let track_artists = [ids::ARTIST_LEA, ids::ARTIST_CYRIL];
|
||||||
|
|
||||||
// Create
|
// Create
|
||||||
let mut c_tx = pool.begin().await.unwrap();
|
let mut c_tx = pool.begin().await.unwrap();
|
||||||
|
@ -686,6 +704,7 @@ mod tests {
|
||||||
let clear = TrackUpdate {
|
let clear = TrackUpdate {
|
||||||
name: Some("empty"),
|
name: Some("empty"),
|
||||||
duration: Some(None),
|
duration: Some(None),
|
||||||
|
duration_ms: Some(false),
|
||||||
size: Some(None),
|
size: Some(None),
|
||||||
loudness: Some(None),
|
loudness: Some(None),
|
||||||
album_id: None,
|
album_id: None,
|
||||||
|
|
38
crates/db/testdata/base.sql
vendored
38
crates/db/testdata/base.sql
vendored
|
@ -36,25 +36,25 @@ INSERT INTO artists_albums (artist_id,album_id) VALUES
|
||||||
(8,6),
|
(8,6),
|
||||||
(9,7);
|
(9,7);
|
||||||
|
|
||||||
INSERT INTO tracks (src_id,service,"name",duration,"size",loudness,album_pos,album_id,ul_artists,isrc,created_at,updated_at,primary_track,downloaded_at,last_streamed_at,n_streams) VALUES
|
INSERT INTO tracks (src_id,service,"name",duration,duration_ms,"size",loudness,album_pos,album_id,ul_artists,isrc,created_at,updated_at,primary_track,downloaded_at,last_streamed_at,n_streams) VALUES
|
||||||
('2txScm52-QI','yt','Die Segel sind gesetzt',186,NULL,NULL,1,1,'{}',NULL,'2023-08-30 22:40:30.15406','2023-08-30 22:49:15.955139',NULL,NULL,NULL,0),
|
('2txScm52-QI','yt','Die Segel sind gesetzt',186000,false,NULL,NULL,1,1,'{}',NULL,'2023-08-30 22:40:30.15406','2023-08-30 22:49:15.955139',NULL,NULL,NULL,0),
|
||||||
('oZKv47vyqQU','yt','Monster',224,NULL,NULL,2,1,'{}',NULL,'2023-08-30 22:41:22.905632','2023-08-30 22:49:15.957959',NULL,NULL,NULL,0),
|
('oZKv47vyqQU','yt','Monster',224000,false,NULL,NULL,2,1,'{}',NULL,'2023-08-30 22:41:22.905632','2023-08-30 22:49:15.957959',NULL,NULL,NULL,0),
|
||||||
('7WXlMU9ItnA','yt','Dach',231,NULL,NULL,3,1,'{}',NULL,'2023-08-30 22:42:00.093845','2023-08-30 22:49:15.959538',NULL,NULL,NULL,0),
|
('7WXlMU9ItnA','yt','Dach',231000,false,NULL,NULL,3,1,'{}',NULL,'2023-08-30 22:42:00.093845','2023-08-30 22:49:15.959538',NULL,NULL,NULL,0),
|
||||||
('ySChj_9rT5Y','yt','Kennst du das',191,NULL,NULL,4,1,'{}',NULL,'2023-08-30 22:42:57.462304','2023-08-30 22:49:15.961008',NULL,NULL,NULL,0),
|
('ySChj_9rT5Y','yt','Kennst du das',191000,false,NULL,NULL,4,1,'{}',NULL,'2023-08-30 22:42:57.462304','2023-08-30 22:49:15.961008',NULL,NULL,NULL,0),
|
||||||
('revpIT2HiNs','yt','Wohin willst du',255,NULL,NULL,5,1,'{}',NULL,'2023-08-30 22:43:39.594609','2023-08-30 22:49:15.962497',NULL,NULL,NULL,0),
|
('revpIT2HiNs','yt','Wohin willst du',255000,false,NULL,NULL,5,1,'{}',NULL,'2023-08-30 22:43:39.594609','2023-08-30 22:49:15.962497',NULL,NULL,NULL,0),
|
||||||
('LeEgBsYfjLU','yt','Vakuum',220,NULL,NULL,6,1,'{}',NULL,'2023-08-30 22:44:14.749107','2023-08-30 22:49:15.963926',NULL,NULL,NULL,0),
|
('LeEgBsYfjLU','yt','Vakuum',220000,false,NULL,NULL,6,1,'{}',NULL,'2023-08-30 22:44:14.749107','2023-08-30 22:49:15.963926',NULL,NULL,NULL,0),
|
||||||
('-i5XjMkQN8M','yt','Melodie',251,NULL,NULL,7,1,'{}',NULL,'2023-08-30 22:44:44.585095','2023-08-30 22:49:15.96526',NULL,NULL,NULL,0),
|
('-i5XjMkQN8M','yt','Melodie',251000,false,NULL,NULL,7,1,'{}',NULL,'2023-08-30 22:44:44.585095','2023-08-30 22:49:15.96526',NULL,NULL,NULL,0),
|
||||||
('DhlIZkoPsxg','yt','Du & Ich',238,NULL,NULL,8,1,'{}',NULL,'2023-08-30 22:45:20.711259','2023-08-30 22:49:15.966532',NULL,NULL,NULL,0),
|
('DhlIZkoPsxg','yt','Du & Ich',238000,false,NULL,NULL,8,1,'{}',NULL,'2023-08-30 22:45:20.711259','2023-08-30 22:49:15.966532',NULL,NULL,NULL,0),
|
||||||
('LCoomBMOkgU','yt','Schwerelos',198,NULL,NULL,9,1,'{}',NULL,'2023-08-30 22:46:13.885768','2023-08-30 22:49:15.967865',NULL,NULL,NULL,0),
|
('LCoomBMOkgU','yt','Schwerelos',198000,false,NULL,NULL,9,1,'{}',NULL,'2023-08-30 22:46:13.885768','2023-08-30 22:49:15.967865',NULL,NULL,NULL,0),
|
||||||
('c6Ot-Z3HEBo','yt','Lichtermeer',164,NULL,NULL,10,1,'{}',NULL,'2023-08-30 22:46:41.10766','2023-08-30 22:49:15.969191',NULL,NULL,NULL,0),
|
('c6Ot-Z3HEBo','yt','Lichtermeer',164000,false,NULL,NULL,10,1,'{}',NULL,'2023-08-30 22:46:41.10766','2023-08-30 22:49:15.969191',NULL,NULL,NULL,0),
|
||||||
('ybm_4hQG0ok','yt','Nachtzug',290,NULL,NULL,11,1,'{}',NULL,'2023-08-30 22:47:28.033803','2023-08-30 22:49:15.97051',NULL,NULL,NULL,0),
|
('ybm_4hQG0ok','yt','Nachtzug',290000,false,NULL,NULL,11,1,'{}',NULL,'2023-08-30 22:47:28.033803','2023-08-30 22:49:15.97051',NULL,NULL,NULL,0),
|
||||||
('DJKmtK5PmSY','yt','Rückenwind',263,NULL,NULL,12,1,'{}',NULL,'2023-08-30 22:47:54.822762','2023-08-30 22:49:15.971789',NULL,NULL,NULL,0),
|
('DJKmtK5PmSY','yt','Rückenwind',263000,false,NULL,NULL,12,1,'{}',NULL,'2023-08-30 22:47:54.822762','2023-08-30 22:49:15.971789',NULL,NULL,NULL,0),
|
||||||
('hWFarQmaQAQ','yt','Immer wenn wir uns sehn ("Das schönste Mädchen der Welt", Soundtrack)',186,NULL,NULL,1,2,'{}',NULL,'2023-08-30 23:00:09.653056','2023-08-30 23:00:09.653056',NULL,NULL,NULL,0),
|
('hWFarQmaQAQ','yt','Immer wenn wir uns sehn ("Das schönste Mädchen der Welt", Soundtrack)',186000,false,NULL,NULL,1,2,'{}',NULL,'2023-08-30 23:00:09.653056','2023-08-30 23:00:09.653056',NULL,NULL,NULL,0),
|
||||||
('tXb7WTkhE1c','yt','Das schönste Mädchen der Welt ("Das schönste Mädchen der Welt", Soundtrack)',142,NULL,NULL,1,3,'{}',NULL,'2023-08-30 23:00:09.653056','2023-08-30 23:00:09.653056',NULL,NULL,NULL,0),
|
('tXb7WTkhE1c','yt','Das schönste Mädchen der Welt ("Das schönste Mädchen der Welt", Soundtrack)',142000,false,NULL,NULL,1,3,'{}',NULL,'2023-08-30 23:00:09.653056','2023-08-30 23:00:09.653056',NULL,NULL,NULL,0),
|
||||||
('ZeerrnuLi5E','yt','Black Mamba',229,NULL,NULL,1,4,'{}',NULL,'2023-09-09 22:53:50.360159','2023-09-09 22:53:50.360159',NULL,NULL,NULL,0),
|
('ZeerrnuLi5E','yt','Black Mamba',229000,false,NULL,NULL,1,4,'{}',NULL,'2023-09-09 22:53:50.360159','2023-09-09 22:53:50.360159',NULL,NULL,NULL,0),
|
||||||
('OCgE2GSL1Pk','yt','Smoke Signals',197,NULL,NULL,NULL,5,'{}',NULL,'2023-09-09 22:53:50.360159','2023-09-09 22:53:50.360159',NULL,NULL,NULL,0),
|
('OCgE2GSL1Pk','yt','Smoke Signals',197000,false,NULL,NULL,NULL,5,'{}',NULL,'2023-09-09 22:53:50.360159','2023-09-09 22:53:50.360159',NULL,NULL,NULL,0),
|
||||||
('6485PhOtHzY','yt','Lieblingsmensch',190,NULL,NULL,1,6,'{}',NULL,'2023-09-09 22:53:50.360159','2023-09-09 22:53:50.360159',NULL,NULL,NULL,0),
|
('6485PhOtHzY','yt','Lieblingsmensch',190000,false,NULL,NULL,1,6,'{}',NULL,'2023-09-09 22:53:50.360159','2023-09-09 22:53:50.360159',NULL,NULL,NULL,0),
|
||||||
('WSBUeFdXiSs','yt','Leicht',206,NULL,NULL,NULL,7,'{}',NULL,'2023-09-09 22:53:50.360159','2023-09-09 22:53:50.360159',NULL,NULL,NULL,0);
|
('WSBUeFdXiSs','yt','Leicht',206000,false,NULL,NULL,NULL,7,'{}',NULL,'2023-09-09 22:53:50.360159','2023-09-09 22:53:50.360159',NULL,NULL,NULL,0);
|
||||||
|
|
||||||
INSERT INTO artists_tracks (artist_id,track_id) VALUES
|
INSERT INTO artists_tracks (artist_id,track_id) VALUES
|
||||||
(1,1),
|
(1,1),
|
||||||
|
|
|
@ -15,7 +15,7 @@ futures.workspace = true
|
||||||
time.workspace = true
|
time.workspace = true
|
||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
regex.workspace = true
|
regex.workspace = true
|
||||||
tracing.workspace = true
|
log.workspace = true
|
||||||
quick_cache.workspace = true
|
quick_cache.workspace = true
|
||||||
siphasher.workspace = true
|
siphasher.workspace = true
|
||||||
hex-literal.workspace = true
|
hex-literal.workspace = true
|
||||||
|
@ -25,6 +25,5 @@ tiraya-db.workspace = true
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
sqlx-database-tester.workspace = true
|
sqlx-database-tester.workspace = true
|
||||||
rstest.workspace = true
|
|
||||||
env_logger.workspace = true
|
env_logger.workspace = true
|
||||||
test-log.workspace = true
|
test-log.workspace = true
|
||||||
|
|
1
crates/extractor/rustypipe_cache.json
Normal file
1
crates/extractor/rustypipe_cache.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{"desktop_client":null,"music_client":{"last_update":"2023-09-20T17:47:33Z","data":{"version":"1.20230913.01.00-canary_control"}},"deobf":null}
|
|
@ -3,20 +3,22 @@
|
||||||
pub mod error;
|
pub mod error;
|
||||||
mod util;
|
mod util;
|
||||||
|
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, hash::Hasher, sync::Arc};
|
||||||
|
|
||||||
use error::ExtractorError;
|
use error::ExtractorError;
|
||||||
use futures::{StreamExt, TryStreamExt};
|
use futures::{StreamExt, TryStreamExt};
|
||||||
|
use hex_literal::hex;
|
||||||
use quick_cache::sync::Cache;
|
use quick_cache::sync::Cache;
|
||||||
use rustypipe::{client::RustyPipe, model::richtext::ToPlaintext};
|
use rustypipe::client::RustyPipe;
|
||||||
|
use siphasher::sip128::{Hasher128, SipHasher};
|
||||||
use sqlx::{Pool, Postgres};
|
use sqlx::{Pool, Postgres};
|
||||||
use time::{Date, Duration, OffsetDateTime};
|
use time::{Date, Duration, OffsetDateTime};
|
||||||
use tiraya_db::{
|
use tiraya_db::{
|
||||||
error::OptionalRes,
|
error::OptionalRes,
|
||||||
models::{
|
models::{
|
||||||
Album, AlbumNew, AlbumType, AlbumUpdate, Artist, ArtistNew, DatePrecision, EntityType, Id,
|
Album, AlbumNew, AlbumType, Artist, ArtistNew, DatePrecision, EntityType, Id, MusicService,
|
||||||
MusicService, Playlist, PlaylistImgType, PlaylistNew, PlaylistType, SrcId, SrcIdOwned,
|
Playlist, PlaylistImgType, PlaylistNew, PlaylistType, SrcId, SrcIdOwned, SyncData, Track,
|
||||||
SyncData, Track, TrackNew, TrackUpdate,
|
TrackNew,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,7 +37,6 @@ const ARTIST_STALE: Duration = Duration::hours(24);
|
||||||
const CONCURRENCY: usize = 4;
|
const CONCURRENCY: usize = 4;
|
||||||
const DB_CONCURRENCY: usize = 8;
|
const DB_CONCURRENCY: usize = 8;
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
struct SyncLastUpdate {
|
struct SyncLastUpdate {
|
||||||
id: i32,
|
id: i32,
|
||||||
state: LastUpdateState,
|
state: LastUpdateState,
|
||||||
|
@ -90,7 +91,6 @@ impl Extractor {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
|
||||||
pub async fn get_artist(&self, id: SrcId<'_>) -> Result<GetResult<Artist>, ExtractorError> {
|
pub async fn get_artist(&self, id: SrcId<'_>) -> Result<GetResult<Artist>, ExtractorError> {
|
||||||
let artist = Artist::get(id.id(), &self.db).await.to_optional().unwrap();
|
let artist = Artist::get(id.id(), &self.db).await.to_optional().unwrap();
|
||||||
let last_update = if let Some(artist) = artist {
|
let last_update = if let Some(artist) = artist {
|
||||||
|
@ -143,7 +143,6 @@ impl Extractor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
|
||||||
async fn update_yt_artist(
|
async fn update_yt_artist(
|
||||||
&self,
|
&self,
|
||||||
id: &str,
|
id: &str,
|
||||||
|
@ -194,7 +193,7 @@ impl Extractor {
|
||||||
|
|
||||||
let top_track_ids =
|
let top_track_ids =
|
||||||
futures::stream::iter(artist.tracks.into_iter().filter(|t| !t.is_video))
|
futures::stream::iter(artist.tracks.into_iter().filter(|t| !t.is_video))
|
||||||
.map(|track| async { self.import_yt_track(track, None, &[]).await })
|
.map(|track| async move { self.import_yt_track(track, None).await })
|
||||||
.buffered(CONCURRENCY)
|
.buffered(CONCURRENCY)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.await;
|
.await;
|
||||||
|
@ -203,14 +202,14 @@ impl Extractor {
|
||||||
.filter_map(|id| match id {
|
.filter_map(|id| match id {
|
||||||
Ok(id) => Some(id),
|
Ok(id) => Some(id),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("could not import artist track: {}", e);
|
log::error!("could not import artist track: {}", e);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let related_artist_ids = futures::stream::iter(artist.similar_artists)
|
let related_artist_ids = futures::stream::iter(artist.similar_artists)
|
||||||
.map(|artist| async { self.import_yt_artist_item(artist).await })
|
.map(|artist| async move { self.import_yt_artist_item(artist).await })
|
||||||
.buffered(DB_CONCURRENCY)
|
.buffered(DB_CONCURRENCY)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.await;
|
.await;
|
||||||
|
@ -219,14 +218,14 @@ impl Extractor {
|
||||||
.filter_map(|id| match id {
|
.filter_map(|id| match id {
|
||||||
Ok(id) => Some(id),
|
Ok(id) => Some(id),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("could not import related artist: {}", e);
|
log::error!("could not import related artist: {}", e);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let related_playlist_ids = futures::stream::iter(artist.playlists)
|
let related_playlist_ids = futures::stream::iter(artist.playlists)
|
||||||
.map(|pl| async { self.import_yt_playlist_item(pl).await })
|
.map(|pl| async move { self.import_yt_playlist_item(pl).await })
|
||||||
.buffered(DB_CONCURRENCY)
|
.buffered(DB_CONCURRENCY)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.await;
|
.await;
|
||||||
|
@ -235,7 +234,7 @@ impl Extractor {
|
||||||
.filter_map(|id| match id {
|
.filter_map(|id| match id {
|
||||||
Ok(id) => Some(id),
|
Ok(id) => Some(id),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
tracing::error!("could not import artist playlist: {}", e);
|
log::error!("could not import artist playlist: {}", e);
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -277,7 +276,7 @@ impl Extractor {
|
||||||
// Insert all albums
|
// Insert all albums
|
||||||
futures::stream::iter(artist.albums)
|
futures::stream::iter(artist.albums)
|
||||||
.map(Ok)
|
.map(Ok)
|
||||||
.try_for_each_concurrent(DB_CONCURRENCY, |album| async {
|
.try_for_each_concurrent(DB_CONCURRENCY, |album| async move {
|
||||||
self.import_yt_album_item(album).await
|
self.import_yt_album_item(album).await
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -289,32 +288,35 @@ impl Extractor {
|
||||||
};
|
};
|
||||||
Artist::set_last_sync(artist_id, this_update_state.into(), &self.db).await?;
|
Artist::set_last_sync(artist_id, this_update_state.into(), &self.db).await?;
|
||||||
|
|
||||||
tracing::info!("fetched artist [yt:{}] {}", artist.id, artist.name);
|
|
||||||
Ok(GetResult::fetched(artist_id))
|
Ok(GetResult::fetched(artist_id))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self))]
|
pub async fn update_yt_artist_albums(&self, id: i32) -> Result<(), ExtractorError> {
|
||||||
pub async fn fetch_yt_artist_albums(&self, id: i32) -> Result<(), ExtractorError> {
|
let dirty_albums = Artist::dirty_album_ids(id, &self.db).await?;
|
||||||
for _ in 0..2 {
|
let more_albums = futures::stream::iter(dirty_albums)
|
||||||
let dirty_albums = Artist::dirty_album_ids(id, &self.db).await?;
|
.map(|id| async move {
|
||||||
let has_more = futures::stream::iter(dirty_albums)
|
match self.import_yt_album(&id.src_id).await {
|
||||||
.map(|id| async move {
|
Ok(more) => more,
|
||||||
match self.fetch_yt_album(&id.src_id).await {
|
Err(e) => {
|
||||||
Ok(more) => more.1,
|
log::error!("could not import album [yt:{}]: {}", id.src_id, e);
|
||||||
Err(e) => {
|
Vec::new()
|
||||||
tracing::error!("could not import album [yt:{}]: {}", id.src_id, e);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.buffer_unordered(DB_CONCURRENCY)
|
})
|
||||||
.collect::<Vec<_>>()
|
.buffer_unordered(DB_CONCURRENCY)
|
||||||
.await;
|
.collect::<Vec<_>>()
|
||||||
if has_more.iter().all(|x| !x) {
|
.await;
|
||||||
break;
|
|
||||||
}
|
futures::stream::iter(more_albums.into_iter().flatten())
|
||||||
}
|
.for_each_concurrent(DB_CONCURRENCY, |album| async move {
|
||||||
tracing::info!("updated artist albums for #{id}");
|
match self.import_yt_album(&album.id).await {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("could not import album [yt:{}]: {}", album.id, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -333,7 +335,6 @@ impl Extractor {
|
||||||
&self,
|
&self,
|
||||||
track: rpmodel::TrackItem,
|
track: rpmodel::TrackItem,
|
||||||
album_id: Option<i32>,
|
album_id: Option<i32>,
|
||||||
album_artists: &[ArtistIdName],
|
|
||||||
) -> Result<i32, ExtractorError> {
|
) -> Result<i32, ExtractorError> {
|
||||||
if album_id.is_none() {
|
if album_id.is_none() {
|
||||||
if let Some(id) =
|
if let Some(id) =
|
||||||
|
@ -343,9 +344,7 @@ impl Extractor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (artists, ul_artists) = self
|
let (artists, ul_artists) = util::split_yt_artists(track.artists);
|
||||||
.split_yt_artists(track.artists, track.artist_id, album_artists)
|
|
||||||
.await;
|
|
||||||
|
|
||||||
let album_id = match album_id {
|
let album_id = match album_id {
|
||||||
Some(album_id) => album_id,
|
Some(album_id) => album_id,
|
||||||
|
@ -389,7 +388,8 @@ impl Extractor {
|
||||||
src_id: &track.id,
|
src_id: &track.id,
|
||||||
service: MusicService::YouTube,
|
service: MusicService::YouTube,
|
||||||
name: &track.name,
|
name: &track.name,
|
||||||
duration: track.duration.and_then(|v| v.try_into().ok()),
|
duration: track.duration.and_then(|v| (v * 1000).try_into().ok()),
|
||||||
|
duration_ms: false,
|
||||||
album_id,
|
album_id,
|
||||||
album_pos: track.track_nr.and_then(|v| v.try_into().ok()),
|
album_pos: track.track_nr.and_then(|v| v.try_into().ok()),
|
||||||
ul_artists: ul_artists.as_deref(),
|
ul_artists: ul_artists.as_deref(),
|
||||||
|
@ -400,42 +400,39 @@ impl Extractor {
|
||||||
Track::set_artists(track_id, &artist_ids, &mut tx).await?;
|
Track::set_artists(track_id, &artist_ids, &mut tx).await?;
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
|
|
||||||
tracing::debug!("imported track [yt:{}] {}", track.id, track.name);
|
|
||||||
Ok(track_id)
|
Ok(track_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import a list of YT Music artist ids
|
|
||||||
async fn import_yt_artist_ids(
|
async fn import_yt_artist_ids(
|
||||||
&self,
|
&self,
|
||||||
artists: &[ArtistIdName],
|
artists: &[ArtistIdName],
|
||||||
) -> Result<Vec<i32>, ExtractorError> {
|
) -> Result<Vec<Id<'_>>, ExtractorError> {
|
||||||
futures::stream::iter(artists)
|
futures::stream::iter(artists)
|
||||||
.map(|aid| async { self.import_yt_artist_id(aid).await })
|
.map(|aid| async move { self.import_yt_artist_id(aid).await.map(Id::Db) })
|
||||||
.buffered(CONCURRENCY)
|
.buffered(CONCURRENCY)
|
||||||
.try_collect::<Vec<_>>()
|
.try_collect::<Vec<_>>()
|
||||||
.await
|
.await
|
||||||
.map_err(ExtractorError::from)
|
.map_err(ExtractorError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import a YT Music artist id (artist with ID and name)
|
|
||||||
async fn import_yt_artist_id(&self, aid: &ArtistIdName) -> Result<i32, ExtractorError> {
|
async fn import_yt_artist_id(&self, aid: &ArtistIdName) -> Result<i32, ExtractorError> {
|
||||||
let id_owned = SrcIdOwned(aid.id.to_owned(), MusicService::YouTube);
|
let id_owned = SrcIdOwned(aid.id.to_owned(), MusicService::YouTube);
|
||||||
self.artist_cache
|
self.artist_cache
|
||||||
.get_or_insert_async(&id_owned, async {
|
.get_or_insert_async(&id_owned, async move {
|
||||||
let artist = ArtistNew {
|
let artist = ArtistNew {
|
||||||
src_id: &aid.id,
|
src_id: &aid.id,
|
||||||
service: MusicService::YouTube,
|
service: MusicService::YouTube,
|
||||||
name: &aid.name,
|
name: &aid.name,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let artist_id = artist.upsert_recessive(&self.db).await?;
|
artist
|
||||||
tracing::debug!("imported artist id [yt:{}] {}", aid.id, aid.name);
|
.upsert_recessive(&self.db)
|
||||||
Ok(artist_id)
|
.await
|
||||||
|
.map_err(ExtractorError::from)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import a YT Music artist item (artist with name, image and subscriber count)
|
|
||||||
async fn import_yt_artist_item(
|
async fn import_yt_artist_item(
|
||||||
&self,
|
&self,
|
||||||
artist: rpmodel::ArtistItem,
|
artist: rpmodel::ArtistItem,
|
||||||
|
@ -452,13 +449,11 @@ impl Extractor {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let artist_id = n_artist.upsert_recessive(&self.db).await?;
|
let artist_id = n_artist.upsert_recessive(&self.db).await?;
|
||||||
tracing::debug!("imported artist item [yt:{}] {}", artist.id, artist.name);
|
|
||||||
self.artist_cache
|
self.artist_cache
|
||||||
.insert(SrcIdOwned(artist.id, MusicService::YouTube), artist_id);
|
.insert(SrcIdOwned(artist.id, MusicService::YouTube), artist_id);
|
||||||
Ok(artist_id)
|
Ok(artist_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import a YT Music album item (album from artist page)
|
|
||||||
async fn import_yt_album_item(&self, album: rpmodel::AlbumItem) -> Result<(), ExtractorError> {
|
async fn import_yt_album_item(&self, album: rpmodel::AlbumItem) -> Result<(), ExtractorError> {
|
||||||
// Return if the album was already imported
|
// Return if the album was already imported
|
||||||
if Album::get_id_clean(SrcId(&album.id, MusicService::YouTube), &self.db)
|
if Album::get_id_clean(SrcId(&album.id, MusicService::YouTube), &self.db)
|
||||||
|
@ -468,9 +463,7 @@ impl Extractor {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let (artists, ul_artists) = self
|
let (artists, ul_artists) = util::split_yt_artists(album.artists);
|
||||||
.split_yt_artists(album.artists, album.artist_id, &[])
|
|
||||||
.await;
|
|
||||||
let artist_ids = self.import_yt_artist_ids(&artists).await?;
|
let artist_ids = self.import_yt_artist_ids(&artists).await?;
|
||||||
|
|
||||||
let image_url = util::get_image_url(&album.cover, false);
|
let image_url = util::get_image_url(&album.cover, false);
|
||||||
|
@ -493,11 +486,9 @@ impl Extractor {
|
||||||
Album::add_artists(album_id, &artist_ids, &mut tx).await?;
|
Album::add_artists(album_id, &artist_ids, &mut tx).await?;
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
|
|
||||||
tracing::debug!("imported album item [yt:{}] {}", album.id, album.name);
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Import a YT Music playlist item
|
|
||||||
async fn import_yt_playlist_item(
|
async fn import_yt_playlist_item(
|
||||||
&self,
|
&self,
|
||||||
pl: rpmodel::MusicPlaylistItem,
|
pl: rpmodel::MusicPlaylistItem,
|
||||||
|
@ -528,46 +519,30 @@ impl Extractor {
|
||||||
image_type: Some(PlaylistImgType::Custom),
|
image_type: Some(PlaylistImgType::Custom),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
let playlist_id = playlist_n.upsert(&self.db).await?;
|
playlist_n
|
||||||
|
.upsert(&self.db)
|
||||||
tracing::debug!("imported playlist item [yt:{}] {}", pl.id, pl.name);
|
.await
|
||||||
Ok(playlist_id)
|
.map_err(ExtractorError::from)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch and import a YT Music album and return its ID and whether the album contains variants
|
async fn import_yt_album(&self, id: &str) -> Result<Vec<rpmodel::AlbumItem>, ExtractorError> {
|
||||||
async fn fetch_yt_album(&self, id: &str) -> Result<(i32, bool), ExtractorError> {
|
|
||||||
// Return if the album was already imported
|
|
||||||
if let Some(album_id) =
|
|
||||||
Album::get_id_clean(SrcId(id, MusicService::YouTube), &self.db).await?
|
|
||||||
{
|
|
||||||
return Ok((album_id, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
let album = self.rp.query().music_album(id).await?;
|
let album = self.rp.query().music_album(id).await?;
|
||||||
|
|
||||||
let (artists, ul_artists) = self
|
let (artists, ul_artists) = util::split_yt_artists(album.artists);
|
||||||
.split_yt_artists(album.artists, album.artist_id, &[])
|
|
||||||
.await;
|
|
||||||
let image_url = util::get_image_url(&album.cover, false);
|
let image_url = util::get_image_url(&album.cover, false);
|
||||||
|
|
||||||
let artist_ids = self.import_yt_artist_ids(&artists).await?;
|
let artist_ids = self.import_yt_artist_ids(&artists).await?;
|
||||||
|
|
||||||
// Get album hash
|
// Get album hash
|
||||||
let mut hasher = util::AlbumHasher::new();
|
let mut hasher = SipHasher::new_with_key(&hex!("e0060fd1ea207d8f43d2bf9bcae63f65"));
|
||||||
for track in &album.tracks {
|
for track in &album.tracks {
|
||||||
hasher.add_track(&track.name, track.duration.unwrap_or_default());
|
hasher.write(track.name.as_bytes());
|
||||||
|
hasher.write_u32(track.duration.unwrap_or_default());
|
||||||
}
|
}
|
||||||
let album_hash = hasher.finish();
|
let album_hash = hasher.finish128().as_bytes();
|
||||||
|
|
||||||
let hidden = if let Some(id) = artist_ids.first() {
|
|
||||||
!Album::ids_from_hash(*id, &album_hash, &self.db)
|
|
||||||
.await?
|
|
||||||
.is_empty()
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
// Insert album
|
// Insert album
|
||||||
|
// TODO: accurate release date
|
||||||
let album_n = AlbumNew {
|
let album_n = AlbumNew {
|
||||||
src_id: &album.id,
|
src_id: &album.id,
|
||||||
service: MusicService::YouTube,
|
service: MusicService::YouTube,
|
||||||
|
@ -580,7 +555,6 @@ impl Extractor {
|
||||||
ul_artists: ul_artists.as_deref(),
|
ul_artists: ul_artists.as_deref(),
|
||||||
by_va: album.by_va,
|
by_va: album.by_va,
|
||||||
image_url: image_url.as_deref(),
|
image_url: image_url.as_deref(),
|
||||||
hidden,
|
|
||||||
album_hash: Some(&album_hash),
|
album_hash: Some(&album_hash),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
@ -589,176 +563,18 @@ impl Extractor {
|
||||||
Album::set_artists(album_id, &artist_ids, &mut tx).await?;
|
Album::set_artists(album_id, &artist_ids, &mut tx).await?;
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
|
|
||||||
let first_track_id = album.tracks.first().map(|t| t.id.to_owned());
|
|
||||||
|
|
||||||
// Insert tracks
|
// Insert tracks
|
||||||
futures::stream::iter(album.tracks.into_iter().map(Ok::<_, ExtractorError>))
|
futures::stream::iter(album.tracks.into_iter().map(Ok::<_, ExtractorError>))
|
||||||
.try_for_each_concurrent(DB_CONCURRENCY, |track| async {
|
.try_for_each_concurrent(DB_CONCURRENCY, |track| async move {
|
||||||
self.import_yt_track(track, Some(album_id), &artists)
|
self.import_yt_track(track, Some(album_id)).await?;
|
||||||
.await?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// Insert album variants
|
|
||||||
let has_variants = !album.variants.is_empty();
|
|
||||||
futures::stream::iter(album.variants.into_iter().map(Ok::<_, ExtractorError>))
|
|
||||||
.try_for_each_concurrent(DB_CONCURRENCY, |album| async {
|
|
||||||
self.import_yt_album_item(album).await
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// Fetch more accurate album date
|
|
||||||
if let Some(first_track_id) = first_track_id {
|
|
||||||
self.fetch_track_details(album_id, album.year, &first_track_id, true)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark album clean
|
// Mark album clean
|
||||||
Album::mark_dirty(album_id, false, &self.db).await?;
|
Album::mark_dirty(album_id, false, &self.db).await?;
|
||||||
|
|
||||||
tracing::info!("imported album [yt:{}] {}", album.id, album.name);
|
Ok(album.variants)
|
||||||
Ok((album_id, has_variants))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Fetch the description text and upload date of a YT video and store it in the database.
|
|
||||||
async fn fetch_track_details(
|
|
||||||
&self,
|
|
||||||
album_id: i32,
|
|
||||||
release_year: Option<u16>,
|
|
||||||
yt_id: &str,
|
|
||||||
ytm_track: bool,
|
|
||||||
) -> Result<(), ExtractorError> {
|
|
||||||
let details = self.rp.query().video_details(yt_id).await?;
|
|
||||||
let description = details.description.to_plaintext();
|
|
||||||
|
|
||||||
let release_date_detail = if ytm_track {
|
|
||||||
util::extract_yt_release_date(&description, details.publish_date)
|
|
||||||
} else {
|
|
||||||
details.publish_date.map(|d| (d.date(), DatePrecision::Day))
|
|
||||||
};
|
|
||||||
|
|
||||||
let release_date = release_date_detail.filter(|d| match release_year {
|
|
||||||
Some(year) => d.0.year() == i32::from(year),
|
|
||||||
None => true,
|
|
||||||
});
|
|
||||||
|
|
||||||
let t_upd = TrackUpdate {
|
|
||||||
description: Some(Some(&description)),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
t_upd
|
|
||||||
.update(Id::Src(yt_id, MusicService::YouTube), &self.db)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
if let Some(release_date) = release_date {
|
|
||||||
let b_upd = AlbumUpdate {
|
|
||||||
release_date: Some(Some(release_date.0)),
|
|
||||||
release_date_precision: Some(Some(release_date.1)),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
b_upd.update(Id::Db(album_id), &self.db).await?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the name of a YouTube artist with the given ID (either from DB or the RSS feed)
|
|
||||||
async fn get_yt_artist_name(&self, id: &str) -> Result<String, ExtractorError> {
|
|
||||||
let artist = Artist::get(Id::Src(id, MusicService::YouTube), &self.db)
|
|
||||||
.await
|
|
||||||
.to_optional()?;
|
|
||||||
if let Some(artist) = artist {
|
|
||||||
Ok(artist.name)
|
|
||||||
} else {
|
|
||||||
let feed = self.rp.query().channel_rss(id).await?;
|
|
||||||
let name = feed.name.strip_suffix(" - Topic").unwrap_or(&feed.name);
|
|
||||||
self.import_yt_artist_id(&ArtistIdName {
|
|
||||||
id: id.to_owned(),
|
|
||||||
name: name.to_owned(),
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
Ok(name.to_owned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert track/album artists scraped from YouTube into the format used by Tiraya
|
|
||||||
///
|
|
||||||
/// YouTube Music's metadata is incomplete and there are a lot of tracks with
|
|
||||||
/// unlinked artists (the user is only shown a list of artists that are not
|
|
||||||
/// clickable links).
|
|
||||||
///
|
|
||||||
/// The YouTube interface also includes the "Go to artist" button in the dropdown
|
|
||||||
/// menu which may be present even if there are only unlinked artists shown.
|
|
||||||
///
|
|
||||||
/// So we can apply a few tricks to improve the metadata:
|
|
||||||
///
|
|
||||||
/// - If the artist ID from the dropdown is not included in the list of artists,
|
|
||||||
/// fetch its name and add it to the beginning
|
|
||||||
/// - Add the album artists (if given) to the list of artists if they appear in the
|
|
||||||
/// list of unlinked artists
|
|
||||||
///
|
|
||||||
/// Example albums for testing:
|
|
||||||
/// - https://music.youtube.com/browse/MPREb_GXN2zsSMUKJ
|
|
||||||
/// - https://music.youtube.com/browse/MPREb_98weK02o4DO
|
|
||||||
/// - https://music.youtube.com/browse/MPREb_1pFivcTlTls
|
|
||||||
async fn split_yt_artists(
|
|
||||||
&self,
|
|
||||||
artists: Vec<rpmodel::ArtistId>,
|
|
||||||
artist_id: Option<String>,
|
|
||||||
album_artists: &[ArtistIdName],
|
|
||||||
) -> (Vec<ArtistIdName>, Option<Vec<String>>) {
|
|
||||||
let mut ul_artists = Vec::new();
|
|
||||||
let mut add_aid = true;
|
|
||||||
|
|
||||||
let mut artists = artists
|
|
||||||
.into_iter()
|
|
||||||
.filter_map(|a| match a.id {
|
|
||||||
Some(id) => {
|
|
||||||
if Some(&id) == artist_id.as_ref() {
|
|
||||||
add_aid = false;
|
|
||||||
}
|
|
||||||
Some(ArtistIdName { id, name: a.name })
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
ul_artists.push(a.name);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
// Artist from artist_id field is not contained in artists list
|
|
||||||
if add_aid {
|
|
||||||
if let Some(artist_id) = artist_id {
|
|
||||||
if let Ok(name) = self.get_yt_artist_name(&artist_id).await {
|
|
||||||
util::extract_ul_artist(&mut ul_artists, &name);
|
|
||||||
artists.insert(
|
|
||||||
0,
|
|
||||||
ArtistIdName {
|
|
||||||
id: artist_id,
|
|
||||||
name,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to match album artists(s)
|
|
||||||
if !ul_artists.is_empty() {
|
|
||||||
for aa in album_artists {
|
|
||||||
if util::extract_ul_artist(&mut ul_artists, &aa.name) {
|
|
||||||
artists.insert(0, aa.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(
|
|
||||||
artists,
|
|
||||||
if ul_artists.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(ul_artists)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -777,7 +593,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
// #[test_tracing::test]
|
// #[test_log::test]
|
||||||
async fn import_album() {
|
async fn import_album() {
|
||||||
sqlx_database_tester::dotenv::dotenv().unwrap();
|
sqlx_database_tester::dotenv::dotenv().unwrap();
|
||||||
let url = std::env::var("DATABASE_URL").unwrap();
|
let url = std::env::var("DATABASE_URL").unwrap();
|
||||||
|
@ -790,7 +606,7 @@ mod tests {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if artist_res.fetched {
|
if artist_res.fetched {
|
||||||
extractor
|
extractor
|
||||||
.fetch_yt_artist_albums(artist_res.c.id)
|
.update_yt_artist_albums(artist_res.c.id)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,39 @@
|
||||||
use std::{borrow::Cow, hash::Hasher};
|
use std::borrow::Cow;
|
||||||
|
|
||||||
use hex_literal::hex;
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use rustypipe::model as rpmodel;
|
use rustypipe::model as rpmodel;
|
||||||
use siphasher::sip128::{Hasher128, SipHasher};
|
use tiraya_db::models::{AlbumType, SyncData, SyncError};
|
||||||
use time::{Date, Duration, OffsetDateTime};
|
|
||||||
use tiraya_db::models::{AlbumType, DatePrecision, SyncData, SyncError};
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct ArtistIdName {
|
pub struct ArtistIdName {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn split_yt_artists(
|
||||||
|
artists: Vec<rpmodel::ArtistId>,
|
||||||
|
) -> (Vec<ArtistIdName>, Option<Vec<String>>) {
|
||||||
|
let mut ul_artists = Vec::new();
|
||||||
|
let artists = artists
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(|a| match a.id {
|
||||||
|
Some(id) => Some(ArtistIdName { id, name: a.name }),
|
||||||
|
None => {
|
||||||
|
ul_artists.push(a.name);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
(
|
||||||
|
artists,
|
||||||
|
if ul_artists.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(ul_artists)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
static YTM_IMAGE_REGEX: Lazy<Regex> =
|
static YTM_IMAGE_REGEX: Lazy<Regex> =
|
||||||
Lazy::new(|| Regex::new(r"^https://[a-z\d]+.googleusercontent.com/[\w\d_/-]+").unwrap());
|
Lazy::new(|| Regex::new(r"^https://[a-z\d]+.googleusercontent.com/[\w\d_/-]+").unwrap());
|
||||||
|
|
||||||
|
@ -67,96 +87,11 @@ pub fn map_album_type(album_type: rpmodel::AlbumType) -> AlbumType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static RELEASE_DATE_REGEX: Lazy<Regex> =
|
/*
|
||||||
Lazy::new(|| Regex::new(r"Released on: (\d{4}-\d{2}-\d{2})").unwrap());
|
|
||||||
const YMD_FORMAT: &[time::format_description::FormatItem] =
|
|
||||||
time::macros::format_description!("[year]-[month]-[day]");
|
|
||||||
|
|
||||||
pub fn extract_yt_release_date(
|
|
||||||
description: &str,
|
|
||||||
upload_date: Option<OffsetDateTime>,
|
|
||||||
) -> Option<(Date, DatePrecision)> {
|
|
||||||
RELEASE_DATE_REGEX
|
|
||||||
.captures(description)
|
|
||||||
.and_then(|cap| {
|
|
||||||
let raw_date = &cap[1];
|
|
||||||
Date::parse(raw_date, YMD_FORMAT).ok()
|
|
||||||
})
|
|
||||||
.map(|release_date| {
|
|
||||||
if let Some(upload_date) = upload_date {
|
|
||||||
// Prefer the video upload date if it lies within 4 days of the release date
|
|
||||||
let upload_date = upload_date.date();
|
|
||||||
let diff = (upload_date - release_date).abs();
|
|
||||||
if diff < Duration::days(4) {
|
|
||||||
return (upload_date, DatePrecision::Day);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
(
|
|
||||||
release_date,
|
|
||||||
// If a date component is unknown, YT will show a 1 in that place
|
|
||||||
if release_date.day() == 1 {
|
|
||||||
if release_date.month() == time::Month::January {
|
|
||||||
DatePrecision::Year
|
|
||||||
} else {
|
|
||||||
DatePrecision::Month
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DatePrecision::Day
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.or_else(|| upload_date.map(|d| (d.date(), DatePrecision::Day)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to extract the given artist name from a list of unlinked artists
|
|
||||||
///
|
|
||||||
/// Returns true if the artist was extracted
|
|
||||||
pub fn extract_ul_artist(ul_artists: &mut [String], name: &str) -> bool {
|
|
||||||
// TODO: add support for other languages
|
|
||||||
static END_RE: Lazy<Regex> = Lazy::new(|| Regex::new(",? *(and|&)? *$").unwrap());
|
|
||||||
static START_RE: Lazy<Regex> = Lazy::new(|| Regex::new("^,? *(and|&)? *").unwrap());
|
|
||||||
|
|
||||||
for ul_a in ul_artists.iter_mut() {
|
|
||||||
if let Some((a, b)) = ul_a.split_once(name) {
|
|
||||||
// Trim end of first part
|
|
||||||
if a.is_empty() {
|
|
||||||
*ul_a = START_RE.replace(b, "").to_string()
|
|
||||||
} else {
|
|
||||||
*ul_a = END_RE.replace(a, "").to_string() + b;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct AlbumHasher(SipHasher);
|
|
||||||
|
|
||||||
impl AlbumHasher {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self(SipHasher::new_with_key(&hex!(
|
|
||||||
"e0060fd1ea207d8f43d2bf9bcae63f65"
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_track(&mut self, name: &str, duration: u32) {
|
|
||||||
self.0.write(name.as_bytes());
|
|
||||||
self.0.write_u32(duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn finish(&self) -> [u8; 16] {
|
|
||||||
self.0.finish128().as_bytes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
use rstest::rstest;
|
|
||||||
use time::macros::{date, datetime};
|
|
||||||
|
|
||||||
/*
|
|
||||||
fn get_image_url_album() {
|
fn get_image_url_album() {
|
||||||
let images = [rpmodel::Thumbnail {
|
let images = [rpmodel::Thumbnail {
|
||||||
url: "https://lh3.googleusercontent.com/46FajCeiWBIdYQmkVkDvNVpprs-ihk9hVulFc8v-pUPEfgUU2uzGmc45vO-sZCYQiEasgo21cbengDYIAQ=w226-h226-l90-rj",
|
url: "https://lh3.googleusercontent.com/46FajCeiWBIdYQmkVkDvNVpprs-ihk9hVulFc8v-pUPEfgUU2uzGmc45vO-sZCYQiEasgo21cbengDYIAQ=w226-h226-l90-rj",
|
||||||
|
@ -164,57 +99,5 @@ mod tests {
|
||||||
height: 226,
|
height: 226,
|
||||||
}];
|
}];
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case("Released on: 2016-04-22", Some(datetime!(2016-05-10 0:0 UTC)), Some((date!(2016-04-22), DatePrecision::Day)))]
|
|
||||||
#[case("", Some(datetime!(2016-05-10 0:0 UTC)), Some((date!(2016-05-10), DatePrecision::Day)))]
|
|
||||||
#[case("Released on: 2016-04-22", Some(datetime!(2016-4-20 0:0 UTC)), Some((date!(2016-04-20), DatePrecision::Day)))]
|
|
||||||
#[case("Released on: 2016-04-20", Some(datetime!(2016-4-22 0:0 UTC)), Some((date!(2016-04-22), DatePrecision::Day)))]
|
|
||||||
#[case("Released on: 2016-04-01", Some(datetime!(2016-4-22 0:0 UTC)), Some((date!(2016-04-01), DatePrecision::Month)))]
|
|
||||||
#[case("Released on: 2016-01-01", Some(datetime!(2016-4-22 0:0 UTC)), Some((date!(2016-01-01), DatePrecision::Year)))]
|
|
||||||
#[case("Released on: 2016-04-20", None, Some((date!(2016-04-20), DatePrecision::Day)))]
|
|
||||||
#[case("", None, None)]
|
|
||||||
fn t_extract_yt_release_date(
|
|
||||||
#[case] description: &str,
|
|
||||||
#[case] upload_date: Option<OffsetDateTime>,
|
|
||||||
#[case] expect: Option<(Date, DatePrecision)>,
|
|
||||||
) {
|
|
||||||
let res = extract_yt_release_date(description, upload_date);
|
|
||||||
assert_eq!(res, expect);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case(&["Aurelio Fierro, Furio Rendine, Vincenzo de Crescenzo"], "Aurelio Fierro", &["Furio Rendine, Vincenzo de Crescenzo"])]
|
|
||||||
#[case(&["Miley Cyrus, Swae Lee, & Mike WiLL Made-It"], "Swae Lee", &["Miley Cyrus, & Mike WiLL Made-It"])]
|
|
||||||
#[case(&["Miley Cyrus, Swae Lee, & Mike WiLL Made-It"], "Mike WiLL Made-It", &["Miley Cyrus, Swae Lee"])]
|
|
||||||
#[case(&["Miley Cyrus, Swae Lee, & Mike WiLL Made-It"], "foobar", &["Miley Cyrus, Swae Lee, & Mike WiLL Made-It"])]
|
|
||||||
#[case(&["Miley Cyrus"], "Miley Cyrus", &[""])]
|
|
||||||
fn t_extract_ul_artist(
|
|
||||||
#[case] ul_artists: &[&str],
|
|
||||||
#[case] name: &str,
|
|
||||||
#[case] expect: &[&str],
|
|
||||||
) {
|
|
||||||
let mut art = ul_artists.iter().map(|s| s.to_string()).collect::<Vec<_>>();
|
|
||||||
assert_eq!(extract_ul_artist(&mut art, name), ul_artists != expect);
|
|
||||||
assert_eq!(art, expect);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[rstest]
|
|
||||||
#[case(&[("Lieblingsmensch", 190)], hex!("290deeaedd84dbdb71d2f62979eeedc8"))]
|
|
||||||
#[case(&[
|
|
||||||
("Ich wache auf", 221),
|
|
||||||
("Waldbrand", 208),
|
|
||||||
("Verlernt", 223),
|
|
||||||
("In Farbe", 221),
|
|
||||||
("Stadt im Hinterland", 197)
|
|
||||||
], hex!("ffbd4cad41df8e99f3d0a3d629f5a5d5"))]
|
|
||||||
fn album_hash(#[case] tracks: &[(&str, u32)], #[case] expect: [u8; 16]) {
|
|
||||||
let mut hasher = AlbumHasher::new();
|
|
||||||
for t in tracks {
|
|
||||||
hasher.add_track(t.0, t.1);
|
|
||||||
}
|
|
||||||
let hash = hasher.finish();
|
|
||||||
assert_eq!(hash, expect, "got {hash:x?}");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -17,4 +17,4 @@ rustypipe.workspace = true
|
||||||
sqlx.workspace = true
|
sqlx.workspace = true
|
||||||
dotenvy.workspace = true
|
dotenvy.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
tracing-subscriber.workspace = true
|
env_logger.workspace = true
|
||||||
|
|
|
@ -6,7 +6,7 @@ use tiraya_extractor::Extractor;
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
dotenvy::dotenv().unwrap();
|
dotenvy::dotenv().unwrap();
|
||||||
tracing_subscriber::fmt::init();
|
env_logger::init();
|
||||||
|
|
||||||
let url = std::env::var("DATABASE_URL").unwrap();
|
let url = std::env::var("DATABASE_URL").unwrap();
|
||||||
let pool = PgPool::connect(&url).await.unwrap();
|
let pool = PgPool::connect(&url).await.unwrap();
|
||||||
|
@ -20,7 +20,7 @@ async fn main() {
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
if artist_res.fetched {
|
if artist_res.fetched {
|
||||||
ext.fetch_yt_artist_albums(artist_res.c.id).await.unwrap();
|
ext.update_yt_artist_albums(artist_res.c.id).await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
dbg!(artist_res.c);
|
dbg!(artist_res.c);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue