""" Run-folder naming + content hashing for doocus. Reuses meetus's `YYYYMMDD-NNN-` run-folder convention (see meetus/output_manager.py) so both trees look the same. Kept here to keep doocus self-contained; meetus could adopt this helper later. """ from pathlib import Path from datetime import datetime import hashlib def sha256_file(path: Path, chunk: int = 1 << 20) -> str: """Streaming sha256 of a file — never loads the whole file into memory.""" h = hashlib.sha256() with open(path, "rb") as f: for block in iter(lambda: f.read(chunk), b""): h.update(block) return h.hexdigest() def resolve_run_dir(base_output_dir: Path, stem: str, use_cache: bool = True) -> Path: """Return (creating if needed) the run folder for `stem` under `base_output_dir`. Mirrors meetus/output_manager.py:37-84 — reuse the most recent `*-` folder when caching, else create `YYYYMMDD-NNN-` with the next run number for today. """ base = Path(base_output_dir) if use_cache and base.exists(): existing = sorted( [d for d in base.iterdir() if d.is_dir() and d.name.endswith(f"-{stem}")], reverse=True, ) if existing: return existing[0] date_str = datetime.now().strftime("%Y%m%d") next_run = 1 if base.exists(): nums = [] for d in base.iterdir(): if d.is_dir() and d.name.startswith(date_str) and d.name.endswith(f"-{stem}"): parts = d.name.split("-") if len(parts) >= 2 and parts[1].isdigit(): nums.append(int(parts[1])) next_run = max(nums) + 1 if nums else 1 run_dir = base / f"{date_str}-{next_run:03d}-{stem}" run_dir.mkdir(parents=True, exist_ok=True) return run_dir