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.