diff --git a/changedetectionio/model/Watch.py b/changedetectionio/model/Watch.py index dc7fbe8a..c7484f01 100644 --- a/changedetectionio/model/Watch.py +++ b/changedetectionio/model/Watch.py @@ -45,7 +45,7 @@ BROTLI_COMPRESS_SIZE_THRESHOLD = int(os.getenv('SNAPSHOT_BROTLI_COMPRESSION_THRE # Module-level favicon filename cache: data_dir → basename (or None) # Keyed by data_dir so it survives Watch object recreation, deepcopy, and concurrent requests. -# Invalidated explicitly in bump_favicon() when a new favicon is saved. +# Invalidated explicitly when the favicon is saved or the watch history is cleared. _FAVICON_FILENAME_CACHE: dict = {} minimum_seconds_recheck_time = int(os.getenv('MINIMUM_SECONDS_RECHECK_TIME', 3)) @@ -334,6 +334,8 @@ class model(EntityPersistenceMixin, watch_base): continue os.unlink(item) + _FAVICON_FILENAME_CACHE.pop(self.data_dir, None) + # Force the attr to recalculate bump = self.history @@ -896,7 +898,7 @@ class model(EntityPersistenceMixin, watch_base): Uses a module-level cache keyed by data_dir to survive Watch object recreation, deepcopy (which drops instance attrs), and concurrent request races. - Invalidated by bump_favicon() when a new favicon is saved. + Invalidated when a favicon is saved or the watch history is cleared. Returns: str: Basename of the favicon file, or None if not found. diff --git a/changedetectionio/tests/unit/test_watch_model.py b/changedetectionio/tests/unit/test_watch_model.py index 23ff1313..f7cb6159 100644 --- a/changedetectionio/tests/unit/test_watch_model.py +++ b/changedetectionio/tests/unit/test_watch_model.py @@ -6,6 +6,7 @@ import unittest import os import pickle +import tempfile from copy import deepcopy from changedetectionio.model import Watch, Tag @@ -249,6 +250,35 @@ class TestDiffBuilder(unittest.TestCase): self.assertLess(elapsed, 0.5, f"Deepcopy too slow ({elapsed:.3f}s for 10 copies) - might be copying datastore") + +class TestFaviconFilenameCache(unittest.TestCase): + + def test_clear_watch_invalidates_cached_favicon_filename(self): + from changedetectionio.model.Watch import _FAVICON_FILENAME_CACHE + + mock_datastore = {'settings': {'application': {}}, 'watching': {}} + + with tempfile.TemporaryDirectory() as datastore_path: + watch = Watch.model( + datastore_path=datastore_path, + __datastore=mock_datastore, + default={'url': 'https://example.com'} + ) + watch.ensure_data_dir_exists() + self.addCleanup(_FAVICON_FILENAME_CACHE.pop, watch.data_dir, None) + + favicon_path = os.path.join(watch.data_dir, 'favicon.ico') + with open(favicon_path, 'wb') as favicon_file: + favicon_file.write(b'favicon') + + self.assertEqual(watch.get_favicon_filename(), 'favicon.ico') + + watch.clear_watch() + + self.assertFalse(os.path.exists(favicon_path)) + self.assertIsNone(watch.get_favicon_filename()) + + class TestLLMDiffSummaryCache(unittest.TestCase): """Tests for get_llm_diff_summary / save_llm_diff_summary — version-pair + prompt-hash caching."""