mirror of
https://gitee.com/johng/gf.git
synced 2024-12-02 04:07:47 +08:00
184 lines
5.1 KiB
Go
184 lines
5.1 KiB
Go
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
|
//
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
// If a copy of the MIT was not distributed with this file,
|
|
// You can obtain one at https://github.com/gogf/gf.
|
|
|
|
package gres
|
|
|
|
import (
|
|
"archive/zip"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gogf/gf/v2/errors/gerror"
|
|
"github.com/gogf/gf/v2/internal/fileinfo"
|
|
"github.com/gogf/gf/v2/os/gfile"
|
|
"github.com/gogf/gf/v2/text/gregex"
|
|
)
|
|
|
|
// ZipPathWriter compresses `paths` to `writer` using zip compressing algorithm.
|
|
// The unnecessary parameter `prefix` indicates the path prefix for zip file.
|
|
//
|
|
// Note that the parameter `paths` can be either a directory or a file, which
|
|
// supports multiple paths join with ','.
|
|
func zipPathWriter(paths string, writer io.Writer, option ...Option) error {
|
|
zipWriter := zip.NewWriter(writer)
|
|
defer zipWriter.Close()
|
|
for _, path := range strings.Split(paths, ",") {
|
|
path = strings.TrimSpace(path)
|
|
if err := doZipPathWriter(path, zipWriter, option...); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// doZipPathWriter compresses the file of given `path` and writes the content to `zipWriter`.
|
|
// The parameter `exclude` specifies the exclusive file path that is not compressed to `zipWriter`,
|
|
// commonly the destination zip file path.
|
|
// The unnecessary parameter `prefix` indicates the path prefix for zip file.
|
|
func doZipPathWriter(srcPath string, zipWriter *zip.Writer, option ...Option) error {
|
|
var (
|
|
err error
|
|
files []string
|
|
usedOption Option
|
|
absolutePath string
|
|
)
|
|
if len(option) > 0 {
|
|
usedOption = option[0]
|
|
}
|
|
absolutePath, err = gfile.Search(srcPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if gfile.IsDir(absolutePath) {
|
|
files, err = gfile.ScanDir(absolutePath, "*", true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
files = []string{absolutePath}
|
|
}
|
|
headerPrefix := strings.TrimRight(usedOption.Prefix, `\/`)
|
|
if headerPrefix != "" && gfile.IsDir(absolutePath) {
|
|
headerPrefix += "/"
|
|
}
|
|
|
|
if headerPrefix == "" {
|
|
if usedOption.KeepPath {
|
|
// It keeps the path from file system to zip info in resource manager.
|
|
// Usually for relative path, it makes little sense for absolute path.
|
|
headerPrefix = srcPath
|
|
} else {
|
|
headerPrefix = gfile.Basename(absolutePath)
|
|
}
|
|
}
|
|
headerPrefix = strings.ReplaceAll(headerPrefix, `//`, `/`)
|
|
for _, file := range files {
|
|
// It here calculates the file name prefix, especially packing the directory.
|
|
// Eg:
|
|
// path: dir1
|
|
// file: dir1/dir2/file
|
|
// file[len(absolutePath):] => /dir2/file
|
|
// gfile.Dir(subFilePath) => /dir2
|
|
var subFilePath string
|
|
// Normal handling: remove the `absolutePath`(source directory path) for file.
|
|
subFilePath = file[len(absolutePath):]
|
|
if subFilePath != "" {
|
|
subFilePath = gfile.Dir(subFilePath)
|
|
}
|
|
if err = zipFile(file, headerPrefix+subFilePath, zipWriter); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
// Add all directories to zip archive.
|
|
if headerPrefix != "" {
|
|
var (
|
|
name string
|
|
tmpPath = headerPrefix
|
|
)
|
|
for {
|
|
name = strings.ReplaceAll(gfile.Basename(tmpPath), `\`, `/`)
|
|
err = zipFileVirtual(fileinfo.New(name, 0, os.ModeDir|os.ModePerm, time.Now()), tmpPath, zipWriter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if tmpPath == `/` || !strings.Contains(tmpPath, `/`) {
|
|
break
|
|
}
|
|
tmpPath = gfile.Dir(tmpPath)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// zipFile compresses the file of given `path` and writes the content to `zw`.
|
|
// The parameter `prefix` indicates the path prefix for zip file.
|
|
func zipFile(path string, prefix string, zw *zip.Writer) error {
|
|
prefix = strings.ReplaceAll(prefix, `//`, `/`)
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
err = gerror.Wrapf(err, `os.Open failed for path "%s"`, path)
|
|
return nil
|
|
}
|
|
defer file.Close()
|
|
|
|
info, err := file.Stat()
|
|
if err != nil {
|
|
err = gerror.Wrapf(err, `read file stat failed for path "%s"`, path)
|
|
return err
|
|
}
|
|
|
|
header, err := createFileHeader(info, prefix)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !info.IsDir() {
|
|
// Default compression level.
|
|
header.Method = zip.Deflate
|
|
}
|
|
// Zip header containing the info of a zip file.
|
|
writer, err := zw.CreateHeader(header)
|
|
if err != nil {
|
|
err = gerror.Wrapf(err, `create zip header failed for %#v`, header)
|
|
return err
|
|
}
|
|
if !info.IsDir() {
|
|
if _, err = io.Copy(writer, file); err != nil {
|
|
err = gerror.Wrapf(err, `io.Copy failed for file "%s"`, path)
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func zipFileVirtual(info os.FileInfo, path string, zw *zip.Writer) error {
|
|
header, err := createFileHeader(info, "")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
header.Name = path
|
|
if _, err = zw.CreateHeader(header); err != nil {
|
|
err = gerror.Wrapf(err, `create zip header failed for %#v`, header)
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func createFileHeader(info os.FileInfo, prefix string) (*zip.FileHeader, error) {
|
|
header, err := zip.FileInfoHeader(info)
|
|
if err != nil {
|
|
err = gerror.Wrapf(err, `create file header failed for name "%s"`, info.Name())
|
|
return nil, err
|
|
}
|
|
if len(prefix) > 0 {
|
|
header.Name = prefix + `/` + header.Name
|
|
header.Name = strings.ReplaceAll(header.Name, `\`, `/`)
|
|
header.Name, _ = gregex.ReplaceString(`/{2,}`, `/`, header.Name)
|
|
}
|
|
return header, nil
|
|
}
|