You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
99 lines
1.8 KiB
99 lines
1.8 KiB
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
|
|
}
|
|
|