blob: 118643ab884ce254f6b7513e7337b71396555ede [file] [log] [blame]
macro_rules! err {
($text:expr, $kind:expr) => {
return Err(Error::new($kind, $text))
};
($text:expr) => {
err!($text, ErrorKind::Other)
};
}
/// The error type for fs_extra operations on files and directories.
pub mod error;
/// This module includes additional methods for working with files.
///
/// One of the distinguishing features is receipt information
/// about process work with files.
///
/// # Example
/// ```rust,ignore
/// use std::path::Path;
/// use std::{thread, time};
/// use std::sync::mpsc::{self, TryRecvError};
///
/// extern crate fs_extra;
/// use fs_extra::file::*;
/// use fs_extra::error::*;
///
/// fn example_copy() -> Result<()> {
/// let path_from = Path::new("./temp");
/// let path_to = path_from.join("out");
/// let test_file = (path_from.join("test_file.txt"), path_to.join("test_file.txt"));
///
///
/// fs_extra::dir::create_all(&path_from, true)?;
/// fs_extra::dir::create_all(&path_to, true)?;
///
/// write_all(&test_file.0, "test_data")?;
/// assert!(test_file.0.exists());
/// assert!(!test_file.1.exists());
///
///
/// let options = CopyOptions {
/// buffer_size: 1,
/// ..Default::default()
/// }
/// let (tx, rx) = mpsc::channel();
/// thread::spawn(move || {
/// let handler = |process_info: TransitProcess| {
/// tx.send(process_info).unwrap();
/// thread::sleep(time::Duration::from_millis(500));
/// };
/// copy_with_progress(&test_file.0, &test_file.1, &options, handler).unwrap();
/// assert!(test_file.0.exists());
/// assert!(test_file.1.exists());
///
/// });
/// loop {
/// match rx.try_recv() {
/// Ok(process_info) => {
/// println!("{} of {} bytes",
/// process_info.copied_bytes,
/// process_info.total_bytes);
/// }
/// Err(TryRecvError::Disconnected) => {
/// println!("finished");
/// break;
/// }
/// Err(TryRecvError::Empty) => {}
/// }
/// }
/// Ok(())
///
/// }
///
///
/// fn main() {
/// example_copy();
/// }
///
/// ```
pub mod file;
/// This module includes additional methods for working with directories.
///
/// One of the additional features is information
/// about process and recursion operations.
///
/// # Example
/// ```rust,ignore
/// use std::path::Path;
/// use std::{thread, time};
/// use std::sync::mpsc::{self, TryRecvError};
///
/// extern crate fs_extra;
/// use fs_extra::dir::*;
/// use fs_extra::error::*;
///
/// fn example_copy() -> Result<()> {
///
/// let path_from = Path::new("./temp");
/// let path_to = path_from.join("out");
/// let test_folder = path_from.join("test_folder");
/// let dir = test_folder.join("dir");
/// let sub = dir.join("sub");
/// let file1 = dir.join("file1.txt");
/// let file2 = sub.join("file2.txt");
///
/// create_all(&sub, true)?;
/// create_all(&path_to, true)?;
/// fs_extra::file::write_all(&file1, "content1")?;
/// fs_extra::file::write_all(&file2, "content2")?;
///
/// assert!(dir.exists());
/// assert!(sub.exists());
/// assert!(file1.exists());
/// assert!(file2.exists());
///
///
/// let options = CopyOptions {
/// buffer_size: 1,
/// ..Default::default(),
/// };
/// let (tx, rx) = mpsc::channel();
/// thread::spawn(move || {
/// let handler = |process_info: TransitProcess| {
/// tx.send(process_info).unwrap();
/// thread::sleep(time::Duration::from_millis(500));
/// };
/// copy_with_progress(&test_folder, &path_to, &options, handler).unwrap();
/// });
///
/// loop {
/// match rx.try_recv() {
/// Ok(process_info) => {
/// println!("{} of {} bytes",
/// process_info.copied_bytes,
/// process_info.total_bytes);
/// }
/// Err(TryRecvError::Disconnected) => {
/// println!("finished");
/// break;
/// }
/// Err(TryRecvError::Empty) => {}
/// }
/// }
/// Ok(())
///
/// }
/// fn main() {
/// example_copy();
/// }
/// ```
///
pub mod dir;
use crate::error::*;
use std::path::Path;
/// Copies a list of directories and files to another place recursively. This function will
/// also copy the permission bits of the original files to destination files (not for
/// directories).
///
/// # Errors
///
/// This function will return an error in the following situations, but is not limited to just
/// these case:
///
/// * List `from` contains file or directory does not exist.
///
/// * List `from` contains file or directory with invalid name.
///
/// * The current process does not have the permission to access to file from `lists from` or
/// `to`.
///
/// # Example
///
/// ```rust,ignore
/// extern crate fs_extra;
/// use fs_extra::dir::copy;
///
/// let options = dir::CopyOptions::new(); //Initialize default values for CopyOptions
///
/// // copy dir1 and file1.txt to target/dir1 and target/file1.txt
/// let mut from_paths = Vec::new();
/// from_paths.push("source/dir1");
/// from_paths.push("source/file.txt");
/// copy_items(&from_paths, "target", &options)?;
/// ```
///
pub fn copy_items<P, Q>(from: &[P], to: Q, options: &dir::CopyOptions) -> Result<u64>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
let mut result: u64 = 0;
if options.content_only {
err!(
"Options 'content_only' not acccess for copy_items function",
ErrorKind::Other
);
}
for item in from {
let item = item.as_ref();
if item.is_dir() {
result += dir::copy(item, &to, options)?;
} else if let Some(file_name) = item.file_name() {
if let Some(file_name) = file_name.to_str() {
let file_options = file::CopyOptions {
overwrite: options.overwrite,
skip_exist: options.skip_exist,
..Default::default()
};
result += file::copy(item, to.as_ref().join(file_name), &file_options)?;
}
} else {
err!("Invalid file name", ErrorKind::InvalidFileName);
}
}
Ok(result)
}
/// A structure which includes information about the current status of copying or moving a directory.
pub struct TransitProcess {
/// Already copied bytes
pub copied_bytes: u64,
/// All the bytes which should be copied or moved (dir size).
pub total_bytes: u64,
/// Copied bytes on this time for file.
pub file_bytes_copied: u64,
/// Size of currently copied file.
pub file_total_bytes: u64,
/// Name of currently copied file.
pub file_name: String,
/// Name of currently copied folder.
pub dir_name: String,
/// Transit state
pub state: dir::TransitState,
}
impl Clone for TransitProcess {
fn clone(&self) -> TransitProcess {
TransitProcess {
copied_bytes: self.copied_bytes,
total_bytes: self.total_bytes,
file_bytes_copied: self.file_bytes_copied,
file_total_bytes: self.file_total_bytes,
file_name: self.file_name.clone(),
dir_name: self.dir_name.clone(),
state: self.state.clone(),
}
}
}
/// Copies a list of directories and files to another place recursively, with
/// information about progress. This function will also copy the permission bits of the
/// original files to destination files (not for directories).
///
/// # Errors
///
/// This function will return an error in the following situations, but is not limited to just
/// these case:
///
/// * List `from` contains file or directory does not exist.
///
/// * List `from` contains file or directory with invalid name.
///
/// * The current process does not have the permission to access to file from `lists from` or
/// `to`.
///
/// # Example
/// ```rust,ignore
///
/// extern crate fs_extra;
/// use fs_extra::dir::copy;
///
/// let options = dir::CopyOptions::new(); //Initialize default values for CopyOptions
/// let handle = |process_info: TransitProcess| {
/// println!("{}", process_info.total_bytes);
/// fs_extra::dir::TransitProcessResult::ContinueOrAbort
/// }
/// // copy dir1 and file1.txt to target/dir1 and target/file1.txt
/// let mut from_paths = Vec::new();
/// from_paths.push("source/dir1");
/// from_paths.push("source/file.txt");
/// copy_items_with_progress(&from_paths, "target", &options, handle)?;
/// ```
///
pub fn copy_items_with_progress<P, Q, F>(
from: &[P],
to: Q,
options: &dir::CopyOptions,
mut progress_handler: F,
) -> Result<u64>
where
P: AsRef<Path>,
Q: AsRef<Path>,
F: FnMut(TransitProcess) -> dir::TransitProcessResult,
{
if options.content_only {
err!(
"Options 'content_only' not access for copy_items_with_progress function",
ErrorKind::Other
);
}
let mut total_size = 0;
let mut list_paths = Vec::new();
for item in from {
let item = item.as_ref();
total_size += dir::get_size(item)?;
list_paths.push(item);
}
let mut result: u64 = 0;
let mut info_process = TransitProcess {
copied_bytes: 0,
total_bytes: total_size,
file_bytes_copied: 0,
file_total_bytes: 0,
file_name: String::new(),
dir_name: String::new(),
state: dir::TransitState::Normal,
};
let mut options = options.clone();
for item in list_paths {
if item.is_dir() {
if let Some(dir_name) = item.components().last() {
if let Ok(dir_name) = dir_name.as_os_str().to_os_string().into_string() {
info_process.dir_name = dir_name;
} else {
err!("Invalid folder from", ErrorKind::InvalidFolder);
}
} else {
err!("Invalid folder from", ErrorKind::InvalidFolder);
}
let copied_bytes = result;
let dir_options = options.clone();
let handler = |info: dir::TransitProcess| {
info_process.copied_bytes = copied_bytes + info.copied_bytes;
info_process.state = info.state;
let result = progress_handler(info_process.clone());
match result {
dir::TransitProcessResult::OverwriteAll => options.overwrite = true,
dir::TransitProcessResult::SkipAll => options.skip_exist = true,
_ => {}
}
result
};
result += dir::copy_with_progress(item, &to, &dir_options, handler)?;
} else {
let mut file_options = file::CopyOptions {
overwrite: options.overwrite,
skip_exist: options.skip_exist,
buffer_size: options.buffer_size,
};
if let Some(file_name) = item.file_name() {
if let Some(file_name) = file_name.to_str() {
info_process.file_name = file_name.to_string();
} else {
err!("Invalid file name", ErrorKind::InvalidFileName);
}
} else {
err!("Invalid file name", ErrorKind::InvalidFileName);
}
info_process.file_bytes_copied = 0;
info_process.file_total_bytes = item.metadata()?.len();
let copied_bytes = result;
let file_name = to.as_ref().join(info_process.file_name.clone());
let mut work = true;
let mut result_copy: Result<u64>;
while work {
{
let handler = |info: file::TransitProcess| {
info_process.copied_bytes = copied_bytes + info.copied_bytes;
info_process.file_bytes_copied = info.copied_bytes;
progress_handler(info_process.clone());
};
result_copy =
file::copy_with_progress(item, &file_name, &file_options, handler);
}
match result_copy {
Ok(val) => {
result += val;
work = false;
}
Err(err) => match err.kind {
ErrorKind::AlreadyExists => {
let mut info_process = info_process.clone();
info_process.state = dir::TransitState::Exists;
let user_decide = progress_handler(info_process);
match user_decide {
dir::TransitProcessResult::Overwrite => {
file_options.overwrite = true;
}
dir::TransitProcessResult::OverwriteAll => {
file_options.overwrite = true;
options.overwrite = true;
}
dir::TransitProcessResult::Skip => {
file_options.skip_exist = true;
}
dir::TransitProcessResult::SkipAll => {
file_options.skip_exist = true;
options.skip_exist = true;
}
dir::TransitProcessResult::Retry => {}
dir::TransitProcessResult::ContinueOrAbort => {
let err_msg = err.to_string();
err!(err_msg.as_str(), err.kind)
}
dir::TransitProcessResult::Abort => {
let err_msg = err.to_string();
err!(err_msg.as_str(), err.kind)
}
}
}
ErrorKind::PermissionDenied => {
let mut info_process = info_process.clone();
info_process.state = dir::TransitState::Exists;
let user_decide = progress_handler(info_process);
match user_decide {
dir::TransitProcessResult::Overwrite => {
err!("Overwrite denied for this situation!", ErrorKind::Other);
}
dir::TransitProcessResult::OverwriteAll => {
err!("Overwrite denied for this situation!", ErrorKind::Other);
}
dir::TransitProcessResult::Skip => {
file_options.skip_exist = true;
}
dir::TransitProcessResult::SkipAll => {
file_options.skip_exist = true;
options.skip_exist = true;
}
dir::TransitProcessResult::Retry => {}
dir::TransitProcessResult::ContinueOrAbort => {
let err_msg = err.to_string();
err!(err_msg.as_str(), err.kind)
}
dir::TransitProcessResult::Abort => {
let err_msg = err.to_string();
err!(err_msg.as_str(), err.kind)
}
}
}
_ => {
let err_msg = err.to_string();
err!(err_msg.as_str(), err.kind)
}
},
}
}
}
}
Ok(result)
}
/// Moves a list of directories and files to another place recursively. This function will
/// also copy the permission bits of the original files to destination files (not for
/// directories).
///
/// # Errors
///
/// This function will return an error in the following situations, but is not limited to just
/// these case:
///
/// * List `from` contains file or directory does not exist.
///
/// * List `from` contains file or directory with invalid name.
///
/// * The current process does not have the permission to access to file from `lists from` or
/// `to`.
///
/// # Example
///
/// ```rust,ignore
/// extern crate fs_extra;
/// use fs_extra::dir::copy;
///
/// let options = dir::CopyOptions::new(); //Initialize default values for CopyOptions
///
/// // move dir1 and file1.txt to target/dir1 and target/file1.txt
/// let mut from_paths = Vec::new();
/// from_paths.push("source/dir1");
/// from_paths.push("source/file.txt");
/// move_items(&from_paths, "target", &options)?;
/// ```
///
pub fn move_items<P, Q>(from_items: &[P], to: Q, options: &dir::CopyOptions) -> Result<u64>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
if options.content_only {
err!(
"Options 'content_only' not access for move_items function",
ErrorKind::Other
);
}
let mut total_size = 0;
let mut list_paths = Vec::new();
for item in from_items {
let item = item.as_ref();
total_size += dir::get_size(item)?;
list_paths.push(item);
}
let mut result = 0;
let mut info_process = TransitProcess {
copied_bytes: 0,
total_bytes: total_size,
file_bytes_copied: 0,
file_total_bytes: 0,
file_name: String::new(),
dir_name: String::new(),
state: dir::TransitState::Normal,
};
for item in list_paths {
if item.is_dir() {
if let Some(dir_name) = item.components().last() {
if let Ok(dir_name) = dir_name.as_os_str().to_os_string().into_string() {
info_process.dir_name = dir_name;
} else {
err!("Invalid folder from", ErrorKind::InvalidFolder);
}
} else {
err!("Invalid folder from", ErrorKind::InvalidFolder);
}
result += dir::move_dir(item, &to, options)?;
} else {
let file_options = file::CopyOptions {
overwrite: options.overwrite,
skip_exist: options.skip_exist,
buffer_size: options.buffer_size,
};
if let Some(file_name) = item.file_name() {
if let Some(file_name) = file_name.to_str() {
info_process.file_name = file_name.to_string();
} else {
err!("Invalid file name", ErrorKind::InvalidFileName);
}
} else {
err!("Invalid file name", ErrorKind::InvalidFileName);
}
info_process.file_bytes_copied = 0;
info_process.file_total_bytes = item.metadata()?.len();
let file_name = to.as_ref().join(info_process.file_name.clone());
result += file::move_file(item, &file_name, &file_options)?;
}
}
Ok(result)
}
/// Moves a list of directories and files to another place recursively, with
/// information about progress. This function will also copy the permission bits of the
/// original files to destination files (not for directories).
///
/// # Errors
///
/// This function will return an error in the following situations, but is not limited to just
/// these case:
///
/// * List `from` contains file or directory does not exist.
///
/// * List `from` contains file or directory with invalid name.
///
/// * The current process does not have the permission to access to file from `lists from` or
/// `to`.
///
/// # Example
///
/// ```rust,ignore
/// extern crate fs_extra;
/// use fs_extra::dir::copy;
///
/// let options = dir::CopyOptions::new(); //Initialize default values for CopyOptions
/// let handle = |process_info: TransitProcess| {
/// println!("{}", process_info.total_bytes);
/// fs_extra::dir::TransitProcessResult::ContinueOrAbort
/// }
/// // move dir1 and file1.txt to target/dir1 and target/file1.txt
/// let mut from_paths = Vec::new();
/// from_paths.push("source/dir1");
/// from_paths.push("source/file.txt");
/// move_items_with_progress(&from_paths, "target", &options, handle)?;
/// ```
///
pub fn move_items_with_progress<P, Q, F>(
from_items: &[P],
to: Q,
options: &dir::CopyOptions,
mut progress_handler: F,
) -> Result<u64>
where
P: AsRef<Path>,
Q: AsRef<Path>,
F: FnMut(TransitProcess) -> dir::TransitProcessResult,
{
if options.content_only {
err!(
"Options 'content_only' not access for move_items_with_progress function",
ErrorKind::Other
);
}
let mut total_size = 0;
let mut list_paths = Vec::new();
for item in from_items {
let item = item.as_ref();
total_size += dir::get_size(item)?;
list_paths.push(item);
}
let mut result = 0;
let mut info_process = TransitProcess {
copied_bytes: 0,
total_bytes: total_size,
file_bytes_copied: 0,
file_total_bytes: 0,
file_name: String::new(),
dir_name: String::new(),
state: dir::TransitState::Normal,
};
let mut options = options.clone();
for item in list_paths {
if item.is_dir() {
if let Some(dir_name) = item.components().last() {
if let Ok(dir_name) = dir_name.as_os_str().to_os_string().into_string() {
info_process.dir_name = dir_name;
} else {
err!("Invalid folder from", ErrorKind::InvalidFolder);
}
} else {
err!("Invalid folder from", ErrorKind::InvalidFolder);
}
let copied_bytes = result;
let dir_options = options.clone();
let handler = |info: dir::TransitProcess| {
info_process.copied_bytes = copied_bytes + info.copied_bytes;
info_process.state = info.state;
let result = progress_handler(info_process.clone());
match result {
dir::TransitProcessResult::OverwriteAll => options.overwrite = true,
dir::TransitProcessResult::SkipAll => options.skip_exist = true,
_ => {}
}
result
};
result += dir::move_dir_with_progress(item, &to, &dir_options, handler)?;
} else {
let mut file_options = file::CopyOptions {
overwrite: options.overwrite,
skip_exist: options.skip_exist,
buffer_size: options.buffer_size,
};
if let Some(file_name) = item.file_name() {
if let Some(file_name) = file_name.to_str() {
info_process.file_name = file_name.to_string();
} else {
err!("Invalid file name", ErrorKind::InvalidFileName);
}
} else {
err!("Invalid file name", ErrorKind::InvalidFileName);
}
info_process.file_bytes_copied = 0;
info_process.file_total_bytes = item.metadata()?.len();
let copied_bytes = result;
let file_name = to.as_ref().join(info_process.file_name.clone());
let mut work = true;
let mut result_copy: Result<u64>;
while work {
{
let handler = |info: file::TransitProcess| {
info_process.copied_bytes = copied_bytes + info.copied_bytes;
info_process.file_bytes_copied = info.copied_bytes;
progress_handler(info_process.clone());
};
result_copy =
file::move_file_with_progress(item, &file_name, &file_options, handler);
}
match result_copy {
Ok(val) => {
result += val;
work = false;
}
Err(err) => match err.kind {
ErrorKind::AlreadyExists => {
let mut info_process = info_process.clone();
info_process.state = dir::TransitState::Exists;
let user_decide = progress_handler(info_process);
match user_decide {
dir::TransitProcessResult::Overwrite => {
file_options.overwrite = true;
}
dir::TransitProcessResult::OverwriteAll => {
file_options.overwrite = true;
options.overwrite = true;
}
dir::TransitProcessResult::Skip => {
file_options.skip_exist = true;
}
dir::TransitProcessResult::SkipAll => {
file_options.skip_exist = true;
options.skip_exist = true;
}
dir::TransitProcessResult::Retry => {}
dir::TransitProcessResult::ContinueOrAbort => {
let err_msg = err.to_string();
err!(err_msg.as_str(), err.kind)
}
dir::TransitProcessResult::Abort => {
let err_msg = err.to_string();
err!(err_msg.as_str(), err.kind)
}
}
}
ErrorKind::PermissionDenied => {
let mut info_process = info_process.clone();
info_process.state = dir::TransitState::Exists;
let user_decide = progress_handler(info_process);
match user_decide {
dir::TransitProcessResult::Overwrite => {
err!("Overwrite denied for this situation!", ErrorKind::Other);
}
dir::TransitProcessResult::OverwriteAll => {
err!("Overwrite denied for this situation!", ErrorKind::Other);
}
dir::TransitProcessResult::Skip => {
file_options.skip_exist = true;
}
dir::TransitProcessResult::SkipAll => {
file_options.skip_exist = true;
options.skip_exist = true;
}
dir::TransitProcessResult::Retry => {}
dir::TransitProcessResult::ContinueOrAbort => {
let err_msg = err.to_string();
err!(err_msg.as_str(), err.kind)
}
dir::TransitProcessResult::Abort => {
let err_msg = err.to_string();
err!(err_msg.as_str(), err.kind)
}
}
}
_ => {
let err_msg = err.to_string();
err!(err_msg.as_str(), err.kind)
}
},
}
}
}
}
Ok(result)
}
/// Removes a list of files or directories.
///
/// # Example
///
/// ```rust,ignore
/// let mut from_paths = Vec::new();
/// from_paths.push("source/dir1");
/// from_paths.push("source/file.txt");
///
/// remove_items(&from_paths).unwrap();
/// ```
///
pub fn remove_items<P>(from_items: &[P]) -> Result<()>
where
P: AsRef<Path>,
{
for item in from_items {
let item = item.as_ref();
if item.is_dir() {
dir::remove(item)?;
} else {
file::remove(item)?
}
}
Ok(())
}