mod backup;
mod ext;
#[cfg(all(test, not(feature = "integration-tests")))]
mod tests;
use crate::Error;
use std::{collections::HashMap, path::Path};
use tempfile::NamedTempFile;
#[derive(Debug)]
pub struct Rollback<'a> {
noted: HashMap<&'a Path, NamedTempFile>,
new_files: HashMap<&'a Path, NamedTempFile>,
new_dirs: Vec<&'a Path>,
}
impl Default for Rollback<'_> {
fn default() -> Self {
Self { noted: HashMap::new(), new_files: HashMap::new(), new_dirs: Vec::new() }
}
}
impl<'a> Rollback<'a> {
pub fn with_capacity(
note_capacity: usize,
new_files_capacity: usize,
new_dirs_capacity: usize,
) -> Self {
Self {
noted: HashMap::with_capacity(note_capacity),
new_files: HashMap::with_capacity(new_files_capacity),
new_dirs: Vec::with_capacity(new_dirs_capacity),
}
}
pub fn note_file(&mut self, original: &'a Path) -> Result<(), Error> {
if !original.is_file() {
return Err(Error::NotAFile(format!("{}", original.display())));
} else if self
.noted
.keys()
.any(|path| same_file::is_same_file(original, path).unwrap_or(false))
{
return Err(Error::AlreadyNoted(format!("{}", original.display())));
}
let temp_file = NamedTempFile::new()?;
std::fs::copy(original, &temp_file)?;
self.noted.insert(original, temp_file);
Ok(())
}
pub fn new_file(&mut self, path: &'a Path) -> Result<(), Error> {
if path.exists() {
return Err(Error::NewItemAlreadyExists(format!("{}", path.display())));
} else if self.new_files.contains_key(path) {
return Err(Error::AlreadyNoted(format!("{}", path.display())));
} else if path.extension().is_none() {
return Err(Error::NotAFile(format!("{}", path.display())));
}
self.new_files.insert(path, NamedTempFile::new()?);
Ok(())
}
pub fn new_dir(&mut self, path: &'a Path) -> Result<(), Error> {
if path.exists() {
return Err(Error::NewItemAlreadyExists(format!("{}", path.display())));
} else if self.new_dirs.contains(&path) {
return Err(Error::AlreadyNoted(format!("{}", path.display())));
} else if path.as_os_str().is_empty() || path.extension().is_some() {
return Err(Error::NotADir(format!("{}", path.display())))
}
self.new_dirs.push(path);
Ok(())
}
pub fn get_noted_file<P: AsRef<Path>>(&self, original: P) -> Option<&Path> {
self.noted.get(original.as_ref()).map_or_else(
|| {
self.noted.keys().find_map(|path| {
same_file::is_same_file(path, original.as_ref())
.ok()
.filter(|&same| same)
.and_then(|_| self.noted.get(path).map(|temp_file| temp_file.path()))
})
},
|temp_file| Some(temp_file.path()),
)
}
pub fn get_new_file<P: AsRef<Path>>(&self, path: P) -> Option<&Path> {
self.new_files.get(path.as_ref()).map(|temp_file| temp_file.path())
}
pub fn commit(self) -> Result<(), Error> {
let mut backups = Vec::with_capacity(self.noted.capacity());
match self.commit_noted_files(backups) {
Ok(computed_backups) => backups = computed_backups,
Err((err, backups)) => {
backups.into_iter().for_each(|backup| backup.rollback());
return Err(err);
},
}
if let Err(err) = self.commit_new_dirs() {
backups.into_iter().for_each(|backup| backup.rollback());
self.rollback_new_dirs();
return Err(err);
}
if let Err(err) = self.commit_new_files() {
backups.into_iter().for_each(|backup| backup.rollback());
self.rollback_new_files();
self.rollback_new_dirs();
return Err(err);
}
Ok(())
}
}