Skip to content

feat: TTL eviction, disk guard, debug logging, and extended cache API#3

Closed
chanphiromsok wants to merge 6 commits into
mainfrom
claude/ttl-debug-logging-api
Closed

feat: TTL eviction, disk guard, debug logging, and extended cache API#3
chanphiromsok wants to merge 6 commits into
mainfrom
claude/ttl-debug-logging-api

Conversation

@chanphiromsok
Copy link
Copy Markdown
Owner

Summary

  • TTL-based cache eviction — files older than cacheTTLDays (default 2) are deleted on server start; fits daily-feed use cases where yesterday's segments are always stale
  • Disk space guard — if free disk drops below 500 MB, oldest files are evicted automatically during prune()
  • JS-controlled debug logging — pass debugLogging: true to startServer() to enable [HLSCache] stdout logs; gated by #if DEBUG so the flag is a compile-time no-op in release builds
  • Extended APIstopServer(), isRunning, invalidateUrl(url), getCacheStats() (fills gaps from CLAUDE.md TODO list)
  • FLOW_GUIDE.md — full technical reference: request lifecycle, eviction flow, design pattern map, API reference, debug log table

New API

// Lifecycle
await stopServer()
isServerRunning()  // synchronous boolean

// Cache management
await invalidateUrl("https://cdn.example.com/video.m3u8")  // single-URL eviction
const stats = await getCacheStats()
// → { fileCount, totalSizeBytes, freeDiskSpaceBytes }

// startServer — new optional params
await startServer(9000, 1_073_741_824, true, 2, true)
//                port  maxCacheSize  head ttl debug

Design patterns applied

Pattern Where
Cache-Aside DataSource.start()
Strategy prune() — TTL / disk guard / LRU passes
Bulkhead DispatchSemaphore(value: 32) in NetworkDownloader
Singleton CacheLogger.shared, NetworkDownloader.shared
Delegate chain ProxyConnectionDelegate → DataSourceDelegate → NetworkDownloaderDelegate

Test plan

  • startServer() with debugLogging: true — verify [HLSCache] lines appear in Xcode console
  • startServer() with release build — verify no log output and no binary strings
  • Play a video, call getCacheStats() — verify fileCount > 0 and totalSizeBytes > 0
  • Call invalidateUrl(url) on a cached URL, replay — verify MISS log on next request
  • Call stopServer(), verify isServerRunning() returns false
  • Start server again after stopServer() — verify playback resumes normally
  • Set cacheTTLDays: 0.00001, restart — verify all old files pruned on next startServer()
  • Run yarn nitrogen to verify bridge regenerates cleanly with new interface

https://claude.ai/code/session_016aKiYNJycuCWkNYogZNqsY

claude and others added 6 commits March 24, 2026 05:24
…ail, URL encoding

Four bugs were causing blank video when no internet connection:

1. serveFileFromDisk returned 200 OK for all range requests instead of
   206 Partial Content with Content-Range. AVPlayer sees a different status
   code offline vs online and fails to render cached segments correctly.

2. ClientConnectionHandler dropped the TCP connection with no HTTP response
   when a network fetch failed (content not cached + offline). AVPlayer
   treats a connection drop as a connect error rather than an HTTP error,
   giving up immediately instead of retrying or showing a useful state.
   Now returns HTTP 502 Bad Gateway when no headers have been sent yet.

3. sendRewrittenManifest omitted Content-Length, forcing AVPlayer to wait
   for connection close to determine manifest end, causing stalls.

4. addingPercentEncoding with .urlQueryAllowed left & and # unencoded in
   segment URLs embedded as query-parameter values. A URL such as
   segment.m4s?token=abc&sig=xyz was silently truncated to
   segment.m4s?token=abc, causing cache key mismatches on playback.
   Fixed in both convertUrl (HlsCache.swift) and rewriteLineToProxy
   (DataSource.swift) using a stricter character set.

https://claude.ai/code/session_016aKiYNJycuCWkNYogZNqsY
- New `cacheTTLDays` param in `startServer()` (default: 2 days) — files
  older than the TTL are evicted on server start, fitting daily-feed use
  cases where yesterday's segments are always stale.

- New `debugLogging` param in `startServer()` — enables `[HLSCache]`
  prefixed log lines (HIT/MISS/SAVED/FAIL, manifest rewrites, prune
  stats, connection count). Gated by `#if DEBUG` in CacheLogger.swift so
  the flag is a no-op in release builds with zero runtime overhead.

- `VideoCacheStorage.prune()` now runs three passes in order:
    1. TTL — delete files older than `cacheTTLDays`
    2. Disk guard — evict oldest files when free disk < 500 MB
    3. LRU — trim to `maxCacheSize` as before

- New `ios/CacheLogger.swift` — lightweight singleton logger that
  compiles out entirely in release builds.

- Android stub updated to match the new `startServer` signature (no-op).

Note: run `yarn nitrogen` to regenerate the Nitro bridge boilerplate
after this interface change.

https://claude.ai/code/session_016aKiYNJycuCWkNYogZNqsY
…GUIDE

New API surface (all platforms — Android stubs return resolved no-ops):

- stopServer()       — gracefully stop the proxy and all active connections
- isRunning          — synchronous boolean property (Nitro getter)
- invalidateUrl(url) — remove cached file for a single remote URL
- getCacheStats()    — returns { fileCount, totalSizeBytes, freeDiskSpaceBytes }

Implementation details:
- VideoCacheStorage.getStats() reads the cache directory once and sums
  file sizes without holding any locks — lightweight enough to call on demand
- VideoProxyServer.storage promoted from private to internal so HybridHlsCache
  can access it directly for invalidateUrl / getCacheStats without an extra
  indirection layer
- CacheStats is defined as a plain Nitro interface (generates a C++ struct
  after yarn nitrogen); Android stub constructs it with zero values

Also adds FLOW_GUIDE.md — a full technical reference covering:
  request lifecycle (hit / miss / manifest paths), eviction flow (TTL →
  disk guard → LRU), design pattern map, API reference, and debug log table.

Note: run yarn nitrogen to regenerate the Nitro bridge after the interface
changes in src/HlsCache.nitro.ts.

https://claude.ai/code/session_016aKiYNJycuCWkNYogZNqsY
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants