Kurzversion
https://github.com/fringd/zipline
Lange Version
so jo5h's Antwort hat bei mir in Rails 3.1.1 nicht funktioniert
Ich habe jedoch ein YouTube-Video gefunden, das geholfen hat.
http://www.youtube.com/watch?v=K0XvnspdPsc
Im Wesentlichen besteht es darin, ein Objekt zu erstellen, das auf 'each' reagiert... Dies ist was ich gemacht habe:
class ZipGenerator
def initialize(model)
@model = model
end
def each( &block )
output = Object.new
output.define_singleton_method :tell, Proc.new { 0 }
output.define_singleton_method :pos=, Proc.new { |x| 0 }
output.define_singleton_method :<<, Proc.new { |x| block.call(x) }
output.define_singleton_method :close, Proc.new { nil }
Zip::IoZip.open(output) do |zip|
@model.attachments.all.each do |attachment|
zip.put_next_entry "#{attachment.name}.pdf"
file = attachment.file.file.send :file
file = File.open(file) if file.is_a? String
while buffer = file.read(2048)
zip << buffer
end
end
end
sleep 10
end
end
def getzip
self.response_body = ZipGenerator.new(@model)
#this is a hack to preven middleware from buffering
headers['Last-Modified'] = Time.now.to_s
end
BEARBEITEN:
Die obige Lösung hat TATSÄCHLICH nicht funktioniert... Das Problem ist, dass RubyZip durch die Datei springen muss, um die Header für die Einträge neu zu schreiben, während es weitergeht. Insbesondere muss es die komprimierte Größe VOR dem Schreiben der Daten schreiben. Dies ist in einer wirklich streamenden Situation einfach nicht möglich... Letztendlich könnte diese Aufgabe also unmöglich sein. Es besteht die Möglichkeit, dass es möglich ist, eine ganze Datei gepuffert zu verarbeiten, aber dies schien weniger sinnvoll. Letztendlich habe ich einfach in eine tmp-Datei geschrieben... auf Heroku kann ich nach Rails.root/tmp schreiben, weniger sofortiges Feedback, und nicht ideal, aber notwendig.
NOCH EINE BEARBEITUNG:
Ich hatte vor kurzem eine andere Idee... wir KÖNNTEN die komprimierte Größe der Dateien kennen, wenn wir sie nicht komprimieren. Der Plan sieht etwa wie folgt aus:
Verwenden Sie die ZipStreamOutput-Klasse wie folgt:
- Verwenden Sie immer die Methode "stored" zur Kompression, komprimieren Sie also nicht
- Stellen Sie sicher, dass wir nie rückwärts springen, um Dateiheader zu ändern, alles vorne richtig machen
- Schreibe jeden Code um, der sich auf TOC bezieht, der sucht
Ich habe das noch nicht versucht zu implementieren, werde aber zurückmelden, wenn es Erfolg gibt.
OK EINE LETZTE BEARBEITUNG:
In der Zip-Standard: http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
erwähnen sie, dass es ein Bit gibt, mit dem man die Größe, die komprimierte Größe und die CRC NACH einer Datei platzieren kann. Also war mein neuer Plan, ZipOutputStream zu unterklassifizieren, sodass es
- diese Flagge setzt
- Größen und CRCs nach den Daten schreibt
- die Ausgabe niemals zurückspult
Außerdem musste ich alle Hacks in Bezug auf das Streamen von Ausgaben in Rails in Ordnung bringen...
Wie auch immer, es hat alles funktioniert!
Hier ist ein Juwel!
https://github.com/fringd/zipline
0 Stimmen
ZipOutputStream kann dies nicht tun, weil es beim Schreiben der komprimierten Daten vor und zurück durch den Stream sucht (siehe
ZipOutputStream#update_local_headers
, aufgerufen vonZipOutputStream#close
). Daher ist es unmöglich, Datenstücke mit ZipOutputStream zu bedienen, bevor der Vorgang abgeschlossen ist.