feat: restore and compare before deletion
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 11s
All checks were successful
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 11s
This commit is contained in:
108
src/lib.rs
108
src/lib.rs
@@ -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(())
|
||||
}
|
||||
}
|
||||
|
||||
52
src/main.rs
52
src/main.rs
@@ -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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user