import os import tempfile import pyzipper from indexer.config import BACKUP_PASSWORD def blob_path(backup_root: str, content_hash: bytes) -> str: """Vrátí cestu k ZIP souboru: BACKUP/ab/cd/abcdef...zip""" hex_hash = content_hash.hex() return os.path.join(backup_root, hex_hash[:2], hex_hash[2:4], hex_hash + ".zip") def ensure_backed_up(files_with_hash: list, backup_root: str) -> int: """ Vytvoří AES-256 šifrovaný ZIP pro každý soubor v content-addressable storage. files_with_hash: [(full_path, content_hash_bytes), ...] Přeskočí soubory, jejichž zip už existuje (deduplikace). Returns: počet nově zálohovaných souborů. """ password = BACKUP_PASSWORD.encode("utf-8") backed_up = 0 for full_path, content_hash in files_with_hash: target = blob_path(backup_root, content_hash) if os.path.exists(target): continue target_dir = os.path.dirname(target) os.makedirs(target_dir, exist_ok=True) tmp_path = None try: # Atomický zápis: temp soubor + přejmenování fd, tmp_path = tempfile.mkstemp(dir=target_dir, suffix=".tmp") os.close(fd) hex_hash = content_hash.hex() with pyzipper.AESZipFile( tmp_path, "w", compression=pyzipper.ZIP_DEFLATED, encryption=pyzipper.WZ_AES, ) as zf: zf.setpassword(password) zf.write(full_path, arcname=hex_hash + ".blob") os.replace(tmp_path, target) backed_up += 1 except (FileNotFoundError, PermissionError, OSError) as e: print(f" WARN: backup failed for {full_path}: {e}") if tmp_path and os.path.exists(tmp_path): os.remove(tmp_path) continue return backed_up