github.com/gogf/gf/v2@v2.7.4/os/gres/gres_func_zip.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
     2  //
     3  // This Source Code Form is subject to the terms of the MIT License.
     4  // If a copy of the MIT was not distributed with this file,
     5  // You can obtain one at https://github.com/gogf/gf.
     6  
     7  package gres
     8  
     9  import (
    10  	"archive/zip"
    11  	"io"
    12  	"os"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/gogf/gf/v2/errors/gerror"
    17  	"github.com/gogf/gf/v2/internal/fileinfo"
    18  	"github.com/gogf/gf/v2/os/gfile"
    19  	"github.com/gogf/gf/v2/text/gregex"
    20  )
    21  
    22  // ZipPathWriter compresses `paths` to `writer` using zip compressing algorithm.
    23  // The unnecessary parameter `prefix` indicates the path prefix for zip file.
    24  //
    25  // Note that the parameter `paths` can be either a directory or a file, which
    26  // supports multiple paths join with ','.
    27  func zipPathWriter(paths string, writer io.Writer, option ...Option) error {
    28  	zipWriter := zip.NewWriter(writer)
    29  	defer zipWriter.Close()
    30  	for _, path := range strings.Split(paths, ",") {
    31  		path = strings.TrimSpace(path)
    32  		if err := doZipPathWriter(path, zipWriter, option...); err != nil {
    33  			return err
    34  		}
    35  	}
    36  	return nil
    37  }
    38  
    39  // doZipPathWriter compresses the file of given `path` and writes the content to `zipWriter`.
    40  // The parameter `exclude` specifies the exclusive file path that is not compressed to `zipWriter`,
    41  // commonly the destination zip file path.
    42  // The unnecessary parameter `prefix` indicates the path prefix for zip file.
    43  func doZipPathWriter(srcPath string, zipWriter *zip.Writer, option ...Option) error {
    44  	var (
    45  		err          error
    46  		files        []string
    47  		usedOption   Option
    48  		absolutePath string
    49  	)
    50  	if len(option) > 0 {
    51  		usedOption = option[0]
    52  	}
    53  	absolutePath, err = gfile.Search(srcPath)
    54  	if err != nil {
    55  		return err
    56  	}
    57  	if gfile.IsDir(absolutePath) {
    58  		files, err = gfile.ScanDir(absolutePath, "*", true)
    59  		if err != nil {
    60  			return err
    61  		}
    62  	} else {
    63  		files = []string{absolutePath}
    64  	}
    65  	headerPrefix := usedOption.Prefix
    66  	if !(headerPrefix == "/") {
    67  		headerPrefix = strings.TrimRight(headerPrefix, `\/`)
    68  	}
    69  	if headerPrefix != "" && gfile.IsDir(absolutePath) {
    70  		headerPrefix += "/"
    71  	}
    72  
    73  	if headerPrefix == "" {
    74  		if usedOption.KeepPath {
    75  			// It keeps the path from file system to zip info in resource manager.
    76  			// Usually for relative path, it makes little sense for absolute path.
    77  			headerPrefix = srcPath
    78  		} else {
    79  			headerPrefix = gfile.Basename(absolutePath)
    80  		}
    81  	}
    82  	headerPrefix = strings.ReplaceAll(headerPrefix, `//`, `/`)
    83  	for _, file := range files {
    84  		// It here calculates the file name prefix, especially packing the directory.
    85  		// Eg:
    86  		// path: dir1
    87  		// file: dir1/dir2/file
    88  		// file[len(absolutePath):] => /dir2/file
    89  		// gfile.Dir(subFilePath)   => /dir2
    90  		var subFilePath string
    91  		// Normal handling: remove the `absolutePath`(source directory path) for file.
    92  		subFilePath = file[len(absolutePath):]
    93  		if subFilePath != "" {
    94  			subFilePath = gfile.Dir(subFilePath)
    95  		}
    96  		if err = zipFile(file, headerPrefix+subFilePath, zipWriter); err != nil {
    97  			return err
    98  		}
    99  	}
   100  	// Add all directories to zip archive.
   101  	if headerPrefix != "" {
   102  		var (
   103  			name    string
   104  			tmpPath = headerPrefix
   105  		)
   106  		for {
   107  			name = strings.ReplaceAll(gfile.Basename(tmpPath), `\`, `/`)
   108  			err = zipFileVirtual(fileinfo.New(name, 0, os.ModeDir|os.ModePerm, time.Now()), tmpPath, zipWriter)
   109  			if err != nil {
   110  				return err
   111  			}
   112  			if tmpPath == `/` || !strings.Contains(tmpPath, `/`) {
   113  				break
   114  			}
   115  			tmpPath = gfile.Dir(tmpPath)
   116  		}
   117  	}
   118  	return nil
   119  }
   120  
   121  // zipFile compresses the file of given `path` and writes the content to `zw`.
   122  // The parameter `prefix` indicates the path prefix for zip file.
   123  func zipFile(path string, prefix string, zw *zip.Writer) error {
   124  	prefix = strings.ReplaceAll(prefix, `//`, `/`)
   125  	file, err := os.Open(path)
   126  	if err != nil {
   127  		err = gerror.Wrapf(err, `os.Open failed for path "%s"`, path)
   128  		return nil
   129  	}
   130  	defer file.Close()
   131  
   132  	info, err := file.Stat()
   133  	if err != nil {
   134  		err = gerror.Wrapf(err, `read file stat failed for path "%s"`, path)
   135  		return err
   136  	}
   137  
   138  	header, err := createFileHeader(info, prefix)
   139  	if err != nil {
   140  		return err
   141  	}
   142  	if !info.IsDir() {
   143  		// Default compression level.
   144  		header.Method = zip.Deflate
   145  	}
   146  	// Zip header containing the info of a zip file.
   147  	writer, err := zw.CreateHeader(header)
   148  	if err != nil {
   149  		err = gerror.Wrapf(err, `create zip header failed for %#v`, header)
   150  		return err
   151  	}
   152  	if !info.IsDir() {
   153  		if _, err = io.Copy(writer, file); err != nil {
   154  			err = gerror.Wrapf(err, `io.Copy failed for file "%s"`, path)
   155  			return err
   156  		}
   157  	}
   158  	return nil
   159  }
   160  
   161  func zipFileVirtual(info os.FileInfo, path string, zw *zip.Writer) error {
   162  	header, err := createFileHeader(info, "")
   163  	if err != nil {
   164  		return err
   165  	}
   166  	header.Name = path
   167  	if _, err = zw.CreateHeader(header); err != nil {
   168  		err = gerror.Wrapf(err, `create zip header failed for %#v`, header)
   169  		return err
   170  	}
   171  	return nil
   172  }
   173  
   174  func createFileHeader(info os.FileInfo, prefix string) (*zip.FileHeader, error) {
   175  	header, err := zip.FileInfoHeader(info)
   176  	if err != nil {
   177  		err = gerror.Wrapf(err, `create file header failed for name "%s"`, info.Name())
   178  		return nil, err
   179  	}
   180  	if len(prefix) > 0 {
   181  		header.Name = prefix + `/` + header.Name
   182  		header.Name = strings.ReplaceAll(header.Name, `\`, `/`)
   183  		header.Name, _ = gregex.ReplaceString(`/{2,}`, `/`, header.Name)
   184  	}
   185  	return header, nil
   186  }