First draft of musicbrainz interface
Some checks failed
Cargo CI / Build and Test (pull_request) Failing after 1m37s
Cargo CI / Lint (pull_request) Failing after 16s

Some reorganisation

Remove unnecessary trait

Basic example working

Handle errors

Handle dates

Expand scope of MusicBrainz reference

Type the musicbrainz refs

Explicit constructors for str

Handle MBIDs for albums

Add search to the API

Handle primary and secondary types

Simplify AlbumDate

Passing unit tests

Tests pass

Prevent compiler/clippy warnings

Finish unit tests

Clippy

Remove old deserialize version
This commit is contained in:
Wojciech Kozlowski 2024-03-10 09:43:53 +01:00
parent c53ba8f35f
commit 473825b396
45 changed files with 2346 additions and 561 deletions

593
Cargo.lock generated
View File

@ -91,6 +91,12 @@ dependencies = [
"rustc-demangle", "rustc-demangle",
] ]
[[package]]
name = "base64"
version = "0.21.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "1.3.2" version = "1.3.2"
@ -103,6 +109,12 @@ version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "bumpalo"
version = "3.15.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.5.0" version = "1.5.0"
@ -167,6 +179,22 @@ dependencies = [
"static_assertions", "static_assertions",
] ]
[[package]]
name = "core-foundation"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]] [[package]]
name = "crossterm" name = "crossterm"
version = "0.27.0" version = "0.27.0"
@ -204,6 +232,21 @@ version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
[[package]]
name = "encoding_rs"
version = "0.8.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7268b386296a025e474d5140678f75d6de9493ae55a5d709eeb9dd08149945e1"
dependencies = [
"cfg-if",
]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.8" version = "0.3.8"
@ -220,6 +263,27 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.1" version = "1.2.1"
@ -235,12 +299,79 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa" checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
[[package]]
name = "futures-channel"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
dependencies = [
"futures-core",
]
[[package]]
name = "futures-core"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
name = "futures-io"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1"
[[package]]
name = "futures-sink"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5"
[[package]]
name = "futures-task"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004"
[[package]]
name = "futures-util"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
dependencies = [
"futures-core",
"futures-io",
"futures-task",
"memchr",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.28.1" version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]]
name = "h2"
version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9"
dependencies = [
"bytes",
"fnv",
"futures-core",
"futures-sink",
"futures-util",
"http",
"indexmap",
"slab",
"tokio",
"tokio-util",
"tracing",
]
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.3" version = "0.14.3"
@ -275,6 +406,77 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "http"
version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "http-body"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2"
dependencies = [
"bytes",
"http",
"pin-project-lite",
]
[[package]]
name = "httparse"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "hyper"
version = "0.14.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80"
dependencies = [
"bytes",
"futures-channel",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"httparse",
"httpdate",
"itoa",
"pin-project-lite",
"socket2",
"tokio",
"tower-service",
"tracing",
"want",
]
[[package]]
name = "hyper-tls"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [
"bytes",
"hyper",
"native-tls",
"tokio",
"tokio-native-tls",
]
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.5.0" version = "0.5.0"
@ -285,12 +487,28 @@ dependencies = [
"unicode-normalization", "unicode-normalization",
] ]
[[package]]
name = "indexmap"
version = "2.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b0b929d511467233429c45a44ac1dcaa21ba0f5ba11e4879e6ed28ddb4f9df4"
dependencies = [
"equivalent",
"hashbrown",
]
[[package]] [[package]]
name = "indoc" name = "indoc"
version = "2.0.4" version = "2.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8" checksum = "1e186cfbae8084e513daff4240b4797e342f988cecda4fb6c939150f96315fd8"
[[package]]
name = "ipnet"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3"
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.12.1" version = "0.12.1"
@ -306,6 +524,15 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
dependencies = [
"wasm-bindgen",
]
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.4.0" version = "1.4.0"
@ -355,6 +582,12 @@ version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.7.2" version = "0.7.2"
@ -413,6 +646,7 @@ dependencies = [
"once_cell", "once_cell",
"openssh", "openssh",
"ratatui", "ratatui",
"reqwest",
"serde", "serde",
"serde_json", "serde_json",
"structopt", "structopt",
@ -423,6 +657,24 @@ dependencies = [
"version_check", "version_check",
] ]
[[package]]
name = "native-tls"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]] [[package]]
name = "non-zero-byte-slice" name = "non-zero-byte-slice"
version = "0.1.0" version = "0.1.0"
@ -491,6 +743,50 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "openssl"
version = "0.10.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f"
dependencies = [
"bitflags 2.4.2",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.101"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.1" version = "0.12.1"
@ -532,6 +828,18 @@ version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
[[package]] [[package]]
name = "predicates" name = "predicates"
version = "3.1.0" version = "3.1.0"
@ -629,6 +937,46 @@ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
] ]
[[package]]
name = "reqwest"
version = "0.11.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eea5a9eb898d3783f17c6407670e3592fd174cb81a10e51d4c37f49450b9946"
dependencies = [
"base64",
"bytes",
"encoding_rs",
"futures-core",
"futures-util",
"h2",
"http",
"http-body",
"hyper",
"hyper-tls",
"ipnet",
"js-sys",
"log",
"mime",
"native-tls",
"once_cell",
"percent-encoding",
"pin-project-lite",
"rustls-pemfile",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper",
"system-configuration",
"tokio",
"tokio-native-tls",
"tower-service",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"winreg",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.23" version = "0.1.23"
@ -648,6 +996,15 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.14" version = "1.0.14"
@ -660,12 +1017,44 @@ version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c"
[[package]]
name = "schannel"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534"
dependencies = [
"windows-sys 0.52.0",
]
[[package]] [[package]]
name = "scopeguard" name = "scopeguard"
version = "1.2.0" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "sendfd" name = "sendfd"
version = "0.4.3" version = "0.4.3"
@ -707,6 +1096,18 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_urlencoded"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
"itoa",
"ryu",
"serde",
]
[[package]] [[package]]
name = "shell-escape" name = "shell-escape"
version = "0.1.5" version = "0.1.5"
@ -743,6 +1144,15 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "slab"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67"
dependencies = [
"autocfg",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.13.1" version = "1.13.1"
@ -868,6 +1278,33 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "system-configuration"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bc6ee10a9b4fcf576e9b0819d95ec16f4d2c02d39fd83ac1c8789785c4a42"
dependencies = [
"bitflags 2.4.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.10.0" version = "3.10.0"
@ -967,6 +1404,16 @@ dependencies = [
"syn 2.0.48", "syn 2.0.48",
] ]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]] [[package]]
name = "tokio-pipe" name = "tokio-pipe"
version = "0.2.12" version = "0.2.12"
@ -977,6 +1424,51 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "tokio-util"
version = "0.7.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15"
dependencies = [
"bytes",
"futures-core",
"futures-sink",
"pin-project-lite",
"tokio",
"tracing",
]
[[package]]
name = "tower-service"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"pin-project-lite",
"tracing-core",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
]
[[package]]
name = "try-lock"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b"
[[package]] [[package]]
name = "typed-builder" name = "typed-builder"
version = "0.18.1" version = "0.18.1"
@ -1047,6 +1539,12 @@ version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]] [[package]]
name = "vec_map" name = "vec_map"
version = "0.8.2" version = "0.8.2"
@ -1059,12 +1557,97 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "want"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e"
dependencies = [
"try-lock",
]
[[package]] [[package]]
name = "wasi" name = "wasi"
version = "0.11.0+wasi-snapshot-preview1" version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
dependencies = [
"cfg-if",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn 2.0.48",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0"
dependencies = [
"cfg-if",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.48",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "web-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -1219,6 +1802,16 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "winreg"
version = "0.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1"
dependencies = [
"cfg-if",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "zerocopy" name = "zerocopy"
version = "0.7.32" version = "0.7.32"

View File

@ -11,6 +11,7 @@ crossterm = { version = "0.27.0", optional = true}
once_cell = { version = "1.19.0", optional = true} once_cell = { version = "1.19.0", optional = true}
openssh = { version = "0.10.3", features = ["native-mux"], default-features = false, optional = true} openssh = { version = "0.10.3", features = ["native-mux"], default-features = false, optional = true}
ratatui = { version = "0.26.0", optional = true} ratatui = { version = "0.26.0", optional = true}
reqwest = { version = "0.11.25", features = ["blocking", "json"], optional = true }
serde = { version = "1.0.196", features = ["derive"], optional = true } serde = { version = "1.0.196", features = ["derive"], optional = true }
serde_json = { version = "1.0.113", optional = true} serde_json = { version = "1.0.113", optional = true}
structopt = { version = "0.3.26", optional = true} structopt = { version = "0.3.26", optional = true}
@ -32,6 +33,7 @@ bin = ["structopt"]
database-json = ["serde", "serde_json"] database-json = ["serde", "serde_json"]
library-beets = [] library-beets = []
library-beets-ssh = ["openssh", "tokio"] library-beets-ssh = ["openssh", "tokio"]
musicbrainz-api = ["reqwest", "serde", "serde_json"]
tui = ["aho-corasick", "crossterm", "once_cell", "ratatui"] tui = ["aho-corasick", "crossterm", "once_cell", "ratatui"]
[[bin]] [[bin]]
@ -42,5 +44,9 @@ required-features = ["bin", "database-json", "library-beets", "library-beets-ssh
name = "musichoard-edit" name = "musichoard-edit"
required-features = ["bin", "database-json"] required-features = ["bin", "database-json"]
[[bin]]
name = "musichoard-reqwest"
required-features = ["musicbrainz-api"]
[package.metadata.docs.rs] [package.metadata.docs.rs]
all-features = true all-features = true

View File

@ -1,5 +1,18 @@
# Music Hoard # Music Hoard
## Developing
### Pre-requisites
#### musicbrainz-api
This feature requires the `openssl` system library.
On Fedora:
``` sh
sudo dnf install openssl-devel
```
## Usage notes ## Usage notes
### Text selection ### Text selection

View File

@ -4,7 +4,7 @@ use structopt::{clap::AppSettings, StructOpt};
use musichoard::{ use musichoard::{
collection::{album::AlbumId, artist::ArtistId}, collection::{album::AlbumId, artist::ArtistId},
database::json::{backend::JsonDatabaseFileBackend, JsonDatabase}, external::database::json::{backend::JsonDatabaseFileBackend, JsonDatabase},
IMusicHoardDatabase, MusicHoard, MusicHoardBuilder, NoLibrary, IMusicHoardDatabase, MusicHoard, MusicHoardBuilder, NoLibrary,
}; };

View File

@ -5,7 +5,7 @@ use std::{
use crate::core::collection::{ use crate::core::collection::{
merge::{Merge, MergeSorted, WithId}, merge::{Merge, MergeSorted, WithId},
musicbrainz::MusicBrainzUrl, musicbrainz::MbAlbumRef,
track::{Track, TrackFormat}, track::{Track, TrackFormat},
}; };
@ -15,7 +15,9 @@ pub struct Album {
pub id: AlbumId, pub id: AlbumId,
pub date: AlbumDate, pub date: AlbumDate,
pub seq: AlbumSeq, pub seq: AlbumSeq,
pub musicbrainz: Option<MusicBrainzUrl>, pub musicbrainz: Option<MbAlbumRef>,
pub primary_type: Option<AlbumPrimaryType>,
pub secondary_types: Vec<AlbumSecondaryType>,
pub tracks: Vec<Track>, pub tracks: Vec<Track>,
} }
@ -35,83 +37,34 @@ pub struct AlbumId {
// There are crates for handling dates, but we don't need much complexity beyond year-month-day. // There are crates for handling dates, but we don't need much complexity beyond year-month-day.
/// The album's release date. /// The album's release date.
#[derive(Clone, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)] #[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct AlbumDate { pub struct AlbumDate {
pub year: u32, pub year: Option<u32>,
pub month: AlbumMonth, pub month: Option<u8>,
pub day: u8, pub day: Option<u8>,
} }
impl AlbumDate { impl AlbumDate {
pub fn new<M: Into<AlbumMonth>>(year: u32, month: M, day: u8) -> Self { pub fn new(year: Option<u32>, month: Option<u8>, day: Option<u8>) -> Self {
AlbumDate { AlbumDate { year, month, day }
year,
month: month.into(),
day,
}
} }
} }
impl From<u32> for AlbumDate { impl From<u32> for AlbumDate {
fn from(value: u32) -> Self { fn from(value: u32) -> Self {
AlbumDate::new(value, AlbumMonth::default(), 0) AlbumDate::new(Some(value), None, None)
} }
} }
impl<M: Into<AlbumMonth>> From<(u32, M)> for AlbumDate { impl From<(u32, u8)> for AlbumDate {
fn from(value: (u32, M)) -> Self { fn from(value: (u32, u8)) -> Self {
AlbumDate::new(value.0, value.1, 0) AlbumDate::new(Some(value.0), Some(value.1), None)
} }
} }
impl<M: Into<AlbumMonth>> From<(u32, M, u8)> for AlbumDate { impl From<(u32, u8, u8)> for AlbumDate {
fn from(value: (u32, M, u8)) -> Self { fn from(value: (u32, u8, u8)) -> Self {
AlbumDate::new(value.0, value.1, value.2) AlbumDate::new(Some(value.0), Some(value.1), Some(value.2))
}
}
#[repr(u8)]
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub enum AlbumMonth {
#[default]
None = 0,
January = 1,
February = 2,
March = 3,
April = 4,
May = 5,
June = 6,
July = 7,
August = 8,
September = 9,
October = 10,
November = 11,
December = 12,
}
impl From<u8> for AlbumMonth {
fn from(value: u8) -> Self {
match value {
1 => AlbumMonth::January,
2 => AlbumMonth::February,
3 => AlbumMonth::March,
4 => AlbumMonth::April,
5 => AlbumMonth::May,
6 => AlbumMonth::June,
7 => AlbumMonth::July,
8 => AlbumMonth::August,
9 => AlbumMonth::September,
10 => AlbumMonth::October,
11 => AlbumMonth::November,
12 => AlbumMonth::December,
_ => AlbumMonth::None,
}
}
}
impl AlbumMonth {
pub fn is_none(&self) -> bool {
matches!(self, AlbumMonth::None)
} }
} }
@ -119,6 +72,50 @@ impl AlbumMonth {
#[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)] #[derive(Clone, Copy, Debug, Default, PartialEq, PartialOrd, Ord, Eq, Hash)]
pub struct AlbumSeq(pub u8); pub struct AlbumSeq(pub u8);
/// Based on [MusicBrainz types](https://musicbrainz.org/doc/Release_Group/Type).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AlbumPrimaryType {
/// Album
Album,
/// Single
Single,
/// EP
Ep,
/// Broadcast
Broadcast,
/// Other
Other,
}
/// Based on [MusicBrainz types](https://musicbrainz.org/doc/Release_Group/Type).
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum AlbumSecondaryType {
/// Compilation
Compilation,
/// Soundtrack
Soundtrack,
/// Spokenword
Spokenword,
/// Interview
Interview,
/// Audiobook
Audiobook,
/// Audio drama
AudioDrama,
/// Live
Live,
/// Remix
Remix,
/// DJ-mix
DjMix,
/// Mixtape/Street
MixtapeStreet,
/// Demo
Demo,
/// Field recording
FieldRecording,
}
/// The album's ownership status. /// The album's ownership status.
pub enum AlbumStatus { pub enum AlbumStatus {
None, None,
@ -135,12 +132,19 @@ impl AlbumStatus {
} }
impl Album { impl Album {
pub fn new<Id: Into<AlbumId>, Date: Into<AlbumDate>>(id: Id, date: Date) -> Self { pub fn new<Id: Into<AlbumId>, Date: Into<AlbumDate>>(
id: Id,
date: Date,
primary_type: Option<AlbumPrimaryType>,
secondary_types: Vec<AlbumSecondaryType>,
) -> Self {
Album { Album {
id: id.into(), id: id.into(),
date: date.into(), date: date.into(),
seq: AlbumSeq::default(), seq: AlbumSeq::default(),
musicbrainz: None, musicbrainz: None,
primary_type,
secondary_types,
tracks: vec![], tracks: vec![],
} }
} }
@ -160,6 +164,14 @@ impl Album {
pub fn clear_seq(&mut self) { pub fn clear_seq(&mut self) {
self.seq = AlbumSeq::default(); self.seq = AlbumSeq::default();
} }
pub fn set_musicbrainz_ref(&mut self, mbref: MbAlbumRef) {
_ = self.musicbrainz.insert(mbref);
}
pub fn clear_musicbrainz_ref(&mut self) {
_ = self.musicbrainz.take();
}
} }
impl PartialOrd for Album { impl PartialOrd for Album {
@ -178,6 +190,11 @@ impl Merge for Album {
fn merge_in_place(&mut self, other: Self) { fn merge_in_place(&mut self, other: Self) {
assert_eq!(self.id, other.id); assert_eq!(self.id, other.id);
self.seq = std::cmp::max(self.seq, other.seq); self.seq = std::cmp::max(self.seq, other.seq);
self.musicbrainz = self.musicbrainz.take().or(other.musicbrainz);
self.primary_type = self.primary_type.take().or(other.primary_type);
self.secondary_types.merge_in_place(other.secondary_types);
let tracks = mem::take(&mut self.tracks); let tracks = mem::take(&mut self.tracks);
self.tracks = MergeSorted::new(tracks.into_iter(), other.tracks.into_iter()).collect(); self.tracks = MergeSorted::new(tracks.into_iter(), other.tracks.into_iter()).collect();
} }
@ -213,54 +230,32 @@ mod tests {
use super::*; use super::*;
#[test]
fn album_month() {
assert_eq!(<u8 as Into<AlbumMonth>>::into(0), AlbumMonth::None);
assert_eq!(<u8 as Into<AlbumMonth>>::into(1), AlbumMonth::January);
assert_eq!(<u8 as Into<AlbumMonth>>::into(2), AlbumMonth::February);
assert_eq!(<u8 as Into<AlbumMonth>>::into(3), AlbumMonth::March);
assert_eq!(<u8 as Into<AlbumMonth>>::into(4), AlbumMonth::April);
assert_eq!(<u8 as Into<AlbumMonth>>::into(5), AlbumMonth::May);
assert_eq!(<u8 as Into<AlbumMonth>>::into(6), AlbumMonth::June);
assert_eq!(<u8 as Into<AlbumMonth>>::into(7), AlbumMonth::July);
assert_eq!(<u8 as Into<AlbumMonth>>::into(8), AlbumMonth::August);
assert_eq!(<u8 as Into<AlbumMonth>>::into(9), AlbumMonth::September);
assert_eq!(<u8 as Into<AlbumMonth>>::into(10), AlbumMonth::October);
assert_eq!(<u8 as Into<AlbumMonth>>::into(11), AlbumMonth::November);
assert_eq!(<u8 as Into<AlbumMonth>>::into(12), AlbumMonth::December);
assert_eq!(<u8 as Into<AlbumMonth>>::into(13), AlbumMonth::None);
assert_eq!(<u8 as Into<AlbumMonth>>::into(255), AlbumMonth::None);
}
#[test] #[test]
fn album_date_from() { fn album_date_from() {
let date: AlbumDate = 1986.into(); let date: AlbumDate = 1986.into();
assert_eq!(date, AlbumDate::new(1986, AlbumMonth::default(), 0)); assert_eq!(date, AlbumDate::new(Some(1986), None, None));
let date: AlbumDate = (1986, 5).into(); let date: AlbumDate = (1986, 5).into();
assert_eq!(date, AlbumDate::new(1986, AlbumMonth::May, 0)); assert_eq!(date, AlbumDate::new(Some(1986), Some(5), None));
let date: AlbumDate = (1986, AlbumMonth::June).into();
assert_eq!(date, AlbumDate::new(1986, AlbumMonth::June, 0));
let date: AlbumDate = (1986, 6, 8).into(); let date: AlbumDate = (1986, 6, 8).into();
assert_eq!(date, AlbumDate::new(1986, AlbumMonth::June, 8)); assert_eq!(date, AlbumDate::new(Some(1986), Some(6), Some(8)));
} }
#[test] #[test]
fn same_date_seq_cmp() { fn same_date_seq_cmp() {
let date = AlbumDate::new(2024, 3, 2); let date: AlbumDate = (2024, 3, 2).into();
let album_id_1 = AlbumId { let album_id_1 = AlbumId {
title: String::from("album z"), title: String::from("album z"),
}; };
let mut album_1 = Album::new(album_id_1, date.clone()); let mut album_1 = Album::new(album_id_1, date.clone(), None, vec![]);
album_1.set_seq(AlbumSeq(1)); album_1.set_seq(AlbumSeq(1));
let album_id_2 = AlbumId { let album_id_2 = AlbumId {
title: String::from("album a"), title: String::from("album a"),
}; };
let mut album_2 = Album::new(album_id_2, date.clone()); let mut album_2 = Album::new(album_id_2, date.clone(), None, vec![]);
album_2.set_seq(AlbumSeq(2)); album_2.set_seq(AlbumSeq(2));
assert_ne!(album_1, album_2); assert_ne!(album_1, album_2);
@ -269,7 +264,7 @@ mod tests {
#[test] #[test]
fn set_clear_seq() { fn set_clear_seq() {
let mut album = Album::new("An album", AlbumDate::default()); let mut album = Album::new("An album", AlbumDate::default(), None, vec![]);
assert_eq!(album.seq, AlbumSeq(0)); assert_eq!(album.seq, AlbumSeq(0));
@ -322,4 +317,34 @@ mod tests {
let merged = left.clone().merge(right); let merged = left.clone().merge(right);
assert_eq!(expected, merged); assert_eq!(expected, merged);
} }
#[test]
fn set_clear_musicbrainz_url() {
const MUSICBRAINZ: &str =
"https://musicbrainz.org/release-group/c12897a3-af7a-3466-8892-58af84765813";
const MUSICBRAINZ_2: &str =
"https://musicbrainz.org/release-group/0eaa9306-e6df-47be-94ce-04bfe3df782c";
let mut album = Album::new(AlbumId::new("an album"), AlbumDate::default(), None, vec![]);
let mut expected: Option<MbAlbumRef> = None;
assert_eq!(album.musicbrainz, expected);
// Setting a URL on an album.
album.set_musicbrainz_ref(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap());
_ = expected.insert(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap());
assert_eq!(album.musicbrainz, expected);
album.set_musicbrainz_ref(MbAlbumRef::from_url_str(MUSICBRAINZ).unwrap());
assert_eq!(album.musicbrainz, expected);
album.set_musicbrainz_ref(MbAlbumRef::from_url_str(MUSICBRAINZ_2).unwrap());
_ = expected.insert(MbAlbumRef::from_url_str(MUSICBRAINZ_2).unwrap());
assert_eq!(album.musicbrainz, expected);
// Clearing URLs.
album.clear_musicbrainz_ref();
_ = expected.take();
assert_eq!(album.musicbrainz, expected);
}
} }

View File

@ -7,7 +7,7 @@ use std::{
use crate::core::collection::{ use crate::core::collection::{
album::Album, album::Album,
merge::{Merge, MergeCollections, WithId}, merge::{Merge, MergeCollections, WithId},
musicbrainz::MusicBrainzUrl, musicbrainz::MbArtistRef,
}; };
/// An artist. /// An artist.
@ -15,7 +15,7 @@ use crate::core::collection::{
pub struct Artist { pub struct Artist {
pub id: ArtistId, pub id: ArtistId,
pub sort: Option<ArtistId>, pub sort: Option<ArtistId>,
pub musicbrainz: Option<MusicBrainzUrl>, pub musicbrainz: Option<MbArtistRef>,
pub properties: HashMap<String, Vec<String>>, pub properties: HashMap<String, Vec<String>>,
pub albums: Vec<Album>, pub albums: Vec<Album>,
} }
@ -58,11 +58,11 @@ impl Artist {
_ = self.sort.take(); _ = self.sort.take();
} }
pub fn set_musicbrainz_url(&mut self, url: MusicBrainzUrl) { pub fn set_musicbrainz_ref(&mut self, mbref: MbArtistRef) {
_ = self.musicbrainz.insert(url); _ = self.musicbrainz.insert(mbref);
} }
pub fn clear_musicbrainz_url(&mut self) { pub fn clear_musicbrainz_ref(&mut self) {
_ = self.musicbrainz.take(); _ = self.musicbrainz.take();
} }
@ -216,23 +216,23 @@ mod tests {
fn set_clear_musicbrainz_url() { fn set_clear_musicbrainz_url() {
let mut artist = Artist::new(ArtistId::new("an artist")); let mut artist = Artist::new(ArtistId::new("an artist"));
let mut expected: Option<MusicBrainzUrl> = None; let mut expected: Option<MbArtistRef> = None;
assert_eq!(artist.musicbrainz, expected); assert_eq!(artist.musicbrainz, expected);
// Setting a URL on an artist. // Setting a URL on an artist.
artist.set_musicbrainz_url(MusicBrainzUrl::artist_from_str(MUSICBRAINZ).unwrap()); artist.set_musicbrainz_ref(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
_ = expected.insert(MusicBrainzUrl::artist_from_str(MUSICBRAINZ).unwrap()); _ = expected.insert(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
assert_eq!(artist.musicbrainz, expected); assert_eq!(artist.musicbrainz, expected);
artist.set_musicbrainz_url(MusicBrainzUrl::artist_from_str(MUSICBRAINZ).unwrap()); artist.set_musicbrainz_ref(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
assert_eq!(artist.musicbrainz, expected); assert_eq!(artist.musicbrainz, expected);
artist.set_musicbrainz_url(MusicBrainzUrl::artist_from_str(MUSICBRAINZ_2).unwrap()); artist.set_musicbrainz_ref(MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap());
_ = expected.insert(MusicBrainzUrl::artist_from_str(MUSICBRAINZ_2).unwrap()); _ = expected.insert(MbArtistRef::from_url_str(MUSICBRAINZ_2).unwrap());
assert_eq!(artist.musicbrainz, expected); assert_eq!(artist.musicbrainz, expected);
// Clearing URLs. // Clearing URLs.
artist.clear_musicbrainz_url(); artist.clear_musicbrainz_ref();
_ = expected.take(); _ = expected.take();
assert_eq!(artist.musicbrainz, expected); assert_eq!(artist.musicbrainz, expected);
} }

View File

@ -3,65 +3,110 @@ use std::fmt::{Debug, Display};
use url::Url; use url::Url;
use uuid::Uuid; use uuid::Uuid;
use crate::core::collection::Error; use crate::{core::collection::Error, interface::musicbrainz::Mbid};
/// MusicBrainz reference. const MB_DOMAIN: &str = "musicbrainz.org";
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct MusicBrainzUrl(Url);
impl MusicBrainzUrl { #[derive(Clone, Debug, PartialEq, Eq)]
pub fn mbid(&self) -> &str { struct MusicBrainzRef {
// The URL is assumed to have been validated. mbid: Mbid,
self.0.path_segments().and_then(|mut ps| ps.nth(1)).unwrap() url: Url,
} }
pub fn artist_from_str<S: AsRef<str>>(url: S) -> Result<Self, Error> { pub trait IMusicBrainzRef {
Self::artist_from_url(url.as_ref().try_into()?) fn mbid(&self) -> &Mbid;
} fn url(&self) -> &Url;
fn entity() -> &'static str;
}
pub fn album_from_str<S: AsRef<str>>(url: S) -> Result<Self, Error> { #[derive(Clone, Debug, PartialEq, Eq)]
Self::album_from_url(url.as_ref().try_into()?) pub struct MbArtistRef(MusicBrainzRef);
}
pub fn artist_from_url(url: Url) -> Result<Self, Error> { #[derive(Clone, Debug, PartialEq, Eq)]
Self::new(url, "artist") pub struct MbAlbumRef(MusicBrainzRef);
}
pub fn album_from_url(url: Url) -> Result<Self, Error> { macro_rules! impl_imusicbrainzref {
Self::new(url, "release-group") ($mbref:ident, $entity:literal) => {
} impl IMusicBrainzRef for $mbref {
fn mbid(&self) -> &Mbid {
&self.0.mbid
}
fn new(url: Url, mb_type: &str) -> Result<Self, Error> { fn url(&self) -> &Url {
&self.0.url
}
fn entity() -> &'static str {
$entity
}
}
impl TryFrom<Url> for $mbref {
type Error = Error;
fn try_from(url: Url) -> Result<Self, Self::Error> {
Ok($mbref(MusicBrainzRef::from_url(url, $mbref::entity())?))
}
}
impl From<Uuid> for $mbref {
fn from(uuid: Uuid) -> Self {
$mbref(MusicBrainzRef::from_uuid(uuid, $mbref::entity()))
}
}
impl $mbref {
pub fn from_url_str<S: AsRef<str>>(url: S) -> Result<Self, Error> {
let url: Url = url.as_ref().try_into()?;
url.try_into()
}
}
impl $mbref {
pub fn from_uuid_str<S: AsRef<str>>(uuid: S) -> Result<Self, Error> {
let uuid: Uuid = uuid.as_ref().try_into()?;
Ok(uuid.into())
}
}
};
}
impl_imusicbrainzref!(MbArtistRef, "artist");
impl_imusicbrainzref!(MbAlbumRef, "release-group");
impl MusicBrainzRef {
fn from_url(url: Url, entity: &'static str) -> Result<Self, Error> {
if !url if !url
.domain() .domain()
.map(|u| u.ends_with("musicbrainz.org")) .map(|u| u.ends_with(MB_DOMAIN))
.unwrap_or(false) .unwrap_or(false)
{ {
return Err(Self::invalid_url_error(url, mb_type)); return Err(Self::invalid_url_error(url, entity));
} }
// path_segments only returns an empty iterator if the URL cannot-be-a-base. However, if the // path_segments only returns an empty iterator if the URL cannot-be-a-base. However, if the
// URL cannot-be-a-base then it will fail the check above already as it won't have a domain. // URL cannot-be-a-base then it will fail the check above already as it won't have a domain.
if url.path_segments().and_then(|mut ps| ps.nth(0)).unwrap() != mb_type { if url.path_segments().and_then(|mut ps| ps.nth(0)).unwrap() != entity {
return Err(Self::invalid_url_error(url, mb_type)); return Err(Self::invalid_url_error(url, entity));
} }
match url.path_segments().and_then(|mut ps| ps.nth(1)) { let mbid = match url.path_segments().and_then(|mut ps| ps.nth(1)) {
Some(segment) => Uuid::try_parse(segment)?, Some(segment) => Uuid::try_parse(segment)?.into(),
None => return Err(Self::invalid_url_error(url, mb_type)), None => return Err(Self::invalid_url_error(url, entity)),
}; };
Ok(MusicBrainzUrl(url)) Ok(MusicBrainzRef { mbid, url })
} }
fn invalid_url_error<U: Display>(url: U, mb_type: &str) -> Error { fn from_uuid(uuid: Uuid, entity: &'static str) -> Self {
Error::UrlError(format!("invalid {mb_type} MusicBrainz URL: {url}")) let uuid_str = uuid.to_string();
let mbid = uuid.into();
let url = Url::parse(&format!("https://{MB_DOMAIN}/{entity}/{uuid_str}")).unwrap();
MusicBrainzRef { mbid, url }
} }
}
impl AsRef<str> for MusicBrainzUrl { fn invalid_url_error<U: Display>(url: U, entity: &'static str) -> Error {
fn as_ref(&self) -> &str { Error::UrlError(format!("invalid {entity} MusicBrainz URL: {url}"))
self.0.as_ref()
} }
} }
@ -74,14 +119,18 @@ mod tests {
let uuid = "d368baa8-21ca-4759-9731-0b2753071ad8"; let uuid = "d368baa8-21ca-4759-9731-0b2753071ad8";
let url_str = format!("https://musicbrainz.org/artist/{uuid}"); let url_str = format!("https://musicbrainz.org/artist/{uuid}");
let mb = MusicBrainzUrl::artist_from_str(&url_str).unwrap(); let mb = MbArtistRef::from_url_str(&url_str).unwrap();
assert_eq!(url_str, mb.as_ref()); assert_eq!(url_str, mb.url().as_ref());
assert_eq!(uuid, mb.mbid()); assert_eq!(uuid, mb.mbid().uuid().to_string());
let mb = MbArtistRef::from_uuid_str(uuid).unwrap();
assert_eq!(url_str, mb.url().as_ref());
assert_eq!(uuid, mb.mbid().uuid().to_string());
let url: Url = url_str.as_str().try_into().unwrap(); let url: Url = url_str.as_str().try_into().unwrap();
let mb = MusicBrainzUrl::artist_from_url(url).unwrap(); let mb: MbArtistRef = url.try_into().unwrap();
assert_eq!(url_str, mb.as_ref()); assert_eq!(url_str, mb.url().as_ref());
assert_eq!(uuid, mb.mbid()); assert_eq!(uuid, mb.mbid().uuid().to_string());
} }
#[test] #[test]
@ -89,21 +138,25 @@ mod tests {
let uuid = "d368baa8-21ca-4759-9731-0b2753071ad8"; let uuid = "d368baa8-21ca-4759-9731-0b2753071ad8";
let url_str = format!("https://musicbrainz.org/release-group/{uuid}"); let url_str = format!("https://musicbrainz.org/release-group/{uuid}");
let mb = MusicBrainzUrl::album_from_str(&url_str).unwrap(); let mb = MbAlbumRef::from_url_str(&url_str).unwrap();
assert_eq!(url_str, mb.as_ref()); assert_eq!(url_str, mb.url().as_ref());
assert_eq!(uuid, mb.mbid()); assert_eq!(uuid, mb.mbid().uuid().to_string());
let mb = MbAlbumRef::from_uuid_str(uuid).unwrap();
assert_eq!(url_str, mb.url().as_ref());
assert_eq!(uuid, mb.mbid().uuid().to_string());
let url: Url = url_str.as_str().try_into().unwrap(); let url: Url = url_str.as_str().try_into().unwrap();
let mb = MusicBrainzUrl::album_from_url(url).unwrap(); let mb: MbAlbumRef = url.try_into().unwrap();
assert_eq!(url_str, mb.as_ref()); assert_eq!(url_str, mb.url().as_ref());
assert_eq!(uuid, mb.mbid()); assert_eq!(uuid, mb.mbid().uuid().to_string());
} }
#[test] #[test]
fn not_a_url() { fn not_a_url() {
let url = "not a url at all"; let url = "not a url at all";
let expected_error: Error = url::ParseError::RelativeUrlWithoutBase.into(); let expected_error: Error = url::ParseError::RelativeUrlWithoutBase.into();
let actual_error = MusicBrainzUrl::artist_from_str(url).unwrap_err(); let actual_error = MbArtistRef::from_url_str(url).unwrap_err();
assert_eq!(actual_error, expected_error); assert_eq!(actual_error, expected_error);
assert_eq!(actual_error.to_string(), expected_error.to_string()); assert_eq!(actual_error.to_string(), expected_error.to_string());
} }
@ -112,7 +165,7 @@ mod tests {
fn invalid_url() { fn invalid_url() {
let url = "https://www.musicbutler.io/artist-page/483340948"; let url = "https://www.musicbutler.io/artist-page/483340948";
let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}")); let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}"));
let actual_error = MusicBrainzUrl::artist_from_str(url).unwrap_err(); let actual_error = MbArtistRef::from_url_str(url).unwrap_err();
assert_eq!(actual_error, expected_error); assert_eq!(actual_error, expected_error);
assert_eq!(actual_error.to_string(), expected_error.to_string()); assert_eq!(actual_error.to_string(), expected_error.to_string());
} }
@ -121,7 +174,7 @@ mod tests {
fn artist_invalid_type() { fn artist_invalid_type() {
let url = "https://musicbrainz.org/release-group/i-am-not-a-uuid"; let url = "https://musicbrainz.org/release-group/i-am-not-a-uuid";
let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}")); let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}"));
let actual_error = MusicBrainzUrl::artist_from_str(url).unwrap_err(); let actual_error = MbArtistRef::from_url_str(url).unwrap_err();
assert_eq!(actual_error, expected_error); assert_eq!(actual_error, expected_error);
assert_eq!(actual_error.to_string(), expected_error.to_string()); assert_eq!(actual_error.to_string(), expected_error.to_string());
} }
@ -131,7 +184,7 @@ mod tests {
let url = "https://musicbrainz.org/artist/i-am-not-a-uuid"; let url = "https://musicbrainz.org/artist/i-am-not-a-uuid";
let expected_error = let expected_error =
Error::UrlError(format!("invalid release-group MusicBrainz URL: {url}")); Error::UrlError(format!("invalid release-group MusicBrainz URL: {url}"));
let actual_error = MusicBrainzUrl::album_from_str(url).unwrap_err(); let actual_error = MbAlbumRef::from_url_str(url).unwrap_err();
assert_eq!(actual_error, expected_error); assert_eq!(actual_error, expected_error);
assert_eq!(actual_error.to_string(), expected_error.to_string()); assert_eq!(actual_error.to_string(), expected_error.to_string());
} }
@ -140,7 +193,7 @@ mod tests {
fn invalid_uuid() { fn invalid_uuid() {
let url = "https://musicbrainz.org/artist/i-am-not-a-uuid"; let url = "https://musicbrainz.org/artist/i-am-not-a-uuid";
let expected_error: Error = Uuid::try_parse("i-am-not-a-uuid").unwrap_err().into(); let expected_error: Error = Uuid::try_parse("i-am-not-a-uuid").unwrap_err().into();
let actual_error = MusicBrainzUrl::artist_from_str(url).unwrap_err(); let actual_error = MbArtistRef::from_url_str(url).unwrap_err();
assert_eq!(actual_error, expected_error); assert_eq!(actual_error, expected_error);
assert_eq!(actual_error.to_string(), expected_error.to_string()); assert_eq!(actual_error.to_string(), expected_error.to_string());
} }
@ -149,7 +202,7 @@ mod tests {
fn missing_type() { fn missing_type() {
let url = "https://musicbrainz.org"; let url = "https://musicbrainz.org";
let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}/")); let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}/"));
let actual_error = MusicBrainzUrl::artist_from_str(url).unwrap_err(); let actual_error = MbArtistRef::from_url_str(url).unwrap_err();
assert_eq!(actual_error, expected_error); assert_eq!(actual_error, expected_error);
assert_eq!(actual_error.to_string(), expected_error.to_string()); assert_eq!(actual_error.to_string(), expected_error.to_string());
} }
@ -158,7 +211,7 @@ mod tests {
fn missing_uuid() { fn missing_uuid() {
let url = "https://musicbrainz.org/artist"; let url = "https://musicbrainz.org/artist";
let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}")); let expected_error = Error::UrlError(format!("invalid artist MusicBrainz URL: {url}"));
let actual_error = MusicBrainzUrl::artist_from_str(url).unwrap_err(); let actual_error = MbArtistRef::from_url_str(url).unwrap_err();
assert_eq!(actual_error, expected_error); assert_eq!(actual_error, expected_error);
assert_eq!(actual_error.to_string(), expected_error.to_string()); assert_eq!(actual_error.to_string(), expected_error.to_string());
} }

View File

@ -97,13 +97,13 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn no_database_load() { fn null_database_load() {
let database = NullDatabase; let database = NullDatabase;
assert!(database.load().unwrap().is_empty()); assert!(database.load().unwrap().is_empty());
} }
#[test] #[test]
fn no_database_save() { fn null_database_save() {
let mut database = NullDatabase; let mut database = NullDatabase;
assert!(database.save(&vec![]).is_ok()); assert!(database.save(&vec![]).is_ok());
} }

View File

@ -5,7 +5,7 @@ use std::{collections::HashSet, fmt, num::ParseIntError, str::Utf8Error};
#[cfg(test)] #[cfg(test)]
use mockall::automock; use mockall::automock;
use crate::core::collection::{album::AlbumMonth, track::TrackFormat}; use crate::core::collection::track::TrackFormat;
/// Trait for interacting with the music library. /// Trait for interacting with the music library.
#[cfg_attr(test, automock)] #[cfg_attr(test, automock)]
@ -29,7 +29,7 @@ pub struct Item {
pub album_artist: String, pub album_artist: String,
pub album_artist_sort: Option<String>, pub album_artist_sort: Option<String>,
pub album_year: u32, pub album_year: u32,
pub album_month: AlbumMonth, pub album_month: u8,
pub album_day: u8, pub album_day: u8,
pub album_title: String, pub album_title: String,
pub track_number: u32, pub track_number: u32,
@ -45,7 +45,7 @@ pub enum Field {
AlbumArtist(String), AlbumArtist(String),
AlbumArtistSort(String), AlbumArtistSort(String),
AlbumYear(u32), AlbumYear(u32),
AlbumMonth(AlbumMonth), AlbumMonth(u8),
AlbumDay(u8), AlbumDay(u8),
AlbumTitle(String), AlbumTitle(String),
TrackNumber(u32), TrackNumber(u32),
@ -136,7 +136,7 @@ mod tests {
use super::*; use super::*;
#[test] #[test]
fn no_library_list() { fn null_library_list() {
let mut library = NullLibrary; let mut library = NullLibrary;
assert!(library.list(&Query::default()).unwrap().is_empty()); assert!(library.list(&Query::default()).unwrap().is_empty());
} }

View File

@ -1,9 +1,6 @@
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::core::{ use crate::core::{collection::track::TrackFormat, interface::library::Item};
collection::{album::AlbumMonth, track::TrackFormat},
interface::library::Item,
};
pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> { pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
vec![ vec![
@ -11,7 +8,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist A"), album_artist: String::from("Album_Artist A"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1998, album_year: 1998,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title a.a"), album_title: String::from("album_title a.a"),
track_number: 1, track_number: 1,
@ -24,7 +21,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist A"), album_artist: String::from("Album_Artist A"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1998, album_year: 1998,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title a.a"), album_title: String::from("album_title a.a"),
track_number: 2, track_number: 2,
@ -40,7 +37,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist A"), album_artist: String::from("Album_Artist A"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1998, album_year: 1998,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title a.a"), album_title: String::from("album_title a.a"),
track_number: 3, track_number: 3,
@ -53,7 +50,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist A"), album_artist: String::from("Album_Artist A"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1998, album_year: 1998,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title a.a"), album_title: String::from("album_title a.a"),
track_number: 4, track_number: 4,
@ -66,7 +63,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist A"), album_artist: String::from("Album_Artist A"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2015, album_year: 2015,
album_month: AlbumMonth::April, album_month: 4,
album_day: 0, album_day: 0,
album_title: String::from("album_title a.b"), album_title: String::from("album_title a.b"),
track_number: 1, track_number: 1,
@ -79,7 +76,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist A"), album_artist: String::from("Album_Artist A"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2015, album_year: 2015,
album_month: AlbumMonth::April, album_month: 4,
album_day: 0, album_day: 0,
album_title: String::from("album_title a.b"), album_title: String::from("album_title a.b"),
track_number: 2, track_number: 2,
@ -92,7 +89,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist B"), album_artist: String::from("Album_Artist B"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2003, album_year: 2003,
album_month: AlbumMonth::June, album_month: 6,
album_day: 6, album_day: 6,
album_title: String::from("album_title b.a"), album_title: String::from("album_title b.a"),
track_number: 1, track_number: 1,
@ -105,7 +102,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist B"), album_artist: String::from("Album_Artist B"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2003, album_year: 2003,
album_month: AlbumMonth::June, album_month: 6,
album_day: 6, album_day: 6,
album_title: String::from("album_title b.a"), album_title: String::from("album_title b.a"),
track_number: 2, track_number: 2,
@ -121,7 +118,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist B"), album_artist: String::from("Album_Artist B"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title b.b"), album_title: String::from("album_title b.b"),
track_number: 1, track_number: 1,
@ -134,7 +131,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist B"), album_artist: String::from("Album_Artist B"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title b.b"), album_title: String::from("album_title b.b"),
track_number: 2, track_number: 2,
@ -150,7 +147,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist B"), album_artist: String::from("Album_Artist B"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2009, album_year: 2009,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title b.c"), album_title: String::from("album_title b.c"),
track_number: 1, track_number: 1,
@ -163,7 +160,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist B"), album_artist: String::from("Album_Artist B"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2009, album_year: 2009,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title b.c"), album_title: String::from("album_title b.c"),
track_number: 2, track_number: 2,
@ -179,7 +176,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist B"), album_artist: String::from("Album_Artist B"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2015, album_year: 2015,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title b.d"), album_title: String::from("album_title b.d"),
track_number: 1, track_number: 1,
@ -192,7 +189,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist B"), album_artist: String::from("Album_Artist B"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2015, album_year: 2015,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title b.d"), album_title: String::from("album_title b.d"),
track_number: 2, track_number: 2,
@ -208,7 +205,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("The Album_Artist C"), album_artist: String::from("The Album_Artist C"),
album_artist_sort: Some(String::from("Album_Artist C, The")), album_artist_sort: Some(String::from("Album_Artist C, The")),
album_year: 1985, album_year: 1985,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title c.a"), album_title: String::from("album_title c.a"),
track_number: 1, track_number: 1,
@ -221,7 +218,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("The Album_Artist C"), album_artist: String::from("The Album_Artist C"),
album_artist_sort: Some(String::from("Album_Artist C, The")), album_artist_sort: Some(String::from("Album_Artist C, The")),
album_year: 1985, album_year: 1985,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title c.a"), album_title: String::from("album_title c.a"),
track_number: 2, track_number: 2,
@ -237,7 +234,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("The Album_Artist C"), album_artist: String::from("The Album_Artist C"),
album_artist_sort: Some(String::from("Album_Artist C, The")), album_artist_sort: Some(String::from("Album_Artist C, The")),
album_year: 2018, album_year: 2018,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title c.b"), album_title: String::from("album_title c.b"),
track_number: 1, track_number: 1,
@ -250,7 +247,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("The Album_Artist C"), album_artist: String::from("The Album_Artist C"),
album_artist_sort: Some(String::from("Album_Artist C, The")), album_artist_sort: Some(String::from("Album_Artist C, The")),
album_year: 2018, album_year: 2018,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title c.b"), album_title: String::from("album_title c.b"),
track_number: 2, track_number: 2,
@ -266,7 +263,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist D"), album_artist: String::from("Album_Artist D"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1995, album_year: 1995,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title d.a"), album_title: String::from("album_title d.a"),
track_number: 1, track_number: 1,
@ -279,7 +276,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist D"), album_artist: String::from("Album_Artist D"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1995, album_year: 1995,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title d.a"), album_title: String::from("album_title d.a"),
track_number: 2, track_number: 2,
@ -295,7 +292,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist D"), album_artist: String::from("Album_Artist D"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2028, album_year: 2028,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title d.b"), album_title: String::from("album_title d.b"),
track_number: 1, track_number: 1,
@ -308,7 +305,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Album_Artist D"), album_artist: String::from("Album_Artist D"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2028, album_year: 2028,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("album_title d.b"), album_title: String::from("album_title d.b"),
track_number: 2, track_number: 2,

