Debounced file-system event handler. Collects file creation/modification events and waits *debounce_seconds* after the last event before calling *callback* with all pending paths. Directories and dotfiles (hidden files) are ignored. Args: callback: Called with a sorted list
| 15 | |
| 16 | |
| 17 | class DebouncedHandler(FileSystemEventHandler): |
| 18 | """Debounced file-system event handler. |
| 19 | |
| 20 | Collects file creation/modification events and waits *debounce_seconds* |
| 21 | after the last event before calling *callback* with all pending paths. |
| 22 | Directories and dotfiles (hidden files) are ignored. |
| 23 | |
| 24 | Args: |
| 25 | callback: Called with a sorted list of path strings when the debounce |
| 26 | timer fires. |
| 27 | debounce_seconds: How long to wait after the last event before |
| 28 | flushing. Defaults to 2.0 seconds. |
| 29 | """ |
| 30 | |
| 31 | def __init__( |
| 32 | self, callback: Callable[[list[str]], None], debounce_seconds: float = 2.0 |
| 33 | ) -> None: |
| 34 | super().__init__() |
| 35 | self._callback = callback |
| 36 | self._debounce_seconds = debounce_seconds |
| 37 | self._pending: set[str] = set() |
| 38 | self._timer: threading.Timer | None = None |
| 39 | self._lock = threading.Lock() |
| 40 | |
| 41 | def _schedule_flush(self) -> None: |
| 42 | """Cancel any existing timer and start a fresh debounce timer.""" |
| 43 | with self._lock: |
| 44 | if self._timer is not None: |
| 45 | self._timer.cancel() |
| 46 | self._timer = threading.Timer(self._debounce_seconds, self._flush) |
| 47 | self._timer.daemon = True |
| 48 | self._timer.start() |
| 49 | |
| 50 | def _flush(self) -> None: |
| 51 | """Call the callback with all collected pending paths, then clear.""" |
| 52 | with self._lock: |
| 53 | paths = sorted(self._pending) |
| 54 | self._pending.clear() |
| 55 | self._timer = None |
| 56 | if paths: |
| 57 | self._callback(paths) |
| 58 | |
| 59 | def _handle_event(self, event) -> None: |
| 60 | """Add the event's source path to pending if it's a supported file.""" |
| 61 | if event.is_directory: |
| 62 | return |
| 63 | path = Path(event.src_path) |
| 64 | # Ignore hidden/dotfiles |
| 65 | if path.name.startswith("."): |
| 66 | return |
| 67 | with self._lock: |
| 68 | self._pending.add(str(path)) |
| 69 | self._schedule_flush() |
| 70 | |
| 71 | def on_created(self, event) -> None: |
| 72 | """Handle file creation events.""" |
| 73 | self._handle_event(event) |
| 74 |
no outgoing calls