bakare 0.1.0

modern and simple, yet efficient backup solution
Documentation
pub mod in_memory {
    use std::path::Path;

    use crate::{
        backup,
        repository::{item::RepositoryItem, ItemId, Repository},
        restore,
        test::source::TestSource,
    };
    use anyhow::Result;
    use vfs::{MemoryFS, VfsFileType, VfsPath};

    use rand::Rng;

    pub fn random_in_memory_path(prefix: &str) -> Result<VfsPath> {
        let path: VfsPath = MemoryFS::new().into();
        let path = path.join(&format!("{}-{}", prefix, rand::thread_rng().gen::<u64>()))?;
        Ok(path)
    }

    pub fn assert_same_after_restore(source_path: &VfsPath) -> Result<()> {
        let repository_path: VfsPath = random_in_memory_path("repository")?;
        let restore_target: VfsPath = random_in_memory_path("target")?;

        assert_ne!(source_path, &repository_path);
        assert_ne!(repository_path, restore_target);

        Repository::init(&repository_path)?;
        {
            let mut backup_repository = Repository::open(&repository_path)?;
            let mut backup_engine = backup::Engine::new(source_path, &mut backup_repository)?;
            backup_engine.backup()?;
        }
        {
            let mut restore_repository = Repository::open(&repository_path)?;

            let mut restore_engine = restore::Engine::new(&mut restore_repository, &restore_target)?;
            restore_engine.restore_all()?;
        }

        assert_directory_trees_have_same_contents(source_path, &restore_target)?;
        Ok(())
    }

    pub fn assert_restored_file_contents(
        repository_path: &VfsPath,
        source_file_full_path: &VfsPath,
        contents: &[u8],
    ) -> Result<()> {
        let mut restore_repository = Repository::open(repository_path)?;
        let item = restore_repository.newest_item_by_source_path(&source_file_full_path)?;
        let restore_target = random_in_memory_path("target")?;
        let restore_engine = restore::Engine::new(&mut restore_repository, &restore_target)?;

        restore_engine.restore(&item.unwrap())?;
        let source_file_relative_path = Path::new(source_file_full_path.as_str()).strip_prefix("/")?;
        let restored_file_path = restore_target.join(&source_file_relative_path.to_string_lossy())?;
        assert_target_file_contents(&restored_file_path, contents)
    }

    pub fn assert_restored_from_version_has_contents(
        repository_path: &VfsPath,
        source_file_full_path: &VfsPath,
        old_contents: &[u8],
        old_id: &ItemId,
    ) -> Result<()> {
        let mut restore_repository = Repository::open(repository_path)?;
        let old_item = restore_repository.item_by_id(&old_id)?;
        let restore_target = random_in_memory_path("target")?;
        let restore_engine = restore::Engine::new(&mut restore_repository, &restore_target)?;
        restore_engine.restore(&old_item.unwrap())?;
        let source_file_relative_path = Path::new(source_file_full_path.as_str()).strip_prefix("/")?;
        let restored_file_path = restore_target.join(&source_file_relative_path.to_string_lossy())?;
        assert_target_file_contents(&restored_file_path, old_contents)
    }

    pub fn newest_item(repository_path: &VfsPath, source_file_full_path: &VfsPath) -> Result<RepositoryItem> {
        let item = {
            let reading_repository = Repository::open(repository_path)?;
            let item = reading_repository.newest_item_by_source_path(&source_file_full_path)?;
            assert!(item.is_some());
            item.unwrap()
        };
        Ok(item)
    }

    pub fn restore_all_from_reloaded_repository(repository_path: &VfsPath, restore_target: &VfsPath) -> Result<()> {
        {
            let mut restore_repository = Repository::open(repository_path)?;
            let mut restore_engine = restore::Engine::new(&mut restore_repository, &restore_target)?;
            restore_engine.restore_all()?;
            Ok(())
        }
    }

    pub fn backup_file_with_text_contents(
        source: &TestSource,
        repository_path: &VfsPath,
        source_file_relative_path: &str,
        contents: &str,
    ) -> Result<()> {
        {
            backup_file_with_byte_contents(source, repository_path, source_file_relative_path, contents.as_bytes())
        }
    }

    pub fn backup_file_with_byte_contents(
        source: &TestSource,
        repository_path: &VfsPath,
        source_file_relative_path: &str,
        contents: &[u8],
    ) -> Result<()> {
        {
            let mut backup_repository = Repository::open(repository_path)?;

            let mut backup_engine = backup::Engine::new(source.path(), &mut backup_repository)?;
            source.write_bytes_to_file(source_file_relative_path, contents).unwrap();
            backup_engine.backup()?;
            Ok(())
        }
    }

    pub fn data_weight(repository_path: &VfsPath) -> Result<u64> {
        {
            let repository = Repository::open(repository_path)?;
            Ok(repository.data_weight()?)
        }
    }

    fn assert_directory_trees_have_same_contents(left: &VfsPath, right: &VfsPath) -> Result<()> {
        let left_files = get_sorted_files_recursively(left)?;
        let right_files = get_sorted_files_recursively(right)?;

        let pairs = left_files.iter().zip(right_files);
        for (l, r) in pairs {
            assert_eq!(l.filename(), r.filename());
            let mut fl = l.open_file()?;
            let mut fr = r.open_file()?;
            let mut bl = vec![];
            let mut br = vec![];
            fl.read_to_end(&mut bl).unwrap();
            fr.read_to_end(&mut br).unwrap();
            assert_eq!(bl, br);
        }
        Ok(())
    }

    pub fn get_sorted_files_recursively(path: &VfsPath) -> Result<Vec<VfsPath>> {
        assert!(
            path.exists(),
            "[get_sorted_files_recursively] invoked on a path that does not exist: {:?}",
            path
        );
        let walker = path.walk_dir()?;

        let mut result = vec![];

        for maybe_entry in walker {
            let entry = &maybe_entry?;
            if entry == path {
                continue;
            }
            if entry.metadata()?.file_type == VfsFileType::File {
                result.push(entry.clone());
            }
        }

        result.sort_by_key(|a| a.filename());

        Ok(result)
    }

    fn assert_target_file_contents(restored_path: &VfsPath, expected_contents: &[u8]) -> Result<()> {
        let mut actual_contents = vec![];
        assert!(restored_path.exists(), "Expected '{:?}' to be there", restored_path);
        restored_path.open_file()?.read_to_end(&mut actual_contents)?;
        assert_eq!(expected_contents, actual_contents);
        Ok(())
    }
}