use 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;
#[derive(Clone)]
pub struct CopyOptions {
pub overwrite: bool,
pub skip_exist: bool,
pub buffer_size: usize,
pub copy_inside: bool,
pub content_only: bool,
pub depth: u64,
}
impl CopyOptions {
pub fn new() -> CopyOptions {
CopyOptions {
overwrite: false,
skip_exist: false,
buffer_size: 64000,
copy_inside: false,
content_only: false,
depth: 0,
}
}
}
impl Default for CopyOptions {
fn default() -> Self {
CopyOptions::new()
}
}
#[derive(Clone, Default)]
pub struct DirOptions {
pub depth: u64,
}
impl DirOptions {
pub fn new() -> DirOptions {
Default::default()
}
}
pub struct DirContent {
pub dir_size: u64,
pub files: Vec<String>,
pub directories: Vec<String>,
}
pub struct TransitProcess {
pub copied_bytes: u64,
pub total_bytes: u64,
pub file_bytes_copied: u64,
pub file_total_bytes: u64,
pub file_name: String,
pub state: TransitState,
}
#[derive(Hash, Eq, PartialEq, Clone)]
pub enum TransitState {
Normal,
Exists,
NoAccess,
}
pub enum TransitProcessResult {
Overwrite,
OverwriteAll,
Skip,
SkipAll,
Retry,
Abort,
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(),
}
}
}
#[derive(Hash, Eq, PartialEq, Clone)]
pub enum DirEntryAttr {
Name,
Ext,
FullName,
Path,
DosPath,
FileSize,
Size,
IsDir,
IsFile,
Modified,
Accessed,
Created,
BaseInfo,
}
pub enum DirEntryValue {
String(String),
Boolean(bool),
SystemTime(SystemTime),
U64(u64),
}
pub struct LsResult {
pub base: HashMap<DirEntryAttr, DirEntryValue>,
pub items: Vec<HashMap<DirEntryAttr, DirEntryValue>>,
}
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)
}
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: items,
base: base,
})
}
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)?)
}
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)?)
}
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 !options.content_only && ((options.copy_inside && to.exists()) || !options.copy_inside) {
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)
}
pub fn get_dir_content<P>(path: P) -> Result<DirContent>
where
P: AsRef<Path>,
{
let options = DirOptions::new();
get_dir_content2(path, &options)
}
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 = 0;
let item = path.as_ref().to_str();
if !item.is_some() {
err!("Invalid path", ErrorKind::InvalidPath);
}
let item = item.unwrap().to_string();
if path.as_ref().is_dir() {
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 _dirrectories = items.directories;
dir_size += items.dir_size;
files.append(&mut _files);
directories.append(&mut _dirrectories);
}
Err(err) => return Err(err),
}
}
}
} else {
dir_size = path.as_ref().metadata()?.len();
files.push(item);
}
Ok(DirContent {
dir_size: dir_size,
files: files,
directories: directories,
})
}
pub fn get_size<P>(path: P) -> Result<u64>
where
P: AsRef<Path>,
{
let mut result = 0;
if path.as_ref().is_dir() {
for entry in read_dir(&path)? {
let _path = entry?.path();
if _path.is_file() {
result += _path.metadata()?.len();
} else {
result += get_size(_path)?;
}
}
} else {
result = path.as_ref().metadata()?.len();
}
Ok(result)
}
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 !options.content_only && ((options.copy_inside && to.exists()) || !options.copy_inside) {
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_some() {
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)
}
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 !options.content_only && ((options.copy_inside && to.exists()) || !options.copy_inside) {
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)
}
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()) || !options.copy_inside) {
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_some() {
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)
}
pub fn remove<P: AsRef<Path>>(path: P) -> Result<()> {
if path.as_ref().exists() {
Ok(remove_dir_all(path)?)
} else {
Ok(())
}
}