From 4a3ebb1a0a71c02c1057b6fd6c6054afe3bfa876 Mon Sep 17 00:00:00 2001 From: Daniel Schadt Date: Wed, 15 Feb 2023 23:36:55 +0100 Subject: try to avoid parsing the GPX more than once --- fietsboek/actions.py | 16 +++++++++++----- fietsboek/data.py | 12 ++++++++++-- fietsboek/models/track.py | 3 ++- fietsboek/util.py | 9 ++++++--- fietsboek/views/edit.py | 3 ++- 5 files changed, 31 insertions(+), 12 deletions(-) diff --git a/fietsboek/actions.py b/fietsboek/actions.py index 4974a2c..2058f4d 100644 --- a/fietsboek/actions.py +++ b/fietsboek/actions.py @@ -8,7 +8,7 @@ the test functions. import datetime import logging import re -from typing import List +from typing import List, Optional import brotli import gpxpy @@ -97,7 +97,7 @@ def add_track( # Best time to build the cache is right after the upload, but *after* the # transformers have been applied! - track.ensure_cache(manager.decompress_gpx()) + track.ensure_cache(gpx) dbsession.add(track.cache) manager.engrave_metadata( @@ -105,6 +105,7 @@ def add_track( description=track.description, author_name=track.owner.name, time=track.date, + gpx=gpx, ) return track @@ -170,14 +171,18 @@ def edit_images(request: Request, track: models.Track): request.dbsession.add(image_meta) -def execute_transformers(request: Request, track: models.Track): +def execute_transformers(request: Request, track: models.Track) -> Optional[gpxpy.gpx.GPX]: """Execute the transformers for the given track. Note that this function "short circuits" if the saved transformer settings already match the settings given in the request. + This function saves the modified data, but does also return it in case you + need to do further processing (unless no transformations have taken place). + :param request: The request. :param track: The track. + :return: The transformed track. """ # pylint: disable=too-many-locals LOGGER.debug("Executing transformers for %d", track.id) @@ -187,7 +192,7 @@ def execute_transformers(request: Request, track: models.Track): serialized = [[tfm.identifier(), tfm.parameters.dict()] for tfm in settings] if serialized == track.transformers: LOGGER.debug("Applied transformations mach on %d, skipping", track.id) - return + return None # We always start with the backup, that way we don't get "deepfried GPX" # files by having the same filters run multiple times on the same input. @@ -210,5 +215,6 @@ def execute_transformers(request: Request, track: models.Track): LOGGER.debug("Rebuilding cache for %d", track.id) request.dbsession.delete(track.cache) track.cache = None - track.ensure_cache(manager.decompress_gpx()) + track.ensure_cache(gpx) request.dbsession.add(track.cache) + return gpx diff --git a/fietsboek/data.py b/fietsboek/data.py index 1a1b66b..6906c84 100644 --- a/fietsboek/data.py +++ b/fietsboek/data.py @@ -158,7 +158,13 @@ class TrackDataDir: return brotli.decompress(self.gpx_path().read_bytes()) def engrave_metadata( - self, title: str, description: str, author_name: str, time: datetime.datetime + self, + title: str, + description: str, + author_name: str, + time: datetime.datetime, + *, + gpx: Optional[gpxpy.gpx.GPX] = None, ): """Engrave the given metadata into the GPX file. @@ -168,8 +174,10 @@ class TrackDataDir: :param description: The description of the track. :param creator: Name of the track's creator. :param time: Time of the track. + :param gpx: The pre-parsed GPX track, to save time if it is already parsed. """ - gpx = gpxpy.parse(self.decompress_gpx()) + if gpx is None: + gpx = gpxpy.parse(self.decompress_gpx()) # First we delete the existing metadata for track in gpx.tracks: track.name = None diff --git a/fietsboek/models/track.py b/fietsboek/models/track.py index 9342cff..e0d2820 100644 --- a/fietsboek/models/track.py +++ b/fietsboek/models/track.py @@ -18,6 +18,7 @@ import logging from itertools import chain from typing import TYPE_CHECKING, List, Optional, Set, Union +import gpxpy from babel.numbers import format_decimal from markupsafe import Markup from pyramid.authorization import ( @@ -330,7 +331,7 @@ class Track(Base): result = ACLHelper().permits(self, principals, "track.view") return isinstance(result, ACLAllowed) - def ensure_cache(self, gpx_data: Union[str, bytes]): + def ensure_cache(self, gpx_data: Union[str, bytes, gpxpy.gpx.GPX]): """Ensure that a cached version of this track's metadata exists. :param gpx_data: GPX data (uncompressed) from which to build the cache. diff --git a/fietsboek/util.py b/fietsboek/util.py index c741550..68ba769 100644 --- a/fietsboek/util.py +++ b/fietsboek/util.py @@ -152,19 +152,22 @@ def guess_gpx_timezone(gpx: gpxpy.gpx.GPX) -> datetime.tzinfo: return datetime.timezone.utc -def tour_metadata(gpx_data: Union[str, bytes]) -> dict: +def tour_metadata(gpx_data: Union[str, bytes, gpxpy.gpx.GPX]) -> dict: """Calculate the metadata of the tour. Returns a dict with ``length``, ``uphill``, ``downhill``, ``moving_time``, ``stopped_time``, ``max_speed``, ``avg_speed``, ``start_time`` and ``end_time``. - :param gpx_data: The GPX data of the tour. + :param gpx_data: The GPX data of the tour. Can be pre-parsed to save time. :return: A dictionary with the computed values. """ if isinstance(gpx_data, bytes): gpx_data = gpx_data.decode("utf-8") - gpx = gpxpy.parse(gpx_data) + if isinstance(gpx_data, gpxpy.gpx.GPX): + gpx = gpx_data + else: + gpx = gpxpy.parse(gpx_data) timezone = guess_gpx_timezone(gpx) uphill, downhill = gpx.get_uphill_downhill() moving_data = gpx.get_moving_data() diff --git a/fietsboek/views/edit.py b/fietsboek/views/edit.py index d60cced..9cc666f 100644 --- a/fietsboek/views/edit.py +++ b/fietsboek/views/edit.py @@ -95,12 +95,13 @@ def do_edit(request): track.sync_tags(tags) actions.edit_images(request, request.context) - actions.execute_transformers(request, request.context) + gpx = actions.execute_transformers(request, request.context) data.engrave_metadata( title=track.title, description=track.description, author_name=track.owner.name, time=track.date, + gpx=gpx, ) return HTTPFound(request.route_url("details", track_id=track.id)) -- cgit v1.2.3