github.com/wangyougui/gf/v2@v2.6.5/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/wangyougui/gf.
     6  
     7  package gres
     8  
     9  import (
    10  	"archive/zip"
    11  	"io"
    12  	"os"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/wangyougui/gf/v2/errors/gerror"
    17  	"github.com/wangyougui/gf/v2/internal/fileinfo"
    18  	"github.com/wangyougui/gf/v2/os/gfile"
    19  	"github.com/wangyougui/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 := strings.TrimRight(usedOption.Prefix, `\/`)
    66  	if headerPrefix != "" && gfile.IsDir(absolutePath) {
    67  		headerPrefix += "/"
    68  	}
    69  
    70  	if headerPrefix == "" {
    71  		if usedOption.KeepPath {
    72  			// It keeps the path from file system to zip info in resource manager.
    73  			// Usually for relative path, it makes little sense for absolute path.
    74  			headerPrefix = srcPath
    75  		} else {
    76  			headerPrefix = gfile.Basename(absolutePath)
    77  		}
    78  	}
    79  	headerPrefix = strings.ReplaceAll(headerPrefix, `//`, `/`)
    80  	for _, file := range files {
    81  		// It here calculates the file name prefix, especially packing the directory.
    82  		// Eg:
    83  		// path: dir1
    84  		// file: dir1/dir2/file
    85  		// file[len(absolutePath):] => /dir2/file
    86  		// gfile.Dir(subFilePath)   => /dir2
    87  		var subFilePath string
    88  		// Normal handling: remove the `absolutePath`(source directory path) for file.
    89  		subFilePath = file[len(absolutePath):]
    90  		if subFilePath != "" {
    91  			subFilePath = gfile.Dir(subFilePath)
    92  		}
    93  		if err = zipFile(file, headerPrefix+subFilePath, zipWriter); err != nil {
    94  			return err
    95  		}
    96  	}
    97  	// Add all directories to zip archive.
    98  	if headerPrefix != "" {
    99  		var (
   100  			name    string
   101  			tmpPath = headerPrefix
   102  		)
   103  		for {
   104  			name = strings.ReplaceAll(gfile.Basename(tmpPath), `\`, `/`)
   105  			err = zipFileVirtual(fileinfo.New(name, 0, os.ModeDir|os.ModePerm, time.Now()), tmpPath, zipWriter)
   106  			if err != nil {
   107  				return err
   108  			}
   109  			if tmpPath == `/` || !strings.Contains(tmpPath, `/`) {
   110  				break
   111  			}
   112  			tmpPath = gfile.Dir(tmpPath)
   113  		}
   114  	}
   115  	return nil
   116  }
   117  
   118  // zipFile compresses the file of given `path` and writes the content to `zw`.
   119  // The parameter `prefix` indicates the path prefix for zip file.
   120  func zipFile(path string, prefix string, zw *zip.Writer) error {
   121  	prefix = strings.ReplaceAll(prefix, `//`, `/`)
   122  	file, err := os.Open(path)
   123  	if err != nil {
   124  		err = gerror.Wrapf(err, `os.Open failed for path "%s"`, path)
   125  		return nil
   126  	}
   127  	defer file.Close()
   128  
   129  	info, err := file.Stat()
   130  	if err != nil {
   131  		err = gerror.Wrapf(err, `read file stat failed for path "%s"`, path)
   132  		return err
   133  	}
   134  
   135  	header, err := createFileHeader(info, prefix)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	if !info.IsDir() {
   140  		// Default compression level.
   141  		header.Method = zip.Deflate
   142  	}
   143  	// Zip header containing the info of a zip file.
   144  	writer, err := zw.CreateHeader(header)
   145  	if err != nil {
   146  		err = gerror.Wrapf(err, `create zip header failed for %#v`, header)
   147  		return err
   148  	}
   149  	if !info.IsDir() {
   150  		if _, err = io.Copy(writer, file); err != nil {
   151  			err = gerror.Wrapf(err, `io.Copy failed for file "%s"`, path)
   152  			return err
   153  		}
   154  	}
   155  	return nil
   156  }
   157  
   158  func zipFileVirtual(info os.FileInfo, path string, zw *zip.Writer) error {
   159  	header, err := createFileHeader(info, "")
   160  	if err != nil {
   161  		return err
   162  	}
   163  	header.Name = path
   164  	if _, err = zw.CreateHeader(header); err != nil {
   165  		err = gerror.Wrapf(err, `create zip header failed for %#v`, header)
   166  		return err
   167  	}
   168  	return nil
   169  }
   170  
   171  func createFileHeader(info os.FileInfo, prefix string) (*zip.FileHeader, error) {
   172  	header, err := zip.FileInfoHeader(info)
   173  	if err != nil {
   174  		err = gerror.Wrapf(err, `create file header failed for name "%s"`, info.Name())
   175  		return nil, err
   176  	}
   177  	if len(prefix) > 0 {
   178  		header.Name = prefix + `/` + header.Name
   179  		header.Name = strings.ReplaceAll(header.Name, `\`, `/`)
   180  		header.Name, _ = gregex.ReplaceString(`/{2,}`, `/`, header.Name)
   181  	}
   182  	return header, nil
   183  }