feat: restore and compare before deletion
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 11s

This commit is contained in:
2025-11-13 12:13:45 +01:00
parent ac912fea08
commit c3e4b276e9
2 changed files with 102 additions and 58 deletions

View File

@@ -1,47 +1,83 @@
// use sha2::{Digest, Sha256};
use std::path::Path;
use sha2::{Digest, Sha256};
use std::fs;
use std::io::{self, Read, Write};
use std::path::{Path, PathBuf};
// fn hash_file(path: &Path) -> Result<Vec<u8>, std::io::Error> {
// let mut file = fs::File::open(path)?;
// let mut hasher = Sha256::new();
// let mut buffer = [0; 4096];
/// Calculate hash sum of given file
fn hash_file(path: &Path) -> Result<Vec<u8>, std::io::Error> {
let mut file = fs::File::open(path)?;
let mut hasher = Sha256::new();
let mut buffer = [0; 4096];
// loop {
// let bytes_read = file.read(&mut buffer)?;
// if bytes_read == 0 {
// break;
// }
// hasher.update(&buffer[..bytes_read]);
// }
loop {
let bytes_read = file.read(&mut buffer)?;
if bytes_read == 0 {
break;
}
hasher.update(&buffer[..bytes_read]);
}
// Ok(hasher.finalize().to_vec())
// }
Ok(hasher.finalize().to_vec())
}
// fn compare_hashes(file1: &Path, file2: &Path) -> Result<bool, std::io::Error> {
// let hash1 = hash_file(file1)?;
// let hash2 = hash_file(file2)?;
// Ok(hash1 == hash2)
// }
/// Check if files differ by comparing hashes
fn files_differ(file1: &Path, file2: &Path) -> Result<bool, std::io::Error> {
if !file1.exists() || !file2.exists() {
return Ok(false); // treat non-existent files as "no difference"
}
Ok(hash_file(file1)? != hash_file(file2)?)
}
/// The function which backups the file
/// It erases any existing backup with the same name
fn set_bak_file_name(file: &Path) -> PathBuf {
file.with_file_name(format!("{}.bak", file.display()))
}
/// Backup the file
/// It overwrites any existing backup with the same name
pub fn backup_file(file: &Path) -> Result<(), std::io::Error> {
let mut bak_name = file.to_path_buf();
bak_name.set_extension("bak");
if let Err(e) = std::fs::copy(file, bak_name) {
let bak_name = set_bak_file_name(file);
fs::copy(file, bak_name)?;
Ok(())
}
/// Delete the backup file, optionally confirming if it differs from the original
pub fn delete_backup_file(file: &Path) -> Result<(), std::io::Error> {
let bak_name = set_bak_file_name(file);
if bak_name.exists() {
if files_differ(file, &bak_name)? {
print!(
"Backup '{}' differs from '{}'. Delete anyway? (y/N): ",
bak_name.display(),
file.display()
);
io::stdout().flush()?; // Make sure the prompt shows
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim().to_lowercase();
if input != "y" && input != "yes" {
println!("Deletion canceled.");
return Ok(());
}
}
fs::remove_file(&bak_name)?;
println!("Backup '{}' deleted.", bak_name.display());
} else {
println!("Backup '{}' does not exist.", bak_name.display());
}
Ok(())
}
/// Restore the backup file, erasing the actual file
pub fn restore_backup_file(file: &Path) -> Result<(), std::io::Error> {
let bak_name = set_bak_file_name(file);
if let Err(e) = std::fs::rename(bak_name, file) {
return Err(e);
} else {
return Ok(());
}
}
/// A function to delete the backup file
pub fn delete_backup_file(file: &Path) -> Result<(), std::io::Error> {
let mut bak_name = file.to_path_buf();
bak_name.set_extension("bak");
if let Err(e) = std::fs::remove_file(&bak_name) {
Err(e)
} else {
Ok(())
}
}

View File

@@ -1,38 +1,46 @@
use bak::{backup_file, delete_backup_file};
use clap::Parser;
use bak::{backup_file, delete_backup_file, restore_backup_file};
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use std::process;
#[derive(Parser)]
#[command(version, about, long_about = None)]
struct Opts {
/// Add this flag to delete the backup file
#[arg(short, long)]
delete: bool,
/// Optional subcommand (restore or delete). Default is backup
#[command(subcommand)]
command: Option<Commands>,
/// Specify the filename to backup
file: PathBuf,
/// File to backup (used if no subcommand is provided)
file: Option<PathBuf>,
}
fn error_handling(e: std::io::Error) {
match e.kind() {
std::io::ErrorKind::NotFound => eprintln!("File not found"),
std::io::ErrorKind::PermissionDenied => eprintln!("Cannot open file, check permissions"),
_ => eprintln!("Critical error: {}", e),
}
std::process::exit(1);
#[derive(Subcommand)]
enum Commands {
Res { file: PathBuf },
Del { file: PathBuf },
}
fn main() -> Result<(), std::io::Error> {
fn main() {
let opts = Opts::parse();
if opts.delete {
if let Err(e) = delete_backup_file(&opts.file) {
error_handling(e);
let result = match opts.command {
Some(Commands::Res { file }) => restore_backup_file(&file),
Some(Commands::Del { file }) => delete_backup_file(&file),
None => {
let file = opts
.file
.as_ref()
.expect("File must be specified for backup");
backup_file(file)
}
} else {
if let Err(e) = backup_file(&opts.file) {
error_handling(e);
};
if let Err(e) = result {
match e.kind() {
std::io::ErrorKind::NotFound => eprintln!("File not found"),
std::io::ErrorKind::PermissionDenied => eprintln!("Permission denied"),
_ => eprintln!("Critical error: {}", e),
}
process::exit(1);
}
Ok(())
}