Added the ability to restore chunks from a backup

+ Added `restore` subcommand
+ Needs more testing, but appears to be working
backup_error_fix
Joey Hines 2020-11-03 21:46:16 -06:00
parent b9aad433ba
commit a94d0221c5
5 changed files with 176 additions and 11 deletions

58
Cargo.lock generated
View File

@ -32,8 +32,9 @@ dependencies = [
[[package]] [[package]]
name = "albatross" name = "albatross"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"anvil-region",
"chrono", "chrono",
"config", "config",
"discord-hooks-rs", "discord-hooks-rs",
@ -55,6 +56,18 @@ dependencies = [
"winapi 0.3.8", "winapi 0.3.8",
] ]
[[package]]
name = "anvil-region"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8e1613ef793128ec9fb0bacd879a09bc5590e9757d81eb3104b2fd08fb5c59f"
dependencies = [
"bitvec",
"byteorder",
"log",
"named-binary-tag",
]
[[package]] [[package]]
name = "atty" name = "atty"
version = "0.2.14" version = "0.2.14"
@ -98,12 +111,28 @@ version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "bitvec"
version = "0.17.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c"
dependencies = [
"either",
"radium",
]
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.4.0" version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "0.5.4" version = "0.5.4"
@ -211,6 +240,12 @@ version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3" checksum = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]] [[package]]
name = "encoding_rs" name = "encoding_rs"
version = "0.8.23" version = "0.8.23"
@ -562,9 +597,9 @@ checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.8" version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
dependencies = [ dependencies = [
"cfg-if 0.1.10", "cfg-if 0.1.10",
] ]
@ -647,6 +682,17 @@ dependencies = [
"ws2_32-sys", "ws2_32-sys",
] ]
[[package]]
name = "named-binary-tag"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ae8ccc227f1ca068299ad32bcac0da2d2cf90b1b51c4679d0cc30900703ba60"
dependencies = [
"byteorder",
"flate2",
"linked-hash-map 0.5.3",
]
[[package]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.4" version = "0.2.4"
@ -861,6 +907,12 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "radium"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.7.3" version = "0.7.3"

View File

@ -1,6 +1,6 @@
[package] [package]
name = "albatross" name = "albatross"
version = "0.2.0" version = "0.3.0"
authors = ["Joey Hines <joey@ahines.net>"] authors = ["Joey Hines <joey@ahines.net>"]
edition = "2018" edition = "2018"
@ -17,3 +17,4 @@ flate2 = "1.0.14"
tar = "0.4.28" tar = "0.4.28"
reqwest = { version = "0.10", features = ["blocking", "json"] } reqwest = { version = "0.10", features = ["blocking", "json"] }
discord-hooks-rs = { git = "https://github.com/joeyahines/discord-hooks-rs" } discord-hooks-rs = { git = "https://github.com/joeyahines/discord-hooks-rs" }
anvil-region = "0.4.0"

View File

@ -198,6 +198,16 @@ pub fn compress_backup(tmp_dir: &PathBuf, output_file: &PathBuf) -> Result<(), s
Ok(()) Ok(())
} }
pub fn uncompress_backup(backup: &PathBuf) -> Result<PathBuf, std::io::Error> {
let backup_file = File::open(backup)?;
let dec = GzDecoder::new(backup_file);
let mut extract = Archive::new(dec);
let extract_path = PathBuf::from("tmp");
extract.unpack(&extract_path)?;
Ok(extract_path)
}
/// Takes an existing backup and converts it to a singleplayer world /// Takes an existing backup and converts it to a singleplayer world
/// ///
/// # Param /// # Param
@ -209,11 +219,8 @@ pub fn convert_backup_to_sp(
backup: &PathBuf, backup: &PathBuf,
output: &PathBuf, output: &PathBuf,
) -> Result<(), std::io::Error> { ) -> Result<(), std::io::Error> {
let backup_file = File::open(backup)?;
let dec = GzDecoder::new(backup_file); let extract_path = uncompress_backup(backup)?;
let mut extract = Archive::new(dec);
let extract_path = PathBuf::from("tmp");
extract.unpack(&extract_path)?;
if let Some(worlds) = &config.world_config { if let Some(worlds) = &config.world_config {
for world in worlds { for world in worlds {

View File

@ -5,9 +5,11 @@ mod backup;
mod config; mod config;
mod discord; mod discord;
mod region; mod region;
mod restore;
use crate::backup::{convert_backup_to_sp, do_backup}; use crate::backup::{convert_backup_to_sp, do_backup};
use crate::config::AlbatrossConfig; use crate::config::AlbatrossConfig;
use crate::restore::{restore_range_from_backup, restore_chunk_from_backup};
#[derive(Debug, StructOpt)] #[derive(Debug, StructOpt)]
#[structopt(about = "Backup your Minecraft Server!")] #[structopt(about = "Backup your Minecraft Server!")]
@ -24,13 +26,13 @@ struct Albatross {
enum SubCommand { enum SubCommand {
/// Backup a server /// Backup a server
Backup { Backup {
/// Output location override /// Output path
#[structopt(short = "o", long = "ouptut", parse(from_os_str))] #[structopt(short = "o", long = "ouptut", parse(from_os_str))]
output: Option<PathBuf>, output: Option<PathBuf>,
}, },
/// Export a backup as a single player world /// Export a backup as a single player world
Export { Export {
/// Convert backup to singleplayer world /// Backup to convert
#[structopt(parse(from_os_str))] #[structopt(parse(from_os_str))]
input_backup: PathBuf, input_backup: PathBuf,
@ -38,6 +40,19 @@ enum SubCommand {
#[structopt(parse(from_os_str))] #[structopt(parse(from_os_str))]
output: PathBuf, output: PathBuf,
}, },
/// Restore certain chunks from a backup
Restore {
/// Output server directory
#[structopt(short, long, parse(from_os_str))]
server_directory: Option<PathBuf>,
/// World to restore
world_name: String,
/// Backup to restore from
#[structopt(parse(from_os_str))]
backup_path: PathBuf,
/// Backup range can be a single chunk coordinate pair or a chunk range
chunk_range: Vec<i32>,
},
} }
fn main() { fn main() {
@ -65,6 +80,38 @@ fn main() {
Err(e) => println!("Error exporting backup: {:?}", e), Err(e) => println!("Error exporting backup: {:?}", e),
}; };
} }
SubCommand::Restore {
server_directory,
world_name,
backup_path,
chunk_range
} => {
println!("Starting restore");
let server_directory = match server_directory {
Some(dir) => dir,
None => cfg.backup.minecraft_dir
};
if chunk_range.len() > 2 {
let lower_x = chunk_range[0];
let lower_z = chunk_range[1];
let upper_x = chunk_range[2];
let upper_z = chunk_range[3];
match restore_range_from_backup(world_name.as_str(), lower_x, upper_x, lower_z, upper_z, &backup_path, &server_directory) {
Ok(count) => println!("Restored {} chunks!", count),
Err(e) => println!("Error restoring backup: {:?}", e),
};
}
else if chunk_range.len() == 2 {
let x = chunk_range[0];
let z = chunk_range[1];
match restore_chunk_from_backup(world_name.as_str(), x, z, &backup_path, &server_directory) {
Ok(_) => println!("Restored chunk!"),
Err(e) => println!("Error restoring backup: {:?}", e),
};
}
}
} }
} else { } else {
println!("No worlds specified in config file!") println!("No worlds specified in config file!")

58
src/restore.rs 100644
View File

@ -0,0 +1,58 @@
use anvil_region::AnvilChunkProvider;
use std::path::PathBuf;
use crate::backup::uncompress_backup;
use std::error;
use std::fs::remove_dir_all;
struct ChunkAccess {
src_path: PathBuf,
dest_path: PathBuf,
}
impl ChunkAccess {
pub fn new(world_name: &str, src_path: &PathBuf, dest_path: &PathBuf) -> Result<Self, std::io::Error> {
let src_path = uncompress_backup(src_path)?.join(world_name).join("region");
let dest_path= dest_path.join("region");
Ok(ChunkAccess {
src_path,
dest_path,
})
}
pub fn copy_chunk(&self, x:i32, z:i32) {
let src_provider = AnvilChunkProvider::new(self.src_path.to_str().unwrap());
let dest_provider = AnvilChunkProvider::new(self.dest_path.to_str().unwrap());
let chunk = src_provider.load_chunk(x, z).expect("Unable to load chunk");
dest_provider.save_chunk(x, z, chunk).expect("Unable to save chunk");
}
pub fn cleanup(self) -> Result<(), std::io::Error> {
remove_dir_all("tmp")
}
}
pub fn restore_range_from_backup(world_name: &str, lower_x: i32, upper_x: i32, lower_z: i32, upper_z: i32, backup_path: &PathBuf, minecraft_dir: &PathBuf) -> Result<u64, Box<dyn error::Error>> {
let chunk_access = ChunkAccess::new(world_name, backup_path, minecraft_dir)?;
let mut count = 0;
for x in lower_x..upper_x {
for z in lower_z..upper_z {
chunk_access.copy_chunk(x, z);
count += 1;
}
}
chunk_access.cleanup()?;
Ok(count)
}
pub fn restore_chunk_from_backup(world_name: &str, x: i32, z: i32, backup_path: &PathBuf, minecraft_dir: &PathBuf) -> Result<(), Box<dyn error::Error>> {
let chunk_access = ChunkAccess::new(world_name, backup_path, minecraft_dir)?;
chunk_access.copy_chunk(x, z);
chunk_access.cleanup()?;
Ok(())
}