| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305 |
- // Copyright 2014-2022 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
- }
|