View File

@ -1,2 +1,3 @@
pub mod database; pub mod database;
pub mod library; pub mod library;
pub mod musicbrainz;

View File

@ -0,0 +1,180 @@
//! Module for accessing MusicBrainz metadata.
use std::{fmt, num};
// TODO: #[cfg(test)]
// TODO: use mockall::automock;
use uuid::{self, Uuid};
use crate::collection::album::Album;
/// Trait for interacting with the MusicBrainz API.
// TODO: #[cfg_attr(test, automock)]
pub trait IMusicBrainz {
fn lookup_artist_release_groups(&mut self, mbid: &Mbid) -> Result<Vec<Album>, Error>;
fn search_release_group(
&mut self,
arid: &Mbid,
album: Album,
) -> Result<Vec<Match<Album>>, Error>;
}
#[derive(Debug, PartialEq, Eq)]
pub struct Match<T> {
pub score: u8,
pub item: T,
}
impl<T> Match<T> {
pub fn new(score: u8, item: T) -> Self {
Match { score, item }
}
}
/// Null implementation of [`IMusicBrainz`] for when the trait is required, but no communication
/// with the MusicBrainz is desired.
pub struct NullMusicBrainz;
impl IMusicBrainz for NullMusicBrainz {
fn lookup_artist_release_groups(&mut self, _mbid: &Mbid) -> Result<Vec<Album>, Error> {
Ok(vec![])
}
fn search_release_group(
&mut self,
_arid: &Mbid,
_album: Album,
) -> Result<Vec<Match<Album>>, Error> {
Ok(vec![])
}
}
/// The MusicBrainz ID.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Mbid(Uuid);
impl Mbid {
pub fn uuid(&self) -> &Uuid {
&self.0
}
}
impl From<Uuid> for Mbid {
fn from(value: Uuid) -> Self {
Mbid(value)
}
}
macro_rules! try_from_impl_for_mbid {
($from:ty) => {
impl TryFrom<$from> for Mbid {
type Error = Error;
fn try_from(value: $from) -> Result<Self, Self::Error> {
Ok(Uuid::parse_str(value.as_ref())?.into())
}
}
};
}
try_from_impl_for_mbid!(&str);
try_from_impl_for_mbid!(&String);
try_from_impl_for_mbid!(String);
/// Error type for musicbrainz calls.
#[derive(Debug, PartialEq, Eq)]
pub enum Error {
/// Failed to parse input into an MBID.
MbidParse(String),
/// The API client failed.
Client(String),
/// The client reached the API rate limit.
RateLimit,
/// The API response could not be understood.
Unknown(u16),
/// Part of the response could not be parsed.
Parse(String),
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Error::MbidParse(s) => write!(f, "failed to parse input into an MBID: {s}"),
Error::Client(s) => write!(f, "the API client failed: {s}"),
Error::RateLimit => write!(f, "the API client reached the rate limit"),
Error::Unknown(u) => write!(f, "the API response could not be understood: status {u}"),
Error::Parse(s) => write!(f, "part of the response could not be parsed: {s}"),
}
}
}
impl From<uuid::Error> for Error {
fn from(value: uuid::Error) -> Self {
Error::MbidParse(value.to_string())
}
}
impl From<num::ParseIntError> for Error {
fn from(err: num::ParseIntError) -> Error {
Error::Parse(err.to_string())
}
}
#[cfg(test)]
mod tests {
use crate::core::collection::album::{AlbumDate, AlbumId};
use super::*;
#[test]
fn null_lookup_artist_release_groups() {
let mut musicbrainz = NullMusicBrainz;
let mbid: Mbid = "d368baa8-21ca-4759-9731-0b2753071ad8".try_into().unwrap();
assert!(musicbrainz
.lookup_artist_release_groups(&mbid)
.unwrap()
.is_empty());
}
#[test]
fn null_search_release_group() {
let mut musicbrainz = NullMusicBrainz;
let mbid: Mbid = "d368baa8-21ca-4759-9731-0b2753071ad8".try_into().unwrap();
let album = Album::new(AlbumId::new("an album"), AlbumDate::default(), None, vec![]);
assert!(musicbrainz
.search_release_group(&mbid, album)
.unwrap()
.is_empty());
}
#[test]
fn match_type() {
let album = Album::new(AlbumId::new("an album"), AlbumDate::default(), None, vec![]);
let hit = Match::new(56, album);
assert!(!format!("{hit:?}").is_empty());
}
#[test]
fn errors() {
let mbid_err: Error = TryInto::<Mbid>::try_into("i-am-not-a-uuid").unwrap_err();
assert!(!mbid_err.to_string().is_empty());
assert!(!format!("{mbid_err:?}").is_empty());
let client_err: Error = Error::Client(String::from("a client error"));
assert!(!client_err.to_string().is_empty());
assert!(!format!("{client_err:?}").is_empty());
let rate_err: Error = Error::RateLimit;
assert!(!rate_err.to_string().is_empty());
assert!(!format!("{rate_err:?}").is_empty());
let unk_err: Error = Error::Unknown(404);
assert!(!unk_err.to_string().is_empty());
assert!(!format!("{unk_err:?}").is_empty());
let parse_err: Error = "not-a-number".parse::<u32>().unwrap_err().into();
assert!(!parse_err.to_string().is_empty());
assert!(!format!("{parse_err:?}").is_empty());
}
}

