-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwriter.go
147 lines (113 loc) · 2.98 KB
/
writer.go
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
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// SPDX-FileCopyrightText: 2024 Shun Sakai
//
// SPDX-License-Identifier: Apache-2.0 OR MIT
package lzip
import (
"bytes"
"encoding/binary"
"hash/crc32"
"io"
"github.com/ulikunitz/xz/lzma"
)
// Writer is an [io.WriteCloser] that can be written to retrieve a lzip-format
// compressed file from data.
type Writer struct {
w io.Writer
compressor *lzma.Writer
buf bytes.Buffer
header *header
wroteHeader bool
trailer
closed bool
}
// WriterOptions configures [Writer].
type WriterOptions struct {
// DictSize sets the dictionary size.
DictSize uint32
}
func newWriterOptions() *WriterOptions {
opt := &WriterOptions{DefaultDictSize}
return opt
}
// Verify checks if [WriterOptions] is valid.
func (o *WriterOptions) Verify() error {
switch dictSize := o.DictSize; {
case dictSize < MinDictSize:
return &DictSizeTooSmallError{dictSize}
case dictSize > MaxDictSize:
return &DictSizeTooLargeError{dictSize}
}
return nil
}
// NewWriter creates a new [Writer] writing the given writer.
//
// This uses the default parameters.
func NewWriter(w io.Writer) *Writer {
opt := newWriterOptions()
z, err := NewWriterOptions(w, opt)
if err != nil {
panic(err)
}
return z
}
// NewWriterOptions creates a new [Writer] writing the given writer.
//
// This uses the given [WriterOptions].
func NewWriterOptions(w io.Writer, opt *WriterOptions) (*Writer, error) {
if err := opt.Verify(); err != nil {
return nil, err
}
z := &Writer{w: w}
compressor, err := lzma.WriterConfig{DictCap: int(opt.DictSize)}.NewWriter(&z.buf)
if err != nil {
return nil, err
}
z.compressor = compressor
header := newHeader(opt.DictSize)
z.header = header
return z, nil
}
// Write compresses the given uncompressed data.
func (z *Writer) Write(p []byte) (int, error) {
if !z.wroteHeader {
z.wroteHeader = true
var header [headerSize]byte
copy(header[:magicSize], z.header.magic[:])
header[4] = byte(z.header.version)
header[5] = z.header.dictSize
if _, err := z.w.Write(header[:]); err != nil {
return 0, err
}
}
n, err := z.compressor.Write(p)
if err != nil {
return n, err
}
z.trailer.crc = crc32.Update(z.trailer.crc, crc32.IEEETable, p)
z.trailer.dataSize += uint64(len(p))
return n, nil
}
// Close closes the [Writer] and writing the lzip trailer. It does not close
// the underlying [io.Writer].
func (z *Writer) Close() error {
if z.closed {
return nil
}
z.closed = true
if err := z.compressor.Close(); err != nil {
return err
}
cb := z.buf.Bytes()[lzma.HeaderLen:]
if _, err := z.w.Write(cb); err != nil {
return err
}
var trailer [trailerSize]byte
binary.LittleEndian.PutUint32(trailer[:4], z.trailer.crc)
binary.LittleEndian.PutUint64(trailer[4:12], z.trailer.dataSize)
binary.LittleEndian.PutUint64(trailer[12:], headerSize+uint64(len(cb))+trailerSize)
if memberSize := binary.LittleEndian.Uint64(trailer[12:]); memberSize > MaxMemberSize {
return &MemberSizeTooLargeError{memberSize}
}
_, err := z.w.Write(trailer[:])
return err
}