fix(gallery): Rescan Output Folder drops the server listing cache and rewalks

The button only re-requested the listing, which the server answers from a
30-second in-memory cache, so a rescan showed exactly what was already on
screen.

- POST /prompt_manager/gallery/rescan invalidates the gallery cache,
  rewalks every output root and returns the file and root counts
- the button calls it, closes the settings modal, reloads the folder tree
  and the first page, and reports what it found
This commit is contained in:
Vito Sansevero
2026-10-08 18:01:35 -07:00
parent 6e3fa65914
commit c5846306f6
3 changed files with 72 additions and 3 deletions
+26
View File
@@ -397,6 +397,11 @@ class ImageRoutesMixin:
async def get_gallery_subfolders_route(request):
return await self.get_gallery_subfolders(request)
# POST: drops the server-side listing cache and rewalks the output roots.
@routes.post("/prompt_manager/gallery/rescan")
async def rescan_gallery_route(request):
return await self.rescan_gallery(request)
def _present_images(self, images):
"""Image dicts ready for a response: urls added, server paths hidden."""
self._enrich_images(images)
@@ -736,6 +741,27 @@ class ImageRoutesMixin:
{"success": False, "error": _safe_error(e)}, status=500
)
async def rescan_gallery(self, request):
"""Forget the cached listing and rewalk every output root now.
The listing is cached for ``_gallery_cache_ttl`` seconds; the gallery's
Rescan button needs files added or removed on disk to show up at once.
"""
try:
self.invalidate_gallery_cache()
output_dirs = list(self._get_all_output_dirs())
total = 0
for output_path in output_dirs:
total += len(await self._get_gallery_files(output_path))
return web.json_response(
{"success": True, "total": total, "roots": len(output_dirs)}
)
except Exception:
self.logger.exception("Gallery rescan error")
return web.json_response(
{"success": False, "error": "Failed to rescan the gallery"}, status=500
)
async def get_gallery_subfolders(self, request):
"""Get distinct subfolders from gallery output directories."""
try:
+40
View File
@@ -1717,6 +1717,46 @@ if __name__ == "__main__":
unittest.main()
class TestGalleryRescan(ImageAPITestCase):
"""POST /prompt_manager/gallery/rescan drops the listing cache and rewalks."""
URL = "/prompt_manager/images/output"
async def _json(self, method, path, **kwargs):
resp = await self.client.request(method, path, **kwargs)
return resp.status, await resp.json()
async def test_rescan_makes_new_files_visible_before_the_cache_expires(self):
self.api._gallery_cache_ttl = 3600 # the cache alone would hide the new file
make_png(self.output_dir / "a.png")
_, first = await self._json("GET", self.URL)
self.assertEqual(first["total"], 1)
make_png(self.output_dir / "b.png")
_, stale = await self._json("GET", self.URL)
self.assertEqual(stale["total"], 1)
status, data = await self._json("POST", "/prompt_manager/gallery/rescan")
_, fresh = await self._json("GET", self.URL)
self.assertEqual(status, 200)
self.assertTrue(data["success"])
self.assertEqual(data["total"], 2)
self.assertEqual(data["roots"], 1)
self.assertEqual(fresh["total"], 2)
async def test_rescan_is_post_only(self):
resp = await self.client.request("GET", "/prompt_manager/gallery/rescan")
self.assertEqual(resp.status, 405)
async def test_rescan_reports_internal_failure(self):
self.api._scan_gallery_files_sync = _raise
status, data = await self._json("POST", "/prompt_manager/gallery/rescan")
self.assertEqual(status, 500)
self.assertFalse(data["success"])
self.assertNotIn("boom", json.dumps(data))
class TestOutputScanHelpers(ImageAPITestCase):
"""Stable ids for filesystem entries, no symlink reads, stop on disconnect."""
+6 -3
View File
@@ -2123,9 +2123,12 @@ Seed: ${this.currentMetadata.seed || 'Unknown'}`;
btn.textContent = 'Scanning...';
try {
// Force reload images
await this.loadImages();
this.showNotification('Folder rescanned successfully!', 'success');
// The listing is cached on the server; ask it to rewalk the folders first
const result = await this.api.post('/prompt_manager/gallery/rescan', {});
this.hideSettings();
await Promise.all([this.loadFolderTree(), this.loadImages(1)]);
const where = result.roots === 1 ? 'the output folder' : `${result.roots} folders`;
this.showNotification(`Rescanned ${where}: ${result.total} files`, 'success');
} catch (error) {
console.error('Rescan error:', error);
this.showNotification('Failed to rescan folder', 'error');