Skip to content

Commit 7fe4755

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 cf533cb commit 7fe4755

2 files changed

Lines changed: 119 additions & 4 deletions

File tree

node/flatpak_node_generator/cache.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import hashlib
34
import os
45
import re
56
import tempfile
@@ -152,17 +153,34 @@ class FilesystemBucketRef(Cache.BucketRef):
152153
def __init__(self, key: str, cache_root: Path) -> None:
153154
super().__init__(key)
154155
self._cache_root = cache_root
156+
self._cache_path = self._cache_root / self._hash_key(key)
157+
self._legacy_cache_path = (
158+
self._cache_root / FilesystemBasedCache._escape_key(key)
159+
)
160+
161+
@staticmethod
162+
def _hash_key(key: str) -> str:
163+
return hashlib.sha256(key.encode('utf-8')).hexdigest()
155164

156-
self._cache_path = self._cache_root / FilesystemBasedCache._escape_key(key)
165+
def _migrate_cache_path(self) -> None:
166+
if not self._cache_path.exists() and self._legacy_cache_path.exists():
167+
try:
168+
self._legacy_cache_path.rename(self._cache_path)
169+
except OSError:
170+
pass
157171

158172
def open_read(self) -> Cache.BucketReader | None:
173+
self._migrate_cache_path()
159174

160175
try:
161176
fp = self._cache_path.open('rb')
162177
except FileNotFoundError:
163-
return None
164-
else:
165-
return FilesystemBasedCache.FilesystemBucketReader(fp)
178+
try:
179+
fp = self._legacy_cache_path.open('rb')
180+
except FileNotFoundError:
181+
return None
182+
183+
return FilesystemBasedCache.FilesystemBucketReader(fp)
166184

167185
def open_write(self) -> Cache.BucketWriter:
168186
target = self._cache_path

node/tests/test_cache.py

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

0 commit comments

Comments
 (0)