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  }