2021-01-07 19:34:46 +08:00
|
|
|
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
2019-08-13 13:45:01 +08:00
|
|
|
//
|
|
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
|
|
// If a copy of the MIT was not distributed with this file,
|
|
|
|
// You can obtain one at https://github.com/gogf/gf.
|
|
|
|
|
|
|
|
package gres
|
|
|
|
|
|
|
|
import (
|
2021-06-26 16:23:54 +08:00
|
|
|
"context"
|
2019-08-13 13:45:01 +08:00
|
|
|
"fmt"
|
2019-09-01 21:34:15 +08:00
|
|
|
"os"
|
2019-08-13 13:45:01 +08:00
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2021-10-11 21:41:56 +08:00
|
|
|
"github.com/gogf/gf/v2/container/gtree"
|
2021-11-15 20:49:02 +08:00
|
|
|
"github.com/gogf/gf/v2/internal/intlog"
|
2021-10-11 21:41:56 +08:00
|
|
|
"github.com/gogf/gf/v2/os/gfile"
|
2021-11-15 20:49:02 +08:00
|
|
|
"github.com/gogf/gf/v2/os/gtime"
|
2021-12-14 23:49:08 +08:00
|
|
|
"github.com/gogf/gf/v2/text/gstr"
|
2019-08-13 13:45:01 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
type Resource struct {
|
|
|
|
tree *gtree.BTree
|
|
|
|
}
|
|
|
|
|
|
|
|
const (
|
2021-10-03 00:22:06 +08:00
|
|
|
defaultTreeM = 100
|
2019-08-13 13:45:01 +08:00
|
|
|
)
|
|
|
|
|
2019-08-13 21:06:11 +08:00
|
|
|
// New creates and returns a new resource object.
|
2019-08-13 13:45:01 +08:00
|
|
|
func New() *Resource {
|
|
|
|
return &Resource{
|
2021-10-03 00:22:06 +08:00
|
|
|
tree: gtree.NewBTree(defaultTreeM, func(v1, v2 interface{}) int {
|
2019-08-13 13:45:01 +08:00
|
|
|
return strings.Compare(v1.(string), v2.(string))
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-21 18:22:47 +08:00
|
|
|
// Add unpacks and adds the `content` into current resource object.
|
|
|
|
// The unnecessary parameter `prefix` indicates the prefix
|
2019-08-13 21:06:11 +08:00
|
|
|
// for each file storing into current resource object.
|
2020-01-15 00:15:56 +08:00
|
|
|
func (r *Resource) Add(content string, prefix ...string) error {
|
2019-08-13 13:45:01 +08:00
|
|
|
files, err := UnpackContent(content)
|
|
|
|
if err != nil {
|
2021-06-26 16:23:54 +08:00
|
|
|
intlog.Printf(context.TODO(), "Add resource files failed: %v", err)
|
2019-08-13 13:45:01 +08:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
namePrefix := ""
|
|
|
|
if len(prefix) > 0 {
|
|
|
|
namePrefix = prefix[0]
|
|
|
|
}
|
|
|
|
for i := 0; i < len(files); i++ {
|
2019-08-14 22:03:52 +08:00
|
|
|
files[i].resource = r
|
|
|
|
r.tree.Set(namePrefix+files[i].file.Name, files[i])
|
2019-08-13 13:45:01 +08:00
|
|
|
}
|
2021-06-26 16:23:54 +08:00
|
|
|
intlog.Printf(context.TODO(), "Add %d files to resource manager", r.tree.Size())
|
2019-08-13 13:45:01 +08:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-21 18:22:47 +08:00
|
|
|
// Load loads, unpacks and adds the data from `path` into current resource object.
|
|
|
|
// The unnecessary parameter `prefix` indicates the prefix
|
2019-08-13 21:06:11 +08:00
|
|
|
// for each file storing into current resource object.
|
2019-08-13 13:45:01 +08:00
|
|
|
func (r *Resource) Load(path string, prefix ...string) error {
|
|
|
|
realPath, err := gfile.Search(path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-01-15 00:15:56 +08:00
|
|
|
return r.Add(gfile.GetContents(realPath), prefix...)
|
2019-08-13 13:45:01 +08:00
|
|
|
}
|
|
|
|
|
2019-08-13 21:06:11 +08:00
|
|
|
// Get returns the file with given path.
|
2019-08-13 13:45:01 +08:00
|
|
|
func (r *Resource) Get(path string) *File {
|
2019-08-19 22:54:37 +08:00
|
|
|
if path == "" {
|
|
|
|
return nil
|
|
|
|
}
|
2022-11-01 20:12:21 +08:00
|
|
|
path = strings.ReplaceAll(path, "\\", "/")
|
|
|
|
path = strings.ReplaceAll(path, "//", "/")
|
2019-08-14 22:03:52 +08:00
|
|
|
if path != "/" {
|
|
|
|
for path[len(path)-1] == '/' {
|
|
|
|
path = path[:len(path)-1]
|
|
|
|
}
|
|
|
|
}
|
2019-08-13 13:45:01 +08:00
|
|
|
result := r.tree.Get(path)
|
|
|
|
if result != nil {
|
|
|
|
return result.(*File)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-21 18:22:47 +08:00
|
|
|
// GetWithIndex searches file with `path`, if the file is directory
|
2019-08-14 22:03:52 +08:00
|
|
|
// it then does index files searching under this directory.
|
|
|
|
//
|
|
|
|
// GetWithIndex is usually used for http static file service.
|
|
|
|
func (r *Resource) GetWithIndex(path string, indexFiles []string) *File {
|
2020-05-14 20:32:01 +08:00
|
|
|
// Necessary for double char '/' replacement in prefix.
|
2022-11-01 20:12:21 +08:00
|
|
|
path = strings.ReplaceAll(path, "\\", "/")
|
|
|
|
path = strings.ReplaceAll(path, "//", "/")
|
2019-08-14 22:03:52 +08:00
|
|
|
if path != "/" {
|
|
|
|
for path[len(path)-1] == '/' {
|
|
|
|
path = path[:len(path)-1]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if file := r.Get(path); file != nil {
|
|
|
|
if len(indexFiles) > 0 && file.FileInfo().IsDir() {
|
|
|
|
var f *File
|
|
|
|
for _, name := range indexFiles {
|
|
|
|
if f = r.Get(path + "/" + name); f != nil {
|
|
|
|
return f
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return file
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-21 18:22:47 +08:00
|
|
|
// GetContent directly returns the content of `path`.
|
2019-08-16 00:29:14 +08:00
|
|
|
func (r *Resource) GetContent(path string) []byte {
|
|
|
|
file := r.Get(path)
|
|
|
|
if file != nil {
|
|
|
|
return file.Content()
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2021-10-21 18:22:47 +08:00
|
|
|
// Contains checks whether the `path` exists in current resource object.
|
2019-08-16 00:29:14 +08:00
|
|
|
func (r *Resource) Contains(path string) bool {
|
|
|
|
return r.Get(path) != nil
|
|
|
|
}
|
|
|
|
|
2019-08-19 21:02:44 +08:00
|
|
|
// IsEmpty checks and returns whether the resource manager is empty.
|
|
|
|
func (r *Resource) IsEmpty() bool {
|
|
|
|
return r.tree.IsEmpty()
|
|
|
|
}
|
|
|
|
|
2021-10-21 18:22:47 +08:00
|
|
|
// ScanDir returns the files under the given path, the parameter `path` should be a folder type.
|
2019-08-13 21:06:11 +08:00
|
|
|
//
|
2021-10-21 18:22:47 +08:00
|
|
|
// The pattern parameter `pattern` supports multiple file name patterns,
|
2019-08-13 21:06:11 +08:00
|
|
|
// using the ',' symbol to separate multiple patterns.
|
|
|
|
//
|
2021-10-21 18:22:47 +08:00
|
|
|
// It scans directory recursively if given parameter `recursive` is true.
|
2019-09-01 22:14:31 +08:00
|
|
|
//
|
2021-10-21 18:22:47 +08:00
|
|
|
// Note that the returned files does not contain given parameter `path`.
|
2019-08-31 18:04:12 +08:00
|
|
|
func (r *Resource) ScanDir(path string, pattern string, recursive ...bool) []*File {
|
|
|
|
isRecursive := false
|
|
|
|
if len(recursive) > 0 {
|
|
|
|
isRecursive = recursive[0]
|
|
|
|
}
|
|
|
|
return r.doScanDir(path, pattern, isRecursive, false)
|
|
|
|
}
|
|
|
|
|
2021-10-21 18:22:47 +08:00
|
|
|
// ScanDirFile returns all sub-files with absolute paths of given `path`,
|
|
|
|
// It scans directory recursively if given parameter `recursive` is true.
|
2019-08-31 18:04:12 +08:00
|
|
|
//
|
|
|
|
// Note that it returns only files, exclusive of directories.
|
|
|
|
func (r *Resource) ScanDirFile(path string, pattern string, recursive ...bool) []*File {
|
|
|
|
isRecursive := false
|
|
|
|
if len(recursive) > 0 {
|
|
|
|
isRecursive = recursive[0]
|
|
|
|
}
|
|
|
|
return r.doScanDir(path, pattern, isRecursive, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
// doScanDir is an internal method which scans directory
|
|
|
|
// and returns the absolute path list of files that are not sorted.
|
|
|
|
//
|
2021-10-21 18:22:47 +08:00
|
|
|
// The pattern parameter `pattern` supports multiple file name patterns,
|
2019-08-31 18:04:12 +08:00
|
|
|
// using the ',' symbol to separate multiple patterns.
|
|
|
|
//
|
2021-10-21 18:22:47 +08:00
|
|
|
// It scans directory recursively if given parameter `recursive` is true.
|
2019-08-31 18:04:12 +08:00
|
|
|
func (r *Resource) doScanDir(path string, pattern string, recursive bool, onlyFile bool) []*File {
|
2022-11-01 20:12:21 +08:00
|
|
|
path = strings.ReplaceAll(path, "\\", "/")
|
|
|
|
path = strings.ReplaceAll(path, "//", "/")
|
2019-08-13 13:45:01 +08:00
|
|
|
if path != "/" {
|
2019-08-14 22:03:52 +08:00
|
|
|
for path[len(path)-1] == '/' {
|
|
|
|
path = path[:len(path)-1]
|
|
|
|
}
|
2019-08-13 13:45:01 +08:00
|
|
|
}
|
2020-05-14 20:32:01 +08:00
|
|
|
var (
|
|
|
|
name = ""
|
|
|
|
files = make([]*File, 0)
|
|
|
|
length = len(path)
|
|
|
|
patterns = strings.Split(pattern, ",")
|
|
|
|
)
|
2019-08-13 13:45:01 +08:00
|
|
|
for i := 0; i < len(patterns); i++ {
|
|
|
|
patterns[i] = strings.TrimSpace(patterns[i])
|
|
|
|
}
|
2019-08-14 22:03:52 +08:00
|
|
|
// Used for type checking for first entry.
|
|
|
|
first := true
|
|
|
|
r.tree.IteratorFrom(path, true, func(key, value interface{}) bool {
|
|
|
|
if first {
|
|
|
|
if !value.(*File).FileInfo().IsDir() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
first = false
|
|
|
|
}
|
2019-08-31 18:04:12 +08:00
|
|
|
if onlyFile && value.(*File).FileInfo().IsDir() {
|
|
|
|
return true
|
|
|
|
}
|
2019-08-13 13:45:01 +08:00
|
|
|
name = key.(string)
|
2019-08-14 22:03:52 +08:00
|
|
|
if len(name) <= length {
|
|
|
|
return true
|
|
|
|
}
|
2019-08-13 13:45:01 +08:00
|
|
|
if path != name[:length] {
|
|
|
|
return false
|
|
|
|
}
|
2019-08-31 18:04:12 +08:00
|
|
|
// To avoid of, eg: /i18n and /i18n-dir
|
|
|
|
if !first && name[length] != '/' {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
if !recursive {
|
2019-08-14 22:03:52 +08:00
|
|
|
if strings.IndexByte(name[length+1:], '/') != -1 {
|
2019-08-13 21:06:11 +08:00
|
|
|
return true
|
2019-08-13 13:45:01 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, p := range patterns {
|
|
|
|
if match, err := filepath.Match(p, gfile.Basename(name)); err == nil && match {
|
|
|
|
files = append(files, value.(*File))
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
})
|
|
|
|
return files
|
|
|
|
}
|
|
|
|
|
2021-12-14 23:49:08 +08:00
|
|
|
// ExportOption is the option for function Export.
|
|
|
|
type ExportOption struct {
|
|
|
|
RemovePrefix string // Remove the prefix of file name from resource.
|
|
|
|
}
|
|
|
|
|
|
|
|
// Export exports and saves specified path `srcPath` and all its sub files to specified system path `dstPath` recursively.
|
|
|
|
func (r *Resource) Export(src, dst string, option ...ExportOption) error {
|
2021-12-14 23:01:20 +08:00
|
|
|
var (
|
2021-12-14 23:49:08 +08:00
|
|
|
err error
|
|
|
|
name string
|
|
|
|
path string
|
|
|
|
exportOption ExportOption
|
2022-07-04 20:17:00 +08:00
|
|
|
files []*File
|
2021-12-14 23:01:20 +08:00
|
|
|
)
|
2022-07-04 20:17:00 +08:00
|
|
|
|
|
|
|
if r.Get(src).FileInfo().IsDir() {
|
|
|
|
files = r.doScanDir(src, "*", true, false)
|
|
|
|
} else {
|
|
|
|
files = append(files, r.Get(src))
|
|
|
|
}
|
|
|
|
|
2021-12-14 23:49:08 +08:00
|
|
|
if len(option) > 0 {
|
|
|
|
exportOption = option[0]
|
|
|
|
}
|
2021-12-14 23:01:20 +08:00
|
|
|
for _, file := range files {
|
2021-12-14 23:49:08 +08:00
|
|
|
name = file.Name()
|
|
|
|
if exportOption.RemovePrefix != "" {
|
|
|
|
name = gstr.TrimLeftStr(name, exportOption.RemovePrefix)
|
|
|
|
}
|
|
|
|
name = gstr.Trim(name, `\/`)
|
|
|
|
if name == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
path = gfile.Join(dst, name)
|
2021-12-14 23:01:20 +08:00
|
|
|
if file.FileInfo().IsDir() {
|
|
|
|
err = gfile.Mkdir(path)
|
|
|
|
} else {
|
|
|
|
err = gfile.PutBytes(path, file.Content())
|
|
|
|
}
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-08-13 21:06:11 +08:00
|
|
|
// Dump prints the files of current resource object.
|
2019-08-13 13:45:01 +08:00
|
|
|
func (r *Resource) Dump() {
|
2019-09-01 21:34:15 +08:00
|
|
|
var info os.FileInfo
|
2019-08-13 13:45:01 +08:00
|
|
|
r.tree.Iterator(func(key, value interface{}) bool {
|
2019-09-01 21:34:15 +08:00
|
|
|
info = value.(*File).FileInfo()
|
2021-12-14 23:01:20 +08:00
|
|
|
fmt.Printf(
|
2022-10-14 10:10:27 +08:00
|
|
|
"%v %8s %s\n",
|
2021-12-14 23:01:20 +08:00
|
|
|
gtime.New(info.ModTime()).ISO8601(),
|
|
|
|
gfile.FormatSize(info.Size()),
|
|
|
|
key,
|
|
|
|
)
|
2019-08-13 13:45:01 +08:00
|
|
|
return true
|
|
|
|
})
|
2019-08-13 21:06:11 +08:00
|
|
|
fmt.Printf("TOTAL FILES: %d\n", r.tree.Size())
|
2019-08-13 13:45:01 +08:00
|
|
|
}
|