import threading import logging from watchdog.events import PatternMatchingEventHandler from pathlib import Path from config import * from helpers import export_single_zone # Internal state for debounce debounce_timer = None debounce_lock = threading.Lock() def run_export(trigger_path): global debounce_timer with debounce_lock: debounce_timer = None try: export_single_zone(trigger_path) except Exception: logging.exception("Export run failed.") def schedule_export(trigger_path): global debounce_timer with debounce_lock: if debounce_timer is not None: debounce_timer.cancel() debounce_timer = threading.Timer(DEBOUNCE_SECONDS, run_export, args=(trigger_path,)) debounce_timer.daemon = True debounce_timer.start() logging.debug("Debounce timer started/reset (%.1fs)", DEBOUNCE_SECONDS) def _most_recent_file_under(path: Path, max_depth: int = 2) -> Path | None: if not path.exists(): return None best = None # (mtime, filepath) base_depth = len(path.parts) for root, dirs, files in os.walk(path): depth = len(Path(root).parts) - base_depth if depth > max_depth: # prune deeper traversal dirs[:] = [] continue for fn in files: p = Path(root) / fn try: m = p.stat().st_mtime except Exception: continue if best is None or m > best[0]: best = (m, p) return best[1] if best else None class DebouncedHandler(PatternMatchingEventHandler): def __init__(self, patterns=None, ignore_patterns=None, ignore_directories=False, case_sensitive=True): super().__init__(patterns=patterns or ["*"], ignore_patterns=ignore_patterns or [], ignore_directories=ignore_directories, case_sensitive=case_sensitive) def on_any_event(self, event): try: src = getattr(event, "src_path", None) dest = getattr(event, "dest_path", None) trigger = None if dest: trigger = dest elif src: p = Path(src) if p.is_file(): trigger = str(p) else: candidate = _most_recent_file_under(p) if candidate: trigger = str(candidate) else: trigger = str(src) else: trigger = WATCH_DIR logging.debug(f"Filesystem event: {event.event_type} on {trigger}") schedule_export(event.src_path) except Exception as e: logging.exception(f"Error handling filesystem event; scheduling export for watch dir as fallback: {e}") schedule_export(WATCH_DIR)