music-kraken-core/src/music_kraken/connection/cache.py

135 lines
3.8 KiB
Python
Raw Normal View History

2024-01-17 11:46:55 +00:00
import json
from pathlib import Path
from dataclasses import dataclass
from datetime import datetime, timedelta
from typing import List, Optional
2024-01-17 11:54:02 +00:00
from functools import lru_cache
import logging
2024-01-17 11:46:55 +00:00
from ..utils.config import main_settings
2024-01-17 11:46:55 +00:00
@dataclass
class CacheAttribute:
module: str
name: str
created: datetime
expires: datetime
@property
def id(self):
return f"{self.module}_{self.name}"
@property
def is_valid(self):
return datetime.now() < self.expires
def __eq__(self, other):
return self.__dict__ == other.__dict__
class Cache:
def __init__(self, module: str, logger: logging.Logger):
self.module = module
self.logger: logging.Logger = logger
2024-01-17 11:46:55 +00:00
self._dir = main_settings["cache_directory"]
self.index = Path(self._dir, "index.json")
if not self.index.is_file():
with self.index.open("w") as i:
i.write(json.dumps([]))
self.cached_attributes: List[CacheAttribute] = []
self._id_to_attribute = {}
self._time_fields = {"created", "expires"}
with self.index.open("r") as i:
for c in json.loads(i.read()):
for key in self._time_fields:
c[key] = datetime.fromisoformat(c[key])
2024-01-17 11:54:02 +00:00
ca = CacheAttribute(**c)
self.cached_attributes.append(ca)
self._id_to_attribute[ca.id] = ca
2024-01-17 11:46:55 +00:00
2024-01-17 11:54:02 +00:00
@lru_cache()
2024-01-17 11:46:55 +00:00
def _init_module(self, module: str) -> Path:
"""
:param module:
:return: the module path
"""
r = Path(self._dir, module)
r.mkdir(exist_ok=True)
return r
def _write_attribute(self, cached_attribute: CacheAttribute, write: bool = True) -> bool:
existing_attribute: Optional[CacheAttribute] = self._id_to_attribute.get(cached_attribute.id)
if existing_attribute is not None:
# the attribute exists
if existing_attribute == cached_attribute:
return True
if existing_attribute.is_valid:
return False
existing_attribute.__dict__ = cached_attribute.__dict__
else:
self.cached_attributes.append(cached_attribute)
self._id_to_attribute[cached_attribute.id] = cached_attribute
if write:
_json = []
for c in self.cached_attributes:
d = c.__dict__
for key in self._time_fields:
d[key] = d[key].isoformat()
_json.append(d)
with self.index.open("w") as f:
f.write(json.dumps(_json, indent=4))
return True
def set(self, content: bytes, name: str, expires_in: float = 10):
2024-01-17 11:46:55 +00:00
"""
:param content:
:param module:
:param name:
:param expires_in: the unit is days
:return:
"""
if name == "":
return
2024-01-17 11:46:55 +00:00
module_path = self._init_module(self.module)
2024-01-17 11:46:55 +00:00
cache_attribute = CacheAttribute(
module=self.module,
2024-01-17 11:46:55 +00:00
name=name,
created=datetime.now(),
expires=datetime.now() + timedelta(days=expires_in),
)
self._write_attribute(cache_attribute)
cache_path = Path(module_path, name)
with cache_path.open("wb") as content_file:
self.logger.debug(f"writing cache to {cache_path}")
2024-01-17 11:46:55 +00:00
content_file.write(content)
2024-01-17 11:54:02 +00:00
def get(self, name: str) -> Optional[bytes]:
path = Path(self._dir, self.module, name)
2024-01-17 11:54:02 +00:00
if not path.is_file():
return None
# check if it is outdated
existing_attribute: CacheAttribute = self._id_to_attribute[f"{self.module}_{name}"]
2024-01-17 11:54:02 +00:00
if not existing_attribute.is_valid:
return
with path.open("rb") as f:
return f.read()