| // Copyright 2019 The Wuffs Authors. |
| // |
| // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or |
| // https://www.apache.org/licenses/LICENSE-2.0> or the MIT license |
| // <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your |
| // option. This file may not be copied, modified, or distributed |
| // except according to those terms. |
| // |
| // SPDX-License-Identifier: Apache-2.0 OR MIT |
| |
| // ---------------- |
| |
| // Package raclz4 provides access to RAC (Random Access Compression) files with |
| // the LZ4 compression codec. |
| // |
| // The RAC specification is at |
| // https://github.com/google/wuffs/blob/main/doc/spec/rac-spec.md |
| package raclz4 |
| |
| import ( |
| "bytes" |
| "errors" |
| "io" |
| |
| "github.com/google/wuffs/lib/cgolz4" |
| "github.com/google/wuffs/lib/compression" |
| "github.com/google/wuffs/lib/rac" |
| ) |
| |
| var ( |
| errCannotCut = errors.New("raclz4: cannot cut") |
| errInvalidCodec = errors.New("raclz4: invalid codec") |
| ) |
| |
| // CodecReader specializes a rac.Reader to decode LZ4-compressed chunks. |
| type CodecReader struct { |
| cachedReader compression.Reader |
| recycler cgolz4.ReaderRecycler |
| lim io.LimitedReader |
| } |
| |
| // Close implements rac.CodecReader. |
| func (r *CodecReader) Close() error { |
| return r.recycler.Close() |
| } |
| |
| // Accepts implements rac.CodecReader. |
| func (r *CodecReader) Accepts(c rac.Codec) bool { |
| return c == rac.CodecLZ4 |
| } |
| |
| // Clone implements rac.CodecReader. |
| func (r *CodecReader) Clone() rac.CodecReader { |
| return &CodecReader{} |
| } |
| |
| // MakeDecompressor implements rac.CodecReader. |
| func (r *CodecReader) MakeDecompressor(racFile io.ReadSeeker, chunk rac.Chunk) (io.Reader, error) { |
| if _, err := racFile.Seek(chunk.CPrimary[0], io.SeekStart); err != nil { |
| return nil, err |
| } |
| r.lim.R = racFile |
| r.lim.N = chunk.CPrimary.Size() |
| |
| if r.cachedReader == nil { |
| zr := &cgolz4.Reader{} |
| r.cachedReader = zr |
| r.recycler.Bind(zr) |
| } |
| if err := r.cachedReader.Reset(&r.lim, nil); err != nil { |
| return nil, err |
| } |
| return r.cachedReader, nil |
| } |
| |
| // CodecWriter specializes a rac.Writer to encode LZ4-compressed chunks. |
| type CodecWriter struct { |
| compressed bytes.Buffer |
| cachedWriter compression.Writer |
| recycler cgolz4.WriterRecycler |
| } |
| |
| // Close implements rac.CodecWriter. |
| func (w *CodecWriter) Close() error { |
| return w.recycler.Close() |
| } |
| |
| // Clone implements rac.CodecWriter. |
| func (w *CodecWriter) Clone() rac.CodecWriter { |
| return &CodecWriter{} |
| } |
| |
| // Compress implements rac.CodecWriter. |
| func (w *CodecWriter) Compress(p []byte, q []byte, resourcesData [][]byte) ( |
| codec rac.Codec, compressed []byte, secondaryResource int, tertiaryResource int, retErr error) { |
| |
| // Compress p+q without any shared dictionary. |
| baseline, err := w.compress(p, q, nil) |
| if err != nil { |
| return 0, nil, 0, 0, err |
| } |
| return rac.CodecLZ4, baseline, rac.NoResourceUsed, rac.NoResourceUsed, nil |
| } |
| |
| func (w *CodecWriter) compress(p []byte, q []byte, dict []byte) ([]byte, error) { |
| w.compressed.Reset() |
| if w.cachedWriter == nil { |
| zw := &cgolz4.Writer{} |
| w.cachedWriter = zw |
| w.recycler.Bind(zw) |
| } |
| if err := w.cachedWriter.Reset(&w.compressed, dict, compression.LevelSmall); err != nil { |
| return nil, err |
| } |
| |
| if len(p) > 0 { |
| if _, err := w.cachedWriter.Write(p); err != nil { |
| w.cachedWriter.Close() |
| return nil, err |
| } |
| } |
| if len(q) > 0 { |
| if _, err := w.cachedWriter.Write(q); err != nil { |
| w.cachedWriter.Close() |
| return nil, err |
| } |
| } |
| |
| if err := w.cachedWriter.Close(); err != nil { |
| return nil, err |
| } |
| return w.compressed.Bytes(), nil |
| } |
| |
| // CanCut implements rac.CodecWriter. |
| func (w *CodecWriter) CanCut() bool { |
| return false |
| } |
| |
| // Cut implements rac.CodecWriter. |
| func (w *CodecWriter) Cut(codec rac.Codec, encoded []byte, maxEncodedLen int) (encodedLen int, decodedLen int, retErr error) { |
| return 0, 0, errCannotCut |
| } |
| |
| // WrapResource implements rac.CodecWriter. |
| func (w *CodecWriter) WrapResource(raw []byte) ([]byte, error) { |
| return nil, nil |
| } |