refactor small things and add some documentation
This commit is contained in:
parent
2eee67bdd1
commit
f8a13f095b
@ -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?))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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> {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user