archiver/archive.go

195 lines
5.1 KiB
Go

package archiver
import (
"archive/tar"
"bytes"
"compress/gzip"
"embed"
"errors"
"github.com/go-git/go-billy/v5"
"io/fs"
"os"
"regexp"
"sync"
)
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
mu sync.Mutex
}
// 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) {
a.mu.Lock()
defer a.mu.Unlock()
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)
}
// 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)
}
// 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
}
}