Project

General

Profile

Feature #7056 » zip-create-via-stream.patch

Yuichi HARADA, 2020-04-08 04:10

View differences:

app/controllers/attachments_controller.rb
137 137
  end
138 138

  
139 139
  def download_all
140
    Tempfile.create('attachments_zip-', Rails.root.join('tmp')) do |tempfile|
141
      zip_file = Attachment.archive_attachments(tempfile, @attachments)
142
      if zip_file
143
        send_data(
144
          File.read(zip_file.path),
145
          :type => 'application/zip',
146
          :filename => "#{@container.class.to_s.downcase}-#{@container.id}-attachments.zip")
147
      else
148
        render_404
149
      end
140
    zip_data = Attachment.archive_attachments(@attachments)
141
    if zip_data
142
      file_name = "#{@container.class.to_s.downcase}-#{@container.id}-attachments.zip"
143
      send_data(
144
        zip_data,
145
        :type => Redmine::MimeType.of(file_name),
146
        :filename => file_name
147
      )
148
    else
149
      render_404
150 150
    end
151 151
  end
152 152

  
app/models/attachment.rb
346 346
    Attachment.where("created_on < ? AND (container_type IS NULL OR container_type = '')", Time.now - age).destroy_all
347 347
  end
348 348

  
349
  def self.archive_attachments(out_file, attachments)
349
  def self.archive_attachments(attachments)
350 350
    attachments = attachments.select(&:readable?)
351 351
    return nil if attachments.blank?
352 352

  
353 353
    Zip.unicode_names = true
354 354
    archived_file_names = []
355
    Zip::File.open(out_file.path, Zip::File::CREATE) do |zip|
355
    Zip::OutputStream.write_buffer do |zos|
356 356
      attachments.each do |attachment|
357 357
        filename = attachment.filename
358 358
        # rename the file if a file with the same name already exists
359 359
        dup_count = 0
360 360
        while archived_file_names.include?(filename)
361 361
          dup_count += 1
362
          basename = File.basename(attachment.filename, '.*')
363 362
          extname = File.extname(attachment.filename)
363
          basename = File.basename(attachment.filename, extname)
364 364
          filename = "#{basename}(#{dup_count})#{extname}"
365 365
        end
366
        zip.add(filename, attachment.diskfile)
366
        zos.put_next_entry(filename)
367
        zos << IO.binread(attachment.diskfile)
367 368
        archived_file_names << filename
368 369
      end
369
    end
370
    out_file
370
    end.string
371 371
  end
372 372

  
373 373
  # Moves an existing attachment to its target directory
test/unit/attachment_test.rb
280 280

  
281 281
  def test_archive_attachments
282 282
    attachment = Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1)
283
    Tempfile.create('attachments_zip', Rails.root.join('tmp')) do |tempfile|
284
      zip_file = Attachment.archive_attachments(tempfile, [attachment])
285
      assert_instance_of File, zip_file
283
    zip_data = Attachment.archive_attachments([attachment])
284
    file_names = []
285
    Zip::InputStream.open(StringIO.new(zip_data)) do |io|
286
      while (entry = io.get_next_entry)
287
        file_names << entry.name
288
      end
286 289
    end
290
    assert_equal ['testfile.txt'], file_names
287 291
  end
288 292

  
289 293
  def test_archive_attachments_without_attachments
290
    Tempfile.create('attachments_zip', Rails.root.join('tmp')) do |tempfile|
291
      zip_file = Attachment.archive_attachments(tempfile, [])
292
      assert_nil zip_file
293
    end
294
    zip_data = Attachment.archive_attachments([])
295
    assert_nil zip_data
294 296
  end
295 297

  
296 298
  def test_archive_attachments_should_rename_duplicate_file_names
297 299
    attachment1 = Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1)
298 300
    attachment2 = Attachment.create!(:file => uploaded_test_file("testfile.txt", ""), :author_id => 1)
299
    Tempfile.create('attachments_zip', Rails.root.join('tmp')) do |tempfile|
300
      zip_file = Attachment.archive_attachments(tempfile, [attachment1, attachment2])
301
      Zip::File.open(zip_file.path) do |z|
302
        assert_equal ['testfile.txt', 'testfile(1).txt'], z.map(&:name)
301
    zip_data = Attachment.archive_attachments([attachment1, attachment2])
302
    file_names = []
303
    Zip::InputStream.open(StringIO.new(zip_data)) do |io|
304
      while (entry = io.get_next_entry)
305
        file_names << entry.name
303 306
      end
304 307
    end
308
    assert_equal ['testfile.txt', 'testfile(1).txt'], file_names
305 309
  end
306 310

  
307 311
  def test_move_from_root_to_target_directory_should_move_root_files
(18-18/22)