Skip to content

Commit

Permalink
Merge pull request #4 from Tom-ne/load-backup
Browse files Browse the repository at this point in the history
Load backup
  • Loading branch information
Tom-ne authored Jul 30, 2023
2 parents 06c20d8 + 676d66b commit 3af4934
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 63 deletions.
66 changes: 4 additions & 62 deletions src/commands/cmd/create_backup.rs
Original file line number Diff line number Diff line change
@@ -1,73 +1,16 @@
use std::{
fs::{self, File},
io::{Read, Write},
os::unix::prelude::PermissionsExt,
path::Path,
};
use std::{fs, path::Path};

use async_trait::async_trait;
use chrono::{DateTime, Datelike, Local, Timelike};
use zip::{write::FileOptions, CompressionMethod, ZipWriter};

use crate::lib::modify::{command::Command, config_helper::read_config};
use crate::lib::modify::{command::Command, config_helper::read_config, backup::backup_helper::{get_backup_dir, zip_folder}};

pub struct CreateBackupCommand;

fn zip_folder(input_folder: &Path, output_zip: &Path) -> Result<(), Box<dyn std::error::Error>> {
let zip_file = File::create(output_zip)?;
let mut zip = ZipWriter::new(zip_file);

let options = FileOptions::default()
.compression_method(CompressionMethod::Stored)
.unix_permissions(fs::metadata(input_folder)?.permissions().mode());

zip_folder_recursive(&input_folder, &mut zip, &input_folder, options)?;

Ok(())
}

