use std::fs;
use std::io::{Error, ErrorKind};
use std::path::{Path, PathBuf};
use chrono::prelude::*;
pub fn backup(path: impl AsRef<Path>) -> Result<PathBuf, std::io::Error> {
if !path.as_ref().exists() {
return Err(Error::new(ErrorKind::NotFound, "Path does not exist."));
}
let parent = match path.as_ref().parent() {
Some(x) => match x.to_str() {
Some("") => ".",
Some(x) => x,
None => {
return Err(Error::new(
ErrorKind::Unsupported,
"Path is not a valid UTF-8.",
))
}
},
None => return Err(Error::new(ErrorKind::Unsupported, "Path is root.")),
};
let filename = match path.as_ref().file_name() {
Some(x) => match x.to_str() {
Some(x) => x,
None => {
return Err(Error::new(
ErrorKind::Unsupported,
"Path is not a valid UTF-8.",
))
}
},
None => return Err(Error::new(ErrorKind::Unsupported, "Path ends in '..'.")),
};
let time = Local::now().format("%Y-%m-%d-%H-%M-%S").to_string();
let mut backup_name = Path::new(&format!("{}/#{}-{}#", parent, filename, &time)).to_path_buf();
while backup_name.exists() {
let time = Local::now();
let micros = time.timestamp_subsec_micros();
let time_fmt = time.format("%Y-%m-%d-%H-%M-%S").to_string();
backup_name = Path::new(&format!(
"{}/#{}-{}-{}#",
parent, filename, &time_fmt, micros
))
.to_path_buf();
}
match fs::rename(path, &backup_name) {
Ok(()) => Ok(backup_name),
Err(e) => Err(e),
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs::{self, File};
use std::io::prelude::*;
#[test]
fn file() {
let mut file = File::create("test_file1.txt").unwrap();
file.write_all(b"Some content to test.").unwrap();
let backup = match backup("test_file1.txt") {
Ok(x) => x,
Err(_) => panic!("Backup failed."),
};
drop(file);
let mut content = String::new();
let mut read = File::open(&backup).unwrap();
read.read_to_string(&mut content).unwrap();
assert_eq!(content, "Some content to test.");
fs::remove_file(backup).unwrap();
}
#[test]
fn file_multiple_backups() {
let mut backups = Vec::new();
for i in 0..20 {
let mut file = File::create("test_file2.txt").unwrap();
let text = format!("Unique string for file {}", i);
file.write_all(text.as_bytes()).unwrap();
let backup = match backup("test_file2.txt") {
Ok(x) => x,
Err(_) => panic!("Backup failed."),
};
backups.push(backup);
}
for (i, path) in backups.iter().enumerate() {
let mut content = String::new();
let mut read = File::open(&path).unwrap();
read.read_to_string(&mut content).unwrap();
let test = format!("Unique string for file {}", i);
assert_eq!(content, test);
fs::remove_file(path).unwrap();
}
}
#[test]
fn file_in_different_directory() {
fs::create_dir("test_dir").unwrap();
let mut file = File::create("test_dir/test_file.txt").unwrap();
file.write_all(b"Some content to test.").unwrap();
let backup = match backup("test_dir/test_file.txt") {
Ok(x) => x,
Err(_) => panic!("Backup failed."),
};
drop(file);
let mut content = String::new();
let mut read = File::open(&backup).unwrap();
read.read_to_string(&mut content).unwrap();
assert_eq!(content, "Some content to test.");
fs::remove_file(&backup).unwrap();
fs::remove_dir("test_dir").unwrap();
}
#[test]
fn file_multiple_backups_in_different_directory() {
fs::create_dir("test_dir2").unwrap();
let mut backups = Vec::new();
for i in 0..20 {
let mut file = File::create("test_dir2/test_file.txt").unwrap();
let text = format!("Unique string for file {}", i);
file.write_all(text.as_bytes()).unwrap();
let backup = match backup("test_dir2/test_file.txt") {
Ok(x) => x,
Err(_) => panic!("Backup failed."),
};
backups.push(backup);
}
for (i, path) in backups.iter().enumerate() {
let mut content = String::new();
let mut read = File::open(&path).unwrap();
read.read_to_string(&mut content).unwrap();
let test = format!("Unique string for file {}", i);
assert_eq!(content, test);
fs::remove_file(path).unwrap();
}
fs::remove_dir("test_dir2").unwrap();
}
#[test]
fn directory() {
fs::create_dir("test_dir3").unwrap();
let mut file = File::create("test_dir3/test_file.txt").unwrap();
file.write_all(b"Some content to test.").unwrap();
let backup = match backup("test_dir3") {
Ok(x) => x,
Err(_) => panic!("Backup failed."),
};
drop(file);
let mut content = String::new();
let file_in_backup = backup.join(Path::new("test_file.txt"));
let mut read = File::open(&file_in_backup).unwrap();
read.read_to_string(&mut content).unwrap();
assert_eq!(content, "Some content to test.");
fs::remove_file(&file_in_backup).unwrap();
fs::remove_dir(&backup).unwrap();
}
#[test]
fn directory_multiple_backups() {
let mut backups = Vec::new();
for i in 0..10 {
fs::create_dir("test_dir4").unwrap();
let mut file = File::create("test_dir4/test_file.txt").unwrap();
let text = format!("Unique string for file {}", i);
file.write_all(text.as_bytes()).unwrap();
let backup = match backup("test_dir4") {
Ok(x) => x,
Err(_) => panic!("Backup failed."),
};
backups.push(backup);
}
for (i, path) in backups.iter().enumerate() {
let file_in_backup = path.join(Path::new("test_file.txt"));
let mut content = String::new();
let mut read = File::open(&file_in_backup).unwrap();
read.read_to_string(&mut content).unwrap();
let test = format!("Unique string for file {}", i);
assert_eq!(content, test);
fs::remove_file(&file_in_backup).unwrap();
fs::remove_dir(&path).unwrap();
}
}
#[test]
fn nonexistent() {
match backup("nonexistent.txt") {
Ok(_) => panic!("Backup should have failed, but it was successful."),
Err(e) => assert_eq!(e.to_string(), "Path does not exist."),
};
}
#[test]
fn root() {
match backup("/") {
Ok(_) => panic!("Backup should have failed, but it was successful."),
Err(e) => assert_eq!(e.to_string(), "Path is root."),
};
}
#[test]
fn empty() {
match backup("") {
Ok(_) => panic!("Backup should have failed, but it was successful."),
Err(e) => assert_eq!(e.to_string(), "Path does not exist."),
};
}
#[test]
fn dotdot() {
match backup("..") {
Ok(_) => panic!("Backup should have failed, but it was successful."),
Err(e) => assert_eq!(e.to_string(), "Path ends in '..'."),
};
}
}