diff --git a/CHANGELOG.md b/CHANGELOG.md index bef4cf9..d4de051 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,17 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -* Extract command for working with the output of export - -### Changed - -* Export command no longer reads backups that do not contribute to the final - state - -## [0.3.1](https://git.rustybever.be/Chewing_Bever/alex/src/tag/0.3.1) - -### Added - * Export command to export any backup as a new full backup ## [0.3.0](https://git.rustybever.be/Chewing_Bever/alex/src/tag/0.3.0) diff --git a/Cargo.lock b/Cargo.lock index 16ec2ca..bbe03a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,7 +10,7 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "alex" -version = "0.3.1" +version = "0.3.0" dependencies = [ "chrono", "clap", diff --git a/Cargo.toml b/Cargo.toml index 01d0593..4e3e88b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "alex" -version = "0.3.1" +version = "0.3.0" description = "Wrapper around Minecraft server processes, designed to complement Docker image installations." authors = ["Jef Roosens"] edition = "2021" diff --git a/src/backup/delta.rs b/src/backup/delta.rs index 21f626c..1bc5477 100644 --- a/src/backup/delta.rs +++ b/src/backup/delta.rs @@ -18,18 +18,11 @@ pub struct Delta { impl Delta { pub fn new() -> Self { Self { - added: State::new(), - removed: State::new(), + added: Default::default(), + removed: Default::default(), } } - /// Returns whether the delta is empty by checking whether both its added and removed state - /// return true for their `is_empty`. - #[allow(dead_code)] - pub fn is_empty(&self) -> bool { - self.added.is_empty() && self.removed.is_empty() - } - /// Calculate the union of this delta with another delta. /// /// The union of two deltas is a delta that produces the same state as if you were to apply diff --git a/src/backup/manager/mod.rs b/src/backup/manager/mod.rs index 7cbd43c..6a43f9f 100644 --- a/src/backup/manager/mod.rs +++ b/src/backup/manager/mod.rs @@ -226,16 +226,9 @@ where let enc = GzEncoder::new(tar_gz, Compression::default()); let mut ar = tar::Builder::new(enc); - // We only need to consider backups that have a non-empty contribution. - // This allows us to skip reading backups that have been completely - // overwritten by their successors anyways. - for (contribution, backup) in contributions - .iter() - .rev() - .zip(chain.iter().take(index + 1)) - .filter(|(contribution, _)| !contribution.is_empty()) + for (contribution, backup) in + contributions.iter().rev().zip(chain.iter().take(index + 1)) { - println!("{}", &backup); backup.append(&self.backup_dir, contribution, &mut ar)?; } diff --git a/src/backup/mod.rs b/src/backup/mod.rs index 6bd798f..8eecc0f 100644 --- a/src/backup/mod.rs +++ b/src/backup/mod.rs @@ -55,45 +55,6 @@ impl Backup<()> { let filename = format!("{}", start_time.format(Self::FILENAME_FORMAT)); backup_dir.join(filename) } - - /// Extract an archive. - /// - /// # Arguments - /// - /// * `backup_path` - Path to the archive to extract - /// * `dirs` - list of tuples `(path_in_tar, dst_dir)` with `dst_dir` the directory on-disk - /// where the files stored under `path_in_tar` inside the tarball should be extracted to. - pub fn extract_archive>( - archive_path: P, - dirs: &Vec<(PathBuf, PathBuf)>, - ) -> io::Result<()> { - let tar_gz = File::open(archive_path)?; - let enc = GzDecoder::new(tar_gz); - let mut ar = tar::Archive::new(enc); - - // Unpack each file by matching it with one of the destination directories and extracting - // it to the right path - for entry in ar.entries()? { - let mut entry = entry?; - let entry_path_in_tar = entry.path()?.to_path_buf(); - - for (path_in_tar, dst_dir) in dirs { - if entry_path_in_tar.starts_with(path_in_tar) { - let dst_path = - dst_dir.join(entry_path_in_tar.strip_prefix(path_in_tar).unwrap()); - - // Ensure all parent directories are present - std::fs::create_dir_all(dst_path.parent().unwrap())?; - - entry.unpack(dst_path)?; - - break; - } - } - } - - Ok(()) - } } impl Backup { @@ -238,8 +199,31 @@ impl Backup { backup_dir: P, dirs: &Vec<(PathBuf, PathBuf)>, ) -> io::Result<()> { - let backup_path = Backup::path(backup_dir, self.start_time); - Backup::extract_archive(backup_path, dirs)?; + let path = Backup::path(backup_dir, self.start_time); + let tar_gz = File::open(path)?; + let enc = GzDecoder::new(tar_gz); + let mut ar = tar::Archive::new(enc); + + // Unpack each file by matching it with one of the destination directories and extracting + // it to the right path + for entry in ar.entries()? { + let mut entry = entry?; + let entry_path_in_tar = entry.path()?.to_path_buf(); + + for (path_in_tar, dst_dir) in dirs { + if entry_path_in_tar.starts_with(path_in_tar) { + let dst_path = + dst_dir.join(entry_path_in_tar.strip_prefix(path_in_tar).unwrap()); + + // Ensure all parent directories are present + std::fs::create_dir_all(dst_path.parent().unwrap())?; + + entry.unpack(dst_path)?; + + break; + } + } + } // Remove any files for (path_in_tar, dst_dir) in dirs { diff --git a/src/backup/state.rs b/src/backup/state.rs index de4b2c0..4b09acf 100644 --- a/src/backup/state.rs +++ b/src/backup/state.rs @@ -41,14 +41,6 @@ impl State { path.starts_with(dir) && files.contains(path.strip_prefix(dir).unwrap()) }) } - - /// Returns whether the state is empty. - /// - /// Note that this does not necessarily mean that the state does not contain any sets, but - /// rather that any sets that it does contain are also empty. - pub fn is_empty(&self) -> bool { - self.0.values().all(|s| s.is_empty()) - } } impl From for State diff --git a/src/cli/backup.rs b/src/cli/backup.rs index 6810e42..3a993fc 100644 --- a/src/cli/backup.rs +++ b/src/cli/backup.rs @@ -9,30 +9,13 @@ use std::path::{Path, PathBuf}; #[derive(Subcommand)] pub enum BackupCommands { /// List all tracked backups - /// - /// Note that this will only list backups for the layers currently configured, and will ignore - /// any other layers also present in the backup directory. List(BackupListArgs), /// Manually create a new backup - /// - /// Note that backups created using this command will count towards the length of a chain, and - /// can therefore shorten how far back in time your backups will be stored. Create(BackupCreateArgs), /// Restore a backup - /// - /// This command will restore the selected backup by extracting its entire chain up to and - /// including the requested backup in-order. Restore(BackupRestoreArgs), /// Export a backup into a full archive - /// - /// Just like the restore command, this will extract each backup from the chain up to and - /// including the requested backup, but instead of writing the files to disk, they will be - /// recompressed into a new tarball, resulting in a new tarball containing a full backup. Export(BackupExportArgs), - /// Extract an archive file, which is assumed to be a full backup. - /// - /// This command mostly exists as a convenience method for working with the output of `export`. - Extract(BackupExtractArgs), } #[derive(Args)] @@ -62,9 +45,6 @@ pub struct BackupRestoreArgs { /// Directory to store worlds in output_worlds: PathBuf, /// Whether to overwrite the contents of the output directories - /// - /// If set, the output directories will be completely cleared before trying to restore the - /// backup. #[arg(short, long, default_value_t = false)] force: bool, /// Create output directories if they don't exist @@ -83,25 +63,6 @@ pub struct BackupExportArgs { make: bool, } -#[derive(Args)] -pub struct BackupExtractArgs { - /// Path to the backup to extract - path: PathBuf, - /// Directory to store config in - output_config: PathBuf, - /// Directory to store worlds in - output_worlds: PathBuf, - /// Whether to overwrite the contents of the output directories - /// - /// If set, the output directories will be completely cleared before trying to restore the - /// backup. - #[arg(short, long, default_value_t = false)] - force: bool, - /// Create output directories if they don't exist - #[arg(short, long, default_value_t = false)] - make: bool, -} - impl BackupArgs { pub fn run(&self, cli: &Cli) -> io::Result<()> { match &self.command { @@ -109,7 +70,6 @@ impl BackupArgs { BackupCommands::List(args) => args.run(cli), BackupCommands::Restore(args) => args.run(cli), BackupCommands::Export(args) => args.run(cli), - BackupCommands::Extract(args) => args.run(cli), } } } @@ -259,44 +219,3 @@ impl BackupExportArgs { } } } - -impl BackupExtractArgs { - pub fn run(&self, _cli: &Cli) -> io::Result<()> { - // Create directories if needed - if self.make { - std::fs::create_dir_all(&self.output_config)?; - std::fs::create_dir_all(&self.output_worlds)?; - } - - let output_config = self.output_config.canonicalize()?; - let output_worlds = self.output_worlds.canonicalize()?; - let backup_path = self.path.canonicalize()?; - - // Clear previous contents of directories - let mut entries = output_config - .read_dir()? - .chain(output_worlds.read_dir()?) - .peekable(); - - if entries.peek().is_some() && !self.force { - return Err(other("Output directories are not empty. If you wish to overwrite these contents, use the force flag.")); - } - - for entry in entries { - let path = entry?.path(); - - if path.is_dir() { - std::fs::remove_dir_all(path)?; - } else { - std::fs::remove_file(path)?; - } - } - - let dirs = vec![ - (PathBuf::from("config"), output_config), - (PathBuf::from("worlds"), output_worlds), - ]; - - Backup::extract_archive(backup_path, &dirs) - } -}