file/archive.go

240 lines
5.6 KiB
Go

package file
import (
"archive/tar"
"archive/zip"
"bytes"
"compress/bzip2"
"compress/gzip"
"compress/zlib"
"io"
"os"
"path/filepath"
"strings"
)
func Compress(data []byte, cType string) ([]byte, error) {
var buf bytes.Buffer
var w io.WriteCloser
switch strings.ToLower(cType) {
case "gzip", "gz":
w = gzip.NewWriter(&buf)
default:
w = zlib.NewWriter(&buf)
}
if _, err := w.Write(data); err != nil {
return nil, err
}
w.Close()
return buf.Bytes(), nil
}
func Decompress(data []byte, cType string) ([]byte, error) {
bufR := bytes.NewReader(data)
var r io.ReadCloser
var err error
switch strings.ToLower(cType) {
case "gzip", "gz":
r, err = gzip.NewReader(bufR)
default:
r, err = zlib.NewReader(bufR)
}
if err != nil {
return nil, err
}
defer r.Close()
return io.ReadAll(r)
}
func MustGzip(data []byte) []byte { b, _ := Compress(data, "gzip"); return b }
func MustGunzip(data []byte) []byte { b, _ := Decompress(data, "gzip"); return b }
func MustZip(data []byte) []byte { b, _ := Compress(data, "zlib"); return b }
func MustUnzip(data []byte) []byte { b, _ := Decompress(data, "zlib"); return b }
func Extract(srcFile, destDir string, stripRoot bool) error {
f, err := os.Open(srcFile)
if err != nil {
return err
}
defer f.Close()
lowerSrc := strings.ToLower(srcFile)
switch {
case strings.HasSuffix(lowerSrc, ".zip"):
return extractZip(srcFile, destDir, stripRoot)
case strings.HasSuffix(lowerSrc, ".tar.gz"), strings.HasSuffix(lowerSrc, ".tgz"):
gzr, err := gzip.NewReader(f)
if err != nil {
return err
}
defer gzr.Close()
return extractTar(gzr, destDir, stripRoot)
case strings.HasSuffix(lowerSrc, ".tar"):
return extractTar(f, destDir, stripRoot)
case strings.HasSuffix(lowerSrc, ".gz"):
gzr, err := gzip.NewReader(f)
if err != nil {
return err
}
defer gzr.Close()
return extractSingleFile(gzr, destDir, strings.TrimSuffix(filepath.Base(srcFile), ".gz"))
case strings.HasSuffix(lowerSrc, ".bz2"):
bzr := bzip2.NewReader(f)
return extractSingleFile(bzr, destDir, strings.TrimSuffix(filepath.Base(srcFile), ".bz2"))
default:
return extractZip(srcFile, destDir, stripRoot)
}
}
func extractTar(r io.Reader, dest string, strip bool) error {
tr := tar.NewReader(r)
for {
h, err := tr.Next()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
if err := writeEntry(dest, h.Name, h.FileInfo().Mode(), h.Typeflag == tar.TypeDir, h.Linkname, tr, strip); err != nil {
return err
}
}
}
func extractZip(src, dest string, strip bool) error {
rz, err := zip.OpenReader(src)
if err != nil {
return err
}
defer rz.Close()
for _, f := range rz.File {
rc, err := f.Open()
if err != nil {
return err
}
var linkTarget string
if f.Mode()&os.ModeSymlink != 0 {
b, _ := io.ReadAll(rc)
linkTarget = string(b)
}
err = writeEntry(dest, f.Name, f.Mode(), f.FileInfo().IsDir(), linkTarget, rc, strip)
rc.Close()
if err != nil {
return err
}
}
return nil
}
func extractSingleFile(r io.Reader, destDir, fileName string) error {
os.MkdirAll(destDir, 0755)
target := filepath.Join(destDir, fileName)
out, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, r)
return err
}
func writeEntry(destDir, name string, mode os.FileMode, isDir bool, linkPath string, r io.Reader, strip bool) error {
if strip {
parts := strings.SplitN(name, "/", 2)
if len(parts) < 2 || parts[1] == "" {
return nil
}
name = parts[1]
}
target := filepath.Join(destDir, name)
if isDir {
return os.MkdirAll(target, 0755)
}
os.MkdirAll(filepath.Dir(target), 0755)
if mode&os.ModeSymlink != 0 {
os.Remove(target)
return os.Symlink(linkPath, target)
}
out, err := os.OpenFile(target, os.O_CREATE|os.O_RDWR|os.O_TRUNC, mode)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, r)
return err
}
func Archive(srcPath, destFile string) error {
f, err := os.Create(destFile)
if err != nil {
return err
}
defer f.Close()
lower := strings.ToLower(destFile)
if strings.HasSuffix(lower, ".zip") {
zw := zip.NewWriter(f)
defer zw.Close()
return walkAndAdd(srcPath, func(relPath string, info os.FileInfo, fileReader io.Reader) error {
header, _ := zip.FileInfoHeader(info)
header.Name = relPath
if info.IsDir() {
header.Name += "/"
} else {
header.Method = zip.Deflate
}
w, err := zw.CreateHeader(header)
if err != nil || info.IsDir() {
return err
}
_, err = io.Copy(w, fileReader)
return err
})
}
gw := gzip.NewWriter(f)
defer gw.Close()
tw := tar.NewWriter(gw)
defer tw.Close()
return walkAndAdd(srcPath, func(relPath string, info os.FileInfo, fileReader io.Reader) error {
header, _ := tar.FileInfoHeader(info, "")
header.Name = relPath
if info.Mode()&os.ModeSymlink != 0 {
link, _ := os.Readlink(filepath.Join(srcPath, "..", relPath))
header.Linkname = link
}
if err := tw.WriteHeader(header); err != nil || info.IsDir() || header.Typeflag == tar.TypeSymlink {
return err
}
_, err = io.Copy(tw, fileReader)
return err
})
}
func walkAndAdd(srcPath string, addFn func(string, os.FileInfo, io.Reader) error) error {
baseDir := filepath.Dir(srcPath)
return filepath.Walk(srcPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, _ := filepath.Rel(baseDir, path)
if relPath == "." {
return nil
}
var r io.Reader
if !info.IsDir() && info.Mode()&os.ModeSymlink == 0 {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
r = f
}
return addFn(filepath.ToSlash(relPath), info, r)
})
}