Split into modules
This commit is contained in:
parent
8d9f605d67
commit
dc497ae9b3
5 changed files with 208 additions and 203 deletions
46
src/backup.rs
Normal file
46
src/backup.rs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
|
use walkdir::DirEntry;
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
pub struct BackupEngine<'a> {
|
||||||
|
source_path: &'a Path,
|
||||||
|
repository_path: &'a Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> BackupEngine<'a> {
|
||||||
|
pub fn new(source_path: &'a Path, repository_path: &'a Path) -> Self {
|
||||||
|
BackupEngine {
|
||||||
|
source_path,
|
||||||
|
repository_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn backup(&self) -> Result<(), io::Error> {
|
||||||
|
let walker = WalkDir::new(self.source_path);
|
||||||
|
for maybe_entry in walker {
|
||||||
|
let entry = maybe_entry?;
|
||||||
|
if entry.path() != self.source_path {
|
||||||
|
self.process_entry(&entry)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file_version(&self, path: &Path) -> u64 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_entry(&self, entry: &DirEntry) -> Result<(), io::Error> {
|
||||||
|
|
||||||
|
if entry.file_type().is_dir() {
|
||||||
|
fs::create_dir(self.repository_path.join(entry.file_name()))?;
|
||||||
|
}
|
||||||
|
if entry.file_type().is_file() {
|
||||||
|
fs::copy(entry.path(), self.repository_path.join(entry.file_name()))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
9
src/lib.rs
Normal file
9
src/lib.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
extern crate core;
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate dir_diff;
|
||||||
|
#[cfg(test)]
|
||||||
|
extern crate tempfile;
|
||||||
|
extern crate walkdir;
|
||||||
|
|
||||||
|
pub mod backup;
|
||||||
|
pub mod restore;
|
203
src/main.rs
203
src/main.rs
|
@ -1,206 +1,3 @@
|
||||||
extern crate core;
|
|
||||||
#[cfg(test)]
|
|
||||||
extern crate dir_diff;
|
|
||||||
#[cfg(test)]
|
|
||||||
extern crate tempfile;
|
|
||||||
extern crate walkdir;
|
|
||||||
|
|
||||||
use std::fs;
|
|
||||||
use std::io;
|
|
||||||
use std::path::Path;
|
|
||||||
use walkdir::DirEntry;
|
|
||||||
use walkdir::WalkDir;
|
|
||||||
|
|
||||||
struct BackupEngine<'a> {
|
|
||||||
source_path: &'a Path,
|
|
||||||
repository_path: &'a Path,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> BackupEngine<'a> {
|
|
||||||
fn new(source_path: &'a Path, repository_path: &'a Path) -> Self {
|
|
||||||
BackupEngine {
|
|
||||||
source_path,
|
|
||||||
repository_path,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn backup(&self) -> Result<(), io::Error> {
|
|
||||||
let walker = WalkDir::new(self.source_path);
|
|
||||||
for maybe_entry in walker {
|
|
||||||
let entry = maybe_entry?;
|
|
||||||
if entry.path() != self.source_path {
|
|
||||||
self.process_entry(&entry)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn file_version(&self, path: &Path) -> u64 {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_entry(&self, entry: &DirEntry) -> Result<(), io::Error> {
|
|
||||||
|
|
||||||
if entry.file_type().is_dir() {
|
|
||||||
fs::create_dir(self.repository_path.join(entry.file_name()))?;
|
|
||||||
}
|
|
||||||
if entry.file_type().is_file() {
|
|
||||||
fs::copy(entry.path(), self.repository_path.join(entry.file_name()))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct RestoreEngine<'a> {
|
|
||||||
repository_path: &'a Path,
|
|
||||||
target_path: &'a Path,
|
|
||||||
}
|
|
||||||
|
|
||||||
enum RestoreDescriptor {
|
|
||||||
All,
|
|
||||||
SpecificPath(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> RestoreEngine<'a> {
|
|
||||||
fn new(repository_path: &'a Path, target_path: &'a Path) -> Self {
|
|
||||||
RestoreEngine {
|
|
||||||
repository_path,
|
|
||||||
target_path,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn restore_all(&self) -> Result<(), io::Error> {
|
|
||||||
self.restore(RestoreDescriptor::All)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn restore(&self, what: RestoreDescriptor) -> Result<(), io::Error> {
|
|
||||||
self.restore_as_of_version(what, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn restore_as_of_version(&self, what: RestoreDescriptor, version: u64) -> Result<(), io::Error> {
|
|
||||||
let walker = WalkDir::new(self.repository_path);
|
|
||||||
for maybe_entry in walker {
|
|
||||||
match maybe_entry {
|
|
||||||
Ok(entry) => {
|
|
||||||
if entry.path() != self.repository_path {
|
|
||||||
self.process_entry(&entry)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(error) => return Err(error.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_entry(&self, entry: &DirEntry) -> Result<(), io::Error> {
|
|
||||||
if entry.file_type().is_dir() {
|
|
||||||
fs::create_dir(self.target_path.join(entry.file_name()))?;
|
|
||||||
}
|
|
||||||
if entry.file_type().is_file() {
|
|
||||||
fs::copy(entry.path(), self.target_path.join(entry.file_name()))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
|
||||||
mod bakare {
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod should {
|
|
||||||
|
|
||||||
use dir_diff::is_different;
|
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Error;
|
|
||||||
use std::io::Read;
|
|
||||||
use std::io::Write;
|
|
||||||
use std::path::Path;
|
|
||||||
use tempfile::tempdir;
|
|
||||||
use tempfile::TempDir;
|
|
||||||
use BackupEngine;
|
|
||||||
use RestoreDescriptor;
|
|
||||||
use RestoreDescriptor::SpecificPath;
|
|
||||||
use RestoreEngine;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn restore_backed_up_files() -> Result<(), Error> {
|
|
||||||
let source = Source::new()?;
|
|
||||||
let repository = tempdir()?;
|
|
||||||
|
|
||||||
source.write_text_to_file("first", "some contents");
|
|
||||||
source.write_text_to_file("second", "some contents");
|
|
||||||
source.write_text_to_file("third", "some other contents");
|
|
||||||
|
|
||||||
assert_same_after_restore(source.path(), repository.path())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn restore_older_version_of_file() -> Result<(), Error> {
|
|
||||||
let source = Source::new()?;
|
|
||||||
let repository = tempdir()?;
|
|
||||||
let backup_engine = BackupEngine::new(source.path(), repository.path());
|
|
||||||
let path = "some path";
|
|
||||||
let new_contents = "totally new contents";
|
|
||||||
let restore_target = tempdir()?;
|
|
||||||
let restore_engine = RestoreEngine::new(repository.path(), &restore_target.path());
|
|
||||||
let old_contents = "some old contents";
|
|
||||||
|
|
||||||
source.write_text_to_file(path, old_contents)?;
|
|
||||||
backup_engine.backup()?;
|
|
||||||
let old_version = backup_engine.file_version(path.as_ref());
|
|
||||||
|
|
||||||
source.write_text_to_file(path, new_contents)?;
|
|
||||||
backup_engine.backup()?;
|
|
||||||
|
|
||||||
restore_engine.restore_as_of_version(SpecificPath(path.into()), old_version)?;
|
|
||||||
|
|
||||||
assert_target_file_contents(restore_target.path(), path, old_contents)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: restore latest version by default
|
|
||||||
// TODO: deduplicate data
|
|
||||||
|
|
||||||
fn assert_target_file_contents(target: &Path, filename: &str, expected_contents: &str) -> Result<(), Error> {
|
|
||||||
let restored_path = target.join(filename);
|
|
||||||
let mut actual_contents = String::new();
|
|
||||||
File::open(restored_path)?.read_to_string(&mut actual_contents)?;
|
|
||||||
assert_eq!(expected_contents, actual_contents);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_same_after_restore(source_path: &Path, repository_path: &Path) -> Result<(), Error> {
|
|
||||||
let backup_engine = BackupEngine::new(source_path, repository_path);
|
|
||||||
backup_engine.backup()?;
|
|
||||||
|
|
||||||
let restore_target = tempdir()?;
|
|
||||||
let restore_engine = RestoreEngine::new(repository_path, &restore_target.path());
|
|
||||||
restore_engine.restore_all()?;
|
|
||||||
|
|
||||||
let are_source_and_target_different = is_different(source_path, &restore_target.path()).unwrap();
|
|
||||||
assert!(!are_source_and_target_different);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Source {
|
|
||||||
directory: TempDir,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Source {
|
|
||||||
fn new() -> Result<Self, Error> {
|
|
||||||
Ok(Self { directory: tempdir()? })
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_text_to_file(&self, filename: &str, text: &str) -> Result<(), Error> {
|
|
||||||
Ok(File::create(self.directory.path().join(filename))?.write_all(text.as_bytes())?)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn path(&self) -> &Path {
|
|
||||||
self.directory.path()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
58
src/restore.rs
Normal file
58
src/restore.rs
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
use walkdir::DirEntry;
|
||||||
|
use std::fs;
|
||||||
|
|
||||||
|
pub struct RestoreEngine<'a> {
|
||||||
|
repository_path: &'a Path,
|
||||||
|
target_path: &'a Path,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum RestoreDescriptor {
|
||||||
|
All,
|
||||||
|
SpecificPath(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RestoreEngine<'a> {
|
||||||
|
pub fn new(repository_path: &'a Path, target_path: &'a Path) -> Self {
|
||||||
|
RestoreEngine {
|
||||||
|
repository_path,
|
||||||
|
target_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore_all(&self) -> Result<(), io::Error> {
|
||||||
|
self.restore(RestoreDescriptor::All)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn restore(&self, what: RestoreDescriptor) -> Result<(), io::Error> {
|
||||||
|
self.restore_as_of_version(what, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn restore_as_of_version(&self, what: RestoreDescriptor, version: u64) -> Result<(), io::Error> {
|
||||||
|
let walker = WalkDir::new(self.repository_path);
|
||||||
|
for maybe_entry in walker {
|
||||||
|
match maybe_entry {
|
||||||
|
Ok(entry) => {
|
||||||
|
if entry.path() != self.repository_path {
|
||||||
|
self.process_entry(&entry)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(error) => return Err(error.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_entry(&self, entry: &DirEntry) -> Result<(), io::Error> {
|
||||||
|
if entry.file_type().is_dir() {
|
||||||
|
fs::create_dir(self.target_path.join(entry.file_name()))?;
|
||||||
|
}
|
||||||
|
if entry.file_type().is_file() {
|
||||||
|
fs::copy(entry.path(), self.target_path.join(entry.file_name()))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
95
tests/system_tests.rs
Normal file
95
tests/system_tests.rs
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
extern crate bakare;
|
||||||
|
extern crate tempfile;
|
||||||
|
extern crate dir_diff;
|
||||||
|
|
||||||
|
use dir_diff::is_different;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Error;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
|
use tempfile::tempdir;
|
||||||
|
use tempfile::TempDir;
|
||||||
|
use bakare::backup::BackupEngine;
|
||||||
|
use bakare::restore::RestoreDescriptor;
|
||||||
|
use bakare::restore::RestoreDescriptor::SpecificPath;
|
||||||
|
use bakare::restore::RestoreEngine;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn restore_backed_up_files() -> Result<(), Error> {
|
||||||
|
let source = Source::new()?;
|
||||||
|
let repository = tempdir()?;
|
||||||
|
|
||||||
|
source.write_text_to_file("first", "some contents");
|
||||||
|
source.write_text_to_file("second", "some contents");
|
||||||
|
source.write_text_to_file("third", "some other contents");
|
||||||
|
|
||||||
|
assert_same_after_restore(source.path(), repository.path())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn restore_older_version_of_file() -> Result<(), Error> {
|
||||||
|
let source = Source::new()?;
|
||||||
|
let repository = tempdir()?;
|
||||||
|
let backup_engine = BackupEngine::new(source.path(), repository.path());
|
||||||
|
let path = "some path";
|
||||||
|
let new_contents = "totally new contents";
|
||||||
|
let restore_target = tempdir()?;
|
||||||
|
let restore_engine = RestoreEngine::new(repository.path(), &restore_target.path());
|
||||||
|
let old_contents = "some old contents";
|
||||||
|
|
||||||
|
source.write_text_to_file(path, old_contents)?;
|
||||||
|
backup_engine.backup()?;
|
||||||
|
let old_version = backup_engine.file_version(path.as_ref());
|
||||||
|
|
||||||
|
source.write_text_to_file(path, new_contents)?;
|
||||||
|
backup_engine.backup()?;
|
||||||
|
|
||||||
|
restore_engine.restore_as_of_version(SpecificPath(path.into()), old_version)?;
|
||||||
|
|
||||||
|
assert_target_file_contents(restore_target.path(), path, old_contents)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: restore latest version by default
|
||||||
|
// TODO: deduplicate data
|
||||||
|
|
||||||
|
fn assert_target_file_contents(target: &Path, filename: &str, expected_contents: &str) -> Result<(), Error> {
|
||||||
|
let restored_path = target.join(filename);
|
||||||
|
let mut actual_contents = String::new();
|
||||||
|
File::open(restored_path)?.read_to_string(&mut actual_contents)?;
|
||||||
|
assert_eq!(expected_contents, actual_contents);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_same_after_restore(source_path: &Path, repository_path: &Path) -> Result<(), Error> {
|
||||||
|
let backup_engine = BackupEngine::new(source_path, repository_path);
|
||||||
|
backup_engine.backup()?;
|
||||||
|
|
||||||
|
let restore_target = tempdir()?;
|
||||||
|
let restore_engine = RestoreEngine::new(repository_path, &restore_target.path());
|
||||||
|
restore_engine.restore_all()?;
|
||||||
|
|
||||||
|
let are_source_and_target_different = is_different(source_path, &restore_target.path()).unwrap();
|
||||||
|
assert!(!are_source_and_target_different);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Source {
|
||||||
|
directory: TempDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Source {
|
||||||
|
fn new() -> Result<Self, Error> {
|
||||||
|
Ok(Self { directory: tempdir()? })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_text_to_file(&self, filename: &str, text: &str) -> Result<(), Error> {
|
||||||
|
Ok(File::create(self.directory.path().join(filename))?.write_all(text.as_bytes())?)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> &Path {
|
||||||
|
self.directory.path()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue