diff --git a/database/run_attribution.py b/database/run_attribution.py index 2f0cbd99..50a671db 100644 --- a/database/run_attribution.py +++ b/database/run_attribution.py @@ -300,18 +300,29 @@ def recorded_output( _LOGGER = logging.getLogger("prompt_manager.run_attribution") -def recorded_output_lookup(db: Any, *, close: bool = False) -> Callable[[Any], Any]: +def recorded_output_lookup( + db: Any, *, close: bool = False, reference: Optional[dict] = None +) -> Callable[[Any], Any]: """``lookup(path) -> RecordedOutput or None`` for the inspect routes (#386). Uses ComfyUI's output folder. Any failure (no history, a locked library) is logged and names no node, so the file is judged from its own metadata. ``close`` closes the calling thread's connection afterwards, for routes - that close theirs after reading. + that close theirs after reading. ``reference`` (a history occurrence's + output reference) spells the file as history recorded it: the route's + target is case-normalized on Windows, and history matches names exactly. """ def lookup(path: Any) -> Optional[RecordedOutput]: + root = host_output_directory() + if reference is not None: + if reference.get("folder_type") != "output" or not root: + return None + path = os.path.join( + root, reference.get("subfolder") or "", reference.get("filename") or "" + ) try: - return recorded_output(db, str(path), host_output_directory()) + return recorded_output(db, str(path), root) except (sqlite3.Error, OSError, ValueError): _LOGGER.warning("Recorded run lookup failed", exc_info=True) return None diff --git a/py/api/history.py b/py/api/history.py index e7f3affd..be86f5ab 100644 --- a/py/api/history.py +++ b/py/api/history.py @@ -730,7 +730,9 @@ class HistoryRoutesMixin: "library", {"status": "unavailable" if saved_unavailable else "none"}, ), - recorded=recorded_output_lookup(db, close=True), + recorded=recorded_output_lookup( + db, close=True, reference=receipt["reference"] + ), ) _reauthorize_current_target(resolved, scope) identity = { diff --git a/tests/test_run_attribution.py b/tests/test_run_attribution.py index 592a7768..050d710a 100644 --- a/tests/test_run_attribution.py +++ b/tests/test_run_attribution.py @@ -336,6 +336,28 @@ class TestRecordedOutputNode(RecordedRuns, unittest.TestCase): ) broken.model.close.assert_called_once_with() + def test_a_history_reference_spells_the_file_as_history_recorded_it(self): + # On Windows the history route's target is normcased (lower case); + # history matches names exactly, so the route passes its reference. + from database.run_attribution import recorded_output_lookup + + upper = os.path.join(self.output, "sub", "IMG_00001_.png") + Path(upper).write_bytes(b"png") + self.record_run(self.db, self.output, [("sub", "IMG_00001_.png")], []) + target = os.path.join(self.output, "sub", "img_00001_.png") + reference = { + "folder_type": "output", + "subfolder": "sub", + "filename": "IMG_00001_.png", + } + with mock.patch( + "database.run_attribution.host_output_directory", return_value=self.output + ): + found = recorded_output_lookup(self.db, reference=reference)(target) + self.assertEqual(found.saver_id, "9") + temp = {**reference, "folder_type": "temp"} + self.assertIsNone(recorded_output_lookup(self.db, reference=temp)(target)) + def test_a_library_without_history_tables_names_nothing(self): broken = mock.Mock() broken.model.get_connection.return_value.execute.side_effect = (