Платформа ЦРНП "Мирокод" для разработки проектов
https://git.mirocod.ru
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
305 lines
7.1 KiB
305 lines
7.1 KiB
// Copyright 2014-2021 Ulrich Kunitz. All rights reserved. |
|
// Use of this source code is governed by a BSD-style |
|
// license that can be found in the LICENSE file. |
|
|
|
package lzma |
|
|
|
import ( |
|
"bytes" |
|
"errors" |
|
"io" |
|
) |
|
|
|
// Writer2Config is used to create a Writer2 using parameters. |
|
type Writer2Config struct { |
|
// The properties for the encoding. If the it is nil the value |
|
// {LC: 3, LP: 0, PB: 2} will be chosen. |
|
Properties *Properties |
|
// The capacity of the dictionary. If DictCap is zero, the value |
|
// 8 MiB will be chosen. |
|
DictCap int |
|
// Size of the lookahead buffer; value 0 indicates default size |
|
// 4096 |
|
BufSize int |
|
// Match algorithm |
|
Matcher MatchAlgorithm |
|
} |
|
|
|
// fill replaces zero values with default values. |
|
func (c *Writer2Config) fill() { |
|
if c.Properties == nil { |
|
c.Properties = &Properties{LC: 3, LP: 0, PB: 2} |
|
} |
|
if c.DictCap == 0 { |
|
c.DictCap = 8 * 1024 * 1024 |
|
} |
|
if c.BufSize == 0 { |
|
c.BufSize = 4096 |
|
} |
|
} |
|
|
|
// Verify checks the Writer2Config for correctness. Zero values will be |
|
// replaced by default values. |
|
func (c *Writer2Config) Verify() error { |
|
c.fill() |
|
var err error |
|
if c == nil { |
|
return errors.New("lzma: WriterConfig is nil") |
|
} |
|
if c.Properties == nil { |
|
return errors.New("lzma: WriterConfig has no Properties set") |
|
} |
|
if err = c.Properties.verify(); err != nil { |
|
return err |
|
} |
|
if !(MinDictCap <= c.DictCap && int64(c.DictCap) <= MaxDictCap) { |
|
return errors.New("lzma: dictionary capacity is out of range") |
|
} |
|
if !(maxMatchLen <= c.BufSize) { |
|
return errors.New("lzma: lookahead buffer size too small") |
|
} |
|
if c.Properties.LC+c.Properties.LP > 4 { |
|
return errors.New("lzma: sum of lc and lp exceeds 4") |
|
} |
|
if err = c.Matcher.verify(); err != nil { |
|
return err |
|
} |
|
return nil |
|
} |
|
|
|
// Writer2 supports the creation of an LZMA2 stream. But note that |
|
// written data is buffered, so call Flush or Close to write data to the |
|
// underlying writer. The Close method writes the end-of-stream marker |
|
// to the stream. So you may be able to concatenate the output of two |
|
// writers as long the output of the first writer has only been flushed |
|
// but not closed. |
|
// |
|
// Any change to the fields Properties, DictCap must be done before the |
|
// first call to Write, Flush or Close. |
|
type Writer2 struct { |
|
w io.Writer |
|
|
|
start *state |
|
encoder *encoder |
|
|
|
cstate chunkState |
|
ctype chunkType |
|
|
|
buf bytes.Buffer |
|
lbw LimitedByteWriter |
|
} |
|
|
|
// NewWriter2 creates an LZMA2 chunk sequence writer with the default |
|
// parameters and options. |
|
func NewWriter2(lzma2 io.Writer) (w *Writer2, err error) { |
|
return Writer2Config{}.NewWriter2(lzma2) |
|
} |
|
|
|
// NewWriter2 creates a new LZMA2 writer using the given configuration. |
|
func (c Writer2Config) NewWriter2(lzma2 io.Writer) (w *Writer2, err error) { |
|
if err = c.Verify(); err != nil { |
|
return nil, err |
|
} |
|
w = &Writer2{ |
|
w: lzma2, |
|
start: newState(*c.Properties), |
|
cstate: start, |
|
ctype: start.defaultChunkType(), |
|
} |
|
w.buf.Grow(maxCompressed) |
|
w.lbw = LimitedByteWriter{BW: &w.buf, N: maxCompressed} |
|
m, err := c.Matcher.new(c.DictCap) |
|
if err != nil { |
|
return nil, err |
|
} |
|
d, err := newEncoderDict(c.DictCap, c.BufSize, m) |
|
if err != nil { |
|
return nil, err |
|
} |
|
w.encoder, err = newEncoder(&w.lbw, cloneState(w.start), d, 0) |
|
if err != nil { |
|
return nil, err |
|
} |
|
return w, nil |
|
} |
|
|
|
// written returns the number of bytes written to the current chunk |
|
func (w *Writer2) written() int { |
|
if w.encoder == nil { |
|
return 0 |
|
} |
|
return int(w.encoder.Compressed()) + w.encoder.dict.Buffered() |
|
} |
|
|
|
// errClosed indicates that the writer is closed. |
|
var errClosed = errors.New("lzma: writer closed") |
|
|
|
// Writes data to LZMA2 stream. Note that written data will be buffered. |
|
// Use Flush or Close to ensure that data is written to the underlying |
|
// writer. |
|
func (w *Writer2) Write(p []byte) (n int, err error) { |
|
if w.cstate == stop { |
|
return 0, errClosed |
|
} |
|
for n < len(p) { |
|
m := maxUncompressed - w.written() |
|
if m <= 0 { |
|
panic("lzma: maxUncompressed reached") |
|
} |
|
var q []byte |
|
if n+m < len(p) { |
|
q = p[n : n+m] |
|
} else { |
|
q = p[n:] |
|
} |
|
k, err := w.encoder.Write(q) |
|
n += k |
|
if err != nil && err != ErrLimit { |
|
return n, err |
|
} |
|
if err == ErrLimit || k == m { |
|
if err = w.flushChunk(); err != nil { |
|
return n, err |
|
} |
|
} |
|
} |
|
return n, nil |
|
} |
|
|
|
// writeUncompressedChunk writes an uncompressed chunk to the LZMA2 |
|
// stream. |
|
func (w *Writer2) writeUncompressedChunk() error { |
|
u := w.encoder.Compressed() |
|
if u <= 0 { |
|
return errors.New("lzma: can't write empty uncompressed chunk") |
|
} |
|
if u > maxUncompressed { |
|
panic("overrun of uncompressed data limit") |
|
} |
|
switch w.ctype { |
|
case cLRND: |
|
w.ctype = cUD |
|
default: |
|
w.ctype = cU |
|
} |
|
w.encoder.state = w.start |
|
|
|
header := chunkHeader{ |
|
ctype: w.ctype, |
|
uncompressed: uint32(u - 1), |
|
} |
|
hdata, err := header.MarshalBinary() |
|
if err != nil { |
|
return err |
|
} |
|
if _, err = w.w.Write(hdata); err != nil { |
|
return err |
|
} |
|
_, err = w.encoder.dict.CopyN(w.w, int(u)) |
|
return err |
|
} |
|
|
|
// writeCompressedChunk writes a compressed chunk to the underlying |
|
// writer. |
|
func (w *Writer2) writeCompressedChunk() error { |
|
if w.ctype == cU || w.ctype == cUD { |
|
panic("chunk type uncompressed") |
|
} |
|
|
|
u := w.encoder.Compressed() |
|
if u <= 0 { |
|
return errors.New("writeCompressedChunk: empty chunk") |
|
} |
|
if u > maxUncompressed { |
|
panic("overrun of uncompressed data limit") |
|
} |
|
c := w.buf.Len() |
|
if c <= 0 { |
|
panic("no compressed data") |
|
} |
|
if c > maxCompressed { |
|
panic("overrun of compressed data limit") |
|
} |
|
header := chunkHeader{ |
|
ctype: w.ctype, |
|
uncompressed: uint32(u - 1), |
|
compressed: uint16(c - 1), |
|
props: w.encoder.state.Properties, |
|
} |
|
hdata, err := header.MarshalBinary() |
|
if err != nil { |
|
return err |
|
} |
|
if _, err = w.w.Write(hdata); err != nil { |
|
return err |
|
} |
|
_, err = io.Copy(w.w, &w.buf) |
|
return err |
|
} |
|
|
|
// writes a single chunk to the underlying writer. |
|
func (w *Writer2) writeChunk() error { |
|
u := int(uncompressedHeaderLen + w.encoder.Compressed()) |
|
c := headerLen(w.ctype) + w.buf.Len() |
|
if u < c { |
|
return w.writeUncompressedChunk() |
|
} |
|
return w.writeCompressedChunk() |
|
} |
|
|
|
// flushChunk terminates the current chunk. The encoder will be reset |
|
// to support the next chunk. |
|
func (w *Writer2) flushChunk() error { |
|
if w.written() == 0 { |
|
return nil |
|
} |
|
var err error |
|
if err = w.encoder.Close(); err != nil { |
|
return err |
|
} |
|
if err = w.writeChunk(); err != nil { |
|
return err |
|
} |
|
w.buf.Reset() |
|
w.lbw.N = maxCompressed |
|
if err = w.encoder.Reopen(&w.lbw); err != nil { |
|
return err |
|
} |
|
if err = w.cstate.next(w.ctype); err != nil { |
|
return err |
|
} |
|
w.ctype = w.cstate.defaultChunkType() |
|
w.start = cloneState(w.encoder.state) |
|
return nil |
|
} |
|
|
|
// Flush writes all buffered data out to the underlying stream. This |
|
// could result in multiple chunks to be created. |
|
func (w *Writer2) Flush() error { |
|
if w.cstate == stop { |
|
return errClosed |
|
} |
|
for w.written() > 0 { |
|
if err := w.flushChunk(); err != nil { |
|
return err |
|
} |
|
} |
|
return nil |
|
} |
|
|
|
// Close terminates the LZMA2 stream with an EOS chunk. |
|
func (w *Writer2) Close() error { |
|
if w.cstate == stop { |
|
return errClosed |
|
} |
|
if err := w.Flush(); err != nil { |
|
return nil |
|
} |
|
// write zero byte EOS chunk |
|
_, err := w.w.Write([]byte{0}) |
|
if err != nil { |
|
return err |
|
} |
|
w.cstate = stop |
|
return nil |
|
}
|
|
|