# Copyright 2023 (c) Anna Schumaker. """A cache to hold Gdk.Textures used by cover art.""" import pathlib import sys from gi.repository import GLib from gi.repository import Gdk if "unittest" in sys.modules: import tempfile TEMP_DIR = tempfile.TemporaryDirectory(prefix="emmental-") CACHE_PATH = pathlib.Path(TEMP_DIR.name) else: from . import gsetup CACHE_PATH = gsetup.CACHE_DIR CACHE_PATH.mkdir(parents=True, exist_ok=True) class _TextureCache(dict): """A custom dictionary for storing texture files.""" def __check_update_cache(self, path: pathlib.Path) -> Gdk.Texture | None: if path.is_file() \ and (cache_path := self.__get_cache_path(path)).exists() \ and cache_path.stat().st_mtime < path.stat().st_mtime: self.__drop(path, cache_path) return self.__load_new_item(path, cache_path) def __drop(self, path: pathlib.Path, cache_path: pathlib.Path) -> None: self.pop(path, None) cache_path.unlink(missing_ok=True) def __get_cache_path(self, path: pathlib.Path) -> pathlib.Path: return CACHE_PATH / path.absolute().relative_to("/") def __load_cached_item(self, path: pathlib.Path, cache_path: pathlib.Path) -> Gdk.Texture: texture = Gdk.Texture.new_from_filename(str(cache_path)) self.__setitem__(path, texture) return texture def __load_new_item(self, path: pathlib.Path, cache_path: pathlib.Path) -> Gdk.Texture: cache_path.parent.mkdir(parents=True, exist_ok=True) with path.open("rb") as f_path: bytes = f_path.read() with cache_path.open("wb") as f_cache: f_cache.write(bytes) texture = Gdk.Texture.new_from_bytes(GLib.Bytes.new(bytes)) self.__setitem__(path, texture) return texture def __get_missing_item(self, path: pathlib.Path, cache_path: pathlib.Path) -> Gdk.Texture: if cache_path.is_file(): return self.__load_cached_item(path, cache_path) elif path.is_file(): return self.__load_new_item(path, cache_path) def __missing__(self, path: pathlib.Path | None) -> Gdk.Texture: """Load a cache item from disk or add a new item entirely.""" return self.__get_missing_item(path, self.__get_cache_path(path)) def __getitem__(self, path: pathlib.Path | None) -> Gdk.Texture | None: """Get a Gdk.Texture cache item from the cache.""" if path is not None: texture = self.__check_update_cache(path) return super().__getitem__(path) if texture is None else texture def drop(self, path: pathlib.Path | None) -> None: """Drop a single cache item from the cache.""" self.__drop(path, self.__get_cache_path(path)) CACHE = _TextureCache()