From afee18f1465d48447f1fd35885e4f5f59940dd8b Mon Sep 17 00:00:00 2001 From: Will Miao <13051207myq@gmail.com> Date: Thu, 3 Apr 2025 11:09:30 +0800 Subject: [PATCH] Enhance file monitoring for LoRA files - Added a method to map symbolic links back to actual paths in the Config class. - Improved file creation handling in LoraFileHandler to check for file size and existence before processing. - Introduced handling for file modification events to update the ignore list and schedule updates. - Increased debounce delay in _process_changes to allow for file downloads to complete. - Enhanced action processing to prioritize 'add' actions and verify file existence before adding to cache. --- py/config.py | 11 ++++++ py/services/file_monitor.py | 74 ++++++++++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/py/config.py b/py/config.py index bae54b63..a081f472 100644 --- a/py/config.py +++ b/py/config.py @@ -85,6 +85,17 @@ class Config: mapped_path = normalized_path.replace(target_path, link_path, 1) return mapped_path return path + + def map_link_to_path(self, link_path: str) -> str: + """将符号链接路径映射回实际路径""" + normalized_link = os.path.normpath(link_path).replace(os.sep, '/') + # 检查路径是否包含在任何映射的目标路径中 + for target_path, link_path in self._path_mappings.items(): + if normalized_link.startswith(target_path): + # 如果路径以目标路径开头,则替换为实际路径 + mapped_path = normalized_link.replace(target_path, link_path, 1) + return mapped_path + return link_path def _init_lora_paths(self) -> List[str]: """Initialize and validate LoRA paths from ComfyUI settings""" diff --git a/py/services/file_monitor.py b/py/services/file_monitor.py index 12a76120..60463695 100644 --- a/py/services/file_monitor.py +++ b/py/services/file_monitor.py @@ -94,17 +94,59 @@ class LoraFileHandler(FileSystemEventHandler): return if self._should_ignore(event.src_path): return - logger.info(f"LoRA file created: {event.src_path}") - self._schedule_update('add', event.src_path) + + # Check if file is still being downloaded + try: + file_size = os.path.getsize(event.src_path) + # Record the file path and size to handle potential deletion during download + self.add_ignore_path(event.src_path, file_size) + + # Only process file if it exists and has non-zero size + if os.path.exists(event.src_path) and file_size > 0: + logger.info(f"LoRA file created: {event.src_path} (size: {file_size} bytes)") + self._schedule_update('add', event.src_path) + else: + logger.debug(f"Ignoring empty or non-existent file: {event.src_path}") + except FileNotFoundError: + # File disappeared between event and our check - likely a temporary download file + logger.debug(f"File disappeared before processing: {event.src_path}") + except Exception as e: + logger.error(f"Error processing create event for {event.src_path}: {str(e)}") def on_deleted(self, event): if event.is_directory or not event.src_path.endswith('.safetensors'): return + + # If this path is in our ignore list, it might be part of a download process + # Don't remove it from the cache yet if self._should_ignore(event.src_path): + logger.debug(f"Ignoring delete event for in-progress download: {event.src_path}") return + logger.info(f"LoRA file deleted: {event.src_path}") self._schedule_update('remove', event.src_path) + def on_modified(self, event): + if event.is_directory or not event.src_path.endswith('.safetensors'): + return + if self._should_ignore(event.src_path): + return + + try: + # File modification could indicate download completion + file_size = os.path.getsize(event.src_path) + if file_size > 0: + logger.debug(f"LoRA file modified: {event.src_path} (size: {file_size} bytes)") + # Update the ignore timeout based on the new size + self.add_ignore_path(event.src_path, file_size) + # Schedule an update to add the file once the ignore period expires + self._schedule_update('add', event.src_path) + except FileNotFoundError: + # File disappeared - ignore + pass + except Exception as e: + logger.error(f"Error processing modify event for {event.src_path}: {str(e)}") + def _schedule_update(self, action: str, file_path: str): #file_path is a real path """Schedule a cache update""" with self.lock: @@ -120,8 +162,8 @@ class LoraFileHandler(FileSystemEventHandler): if self.update_task is None or self.update_task.done(): self.update_task = asyncio.create_task(self._process_changes()) - async def _process_changes(self, delay: float = 2.0): - """Process pending changes with debouncing""" + async def _process_changes(self, delay: float = 5.0): + """Process pending changes with debouncing - increased delay to allow downloads to complete""" await asyncio.sleep(delay) try: @@ -134,13 +176,34 @@ class LoraFileHandler(FileSystemEventHandler): logger.info(f"Processing {len(changes)} file changes") + # First collect all actions by file path to handle contradicting events + actions_by_path = {} + for action, file_path in changes: + # For the same file path, 'add' takes precedence over 'remove' + if file_path not in actions_by_path or action == 'add': + actions_by_path[file_path] = action + + # Process the final actions cache = await self.scanner.get_cached_data() needs_resort = False new_folders = set() - for action, file_path in changes: + for file_path, action in actions_by_path.items(): try: + # For 'add' actions, verify the file still exists and is complete if action == 'add': + # Convert to real path for file system operations + real_path = config.map_link_to_path(file_path) + + if not os.path.exists(real_path): + logger.warning(f"Skipping add for non-existent file: {real_path}") + continue + + file_size = os.path.getsize(real_path) + if file_size == 0: + logger.warning(f"Skipping add for empty file: {real_path}") + continue + # Scan new file lora_data = await self.scanner.scan_single_lora(file_path) if lora_data: @@ -157,6 +220,7 @@ class LoraFileHandler(FileSystemEventHandler): lora_data['file_path'] ) needs_resort = True + logger.info(f"Added LoRA to cache: {file_path}") elif action == 'remove': # Find the lora to remove so we can update tags count