diff --git a/py/api/images.py b/py/api/images.py index 3ebe133..27aa0c1 100644 --- a/py/api/images.py +++ b/py/api/images.py @@ -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: diff --git a/tests/test_api_images.py b/tests/test_api_images.py index e81cda3..b52437d 100644 --- a/tests/test_api_images.py +++ b/tests/test_api_images.py @@ -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.""" diff --git a/web/js/gallery.js b/web/js/gallery.js index 0bb2831..2af6d51 100644 --- a/web/js/gallery.js +++ b/web/js/gallery.js @@ -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');