commit bb38e054799e64df56397a72dada3fb67b98293a Author: Joey Hines Date: Sat Jun 6 14:41:42 2020 -0500 Initial Commit + Handles overworld back up of regions + Needs to do player data backup and + Support for other world types needed as well diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..cb8f0d2 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,448 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" +dependencies = [ + "memchr", +] + +[[package]] +name = "albatross" +version = "0.1.0" +dependencies = [ + "chrono", + "clap", + "config", + "log", + "regex", + "serde 1.0.111", + "serde_derive", + "walkdir", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chrono" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" +dependencies = [ + "num-integer", + "num-traits 0.2.11", + "time", +] + +[[package]] +name = "clap" +version = "2.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "config" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9107d78ed62b3fa5a86e7d18e647abed48cfd8f8fab6c72f4cdb982d196f7e6" +dependencies = [ + "lazy_static 1.4.0", + "nom", + "rust-ini", + "serde 1.0.111", + "serde-hjson", + "serde_json", + "toml", + "yaml-rust", +] + +[[package]] +name = "hermit-abi" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91780f809e750b0a89f5544be56617ff6b1227ee485bcb06ebe10cdf89bd3b71" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" + +[[package]] +name = "lazy_static" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" + +[[package]] +name = "linked-hash-map" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" +dependencies = [ + "serde 0.8.23", + "serde_test", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "nom" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "num-integer" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba" +dependencies = [ + "autocfg", + "num-traits 0.2.11", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.11", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg", +] + +[[package]] +name = "proc-macro2" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quote" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54a21852a652ad6f610c9510194f398ff6f8692e334fd1145fed931f7fbe44ea" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + +[[package]] +name = "serde" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9124df5b40cbd380080b2cc6ab894c040a3070d995f5c9dc77e18c34a8ae37d" + +[[package]] +name = "serde-hjson" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b833c5ad67d52ced5f5938b2980f32a9c1c5ef047f0b4fb3127e7a423c76153" +dependencies = [ + "lazy_static 0.2.11", + "linked-hash-map 0.3.0", + "num-traits 0.1.43", + "regex", + "serde 0.8.23", +] + +[[package]] +name = "serde_derive" +version = "1.0.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2c3ac8e6ca1e9c80b8be1023940162bf81ae3cffbb1809474152f2ce1eb250" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993948e75b189211a9b31a7528f950c6adc21f9720b6438ff80a7fa2f864cea2" +dependencies = [ + "itoa", + "ryu", + "serde 1.0.111", +] + +[[package]] +name = "serde_test" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" +dependencies = [ + "serde 0.8.23", +] + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93a56fabc59dce20fe48b6c832cc249c713e7ed88fa28b0ee0a3bfcaae5fe4e2" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static 1.4.0", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "toml" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +dependencies = [ + "serde 1.0.111", +] + +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "winapi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "yaml-rust" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" +dependencies = [ + "linked-hash-map 0.5.3", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..b511bbb --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "albatross" +version = "0.1.0" +authors = ["Joey Hines "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = "2.33.0" +serde = "1.0.106" +serde_derive = "1.0.104" +config = "0.9" +log = "0.4.8" +chrono = "0.4" +walkdir = "2.3.1" +regex = "1.3.9" diff --git a/README.md b/README.md new file mode 100644 index 0000000..d14268f --- /dev/null +++ b/README.md @@ -0,0 +1,20 @@ +# Albatross +Back up what you care about in your Minecraft worlds. + +## Config +```toml +[backup] +# Minecraft sever directory +minecraft_dir = "/home/mc/server" +# Directory to place backups +output_dir = "/home/mc/backups" +# Number of backups to keep +backups_to_keep = 10 + +# Work config option +[[world_config]] +# world name +world_name = "world" +# world save radius (in blocks) +save_radius = 8000 +``` \ No newline at end of file diff --git a/src/albatross_config.rs b/src/albatross_config.rs new file mode 100644 index 0000000..ce569c0 --- /dev/null +++ b/src/albatross_config.rs @@ -0,0 +1,48 @@ +use config::{Config, ConfigError, File}; +use std::path::PathBuf; + +#[derive(Debug, Deserialize, Clone)] +pub enum WorldType { + END, + NETHER, + OVERWORLD, +} + +impl From for WorldType { + fn from(string: String) -> Self { + match string.as_str() { + "END" => WorldType::END, + "NETHER" => WorldType::NETHER, + _ => WorldType::OVERWORLD, + } + } +} + +#[derive(Debug, Deserialize, Clone)] +pub struct WorldConfig { + pub world_name: String, + pub save_radius: u64, + pub world_type: Option, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct BackupConfig { + pub minecraft_dir: PathBuf, + pub output_dir: PathBuf, + pub backups_to_keep: u64, +} + +#[derive(Debug, Deserialize, Clone)] +pub struct AlbatrossConfig { + pub backup: BackupConfig, + pub world_config: Option>, +} + +impl AlbatrossConfig { + pub fn new(config_path: &str) -> Result { + let mut cfg = Config::new(); + cfg.merge(File::with_name(config_path))?; + + cfg.try_into() + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..23c09a6 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,184 @@ +extern crate serde; + +#[macro_use] +extern crate serde_derive; + +use chrono::Utc; +use clap::{App, Arg}; +use regex::Regex; +use std::fs::{copy, create_dir, create_dir_all}; +use std::path::PathBuf; + +mod albatross_config; + +use albatross_config::{AlbatrossConfig, WorldConfig, WorldType}; + +struct Region { + x: i64, + y: i64, +} + +impl Region { + fn from_string(string: String) -> Option { + let re = Regex::new(r"r\.(?P-?[0-9])+\.(?P-?[0-9])").unwrap(); + if re.is_match(string.as_str()) { + let captures = re.captures(string.as_str()).unwrap(); + + return Some(Region { + x: captures["x"].parse::().unwrap(), + y: captures["y"].parse::().unwrap(), + }); + } + + None + } +} + +fn backup_dir( + dir_name: &str, + world_path: &PathBuf, + backup_path: &PathBuf, +) -> Result<(), std::io::Error> { + let mut src_dir = world_path.clone(); + src_dir.push(dir_name); + let mut backup_dir = backup_path.clone(); + backup_dir.push(dir_name); + create_dir(&backup_dir)?; + + for entry in src_dir.read_dir().unwrap() { + let entry = entry.unwrap(); + let mut target = backup_dir.clone(); + target.push(entry.file_name()); + + copy(entry.path(), target)?; + } + + Ok(()) +} + +fn backup_region( + dir_name: &str, + save_radius: u64, + world_path: &PathBuf, + backup_path: &PathBuf, +) -> Result<(), std::io::Error> { + let mut src_dir = world_path.clone(); + src_dir.push(dir_name); + let mut backup_dir = backup_path.clone(); + backup_dir.push(dir_name); + create_dir(&backup_dir)?; + + let save_radius = if save_radius < 512 { + 1 as i64 + } else { + (save_radius / 512) as i64 + }; + + for entry in src_dir.read_dir().unwrap() { + let entry = entry.unwrap(); + let file_name = entry.file_name().to_str().unwrap().to_string(); + + if let Some(region) = Region::from_string(file_name) { + if region.x.abs() < save_radius && region.y.abs() < save_radius { + let mut target = backup_dir.clone(); + target.push(entry.file_name()); + + copy(entry.path(), target)?; + } + } + } + + Ok(()) +} + +fn backup_world( + world_path: PathBuf, + backup_path: PathBuf, + world_config: &WorldConfig, +) -> Result<(), std::io::Error> { + let mut backup_path = backup_path.clone(); + backup_path.push(&world_config.world_name); + create_dir(backup_path.as_path())?; + + backup_region("poi", world_config.save_radius, &world_path, &backup_path)?; + backup_region( + "region", + world_config.save_radius, + &world_path, + &backup_path, + )?; + backup_dir("data", &world_path, &backup_path)?; + + Ok(()) +} + +fn backup_overworld( + world_path: PathBuf, + backup_path: PathBuf, + world_config: &WorldConfig, +) -> Result<(), std::io::Error> { + backup_world(world_path, backup_path, world_config)?; + Ok(()) +} + +fn do_backup(cfg: AlbatrossConfig) -> Result<(), std::io::Error> { + let server_base_dir = cfg.backup.minecraft_dir.clone(); + let worlds = cfg.world_config.unwrap().clone(); + let time_str = Utc::now().format("%d-%m-%y_%H:%M%S").to_string(); + let backup_name = format!("{}-backup", time_str); + let mut tmp_dir = cfg.backup.output_dir.clone(); + tmp_dir.push(backup_name); + + create_dir_all(tmp_dir.clone()).unwrap(); + + for world in worlds { + let mut world_dir = server_base_dir.clone(); + let world_name = world.world_name.clone(); + let world_type = match world.world_type.clone() { + Some(world_type) => world_type, + None => WorldType::OVERWORLD, + }; + world_dir.push(world_name.clone()); + + if world_dir.exists() && world_dir.is_dir() { + match world_type { + WorldType::OVERWORLD => { + backup_overworld(world_dir, tmp_dir.clone(), &world)?; + } + _ => {} + }; + } else { + println!("World \"{}\" not found", world_name.clone()); + } + } + + Ok(()) +} + +fn main() { + let mut app = App::new("Albatross").about("Backup your worlds").arg( + Arg::with_name("config") + .index(1) + .short("c") + .long("config") + .value_name("CONFIG_PATH") + .help("Config file path"), + ); + // Get arg parser + let matches = app.clone().get_matches(); + + if let Some(cfg_path) = matches.value_of("config") { + let cfg = AlbatrossConfig::new(cfg_path).expect("Config not found"); + + if cfg.world_config.is_some() { + match do_backup(cfg) { + Err(e) => println!("Error doing backup: {}", e), + _ => {} + } + } else { + println!("No worlds specified to backed up!") + } + } else { + app.print_help().expect("Unable to print help"); + } +}