aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--fietsboek/data.py81
-rw-r--r--fietsboek/views/detail.py11
-rw-r--r--fietsboek/views/edit.py17
3 files changed, 78 insertions, 31 deletions
diff --git a/fietsboek/data.py b/fietsboek/data.py
index 94c6c11..83cabed 100644
--- a/fietsboek/data.py
+++ b/fietsboek/data.py
@@ -50,58 +50,90 @@ class DataManager:
def _track_data_dir(self, track_id):
return self.data_dir / "tracks" / str(track_id)
- def images(self, track_id: int) -> List[str]:
- """Returns a list of images that belong to a track.
+ def initialize(self, track_id: int) -> "TrackDataDir":
+ """Creates the data directory for a track.
- :param track_id: Numerical ID of the track.
- :return: A list of image IDs.
+ :raises FileExistsError: If the directory already exists.
+ :param track_id: ID of the track.
+ :return: The manager that can be used to manage this track's data.
"""
- image_dir = self._track_data_dir(track_id) / "images"
- if not image_dir.exists():
- return []
- images = []
- for image in image_dir.iterdir():
- images.append(image.name)
- return images
+ path = self._track_data_dir(track_id)
+ path.mkdir(parents=True)
+ return TrackDataDir(track_id, path)
def purge(self, track_id: int):
- """Purge all data pertaining to the given track.
+ """Forcefully purges all data from the given track.
This function logs errors but raises no exception, as such it can
always be used to clean up after a track.
+ """
+ TrackDataDir(track_id, self._track_data_dir(track_id)).purge()
- :param track_id: The ID of the track.
+ def open(self, track_id: int) -> "TrackDataDir":
+ """Opens a track's data directory.
+
+ :raises FileNotFoundError: If the track directory does not exist.
+ :param track_id: ID of the track.
+ :return: The manager that can be used to manage this track's data.
+ """
+ path = self._track_data_dir(track_id)
+ if not path.is_dir():
+ raise FileNotFoundError(f"The path {path} is not a directory") from None
+ return TrackDataDir(track_id, path)
+
+
+class TrackDataDir:
+ """Manager for a single track's data."""
+
+ def __init__(self, track_id: int, path: Path):
+ self.track_id: int = track_id
+ self.path: Path = path
+
+ def purge(self):
+ """Purge all data pertaining to the track.
+
+ This function logs errors but raises no exception, as such it can
+ always be used to clean up after a track.
"""
def log_error(_, path, exc_info):
LOGGER.warning("Failed to remove %s", path, exc_info=exc_info)
- path = self._track_data_dir(track_id)
- if path.is_dir():
- shutil.rmtree(path, ignore_errors=False, onerror=log_error)
+ if self.path.is_dir():
+ shutil.rmtree(self.path, ignore_errors=False, onerror=log_error)
- def image_path(self, track_id: int, image_id: str) -> Path:
+ def images(self) -> List[str]:
+ """Returns a list of images that belong to the track.
+
+ :param track_id: Numerical ID of the track.
+ :return: A list of image IDs.
+ """
+ image_dir = self.path / "images"
+ if not image_dir.exists():
+ return []
+ images = [image.name for image in image_dir.iterdir()]
+ return images
+
+ def image_path(self, image_id: str) -> Path:
"""Returns a path to a saved image.
:raises FileNotFoundError: If the given image could not be found.
- :param track_id: ID of the track.
:param image_id: ID of the image.
:return: A path pointing to the requested image.
"""
- image = self._track_data_dir(track_id) / "images" / secure_filename(image_id)
+ image = self.path / "images" / secure_filename(image_id)
if not image.exists():
raise FileNotFoundError("The requested image does not exist")
return image
- def add_image(self, track_id: int, image: BinaryIO, filename: Optional[str] = None) -> str:
+ def add_image(self, image: BinaryIO, filename: Optional[str] = None) -> str:
"""Saves an image to a track.
- :param track_id: ID of the track.
:param image: The image, as a file-like object to read from.
:param filename: The image's original filename.
:return: The ID of the saved image.
"""
- image_dir = self._track_data_dir(track_id) / "images"
+ image_dir = self.path / "images"
image_dir.mkdir(parents=True, exist_ok=True)
filename = generate_filename(filename)
@@ -111,16 +143,15 @@ class DataManager:
return filename
- def delete_image(self, track_id: int, image_id: str):
+ def delete_image(self, image_id: str):
"""Deletes an image from a track.
:raises FileNotFoundError: If the given image could not be found.
- :param track_id: ID of the track.
:param image_id: ID of the image.
"""
# Be sure to not delete anything else than the image file
image_id = secure_filename(image_id)
if "/" in image_id or "\\" in image_id:
return
- path = self.image_path(track_id, image_id)
+ path = self.image_path(image_id)
path.unlink()
diff --git a/fietsboek/views/detail.py b/fietsboek/views/detail.py
index 485bad3..e602780 100644
--- a/fietsboek/views/detail.py
+++ b/fietsboek/views/detail.py
@@ -26,8 +26,15 @@ def details(request):
description = util.safe_markdown(track.description)
show_edit_link = track.owner == request.identity
+ on_disk_images = []
+ try:
+ manager = request.data_manager.open(track.id)
+ on_disk_images.extend(manager.images())
+ except FileNotFoundError:
+ pass
+
images = []
- for image_name in request.data_manager.images(track.id):
+ for image_name in on_disk_images:
query = []
if "secret" in request.GET:
query.append(("secret", request.GET["secret"]))
@@ -138,7 +145,7 @@ def image(request):
"""
track = request.context
try:
- image_path = request.data_manager.image_path(track.id, request.matchdict["image_name"])
+ image_path = request.data_manager.open(track.id).image_path(request.matchdict["image_name"])
except FileNotFoundError:
return HTTPNotFound()
else:
diff --git a/fietsboek/views/edit.py b/fietsboek/views/edit.py
index 003f7c7..a26452d 100644
--- a/fietsboek/views/edit.py
+++ b/fietsboek/views/edit.py
@@ -36,8 +36,13 @@ def edit(request):
badges = request.dbsession.execute(select(models.Badge)).scalars()
badges = [(badge in track.badges, badge) for badge in badges]
+ on_disk_images = []
+ try:
+ on_disk_images = request.data_manager.open(track.id).images()
+ except FileNotFoundError:
+ pass
images = []
- for image in request.data_manager.images(track.id):
+ for image in on_disk_images:
metadata = request.dbsession.execute(
select(models.ImageMetadata).filter_by(track=track, image_name=image)
).scalar_one_or_none()
@@ -106,9 +111,13 @@ def edit_images(request, track):
:type track: fietsboek.models.track.Track
"""
+ try:
+ manager = request.data_manager.open(track.id)
+ except FileNotFoundError:
+ manager = request.data_manager.initialize(track.id)
# Delete requested images
for image in request.params.getall("delete-image[]"):
- request.data_manager.delete_image(track.id, image)
+ manager.delete_image(image)
image_meta = request.dbsession.execute(
select(models.ImageMetadata).filter_by(track_id=track.id, image_name=image)
).scalar_one_or_none()
@@ -127,14 +136,14 @@ def edit_images(request, track):
continue
upload_id = match.group(1)
- image_name = request.data_manager.add_image(track.id, image.file, image.filename)
+ image_name = manager.add_image(image.file, image.filename)
image_meta = models.ImageMetadata(track=track, image_name=image_name)
image_meta.description = request.params.get(f"image-description[{upload_id}]", "")
request.dbsession.add(image_meta)
LOGGER.debug("Uploaded image %s %s", track.id, image_name)
set_descriptions.add(upload_id)
- images = request.data_manager.images(track.id)
+ images = manager.images()
# Set image descriptions
for param_name, description in request.params.items():
match = re.match("image-description\\[(.+)\\]$", param_name)