Skip to content

Commit 8e6febb

Browse files
committed
node: Use hash based cache path in FilesystemBasedCache
FilesystemBasedCache previously used an escaped version of the full cache key as the filename. Since cache keys include full URLs, this could exceed the 255-byte filename limit. So use SHA256 based cache path. For backward compatibility, legacy cache entries are migrated to the new cache path. Fixes #443
1 parent 5777e68 commit 8e6febb

2 files changed

Lines changed: 116 additions & 4 deletions

File tree

node/flatpak_node_generator/cache.py

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import re
33
import tempfile
44
import types
5+
import hashlib
56
from pathlib import Path
67
from typing import IO, Iterator, Optional, Type
78

@@ -149,16 +150,34 @@ class FilesystemBucketRef(Cache.BucketRef):
149150
def __init__(self, key: str, cache_root: Path) -> None:
150151
super().__init__(key)
151152
self._cache_root = cache_root
153+
self._cache_path = self._cache_root / self._hash_key(key)
154+
self._legacy_cache_path = (
155+
self._cache_root / FilesystemBasedCache._escape_key(key)
156+
)
157+
158+
@staticmethod
159+
def _hash_key(key: str) -> str:
160+
return hashlib.sha256(key.encode('utf-8')).hexdigest()
152161

153-
self._cache_path = self._cache_root / FilesystemBasedCache._escape_key(key)
162+
def _migrate_cache_path(self) -> None:
163+
if not self._cache_path.exists() and self._legacy_cache_path.exists():
164+
try:
165+
self._legacy_cache_path.rename(self._cache_path)
166+
except OSError:
167+
pass
154168

155169
def open_read(self) -> Optional[Cache.BucketReader]:
170+
self._migrate_cache_path()
171+
156172
try:
157173
fp = self._cache_path.open('rb')
158174
except FileNotFoundError:
159-
return None
160-
else:
161-
return FilesystemBasedCache.FilesystemBucketReader(fp)
175+
try:
176+
fp = self._legacy_cache_path.open('rb')
177+
except FileNotFoundError:
178+
return None
179+
180+
return FilesystemBasedCache.FilesystemBucketReader(fp)
162181

163182
def open_write(self) -> Cache.BucketWriter:
164183
target = self._cache_path

node/tests/test_cache.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import hashlib
2+
from pathlib import Path
3+
4+
from flatpak_node_generator.cache import Cache, FilesystemBasedCache
5+
6+
7+
def test_cache_uses_hashed_filename(tmp_path: Path) -> None:
8+
cache = FilesystemBasedCache(tmp_path)
9+
Cache.instance = cache
10+
11+
key = 'remote-url-metadata:size:https://example.com/very/long/url'
12+
bucket = cache.get(key)
13+
14+
with bucket.open_write() as writer:
15+
writer.write(b'123')
16+
17+
files = list(tmp_path.iterdir())
18+
assert len(files) == 1
19+
20+
filename = files[0].name
21+
22+
assert len(filename) == 64
23+
assert filename == hashlib.sha256(key.encode('utf-8')).hexdigest()
24+
25+
26+
def test_cache_migrates_legacy_file(tmp_path: Path) -> None:
27+
cache = FilesystemBasedCache(tmp_path)
28+
Cache.instance = cache
29+
30+
key = 'remote-url-metadata:size:https://example.com/legacy'
31+
legacy_name = FilesystemBasedCache._escape_key(key)
32+
legacy_path = tmp_path / legacy_name
33+
34+
legacy_path.write_bytes(b'legacy-data')
35+
36+
bucket = cache.get(key)
37+
38+
reader = bucket.open_read()
39+
assert reader is not None
40+
assert reader.read_all() == b'legacy-data'
41+
reader.close()
42+
43+
files = list(tmp_path.iterdir())
44+
assert len(files) == 1
45+
46+
expected_hash = hashlib.sha256(key.encode('utf-8')).hexdigest()
47+
assert files[0].name == expected_hash
48+
49+
50+
def test_cache_fallback_if_migration_fails(tmp_path: Path, monkeypatch) -> None:
51+
cache = FilesystemBasedCache(tmp_path)
52+
Cache.instance = cache
53+
54+
key = 'remote-url-metadata:size:https://example.com/fallback'
55+
legacy_name = FilesystemBasedCache._escape_key(key)
56+
legacy_path = tmp_path / legacy_name
57+
58+
legacy_path.write_bytes(b'fallback-data')
59+
60+
def fail_rename(self, target):
61+
raise OSError('rename failed')
62+
63+
monkeypatch.setattr(Path, 'rename', fail_rename)
64+
65+
bucket = cache.get(key)
66+
67+
reader = bucket.open_read()
68+
assert reader is not None
69+
assert reader.read_all() == b'fallback-data'
70+
reader.close()
71+
72+
assert legacy_path.exists()
73+
74+
expected_hash = hashlib.sha256(key.encode('utf-8')).hexdigest()
75+
assert not (tmp_path / expected_hash).exists()
76+
77+
78+
def test_cache_never_creates_escaped_filename(tmp_path: Path) -> None:
79+
cache = FilesystemBasedCache(tmp_path)
80+
Cache.instance = cache
81+
82+
key = 'remote-url-metadata:size:https://example.com/test'
83+
escaped_name = FilesystemBasedCache._escape_key(key)
84+
85+
bucket = cache.get(key)
86+
87+
with bucket.open_write() as writer:
88+
writer.write(b'data')
89+
90+
assert not (tmp_path / escaped_name).exists()
91+
92+
expected_hash = hashlib.sha256(key.encode('utf-8')).hexdigest()
93+
assert (tmp_path / expected_hash).exists()

0 commit comments

Comments
 (0)