Source code for stouputils.backup.retrieve
# Imports
import os
import zipfile
from ..decorators import measure_time
from ..io.path import clean_path
from ..print.message import warning
from .hash import extract_hash_from_zipinfo
# Function to retrieve all previous backups in a folder
[docs]
@measure_time(message="Retrieving previous backups")
def get_all_previous_backups(backup_folder: str, all_before: str | None = None) -> dict[str, dict[str, str]]:
""" Retrieves all previous backups in a folder and maps each backup to a dictionary of file paths and their hashes.
Args:
backup_folder (str): The folder containing previous backup zip files
all_before (str | None): Path to the latest backup ZIP file
(If endswith "/latest.zip" or "/", the latest backup will be used)
Returns:
dict[str, dict[str, str]]: Dictionary mapping backup file paths to dictionaries of {file_path: file_hash}
"""
backups: dict[str, dict[str, str]] = {}
list_dir: list[str] = sorted([clean_path(os.path.join(backup_folder, f)) for f in os.listdir(backup_folder)])
# If all_before is provided, don't include backups after it
if isinstance(all_before, str) and not (
all_before.endswith("/latest.zip") or all_before.endswith("/") or os.path.isdir(all_before)
):
list_dir = list_dir[:list_dir.index(all_before) + 1]
# Get all the backups
for filename in list_dir:
if filename.endswith(".zip"):
zip_path: str = clean_path(os.path.join(backup_folder, filename))
file_hashes: dict[str, str] = {}
try:
with zipfile.ZipFile(zip_path, "r") as zipf:
for inf in zipf.infolist():
if inf.filename != "__deleted_files__.txt":
stored_hash: str | None = extract_hash_from_zipinfo(inf)
if stored_hash is not None: # Only store if hash exists
file_hashes[inf.filename] = stored_hash
backups[zip_path] = file_hashes
except Exception as e:
warning(f"Error reading backup {zip_path}: {e}")
return dict(reversed(backups.items()))
# Function to check if a file exists in any previous backup
[docs]
def is_file_in_any_previous_backup(file_path: str, file_hash: str, previous_backups: dict[str, dict[str, str]]) -> bool:
""" Checks if a file with the same hash exists in any previous backup.
Args:
file_path (str): The relative path of the file
file_hash (str): The SHA-256 hash of the file
previous_backups (dict[str, dict[str, str]]): Dictionary mapping backup zip paths to their stored file hashes
Returns:
bool: True if the file exists unchanged in any previous backup, False otherwise
"""
for file_hashes in previous_backups.values():
if file_hashes.get(file_path) == file_hash:
return True
return False