github.com/wangyougui/gf/v2@v2.6.5/os/gres/gres_resource.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 gres 8 9 import ( 10 "context" 11 "fmt" 12 "os" 13 "path/filepath" 14 "strings" 15 16 "github.com/wangyougui/gf/v2/container/gtree" 17 "github.com/wangyougui/gf/v2/internal/intlog" 18 "github.com/wangyougui/gf/v2/os/gfile" 19 "github.com/wangyougui/gf/v2/os/gtime" 20 "github.com/wangyougui/gf/v2/text/gstr" 21 ) 22 23 type Resource struct { 24 tree *gtree.BTree 25 } 26 27 const ( 28 defaultTreeM = 100 29 ) 30 31 // New creates and returns a new resource object. 32 func New() *Resource { 33 return &Resource{ 34 tree: gtree.NewBTree(defaultTreeM, func(v1, v2 interface{}) int { 35 return strings.Compare(v1.(string), v2.(string)) 36 }), 37 } 38 } 39 40 // Add unpacks and adds the `content` into current resource object. 41 // The unnecessary parameter `prefix` indicates the prefix 42 // for each file storing into current resource object. 43 func (r *Resource) Add(content string, prefix ...string) error { 44 files, err := UnpackContent(content) 45 if err != nil { 46 intlog.Printf(context.TODO(), "Add resource files failed: %v", err) 47 return err 48 } 49 namePrefix := "" 50 if len(prefix) > 0 { 51 namePrefix = prefix[0] 52 } 53 for i := 0; i < len(files); i++ { 54 files[i].resource = r 55 r.tree.Set(namePrefix+files[i].file.Name, files[i]) 56 } 57 intlog.Printf(context.TODO(), "Add %d files to resource manager", r.tree.Size()) 58 return nil 59 } 60 61 // Load loads, unpacks and adds the data from `path` into current resource object. 62 // The unnecessary parameter `prefix` indicates the prefix 63 // for each file storing into current resource object. 64 func (r *Resource) Load(path string, prefix ...string) error { 65 realPath, err := gfile.Search(path) 66 if err != nil { 67 return err 68 } 69 return r.Add(gfile.GetContents(realPath), prefix...) 70 } 71 72 // Get returns the file with given path. 73 func (r *Resource) Get(path string) *File { 74 if path == "" { 75 return nil 76 } 77 path = strings.ReplaceAll(path, "\\", "/") 78 path = strings.ReplaceAll(path, "//", "/") 79 if path != "/" { 80 for path[len(path)-1] == '/' { 81 path = path[:len(path)-1] 82 } 83 } 84 result := r.tree.Get(path) 85 if result != nil { 86 return result.(*File) 87 } 88 return nil 89 } 90 91 // GetWithIndex searches file with `path`, if the file is directory 92 // it then does index files searching under this directory. 93 // 94 // GetWithIndex is usually used for http static file service. 95 func (r *Resource) GetWithIndex(path string, indexFiles []string) *File { 96 // Necessary for double char '/' replacement in prefix. 97 path = strings.ReplaceAll(path, "\\", "/") 98 path = strings.ReplaceAll(path, "//", "/") 99 if path != "/" { 100 for path[len(path)-1] == '/' { 101 path = path[:len(path)-1] 102 } 103 } 104 if file := r.Get(path); file != nil { 105 if len(indexFiles) > 0 && file.FileInfo().IsDir() { 106 var f *File 107 for _, name := range indexFiles { 108 if f = r.Get(path + "/" + name); f != nil { 109 return f 110 } 111 } 112 } 113 return file 114 } 115 return nil 116 } 117 118 // GetContent directly returns the content of `path`. 119 func (r *Resource) GetContent(path string) []byte { 120 file := r.Get(path) 121 if file != nil { 122 return file.Content() 123 } 124 return nil 125 } 126 127 // Contains checks whether the `path` exists in current resource object. 128 func (r *Resource) Contains(path string) bool { 129 return r.Get(path) != nil 130 } 131 132 // IsEmpty checks and returns whether the resource manager is empty. 133 func (r *Resource) IsEmpty() bool { 134 return r.tree.IsEmpty() 135 } 136 137 // ScanDir returns the files under the given path, the parameter `path` should be a folder type. 138 // 139 // The pattern parameter `pattern` supports multiple file name patterns, 140 // using the ',' symbol to separate multiple patterns. 141 // 142 // It scans directory recursively if given parameter `recursive` is true. 143 // 144 // Note that the returned files does not contain given parameter `path`. 145 func (r *Resource) ScanDir(path string, pattern string, recursive ...bool) []*File { 146 isRecursive := false 147 if len(recursive) > 0 { 148 isRecursive = recursive[0] 149 } 150 return r.doScanDir(path, pattern, isRecursive, false) 151 } 152 153 // ScanDirFile returns all sub-files with absolute paths of given `path`, 154 // It scans directory recursively if given parameter `recursive` is true. 155 // 156 // Note that it returns only files, exclusive of directories. 157 func (r *Resource) ScanDirFile(path string, pattern string, recursive ...bool) []*File { 158 isRecursive := false 159 if len(recursive) > 0 { 160 isRecursive = recursive[0] 161 } 162 return r.doScanDir(path, pattern, isRecursive, true) 163 } 164 165 // doScanDir is an internal method which scans directory 166 // and returns the absolute path list of files that are not sorted. 167 // 168 // The pattern parameter `pattern` supports multiple file name patterns, 169 // using the ',' symbol to separate multiple patterns. 170 // 171 // It scans directory recursively if given parameter `recursive` is true. 172 func (r *Resource) doScanDir(path string, pattern string, recursive bool, onlyFile bool) []*File { 173 path = strings.ReplaceAll(path, "\\", "/") 174 path = strings.ReplaceAll(path, "//", "/") 175 if path != "/" { 176 for path[len(path)-1] == '/' { 177 path = path[:len(path)-1] 178 } 179 } 180 var ( 181 name = "" 182 files = make([]*File, 0) 183 length = len(path) 184 patterns = strings.Split(pattern, ",") 185 ) 186 for i := 0; i < len(patterns); i++ { 187 patterns[i] = strings.TrimSpace(patterns[i]) 188 } 189 // Used for type checking for first entry. 190 first := true 191 r.tree.IteratorFrom(path, true, func(key, value interface{}) bool { 192 if first { 193 if !value.(*File).FileInfo().IsDir() { 194 return false 195 } 196 first = false 197 } 198 if onlyFile && value.(*File).FileInfo().IsDir() { 199 return true 200 } 201 name = key.(string) 202 if len(name) <= length { 203 return true 204 } 205 if path != name[:length] { 206 return false 207 } 208 // To avoid of, eg: /i18n and /i18n-dir 209 if !first && name[length] != '/' { 210 return true 211 } 212 if !recursive { 213 if strings.IndexByte(name[length+1:], '/') != -1 { 214 return true 215 } 216 } 217 for _, p := range patterns { 218 if match, err := filepath.Match(p, gfile.Basename(name)); err == nil && match { 219 files = append(files, value.(*File)) 220 return true 221 } 222 } 223 return true 224 }) 225 return files 226 } 227 228 // ExportOption is the option for function Export. 229 type ExportOption struct { 230 RemovePrefix string // Remove the prefix of file name from resource. 231 } 232 233 // Export exports and saves specified path `srcPath` and all its sub files to specified system path `dstPath` recursively. 234 func (r *Resource) Export(src, dst string, option ...ExportOption) error { 235 var ( 236 err error 237 name string 238 path string 239 exportOption ExportOption 240 files []*File 241 ) 242 243 if r.Get(src).FileInfo().IsDir() { 244 files = r.doScanDir(src, "*", true, false) 245 } else { 246 files = append(files, r.Get(src)) 247 } 248 249 if len(option) > 0 { 250 exportOption = option[0] 251 } 252 for _, file := range files { 253 name = file.Name() 254 if exportOption.RemovePrefix != "" { 255 name = gstr.TrimLeftStr(name, exportOption.RemovePrefix) 256 } 257 name = gstr.Trim(name, `\/`) 258 if name == "" { 259 continue 260 } 261 path = gfile.Join(dst, name) 262 if file.FileInfo().IsDir() { 263 err = gfile.Mkdir(path) 264 } else { 265 err = gfile.PutBytes(path, file.Content()) 266 } 267 if err != nil { 268 return err 269 } 270 } 271 return nil 272 } 273 274 // Dump prints the files of current resource object. 275 func (r *Resource) Dump() { 276 var info os.FileInfo 277 r.tree.Iterator(func(key, value interface{}) bool { 278 info = value.(*File).FileInfo() 279 fmt.Printf( 280 "%v %8s %s\n", 281 gtime.New(info.ModTime()).ISO8601(), 282 gfile.FormatSize(info.Size()), 283 key, 284 ) 285 return true 286 }) 287 fmt.Printf("TOTAL FILES: %d\n", r.tree.Size()) 288 }