From 5bbd7f633d0132a9deca2716b1ce335ef7d09e22 Mon Sep 17 00:00:00 2001 From: "Stanko K.R." Date: Fri, 30 Jan 2026 16:48:33 +0100 Subject: [PATCH] Process everything in chunks The old implementation loaded files into memory to provide an IO interface. This has the obvious downside of loading any file included in the import, e.g. a 10GB video file, into memory. ZipKit has no native IO object for reading but it provides all the necessary methods to implement one. --- app/models/zip_file/reader.rb | 5 ++--- app/models/zip_file/reader/extractor_io.rb | 23 ++++++++++++++++++++++ app/models/zip_file/writer.rb | 10 +++------- 3 files changed, 28 insertions(+), 10 deletions(-) create mode 100644 app/models/zip_file/reader/extractor_io.rb diff --git a/app/models/zip_file/reader.rb b/app/models/zip_file/reader.rb index ca69d7d1d..5cc14b006 100644 --- a/app/models/zip_file/reader.rb +++ b/app/models/zip_file/reader.rb @@ -10,12 +10,11 @@ class ZipFile::Reader raise ArgumentError, "Cannot read directory entry: #{file_path}" if entry.filename.end_with?("/") extractor = entry.extractor_from(@io) - content = extractor.extract if block_given? - yield StringIO.new(content) + yield ZipFile::Reader::ExtractorIO.new(extractor) else - content + extractor.extract end end diff --git a/app/models/zip_file/reader/extractor_io.rb b/app/models/zip_file/reader/extractor_io.rb new file mode 100644 index 000000000..22824b8cb --- /dev/null +++ b/app/models/zip_file/reader/extractor_io.rb @@ -0,0 +1,23 @@ +class ZipFile::Reader::ExtractorIO + def initialize(extractor) + @extractor = extractor + end + + def read(length = nil, buffer = nil) + return nil if @extractor.eof? + + data = @extractor.extract(length) + return nil if data.nil? + + if buffer + buffer.replace(data) + buffer + else + data + end + end + + def eof? + @extractor.eof? + end +end diff --git a/app/models/zip_file/writer.rb b/app/models/zip_file/writer.rb index 179c0a926..144416c79 100644 --- a/app/models/zip_file/writer.rb +++ b/app/models/zip_file/writer.rb @@ -26,16 +26,12 @@ class ZipFile::Writer def add_file(path, content = nil, compress: true) @entries << path - compression = compress ? :deflate : :stored + write_method = compress ? :write_deflated_file : :write_stored_file if block_given? - streamer.write_deflated_file(path) do |sink| - yield sink - end + streamer.public_send(write_method, path) { |sink| yield sink } else - streamer.write_deflated_file(path) do |sink| - sink.write(content) - end + streamer.public_send(write_method, path) { |sink| sink.write(content) } end end