use std::{fs::File, path::PathBuf, str::FromStr, sync::Arc}; use anyhow::{bail, Context, Ok}; use chashmap::CHashMap; use serde::{Deserialize, Serialize}; pub struct Library { path: PathBuf, cache: CHashMap, // TODO } #[derive(Debug, Clone)] pub enum LibNode { Directory(Arc), Item(Arc), } #[derive(Debug, Clone)] pub struct LibDirectory { pub path: PathBuf, pub child_names: Vec, pub data: LibDirectoryData, } #[derive(Debug, Clone)] pub struct LibItem { pub data: LibItemData, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct LibDirectoryData { pub name: String, } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct LibItemData { title: String, } impl Library { pub fn open(path: &str) -> anyhow::Result { Ok(Self { path: PathBuf::from_str(path).unwrap(), cache: CHashMap::new(), }) } pub fn root(&self) -> anyhow::Result> { LibNode::from_path(self.path.clone()) } pub fn nested(&self, path: &str) -> anyhow::Result> { let mut n = self.root()?; if path == "" { return Ok(n); } for seg in path.split("/") { n = n.get_directory()?.get_child(seg)? } Ok(n) } } impl LibNode { pub fn get_directory(&self) -> anyhow::Result<&LibDirectory> { match self { LibNode::Directory(d) => Ok(d), LibNode::Item(_) => bail!("not a directory"), } } pub fn title(&self) -> &str { match self { LibNode::Directory(d) => &d.data.name, LibNode::Item(i) => &i.data.title, } } pub fn from_path(path: PathBuf) -> anyhow::Result> { if path.is_dir() { let mpath = path.join("directory.json"); let data: LibDirectoryData = serde_json::from_reader(File::open(mpath).context("metadata missing")?)?; let child_names = path .read_dir()? .map(|e| e.unwrap().file_name().to_str().unwrap().to_string()) .filter(|e| !e.ends_with(".json")) .collect(); Ok(LibNode::Directory(Arc::new(LibDirectory { path, child_names, data, })) .into()) } else if path.is_file() { let mpath = path.clone().with_extension("metadata.json"); let data: LibItemData = serde_json::from_reader(File::open(mpath).context("metadata missing")?) .context("invalid metadata")?; Ok(LibNode::Item(Arc::new(LibItem { data })).into()) } else { bail!("did somebody really put a fifo or socket in the library?!") } } } impl LibDirectory { pub fn get_child(&self, p: &str) -> anyhow::Result> { if p.contains("..") || p.starts_with("/") { bail!("no! dont do that.") } let path = self.path.join(p); // if !path.exists() {bail!("does not exist");} LibNode::from_path(path) } pub fn child_nodes(&self) -> anyhow::Result>> { let mut o = vec![]; for name in &self.child_names { o.push(self.get_child(&name)?) } Ok(o) } }