use crate::addressing::Address; use crate::database::{Entry, EntryValue, InnerEntry, InsertEntry, QueryEntries}; use crate::hash::{ComputeHash, Hash, HasherWorker}; use crate::models; use anyhow::{anyhow, Result}; use log::{info, warn}; use serde::export::Formatter; use serde_json::Value; use std::fs; use std::path::{Path, PathBuf}; use walkdir::WalkDir; use actix::prelude::*; use chrono::prelude::*; use uuid::Uuid; const DIR_KEY: &str = "DIR"; const DIR_HAS_KEY: &str = "DIR_HAS"; #[derive(Debug, PartialEq)] pub struct UDirectory { name: String, } #[derive(Debug, PartialEq)] pub struct UPath(Vec); const TOP_SEPARATOR: &str = "//"; impl std::str::FromStr for UPath { type Err = anyhow::Error; fn from_str(s: &str) -> Result { if s.len() == 0 { Ok(UPath(vec![])) } else { match s.find(TOP_SEPARATOR) { Some(head_idx) => { let (head, rest) = s.split_at(head_idx); let mut result: Vec = Vec::new(); result.push(UDirectory { name: String::from(head), }); result.append( rest[TOP_SEPARATOR.len()..rest.len()] .split("/") .map(|part| UDirectory { name: String::from(part), }) .collect::>() .as_mut(), ); Ok(UPath(result)) } None => Ok(UPath( s.split("/") .map(|part| UDirectory { name: String::from(part), }) .collect(), )), } } } } impl std::fmt::Display for UDirectory { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) } } impl std::fmt::Display for UPath { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self.0.len() { 0 => write!(f, ""), 1 => write!(f, "{}", self.0.first().unwrap().name), _ => { let (head, tail) = self.0.split_first().unwrap(); write!( f, "{}//{}", head.name, tail.iter() .map(|udir| udir.name.clone()) .collect::>() .join("/") ) } } } } pub async fn fetch_or_create_dir( db_executor: &Addr, parent: Option
, directory: UDirectory, ) -> Result
{ let dir_value = EntryValue::Value(Value::String(directory.name)); let directories: Vec
= db_executor .send(QueryEntries { target: None, key: Some(String::from(DIR_KEY)), value: Some(dir_value.clone()), }) .await?? .into_iter() .map(|e: Entry| e.target) .collect(); let valid_directories: Vec
= match parent.clone() { Some(address) => { let parent_has: Vec
= db_executor .send(QueryEntries { target: Some(address), key: Some(String::from(DIR_HAS_KEY)), value: None, }) .await?? .into_iter() .map(|e: Entry| e.target) .collect(); directories .into_iter() .filter(|a| parent_has.contains(a)) .collect() } None => directories, }; match valid_directories.len() { 0 => { let new_directory_address = Address::UUID(Uuid::new_v4()); let directory_entry = InnerEntry { target: new_directory_address.clone(), key: String::from(DIR_KEY), value: dir_value, }; let _ = db_executor .send(InsertEntry { entry: directory_entry, }) .await??; if parent.is_some() { let has_entry = InnerEntry { target: parent.unwrap(), key: String::from(DIR_HAS_KEY), value: EntryValue::Address(new_directory_address.clone()), }; let _ = db_executor.send(InsertEntry { entry: has_entry }).await??; } Ok(new_directory_address) } 1 => Ok(valid_directories[0].clone()), _ => Err(anyhow!( "Invalid database state - more than one directory matches the query!" )), } } pub fn fetch_path(path: &UPath) -> Vec { unimplemented!(); } async fn _reimport_directory>( directory: T, db_executor: &Addr, hasher_worker: &Addr, ) -> Result<()> { for entry in WalkDir::new(&directory) .into_iter() .filter_map(|e| e.ok()) .filter(|e| e.path().is_file()) { info!("Processing: {}", entry.path().display()); let metadata = fs::metadata(entry.path())?; let size = metadata.len() as i64; if size < 0 { panic!("File {} too large?!", entry.path().display()); } let msg = ComputeHash { path: entry.path().to_path_buf(), }; let digest: Result, MailboxError> = hasher_worker.send(msg).await; let new_file = models::NewFile { path: entry .path() .to_str() .expect("path not valid unicode?!") .to_string(), hash: digest??.0, size, created: NaiveDateTime::from_timestamp(Utc::now().timestamp(), 0), }; let _insert_result = db_executor .send(crate::database::InsertFile { file: new_file }) .await?; } info!("Finished updating {}.", directory.as_ref().display()); Ok(()) } pub async fn reimport_directory( directory: PathBuf, db_executor: Addr, hasher_worker: Addr, ) { let result = _reimport_directory(directory, &db_executor, &hasher_worker).await; if result.is_err() { warn!("Update did not succeed!"); } } #[cfg(test)] mod tests { use crate::filesystem::{UDirectory, UPath}; use anyhow::Result; #[test] fn test_path_codec() { let path = UPath(vec![ UDirectory { name: "top".to_string(), }, UDirectory { name: "foo".to_string(), }, UDirectory { name: "bar".to_string(), }, UDirectory { name: "baz".to_string(), }, ]); let str_path = path.to_string(); assert!(str_path.len() > 0); let decoded_path: Result = str_path.parse(); assert!(decoded_path.is_ok()); assert_eq!(path, decoded_path.unwrap()); } }