Class: ZipTricks::Microzip
- Inherits:
-
Object
- Object
- ZipTricks::Microzip
- Defined in:
- lib/zip_tricks/microzip.rb
Overview
A replacement for RubyZip for streaming, with a couple of small differences. The first difference is that it is verbosely-written-to-the-spec and you can actually follow what is happening. It does not support quite a few fancy features of Rubyzip, but instead it can be digested in one reading, and has solid Zip64 support. It also does not attempt any tricks with Zip64 placeholder extra fields because the ZipTricks streaming engine assumes you know how large your file is (both compressed and uncompressed) and you have the file's CRC32 checksum upfront.
Just like Rubyzip it will switch to Zip64 automatically if required, but there is no global setting to enable that behavior - it is always on.
Constant Summary collapse
- STORED =
0- DEFLATED =
8- TooMuch =
Class.new(StandardError)
- PathError =
Class.new(StandardError)
- DuplicateFilenames =
Class.new(StandardError)
- UnknownMode =
Class.new(StandardError)
Instance Method Summary collapse
-
#add_local_file_header(io:, filename:, crc32:, compressed_size:, uncompressed_size:, storage_mode:, mtime: Time.now.utc) ⇒ void
Adds a file to the entry list and immediately writes out it's local file header into the output stream.
-
#initialize ⇒ Microzip
constructor
Creates a new streaming writer.
-
#write_central_directory(io) ⇒ void
Writes the central directory (including the Zip6 salient bits if necessary).
Constructor Details
#initialize ⇒ Microzip
Creates a new streaming writer. The writer is stateful and knows it's list of ZIP file entries as they are being added.
218 219 220 221 |
# File 'lib/zip_tricks/microzip.rb', line 218 def initialize @files = [] @local_header_offsets = [] end |
Instance Method Details
#add_local_file_header(io:, filename:, crc32:, compressed_size:, uncompressed_size:, storage_mode:, mtime: Time.now.utc) ⇒ void
This method returns an undefined value.
Adds a file to the entry list and immediately writes out it's local file header into the output stream.
234 235 236 237 238 239 240 241 242 243 |
# File 'lib/zip_tricks/microzip.rb', line 234 def add_local_file_header(io:, filename:, crc32:, compressed_size:, uncompressed_size:, storage_mode:, mtime: Time.now.utc) if @files.any?{|e| e.filename == filename } raise DuplicateFilenames, "Filename #{filename.inspect} already used in the archive" end raise UnknownMode, "Unknown compression mode #{storage_mode}" unless [STORED, DEFLATED].include?(storage_mode) e = Entry.new(filename, crc32, compressed_size, uncompressed_size, storage_mode, mtime) @files << e @local_header_offsets << io.tell e.write_local_file_header(io) end |
#write_central_directory(io) ⇒ void
This method returns an undefined value.
Writes the central directory (including the Zip6 salient bits if necessary)
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 |
# File 'lib/zip_tricks/microzip.rb', line 250 def write_central_directory(io) start_of_central_directory = io.tell # Central directory file headers, per file in order @files.each_with_index do |file, i| local_file_header_offset_from_start_of_file = @local_header_offsets.fetch(i) file.write_central_directory_file_header(io, local_file_header_offset_from_start_of_file) end central_dir_size = io.tell - start_of_central_directory zip64_required = central_dir_size > FOUR_BYTE_MAX_UINT || start_of_central_directory > FOUR_BYTE_MAX_UINT || @files.length > TWO_BYTE_MAX_UINT || @files.any?(&:requires_zip64?) # Then, if zip64 is used if zip64_required # [zip64 end of central directory record] zip64_eocdr_offset = io.tell # zip64 end of central dir io << [0x06064b50].pack(C_V) # signature 4 bytes (0x06064b50) io << [44].pack(C_Qe) # size of zip64 end of central # directory record 8 bytes # (this is ex. the 12 bytes of the signature and the size value itself). # Without the extensible data sector it is always 44. io << MADE_BY_SIGNATURE # version made by 2 bytes io << [VERSION_NEEDED_TO_EXTRACT_ZIP64].pack(C_v) # version needed to extract 2 bytes io << [0].pack(C_V) # number of this disk 4 bytes io << [0].pack(C_V) # number of the disk with the # start of the central directory 4 bytes io << [@files.length].pack(C_Qe) # total number of entries in the # central directory on this disk 8 bytes io << [@files.length].pack(C_Qe) # total number of entries in the # central directory 8 bytes io << [central_dir_size].pack(C_Qe) # size of the central directory 8 bytes # offset of start of central # directory with respect to io << [start_of_central_directory].pack(C_Qe) # the starting disk number 8 bytes # zip64 extensible data sector (variable size), blank for us # [zip64 end of central directory locator] io << [0x07064b50].pack(C_V) # zip64 end of central dir locator # signature 4 bytes (0x07064b50) io << [0].pack(C_V) # number of the disk with the # start of the zip64 end of # central directory 4 bytes io << [zip64_eocdr_offset].pack(C_Qe) # relative offset of the zip64 # end of central directory record 8 bytes # (note: "relative" is actually "from the start of the file") io << [1].pack(C_V) # total number of disks 4 bytes end # Then the end of central directory record: io << [0x06054b50].pack(C_V) # end of central dir signature 4 bytes (0x06054b50) io << [0].pack(C_v) # number of this disk 2 bytes io << [0].pack(C_v) # number of the disk with the # start of the central directory 2 bytes if zip64_required # the number of entries will be read from the zip64 part of the central directory io << [TWO_BYTE_MAX_UINT].pack(C_v) # total number of entries in the # central directory on this disk 2 bytes io << [TWO_BYTE_MAX_UINT].pack(C_v) # total number of entries in # the central directory 2 bytes else io << [@files.length].pack(C_v) # total number of entries in the # central directory on this disk 2 bytes io << [@files.length].pack(C_v) # total number of entries in # the central directory 2 bytes end if zip64_required io << [FOUR_BYTE_MAX_UINT].pack(C_V) # size of the central directory 4 bytes io << [FOUR_BYTE_MAX_UINT].pack(C_V) # offset of start of central # directory with respect to # the starting disk number 4 bytes else io << [central_dir_size].pack(C_V) # size of the central directory 4 bytes io << [start_of_central_directory].pack(C_V) # offset of start of central # directory with respect to # the starting disk number 4 bytes end io << [0].pack(C_v) # .ZIP file comment length 2 bytes # .ZIP file comment (variable size) end |