use std::{
fs::{self},
path::{Path, PathBuf},
};
use eyre::{Context, Result};
use flate2::Compression;
use log::warn;
pub const DEFAULT_GZIP_COMPRESSION_LEVEL: Compression = Compression::new(4);
#[allow(dead_code)] pub fn get_files_sorted_by_mtime(dir: &Path) -> Result<Vec<PathBuf>> {
let read_dir = std::fs::read_dir(dir)?;
let mut entries = read_dir
.filter_map(|e| match e {
Ok(e) => Some(e),
Err(e) => {
warn!("Error reading directory entry: {:#}", e);
None
}
})
.filter(|entry| entry.path().is_file())
.collect::<Vec<_>>();
entries.sort_by_key(|entry| {
entry
.metadata()
.map(|m| m.modified().unwrap_or(std::time::SystemTime::UNIX_EPOCH))
.unwrap_or(std::time::SystemTime::UNIX_EPOCH)
});
Ok(entries.into_iter().map(|m| m.path()).collect())
}
pub fn move_file(source: &PathBuf, target: &PathBuf) -> Result<()> {
if fs::rename(source, target).is_err() {
fs::copy(source, target).wrap_err_with(|| {
format!(
"Error moving file {} to {}",
source.display(),
target.display()
)
})?;
fs::remove_file(source)?;
}
Ok(())
}
pub fn move_dir(source: &PathBuf, target: &PathBuf) -> Result<()> {
if target.starts_with(source) {
return Err(eyre::eyre!(
"Failed to move directory, target {} is inside source {}",
target.display(),
source.display()
));
}
if fs::rename(source, target).is_ok() {
return Ok(());
}
copy_dir(source.as_path(), target.as_path())?;
fs::remove_dir_all(source)
.wrap_err_with(|| format!("Error removing source directory {}", source.display()))?;
Ok(())
}
fn copy_dir(src: &Path, dst: &Path) -> Result<()> {
use std::collections::VecDeque;
if dst.starts_with(src) {
return Err(eyre::eyre!(
"Failed to copy directory, target {} is inside source {}",
dst.display(),
src.display()
));
}
let mut stack: VecDeque<(PathBuf, PathBuf)> = VecDeque::new();
stack.push_back((src.to_path_buf(), dst.to_path_buf()));
while let Some((cur_src, cur_dst)) = stack.pop_back() {
fs::create_dir_all(&cur_dst)
.wrap_err_with(|| format!("Error creating directory {}", cur_dst.display()))?;
for entry in fs::read_dir(&cur_src)
.wrap_err_with(|| format!("Error reading directory {}", cur_src.display()))?
{
let entry = entry?;
let file_type = entry.file_type()?;
let from = entry.path();
let to = cur_dst.join(entry.file_name());
if file_type.is_dir() {
stack.push_back((from, to));
} else if file_type.is_file() {
fs::copy(&from, &to).wrap_err_with(|| {
format!("Error copying file {} to {}", from.display(), to.display())
})?;
} else if file_type.is_symlink() {
#[cfg(unix)]
{
use std::os::unix::fs::symlink;
let link_target = fs::read_link(&from)?;
symlink(&link_target, &to)?;
}
#[cfg(not(unix))]
{
fs::copy(&from, &to).wrap_err_with(|| {
format!(
"Error copying symlink {} to {}",
from.display(),
to.display()
)
})?;
}
}
}
}
Ok(())
}
pub fn copy_file(source: &PathBuf, target: &PathBuf) -> Result<()> {
if source == target {
return Ok(());
}
fs::copy(source, target).wrap_err_with(|| {
format!(
"Error copying file {} to {}",
source.display(),
target.display()
)
})?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_move_dir_rename_success() -> Result<()> {
let src_dir = tempdir()?;
let dst_dir = tempdir()?;
let src_path = src_dir.path().join("a");
fs::create_dir(&src_path)?;
let f = src_path.join("foo.txt");
fs::write(&f, b"hello")?;
let target = dst_dir.path().join("moved");
move_dir(&src_path, &target)?;
assert!(target.exists());
assert!(target.join("foo.txt").exists());
assert!(!src_path.exists());
Ok(())
}
#[test]
fn test_copy_dir_contents() -> Result<()> {
let src_dir = tempdir()?;
let dst_dir = tempdir()?;
let src_path = src_dir.path().join("src");
fs::create_dir(&src_path)?;
fs::write(src_path.join("one.txt"), b"1")?;
fs::write(src_path.join("two.txt"), b"2")?;
let target = dst_dir.path().join("copied");
copy_dir(&src_path, &target)?;
assert!(target.exists());
assert!(target.join("one.txt").exists());
assert!(target.join("two.txt").exists());
Ok(())
}
#[test]
fn test_move_dir_target_inside_source_is_error() {
let src_dir = tempdir().unwrap();
let src_path = src_dir.path().join("parent");
fs::create_dir(&src_path).unwrap();
let target = src_path.join("parent/child/target");
let res = move_dir(&src_path, &target);
assert!(res.is_err());
}
#[test]
fn test_copy_dir_target_inside_source_is_error() {
let src_dir = tempdir().unwrap();
let src_path = src_dir.path().join("parent");
fs::create_dir(&src_path).unwrap();
let target = src_path.join("parent/child/target");
let res = copy_dir(&src_path, &target);
assert!(res.is_err());
}
}