2009-06-14 28 views
5

En una aplicación web en la que estoy trabajando, el usuario puede crear un archivo zip de una carpeta llena de archivos. Aquí aquí está el código:Crear archivo zip para descarga instantánea

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) 

Pero esto tiene el efecto secundario desagradable de una larga espera (10 + segundos) mientras que el archivo zip se está descargando. ¿Es posible omitir esto? En lugar de guardar el archivo en un archivo, ¿es posible enviarlo directamente al usuario?

Creo que torrentflux proporciona esta característica de excat de la que estoy hablando. Ser capaz de comprimir los GB de datos y descargarlos en un segundo.

Respuesta

2

¿La biblioteca de cremallera que está utilizando, permite una salida a una corriente. Puede transmitir directamente al usuario en lugar de escribir temporalmente en un archivo comprimido ENTONCES transmitiendo al usuario.

+0

creo que esto puede ser lo que está pidiendo. – Travis

+0

Permite objetos similares a archivos. Uno puede tener un objeto parecido a un archivo que actúa como transmisión en búfer: ¡mira mi respuesta! –

5

Aquí hay una función simple de vista de Django que comprime (por ejemplo) cualquier archivo legible en /tmp y devuelve el archivo 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 

Por supuesto, este enfoque sólo funcionará si los archivos zip caben cómodamente en la memoria - si no, tendrá que utilizar un archivo de disco (que se está tratando de evitar). En ese caso, simplemente reemplace el file = StringIO() con file = open('/path/to/yourfiles.zip', 'wb') y reemplace el file.getvalue() con el código para leer el contenido del archivo del disco.

0

Es posible pasar un iterador al constructor de una HttpResponse (see docs). Eso le permitiría crear un iterador personalizado que genera datos a medida que se solicitan. Sin embargo, no creo que funcione con un zip (tendrías que enviar un zip parcial a medida que se crea).

La forma correcta, creo, sería crear los archivos fuera de línea, en un proceso separado. El usuario podría entonces monitorear el progreso y luego descargar el archivo cuando esté listo (posiblemente utilizando el método del iterador descrito anteriormente). Esto sería similar a lo que los sitios como YouTube usan cuando carga un archivo y espera a que se procese.

8

Como dice mandrake, el constructor de HttpResponse acepta objetos iterables.

Por suerte, formato ZIP es tal que archivo se puede crear en un solo pase, ficha directorio central se encuentra al final del archivo:

enter image description here

(Foto de Wikipedia)

Y afortunadamente, zipfile no hace ninguna búsqueda siempre que solo agregue archivos.

Aquí está el código que se me ocurrió. Algunas notas:

  • Estoy usando este código para comprimir un montón de imágenes JPEG. No hay ningún punto comprimiendo ellos, estoy usando ZIP solo como contenedor.
  • El uso de la memoria es O (size_of_largest_file) no O (size_of_archive).Y esto es suficiente para mí: muchos archivos relativamente pequeños que se añaden a un archivo potencialmente enorme
  • Este código no establece el encabezado Content-Length, por lo que el usuario no obtiene una buena indicación de progreso. Es debería ser posible para calcular esto de antemano si se conocen los tamaños de todos los archivos.
  • Servir el ZIP directamente al usuario de esta manera significa que la reanudación de las descargas no funcionará.

Por lo tanto, aquí va:

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 
Cuestiones relacionadas