SXXXXXXX_BackupTools/backuptools/core/backup_operations.py
2025-05-07 14:02:56 +02:00

85 lines
3.7 KiB
Python

# BackupApp/backup_app/core/backup_operations.py
import zipfile
import os
from pathlib import Path
from typing import List, Tuple, Callable, Optional
# Tuple format for file details: (filename: str, size_mb: float, full_path: str)
FileDetail = Tuple[str, float, str]
class BackupError(Exception):
"""Custom exception for errors during backup creation."""
pass
def create_backup_archive(
zip_file_path_str: str,
source_root_dir_str: str,
included_file_details: List[FileDetail],
backup_description: str,
progress_callback: Optional[Callable[[float, float, str], None]] = None
) -> None:
"""
Creates a ZIP archive with the specified files and description.
Args:
zip_file_path_str: Full path for the output ZIP file.
source_root_dir_str: The root directory from which files were sourced.
Used to determine the relative path in the archive.
included_file_details: A list of tuples (filename, size_mb, full_path)
for files to include in the backup.
backup_description: A string description to be saved inside the ZIP archive.
progress_callback: An optional function to call for progress updates.
It receives (processed_size_mb, total_size_mb, arcname).
Raises:
BackupError: If an error occurs during ZIP creation.
ValueError: If inputs are invalid (e.g., no files to backup).
"""
zip_file_path = Path(zip_file_path_str)
source_root_path = Path(source_root_dir_str)
if not included_file_details:
raise ValueError("No files provided to include in the backup.")
if not zip_file_path.parent.exists():
try:
zip_file_path.parent.mkdir(parents=True, exist_ok=True)
except OSError as e:
raise BackupError(f"Failed to create destination directory {zip_file_path.parent}: {e}")
total_size_to_zip_mb = sum(size for _, size, _ in included_file_details)
processed_size_mb = 0.0
try:
with zipfile.ZipFile(zip_file_path, 'w', zipfile.ZIP_DEFLATED, compresslevel=6) as backup_zip:
# 1. Add files to be backed up
for _, size_mb, full_file_path_str in included_file_details:
full_file_path = Path(full_file_path_str)
# arcname is the path as it will appear inside the ZIP archive
arcname = full_file_path.relative_to(source_root_path)
backup_zip.write(full_file_path, arcname=arcname)
processed_size_mb += size_mb
if progress_callback:
progress_callback(processed_size_mb, total_size_to_zip_mb, str(arcname))
# 2. Add the description file
# Using a distinct filename for the description.
description_filename = "____backup_description.txt"
if backup_description: # Only add if description is not empty
backup_zip.writestr(description_filename, backup_description.encode('utf-8'))
print(f"Info: Backup archive created successfully at {zip_file_path}")
except FileNotFoundError as e: # Should not happen if files are from scanner
raise BackupError(f"File not found during backup: {e}")
except PermissionError as e:
raise BackupError(f"Permission error during backup: {e}")
except zipfile.BadZipFile as e:
raise BackupError(f"Error creating zip file (BadZipFile): {e}")
except OSError as e: # General OS error (disk full, etc.)
raise BackupError(f"OS error during backup: {e}")
except Exception as e: # Catch-all for other unexpected errors
raise BackupError(f"An unexpected error occurred during backup: {e}")