Honor configurable cache size with LRU eviction

This commit is contained in:
SpiderFish
2026-08-26 10:06:03 +10:00
parent 093315b382
commit 0a07afef85
3 changed files with 64 additions and 6 deletions
+31 -6
View File
@@ -31,6 +31,7 @@ JELLYFIN_LIBRARY_ROOT = os.environ.get("JELLYFIN_LIBRARY_ROOT", "/data/library")
JELLYFIN_ORIGINAL_ROOT = os.environ.get("JELLYFIN_ORIGINAL_ROOT", "/data/original").rstrip("/")
JELLYFIN_CACHE_ROOT = os.environ.get("JELLYFIN_CACHE_ROOT", "/data/cache").rstrip("/")
JELLYFIN_API_KEY = os.environ.get("JELLYFIN_API_KEY", "")
CACHE_MAX_BYTES = env_int("CACHE_MAX_BYTES", 1_000_000_000_000)
SERIES_COOLDOWN_SECONDS = env_int("SERIES_COOLDOWN_SECONDS", 3600)
POLL_SECONDS = env_int("POLL_SECONDS", 5)
RESCAN_SECONDS = env_int("RESCAN_SECONDS", 300)
@@ -501,9 +502,33 @@ class Manager:
manifest.unlink(missing_ok=True)
return True
def cache_usage(self):
return sum(
path.stat().st_size
for path in (CACHE_ROOT / "files").iterdir()
if path.is_file()
)
def cache_available(self):
stat = os.statvfs(CACHE_ROOT)
filesystem_free = stat.f_bavail * stat.f_frsize
configured_free = max(0, CACHE_MAX_BYTES - self.cache_usage())
return min(filesystem_free, configured_free)
def enforce_cache_limit(self):
usage = self.cache_usage()
for _, relative, output, manifest in self.cache_entries():
if usage <= CACHE_MAX_BYTES:
break
if self.is_locked(relative):
continue
size = output.stat().st_size + manifest.stat().st_size
log(f"Evicting {relative} after cache resize")
if self.remove_cache(relative):
usage -= size
def ensure_space(self, required, may_evict):
free = os.statvfs(CACHE_ROOT).f_bavail * os.statvfs(CACHE_ROOT).f_frsize
if free >= required:
if self.cache_available() >= required:
return True
if not may_evict:
return False
@@ -512,8 +537,7 @@ class Manager:
continue
log(f"Evicting {relative}")
self.remove_cache(relative)
free = os.statvfs(CACHE_ROOT).f_bavail * os.statvfs(CACHE_ROOT).f_frsize
if free >= required:
if self.cache_available() >= required:
return True
return False
@@ -625,6 +649,7 @@ class Manager:
if not JELLYFIN_API_KEY:
raise RuntimeError("JELLYFIN_API_KEY is required")
self.read_sessions()
self.enforce_cache_limit()
self.scan_inventory()
self.sync_view()
self.save()
@@ -632,6 +657,7 @@ class Manager:
if not self.read_sessions():
time.sleep(POLL_SECONDS)
continue
self.enforce_cache_limit()
now = time.time()
if now - self.last_scan >= RESCAN_SECONDS:
changed = self.scan_inventory()
@@ -651,8 +677,7 @@ class Manager:
for rank, _, relative in candidates:
source = SOURCE_ROOT / relative
estimate = min(max(int(source.stat().st_size * 2.2), 1_000_000_000), 100_000_000_000)
free = os.statvfs(CACHE_ROOT).f_bavail * os.statvfs(CACHE_ROOT).f_frsize
if rank == 0 or free >= estimate:
if rank == 0 or self.cache_available() >= estimate:
selected = (rank == 0, relative)
break
if not selected: