5
5
# Is used to write ZIP archives without having to read them back or to overwrite
6
6
# data. It outputs into any object that supports `<<` or `write`, namely:
7
7
#
8
- # An `Array`, `File`, `IO`, `Socket` and even `String` all can be output destinations
9
- # for the `Streamer`.
8
+ # * `Array` - will contain binary strings
9
+ # * `File` - data will be written to it as it gets generated
10
+ # * `IO` (`Socket`, `StringIO`) - data gets written into it
11
+ # * `String` - in binary encoding and unfrozen - also makes a decent output target
12
+ #
13
+ # or anything else that responds to `#<<` or `#write`.
10
14
#
11
15
# You can also combine output through the `Streamer` with direct output to the destination,
12
16
# all while preserving the correct offsets in the ZIP file structures. This allows usage
@@ -482,6 +486,10 @@ def update_last_entry_and_write_data_descriptor(crc32:, compressed_size:, uncomp
482
486
# is likely already on the wire. However, excluding the entry from the central directory of the ZIP
483
487
# file will allow better-behaved ZIP unarchivers to extract the entries which did store correctly,
484
488
# provided they read the ZIP from the central directory and not straight-ahead.
489
+ # Rolling back does not perform any writes.
490
+ #
491
+ # `rollback!` gets called for you if an exception is raised inside the block of `write_file`,
492
+ # `write_deflated_file` and `write_stored_file`.
485
493
#
486
494
# @example
487
495
# zip.add_stored_entry(filename: "data.bin", size: 4.megabytes, crc32: the_crc)
@@ -493,14 +501,17 @@ def update_last_entry_and_write_data_descriptor(crc32:, compressed_size:, uncomp
493
501
# end
494
502
# @return [Integer] position in the output stream / ZIP archive
495
503
def rollback!
496
- removed_entry = @files . pop
497
- return @out . tell unless removed_entry
504
+ @files . pop if @remove_last_file_at_rollback
498
505
506
+ # Recreate the path set from remaining entries (PathSet does not support cheap deletes yet)
499
507
@path_set . clear
500
508
@files . each do |e |
501
509
@path_set . add_directory_or_file_path ( e . filename ) unless e . filler?
502
510
end
503
- @files << Filler . new ( @out . tell - removed_entry . local_header_offset )
511
+
512
+ # Create filler for the truncated or unusable local file entry that did get written into the output
513
+ filler_size_bytes = @out . tell - @offset_before_last_local_file_header
514
+ @files << Filler . new ( filler_size_bytes )
504
515
505
516
@out . tell
506
517
end
@@ -554,6 +565,11 @@ def add_file_and_write_local_header(
554
565
use_data_descriptor :,
555
566
unix_permissions :
556
567
)
568
+ # Set state needed for proper rollback later. If write_local_file_header
569
+ # does manage to write _some_ bytes, but fails later (we write in tiny bits sometimes)
570
+ # we should be able to create a filler from this offset on when we
571
+ @offset_before_last_local_file_header = @out . tell
572
+ @remove_last_file_at_rollback = false
557
573
558
574
# Clean backslashes
559
575
filename = remove_backslash ( filename )
@@ -600,9 +616,11 @@ def add_file_and_write_local_header(
600
616
mtime : e . mtime ,
601
617
filename : e . filename ,
602
618
storage_mode : e . storage_mode )
619
+
603
620
e . bytes_used_for_local_header = @out . tell - e . local_header_offset
604
621
605
622
@files << e
623
+ @remove_last_file_at_rollback = true
606
624
end
607
625
608
626
def remove_backslash ( filename )
0 commit comments