mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-04-29 10:10:19 +00:00
Backups: export and download archives in one step
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
parent
629e34f93c
commit
ff673b0d73
@ -132,6 +132,9 @@ def subcommand_extract(arguments):
|
|||||||
|
|
||||||
def subcommand_export(arguments):
|
def subcommand_export(arguments):
|
||||||
"""Export archive contents as tarball."""
|
"""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)
|
path = os.path.dirname(arguments.filename)
|
||||||
if not os.path.exists(path):
|
if not os.path.exists(path):
|
||||||
os.makedirs(path)
|
os.makedirs(path)
|
||||||
|
|||||||
@ -47,6 +47,8 @@ service = None
|
|||||||
MANIFESTS_FOLDER = '/var/lib/plinth/backups-manifests/'
|
MANIFESTS_FOLDER = '/var/lib/plinth/backups-manifests/'
|
||||||
|
|
||||||
BACKUP_FOLDER_NAME = 'FreedomBox-backups'
|
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():
|
def init():
|
||||||
@ -108,10 +110,14 @@ def delete_archive(name):
|
|||||||
actions.superuser_run('backups', ['delete', '--name', name])
|
actions.superuser_run('backups', ['delete', '--name', name])
|
||||||
|
|
||||||
|
|
||||||
def export_archive(name, location):
|
def export_archive(name, location, tmp_dir=False):
|
||||||
location_path = get_location_path(location)
|
# TODO: find a better solution for distinguishing exports to /tmp
|
||||||
filepath = get_archive_path(location_path,
|
if tmp_dir:
|
||||||
get_valid_filename(name) + '.tar.gz')
|
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
|
# TODO: that's a full path, not a filename; rename argument
|
||||||
actions.superuser_run('backups',
|
actions.superuser_run('backups',
|
||||||
['export', '--name', name, '--filename', filepath])
|
['export', '--name', name, '--filename', filepath])
|
||||||
|
|||||||
@ -86,6 +86,10 @@
|
|||||||
href="{% url 'backups:export' archive.name %}">
|
href="{% url 'backups:export' archive.name %}">
|
||||||
{% trans "Export" %}
|
{% trans "Export" %}
|
||||||
</a>
|
</a>
|
||||||
|
<a class="archive-export btn btn-sm btn-default" target="_blank"
|
||||||
|
href="{% url 'backups:export-and-download' archive.name %}">
|
||||||
|
{% trans "Download" %}
|
||||||
|
</a>
|
||||||
<a class="archive-delete btn btn-sm btn-default"
|
<a class="archive-delete btn btn-sm btn-default"
|
||||||
href="{% url 'backups:delete' archive.name %}">
|
href="{% url 'backups:delete' archive.name %}">
|
||||||
<span class="glyphicon glyphicon-trash" aria-hidden="true">
|
<span class="glyphicon glyphicon-trash" aria-hidden="true">
|
||||||
|
|||||||
@ -21,7 +21,8 @@ URLs for the backups module.
|
|||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
from .views import IndexView, CreateArchiveView, DownloadArchiveView, \
|
from .views import IndexView, CreateArchiveView, DownloadArchiveView, \
|
||||||
DeleteArchiveView, ExportArchiveView, RestoreView, UploadArchiveView
|
DeleteArchiveView, ExportArchiveView, RestoreView, UploadArchiveView, \
|
||||||
|
ExportAndDownloadView
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
url(r'^sys/backups/$', IndexView.as_view(), name='index'),
|
url(r'^sys/backups/$', IndexView.as_view(), name='index'),
|
||||||
@ -30,6 +31,8 @@ urlpatterns = [
|
|||||||
ExportArchiveView.as_view(), name='export'),
|
ExportArchiveView.as_view(), name='export'),
|
||||||
url(r'^sys/backups/download/(?P<device>[^/]+)/(?P<name>[^/]+)/$',
|
url(r'^sys/backups/download/(?P<device>[^/]+)/(?P<name>[^/]+)/$',
|
||||||
DownloadArchiveView.as_view(), name='download'),
|
DownloadArchiveView.as_view(), name='download'),
|
||||||
|
url(r'^sys/backups/export-and-download/(?P<name>[^/]+)/$',
|
||||||
|
ExportAndDownloadView.as_view(), name='export-and-download'),
|
||||||
url(r'^sys/backups/delete/(?P<name>[^/]+)/$',
|
url(r'^sys/backups/delete/(?P<name>[^/]+)/$',
|
||||||
DeleteArchiveView.as_view(), name='delete'),
|
DeleteArchiveView.as_view(), name='delete'),
|
||||||
url(r'^sys/backups/upload/$', UploadArchiveView.as_view(), name='upload'),
|
url(r'^sys/backups/upload/$', UploadArchiveView.as_view(), name='upload'),
|
||||||
|
|||||||
@ -19,6 +19,7 @@ Views for the backups app.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from urllib.parse import unquote
|
from urllib.parse import unquote
|
||||||
|
|
||||||
@ -33,7 +34,7 @@ from django.views.generic import View, FormView, TemplateView
|
|||||||
|
|
||||||
from plinth.modules import backups
|
from plinth.modules import backups
|
||||||
|
|
||||||
from . import api, find_exported_archive, forms
|
from . import api, find_exported_archive, TMP_BACKUP_PATH, forms
|
||||||
|
|
||||||
|
|
||||||
subsubmenu = [{
|
subsubmenu = [{
|
||||||
@ -131,6 +132,39 @@ def _get_file_response(path, filename):
|
|||||||
return response
|
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):
|
class UploadArchiveView(SuccessMessageMixin, FormView):
|
||||||
form_class = forms.UploadForm
|
form_class = forms.UploadForm
|
||||||
prefix = 'backups'
|
prefix = 'backups'
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user