diff options
| author | Ophestra <cat@gensokyo.uk> | 2026-06-04 18:12:03 +0900 |
|---|---|---|
| committer | Ophestra <cat@gensokyo.uk> | 2026-06-04 18:33:04 +0900 |
| commit | 76c1fb84c87ba12a22b64f7c471a92603365837f (patch) | |
| tree | de04c0a8a45a636ebe78fd06b3097e5b8493e9cc /internal/pkg/compress.go | |
| parent | 729be19af39fb73e827bdb2798ac23942f9cf9e5 (diff) | |
internal/pkg: stream decompress artifact
The tarArtifact predates FileArtifact pipelining. This migrates decompression and buffering into a standalone artifact implementation.
Signed-off-by: Ophestra <cat@gensokyo.uk>
Diffstat (limited to 'internal/pkg/compress.go')
| -rw-r--r-- | internal/pkg/compress.go | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/internal/pkg/compress.go b/internal/pkg/compress.go new file mode 100644 index 00000000..310fe065 --- /dev/null +++ b/internal/pkg/compress.go @@ -0,0 +1,119 @@ +package pkg + +import ( + "compress/bzip2" + "compress/gzip" + "fmt" + "io" + "os" +) + +const ( + // Gzip denotes a stream compressed via [gzip]. + Gzip = iota + // Bzip2 denotes a stream compressed via [bzip2]. + Bzip2 +) + +// A decompressArtifact is a [FileArtifact] decompressing a backing +// [FileArtifact] stream. +type decompressArtifact struct { + // Caller-supplied backing stream. + f Artifact + // Compression on top of the stream. + compress uint32 +} + +var _ FileArtifact = new(decompressArtifact) + +// decompressArtifactNamed embeds decompressArtifact for a [fmt.Stringer] stream. +type decompressArtifactNamed struct { + decompressArtifact + // Copied from decompressArtifact.f. + name string +} + +var _ fmt.Stringer = new(decompressArtifactNamed) + +// NewDecompress returns a [FileArtifact] decompressing the supplied [Artifact]. +func NewDecompress(a Artifact, compress uint32) Artifact { + da := decompressArtifact{a, compress} + if s, ok := a.(fmt.Stringer); ok { + if name := s.String(); name != "" { + return &decompressArtifactNamed{da, name} + } + } + return &da +} + +// String returns the name of the underlying [Artifact] suffixed with decompress. +func (a *decompressArtifactNamed) String() string { return a.name + "-decompress" } + +// Kind returns the hardcoded [Kind] constant. +func (a *decompressArtifact) Kind() Kind { return KindDecompress } + +// Params writes value of compression enum. +func (a *decompressArtifact) Params(ctx *IContext) { ctx.WriteUint32(a.compress) } + +func init() { + register(KindDecompress, func(r *IRReader) Artifact { + a := NewDecompress(r.Next(), r.ReadUint32()) + if _, ok := r.Finalise(); ok { + panic(ErrUnexpectedChecksum) + } + return a + }) +} + +// Dependencies returns a slice containing the backing file. +func (a *decompressArtifact) Dependencies() []Artifact { + return []Artifact{a.f} +} + +// IsExclusive returns false: decompressor is fully sequential. +func (a *decompressArtifact) IsExclusive() bool { return false } + +// compoundCloser is an [io.ReadCloser] with an additional [io.Closer] attached. +type compoundCloser struct { + io.ReadCloser + c io.Closer +} + +// Close closes [io.ReadCloser] and the additional [io.Closer]. It returns the +// non-nil error returned by the underlying [io.ReadCloser], otherwise it +// returns the error returned by the additional [io.Closer]. +func (c compoundCloser) Close() error { + err := c.ReadCloser.Close() + if _err := c.c.Close(); err == nil { + err = _err + } + return err +} + +// Cure returns a decompressor [io.ReadCloser]. +func (a *decompressArtifact) Cure(r *RContext) (io.ReadCloser, error) { + sr, err := r.Open(a.f) + if err != nil { + return nil, err + } + br := r.cache.getReaderRC(sr) + + var dr io.ReadCloser + switch a.compress { + case Gzip: + if dr, err = gzip.NewReader(br); err != nil { + _ = br.Close() + return nil, err + } + return compoundCloser{dr, br}, nil + + case Bzip2: + return struct { + io.Reader + io.Closer + }{bzip2.NewReader(br), br}, nil + + default: + return nil, os.ErrInvalid + } +} |
