refactor: centralize hash calculation callbacks and catalog backup logic

This commit is contained in:
Dariusz L
2026-07-04 00:18:41 +02:00
parent 5de1500537
commit cb666902e5
4 changed files with 93 additions and 69 deletions
+43 -3
View File
@@ -8,7 +8,7 @@ and directory matching to be shared across all modules.
import os
import json
import hashlib
from typing import Any, Tuple, Optional
from typing import Any, Tuple, Optional, Callable
_log = None
@@ -178,22 +178,62 @@ METADATA_DIR = os.path.join(
)
def calculate_file_sha256(file_path: str, chunk_size: int = 131072) -> Optional[str]:
"""Calculate SHA256 hex digest for an existing local file safely."""
class HashCalculationCancelled(Exception):
"""Exception raised when file SHA256 calculation is cancelled by the caller."""
pass
def calculate_file_sha256(
file_path: str,
chunk_size: int = 131072,
on_progress: Optional[Callable[[int, int], None]] = None,
is_cancelled: Optional[Callable[[], bool]] = None,
) -> Optional[str]:
"""Calculate SHA256 hex digest for an existing local file safely, supporting progress callbacks and cancellation check."""
if not file_path or not os.path.exists(file_path):
return None
sha256_hash = hashlib.sha256()
try:
total_bytes = os.path.getsize(file_path) if on_progress else 0
bytes_read = 0
with open(file_path, "rb") as f:
for byte_block in iter(lambda: f.read(chunk_size), b""):
if is_cancelled and is_cancelled():
raise HashCalculationCancelled("Hash calculation cancelled")
if byte_block:
sha256_hash.update(byte_block)
bytes_read += len(byte_block)
if on_progress:
on_progress(bytes_read, total_bytes)
return sha256_hash.hexdigest()
except HashCalculationCancelled:
raise
except Exception as e:
_get_log().error(f"Error computing hash for {file_path}: {e}")
return None
def save_catalog_with_backup(
data_path: str, data: Any, meta_path: str, meta: Any, indent: int = 2
) -> None:
"""Backup an existing catalog file to .bak, then atomically write the new data and its metadata."""
import shutil
# Ensure directory exists
dir_name = os.path.dirname(data_path)
if dir_name:
os.makedirs(dir_name, exist_ok=True)
# Backup existing data file
if os.path.exists(data_path):
shutil.copy2(data_path, f"{data_path}.bak")
# Write data file atomically
write_json_atomic(data_path, data, indent=indent)
# Write metadata file atomically
write_json_atomic(meta_path, meta, indent=indent)
def read_json_safe(file_path: str, default: Any = None) -> Any:
"""Safely read and parse a JSON file, returning a default value on error."""
if not file_path or not os.path.exists(file_path):