View File

@ -1,11 +1,8 @@
use std::collections::HashMap; use std::collections::HashMap;
use crate::{ use crate::core::{
core::{ interface::{database::IDatabase, library::ILibrary},
interface::{database::IDatabase, library::ILibrary}, musichoard::{database::IMusicHoardDatabase, Error, MusicHoard, NoDatabase, NoLibrary},
musichoard::{database::IMusicHoardDatabase, MusicHoard, NoDatabase, NoLibrary},
},
Error,
}; };
/// Builder for [`MusicHoard`]. Its purpose is to make it easier to set various combinations of /// Builder for [`MusicHoard`]. Its purpose is to make it easier to set various combinations of

View File

@ -2,7 +2,7 @@ use crate::core::{
collection::{ collection::{
album::{Album, AlbumId, AlbumSeq}, album::{Album, AlbumId, AlbumSeq},
artist::{Artist, ArtistId}, artist::{Artist, ArtistId},
musicbrainz::MusicBrainzUrl, musicbrainz::MbArtistRef,
Collection, Collection,
}, },
interface::database::IDatabase, interface::database::IDatabase,
@ -123,15 +123,15 @@ impl<Database: IDatabase, Library> IMusicHoardDatabase for MusicHoard<Database,
artist_id: Id, artist_id: Id,
url: S, url: S,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mb = MusicBrainzUrl::artist_from_str(url)?; let mb = MbArtistRef::from_url_str(url)?;
self.update_artist(artist_id.as_ref(), |artist| artist.set_musicbrainz_url(mb)) self.update_artist(artist_id.as_ref(), |artist| artist.set_musicbrainz_ref(mb))
} }
fn clear_artist_musicbrainz<Id: AsRef<ArtistId>>( fn clear_artist_musicbrainz<Id: AsRef<ArtistId>>(
&mut self, &mut self,
artist_id: Id, artist_id: Id,
) -> Result<(), Error> { ) -> Result<(), Error> {
self.update_artist(artist_id.as_ref(), |artist| artist.clear_musicbrainz_url()) self.update_artist(artist_id.as_ref(), |artist| artist.clear_musicbrainz_ref())
} }
fn add_to_artist_property<Id: AsRef<ArtistId>, S: AsRef<str> + Into<String>>( fn add_to_artist_property<Id: AsRef<ArtistId>, S: AsRef<str> + Into<String>>(
@ -290,7 +290,7 @@ mod tests {
use mockall::{predicate, Sequence}; use mockall::{predicate, Sequence};
use crate::core::{ use crate::core::{
collection::{album::AlbumDate, artist::ArtistId, musicbrainz::MusicBrainzUrl}, collection::{album::AlbumDate, artist::ArtistId},
interface::database::{self, MockIDatabase}, interface::database::{self, MockIDatabase},
musichoard::{base::IMusicHoardBase, NoLibrary}, musichoard::{base::IMusicHoardBase, NoLibrary},
testmod::FULL_COLLECTION, testmod::FULL_COLLECTION,
@ -433,7 +433,7 @@ mod tests {
assert!(music_hoard.add_artist(artist_id.clone()).is_ok()); assert!(music_hoard.add_artist(artist_id.clone()).is_ok());
let mut expected: Option<MusicBrainzUrl> = None; let mut expected: Option<MbArtistRef> = None;
assert_eq!(music_hoard.collection[0].musicbrainz, expected); assert_eq!(music_hoard.collection[0].musicbrainz, expected);
// Setting a URL on an artist not in the collection is an error. // Setting a URL on an artist not in the collection is an error.
@ -446,7 +446,7 @@ mod tests {
assert!(music_hoard assert!(music_hoard
.set_artist_musicbrainz(&artist_id, MUSICBRAINZ) .set_artist_musicbrainz(&artist_id, MUSICBRAINZ)
.is_ok()); .is_ok());
_ = expected.insert(MusicBrainzUrl::artist_from_str(MUSICBRAINZ).unwrap()); _ = expected.insert(MbArtistRef::from_url_str(MUSICBRAINZ).unwrap());
assert_eq!(music_hoard.collection[0].musicbrainz, expected); assert_eq!(music_hoard.collection[0].musicbrainz, expected);
// Clearing URLs on an artist that does not exist is an error. // Clearing URLs on an artist that does not exist is an error.
@ -567,9 +567,12 @@ mod tests {
let album_id_2 = AlbumId::new("another album"); let album_id_2 = AlbumId::new("another album");
let mut database_result = vec![Artist::new(artist_id.clone())]; let mut database_result = vec![Artist::new(artist_id.clone())];
database_result[0] database_result[0].albums.push(Album::new(
.albums album_id.clone(),
.push(Album::new(album_id.clone(), AlbumDate::default())); AlbumDate::default(),
None,
vec![],
));
database database
.expect_load() .expect_load()

View File

@ -59,9 +59,9 @@ impl<Database, Library: ILibrary> MusicHoard<Database, Library> {
}; };
let album_date = AlbumDate { let album_date = AlbumDate {
year: item.album_year, year: Some(item.album_year).filter(|y| y > &0),
month: item.album_month, month: Some(item.album_month).filter(|m| m > &0),
day: item.album_day, day: Some(item.album_day).filter(|d| d > &0),
}; };
let track = Track { let track = Track {
@ -109,7 +109,7 @@ impl<Database, Library: ILibrary> MusicHoard<Database, Library> {
{ {
Some(album) => album.tracks.push(track), Some(album) => album.tracks.push(track),
None => { None => {
let mut album = Album::new(album_id, album_date); let mut album = Album::new(album_id, album_date, None, vec![]);
album.tracks.push(track); album.tracks.push(track);
artist.albums.push(album); artist.albums.push(album);
} }

View File

@ -2,12 +2,12 @@ use once_cell::sync::Lazy;
use std::collections::HashMap; use std::collections::HashMap;
use crate::core::collection::{ use crate::core::collection::{
album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq}, album::{Album, AlbumId, AlbumPrimaryType, AlbumSeq},
artist::{Artist, ArtistId}, artist::{Artist, ArtistId},
musicbrainz::MusicBrainzUrl, musicbrainz::{MbAlbumRef, MbArtistRef},
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality}, track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
}; };
use crate::tests::*; use crate::testmod::*;
pub static LIBRARY_COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| library_collection!()); pub static LIBRARY_COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| library::library_collection!());
pub static FULL_COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| full_collection!()); pub static FULL_COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| full::full_collection!());

View File

@ -3,7 +3,7 @@
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
use crate::database::json::IJsonDatabaseBackend; use crate::external::database::json::IJsonDatabaseBackend;
/// JSON database backend that uses a local file for persistent storage. /// JSON database backend that uses a local file for persistent storage.
pub struct JsonDatabaseFileBackend { pub struct JsonDatabaseFileBackend {

View File

@ -109,6 +109,7 @@ mod tests {
fn load() { fn load() {
let expected = expected(); let expected = expected();
let result = Ok(DATABASE_JSON.to_owned()); let result = Ok(DATABASE_JSON.to_owned());
eprintln!("{DATABASE_JSON}");
let mut backend = MockIJsonDatabaseBackend::new(); let mut backend = MockIJsonDatabaseBackend::new();
backend.expect_read().times(1).return_once(|| result); backend.expect_read().times(1).return_once(|| result);

View File

@ -1,5 +1,5 @@
pub static DATABASE_JSON: &str = "{\ pub static DATABASE_JSON: &str = "{\
\"V20240308\":\ \"V20240313\":\
[\ [\
{\ {\
\"name\":\"Album_Artist A\",\ \"name\":\"Album_Artist A\",\
@ -12,9 +12,13 @@ pub static DATABASE_JSON: &str = "{\
\"albums\":[\ \"albums\":[\
{\ {\
\"title\":\"album_title a.a\",\"seq\":1,\ \"title\":\"album_title a.a\",\"seq\":1,\
\"musicbrainz\":\"https://musicbrainz.org/release-group/00000000-0000-0000-0000-000000000000\"\ \"musicbrainz\":\"https://musicbrainz.org/release-group/00000000-0000-0000-0000-000000000000\",\
\"primary_type\":\"Album\",\"secondary_types\":[]\
},\ },\
{\"title\":\"album_title a.b\",\"seq\":1,\"musicbrainz\":null}\ {\
\"title\":\"album_title a.b\",\"seq\":1,\"musicbrainz\":null,\
\"primary_type\":\"Album\",\"secondary_types\":[]\
}\
]\ ]\
},\ },\
{\ {\
@ -30,16 +34,24 @@ pub static DATABASE_JSON: &str = "{\
\"Qobuz\":[\"https://www.qobuz.com/nl-nl/interpreter/artist-b/download-streaming-albums\"]\ \"Qobuz\":[\"https://www.qobuz.com/nl-nl/interpreter/artist-b/download-streaming-albums\"]\
},\ },\
\"albums\":[\ \"albums\":[\
{\"title\":\"album_title b.a\",\"seq\":1,\"musicbrainz\":null},\ {\
\"title\":\"album_title b.a\",\"seq\":1,\"musicbrainz\":null,\
\"primary_type\":\"Album\",\"secondary_types\":[]\
},\
{\ {\
\"title\":\"album_title b.b\",\"seq\":3,\ \"title\":\"album_title b.b\",\"seq\":3,\
\"musicbrainz\":\"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111111\"\ \"musicbrainz\":\"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111111\",\
\"primary_type\":\"Album\",\"secondary_types\":[]\
},\ },\
{\ {\
\"title\":\"album_title b.c\",\"seq\":2,\ \"title\":\"album_title b.c\",\"seq\":2,\
\"musicbrainz\":\"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111112\"\ \"musicbrainz\":\"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111112\",\
\"primary_type\":\"Album\",\"secondary_types\":[]\
},\ },\
{\"title\":\"album_title b.d\",\"seq\":4,\"musicbrainz\":null}\ {\
\"title\":\"album_title b.d\",\"seq\":4,\"musicbrainz\":null,\
\"primary_type\":\"Album\",\"secondary_types\":[]\
}\
]\ ]\
},\ },\
{\ {\
@ -48,8 +60,14 @@ pub static DATABASE_JSON: &str = "{\
\"musicbrainz\":\"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111\",\ \"musicbrainz\":\"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111\",\
\"properties\":{},\ \"properties\":{},\
\"albums\":[\ \"albums\":[\
{\"title\":\"album_title c.a\",\"seq\":0,\"musicbrainz\":null},\ {\
{\"title\":\"album_title c.b\",\"seq\":0,\"musicbrainz\":null}\ \"title\":\"album_title c.a\",\"seq\":0,\"musicbrainz\":null,\
\"primary_type\":\"Album\",\"secondary_types\":[]\
},\
{\
\"title\":\"album_title c.b\",\"seq\":0,\"musicbrainz\":null,\
\"primary_type\":\"Album\",\"secondary_types\":[]\
}\
]\ ]\
},\ },\
{\ {\
@ -58,8 +76,14 @@ pub static DATABASE_JSON: &str = "{\
\"musicbrainz\":null,\ \"musicbrainz\":null,\
\"properties\":{},\ \"properties\":{},\
\"albums\":[\ \"albums\":[\
{\"title\":\"album_title d.a\",\"seq\":0,\"musicbrainz\":null},\ {\
{\"title\":\"album_title d.b\",\"seq\":0,\"musicbrainz\":null}\ \"title\":\"album_title d.a\",\"seq\":0,\"musicbrainz\":null,\
\"primary_type\":\"Album\",\"secondary_types\":[]\
},\
{\
\"title\":\"album_title d.b\",\"seq\":0,\"musicbrainz\":null,\
\"primary_type\":\"Album\",\"secondary_types\":[]\
}\
]\ ]\
}\ }\
]\ ]\

62
src/external/database/serde/common.rs vendored Normal file
View File

@ -0,0 +1,62 @@
use serde::{Deserialize, Serialize};
use crate::core::collection::album::{AlbumPrimaryType, AlbumSecondaryType};
#[derive(Debug, Deserialize, Serialize)]
#[serde(remote = "AlbumPrimaryType")]
pub enum SerdeAlbumPrimaryTypeDef {
Album,
Single,
Ep,
Broadcast,
Other,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct SerdeAlbumPrimaryType(#[serde(with = "SerdeAlbumPrimaryTypeDef")] AlbumPrimaryType);
impl From<SerdeAlbumPrimaryType> for AlbumPrimaryType {
fn from(value: SerdeAlbumPrimaryType) -> Self {
value.0
}
}
impl From<AlbumPrimaryType> for SerdeAlbumPrimaryType {
fn from(value: AlbumPrimaryType) -> Self {
SerdeAlbumPrimaryType(value)
}
}
#[derive(Debug, Deserialize, Serialize)]
#[serde(remote = "AlbumSecondaryType")]
pub enum SerdeAlbumSecondaryTypeDef {
Compilation,
Soundtrack,
Spokenword,
Interview,
Audiobook,
AudioDrama,
Live,
Remix,
DjMix,
MixtapeStreet,
Demo,
FieldRecording,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct SerdeAlbumSecondaryType(
#[serde(with = "SerdeAlbumSecondaryTypeDef")] AlbumSecondaryType,
);
impl From<SerdeAlbumSecondaryType> for AlbumSecondaryType {
fn from(value: SerdeAlbumSecondaryType) -> Self {
value.0
}
}
impl From<AlbumSecondaryType> for SerdeAlbumSecondaryType {
fn from(value: AlbumSecondaryType) -> Self {
SerdeAlbumSecondaryType(value)
}
}

View File

@ -2,19 +2,22 @@ use std::collections::HashMap;
use serde::Deserialize; use serde::Deserialize;
use crate::core::{ use crate::{
collection::{ core::{
album::{Album, AlbumDate, AlbumId, AlbumSeq}, collection::{
artist::{Artist, ArtistId}, album::{Album, AlbumDate, AlbumId, AlbumSeq},
musicbrainz::MusicBrainzUrl, artist::{Artist, ArtistId},
Collection, musicbrainz::{MbAlbumRef, MbArtistRef},
Collection,
},
interface::database::LoadError,
}, },
interface::database::LoadError, external::database::serde::common::{SerdeAlbumPrimaryType, SerdeAlbumSecondaryType},
}; };
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub enum DeserializeDatabase { pub enum DeserializeDatabase {
V20240308(Vec<DeserializeArtist>), V20240313(Vec<DeserializeArtist>),
} }
impl TryFrom<DeserializeDatabase> for Collection { impl TryFrom<DeserializeDatabase> for Collection {
@ -22,10 +25,9 @@ impl TryFrom<DeserializeDatabase> for Collection {
fn try_from(database: DeserializeDatabase) -> Result<Self, Self::Error> { fn try_from(database: DeserializeDatabase) -> Result<Self, Self::Error> {
match database { match database {
DeserializeDatabase::V20240308(collection) => collection DeserializeDatabase::V20240313(collection) => {
.into_iter() collection.into_iter().map(TryInto::try_into).collect()
.map(|artist| artist.try_into()) }
.collect(),
} }
} }
} }
@ -44,6 +46,8 @@ pub struct DeserializeAlbum {
title: String, title: String,
seq: u8, seq: u8,
musicbrainz: Option<String>, musicbrainz: Option<String>,
primary_type: Option<SerdeAlbumPrimaryType>,
secondary_types: Vec<SerdeAlbumSecondaryType>,
} }
impl TryFrom<DeserializeArtist> for Artist { impl TryFrom<DeserializeArtist> for Artist {
@ -55,7 +59,7 @@ impl TryFrom<DeserializeArtist> for Artist {
sort: artist.sort.map(ArtistId::new), sort: artist.sort.map(ArtistId::new),
musicbrainz: artist musicbrainz: artist
.musicbrainz .musicbrainz
.map(MusicBrainzUrl::artist_from_str) .map(MbArtistRef::from_url_str)
.transpose()?, .transpose()?,
properties: artist.properties, properties: artist.properties,
albums: artist albums: artist
@ -77,8 +81,10 @@ impl TryFrom<DeserializeAlbum> for Album {
seq: AlbumSeq(album.seq), seq: AlbumSeq(album.seq),
musicbrainz: album musicbrainz: album
.musicbrainz .musicbrainz
.map(MusicBrainzUrl::album_from_str) .map(MbAlbumRef::from_url_str)
.transpose()?, .transpose()?,
primary_type: album.primary_type.map(Into::into),
secondary_types: album.secondary_types.into_iter().map(Into::into).collect(),
tracks: vec![], tracks: vec![],
}) })
} }

View File

@ -1,4 +1,5 @@
//! Helper module for backends that can use serde for (de)serialisation. //! Helper module for backends that can use serde for (de)serialisation.
mod common;
pub mod deserialize; pub mod deserialize;
pub mod serialize; pub mod serialize;

View File

@ -2,16 +2,19 @@ use std::collections::BTreeMap;
use serde::Serialize; use serde::Serialize;
use crate::core::collection::{album::Album, artist::Artist, Collection}; use crate::{
core::collection::{album::Album, artist::Artist, musicbrainz::IMusicBrainzRef, Collection},
external::database::serde::common::{SerdeAlbumPrimaryType, SerdeAlbumSecondaryType},
};
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub enum SerializeDatabase<'a> { pub enum SerializeDatabase<'a> {
V20240308(Vec<SerializeArtist<'a>>), V20240313(Vec<SerializeArtist<'a>>),
} }
impl<'a> From<&'a Collection> for SerializeDatabase<'a> { impl<'a> From<&'a Collection> for SerializeDatabase<'a> {
fn from(collection: &'a Collection) -> Self { fn from(collection: &'a Collection) -> Self {
SerializeDatabase::V20240308(collection.iter().map(Into::into).collect()) SerializeDatabase::V20240313(collection.iter().map(Into::into).collect())
} }
} }
@ -29,6 +32,8 @@ pub struct SerializeAlbum<'a> {
title: &'a str, title: &'a str,
seq: u8, seq: u8,
musicbrainz: Option<&'a str>, musicbrainz: Option<&'a str>,
primary_type: Option<SerdeAlbumPrimaryType>,
secondary_types: Vec<SerdeAlbumSecondaryType>,
} }
impl<'a> From<&'a Artist> for SerializeArtist<'a> { impl<'a> From<&'a Artist> for SerializeArtist<'a> {
@ -36,7 +41,7 @@ impl<'a> From<&'a Artist> for SerializeArtist<'a> {
SerializeArtist { SerializeArtist {
name: &artist.id.name, name: &artist.id.name,
sort: artist.sort.as_ref().map(|id| id.name.as_ref()), sort: artist.sort.as_ref().map(|id| id.name.as_ref()),
musicbrainz: artist.musicbrainz.as_ref().map(AsRef::as_ref), musicbrainz: artist.musicbrainz.as_ref().map(|mb| mb.url().as_str()),
properties: artist properties: artist
.properties .properties
.iter() .iter()
@ -52,7 +57,14 @@ impl<'a> From<&'a Album> for SerializeAlbum<'a> {
SerializeAlbum { SerializeAlbum {
title: &album.id.title, title: &album.id.title,
seq: album.seq.0, seq: album.seq.0,
musicbrainz: album.musicbrainz.as_ref().map(AsRef::as_ref), musicbrainz: album.musicbrainz.as_ref().map(|mb| mb.url().as_str()),
primary_type: album.primary_type.map(Into::into),
secondary_types: album
.secondary_types
.iter()
.copied()
.map(Into::into)
.collect(),
} }
} }
} }

View File

@ -8,8 +8,7 @@ use std::{
str, str,
}; };
use crate::core::interface::library::Error; use crate::{core::interface::library::Error, external::library::beets::IBeetsLibraryExecutor};
use crate::library::beets::IBeetsLibraryExecutor;
const BEET_DEFAULT: &str = "beet"; const BEET_DEFAULT: &str = "beet";

View File

@ -76,7 +76,7 @@ impl ToBeetsArg for Field {
Field::AlbumArtist(ref s) => format!("{negate}albumartist:{s}"), Field::AlbumArtist(ref s) => format!("{negate}albumartist:{s}"),
Field::AlbumArtistSort(ref s) => format!("{negate}albumartist_sort:{s}"), Field::AlbumArtistSort(ref s) => format!("{negate}albumartist_sort:{s}"),
Field::AlbumYear(ref u) => format!("{negate}year:{u}"), Field::AlbumYear(ref u) => format!("{negate}year:{u}"),
Field::AlbumMonth(ref e) => format!("{negate}month:{}", *e as u8), Field::AlbumMonth(ref e) => format!("{negate}month:{}", { *e }),
Field::AlbumDay(ref u) => format!("{negate}day:{u}"), Field::AlbumDay(ref u) => format!("{negate}day:{u}"),
Field::AlbumTitle(ref s) => format!("{negate}album:{s}"), Field::AlbumTitle(ref s) => format!("{negate}album:{s}"),
Field::TrackNumber(ref u) => format!("{negate}track:{u}"), Field::TrackNumber(ref u) => format!("{negate}track:{u}"),
@ -111,11 +111,6 @@ pub struct BeetsLibrary<BLE> {
executor: BLE, executor: BLE,
} }
trait ILibraryPrivate {
fn list_cmd_and_args(query: &Query) -> Vec<String>;
fn list_to_items<S: AsRef<str>>(list_output: &[S]) -> Result<Vec<Item>, Error>;
}
impl<BLE: IBeetsLibraryExecutor> BeetsLibrary<BLE> { impl<BLE: IBeetsLibraryExecutor> BeetsLibrary<BLE> {
/// Create a new beets library with the provided executor, e.g. /// Create a new beets library with the provided executor, e.g.
/// [`executor::BeetsLibraryProcessExecutor`]. /// [`executor::BeetsLibraryProcessExecutor`].
@ -132,7 +127,7 @@ impl<BLE: IBeetsLibraryExecutor> ILibrary for BeetsLibrary<BLE> {
} }
} }
impl<BLE: IBeetsLibraryExecutor> ILibraryPrivate for BeetsLibrary<BLE> { impl<BLE: IBeetsLibraryExecutor> BeetsLibrary<BLE> {
fn list_cmd_and_args(query: &Query) -> Vec<String> { fn list_cmd_and_args(query: &Query) -> Vec<String> {
let mut cmd: Vec<String> = vec![String::from(CMD_LIST)]; let mut cmd: Vec<String> = vec![String::from(CMD_LIST)];
cmd.push(LIST_FORMAT_ARG.to_string()); cmd.push(LIST_FORMAT_ARG.to_string());
@ -159,7 +154,7 @@ impl<BLE: IBeetsLibraryExecutor> ILibraryPrivate for BeetsLibrary<BLE> {
false => None, false => None,
}; };
let album_year = split[2].parse::<u32>()?; let album_year = split[2].parse::<u32>()?;
let album_month = split[3].parse::<u8>()?.into(); let album_month = split[3].parse::<u8>()?;
let album_day = split[4].parse::<u8>()?; let album_day = split[4].parse::<u8>()?;
let album_title = split[5].to_string(); let album_title = split[5].to_string();
let track_number = split[6].parse::<u32>()?; let track_number = split[6].parse::<u32>()?;
@ -201,7 +196,7 @@ mod testmod;
mod tests { mod tests {
use mockall::predicate; use mockall::predicate;
use crate::{collection::album::AlbumMonth, core::interface::library::testmod::LIBRARY_ITEMS}; use crate::core::interface::library::testmod::LIBRARY_ITEMS;
use super::*; use super::*;
use testmod::LIBRARY_BEETS; use testmod::LIBRARY_BEETS;
@ -235,7 +230,7 @@ mod tests {
.exclude(Field::AlbumArtist(String::from("some.albumartist"))) .exclude(Field::AlbumArtist(String::from("some.albumartist")))
.exclude(Field::AlbumArtistSort(String::from("some.albumartist"))) .exclude(Field::AlbumArtistSort(String::from("some.albumartist")))
.include(Field::AlbumYear(3030)) .include(Field::AlbumYear(3030))
.include(Field::AlbumMonth(AlbumMonth::April)) .include(Field::AlbumMonth(4))
.include(Field::AlbumDay(6)) .include(Field::AlbumDay(6))
.include(Field::TrackTitle(String::from("some.track"))) .include(Field::TrackTitle(String::from("some.track")))
.include(Field::TrackFormat(TrackFormat::Flac)) .include(Field::TrackFormat(TrackFormat::Flac))

3
src/external/mod.rs vendored Normal file
View File

@ -0,0 +1,3 @@
pub mod database;
pub mod library;
pub mod musicbrainz;

46
src/external/musicbrainz/api/client.rs vendored Normal file
View File

@ -0,0 +1,46 @@
//! Module for interacting with the MusicBrainz API via an HTTP client.
use reqwest::{self, blocking::Client, header};
use serde::de::DeserializeOwned;
use crate::external::musicbrainz::api::{ClientError, IMusicBrainzApiClient};
// GRCOV_EXCL_START
pub struct MusicBrainzApiClient(Client);
impl MusicBrainzApiClient {
pub fn new(user_agent: &'static str) -> Result<Self, ClientError> {
let mut headers = header::HeaderMap::new();
headers.insert(
header::USER_AGENT,
header::HeaderValue::from_static(user_agent),
);
headers.insert(
header::ACCEPT,
header::HeaderValue::from_static("application/json"),
);
Ok(MusicBrainzApiClient(
Client::builder().default_headers(headers).build()?,
))
}
}
impl IMusicBrainzApiClient for MusicBrainzApiClient {
fn get<D: DeserializeOwned + 'static>(&mut self, url: &str) -> Result<D, ClientError> {
let response = self.0.get(url).send()?;
if response.status().is_success() {
Ok(response.json()?)
} else {
Err(ClientError::Status(response.status().as_u16()))
}
}
}
impl From<reqwest::Error> for ClientError {
fn from(err: reqwest::Error) -> Self {
ClientError::Client(err.to_string())
}
}
// GRCOV_EXCL_STOP

404
src/external/musicbrainz/api/mod.rs vendored Normal file
View File

@ -0,0 +1,404 @@
//! Module for interacting with the [MusicBrainz API](https://musicbrainz.org/doc/MusicBrainz_API).
pub mod client;
use serde::{de::DeserializeOwned, Deserialize};
use url::form_urlencoded;
#[cfg(test)]
use mockall::automock;
use crate::core::{
collection::{
album::{Album, AlbumDate, AlbumPrimaryType, AlbumSecondaryType},
musicbrainz::MbAlbumRef,
},
interface::musicbrainz::{Error, IMusicBrainz, Match, Mbid},
};
const MB_BASE_URL: &str = "https://musicbrainz.org/ws/2";
const MB_RATE_LIMIT_CODE: u16 = 503;
#[cfg_attr(test, automock)]
pub trait IMusicBrainzApiClient {
fn get<D: DeserializeOwned + 'static>(&mut self, url: &str) -> Result<D, ClientError>;
}
#[derive(Debug)]
pub enum ClientError {
Client(String),
Status(u16),
}
impl From<ClientError> for Error {
fn from(err: ClientError) -> Self {
match err {
ClientError::Client(s) => Error::Client(s),
ClientError::Status(status) => match status {
MB_RATE_LIMIT_CODE => Error::RateLimit,
_ => Error::Unknown(status),
},
}
}
}
pub struct MusicBrainzApi<Mbc> {
client: Mbc,
}
impl<Mbc> MusicBrainzApi<Mbc> {
pub fn new(client: Mbc) -> Self {
MusicBrainzApi { client }
}
}
impl<Mbc: IMusicBrainzApiClient> IMusicBrainz for MusicBrainzApi<Mbc> {
fn lookup_artist_release_groups(&mut self, mbid: &Mbid) -> Result<Vec<Album>, Error> {
let mbid = mbid.uuid().as_hyphenated().to_string();
let artist: ResponseLookupArtist = self
.client
.get(&format!("{MB_BASE_URL}/artist/{mbid}?inc=release-groups"))?;
artist
.release_groups
.into_iter()
.map(TryInto::try_into)
.collect()
}
fn search_release_group(
&mut self,
arid: &Mbid,
album: Album,
) -> Result<Vec<Match<Album>>, Error> {
let title = &album.id.title;
let arid = arid.uuid().as_hyphenated().to_string();
let mut query = format!("\"{title}\" AND arid:{arid}");
if let Some(year) = album.date.year {
query.push_str(&format!(" AND firstreleasedate:{year}"));
}
let query: String = form_urlencoded::byte_serialize(query.as_bytes()).collect();
let results: ResponseSearchReleaseGroup = self
.client
.get(&format!("{MB_BASE_URL}/release-group?query={query}"))?;
results
.release_groups
.into_iter()
.map(TryInto::try_into)
.collect()
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
struct ResponseLookupArtist {
release_groups: Vec<LookupReleaseGroup>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
struct LookupReleaseGroup {
id: String,
title: String,
first_release_date: String,
primary_type: SerdeAlbumPrimaryType,
secondary_types: Vec<SerdeAlbumSecondaryType>,
}
impl TryFrom<LookupReleaseGroup> for Album {
type Error = Error;
fn try_from(entity: LookupReleaseGroup) -> Result<Self, Self::Error> {
let mut album = Album::new(
entity.title,
AlbumDate::from_mb_date(&entity.first_release_date)?,
Some(entity.primary_type.into()),
entity.secondary_types.into_iter().map(Into::into).collect(),
);
let mbref = MbAlbumRef::from_uuid_str(entity.id)
.map_err(|err| Error::MbidParse(err.to_string()))?;
album.set_musicbrainz_ref(mbref);
Ok(album)
}
}
#[derive(Debug, Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
struct ResponseSearchReleaseGroup {
release_groups: Vec<SearchReleaseGroup>,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all(deserialize = "kebab-case"))]
struct SearchReleaseGroup {
score: u8,
id: String,
title: String,
first_release_date: String,
primary_type: SerdeAlbumPrimaryType,
}
impl TryFrom<SearchReleaseGroup> for Match<Album> {
type Error = Error;
fn try_from(entity: SearchReleaseGroup) -> Result<Self, Self::Error> {
let mut album = Album::new(
entity.title,
AlbumDate::from_mb_date(&entity.first_release_date)?,
Some(entity.primary_type.into()),
vec![],
);
let mbref = MbAlbumRef::from_uuid_str(entity.id)
.map_err(|err| Error::MbidParse(err.to_string()))?;
album.set_musicbrainz_ref(mbref);
Ok(Match::new(entity.score, album))
}
}
impl AlbumDate {
fn from_mb_date(mb_date: &str) -> Result<AlbumDate, Error> {
let mut elems = mb_date.split('-');
let elem = elems.next();
let year = elem
.and_then(|s| if s.is_empty() { None } else { Some(s.parse()) })
.transpose()?;
let elem = elems.next();
let month = elem.map(|s| s.parse()).transpose()?;
let elem = elems.next();
let day = elem.map(|s| s.parse()).transpose()?;
Ok(AlbumDate::new(year, month, day))
}
}
#[derive(Debug, Deserialize)]
#[serde(remote = "AlbumPrimaryType")]
pub enum SerdeAlbumPrimaryTypeDef {
Album,
Single,
#[serde(rename = "EP")]
Ep,
Broadcast,
Other,
}
#[derive(Debug, Deserialize)]
pub struct SerdeAlbumPrimaryType(#[serde(with = "SerdeAlbumPrimaryTypeDef")] AlbumPrimaryType);
impl From<SerdeAlbumPrimaryType> for AlbumPrimaryType {
fn from(value: SerdeAlbumPrimaryType) -> Self {
value.0
}
}
#[derive(Debug, Deserialize)]
#[serde(remote = "AlbumSecondaryType")]
pub enum SerdeAlbumSecondaryTypeDef {
Compilation,
Soundtrack,
Spokenword,
Interview,
Audiobook,
#[serde(rename = "Audio drama")]
AudioDrama,
Live,
Remix,
#[serde(rename = "DJ-mix")]
DjMix,
#[serde(rename = "Mixtape/Street")]
MixtapeStreet,
Demo,
#[serde(rename = "Field recording")]
FieldRecording,
}
#[derive(Debug, Deserialize)]
pub struct SerdeAlbumSecondaryType(
#[serde(with = "SerdeAlbumSecondaryTypeDef")] AlbumSecondaryType,
);
impl From<SerdeAlbumSecondaryType> for AlbumSecondaryType {
fn from(value: SerdeAlbumSecondaryType) -> Self {
value.0
}
}
#[cfg(test)]
mod tests {
use mockall::predicate;
use crate::collection::album::AlbumId;
use super::*;
#[test]
fn lookup_artist_release_group() {
let mut client = MockIMusicBrainzApiClient::new();
let url = format!(
"https://musicbrainz.org/ws/2/artist/{mbid}?inc=release-groups",
mbid = "00000000-0000-0000-0000-000000000000",
);
let release_group = LookupReleaseGroup {
id: String::from("11111111-1111-1111-1111-111111111111"),
title: String::from("an album"),
first_release_date: String::from("1986-04"),
primary_type: SerdeAlbumPrimaryType(AlbumPrimaryType::Album),
secondary_types: vec![SerdeAlbumSecondaryType(AlbumSecondaryType::Compilation)],
};
let response = ResponseLookupArtist {
release_groups: vec![release_group],
};
// For code coverage of derive(Debug).
assert!(!format!("{response:?}").is_empty());
client
.expect_get()
.times(1)
.with(predicate::eq(url))
.return_once(|_| Ok(response));
let mut api = MusicBrainzApi::new(client);
let mbid: Mbid = "00000000-0000-0000-0000-000000000000".try_into().unwrap();
let results = api.lookup_artist_release_groups(&mbid).unwrap();
let mut album = Album::new(
AlbumId::new("an album"),
(1986, 4),
Some(AlbumPrimaryType::Album),
vec![AlbumSecondaryType::Compilation],
);
album.set_musicbrainz_ref(
MbAlbumRef::from_uuid_str("11111111-1111-1111-1111-111111111111").unwrap(),
);
let expected = vec![album];
assert_eq!(results, expected);
}
#[test]
fn search_release_group() {
let mut client = MockIMusicBrainzApiClient::new();
let url = format!(
"https://musicbrainz.org/ws/2\
/release-group\
?query=%22{title}%22+AND+arid%3A{arid}+AND+firstreleasedate%3A{year}",
title = "an+album",
arid = "00000000-0000-0000-0000-000000000000",
year = "1986"
);
let release_group = SearchReleaseGroup {
score: 67,
id: String::from("11111111-1111-1111-1111-111111111111"),
title: String::from("an album"),
first_release_date: String::from("1986-04"),
primary_type: SerdeAlbumPrimaryType(AlbumPrimaryType::Album),
};
let response = ResponseSearchReleaseGroup {
release_groups: vec![release_group],
};
// For code coverage of derive(Debug).
assert!(!format!("{response:?}").is_empty());
client
.expect_get()
.times(1)
.with(predicate::eq(url))
.return_once(|_| Ok(response));
let mut api = MusicBrainzApi::new(client);
let arid: Mbid = "00000000-0000-0000-0000-000000000000".try_into().unwrap();
let album = Album::new(AlbumId::new("an album"), (1986, 4), None, vec![]);
let matches = api.search_release_group(&arid, album).unwrap();
let mut album = Album::new(
AlbumId::new("an album"),
(1986, 4),
Some(AlbumPrimaryType::Album),
vec![],
);
album.set_musicbrainz_ref(
MbAlbumRef::from_uuid_str("11111111-1111-1111-1111-111111111111").unwrap(),
);
let expected = vec![Match::new(67, album)];
assert_eq!(matches, expected);
}
#[test]
fn client_errors() {
let mut client = MockIMusicBrainzApiClient::new();
let error = ClientError::Client(String::from("get rekt"));
assert!(!format!("{error:?}").is_empty());
client
.expect_get::<ResponseLookupArtist>()
.times(1)
.return_once(|_| Err(ClientError::Client(String::from("get rekt scrub"))));
client
.expect_get::<ResponseLookupArtist>()
.times(1)
.return_once(|_| Err(ClientError::Status(503)));
client
.expect_get::<ResponseLookupArtist>()
.times(1)
.return_once(|_| Err(ClientError::Status(504)));
let mut api = MusicBrainzApi::new(client);
let mbid: Mbid = "00000000-0000-0000-0000-000000000000".try_into().unwrap();
let error = api.lookup_artist_release_groups(&mbid).unwrap_err();
assert_eq!(error, Error::Client(String::from("get rekt scrub")));
let error = api.lookup_artist_release_groups(&mbid).unwrap_err();
assert_eq!(error, Error::RateLimit);
let error = api.lookup_artist_release_groups(&mbid).unwrap_err();
assert_eq!(error, Error::Unknown(504));
}
#[test]
fn from_mb_date() {
assert_eq!(AlbumDate::from_mb_date("").unwrap(), AlbumDate::default());
assert_eq!(AlbumDate::from_mb_date("1984").unwrap(), 1984.into());
assert_eq!(
AlbumDate::from_mb_date("1984-05").unwrap(),
(1984, 5).into()
);
assert_eq!(
AlbumDate::from_mb_date("1984-05-18").unwrap(),
(1984, 5, 18).into()
);
assert!(AlbumDate::from_mb_date("1984-get-rekt").is_err());
}
#[test]
fn serde() {
let primary_type = "\"EP\"";
let primary_type: SerdeAlbumPrimaryType = serde_json::from_str(primary_type).unwrap();
let primary_type: AlbumPrimaryType = primary_type.into();
assert_eq!(primary_type, AlbumPrimaryType::Ep);
let secondary_type = "\"Field recording\"";
let secondary_type: SerdeAlbumSecondaryType = serde_json::from_str(secondary_type).unwrap();
let secondary_type: AlbumSecondaryType = secondary_type.into();
assert_eq!(secondary_type, AlbumSecondaryType::FieldRecording);
}
}

2
src/external/musicbrainz/mod.rs vendored Normal file
View File

@ -0,0 +1,2 @@
#[cfg(feature = "musicbrainz-api")]
pub mod api;

View File

@ -1,8 +1,7 @@
//! MusicHoard - a music collection manager. //! MusicHoard - a music collection manager.
mod core; mod core;
pub mod database; pub mod external;
pub mod library;
pub use core::collection; pub use core::collection;
pub use core::interface; pub use core::interface;
@ -14,4 +13,4 @@ pub use core::musichoard::{
#[cfg(test)] #[cfg(test)]
#[macro_use] #[macro_use]
mod tests; mod testmod;

View File

@ -10,15 +10,17 @@ use ratatui::{backend::CrosstermBackend, Terminal};
use structopt::StructOpt; use structopt::StructOpt;
use musichoard::{ use musichoard::{
database::json::{backend::JsonDatabaseFileBackend, JsonDatabase}, external::{
database::json::{backend::JsonDatabaseFileBackend, JsonDatabase},
library::beets::{
executor::{ssh::BeetsLibrarySshExecutor, BeetsLibraryProcessExecutor},
BeetsLibrary,
},
},
interface::{ interface::{
database::{IDatabase, NullDatabase}, database::{IDatabase, NullDatabase},
library::{ILibrary, NullLibrary}, library::{ILibrary, NullLibrary},
}, },
library::beets::{
executor::{ssh::BeetsLibrarySshExecutor, BeetsLibraryProcessExecutor},
BeetsLibrary,
},
MusicHoardBuilder, NoDatabase, NoLibrary, MusicHoardBuilder, NoDatabase, NoLibrary,
}; };
@ -135,4 +137,4 @@ fn main() {
#[cfg(test)] #[cfg(test)]
#[macro_use] #[macro_use]
mod tests; mod testmod;

473
src/testmod/full.rs Normal file
View File

@ -0,0 +1,473 @@
macro_rules! full_collection {
() => {
vec![
Artist {
id: ArtistId {
name: "Album_Artist A".to_string(),
},
sort: None,
musicbrainz: Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000"
).unwrap()),
properties: HashMap::from([
(String::from("MusicButler"), vec![
String::from("https://www.musicbutler.io/artist-page/000000000"),
]),
(String::from("Qobuz"), vec![
String::from(
"https://www.qobuz.com/nl-nl/interpreter/artist-a/download-streaming-albums",
)
]),
]),
albums: vec![
Album {
id: AlbumId {
title: "album_title a.a".to_string(),
},
date: 1998.into(),
seq: AlbumSeq(1),
musicbrainz: Some(MbAlbumRef::from_url_str(
"https://musicbrainz.org/release-group/00000000-0000-0000-0000-000000000000"
).unwrap()),
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![
Track {
id: TrackId {
title: "track a.a.1".to_string(),
},
number: TrackNum(1),
artist: vec!["artist a.a.1".to_string()],
quality: TrackQuality {
format: TrackFormat::Flac,
bitrate: 992,
},
},
Track {
id: TrackId {
title: "track a.a.2".to_string(),
},
number: TrackNum(2),
artist: vec![
"artist a.a.2.1".to_string(),
"artist a.a.2.2".to_string(),
],
quality: TrackQuality {
format: TrackFormat::Mp3,
bitrate: 320,
},
},
Track {
id: TrackId {
title: "track a.a.3".to_string(),
},
number: TrackNum(3),
artist: vec!["artist a.a.3".to_string()],
quality: TrackQuality {
format: TrackFormat::Flac,
bitrate: 1061,
},
},
Track {
id: TrackId {
title: "track a.a.4".to_string(),
},
number: TrackNum(4),
artist: vec!["artist a.a.4".to_string()],
quality: TrackQuality {
format: TrackFormat::Flac,
bitrate: 1042,
},
},
],
},
Album {
id: AlbumId {
title: "album_title a.b".to_string(),
},
date: (2015, 4).into(),
seq: AlbumSeq(1),
musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![
Track {
id: TrackId {
title: "track a.b.1".to_string(),
},
number: TrackNum(1),
artist: vec!["artist a.b.1".to_string()],
quality: TrackQuality {
format: TrackFormat::Flac,
bitrate: 1004,
},
},
Track {
id: TrackId {
title: "track a.b.2".to_string(),
},
number: TrackNum(2),
artist: vec!["artist a.b.2".to_string()],
quality: TrackQuality {
format: TrackFormat::Flac,
bitrate: 1077,
},
},
],
},
],
},
Artist {
id: ArtistId {
name: "Album_Artist B".to_string(),
},
sort: None,
musicbrainz: Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111"
).unwrap()),
properties: HashMap::from([
(String::from("MusicButler"), vec![
String::from("https://www.musicbutler.io/artist-page/111111111"),
String::from("https://www.musicbutler.io/artist-page/111111112"),
]),
(String::from("Bandcamp"), vec![
String::from("https://artist-b.bandcamp.com/")
]),
(String::from("Qobuz"), vec![
String::from(
"https://www.qobuz.com/nl-nl/interpreter/artist-b/download-streaming-albums",
)
]),
]),
albums: vec![
Album {
id: AlbumId {
title: "album_title b.a".to_string(),
},
date: (2003, 6, 6).into(),
seq: AlbumSeq(1),
musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![
Track {
id: TrackId {
title: "track b.a.1".to_string(),
},
number: TrackNum(1),
artist: vec!["artist b.a.1".to_string()],
quality: TrackQuality {
format: TrackFormat::Mp3,
bitrate: 190,
},
},
Track {
id: TrackId {
title: "track b.a.2".to_string(),
},
number: TrackNum(2),
artist: vec![
"artist b.a.2.1".to_string(),
"artist b.a.2.2".to_string(),
],
quality: TrackQuality {
format: TrackFormat::Mp3,
bitrate: 120,
},
},
],
},
Album {
id: AlbumId {
title: "album_title b.b".to_string(),
},
date: 2008.into(),
seq: AlbumSeq(3),
musicbrainz: Some(MbAlbumRef::from_url_str(
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111111"
).unwrap()),
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![
Track {
id: TrackId {
title: "track b.b.1".to_string(),
},
number: TrackNum(1),
artist: vec!["artist b.b.1".to_string()],
quality: TrackQuality {
format: TrackFormat::Flac,
bitrate: 1077,
},
},
Track {
id: TrackId {
title: "track b.b.2".to_string(),
},
number: TrackNum(2),
artist: vec![
"artist b.b.2.1".to_string(),
"artist b.b.2.2".to_string(),
],
quality: TrackQuality {
format: TrackFormat::Mp3,
bitrate: 320,
},
},
],
},
Album {
id: AlbumId {
title: "album_title b.c".to_string(),
},
date: 2009.into(),
seq: AlbumSeq(2),
musicbrainz: Some(MbAlbumRef::from_url_str(
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111112"
).unwrap()),
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![
Track {
id: TrackId {
title: "track b.c.1".to_string(),
},
number: TrackNum(1),
artist: vec!["artist b.c.1".to_string()],
quality: TrackQuality {
format: TrackFormat::Mp3,
bitrate: 190,
},
},
Track {
id: TrackId {
title: "track b.c.2".to_string(),
},
number: TrackNum(2),
artist: vec![
"artist b.c.2.1".to_string(),
"artist b.c.2.2".to_string(),
],
quality: TrackQuality {
format: TrackFormat::Mp3,
bitrate: 120,
},
},
],
},
Album {
id: AlbumId {
title: "album_title b.d".to_string(),
},
date: 2015.into(),
seq: AlbumSeq(4),
musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![
Track {
id: TrackId {
title: "track b.d.1".to_string(),
},
number: TrackNum(1),
artist: vec!["artist b.d.1".to_string()],
quality: TrackQuality {
format: TrackFormat::Mp3,
bitrate: 190,
},
},
Track {
id: TrackId {
title: "track b.d.2".to_string(),
},
number: TrackNum(2),
artist: vec![
"artist b.d.2.1".to_string(),
"artist b.d.2.2".to_string(),
],
quality: TrackQuality {
format: TrackFormat::Mp3,
bitrate: 120,
},
},
],
},
],
},
Artist {
id: ArtistId {
name: "The Album_Artist C".to_string(),
},
sort: Some(ArtistId {
name: "Album_Artist C, The".to_string(),
}),
musicbrainz: Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111"
).unwrap()),
properties: HashMap::new(),
albums: vec![
Album {
id: AlbumId {
title: "album_title c.a".to_string(),
},
date: 1985.into(),
seq: AlbumSeq(0),
musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![
Track {
id: TrackId {
title: "track c.a.1".to_string(),
},
number: TrackNum(1),
artist: vec!["artist c.a.1".to_string()],
quality: TrackQuality {
format: TrackFormat::Mp3,
bitrate: 320,
},
},
Track {
id: TrackId {
title: "track c.a.2".to_string(),
},
number: TrackNum(2),
artist: vec![
"artist c.a.2.1".to_string(),
"artist c.a.2.2".to_string(),
],
quality: TrackQuality {
format: TrackFormat::Mp3,
bitrate: 120,
},
},
],
},
Album {
id: AlbumId {
title: "album_title c.b".to_string(),
},
date: 2018.into(),
seq: AlbumSeq(0),
musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![
Track {
id: TrackId {
title: "track c.b.1".to_string(),
},
number: TrackNum(1),
artist: vec!["artist c.b.1".to_string()],
quality: TrackQuality {
format: TrackFormat::Flac,
bitrate: 1041,
},
},
Track {
id: TrackId {
title: "track c.b.2".to_string(),
},
number: TrackNum(2),
artist: vec![
"artist c.b.2.1".to_string(),
"artist c.b.2.2".to_string(),
],
quality: TrackQuality {
format: TrackFormat::Flac,
bitrate: 756,
},
},
],
},
],
},
Artist {
id: ArtistId {
name: "Album_Artist D".to_string(),
},
sort: None,
musicbrainz: None,
properties: HashMap::new(),
albums: vec![
Album {
id: AlbumId {
title: "album_title d.a".to_string(),
},
date: 1995.into(),
seq: AlbumSeq(0),
musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![
Track {
id: TrackId {
title: "track d.a.1".to_string(),
},
number: TrackNum(1),
artist: vec!["artist d.a.1".to_string()],
quality: TrackQuality {
format: TrackFormat::Mp3,
bitrate: 120,
},
},
Track {
id: TrackId {
title: "track d.a.2".to_string(),
},
number: TrackNum(2),
artist: vec![
"artist d.a.2.1".to_string(),
"artist d.a.2.2".to_string(),
],
quality: TrackQuality {
format: TrackFormat::Mp3,
bitrate: 120,
},
},
],
},
Album {
id: AlbumId {
title: "album_title d.b".to_string(),
},
date: 2028.into(),
seq: AlbumSeq(0),
musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![
Track {
id: TrackId {
title: "track d.b.1".to_string(),
},
number: TrackNum(1),
artist: vec!["artist d.b.1".to_string()],
quality: TrackQuality {
format: TrackFormat::Flac,
bitrate: 841,
},
},
Track {
id: TrackId {
title: "track d.b.2".to_string(),
},
number: TrackNum(2),
artist: vec![
"artist d.b.2.1".to_string(),
"artist d.b.2.2".to_string(),
],
quality: TrackQuality {
format: TrackFormat::Flac,
bitrate: 756,
},
},
],
},
],
},
]
};
}
pub(crate) use full_collection;

View File

@ -1,3 +1,4 @@
#[allow(unused_macros)]
macro_rules! library_collection { macro_rules! library_collection {
() => { () => {
vec![ vec![
@ -13,13 +14,11 @@ macro_rules! library_collection {
id: AlbumId { id: AlbumId {
title: "album_title a.a".to_string(), title: "album_title a.a".to_string(),
}, },
date: AlbumDate { date: 1998.into(),
year: 1998,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: None,
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -74,13 +73,11 @@ macro_rules! library_collection {
id: AlbumId { id: AlbumId {
title: "album_title a.b".to_string(), title: "album_title a.b".to_string(),
}, },
date: AlbumDate { date: (2015, 4).into(),
year: 2015,
month: AlbumMonth::April,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: None,
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -120,13 +117,11 @@ macro_rules! library_collection {
id: AlbumId { id: AlbumId {
title: "album_title b.a".to_string(), title: "album_title b.a".to_string(),
}, },
date: AlbumDate { date: (2003, 6, 6).into(),
year: 2003,
month: AlbumMonth::June,
day: 6,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: None,
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -159,13 +154,11 @@ macro_rules! library_collection {
id: AlbumId { id: AlbumId {
title: "album_title b.b".to_string(), title: "album_title b.b".to_string(),
}, },
date: AlbumDate { date: 2008.into(),
year: 2008,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: None,
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -198,13 +191,11 @@ macro_rules! library_collection {
id: AlbumId { id: AlbumId {
title: "album_title b.c".to_string(), title: "album_title b.c".to_string(),
}, },
date: AlbumDate { date: 2009.into(),
year: 2009,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: None,
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -237,13 +228,11 @@ macro_rules! library_collection {
id: AlbumId { id: AlbumId {
title: "album_title b.d".to_string(), title: "album_title b.d".to_string(),
}, },
date: AlbumDate { date: 2015.into(),
year: 2015,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: None,
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -288,13 +277,11 @@ macro_rules! library_collection {
id: AlbumId { id: AlbumId {
title: "album_title c.a".to_string(), title: "album_title c.a".to_string(),
}, },
date: AlbumDate { date: 1985.into(),
year: 1985,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: None,
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -327,13 +314,11 @@ macro_rules! library_collection {
id: AlbumId { id: AlbumId {
title: "album_title c.b".to_string(), title: "album_title c.b".to_string(),
}, },
date: AlbumDate { date: 2018.into(),
year: 2018,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: None,
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -376,13 +361,11 @@ macro_rules! library_collection {
id: AlbumId { id: AlbumId {
title: "album_title d.a".to_string(), title: "album_title d.a".to_string(),
}, },
date: AlbumDate { date: 1995.into(),
year: 1995,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: None,
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -415,13 +398,11 @@ macro_rules! library_collection {
id: AlbumId { id: AlbumId {
title: "album_title d.b".to_string(), title: "album_title d.b".to_string(),
}, },
date: AlbumDate { date: 2028.into(),
year: 2028,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: None,
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -456,81 +437,5 @@ macro_rules! library_collection {
}; };
} }
macro_rules! full_collection { #[allow(unused_imports)]
() => {{
let mut collection = library_collection!();
let mut iter = collection.iter_mut();
let artist_a = iter.next().unwrap();
assert_eq!(artist_a.id.name, "Album_Artist A");
artist_a.musicbrainz = Some(MusicBrainzUrl::artist_from_str(
"https://musicbrainz.org/artist/00000000-0000-0000-0000-000000000000",
).unwrap());
artist_a.properties = HashMap::from([
(String::from("MusicButler"), vec![
String::from("https://www.musicbutler.io/artist-page/000000000"),
]),
(String::from("Qobuz"), vec![
String::from(
"https://www.qobuz.com/nl-nl/interpreter/artist-a/download-streaming-albums",
)
]),
]);
artist_a.albums[0].seq = AlbumSeq(1);
artist_a.albums[1].seq = AlbumSeq(1);
artist_a.albums[0].musicbrainz = Some(MusicBrainzUrl::album_from_str(
"https://musicbrainz.org/release-group/00000000-0000-0000-0000-000000000000"
).unwrap());
let artist_b = iter.next().unwrap();
assert_eq!(artist_b.id.name, "Album_Artist B");
artist_b.musicbrainz = Some(MusicBrainzUrl::artist_from_str(
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111",
).unwrap());
artist_b.properties = HashMap::from([
(String::from("MusicButler"), vec![
String::from("https://www.musicbutler.io/artist-page/111111111"),
String::from("https://www.musicbutler.io/artist-page/111111112"),
]),
(String::from("Bandcamp"), vec![String::from("https://artist-b.bandcamp.com/")]),
(String::from("Qobuz"), vec![
String::from(
"https://www.qobuz.com/nl-nl/interpreter/artist-b/download-streaming-albums",
)
]),
]);
artist_b.albums[0].seq = AlbumSeq(1);
artist_b.albums[1].seq = AlbumSeq(3);
artist_b.albums[2].seq = AlbumSeq(2);
artist_b.albums[3].seq = AlbumSeq(4);
artist_b.albums[1].musicbrainz = Some(MusicBrainzUrl::album_from_str(
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111111"
).unwrap());
artist_b.albums[2].musicbrainz = Some(MusicBrainzUrl::album_from_str(
"https://musicbrainz.org/release-group/11111111-1111-1111-1111-111111111112"
).unwrap());
let artist_c = iter.next().unwrap();
assert_eq!(artist_c.id.name, "The Album_Artist C");
artist_c.musicbrainz = Some(MusicBrainzUrl::artist_from_str(
"https://musicbrainz.org/artist/11111111-1111-1111-1111-111111111111",
).unwrap());
// Nothing for artist_d
collection
}};
}
pub(crate) use full_collection;
pub(crate) use library_collection; pub(crate) use library_collection;

2
src/testmod/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod full;
pub mod library;

View File

@ -1,13 +1,13 @@
use std::collections::HashMap; use std::collections::HashMap;
use musichoard::collection::{ use musichoard::collection::{
album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq}, album::{Album, AlbumId, AlbumPrimaryType, AlbumSeq},
artist::{Artist, ArtistId}, artist::{Artist, ArtistId},
musicbrainz::MusicBrainzUrl, musicbrainz::{MbAlbumRef, MbArtistRef},
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality}, track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
}; };
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use crate::tests::*; use crate::testmod::*;
pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| full_collection!()); pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| full::full_collection!());

View File

@ -3,6 +3,7 @@ use std::collections::HashMap;
use musichoard::collection::{ use musichoard::collection::{
album::{Album, AlbumDate, AlbumSeq, AlbumStatus}, album::{Album, AlbumDate, AlbumSeq, AlbumStatus},
artist::Artist, artist::Artist,
musicbrainz::IMusicBrainzRef,
track::{Track, TrackFormat, TrackQuality}, track::{Track, TrackFormat, TrackQuality},
Collection, Collection,
}; };
@ -202,7 +203,7 @@ struct ArtistOverlay<'a> {
} }
impl<'a> ArtistOverlay<'a> { impl<'a> ArtistOverlay<'a> {
fn opt_opt_to_str<S: AsRef<str>>(opt: Option<Option<&S>>) -> &str { fn opt_opt_to_str<S: AsRef<str> + ?Sized>(opt: Option<Option<&S>>) -> &str {
opt.flatten().map(|item| item.as_ref()).unwrap_or("") opt.flatten().map(|item| item.as_ref()).unwrap_or("")
} }
@ -264,7 +265,7 @@ impl<'a> ArtistOverlay<'a> {
MusicBrainz: {}\n{item_indent}\ MusicBrainz: {}\n{item_indent}\
Properties: {}", Properties: {}",
artist.map(|a| a.id.name.as_str()).unwrap_or(""), artist.map(|a| a.id.name.as_str()).unwrap_or(""),
Self::opt_opt_to_str(artist.map(|a| a.musicbrainz.as_ref())), Self::opt_opt_to_str(artist.map(|a| a.musicbrainz.as_ref().map(|mb| mb.url()))),
Self::opt_hashmap_to_string( Self::opt_hashmap_to_string(
artist.map(|a| &a.properties), artist.map(|a| &a.properties),
&double_item_indent, &double_item_indent,
@ -329,12 +330,15 @@ impl<'a, 'b> AlbumState<'a, 'b> {
} }
fn display_album_date(date: &AlbumDate) -> String { fn display_album_date(date: &AlbumDate) -> String {
if date.month.is_none() { match date.year {
format!("{}", date.year) Some(year) => match date.month {
} else if date.day == 0 { Some(month) => match date.day {
format!("{}{:02}", date.year, date.month as u8) Some(day) => format!("{year}{month:02}{day:02}"),
} else { None => format!("{year}{month:02}"),
format!("{}{:02}{:02}", date.year, date.month as u8, date.day) },
None => format!("{year}"),
},
None => String::from(""),
} }
} }
@ -804,17 +808,11 @@ mod tests {
#[test] #[test]
fn display_album_date() { fn display_album_date() {
assert_eq!(AlbumState::display_album_date(&AlbumDate::default()), "0"); assert_eq!(AlbumState::display_album_date(&AlbumDate::default()), "");
assert_eq!(AlbumState::display_album_date(&1990.into()), "1990");
assert_eq!(AlbumState::display_album_date(&(1990, 5).into()), "199005");
assert_eq!( assert_eq!(
AlbumState::display_album_date(&AlbumDate::new(1990, 0, 0)), AlbumState::display_album_date(&(1990, 5, 6).into()),
"1990"
);
assert_eq!(
AlbumState::display_album_date(&AlbumDate::new(1990, 5, 0)),
"199005"
);
assert_eq!(
AlbumState::display_album_date(&AlbumDate::new(1990, 5, 6)),
"19900506" "19900506"
); );
} }
@ -870,7 +868,7 @@ mod tests {
let mut artists: Vec<Artist> = vec![Artist::new(ArtistId::new("An artist"))]; let mut artists: Vec<Artist> = vec![Artist::new(ArtistId::new("An artist"))];
artists[0] artists[0]
.albums .albums
.push(Album::new("An album", AlbumDate::default())); .push(Album::new("An album", AlbumDate::default(), None, vec![]));
let mut selection = Selection::new(&artists); let mut selection = Selection::new(&artists);
draw_test_suite(&artists, &mut selection); draw_test_suite(&artists, &mut selection);

View File

@ -5,7 +5,7 @@ use tempfile::NamedTempFile;
use musichoard::{ use musichoard::{
collection::{album::AlbumDate, artist::Artist, Collection}, collection::{album::AlbumDate, artist::Artist, Collection},
database::json::{backend::JsonDatabaseFileBackend, JsonDatabase}, external::database::json::{backend::JsonDatabaseFileBackend, JsonDatabase},
interface::database::IDatabase, interface::database::IDatabase,
}; };

View File

@ -1 +1 @@
{"V20240308":[{"name":"Аркона","sort":"Arkona","musicbrainz":"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212","properties":{"Bandcamp":["https://arkonamoscow.bandcamp.com/"],"MusicButler":["https://www.musicbutler.io/artist-page/283448581"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/arkona/download-streaming-albums"]},"albums":[{"title":"Slovo","seq":0,"musicbrainz":null}]},{"name":"Eluveitie","sort":null,"musicbrainz":"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/269358403"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/eluveitie/download-streaming-albums"]},"albums":[{"title":"Vên [rerecorded]","seq":0,"musicbrainz":null},{"title":"Slania","seq":0,"musicbrainz":null}]},{"name":"Frontside","sort":null,"musicbrainz":"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/826588800"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/frontside/download-streaming-albums"]},"albums":[{"title":"…nasze jest królestwo, potęga i chwała na wieki…","seq":0,"musicbrainz":null}]},{"name":"Heavens Basement","sort":"Heavens Basement","musicbrainz":"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/291158685"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/heaven-s-basement/download-streaming-albums"]},"albums":[{"title":"Paper Plague","seq":0,"musicbrainz":null},{"title":"Unbreakable","seq":0,"musicbrainz":null}]},{"name":"Metallica","sort":null,"musicbrainz":"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/3996865"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/metallica/download-streaming-albums"]},"albums":[{"title":"Ride the Lightning","seq":0,"musicbrainz":null},{"title":"S&M","seq":0,"musicbrainz":null}]}]} {"V20240313":[{"name":"Аркона","sort":"Arkona","musicbrainz":"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212","properties":{"Bandcamp":["https://arkonamoscow.bandcamp.com/"],"MusicButler":["https://www.musicbutler.io/artist-page/283448581"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/arkona/download-streaming-albums"]},"albums":[{"title":"Slovo","seq":0,"musicbrainz":null,"primary_type":"Album","secondary_types":[]}]},{"name":"Eluveitie","sort":null,"musicbrainz":"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/269358403"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/eluveitie/download-streaming-albums"]},"albums":[{"title":"Vên [rerecorded]","seq":0,"musicbrainz":null,"primary_type":"Ep","secondary_types":[]},{"title":"Slania","seq":0,"musicbrainz":null,"primary_type":"Album","secondary_types":[]}]},{"name":"Frontside","sort":null,"musicbrainz":"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/826588800"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/frontside/download-streaming-albums"]},"albums":[{"title":"…nasze jest królestwo, potęga i chwała na wieki…","seq":0,"musicbrainz":null,"primary_type":"Album","secondary_types":[]}]},{"name":"Heavens Basement","sort":"Heavens Basement","musicbrainz":"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/291158685"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/heaven-s-basement/download-streaming-albums"]},"albums":[{"title":"Paper Plague","seq":0,"musicbrainz":null,"primary_type":null,"secondary_types":[]},{"title":"Unbreakable","seq":0,"musicbrainz":null,"primary_type":"Album","secondary_types":[]}]},{"name":"Metallica","sort":null,"musicbrainz":"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab","properties":{"MusicButler":["https://www.musicbutler.io/artist-page/3996865"],"Qobuz":["https://www.qobuz.com/nl-nl/interpreter/metallica/download-streaming-albums"]},"albums":[{"title":"Ride the Lightning","seq":0,"musicbrainz":null,"primary_type":"Album","secondary_types":[]},{"title":"S&M","seq":0,"musicbrainz":null,"primary_type":"Album","secondary_types":["Live"]}]}]}

View File

@ -7,8 +7,10 @@ mod library;
mod testlib; mod testlib;
use musichoard::{ use musichoard::{
database::json::{backend::JsonDatabaseFileBackend, JsonDatabase}, external::{
library::beets::{executor::BeetsLibraryProcessExecutor, BeetsLibrary}, database::json::{backend::JsonDatabaseFileBackend, JsonDatabase},
library::beets::{executor::BeetsLibraryProcessExecutor, BeetsLibrary},
},
IMusicHoardBase, IMusicHoardDatabase, IMusicHoardLibrary, MusicHoard, IMusicHoardBase, IMusicHoardDatabase, IMusicHoardLibrary, MusicHoard,
}; };

View File

@ -8,8 +8,8 @@ use std::{
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use musichoard::{ use musichoard::{
external::library::beets::{executor::BeetsLibraryProcessExecutor, BeetsLibrary},
interface::library::{Field, ILibrary, Item, Query}, interface::library::{Field, ILibrary, Item, Query},
library::beets::{executor::BeetsLibraryProcessExecutor, BeetsLibrary},
}; };
use crate::library::testmod::LIBRARY_ITEMS; use crate::library::testmod::LIBRARY_ITEMS;

View File

@ -1,9 +1,6 @@
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use musichoard::{ use musichoard::{collection::track::TrackFormat, interface::library::Item};
collection::{album::AlbumMonth, track::TrackFormat},
interface::library::Item,
};
pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> { pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
vec![ vec![
@ -11,7 +8,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 1, track_number: 1,
@ -24,7 +21,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 2, track_number: 2,
@ -37,7 +34,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 3, track_number: 3,
@ -50,7 +47,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 4, track_number: 4,
@ -63,7 +60,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 5, track_number: 5,
@ -76,7 +73,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 6, track_number: 6,
@ -89,7 +86,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 7, track_number: 7,
@ -102,7 +99,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 8, track_number: 8,
@ -115,7 +112,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 9, track_number: 9,
@ -128,7 +125,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 10, track_number: 10,
@ -141,7 +138,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 11, track_number: 11,
@ -154,7 +151,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 12, track_number: 12,
@ -167,7 +164,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 13, track_number: 13,
@ -180,7 +177,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Аркона"), album_artist: String::from("Аркона"),
album_artist_sort: Some(String::from("Arkona")), album_artist_sort: Some(String::from("Arkona")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slovo"), album_title: String::from("Slovo"),
track_number: 14, track_number: 14,
@ -193,7 +190,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2004, album_year: 2004,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Vên [rerecorded]"), album_title: String::from("Vên [rerecorded]"),
track_number: 1, track_number: 1,
@ -206,7 +203,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2004, album_year: 2004,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Vên [rerecorded]"), album_title: String::from("Vên [rerecorded]"),
track_number: 2, track_number: 2,
@ -219,7 +216,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2004, album_year: 2004,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Vên [rerecorded]"), album_title: String::from("Vên [rerecorded]"),
track_number: 3, track_number: 3,
@ -232,7 +229,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2004, album_year: 2004,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Vên [rerecorded]"), album_title: String::from("Vên [rerecorded]"),
track_number: 4, track_number: 4,
@ -245,7 +242,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2004, album_year: 2004,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Vên [rerecorded]"), album_title: String::from("Vên [rerecorded]"),
track_number: 5, track_number: 5,
@ -258,7 +255,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2004, album_year: 2004,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Vên [rerecorded]"), album_title: String::from("Vên [rerecorded]"),
track_number: 6, track_number: 6,
@ -271,7 +268,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slania"), album_title: String::from("Slania"),
track_number: 1, track_number: 1,
@ -284,7 +281,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slania"), album_title: String::from("Slania"),
track_number: 2, track_number: 2,
@ -297,7 +294,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slania"), album_title: String::from("Slania"),
track_number: 3, track_number: 3,
@ -310,7 +307,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slania"), album_title: String::from("Slania"),
track_number: 4, track_number: 4,
@ -323,7 +320,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slania"), album_title: String::from("Slania"),
track_number: 5, track_number: 5,
@ -336,7 +333,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slania"), album_title: String::from("Slania"),
track_number: 6, track_number: 6,
@ -349,7 +346,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slania"), album_title: String::from("Slania"),
track_number: 7, track_number: 7,
@ -362,7 +359,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slania"), album_title: String::from("Slania"),
track_number: 8, track_number: 8,
@ -375,7 +372,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slania"), album_title: String::from("Slania"),
track_number: 9, track_number: 9,
@ -388,7 +385,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slania"), album_title: String::from("Slania"),
track_number: 10, track_number: 10,
@ -401,7 +398,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slania"), album_title: String::from("Slania"),
track_number: 11, track_number: 11,
@ -414,7 +411,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Eluveitie"), album_artist: String::from("Eluveitie"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2008, album_year: 2008,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Slania"), album_title: String::from("Slania"),
track_number: 12, track_number: 12,
@ -427,7 +424,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Frontside"), album_artist: String::from("Frontside"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2001, album_year: 2001,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"),
track_number: 1, track_number: 1,
@ -440,7 +437,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Frontside"), album_artist: String::from("Frontside"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2001, album_year: 2001,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"),
track_number: 2, track_number: 2,
@ -453,7 +450,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Frontside"), album_artist: String::from("Frontside"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2001, album_year: 2001,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"),
track_number: 3, track_number: 3,
@ -466,7 +463,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Frontside"), album_artist: String::from("Frontside"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2001, album_year: 2001,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"),
track_number: 4, track_number: 4,
@ -479,7 +476,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Frontside"), album_artist: String::from("Frontside"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2001, album_year: 2001,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"),
track_number: 5, track_number: 5,
@ -492,7 +489,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Frontside"), album_artist: String::from("Frontside"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2001, album_year: 2001,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"),
track_number: 6, track_number: 6,
@ -505,7 +502,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Frontside"), album_artist: String::from("Frontside"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2001, album_year: 2001,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"),
track_number: 7, track_number: 7,
@ -518,7 +515,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Frontside"), album_artist: String::from("Frontside"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2001, album_year: 2001,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"),
track_number: 8, track_number: 8,
@ -531,7 +528,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Frontside"), album_artist: String::from("Frontside"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2001, album_year: 2001,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"),
track_number: 9, track_number: 9,
@ -544,7 +541,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Frontside"), album_artist: String::from("Frontside"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2001, album_year: 2001,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"),
track_number: 10, track_number: 10,
@ -557,7 +554,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Frontside"), album_artist: String::from("Frontside"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2001, album_year: 2001,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), album_title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"),
track_number: 11, track_number: 11,
@ -570,7 +567,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Heavens Basement"), album_artist: String::from("Heavens Basement"),
album_artist_sort: None, album_artist_sort: None,
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Paper Plague"), album_title: String::from("Paper Plague"),
track_number: 0, track_number: 0,
@ -583,7 +580,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Heavens Basement"), album_artist: String::from("Heavens Basement"),
album_artist_sort: Some(String::from("Heavens Basement")), album_artist_sort: Some(String::from("Heavens Basement")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Unbreakable"), album_title: String::from("Unbreakable"),
track_number: 1, track_number: 1,
@ -596,7 +593,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Heavens Basement"), album_artist: String::from("Heavens Basement"),
album_artist_sort: Some(String::from("Heavens Basement")), album_artist_sort: Some(String::from("Heavens Basement")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Unbreakable"), album_title: String::from("Unbreakable"),
track_number: 2, track_number: 2,
@ -609,7 +606,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Heavens Basement"), album_artist: String::from("Heavens Basement"),
album_artist_sort: Some(String::from("Heavens Basement")), album_artist_sort: Some(String::from("Heavens Basement")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Unbreakable"), album_title: String::from("Unbreakable"),
track_number: 3, track_number: 3,
@ -622,7 +619,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Heavens Basement"), album_artist: String::from("Heavens Basement"),
album_artist_sort: Some(String::from("Heavens Basement")), album_artist_sort: Some(String::from("Heavens Basement")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Unbreakable"), album_title: String::from("Unbreakable"),
track_number: 4, track_number: 4,
@ -635,7 +632,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Heavens Basement"), album_artist: String::from("Heavens Basement"),
album_artist_sort: Some(String::from("Heavens Basement")), album_artist_sort: Some(String::from("Heavens Basement")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Unbreakable"), album_title: String::from("Unbreakable"),
track_number: 5, track_number: 5,
@ -648,7 +645,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Heavens Basement"), album_artist: String::from("Heavens Basement"),
album_artist_sort: Some(String::from("Heavens Basement")), album_artist_sort: Some(String::from("Heavens Basement")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Unbreakable"), album_title: String::from("Unbreakable"),
track_number: 6, track_number: 6,
@ -661,7 +658,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Heavens Basement"), album_artist: String::from("Heavens Basement"),
album_artist_sort: Some(String::from("Heavens Basement")), album_artist_sort: Some(String::from("Heavens Basement")),
album_year: 2011, album_year: 2011,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Unbreakable"), album_title: String::from("Unbreakable"),
track_number: 7, track_number: 7,
@ -674,7 +671,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1984, album_year: 1984,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Ride the Lightning"), album_title: String::from("Ride the Lightning"),
track_number: 1, track_number: 1,
@ -687,7 +684,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1984, album_year: 1984,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Ride the Lightning"), album_title: String::from("Ride the Lightning"),
track_number: 2, track_number: 2,
@ -700,7 +697,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1984, album_year: 1984,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Ride the Lightning"), album_title: String::from("Ride the Lightning"),
track_number: 3, track_number: 3,
@ -713,7 +710,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1984, album_year: 1984,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Ride the Lightning"), album_title: String::from("Ride the Lightning"),
track_number: 4, track_number: 4,
@ -726,7 +723,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1984, album_year: 1984,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Ride the Lightning"), album_title: String::from("Ride the Lightning"),
track_number: 5, track_number: 5,
@ -739,7 +736,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1984, album_year: 1984,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Ride the Lightning"), album_title: String::from("Ride the Lightning"),
track_number: 6, track_number: 6,
@ -752,7 +749,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1984, album_year: 1984,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Ride the Lightning"), album_title: String::from("Ride the Lightning"),
track_number: 7, track_number: 7,
@ -765,7 +762,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1984, album_year: 1984,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("Ride the Lightning"), album_title: String::from("Ride the Lightning"),
track_number: 8, track_number: 8,
@ -778,7 +775,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 1, track_number: 1,
@ -791,7 +788,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 2, track_number: 2,
@ -804,7 +801,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 3, track_number: 3,
@ -817,7 +814,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 4, track_number: 4,
@ -830,7 +827,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 5, track_number: 5,
@ -843,7 +840,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 6, track_number: 6,
@ -856,7 +853,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 7, track_number: 7,
@ -869,7 +866,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 8, track_number: 8,
@ -882,7 +879,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 9, track_number: 9,
@ -895,7 +892,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 10, track_number: 10,
@ -908,7 +905,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 11, track_number: 11,
@ -921,7 +918,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 12, track_number: 12,
@ -934,7 +931,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 13, track_number: 13,
@ -947,7 +944,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 14, track_number: 14,
@ -960,7 +957,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 15, track_number: 15,
@ -973,7 +970,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 16, track_number: 16,
@ -986,7 +983,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 17, track_number: 17,
@ -999,7 +996,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 18, track_number: 18,
@ -1012,7 +1009,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 19, track_number: 19,
@ -1025,7 +1022,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 20, track_number: 20,
@ -1038,7 +1035,7 @@ pub static LIBRARY_ITEMS: Lazy<Vec<Item>> = Lazy::new(|| -> Vec<Item> {
album_artist: String::from("Metallica"), album_artist: String::from("Metallica"),
album_artist_sort: None, album_artist_sort: None,
album_year: 1999, album_year: 1999,
album_month: AlbumMonth::None, album_month: 0,
album_day: 0, album_day: 0,
album_title: String::from("S&M"), album_title: String::from("S&M"),
track_number: 21, track_number: 21,

View File

@ -2,9 +2,9 @@ use once_cell::sync::Lazy;
use std::collections::HashMap; use std::collections::HashMap;
use musichoard::collection::{ use musichoard::collection::{
album::{Album, AlbumDate, AlbumId, AlbumMonth, AlbumSeq}, album::{Album, AlbumId, AlbumPrimaryType, AlbumSecondaryType, AlbumSeq},
artist::{Artist, ArtistId}, artist::{Artist, ArtistId},
musicbrainz::MusicBrainzUrl, musicbrainz::MbArtistRef,
track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality}, track::{Track, TrackFormat, TrackId, TrackNum, TrackQuality},
Collection, Collection,
}; };
@ -18,7 +18,7 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
sort: Some(ArtistId{ sort: Some(ArtistId{
name: String::from("Arkona") name: String::from("Arkona")
}), }),
musicbrainz: Some(MusicBrainzUrl::artist_from_str( musicbrainz: Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212" "https://musicbrainz.org/artist/baad262d-55ef-427a-83c7-f7530964f212"
).unwrap()), ).unwrap()),
properties: HashMap::from([ properties: HashMap::from([
@ -36,13 +36,11 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
id: AlbumId { id: AlbumId {
title: String::from("Slovo"), title: String::from("Slovo"),
}, },
date: AlbumDate { date: 2011.into(),
year: 2011,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -206,8 +204,8 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
name: String::from("Eluveitie"), name: String::from("Eluveitie"),
}, },
sort: None, sort: None,
musicbrainz: Some(MusicBrainzUrl::artist_from_str( musicbrainz: Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38", "https://musicbrainz.org/artist/8000598a-5edb-401c-8e6d-36b167feaf38"
).unwrap()), ).unwrap()),
properties: HashMap::from([ properties: HashMap::from([
(String::from("MusicButler"), vec![ (String::from("MusicButler"), vec![
@ -222,13 +220,11 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
id: AlbumId { id: AlbumId {
title: String::from("Vên [rerecorded]"), title: String::from("Vên [rerecorded]"),
}, },
date: AlbumDate { date: 2004.into(),
year: 2004,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Ep),
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -302,13 +298,11 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
id: AlbumId { id: AlbumId {
title: String::from("Slania"), title: String::from("Slania"),
}, },
date: AlbumDate { date: 2008.into(),
year: 2008,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -451,8 +445,8 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
name: String::from("Frontside"), name: String::from("Frontside"),
}, },
sort: None, sort: None,
musicbrainz: Some(MusicBrainzUrl::artist_from_str( musicbrainz: Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490", "https://musicbrainz.org/artist/3a901353-fccd-4afd-ad01-9c03f451b490"
).unwrap()), ).unwrap()),
properties: HashMap::from([ properties: HashMap::from([
(String::from("MusicButler"), vec![ (String::from("MusicButler"), vec![
@ -466,13 +460,11 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
id: AlbumId { id: AlbumId {
title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"), title: String::from("…nasze jest królestwo, potęga i chwała na wieki…"),
}, },
date: AlbumDate { date: 2001.into(),
year: 2001,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -605,8 +597,8 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
sort: Some(ArtistId { sort: Some(ArtistId {
name: String::from("Heavens Basement"), name: String::from("Heavens Basement"),
}), }),
musicbrainz: Some(MusicBrainzUrl::artist_from_str( musicbrainz: Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc", "https://musicbrainz.org/artist/c2c4d56a-d599-4a18-bd2f-ae644e2198cc"
).unwrap()), ).unwrap()),
properties: HashMap::from([ properties: HashMap::from([
(String::from("MusicButler"), vec![ (String::from("MusicButler"), vec![
@ -620,13 +612,11 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
id: AlbumId { id: AlbumId {
title: String::from("Paper Plague"), title: String::from("Paper Plague"),
}, },
date: AlbumDate { date: 2011.into(),
year: 2011,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: None,
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -644,13 +634,11 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
id: AlbumId { id: AlbumId {
title: String::from("Unbreakable"), title: String::from("Unbreakable"),
}, },
date: AlbumDate { date: 2011.into(),
year: 2011,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -737,8 +725,8 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
name: String::from("Metallica"), name: String::from("Metallica"),
}, },
sort: None, sort: None,
musicbrainz: Some(MusicBrainzUrl::artist_from_str( musicbrainz: Some(MbArtistRef::from_url_str(
"https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab", "https://musicbrainz.org/artist/65f4f0c5-ef9e-490c-aee3-909e7ae6b2ab"
).unwrap()), ).unwrap()),
properties: HashMap::from([ properties: HashMap::from([
(String::from("MusicButler"), vec![ (String::from("MusicButler"), vec![
@ -753,13 +741,11 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
id: AlbumId { id: AlbumId {
title: String::from("Ride the Lightning"), title: String::from("Ride the Lightning"),
}, },
date: AlbumDate { date: 1984.into(),
year: 1984,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {
@ -855,13 +841,11 @@ pub static COLLECTION: Lazy<Vec<Artist>> = Lazy::new(|| -> Collection {
id: AlbumId { id: AlbumId {
title: String::from("S&M"), title: String::from("S&M"),
}, },
date: AlbumDate { date: 1999.into(),
year: 1999,
month: AlbumMonth::None,
day: 0,
},
seq: AlbumSeq(0), seq: AlbumSeq(0),
musicbrainz: None, musicbrainz: None,
primary_type: Some(AlbumPrimaryType::Album),
secondary_types: vec![AlbumSecondaryType::Live],
tracks: vec![ tracks: vec![
Track { Track {
id: TrackId { id: TrackId {