1399 lines
45 KiB
Rust
1399 lines
45 KiB
Rust
|
|
use crate::error::*;
|
||
|
|
use std::collections::{HashMap, HashSet};
|
||
|
|
use std::fs::{create_dir, create_dir_all, read_dir, remove_dir_all, Metadata};
|
||
|
|
use std::path::{Path, PathBuf};
|
||
|
|
use std::time::SystemTime;
|
||
|
|
|
||
|
|
/// Options and flags which can be used to configure how a file will be copied or moved.
|
||
|
|
#[derive(Clone)]
|
||
|
|
pub struct CopyOptions {
|
||
|
|
/// Overwrite existing files if true (default: false).
|
||
|
|
pub overwrite: bool,
|
||
|
|
/// Skip existing files if true (default: false).
|
||
|
|
pub skip_exist: bool,
|
||
|
|
/// Buffer size that specifies the amount of bytes to be moved or copied before the progress handler is called. This only affects functions with progress handlers. (default: 64000)
|
||
|
|
pub buffer_size: usize,
|
||
|
|
/// Recursively copy a directory with a new name or place it inside the destination (default: false, same behaviors as cp -r on Unix)
|
||
|
|
pub copy_inside: bool,
|
||
|
|
/// Copy only contents without a creating a new folder in the destination folder (default: false).
|
||
|
|
pub content_only: bool,
|
||
|
|
/// Sets levels reading. Set 0 for read all directory folder (default: 0).
|
||
|
|
///
|
||
|
|
/// Warning: Work only for copy operations!
|
||
|
|
pub depth: u64,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl CopyOptions {
|
||
|
|
/// Initialize struct CopyOptions with default value.
|
||
|
|
///
|
||
|
|
/// ```rust,ignore
|
||
|
|
/// overwrite: false
|
||
|
|
///
|
||
|
|
/// skip_exist: false
|
||
|
|
///
|
||
|
|
/// buffer_size: 64000 // 64kb
|
||
|
|
///
|
||
|
|
/// copy_inside: false
|
||
|
|
/// ```
|
||
|
|
pub fn new() -> CopyOptions {
|
||
|
|
CopyOptions {
|
||
|
|
overwrite: false,
|
||
|
|
skip_exist: false,
|
||
|
|
buffer_size: 64000, // 64kb
|
||
|
|
copy_inside: false,
|
||
|
|
content_only: false,
|
||
|
|
depth: 0,
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Overwrite existing files if true.
|
||
|
|
pub fn overwrite(mut self, overwrite: bool) -> Self {
|
||
|
|
self.overwrite = overwrite;
|
||
|
|
self
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Skip existing files if true.
|
||
|
|
pub fn skip_exist(mut self, skip_exist: bool) -> Self {
|
||
|
|
self.skip_exist = skip_exist;
|
||
|
|
self
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Buffer size that specifies the amount of bytes to be moved or copied before the progress handler is called. This only affects functions with progress handlers.
|
||
|
|
pub fn buffer_size(mut self, buffer_size: usize) -> Self {
|
||
|
|
self.buffer_size = buffer_size;
|
||
|
|
self
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Recursively copy a directory with a new name or place it inside the destination (default: false, same behaviors as cp -r on Unix)
|
||
|
|
pub fn copy_inside(mut self, copy_inside: bool) -> Self {
|
||
|
|
self.copy_inside = copy_inside;
|
||
|
|
self
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Copy only contents without a creating a new folder in the destination folder.
|
||
|
|
pub fn content_only(mut self, content_only: bool) -> Self {
|
||
|
|
self.content_only = content_only;
|
||
|
|
self
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Sets levels reading. Set 0 for read all directory folder
|
||
|
|
pub fn depth(mut self, depth: u64) -> Self {
|
||
|
|
self.depth = depth;
|
||
|
|
self
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Default for CopyOptions {
|
||
|
|
fn default() -> Self {
|
||
|
|
CopyOptions::new()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Options and flags which can be used to configure how to read a directory.
|
||
|
|
#[derive(Clone, Default)]
|
||
|
|
pub struct DirOptions {
|
||
|
|
/// Sets levels reading. Set value 0 for read all directory folder. By default 0.
|
||
|
|
pub depth: u64,
|
||
|
|
}
|
||
|
|
|
||
|
|
impl DirOptions {
|
||
|
|
/// Initialize struct DirOptions with default value.
|
||
|
|
pub fn new() -> DirOptions {
|
||
|
|
Default::default()
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// A structure which include information about directory
|
||
|
|
pub struct DirContent {
|
||
|
|
/// Directory size in bytes.
|
||
|
|
pub dir_size: u64,
|
||
|
|
/// List all files directory and sub directories.
|
||
|
|
pub files: Vec<String>,
|
||
|
|
/// List all folders and sub folders directory.
|
||
|
|
pub directories: Vec<String>,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// A structure which include information about the current status of the copy or move directory.
|
||
|
|
pub struct TransitProcess {
|
||
|
|
/// Copied bytes on this time for folder
|
||
|
|
pub copied_bytes: u64,
|
||
|
|
/// All the bytes which should to copy or move (dir size).
|
||
|
|
pub total_bytes: u64,
|
||
|
|
/// Copied bytes on this time for file.
|
||
|
|
pub file_bytes_copied: u64,
|
||
|
|
/// Size current copied file.
|
||
|
|
pub file_total_bytes: u64,
|
||
|
|
/// Name current copied file.
|
||
|
|
pub file_name: String,
|
||
|
|
/// Transit state
|
||
|
|
pub state: TransitState,
|
||
|
|
}
|
||
|
|
|
||
|
|
///
|
||
|
|
#[derive(Hash, Eq, PartialEq, Clone)]
|
||
|
|
pub enum TransitState {
|
||
|
|
/// Standard state.
|
||
|
|
Normal,
|
||
|
|
/// Pause state when destination path exists.
|
||
|
|
Exists,
|
||
|
|
/// Pause state when current process does not have the permission to access from or to
|
||
|
|
/// path.
|
||
|
|
NoAccess,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Available returns codes for user decide
|
||
|
|
pub enum TransitProcessResult {
|
||
|
|
/// Rewrite exist file or directory.
|
||
|
|
Overwrite,
|
||
|
|
/// Rewrite for all exist files or directories.
|
||
|
|
OverwriteAll,
|
||
|
|
/// Skip current problem file or directory.
|
||
|
|
Skip,
|
||
|
|
/// Skip for all problems file or directory.
|
||
|
|
SkipAll,
|
||
|
|
/// Retry current operation.
|
||
|
|
Retry,
|
||
|
|
/// Abort current operation.
|
||
|
|
Abort,
|
||
|
|
/// Continue execute process if process not have error and abort if process content error.
|
||
|
|
ContinueOrAbort,
|
||
|
|
}
|
||
|
|
|
||
|
|
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(),
|
||
|
|
state: self.state.clone(),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Available attributes for get information about directory entry.
|
||
|
|
#[derive(Hash, Eq, PartialEq, Clone)]
|
||
|
|
pub enum DirEntryAttr {
|
||
|
|
/// Folder name or file name without extension.
|
||
|
|
Name,
|
||
|
|
/// File extension.
|
||
|
|
Ext,
|
||
|
|
/// Folder name or file name with extension.
|
||
|
|
FullName,
|
||
|
|
/// Path to file or directory.
|
||
|
|
Path,
|
||
|
|
/// Dos path to file or directory.
|
||
|
|
DosPath,
|
||
|
|
/// File size in bytes.
|
||
|
|
FileSize,
|
||
|
|
/// Size file or directory in bytes.
|
||
|
|
///
|
||
|
|
/// `Attention!`: This operation very expensive and sometimes required additional rights.
|
||
|
|
Size,
|
||
|
|
/// Return whether entry is directory or not.
|
||
|
|
IsDir,
|
||
|
|
/// Return whether entry is file or not.
|
||
|
|
IsFile,
|
||
|
|
/// Last modification time for directory entry.
|
||
|
|
Modified,
|
||
|
|
/// Last access time for directory entry.
|
||
|
|
Accessed,
|
||
|
|
/// Created time for directory entry.
|
||
|
|
///
|
||
|
|
/// `Attention!`: Not supported UNIX platform.
|
||
|
|
Created,
|
||
|
|
/// Return or not return base information target folder.
|
||
|
|
BaseInfo,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Available types for directory entry.
|
||
|
|
pub enum DirEntryValue {
|
||
|
|
/// String type
|
||
|
|
String(String),
|
||
|
|
/// Boolean type
|
||
|
|
Boolean(bool),
|
||
|
|
/// SystemTime type
|
||
|
|
SystemTime(SystemTime),
|
||
|
|
/// u64 type
|
||
|
|
U64(u64),
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Result returned by the `ls` function.
|
||
|
|
pub struct LsResult {
|
||
|
|
/// Base folder target path
|
||
|
|
pub base: HashMap<DirEntryAttr, DirEntryValue>,
|
||
|
|
/// Collection directory entry with information.
|
||
|
|
pub items: Vec<HashMap<DirEntryAttr, DirEntryValue>>,
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Returned information about directory entry with information which you choose in config.
|
||
|
|
///
|
||
|
|
/// This function takes to arguments:
|
||
|
|
///
|
||
|
|
/// * `path` - Path to directory.
|
||
|
|
///
|
||
|
|
/// * `config` - Set attributes which you want see inside return data.
|
||
|
|
///
|
||
|
|
/// # Errors
|
||
|
|
///
|
||
|
|
/// This function will return an error in the following situations, but is not limited to just
|
||
|
|
/// these cases:
|
||
|
|
///
|
||
|
|
/// * This `path` does not exist.
|
||
|
|
/// * Invalid `path`.
|
||
|
|
/// * The current process does not have the permission to access `path`.
|
||
|
|
///
|
||
|
|
/// #Examples
|
||
|
|
///
|
||
|
|
/// ```rust,ignore
|
||
|
|
/// extern crate fs_extra;
|
||
|
|
/// use fs_extra::dir::{get_details_entry, DirEntryAttr};
|
||
|
|
/// use std::collections::{HashMap, HashSet};
|
||
|
|
///
|
||
|
|
/// let mut config = HashSet::new();
|
||
|
|
/// config.insert(DirEntryAttr::Name);
|
||
|
|
/// config.insert(DirEntryAttr::Size);
|
||
|
|
///
|
||
|
|
/// let entry_info = get_details_entry("test", &config);
|
||
|
|
/// assert_eq!(2, entry_info.len());
|
||
|
|
/// ```
|
||
|
|
pub fn get_details_entry<P>(
|
||
|
|
path: P,
|
||
|
|
config: &HashSet<DirEntryAttr>,
|
||
|
|
) -> Result<HashMap<DirEntryAttr, DirEntryValue>>
|
||
|
|
where
|
||
|
|
P: AsRef<Path>,
|
||
|
|
{
|
||
|
|
let path = path.as_ref();
|
||
|
|
let metadata = path.metadata()?;
|
||
|
|
get_details_entry_with_meta(path, config, metadata)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn get_details_entry_with_meta<P>(
|
||
|
|
path: P,
|
||
|
|
config: &HashSet<DirEntryAttr>,
|
||
|
|
metadata: Metadata,
|
||
|
|
) -> Result<HashMap<DirEntryAttr, DirEntryValue>>
|
||
|
|
where
|
||
|
|
P: AsRef<Path>,
|
||
|
|
{
|
||
|
|
let path = path.as_ref();
|
||
|
|
let mut item = HashMap::new();
|
||
|
|
if config.contains(&DirEntryAttr::Name) {
|
||
|
|
if metadata.is_dir() {
|
||
|
|
if let Some(file_name) = path.file_name() {
|
||
|
|
item.insert(
|
||
|
|
DirEntryAttr::Name,
|
||
|
|
DirEntryValue::String(file_name.to_os_string().into_string()?),
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
item.insert(DirEntryAttr::Name, DirEntryValue::String(String::new()));
|
||
|
|
}
|
||
|
|
} else if let Some(file_stem) = path.file_stem() {
|
||
|
|
item.insert(
|
||
|
|
DirEntryAttr::Name,
|
||
|
|
DirEntryValue::String(file_stem.to_os_string().into_string()?),
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
item.insert(DirEntryAttr::Name, DirEntryValue::String(String::new()));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if config.contains(&DirEntryAttr::Ext) {
|
||
|
|
if let Some(value) = path.extension() {
|
||
|
|
item.insert(
|
||
|
|
DirEntryAttr::Ext,
|
||
|
|
DirEntryValue::String(value.to_os_string().into_string()?),
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
item.insert(DirEntryAttr::Ext, DirEntryValue::String(String::from("")));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if config.contains(&DirEntryAttr::FullName) {
|
||
|
|
if let Some(file_name) = path.file_name() {
|
||
|
|
item.insert(
|
||
|
|
DirEntryAttr::FullName,
|
||
|
|
DirEntryValue::String(file_name.to_os_string().into_string()?),
|
||
|
|
);
|
||
|
|
} else {
|
||
|
|
item.insert(DirEntryAttr::FullName, DirEntryValue::String(String::new()));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if config.contains(&DirEntryAttr::Path) {
|
||
|
|
let mut result_path: PathBuf;
|
||
|
|
match path.canonicalize() {
|
||
|
|
Ok(new_path) => {
|
||
|
|
result_path = new_path;
|
||
|
|
}
|
||
|
|
Err(_) => {
|
||
|
|
if let Some(parent_path) = path.parent() {
|
||
|
|
if let Some(name) = path.file_name() {
|
||
|
|
result_path = parent_path.canonicalize()?;
|
||
|
|
result_path.push(name);
|
||
|
|
} else {
|
||
|
|
err!("Error get part name path", ErrorKind::Other);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
err!("Error get parent path", ErrorKind::Other);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
let mut path = result_path.as_os_str().to_os_string().into_string()?;
|
||
|
|
if path.find("\\\\?\\") == Some(0) {
|
||
|
|
path = path[4..].to_string();
|
||
|
|
}
|
||
|
|
item.insert(DirEntryAttr::Path, DirEntryValue::String(path));
|
||
|
|
}
|
||
|
|
if config.contains(&DirEntryAttr::DosPath) {
|
||
|
|
let mut result_path: PathBuf;
|
||
|
|
match path.canonicalize() {
|
||
|
|
Ok(new_path) => {
|
||
|
|
result_path = new_path;
|
||
|
|
}
|
||
|
|
Err(_) => {
|
||
|
|
if let Some(parent_path) = path.parent() {
|
||
|
|
if let Some(name) = path.file_name() {
|
||
|
|
result_path = parent_path.canonicalize()?;
|
||
|
|
result_path.push(name);
|
||
|
|
} else {
|
||
|
|
err!("Error get part name path", ErrorKind::Other);
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
err!("Error get parent path", ErrorKind::Other);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
let path = result_path.as_os_str().to_os_string().into_string()?;
|
||
|
|
item.insert(DirEntryAttr::DosPath, DirEntryValue::String(path));
|
||
|
|
}
|
||
|
|
if config.contains(&DirEntryAttr::Size) {
|
||
|
|
item.insert(DirEntryAttr::Size, DirEntryValue::U64(get_size(&path)?));
|
||
|
|
}
|
||
|
|
if config.contains(&DirEntryAttr::FileSize) {
|
||
|
|
item.insert(DirEntryAttr::FileSize, DirEntryValue::U64(metadata.len()));
|
||
|
|
}
|
||
|
|
if config.contains(&DirEntryAttr::IsDir) {
|
||
|
|
item.insert(
|
||
|
|
DirEntryAttr::IsDir,
|
||
|
|
DirEntryValue::Boolean(metadata.is_dir()),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if config.contains(&DirEntryAttr::IsFile) {
|
||
|
|
item.insert(
|
||
|
|
DirEntryAttr::IsFile,
|
||
|
|
DirEntryValue::Boolean(metadata.is_file()),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if config.contains(&DirEntryAttr::Modified) {
|
||
|
|
item.insert(
|
||
|
|
DirEntryAttr::Modified,
|
||
|
|
DirEntryValue::SystemTime(metadata.modified()?),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if config.contains(&DirEntryAttr::Accessed) {
|
||
|
|
item.insert(
|
||
|
|
DirEntryAttr::Accessed,
|
||
|
|
DirEntryValue::SystemTime(metadata.accessed()?),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if config.contains(&DirEntryAttr::Created) {
|
||
|
|
item.insert(
|
||
|
|
DirEntryAttr::Created,
|
||
|
|
DirEntryValue::SystemTime(metadata.created()?),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
Ok(item)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Returns a collection of directory entries with attributes specifying the information that should be returned.
|
||
|
|
///
|
||
|
|
/// This function takes to arguments:
|
||
|
|
///
|
||
|
|
/// * `path` - Path to directory.
|
||
|
|
///
|
||
|
|
/// * `config` - Set attributes which you want see in return data.
|
||
|
|
///
|
||
|
|
/// # Errors
|
||
|
|
///
|
||
|
|
/// This function will return an error in the following situations, but is not limited to just
|
||
|
|
/// these cases:
|
||
|
|
///
|
||
|
|
/// * This `path` directory does not exist.
|
||
|
|
/// * Invalid `path`.
|
||
|
|
/// * The current process does not have the permission to access `path`.
|
||
|
|
///
|
||
|
|
/// #Examples
|
||
|
|
///
|
||
|
|
/// ```rust,ignore
|
||
|
|
/// extern crate fs_extra;
|
||
|
|
/// use fs_extra::dir::{ls, DirEntryAttr, LsResult};
|
||
|
|
/// use std::collections::HashSet;
|
||
|
|
///
|
||
|
|
/// let mut config = HashSet::new();
|
||
|
|
/// config.insert(DirEntryAttr::Name);
|
||
|
|
/// config.insert(DirEntryAttr::Size);
|
||
|
|
/// config.insert(DirEntryAttr::BaseInfo);
|
||
|
|
///
|
||
|
|
/// let result = ls("test", &config);
|
||
|
|
/// assert_eq!(2, ls_result.items.len());
|
||
|
|
/// assert_eq!(2, ls_result.base.len());
|
||
|
|
/// ```
|
||
|
|
pub fn ls<P>(path: P, config: &HashSet<DirEntryAttr>) -> Result<LsResult>
|
||
|
|
where
|
||
|
|
P: AsRef<Path>,
|
||
|
|
{
|
||
|
|
let mut items = Vec::new();
|
||
|
|
let path = path.as_ref();
|
||
|
|
if !path.is_dir() {
|
||
|
|
err!("Path does not directory", ErrorKind::InvalidFolder);
|
||
|
|
}
|
||
|
|
for entry in read_dir(&path)? {
|
||
|
|
let entry = entry?;
|
||
|
|
let path = entry.path();
|
||
|
|
let metadata = entry.metadata()?;
|
||
|
|
let item = get_details_entry_with_meta(path, &config, metadata)?;
|
||
|
|
items.push(item);
|
||
|
|
}
|
||
|
|
let mut base = HashMap::new();
|
||
|
|
if config.contains(&DirEntryAttr::BaseInfo) {
|
||
|
|
base = get_details_entry(&path, &config)?;
|
||
|
|
}
|
||
|
|
Ok(LsResult { items, base })
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Creates a new, empty directory at the provided path.
|
||
|
|
///
|
||
|
|
/// This function takes to arguments:
|
||
|
|
///
|
||
|
|
/// * `path` - Path to new directory.
|
||
|
|
///
|
||
|
|
/// * `erase` - If set true and folder exist, then folder will be erased.
|
||
|
|
///
|
||
|
|
/// #Errors
|
||
|
|
///
|
||
|
|
/// This function will return an error in the following situations,
|
||
|
|
/// but is not limited to just these cases:
|
||
|
|
///
|
||
|
|
/// * User lacks permissions to create directory at `path`.
|
||
|
|
///
|
||
|
|
/// * `path` already exists if `erase` set false.
|
||
|
|
///
|
||
|
|
/// #Examples
|
||
|
|
///
|
||
|
|
/// ```rust,ignore
|
||
|
|
/// extern crate fs_extra;
|
||
|
|
/// use fs_extra::dir::create;
|
||
|
|
///
|
||
|
|
/// create("dir", false); // create directory
|
||
|
|
/// ```
|
||
|
|
pub fn create<P>(path: P, erase: bool) -> Result<()>
|
||
|
|
where
|
||
|
|
P: AsRef<Path>,
|
||
|
|
{
|
||
|
|
if erase && path.as_ref().exists() {
|
||
|
|
remove(&path)?;
|
||
|
|
}
|
||
|
|
Ok(create_dir(&path)?)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Recursively create a directory and all of its parent components if they are missing.
|
||
|
|
///
|
||
|
|
/// This function takes to arguments:
|
||
|
|
///
|
||
|
|
/// * `path` - Path to new directory.
|
||
|
|
///
|
||
|
|
/// * `erase` - If set true and folder exist, then folder will be erased.
|
||
|
|
///
|
||
|
|
///#Errors
|
||
|
|
///
|
||
|
|
/// This function will return an error in the following situations,
|
||
|
|
/// but is not limited to just these cases:
|
||
|
|
///
|
||
|
|
/// * User lacks permissions to create directory at `path`.
|
||
|
|
///
|
||
|
|
/// * `path` already exists if `erase` set false.
|
||
|
|
///
|
||
|
|
/// #Examples
|
||
|
|
///
|
||
|
|
/// ```rust,ignore
|
||
|
|
/// extern crate fs_extra;
|
||
|
|
/// use fs_extra::dir::create_all;
|
||
|
|
///
|
||
|
|
/// create_all("/some/dir", false); // create directory some and dir
|
||
|
|
pub fn create_all<P>(path: P, erase: bool) -> Result<()>
|
||
|
|
where
|
||
|
|
P: AsRef<Path>,
|
||
|
|
{
|
||
|
|
if erase && path.as_ref().exists() {
|
||
|
|
remove(&path)?;
|
||
|
|
}
|
||
|
|
Ok(create_dir_all(&path)?)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Copies the directory contents from one place to another using recursive method.
|
||
|
|
/// 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 cases:
|
||
|
|
///
|
||
|
|
/// * This `from` path is not a directory.
|
||
|
|
/// * This `from` directory does not exist.
|
||
|
|
/// * Invalid folder name for `from` or `to`.
|
||
|
|
/// * The current process does not have the permission to access `from` or write `to`.
|
||
|
|
///
|
||
|
|
/// # Example
|
||
|
|
/// ```rust,ignore
|
||
|
|
/// extern crate fs_extra;
|
||
|
|
/// use fs_extra::dir::copy;
|
||
|
|
///
|
||
|
|
/// let options = CopyOptions::new(); //Initialize default values for CopyOptions
|
||
|
|
/// // options.mirror_copy = true; // To mirror copy the whole structure of the source directory
|
||
|
|
///
|
||
|
|
///
|
||
|
|
/// // copy source/dir1 to target/dir1
|
||
|
|
/// copy("source/dir1", "target/dir1", &options)?;
|
||
|
|
///
|
||
|
|
/// ```
|
||
|
|
pub fn copy<P, Q>(from: P, to: Q, options: &CopyOptions) -> Result<u64>
|
||
|
|
where
|
||
|
|
P: AsRef<Path>,
|
||
|
|
Q: AsRef<Path>,
|
||
|
|
{
|
||
|
|
let from = from.as_ref();
|
||
|
|
|
||
|
|
if !from.exists() {
|
||
|
|
if let Some(msg) = from.to_str() {
|
||
|
|
let msg = format!("Path \"{}\" does not exist or you don't have access!", msg);
|
||
|
|
err!(&msg, ErrorKind::NotFound);
|
||
|
|
}
|
||
|
|
err!(
|
||
|
|
"Path does not exist Or you don't have access!",
|
||
|
|
ErrorKind::NotFound
|
||
|
|
);
|
||
|
|
}
|
||
|
|
if !from.is_dir() {
|
||
|
|
if let Some(msg) = from.to_str() {
|
||
|
|
let msg = format!("Path \"{}\" is not a directory!", msg);
|
||
|
|
err!(&msg, ErrorKind::InvalidFolder);
|
||
|
|
}
|
||
|
|
err!("Path is not a directory!", ErrorKind::InvalidFolder);
|
||
|
|
}
|
||
|
|
let dir_name;
|
||
|
|
if let Some(val) = from.components().last() {
|
||
|
|
dir_name = val.as_os_str();
|
||
|
|
} else {
|
||
|
|
err!("Invalid folder from", ErrorKind::InvalidFolder);
|
||
|
|
}
|
||
|
|
let mut to: PathBuf = to.as_ref().to_path_buf();
|
||
|
|
if (to.exists() || !options.copy_inside) && !options.content_only {
|
||
|
|
to.push(dir_name);
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut read_options = DirOptions::new();
|
||
|
|
if options.depth > 0 {
|
||
|
|
read_options.depth = options.depth;
|
||
|
|
}
|
||
|
|
|
||
|
|
let dir_content = get_dir_content2(from, &read_options)?;
|
||
|
|
for directory in dir_content.directories {
|
||
|
|
let tmp_to = Path::new(&directory).strip_prefix(from)?;
|
||
|
|
let dir = to.join(&tmp_to);
|
||
|
|
if !dir.exists() {
|
||
|
|
if options.copy_inside {
|
||
|
|
create_all(dir, false)?;
|
||
|
|
} else {
|
||
|
|
create(dir, false)?;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
let mut result: u64 = 0;
|
||
|
|
for file in dir_content.files {
|
||
|
|
let to = to.to_path_buf();
|
||
|
|
let tp = Path::new(&file).strip_prefix(from)?;
|
||
|
|
let path = to.join(&tp);
|
||
|
|
|
||
|
|
let file_options = super::file::CopyOptions {
|
||
|
|
overwrite: options.overwrite,
|
||
|
|
skip_exist: options.skip_exist,
|
||
|
|
buffer_size: options.buffer_size,
|
||
|
|
};
|
||
|
|
let mut result_copy: Result<u64>;
|
||
|
|
let mut work = true;
|
||
|
|
|
||
|
|
while work {
|
||
|
|
result_copy = super::file::copy(&file, &path, &file_options);
|
||
|
|
match result_copy {
|
||
|
|
Ok(val) => {
|
||
|
|
result += val;
|
||
|
|
work = false;
|
||
|
|
}
|
||
|
|
Err(err) => {
|
||
|
|
let err_msg = err.to_string();
|
||
|
|
err!(err_msg.as_str(), err.kind)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
Ok(result)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Return DirContent which contains information about directory:
|
||
|
|
///
|
||
|
|
/// * Size of the directory in bytes.
|
||
|
|
/// * List of source paths of files in the directory (files inside subdirectories included too).
|
||
|
|
/// * List of source paths of all directories and subdirectories.
|
||
|
|
///
|
||
|
|
/// # Errors
|
||
|
|
///
|
||
|
|
/// This function will return an error in the following situations, but is not limited to just
|
||
|
|
/// these cases:
|
||
|
|
///
|
||
|
|
/// * This `path` directory does not exist.
|
||
|
|
/// * Invalid `path`.
|
||
|
|
/// * The current process does not have the permission to access `path`.
|
||
|
|
///
|
||
|
|
/// # Examples
|
||
|
|
/// ```rust,ignore
|
||
|
|
/// extern crate fs_extra;
|
||
|
|
/// use fs_extra::dir::get_dir_content;
|
||
|
|
///
|
||
|
|
/// let dir_content = get_dir_content("dir")?;
|
||
|
|
/// for directory in dir_content.directories {
|
||
|
|
/// println!("{}", directory); // print directory path
|
||
|
|
/// }
|
||
|
|
/// ```
|
||
|
|
///
|
||
|
|
pub fn get_dir_content<P>(path: P) -> Result<DirContent>
|
||
|
|
where
|
||
|
|
P: AsRef<Path>,
|
||
|
|
{
|
||
|
|
let options = DirOptions::new();
|
||
|
|
get_dir_content2(path, &options)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Return DirContent which contains information about directory:
|
||
|
|
///
|
||
|
|
/// * Size directory.
|
||
|
|
/// * List all files source directory(files subdirectories included too).
|
||
|
|
/// * List all directory and subdirectories source path.
|
||
|
|
///
|
||
|
|
/// # Errors
|
||
|
|
///
|
||
|
|
/// This function will return an error in the following situations, but is not limited to just
|
||
|
|
/// these cases:
|
||
|
|
///
|
||
|
|
/// * This `path` directory does not exist.
|
||
|
|
/// * Invalid `path`.
|
||
|
|
/// * The current process does not have the permission to access `path`.
|
||
|
|
///
|
||
|
|
/// # Examples
|
||
|
|
/// ```rust,ignore
|
||
|
|
/// extern crate fs_extra;
|
||
|
|
/// use fs_extra::dir::{DirOptions, get_dir_content2};
|
||
|
|
///
|
||
|
|
/// let mut options = DirOptions::new();
|
||
|
|
/// options.depth = 3; // Get 3 levels of folder.
|
||
|
|
/// let dir_content = get_dir_content2("dir", &options)?;
|
||
|
|
/// for directory in dir_content.directories {
|
||
|
|
/// println!("{}", directory); // print directory path
|
||
|
|
/// }
|
||
|
|
/// ```
|
||
|
|
///
|
||
|
|
pub fn get_dir_content2<P>(path: P, options: &DirOptions) -> Result<DirContent>
|
||
|
|
where
|
||
|
|
P: AsRef<Path>,
|
||
|
|
{
|
||
|
|
let mut depth = 0;
|
||
|
|
if options.depth != 0 {
|
||
|
|
depth = options.depth + 1;
|
||
|
|
}
|
||
|
|
_get_dir_content(path, depth)
|
||
|
|
}
|
||
|
|
|
||
|
|
fn _get_dir_content<P>(path: P, mut depth: u64) -> Result<DirContent>
|
||
|
|
where
|
||
|
|
P: AsRef<Path>,
|
||
|
|
{
|
||
|
|
let mut directories = Vec::new();
|
||
|
|
let mut files = Vec::new();
|
||
|
|
let mut dir_size;
|
||
|
|
let item = path.as_ref().to_str();
|
||
|
|
if item.is_none() {
|
||
|
|
err!("Invalid path", ErrorKind::InvalidPath);
|
||
|
|
}
|
||
|
|
let item = item.unwrap().to_string();
|
||
|
|
|
||
|
|
if path.as_ref().is_dir() {
|
||
|
|
dir_size = path.as_ref().metadata()?.len();
|
||
|
|
directories.push(item);
|
||
|
|
if depth == 0 || depth > 1 {
|
||
|
|
if depth > 1 {
|
||
|
|
depth -= 1;
|
||
|
|
}
|
||
|
|
for entry in read_dir(&path)? {
|
||
|
|
let _path = entry?.path();
|
||
|
|
|
||
|
|
match _get_dir_content(_path, depth) {
|
||
|
|
Ok(items) => {
|
||
|
|
let mut _files = items.files;
|
||
|
|
let mut _directories = items.directories;
|
||
|
|
dir_size += items.dir_size;
|
||
|
|
files.append(&mut _files);
|
||
|
|
directories.append(&mut _directories);
|
||
|
|
}
|
||
|
|
Err(err) => return Err(err),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
dir_size = path.as_ref().metadata()?.len();
|
||
|
|
files.push(item);
|
||
|
|
}
|
||
|
|
Ok(DirContent {
|
||
|
|
dir_size,
|
||
|
|
files,
|
||
|
|
directories,
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Returns the size of the file or directory in bytes.(!important: folders size not count)
|
||
|
|
///
|
||
|
|
/// If used on a directory, this function will recursively iterate over every file and every
|
||
|
|
/// directory inside the directory. This can be very time consuming if used on large directories.
|
||
|
|
///
|
||
|
|
/// Does not follow symlinks.
|
||
|
|
///
|
||
|
|
/// # Errors
|
||
|
|
///
|
||
|
|
/// This function will return an error in the following situations, but is not limited to just
|
||
|
|
/// these cases:
|
||
|
|
///
|
||
|
|
/// * This `path` directory does not exist.
|
||
|
|
/// * Invalid `path`.
|
||
|
|
/// * The current process does not have the permission to access `path`.
|
||
|
|
///
|
||
|
|
/// # Examples
|
||
|
|
/// ```rust,ignore
|
||
|
|
/// extern crate fs_extra;
|
||
|
|
/// use fs_extra::dir::get_size;
|
||
|
|
///
|
||
|
|
/// let folder_size = get_size("dir")?;
|
||
|
|
/// println!("{}", folder_size); // print directory size in bytes
|
||
|
|
/// ```
|
||
|
|
pub fn get_size<P>(path: P) -> Result<u64>
|
||
|
|
where
|
||
|
|
P: AsRef<Path>,
|
||
|
|
{
|
||
|
|
// Using `fs::symlink_metadata` since we don't want to follow symlinks,
|
||
|
|
// as we're calculating the exact size of the requested path itself.
|
||
|
|
let path_metadata = path.as_ref().symlink_metadata()?;
|
||
|
|
|
||
|
|
let mut size_in_bytes = 0;
|
||
|
|
|
||
|
|
if path_metadata.is_dir() {
|
||
|
|
for entry in read_dir(&path)? {
|
||
|
|
let entry = entry?;
|
||
|
|
// `DirEntry::metadata` does not follow symlinks (unlike `fs::metadata`), so in the
|
||
|
|
// case of symlinks, this is the size of the symlink itself, not its target.
|
||
|
|
let entry_metadata = entry.metadata()?;
|
||
|
|
|
||
|
|
if entry_metadata.is_dir() {
|
||
|
|
// The size of the directory entry itself will be counted inside the `get_size()` call,
|
||
|
|
// so we intentionally don't also add `entry_metadata.len()` to the total here.
|
||
|
|
size_in_bytes += get_size(entry.path())?;
|
||
|
|
} else {
|
||
|
|
size_in_bytes += entry_metadata.len();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
size_in_bytes = path_metadata.len();
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(size_in_bytes)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Copies the directory contents from one place to another using recursive method,
|
||
|
|
/// 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 cases:
|
||
|
|
///
|
||
|
|
/// * This `from` path is not a directory.
|
||
|
|
/// * This `from` directory does not exist.
|
||
|
|
/// * Invalid folder name for `from` or `to`.
|
||
|
|
/// * The current process does not have the permission to access `from` or write `to`.
|
||
|
|
///
|
||
|
|
/// # Example
|
||
|
|
/// ```rust,ignore
|
||
|
|
/// extern crate fs_extra;
|
||
|
|
/// use fs_extra::dir::copy;
|
||
|
|
///
|
||
|
|
/// let options = CopyOptions::new(); //Initialize default values for CopyOptions
|
||
|
|
/// let handle = |process_info: TransitProcess| {
|
||
|
|
/// println!("{}", process_info.total_bytes);
|
||
|
|
/// fs_extra::dir::TransitProcessResult::ContinueOrAbort
|
||
|
|
/// }
|
||
|
|
/// // copy source/dir1 to target/dir1
|
||
|
|
/// copy_with_progress("source/dir1", "target/dir1", &options, handle)?;
|
||
|
|
///
|
||
|
|
/// ```
|
||
|
|
pub fn copy_with_progress<P, Q, F>(
|
||
|
|
from: P,
|
||
|
|
to: Q,
|
||
|
|
options: &CopyOptions,
|
||
|
|
mut progress_handler: F,
|
||
|
|
) -> Result<u64>
|
||
|
|
where
|
||
|
|
P: AsRef<Path>,
|
||
|
|
Q: AsRef<Path>,
|
||
|
|
F: FnMut(TransitProcess) -> TransitProcessResult,
|
||
|
|
{
|
||
|
|
let from = from.as_ref();
|
||
|
|
|
||
|
|
if !from.exists() {
|
||
|
|
if let Some(msg) = from.to_str() {
|
||
|
|
let msg = format!("Path \"{}\" does not exist or you don't have access!", msg);
|
||
|
|
err!(&msg, ErrorKind::NotFound);
|
||
|
|
}
|
||
|
|
err!(
|
||
|
|
"Path does not exist or you don't have access!",
|
||
|
|
ErrorKind::NotFound
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut to: PathBuf = to.as_ref().to_path_buf();
|
||
|
|
if !from.is_dir() {
|
||
|
|
if let Some(msg) = from.to_str() {
|
||
|
|
let msg = format!("Path \"{}\" is not a directory!", msg);
|
||
|
|
err!(&msg, ErrorKind::InvalidFolder);
|
||
|
|
}
|
||
|
|
err!("Path is not a directory!", ErrorKind::InvalidFolder);
|
||
|
|
}
|
||
|
|
|
||
|
|
let dir_name;
|
||
|
|
if let Some(val) = from.components().last() {
|
||
|
|
dir_name = val.as_os_str();
|
||
|
|
} else {
|
||
|
|
err!("Invalid folder from", ErrorKind::InvalidFolder);
|
||
|
|
}
|
||
|
|
if (to.exists() || !options.copy_inside) && !options.content_only {
|
||
|
|
to.push(dir_name);
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut read_options = DirOptions::new();
|
||
|
|
if options.depth > 0 {
|
||
|
|
read_options.depth = options.depth;
|
||
|
|
}
|
||
|
|
|
||
|
|
let dir_content = get_dir_content2(from, &read_options)?;
|
||
|
|
for directory in dir_content.directories {
|
||
|
|
let tmp_to = Path::new(&directory).strip_prefix(from)?;
|
||
|
|
let dir = to.join(&tmp_to);
|
||
|
|
if !dir.exists() {
|
||
|
|
if options.copy_inside {
|
||
|
|
create_all(dir, false)?;
|
||
|
|
} else {
|
||
|
|
create(dir, false)?;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut result: u64 = 0;
|
||
|
|
let mut info_process = TransitProcess {
|
||
|
|
copied_bytes: 0,
|
||
|
|
total_bytes: dir_content.dir_size,
|
||
|
|
file_bytes_copied: 0,
|
||
|
|
file_total_bytes: 0,
|
||
|
|
file_name: String::new(),
|
||
|
|
state: TransitState::Normal,
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut options = options.clone();
|
||
|
|
for file in dir_content.files {
|
||
|
|
let mut to = to.to_path_buf();
|
||
|
|
let tp = Path::new(&file).strip_prefix(from)?;
|
||
|
|
let path = to.join(&tp);
|
||
|
|
|
||
|
|
let file_name = path.file_name();
|
||
|
|
if file_name.is_none() {
|
||
|
|
err!("No file name");
|
||
|
|
}
|
||
|
|
let file_name = file_name.unwrap();
|
||
|
|
to.push(file_name);
|
||
|
|
|
||
|
|
let mut file_options = super::file::CopyOptions {
|
||
|
|
overwrite: options.overwrite,
|
||
|
|
skip_exist: options.skip_exist,
|
||
|
|
buffer_size: options.buffer_size,
|
||
|
|
};
|
||
|
|
|
||
|
|
if let Some(file_name) = file_name.to_str() {
|
||
|
|
info_process.file_name = file_name.to_string();
|
||
|
|
} else {
|
||
|
|
err!("Invalid file name", ErrorKind::InvalidFileName);
|
||
|
|
}
|
||
|
|
|
||
|
|
info_process.file_bytes_copied = 0;
|
||
|
|
info_process.file_total_bytes = Path::new(&file).metadata()?.len();
|
||
|
|
|
||
|
|
let mut result_copy: Result<u64>;
|
||
|
|
let mut work = true;
|
||
|
|
let copied_bytes = result;
|
||
|
|
while work {
|
||
|
|
{
|
||
|
|
let _progress_handler = |info: super::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 =
|
||
|
|
super::file::copy_with_progress(&file, &path, &file_options, _progress_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 = TransitState::Exists;
|
||
|
|
let user_decide = progress_handler(info_process);
|
||
|
|
match user_decide {
|
||
|
|
TransitProcessResult::Overwrite => {
|
||
|
|
file_options.overwrite = true;
|
||
|
|
}
|
||
|
|
TransitProcessResult::OverwriteAll => {
|
||
|
|
file_options.overwrite = true;
|
||
|
|
options.overwrite = true;
|
||
|
|
}
|
||
|
|
TransitProcessResult::Skip => {
|
||
|
|
file_options.skip_exist = true;
|
||
|
|
}
|
||
|
|
TransitProcessResult::SkipAll => {
|
||
|
|
file_options.skip_exist = true;
|
||
|
|
options.skip_exist = true;
|
||
|
|
}
|
||
|
|
TransitProcessResult::Retry => {}
|
||
|
|
TransitProcessResult::ContinueOrAbort => {
|
||
|
|
let err_msg = err.to_string();
|
||
|
|
err!(err_msg.as_str(), err.kind)
|
||
|
|
}
|
||
|
|
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 = TransitState::Exists;
|
||
|
|
let user_decide = progress_handler(info_process);
|
||
|
|
match user_decide {
|
||
|
|
TransitProcessResult::Overwrite => {
|
||
|
|
err!("Overwrite denied for this situation!", ErrorKind::Other);
|
||
|
|
}
|
||
|
|
TransitProcessResult::OverwriteAll => {
|
||
|
|
err!("Overwrite denied for this situation!", ErrorKind::Other);
|
||
|
|
}
|
||
|
|
TransitProcessResult::Skip => {
|
||
|
|
file_options.skip_exist = true;
|
||
|
|
}
|
||
|
|
TransitProcessResult::SkipAll => {
|
||
|
|
file_options.skip_exist = true;
|
||
|
|
options.skip_exist = true;
|
||
|
|
}
|
||
|
|
TransitProcessResult::Retry => {}
|
||
|
|
TransitProcessResult::ContinueOrAbort => {
|
||
|
|
let err_msg = err.to_string();
|
||
|
|
err!(err_msg.as_str(), err.kind)
|
||
|
|
}
|
||
|
|
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 the directory contents from one place to another.
|
||
|
|
/// 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 cases:
|
||
|
|
///
|
||
|
|
/// * This `from` path is not a directory.
|
||
|
|
/// * This `from` directory does not exist.
|
||
|
|
/// * Invalid folder name for `from` or `to`.
|
||
|
|
/// * The current process does not have the permission to access `from` or write `to`.
|
||
|
|
///
|
||
|
|
/// # Example
|
||
|
|
/// ```rust,ignore
|
||
|
|
/// extern crate fs_extra;
|
||
|
|
/// use fs_extra::dir::move_dir;
|
||
|
|
///
|
||
|
|
/// let options = CopyOptions::new(); //Initialize default values for CopyOptions
|
||
|
|
///
|
||
|
|
/// // move source/dir1 to target/dir1
|
||
|
|
/// move_dir("source/dir1", "target/dir1", &options)?;
|
||
|
|
///
|
||
|
|
/// ```
|
||
|
|
pub fn move_dir<P, Q>(from: P, to: Q, options: &CopyOptions) -> Result<u64>
|
||
|
|
where
|
||
|
|
P: AsRef<Path>,
|
||
|
|
Q: AsRef<Path>,
|
||
|
|
{
|
||
|
|
let mut is_remove = true;
|
||
|
|
if options.skip_exist && to.as_ref().exists() && !options.overwrite {
|
||
|
|
is_remove = false;
|
||
|
|
}
|
||
|
|
let from = from.as_ref();
|
||
|
|
|
||
|
|
if !from.exists() {
|
||
|
|
if let Some(msg) = from.to_str() {
|
||
|
|
let msg = format!("Path \"{}\" does not exist", msg);
|
||
|
|
err!(&msg, ErrorKind::NotFound);
|
||
|
|
}
|
||
|
|
err!(
|
||
|
|
"Path does not exist or you don't have access!",
|
||
|
|
ErrorKind::NotFound
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut to: PathBuf = to.as_ref().to_path_buf();
|
||
|
|
if !from.is_dir() {
|
||
|
|
if let Some(msg) = from.to_str() {
|
||
|
|
let msg = format!(
|
||
|
|
"Path \"{}\" is not a directory or you don't have access!",
|
||
|
|
msg
|
||
|
|
);
|
||
|
|
err!(&msg, ErrorKind::InvalidFolder);
|
||
|
|
}
|
||
|
|
err!(
|
||
|
|
"Path is not a directory or you don't have access!",
|
||
|
|
ErrorKind::InvalidFolder
|
||
|
|
);
|
||
|
|
}
|
||
|
|
let dir_name;
|
||
|
|
if let Some(val) = from.components().last() {
|
||
|
|
dir_name = val.as_os_str();
|
||
|
|
} else {
|
||
|
|
err!("Invalid folder from", ErrorKind::InvalidFolder);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (to.exists() || !options.copy_inside) && !options.content_only {
|
||
|
|
to.push(dir_name);
|
||
|
|
}
|
||
|
|
let dir_content = get_dir_content(from)?;
|
||
|
|
for directory in dir_content.directories {
|
||
|
|
let tmp_to = Path::new(&directory).strip_prefix(from)?;
|
||
|
|
let dir = to.join(&tmp_to);
|
||
|
|
if !dir.exists() {
|
||
|
|
if options.copy_inside {
|
||
|
|
create_all(dir, false)?;
|
||
|
|
} else {
|
||
|
|
create(dir, false)?;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
let mut result: u64 = 0;
|
||
|
|
for file in dir_content.files {
|
||
|
|
let to = to.to_path_buf();
|
||
|
|
let tp = Path::new(&file).strip_prefix(from)?;
|
||
|
|
let path = to.join(&tp);
|
||
|
|
|
||
|
|
let file_options = super::file::CopyOptions {
|
||
|
|
overwrite: options.overwrite,
|
||
|
|
skip_exist: options.skip_exist,
|
||
|
|
buffer_size: options.buffer_size,
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut result_copy: Result<u64>;
|
||
|
|
let mut work = true;
|
||
|
|
while work {
|
||
|
|
{
|
||
|
|
result_copy = super::file::move_file(&file, &path, &file_options);
|
||
|
|
match result_copy {
|
||
|
|
Ok(val) => {
|
||
|
|
result += val;
|
||
|
|
work = false;
|
||
|
|
}
|
||
|
|
Err(err) => {
|
||
|
|
let err_msg = err.to_string();
|
||
|
|
err!(err_msg.as_str(), err.kind)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if is_remove {
|
||
|
|
remove(from)?;
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(result)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Moves the directory contents from one place to another 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 cases:
|
||
|
|
///
|
||
|
|
/// * This `from` path is not a directory.
|
||
|
|
/// * This `from` directory does not exist.
|
||
|
|
/// * Invalid folder name for `from` or `to`.
|
||
|
|
/// * The current process does not have the permission to access `from` or write `to`.
|
||
|
|
///
|
||
|
|
/// # Example
|
||
|
|
/// ```rust,ignore
|
||
|
|
/// extern crate fs_extra;
|
||
|
|
/// use fs_extra::dir::move_dir_with_progress;
|
||
|
|
///
|
||
|
|
/// let options = CopyOptions::new(); //Initialize default values for CopyOptions
|
||
|
|
/// let handle = |process_info: TransitProcess| {
|
||
|
|
/// println!("{}", process_info.total_bytes);
|
||
|
|
/// fs_extra::dir::TransitProcessResult::ContinueOrAbort
|
||
|
|
/// }
|
||
|
|
///
|
||
|
|
/// // move source/dir1 to target/dir1
|
||
|
|
/// move_dir_with_progress("source/dir1", "target/dir1", &options, handle)?;
|
||
|
|
///
|
||
|
|
/// ```
|
||
|
|
pub fn move_dir_with_progress<P, Q, F>(
|
||
|
|
from: P,
|
||
|
|
to: Q,
|
||
|
|
options: &CopyOptions,
|
||
|
|
mut progress_handler: F,
|
||
|
|
) -> Result<u64>
|
||
|
|
where
|
||
|
|
P: AsRef<Path>,
|
||
|
|
Q: AsRef<Path>,
|
||
|
|
F: FnMut(TransitProcess) -> TransitProcessResult,
|
||
|
|
{
|
||
|
|
let mut is_remove = true;
|
||
|
|
if options.skip_exist && to.as_ref().exists() && !options.overwrite {
|
||
|
|
is_remove = false;
|
||
|
|
}
|
||
|
|
let from = from.as_ref();
|
||
|
|
|
||
|
|
if !from.exists() {
|
||
|
|
if let Some(msg) = from.to_str() {
|
||
|
|
let msg = format!("Path \"{}\" does not exist or you don't have access!", msg);
|
||
|
|
err!(&msg, ErrorKind::NotFound);
|
||
|
|
}
|
||
|
|
err!(
|
||
|
|
"Path does not exist or you don't have access!",
|
||
|
|
ErrorKind::NotFound
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut to: PathBuf = to.as_ref().to_path_buf();
|
||
|
|
if !from.is_dir() {
|
||
|
|
if let Some(msg) = from.to_str() {
|
||
|
|
let msg = format!("Path \"{}\" is not a directory!", msg);
|
||
|
|
err!(&msg, ErrorKind::InvalidFolder);
|
||
|
|
}
|
||
|
|
err!("Path is not a directory!", ErrorKind::InvalidFolder);
|
||
|
|
}
|
||
|
|
let dir_name;
|
||
|
|
if let Some(val) = from.components().last() {
|
||
|
|
dir_name = val.as_os_str();
|
||
|
|
} else {
|
||
|
|
err!("Invalid folder from", ErrorKind::InvalidFolder);
|
||
|
|
}
|
||
|
|
if !(options.content_only || options.copy_inside && !to.exists()) {
|
||
|
|
to.push(dir_name);
|
||
|
|
}
|
||
|
|
|
||
|
|
let dir_content = get_dir_content(from)?;
|
||
|
|
for directory in dir_content.directories {
|
||
|
|
let tmp_to = Path::new(&directory).strip_prefix(from)?;
|
||
|
|
let dir = to.join(&tmp_to);
|
||
|
|
if !dir.exists() {
|
||
|
|
if options.copy_inside {
|
||
|
|
create_all(dir, false)?;
|
||
|
|
} else {
|
||
|
|
create(dir, false)?;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let mut result: u64 = 0;
|
||
|
|
let mut info_process = TransitProcess {
|
||
|
|
copied_bytes: 0,
|
||
|
|
total_bytes: dir_content.dir_size,
|
||
|
|
file_bytes_copied: 0,
|
||
|
|
file_total_bytes: 0,
|
||
|
|
file_name: String::new(),
|
||
|
|
state: TransitState::Normal,
|
||
|
|
};
|
||
|
|
|
||
|
|
let mut options = options.clone();
|
||
|
|
for file in dir_content.files {
|
||
|
|
let mut to = to.to_path_buf();
|
||
|
|
let tp = Path::new(&file).strip_prefix(from)?;
|
||
|
|
let path = to.join(&tp);
|
||
|
|
|
||
|
|
let file_name = path.file_name();
|
||
|
|
if file_name.is_none() {
|
||
|
|
err!("No file name");
|
||
|
|
}
|
||
|
|
let file_name = file_name.unwrap();
|
||
|
|
to.push(file_name);
|
||
|
|
|
||
|
|
let mut file_options = super::file::CopyOptions {
|
||
|
|
overwrite: options.overwrite,
|
||
|
|
skip_exist: options.skip_exist,
|
||
|
|
buffer_size: options.buffer_size,
|
||
|
|
};
|
||
|
|
|
||
|
|
if let Some(file_name) = file_name.to_str() {
|
||
|
|
info_process.file_name = file_name.to_string();
|
||
|
|
} else {
|
||
|
|
err!("Invalid file name", ErrorKind::InvalidFileName);
|
||
|
|
}
|
||
|
|
|
||
|
|
info_process.file_bytes_copied = 0;
|
||
|
|
info_process.file_total_bytes = Path::new(&file).metadata()?.len();
|
||
|
|
|
||
|
|
let mut result_copy: Result<u64>;
|
||
|
|
let mut work = true;
|
||
|
|
let copied_bytes = result;
|
||
|
|
while work {
|
||
|
|
{
|
||
|
|
let _progress_handler = |info: super::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 = super::file::move_file_with_progress(
|
||
|
|
&file,
|
||
|
|
&path,
|
||
|
|
&file_options,
|
||
|
|
_progress_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 = TransitState::Exists;
|
||
|
|
let user_decide = progress_handler(info_process);
|
||
|
|
match user_decide {
|
||
|
|
TransitProcessResult::Overwrite => {
|
||
|
|
file_options.overwrite = true;
|
||
|
|
}
|
||
|
|
TransitProcessResult::OverwriteAll => {
|
||
|
|
file_options.overwrite = true;
|
||
|
|
options.overwrite = true;
|
||
|
|
}
|
||
|
|
TransitProcessResult::Skip => {
|
||
|
|
is_remove = false;
|
||
|
|
file_options.skip_exist = true;
|
||
|
|
}
|
||
|
|
TransitProcessResult::SkipAll => {
|
||
|
|
is_remove = false;
|
||
|
|
file_options.skip_exist = true;
|
||
|
|
options.skip_exist = true;
|
||
|
|
}
|
||
|
|
TransitProcessResult::Retry => {}
|
||
|
|
TransitProcessResult::ContinueOrAbort => {
|
||
|
|
let err_msg = err.to_string();
|
||
|
|
err!(err_msg.as_str(), err.kind)
|
||
|
|
}
|
||
|
|
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 = TransitState::Exists;
|
||
|
|
let user_decide = progress_handler(info_process);
|
||
|
|
match user_decide {
|
||
|
|
TransitProcessResult::Overwrite => {
|
||
|
|
err!("Overwrite denied for this situation!", ErrorKind::Other);
|
||
|
|
}
|
||
|
|
TransitProcessResult::OverwriteAll => {
|
||
|
|
err!("Overwrite denied for this situation!", ErrorKind::Other);
|
||
|
|
}
|
||
|
|
TransitProcessResult::Skip => {
|
||
|
|
is_remove = false;
|
||
|
|
file_options.skip_exist = true;
|
||
|
|
}
|
||
|
|
TransitProcessResult::SkipAll => {
|
||
|
|
file_options.skip_exist = true;
|
||
|
|
options.skip_exist = true;
|
||
|
|
}
|
||
|
|
TransitProcessResult::Retry => {}
|
||
|
|
TransitProcessResult::ContinueOrAbort => {
|
||
|
|
let err_msg = err.to_string();
|
||
|
|
err!(err_msg.as_str(), err.kind)
|
||
|
|
}
|
||
|
|
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)
|
||
|
|
}
|
||
|
|
},
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if is_remove {
|
||
|
|
remove(from)?;
|
||
|
|
}
|
||
|
|
|
||
|
|
Ok(result)
|
||
|
|
}
|
||
|
|
|
||
|
|
/// Removes directory.
|
||
|
|
///
|
||
|
|
/// # Example
|
||
|
|
/// ```rust,ignore
|
||
|
|
/// extern crate fs_extra;
|
||
|
|
/// use fs_extra::dir::remove;
|
||
|
|
///
|
||
|
|
/// remove("source/dir1"); // remove dir1
|
||
|
|
/// ```
|
||
|
|
pub fn remove<P: AsRef<Path>>(path: P) -> Result<()> {
|
||
|
|
if path.as_ref().exists() {
|
||
|
|
Ok(remove_dir_all(path)?)
|
||
|
|
} else {
|
||
|
|
Ok(())
|
||
|
|
}
|
||
|
|
}
|