github.com/wangyougui/gf/v2@v2.6.5/os/gspath/gspath.go (about)

     1  // Copyright GoFrame Author(https://goframe.org). 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/wangyougui/gf.
     6  
     7  // Package gspath implements file index and search for folders.
     8  //
     9  // It searches file internally with high performance in order by the directory adding sequence.
    10  // Note that:
    11  // If caching feature enabled, there would be a searching delay after adding/deleting files.
    12  package gspath
    13  
    14  import (
    15  	"context"
    16  	"os"
    17  	"sort"
    18  	"strings"
    19  
    20  	"github.com/wangyougui/gf/v2/container/garray"
    21  	"github.com/wangyougui/gf/v2/container/gmap"
    22  	"github.com/wangyougui/gf/v2/errors/gcode"
    23  	"github.com/wangyougui/gf/v2/errors/gerror"
    24  	"github.com/wangyougui/gf/v2/internal/intlog"
    25  	"github.com/wangyougui/gf/v2/os/gfile"
    26  	"github.com/wangyougui/gf/v2/text/gstr"
    27  )
    28  
    29  // SPath manages the path searching feature.
    30  type SPath struct {
    31  	paths *garray.StrArray // The searching directories array.
    32  	cache *gmap.StrStrMap  // Searching cache map, it is not enabled if it's nil.
    33  }
    34  
    35  // SPathCacheItem is a cache item for searching.
    36  type SPathCacheItem struct {
    37  	path  string // Absolute path for file/dir.
    38  	isDir bool   // Is directory or not.
    39  }
    40  
    41  var (
    42  	// Path to searching object mapping, used for instance management.
    43  	pathsMap = gmap.NewStrAnyMap(true)
    44  )
    45  
    46  // New creates and returns a new path searching manager.
    47  func New(path string, cache bool) *SPath {
    48  	sp := &SPath{
    49  		paths: garray.NewStrArray(true),
    50  	}
    51  	if cache {
    52  		sp.cache = gmap.NewStrStrMap(true)
    53  	}
    54  	if len(path) > 0 {
    55  		if _, err := sp.Add(path); err != nil {
    56  			// intlog.Print(err)
    57  		}
    58  	}
    59  	return sp
    60  }
    61  
    62  // Get creates and returns an instance of searching manager for given path.
    63  // The parameter `cache` specifies whether using cache feature for this manager.
    64  // If cache feature is enabled, it asynchronously and recursively scans the path
    65  // and updates all sub files/folders to the cache using package gfsnotify.
    66  func Get(root string, cache bool) *SPath {
    67  	if root == "" {
    68  		root = "/"
    69  	}
    70  	return pathsMap.GetOrSetFuncLock(root, func() interface{} {
    71  		return New(root, cache)
    72  	}).(*SPath)
    73  }
    74  
    75  // Search searches file `name` under path `root`.
    76  // The parameter `root` should be an absolute path. It will not automatically
    77  // convert `root` to absolute path for performance reason.
    78  // The optional parameter `indexFiles` specifies the searching index files when the result is a directory.
    79  // For example, if the result `filePath` is a directory, and `indexFiles` is [index.html, main.html], it will also
    80  // search [index.html, main.html] under `filePath`. It returns the absolute file path if any of them found,
    81  // or else it returns `filePath`.
    82  func Search(root string, name string, indexFiles ...string) (filePath string, isDir bool) {
    83  	return Get(root, false).Search(name, indexFiles...)
    84  }
    85  
    86  // SearchWithCache searches file `name` under path `root` with cache feature enabled.
    87  // The parameter `root` should be an absolute path. It will not automatically
    88  // convert `root` to absolute path for performance reason.
    89  // The optional parameter `indexFiles` specifies the searching index files when the result is a directory.
    90  // For example, if the result `filePath` is a directory, and `indexFiles` is [index.html, main.html], it will also
    91  // search [index.html, main.html] under `filePath`. It returns the absolute file path if any of them found,
    92  // or else it returns `filePath`.
    93  func SearchWithCache(root string, name string, indexFiles ...string) (filePath string, isDir bool) {
    94  	return Get(root, true).Search(name, indexFiles...)
    95  }
    96  
    97  // Set deletes all other searching directories and sets the searching directory for this manager.
    98  func (sp *SPath) Set(path string) (realPath string, err error) {
    99  	realPath = gfile.RealPath(path)
   100  	if realPath == "" {
   101  		realPath, _ = sp.Search(path)
   102  		if realPath == "" {
   103  			realPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path)
   104  		}
   105  	}
   106  	if realPath == "" {
   107  		return realPath, gerror.NewCodef(gcode.CodeInvalidParameter, `path "%s" does not exist`, path)
   108  	}
   109  	// The set path must be a directory.
   110  	if gfile.IsDir(realPath) {
   111  		realPath = strings.TrimRight(realPath, gfile.Separator)
   112  		if sp.paths.Search(realPath) != -1 {
   113  			for _, v := range sp.paths.Slice() {
   114  				sp.removeMonitorByPath(v)
   115  			}
   116  		}
   117  		intlog.Print(context.TODO(), "paths clear:", sp.paths)
   118  		sp.paths.Clear()
   119  		if sp.cache != nil {
   120  			sp.cache.Clear()
   121  		}
   122  		sp.paths.Append(realPath)
   123  		sp.updateCacheByPath(realPath)
   124  		sp.addMonitorByPath(realPath)
   125  		return realPath, nil
   126  	} else {
   127  		return "", gerror.NewCode(gcode.CodeInvalidParameter, path+" should be a folder")
   128  	}
   129  }
   130  
   131  // Add adds more searching directory to the manager.
   132  // The manager will search file in added order.
   133  func (sp *SPath) Add(path string) (realPath string, err error) {
   134  	realPath = gfile.RealPath(path)
   135  	if realPath == "" {
   136  		realPath, _ = sp.Search(path)
   137  		if realPath == "" {
   138  			realPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path)
   139  		}
   140  	}
   141  	if realPath == "" {
   142  		return realPath, gerror.NewCodef(gcode.CodeInvalidParameter, `path "%s" does not exist`, path)
   143  	}
   144  	// The added path must be a directory.
   145  	if gfile.IsDir(realPath) {
   146  		// fmt.Println("gspath:", realPath, sp.paths.Search(realPath))
   147  		// It will not add twice for the same directory.
   148  		if sp.paths.Search(realPath) < 0 {
   149  			realPath = strings.TrimRight(realPath, gfile.Separator)
   150  			sp.paths.Append(realPath)
   151  			sp.updateCacheByPath(realPath)
   152  			sp.addMonitorByPath(realPath)
   153  		}
   154  		return realPath, nil
   155  	} else {
   156  		return "", gerror.NewCode(gcode.CodeInvalidParameter, path+" should be a folder")
   157  	}
   158  }
   159  
   160  // Search searches file `name` in the manager.
   161  // The optional parameter `indexFiles` specifies the searching index files when the result is a directory.
   162  // For example, if the result `filePath` is a directory, and `indexFiles` is [index.html, main.html], it will also
   163  // search [index.html, main.html] under `filePath`. It returns the absolute file path if any of them found,
   164  // or else it returns `filePath`.
   165  func (sp *SPath) Search(name string, indexFiles ...string) (filePath string, isDir bool) {
   166  	// No cache enabled.
   167  	if sp.cache == nil {
   168  		sp.paths.LockFunc(func(array []string) {
   169  			path := ""
   170  			for _, v := range array {
   171  				path = gfile.Join(v, name)
   172  				if stat, err := os.Stat(path); stat != nil && !os.IsNotExist(err) {
   173  					path = gfile.Abs(path)
   174  					// Security check: the result file path must be under the searching directory.
   175  					if len(path) >= len(v) && path[:len(v)] == v {
   176  						filePath = path
   177  						isDir = stat.IsDir()
   178  						break
   179  					}
   180  				}
   181  			}
   182  		})
   183  		if len(indexFiles) > 0 && isDir {
   184  			if name == "/" {
   185  				name = ""
   186  			}
   187  			path := ""
   188  			for _, file := range indexFiles {
   189  				path = filePath + gfile.Separator + file
   190  				if gfile.Exists(path) {
   191  					filePath = path
   192  					isDir = false
   193  					break
   194  				}
   195  			}
   196  		}
   197  		return
   198  	}
   199  	// Using cache feature.
   200  	name = sp.formatCacheName(name)
   201  	if v := sp.cache.Get(name); v != "" {
   202  		filePath, isDir = sp.parseCacheValue(v)
   203  		if len(indexFiles) > 0 && isDir {
   204  			if name == "/" {
   205  				name = ""
   206  			}
   207  			for _, file := range indexFiles {
   208  				if v = sp.cache.Get(name + "/" + file); v != "" {
   209  					return sp.parseCacheValue(v)
   210  				}
   211  			}
   212  		}
   213  	}
   214  	return
   215  }
   216  
   217  // Remove deletes the `path` from cache files of the manager.
   218  // The parameter `path` can be either an absolute path or just a relative file name.
   219  func (sp *SPath) Remove(path string) {
   220  	if sp.cache == nil {
   221  		return
   222  	}
   223  	if gfile.Exists(path) {
   224  		for _, v := range sp.paths.Slice() {
   225  			name := gstr.Replace(path, v, "")
   226  			name = sp.formatCacheName(name)
   227  			sp.cache.Remove(name)
   228  		}
   229  	} else {
   230  		name := sp.formatCacheName(path)
   231  		sp.cache.Remove(name)
   232  	}
   233  }
   234  
   235  // Paths returns all searching directories.
   236  func (sp *SPath) Paths() []string {
   237  	return sp.paths.Slice()
   238  }
   239  
   240  // AllPaths returns all paths cached in the manager.
   241  func (sp *SPath) AllPaths() []string {
   242  	if sp.cache == nil {
   243  		return nil
   244  	}
   245  	paths := sp.cache.Keys()
   246  	if len(paths) > 0 {
   247  		sort.Strings(paths)
   248  	}
   249  	return paths
   250  }
   251  
   252  // Size returns the count of the searching directories.
   253  func (sp *SPath) Size() int {
   254  	return sp.paths.Len()
   255  }