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 }