v0.9.20
This commit is contained in:
@@ -1,3 +1,14 @@
|
||||
"""
|
||||
File System Watcher Module for Bagheera Image Viewer.
|
||||
|
||||
This module provides functionality to monitor file system changes in real-time
|
||||
using the watchdog library. It notifies the application about new, deleted, or
|
||||
modified image files within watched directories, handling debouncing to ensure
|
||||
stability during rapid file operations.
|
||||
|
||||
Classes:
|
||||
FileSystemWatcher: Coordinates file system monitoring and emits Qt signals.
|
||||
"""
|
||||
import os
|
||||
try:
|
||||
from watchdog.observers import Observer
|
||||
@@ -14,6 +25,10 @@ class FileSystemWatcher(QObject):
|
||||
Monitors file system events (created, deleted, modified) for specified directories.
|
||||
Emits signals to notify the main application thread of changes.
|
||||
"""
|
||||
|
||||
# Signals emitted to the rest of the application
|
||||
# ---------------------------------------------
|
||||
|
||||
file_created = Signal(str)
|
||||
file_deleted = Signal(str)
|
||||
file_modified = Signal(str)
|
||||
@@ -24,10 +39,18 @@ class FileSystemWatcher(QObject):
|
||||
directory_modified = Signal(str) # For changes that might not be specific files
|
||||
|
||||
_modified_events_queue = {} # {path: QTimer}
|
||||
"""Queue to manage debouncing of modification events."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
"""
|
||||
Initializes the FileSystemWatcher.
|
||||
|
||||
Args:
|
||||
parent (QObject, optional): The parent object. Defaults to None.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
self._watched_directories = set()
|
||||
self._debounce_interval = 500 # milliseconds
|
||||
|
||||
if HAVE_WATCHDOG:
|
||||
self._observer = Observer()
|
||||
@@ -36,16 +59,21 @@ class FileSystemWatcher(QObject):
|
||||
else:
|
||||
self._observer = None # Keep observer as None if watchdog is not available
|
||||
|
||||
# Debounce timer for modified events to avoid multiple signals for a single save
|
||||
self._debounce_interval = 500 # milliseconds
|
||||
|
||||
# Connect the internal signal to the debouncing slot
|
||||
if HAVE_WATCHDOG:
|
||||
self._file_modified_from_handler.connect(self._on_file_modified_debounced)
|
||||
|
||||
def _on_file_modified_debounced(self, path):
|
||||
"""Slot to handle modified events from the watchdog thread, debounced in the
|
||||
main thread."""
|
||||
"""
|
||||
Slot to handle modified events from the watchdog thread.
|
||||
|
||||
Implements a debouncing mechanism: if multiple modification events
|
||||
arrive for the same path within the interval, previous timers are
|
||||
reset to avoid redundant UI updates or heavy disk operations.
|
||||
|
||||
Args:
|
||||
path (str): The path of the modified file.
|
||||
"""
|
||||
# Debounce timer for modified events to avoid multiple signals for a single save
|
||||
if path in self._modified_events_queue:
|
||||
self._modified_events_queue[path].stop()
|
||||
@@ -59,7 +87,12 @@ class FileSystemWatcher(QObject):
|
||||
self._modified_events_queue[path].start()
|
||||
|
||||
def _emit_modified_after_debounce(self, path):
|
||||
"""Emits the file_modified signal after the debounce period."""
|
||||
"""
|
||||
Emits the file_modified signal after the debounce period.
|
||||
|
||||
Args:
|
||||
path (str): The path of the modified file.
|
||||
"""
|
||||
self.file_modified.emit(path)
|
||||
if path in self._modified_events_queue:
|
||||
# Safely delete the QTimer object when done
|
||||
@@ -67,7 +100,16 @@ class FileSystemWatcher(QObject):
|
||||
del self._modified_events_queue[path]
|
||||
|
||||
def add_path(self, path):
|
||||
"""Adds a directory to be monitored."""
|
||||
"""
|
||||
Adds a directory to be monitored.
|
||||
|
||||
This method ensures that redundant watches are avoided by checking if
|
||||
the path is already covered by an existing watch or if it should
|
||||
consolidate multiple sub-watches into a single parent watch.
|
||||
|
||||
Args:
|
||||
path (str): The directory path to monitor.
|
||||
"""
|
||||
if not HAVE_WATCHDOG or self._observer is None:
|
||||
return
|
||||
|
||||
@@ -111,7 +153,12 @@ class FileSystemWatcher(QObject):
|
||||
self.monitoring_status_changed.emit(True)
|
||||
|
||||
def remove_path(self, path):
|
||||
"""Removes a directory from monitoring."""
|
||||
"""
|
||||
Removes a directory from monitoring.
|
||||
|
||||
Args:
|
||||
path (str): The directory path to stop monitoring.
|
||||
"""
|
||||
if not HAVE_WATCHDOG or self._observer is None:
|
||||
return
|
||||
abs_path = os.path.normpath(os.path.abspath(os.path.expanduser(path)))
|
||||
@@ -138,7 +185,9 @@ class FileSystemWatcher(QObject):
|
||||
self.monitoring_status_changed.emit(False)
|
||||
|
||||
def stop(self):
|
||||
"""Stops the file system observer."""
|
||||
"""
|
||||
Stops the file system observer and cleans up active timers.
|
||||
"""
|
||||
if HAVE_WATCHDOG and self._observer:
|
||||
self._observer.stop()
|
||||
self._observer.join()
|
||||
@@ -150,10 +199,19 @@ class FileSystemWatcher(QObject):
|
||||
|
||||
if HAVE_WATCHDOG:
|
||||
class _Handler(FileSystemEventHandler):
|
||||
"""
|
||||
Custom event handler for watchdog events.
|
||||
|
||||
Translates low-level file system events into high-level application
|
||||
signals, filtering for supported image types.
|
||||
"""
|
||||
# Signal to communicate to main thread
|
||||
file_modified_from_thread = Signal(str)
|
||||
"""Custom event handler for watchdog events."""
|
||||
|
||||
def __init__(self, watcher):
|
||||
"""
|
||||
Initializes the handler with a reference to the main watcher.
|
||||
"""
|
||||
super().__init__()
|
||||
self.watcher = watcher
|
||||
|
||||
@@ -199,11 +257,21 @@ class FileSystemWatcher(QObject):
|
||||
self.watcher._file_modified_from_handler.emit(event.src_path)
|
||||
|
||||
def _emit_modified(self, path):
|
||||
"""Internal helper to emit the modified signal."""
|
||||
"""
|
||||
Internal helper to emit the modified signal.
|
||||
|
||||
Args:
|
||||
path (str): The modified path.
|
||||
"""
|
||||
self.watcher.file_modified.emit(path)
|
||||
if path in self.watcher._modified_events_queue:
|
||||
del self.watcher._modified_events_queue[path]
|
||||
|
||||
def _is_image_file(self, path):
|
||||
"""Checks if a given path has a supported image extension."""
|
||||
"""
|
||||
Checks if a given path has a supported image extension.
|
||||
|
||||
Args:
|
||||
path (str): The file path to check.
|
||||
"""
|
||||
return os.path.splitext(path)[1].lower() in IMAGE_EXTENSIONS
|
||||
|
||||
Reference in New Issue
Block a user