From 6a5834e243b496db7dfd252e37b69c0a790c57a9 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Mon, 9 Feb 2026 04:52:03 -0800 Subject: [PATCH 1/3] 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() From 30f83f2401cbc1c193c27a395ad427eaec7d3cb2 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Mon, 9 Feb 2026 05:46:38 -0800 Subject: [PATCH 2/3] =?UTF-8?q?fix:=20address=20code=20review=20=E2=80=94?= =?UTF-8?q?=20unused=20import,=20silent=20except,=20gallery=20cache?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove unused `patch` import from test_output_dir.py - Add debug log message to ImportError handler instead of bare pass - Invalidate gallery file listing cache alongside output dir cache when the user changes gallery_root_path in settings --- py/api/__init__.py | 4 +++- py/api/admin.py | 4 +++- tests/test_output_dir.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/py/api/__init__.py b/py/api/__init__.py index 4a496a0..723c78c 100644 --- a/py/api/__init__.py +++ b/py/api/__init__.py @@ -482,7 +482,9 @@ class PromptManagerAPI( f"Configured directory does not exist: {GalleryConfig.MONITORING_DIRECTORIES[0]}" ) except ImportError: - pass + self.logger.debug( + "GalleryConfig not available, skipping configured directory check" + ) 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 b5e49a3..b4060c6 100644 --- a/py/api/admin.py +++ b/py/api/admin.py @@ -448,8 +448,10 @@ class AdminRoutesMixin: GalleryConfig.MONITORING_DIRECTORIES = [new_path] else: GalleryConfig.MONITORING_DIRECTORIES = [] - # Invalidate cached output dir so next lookup uses new config + # Invalidate caches so next lookup uses new config self._cached_output_dir = None + self._gallery_cache = None + self._gallery_cache_time = 0 restart_required = True # Save to config file for persistence diff --git a/tests/test_output_dir.py b/tests/test_output_dir.py index 21b1ad7..4920690 100644 --- a/tests/test_output_dir.py +++ b/tests/test_output_dir.py @@ -9,7 +9,7 @@ import sys import tempfile import unittest from pathlib import Path -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) From d60e4ca52f3ebabc6ba848ffc5166e9f24feea22 Mon Sep 17 00:00:00 2001 From: Vito Sansevero Date: Mon, 9 Feb 2026 05:48:19 -0800 Subject: [PATCH 3/3] chore: bump project version to 3.0.37 in pyproject.toml --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index c00407e..75cbf53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "promptmanager" description = "A powerful ComfyUI custom node that extends the standard text encoder with persistent prompt storage, advanced search capabilities, and an automatic image gallery system using SQLite." -version = "3.0.36" +version = "3.0.37" license = {file = "LICENSE"} dependencies = ["# Core dependencies for PromptManager", "# Note: Most dependencies are already included with ComfyUI", "# Already included with Python standard library:", "# - sqlite3", "# - hashlib", "# - json", "# - datetime", "# - os", "# - typing", "# - threading", "# - uuid", "# Required for gallery functionality:", "watchdog>=2.1.0 # For file system monitoring", "Pillow>=8.0.0 # For image metadata extraction (usually included with ComfyUI)", "# Optional dependencies for enhanced search functionality:", "# fuzzywuzzy[speedup]>=0.18.0 # For fuzzy string matching (optional)", "# sqlalchemy>=1.4.0 # For advanced ORM features (optional)", "# Development dependencies (optional):", "# pytest>=6.0.0 # For running tests", "# black>=22.0.0 # For code formatting", "# flake8>=4.0.0 # For linting", "# mypy>=0.910 # For type checking"]