summaryrefslogtreecommitdiff
path: root/src/post/asset/loader_archive.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/post/asset/loader_archive.go')
-rw-r--r--src/post/asset/loader_archive.go99
1 files changed, 99 insertions, 0 deletions
diff --git a/src/post/asset/loader_archive.go b/src/post/asset/loader_archive.go
new file mode 100644
index 0000000..1c8697f
--- /dev/null
+++ b/src/post/asset/loader_archive.go
@@ -0,0 +1,99 @@
+package asset
+
+import (
+ "bytes"
+ "compress/gzip"
+ "errors"
+ "fmt"
+ "io"
+ "io/fs"
+ "strings"
+
+ "github.com/omeid/go-tarfs"
+)
+
+type archiveLoader struct {
+ loader Loader
+}
+
+// NewArchiveLoader wraps an existing Loader in order to support extracting
+// files from within an archive file asset.
+//
+// For example, loading the path `foo.tgz/foo/bar.jpg` will call
+// `Load("foo.tgz")`, load that archive into memory, and serve the file
+// `./foo/bar.jpg` from within the archive.
+func NewArchiveLoader(loader Loader) Loader {
+ return &archiveLoader{loader: loader}
+}
+
+func (l *archiveLoader) Load(path string, into io.Writer, opts LoadOpts) error {
+
+ id, subPath, ok := strings.Cut(strings.TrimPrefix(path, "/"), "/")
+
+ if !ok {
+ return l.loader.Load(path, into, opts)
+ }
+
+ var isGzipped bool
+
+ switch {
+
+ case strings.HasSuffix(id, ".tar.gz"),
+ strings.HasSuffix(id, ".tgz"):
+ isGzipped = true
+
+ case strings.HasSuffix(id, ".tar"):
+ // ok
+
+ default:
+ // unsupported
+ return l.loader.Load(path, into, opts)
+ }
+
+ buf := new(bytes.Buffer)
+
+ if err := l.loader.Load(id, buf, opts); err != nil {
+ return fmt.Errorf("loading archive into buffer: %w", err)
+ }
+
+ var (
+ from io.Reader = buf
+ err error
+ )
+
+ if isGzipped {
+
+ if from, err = gzip.NewReader(from); err != nil {
+ return fmt.Errorf("decompressing archive asset with id %q: %w", id, err)
+ }
+ }
+
+ tarFS, err := tarfs.New(from)
+
+ if err != nil {
+ return fmt.Errorf("reading archive asset with id %q as fs: %w", id, err)
+ }
+
+ f, err := tarFS.Open(subPath)
+
+ if errors.Is(err, fs.ErrExist) {
+ return ErrNotFound
+
+ } else if err != nil {
+ return fmt.Errorf(
+ "opening path %q from archive asset with id %q as fs: %w",
+ subPath, id, err,
+ )
+ }
+
+ defer f.Close()
+
+ if _, err = io.Copy(into, f); err != nil {
+ return fmt.Errorf(
+ "reading %q from archive asset with id %q as fs: %w",
+ subPath, id, err,
+ )
+ }
+
+ return nil
+}