github.com/zhongdalu/gf@v1.0.0/g/os/gspath/gspath.go (about) 1 // Copyright 2018 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 gspath implements file index and search for folders. 8 // 9 // 搜索目录管理, 10 // 可以添加搜索目录,按照添加的优先级进行文件检索,并在内部进行高效缓存处理(可选)。 11 // 注意:当开启缓存功能后,在新增/删除文件时,会存在检索延迟。 12 package gspath 13 14 import ( 15 "errors" 16 "fmt" 17 "github.com/zhongdalu/gf/g/container/garray" 18 "github.com/zhongdalu/gf/g/container/gmap" 19 "github.com/zhongdalu/gf/g/os/gfile" 20 "github.com/zhongdalu/gf/g/text/gstr" 21 "os" 22 "sort" 23 "strings" 24 ) 25 26 // 文件目录搜索管理对象 27 type SPath struct { 28 paths *garray.StringArray // 搜索路径,按照优先级进行排序 29 cache *gmap.StrStrMap // 搜索结果缓存map(如果未nil表示未启用缓存功能) 30 } 31 32 // 文件搜索缓存项 33 type SPathCacheItem struct { 34 path string // 文件/目录绝对路径 35 isDir bool // 是否目录 36 } 37 38 var ( 39 // 单个目录路径对应的SPath对象指针,用于路径检索对象复用 40 pathsMap = gmap.NewStrAnyMap() 41 pathsCacheMap = gmap.NewStrAnyMap() 42 ) 43 44 // 创建一个搜索对象 45 func New(path string, cache bool) *SPath { 46 sp := &SPath{ 47 paths: garray.NewStringArray(), 48 } 49 if cache { 50 sp.cache = gmap.NewStrStrMap() 51 } 52 if len(path) > 0 { 53 if _, err := sp.Add(path); err != nil { 54 //fmt.Errorf(err.Error()) 55 } 56 } 57 return sp 58 } 59 60 // 创建/获取一个单例的搜索对象, root必须为目录的绝对路径 61 func Get(root string, cache bool) *SPath { 62 return pathsMap.GetOrSetFuncLock(root, func() interface{} { 63 return New(root, cache) 64 }).(*SPath) 65 } 66 67 // 检索root目录(必须为绝对路径)下面的name文件的绝对路径,indexFiles用于指定当检索到的结果为目录时,同时检索是否存在这些indexFiles文件 68 func Search(root string, name string, indexFiles ...string) (filePath string, isDir bool) { 69 return Get(root, false).Search(name, indexFiles...) 70 } 71 72 // 检索root目录(必须为绝对路径)下面的name文件的绝对路径,indexFiles用于指定当检索到的结果为目录时,同时检索是否存在这些indexFiles文件 73 func SearchWithCache(root string, name string, indexFiles ...string) (filePath string, isDir bool) { 74 return Get(root, true).Search(name, indexFiles...) 75 } 76 77 // 设置搜索路径,只保留当前设置项,其他搜索路径被清空 78 func (sp *SPath) Set(path string) (realPath string, err error) { 79 realPath = gfile.RealPath(path) 80 if realPath == "" { 81 realPath, _ = sp.Search(path) 82 if realPath == "" { 83 realPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path) 84 } 85 } 86 if realPath == "" { 87 return realPath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path)) 88 } 89 // 设置的搜索路径必须为目录 90 if gfile.IsDir(realPath) { 91 realPath = strings.TrimRight(realPath, gfile.Separator) 92 if sp.paths.Search(realPath) != -1 { 93 for _, v := range sp.paths.Slice() { 94 sp.removeMonitorByPath(v) 95 } 96 } 97 sp.paths.Clear() 98 if sp.cache != nil { 99 sp.cache.Clear() 100 } 101 sp.paths.Append(realPath) 102 sp.updateCacheByPath(realPath) 103 sp.addMonitorByPath(realPath) 104 return realPath, nil 105 } else { 106 return "", errors.New(path + " should be a folder") 107 } 108 } 109 110 // 添加搜索路径 111 func (sp *SPath) Add(path string) (realPath string, err error) { 112 realPath = gfile.RealPath(path) 113 if realPath == "" { 114 realPath, _ = sp.Search(path) 115 if realPath == "" { 116 realPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path) 117 } 118 } 119 if realPath == "" { 120 return realPath, errors.New(fmt.Sprintf(`path "%s" does not exist`, path)) 121 } 122 // 添加的搜索路径必须为目录 123 if gfile.IsDir(realPath) { 124 //fmt.Println("gspath:", realPath, sp.paths.Search(realPath)) 125 // 如果已经添加则不再添加 126 if sp.paths.Search(realPath) < 0 { 127 realPath = strings.TrimRight(realPath, gfile.Separator) 128 sp.paths.Append(realPath) 129 sp.updateCacheByPath(realPath) 130 sp.addMonitorByPath(realPath) 131 } 132 return realPath, nil 133 } else { 134 return "", errors.New(path + " should be a folder") 135 } 136 } 137 138 // 给定的name只是相对文件路径,找不到该文件时,返回空字符串; 139 // 当给定indexFiles时,如果name是一个目录,那么会进一步检索其下对应的indexFiles文件是否存在,存在则返回indexFile绝对路径; 140 // 否则返回name目录绝对路径。 141 func (sp *SPath) Search(name string, indexFiles ...string) (filePath string, isDir bool) { 142 // 不使用缓存 143 if sp.cache == nil { 144 sp.paths.LockFunc(func(array []string) { 145 path := "" 146 for _, v := range array { 147 path = v + gfile.Separator + name 148 if stat, err := os.Stat(path); !os.IsNotExist(err) { 149 filePath = path 150 isDir = stat.IsDir() 151 break 152 } 153 } 154 }) 155 if len(indexFiles) > 0 && isDir { 156 if name == "/" { 157 name = "" 158 } 159 path := "" 160 for _, file := range indexFiles { 161 path = filePath + gfile.Separator + file 162 if gfile.Exists(path) { 163 filePath = path 164 isDir = false 165 break 166 } 167 } 168 } 169 return 170 } 171 // 使用缓存功能 172 name = sp.formatCacheName(name) 173 if v := sp.cache.Get(name); v != "" { 174 filePath, isDir = sp.parseCacheValue(v) 175 if len(indexFiles) > 0 && isDir { 176 if name == "/" { 177 name = "" 178 } 179 for _, file := range indexFiles { 180 if v := sp.cache.Get(name + "/" + file); v != "" { 181 return sp.parseCacheValue(v) 182 } 183 } 184 } 185 } 186 return 187 } 188 189 // 从搜索路径中移除指定的文件,这样该文件无法给搜索。 190 // path可以是绝对路径,也可以相对路径。 191 func (sp *SPath) Remove(path string) { 192 if sp.cache == nil { 193 return 194 } 195 if gfile.Exists(path) { 196 for _, v := range sp.paths.Slice() { 197 name := gstr.Replace(path, v, "") 198 name = sp.formatCacheName(name) 199 sp.cache.Remove(name) 200 } 201 } else { 202 name := sp.formatCacheName(path) 203 sp.cache.Remove(name) 204 } 205 } 206 207 // 返回当前对象搜索目录路径列表 208 func (sp *SPath) Paths() []string { 209 return sp.paths.Slice() 210 } 211 212 // 返回当前对象缓存的所有路径列表 213 func (sp *SPath) AllPaths() []string { 214 if sp.cache == nil { 215 return nil 216 } 217 paths := sp.cache.Keys() 218 if len(paths) > 0 { 219 sort.Strings(paths) 220 } 221 return paths 222 } 223 224 // 当前的搜索路径数量 225 func (sp *SPath) Size() int { 226 return sp.paths.Len() 227 }