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"); } }