diff --git a/actions/backups b/actions/backups
index 788995f08..aaad064b7 100755
--- a/actions/backups
+++ b/actions/backups
@@ -132,6 +132,9 @@ def subcommand_extract(arguments):
def subcommand_export(arguments):
"""Export archive contents as tarball."""
+ # TODO: if this is only used for files in /tmp, add checks to verify that
+ # arguments.filename is within /tmp
+ # TODO: arguments.filename is not a filename but a path
path = os.path.dirname(arguments.filename)
if not os.path.exists(path):
os.makedirs(path)
diff --git a/plinth/modules/backups/__init__.py b/plinth/modules/backups/__init__.py
index bb00be006..3656a779e 100644
--- a/plinth/modules/backups/__init__.py
+++ b/plinth/modules/backups/__init__.py
@@ -47,6 +47,8 @@ service = None
MANIFESTS_FOLDER = '/var/lib/plinth/backups-manifests/'
BACKUP_FOLDER_NAME = 'FreedomBox-backups'
+# default backup path for temporary actions like imports or download
+TMP_BACKUP_PATH = '/tmp/freedombox-backup.tar.gz'
def init():
@@ -108,10 +110,14 @@ def delete_archive(name):
actions.superuser_run('backups', ['delete', '--name', name])
-def export_archive(name, location):
- location_path = get_location_path(location)
- filepath = get_archive_path(location_path,
- get_valid_filename(name) + '.tar.gz')
+def export_archive(name, location, tmp_dir=False):
+ # TODO: find a better solution for distinguishing exports to /tmp
+ if tmp_dir:
+ filepath = TMP_BACKUP_PATH
+ else:
+ location_path = get_location_path(location)
+ filepath = get_archive_path(location_path,
+ get_valid_filename(name) + '.tar.gz')
# TODO: that's a full path, not a filename; rename argument
actions.superuser_run('backups',
['export', '--name', name, '--filename', filepath])
diff --git a/plinth/modules/backups/templates/backups.html b/plinth/modules/backups/templates/backups.html
index 3797bd3ac..440dcac0e 100644
--- a/plinth/modules/backups/templates/backups.html
+++ b/plinth/modules/backups/templates/backups.html
@@ -86,6 +86,10 @@
href="{% url 'backups:export' archive.name %}">
{% trans "Export" %}
+
+ {% trans "Download" %}
+
diff --git a/plinth/modules/backups/urls.py b/plinth/modules/backups/urls.py
index e81b7eb9d..cde0f1528 100644
--- a/plinth/modules/backups/urls.py
+++ b/plinth/modules/backups/urls.py
@@ -21,7 +21,8 @@ URLs for the backups module.
from django.conf.urls import url
from .views import IndexView, CreateArchiveView, DownloadArchiveView, \
- DeleteArchiveView, ExportArchiveView, RestoreView, UploadArchiveView
+ DeleteArchiveView, ExportArchiveView, RestoreView, UploadArchiveView, \
+ ExportAndDownloadView
urlpatterns = [
url(r'^sys/backups/$', IndexView.as_view(), name='index'),
@@ -30,6 +31,8 @@ urlpatterns = [
ExportArchiveView.as_view(), name='export'),
url(r'^sys/backups/download/(?P[^/]+)/(?P[^/]+)/$',
DownloadArchiveView.as_view(), name='download'),
+ url(r'^sys/backups/export-and-download/(?P[^/]+)/$',
+ ExportAndDownloadView.as_view(), name='export-and-download'),
url(r'^sys/backups/delete/(?P[^/]+)/$',
DeleteArchiveView.as_view(), name='delete'),
url(r'^sys/backups/upload/$', UploadArchiveView.as_view(), name='upload'),
diff --git a/plinth/modules/backups/views.py b/plinth/modules/backups/views.py
index fd9a90a1e..37daafbe8 100644
--- a/plinth/modules/backups/views.py
+++ b/plinth/modules/backups/views.py
@@ -19,6 +19,7 @@ Views for the backups app.
"""
import mimetypes
+import os
from datetime import datetime
from urllib.parse import unquote
@@ -33,7 +34,7 @@ from django.views.generic import View, FormView, TemplateView
from plinth.modules import backups
-from . import api, find_exported_archive, forms
+from . import api, find_exported_archive, TMP_BACKUP_PATH, forms
subsubmenu = [{
@@ -131,6 +132,39 @@ def _get_file_response(path, filename):
return response
+class ExportAndDownloadView(View):
+ """View to export and download an archive."""
+ def get(self, request, name):
+ name = unquote(name)
+ with create_temporary_backup_file(name) as filename, \
+ open(filename, 'rb') as file_handle:
+ (content_type, encoding) = mimetypes.guess_type(filename)
+ response = HttpResponse(File(file_handle),
+ content_type=content_type)
+ content_disposition = 'attachment; filename="%s"' % filename
+ response['Content-Disposition'] = content_disposition
+ if encoding:
+ response['Content-Encoding'] = encoding
+
+ return response
+
+
+class create_temporary_backup_file:
+ """Create a temporary backup file that gets deleted after using it"""
+
+ def __init__(self, name):
+ self.name = name
+ self.path = TMP_BACKUP_PATH
+
+ def __enter__(self):
+ backups.export_archive(self.name, self.path, tmp_dir=True)
+ return self.path
+
+ def __exit__(self, type, value, traceback):
+ if os.path.isfile(self.path):
+ os.remove(self.path)
+
+
class UploadArchiveView(SuccessMessageMixin, FormView):
form_class = forms.UploadForm
prefix = 'backups'