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,