55trigger index rebuilds when relevant files are modified, created, or deleted.
66It uses the watchdog library for cross-platform file system event monitoring.
77"""
8+ # pylint: disable=missing-function-docstring # Fallback stub methods don't need docstrings
89
910import logging
11+ import os
12+ import traceback
1013from threading import Timer
1114from typing import Optional , Callable
1215from pathlib import Path
1821except ImportError :
1922 # Fallback classes for when watchdog is not available
2023 class Observer :
21- def __init__ (self ): pass
22- def schedule (self , * args , ** kwargs ): pass
23- def start (self ): pass
24- def stop (self ): pass
25- def join (self , * args , ** kwargs ): pass
26- def is_alive (self ): return False
24+ """Fallback Observer class when watchdog library is not available."""
25+ def __init__ (self ):
26+ pass
27+ def schedule (self , * args , ** kwargs ):
28+ pass
29+ def start (self ):
30+ pass
31+ def stop (self ):
32+ pass
33+ def join (self , * args , ** kwargs ):
34+ pass
35+ def is_alive (self ):
36+ return False
2737
2838 class FileSystemEventHandler :
29- def __init__ (self ): pass
39+ """Fallback FileSystemEventHandler class when watchdog library is not available."""
40+ def __init__ (self ):
41+ pass
3042
3143 class FileSystemEvent :
44+ """Fallback FileSystemEvent class when watchdog library is not available."""
3245 def __init__ (self ):
3346 self .is_directory = False
3447 self .src_path = ""
@@ -111,8 +124,8 @@ def start_monitoring(self, rebuild_callback: Callable) -> bool:
111124
112125 # Log detailed Observer setup
113126 watch_path = str (self .base_path )
114- self .logger .debug (f "Scheduling Observer for path: { watch_path } " )
115-
127+ self .logger .debug ("Scheduling Observer for path: %s" , watch_path )
128+
116129 self .observer .schedule (
117130 self .event_handler ,
118131 watch_path ,
@@ -124,11 +137,10 @@ def start_monitoring(self, rebuild_callback: Callable) -> bool:
124137 self .observer .start ()
125138 self .is_monitoring = True
126139 self .restart_attempts = 0
127-
140+
128141 # Log Observer thread info
129142 if hasattr (self .observer , '_thread' ):
130- thread_info = f"Observer thread: { self .observer ._thread } "
131- self .logger .debug (thread_info )
143+ self .logger .debug ("Observer thread: %s" , self .observer ._thread )
132144
133145 # Verify observer is actually running
134146 if self .observer .is_alive ():
@@ -140,18 +152,17 @@ def start_monitoring(self, rebuild_callback: Callable) -> bool:
140152 "supported_extensions" : len (SUPPORTED_EXTENSIONS )
141153 }
142154 )
143-
155+
144156 # Add diagnostic test - create a test event to verify Observer works
145- import os
146- self .logger .debug (f"Observer thread is alive: { self .observer .is_alive ()} " )
147- self .logger .debug (f"Monitored path exists: { os .path .exists (str (self .base_path ))} " )
148- self .logger .debug (f"Event handler is set: { self .event_handler is not None } " )
149-
157+ self .logger .debug ("Observer thread is alive: %s" , self .observer .is_alive ())
158+ self .logger .debug ("Monitored path exists: %s" , os .path .exists (str (self .base_path )))
159+ self .logger .debug ("Event handler is set: %s" , self .event_handler is not None )
160+
150161 # Log current directory for comparison
151162 current_dir = os .getcwd ()
152- self .logger .debug (f "Current working directory: { current_dir } " )
153- self .logger .debug (f "Are paths same: { os .path .normpath (current_dir ) == os .path .normpath (str (self .base_path ))} " )
154-
163+ self .logger .debug ("Current working directory: %s" , current_dir )
164+ self .logger .debug ("Are paths same: %s" , os .path .normpath (current_dir ) == os .path .normpath (str (self .base_path )))
165+
155166 return True
156167 else :
157168 self .logger .error ("File watcher failed to start - Observer not alive" )
@@ -165,7 +176,7 @@ def start_monitoring(self, rebuild_callback: Callable) -> bool:
165176 def stop_monitoring (self ) -> None :
166177 """
167178 Stop file system monitoring and cleanup all resources.
168-
179+
169180 This method ensures complete cleanup of:
170181 - Observer thread
171182 - Event handler
@@ -175,43 +186,43 @@ def stop_monitoring(self) -> None:
175186 if not self .observer and not self .is_monitoring :
176187 # Already stopped or never started
177188 return
178-
189+
179190 self .logger .info ("Stopping file watcher monitoring..." )
180-
191+
181192 try :
182193 # Step 1: Stop the observer first
183194 if self .observer :
184195 self .logger .debug ("Stopping observer..." )
185196 self .observer .stop ()
186-
197+
187198 # Step 2: Cancel any active debounce timer
188199 if self .event_handler and self .event_handler .debounce_timer :
189200 self .logger .debug ("Cancelling debounce timer..." )
190201 self .event_handler .debounce_timer .cancel ()
191-
202+
192203 # Step 3: Wait for observer thread to finish (with timeout)
193204 self .logger .debug ("Waiting for observer thread to finish..." )
194205 self .observer .join (timeout = 5.0 )
195-
206+
196207 # Step 4: Check if thread actually finished
197208 if self .observer .is_alive ():
198209 self .logger .warning ("Observer thread did not stop within timeout" )
199210 else :
200211 self .logger .debug ("Observer thread stopped successfully" )
201-
212+
202213 # Step 5: Clear all references
203214 self .observer = None
204215 self .event_handler = None
205216 self .rebuild_callback = None
206217 self .is_monitoring = False
207-
218+
208219 self .logger .info ("File watcher stopped and cleaned up successfully" )
209220 print ("STOPPED: File watcher stopped" )
210-
221+
211222 except Exception as e :
212223 self .logger .error ("Error stopping file watcher: %s" , e )
213224 print (f"WARNING: Error stopping file watcher: { e } " )
214-
225+
215226 # Force cleanup even if there were errors
216227 self .observer = None
217228 self .event_handler = None
@@ -340,21 +351,17 @@ def on_any_event(self, event: FileSystemEvent) -> None:
340351 event: The file system event
341352 """
342353 # Always log events for debugging
343- event_info = f"Raw event: { event .event_type } - { event .src_path } (is_directory: { event .is_directory } )"
344- self .logger .debug (event_info )
345-
354+ self .logger .debug ("Raw event: %s - %s (is_directory: %s)" , event .event_type , event .src_path , event .is_directory )
355+
346356 # Test event processing
347357 should_process = self .should_process_event (event )
348- process_info = f"Should process: { should_process } "
349- self .logger .debug (process_info )
350-
358+ self .logger .debug ("Should process: %s" , should_process )
359+
351360 if should_process :
352- process_msg = f"Processing file system event: { event .event_type } - { event .src_path } "
353- self .logger .debug (process_msg )
361+ self .logger .debug ("Processing file system event: %s - %s" , event .event_type , event .src_path )
354362 self .reset_debounce_timer ()
355363 else :
356- filter_msg = f"Event filtered out: { event .event_type } - { event .src_path } "
357- self .logger .debug (filter_msg )
364+ self .logger .debug ("Event filtered out: %s - %s" , event .event_type , event .src_path )
358365
359366 def should_process_event (self , event : FileSystemEvent ) -> bool :
360367 """
@@ -368,38 +375,60 @@ def should_process_event(self, event: FileSystemEvent) -> bool:
368375 """
369376 # Skip directory events
370377 if event .is_directory :
371- self .logger .debug (f "Skipping directory event: { event .src_path } " )
378+ self .logger .debug ("Skipping directory event: %s" , event .src_path )
372379 return False
373380
381+ # Select path to check: dest_path for moves, src_path for others
382+ if event .event_type == 'moved' :
383+ if not hasattr (event , 'dest_path' ):
384+ self .logger .debug ("Move event missing dest_path" )
385+ return False
386+ target_path = event .dest_path
387+ self .logger .debug ("Move event: checking destination path %s" , target_path )
388+ else :
389+ target_path = event .src_path
390+ self .logger .debug ("%s event: checking source path %s" , event .event_type , target_path )
391+
392+ # Unified path checking
374393 try :
375- path = Path (event .src_path )
394+ path = Path (target_path )
395+ return self ._should_process_path (path )
376396 except Exception as e :
377- # Handle any path conversion issues
378- self .logger .debug (f"Path conversion failed for { event .src_path } : { e } " )
397+ self .logger .debug ("Path conversion failed for %s: %s" , target_path , e )
379398 return False
380399
400+ def _should_process_path (self , path : Path ) -> bool :
401+ """
402+ Check if a specific path should trigger index rebuild.
403+
404+ Args:
405+ path: The file path to check
406+
407+ Returns:
408+ True if path should trigger rebuild, False otherwise
409+ """
381410 # Log detailed filtering steps
382- self .logger .debug (f "Checking path: { path } " )
383-
411+ self .logger .debug ("Checking path: %s" , path )
412+
384413 # Skip excluded paths
385414 is_excluded = self .is_excluded_path (path )
386- self .logger .debug (f "Is excluded path: { is_excluded } " )
415+ self .logger .debug ("Is excluded path: %s" , is_excluded )
387416 if is_excluded :
388417 return False
389418
390419 # Only process supported file types
391420 is_supported = self .is_supported_file_type (path )
392- self .logger .debug (f "Is supported file type: { is_supported } (extension: { path .suffix } )" )
421+ self .logger .debug ("Is supported file type: %s (extension: %s)" , is_supported , path .suffix )
393422 if not is_supported :
394423 return False
395424
396425 # Skip temporary files
397426 is_temp = self .is_temporary_file (path )
398- self .logger .debug (f "Is temporary file: { is_temp } " )
427+ self .logger .debug ("Is temporary file: %s" , is_temp )
399428 if is_temp :
400429 return False
401430
402- self .logger .debug (f "Event will be processed: { event . src_path } " )
431+ self .logger .debug ("Event will be processed: %s" , path )
403432 return True
404433
405434 def is_excluded_path (self , path : Path ) -> bool :
@@ -472,15 +501,14 @@ def reset_debounce_timer(self) -> None:
472501 self .debounce_timer .cancel ()
473502 self .logger .debug ("Previous debounce timer cancelled" )
474503
475- timer_msg = f"Starting debounce timer for { self .debounce_seconds } seconds"
476- self .logger .debug (timer_msg )
477-
504+ self .logger .debug ("Starting debounce timer for %s seconds" , self .debounce_seconds )
505+
478506 self .debounce_timer = Timer (
479507 self .debounce_seconds ,
480508 self .trigger_rebuild
481509 )
482510 self .debounce_timer .start ()
483-
511+
484512 self .logger .debug ("Debounce timer started successfully" )
485513
486514 def trigger_rebuild (self ) -> None :
@@ -490,19 +518,14 @@ def trigger_rebuild(self) -> None:
490518
491519 if self .rebuild_callback :
492520 try :
493- callback_msg = "Calling rebuild callback..."
494- self .logger .debug (callback_msg )
495-
521+ self .logger .debug ("Calling rebuild callback..." )
522+
496523 result = self .rebuild_callback ()
497-
498- result_msg = f"Rebuild callback completed with result: { result } "
499- self .logger .debug (result_msg )
524+
525+ self .logger .debug ("Rebuild callback completed with result: %s" , result )
500526 except Exception as e :
501- error_msg = f"Rebuild callback failed: { e } "
502- self .logger .error (error_msg )
503- import traceback
527+ self .logger .error ("Rebuild callback failed: %s" , e )
504528 traceback_msg = traceback .format_exc ()
505529 self .logger .error ("Traceback: %s" , traceback_msg )
506530 else :
507- no_callback_msg = "No rebuild callback configured"
508- self .logger .warning (no_callback_msg )
531+ self .logger .warning ("No rebuild callback configured" )
0 commit comments