2009-06-14 9 views
5

In einer Web-App, an der ich gerade arbeite, kann der Benutzer ein Zip-Archiv eines Ordners voller Dateien erstellen. Hier ist hier der Code:Zip-Archiv zum sofortigen Download erstellen

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) 

Das hat aber die unangenehme Nebenwirkung von einer langen Wartezeit (+ 10 Sekunden), während das Zip-Archiv heruntergeladen wird. Ist es möglich, dies zu überspringen? Anstatt das Archiv in einer Datei zu speichern, ist es möglich, es direkt an den Benutzer zu senden?

Ich glaube, dass torrentflux bietet diese excat-Funktion, über die ich spreche. In der Lage, GBs von Daten zu zippen und innerhalb einer Sekunde herunterzuladen.

Antwort

2

Ist die Zip-Bibliothek, die Sie für die Ausgabe in einen Stream erlauben verwenden. Sie können direkt an den Benutzer streamen, anstatt vorübergehend in eine ZIP-Datei zu schreiben, die dann an den Benutzer streamt.

+0

Ich glaube, das sein kann, was er zu fragen. – Travis

+0

Es ermöglicht dateiähnliche Objekte. Man kann ein dateiähnliches Objekt haben, das als gepufferter Stream fungiert - siehe meine Antwort! –

5

Hier ist eine einfache Django-View-Funktion, die (z. B.) alle lesbaren Dateien in /tmp zipt und die Zip-Datei zurückgibt.

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 

Natürlich ist dieser Ansatz funktioniert nur, wenn die ZIP-Dateien bequem in den Speicher passen - wenn nicht, werden Sie eine Plattendatei verwenden müssen (die Sie zu vermeiden, sind versuchen). In diesem Fall ersetzen Sie einfach die file = StringIO() durch file = open('/path/to/yourfiles.zip', 'wb') und ersetzen Sie die file.getvalue() mit Code, um den Inhalt der Datei zu lesen.

0

Es ist möglich, einen Iterator an den Konstruktor einer HttpResponse (see docs) zu übergeben. Dadurch können Sie einen benutzerdefinierten Iterator erstellen, der Daten bei der Anforderung generiert. Ich denke jedoch nicht, dass das mit einer Zip-Datei funktionieren wird (Sie müssten eine teilweise Zip-Datei senden, wenn sie erstellt wird).

Der richtige Weg, denke ich, wäre, die Dateien offline zu erstellen, in einem separaten Prozess. Der Benutzer könnte dann den Fortschritt überwachen und dann die Datei herunterladen, wenn sie bereit ist (möglicherweise unter Verwendung der oben beschriebenen Iterator-Methode). Dies wäre vergleichbar mit dem, was Websites wie youtube verwenden, wenn Sie eine Datei hochladen und darauf warten, dass sie verarbeitet wird.

8

Wie Alrakee sagt, akzeptiert der Konstruktor von HttpResponse iterierbare Objekte.

Zum Glück, ZIP-Format ist so, dass Archiv bei einmaligen Durchgang erstellt werden kann, wird zentraler Verzeichniseintrag am Ende der Datei befindet:

enter image description here

(Bild von Wikipedia)

Und zum Glück, zipfile in der Tat sucht nicht, solange Sie nur Dateien hinzufügen.

Hier ist der Code, den ich mir ausgedacht habe. Einige Hinweise:

  • Ich benutze diesen Code zum Zippen einer Reihe von JPEG-Bildern. Es gibt keinen Punkt Komprimierung sie, ich benutze ZIP nur als Container.
  • Speicherauslastung ist O (size_of_largest_file) nicht O (size_of_archive).Und das ist gut genug für mich: viele relativ kleine Dateien, die zu potenziell großen Archiv summieren
  • Dieser Code setzt nicht Content-Length-Header, so dass Benutzer keine nette Fortschrittsanzeige erhalten. Es sollte möglich sein, dies im Voraus zu berechnen, wenn Größen aller Dateien bekannt sind.
  • Wenn Sie die ZIP-Datei direkt an den Benutzer senden, bedeutet dies, dass der Lebenslauf bei Downloads nicht funktioniert.

So, hier geht:

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