Build and Push Docker Container / build-and-push (push) Successful in 2m37s
File cache: - Add my_modules/filecache with metadata, blob, validator and store layers - Cache convex.get_file via AsyncCache, TTL clamped to the file expiry - Skip caching for missing, incomplete or already expired records - Add a process-local, size-bounded LRU for immutable storage blobs - Build the cache stack from FILE_CACHE_* env vars in build_file_cache - Attach the cache to the app in my_modules/app/setup.py - Expose app-bound helpers in filecache/access.py for routes Serving: - Send strong ETag, Last-Modified and Cache-Control on served files - Answer If-None-Match and If-Modified-Since with 304 from metadata only - Avoid pulling the blob from Convex on a revalidation hit - Add a short Cloudflare-CDN-Cache-Control edge window for deleted files - Record cache_status in the wide event on a 304 - Invalidate cached metadata after file update and delete in both routes Text viewer: - Add my_modules/textview to render text/plain and text/markdown as HTML - Linkify http(s) URLs and trim trailing sentence punctuation - Resolve links pointing at this instance to the referenced file - Inline image previews, mark missing and expired targets - Cap resolution at MAX_LOOKUPS backend lookups per document - Serve the viewer only for real browser navigations, never for curl or CLI - Keep raw bytes for ?raw=1, downloads and Accept headers without text/html - Add templates/side/views/webpage/files/text_view.htm with a raw link Other: - Add tests for cache layers, serving, text rendering and the route - Bump multidict to 7.2.1 in uv.lock - Update the my_helpers submodule pointer
44 lines
1.3 KiB
Python
44 lines
1.3 KiB
Python
from __future__ import annotations
|
|
|
|
from quart import current_app
|
|
|
|
DEFAULT_BROWSER_MAX_AGE = 86400
|
|
DEFAULT_EDGE_MAX_AGE = 1800
|
|
|
|
def file_cache():
|
|
"""The app's FileCache, or None when caching is not configured."""
|
|
return getattr(current_app, 'file_cache', None)
|
|
|
|
def browser_max_age() -> int:
|
|
cache = file_cache()
|
|
return DEFAULT_BROWSER_MAX_AGE if cache is None else cache.blob_max_age
|
|
|
|
def edge_max_age() -> int:
|
|
cache = file_cache()
|
|
return DEFAULT_EDGE_MAX_AGE if cache is None else cache.edge_max_age
|
|
|
|
async def load_file(file_id: str):
|
|
async def loader():
|
|
return await current_app.convex.get_file(file_id=file_id)
|
|
|
|
cache = file_cache()
|
|
if cache is None:
|
|
return await loader()
|
|
return await cache.get_metadata(file_id, loader)
|
|
|
|
async def load_blob(storage_key: str) -> bytes:
|
|
async def loader() -> bytes:
|
|
payload = await current_app.convex.get_from_storage(storage_key)
|
|
return payload.getvalue() if hasattr(payload, 'getvalue') else bytes(payload)
|
|
|
|
cache = file_cache()
|
|
if cache is None:
|
|
return await loader()
|
|
return await cache.get_blob(storage_key, loader)
|
|
|
|
async def drop_file_cache(file_id: str) -> None:
|
|
"""Invalidate metadata after a mutation; blobs are immutable and expire on TTL."""
|
|
cache = file_cache()
|
|
if cache is not None:
|
|
await cache.invalidate(file_id)
|