github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/Unknwon/cae/tz/tz.go (about)

     1  // Copyright 2014 Unknown
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"): you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    11  // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    12  // License for the specific language governing permissions and limitations
    13  // under the License.
    14  
    15  // Package tz enables you to transparently read or write TAR.GZ compressed archives and the files inside them.
    16  package tz
    17  
    18  import (
    19  	"archive/tar"
    20  	"errors"
    21  	"io"
    22  	"os"
    23  	"path"
    24  	"path/filepath"
    25  	"strings"
    26  
    27  	"github.com/insionng/yougam/libraries/Unknwon/cae"
    28  )
    29  
    30  // A File represents a file or directory entry in archive.
    31  type File struct {
    32  	*tar.Header
    33  	absPath string
    34  }
    35  
    36  // A TzArchive represents a file archive, compressed with Tar and Gzip.
    37  type TzArchive struct {
    38  	*ReadCloser
    39  	FileName   string
    40  	NumFiles   int
    41  	Flag       int
    42  	Permission os.FileMode
    43  
    44  	files        []*File
    45  	isHasChanged bool
    46  
    47  	// For supporting flushing to io.Writer.
    48  	writer      io.Writer
    49  	isHasWriter bool
    50  }
    51  
    52  // OpenFile is the generalized open call; most users will use Open
    53  // instead. It opens the named tar.gz file with specified flag
    54  // (O_RDONLY etc.) if applicable. If successful,
    55  // methods on the returned TzArchive can be used for I/O.
    56  // If there is an error, it will be of type *PathError.
    57  func OpenFile(fileName string, flag int, perm os.FileMode) (*TzArchive, error) {
    58  	tz := new(TzArchive)
    59  	err := tz.Open(fileName, flag, perm)
    60  	return tz, err
    61  }
    62  
    63  // Create creates the named tar.gz file, truncating
    64  // it if it already exists. If successful, methods on the returned
    65  // TzArchive can be used for I/O; the associated file descriptor has mode
    66  // O_RDWR.
    67  // If there is an error, it will be of type *PathError.
    68  func Create(fileName string) (*TzArchive, error) {
    69  	os.MkdirAll(path.Dir(fileName), os.ModePerm)
    70  	return OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
    71  }
    72  
    73  // Open opens the named tar.gz file for reading. If successful, methods on
    74  // the returned TzArchive can be used for reading; the associated file
    75  // descriptor has mode O_RDONLY.
    76  // If there is an error, it will be of type *PathError.
    77  func Open(fileName string) (*TzArchive, error) {
    78  	return OpenFile(fileName, os.O_RDONLY, 0)
    79  }
    80  
    81  // New accepts a variable that implemented interface io.Writer
    82  // for write-only purpose operations.
    83  func New(w io.Writer) *TzArchive {
    84  	return &TzArchive{
    85  		writer:      w,
    86  		isHasWriter: true,
    87  	}
    88  }
    89  
    90  // List returns a string slice of files' name in TzArchive.
    91  // Specify prefixes will be used as filters.
    92  func (tz *TzArchive) List(prefixes ...string) []string {
    93  	isHasPrefix := len(prefixes) > 0
    94  	names := make([]string, 0, tz.NumFiles)
    95  	for _, f := range tz.files {
    96  		if isHasPrefix && !cae.HasPrefix(f.Name, prefixes) {
    97  			continue
    98  		}
    99  		names = append(names, f.Name)
   100  	}
   101  	return names
   102  }
   103  
   104  // AddEmptyDir adds a raw directory entry to TzArchive,
   105  // it returns false if same directory enry already existed.
   106  func (tz *TzArchive) AddEmptyDir(dirPath string) bool {
   107  	if !strings.HasSuffix(dirPath, "/") {
   108  		dirPath += "/"
   109  	}
   110  
   111  	for _, f := range tz.files {
   112  		if dirPath == f.Name {
   113  			return false
   114  		}
   115  	}
   116  
   117  	dirPath = strings.TrimSuffix(dirPath, "/")
   118  	if strings.Contains(dirPath, "/") {
   119  		// Auto add all upper level directories.
   120  		tz.AddEmptyDir(path.Dir(dirPath))
   121  	}
   122  	tz.files = append(tz.files, &File{
   123  		Header: &tar.Header{
   124  			Name: dirPath + "/",
   125  		},
   126  	})
   127  	tz.updateStat()
   128  	return true
   129  }
   130  
   131  // AddDir adds a directory and subdirectories entries to TzArchive.
   132  func (tz *TzArchive) AddDir(dirPath, absPath string) error {
   133  	dir, err := os.Open(absPath)
   134  	if err != nil {
   135  		return err
   136  	}
   137  	defer dir.Close()
   138  
   139  	tz.AddEmptyDir(dirPath)
   140  
   141  	fis, err := dir.Readdir(0)
   142  	if err != nil {
   143  		return err
   144  	}
   145  	for _, fi := range fis {
   146  		curPath := strings.Replace(absPath+"/"+fi.Name(), "\\", "/", -1)
   147  		tmpRecPath := strings.Replace(filepath.Join(dirPath, fi.Name()), "\\", "/", -1)
   148  		if fi.IsDir() {
   149  			if err = tz.AddDir(tmpRecPath, curPath); err != nil {
   150  				return err
   151  			}
   152  		} else {
   153  			if err = tz.AddFile(tmpRecPath, curPath); err != nil {
   154  				return err
   155  			}
   156  		}
   157  	}
   158  	return nil
   159  }
   160  
   161  // updateStat should be called after every change for rebuilding statistic.
   162  func (tz *TzArchive) updateStat() {
   163  	tz.NumFiles = len(tz.files)
   164  	tz.isHasChanged = true
   165  }
   166  
   167  // AddFile adds a file entry to TzArchive.
   168  func (tz *TzArchive) AddFile(fileName, absPath string) error {
   169  	if cae.IsFilter(absPath) {
   170  		return nil
   171  	}
   172  
   173  	si, err := os.Lstat(absPath)
   174  	if err != nil {
   175  		return err
   176  	}
   177  
   178  	target := ""
   179  	if si.Mode()&os.ModeSymlink != 0 {
   180  		target, err = os.Readlink(absPath)
   181  		if err != nil {
   182  			return err
   183  		}
   184  	}
   185  
   186  	file := new(File)
   187  	file.Header, err = tar.FileInfoHeader(si, target)
   188  	if err != nil {
   189  		return err
   190  	}
   191  	file.Name = fileName
   192  	file.absPath = absPath
   193  
   194  	tz.AddEmptyDir(path.Dir(fileName))
   195  
   196  	isExist := false
   197  	for _, f := range tz.files {
   198  		if fileName == f.Name {
   199  			f = file
   200  			isExist = true
   201  			break
   202  		}
   203  	}
   204  	if !isExist {
   205  		tz.files = append(tz.files, file)
   206  	}
   207  
   208  	tz.updateStat()
   209  	return nil
   210  }
   211  
   212  // DeleteIndex deletes an entry in the archive by its index.
   213  func (tz *TzArchive) DeleteIndex(idx int) error {
   214  	if idx >= tz.NumFiles {
   215  		return errors.New("index out of range of number of files")
   216  	}
   217  
   218  	tz.files = append(tz.files[:idx], tz.files[idx+1:]...)
   219  	return nil
   220  }
   221  
   222  // DeleteName deletes an entry in the archive by its name.
   223  func (tz *TzArchive) DeleteName(name string) error {
   224  	for i, f := range tz.files {
   225  		if f.Name == name {
   226  			return tz.DeleteIndex(i)
   227  		}
   228  	}
   229  	return errors.New("entry with given name not found")
   230  }