From 6a5834e243b496db7dfd252e37b69c0a790c57a9 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Mon, 9 Feb 2026 04:52:03 -0800 Subject: [PATCH] fix: use configured directory for image scan instead of always auto-detecting _find_comfyui_output_dir() now checks GalleryConfig.MONITORING_DIRECTORIES first before falling back to filesystem auto-detection. Also invalidates the cached directory when the user changes the setting via save_settings(). Closes #108 --- py/api/__init__.py | 30 +++++++++- py/api/admin.py | 2 + tests/test_output_dir.py | 115 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 tests/test_output_dir.py diff --git a/py/api/__init__.py b/py/api/__init__.py index 457565d..4a496a0 100644 --- a/py/api/__init__.py +++ b/py/api/__init__.py @@ -448,8 +448,15 @@ class PromptManagerAPI( def _find_comfyui_output_dir(self): """Locate the ComfyUI output directory using multiple detection strategies. - Results are cached after first successful lookup since the output - directory does not change during runtime. + Checks in order: + 1. User-configured directory (GalleryConfig.MONITORING_DIRECTORIES) + 2. Upward filesystem search for ComfyUI markers + 3. Common installation path patterns + 4. Common ComfyUI installation locations + + Results are cached after first successful lookup. The cache is + invalidated when the user changes the configured directory via + save_settings(). Returns: str or None: Absolute path to ComfyUI output directory, or None @@ -458,6 +465,25 @@ class PromptManagerAPI( if self._cached_output_dir is not None: return self._cached_output_dir + # Method 0: Check user-configured directory first + try: + from ..config import GalleryConfig + + if GalleryConfig.MONITORING_DIRECTORIES: + configured_dir = Path(GalleryConfig.MONITORING_DIRECTORIES[0]).resolve() + if configured_dir.exists() and configured_dir.is_dir(): + self.logger.info( + f"Using configured monitoring directory: {configured_dir}" + ) + self._cached_output_dir = str(configured_dir) + return self._cached_output_dir + else: + self.logger.warning( + f"Configured directory does not exist: {GalleryConfig.MONITORING_DIRECTORIES[0]}" + ) + except ImportError: + pass + current_file = Path(__file__).resolve() self.logger.debug(f"Starting ComfyUI output search from: {current_file}") diff --git a/py/api/admin.py b/py/api/admin.py index 2e0db4e..b5e49a3 100644 --- a/py/api/admin.py +++ b/py/api/admin.py @@ -448,6 +448,8 @@ class AdminRoutesMixin: GalleryConfig.MONITORING_DIRECTORIES = [new_path] else: GalleryConfig.MONITORING_DIRECTORIES = [] + # Invalidate cached output dir so next lookup uses new config + self._cached_output_dir = None restart_required = True # Save to config file for persistence diff --git a/tests/test_output_dir.py b/tests/test_output_dir.py new file mode 100644 index 0000000..21b1ad7 --- /dev/null +++ b/tests/test_output_dir.py @@ -0,0 +1,115 @@ +""" +Tests for _find_comfyui_output_dir() — verifies that user-configured +directories take priority over auto-detection, and that the cache is +invalidated when the setting changes. +""" + +import os +import sys +import tempfile +import unittest +from pathlib import Path +from unittest.mock import MagicMock, patch + +sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + +# Mock ComfyUI's server module before importing anything that touches config +_mock_server = MagicMock() +_mock_server.PromptServer.instance.routes = MagicMock() +sys.modules["server"] = _mock_server + +from py.api import PromptManagerAPI +from py.config import GalleryConfig + + +class TestFindComfyuiOutputDir(unittest.TestCase): + """Test _find_comfyui_output_dir() respects user-configured directories.""" + + def setUp(self): + self.api = PromptManagerAPI() + self.api._cached_output_dir = None + self._orig_dirs = list(GalleryConfig.MONITORING_DIRECTORIES) + + def tearDown(self): + GalleryConfig.MONITORING_DIRECTORIES = self._orig_dirs + + def test_configured_directory_takes_priority(self): + """When a valid directory is configured, it should be used instead of auto-detect.""" + with tempfile.TemporaryDirectory() as tmpdir: + GalleryConfig.MONITORING_DIRECTORIES = [tmpdir] + self.api._cached_output_dir = None + + result = self.api._find_comfyui_output_dir() + + self.assertEqual(result, str(Path(tmpdir).resolve())) + + def test_nonexistent_configured_directory_falls_through(self): + """When the configured directory doesn't exist, fall back to auto-detection.""" + GalleryConfig.MONITORING_DIRECTORIES = ["/nonexistent/fake/path/output"] + self.api._cached_output_dir = None + + # Should not return the nonexistent path — will either find + # ComfyUI output via auto-detect or return None + result = self.api._find_comfyui_output_dir() + + self.assertNotEqual(result, "/nonexistent/fake/path/output") + + def test_empty_config_uses_auto_detection(self): + """When MONITORING_DIRECTORIES is empty, auto-detection should be used.""" + GalleryConfig.MONITORING_DIRECTORIES = [] + self.api._cached_output_dir = None + + # Should not raise — auto-detection may or may not find a dir + result = self.api._find_comfyui_output_dir() + # Result is either a valid path or None + if result is not None: + self.assertTrue(Path(result).is_dir()) + + def test_cache_returns_same_value(self): + """After first lookup, cached value should be returned.""" + with tempfile.TemporaryDirectory() as tmpdir: + GalleryConfig.MONITORING_DIRECTORIES = [tmpdir] + self.api._cached_output_dir = None + + result1 = self.api._find_comfyui_output_dir() + # Change config — should NOT affect result because of cache + GalleryConfig.MONITORING_DIRECTORIES = ["/some/other/path"] + result2 = self.api._find_comfyui_output_dir() + + self.assertEqual(result1, result2) + + def test_cache_invalidation_picks_up_new_directory(self): + """After clearing the cache, _find_comfyui_output_dir should use new config.""" + with tempfile.TemporaryDirectory() as tmpdir1: + with tempfile.TemporaryDirectory() as tmpdir2: + GalleryConfig.MONITORING_DIRECTORIES = [tmpdir1] + self.api._cached_output_dir = None + + result1 = self.api._find_comfyui_output_dir() + self.assertEqual(result1, str(Path(tmpdir1).resolve())) + + # Simulate what save_settings does: update config + invalidate cache + GalleryConfig.MONITORING_DIRECTORIES = [tmpdir2] + self.api._cached_output_dir = None + + result2 = self.api._find_comfyui_output_dir() + self.assertEqual(result2, str(Path(tmpdir2).resolve())) + + def test_configured_directory_is_resolved(self): + """Configured paths with symlinks or .. should be resolved to absolute.""" + with tempfile.TemporaryDirectory() as tmpdir: + # Use a relative-ish path with .. + parent = str(Path(tmpdir).parent) + basename = Path(tmpdir).name + dotdot_path = os.path.join(parent, ".", basename) + + GalleryConfig.MONITORING_DIRECTORIES = [dotdot_path] + self.api._cached_output_dir = None + + result = self.api._find_comfyui_output_dir() + + self.assertEqual(result, str(Path(tmpdir).resolve())) + + +if __name__ == "__main__": + unittest.main()