use log::*;
use std::fs::copy;
use std::io::{Error, ErrorKind};
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use walkdir::WalkDir;
#[cfg(test)]
mod tests;
#[derive(Debug, Clone)]
pub struct CopyBuilder {
pub source: PathBuf,
pub destination: PathBuf,
overwrite_all: bool,
overwrite_if_newer: bool,
overwrite_if_size_differs: bool,
exclude_filters: Vec<String>,
include_filters: Vec<String>,
}
fn is_file_newer(file_a: &Path, file_b: &Path) -> bool {
match (file_a.metadata(), file_b.metadata()) {
(Ok(meta_a), Ok(meta_b)) => {
meta_a.modified().unwrap_or_else(|_| SystemTime::now())
> meta_b.modified().unwrap_or(SystemTime::UNIX_EPOCH)
}
_ => false,
}
}
fn is_filesize_different(file_a: &Path, file_b: &Path) -> bool {
match (file_a.metadata(), file_b.metadata()) {
(Ok(meta_a), Ok(meta_b)) => meta_a.len() != meta_b.len(),
_ => false,
}
}
fn copy_file(source: &Path, options: CopyBuilder) -> Result<(), std::io::Error> {
let abs_source = options.source.canonicalize()?;
let abs_dest = options.destination.canonicalize()?;
let rel_dest = source
.strip_prefix(&abs_source)
.map_err(|e| Error::new(ErrorKind::Other, format!("Could not strip prefix: {:?}", e)))?;
let dest_entry = abs_dest.join(rel_dest);
if source.is_file() {
if !options.overwrite_all
&& dest_entry.is_file()
&& !options.overwrite_if_newer
&& !options.overwrite_if_size_differs
{
return Ok(());
}
for f in &options.exclude_filters {
if source.to_string_lossy().contains(f) {
return Ok(());
}
}
for f in &options.include_filters {
if !source.to_string_lossy().contains(f) {
return Ok(());
}
}
if !dest_entry.is_file() {
debug!(
"Dest not present: CP {} DST {}",
source.display(),
dest_entry.display()
);
copy(source, dest_entry)?;
return Ok(());
}
if options.overwrite_if_newer {
if is_file_newer(source, &dest_entry) {
debug!(
"Source newer: CP {} DST {}",
source.display(),
dest_entry.display()
);
copy(source, &dest_entry)?;
}
return Ok(());
}
if options.overwrite_if_size_differs {
if is_filesize_different(source, &dest_entry) {
debug!(
"Source differs: CP {} DST {}",
source.display(),
dest_entry.display()
);
copy(source, &dest_entry)?;
}
return Ok(());
}
debug!("CP {} DST {}", source.display(), dest_entry.display());
copy(source, dest_entry)?;
} else if source.is_dir() && !dest_entry.is_dir() {
debug!("MKDIR {}", source.display());
std::fs::create_dir_all(dest_entry)?;
}
Ok(())
}
impl CopyBuilder {
pub fn new<P: AsRef<Path>, Q: AsRef<Path>>(source: P, dest: Q) -> CopyBuilder {
CopyBuilder {
source: source.as_ref().to_path_buf(),
destination: dest.as_ref().to_path_buf(),
overwrite_all: false,
overwrite_if_newer: false,
overwrite_if_size_differs: false,
exclude_filters: vec![],
include_filters: vec![],
}
}
pub fn overwrite(self, overwrite: bool) -> CopyBuilder {
CopyBuilder {
overwrite_all: overwrite,
..self
}
}
pub fn overwrite_if_newer(self, overwrite_only_newer: bool) -> CopyBuilder {
CopyBuilder {
overwrite_if_newer: overwrite_only_newer,
..self
}
}
pub fn overwrite_if_size_differs(self, overwrite_if_size_differs: bool) -> CopyBuilder {
CopyBuilder {
overwrite_if_size_differs,
..self
}
}
pub fn with_exclude_filter(self, f: &str) -> CopyBuilder {
let mut filters = self.exclude_filters.clone();
filters.push(f.to_owned());
CopyBuilder {
exclude_filters: filters,
..self
}
}
pub fn with_include_filter(self, f: &str) -> CopyBuilder {
let mut filters = self.exclude_filters.clone();
filters.push(f.to_owned());
CopyBuilder {
include_filters: filters,
..self
}
}
pub fn run(&self) -> Result<(), std::io::Error> {
if !self.destination.is_dir() {
debug!("MKDIR {:?}", &self.destination);
std::fs::create_dir_all(&self.destination)?;
}
let abs_source = self.source.canonicalize()?;
let abs_dest = self.destination.canonicalize()?;
debug!(
"Building copy operation: SRC {} DST {}",
abs_source.display(),
abs_dest.display()
);
for entry in WalkDir::new(&abs_source).into_iter().filter_map(|e| e.ok()) {
let rel_dest = entry.path().strip_prefix(&abs_source).map_err(|e| {
Error::new(ErrorKind::Other, format!("Could not strip prefix: {:?}", e))
})?;
let dest_entry = abs_dest.join(rel_dest);
if entry.path().is_file() {
if !self.overwrite_all
&& dest_entry.is_file()
&& !self.overwrite_if_newer
&& !self.overwrite_if_size_differs
{
continue;
}
for f in &self.exclude_filters {
if entry.path().to_string_lossy().contains(f) {
continue;
}
}
for f in &self.include_filters {
if !entry.path().to_string_lossy().contains(f) {
continue;
}
}
if !dest_entry.is_file() {
debug!(
"Dest not present: CP {} DST {}",
entry.path().display(),
dest_entry.display()
);
copy(entry.path(), dest_entry)?;
continue;
}
if self.overwrite_if_newer {
if is_file_newer(entry.path(), &dest_entry) {
debug!(
"Source newer: CP {} DST {}",
entry.path().display(),
dest_entry.display()
);
copy(entry.path(), &dest_entry)?;
}
continue;
}
if self.overwrite_if_size_differs {
if is_filesize_different(entry.path(), &dest_entry) {
debug!(
"Source differs: CP {} DST {}",
entry.path().display(),
dest_entry.display()
);
copy(entry.path(), &dest_entry)?;
}
continue;
}
debug!("CP {} DST {}", entry.path().display(), dest_entry.display());
copy(entry.path(), dest_entry)?;
} else if entry.path().is_dir() && !dest_entry.is_dir() {
debug!("MKDIR {}", entry.path().display());
std::fs::create_dir_all(dest_entry)?;
}
}
Ok(())
}
pub fn run_par(&self) -> Result<(), std::io::Error> {
if !self.destination.is_dir() {
debug!("MKDIR {:?}", &self.destination);
std::fs::create_dir_all(&self.destination)?;
}
let abs_source = self.source.canonicalize()?;
let abs_dest = self.destination.canonicalize()?;
debug!(
"Building copy operation: SRC {} DST {}",
abs_source.display(),
abs_dest.display()
);
for entry in WalkDir::new(&abs_source).into_iter().filter_map(|e| e.ok()) {
copy_file(&entry.path(), self.clone());
}
Ok(())
}
}
pub fn copy_dir_advanced<P: AsRef<Path>, Q: AsRef<Path>>(
source: P,
dest: Q,
overwrite_all: bool,
overwrite_if_newer: bool,
overwrite_if_size_differs: bool,
exclude_filters: Vec<String>,
include_filters: Vec<String>,
) -> Result<(), std::io::Error> {
CopyBuilder {
source: source.as_ref().to_path_buf(),
destination: dest.as_ref().to_path_buf(),
overwrite_all,
overwrite_if_newer,
overwrite_if_size_differs,
exclude_filters,
include_filters,
}
.run()
}
pub fn copy_dir<P: AsRef<Path>, Q: AsRef<Path>>(source: P, dest: Q) -> Result<(), std::io::Error> {
CopyBuilder {
source: source.as_ref().to_path_buf(),
destination: dest.as_ref().to_path_buf(),
overwrite_all: false,
overwrite_if_newer: false,
overwrite_if_size_differs: false,
exclude_filters: vec![],
include_filters: vec![],
}
.run()
}