examples/gzip.rb in bindata-1.8.3 vs examples/gzip.rb in bindata-2.0.0

- old
+ new

@@ -1,161 +1,125 @@ require 'bindata' -require 'forwardable' +require 'zlib' # An example of a reader / writer for the GZIP file format as per rfc1952. -# Note that compression is not implemented to keep the example small. -class Gzip - extend Forwardable +# See notes at the end of this file for implementation discussions. +class Gzip < BinData::Record + # Binary representation of a ruby Time object + class Mtime < BinData::Primitive + uint32le :time - # Known compression methods - DEFLATE = 8 + def set(val) + self.time = val.to_i + end - class Extra < BinData::Record - endian :little - - uint16 :len, :length => lambda { data.length } - string :data, :read_length => :len + def get + Time.at(time) + end end - class Header < BinData::Record - endian :little + # Known compression methods + DEFLATE = 8 - uint16 :ident, :asserted_value => 0x8b1f - uint8 :compression_method, :initial_value => DEFLATE + endian :little - bit3 :freserved, :asserted_value => 0 - bit1 :fcomment, :value => lambda { comment.length > 0 ? 1 : 0 } - bit1 :ffile_name, :value => lambda { file_name.length > 0 ? 1 : 0 } - bit1 :fextra, :value => lambda { extra.len > 0 ? 1 : 0 } - bit1 :fcrc16, :value => 0 # see comment below - bit1 :ftext + uint16 :ident, :asserted_value => 0x8b1f + uint8 :compression_method, :initial_value => DEFLATE - # Never include header crc. This is because the current versions of the - # command-line version of gzip (up through version 1.3.x) do not - # support header crc's, and will report that it is a "multi-part gzip - # file" and give up. + bit3 :freserved, :asserted_value => 0 + bit1 :fcomment, :value => lambda { comment.length > 0 ? 1 : 0 } + bit1 :ffile_name, :value => lambda { file_name.length > 0 ? 1 : 0 } + bit1 :fextra, :value => lambda { extra.len > 0 ? 1 : 0 } + bit1 :fcrc16, :value => 0 # see note at end of file + bit1 :ftext - uint32 :mtime - uint8 :extra_flags - uint8 :os, :initial_value => 255 # unknown OS + mtime :mtime + uint8 :extra_flags + uint8 :os, :initial_value => 255 # unknown OS - # These fields are optional depending on the bits in flags - extra :extra, :onlyif => lambda { fextra.nonzero? } - stringz :file_name, :onlyif => lambda { ffile_name.nonzero? } - stringz :comment, :onlyif => lambda { fcomment.nonzero? } - uint16 :crc16, :onlyif => lambda { fcrc16.nonzero? } + # The following fields are optional depending on the bits in flags + + struct :extra, :onlyif => lambda { fextra.nonzero? } do + uint16 :len, :length => lambda { data.length } + string :data, :read_length => :len end + stringz :file_name, :onlyif => lambda { ffile_name.nonzero? } + stringz :comment, :onlyif => lambda { fcomment.nonzero? } + uint16 :crc16, :onlyif => lambda { fcrc16.nonzero? } - class Footer < BinData::Record - endian :little + # The length of compressed data must be calculated from the current file offset + count_bytes_remaining :bytes_remaining + string :compressed_data, :read_length => lambda { bytes_remaining - footer.num_bytes } + struct :footer do uint32 :crc32 uint32 :uncompressed_size end - def initialize - @header = Header.new - @footer = Footer.new + def data=(data) + # Zlib.deflate includes a header + footer which we must discard + self.compressed_data = Zlib::Deflate.deflate(data)[2..-5] + self.footer.crc32 = Zlib::crc32(data) + self.footer.uncompressed_size = data.size end - - attr_accessor :compressed - def_delegators :@header, :file_name=, :file_name - def_delegators :@header, :comment=, :comment, :comment? - def_delegators :@header, :compression_method - def_delegators :@footer, :crc32, :uncompressed_size - - def mtime - Time.at(@header.mtime.snapshot) - end - - def mtime=(tm) - @header.mtime = tm.to_i - end - - def total_size - @header.num_bytes + @compressed.size + @footer.num_bytes - end - - def compressed_data - @compressed - end - - def set_compressed_data(compressed, crc32, uncompressed_size) - @compressed = compressed - @footer.crc32 = crc32 - @footer.uncompressed_size = uncompressed_size - end - - def read(file_name) - File.open(file_name, "r") do |io| - @header.read(io) - - # Determine the size of the compressed data. This is needed because - # we don't actually uncompress the data. Ideally the uncompression - # method would read the correct number of bytes from the IO and the - # IO would be positioned ready to read the footer. - - pos = io.pos - io.seek(-@footer.num_bytes, IO::SEEK_END) - compressed_size = io.pos - pos - io.seek(pos) - - @compressed = io.read(compressed_size) - @footer.read(io) - end - end - - def write(file_name) - File.open(file_name, "w") do |io| - @header.write(io) - io.write(@compressed) - @footer.write(io) - end - end end if __FILE__ == $0 # Write a gzip file. print "Creating a gzip file ... " g = Gzip.new - # Uncompressed data is "the cat sat on the mat" - g.set_compressed_data("+\311HUHN,Q(\006\342\374<\205\022 77\261\004\000", - 3464689835, 22) + g.data = "the cat sat on the mat" g.file_name = "poetry" g.mtime = Time.now g.comment = "A stunning piece of prose" - g.write("poetry.gz") + File.open("poetry.gz", "w") do |io| + g.write(io) + end puts "done." puts # Read the created gzip file. print "Reading newly created gzip file ... " g = Gzip.new - g.read("poetry.gz") + File.open("poetry.gz", "r") do |io| + g.read(io) + end puts "done." puts puts "Printing gzip file details in the format of gzip -l -v" # compression ratio - ratio = 100.0 * (g.uncompressed_size - g.compressed.size) / - g.uncompressed_size + ratio = 100.0 * (g.footer.uncompressed_size - g.compressed_data.size) / + g.footer.uncompressed_size comp_meth = (g.compression_method == Gzip::DEFLATE) ? "defla" : "" # Output using the same format as gzip -l -v puts "method crc date time compressed " + "uncompressed ratio uncompressed_name" puts "%5s %08x %6s %5s %19s %19s %5.1f%% %s" % [comp_meth, - g.crc32, + g.footer.crc32, g.mtime.strftime('%b %d'), g.mtime.strftime('%H:%M'), - g.total_size, - g.uncompressed_size, + g.num_bytes, + g.footer.uncompressed_size, ratio, g.file_name] - puts "Comment: #{g.comment}" if g.comment? + puts "Comment: #{g.comment}" if g.comment != "" puts puts "Executing gzip -l -v" puts `gzip -l -v poetry.gz` end + +# Notes: +# +# Mtime: A convenience wrapper that allow a ruby Time object to be used instead +# of manually dealing with the raw form (seconds since 1 Jan 1970) +# +# rfc1952 specifies an optional crc16 field. The gzip command line client +# uses this field for multi-part gzip. Hence we ignore this. + +# We are cheating and using the Zlib library for compression. We can't use +# this library for decompression as zlib requires an adler32 checksum while +# gzip uses crc32.