gres_resource.go 7.4 KB

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