github.com/zhongdalu/gf@v1.0.0/g/os/gfile/gfile.go (about)

     1  // Copyright 2017 gf Author(https://github.com/zhongdalu/gf). 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/zhongdalu/gf.
     6  
     7  // Package gfile provides easy-to-use operations for file system.
     8  package gfile
     9  
    10  import (
    11  	"bytes"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"io/ioutil"
    16  	"os"
    17  	"os/exec"
    18  	"os/user"
    19  	"path/filepath"
    20  	"runtime"
    21  	"sort"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/zhongdalu/gf/g/container/gtype"
    26  	"github.com/zhongdalu/gf/g/text/gregex"
    27  	"github.com/zhongdalu/gf/g/text/gstr"
    28  	"github.com/zhongdalu/gf/g/util/gconv"
    29  )
    30  
    31  const (
    32  	// Separator for file system.
    33  	Separator = string(filepath.Separator)
    34  	// Default perm for file opening.
    35  	gDEFAULT_PERM = 0666
    36  )
    37  
    38  var (
    39  	// The absolute file path for main package.
    40  	// It can be only checked and set once.
    41  	mainPkgPath = gtype.NewString()
    42  )
    43  
    44  // Mkdir creates directories recursively with given <path>.
    45  // The parameter <path> is suggested to be absolute path.
    46  func Mkdir(path string) error {
    47  	err := os.MkdirAll(path, os.ModePerm)
    48  	if err != nil {
    49  		return err
    50  	}
    51  	return nil
    52  }
    53  
    54  // Create creates file with given <path> recursively.
    55  // The parameter <path> is suggested to be absolute path.
    56  func Create(path string) (*os.File, error) {
    57  	dir := Dir(path)
    58  	if !Exists(dir) {
    59  		Mkdir(dir)
    60  	}
    61  	return os.Create(path)
    62  }
    63  
    64  // Open opens file/directory readonly.
    65  func Open(path string) (*os.File, error) {
    66  	return os.Open(path)
    67  }
    68  
    69  // OpenFile opens file/directory with given <flag> and <perm>.
    70  func OpenFile(path string, flag int, perm os.FileMode) (*os.File, error) {
    71  	return os.OpenFile(path, flag, perm)
    72  }
    73  
    74  // OpenWithFlag opens file/directory with default perm and given <flag>.
    75  func OpenWithFlag(path string, flag int) (*os.File, error) {
    76  	f, err := os.OpenFile(path, flag, gDEFAULT_PERM)
    77  	if err != nil {
    78  		return nil, err
    79  	}
    80  	return f, nil
    81  }
    82  
    83  // OpenWithFlagPerm opens file/directory with given <flag> and <perm>.
    84  func OpenWithFlagPerm(path string, flag int, perm int) (*os.File, error) {
    85  	f, err := os.OpenFile(path, flag, os.FileMode(perm))
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	return f, nil
    90  }
    91  
    92  // Exists checks whether given <path> exist.
    93  func Exists(path string) bool {
    94  	if _, err := os.Stat(path); !os.IsNotExist(err) {
    95  		return true
    96  	}
    97  	return false
    98  }
    99  
   100  // IsDir checks whether given <path> a directory.
   101  func IsDir(path string) bool {
   102  	s, err := os.Stat(path)
   103  	if err != nil {
   104  		return false
   105  	}
   106  	return s.IsDir()
   107  }
   108  
   109  // Pwd returns absolute path of current working directory.
   110  func Pwd() string {
   111  	path, _ := os.Getwd()
   112  	return path
   113  }
   114  
   115  // IsFile checks whether given <path> a file, which means it's not a directory.
   116  func IsFile(path string) bool {
   117  	s, err := os.Stat(path)
   118  	if err != nil {
   119  		return false
   120  	}
   121  	return !s.IsDir()
   122  }
   123  
   124  // Alias of Stat.
   125  // See Stat.
   126  func Info(path string) (os.FileInfo, error) {
   127  	return Stat(path)
   128  }
   129  
   130  // Stat returns a FileInfo describing the named file.
   131  // If there is an error, it will be of type *PathError.
   132  func Stat(path string) (os.FileInfo, error) {
   133  	return os.Stat(path)
   134  }
   135  
   136  // Move renames (moves) <src> to <dst> path.
   137  func Move(src string, dst string) error {
   138  	return os.Rename(src, dst)
   139  }
   140  
   141  // Alias of Move.
   142  // See Move.
   143  func Rename(src string, dst string) error {
   144  	return Move(src, dst)
   145  }
   146  
   147  // Copy file/directory from <src> to <dst>.
   148  //
   149  // If <src> is file, it calls CopyFile to implements copy feature,
   150  // or else it calls CopyDir.
   151  func Copy(src string, dst string) error {
   152  	if IsFile(src) {
   153  		return CopyFile(src, dst)
   154  	}
   155  	return CopyDir(src, dst)
   156  }
   157  
   158  // CopyFile copies the contents of the file named src to the file named
   159  // by dst. The file will be created if it does not already exist. If the
   160  // destination file exists, all it's contents will be replaced by the contents
   161  // of the source file. The file mode will be copied from the source and
   162  // the copied data is synced/flushed to stable storage.
   163  // Thanks: https://gist.github.com/r0l1/92462b38df26839a3ca324697c8cba04
   164  func CopyFile(src, dst string) (err error) {
   165  	in, err := os.Open(src)
   166  	if err != nil {
   167  		return
   168  	}
   169  	defer func() {
   170  		if e := in.Close(); e != nil {
   171  			err = e
   172  		}
   173  	}()
   174  	out, err := os.Create(dst)
   175  	if err != nil {
   176  		return
   177  	}
   178  	defer func() {
   179  		if e := out.Close(); e != nil {
   180  			err = e
   181  		}
   182  	}()
   183  	_, err = io.Copy(out, in)
   184  	if err != nil {
   185  		return
   186  	}
   187  	err = out.Sync()
   188  	if err != nil {
   189  		return
   190  	}
   191  	si, err := os.Stat(src)
   192  	if err != nil {
   193  		return
   194  	}
   195  	err = os.Chmod(dst, si.Mode())
   196  	if err != nil {
   197  		return
   198  	}
   199  	return
   200  }
   201  
   202  // CopyDir recursively copies a directory tree, attempting to preserve permissions.
   203  // Source directory must exist, destination directory must *not* exist.
   204  // Symlinks are ignored and skipped.
   205  func CopyDir(src string, dst string) (err error) {
   206  	src = filepath.Clean(src)
   207  	dst = filepath.Clean(dst)
   208  	si, err := os.Stat(src)
   209  	if err != nil {
   210  		return err
   211  	}
   212  	if !si.IsDir() {
   213  		return fmt.Errorf("source is not a directory")
   214  	}
   215  	_, err = os.Stat(dst)
   216  	if err != nil && !os.IsNotExist(err) {
   217  		return
   218  	}
   219  	if err == nil {
   220  		return fmt.Errorf("destination already exists")
   221  	}
   222  	err = os.MkdirAll(dst, si.Mode())
   223  	if err != nil {
   224  		return
   225  	}
   226  	entries, err := ioutil.ReadDir(src)
   227  	if err != nil {
   228  		return
   229  	}
   230  	for _, entry := range entries {
   231  		srcPath := filepath.Join(src, entry.Name())
   232  		dstPath := filepath.Join(dst, entry.Name())
   233  		if entry.IsDir() {
   234  			err = CopyDir(srcPath, dstPath)
   235  			if err != nil {
   236  				return
   237  			}
   238  		} else {
   239  			// Skip symlinks.
   240  			if entry.Mode()&os.ModeSymlink != 0 {
   241  				continue
   242  			}
   243  			err = CopyFile(srcPath, dstPath)
   244  			if err != nil {
   245  				return
   246  			}
   247  		}
   248  	}
   249  	return
   250  }
   251  
   252  // DirNames returns sub-file names of given directory <path>.
   253  func DirNames(path string) ([]string, error) {
   254  	f, err := os.Open(path)
   255  	if err != nil {
   256  		return nil, err
   257  	}
   258  	list, err := f.Readdirnames(-1)
   259  	f.Close()
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  	return list, nil
   264  }
   265  
   266  // Glob returns the names of all files matching pattern or nil
   267  // if there is no matching file. The syntax of patterns is the same
   268  // as in Match. The pattern may describe hierarchical names such as
   269  // /usr/*/bin/ed (assuming the Separator is '/').
   270  //
   271  // Glob ignores file system errors such as I/O errors reading directories.
   272  // The only possible returned error is ErrBadPattern, when pattern
   273  // is malformed.
   274  func Glob(pattern string, onlyNames ...bool) ([]string, error) {
   275  	if list, err := filepath.Glob(pattern); err == nil {
   276  		if len(onlyNames) > 0 && onlyNames[0] && len(list) > 0 {
   277  			array := make([]string, len(list))
   278  			for k, v := range list {
   279  				array[k] = Basename(v)
   280  			}
   281  			return array, nil
   282  		}
   283  		return list, nil
   284  	} else {
   285  		return nil, err
   286  	}
   287  }
   288  
   289  // Remove deletes all file/directory with <path> parameter.
   290  // If parameter <path> is directory, it deletes it recursively.
   291  func Remove(path string) error {
   292  	return os.RemoveAll(path)
   293  }
   294  
   295  // IsReadable checks whether given <path> is readable.
   296  func IsReadable(path string) bool {
   297  	result := true
   298  	file, err := os.OpenFile(path, os.O_RDONLY, gDEFAULT_PERM)
   299  	if err != nil {
   300  		result = false
   301  	}
   302  	file.Close()
   303  	return result
   304  }
   305  
   306  // IsWritable checks whether given <path> is writable.
   307  //
   308  // @TODO improve performance; use golang.org/x/sys to cross-plat-form
   309  func IsWritable(path string) bool {
   310  	result := true
   311  	if IsDir(path) {
   312  		// If it's a directory, create a temporary file to test whether it's writable.
   313  		tmpFile := strings.TrimRight(path, Separator) + Separator + gconv.String(time.Now().UnixNano())
   314  		if f, err := Create(tmpFile); err != nil || !Exists(tmpFile) {
   315  			result = false
   316  		} else {
   317  			f.Close()
   318  			Remove(tmpFile)
   319  		}
   320  	} else {
   321  		// 如果是文件,那么判断文件是否可打开
   322  		file, err := os.OpenFile(path, os.O_WRONLY, gDEFAULT_PERM)
   323  		if err != nil {
   324  			result = false
   325  		}
   326  		file.Close()
   327  	}
   328  	return result
   329  }
   330  
   331  // See os.Chmod.
   332  func Chmod(path string, mode os.FileMode) error {
   333  	return os.Chmod(path, mode)
   334  }
   335  
   336  // ScanDir returns all sub-files with absolute paths of given <path>,
   337  // It scans directory recursively if given parameter <recursive> is true.
   338  func ScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
   339  	list, err := doScanDir(path, pattern, recursive...)
   340  	if err != nil {
   341  		return nil, err
   342  	}
   343  	if len(list) > 0 {
   344  		sort.Strings(list)
   345  	}
   346  	return list, nil
   347  }
   348  
   349  // doScanDir is an internal method which scans directory
   350  // and returns the absolute path list of files that are not sorted.
   351  //
   352  // The pattern parameter <pattern> supports multiple file name patterns,
   353  // using the ',' symbol to separate multiple patterns.
   354  //
   355  // It scans directory recursively if given parameter <recursive> is true.
   356  func doScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
   357  	list := ([]string)(nil)
   358  	file, err := os.Open(path)
   359  	if err != nil {
   360  		return nil, err
   361  	}
   362  	defer file.Close()
   363  	names, err := file.Readdirnames(-1)
   364  	if err != nil {
   365  		return nil, err
   366  	}
   367  	for _, name := range names {
   368  		path := fmt.Sprintf("%s%s%s", path, Separator, name)
   369  		if IsDir(path) && len(recursive) > 0 && recursive[0] {
   370  			array, _ := doScanDir(path, pattern, true)
   371  			if len(array) > 0 {
   372  				list = append(list, array...)
   373  			}
   374  		}
   375  		// If it meets pattern, then add it to the result list.
   376  		for _, p := range strings.Split(pattern, ",") {
   377  			if match, err := filepath.Match(strings.TrimSpace(p), name); err == nil && match {
   378  				list = append(list, path)
   379  			}
   380  		}
   381  	}
   382  	return list, nil
   383  }
   384  
   385  // RealPath converts the given <path> to its absolute path
   386  // and checks if the file path exists.
   387  // If the file does not exist, return an empty string.
   388  func RealPath(path string) string {
   389  	p, err := filepath.Abs(path)
   390  	if err != nil {
   391  		return ""
   392  	}
   393  	if !Exists(p) {
   394  		return ""
   395  	}
   396  	return p
   397  }
   398  
   399  // SelfPath returns absolute file path of current running process(binary).
   400  func SelfPath() string {
   401  	p, _ := filepath.Abs(os.Args[0])
   402  	return p
   403  }
   404  
   405  // SelfName returns file name of current running process(binary).
   406  func SelfName() string {
   407  	return Basename(SelfPath())
   408  }
   409  
   410  // SelfDir returns absolute directory path of current running process(binary).
   411  func SelfDir() string {
   412  	return filepath.Dir(SelfPath())
   413  }
   414  
   415  // Basename returns the last element of path.
   416  // Trailing path separators are removed before extracting the last element.
   417  // If the path is empty, Base returns ".".
   418  // If the path consists entirely of separators, Base returns a single separator.
   419  func Basename(path string) string {
   420  	return filepath.Base(path)
   421  }
   422  
   423  // Dir returns all but the last element of path, typically the path's directory.
   424  // After dropping the final element, Dir calls Clean on the path and trailing
   425  // slashes are removed.
   426  // If the path is empty, Dir returns ".".
   427  // If the path consists entirely of separators, Dir returns a single separator.
   428  // The returned path does not end in a separator unless it is the root directory.
   429  func Dir(path string) string {
   430  	return filepath.Dir(path)
   431  }
   432  
   433  // Ext returns the file name extension used by path.
   434  // The extension is the suffix beginning at the final dot
   435  // in the final element of path; it is empty if there is
   436  // no dot.
   437  //
   438  // Note: the result contains symbol '.'.
   439  func Ext(path string) string {
   440  	return filepath.Ext(path)
   441  }
   442  
   443  // Home returns absolute path of current user's home directory.
   444  func Home() (string, error) {
   445  	u, err := user.Current()
   446  	if nil == err {
   447  		return u.HomeDir, nil
   448  	}
   449  	if "windows" == runtime.GOOS {
   450  		return homeWindows()
   451  	}
   452  	return homeUnix()
   453  }
   454  
   455  func homeUnix() (string, error) {
   456  	if home := os.Getenv("HOME"); home != "" {
   457  		return home, nil
   458  	}
   459  	var stdout bytes.Buffer
   460  	cmd := exec.Command("sh", "-c", "eval echo ~$USER")
   461  	cmd.Stdout = &stdout
   462  	if err := cmd.Run(); err != nil {
   463  		return "", err
   464  	}
   465  
   466  	result := strings.TrimSpace(stdout.String())
   467  	if result == "" {
   468  		return "", errors.New("blank output when reading home directory")
   469  	}
   470  
   471  	return result, nil
   472  }
   473  
   474  func homeWindows() (string, error) {
   475  	drive := os.Getenv("HOMEDRIVE")
   476  	path := os.Getenv("HOMEPATH")
   477  	home := drive + path
   478  	if drive == "" || path == "" {
   479  		home = os.Getenv("USERPROFILE")
   480  	}
   481  	if home == "" {
   482  		return "", errors.New("HOMEDRIVE, HOMEPATH, and USERPROFILE are blank")
   483  	}
   484  
   485  	return home, nil
   486  }
   487  
   488  // MainPkgPath returns absolute file path of package main,
   489  // which contains the entrance function main.
   490  //
   491  // It's only available in develop environment.
   492  //
   493  // Note1: Only valid for source development environments,
   494  // IE only valid for systems that generate this executable.
   495  // Note2: When the method is called for the first time, if it is in an asynchronous goroutine,
   496  // the method may not get the main package path.
   497  func MainPkgPath() string {
   498  	path := mainPkgPath.Val()
   499  	if path != "" {
   500  		if path == "-" {
   501  			return ""
   502  		}
   503  		return path
   504  	}
   505  	for i := 1; i < 10000; i++ {
   506  		if _, file, _, ok := runtime.Caller(i); ok {
   507  			// <file> is separated by '/'
   508  			if gstr.Contains(file, "/gf/g/") {
   509  				continue
   510  			}
   511  			if Ext(file) != ".go" {
   512  				continue
   513  			}
   514  			// separator of <file> '/' will be converted to Separator.
   515  			for path = Dir(file); len(path) > 1 && Exists(path) && path[len(path)-1] != os.PathSeparator; {
   516  				files, _ := ScanDir(path, "*.go")
   517  				for _, v := range files {
   518  					if gregex.IsMatchString(`package\s+main`, GetContents(v)) {
   519  						mainPkgPath.Set(path)
   520  						return path
   521  					}
   522  				}
   523  				path = Dir(path)
   524  			}
   525  
   526  		} else {
   527  			break
   528  		}
   529  	}
   530  	// If it fails finding the path, then mark it as "-",
   531  	// which means it will never do this search again.
   532  	mainPkgPath.Set("-")
   533  	return ""
   534  }
   535  
   536  // See os.TempDir().
   537  func TempDir() string {
   538  	return os.TempDir()
   539  }