Created
December 6, 2021 19:16
-
-
Save thedanbob/84ae41d3a521dd2c6cb6d5ada11c75b9 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class FoldersController < ApplicationController | |
include Streamable | |
def download | |
@folder = Folder.find(params[:id]) # has_many_attached :files | |
response.headers['Last-Modified'] = @folder.updated_at.httpdate | |
zip = ZipStream.open | |
send_stream(filename: "folder_#{@folder.id}.zip", type: :zip) do |stream| | |
@folder.files.includes(:blob).each do |file| | |
data = zip.add_stored(file.filename.to_s, file.download) | |
stream.write(data) | |
end | |
stream.write(zip.close) | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Streamable | |
# Adds `send_stream` method from rails 7 alpha | |
# https://github.com/rails/rails/pull/41488 | |
extend ActiveSupport::Concern | |
include ActionController::Live | |
def send_stream(filename:, disposition: 'attachment', type: nil) | |
response.headers['Content-Type'] = | |
(type.is_a?(Symbol) ? Mime[type].to_s : type) || | |
Mime::Type.lookup_by_extension(File.extname(filename).downcase.delete('.')) || | |
'application/octet-stream' | |
response.headers['Content-Disposition'] = | |
ActionDispatch::Http::ContentDisposition.format(disposition: disposition, filename: filename) | |
yield response.stream | |
ensure | |
response.stream.close | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'zip' | |
class ZipStream < Zip::OutputStream | |
# Rolling buffer to trick RubyZip into thinking it's working with the entire zip file | |
# while only holding a single entry in memory at a time | |
class ZipBuffer < StringIO | |
# buffer_pos keeps track of the buffer's absolute position in the zip file | |
attr_accessor :buffer_pos | |
def initialize(*) | |
@buffer_pos = 0 | |
super | |
end | |
# RubyZip uses `tell` to calculate header offsets | |
def tell | |
@buffer_pos + super | |
end | |
# Advance buffer_pos to end of file, return contents, and clear buffer | |
def dump | |
@buffer_pos += size | |
tmp = string.dup | |
self.string = '' | |
tmp | |
end | |
end | |
class << self | |
def open | |
io = ZipBuffer.new('') | |
io.binmode | |
new(io, true) | |
end | |
end | |
# Equivalent to Zip::File#add_stored | |
def add_stored(filename, data) | |
put_next_entry(filename, nil, nil, Zip::Entry::STORED) | |
write(data) | |
# Save current entry before finalize clears it | |
entry = @current_entry | |
finalize_current_entry | |
# Rewrite entry updated by finalize_current_entry | |
@output_stream.pos = entry.local_header_offset - @output_stream.buffer_pos | |
entry.write_local_entry(@output_stream, true) | |
# Return entry data and advance buffer | |
@output_stream.dump | |
end | |
# Close zip and return final data | |
def close | |
return if @closed | |
write_central_directory | |
data = @output_stream.dump | |
@output_stream.close | |
@closed = true | |
data | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment