0
Fork 0
mirror of https://codeberg.org/forgejo/forgejo.git synced 2024-12-28 18:34:10 -05:00
forgejo/vendor/github.com/Unknwon/cae/zip/write.go
2016-11-04 08:43:11 +01:00

364 lines
8.5 KiB
Go

// Copyright 2013 Unknown
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
package zip
import (
"archive/zip"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"github.com/Unknwon/cae"
)
// Switcher of printing trace information when pack and extract.
var Verbose = true
// extractFile extracts zip.File to file system.
func extractFile(f *zip.File, destPath string) error {
filePath := path.Join(destPath, f.Name)
os.MkdirAll(path.Dir(filePath), os.ModePerm)
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
fw, err := os.Create(filePath)
if err != nil {
return err
}
defer fw.Close()
if _, err = io.Copy(fw, rc); err != nil {
return err
}
// Skip symbolic links.
if f.FileInfo().Mode()&os.ModeSymlink != 0 {
return nil
}
// Set back file information.
if err = os.Chtimes(filePath, f.ModTime(), f.ModTime()); err != nil {
return err
}
return os.Chmod(filePath, f.FileInfo().Mode())
}
var defaultExtractFunc = func(fullName string, fi os.FileInfo) error {
if !Verbose {
return nil
}
fmt.Println("Extracting file..." + fullName)
return nil
}
// ExtractToFunc extracts the whole archive or the given files to the
// specified destination.
// It accepts a function as a middleware for custom operations.
func (z *ZipArchive) ExtractToFunc(destPath string, fn cae.HookFunc, entries ...string) (err error) {
destPath = strings.Replace(destPath, "\\", "/", -1)
isHasEntry := len(entries) > 0
if Verbose {
fmt.Println("Unzipping " + z.FileName + "...")
}
os.MkdirAll(destPath, os.ModePerm)
for _, f := range z.File {
f.Name = strings.Replace(f.Name, "\\", "/", -1)
// Directory.
if strings.HasSuffix(f.Name, "/") {
if isHasEntry {
if cae.IsEntry(f.Name, entries) {
if err = fn(f.Name, f.FileInfo()); err != nil {
continue
}
os.MkdirAll(path.Join(destPath, f.Name), os.ModePerm)
}
continue
}
if err = fn(f.Name, f.FileInfo()); err != nil {
continue
}
os.MkdirAll(path.Join(destPath, f.Name), os.ModePerm)
continue
}
// File.
if isHasEntry {
if cae.IsEntry(f.Name, entries) {
if err = fn(f.Name, f.FileInfo()); err != nil {
continue
}
err = extractFile(f, destPath)
}
} else {
if err = fn(f.Name, f.FileInfo()); err != nil {
continue
}
err = extractFile(f, destPath)
}
if err != nil {
return err
}
}
return nil
}
// ExtractToFunc extracts the whole archive or the given files to the
// specified destination.
// It accepts a function as a middleware for custom operations.
func ExtractToFunc(srcPath, destPath string, fn cae.HookFunc, entries ...string) (err error) {
z, err := Open(srcPath)
if err != nil {
return err
}
defer z.Close()
return z.ExtractToFunc(destPath, fn, entries...)
}
// ExtractTo extracts the whole archive or the given files to the
// specified destination.
// Call Flush() to apply changes before this.
func (z *ZipArchive) ExtractTo(destPath string, entries ...string) (err error) {
return z.ExtractToFunc(destPath, defaultExtractFunc, entries...)
}
// ExtractTo extracts given archive or the given files to the
// specified destination.
func ExtractTo(srcPath, destPath string, entries ...string) (err error) {
return ExtractToFunc(srcPath, destPath, defaultExtractFunc, entries...)
}
// extractFile extracts file from ZipArchive to file system.
func (z *ZipArchive) extractFile(f *File) error {
if !z.isHasWriter {
for _, zf := range z.ReadCloser.File {
if f.Name == zf.Name {
return extractFile(zf, path.Dir(f.tmpPath))
}
}
}
return cae.Copy(f.tmpPath, f.absPath)
}
// Flush saves changes to original zip file if any.
func (z *ZipArchive) Flush() error {
if !z.isHasChanged || (z.ReadCloser == nil && !z.isHasWriter) {
return nil
}
// Extract to tmp path and pack back.
tmpPath := path.Join(os.TempDir(), "cae", path.Base(z.FileName))
os.RemoveAll(tmpPath)
defer os.RemoveAll(tmpPath)
for _, f := range z.files {
if strings.HasSuffix(f.Name, "/") {
os.MkdirAll(path.Join(tmpPath, f.Name), os.ModePerm)
continue
}
// Relative path inside zip temporary changed.
f.tmpPath = path.Join(tmpPath, f.Name)
if err := z.extractFile(f); err != nil {
return err
}
}
if z.isHasWriter {
return packToWriter(tmpPath, z.writer, defaultPackFunc, true)
}
if err := PackTo(tmpPath, z.FileName); err != nil {
return err
}
return z.Open(z.FileName, os.O_RDWR|os.O_TRUNC, z.Permission)
}
// packFile packs a file or directory to zip.Writer.
func packFile(srcFile string, recPath string, zw *zip.Writer, fi os.FileInfo) error {
if fi.IsDir() {
fh, err := zip.FileInfoHeader(fi)
if err != nil {
return err
}
fh.Name = recPath + "/"
if _, err = zw.CreateHeader(fh); err != nil {
return err
}
} else {
fh, err := zip.FileInfoHeader(fi)
if err != nil {
return err
}
fh.Name = recPath
fh.Method = zip.Deflate
fw, err := zw.CreateHeader(fh)
if err != nil {
return err
}
if fi.Mode()&os.ModeSymlink != 0 {
target, err := os.Readlink(srcFile)
if err != nil {
return err
}
if _, err = fw.Write([]byte(target)); err != nil {
return err
}
} else {
f, err := os.Open(srcFile)
if err != nil {
return err
}
defer f.Close()
if _, err = io.Copy(fw, f); err != nil {
return err
}
}
}
return nil
}
// packDir packs a directory and its subdirectories and files
// recursively to zip.Writer.
func packDir(srcPath string, recPath string, zw *zip.Writer, fn cae.HookFunc) error {
dir, err := os.Open(srcPath)
if err != nil {
return err
}
defer dir.Close()
fis, err := dir.Readdir(0)
if err != nil {
return err
}
for _, fi := range fis {
if cae.IsFilter(fi.Name()) {
continue
}
curPath := srcPath + "/" + fi.Name()
tmpRecPath := filepath.Join(recPath, fi.Name())
if err = fn(curPath, fi); err != nil {
continue
}
if fi.IsDir() {
if err = packFile(srcPath, tmpRecPath, zw, fi); err != nil {
return err
}
err = packDir(curPath, tmpRecPath, zw, fn)
} else {
err = packFile(curPath, tmpRecPath, zw, fi)
}
if err != nil {
return err
}
}
return nil
}
// packToWriter packs given path object to io.Writer.
func packToWriter(srcPath string, w io.Writer, fn func(fullName string, fi os.FileInfo) error, includeDir bool) error {
zw := zip.NewWriter(w)
defer zw.Close()
f, err := os.Open(srcPath)
if err != nil {
return err
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
return err
}
basePath := path.Base(srcPath)
if fi.IsDir() {
if includeDir {
if err = packFile(srcPath, basePath, zw, fi); err != nil {
return err
}
} else {
basePath = ""
}
return packDir(srcPath, basePath, zw, fn)
}
return packFile(srcPath, basePath, zw, fi)
}
// packTo packs given source path object to target path.
func packTo(srcPath, destPath string, fn cae.HookFunc, includeDir bool) error {
fw, err := os.Create(destPath)
if err != nil {
return err
}
defer fw.Close()
return packToWriter(srcPath, fw, fn, includeDir)
}
// PackToFunc packs the complete archive to the specified destination.
// It accepts a function as a middleware for custom operations.
func PackToFunc(srcPath, destPath string, fn func(fullName string, fi os.FileInfo) error, includeDir ...bool) error {
isIncludeDir := false
if len(includeDir) > 0 && includeDir[0] {
isIncludeDir = true
}
return packTo(srcPath, destPath, fn, isIncludeDir)
}
var defaultPackFunc = func(fullName string, fi os.FileInfo) error {
if !Verbose {
return nil
}
if fi.IsDir() {
fmt.Printf("Adding dir...%s\n", fullName)
} else {
fmt.Printf("Adding file...%s\n", fullName)
}
return nil
}
// PackTo packs the whole archive to the specified destination.
// Call Flush() will automatically call this in the end.
func PackTo(srcPath, destPath string, includeDir ...bool) error {
return PackToFunc(srcPath, destPath, defaultPackFunc, includeDir...)
}
// Close opens or creates archive and save changes.
func (z *ZipArchive) Close() (err error) {
if err = z.Flush(); err != nil {
return err
}
if z.ReadCloser != nil {
if err = z.ReadCloser.Close(); err != nil {
return err
}
z.ReadCloser = nil
}
return nil
}