Vous pouvez le faire en générant et en diffusant un fichier zip sans compression, ce qui consiste essentiellement à ajouter les en-têtes avant le contenu de chaque fichier. Vous avez raison, les bibliothèques ne supportent pas cela, mais vous pouvez les contourner pour que cela fonctionne.
Ce code enveloppe zipfile.ZipFile avec une classe qui gère le flux et crée des instances de zipfile.ZipInfo pour les fichiers à mesure qu'ils arrivent. Le CRC et la taille peuvent être définis à la fin. Vous pouvez y insérer des données provenant du flux d'entrée avec put_file(), write() et flush(), et lire des données à partir de ce flux vers le flux de sortie avec read().
import struct
import zipfile
import time
from StringIO import StringIO
class ZipStreamer(object):
def __init__(self):
self.out_stream = StringIO()
# write to the stringIO with no compression
self.zipfile = zipfile.ZipFile(self.out_stream, 'w', zipfile.ZIP_STORED)
self.current_file = None
self._last_streamed = 0
def put_file(self, name, date_time=None):
if date_time is None:
date_time = time.localtime(time.time())[:6]
zinfo = zipfile.ZipInfo(name, date_time)
zinfo.compress_type = zipfile.ZIP_STORED
zinfo.flag_bits = 0x08
zinfo.external_attr = 0600 << 16
zinfo.header_offset = self.out_stream.pos
# write right values later
zinfo.CRC = 0
zinfo.file_size = 0
zinfo.compress_size = 0
self.zipfile._writecheck(zinfo)
# write header to stream
self.out_stream.write(zinfo.FileHeader())
self.current_file = zinfo
def flush(self):
zinfo = self.current_file
self.out_stream.write(struct.pack("<LLL", zinfo.CRC, zinfo.compress_size, zinfo.file_size))
self.zipfile.filelist.append(zinfo)
self.zipfile.NameToInfo[zinfo.filename] = zinfo
self.current_file = None
def write(self, bytes):
self.out_stream.write(bytes)
self.out_stream.flush()
zinfo = self.current_file
# update these...
zinfo.CRC = zipfile.crc32(bytes, zinfo.CRC) & 0xffffffff
zinfo.file_size += len(bytes)
zinfo.compress_size += len(bytes)
def read(self):
i = self.out_stream.pos
self.out_stream.seek(self._last_streamed)
bytes = self.out_stream.read()
self.out_stream.seek(i)
self._last_streamed = i
return bytes
def close(self):
self.zipfile.close()
Gardez à l'esprit que ce code n'était qu'une rapide preuve de concept et que je n'ai pas fait de développement ou de test supplémentaire une fois que j'ai décidé de laisser le serveur http lui-même gérer ce problème. Si vous décidez de l'utiliser, vous devriez vérifier que les dossiers imbriqués sont archivés correctement, ainsi que l'encodage des noms de fichiers (ce qui est toujours un problème avec les fichiers zip).