2009-06-14 16 views
5

In un'app Web su cui sto lavorando, l'utente può creare un archivio zip di una cartella piena di file. Qui ecco il codice:Crea archivio zip per download immediato

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) 

Ma questo ha l'effetto collaterale sgradevole di una lunga attesa (10 + secondi), mentre l'archivio zip in corso. È possibile saltare questo? Invece di salvare l'archivio in un file, è possibile inviarlo direttamente all'utente?

Credo che torrentflux fornisca questa funzione di excat di cui sto parlando. Essere in grado di comprimere GB di dati e scaricarli in un secondo.

risposta

2

Fa biblioteca zip che si sta utilizzando per l'uscita permettono a un flusso. È possibile eseguire lo streaming direttamente all'utente invece di scrivere temporaneamente in un file zip, quindi lo streaming all'utente.

+0

Penso che questo possa essere quello che sta chiedendo. – Travis

+0

Consente oggetti simili a file. Si può avere un oggetto simile a un file che funge da stream bufferizzato - guarda la mia risposta! –

5

Ecco una semplice funzione di visualizzazione di Django che esegue la chiusura lampo (ad esempio) di tutti i file leggibili in /tmp e restituisce il file 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 

Naturalmente questo approccio funziona solo se i file zip verranno comodamente entrare nella memoria - in caso contrario, si dovrà utilizzare un file su disco (che si sta cercando di evitare). In tal caso, è sufficiente sostituire lo file = StringIO() con file = open('/path/to/yourfiles.zip', 'wb') e sostituire lo file.getvalue() con il codice per leggere il contenuto del file del disco.

0

È possibile passare un iteratore al costruttore di un HttpResponse (see docs). Ciò consentirebbe di creare un iteratore personalizzato che genera dati mentre viene richiesto. Tuttavia non penso che funzionerà con un zip (dovresti inviare il partial zip mentre viene creato).

Il modo corretto, penso, sarebbe quello di creare i file offline, in un processo separato. L'utente può quindi monitorare l'avanzamento e quindi scaricare il file quando è pronto (possibilmente utilizzando il metodo iteratore descritto sopra). Questo sarebbe simile a ciò che i siti come YouTube usano quando carichi un file e attendi che venga elaborato.

8

Come dice il mandrake, il costruttore di HttpResponse accetta oggetti iterabili.

Fortunatamente, formato ZIP è tale che archivio può essere creato in un'unica passata, record di directory centrale si trova alla fine del file:

enter image description here

(Picture da Wikipedia)

E per fortuna, zipfile non esegue ricerche finché si aggiungono solo file.

Ecco il codice che ho trovato. Alcune note:

  • Sto usando questo codice per zippare un mucchio di immagini JPEG. Non c'è il punto comprimendo, sto usando ZIP solo come contenitore.
  • L'utilizzo della memoria è O (size_of_largest_file) non O (size_of_archive).E questo è abbastanza buono per me: molti file relativamente piccoli che si sommano a un archivio potenzialmente enorme
  • Questo codice non imposta l'intestazione Content-Length, quindi l'utente non ottiene una buona indicazione di avanzamento. È dovrebbe essere possibile calcolare questo in anticipo se le dimensioni di tutti i file sono noti.
  • Fornire direttamente all'utente ZIP come questo significa che il ripristino dei download non funzionerà.

Quindi, ecco qui:

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 
Problemi correlati