package bundle import ( "archive/tar" "compress/gzip" "context" "io" "os" "path/filepath" "strings" "github.com/pkg/errors" "gitlab.com/wpetit/goweb/logger" ) type TarBundle struct { archivePath string } func (b *TarBundle) File(filename string) (io.ReadCloser, os.FileInfo, error) { reader, archive, err := b.openArchive() if err != nil { return nil, nil, err } ctx := logger.With( context.Background(), logger.F("filename", filename), ) logger.Debug(ctx, "opening file") for { header, err := reader.Next() if err == io.EOF { break } if err != nil { return nil, nil, errors.Wrap(err, "could not get next tar file") } p := strings.TrimPrefix(strings.TrimSuffix(header.Name, "/"), "./") logger.Debug(ctx, "reading archive file", logger.F("path", p)) if filename != p { continue } if header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeDir { continue } rc := &archiveFile{reader, archive} return rc, header.FileInfo(), nil } return nil, nil, os.ErrNotExist } func (b *TarBundle) Dir(dirname string) ([]os.FileInfo, error) { reader, archive, err := b.openArchive() if err != nil { return nil, err } defer archive.Close() files := make([]os.FileInfo, 0) ctx := context.Background() for { header, err := reader.Next() if errors.Is(err, io.EOF) { break } if err != nil { return nil, errors.Wrap(err, "could not get next tar file") } if header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeDir { continue } if !strings.HasPrefix(header.Name, dirname) { continue } relPath, err := filepath.Rel(dirname, header.Name) if err != nil { return nil, errors.Wrap(err, "could not get relative path") } logger.Debug( ctx, "checking file prefix", logger.F("dirname", dirname), logger.F("filename", header.Name), logger.F("relpath", relPath), ) if relPath == filepath.Base(header.Name) { files = append(files, header.FileInfo()) } } return files, nil } func (b *TarBundle) openArchive() (*tar.Reader, *os.File, error) { f, err := os.Open(b.archivePath) if err != nil { return nil, nil, errors.Wrapf(err, "could not open '%v'", b.archivePath) } gzf, err := gzip.NewReader(f) if err != nil { return nil, nil, errors.Wrapf(err, "could not decompress '%v'", b.archivePath) } tr := tar.NewReader(gzf) return tr, f, nil } func NewTarBundle(archivePath string) *TarBundle { return &TarBundle{ archivePath: archivePath, } } type archiveFile struct { reader io.Reader closer io.Closer } func (f *archiveFile) Read(p []byte) (n int, err error) { return f.reader.Read(p) } func (f *archiveFile) Close() error { return f.closer.Close() }