memory (cache): add persistent cache entries:

`max_age` can now be `C.MAX_AGE_PERSISTENT` to keep a cache entry indefinitely: its `eol`
is then `None` and no expiration check is done.
This commit is contained in:
Goffi 2026-10-20 12:27:03 +02:00
commit bd1c93f7d4
3 changed files with 12 additions and 7 deletions

View file

@ -525,6 +525,7 @@ class Const:
IGNORE = "ignore" IGNORE = "ignore"
NO_LIMIT = -1 # used in bridge when a integer value is expected NO_LIMIT = -1 # used in bridge when a integer value is expected
DEFAULT_MAX_AGE = 1209600 # default max age of cached files, in seconds DEFAULT_MAX_AGE = 1209600 # default max age of cached files, in seconds
MAX_AGE_PERSISTENT = -1 # keep a cache entry indefinitely
STANZA_NAMES = ("iq", "message", "presence") STANZA_NAMES = ("iq", "message", "presence")
# Stream Hooks # Stream Hooks

View file

@ -22,7 +22,7 @@ from pathlib import Path
import time import time
from typing import Any from typing import Any
from pydantic import BaseModel, ValidationError from pydantic import BaseModel, Field, ValidationError
from libervia.backend import G from libervia.backend import G
from libervia.backend.core import exceptions from libervia.backend.core import exceptions
@ -42,7 +42,9 @@ class CacheMetadata(BaseModel):
uid: str uid: str
filename: str filename: str
creation: int creation: int
eol: int eol: int | None = Field(
default=None, description="End of life. If None, entry is kept indefinitely"
)
max_age: int = C.DEFAULT_MAX_AGE max_age: int = C.DEFAULT_MAX_AGE
original_filename: str | None = None original_filename: str | None = None
mime_type: str | None = None mime_type: str | None = None
@ -95,7 +97,7 @@ class Cache:
f"and will be deleted: {cache_data_file!r}." f"and will be deleted: {cache_data_file!r}."
) )
to_delete.add(cache_data_file) to_delete.add(cache_data_file)
elif cache_data.eol < now: elif cache_data.eol is not None and cache_data.eol < now:
log.debug( log.debug(
f"Purging expired cache file {cache_data_file} (expired for " f"Purging expired cache file {cache_data_file} (expired for "
f"{int(time.time() - cache_data.eol)}s)" f"{int(time.time() - cache_data.eol)}s)"
@ -156,7 +158,7 @@ class Cache:
log.warning(f"Invalid encoding, this is not a cache metadata file.") log.warning(f"Invalid encoding, this is not a cache metadata file.")
return None return None
if cache_data.eol < time.time(): if cache_data.eol is not None and cache_data.eol < time.time():
log.debug( log.debug(
"removing expired cache (expired for {}s)".format( "removing expired cache (expired for {}s)".format(
time.time() - cache_data.eol time.time() - cache_data.eol
@ -164,7 +166,7 @@ class Cache:
) )
return None return None
if update_eol: if update_eol and cache_data.eol is not None:
now = int(time.time()) now = int(time.time())
cache_data.last_access = now cache_data.last_access = now
cache_data.eol = now + cache_data.max_age cache_data.eol = now + cache_data.max_age
@ -236,6 +238,7 @@ class Cache:
the cache metadata will have an "eol" (end of life) the cache metadata will have an "eol" (end of life)
None to use default value None to use default value
0 to ignore cache (file will be re-downloaded on each access) 0 to ignore cache (file will be re-downloaded on each access)
C.MAX_AGE_PERSISTENT to keep the entry in cache indefinitely
@param original_filename: if not None, will be used to retrieve file extension and @param original_filename: if not None, will be used to retrieve file extension and
guess mime type, and stored in "original_filename" guess mime type, and stored in "original_filename"
@return: file object opened in write mode @return: file object opened in write mode
@ -266,7 +269,7 @@ class Cache:
original_filename=original_filename, original_filename=original_filename,
filename=filename, filename=filename,
creation=now, creation=now,
eol=now + max_age, eol=None if max_age == C.MAX_AGE_PERSISTENT else now + max_age,
) )
cache_metadata_file = self.get_path(f"{uid}{CACHE_METADATA_EXT}") cache_metadata_file = self.get_path(f"{uid}{CACHE_METADATA_EXT}")

View file

@ -194,7 +194,8 @@ class XEP_0231(object):
) )
data_elt["cid"] = cid data_elt["cid"] = cid
data_elt["type"] = metadata["mime_type"] data_elt["type"] = metadata["mime_type"]
data_elt["max-age"] = str(int(max(0, metadata["eol"] - time.time()))) if metadata["eol"] is not None:
data_elt["max-age"] = str(int(max(0, metadata["eol"] - time.time())))
client.send(result_elt) client.send(result_elt)
def _get_file(self, peer_jid_s, cid, profile): def _get_file(self, peer_jid_s, cid, profile):