Database trait and JSON implementation (#6)
Closes #3 Reviewed-on: https://git.wojciechkozlowski.eu/wojtek/musichoard/pulls/6
This commit is contained in:
parent
4de22e5584
commit
c40b29cd27
213
Cargo.lock
generated
213
Cargo.lock
generated
@ -2,11 +2,105 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
|
||||
dependencies = [
|
||||
"errno-dragonfly",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "errno-dragonfly"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286"
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "io-lifetimes"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.140"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd550e73688e6d578f0ac2119e32b797a327631a42f9433e59d02e139c8df60d"
|
||||
|
||||
[[package]]
|
||||
name = "musichoard"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
@ -28,6 +122,35 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "0.37.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c348b5dc624ecee40108aa2922fed8bad89d7fcc2b9f8cb18f632898ac4a37f9"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"io-lifetimes",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.159"
|
||||
@ -48,6 +171,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.11"
|
||||
@ -59,6 +193,19 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"fastrand",
|
||||
"redox_syscall",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.8"
|
||||
@ -73,3 +220,69 @@ checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.45.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.42.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
|
||||
|
@ -7,4 +7,8 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
uuid = { version = "1.3", features = ["serde"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.5"
|
||||
|
197
src/database/json.rs
Normal file
197
src/database/json.rs
Normal file
@ -0,0 +1,197 @@
|
||||
use std::fs::File;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::database::{DatabaseRead, DatabaseWrite};
|
||||
|
||||
/// A JSON file database.
|
||||
pub struct DatabaseJson {
|
||||
database_file: File,
|
||||
}
|
||||
|
||||
impl DatabaseJson {
|
||||
/// Create a read-only database instance. If the JSON file does not exist, an error is returned.
|
||||
pub fn reader(path: &Path) -> Result<Self, std::io::Error> {
|
||||
Ok(Self {
|
||||
database_file: File::open(path)?,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a write-only database instance. If the file does not exist, it is created, if it does
|
||||
/// exist, it is truncated.
|
||||
pub fn writer(path: &Path) -> Result<Self, std::io::Error> {
|
||||
Ok(Self {
|
||||
database_file: File::create(path)?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseRead for DatabaseJson {
|
||||
fn read<D>(&mut self, collection: &mut D) -> Result<(), std::io::Error>
|
||||
where
|
||||
D: DeserializeOwned,
|
||||
{
|
||||
// Read entire file to memory as for now this is faster than a buffered read from disk:
|
||||
// https://github.com/serde-rs/json/issues/160
|
||||
let mut serialized = String::new();
|
||||
self.database_file.read_to_string(&mut serialized)?;
|
||||
|
||||
*collection = serde_json::from_str(&serialized)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseWrite for DatabaseJson {
|
||||
fn write<S>(&mut self, collection: &S) -> Result<(), std::io::Error>
|
||||
where
|
||||
S: Serialize,
|
||||
{
|
||||
let serialized = serde_json::to_string(&collection)?;
|
||||
self.database_file.write_all(serialized.as_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::path::Path;
|
||||
use tempfile::NamedTempFile;
|
||||
use uuid::uuid;
|
||||
|
||||
use super::*;
|
||||
|
||||
use crate::{Artist, Release, ReleaseGroup, ReleaseGroupType, Track};
|
||||
|
||||
const TEST_FILENAME: &str = "tests/files/database_json_test.json";
|
||||
|
||||
fn test_data() -> Vec<ReleaseGroup> {
|
||||
vec![
|
||||
ReleaseGroup {
|
||||
r#type: ReleaseGroupType::Album,
|
||||
title: String::from("Release group A"),
|
||||
artist: vec![Artist {
|
||||
name: String::from("Artist A"),
|
||||
mbid: Some(uuid!("f7769831-746b-4a12-8124-0123d7fe17c9")),
|
||||
}],
|
||||
year: 1998,
|
||||
mbid: Some(uuid!("89efbf43-3395-4f6e-ac11-32c1ce514bb0")),
|
||||
releases: vec![Release {
|
||||
tracks: vec![
|
||||
Track {
|
||||
number: 1,
|
||||
title: String::from("Track A.1"),
|
||||
artist: vec![Artist {
|
||||
name: String::from("Artist A.A"),
|
||||
mbid: Some(uuid!("b7f7163d-61d5-4c96-b305-005df54fb999")),
|
||||
}],
|
||||
mbid: None,
|
||||
},
|
||||
Track {
|
||||
number: 2,
|
||||
title: String::from("Track A.2"),
|
||||
artist: vec![Artist {
|
||||
name: String::from("Artist A.A"),
|
||||
mbid: Some(uuid!("b7f7163d-61d5-4c96-b305-005df54fb999")),
|
||||
}],
|
||||
mbid: None,
|
||||
},
|
||||
Track {
|
||||
number: 3,
|
||||
title: String::from("Track A.3"),
|
||||
artist: vec![
|
||||
Artist {
|
||||
name: String::from("Artist A.A"),
|
||||
mbid: Some(uuid!("b7f7163d-61d5-4c96-b305-005df54fb999")),
|
||||
},
|
||||
Artist {
|
||||
name: String::from("Artist A.B"),
|
||||
mbid: Some(uuid!("6f6b46f2-4bb5-47e7-a8c8-03ebde30164f")),
|
||||
},
|
||||
],
|
||||
mbid: None,
|
||||
},
|
||||
],
|
||||
mbid: None,
|
||||
}],
|
||||
},
|
||||
ReleaseGroup {
|
||||
r#type: ReleaseGroupType::Single,
|
||||
title: String::from("Release group B"),
|
||||
artist: vec![Artist {
|
||||
name: String::from("Artist B"),
|
||||
mbid: None,
|
||||
}],
|
||||
year: 2008,
|
||||
mbid: None,
|
||||
releases: vec![Release {
|
||||
tracks: vec![Track {
|
||||
number: 1,
|
||||
title: String::from("Track B.1"),
|
||||
artist: vec![Artist {
|
||||
name: String::from("Artist B.A"),
|
||||
mbid: Some(uuid!("d927e216-2e63-415c-acec-bf9f1abd3e3c")),
|
||||
}],
|
||||
mbid: Some(uuid!("dacc9ce4-118c-4c92-aed7-1ebe4c7543b5")),
|
||||
}],
|
||||
mbid: Some(uuid!("ac7b642d-8b71-4588-a694-e5ae43fac873")),
|
||||
}],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write() {
|
||||
let write_data = test_data();
|
||||
let temp_file = NamedTempFile::new().unwrap();
|
||||
DatabaseJson::writer(temp_file.path())
|
||||
.unwrap()
|
||||
.write(&write_data)
|
||||
.unwrap();
|
||||
|
||||
let mut write_data_str = String::new();
|
||||
File::open(temp_file.path())
|
||||
.unwrap()
|
||||
.read_to_string(&mut write_data_str)
|
||||
.unwrap();
|
||||
|
||||
let mut test_data_str = String::new();
|
||||
File::open(TEST_FILENAME)
|
||||
.unwrap()
|
||||
.read_to_string(&mut test_data_str)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(write_data_str, test_data_str);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read() {
|
||||
let mut read_data: Vec<ReleaseGroup> = vec![];
|
||||
DatabaseJson::reader(Path::new(TEST_FILENAME))
|
||||
.unwrap()
|
||||
.read(&mut read_data)
|
||||
.unwrap();
|
||||
assert_eq!(read_data, test_data());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reverse() {
|
||||
let write_data = test_data();
|
||||
let mut read_data: Vec<ReleaseGroup> = vec![];
|
||||
|
||||
let temp_file = NamedTempFile::new().unwrap();
|
||||
|
||||
DatabaseJson::writer(temp_file.path())
|
||||
.unwrap()
|
||||
.write(&write_data)
|
||||
.unwrap();
|
||||
DatabaseJson::reader(temp_file.path())
|
||||
.unwrap()
|
||||
.read(&mut read_data)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(write_data, read_data);
|
||||
}
|
||||
}
|
18
src/database/mod.rs
Normal file
18
src/database/mod.rs
Normal file
@ -0,0 +1,18 @@
|
||||
use serde::de::DeserializeOwned;
|
||||
use serde::Serialize;
|
||||
|
||||
pub mod json;
|
||||
|
||||
/// Trait for database reads.
|
||||
pub trait DatabaseRead {
|
||||
fn read<D>(&mut self, collection: &mut D) -> Result<(), std::io::Error>
|
||||
where
|
||||
D: DeserializeOwned;
|
||||
}
|
||||
|
||||
/// Trait for database writes.
|
||||
pub trait DatabaseWrite {
|
||||
fn write<S>(&mut self, collection: &S) -> Result<(), std::io::Error>
|
||||
where
|
||||
S: Serialize;
|
||||
}
|
22
src/lib.rs
22
src/lib.rs
@ -1,38 +1,38 @@
|
||||
//! MusicHoard - a music collection manager.
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid;
|
||||
use uuid::Uuid;
|
||||
|
||||
pub mod database;
|
||||
|
||||
/// [MusicBrainz Identifier](https://musicbrainz.org/doc/MusicBrainz_Identifier) (MBID).
|
||||
pub type Mbid = uuid::Uuid;
|
||||
pub type Mbid = Uuid;
|
||||
|
||||
/// [Artist](https://musicbrainz.org/doc/Artist).
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Artist {
|
||||
pub name: String,
|
||||
pub mbid: Option<Mbid>,
|
||||
}
|
||||
|
||||
/// [Track](https://musicbrainz.org/doc/Track).
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Track {
|
||||
pub number: u32,
|
||||
pub title: String,
|
||||
pub artist: Artist,
|
||||
pub artist: Vec<Artist>,
|
||||
pub mbid: Option<Mbid>,
|
||||
}
|
||||
|
||||
/// [Release](https://musicbrainz.org/doc/Release).
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct Release {
|
||||
pub title: String,
|
||||
pub artist: String,
|
||||
pub tracks: Vec<Track>,
|
||||
pub mbid: Option<Mbid>,
|
||||
}
|
||||
|
||||
/// [Release group primary type](https://musicbrainz.org/doc/Release_Group/Type).
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub enum ReleaseGroupType {
|
||||
Album,
|
||||
Ep,
|
||||
@ -41,11 +41,11 @@ pub enum ReleaseGroupType {
|
||||
}
|
||||
|
||||
/// [Release group](https://musicbrainz.org/doc/Release_Group).
|
||||
#[derive(Deserialize, Serialize)]
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
pub struct ReleaseGroup {
|
||||
pub r#type: ReleaseGroupType,
|
||||
pub title: String,
|
||||
pub artist: Artist,
|
||||
pub artist: Vec<Artist>,
|
||||
pub year: u32,
|
||||
pub mbid: Option<Mbid>,
|
||||
pub releases: Vec<Release>,
|
||||
|
1
tests/files/database_json_test.json
Normal file
1
tests/files/database_json_test.json
Normal file
@ -0,0 +1 @@
|
||||
[{"type":"Album","title":"Release group A","artist":[{"name":"Artist A","mbid":"f7769831-746b-4a12-8124-0123d7fe17c9"}],"year":1998,"mbid":"89efbf43-3395-4f6e-ac11-32c1ce514bb0","releases":[{"tracks":[{"number":1,"title":"Track A.1","artist":[{"name":"Artist A.A","mbid":"b7f7163d-61d5-4c96-b305-005df54fb999"}],"mbid":null},{"number":2,"title":"Track A.2","artist":[{"name":"Artist A.A","mbid":"b7f7163d-61d5-4c96-b305-005df54fb999"}],"mbid":null},{"number":3,"title":"Track A.3","artist":[{"name":"Artist A.A","mbid":"b7f7163d-61d5-4c96-b305-005df54fb999"},{"name":"Artist A.B","mbid":"6f6b46f2-4bb5-47e7-a8c8-03ebde30164f"}],"mbid":null}],"mbid":null}]},{"type":"Single","title":"Release group B","artist":[{"name":"Artist B","mbid":null}],"year":2008,"mbid":null,"releases":[{"tracks":[{"number":1,"title":"Track B.1","artist":[{"name":"Artist B.A","mbid":"d927e216-2e63-415c-acec-bf9f1abd3e3c"}],"mbid":"dacc9ce4-118c-4c92-aed7-1ebe4c7543b5"}],"mbid":"ac7b642d-8b71-4588-a694-e5ae43fac873"}]}]
|
Loading…
Reference in New Issue
Block a user