5

In a web app I am working on, the user can create a zip archive of a folder full of files. Here here's the code:

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)

But this has the nasty side effect of a long wait (10+ seconds) while the zip archive is being downloaded. Is it possible to skip this? Instead of saving the archive to a file, is it possible to send it straight to the user?

I do beleive that torrentflux provides this excat feature I am talking about. Being able to zip GBs of data and download it within a second.

4

5 回答 5

12

Check this Serving dynamically generated ZIP archives in Django

于 2009-06-14T10:47:16.027 回答
9

正如 Mandrake 所说, HttpResponse 的构造函数接受可迭代对象。

幸运的是,ZIP 格式可以一次性创建存档,中央目录记录位于文件的最后:

在此处输入图像描述

(图片来自维基百科

幸运的是,zipfile只要您只添加文件,确实不会进行任何搜索。

这是我想出的代码。一些注意事项:

  • 我正在使用此代码压缩一堆 JPEG 图片。压缩它们没有意义,我只使用 ZIP 作为容器。
  • 内存使用量是 O(size_of_largest_file) 而不是 O(size_of_archive)。这对我来说已经足够了:许多相对较小的文件加起来可能是巨大的存档
  • 此代码未设置 Content-Length 标头,因此用户无法获得良好的进度指示。如果所有文件的大小都是已知的,那么应该可以提前计算出来。
  • 像这样直接向用户提供 ZIP 意味着下载时的恢复将不起作用。

所以,这里是:

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
于 2012-03-22T19:19:18.747 回答
5

这是一个简单的 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()withfile = open('/path/to/yourfiles.zip', 'wb')和替换file.getvalue()with 代码即可读取磁盘文件的内容。

于 2009-07-03T20:16:18.627 回答
2

Does the zip library you are using allow for output to a stream. You could stream directly to the user instead of temporarily writing to a zip file THEN streaming to the user.

于 2009-06-14T10:57:08.547 回答
0

可以将迭代器传递给 HttpResponse 的构造函数(参见文档)。这将允许您创建一个自定义迭代器,该迭代器在请求数据时生成数据。但是,我认为这不适用于 zip(您必须在创建部分 zip 时发送它)。

我认为正确的方法是在单独的过程中离线创建文件。然后,用户可以监视进度,然后在文件准备好时下载文件(可能通过使用上述迭代器方法)。这与您上传文件并等待处理时使用的 youtube 等网站类似。

于 2009-07-09T12:19:50.837 回答