Basic manifest implementation
This commit is contained in:
parent
d9b23d4a24
commit
a00486eeaf
169
Cargo.lock
generated
169
Cargo.lock
generated
|
@ -51,6 +51,18 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anyhow"
|
||||||
|
version = "1.0.91"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.20"
|
version = "4.5.20"
|
||||||
|
@ -97,18 +109,72 @@ version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "heck"
|
name = "heck"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "idna"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-bidi",
|
||||||
|
"unicode-normalization",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "is_terminal_polyfill"
|
name = "is_terminal_polyfill"
|
||||||
version = "1.70.1"
|
version = "1.70.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.161"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.7.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.89"
|
version = "1.0.89"
|
||||||
|
@ -127,6 +193,44 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.214"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.214"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.132"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
|
@ -144,18 +248,76 @@ dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec_macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tinyvec_macros"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-bidi"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-ident"
|
name = "unicode-ident"
|
||||||
version = "1.0.13"
|
version = "1.0.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-normalization"
|
||||||
|
version = "0.1.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
|
||||||
|
dependencies = [
|
||||||
|
"tinyvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "url"
|
||||||
|
version = "2.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"idna",
|
||||||
|
"percent-encoding",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-sys"
|
name = "windows-sys"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
|
@ -254,3 +416,10 @@ version = "2.0.0"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xmpd-manifest"
|
name = "xmpd-manifest"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"url",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
|
@ -18,3 +18,8 @@ crate-type = ["rlib"]
|
||||||
bench = false
|
bench = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
uuid.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
|
serde_json.workspace = true
|
||||||
|
url.workspace = true
|
||||||
|
|
|
@ -1,3 +1,54 @@
|
||||||
pub fn test() {
|
use std::path::Path;
|
||||||
println!("Hello, world!");
|
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests;
|
||||||
|
pub mod store;
|
||||||
|
pub mod song;
|
||||||
|
pub mod playlist;
|
||||||
|
pub mod query;
|
||||||
|
|
||||||
|
pub type Result<T> = anyhow::Result<T>;
|
||||||
|
|
||||||
|
pub struct Manifest<ST: store::BaseStore> {
|
||||||
|
store: Box<ST>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<ST: store::BaseStore + Clone> Manifest<ST> {
|
||||||
|
pub fn new(p: &Path) -> Result<Self>{
|
||||||
|
let mut store = ST::empty();
|
||||||
|
if p.exists() {
|
||||||
|
store.load_from(p)?;
|
||||||
|
} else {
|
||||||
|
store.save_to(p)?;
|
||||||
|
}
|
||||||
|
store.save_original_path(p);
|
||||||
|
Ok(Self {
|
||||||
|
store: Box::new(store)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn store(&self) -> &ST {
|
||||||
|
self.store.as_ref()
|
||||||
|
}
|
||||||
|
pub fn store_mut(&mut self) -> &mut ST {
|
||||||
|
self.store.as_mut()
|
||||||
|
}
|
||||||
|
pub fn save(&self) -> Result<()> {
|
||||||
|
self.store().save_to(self.store().get_original_path())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
pub fn load(&mut self) -> Result<()> {
|
||||||
|
let p = self.store().get_original_path().to_path_buf();
|
||||||
|
self.store_mut().load_from(&p)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
40
xmpd-manifest/src/playlist.rs
Normal file
40
xmpd-manifest/src/playlist.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, PartialOrd, Default)]
|
||||||
|
pub struct Playlist {
|
||||||
|
name: String,
|
||||||
|
author: String,
|
||||||
|
songs: Vec<Uuid>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Playlist {
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
pub fn author(&self) -> &str {
|
||||||
|
&self.author
|
||||||
|
}
|
||||||
|
pub fn songs(&self) -> &Vec<Uuid> {
|
||||||
|
&self.songs
|
||||||
|
}
|
||||||
|
pub fn songs_mut(&mut self) -> &mut Vec<Uuid> {
|
||||||
|
&mut self.songs
|
||||||
|
}
|
||||||
|
pub fn set_name(&mut self, v: &str) {
|
||||||
|
self.name = v.to_string();
|
||||||
|
}
|
||||||
|
pub fn set_author(&mut self, v: &str) {
|
||||||
|
self.author = v.to_string();
|
||||||
|
}
|
||||||
|
pub fn add_song(&mut self, v: &Uuid) {
|
||||||
|
self.songs.push(v.clone());
|
||||||
|
}
|
||||||
|
pub fn remove_song(&mut self, v: &Uuid) {
|
||||||
|
for (i, id) in self.songs.iter().enumerate() {
|
||||||
|
if id == v {
|
||||||
|
self.songs.remove(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
xmpd-manifest/src/query/mod.rs
Normal file
35
xmpd-manifest/src/query/mod.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
|
||||||
|
mod playlist;
|
||||||
|
mod song;
|
||||||
|
|
||||||
|
pub struct Query<QT: ?Sized> {
|
||||||
|
playlist: Option<Uuid>,
|
||||||
|
song: Option<Uuid>,
|
||||||
|
_phantom: PhantomData<QT>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Query<()> {
|
||||||
|
pub fn song(id: Uuid) -> Query<song::QuerySong> {
|
||||||
|
Query {
|
||||||
|
song: Some(id),
|
||||||
|
playlist: None,
|
||||||
|
_phantom: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn playlist(id: Uuid) -> Query<playlist::QueryPlaylist> {
|
||||||
|
Query {
|
||||||
|
playlist: Some(id),
|
||||||
|
song: None,
|
||||||
|
_phantom: PhantomData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub trait QueryType {}
|
||||||
|
|
||||||
|
|
47
xmpd-manifest/src/query/playlist.rs
Normal file
47
xmpd-manifest/src/query/playlist.rs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{song::Song, store, Manifest};
|
||||||
|
|
||||||
|
use super::{Query, QueryType};
|
||||||
|
|
||||||
|
pub struct QueryPlaylist;
|
||||||
|
|
||||||
|
impl QueryType for QueryPlaylist {}
|
||||||
|
|
||||||
|
impl Query<QueryPlaylist> {
|
||||||
|
pub fn id(&self) -> &Uuid {
|
||||||
|
if let Some(id) = &self.playlist {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
pub fn name<'a, ST: store::BaseStore + Clone>(&self, manifest: &'a Manifest<ST>) -> Option<&'a str> {
|
||||||
|
let pl = manifest.store().get_playlist(self.id())?;
|
||||||
|
Some(pl.name())
|
||||||
|
}
|
||||||
|
pub fn set_name<ST: store::BaseStore + Clone>(&self, manifest: &mut Manifest<ST>, name: &str) -> Option<()> {
|
||||||
|
let pl = manifest.store_mut().get_playlist_mut(self.id())?;
|
||||||
|
pl.set_name(name);
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
pub fn author<'a, ST: store::BaseStore + Clone>(&self, manifest: &'a Manifest<ST>) -> Option<&'a str> {
|
||||||
|
let pl = manifest.store().get_playlist(self.id())?;
|
||||||
|
Some(pl.author())
|
||||||
|
}
|
||||||
|
pub fn set_author<ST: store::BaseStore + Clone>(&self, manifest: &mut Manifest<ST>, author: &str) -> Option<()> {
|
||||||
|
let pl = manifest.store_mut().get_playlist_mut(self.id())?;
|
||||||
|
pl.set_author(author);
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
pub fn songs<'a, ST: store::BaseStore + Clone>(&self, manifest: &'a Manifest<ST>) -> Option<&'a Vec<Uuid>> {
|
||||||
|
let pl = manifest.store().get_playlist(self.id())?;
|
||||||
|
Some(pl.songs())
|
||||||
|
}
|
||||||
|
pub fn add_song<ST: store::BaseStore + Clone>(&self, manifest: &mut Manifest<ST>, song: Song) -> Option<Uuid> {
|
||||||
|
let pl = manifest.store_mut().get_playlist_mut(self.id())?;
|
||||||
|
let id = Uuid::new_v4();
|
||||||
|
pl.add_song(&id);
|
||||||
|
manifest.store_mut().get_songs_mut().insert(id, song);
|
||||||
|
Some(id)
|
||||||
|
}
|
||||||
|
}
|
68
xmpd-manifest/src/query/song.rs
Normal file
68
xmpd-manifest/src/query/song.rs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use url::Url;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{song::SourceType, store, Manifest};
|
||||||
|
|
||||||
|
use super::{Query, QueryType};
|
||||||
|
|
||||||
|
|
||||||
|
pub struct QuerySong;
|
||||||
|
impl QueryType for QuerySong {}
|
||||||
|
|
||||||
|
impl Query<QuerySong> {
|
||||||
|
pub fn id(&self) -> &Uuid {
|
||||||
|
if let Some(id) = &self.song {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
pub fn name<'a, ST: store::BaseStore + Clone>(&self, manifest: &'a Manifest<ST>) -> Option<&'a str> {
|
||||||
|
let pl = manifest.store().get_song(self.id())?;
|
||||||
|
Some(pl.name())
|
||||||
|
}
|
||||||
|
pub fn set_name<ST: store::BaseStore + Clone>(&self, manifest: &mut Manifest<ST>, name: &str) -> Option<()> {
|
||||||
|
let pl = manifest.store_mut().get_song_mut(self.id())?;
|
||||||
|
pl.set_name(name);
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
pub fn author<'a, ST: store::BaseStore + Clone>(&self, manifest: &'a Manifest<ST>) -> Option<&'a str> {
|
||||||
|
let pl = manifest.store().get_song(self.id())?;
|
||||||
|
Some(pl.author())
|
||||||
|
}
|
||||||
|
pub fn set_author<ST: store::BaseStore + Clone>(&self, manifest: &mut Manifest<ST>, author: &str) -> Option<()> {
|
||||||
|
let pl = manifest.store_mut().get_song_mut(self.id())?;
|
||||||
|
pl.set_author(author);
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
pub fn url<'a, ST: store::BaseStore + Clone>(&self, manifest: &'a Manifest<ST>) -> Option<&'a Url> {
|
||||||
|
let pl = manifest.store().get_song(self.id())?;
|
||||||
|
Some(pl.url())
|
||||||
|
}
|
||||||
|
pub fn url_as_str<'a, ST: store::BaseStore + Clone>(&self, manifest: &'a Manifest<ST>) -> Option<&'a str> {
|
||||||
|
Some(self.url(manifest)?.as_str())
|
||||||
|
}
|
||||||
|
pub fn set_url<ST: store::BaseStore + Clone>(&self, manifest: &mut Manifest<ST>, url: &Url) -> Option<()> {
|
||||||
|
let pl = manifest.store_mut().get_song_mut(self.id())?;
|
||||||
|
pl.set_url(url);
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
pub fn set_url_from_str<ST: store::BaseStore + Clone>(&self, manifest: &mut Manifest<ST>, url: &str) -> Option<()> {
|
||||||
|
let Ok(url) = Url::from_str(url) else {return None};
|
||||||
|
self.set_url(manifest, &url);
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
pub fn source_type<'a, ST: store::BaseStore + Clone>(&self, manifest: &'a Manifest<ST>) -> Option<&'a SourceType> {
|
||||||
|
let pl = manifest.store().get_song(self.id())?;
|
||||||
|
Some(pl.source_type())
|
||||||
|
}
|
||||||
|
pub fn set_source_type<ST: store::BaseStore + Clone>(&self, manifest: &mut Manifest<ST>, source_type: &SourceType) -> Option<()> {
|
||||||
|
let pl = manifest.store_mut().get_song_mut(self.id())?;
|
||||||
|
pl.set_source_type(source_type);
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
69
xmpd-manifest/src/song.rs
Normal file
69
xmpd-manifest/src/song.rs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, PartialOrd)]
|
||||||
|
pub struct Song {
|
||||||
|
name: String,
|
||||||
|
author: String,
|
||||||
|
url: url::Url,
|
||||||
|
source_type: SourceType,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Song {
|
||||||
|
pub fn new(url: &url::Url) -> crate::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
name: String::default(),
|
||||||
|
author: String::default(),
|
||||||
|
source_type: SourceType::from_url(url)?,
|
||||||
|
url: url.clone()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn new_from_str(url: &str) -> crate::Result<Self> {
|
||||||
|
Self::new(&url::Url::from_str(url)?)
|
||||||
|
}
|
||||||
|
pub fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
pub fn author(&self) -> &str {
|
||||||
|
&self.author
|
||||||
|
}
|
||||||
|
pub fn url(&self) -> &url::Url {
|
||||||
|
&self.url
|
||||||
|
}
|
||||||
|
pub fn source_type(&self) -> &SourceType {
|
||||||
|
&self.source_type
|
||||||
|
}
|
||||||
|
pub fn set_name(&mut self, v: &str) {
|
||||||
|
self.name = v.to_string();
|
||||||
|
}
|
||||||
|
pub fn set_author(&mut self, v: &str) {
|
||||||
|
self.author = v.to_string();
|
||||||
|
}
|
||||||
|
pub fn set_url(&mut self, v: &url::Url) {
|
||||||
|
self.url.clone_from(v);
|
||||||
|
}
|
||||||
|
pub fn set_source_type(&mut self, v: &SourceType) {
|
||||||
|
self.source_type.clone_from(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, PartialOrd)]
|
||||||
|
pub enum SourceType {
|
||||||
|
Youtube,
|
||||||
|
Spotify,
|
||||||
|
Soundcloud,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SourceType {
|
||||||
|
fn from_url(url: &url::Url) -> crate::Result<Self> {
|
||||||
|
match url.host_str() {
|
||||||
|
Some("youtube.com") | Some("youtu.be") => Ok(Self::Youtube),
|
||||||
|
Some("open.spotify.com") => Ok(Self::Spotify),
|
||||||
|
Some("soundcloud.com") |Some("on.soundcloud.com") => Ok(Self::Soundcloud),
|
||||||
|
Some(host) => anyhow::bail!("Unknown host {host:?}"),
|
||||||
|
None => anyhow::bail!("Unknown host: (none)"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
71
xmpd-manifest/src/store/json.rs
Normal file
71
xmpd-manifest/src/store/json.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
use uuid::Uuid;
|
||||||
|
use crate::{playlist::Playlist, song::Song};
|
||||||
|
|
||||||
|
const DEFAULT_TEXT: &str = r#"{
|
||||||
|
"songs": {},
|
||||||
|
"playlists": {}
|
||||||
|
}"#;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)]
|
||||||
|
pub struct JsonStore {
|
||||||
|
#[serde(skip)]
|
||||||
|
original_path: PathBuf,
|
||||||
|
songs: HashMap<Uuid, Song>,
|
||||||
|
playlists: HashMap<Uuid, Playlist>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl super::BaseStore for JsonStore {
|
||||||
|
fn get_default_file_contents() -> &'static str {
|
||||||
|
&DEFAULT_TEXT
|
||||||
|
}
|
||||||
|
fn get_file_extension() -> &'static str {
|
||||||
|
"json"
|
||||||
|
}
|
||||||
|
fn empty() -> Self where Self: Sized {
|
||||||
|
Self {
|
||||||
|
original_path: PathBuf::new(),
|
||||||
|
songs: HashMap::default(),
|
||||||
|
playlists: HashMap::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn to_bytes(&self) -> crate::Result<Vec<u8>> {
|
||||||
|
let s = serde_json::to_vec_pretty(self)?;
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
fn from_bytes(s: &[u8]) -> crate::Result<Self> where Self: Sized {
|
||||||
|
let s: Self = serde_json::from_slice(s)?;
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
fn get_songs(&self) -> &HashMap<Uuid, Song> {
|
||||||
|
&self.songs
|
||||||
|
}
|
||||||
|
fn get_songs_mut(&mut self) -> &mut HashMap<Uuid, Song> {
|
||||||
|
&mut self.songs
|
||||||
|
}
|
||||||
|
fn get_song(&self, id: &Uuid) -> Option<&Song> {
|
||||||
|
self.songs.get(id)
|
||||||
|
}
|
||||||
|
fn get_song_mut(&mut self, id: &Uuid) -> Option<&mut Song> {
|
||||||
|
self.songs.get_mut(id)
|
||||||
|
}
|
||||||
|
fn get_playlists(&self) -> &HashMap<Uuid, Playlist> {
|
||||||
|
&self.playlists
|
||||||
|
}
|
||||||
|
fn get_playlists_mut(&mut self) -> &mut HashMap<Uuid, Playlist> {
|
||||||
|
&mut self.playlists
|
||||||
|
}
|
||||||
|
fn get_playlist(&self, id: &Uuid) -> Option<&Playlist> {
|
||||||
|
self.playlists.get(id)
|
||||||
|
}
|
||||||
|
fn get_playlist_mut(&mut self, id: &Uuid) -> Option<&mut Playlist> {
|
||||||
|
self.playlists.get_mut(id)
|
||||||
|
}
|
||||||
|
fn save_original_path(&mut self, p: &std::path::Path) {
|
||||||
|
self.original_path = p.to_path_buf();
|
||||||
|
}
|
||||||
|
fn get_original_path(&self) -> &std::path::Path {
|
||||||
|
&self.original_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
40
xmpd-manifest/src/store/mod.rs
Normal file
40
xmpd-manifest/src/store/mod.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use std::{collections::HashMap, path::Path};
|
||||||
|
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{playlist::Playlist, song::Song};
|
||||||
|
|
||||||
|
mod json;
|
||||||
|
pub use json::JsonStore;
|
||||||
|
|
||||||
|
pub trait BaseStore {
|
||||||
|
fn get_songs(&self) -> &HashMap<Uuid, Song>;
|
||||||
|
fn get_songs_mut(&mut self) -> &mut HashMap<Uuid, Song>;
|
||||||
|
fn get_song(&self, id: &Uuid) -> Option<&Song>;
|
||||||
|
fn get_song_mut(&mut self, id: &Uuid) -> Option<&mut Song>;
|
||||||
|
|
||||||
|
fn get_playlists(&self) -> &HashMap<Uuid, Playlist>;
|
||||||
|
fn get_playlists_mut(&mut self) -> &mut HashMap<Uuid, Playlist>;
|
||||||
|
fn get_playlist(&self, id: &Uuid) -> Option<&Playlist>;
|
||||||
|
fn get_playlist_mut(&mut self, id: &Uuid) -> Option<&mut Playlist>;
|
||||||
|
|
||||||
|
fn to_bytes(&self) -> crate::Result<Vec<u8>>;
|
||||||
|
fn from_bytes(s: &[u8]) -> crate::Result<Self> where Self: Sized;
|
||||||
|
fn empty() -> Self where Self: Sized;
|
||||||
|
fn save_to(&self, p: &Path) -> crate::Result<()> {
|
||||||
|
let bin = self.to_bytes()?;
|
||||||
|
std::fs::write(p, bin)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn load_from(&mut self, p: &Path) -> crate::Result<()> where Self: Clone {
|
||||||
|
let bin = std::fs::read(p)?;
|
||||||
|
let s = Self::from_bytes(&bin)?;
|
||||||
|
self.clone_from(&s);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn get_default_file_contents() -> &'static str;
|
||||||
|
fn get_file_extension() -> &'static str;
|
||||||
|
fn save_original_path(&mut self, p: &Path);
|
||||||
|
fn get_original_path(&self) -> &Path;
|
||||||
|
}
|
||||||
|
|
19
xmpd-manifest/src/tests/mod.rs
Normal file
19
xmpd-manifest/src/tests/mod.rs
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
|
||||||
|
fn manifest_creation_base<ST: crate::store::BaseStore + Clone>() {
|
||||||
|
use crate::Manifest;
|
||||||
|
|
||||||
|
let mut p = std::env::temp_dir();
|
||||||
|
p.push("test_manifest");
|
||||||
|
p.with_extension(ST::get_file_extension());
|
||||||
|
|
||||||
|
let manifest: Manifest<ST> = Manifest::new(&p).unwrap();
|
||||||
|
manifest.save().unwrap();
|
||||||
|
let content = std::fs::read_to_string(&p).unwrap();
|
||||||
|
println!("{}", content);
|
||||||
|
assert!(content == ST::get_default_file_contents().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn manifest_creation_json() {
|
||||||
|
manifest_creation_base::<crate::store::JsonStore>();
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user