aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/pkg/compress.go
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-06-04 18:12:03 +0900
committerOphestra <cat@gensokyo.uk>2026-06-04 18:33:04 +0900
commit76c1fb84c87ba12a22b64f7c471a92603365837f (patch)
treede04c0a8a45a636ebe78fd06b3097e5b8493e9cc /internal/pkg/compress.go
parent729be19af39fb73e827bdb2798ac23942f9cf9e5 (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.go119
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
+ }
+}