diff options
| author | Daniel Schadt <kingdread@gmx.de> | 2025-12-28 22:31:02 +0100 |
|---|---|---|
| committer | Daniel Schadt <kingdread@gmx.de> | 2025-12-30 19:19:53 +0100 |
| commit | c4c76b6a758cd7c3979d6d109da5efcf384a3b40 (patch) | |
| tree | bd6467ebbc762f61c593fc27e2825ac7c4f13406 | |
| parent | 9bf3d075cdd36d7f5f8b896cb064a6a09f863d75 (diff) | |
| download | fietsboek-c4c76b6a758cd7c3979d6d109da5efcf384a3b40.tar.gz fietsboek-c4c76b6a758cd7c3979d6d109da5efcf384a3b40.tar.bz2 fietsboek-c4c76b6a758cd7c3979d6d109da5efcf384a3b40.zip | |
cache journey preview images
| -rw-r--r-- | fietsboek/data.py | 83 | ||||
| -rw-r--r-- | fietsboek/views/journey.py | 18 |
2 files changed, 94 insertions, 7 deletions
diff --git a/fietsboek/data.py b/fietsboek/data.py index d4bbb07..e288a03 100644 --- a/fietsboek/data.py +++ b/fietsboek/data.py @@ -42,6 +42,10 @@ def generate_filename(filename: Optional[str]) -> str: return str(uuid.uuid4()) +def _log_deletion_error(_, path, exc_info): + LOGGER.warning("Failed to remove %s", path, exc_info=exc_info) + + class DataManager: """Data manager. @@ -61,6 +65,9 @@ class DataManager: def _user_data_dir(self, user_id): return self.data_dir / "users" / str(user_id) + def _journey_data_dir(self, journey_id): + return self.data_dir / "journeys" / str(journey_id) + def maintenance_mode(self) -> Optional[str]: """Checks whether the maintenance mode is enabled. @@ -103,6 +110,17 @@ class DataManager: path.mkdir(parents=True) return UserDataDir(user_id, path, txn=self.txn) + def initialize_journey(self, journey_id: int) -> "JourneyDataDir": + """Creates the data directory for a journey. + + :raises FileExistsError: If the directory already exists. + :param journey_id: ID of the journey. + :return: The manager that can be used to manage this journey's data. + """ + path = self._journey_data_dir(journey_id) + path.mkdir(parents=True) + return JourneyDataDir(journey_id, path) + def purge(self, track_id: int): """Forcefully purges all data from the given track. @@ -135,6 +153,18 @@ class DataManager: raise FileNotFoundError(f"The path {path} is not a directory") from None return UserDataDir(user_id, path, txn=self.txn) + def open_journey(self, journey_id: int) -> "JourneyDataDir": + """Open a journey's data directory. + + :raises FileNotFoundError: If the journey directory does not exist. + :param journey_id: ID of the journey. + :return: The manager that can be used to manage this journey's data. + """ + path = self._journey_data_dir(journey_id) + if not path.is_dir(): + raise FileNotFoundError(f"The path {path} is not a directory") from None + return JourneyDataDir(journey_id, path) + def size(self) -> int: """Returns the size of all data. @@ -162,6 +192,16 @@ class DataManager: except FileNotFoundError: return [] + def list_journeys(self) -> list[int]: + """Returns a list of all journeys. + + :return: A list of all journey IDs. + """ + try: + return [int(journey.name) for journey in self._journey_data_dir(".").iterdir()] + except FileNotFoundError: + return [] + class TrackDataDir: """Manager for a single track's data. @@ -185,10 +225,6 @@ class TrackDataDir: """ return FileLock(self.path / "lock") - @staticmethod - def _log_deletion_error(_, path, exc_info): - LOGGER.warning("Failed to remove %s", path, exc_info=exc_info) - def purge(self): """Purge all data pertaining to the track. @@ -199,7 +235,7 @@ class TrackDataDir: self.txn.purge(self.path) else: if self.path.is_dir(): - shutil.rmtree(self.path, ignore_errors=False, onerror=self._log_deletion_error) + shutil.rmtree(self.path, ignore_errors=False, onerror=_log_deletion_error) def size(self) -> int: """Returns the size of the data that this track entails. @@ -343,4 +379,39 @@ class UserDataDir: return self.path / "tilehunt.sqlite" -__all__ = ["generate_filename", "DataManager", "TrackDataDir", "UserDataDir"] +class JourneyDataDir: + """Manager for a single journey's data.""" + + def __init__(self, journey_id: int, path: Path): + self.journey_id = journey_id + self.path = path + + def purge(self): + """Purge all data pertaining to the journey. + + This function logs errors but raises no exception, as such it can + always be used to clean up after a track. + """ + if self.path.is_dir(): + shutil.rmtree(self.path, ignore_errors=False, onerror=_log_deletion_error) + + def preview_path(self) -> Path: + """Gets the path to the "preview image". + + :return: The path to the preview image. + """ + return self.path / "preview.png" + + def set_preview(self, data: bytes): + """Sets the preview image to the given data. + + :param data: The data of the preview image. + """ + self.preview_path().write_bytes(data) + + def remove_preview(self): + """Deletes the preview image.""" + self.preview_path().unlink() + + +__all__ = ["generate_filename", "DataManager", "TrackDataDir", "UserDataDir", "JourneyDataDir"] diff --git a/fietsboek/views/journey.py b/fietsboek/views/journey.py index 60ed433..8621f4a 100644 --- a/fietsboek/views/journey.py +++ b/fietsboek/views/journey.py @@ -10,6 +10,7 @@ from sqlalchemy import select from sqlalchemy.orm import aliased from .. import trackmap, util +from ..data import JourneyDataDir from ..models.journey import Journey, Visibility from ..models.track import Track, TrackWithMetadata from ..models import User @@ -62,7 +63,14 @@ def journey_gpx(request: Request): @view_config(route_name="journey-map", http_cache=3600, permission="journey.view") def journey_map(request: Request): - journey = request.context + journey: Journey = request.context + journey_data: JourneyDataDir = request.data_manager.open_journey(journey.id) + preview_path = journey_data.preview_path() + + if preview_path.exists(): + response = Response(preview_path.read_bytes(), content_type="image/png") + response.md5_etag() + return response loader: ITileRequester = request.registry.getUtility(ITileRequester) layer = request.config.public_tile_layers()[0] @@ -73,6 +81,10 @@ def journey_map(request: Request): track_image.save(imageio, "png") tile_data = imageio.getvalue() + if not preview_path.exists(): + LOGGER.debug("Setting preview at %s", preview_path) + journey_data.set_preview(tile_data) + response = Response(tile_data, content_type="image/png") response.md5_etag() return response @@ -108,6 +120,8 @@ def do_journey_new(request: Request): track_ids = _extract_valid_tracks(request) journey.set_track_ids(track_ids) + request.data_manager.initialize_journey(journey.id) + return HTTPFound(request.route_url("journey-details", journey_id=journey.id)) @@ -130,6 +144,7 @@ def journey_edit(request: Request): ) def do_journey_edit(request: Request): journey: Journey = request.context + request.data_manager.open_journey(journey.id).remove_preview() journey.title = request.params.get("journeyTitle") journey.description = request.params.get("journeyDescription") @@ -182,6 +197,7 @@ def _extract_valid_tracks(request: Request) -> list[int]: ) def do_journey_delete(request: Request): journey: Journey = request.context + request.data_manager.open_journey(journey.id).purge() request.dbsession.delete(journey) request.session.flash(request.localizer.translate(_("journeys.deleted"))) return HTTPFound(request.route_url("journey-list")) |
