短版
https://github.com/fringd/zipline
加长版
所以 jo5h 的回答在 rails 3.1.1 中对我不起作用
不过,我发现了一个有用的 youtube 视频。
http://www.youtube.com/watch?v=K0XvnspdPsc
它的关键是创建一个响应每个对象的对象......这就是我所做的:
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
编辑:
上述解决方案实际上不起作用...问题是 rubyzip 需要在文件中跳转以重写条目的标题。特别是它需要在写入数据之前写入压缩大小。这在真正的流媒体情况下是不可能的……所以最终这项任务可能是不可能的。有可能一次缓冲整个文件,但这似乎不太值得。最终我只是写了一个 tmp 文件......在 heroku 上我可以写到 Rails.root/tmp 更少的即时反馈,并不理想,但有必要。
另一个编辑:
我最近有了另一个想法......如果我们不压缩文件,我们可以知道文件的压缩大小。计划是这样的:
ZipStreamOutput 类的子类如下:
- 始终使用“存储”压缩方法,即不压缩
- 确保我们从不向后寻求更改文件头,提前做好准备
- 重写任何与 TOC 相关的代码
我还没有尝试实现这个,但如果有任何成功,我会报告。
确定最后一次编辑:
在 zip 标准中:http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers
他们提到有一点你可以翻转来放置文件的大小、压缩大小和 crc。所以我的新计划是对 zipoutput 流进行子类化,以便它
- 设置此标志
- 在数据之后写入大小和 CRC
- 从不回退输出
此外,我需要掌握所有技巧,以便在固定的导轨中流式输出...
不管怎样,一切都奏效了!
这是一颗宝石!
https://github.com/fringd/zipline