use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::io::{Read, Write};
use chrono::Utc;
use zip::write::SimpleFileOptions;
use zip::ZipWriter;
use crate::error::{Result, WalletError};
use crate::database::Database;
use crate::DATABASE_FILENAME;
use super::{BACKUP_PREFIX, BACKUP_AUTO, BACKUP_MANUAL, BACKUP_DATE_FORMAT};
pub fn create_backup(backup_folder: &Path, db: &Database, manual: bool) -> Result<PathBuf> {
db.checkpoint()?;
fs::create_dir_all(backup_folder)?;
let now = Utc::now();
let type_str = if manual { BACKUP_MANUAL } else { BACKUP_AUTO };
let filename = format!(
"{}-{}-{}.zip",
BACKUP_PREFIX,
now.format(BACKUP_DATE_FORMAT),
type_str
);
let backup_path = backup_folder.join(&filename);
let db_path = db.path();
let mut db_file = File::open(db_path)
.map_err(|e| WalletError::BackupError(format!("Failed to open database: {}", e)))?;
let mut db_data = Vec::new();
db_file.read_to_end(&mut db_data)
.map_err(|e| WalletError::BackupError(format!("Failed to read database: {}", e)))?;
let zip_file = File::create(&backup_path)
.map_err(|e| WalletError::BackupError(format!("Failed to create backup file: {}", e)))?;
let mut zip = ZipWriter::new(zip_file);
let options = SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Deflated)
.unix_permissions(0o644);
zip.start_file(DATABASE_FILENAME, options)
.map_err(|e| WalletError::BackupError(format!("Failed to add file to zip: {}", e)))?;
zip.write_all(&db_data)
.map_err(|e| WalletError::BackupError(format!("Failed to write to zip: {}", e)))?;
zip.finish()
.map_err(|e| WalletError::BackupError(format!("Failed to finalize zip: {}", e)))?;
Ok(backup_path)
}
pub fn create_backup_from_path(backup_folder: &Path, db_path: &Path, manual: bool) -> Result<PathBuf> {
if !db_path.exists() {
return Err(WalletError::BackupError(
format!("Database file not found: {}", db_path.display()),
));
}
fs::create_dir_all(backup_folder)?;
let now = Utc::now();
let type_str = if manual { BACKUP_MANUAL } else { BACKUP_AUTO };
let filename = format!(
"{}-{}-{}.zip",
BACKUP_PREFIX,
now.format(BACKUP_DATE_FORMAT),
type_str
);
let backup_path = backup_folder.join(&filename);
let mut db_file = File::open(db_path)
.map_err(|e| WalletError::BackupError(format!("Failed to open database: {}", e)))?;
let mut db_data = Vec::new();
db_file.read_to_end(&mut db_data)
.map_err(|e| WalletError::BackupError(format!("Failed to read database: {}", e)))?;
let zip_file = File::create(&backup_path)
.map_err(|e| WalletError::BackupError(format!("Failed to create backup file: {}", e)))?;
let mut zip = ZipWriter::new(zip_file);
let options = SimpleFileOptions::default()
.compression_method(zip::CompressionMethod::Deflated)
.unix_permissions(0o644);
zip.start_file(DATABASE_FILENAME, options)
.map_err(|e| WalletError::BackupError(format!("Failed to add file to zip: {}", e)))?;
zip.write_all(&db_data)
.map_err(|e| WalletError::BackupError(format!("Failed to write to zip: {}", e)))?;
zip.finish()
.map_err(|e| WalletError::BackupError(format!("Failed to finalize zip: {}", e)))?;
Ok(backup_path)
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn test_create_backup() {
let temp_dir = TempDir::new().unwrap();
let backup_dir = temp_dir.path().join("backups");
let db_path = temp_dir.path().join("nswallet.dat");
let db = Database::create(&db_path).unwrap();
let backup_path = create_backup(&backup_dir, &db, true).unwrap();
assert!(backup_path.exists());
assert!(backup_path.file_name().unwrap().to_str().unwrap().contains("manual"));
}
#[test]
fn test_create_auto_backup() {
let temp_dir = TempDir::new().unwrap();
let backup_dir = temp_dir.path().join("backups");
let db_path = temp_dir.path().join("nswallet.dat");
let db = Database::create(&db_path).unwrap();
let backup_path = create_backup(&backup_dir, &db, false).unwrap();
assert!(backup_path.exists());
assert!(backup_path.file_name().unwrap().to_str().unwrap().contains("auto"));
}
#[test]
fn test_create_backup_from_path() {
let temp_dir = TempDir::new().unwrap();
let backup_dir = temp_dir.path().join("backups");
let db_path = temp_dir.path().join("nswallet.dat");
std::fs::write(&db_path, b"fake database content").unwrap();
let backup_path = create_backup_from_path(&backup_dir, &db_path, true).unwrap();
assert!(backup_path.exists());
assert!(backup_path.file_name().unwrap().to_str().unwrap().contains("manual"));
let zip_file = File::open(&backup_path).unwrap();
let mut archive = zip::ZipArchive::new(zip_file).unwrap();
assert_eq!(archive.len(), 1);
let file = archive.by_name(DATABASE_FILENAME).unwrap();
assert!(file.size() > 0);
}
#[test]
fn test_create_backup_from_path_auto() {
let temp_dir = TempDir::new().unwrap();
let backup_dir = temp_dir.path().join("backups");
let db_path = temp_dir.path().join("nswallet.dat");
std::fs::write(&db_path, b"fake database content").unwrap();
let backup_path = create_backup_from_path(&backup_dir, &db_path, false).unwrap();
assert!(backup_path.exists());
assert!(backup_path.file_name().unwrap().to_str().unwrap().contains("auto"));
}
#[test]
fn test_create_backup_from_nonexistent_path() {
let temp_dir = TempDir::new().unwrap();
let backup_dir = temp_dir.path().join("backups");
let db_path = temp_dir.path().join("nonexistent.dat");
let result = create_backup_from_path(&backup_dir, &db_path, false);
assert!(result.is_err());
}
}