use anyhow::{anyhow, bail, Context, Ok}; use jellycommon::{DirectoryInfo, ItemInfo}; use log::info; use std::{ffi::OsStr, fs::File, path::PathBuf, sync::Arc}; pub struct Library { pub root: Arc, } #[derive(Debug, Clone)] pub enum Node { Directory(Arc), Item(Arc), } #[derive(Debug, Clone)] pub struct Directory { pub lib_path: PathBuf, pub identifier: String, pub data: DirectoryInfo, pub children: Vec>, } #[derive(Debug, Clone)] pub struct Item { pub fs_path: PathBuf, pub lib_path: PathBuf, pub identifier: String, pub data: ItemInfo, } impl Library { pub fn open(path: &str) -> anyhow::Result { Ok(Self { root: Node::from_path(path.into(), PathBuf::new(), true).context("indexing root")?, }) } pub fn nested(&self, path: &str) -> anyhow::Result> { let mut n = self.root.clone(); if path == "" { return Ok(n); } for seg in path.split("/") { n = n .get_directory()? .child_by_ident(seg) .ok_or(anyhow!("does not exist"))?; } Ok(n) } } impl Node { pub fn get_directory(&self) -> anyhow::Result> { match self { Node::Directory(d) => Ok(d.clone()), Node::Item(_) => bail!("not a directory"), } } pub fn get_item(&self) -> anyhow::Result> { match self { Node::Item(i) => Ok(i.clone()), Node::Directory(_) => bail!("not an item"), } } pub fn title(&self) -> &str { match self { Node::Directory(d) => &d.data.name, Node::Item(i) => &i.data.title, } } pub fn identifier(&self) -> &str { match self { Node::Directory(d) => &d.identifier, Node::Item(i) => &i.identifier, } } pub fn from_path( path: PathBuf, mut lib_path: PathBuf, root: bool, ) -> anyhow::Result> { if path.is_dir() { let mpath = path.join("directory.json"); let data: DirectoryInfo = serde_json::from_reader(File::open(mpath).context("metadata missing")?)?; let identifier = path.file_name().unwrap().to_str().unwrap().to_string(); if !root { lib_path = lib_path.join(identifier.clone()); } let children = path .read_dir()? .filter_map(|e| { let e = e.unwrap(); // TODO if ((e.path().extension() != Some(OsStr::new("mkv")) && e.path().extension() != Some(OsStr::new("webm"))) || e.metadata().unwrap().is_dir()) && !e.path().ends_with("directory.json") { Some(e.path()) } else { None } }) .map(|e| { Node::from_path(e.clone(), lib_path.clone(), false) .context(format!("loading {e:?}")) }) .into_iter() .collect::>>()?; Ok(Node::Directory(Arc::new(Directory { lib_path, children, data, identifier, })) .into()) } else if path.is_file() { info!("loading {path:?}"); let datafile = File::open(path.clone()).context("cant load metadata")?; let data: ItemInfo = serde_json::from_reader(datafile).context("invalid metadata")?; let identifier = path .with_extension("") .file_name() .unwrap() .to_str() .unwrap() .to_string(); Ok(Node::Item(Arc::new(Item { fs_path: path.clone(), lib_path: lib_path.join(identifier.clone()), data, identifier, })) .into()) } else { bail!("did somebody really put a fifo or socket in the library?!") } } } impl Item { pub fn path(&self) -> String { self.lib_path.to_str().unwrap().to_string() } } impl Directory { pub fn path(&self) -> String { self.lib_path.to_str().unwrap().to_string() } pub fn child_by_ident(&self, i: &str) -> Option> { self.children .iter() .find(|e| e.identifier() == i) .map(|e| e.to_owned()) } }