fn zip_folder_recursive(
input_folder: &Path,
zip: &mut ZipWriter<File>,
base_folder: &Path,
options: FileOptions,
) -> Result<(), Box<dyn std::error::Error>> {
for entry in fs::read_dir(input_folder)? {
let entry = entry?;
let path = entry.path();
let name = path
.strip_prefix(base_folder)?
.to_string_lossy()
.into_owned();

if path.is_dir() {
zip.add_directory(name, options)?;
zip_folder_recursive(&path, zip, base_folder, options)?;
} else {
zip.start_file(name, options)?;
let mut file = File::open(&path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
zip.write_all(&buffer)?;
}
}

Ok(())
}

#[async_trait]
impl Command for CreateBackupCommand {
async fn run(&self) {
let settings = read_config().unwrap();
let mc_folder: &Path;

let path = Path::new(&settings.mc_mod_dir);
if let Some(parent) = path.parent() {
mc_folder = parent;
} else {
println!("Error: unable to get minecraft folder!");
return;
}

let local_time: DateTime<Local> = Local::now();
let formatted_time = format!(
Expand All @@ -79,10 +22,9 @@ impl Command for CreateBackupCommand {
local_time.year()
);

let backup_folder_str = format!("{}/{}", mc_folder.display(), "mod-backups");
let backup_folder = Path::new(&backup_folder_str);
let backup_folder = get_backup_dir();

if let Err(e) = fs::create_dir_all(backup_folder) {
if let Err(e) = fs::create_dir_all(backup_folder.clone()) {
println!("Error creating backup folder: {:?}", e);
return;
}
Expand Down
159 changes: 159 additions & 0 deletions src/commands/cmd/load_backup.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
use std::{
fs::{self},
path::{Path, PathBuf},
};

use async_trait::async_trait;
use chrono::{Local, NaiveDate, NaiveDateTime, NaiveTime, TimeZone};

use crate::lib::{
io::io_helper::{flush_output_stream, get_user_input},
modify::{
backup::backup_helper::{get_backup_dir, unzip_file},
command::Command,
config_helper::read_config,
},
};

pub struct LoadBackupCommand;

struct BackupInfo {
timestamp: NaiveDateTime,
path: PathBuf,
}

fn list_zip_files(dir_path: &Path) -> Vec<PathBuf> {
fs::read_dir(dir_path)
.unwrap()
.filter_map(|entry| {
let entry = entry.unwrap();
let path = entry.path();
if path.is_file() && path.extension().unwrap_or_default() == "zip" {
Some(path)
} else {
None
}
})
.collect()
}

fn parse_timestamp_from_filename(file_path: &Path) -> NaiveDateTime {
let file_name = file_path.file_stem().unwrap().to_string_lossy();
let parts: Vec<&str> = file_name.split('-').collect();

let year: i32 = parts[4].parse().unwrap();
let month: u32 = parts[3].parse().unwrap();
let day: u32 = parts[2].parse().unwrap();
let hour: u32 = parts[0].parse().unwrap();
let minute: u32 = parts[1].parse().unwrap();

NaiveDateTime::new(
NaiveDate::from_ymd_opt(year, month, day)
.unwrap_or_else(|| NaiveDate::from_ymd(1970, 1, 1)),
NaiveTime::from_hms_opt(hour, minute, 0).unwrap_or_else(|| NaiveTime::from_hms(0, 0, 0)),
)
}

fn remove_directory_contents(dir_path: &str) -> Result<(), std::io::Error> {
if !fs::metadata(dir_path)?.is_dir() {
return Err(std::io::Error::new(
std::io::ErrorKind::NotFound,
"Directory not found",
));
}

for entry in fs::read_dir(dir_path)? {
let entry = entry?;
let path = entry.path();

if path.is_dir() {
remove_directory_contents(&path.to_string_lossy())?;
} else {
fs::remove_file(&path)?;
}
}

Ok(())
}

#[async_trait]
impl Command for LoadBackupCommand {
async fn run(&self) {
let backup_folder = get_backup_dir();

if !backup_folder.exists() {
println!("You do not have any backups to restore from!");
return;
}

let zip_files = list_zip_files(&backup_folder);
let mut backup_files_with_timestamp: Vec<_> = zip_files
.iter()
.map(|file| (file.clone(), parse_timestamp_from_filename(file)))
.collect();

backup_files_with_timestamp.sort_by(|a, b| b.1.cmp(&a.1));

let backups: Vec<BackupInfo> = backup_files_with_timestamp
.into_iter()
.map(|(path, timestamp)| BackupInfo { timestamp, path })
.collect();

for (backup_number, backup_info) in backups.iter().enumerate() {
let local_time = Local
.from_local_datetime(&backup_info.timestamp)
.single()
.unwrap();
println!(
"Backup {}: {} at {}",
backup_number + 1,
local_time.format("%Y-%m-%d"),
local_time.format("%H:%M:%S")
);
}

loop {
print!("Select backup number to restore from: ");
flush_output_stream();
let input = get_user_input().to_lowercase();

if let Ok(backup_index) = input.parse::<usize>() {
if backup_index > 0 && backup_index <= backups.len() {
// Now, you have the selected backup index in 'backup_index', which is a valid backup number.
// You can use this index to retrieve the corresponding backup information from the 'backups' vector.
let selected_backup = backups.get(backup_index - 1).unwrap();
println!("Selected backup: {:?}", selected_backup.path);

print!("This action will remove all mods from your current mods folder, would you like to continue (Y/n)? ");
flush_output_stream();
let should_continue = get_user_input().contains('Y');

if should_continue {
let settings = read_config().unwrap();
println!("removing contents of old mods directory...");
let _ = remove_directory_contents(&settings.mc_mod_dir);

println!("loading backup...");
let _ = unzip_file(
selected_backup.path.to_str().unwrap(),
&settings.mc_mod_dir,
);
println!("loaded backup successfully");
} else {
println!("Ok, I won't do anything.");
}

break;
} else {
println!("Invalid backup number. Please select a valid backup number.");
}
} else {
println!("Invalid input. Please enter a number for the backup selection.");
}
}
}

fn description(&self) -> &str {
"load mods from backup"
}
}
1 change: 1 addition & 0 deletions src/commands/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod create_backup;
pub mod load_backup;
pub mod clear;
pub mod help;
pub mod install;
Expand Down
100 changes: 100 additions & 0 deletions src/lib/modify/backup/backup_helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
use std::{
fs::{self, File},
io::{Read, Write, copy},
os::unix::prelude::PermissionsExt,
path::{Path, PathBuf},
};
use zip::{write::FileOptions, CompressionMethod, ZipArchive, ZipWriter};

use crate::lib::modify::config_helper::read_config;

pub(crate) fn get_backup_dir() -> PathBuf {
let settings = read_config().unwrap();
let mc_folder: &Path;

let path = Path::new(&settings.mc_mod_dir);
if let Some(parent) = path.parent() {
mc_folder = parent;
} else {
println!("Unable to get minecraft directory!");
// Return a default backup directory path or any other appropriate path.
return PathBuf::from(&settings.mc_mod_dir);
}

let backup_folder_str = format!("{}/{}", mc_folder.display(), "mod-backups");
PathBuf::from(backup_folder_str)
}

pub(crate) fn zip_folder(
input_folder: &Path,
output_zip: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let zip_file = File::create(output_zip)?;
let mut zip = ZipWriter::new(zip_file);

let options = FileOptions::default()
.compression_method(CompressionMethod::Stored)
.unix_permissions(fs::metadata(input_folder)?.permissions().mode());

zip_folder_recursive(&input_folder, &mut zip, &input_folder, options)?;

Ok(())
}

pub(crate) fn unzip_file(
zip_file_path: &str,
output_dir: &str,
) -> Result<(), Box<dyn std::error::Error>> {
// Open the ZIP archive
let zip_file = File::open(zip_file_path)?;
let mut zip_archive = ZipArchive::new(zip_file)?;

// Create the output directory if it doesn't exist
std::fs::create_dir_all(output_dir)?;

// Extract each file from the ZIP archive
for i in 0..zip_archive.len() {
let mut file = zip_archive.by_index(i)?;
let file_name = file.sanitized_name();

// Create the output file path
let output_path = format!("{}/{}", output_dir, file_name.to_string_lossy());

// Create the output file
let mut output_file = File::create(&output_path)?;

// Read the file data from the ZIP archive and write it to the output file
copy(&mut file, &mut output_file)?;
}

Ok(())
}

fn zip_folder_recursive(
input_folder: &Path,
zip: &mut ZipWriter<File>,
base_folder: &Path,
options: FileOptions,
) -> Result<(), Box<dyn std::error::Error>> {
for entry in fs::read_dir(input_folder)? {
let entry = entry?;
let path = entry.path();
let name = path
.strip_prefix(base_folder)?
.to_string_lossy()
.into_owned();

if path.is_dir() {
zip.add_directory(name, options)?;
zip_folder_recursive(&path, zip, base_folder, options)?;
} else {
zip.start_file(name, options)?;
let mut file = File::open(&path)?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer)?;
zip.write_all(&buffer)?;
}
}

Ok(())
}
1 change: 1 addition & 0 deletions src/lib/modify/backup/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod backup_helper;
3 changes: 2 additions & 1 deletion src/lib/modify/command_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use crate::{
cmd::{
clear::ClearCommand, help::HelpCommand, install::InstallCommand,
list_mods::ListCommand, quit::QuitCommand, search::SearchCommand,
uninstall::UninstallCommand, create_backup::CreateBackupCommand,
uninstall::UninstallCommand, create_backup::CreateBackupCommand, load_backup::LoadBackupCommand,
},
configuration::{edit_config::EditConfigCommand, print_config::PrintConfigCommand},
},
Expand All @@ -25,6 +25,7 @@ pub(crate) fn create_command_handler() -> IndexMap<&'static str, Box<dyn Command
dispatcher.insert("clear", Box::new(ClearCommand));
dispatcher.insert("h", Box::new(HelpCommand));
dispatcher.insert("cb", Box::new(CreateBackupCommand));
dispatcher.insert("lb", Box::new(LoadBackupCommand));
dispatcher.insert("q", Box::new(QuitCommand));

// Add more commands here
Expand Down
1 change: 1 addition & 0 deletions src/lib/modify/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ pub mod command;
pub mod command_handler;
pub mod config_helper;
pub mod modify_settings;
pub mod backup;

0 comments on commit 3af4934

Please sign in to comment.