github.com/PandaGoAdmin/utils@v0.0.0-20211208134815-d5461603a00f/file.go (about)

     1  package kgo
     2  
     3  import (
     4  	"archive/tar"
     5  	"archive/zip"
     6  	"bufio"
     7  	"bytes"
     8  	"compress/gzip"
     9  	"crypto/md5"
    10  	"encoding/hex"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  	"mime"
    15  	"net/http"
    16  	"os"
    17  	"path"
    18  	"path/filepath"
    19  	"regexp"
    20  	"strings"
    21  )
    22  
    23  // GetExt 获取文件的小写扩展名,不包括点"." .
    24  func (kf *LkkFile) GetExt(fpath string) string {
    25  	suffix := filepath.Ext(fpath)
    26  	if suffix != "" {
    27  		return strings.ToLower(suffix[1:])
    28  	}
    29  	return suffix
    30  }
    31  
    32  // ReadFile 读取文件内容.
    33  func (kf *LkkFile) ReadFile(fpath string) ([]byte, error) {
    34  	data, err := os.ReadFile(fpath)
    35  	return data, err
    36  }
    37  
    38  // ReadInArray 把整个文件读入一个数组中,每行作为一个元素.
    39  func (kf *LkkFile) ReadInArray(fpath string) ([]string, error) {
    40  	data, err := os.ReadFile(fpath)
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  
    45  	return strings.Split(string(data), "\n"), nil
    46  }
    47  
    48  // ReadFirstLine 读取文件首行.
    49  func (kf *LkkFile) ReadFirstLine(fpath string) []byte {
    50  	var res []byte
    51  	fh, err := os.Open(fpath)
    52  	if err == nil {
    53  		scanner := bufio.NewScanner(fh)
    54  		for scanner.Scan() {
    55  			res = scanner.Bytes()
    56  			break
    57  		}
    58  	}
    59  	defer func() {
    60  		_ = fh.Close()
    61  	}()
    62  
    63  	return res
    64  }
    65  
    66  // ReadLastLine 读取文件末行.
    67  func (kf *LkkFile) ReadLastLine(fpath string) []byte {
    68  	var res []byte
    69  	fh, err := os.Open(fpath)
    70  	if err == nil {
    71  		var lastLineSize int
    72  		reader := bufio.NewReader(fh)
    73  
    74  		for {
    75  			bs, err := reader.ReadBytes('\n')
    76  			lastLineSize = len(bs)
    77  			if err == io.EOF {
    78  				break
    79  			}
    80  		}
    81  
    82  		fileInfo, _ := os.Stat(fpath)
    83  
    84  		// make a buffer size according to the lastLineSize
    85  		buffer := make([]byte, lastLineSize)
    86  		offset := fileInfo.Size() - int64(lastLineSize)
    87  		numRead, _ := fh.ReadAt(buffer, offset)
    88  		res = buffer[:numRead]
    89  	}
    90  	defer func() {
    91  		_ = fh.Close()
    92  	}()
    93  
    94  	return res
    95  }
    96  
    97  // WriteFile 将内容写入文件.
    98  // fpath为文件路径;data为内容;perm为权限,默认为0655.
    99  func (kf *LkkFile) WriteFile(fpath string, data []byte, perm ...os.FileMode) error {
   100  	dir := path.Dir(fpath)
   101  	if err := os.MkdirAll(dir, os.ModePerm); err != nil {
   102  		return err
   103  	}
   104  
   105  	var p os.FileMode = 0777
   106  	if len(perm) > 0 {
   107  		p = perm[0]
   108  	}
   109  
   110  	return os.WriteFile(fpath, data, p)
   111  }
   112  
   113  // GetFileMode 获取路径的权限模式.
   114  func (kf *LkkFile) GetFileMode(fpath string) (os.FileMode, error) {
   115  	finfo, err := os.Lstat(fpath)
   116  	if err != nil {
   117  		return 0, err
   118  	}
   119  	return finfo.Mode(), nil
   120  }
   121  
   122  // AppendFile 插入文件内容.
   123  func (kf *LkkFile) AppendFile(fpath string, data []byte) error {
   124  	if fpath == "" {
   125  		return errors.New("[AppendFile] no path provided")
   126  	}
   127  
   128  	var file *os.File
   129  	filePerm, err := kf.GetFileMode(fpath)
   130  	if err != nil {
   131  		// create the file
   132  		file, err = os.Create(fpath)
   133  	} else {
   134  		// open for append
   135  		file, err = os.OpenFile(fpath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, filePerm)
   136  	}
   137  	if err != nil {
   138  		// failed to create or open-for-append the file
   139  		return err
   140  	}
   141  
   142  	defer func() {
   143  		_ = file.Close()
   144  	}()
   145  
   146  	_, err = file.Write(data)
   147  
   148  	return err
   149  }
   150  
   151  // GetMime 获取文件mime类型;fast为true时根据后缀快速获取;为false时读取文件头获取.
   152  func (kf *LkkFile) GetMime(fpath string, fast bool) string {
   153  	var res string
   154  	if fast {
   155  		suffix := filepath.Ext(fpath)
   156  		//当unix系统中没有相关的mime.types文件时,将返回空
   157  		res = mime.TypeByExtension(suffix)
   158  	} else {
   159  		srcFile, err := os.Open(fpath)
   160  		if err != nil {
   161  			return res
   162  		}
   163  
   164  		buffer := make([]byte, 512)
   165  		_, err = srcFile.Read(buffer)
   166  		if err != nil {
   167  			return res
   168  		}
   169  
   170  		res = http.DetectContentType(buffer)
   171  	}
   172  
   173  	return res
   174  }
   175  
   176  // FileSize 获取文件大小(bytes字节);注意:文件不存在或无法访问时返回-1 .
   177  func (kf *LkkFile) FileSize(fpath string) int64 {
   178  	f, err := os.Stat(fpath)
   179  	if nil != err {
   180  		return -1
   181  	}
   182  	return f.Size()
   183  }
   184  
   185  // DirSize 获取目录大小(bytes字节).
   186  func (kf *LkkFile) DirSize(fpath string) int64 {
   187  	var size int64
   188  	//filepath.Walk压测很慢
   189  	_ = filepath.Walk(fpath, func(_ string, info os.FileInfo, err error) error {
   190  		if err != nil {
   191  			return err
   192  		}
   193  		if !info.IsDir() {
   194  			size += info.Size()
   195  		}
   196  		return err
   197  	})
   198  	return size
   199  }
   200  
   201  // IsExist 路径(文件/目录)是否存在.
   202  func (kf *LkkFile) IsExist(fpath string) bool {
   203  	_, err := os.Stat(fpath)
   204  	return err == nil || os.IsExist(err)
   205  }
   206  
   207  // IsLink 是否链接文件(软链接,且存在).
   208  func (kf *LkkFile) IsLink(fpath string) bool {
   209  	f, err := os.Lstat(fpath)
   210  	if err != nil {
   211  		return false
   212  	}
   213  
   214  	return f.Mode()&os.ModeSymlink == os.ModeSymlink
   215  }
   216  
   217  // IsFile 是否(某类型)文件,且存在.
   218  // ftype为枚举(FILE_TYPE_ANY、FILE_TYPE_LINK、FILE_TYPE_REGULAR、FILE_TYPE_COMMON),默认FILE_TYPE_ANY;
   219  func (kf *LkkFile) IsFile(fpath string, ftype ...LkkFileType) (res bool) {
   220  	var t LkkFileType = FILE_TYPE_ANY
   221  	if len(ftype) > 0 {
   222  		t = ftype[0]
   223  	}
   224  
   225  	var f os.FileInfo
   226  	var e error
   227  	var musLink, musRegular bool
   228  
   229  	if t == FILE_TYPE_LINK {
   230  		musLink = true
   231  	} else if t == FILE_TYPE_REGULAR {
   232  		musRegular = true
   233  	} else if t == FILE_TYPE_COMMON {
   234  		musLink = true
   235  		musRegular = true
   236  	}
   237  
   238  	if (!musLink && !musRegular) || musRegular {
   239  		f, e := os.Stat(fpath)
   240  		if musRegular {
   241  			res = (e == nil) && f.Mode().IsRegular()
   242  		} else {
   243  			res = (e == nil) && !f.IsDir()
   244  		}
   245  	}
   246  
   247  	if !res && musLink {
   248  		f, e = os.Lstat(fpath)
   249  		res = (e == nil) && (f.Mode()&os.ModeSymlink == os.ModeSymlink)
   250  	}
   251  
   252  	return
   253  }
   254  
   255  // IsDir 是否目录(且存在).
   256  func (kf *LkkFile) IsDir(fpath string) bool {
   257  	f, err := os.Lstat(fpath)
   258  	if os.IsNotExist(err) || nil != err {
   259  		return false
   260  	}
   261  	return f.IsDir()
   262  }
   263  
   264  // IsBinary 是否二进制文件(且存在).
   265  func (kf *LkkFile) IsBinary(fpath string) bool {
   266  	cont, err := kf.ReadFile(fpath)
   267  	if err != nil {
   268  		return false
   269  	}
   270  
   271  	return isBinary(string(cont))
   272  }
   273  
   274  // IsImg 是否图片文件(仅检查后缀).
   275  func (kf *LkkFile) IsImg(fpath string) bool {
   276  	ext := kf.GetExt(fpath)
   277  	switch ext {
   278  	case "jpg", "jpeg", "bmp", "gif", "png", "svg", "ico", "webp":
   279  		return true
   280  	default:
   281  		return false
   282  	}
   283  }
   284  
   285  // Mkdir 创建目录,允许多级.
   286  func (kf *LkkFile) Mkdir(fpath string, mode os.FileMode) error {
   287  	return os.MkdirAll(fpath, mode)
   288  }
   289  
   290  // AbsPath 获取绝对路径,path可允许不存在.
   291  func (kf *LkkFile) AbsPath(fpath string) string {
   292  	fullPath := ""
   293  	res, err := filepath.Abs(fpath) // filepath.Abs最终使用到os.Getwd()检查
   294  	if err != nil {
   295  		fullPath = filepath.Clean(filepath.Join(`/`, fpath))
   296  	} else {
   297  		fullPath = res
   298  	}
   299  
   300  	return fullPath
   301  }
   302  
   303  // RealPath 返回规范化的真实绝对路径名.path必须存在,若路径不存在则返回空字符串.
   304  func (kf *LkkFile) RealPath(fpath string) string {
   305  	fullPath := fpath
   306  	if !filepath.IsAbs(fpath) {
   307  		wd, err := os.Getwd()
   308  		if err != nil {
   309  			return ""
   310  		}
   311  		fullPath = filepath.Clean(wd + `/` + fpath)
   312  	}
   313  
   314  	_, err := os.Stat(fullPath)
   315  	if err != nil {
   316  		return ""
   317  	}
   318  
   319  	return fullPath
   320  }
   321  
   322  // Touch 快速创建指定大小的文件,size为字节.
   323  func (kf *LkkFile) Touch(fpath string, size int64) bool {
   324  	//创建目录
   325  	destDir := filepath.Dir(fpath)
   326  	if err := os.MkdirAll(destDir, 0777); err != nil {
   327  		return false
   328  	}
   329  
   330  	fd, err := os.OpenFile(fpath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777)
   331  	if err != nil {
   332  		return false
   333  	}
   334  	defer func() {
   335  		_ = fd.Close()
   336  	}()
   337  
   338  	if size > 1 {
   339  		_, _ = fd.Seek(size-1, 0)
   340  		_, _ = fd.Write([]byte{0})
   341  	}
   342  
   343  	return true
   344  }
   345  
   346  // Rename 重命名(或移动)文件/目录.
   347  func (kf *LkkFile) Rename(oldname, newname string) error {
   348  	return os.Rename(oldname, newname)
   349  }
   350  
   351  // Unlink 删除文件.
   352  func (kf *LkkFile) Unlink(fpath string) error {
   353  	return os.Remove(fpath)
   354  }
   355  
   356  // CopyFile 拷贝源文件到目标文件,cover为枚举(FILE_COVER_ALLOW、FILE_COVER_IGNORE、FILE_COVER_DENY).
   357  func (kf *LkkFile) CopyFile(source string, dest string, cover LkkFileCover) (int64, error) {
   358  	if source == dest {
   359  		return 0, nil
   360  	}
   361  
   362  	sourceStat, err := os.Stat(source)
   363  	if err != nil {
   364  		return 0, err
   365  	} else if !sourceStat.Mode().IsRegular() {
   366  		return 0, fmt.Errorf("[CopyFile]`source %s is not a regular file", source)
   367  	}
   368  
   369  	if cover != FILE_COVER_ALLOW {
   370  		if _, err := os.Stat(dest); err == nil {
   371  			if cover == FILE_COVER_IGNORE {
   372  				return 0, nil
   373  			} else if cover == FILE_COVER_DENY {
   374  				return 0, fmt.Errorf("[CopyFile]`dest File %s already exists", dest)
   375  			}
   376  		}
   377  	}
   378  
   379  	sourceFile, _ := os.Open(source)
   380  	defer func() {
   381  		_ = sourceFile.Close()
   382  	}()
   383  
   384  	//源目录
   385  	srcDirStat, _ := os.Stat(filepath.Dir(source))
   386  
   387  	//创建目录
   388  	destDir := filepath.Dir(dest)
   389  	if err = os.MkdirAll(destDir, srcDirStat.Mode()); err != nil {
   390  		return 0, err
   391  	}
   392  
   393  	destFile, err := os.Create(dest)
   394  	if err != nil {
   395  		return 0, err
   396  	}
   397  	defer func() {
   398  		_ = destFile.Close()
   399  	}()
   400  
   401  	var nBytes int64
   402  	sourceSize := sourceStat.Size()
   403  	if sourceSize <= 1048576 { //1M以内小文件使用buffer拷贝
   404  		var total int
   405  		var bufferSize int = 102400
   406  		if sourceSize < 524288 {
   407  			bufferSize = 51200
   408  		}
   409  
   410  		buf := make([]byte, bufferSize)
   411  		for {
   412  			n, err := sourceFile.Read(buf)
   413  			if err != nil && err != io.EOF {
   414  				return int64(total), err
   415  			} else if n == 0 {
   416  				break
   417  			}
   418  
   419  			if _, err := destFile.Write(buf[:n]); err != nil || !kf.IsExist(dest) {
   420  				return int64(total), err
   421  			}
   422  
   423  			total += n
   424  		}
   425  		nBytes = int64(total)
   426  	} else {
   427  		nBytes, err = io.Copy(destFile, sourceFile)
   428  		if err == nil {
   429  			err = os.Chmod(dest, sourceStat.Mode())
   430  		}
   431  	}
   432  
   433  	return nBytes, err
   434  }
   435  
   436  // FastCopy 快速拷贝源文件到目标文件,不做安全检查.
   437  func (kf *LkkFile) FastCopy(source string, dest string) (int64, error) {
   438  	sourceFile, err := os.Open(source)
   439  	if err != nil {
   440  		return 0, err
   441  	}
   442  	defer func() {
   443  		_ = sourceFile.Close()
   444  	}()
   445  
   446  	//创建目录
   447  	destDir := filepath.Dir(dest)
   448  	if err = os.MkdirAll(destDir, 0777); err != nil {
   449  		return 0, err
   450  	}
   451  
   452  	destFile, err := os.Create(dest)
   453  	if err != nil {
   454  		return 0, err
   455  	}
   456  	defer func() {
   457  		_ = destFile.Close()
   458  	}()
   459  
   460  	var bufferSize int = 32768
   461  	var nBytes int
   462  	buf := make([]byte, bufferSize)
   463  	for {
   464  		n, err := sourceFile.Read(buf)
   465  		if err != nil && err != io.EOF {
   466  			return int64(nBytes), err
   467  		} else if n == 0 {
   468  			break
   469  		}
   470  
   471  		if _, err := destFile.Write(buf[:n]); err != nil || !kf.IsExist(dest) {
   472  			return int64(nBytes), err
   473  		}
   474  
   475  		nBytes += n
   476  	}
   477  
   478  	return int64(nBytes), err
   479  }
   480  
   481  // CopyLink 拷贝链接.
   482  func (kf *LkkFile) CopyLink(source string, dest string) error {
   483  	if source == dest {
   484  		return nil
   485  	}
   486  
   487  	source, err := os.Readlink(source)
   488  	if err != nil {
   489  		return err
   490  	}
   491  
   492  	//移除已存在的目标文件
   493  	_, err = os.Lstat(dest)
   494  	if err == nil {
   495  		_ = os.Remove(dest)
   496  	}
   497  
   498  	//创建目录
   499  	destDir := filepath.Dir(dest)
   500  	if err := os.MkdirAll(destDir, 0777); err != nil {
   501  		return err
   502  	}
   503  
   504  	return os.Symlink(source, dest)
   505  }
   506  
   507  // CopyDir 拷贝源目录到目标目录,cover为枚举(FILE_COVER_ALLOW、FILE_COVER_IGNORE、FILE_COVER_DENY).
   508  func (kf *LkkFile) CopyDir(source string, dest string, cover LkkFileCover) (int64, error) {
   509  	var total, nBytes int64
   510  	var err error
   511  
   512  	if source == "" || source == dest {
   513  		return 0, nil
   514  	}
   515  
   516  	sourceInfo, err := os.Stat(source)
   517  	if err != nil {
   518  		return 0, err
   519  	} else if !sourceInfo.IsDir() {
   520  		return 0, fmt.Errorf("[CopyDir]`source %s is not a directory", source)
   521  	}
   522  
   523  	// create dest dir
   524  	if err = os.MkdirAll(dest, sourceInfo.Mode()); err != nil {
   525  		return 0, err
   526  	}
   527  
   528  	directory, _ := os.Open(source)
   529  	defer func() {
   530  		_ = directory.Close()
   531  	}()
   532  
   533  	objects, err := directory.Readdir(-1)
   534  	if err != nil {
   535  		return 0, err
   536  	}
   537  
   538  	for _, obj := range objects {
   539  		srcFilePath := filepath.Join(source, obj.Name())
   540  		destFilePath := filepath.Join(dest, obj.Name())
   541  
   542  		if obj.IsDir() {
   543  			// create sub-directories - recursively
   544  			nBytes, err = kf.CopyDir(srcFilePath, destFilePath, cover)
   545  		} else {
   546  			destFileInfo, err := os.Stat(destFilePath)
   547  			if err == nil {
   548  				if cover != FILE_COVER_ALLOW || os.SameFile(obj, destFileInfo) {
   549  					continue
   550  				}
   551  			}
   552  
   553  			if obj.Mode()&os.ModeSymlink != 0 {
   554  				// a link
   555  				_ = kf.CopyLink(srcFilePath, destFilePath)
   556  			} else {
   557  				nBytes, err = kf.CopyFile(srcFilePath, destFilePath, cover)
   558  			}
   559  		}
   560  
   561  		if err == nil {
   562  			total += nBytes
   563  		}
   564  	}
   565  
   566  	return total, err
   567  }
   568  
   569  // DelDir 删除目录.delete为true时连该目录一起删除;为false时只清空该目录.
   570  func (kf *LkkFile) DelDir(dir string, delete bool) error {
   571  	realPath := kf.AbsPath(dir)
   572  	if !kf.IsDir(realPath) {
   573  		return fmt.Errorf("[DelDir]`dir %s not exists", dir)
   574  	}
   575  
   576  	names, err := os.ReadDir(realPath)
   577  	if err != nil {
   578  		return err
   579  	}
   580  
   581  	for _, entery := range names {
   582  		file := path.Join([]string{realPath, entery.Name()}...)
   583  		err = os.RemoveAll(file)
   584  	}
   585  
   586  	//删除目录
   587  	if delete {
   588  		err = os.RemoveAll(realPath)
   589  	}
   590  
   591  	return err
   592  }
   593  
   594  // Img2Base64 读取图片文件,并转换为base64字符串.
   595  func (kf *LkkFile) Img2Base64(fpath string) (string, error) {
   596  	if !kf.IsImg(fpath) {
   597  		return "", fmt.Errorf("[Img2Base64]`fpath %s is not a image", fpath)
   598  	}
   599  
   600  	imgBuffer, err := os.ReadFile(fpath)
   601  	if err != nil {
   602  		return "", err
   603  	}
   604  
   605  	ext := kf.GetExt(fpath)
   606  	return img2Base64(imgBuffer, ext), nil
   607  }
   608  
   609  // FileTree 获取目录的文件树列表.
   610  // ftype为枚举(FILE_TREE_ALL、FILE_TREE_DIR、FILE_TREE_FILE);
   611  // recursive为是否递归;
   612  // filters为一个或多个文件过滤器函数,FileFilter类型.
   613  func (kf *LkkFile) FileTree(fpath string, ftype LkkFileTree, recursive bool, filters ...FileFilter) []string {
   614  	var trees []string
   615  
   616  	if kf.IsFile(fpath, FILE_TYPE_ANY) {
   617  		if ftype != FILE_TREE_DIR {
   618  			trees = append(trees, fpath)
   619  		}
   620  		return trees
   621  	}
   622  
   623  	fpath = strings.TrimRight(fpath, "/\\")
   624  	files, err := filepath.Glob(filepath.Join(fpath, "*"))
   625  	if err != nil || len(files) == 0 {
   626  		return trees
   627  	}
   628  
   629  	for _, file := range files {
   630  		file = strings.ReplaceAll(file, "\\", "/")
   631  		if kf.IsDir(file) {
   632  			if ftype != FILE_TREE_FILE {
   633  				trees = append(trees, file)
   634  			}
   635  
   636  			if recursive {
   637  				subs := kf.FileTree(file, ftype, recursive, filters...)
   638  				trees = append(trees, subs...)
   639  			}
   640  		} else if ftype != FILE_TREE_DIR {
   641  			//文件过滤
   642  			chk := true
   643  			if len(filters) > 0 {
   644  				for _, filter := range filters {
   645  					chk = filter(file)
   646  					if !chk {
   647  						break
   648  					}
   649  				}
   650  			}
   651  			if !chk {
   652  				continue
   653  			}
   654  
   655  			trees = append(trees, file)
   656  		}
   657  	}
   658  
   659  	return trees
   660  }
   661  
   662  // FormatDir 格式化目录,将"\","//"替换为"/",且以"/"结尾.
   663  func (kf *LkkFile) FormatDir(fpath string) string {
   664  	return formatDir(fpath)
   665  }
   666  
   667  // Md5 获取文件md5值,length指定结果长度32/16.
   668  func (kf *LkkFile) Md5(fpath string, length uint8) (string, error) {
   669  	var res string
   670  	f, err := os.Open(fpath)
   671  	if err != nil {
   672  		return res, err
   673  	}
   674  	defer func() {
   675  		_ = f.Close()
   676  	}()
   677  
   678  	hash := md5.New()
   679  	if _, err := io.Copy(hash, f); err != nil {
   680  		return res, err
   681  	}
   682  
   683  	hashInBytes := hash.Sum(nil)
   684  	if length > 0 && length < 32 {
   685  		dst := make([]byte, hex.EncodedLen(len(hashInBytes)))
   686  		hex.Encode(dst, hashInBytes)
   687  		res = string(dst[:length])
   688  	} else {
   689  		res = hex.EncodeToString(hashInBytes)
   690  	}
   691  
   692  	return res, nil
   693  }
   694  
   695  // ShaX 计算文件的 shaX 散列值,x为1/256/512.
   696  func (kf *LkkFile) ShaX(fpath string, x uint16) (string, error) {
   697  	data, err := os.ReadFile(fpath)
   698  	if err != nil {
   699  		return "", err
   700  	}
   701  
   702  	return string(shaXByte(data, x)), nil
   703  }
   704  
   705  // Pathinfo 获取文件路径的信息.
   706  // option为要返回的信息,枚举值如下:
   707  // -1: all; 1: dirname; 2: basename; 4: extension; 8: filename;
   708  // 若要查看某几项,则为它们之间的和.
   709  func (kf *LkkFile) Pathinfo(fpath string, option int) map[string]string {
   710  	if option == -1 {
   711  		option = 1 | 2 | 4 | 8
   712  	}
   713  	res := make(map[string]string)
   714  	if (option & 1) == 1 {
   715  		res["dirname"] = filepath.Dir(fpath)
   716  	}
   717  	if (option & 2) == 2 {
   718  		res["basename"] = filepath.Base(fpath)
   719  	}
   720  	if ((option & 4) == 4) || ((option & 8) == 8) {
   721  		basename := ""
   722  		if (option & 2) == 2 {
   723  			basename, _ = res["basename"]
   724  		} else {
   725  			basename = filepath.Base(fpath)
   726  		}
   727  		p := strings.LastIndex(basename, ".")
   728  		filename, extension := "", ""
   729  		if p > 0 {
   730  			filename, extension = basename[:p], basename[p+1:]
   731  		} else if p == -1 {
   732  			filename = basename
   733  		} else if p == 0 {
   734  			extension = basename[p+1:]
   735  		}
   736  		if (option & 4) == 4 {
   737  			res["extension"] = extension
   738  		}
   739  		if (option & 8) == 8 {
   740  			res["filename"] = filename
   741  		}
   742  	}
   743  	return res
   744  }
   745  
   746  // Basename 返回路径中的文件名部分(包括后缀),空路径时返回".".
   747  func (kf *LkkFile) Basename(fpath string) string {
   748  	return filepath.Base(fpath)
   749  }
   750  
   751  // Dirname 返回路径中的目录部分,注意空路径或无目录的返回".".
   752  func (kf *LkkFile) Dirname(fpath string) string {
   753  	return filepath.Dir(fpath)
   754  }
   755  
   756  // GetModTime 获取文件的修改时间戳,秒.
   757  func (kf *LkkFile) GetModTime(fpath string) (res int64) {
   758  	fileinfo, err := os.Stat(fpath)
   759  	if err == nil {
   760  		res = fileinfo.ModTime().Unix()
   761  	}
   762  	return
   763  }
   764  
   765  // Glob 寻找与模式匹配的文件路径.
   766  func (kf *LkkFile) Glob(pattern string) ([]string, error) {
   767  	return filepath.Glob(pattern)
   768  }
   769  
   770  // SafeFileName 将文件名转换为安全可用的字符串.
   771  func (kf *LkkFile) SafeFileName(str string) string {
   772  	name := path.Clean(path.Base(str))
   773  	name = strings.Trim(name, " ")
   774  	separators, err := regexp.Compile(`[ &_=+:]`)
   775  	if err == nil {
   776  		name = separators.ReplaceAllString(name, "-")
   777  	}
   778  	legal, err := regexp.Compile(`[^[:alnum:]-.]`)
   779  	if err == nil {
   780  		name = legal.ReplaceAllString(name, "")
   781  	}
   782  	for strings.Contains(name, "--") {
   783  		name = strings.Replace(name, "--", "-", -1)
   784  	}
   785  	return name
   786  }
   787  
   788  // TarGz 打包压缩tar.gz.
   789  // src为源文件或目录,dstTar为打包的路径名,ignorePatterns为要忽略的文件正则.
   790  func (kf *LkkFile) TarGz(src string, dstTar string, ignorePatterns ...string) (bool, error) {
   791  	//过滤器,检查要忽略的文件
   792  	var filter = func(file string) bool {
   793  		res := true
   794  		for _, pattern := range ignorePatterns {
   795  			re, err := regexp.Compile(pattern)
   796  			if err != nil {
   797  				continue
   798  			}
   799  			chk := re.MatchString(file)
   800  			if chk {
   801  				res = false
   802  				break
   803  			}
   804  		}
   805  		return res
   806  	}
   807  
   808  	src = kf.AbsPath(src)
   809  	dstTar = kf.AbsPath(dstTar)
   810  
   811  	dstDir := kf.Dirname(dstTar)
   812  	if !kf.IsDir(dstDir) {
   813  		err := os.MkdirAll(dstDir, os.ModePerm)
   814  		if err != nil {
   815  			return false, err
   816  		}
   817  	}
   818  
   819  	files := kf.FileTree(src, FILE_TREE_ALL, true, filter)
   820  	if len(files) == 0 {
   821  		return false, fmt.Errorf("[TarGz]`src no files to tar.gz")
   822  	}
   823  
   824  	// dest file write
   825  	fw, err := os.Create(dstTar)
   826  	if err != nil {
   827  		return false, err
   828  	}
   829  	defer func() {
   830  		_ = fw.Close()
   831  	}()
   832  	// gzip write
   833  	gw := gzip.NewWriter(fw)
   834  	defer func() {
   835  		_ = gw.Close()
   836  	}()
   837  
   838  	// tar write
   839  	tw := tar.NewWriter(gw)
   840  	defer func() {
   841  		_ = tw.Close()
   842  	}()
   843  
   844  	parentDir := filepath.Dir(src)
   845  	for _, file := range files {
   846  		if file == dstTar {
   847  			continue
   848  		}
   849  		fi, err := os.Stat(file)
   850  		if err != nil {
   851  			continue
   852  		}
   853  		newName := strings.Replace(file, parentDir, "", -1)
   854  		newName = strings.ReplaceAll(newName, ":", "") //防止wins下 tmp/D: 创建失败
   855  
   856  		// Create tar header
   857  		hdr := new(tar.Header)
   858  		hdr.Format = tar.FormatGNU
   859  
   860  		if fi.IsDir() {
   861  			// if last character of header name is '/' it also can be directory
   862  			// but if you don't set Typeflag, error will occur when you untargz
   863  			hdr.Name = newName + "/"
   864  			hdr.Typeflag = tar.TypeDir
   865  			hdr.Size = 0
   866  			//hdr.Mode = 0755 | c_ISDIR
   867  			hdr.Mode = int64(fi.Mode())
   868  			hdr.ModTime = fi.ModTime()
   869  
   870  			// Write hander
   871  			err := tw.WriteHeader(hdr)
   872  			if err != nil {
   873  				return false, fmt.Errorf("[TarGz] DirErr: %s file:%s\n", err.Error(), file)
   874  			}
   875  		} else {
   876  			// File reader
   877  			fr, err := os.Open(file)
   878  			if err != nil {
   879  				return false, fmt.Errorf("[TarGz] OpenErr: %s file:%s\n", err.Error(), file)
   880  			}
   881  			defer func() {
   882  				_ = fr.Close()
   883  			}()
   884  
   885  			hdr.Name = newName
   886  			hdr.Size = fi.Size()
   887  			hdr.Mode = int64(fi.Mode())
   888  			hdr.ModTime = fi.ModTime()
   889  
   890  			// Write hander
   891  			err = tw.WriteHeader(hdr)
   892  			if err != nil {
   893  				return false, fmt.Errorf("[TarGz] FileErr: %s file:%s\n", err.Error(), file)
   894  			}
   895  
   896  			// Write file data
   897  			_, err = io.Copy(tw, fr)
   898  			if err != nil {
   899  				return false, fmt.Errorf("[TarGz] CopyErr: %s file:%s\n", err.Error(), file)
   900  			}
   901  			_ = fr.Close()
   902  		}
   903  	}
   904  
   905  	return true, nil
   906  }
   907  
   908  // UnTarGz 将tar.gz文件解压缩.
   909  // srcTar为压缩包,dstDir为解压目录.
   910  func (kf *LkkFile) UnTarGz(srcTar, dstDir string) (bool, error) {
   911  	fr, err := os.Open(srcTar)
   912  	if err != nil {
   913  		return false, err
   914  	}
   915  	defer func() {
   916  		_ = fr.Close()
   917  	}()
   918  
   919  	dstDir = strings.TrimRight(kf.AbsPath(dstDir), "/\\")
   920  	if !kf.IsDir(dstDir) {
   921  		err := os.MkdirAll(dstDir, os.ModePerm)
   922  		if err != nil {
   923  			return false, err
   924  		}
   925  	}
   926  
   927  	// Gzip reader
   928  	gr, err := gzip.NewReader(fr)
   929  	if err != nil {
   930  		return false, err
   931  	}
   932  
   933  	// Tar reader
   934  	tr := tar.NewReader(gr)
   935  	for {
   936  		hdr, err := tr.Next()
   937  		if err == io.EOF {
   938  			// End of tar archive
   939  			break
   940  		} else if err != nil {
   941  			return false, err
   942  		}
   943  
   944  		// Create diretory before create file
   945  		newPath := dstDir + "/" + strings.TrimLeft(hdr.Name, "/\\")
   946  		parentDir := path.Dir(newPath)
   947  		if !kf.IsDir(parentDir) {
   948  			err := os.MkdirAll(parentDir, os.ModePerm)
   949  			if err != nil {
   950  				return false, err
   951  			}
   952  		}
   953  
   954  		if hdr.Typeflag != tar.TypeDir {
   955  			// Write data to file
   956  			fw, err := os.Create(newPath)
   957  			if err != nil {
   958  				return false, fmt.Errorf("[UnTarGz] CreateErr: %s file:%s\n", err.Error(), newPath)
   959  			}
   960  
   961  			_, err = io.Copy(fw, tr)
   962  			if err != nil {
   963  				return false, fmt.Errorf("[UnTarGz] CopyErr: %s file:%s\n", err.Error(), newPath)
   964  			}
   965  
   966  			_ = fw.Close()
   967  		}
   968  	}
   969  
   970  	return true, nil
   971  }
   972  
   973  // ChmodBatch 批量改变路径权限模式(包括子目录和所属文件).
   974  // filemode为文件权限模式,dirmode为目录权限模式.
   975  func (kf *LkkFile) ChmodBatch(fpath string, filemode, dirmode os.FileMode) (res bool) {
   976  	var err error
   977  	err = filepath.Walk(fpath, func(fpath string, f os.FileInfo, err error) error {
   978  		if f == nil {
   979  			return err
   980  		}
   981  
   982  		if f.IsDir() {
   983  			err = os.Chmod(fpath, dirmode)
   984  		} else {
   985  			err = os.Chmod(fpath, filemode)
   986  		}
   987  
   988  		return err
   989  	})
   990  
   991  	if err == nil {
   992  		res = true
   993  	}
   994  
   995  	return
   996  }
   997  
   998  // CountLines 统计文件行数.buffLength为缓冲长度,kb.
   999  func (kf *LkkFile) CountLines(fpath string, buffLength int) (int, error) {
  1000  	fh, err := os.Open(fpath)
  1001  	if err != nil {
  1002  		return -1, err
  1003  	}
  1004  	defer func() {
  1005  		_ = fh.Close()
  1006  	}()
  1007  
  1008  	count := 0
  1009  	lineSep := []byte{'\n'}
  1010  
  1011  	if buffLength <= 0 {
  1012  		buffLength = 32
  1013  	}
  1014  
  1015  	r := bufio.NewReader(fh)
  1016  	buf := make([]byte, buffLength*1024)
  1017  	for {
  1018  		c, err := r.Read(buf)
  1019  		count += bytes.Count(buf[:c], lineSep)
  1020  
  1021  		switch {
  1022  		case err == io.EOF:
  1023  			return count, nil
  1024  		case err != nil:
  1025  			return count, err
  1026  		}
  1027  	}
  1028  }
  1029  
  1030  // Zip 将文件或目录进行zip打包.fpaths为源文件或目录的路径.
  1031  func (kf *LkkFile) Zip(dst string, fpaths ...string) (bool, error) {
  1032  	dst = kf.AbsPath(dst)
  1033  	dstDir := kf.Dirname(dst)
  1034  	if !kf.IsDir(dstDir) {
  1035  		err := os.MkdirAll(dstDir, os.ModePerm)
  1036  		if err != nil {
  1037  			return false, err
  1038  		}
  1039  	}
  1040  
  1041  	fzip, err := os.Create(dst)
  1042  	if err != nil {
  1043  		return false, err
  1044  	}
  1045  	defer func() {
  1046  		_ = fzip.Close()
  1047  	}()
  1048  
  1049  	if len(fpaths) == 0 {
  1050  		return false, errors.New("[Zip] no input files.")
  1051  	}
  1052  
  1053  	var allfiles, files []string
  1054  	var fpath string
  1055  	for _, fpath = range fpaths {
  1056  		if kf.IsDir(fpath) {
  1057  			files = kf.FileTree(fpath, FILE_TREE_FILE, true)
  1058  			if len(files) != 0 {
  1059  				allfiles = append(allfiles, files...)
  1060  			}
  1061  		} else if fpath != "" {
  1062  			allfiles = append(allfiles, fpath)
  1063  		}
  1064  	}
  1065  
  1066  	if len(allfiles) == 0 {
  1067  		return false, errors.New("[Zip] no exist files.")
  1068  	}
  1069  
  1070  	zipw := zip.NewWriter(fzip)
  1071  	defer func() {
  1072  		_ = zipw.Close()
  1073  	}()
  1074  
  1075  	keys := make(map[string]bool)
  1076  	for _, fpath = range allfiles {
  1077  		if _, ok := keys[fpath]; ok || kf.AbsPath(fpath) == dst {
  1078  			continue
  1079  		}
  1080  
  1081  		fileToZip, err := os.Open(fpath)
  1082  		if err != nil {
  1083  			return false, fmt.Errorf("[Zip] failed to open %s: %s", fpath, err)
  1084  		}
  1085  		defer func() {
  1086  			_ = fileToZip.Close()
  1087  		}()
  1088  
  1089  		wr, _ := zipw.Create(fpath)
  1090  		keys[fpath] = true
  1091  		if _, err := io.Copy(wr, fileToZip); err != nil {
  1092  			return false, fmt.Errorf("[Zip] failed to write %s to zip: %s", fpath, err)
  1093  		}
  1094  	}
  1095  
  1096  	return true, nil
  1097  }
  1098  
  1099  // UnZip 解压zip文件.srcZip为zip文件路径,dstDir为解压目录.
  1100  func (kf *LkkFile) UnZip(srcZip, dstDir string) (bool, error) {
  1101  	reader, err := zip.OpenReader(srcZip)
  1102  	if err != nil {
  1103  		return false, err
  1104  	}
  1105  	defer func() {
  1106  		_ = reader.Close()
  1107  	}()
  1108  
  1109  	dstDir = strings.TrimRight(kf.AbsPath(dstDir), "/\\")
  1110  	if !kf.IsDir(dstDir) {
  1111  		err := os.MkdirAll(dstDir, os.ModePerm)
  1112  		if err != nil {
  1113  			return false, err
  1114  		}
  1115  	}
  1116  
  1117  	// 迭代压缩文件中的文件
  1118  	for _, f := range reader.File {
  1119  		// Create diretory before create file
  1120  		//newPath := dstDir + string(os.PathSeparator) + strings.TrimLeft(f.Name, string(os.PathSeparator))
  1121  		newPath := dstDir + "/" + strings.TrimLeft(f.Name, "/\\")
  1122  		parentDir := path.Dir(newPath)
  1123  		if !kf.IsDir(parentDir) {
  1124  			err := os.MkdirAll(parentDir, os.ModePerm)
  1125  			if err != nil {
  1126  				return false, err
  1127  			}
  1128  		}
  1129  
  1130  		if !f.FileInfo().IsDir() {
  1131  			if fcreate, err := os.Create(newPath); err == nil {
  1132  				if rc, err := f.Open(); err == nil {
  1133  					_, _ = io.Copy(fcreate, rc)
  1134  					_ = rc.Close() //不要用defer来关闭,如果文件太多的话,会报too many open files 的错误
  1135  					_ = fcreate.Close()
  1136  				} else {
  1137  					_ = fcreate.Close()
  1138  					return false, err
  1139  				}
  1140  			} else {
  1141  				return false, err
  1142  			}
  1143  		}
  1144  	}
  1145  
  1146  	return true, nil
  1147  }
  1148  
  1149  // IsZip 是否zip文件.
  1150  func (kf *LkkFile) IsZip(fpath string) bool {
  1151  	ext := kf.GetExt(fpath)
  1152  	if ext != "zip" {
  1153  		return false
  1154  	}
  1155  
  1156  	f, err := os.Open(fpath)
  1157  	if err != nil {
  1158  		return false
  1159  	}
  1160  	defer func() {
  1161  		_ = f.Close()
  1162  	}()
  1163  
  1164  	buf := make([]byte, 4)
  1165  	n, err := f.Read(buf)
  1166  
  1167  	return err == nil && n == 4 && bytes.Equal(buf, []byte("PK\x03\x04"))
  1168  }