/
writer.cr
97 lines (81 loc) · 3.07 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
# A write-only `IO` object to compress data in the DEFLATE 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 Flate::Writer instance.
class Flate::Writer < IO
# If `#sync_close?` is `true`, closing this IO will close the underlying IO.
property? sync_close : Bool
# Creates an instance of Flate::Writer. `close` must be invoked after all data
# has written.
def initialize(@output : IO, level : Int32 = Flate::DEFAULT_COMPRESSION,
strategy : Flate::Strategy = Flate::Strategy::DEFAULT,
@sync_close : Bool = false, @dict : Bytes? = nil)
unless -1 <= level <= 9
raise ArgumentError.new("Invalid Flate level: #{level} (must be in -1..9)")
end
@buf = uninitialized UInt8[8192] # output buffer used by zlib
@stream = LibZ::ZStream.new
@stream.zalloc = LibZ::AllocFunc.new { |opaque, items, size| GC.malloc(items * size) }
@stream.zfree = LibZ::FreeFunc.new { |opaque, address| GC.free(address) }
@closed = false
ret = LibZ.deflateInit2(pointerof(@stream), level, LibZ::Z_DEFLATED, -LibZ::MAX_BITS, LibZ::DEF_MEM_LEVEL,
strategy.value, LibZ.zlibVersion, sizeof(LibZ::ZStream))
raise Flate::Error.new(ret, @stream) unless ret.ok?
end
# Creates a new writer for the given *io*, yields it to the given block,
# and closes it at its end.
def self.open(io : IO, level : Int32 = Flate::DEFAULT_COMPRESSION,
strategy : Flate::Strategy = Flate::Strategy::DEFAULT,
sync_close : Bool = false, dict : Bytes? = nil)
writer = new(io, level: level, strategy: strategy, sync_close: sync_close, dict: dict)
yield writer ensure writer.close
end
# Always raises `IO::Error` because this is a write-only `IO`.
def read(slice : Bytes)
raise "Can't read from Flate::Writer"
end
# See `IO#write`.
def write(slice : Bytes)
check_open
return if slice.empty?
@stream.avail_in = slice.size
@stream.next_in = slice
consume_output LibZ::Flush::NO_FLUSH
end
# See `IO#flush`.
def flush
return if @closed
consume_output LibZ::Flush::SYNC_FLUSH
end
# Closes this writer. Must be invoked after all data has been written.
def close
return if @closed
@closed = true
@stream.avail_in = 0
@stream.next_in = Pointer(UInt8).null
consume_output LibZ::Flush::FINISH
ret = LibZ.deflateEnd(pointerof(@stream))
raise Flate::Error.new(ret, @stream) unless ret.ok?
@output.close if @sync_close
end
# Returns `true` if this IO is closed.
def closed?
@closed
end
# :nodoc:
def inspect(io)
to_s(io)
end
private def consume_output(flush)
loop do
@stream.next_out = @buf.to_unsafe
@stream.avail_out = @buf.size.to_u32
LibZ.deflate(pointerof(@stream), flush) # no bad return value
@output.write(@buf.to_slice[0, @buf.size - @stream.avail_out])
break if @stream.avail_out != 0
end
end
end