-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
/
writer.cr
120 lines (94 loc) · 3.25 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 zlib 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 Zlib::Writer instance.
class Compress::Zlib::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
# Creates a new writer to the given *io*.
def initialize(@io : IO, @level = Zlib::DEFAULT_COMPRESSION, @sync_close = false, @dict : Bytes? = nil)
@wrote_header = false
@adler32 = ::Digest::Adler32.initial
@flate_io = Compress::Deflate::Writer.new(@io, level: level, dict: @dict)
end
# Creates a new writer to the given *filename*.
def self.new(filename : String, level = Zlib::DEFAULT_COMPRESSION, dict : Bytes? = nil)
new(::File.new(filename, "w"), level: level, sync_close: true, dict: dict)
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 = Zlib::DEFAULT_COMPRESSION, sync_close = false, dict : Bytes? = nil)
writer = new(io, level: level, sync_close: sync_close, dict: dict)
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 = Zlib::DEFAULT_COMPRESSION, dict : Bytes? = nil)
writer = new(filename, level: level, dict: dict)
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?
write_header unless @wrote_header
@flate_io.write(slice)
@adler32 = ::Digest::Adler32.update(slice, @adler32)
end
# Flushes data, forcing writing the zlib header if no
# data has been written yet.
#
# See `IO#flush`.
def flush : Nil
check_open
write_header unless @wrote_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
write_header unless @wrote_header
@flate_io.close
@io.write_bytes(@adler32, IO::ByteFormat::BigEndian)
@io.close if @sync_close
end
private def write_header
@wrote_header = true
# CMF byte: 7 for window size, 8 for compression method (deflate)
cmf = 0x78_u8
@io.write_byte cmf
dict = @dict
flg = 0_u8
if dict
flg |= 1 << 5
end
case @level
when 0..1
flg |= 0 << 6
when 2..5
flg |= 1 << 6
when 6, -1
flg |= 2 << 6
else
flg |= 3 << 6
end
# CMF and FLG, when viewed as a 16-bit unsigned integer stored
# in MSB order (CMF*256 + FLG), must be a multiple of 31
flg += 31 - (cmf.to_u16*256 + flg.to_u16).remainder(31)
@io.write_byte flg
if dict
dict_checksum = ::Digest::Adler32.checksum(dict)
@io.write_bytes(dict_checksum, IO::ByteFormat::BigEndian)
end
end
end