Создать zip-архив для мгновенной загрузки - PullRequest
5 голосов
/ 14 июня 2009

В веб-приложении, над которым я работаю, пользователь может создать zip-архив из папки, полной файлов. Вот код:

files = torrent[0].files
    zipfile = z.ZipFile(zipname, 'w')
    output = ""

    for f in files:
        zipfile.write(settings.PYRAT_TRANSMISSION_DOWNLOAD_DIR + "/" + f.name, f.name)

downloadurl = settings.PYRAT_DOWNLOAD_BASE_URL + "/" + settings.PYRAT_ARCHIVE_DIR + "/" + filename
output = "Download <a href=\"" + downloadurl + "\">" + torrent_name + "</a>"
return HttpResponse(output)

Но это имеет неприятный побочный эффект длительного ожидания (более 10 секунд), пока загружается zip-архив. Можно ли это пропустить? Вместо сохранения архива в файл, можно ли отправить его прямо пользователю?

Я верю, что torrentflux предоставляет эту функцию, о которой я говорю. Возможность заархивировать ГБ данных и загрузить их в течение секунды.

Ответы [ 5 ]

12 голосов
/ 14 июня 2009
9 голосов
/ 22 марта 2012

Как говорит Мандрейк, конструктор HttpResponse принимает итерируемые объекты.

К счастью, формат ZIP таков, что архив может быть создан за один проход, запись центрального каталога находится в самом конце файла:

enter image description here

(Изображение из Википедия )

И, к счастью, zipfile действительно ничего не ищет, пока вы только добавляете файлы.

Вот код, который я придумал. Некоторые заметки:

  • Я использую этот код для архивирования нескольких изображений JPEG. Нет смысла сжимать их, я использую ZIP только как контейнер.
  • Использование памяти равно O (size_of_largest_file), а не O (size_of_archive). И это достаточно хорошо для меня: множество сравнительно небольших файлов, которые складываются в потенциально огромный архив
  • Этот код не устанавливает заголовок Content-Length, поэтому пользователь не может получить хороший индикатор прогресса. должно быть возможно , чтобы рассчитать это заранее, если известны размеры всех файлов.
  • Предоставление почтового индекса пользователю таким образом означает, что возобновление загрузки не будет работать.

Итак, вот так:

import zipfile

class ZipBuffer(object):
    """ A file-like object for zipfile.ZipFile to write into. """

    def __init__(self):
        self.data = []
        self.pos = 0

    def write(self, data):
        self.data.append(data)
        self.pos += len(data)

    def tell(self):
        # zipfile calls this so we need it
        return self.pos

    def flush(self):
        # zipfile calls this so we need it
        pass

    def get_and_clear(self):
        result = self.data
        self.data = []
        return result

def generate_zipped_stream():
    sink = ZipBuffer()
    archive = zipfile.ZipFile(sink, "w")
    for filename in ["file1.txt", "file2.txt"]:
        archive.writestr(filename, "contents of file here")
        for chunk in sink.get_and_clear():
            yield chunk

    archive.close()
    # close() generates some more data, so we yield that too
    for chunk in sink.get_and_clear():
        yield chunk

def my_django_view(request):
    response = HttpResponse(generate_zipped_stream(), mimetype="application/zip")
    response['Content-Disposition'] = 'attachment; filename=archive.zip'
    return response
5 голосов
/ 04 июля 2009

Вот простая функция просмотра Django, которая архивирует (в качестве примера) все читаемые файлы в /tmp и возвращает zip-файл.

from django.http import HttpResponse
import zipfile
import os
from cStringIO import StringIO # caveats for Python 3.0 apply

def somezip(request):
    file = StringIO()
    zf = zipfile.ZipFile(file, mode='w', compression=zipfile.ZIP_DEFLATED)
    for fn in os.listdir("/tmp"):
        path = os.path.join("/tmp", fn)
        if os.path.isfile(path):
            try:
                zf.write(path)
            except IOError:
                pass
    zf.close()
    response = HttpResponse(file.getvalue(), mimetype="application/zip")
    response['Content-Disposition'] = 'attachment; filename=yourfiles.zip'
    return response

Конечно, этот подход будет работать только в том случае, если zip-файлы удобно помещаются в памяти - если нет, вам придется использовать файл на диске (которого вы пытаетесь избежать). В этом случае вы просто заменяете file = StringIO() на file = open('/path/to/yourfiles.zip', 'wb') и заменяете file.getvalue() кодом для чтения содержимого файла на диске.

2 голосов
/ 14 июня 2009

Позволяет ли используемая вами zip-библиотека выводиться в поток. Вы можете выполнять потоковую передачу непосредственно пользователю вместо временной записи в zip-файл, а затем выполнять потоковую передачу пользователю.

0 голосов
/ 09 июля 2009

Можно передать итератор в конструктор HttpResponse (см. Документы) . Это позволит вам создать пользовательский итератор, который генерирует данные по мере их запроса. Однако я не думаю, что это будет работать с почтовым индексом (вам нужно будет отправить частичный почтовый индекс по мере его создания).

Думаю, правильным способом было бы создать файлы в автономном режиме, в отдельном процессе. Затем пользователь может отслеживать ход выполнения и затем загружать файл, когда он будет готов (возможно, с помощью метода итератора, описанного выше). Это будет похоже на то, что используют такие сайты, как youtube, когда вы загружаете файл и ждете его обработки.

...