"""Skip tracking persistence layer.""" import os import csv import tempfile import logging from datetime import datetime from pathlib import Path from filelock import FileLock, Timeout logger = logging.getLogger(__name__) SKIP_CSV_COLUMNS = ["labeler", "source", "reason", "timestamp"] def save_skip(labeler: str, source: str, reason: str, csv_path: str) -> None: """Save a skip record to the shared skipped_audios.csv. Uses filelock + temp-file-then-rename for atomicity. Args: labeler: Username of the labeler who skipped. source: Audio filename. reason: Reason for skipping. csv_path: Path to the shared skipped_audios.csv. Raises: IOError: If the write operation fails. """ lock_path = csv_path + ".lock" lock = FileLock(lock_path, timeout=10) try: with lock: rows: list[dict] = [] path = Path(csv_path) if path.exists(): with open(path, "r", encoding="utf-8", newline="") as f: reader = csv.DictReader(f) rows = [row for row in reader] new_row = { "labeler": labeler, "source": source, "reason": reason, "timestamp": datetime.now().isoformat(), } # Upsert: overwrite if same labeler+source already skipped updated = False for i, row in enumerate(rows): if row["labeler"] == labeler and row["source"] == source: rows[i] = new_row updated = True break if not updated: rows.append(new_row) # Atomic write dir_name = os.path.dirname(csv_path) or "." os.makedirs(dir_name, exist_ok=True) fd, tmp_path = tempfile.mkstemp(dir=dir_name, suffix=".tmp") try: with os.fdopen(fd, "w", encoding="utf-8", newline="") as f: writer = csv.DictWriter(f, fieldnames=SKIP_CSV_COLUMNS) writer.writeheader() writer.writerows(rows) os.replace(tmp_path, csv_path) logger.info(f"Saved skip for '{source}' by '{labeler}': {reason}") except Exception: if os.path.exists(tmp_path): os.unlink(tmp_path) raise except Timeout: logger.error(f"Lock timeout for skip CSV: {csv_path}") raise IOError("Failed to save skip record. Please try again.") except IOError: raise except Exception as e: logger.error(f"Failed to save skip for {source}: {e}") raise IOError("Failed to save skip record. Please try again.") from e