2024-11-01 17:34:12 +01:00
|
|
|
package archiver
|
|
|
|
|
|
|
|
import (
|
|
|
|
"archive/tar"
|
|
|
|
"bytes"
|
|
|
|
"compress/gzip"
|
|
|
|
"embed"
|
|
|
|
"errors"
|
|
|
|
"github.com/go-git/go-billy/v5"
|
|
|
|
"io/fs"
|
|
|
|
"os"
|
|
|
|
"regexp"
|
2024-12-03 16:59:26 +01:00
|
|
|
"sync"
|
2024-11-01 17:34:12 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type Type string
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrArchiveTypeNotSupported = errors.New("archive type not supported")
|
|
|
|
ErrArchiveFileNotFound = errors.New("archive file not found")
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
TarGzip Type = "tar.gz"
|
|
|
|
TarBzip = "tar.bz2"
|
|
|
|
TarXz = "tar.xz"
|
|
|
|
Tar = "tar"
|
|
|
|
Zip = "zip"
|
|
|
|
)
|
|
|
|
|
|
|
|
type Archive struct {
|
|
|
|
// Type of Archive
|
|
|
|
Type Type
|
|
|
|
// Path of Archive file
|
|
|
|
Path string
|
|
|
|
reader interface{}
|
|
|
|
tarReader *tar.Reader // Used for anything with .tar due to how tar.Reader cannot be reset.
|
|
|
|
files map[string]*File
|
|
|
|
archiveFile *bytes.Reader
|
2024-12-03 16:59:26 +01:00
|
|
|
mu sync.Mutex
|
2024-11-01 17:34:12 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Filesystem represents a standard interface for filesystems.
|
|
|
|
// Currently, supports fs.FS and billy.Filesystem.
|
|
|
|
type Filesystem struct {
|
|
|
|
billyFS billy.Filesystem
|
|
|
|
fs fs.FS
|
|
|
|
embed *embed.FS
|
|
|
|
file bool
|
|
|
|
path string
|
|
|
|
}
|
|
|
|
|
|
|
|
// WrapBillyFS wraps billy.Filesystem to the Filesystem interface.
|
|
|
|
// Returns Filesystem.
|
|
|
|
func WrapBillyFS(filesystem billy.Filesystem) Filesystem {
|
|
|
|
return Filesystem{
|
|
|
|
billyFS: filesystem,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WrapFS wraps fs.FS to the Filesystem interface.
|
|
|
|
// Returns Filesystem.
|
|
|
|
func WrapFS(filesystem fs.FS) Filesystem {
|
|
|
|
return Filesystem{
|
|
|
|
fs: filesystem,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// WrapPath wraps a path to the Filesystem interface.
|
|
|
|
// Returns Filesystem.
|
|
|
|
func WrapPath(path string) Filesystem {
|
|
|
|
return Filesystem{
|
|
|
|
path: path,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExtractOptions represents options for extracting an Archive
|
|
|
|
type ExtractOptions struct {
|
|
|
|
// Whether to overwrite files if they already exist.
|
|
|
|
Overwrite bool
|
|
|
|
// Folder to extract Archive to.
|
|
|
|
Folder string
|
|
|
|
// Whether to preserve the file structure in the Archive or not.
|
|
|
|
NotPreserveFileStructure bool
|
|
|
|
// Optional regex filter for specific files.
|
|
|
|
Filter *regexp.Regexp
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExtractFileOptions represents options for extracting an ArchiveFile
|
|
|
|
type ExtractFileOptions struct {
|
|
|
|
// Whether to overwrite files if they already exist.
|
|
|
|
Overwrite bool
|
|
|
|
// Perms for output file, default is the permission in the Archive.
|
|
|
|
Perms os.FileMode
|
|
|
|
// Folder to extract Archive to.
|
|
|
|
Folder string
|
|
|
|
// Whether to preserve the file structure in the Archive or not.
|
|
|
|
NotPreserveFileStructure bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// File represents a file in an archive.
|
|
|
|
type File struct {
|
|
|
|
// Name of file (e.g "test.txt")
|
|
|
|
FileName string
|
|
|
|
// Path of file in archive (e.g "folder/test.txt")
|
|
|
|
Path string
|
|
|
|
archive *Archive
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetFile gets a File from the Archive
|
|
|
|
// It takes the path of the file in the Archive as its parameter.
|
|
|
|
// The function returns ArchiveFile and an error, if any.
|
|
|
|
func (a *Archive) GetFile(path string) (*File, error) {
|
2024-12-03 16:59:26 +01:00
|
|
|
a.mu.Lock()
|
|
|
|
defer a.mu.Unlock()
|
2024-11-01 17:34:12 +01:00
|
|
|
file, ok := a.files[path]
|
|
|
|
if !ok {
|
|
|
|
return nil, ErrArchiveFileNotFound
|
|
|
|
}
|
|
|
|
|
|
|
|
return file, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// FileCount gets the total file count in the Archive.
|
|
|
|
// The function returns an integer.
|
|
|
|
func (a *Archive) FileCount() int {
|
|
|
|
return len(a.files)
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExtractBillyFS extracts the specified Archive into billy.Filesystem.
|
|
|
|
// It takes billy.Filesystem and ExtractOptions as its parameters.
|
|
|
|
// The function returns an error, if any.
|
|
|
|
func (a *Archive) ExtractBillyFS(filesystem billy.Filesystem, opts ExtractOptions) error {
|
|
|
|
return extract(WrapBillyFS(filesystem), opts, a)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract extracts the specified Archive to a specified directory.
|
|
|
|
// It takes ExtractOptions as its parameter
|
|
|
|
// The function returns an error, if any.
|
|
|
|
func (a *Archive) Extract(opts ExtractOptions) error {
|
|
|
|
return extract(Filesystem{file: true}, opts, a)
|
|
|
|
}
|
|
|
|
|
2024-12-09 02:50:43 +01:00
|
|
|
// ExtractFile extracts a specified file from an archive to a specified directory.
|
|
|
|
// It takes a file path, and ExtractFileOptions as its parameters.
|
|
|
|
// The function returns an error, if any.
|
|
|
|
func (a *Archive) ExtractFile(path string, opts ExtractFileOptions) error {
|
|
|
|
file, err := a.GetFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return file.Extract(opts)
|
|
|
|
}
|
|
|
|
|
2024-11-01 17:34:12 +01:00
|
|
|
// Close closes the Archive.
|
|
|
|
// The function returns an error, if any.
|
|
|
|
func (a *Archive) Close() error {
|
|
|
|
a.archiveFile = nil
|
|
|
|
|
|
|
|
switch a.Type {
|
|
|
|
case TarGzip:
|
|
|
|
if err := a.reader.(*gzip.Reader).Close(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ExtractBillyFS extracts the specified File in the archive into billy.Filesystem.
|
|
|
|
// It takes billy.Filesystem and ExtractFileOptions as its parameters.
|
|
|
|
// The function returns an error, if any.
|
|
|
|
func (f *File) ExtractBillyFS(filesystem billy.Filesystem, opts ExtractFileOptions) error {
|
|
|
|
return extractFile(WrapBillyFS(filesystem), opts, f)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Extract extracts the specified File in the archive to a specified directory.
|
|
|
|
// It takes ExtractFileOptions as its parameter.
|
|
|
|
// The function returns an error, if any.
|
|
|
|
func (f *File) Extract(opts ExtractFileOptions) error {
|
|
|
|
return extractFile(Filesystem{file: true}, opts, f)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read reads the specified File in the archive and returns the content.
|
|
|
|
// The function returns the content of the File, and an error, if any.
|
|
|
|
func (f *File) Read() ([]byte, error) {
|
|
|
|
switch f.archive.Type {
|
|
|
|
case Tar, TarGzip, TarBzip, TarXz:
|
|
|
|
return tarRead(f)
|
|
|
|
case Zip:
|
|
|
|
return zipRead(f)
|
|
|
|
default:
|
|
|
|
return nil, ErrArchiveTypeNotSupported
|
|
|
|
}
|
|
|
|
}
|