From bf8cb92e932e117b0de5c513b60a4acb66bb5392 Mon Sep 17 00:00:00 2001 From: nendo Date: Tue, 3 Feb 2026 20:07:18 +0900 Subject: [PATCH] refactor(assets): move content hash functions to assets_handler Move compute_file_hash, compute_zip_hash, and compute_content_hash from scan_handler.py to filesystem/assets_handler.py as standalone module-level functions. This follows the existing pattern for utility functions in filesystem handlers. --- backend/handler/filesystem/assets_handler.py | 33 ++++++++++++++++ backend/handler/scan_handler.py | 41 +++----------------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/backend/handler/filesystem/assets_handler.py b/backend/handler/filesystem/assets_handler.py index 8d8466e92..fa1114f60 100644 --- a/backend/handler/filesystem/assets_handler.py +++ b/backend/handler/filesystem/assets_handler.py @@ -1,11 +1,44 @@ +import hashlib import os +import zipfile from config import ASSETS_BASE_PATH +from logger.logger import log from models.user import User from .base_handler import FSHandler +def compute_file_hash(file_path: str) -> str: + hash_obj = hashlib.md5(usedforsecurity=False) + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(8192), b""): + hash_obj.update(chunk) + return hash_obj.hexdigest() + + +def compute_zip_hash(zip_path: str) -> str: + with zipfile.ZipFile(zip_path, "r") as zf: + file_hashes = [] + for name in sorted(zf.namelist()): + if not name.endswith("/"): + content = zf.read(name) + file_hash = hashlib.md5(content, usedforsecurity=False).hexdigest() + file_hashes.append(f"{name}:{file_hash}") + combined = "\n".join(file_hashes) + return hashlib.md5(combined.encode(), usedforsecurity=False).hexdigest() + + +def compute_content_hash(file_path: str) -> str | None: + try: + if zipfile.is_zipfile(file_path): + return compute_zip_hash(file_path) + return compute_file_hash(file_path) + except Exception as e: + log.debug(f"Failed to compute content hash for {file_path}: {e}") + return None + + class FSAssetsHandler(FSHandler): def __init__(self) -> None: super().__init__(base_path=ASSETS_BASE_PATH) diff --git a/backend/handler/scan_handler.py b/backend/handler/scan_handler.py index 2ac170ded..02deb304f 100644 --- a/backend/handler/scan_handler.py +++ b/backend/handler/scan_handler.py @@ -1,7 +1,5 @@ import asyncio import enum -import hashlib -import zipfile from typing import Any import socketio # type: ignore @@ -11,6 +9,7 @@ from config.config_manager import config_manager as cm from endpoints.responses.rom import SimpleRomSchema from handler.database import db_platform_handler, db_rom_handler from handler.filesystem import fs_asset_handler, fs_firmware_handler +from handler.filesystem.assets_handler import compute_content_hash from handler.filesystem.roms_handler import FSRom from handler.metadata import ( meta_flashpoint_handler, @@ -820,37 +819,7 @@ async def scan_rom( return Rom(**rom_attrs) -def _compute_file_hash(file_path: str) -> str: - hash_obj = hashlib.md5(usedforsecurity=False) - with open(file_path, "rb") as f: - for chunk in iter(lambda: f.read(8192), b""): - hash_obj.update(chunk) - return hash_obj.hexdigest() - - -def _compute_zip_hash(zip_path: str) -> str: - with zipfile.ZipFile(zip_path, "r") as zf: - file_hashes = [] - for name in sorted(zf.namelist()): - if not name.endswith("/"): - content = zf.read(name) - file_hash = hashlib.md5(content, usedforsecurity=False).hexdigest() - file_hashes.append(f"{name}:{file_hash}") - combined = "\n".join(file_hashes) - return hashlib.md5(combined.encode(), usedforsecurity=False).hexdigest() - - -def _compute_content_hash(file_path: str) -> str | None: - try: - if zipfile.is_zipfile(file_path): - return _compute_zip_hash(file_path) - return _compute_file_hash(file_path) - except Exception as e: - log.debug(f"Failed to compute content hash for {file_path}: {e}") - return None - - -async def _scan_asset(file_name: str, asset_path: str, compute_hash: bool = False): +async def _scan_asset(file_name: str, asset_path: str, should_hash: bool = False): file_path = f"{asset_path}/{file_name}" file_size = await fs_asset_handler.get_file_size(file_path) @@ -863,9 +832,9 @@ async def _scan_asset(file_name: str, asset_path: str, compute_hash: bool = Fals "file_size_bytes": file_size, } - if compute_hash: + if should_hash: absolute_path = f"{ASSETS_BASE_PATH}/{file_path}" - result["content_hash"] = _compute_content_hash(absolute_path) + result["content_hash"] = compute_content_hash(absolute_path) return result @@ -880,7 +849,7 @@ async def scan_save( saves_path = fs_asset_handler.build_saves_file_path( user=user, platform_fs_slug=platform_fs_slug, rom_id=rom_id, emulator=emulator ) - scanned_asset = await _scan_asset(file_name, saves_path, compute_hash=True) + scanned_asset = await _scan_asset(file_name, saves_path, should_hash=True) return Save(**scanned_asset)