82 lines
2.8 KiB
Python
82 lines
2.8 KiB
Python
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) |