backups: Fix incomplete download archives

- Downloaded archives can't be fully extracted as tar.gz is incomplete at the
  end and corrupt. This is due to complete gzip streaming implementation that is
  does not flush the final bytes of gzip stream. Remove custom implementation
  and get gzipped stream directly from borg.

- Fix mimetype for .tar.gz to application/gzip.

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: James Valleroy <jvalleroy@mailbox.org>
This commit is contained in:
Sunil Mohan Adapa 2019-01-30 12:29:46 -08:00 committed by James Valleroy
parent eb2b3bd86e
commit 01492895c4
No known key found for this signature in database
GPG Key ID: 77C0C75E7B650808
4 changed files with 7 additions and 53 deletions

View File

@ -174,7 +174,8 @@ def _extract(archive_path, destination, locations=None, env=None):
def subcommand_export_tar(arguments):
"""Export archive contents as tar stream on stdout."""
run(['borg', 'export-tar', arguments.path, '-'], arguments=arguments)
run(['borg', 'export-tar', arguments.path, '-', '--tar-filter=gzip'],
arguments=arguments)
def _read_archive_file(archive, filepath, env=None):

View File

@ -29,8 +29,7 @@ from plinth import actions
from plinth.errors import ActionError
from . import api, network_storage, _backup_handler, ROOT_REPOSITORY_NAME, \
ROOT_REPOSITORY_UUID, ROOT_REPOSITORY, restore_archive_handler, \
zipstream
ROOT_REPOSITORY_UUID, ROOT_REPOSITORY, restore_archive_handler
from .errors import BorgError, BorgRepositoryDoesNotExistError, SshfsError
logger = logging.getLogger(__name__)
@ -141,11 +140,11 @@ class BorgRepository():
def create_repository(self):
self.run(['init', '--path', self.repo_path, '--encryption', 'none'])
def get_zipstream(self, archive_name):
def get_download_stream(self, archive_name):
args = ['export-tar', '--path', self._get_archive_path(archive_name)]
args += self._get_encryption_arguments(self.credentials)
proc = self._run('backups', args, run_in_background=True)
return zipstream.ZipStream(proc.stdout, 'readline')
return proc.stdout
def get_archive(self, name):
for archive in self.list_archives():

View File

@ -239,7 +239,8 @@ class DownloadArchiveView(View):
filename = '%s.tar.gz' % name
response = StreamingHttpResponse(
repository.get_zipstream(name), content_type='application/x-gzip')
repository.get_download_stream(name),
content_type='application/gzip')
response['Content-Disposition'] = 'attachment; filename="%s"' % \
filename
return response

View File

@ -1,47 +0,0 @@
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import gzip
from io import BytesIO
class ZipStream(object):
"""Zip a stream that yields binary data"""
def __init__(self, stream, get_chunk_method):
"""
- stream: the input stream
- get_chunk_method: name of the method to get a chunk of the stream
"""
self.stream = stream
self.buffer = BytesIO()
self.zipfile = gzip.GzipFile(mode='wb', fileobj=self.buffer)
self.get_chunk = getattr(self.stream, get_chunk_method)
def __next__(self):
line = self.get_chunk()
if not len(line):
raise StopIteration
self.zipfile.write(line)
self.zipfile.flush()
zipped = self.buffer.getvalue()
self.buffer.truncate(0)
self.buffer.seek(0)
return zipped
def __iter__(self):
return self