A review against the Python client, which shares this client's config and login cache, found the transfers holding whole files in memory and several commands answering differently from their Python counterparts. - Stream uploads and downloads instead of buffering the whole file, so memory no longer scales with file size times concurrency. Uploads still declare a Content-Length rather than going out chunked. - Write a download beside its destination and move it into place once complete, and refuse a destination that cannot be written, so a failed transfer leaves what was already there untouched. - Report the server's own error detail instead of the bare status. - List locked sub-folders in `ls --json --recursive` using the given password and skip only those that cannot be unlocked, instead of failing the whole listing. - Name each folder's laboratory in JSON output and sort its entries, matching the Python client's schema and order. - Ask the server who is logged in for `whoami`, rather than trusting the cached name. - Collapse repeated separators and refuse `..` in remote paths. - Fall back to the API when a laboratory is missing from the cache, so a newly added one no longer needs a fresh login. - Verify the login cache digest on read, as the Python client does. - Accept `-e` as the short form of `--exclude`. - Satisfy clippy and rustfmt across the crate.
58 lines
2.1 KiB
Rust
58 lines
2.1 KiB
Rust
use crate::cache::{create_authenticated_conn, load_cache_with_token_refresh};
|
|
use crate::commands::shared::{
|
|
find_file_by_name, find_folder, find_laboratory, find_subfolder_by_name, parse_remote_path,
|
|
};
|
|
use crate::error::response_error;
|
|
use anyhow::{anyhow, bail};
|
|
|
|
pub async fn rm(remote_path: &str, recursive: bool) -> Result<(), anyhow::Error> {
|
|
let (remote, labname, path) = parse_remote_path(remote_path)?;
|
|
|
|
// Split into parent path and target name
|
|
let path = path.trim_end_matches('/');
|
|
let last_slash = path.rfind('/').ok_or_else(|| anyhow!("Invalid path"))?;
|
|
let parent_path = if last_slash == 0 {
|
|
"/"
|
|
} else {
|
|
&path[..last_slash]
|
|
};
|
|
let target_name = &path[last_slash + 1..];
|
|
if target_name.is_empty() {
|
|
bail!("Cannot remove root folder");
|
|
}
|
|
|
|
let cache = load_cache_with_token_refresh(&remote).await?;
|
|
let conn = create_authenticated_conn(&remote, &cache)?;
|
|
let lab = find_laboratory(&conn, Some(&cache), &labname).await?;
|
|
let parent_folder = find_folder(&conn, lab.id, parent_path, None).await?;
|
|
|
|
// Check if target is a file
|
|
let files = conn.list_all_files(&parent_folder.id).await?;
|
|
if let Some(file) = find_file_by_name(&files, target_name) {
|
|
let resp = conn.delete(&format!("v3/files/{}/", file.id)).await?;
|
|
if !resp.status().is_success() {
|
|
return Err(response_error("Failed to delete file", resp).await);
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
// Check if target is a sub-folder
|
|
if let Some(subfolder) = find_subfolder_by_name(&parent_folder.sub_folders, target_name) {
|
|
if !recursive {
|
|
bail!("Cannot remove `{}`: Is a folder.", path);
|
|
}
|
|
let resp = conn
|
|
.delete_with_query(
|
|
&format!("v3/folders/{}/", subfolder.id),
|
|
&[("recursive", "true")],
|
|
)
|
|
.await?;
|
|
if !resp.status().is_success() {
|
|
return Err(response_error("Failed to delete folder", resp).await);
|
|
}
|
|
return Ok(());
|
|
}
|
|
|
|
Err(anyhow!("Cannot remove `{}`: No such file or folder.", path))
|
|
}
|