delete multiple files with selection
This commit is contained in:
@@ -48,7 +48,7 @@ impl AbsoluteFilePathList {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
#[derive(Debug, Clone, Error)]
|
||||
pub enum AbsoluteFilePathListError {
|
||||
#[error("A list must not be empty")]
|
||||
Empty,
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::domain::warren::models::file::AbsoluteFilePath;
|
||||
use crate::domain::warren::models::file::{AbsoluteFilePath, AbsoluteFilePathList};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct RmRequest {
|
||||
path: AbsoluteFilePath,
|
||||
paths: AbsoluteFilePathList,
|
||||
force: bool,
|
||||
}
|
||||
|
||||
impl RmRequest {
|
||||
pub fn new(path: AbsoluteFilePath, force: bool) -> Self {
|
||||
Self { path, force }
|
||||
pub fn new(paths: AbsoluteFilePathList, force: bool) -> Self {
|
||||
Self { paths, force }
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &AbsoluteFilePath {
|
||||
&self.path
|
||||
pub fn paths(&self) -> &AbsoluteFilePathList {
|
||||
&self.paths
|
||||
}
|
||||
|
||||
pub fn into_path(self) -> AbsoluteFilePath {
|
||||
self.path
|
||||
pub fn into_paths(self) -> AbsoluteFilePathList {
|
||||
self.paths
|
||||
}
|
||||
|
||||
pub fn force(&self) -> bool {
|
||||
@@ -28,10 +28,10 @@ impl RmRequest {
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RmError {
|
||||
#[error("The path does not exist")]
|
||||
NotFound,
|
||||
#[error("The directory is not empty")]
|
||||
NotEmpty,
|
||||
#[error("At least one file does not exist")]
|
||||
NotFound(AbsoluteFilePath),
|
||||
#[error("At least one directory is not empty")]
|
||||
NotEmpty(AbsoluteFilePath),
|
||||
#[error(transparent)]
|
||||
Unknown(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
@@ -221,12 +221,15 @@ impl WarrenRmRequest {
|
||||
|
||||
pub fn build_fs_request(self, warren: &Warren) -> RmRequest {
|
||||
let force = self.base.force();
|
||||
let path = warren
|
||||
.path()
|
||||
.clone()
|
||||
.join(&self.base.into_path().to_relative());
|
||||
|
||||
RmRequest::new(path, force)
|
||||
let mut paths = self.base.into_paths();
|
||||
|
||||
paths
|
||||
.paths_mut()
|
||||
.into_iter()
|
||||
.for_each(|path| *path = warren.path.clone().join(&path.clone().to_relative()));
|
||||
|
||||
RmRequest::new(paths, force)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -242,34 +245,30 @@ impl Into<FetchWarrenRequest> for &WarrenRmRequest {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[derive(Debug)]
|
||||
pub struct WarrenRmResponse {
|
||||
warren: Warren,
|
||||
path: AbsoluteFilePath,
|
||||
results: Vec<Result<AbsoluteFilePath, RmError>>,
|
||||
}
|
||||
|
||||
impl WarrenRmResponse {
|
||||
pub fn new(warren: Warren, path: AbsoluteFilePath) -> Self {
|
||||
Self { warren, path }
|
||||
pub fn new(warren: Warren, results: Vec<Result<AbsoluteFilePath, RmError>>) -> Self {
|
||||
Self { warren, results }
|
||||
}
|
||||
|
||||
pub fn warren(&self) -> &Warren {
|
||||
&self.warren
|
||||
}
|
||||
|
||||
pub fn path(&self) -> &AbsoluteFilePath {
|
||||
&self.path
|
||||
pub fn results(&self) -> &Vec<Result<AbsoluteFilePath, RmError>> {
|
||||
&self.results
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum WarrenRmError {
|
||||
#[error(transparent)]
|
||||
FileSystem(#[from] RmError),
|
||||
#[error(transparent)]
|
||||
FetchWarren(#[from] FetchWarrenError),
|
||||
#[error(transparent)]
|
||||
Unknown(#[from] anyhow::Error),
|
||||
}
|
||||
|
||||
pub struct WarrenSaveRequest<'s> {
|
||||
|
||||
@@ -15,9 +15,10 @@ use super::models::{
|
||||
},
|
||||
},
|
||||
file::{
|
||||
CatError, CatRequest, CpError, CpRequest, CpResponse, FileStream, LsError, LsRequest,
|
||||
LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RmError, RmRequest, SaveError,
|
||||
SaveRequest, SaveResponse, StatError, StatRequest, StatResponse, TouchError, TouchRequest,
|
||||
AbsoluteFilePath, CatError, CatRequest, CpError, CpRequest, CpResponse, FileStream,
|
||||
LsError, LsRequest, LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RmError,
|
||||
RmRequest, SaveError, SaveRequest, SaveResponse, StatError, StatRequest, StatResponse,
|
||||
TouchError, TouchRequest,
|
||||
},
|
||||
share::{
|
||||
CreateShareBaseRequest, CreateShareError, CreateShareRequest, CreateShareResponse,
|
||||
@@ -144,7 +145,10 @@ pub trait FileSystemService: Clone + Send + Sync + 'static {
|
||||
fn cat(&self, request: CatRequest)
|
||||
-> impl Future<Output = Result<FileStream, CatError>> + Send;
|
||||
fn mkdir(&self, request: MkdirRequest) -> impl Future<Output = Result<(), MkdirError>> + Send;
|
||||
fn rm(&self, request: RmRequest) -> impl Future<Output = Result<(), RmError>> + Send;
|
||||
fn rm(
|
||||
&self,
|
||||
request: RmRequest,
|
||||
) -> impl Future<Output = Vec<Result<AbsoluteFilePath, RmError>>> + Send;
|
||||
fn mv(&self, request: MvRequest) -> impl Future<Output = Result<(), MvError>> + Send;
|
||||
fn save(
|
||||
&self,
|
||||
|
||||
@@ -68,7 +68,7 @@ pub trait WarrenNotifier: Clone + Send + Sync + 'static {
|
||||
|
||||
pub trait FileSystemNotifier: Clone + Send + Sync + 'static {
|
||||
fn ls(&self, response: &LsResponse) -> impl Future<Output = ()> + Send;
|
||||
fn cat(&self, path: &AbsoluteFilePathList) -> impl Future<Output = ()> + Send;
|
||||
fn cat(&self, paths: &AbsoluteFilePathList) -> impl Future<Output = ()> + Send;
|
||||
fn mkdir(&self, path: &AbsoluteFilePath) -> impl Future<Output = ()> + Send;
|
||||
fn rm(&self, path: &AbsoluteFilePath) -> impl Future<Output = ()> + Send;
|
||||
fn mv(
|
||||
|
||||
@@ -7,9 +7,10 @@ use crate::domain::warren::models::{
|
||||
},
|
||||
},
|
||||
file::{
|
||||
CatError, CatRequest, CpError, CpRequest, CpResponse, FileStream, LsError, LsRequest,
|
||||
LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RmError, RmRequest, SaveError,
|
||||
SaveRequest, SaveResponse, StatError, StatRequest, StatResponse, TouchError, TouchRequest,
|
||||
AbsoluteFilePath, CatError, CatRequest, CpError, CpRequest, CpResponse, FileStream,
|
||||
LsError, LsRequest, LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RmError,
|
||||
RmRequest, SaveError, SaveRequest, SaveResponse, StatError, StatRequest, StatResponse,
|
||||
TouchError, TouchRequest,
|
||||
},
|
||||
share::{
|
||||
CreateShareError, CreateShareRequest, CreateShareResponse, DeleteShareError,
|
||||
@@ -97,7 +98,10 @@ pub trait FileSystemRepository: Clone + Send + Sync + 'static {
|
||||
fn cat(&self, request: CatRequest)
|
||||
-> impl Future<Output = Result<FileStream, CatError>> + Send;
|
||||
fn mkdir(&self, request: MkdirRequest) -> impl Future<Output = Result<(), MkdirError>> + Send;
|
||||
fn rm(&self, request: RmRequest) -> impl Future<Output = Result<(), RmError>> + Send;
|
||||
fn rm(
|
||||
&self,
|
||||
request: RmRequest,
|
||||
) -> impl Future<Output = Vec<Result<AbsoluteFilePath, RmError>>> + Send;
|
||||
fn mv(&self, request: MvRequest) -> impl Future<Output = Result<(), MvError>> + Send;
|
||||
fn save(
|
||||
&self,
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use crate::domain::warren::{
|
||||
models::file::{
|
||||
CatError, CatRequest, CpError, CpRequest, CpResponse, FileStream, LsError, LsRequest,
|
||||
LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RmError, RmRequest, SaveError,
|
||||
SaveRequest, SaveResponse, StatError, StatRequest, StatResponse, TouchError, TouchRequest,
|
||||
AbsoluteFilePath, CatError, CatRequest, CpError, CpRequest, CpResponse, FileStream,
|
||||
LsError, LsRequest, LsResponse, MkdirError, MkdirRequest, MvError, MvRequest, RmError,
|
||||
RmRequest, SaveError, SaveRequest, SaveResponse, StatError, StatRequest, StatResponse,
|
||||
TouchError, TouchRequest,
|
||||
},
|
||||
ports::{FileSystemMetrics, FileSystemNotifier, FileSystemRepository, FileSystemService},
|
||||
};
|
||||
@@ -81,18 +82,19 @@ where
|
||||
result
|
||||
}
|
||||
|
||||
async fn rm(&self, request: RmRequest) -> Result<(), RmError> {
|
||||
let path = request.path().clone();
|
||||
let result = self.repository.rm(request).await;
|
||||
async fn rm(&self, request: RmRequest) -> Vec<Result<AbsoluteFilePath, RmError>> {
|
||||
let results = self.repository.rm(request).await;
|
||||
|
||||
if result.is_ok() {
|
||||
self.metrics.record_rm_success().await;
|
||||
self.notifier.rm(&path).await;
|
||||
} else {
|
||||
self.metrics.record_rm_failure().await;
|
||||
for result in results.iter() {
|
||||
if let Ok(path) = result.as_ref() {
|
||||
self.metrics.record_rm_success().await;
|
||||
self.notifier.rm(path).await;
|
||||
} else {
|
||||
self.metrics.record_rm_failure().await;
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
results
|
||||
}
|
||||
|
||||
async fn mv(&self, request: MvRequest) -> Result<(), MvError> {
|
||||
|
||||
@@ -250,26 +250,22 @@ where
|
||||
}
|
||||
|
||||
async fn warren_rm(&self, request: WarrenRmRequest) -> Result<WarrenRmResponse, WarrenRmError> {
|
||||
let warren = self.repository.fetch_warren((&request).into()).await?;
|
||||
let warren = match self.repository.fetch_warren((&request).into()).await {
|
||||
Ok(warren) => warren,
|
||||
Err(e) => {
|
||||
self.metrics.record_warren_rm_failure().await;
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
let path = request.base().path().clone();
|
||||
let rm_request = request.build_fs_request(&warren);
|
||||
|
||||
let result = self
|
||||
.fs_service
|
||||
.rm(rm_request)
|
||||
.await
|
||||
.map(|_| WarrenRmResponse::new(warren, path))
|
||||
.map_err(Into::into);
|
||||
let response = WarrenRmResponse::new(warren, self.fs_service.rm(rm_request).await);
|
||||
|
||||
if let Ok(response) = result.as_ref() {
|
||||
self.metrics.record_warren_rm_success().await;
|
||||
self.notifier.warren_rm(response).await;
|
||||
} else {
|
||||
self.metrics.record_warren_rm_failure().await;
|
||||
}
|
||||
self.metrics.record_warren_rm_success().await;
|
||||
self.notifier.warren_rm(&response).await;
|
||||
|
||||
result
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn warren_mv(&self, request: WarrenMvRequest) -> Result<WarrenMvResponse, WarrenMvError> {
|
||||
|
||||
@@ -41,8 +41,12 @@ impl From<WarrenMkdirError> for ApiError {
|
||||
impl From<RmError> for ApiError {
|
||||
fn from(value: RmError) -> Self {
|
||||
match value {
|
||||
RmError::NotFound => Self::NotFound("The directory does not exist".to_string()),
|
||||
RmError::NotEmpty => Self::BadRequest("The directory is not empty".to_string()),
|
||||
RmError::NotFound(_) => {
|
||||
Self::NotFound("At least one of the specified files does not exist".to_string())
|
||||
}
|
||||
RmError::NotEmpty(_) => Self::BadRequest(
|
||||
"At least one of the specified directories does not exist".to_string(),
|
||||
),
|
||||
RmError::Unknown(e) => Self::InternalServerError(e.to_string()),
|
||||
}
|
||||
}
|
||||
@@ -51,9 +55,7 @@ impl From<RmError> for ApiError {
|
||||
impl From<WarrenRmError> for ApiError {
|
||||
fn from(value: WarrenRmError) -> Self {
|
||||
match value {
|
||||
WarrenRmError::FileSystem(fs) => fs.into(),
|
||||
WarrenRmError::FetchWarren(err) => err.into(),
|
||||
WarrenRmError::Unknown(error) => Self::InternalServerError(error.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,10 @@ use crate::{
|
||||
domain::warren::{
|
||||
models::{
|
||||
auth_session::AuthRequest,
|
||||
file::{AbsoluteFilePathError, FilePath, FilePathError, RmRequest},
|
||||
file::{
|
||||
AbsoluteFilePath, AbsoluteFilePathError, AbsoluteFilePathList,
|
||||
AbsoluteFilePathListError, FilePath, FilePathError, RmRequest,
|
||||
},
|
||||
warren::WarrenRmRequest,
|
||||
},
|
||||
ports::{AuthService, WarrenService},
|
||||
@@ -23,7 +26,7 @@ use crate::{
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub(super) struct WarrenRmHttpRequestBody {
|
||||
warren_id: Uuid,
|
||||
path: String,
|
||||
paths: Vec<String>,
|
||||
force: bool,
|
||||
}
|
||||
|
||||
@@ -33,6 +36,8 @@ pub(super) enum ParseWarrenRmHttpRequestError {
|
||||
FilePath(#[from] FilePathError),
|
||||
#[error(transparent)]
|
||||
AbsoluteFilePath(#[from] AbsoluteFilePathError),
|
||||
#[error(transparent)]
|
||||
AbsoluteFilePathList(#[from] AbsoluteFilePathListError),
|
||||
}
|
||||
|
||||
impl From<ParseWarrenRmHttpRequestError> for ApiError {
|
||||
@@ -40,12 +45,17 @@ impl From<ParseWarrenRmHttpRequestError> for ApiError {
|
||||
match value {
|
||||
ParseWarrenRmHttpRequestError::FilePath(err) => match err {
|
||||
FilePathError::InvalidPath => {
|
||||
ApiError::BadRequest("The file path must be valid".to_string())
|
||||
ApiError::BadRequest("File paths must be valid".to_string())
|
||||
}
|
||||
},
|
||||
ParseWarrenRmHttpRequestError::AbsoluteFilePath(err) => match err {
|
||||
AbsoluteFilePathError::NotAbsolute => {
|
||||
ApiError::BadRequest("The file path must be absolute".to_string())
|
||||
ApiError::BadRequest("File paths must be absolute".to_string())
|
||||
}
|
||||
},
|
||||
ParseWarrenRmHttpRequestError::AbsoluteFilePathList(err) => match err {
|
||||
AbsoluteFilePathListError::Empty => {
|
||||
Self::BadRequest("At least one file path is required".to_string())
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -54,11 +64,17 @@ impl From<ParseWarrenRmHttpRequestError> for ApiError {
|
||||
|
||||
impl WarrenRmHttpRequestBody {
|
||||
fn try_into_domain(self) -> Result<WarrenRmRequest, ParseWarrenRmHttpRequestError> {
|
||||
let path = FilePath::new(&self.path)?;
|
||||
let mut paths = Vec::<AbsoluteFilePath>::new();
|
||||
|
||||
for path in self.paths.iter() {
|
||||
paths.push(FilePath::new(path)?.try_into()?);
|
||||
}
|
||||
|
||||
let path_list = AbsoluteFilePathList::new(paths)?;
|
||||
|
||||
Ok(WarrenRmRequest::new(
|
||||
self.warren_id,
|
||||
RmRequest::new(path.try_into()?, self.force),
|
||||
RmRequest::new(path_list, self.force),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use anyhow::{Context as _, anyhow, bail};
|
||||
use futures_util::TryStreamExt;
|
||||
use futures_util::{TryStreamExt, future::join_all};
|
||||
use rustix::fs::{Statx, statx};
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
@@ -182,10 +182,10 @@ impl FileSystem {
|
||||
|
||||
/// Actually removes a file or directory from the underlying file system
|
||||
///
|
||||
/// * `path`: The directory's absolute path (absolute not in relation to the root file system but `self.base_directory`)
|
||||
/// * `path`: The file's absolute path (absolute not in relation to the root file system but `self.base_directory`)
|
||||
/// * `force`: Whether to delete directories that are not empty
|
||||
async fn rm(&self, path: &AbsoluteFilePath, force: bool) -> io::Result<()> {
|
||||
let file_path = self.get_target_path(path);
|
||||
let file_path = self.get_target_path(&path);
|
||||
|
||||
if fs::metadata(&file_path).await?.is_file() {
|
||||
return fs::remove_file(&file_path).await;
|
||||
@@ -412,14 +412,34 @@ impl FileSystemRepository for FileSystem {
|
||||
})
|
||||
}
|
||||
|
||||
async fn rm(&self, request: RmRequest) -> Result<(), RmError> {
|
||||
self.rm(request.path(), request.force())
|
||||
.await
|
||||
.map_err(|e| match e.kind() {
|
||||
std::io::ErrorKind::NotFound => RmError::NotFound,
|
||||
std::io::ErrorKind::DirectoryNotEmpty => RmError::NotEmpty,
|
||||
_ => anyhow!("Failed to delete file at {}: {e:?}", request.path()).into(),
|
||||
})
|
||||
async fn rm(&self, request: RmRequest) -> Vec<Result<AbsoluteFilePath, RmError>> {
|
||||
let force = request.force();
|
||||
let paths: Vec<AbsoluteFilePath> = request.into_paths().into();
|
||||
|
||||
async fn _rm(
|
||||
fs: &FileSystem,
|
||||
path: AbsoluteFilePath,
|
||||
force: bool,
|
||||
) -> Result<AbsoluteFilePath, RmError> {
|
||||
fs.rm(&path, force)
|
||||
.await
|
||||
.map(|_| path.clone())
|
||||
.map_err(|e| match e.kind() {
|
||||
std::io::ErrorKind::NotFound => RmError::NotFound(path),
|
||||
std::io::ErrorKind::DirectoryNotEmpty => RmError::NotEmpty(path),
|
||||
_ => anyhow!("Failed to delete file at {}: {e:?}", path).into(),
|
||||
})
|
||||
}
|
||||
|
||||
let results: Vec<Result<AbsoluteFilePath, RmError>> = join_all(
|
||||
paths
|
||||
.into_iter()
|
||||
.map(|path| _rm(&self, path, force))
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.await;
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
async fn mv(&self, request: MvRequest) -> Result<(), MvError> {
|
||||
|
||||
@@ -87,11 +87,26 @@ impl WarrenNotifier for NotifierDebugLogger {
|
||||
}
|
||||
|
||||
async fn warren_rm(&self, response: &WarrenRmResponse) {
|
||||
tracing::debug!(
|
||||
"[Notifier] Deleted file {} from warren {}",
|
||||
response.path(),
|
||||
response.warren().name(),
|
||||
);
|
||||
let span = tracing::debug_span!("warren_rm", "{}", response.warren().name()).entered();
|
||||
|
||||
let results = response.results();
|
||||
|
||||
for result in results {
|
||||
match result.as_ref() {
|
||||
Ok(path) => tracing::debug!("Deleted file: {path}"),
|
||||
Err(e) => match e {
|
||||
crate::domain::warren::models::file::RmError::NotFound(path) => {
|
||||
tracing::debug!("File not found: {path}")
|
||||
}
|
||||
crate::domain::warren::models::file::RmError::NotEmpty(path) => {
|
||||
tracing::debug!("Directory not empty: {path}")
|
||||
}
|
||||
crate::domain::warren::models::file::RmError::Unknown(_) => (),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
span.exit();
|
||||
}
|
||||
|
||||
async fn warren_mv(&self, response: &WarrenMvResponse) {
|
||||
@@ -392,9 +407,11 @@ impl AuthNotifier for NotifierDebugLogger {
|
||||
}
|
||||
|
||||
async fn auth_warren_rm(&self, user: &User, response: &WarrenRmResponse) {
|
||||
let results = response.results();
|
||||
let successes = results.iter().filter(|r| r.is_ok()).count();
|
||||
|
||||
tracing::debug!(
|
||||
"[Notifier] Deleted file {} from warren {} for authenticated user {}",
|
||||
response.path(),
|
||||
"[Notifier] Deleted {successes} file(s) from warren {} for authenticated user {}",
|
||||
response.warren().name(),
|
||||
user.id(),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user