github.com/webx-top/com@v1.2.12/file.go (about)

     1  // Copyright 2013 com authors
     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 com
    16  
    17  import (
    18  	"archive/tar"
    19  	"archive/zip"
    20  	"bufio"
    21  	"bytes"
    22  	"compress/gzip"
    23  	"errors"
    24  	"fmt"
    25  	"io"
    26  	"log"
    27  	"math"
    28  	"os"
    29  	"path/filepath"
    30  	"regexp"
    31  	"strconv"
    32  	"strings"
    33  	"time"
    34  )
    35  
    36  // Storage unit constants.
    37  const (
    38  	Byte  = 1
    39  	KByte = Byte * 1024
    40  	MByte = KByte * 1024
    41  	GByte = MByte * 1024
    42  	TByte = GByte * 1024
    43  	PByte = TByte * 1024
    44  	EByte = PByte * 1024
    45  )
    46  
    47  func logn(n, b float64) float64 {
    48  	return math.Log(n) / math.Log(b)
    49  }
    50  
    51  func humanateBytes(s uint64, base float64, sizes []string) string {
    52  	if s < 10 {
    53  		return fmt.Sprintf("%dB", s)
    54  	}
    55  	e := math.Floor(logn(float64(s), base))
    56  	suffix := sizes[int(e)]
    57  	val := float64(s) / math.Pow(base, math.Floor(e))
    58  	f := "%.0f"
    59  	if val < 10 {
    60  		f = "%.1f"
    61  	}
    62  
    63  	return fmt.Sprintf(f+"%s", val, suffix)
    64  }
    65  
    66  // HumaneFileSize calculates the file size and generate user-friendly string.
    67  func HumaneFileSize(s uint64) string {
    68  	sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"}
    69  	return humanateBytes(s, 1024, sizes)
    70  }
    71  
    72  // FileMTime returns file modified time and possible error.
    73  func FileMTime(file string) (int64, error) {
    74  	f, err := os.Stat(file)
    75  	if err != nil {
    76  		return 0, err
    77  	}
    78  	return f.ModTime().Unix(), nil
    79  }
    80  
    81  // TrimFileName trim the file name
    82  func TrimFileName(ppath string) string {
    83  	if len(ppath) == 0 {
    84  		return ppath
    85  	}
    86  	for i := len(ppath) - 1; i >= 0; i-- {
    87  		if ppath[i] == '/' || ppath[i] == '\\' {
    88  			if i+1 < len(ppath) {
    89  				return ppath[0 : i+1]
    90  			}
    91  			return ppath
    92  		}
    93  	}
    94  	return ``
    95  }
    96  
    97  func BaseFileName(ppath string) string {
    98  	if len(ppath) == 0 {
    99  		return ppath
   100  	}
   101  	for i := len(ppath) - 1; i >= 0; i-- {
   102  		if ppath[i] == '/' || ppath[i] == '\\' {
   103  			if i+1 < len(ppath) {
   104  				return ppath[i+1:]
   105  			}
   106  			return ``
   107  		}
   108  	}
   109  	return ppath
   110  }
   111  
   112  func HasPathSeperatorPrefix(ppath string) bool {
   113  	return strings.HasPrefix(ppath, `/`) || strings.HasPrefix(ppath, `\`)
   114  }
   115  
   116  func HasPathSeperatorSuffix(ppath string) bool {
   117  	return strings.HasSuffix(ppath, `/`) || strings.HasSuffix(ppath, `\`)
   118  }
   119  
   120  var pathSeperatorRegex = regexp.MustCompile(`(\\|/)`)
   121  var winPathSeperatorRegex = regexp.MustCompile(`[\\]+`)
   122  
   123  func GetPathSeperator(ppath string) string {
   124  	matches := pathSeperatorRegex.FindAllStringSubmatch(ppath, 1)
   125  	if len(matches) > 0 && len(matches[0]) > 1 {
   126  		return matches[0][1]
   127  	}
   128  	return ``
   129  }
   130  
   131  func RealPath(fullPath string) string {
   132  	if len(fullPath) == 0 {
   133  		return fullPath
   134  	}
   135  	var root string
   136  	var pathSeperator string
   137  	if strings.HasPrefix(fullPath, `/`) {
   138  		pathSeperator = `/`
   139  	} else {
   140  		pathSeperator = GetPathSeperator(fullPath)
   141  		if pathSeperator == `/` {
   142  			fullPath = `/` + fullPath
   143  		} else {
   144  			cleanedFullPath := winPathSeperatorRegex.ReplaceAllString(fullPath, `\`)
   145  			parts := strings.SplitN(cleanedFullPath, `\`, 2)
   146  			if !strings.HasSuffix(parts[0], `:`) {
   147  				if len(parts[0]) > 0 {
   148  					parts[0] = `c:\` + parts[0]
   149  				} else {
   150  					parts[0] = `c:`
   151  				}
   152  			}
   153  			root = parts[0] + `\`
   154  			if len(parts) != 2 {
   155  				return fullPath
   156  			}
   157  			fullPath = parts[1]
   158  		}
   159  	}
   160  	parts := pathSeperatorRegex.Split(fullPath, -1)
   161  	result := make([]string, 0, len(parts))
   162  	for _, part := range parts {
   163  		if part == `.` {
   164  			continue
   165  		}
   166  		if part == `..` {
   167  			if len(result) <= 1 {
   168  				continue
   169  			}
   170  			result = result[0 : len(result)-1]
   171  			continue
   172  		}
   173  		result = append(result, part)
   174  	}
   175  	return root + strings.Join(result, pathSeperator)
   176  }
   177  
   178  func SplitFileDirAndName(ppath string) (dir string, name string) {
   179  	if len(ppath) == 0 {
   180  		return
   181  	}
   182  	for i := len(ppath) - 1; i >= 0; i-- {
   183  		if ppath[i] == '/' || ppath[i] == '\\' {
   184  			if i+1 < len(ppath) {
   185  				return ppath[0:i], ppath[i+1:]
   186  			}
   187  			dir = ppath[0:i]
   188  			return
   189  		}
   190  	}
   191  	name = ppath
   192  	return
   193  }
   194  
   195  // FileSize returns file size in bytes and possible error.
   196  func FileSize(file string) (int64, error) {
   197  	f, err := os.Stat(file)
   198  	if err != nil {
   199  		return 0, err
   200  	}
   201  	return f.Size(), nil
   202  }
   203  
   204  // Copy copies file from source to target path.
   205  func Copy(src, dest string) error {
   206  	// Gather file information to set back later.
   207  	si, err := os.Lstat(src)
   208  	if err != nil {
   209  		return fmt.Errorf("couldn't open source file: %w", err)
   210  	}
   211  
   212  	// Handle symbolic link.
   213  	if si.Mode()&os.ModeSymlink != 0 {
   214  		target, err := os.Readlink(src)
   215  		if err != nil {
   216  			return err
   217  		}
   218  		// NOTE: os.Chmod and os.Chtimes don't recoganize symbolic link,
   219  		// which will lead "no such file or directory" error.
   220  		return os.Symlink(target, dest)
   221  	}
   222  
   223  	sr, err := os.Open(src)
   224  	if err != nil {
   225  		return fmt.Errorf("couldn't open source file: %w", err)
   226  	}
   227  	defer sr.Close()
   228  
   229  	dw, err := os.Create(dest)
   230  	if err != nil {
   231  		return fmt.Errorf("couldn't open dest file: %w", err)
   232  	}
   233  	defer dw.Close()
   234  
   235  	if _, err = io.Copy(dw, sr); err != nil {
   236  		return fmt.Errorf("writing to output file failed: %w", err)
   237  	}
   238  	dw.Sync()
   239  
   240  	// Set back file information.
   241  	if err = os.Chtimes(dest, si.ModTime(), si.ModTime()); err != nil {
   242  		return err
   243  	}
   244  	return os.Chmod(dest, si.Mode())
   245  }
   246  
   247  func Remove(name string) error {
   248  	err := os.Remove(name)
   249  	if err == nil || os.IsNotExist(err) {
   250  		return nil
   251  	}
   252  	return err
   253  }
   254  
   255  /*
   256  GoLang: os.Rename() give error "invalid cross-device link" for Docker container with Volumes.
   257  Rename(source, destination) will work moving file between folders
   258  */
   259  func Rename(src, dest string) error {
   260  	err := os.Rename(src, dest)
   261  	if err == nil {
   262  		return nil
   263  	}
   264  	if !strings.HasSuffix(err.Error(), `invalid cross-device link`) {
   265  		return err
   266  	}
   267  	err = Copy(src, dest)
   268  	if err != nil {
   269  		if !strings.HasSuffix(err.Error(), `operation not permitted`) {
   270  			return err
   271  		}
   272  	}
   273  	// The copy was successful, so now delete the original file
   274  	err = Remove(src)
   275  	if err != nil {
   276  		return fmt.Errorf("failed removing original file: %w", err)
   277  	}
   278  	return nil
   279  }
   280  
   281  // WriteFile writes data to a file named by filename.
   282  // If the file does not exist, WriteFile creates it
   283  // and its upper level paths.
   284  func WriteFile(filename string, data []byte) error {
   285  	os.MkdirAll(filepath.Dir(filename), os.ModePerm)
   286  	return os.WriteFile(filename, data, 0655)
   287  }
   288  
   289  // CreateFile create file
   290  func CreateFile(filename string) (fp *os.File, err error) {
   291  	fp, err = os.Create(filename)
   292  	if err != nil {
   293  		if !os.IsNotExist(err) {
   294  			return
   295  		}
   296  		err = MkdirAll(filepath.Dir(filename), os.ModePerm)
   297  		if err != nil {
   298  			return
   299  		}
   300  		fp, err = os.Create(filename)
   301  	}
   302  	return
   303  }
   304  
   305  // IsFile returns true if given path is a file,
   306  // or returns false when it's a directory or does not exist.
   307  func IsFile(filePath string) bool {
   308  	f, e := os.Stat(filePath)
   309  	if e != nil {
   310  		return false
   311  	}
   312  	return !f.IsDir()
   313  }
   314  
   315  // IsExist checks whether a file or directory exists.
   316  // It returns false when the file or directory does not exist.
   317  func IsExist(path string) bool {
   318  	_, err := os.Stat(path)
   319  	return err == nil || os.IsExist(err)
   320  }
   321  
   322  func Unlink(file string) bool {
   323  	return os.Remove(file) == nil
   324  }
   325  
   326  // SaveFile saves content type '[]byte' to file by given path.
   327  // It returns error when fail to finish operation.
   328  func SaveFile(filePath string, b []byte) (int, error) {
   329  	os.MkdirAll(filepath.Dir(filePath), os.ModePerm)
   330  	fw, err := os.Create(filePath)
   331  	if err != nil {
   332  		return 0, err
   333  	}
   334  	defer fw.Close()
   335  	return fw.Write(b)
   336  }
   337  
   338  // SaveFileS saves content type 'string' to file by given path.
   339  // It returns error when fail to finish operation.
   340  func SaveFileS(filePath string, s string) (int, error) {
   341  	return SaveFile(filePath, []byte(s))
   342  }
   343  
   344  // ReadFile reads data type '[]byte' from file by given path.
   345  // It returns error when fail to finish operation.
   346  func ReadFile(filePath string) ([]byte, error) {
   347  	b, err := os.ReadFile(filePath)
   348  	if err != nil {
   349  		return []byte(""), err
   350  	}
   351  	return b, nil
   352  }
   353  
   354  // ReadFileS reads data type 'string' from file by given path.
   355  // It returns error when fail to finish operation.
   356  func ReadFileS(filePath string) (string, error) {
   357  	b, err := ReadFile(filePath)
   358  	return string(b), err
   359  }
   360  
   361  // Zip 压缩为zip
   362  func Zip(srcDirPath string, destFilePath string, args ...*regexp.Regexp) (n int64, err error) {
   363  	root, err := filepath.Abs(srcDirPath)
   364  	if err != nil {
   365  		return 0, err
   366  	}
   367  
   368  	f, err := os.Create(destFilePath)
   369  	if err != nil {
   370  		return
   371  	}
   372  	defer f.Close()
   373  
   374  	w := zip.NewWriter(f)
   375  	var regexpIgnoreFile, regexpFileName *regexp.Regexp
   376  	argLen := len(args)
   377  	if argLen > 1 {
   378  		regexpIgnoreFile = args[1]
   379  		regexpFileName = args[0]
   380  	} else if argLen == 1 {
   381  		regexpFileName = args[0]
   382  	}
   383  	err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
   384  		if err != nil {
   385  			return err
   386  		}
   387  		name := info.Name()
   388  		nameBytes := []byte(name)
   389  		if regexpIgnoreFile != nil && regexpIgnoreFile.Match(nameBytes) {
   390  			if info.IsDir() {
   391  				return filepath.SkipDir
   392  			}
   393  			return nil
   394  		} else if info.IsDir() {
   395  			return nil
   396  		}
   397  		if regexpFileName != nil && !regexpFileName.Match(nameBytes) {
   398  			return nil
   399  		}
   400  		relativePath := strings.TrimPrefix(path, root)
   401  		relativePath = strings.Replace(relativePath, `\`, `/`, -1)
   402  		relativePath = strings.TrimPrefix(relativePath, `/`)
   403  		f, err := w.Create(relativePath)
   404  		if err != nil {
   405  			return err
   406  		}
   407  		sf, err := os.Open(path)
   408  		if err != nil {
   409  			return err
   410  		}
   411  		defer sf.Close()
   412  		_, err = io.Copy(f, sf)
   413  		return err
   414  	})
   415  
   416  	err = w.Close()
   417  	if err != nil {
   418  		return 0, err
   419  	}
   420  
   421  	fi, err := f.Stat()
   422  	if err != nil {
   423  		n = fi.Size()
   424  	}
   425  	return
   426  }
   427  
   428  // Unzip unzips .zip file to 'destPath'.
   429  // It returns error when fail to finish operation.
   430  func Unzip(srcPath, destPath string) error {
   431  	// Open a zip archive for reading
   432  	r, err := zip.OpenReader(srcPath)
   433  	if err != nil {
   434  		return err
   435  	}
   436  	defer r.Close()
   437  
   438  	// Iterate through the files in the archive
   439  	for _, f := range r.File {
   440  		// Get files from archive
   441  		rc, err := f.Open()
   442  		if err != nil {
   443  			return err
   444  		}
   445  
   446  		dir := filepath.Dir(f.Name)
   447  		// Create directory before create file
   448  		os.MkdirAll(destPath+"/"+dir, os.ModePerm)
   449  
   450  		if f.FileInfo().IsDir() {
   451  			continue
   452  		}
   453  
   454  		// Write data to file
   455  		fw, _ := os.Create(filepath.Join(destPath, f.Name))
   456  		if err != nil {
   457  			return err
   458  		}
   459  		_, err = io.Copy(fw, rc)
   460  
   461  		if fw != nil {
   462  			fw.Close()
   463  		}
   464  		if err != nil {
   465  			return err
   466  		}
   467  	}
   468  	return nil
   469  }
   470  
   471  func TarGz(srcDirPath string, destFilePath string) error {
   472  	fw, err := os.Create(destFilePath)
   473  	if err != nil {
   474  		return err
   475  	}
   476  	defer fw.Close()
   477  
   478  	// Gzip writer
   479  	gw := gzip.NewWriter(fw)
   480  	defer gw.Close()
   481  
   482  	// Tar writer
   483  	tw := tar.NewWriter(gw)
   484  	defer tw.Close()
   485  
   486  	// Check if it's a file or a directory
   487  	f, err := os.Open(srcDirPath)
   488  	if err != nil {
   489  		return err
   490  	}
   491  	fi, err := f.Stat()
   492  	if err != nil {
   493  		return err
   494  	}
   495  	if fi.IsDir() {
   496  		// handle source directory
   497  		fmt.Println("Cerating tar.gz from directory...")
   498  		if err := tarGzDir(srcDirPath, filepath.Base(srcDirPath), tw); err != nil {
   499  			return err
   500  		}
   501  	} else {
   502  		// handle file directly
   503  		fmt.Println("Cerating tar.gz from " + fi.Name() + "...")
   504  		if err := tarGzFile(srcDirPath, fi.Name(), tw, fi); err != nil {
   505  			return err
   506  		}
   507  	}
   508  	fmt.Println("Well done!")
   509  	return err
   510  }
   511  
   512  // Deal with directories
   513  // if find files, handle them with tarGzFile
   514  // Every recurrence append the base path to the recPath
   515  // recPath is the path inside of tar.gz
   516  func tarGzDir(srcDirPath string, recPath string, tw *tar.Writer) error {
   517  	// Open source diretory
   518  	dir, err := os.Open(srcDirPath)
   519  	if err != nil {
   520  		return err
   521  	}
   522  	defer dir.Close()
   523  
   524  	// Get file info slice
   525  	fis, err := dir.Readdir(0)
   526  	if err != nil {
   527  		return err
   528  	}
   529  	for _, fi := range fis {
   530  		// Append path
   531  		curPath := srcDirPath + "/" + fi.Name()
   532  		// Check it is directory or file
   533  		if fi.IsDir() {
   534  			// Directory
   535  			// (Directory won't add unitl all subfiles are added)
   536  			fmt.Printf("Adding path...%s\n", curPath)
   537  			tarGzDir(curPath, recPath+"/"+fi.Name(), tw)
   538  		} else {
   539  			// File
   540  			fmt.Printf("Adding file...%s\n", curPath)
   541  		}
   542  
   543  		tarGzFile(curPath, recPath+"/"+fi.Name(), tw, fi)
   544  	}
   545  	return err
   546  }
   547  
   548  // Deal with files
   549  func tarGzFile(srcFile string, recPath string, tw *tar.Writer, fi os.FileInfo) error {
   550  	if fi.IsDir() {
   551  		// Create tar header
   552  		hdr := new(tar.Header)
   553  		// if last character of header name is '/' it also can be directory
   554  		// but if you don't set Typeflag, error will occur when you untargz
   555  		hdr.Name = recPath + "/"
   556  		hdr.Typeflag = tar.TypeDir
   557  		hdr.Size = 0
   558  		//hdr.Mode = 0755 | c_ISDIR
   559  		hdr.Mode = int64(fi.Mode())
   560  		hdr.ModTime = fi.ModTime()
   561  
   562  		// Write hander
   563  		err := tw.WriteHeader(hdr)
   564  		if err != nil {
   565  			return err
   566  		}
   567  	} else {
   568  		// File reader
   569  		fr, err := os.Open(srcFile)
   570  		if err != nil {
   571  			return err
   572  		}
   573  		defer fr.Close()
   574  
   575  		// Create tar header
   576  		hdr := new(tar.Header)
   577  		hdr.Name = recPath
   578  		hdr.Size = fi.Size()
   579  		hdr.Mode = int64(fi.Mode())
   580  		hdr.ModTime = fi.ModTime()
   581  
   582  		// Write hander
   583  		err = tw.WriteHeader(hdr)
   584  		if err != nil {
   585  			return err
   586  		}
   587  
   588  		// Write file data
   589  		_, err = io.Copy(tw, fr)
   590  		if err != nil {
   591  			return err
   592  		}
   593  	}
   594  	return nil
   595  }
   596  
   597  // UnTarGz ungzips and untars .tar.gz file to 'destPath' and returns sub-directories.
   598  // It returns error when fail to finish operation.
   599  func UnTarGz(srcFilePath string, destDirPath string) ([]string, error) {
   600  	// Create destination directory
   601  	os.Mkdir(destDirPath, os.ModePerm)
   602  
   603  	fr, err := os.Open(srcFilePath)
   604  	if err != nil {
   605  		return nil, err
   606  	}
   607  	defer fr.Close()
   608  
   609  	// Gzip reader
   610  	gr, err := gzip.NewReader(fr)
   611  	if err != nil {
   612  		return nil, err
   613  	}
   614  	defer gr.Close()
   615  
   616  	// Tar reader
   617  	tr := tar.NewReader(gr)
   618  
   619  	dirs := make([]string, 0, 5)
   620  	for {
   621  		hdr, err := tr.Next()
   622  		if err == io.EOF {
   623  			// End of tar archive
   624  			break
   625  		}
   626  
   627  		// Check if it is directory or file
   628  		if hdr.Typeflag != tar.TypeDir {
   629  			// Get files from archive
   630  			// Create directory before create file
   631  			dir := filepath.Dir(hdr.Name)
   632  			os.MkdirAll(destDirPath+"/"+dir, os.ModePerm)
   633  			dirs = AppendStr(dirs, dir)
   634  
   635  			// Write data to file
   636  			fw, _ := os.Create(destDirPath + "/" + hdr.Name)
   637  			if err != nil {
   638  				return nil, err
   639  			}
   640  			_, err = io.Copy(fw, tr)
   641  			if err != nil {
   642  				return nil, err
   643  			}
   644  		}
   645  	}
   646  	return dirs, nil
   647  }
   648  
   649  var (
   650  	selfPath string
   651  	selfDir  string
   652  )
   653  
   654  func SelfPath() string {
   655  	if len(selfPath) == 0 {
   656  		selfPath, _ = filepath.Abs(os.Args[0])
   657  	}
   658  	return selfPath
   659  }
   660  
   661  func SelfDir() string {
   662  	if len(selfDir) == 0 {
   663  		selfDir = filepath.Dir(SelfPath())
   664  	}
   665  	return selfDir
   666  }
   667  
   668  // FileExists reports whether the named file or directory exists.
   669  func FileExists(name string) bool {
   670  	if _, err := os.Stat(name); err != nil {
   671  		if os.IsNotExist(err) {
   672  			return false
   673  		}
   674  	}
   675  	return true
   676  }
   677  
   678  // SearchFile search a file in paths.
   679  // this is offen used in search config file in /etc ~/
   680  func SearchFile(filename string, paths ...string) (fullpath string, err error) {
   681  	for _, path := range paths {
   682  		if fullpath = filepath.Join(path, filename); FileExists(fullpath) {
   683  			return
   684  		}
   685  	}
   686  	err = errors.New(fullpath + " not found in paths")
   687  	return
   688  }
   689  
   690  // GrepFile like command grep -E
   691  // for example: GrepFile(`^hello`, "hello.txt")
   692  // \n is striped while read
   693  func GrepFile(patten string, filename string) (lines []string, err error) {
   694  	re, err := regexp.Compile(patten)
   695  	if err != nil {
   696  		return
   697  	}
   698  
   699  	lines = make([]string, 0)
   700  	err = SeekFileLines(filename, func(line string) error {
   701  		if re.MatchString(line) {
   702  			lines = append(lines, line)
   703  		}
   704  		return nil
   705  	})
   706  	return lines, err
   707  }
   708  
   709  func SeekFileLines(filename string, callback func(string) error) error {
   710  	fd, err := os.Open(filename)
   711  	if err != nil {
   712  		return err
   713  	}
   714  	defer fd.Close()
   715  	return SeekLines(fd, callback)
   716  }
   717  
   718  type LineReader interface {
   719  	ReadLine() (line []byte, isPrefix bool, err error)
   720  }
   721  
   722  func SeekLines(r io.Reader, callback func(string) error) (err error) {
   723  	var reader LineReader
   724  	var prefix string
   725  	if rd, ok := r.(LineReader); ok {
   726  		reader = rd
   727  	} else {
   728  		reader = bufio.NewReader(r)
   729  	}
   730  	for {
   731  		byteLine, isPrefix, er := reader.ReadLine()
   732  		if er != nil && er != io.EOF {
   733  			return er
   734  		}
   735  		line := string(byteLine)
   736  		if isPrefix {
   737  			prefix += line
   738  			continue
   739  		}
   740  		line = prefix + line
   741  		if e := callback(line); e != nil {
   742  			return e
   743  		}
   744  		prefix = ""
   745  		if er == io.EOF {
   746  			break
   747  		}
   748  	}
   749  	return nil
   750  }
   751  
   752  func Readbuf(r io.Reader, length int) ([]byte, error) {
   753  	buf := make([]byte, length)
   754  	offset := 0
   755  
   756  	for offset < length {
   757  		read, err := r.Read(buf[offset:])
   758  		if err != nil {
   759  			return buf, err
   760  		}
   761  
   762  		offset += read
   763  	}
   764  
   765  	return buf, nil
   766  }
   767  
   768  func Bytes2readCloser(b []byte) io.ReadCloser {
   769  	return io.NopCloser(bytes.NewBuffer(b))
   770  }
   771  
   772  const HalfSecond = 500 * time.Millisecond // 0.5秒
   773  var debugFileIsCompleted bool
   774  
   775  // FileIsCompleted 等待文件有数据且已写完
   776  // 费时操作 放在子线程中执行
   777  // @param file  文件
   778  // @param start 需要传入 time.Now.Local(),用于兼容遍历的情况
   779  // @return true:已写完 false:外部程序阻塞或者文件不存在
   780  // 翻译自:https://blog.csdn.net/northernice/article/details/115986671
   781  func FileIsCompleted(file *os.File, start time.Time) (bool, error) {
   782  	var (
   783  		fileLength  int64
   784  		i           int
   785  		waitTime    = HalfSecond
   786  		lastModTime time.Time
   787  		finished    int
   788  	)
   789  	for {
   790  		fi, err := file.Stat()
   791  		if err != nil {
   792  			return false, err
   793  		}
   794  		if debugFileIsCompleted {
   795  			fmt.Printf("FileIsCompleted> size:%d (time:%s) [finished:%d]\n", fi.Size(), fi.ModTime(), finished)
   796  		}
   797  		//文件在外部一直在填充数据,每次进入循环体时,文件大小都会改变,一直到不改变时,说明文件数据填充完毕 或者文件大小一直都是0(外部程序阻塞)
   798  		//判断文件大小是否有改变
   799  		if fi.Size() > fileLength { //有改变说明还未写完
   800  			fileLength = fi.Size()
   801  			if i%120 == 0 { //每隔1分钟输出一次日志 (i为120时:120*500/1000=60秒)
   802  				log.Println("文件: " + fi.Name() + " 正在被填充,请稍候...")
   803  			}
   804  			time.Sleep(waitTime) //半秒后再循环一次
   805  			lastModTime = fi.ModTime()
   806  		} else { //否则:只能等于 不会小于,等于有两种情况,一种是数据写完了,一种是外部程序阻塞了,导致文件大小一直为0
   807  			if lastModTime.IsZero() {
   808  				lastModTime = fi.ModTime()
   809  			} else {
   810  				if fileLength != fi.Size() {
   811  					fileLength = fi.Size()
   812  				} else if lastModTime.Equal(fi.ModTime()) {
   813  					if fi.Size() != 0 {
   814  						if finished < 3 {
   815  							time.Sleep(waitTime)
   816  							finished++
   817  							continue
   818  						}
   819  						return true, nil
   820  					}
   821  				}
   822  			}
   823  			//等待外部程序开始写 只等60秒 120*500/1000=60秒
   824  			//每隔1分钟输出一次日志 (i为120时:120*500/1000=60秒)
   825  			if i%120 == 0 {
   826  				log.Println("文件: " + fi.Name() + " 大小为" + strconv.FormatInt(fi.Size(), 10) + ",正在等待外部程序填充,已等待:" + time.Since(start).String())
   827  			}
   828  
   829  			//如果一直(i为120时:120*500/1000=60秒)等于0,说明外部程序阻塞了
   830  			if i >= 3600 { //120为1分钟 3600为30分钟
   831  				log.Println("文件: " + fi.Name() + " 大小在:" + time.Since(start).String() + " 内始终为" + strconv.FormatInt(fi.Size(), 10) + ",说明:在[程序监测时间内]文件写入进程依旧在运行,程序监测时间结束") //入库未完成或发生阻塞
   832  				return false, nil
   833  			}
   834  
   835  			time.Sleep(waitTime)
   836  		}
   837  		if finished > 0 {
   838  			finished = 0
   839  		}
   840  		i++
   841  	}
   842  }