github.com/gogf/gf/v2@v2.7.4/encoding/gcompress/gcompress_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 gcompress
     8  
     9  import (
    10  	"archive/zip"
    11  	"bytes"
    12  	"context"
    13  	"io"
    14  	"os"
    15  	"path/filepath"
    16  	"strings"
    17  
    18  	"github.com/gogf/gf/v2/errors/gerror"
    19  	"github.com/gogf/gf/v2/internal/intlog"
    20  	"github.com/gogf/gf/v2/os/gfile"
    21  	"github.com/gogf/gf/v2/text/gstr"
    22  )
    23  
    24  // ZipPath compresses `fileOrFolderPaths` to `dstFilePath` using zip compressing algorithm.
    25  //
    26  // The parameter `paths` can be either a directory or a file, which
    27  // supports multiple paths join with ','.
    28  // The unnecessary parameter `prefix` indicates the path prefix for zip file.
    29  func ZipPath(fileOrFolderPaths, dstFilePath string, prefix ...string) error {
    30  	writer, err := os.Create(dstFilePath)
    31  	if err != nil {
    32  		err = gerror.Wrapf(err, `os.Create failed for name "%s"`, dstFilePath)
    33  		return err
    34  	}
    35  	defer writer.Close()
    36  	zipWriter := zip.NewWriter(writer)
    37  	defer zipWriter.Close()
    38  	for _, path := range strings.Split(fileOrFolderPaths, ",") {
    39  		path = strings.TrimSpace(path)
    40  		if err = doZipPathWriter(path, gfile.RealPath(dstFilePath), zipWriter, prefix...); err != nil {
    41  			return err
    42  		}
    43  	}
    44  	return nil
    45  }
    46  
    47  // ZipPathWriter compresses `fileOrFolderPaths` to `writer` using zip compressing algorithm.
    48  //
    49  // Note that the parameter `fileOrFolderPaths` can be either a directory or a file, which
    50  // supports multiple paths join with ','.
    51  // The unnecessary parameter `prefix` indicates the path prefix for zip file.
    52  func ZipPathWriter(fileOrFolderPaths string, writer io.Writer, prefix ...string) error {
    53  	zipWriter := zip.NewWriter(writer)
    54  	defer zipWriter.Close()
    55  	for _, path := range strings.Split(fileOrFolderPaths, ",") {
    56  		path = strings.TrimSpace(path)
    57  		if err := doZipPathWriter(path, "", zipWriter, prefix...); err != nil {
    58  			return err
    59  		}
    60  	}
    61  	return nil
    62  }
    63  
    64  // ZipPathContent compresses `fileOrFolderPaths` to []byte using zip compressing algorithm.
    65  //
    66  // Note that the parameter `fileOrFolderPaths` can be either a directory or a file, which
    67  // supports multiple paths join with ','.
    68  // The unnecessary parameter `prefix` indicates the path prefix for zip file.
    69  func ZipPathContent(fileOrFolderPaths string, prefix ...string) ([]byte, error) {
    70  	var (
    71  		err    error
    72  		buffer = bytes.NewBuffer(nil)
    73  	)
    74  	if err = ZipPathWriter(fileOrFolderPaths, buffer, prefix...); err != nil {
    75  		return nil, err
    76  	}
    77  	return buffer.Bytes(), nil
    78  }
    79  
    80  // doZipPathWriter compresses given `fileOrFolderPaths` and writes the content to `zipWriter`.
    81  //
    82  // The parameter `fileOrFolderPath` can be either a single file or folder path.
    83  // The parameter `exclude` specifies the exclusive file path that is not compressed to `zipWriter`,
    84  // commonly the destination zip file path.
    85  // The unnecessary parameter `prefix` indicates the path prefix for zip file.
    86  func doZipPathWriter(fileOrFolderPath string, exclude string, zipWriter *zip.Writer, prefix ...string) error {
    87  	var (
    88  		err   error
    89  		files []string
    90  	)
    91  	fileOrFolderPath, err = gfile.Search(fileOrFolderPath)
    92  	if err != nil {
    93  		return err
    94  	}
    95  	if gfile.IsDir(fileOrFolderPath) {
    96  		files, err = gfile.ScanDir(fileOrFolderPath, "*", true)
    97  		if err != nil {
    98  			return err
    99  		}
   100  	} else {
   101  		files = []string{fileOrFolderPath}
   102  	}
   103  	headerPrefix := ""
   104  	if len(prefix) > 0 && prefix[0] != "" {
   105  		headerPrefix = prefix[0]
   106  	}
   107  	headerPrefix = strings.TrimRight(headerPrefix, "\\/")
   108  	if gfile.IsDir(fileOrFolderPath) {
   109  		if len(headerPrefix) > 0 {
   110  			headerPrefix += "/"
   111  		} else {
   112  			headerPrefix = gfile.Basename(fileOrFolderPath)
   113  		}
   114  	}
   115  	headerPrefix = strings.ReplaceAll(headerPrefix, "//", "/")
   116  	for _, file := range files {
   117  		if exclude == file {
   118  			intlog.Printf(context.TODO(), `exclude file path: %s`, file)
   119  			continue
   120  		}
   121  		dir := gfile.Dir(file[len(fileOrFolderPath):])
   122  		if dir == "." {
   123  			dir = ""
   124  		}
   125  		if err = zipFile(file, headerPrefix+dir, zipWriter); err != nil {
   126  			return err
   127  		}
   128  	}
   129  	return nil
   130  }
   131  
   132  // UnZipFile decompresses `archive` to `dstFolderPath` using zip compressing algorithm.
   133  //
   134  // The parameter `dstFolderPath` should be a directory.
   135  // The optional parameter `zippedPrefix` specifies the unzipped path of `zippedFilePath`,
   136  // which can be used to specify part of the archive file to unzip.
   137  func UnZipFile(zippedFilePath, dstFolderPath string, zippedPrefix ...string) error {
   138  	readerCloser, err := zip.OpenReader(zippedFilePath)
   139  	if err != nil {
   140  		err = gerror.Wrapf(err, `zip.OpenReader failed for name "%s"`, dstFolderPath)
   141  		return err
   142  	}
   143  	defer readerCloser.Close()
   144  	return unZipFileWithReader(&readerCloser.Reader, dstFolderPath, zippedPrefix...)
   145  }
   146  
   147  // UnZipContent decompresses `zippedContent` to `dstFolderPath` using zip compressing algorithm.
   148  //
   149  // The parameter `dstFolderPath` should be a directory.
   150  // The parameter `zippedPrefix` specifies the unzipped path of `zippedContent`,
   151  // which can be used to specify part of the archive file to unzip.
   152  func UnZipContent(zippedContent []byte, dstFolderPath string, zippedPrefix ...string) error {
   153  	reader, err := zip.NewReader(bytes.NewReader(zippedContent), int64(len(zippedContent)))
   154  	if err != nil {
   155  		err = gerror.Wrapf(err, `zip.NewReader failed`)
   156  		return err
   157  	}
   158  	return unZipFileWithReader(reader, dstFolderPath, zippedPrefix...)
   159  }
   160  
   161  func unZipFileWithReader(reader *zip.Reader, dstFolderPath string, zippedPrefix ...string) error {
   162  	prefix := ""
   163  	if len(zippedPrefix) > 0 {
   164  		prefix = gstr.Replace(zippedPrefix[0], `\`, `/`)
   165  	}
   166  	if err := os.MkdirAll(dstFolderPath, 0755); err != nil {
   167  		return err
   168  	}
   169  	var (
   170  		name    string
   171  		dstPath string
   172  		dstDir  string
   173  	)
   174  	for _, file := range reader.File {
   175  		name = gstr.Replace(file.Name, `\`, `/`)
   176  		name = gstr.Trim(name, "/")
   177  		if prefix != "" {
   178  			if !strings.HasPrefix(name, prefix) {
   179  				continue
   180  			}
   181  			name = name[len(prefix):]
   182  		}
   183  		dstPath = filepath.Join(dstFolderPath, name)
   184  		if file.FileInfo().IsDir() {
   185  			_ = os.MkdirAll(dstPath, file.Mode())
   186  			continue
   187  		}
   188  		dstDir = filepath.Dir(dstPath)
   189  		if len(dstDir) > 0 {
   190  			if _, err := os.Stat(dstDir); os.IsNotExist(err) {
   191  				if err = os.MkdirAll(dstDir, 0755); err != nil {
   192  					err = gerror.Wrapf(err, `os.MkdirAll failed for path "%s"`, dstDir)
   193  					return err
   194  				}
   195  			}
   196  		}
   197  		fileReader, err := file.Open()
   198  		if err != nil {
   199  			err = gerror.Wrapf(err, `file.Open failed`)
   200  			return err
   201  		}
   202  		// The fileReader is closed in function doCopyForUnZipFileWithReader.
   203  		if err = doCopyForUnZipFileWithReader(file, fileReader, dstPath); err != nil {
   204  			return err
   205  		}
   206  	}
   207  	return nil
   208  }
   209  
   210  func doCopyForUnZipFileWithReader(file *zip.File, fileReader io.ReadCloser, dstPath string) error {
   211  	defer fileReader.Close()
   212  	targetFile, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
   213  	if err != nil {
   214  		err = gerror.Wrapf(err, `os.OpenFile failed for name "%s"`, dstPath)
   215  		return err
   216  	}
   217  	defer targetFile.Close()
   218  
   219  	if _, err = io.Copy(targetFile, fileReader); err != nil {
   220  		err = gerror.Wrapf(err, `io.Copy failed from "%s" to "%s"`, file.Name, dstPath)
   221  		return err
   222  	}
   223  	return nil
   224  }
   225  
   226  // zipFile compresses the file of given `filePath` and writes the content to `zw`.
   227  // The parameter `prefix` indicates the path prefix for zip file.
   228  func zipFile(filePath string, prefix string, zw *zip.Writer) error {
   229  	file, err := os.Open(filePath)
   230  	if err != nil {
   231  		err = gerror.Wrapf(err, `os.Open failed for name "%s"`, filePath)
   232  		return err
   233  	}
   234  	defer file.Close()
   235  
   236  	info, err := file.Stat()
   237  	if err != nil {
   238  		err = gerror.Wrapf(err, `file.Stat failed for name "%s"`, filePath)
   239  		return err
   240  	}
   241  
   242  	header, err := createFileHeader(info, prefix)
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	if info.IsDir() {
   248  		header.Name += "/"
   249  	} else {
   250  		header.Method = zip.Deflate
   251  	}
   252  
   253  	writer, err := zw.CreateHeader(header)
   254  	if err != nil {
   255  		err = gerror.Wrapf(err, `zip.Writer.CreateHeader failed for header "%#v"`, header)
   256  		return err
   257  	}
   258  	if !info.IsDir() {
   259  		if _, err = io.Copy(writer, file); err != nil {
   260  			err = gerror.Wrapf(err, `io.Copy failed from "%s" to "%s"`, filePath, header.Name)
   261  			return err
   262  		}
   263  	}
   264  	return nil
   265  }
   266  
   267  func createFileHeader(info os.FileInfo, prefix string) (*zip.FileHeader, error) {
   268  	header, err := zip.FileInfoHeader(info)
   269  	if err != nil {
   270  		err = gerror.Wrapf(err, `zip.FileInfoHeader failed for info "%#v"`, info)
   271  		return nil, err
   272  	}
   273  
   274  	if len(prefix) > 0 {
   275  		prefix = strings.ReplaceAll(prefix, `\`, `/`)
   276  		prefix = strings.TrimRight(prefix, `/`)
   277  		header.Name = prefix + `/` + header.Name
   278  	}
   279  	return header, nil
   280  }