use std::path::Path;
use_enabled_fs_module!();
use super::{
copy::copy_file_with_progress_unchecked,
validate_destination_file_path,
validate_source_file_path,
CollidingFileBehaviour,
DestinationValidationAction,
FileCopyWithProgressOptions,
FileProgress,
};
use crate::{
error::{FileError, FileRemoveError},
file::ValidatedSourceFilePath,
DEFAULT_PROGRESS_UPDATE_BYTE_INTERVAL,
DEFAULT_READ_BUFFER_SIZE,
DEFAULT_WRITE_BUFFER_SIZE,
};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct FileMoveOptions {
pub colliding_file_behaviour: CollidingFileBehaviour,
}
#[allow(clippy::derivable_impls)]
impl Default for FileMoveOptions {
fn default() -> Self {
Self {
colliding_file_behaviour: CollidingFileBehaviour::Abort,
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FileMoveFinished {
Created {
bytes_copied: u64,
method: FileMoveMethod,
},
Overwritten {
bytes_copied: u64,
method: FileMoveMethod,
},
Skipped,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum FileMoveMethod {
Rename,
CopyAndDelete,
}
pub fn move_file<S, D>(
source_file_path: S,
destination_file_path: D,
options: FileMoveOptions,
) -> Result<FileMoveFinished, FileError>
where
S: AsRef<Path>,
D: AsRef<Path>,
{
let source_file_path = source_file_path.as_ref();
let destination_file_path = destination_file_path.as_ref();
let validated_source_path = validate_source_file_path(source_file_path)?;
let (validated_destination_file_path, destination_file_exists) =
match validate_destination_file_path(
&validated_source_path,
destination_file_path,
options.colliding_file_behaviour,
)? {
DestinationValidationAction::SkipCopyOrMove => {
return Ok(FileMoveFinished::Skipped);
}
DestinationValidationAction::Continue(info) => {
(info.destination_file_path, info.exists)
}
};
let ValidatedSourceFilePath {
source_file_path: validated_source_file_path,
original_was_symlink_to_file: source_file_was_symlink_to_file,
} = validated_source_path;
let source_file_path_to_rename = if source_file_was_symlink_to_file {
source_file_path
} else {
validated_source_file_path.as_path()
};
if fs::rename(source_file_path_to_rename, &validated_destination_file_path).is_ok() {
let target_file_path_metadata = fs::metadata(&validated_destination_file_path)
.map_err(|error| FileError::OtherIoError { error })?;
match destination_file_exists {
true => Ok(FileMoveFinished::Overwritten {
bytes_copied: target_file_path_metadata.len(),
method: FileMoveMethod::Rename,
}),
false => Ok(FileMoveFinished::Created {
bytes_copied: target_file_path_metadata.len(),
method: FileMoveMethod::Rename,
}),
}
} else {
let num_bytes_copied =
fs::copy(&validated_source_file_path, validated_destination_file_path)
.map_err(|error| FileError::OtherIoError { error })?;
let source_file_path_to_remove = if source_file_was_symlink_to_file {
source_file_path
} else {
validated_source_file_path.as_path()
};
super::remove_file(source_file_path_to_remove).map_err(|error| match error {
FileRemoveError::NotFound { path } => FileError::SourceFileNotFound { path },
FileRemoveError::NotAFile { path } => FileError::SourcePathNotAFile { path },
FileRemoveError::UnableToAccessFile { path, error } => {
FileError::UnableToAccessSourceFile { path, error }
}
FileRemoveError::OtherIoError { error } => FileError::OtherIoError { error },
})?;
match destination_file_exists {
true => Ok(FileMoveFinished::Overwritten {
bytes_copied: num_bytes_copied,
method: FileMoveMethod::CopyAndDelete,
}),
false => Ok(FileMoveFinished::Created {
bytes_copied: num_bytes_copied,
method: FileMoveMethod::CopyAndDelete,
}),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct FileMoveWithProgressOptions {
pub colliding_file_behaviour: CollidingFileBehaviour,
pub read_buffer_size: usize,
pub write_buffer_size: usize,
pub progress_update_byte_interval: u64,
}
impl Default for FileMoveWithProgressOptions {
fn default() -> Self {
Self {
colliding_file_behaviour: CollidingFileBehaviour::Abort,
read_buffer_size: DEFAULT_READ_BUFFER_SIZE,
write_buffer_size: DEFAULT_WRITE_BUFFER_SIZE,
progress_update_byte_interval: DEFAULT_PROGRESS_UPDATE_BYTE_INTERVAL,
}
}
}
pub fn move_file_with_progress<S, D, P>(
source_file_path: S,
destination_file_path: D,
options: FileMoveWithProgressOptions,
mut progress_handler: P,
) -> Result<FileMoveFinished, FileError>
where
S: AsRef<Path>,
D: AsRef<Path>,
P: FnMut(&FileProgress),
{
let source_file_path = source_file_path.as_ref();
let destination_file_path = destination_file_path.as_ref();
let validated_source_path = validate_source_file_path(source_file_path)?;
let (validated_destination_file_path, destination_file_exists) =
match validate_destination_file_path(
&validated_source_path,
destination_file_path,
options.colliding_file_behaviour,
)? {
DestinationValidationAction::SkipCopyOrMove => {
return Ok(FileMoveFinished::Skipped);
}
DestinationValidationAction::Continue(info) => {
(info.destination_file_path, info.exists)
}
};
let ValidatedSourceFilePath {
source_file_path: validated_source_file_path,
original_was_symlink_to_file: source_file_was_symlink_to_file,
} = validated_source_path;
let source_file_path_to_rename = if source_file_was_symlink_to_file {
source_file_path
} else {
validated_source_file_path.as_path()
};
if fs::rename(source_file_path_to_rename, &validated_destination_file_path).is_ok() {
let target_file_path_size_bytes = fs::metadata(&validated_destination_file_path)
.map_err(|error| FileError::OtherIoError { error })?
.len();
progress_handler(&FileProgress {
bytes_finished: target_file_path_size_bytes,
bytes_total: target_file_path_size_bytes,
});
match destination_file_exists {
true => Ok(FileMoveFinished::Overwritten {
bytes_copied: target_file_path_size_bytes,
method: FileMoveMethod::Rename,
}),
false => Ok(FileMoveFinished::Created {
bytes_copied: target_file_path_size_bytes,
method: FileMoveMethod::Rename,
}),
}
} else {
let bytes_written = copy_file_with_progress_unchecked(
&validated_source_file_path,
&validated_destination_file_path,
FileCopyWithProgressOptions {
colliding_file_behaviour: options.colliding_file_behaviour,
read_buffer_size: options.read_buffer_size,
write_buffer_size: options.write_buffer_size,
progress_update_byte_interval: options.progress_update_byte_interval,
},
progress_handler,
)?;
let source_file_path_to_remove = if source_file_was_symlink_to_file {
source_file_path
} else {
validated_source_file_path.as_path()
};
super::remove_file(source_file_path_to_remove).map_err(|error| match error {
FileRemoveError::NotFound { path } => FileError::SourceFileNotFound { path },
FileRemoveError::NotAFile { path } => FileError::SourcePathNotAFile { path },
FileRemoveError::UnableToAccessFile { path, error } => {
FileError::UnableToAccessSourceFile { path, error }
}
FileRemoveError::OtherIoError { error } => FileError::OtherIoError { error },
})?;
match destination_file_exists {
true => Ok(FileMoveFinished::Overwritten {
bytes_copied: bytes_written,
method: FileMoveMethod::CopyAndDelete,
}),
false => Ok(FileMoveFinished::Created {
bytes_copied: bytes_written,
method: FileMoveMethod::CopyAndDelete,
}),
}
}
}