fix: invalidate favicon cache when clearing watch history (#4286)

Co-authored-by: snowyukitty <270071858+snowyukitty@users.noreply.github.com>
This commit is contained in:
snowyukitty
2026-08-16 14:24:45 +02:00
committed by GitHub
co-authored by snowyukitty
parent c3361943aa
commit 3e3300896f
2 changed files with 34 additions and 2 deletions
+4 -2
View File
@@ -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.
@@ -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."""