-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
writer.cr
120 lines (100 loc) · 3.36 KB
/
writer.cr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# A write-only `IO` object to compress data in the gzip format.
#
# Instances of this class wrap another `IO` object. When you write to this
# instance, it compresses the data and writes it to the underlying `IO`.
#
# NOTE: unless created with a block, `close` must be invoked after all
# data has been written to a `Gzip::Writer` instance.
#
# ### Example: compress a file
#
# ```
# require "compress/gzip"
#
# File.write("file.txt", "abc")
#
# File.open("./file.txt", "r") do |input_file|
# File.open("./file.gzip", "w") do |output_file|
# Compress::Gzip::Writer.open(output_file) do |gzip|
# IO.copy(input_file, gzip)
# end
# end
# end
# ```
class Compress::Gzip::Writer < IO
# Whether to close the enclosed `IO` when closing this writer.
property? sync_close = false
# Returns `true` if this writer is closed.
getter? closed = false
# The header to write to the gzip stream. It will be
# written just before the first write to this writer.
# Changes to the header after the first write are
# ignored.
getter header = Header.new
# Creates a new writer to the given *io*.
def initialize(@io : IO, @level = Compress::Gzip::DEFAULT_COMPRESSION, @sync_close = false)
# CRC32 of written data
@crc32 = ::Digest::CRC32.initial
# Total size of the original (uncompressed) input data modulo 2^32.
@isize = 0
end
# Creates a new writer to the given *filename*.
def self.new(filename : String, level = Compress::Gzip::DEFAULT_COMPRESSION)
new(::File.new(filename, "w"), level: level, sync_close: true)
end
# Creates a new writer to the given *io*, yields it to the given block,
# and closes it at the end.
def self.open(io : IO, level = Compress::Gzip::DEFAULT_COMPRESSION, sync_close = false)
writer = new(io, level: level, sync_close: sync_close)
yield writer ensure writer.close
end
# Creates a new writer to the given *filename*, yields it to the given block,
# and closes it at the end.
def self.open(filename : String, level = Compress::Gzip::DEFAULT_COMPRESSION)
writer = new(filename, level: level)
yield writer ensure writer.close
end
# Always raises `IO::Error` because this is a write-only `IO`.
def read(slice : Bytes) : NoReturn
raise IO::Error.new("Can't read from Gzip::Writer")
end
# See `IO#write`.
def write(slice : Bytes) : Nil
check_open
return if slice.empty?
flate_io = write_header
flate_io.write(slice)
# Update CRC32 and total data size
@crc32 = ::Digest::CRC32.update(slice, @crc32)
# Using wrapping addition here because isize is only 32 bits wide but
# uncompressed data size can be bigger.
@isize &+= slice.size
end
# Flushes data, forcing writing the gzip header if no
# data has been written yet.
#
# See `IO#flush`.
def flush : Nil
check_open
flate_io = write_header
flate_io.flush
end
# Closes this writer. Must be invoked after all data has been written.
def close : Nil
return if @closed
@closed = true
flate_io = write_header
flate_io.close
@io.write_bytes @crc32, IO::ByteFormat::LittleEndian
@io.write_bytes @isize, IO::ByteFormat::LittleEndian
@io.close if @sync_close
end
private def write_header
flate_io = @flate_io
unless flate_io
flate_io = @flate_io = Compress::Deflate::Writer.new(@io, level: @level)
header.to_io(@io)
end
flate_io
end
end