Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions server/mergin/sync/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,5 @@ class Configuration(object):
"GEODIFF_WORKING_DIR",
default=os.path.join(LOCAL_PROJECTS, "geodiff_tmp"),
)
# in seconds, older unfinished zips are moved to temp
PARTIAL_ZIP_EXPIRATION = config("PARTIAL_ZIP_EXPIRATION", default=300, cast=int)
7 changes: 3 additions & 4 deletions server/mergin/sync/private_api_controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-MerginMaps-Commercial
import os
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from blinker import signal
from connexion import NoContent
from flask import (
Expand Down Expand Up @@ -45,7 +45,6 @@
from .utils import get_x_accel_uri

project_access_granted = signal("project_access_granted")
PARTIAL_ZIP_EXPIRATION = 300 # seconds


@auth_required
Expand Down Expand Up @@ -365,8 +364,8 @@ def download_project(id: str, version=None): # noqa: E501 # pylint: disable=W06
temp_zip_path = project_version.zip_path + ".partial"
# to be safe we are not in vicious circle remove inactive partial zip
if os.path.exists(temp_zip_path) and datetime.fromtimestamp(
os.path.getmtime(temp_zip_path)
) < datetime.now(datetime.timezone.utc) - timedelta(
os.path.getmtime(temp_zip_path), tz=timezone.utc
) < datetime.now(timezone.utc) - timedelta(
seconds=current_app.config["PARTIAL_ZIP_EXPIRATION"]
):
move_to_tmp(temp_zip_path)
Expand Down
23 changes: 23 additions & 0 deletions server/mergin/tests/test_private_project_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,3 +473,26 @@ def test_large_project_download_fail(client, diff_project):
)
assert resp.status_code == 400
assert "The total size of requested files is too large" in resp.json["detail"]


@patch("mergin.sync.tasks.create_project_version_zip.delay")
def test_remove_abandoned_zip(mock_prepare_zip, client, diff_project):
"""Test project download removes partial zip which is inactive for some time"""
latest_version = diff_project.get_latest_version()
# pretend an incomplete zip remained
partial_zip_path = latest_version.zip_path + ".partial"
os.makedirs(os.path.dirname(partial_zip_path), exist_ok=True)
os.mknod(partial_zip_path)
assert os.path.exists(partial_zip_path)
# pretend abandoned partial zip by lowering the expiration limit
client.application.config["PARTIAL_ZIP_EXPIRATION"] = 0
# download should remove it (move to temp folder) and call a celery task which will try to create a correct zip
resp = client.get(
url_for(
"/app.mergin_sync_private_api_controller_download_project",
id=diff_project.id,
)
)
assert mock_prepare_zip.called
assert resp.status_code == 202
assert not os.path.exists(partial_zip_path)
Loading