mirror of
https://github.com/dgtlmoon/changedetection.io.git
synced 2026-08-23 06:37:10 +00:00
fix: invalidate favicon cache when clearing watch history (#4286)
Co-authored-by: snowyukitty <270071858+snowyukitty@users.noreply.github.com>
This commit is contained in:
co-authored by
snowyukitty
parent
c3361943aa
commit
3e3300896f
@@ -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."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user