diff --git a/src/database.rs b/src/database.rs index c8aa596..a0476d1 100644 --- a/src/database.rs +++ b/src/database.rs @@ -31,7 +31,7 @@ pub struct InnerEntry { pub value: EntryValue, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub enum EntryValue { Value(serde_json::Value), Address(Address), diff --git a/src/filesystem.rs b/src/filesystem.rs index d38a582..f4f2752 100644 --- a/src/filesystem.rs +++ b/src/filesystem.rs @@ -1,6 +1,7 @@ use crate::addressing::Address; use crate::database::{ DbExecutor, Entry, EntryValue, InnerEntry, InsertEntry, QueryEntries, RetrieveByHash, + RetrieveObject, }; use crate::hash::{ComputeHash, HasherWorker}; use crate::models; @@ -48,6 +49,7 @@ impl std::str::FromStr for UPath { }); result.append( rest[TOP_SEPARATOR.len()..rest.len()] + .trim_end_matches('/') .split("/") .map(|part| UDirectory { name: String::from(part), @@ -59,6 +61,7 @@ impl std::str::FromStr for UPath { result } None => string + .trim_end_matches('/') .split("/") .map(|part| UDirectory { name: String::from(part), @@ -104,10 +107,89 @@ impl std::fmt::Display for UPath { } } +trait EntryList { + fn extract_addresses(&self) -> Vec
; +} + +impl EntryList for Vec { + fn extract_addresses(&self) -> Vec
{ + self.into_iter() + .filter_map(|e| { + if let EntryValue::Address(address) = &e.value { + Some(address.clone()) + } else { + None + } + }) + .collect() + } +} + +pub async fn list_roots(db_executor: &Addr) -> Result> { + let all_directories: Vec = db_executor + .send(QueryEntries { + target: None, + key: Some(DIR_KEY.to_string()), + value: None, + }) + .await??; + + let directories_with_parents: Vec
= db_executor + .send(QueryEntries { + target: None, + key: Some(DIR_HAS_KEY.to_string()), + value: None, + }) + .await?? + .extract_addresses(); + + Ok(all_directories + .into_iter() + .filter(|entry| !directories_with_parents.contains(&entry.target)) + .collect()) +} + +pub async fn list_directory(db_executor: &Addr, path: &UPath) -> Result> { + let entry_addresses = match path.0.len() { + 0 => list_roots(db_executor) + .await? + .into_iter() + .map(|e| e.target) + .collect(), + _ => { + let resolved_path: Vec
= resolve_path(db_executor, path, false).await?; + let last = resolved_path.last().unwrap(); + + db_executor + .send(QueryEntries { + target: Some(last.clone()), + key: Some(DIR_HAS_KEY.to_string()), + value: None, + }) + .await?? + .extract_addresses() + } + }; + + let mut result: Vec = vec![]; + for address in entry_addresses { + result.extend( + db_executor + .send(RetrieveObject { target: address }) + .await?? + .into_iter() + .filter(|e| [DIR_KEY, FILENAME_KEY, FILE_IDENTITY_KEY].contains(&e.key.as_str())) + .collect::>(), + ); + } + Ok(result) +} + pub async fn fetch_or_create_dir( db_executor: &Addr, parent: Option
, directory: UDirectory, + create: bool, ) -> Result
{ match parent.clone() { Some(address) => trace!("FETCHING/CREATING {}/{:#}", address, directory), @@ -135,15 +217,7 @@ pub async fn fetch_or_create_dir( value: None, }) .await?? - .into_iter() - .filter_map(|e: Entry| { - if let EntryValue::Address(address) = e.value { - Some(address) - } else { - None - } - }) - .collect(); + .extract_addresses(); let valid = directories .into_iter() @@ -157,28 +231,32 @@ pub async fn fetch_or_create_dir( 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()), + if create { + 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: has_entry }).await??; - } + let _ = db_executor + .send(InsertEntry { + entry: directory_entry, + }) + .await??; - Ok(new_directory_address) + 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) + } else { + Err(anyhow!("Directory does not exist.")) + } } 1 => Ok(valid_directories[0].clone()), _ => Err(anyhow!( @@ -187,9 +265,10 @@ pub async fn fetch_or_create_dir( } } -pub async fn resolve_path_with_parents( +pub async fn resolve_path( db_executor: &Addr, path: &UPath, + create: bool, ) -> Result> { let mut result: Vec
= vec![]; let mut path_stack = path.0.to_vec(); @@ -200,6 +279,7 @@ pub async fn resolve_path_with_parents( db_executor, result.last().cloned(), path_stack.pop().unwrap(), + create, ) .await?; result.push(dir_address); @@ -288,7 +368,7 @@ async fn _reimport_directory>( })) .collect(), ); - let resolved_path = resolve_path_with_parents(db_executor, &upath).await?; + let resolved_path = resolve_path(db_executor, &upath, true).await?; let parent_dir = resolved_path.last().unwrap(); let dir_has_entry = InnerEntry { target: parent_dir.clone(), diff --git a/src/main.rs b/src/main.rs index 5d82968..024c7a6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -82,6 +82,7 @@ fn main() -> std::io::Result<()> { .wrap(middleware::Logger::default()) .service(routes::get_raw) .service(routes::get_object) + .service(routes::list_hier) .service(routes::get_lookup) .service(routes::api_refresh) .service( diff --git a/src/routes.rs b/src/routes.rs index 4d86fde..5a892f9 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,9 +1,10 @@ use crate::addressing::Address; use crate::database::Entry; +use crate::filesystem::{list_directory, UPath}; use crate::hash::{decode, encode}; use actix::prelude::*; use actix_files::NamedFile; -use actix_web::error::{ErrorBadRequest, ErrorInternalServerError}; +use actix_web::error::{ErrorBadRequest, ErrorInternalServerError, ErrorNotFound}; use actix_web::{error, get, post, web, Error, HttpResponse}; use anyhow::Result; use log::debug; @@ -66,6 +67,24 @@ pub async fn get_object( Ok(HttpResponse::Ok().json(result)) } +#[get("/hier/{path:.*}")] +pub async fn list_hier( + state: web::Data, + path: web::Path, +) -> Result { + let upath: UPath = path.into_inner().parse().map_err(ErrorBadRequest)?; + let entries: Vec = list_directory(&state.db, &upath) + .await + .map_err(ErrorNotFound)?; // todo: 500 if actual error occurs + + Ok(HttpResponse::Ok().json( + entries + .iter() + .map(Entry::as_json) + .collect::(), + )) +} + #[derive(Deserialize)] pub struct LookupRequest { query: String,