refactor small things and add some documentation

This commit is contained in:
Nico Fricke 2025-01-16 17:12:00 +01:00
parent 2eee67bdd1
commit f8a13f095b
2 changed files with 69 additions and 45 deletions

View File

@ -12,6 +12,7 @@ use std::path::PathBuf;
use tokio::fs::{read_dir, DirEntry}; use tokio::fs::{read_dir, DirEntry};
use tracing::{debug, error}; use tracing::{debug, error};
/// Handler to query a file tree. The query parameters can be used to control what is is queried.
#[debug_handler] #[debug_handler]
pub async fn query_file_tree( pub async fn query_file_tree(
State(state): State<AppState>, State(state): State<AppState>,
@ -20,56 +21,86 @@ pub async fn query_file_tree(
) -> Result<Json<PathElements>, ProblemDetails> { ) -> Result<Json<PathElements>, ProblemDetails> {
let path = build_system_path(&state.config.files.directory, &user_id, &query.path)?; let path = build_system_path(&state.config.files.directory, &user_id, &query.path)?;
debug!("Loading path: {:?}", path); debug!("Loading path: {:?}", path);
Ok(PathElements::new(load_directory(path, query.nesting).await?).into()) PathElements::load(path, query.nesting)
} .await
.map(Json::from)
async fn load_directory(path: PathBuf, nesting: u32) -> Result<Vec<PathElement>, ProblemDetails> {
match read_dir(path).await {
Err(error) => Err(map_io_error(error)),
Ok(mut value) => {
let mut result = vec![];
while let Some(next) = value.next_entry().await.map_err(map_io_error)? {
result.push(PathElement::from(next, nesting).await?);
}
Ok(result)
}
}
}
fn map_io_error(error: Error) -> ProblemDetails {
match error.kind() {
std::io::ErrorKind::NotFound => ProblemDetails::from_status_code(StatusCode::NOT_FOUND),
_ => to_internal_error(error),
}
} }
/// Query to control how a file tree is queried.
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct FileQuery { pub struct FileQuery {
/// Path which is used as base path to query the tree.
path: PathBuf, path: PathBuf,
#[serde(default = "u32::min_value")] #[serde(default = "u32::min_value")]
/// The amount of layers the tree should be loaded for. Can be used to cache a certain amount
/// of levels for a better UX when browsing the data.
nesting: u32, nesting: u32,
} }
/// Represents the root of a file tree.
#[derive(Serialize)] #[derive(Serialize)]
pub struct PathElements { pub struct PathElements {
files: Vec<PathElement>, files: Vec<PathElement>,
} }
/// Represents a node in the file tree.
#[derive(Serialize)]
pub struct PathElement {
/// The name of the directory or file.
name: String,
is_dir: bool,
/// Empty children means that the node is either a file or an empty directory.
/// None means that the children have not been loaded.
children: Option<Vec<PathElement>>,
}
impl PathElements { impl PathElements {
pub fn new(files: Vec<PathElement>) -> Self { pub fn new(files: Vec<PathElement>) -> Self {
Self { files } Self { files }
} }
}
#[derive(Serialize)] /// Loads a path recursively nested for up to the provided nesting parameter.
pub struct PathElement { pub async fn load(path: PathBuf, nesting: u32) -> Result<Self, ProblemDetails> {
name: String, Self::load_directory(path, nesting).await.map(Self::new)
is_dir: bool, }
children: Option<Vec<PathElement>>,
}
impl PathElement { async fn load_directory(
async fn from(value: DirEntry, nesting: u32) -> Result<Self, ProblemDetails> { path: PathBuf,
nesting: u32,
) -> Result<Vec<PathElement>, ProblemDetails> {
match read_dir(path).await {
Err(error) => Err(Self::map_io_error(error)),
Ok(mut value) => {
let mut result = vec![];
while let Some(next) = value.next_entry().await.map_err(Self::map_io_error)? {
result.push(Self::load_path_element(next, nesting).await?);
}
Ok(result)
}
}
}
async fn load_children(
path: PathBuf,
nesting: u32,
is_dir: bool,
) -> Result<Option<Vec<PathElement>>, ProblemDetails> {
if nesting == 0 {
Ok(None)
} else if !is_dir {
Ok(Some(vec![]))
} else {
Ok(Some(
// needs to call pin to support recursion in async contexts.
Box::pin(Self::load_directory(path, nesting - 1)).await?,
))
}
}
async fn load_path_element(
value: DirEntry,
nesting: u32,
) -> Result<PathElement, ProblemDetails> {
let metadata = value.metadata().await.unwrap(); let metadata = value.metadata().await.unwrap();
debug!("File type: {:?}", value.file_type().await.unwrap()); debug!("File type: {:?}", value.file_type().await.unwrap());
let is_dir = metadata.is_dir(); let is_dir = metadata.is_dir();
@ -77,11 +108,11 @@ impl PathElement {
Ok(PathElement { Ok(PathElement {
name: value.file_name().into_string().unwrap(), name: value.file_name().into_string().unwrap(),
is_dir, is_dir,
children: load_children(value.path(), nesting, is_dir).await?, children: Self::load_children(value.path(), nesting, is_dir).await?,
}) })
} else { } else {
error!( error!(
"File <{:?}> is neither a directory nor a file. ", "File <{:?}> is neither a directory nor a file.",
value.file_name() value.file_name()
); );
Err( Err(
@ -90,18 +121,11 @@ impl PathElement {
) )
} }
} }
}
async fn load_children( fn map_io_error(error: Error) -> ProblemDetails {
path: PathBuf, match error.kind() {
nesting: u32, std::io::ErrorKind::NotFound => ProblemDetails::from_status_code(StatusCode::NOT_FOUND),
is_dir: bool, _ => to_internal_error(error),
) -> Result<Option<Vec<PathElement>>, ProblemDetails> { }
if nesting == 0 {
Ok(None)
} else if !is_dir {
Ok(Some(vec![]))
} else {
Ok(Some(Box::pin(load_directory(path, nesting - 1)).await?))
} }
} }

View File

@ -53,7 +53,7 @@ async fn handle_single_file_upload<'field_lifetime>(
} }
} }
pub async fn save_file( async fn save_file(
path: PathBuf, path: PathBuf,
mut content: impl Stream<Item = Result<Bytes, Error>> + Unpin, mut content: impl Stream<Item = Result<Bytes, Error>> + Unpin,
) -> Result<(), Error> { ) -> Result<(), Error> {