mirror of
https://gitee.com/johng/gf
synced 2026-07-07 14:25:17 +08:00
up
This commit is contained in:
@ -7,11 +7,6 @@
|
||||
// Package gres provides resource management and packing/unpacking feature between files and bytes.
|
||||
package gres
|
||||
|
||||
const (
|
||||
// Separator for directories.
|
||||
Separator = "/"
|
||||
)
|
||||
|
||||
var (
|
||||
// Default resource object.
|
||||
defaultResource = Instance()
|
||||
@ -56,7 +51,7 @@ func Contains(path string) bool {
|
||||
|
||||
// IsEmpty checks and returns whether the resource manager is empty.
|
||||
func IsEmpty() bool {
|
||||
return defaultResource.tree.IsEmpty()
|
||||
return defaultResource.IsEmpty()
|
||||
}
|
||||
|
||||
// ScanDir returns the files under the given path, the parameter `path` should be a folder type.
|
||||
|
||||
@ -7,62 +7,192 @@
|
||||
package gres
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/json"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// File implements the interface fs.File.
|
||||
type File struct {
|
||||
file *zip.File
|
||||
reader *bytes.Reader
|
||||
resource *Resource
|
||||
name string // Name is the file name
|
||||
path string // Path is the file path
|
||||
file os.FileInfo // FileInfo is the underlying file info
|
||||
reader io.Reader // Reader is the file content reader
|
||||
resource []byte // Resource is the file content in binary format
|
||||
fs FS // FS is the file system that contains this file
|
||||
}
|
||||
|
||||
// Name returns the name of the file.
|
||||
// Name returns the name of the file
|
||||
func (f *File) Name() string {
|
||||
return f.file.Name
|
||||
return f.name
|
||||
}
|
||||
|
||||
// Open returns a ReadCloser that provides access to the File's contents.
|
||||
// Multiple files may be read concurrently.
|
||||
func (f *File) Open() (io.ReadCloser, error) {
|
||||
return f.file.Open()
|
||||
// Path returns the path of the file
|
||||
func (f *File) Path() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
// Content returns the content of the file.
|
||||
func (f *File) Content() []byte {
|
||||
reader, err := f.Open()
|
||||
if err != nil {
|
||||
return nil
|
||||
// Open opens the file for reading
|
||||
func (f *File) Open() error {
|
||||
if f.reader == nil && len(f.resource) > 0 {
|
||||
f.reader = bytes.NewReader(f.resource)
|
||||
}
|
||||
defer reader.Close()
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if _, err = io.Copy(buffer, reader); err != nil {
|
||||
return nil
|
||||
}
|
||||
return buffer.Bytes()
|
||||
return nil
|
||||
}
|
||||
|
||||
// FileInfo returns an os.FileInfo for the FileHeader.
|
||||
// Close closes the file
|
||||
func (f *File) Close() error {
|
||||
if closer, ok := f.reader.(io.Closer); ok {
|
||||
return closer.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read reads up to len(p) bytes into p
|
||||
func (f *File) Read(p []byte) (n int, err error) {
|
||||
if f.reader == nil {
|
||||
if err := f.Open(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
return f.reader.Read(p)
|
||||
}
|
||||
|
||||
// Seek implements the io.Seeker interface
|
||||
func (f *File) Seek(offset int64, whence int) (int64, error) {
|
||||
if seeker, ok := f.reader.(io.Seeker); ok {
|
||||
return seeker.Seek(offset, whence)
|
||||
}
|
||||
return 0, fs.ErrInvalid
|
||||
}
|
||||
|
||||
// FileInfo returns an os.FileInfo describing this file
|
||||
func (f *File) FileInfo() os.FileInfo {
|
||||
return f.file.FileInfo()
|
||||
return f.file
|
||||
}
|
||||
|
||||
// Stat returns the FileInfo structure describing file
|
||||
func (f *File) Stat() (os.FileInfo, error) {
|
||||
return f.file, nil
|
||||
}
|
||||
|
||||
// Content returns the file content
|
||||
func (f *File) Content() []byte {
|
||||
if len(f.resource) > 0 {
|
||||
return f.resource
|
||||
}
|
||||
buffer := new(bytes.Buffer)
|
||||
if err := f.Open(); err != nil {
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
if _, err := io.Copy(buffer, f); err != nil {
|
||||
return nil
|
||||
}
|
||||
f.resource = buffer.Bytes()
|
||||
return f.resource
|
||||
}
|
||||
|
||||
// Export exports and saves all its sub files to specified system path `dst` recursively.
|
||||
func (f *File) Export(dst string, option ...ExportOption) error {
|
||||
return f.resource.Export(f.Name(), dst, option...)
|
||||
var (
|
||||
err error
|
||||
name string
|
||||
path string
|
||||
exportOption ExportOption
|
||||
exportFiles []*File
|
||||
)
|
||||
|
||||
if f.FileInfo().IsDir() {
|
||||
exportFiles = f.fs.ScanDir(f.path, "*", true)
|
||||
} else {
|
||||
exportFiles = append(exportFiles, f)
|
||||
}
|
||||
|
||||
if len(option) > 0 {
|
||||
exportOption = option[0]
|
||||
}
|
||||
for _, exportFile := range exportFiles {
|
||||
name = exportFile.Name()
|
||||
if exportOption.RemovePrefix != "" {
|
||||
name = gstr.TrimLeftStr(name, exportOption.RemovePrefix)
|
||||
}
|
||||
name = gstr.Trim(name, `\/`)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
path = gfile.Join(dst, name)
|
||||
if exportFile.FileInfo().IsDir() {
|
||||
err = gfile.Mkdir(path)
|
||||
} else {
|
||||
err = gfile.PutBytes(path, exportFile.Content())
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (f File) MarshalJSON() ([]byte, error) {
|
||||
func (f *File) MarshalJSON() ([]byte, error) {
|
||||
info := f.FileInfo()
|
||||
return json.Marshal(map[string]interface{}{
|
||||
"name": f.Name(),
|
||||
"size": info.Size(),
|
||||
"time": info.ModTime(),
|
||||
"file": !info.IsDir(),
|
||||
return gjson.Marshal(map[string]interface{}{
|
||||
"name": f.name,
|
||||
"path": f.path,
|
||||
"size": info.Size(),
|
||||
"time": info.ModTime(),
|
||||
"isDir": info.IsDir(),
|
||||
"content": f.Content(),
|
||||
})
|
||||
}
|
||||
|
||||
// fileInfo is the internal implementation of os.FileInfo interface.
|
||||
type fileInfo struct {
|
||||
file *File
|
||||
}
|
||||
|
||||
// Name returns the base name of the file.
|
||||
func (fi *fileInfo) Name() string {
|
||||
return fi.file.Name()
|
||||
}
|
||||
|
||||
// Size returns the size in bytes of the file.
|
||||
func (fi *fileInfo) Size() int64 {
|
||||
return int64(len(fi.file.Content()))
|
||||
}
|
||||
|
||||
// Mode returns the file mode bits.
|
||||
func (fi *fileInfo) Mode() fs.FileMode {
|
||||
if fi.IsDir() {
|
||||
return os.ModeDir | 0755
|
||||
}
|
||||
return 0644
|
||||
}
|
||||
|
||||
// ModTime returns the modification time.
|
||||
func (fi *fileInfo) ModTime() time.Time {
|
||||
if fi.file.file != nil {
|
||||
return fi.file.file.ModTime()
|
||||
}
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
// IsDir reports whether the file is a directory.
|
||||
func (fi *fileInfo) IsDir() bool {
|
||||
if fi.file.file != nil {
|
||||
return fi.file.file.IsDir()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Sys returns the underlying data source.
|
||||
func (fi *fileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
25
os/gres/gres_fs.go
Normal file
25
os/gres/gres_fs.go
Normal file
@ -0,0 +1,25 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
// FS is the interface that defines a virtual file system.
|
||||
type FS interface {
|
||||
// Get returns the file with given path.
|
||||
Get(path string) *File
|
||||
|
||||
// IsEmpty checks and returns whether the resource is empty.
|
||||
IsEmpty() bool
|
||||
|
||||
// ScanDir returns the files under the given path,
|
||||
// the parameter `path` should be a folder type.
|
||||
ScanDir(path string, pattern string, recursive ...bool) []*File
|
||||
}
|
||||
|
||||
// ExportOption contains options for Export.
|
||||
type ExportOption struct {
|
||||
RemovePrefix string // Remove the prefix from source file before export.
|
||||
}
|
||||
87
os/gres/gres_fs_mixed.go
Normal file
87
os/gres/gres_fs_mixed.go
Normal file
@ -0,0 +1,87 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// 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 (
|
||||
"io/fs"
|
||||
)
|
||||
|
||||
// MixedFS implements the FS interface by combining StdFS and ResFS.
|
||||
// It prioritizes using StdFS and falls back to ResFS when file not found.
|
||||
type MixedFS struct {
|
||||
stdFS *StdFS
|
||||
resFS *ResFS
|
||||
}
|
||||
|
||||
// NewMixedFS creates and returns a new MixedFS.
|
||||
func NewMixedFS(stdFs fs.FS, resFS *ResFS) *MixedFS {
|
||||
return &MixedFS{
|
||||
resFS: resFS,
|
||||
stdFS: NewStdFS(stdFs),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the file with given path.
|
||||
func (fs *MixedFS) Get(path string) *File {
|
||||
if file := fs.resFS.Get(path); file != nil {
|
||||
return file
|
||||
}
|
||||
return fs.stdFS.Get(path)
|
||||
}
|
||||
|
||||
// IsEmpty checks and returns whether the resource is empty.
|
||||
func (fs *MixedFS) IsEmpty() bool {
|
||||
return fs.resFS.IsEmpty() && fs.stdFS.IsEmpty()
|
||||
}
|
||||
|
||||
// ScanDir returns the files under the given path,
|
||||
// the parameter `path` should be a folder type.
|
||||
func (fs *MixedFS) ScanDir(path string, pattern string, recursive ...bool) []*File {
|
||||
var (
|
||||
filesMap = make(map[string]*File)
|
||||
files = make([]*File, 0)
|
||||
)
|
||||
|
||||
// Get files from ResFS
|
||||
defaultFiles := fs.resFS.ScanDir(path, pattern, recursive...)
|
||||
for _, file := range defaultFiles {
|
||||
if _, exists := filesMap[file.Path()]; !exists {
|
||||
filesMap[file.Path()] = file
|
||||
}
|
||||
}
|
||||
|
||||
// Get files from StdFS
|
||||
stdFiles := fs.stdFS.ScanDir(path, pattern, recursive...)
|
||||
if len(stdFiles) > 0 {
|
||||
|
||||
}
|
||||
for _, file := range stdFiles {
|
||||
filesMap[file.Path()] = file
|
||||
}
|
||||
|
||||
// Convert map to slice
|
||||
for _, file := range filesMap {
|
||||
files = append(files, file)
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
|
||||
// ScanDirFile returns all sub-files with absolute paths of given `path`,
|
||||
// It scans directory recursively if given parameter `recursive` is true.
|
||||
func (fs *MixedFS) ScanDirFile(path string, pattern string, recursive ...bool) []*File {
|
||||
return fs.ScanDir(path, pattern, recursive...)
|
||||
}
|
||||
|
||||
// Export exports and saves specified path `src` and all its sub files
|
||||
// to specified system path `dst` recursively.
|
||||
func (fs *MixedFS) Export(src, dst string, option ...ExportOption) error {
|
||||
if file := fs.Get(src); file != nil {
|
||||
return file.Export(dst, option...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
178
os/gres/gres_fs_res.go
Normal file
178
os/gres/gres_fs_res.go
Normal file
@ -0,0 +1,178 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// 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 (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtree"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// ResFS implements the FS interface using the default resource implementation.
|
||||
type ResFS struct {
|
||||
tree *gtree.BTree // The tree storing all resource files.
|
||||
}
|
||||
|
||||
const (
|
||||
defaultTreeM = 100
|
||||
)
|
||||
|
||||
// NewResFS creates and returns a new ResFS.
|
||||
func NewResFS() *ResFS {
|
||||
return &ResFS{
|
||||
tree: gtree.NewBTree(defaultTreeM, func(v1, v2 interface{}) int {
|
||||
return strings.Compare(v1.(string), v2.(string))
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the file with given path.
|
||||
func (fs *ResFS) Get(path string) *File {
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
path = strings.ReplaceAll(path, "\\", "/")
|
||||
path = strings.ReplaceAll(path, "//", "/")
|
||||
if path != "/" {
|
||||
for path[len(path)-1] == '/' {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
}
|
||||
result := fs.tree.Get(path)
|
||||
if result != nil {
|
||||
return result.(*File)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsEmpty checks and returns whether the resource is empty.
|
||||
func (fs *ResFS) IsEmpty() bool {
|
||||
return fs.tree.IsEmpty()
|
||||
}
|
||||
|
||||
// ScanDir returns the files under the given path,
|
||||
// the parameter `path` should be a folder type.
|
||||
func (fs *ResFS) ScanDir(path string, pattern string, recursive ...bool) []*File {
|
||||
isRecursive := false
|
||||
if len(recursive) > 0 {
|
||||
isRecursive = recursive[0]
|
||||
}
|
||||
return fs.doScanDir(path, pattern, isRecursive)
|
||||
}
|
||||
|
||||
// doScanDir is an internal method which scans directory
|
||||
// and returns the absolute path list of files that are not sorted.
|
||||
//
|
||||
// The pattern parameter `pattern` supports multiple file name patterns,
|
||||
// using the ',' symbol to separate multiple patterns.
|
||||
//
|
||||
// It scans directory recursively if given parameter `recursive` is true.
|
||||
func (fs *ResFS) doScanDir(path string, pattern string, recursive bool) []*File {
|
||||
path = strings.ReplaceAll(path, "\\", "/")
|
||||
path = strings.ReplaceAll(path, "//", "/")
|
||||
if path != "/" {
|
||||
for path[len(path)-1] == '/' {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
}
|
||||
var (
|
||||
files = make([]*File, 0)
|
||||
patterns = strings.Split(pattern, ",")
|
||||
)
|
||||
for i := 0; i < len(patterns); i++ {
|
||||
patterns[i] = strings.TrimSpace(patterns[i])
|
||||
}
|
||||
|
||||
// Get root directory
|
||||
rootFile := fs.Get(path)
|
||||
if rootFile == nil || !rootFile.FileInfo().IsDir() {
|
||||
return files
|
||||
}
|
||||
|
||||
// Walk through the tree to find matching files
|
||||
fs.tree.IteratorAsc(func(key, value interface{}) bool {
|
||||
var (
|
||||
file = value.(*File)
|
||||
filePath = key.(string)
|
||||
)
|
||||
|
||||
// Skip if not under the target path
|
||||
if !strings.HasPrefix(filePath, path) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Skip if not recursive and file is in subdirectory
|
||||
if !recursive {
|
||||
relPath := strings.TrimPrefix(filePath, path)
|
||||
if strings.Contains(relPath, "/") {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check if file matches any pattern
|
||||
name := gfile.Basename(filePath)
|
||||
for _, p := range patterns {
|
||||
if match, _ := filepath.Match(p, name); match {
|
||||
files = append(files, file)
|
||||
break
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
return files
|
||||
}
|
||||
|
||||
// Add adds the `content` into current ResFS with given `prefix`.
|
||||
func (fs *ResFS) Add(content string, prefix ...string) error {
|
||||
files, err := UnpackContent(content)
|
||||
if err != nil {
|
||||
intlog.Printf(context.TODO(), "Add resource files failed: %v", err)
|
||||
return err
|
||||
}
|
||||
namePrefix := ""
|
||||
if len(prefix) > 0 {
|
||||
namePrefix = prefix[0]
|
||||
}
|
||||
for i := 0; i < len(files); i++ {
|
||||
files[i].fs = fs
|
||||
fs.tree.Set(namePrefix+files[i].Path(), files[i])
|
||||
}
|
||||
intlog.Printf(context.TODO(), "Add %d files to resource manager", fs.tree.Size())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load loads, unpacks and adds the data from `path` into ResFS.
|
||||
func (fs *ResFS) Load(path string, prefix ...string) error {
|
||||
realPath, err := gfile.Search(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return fs.Add(gfile.GetContents(realPath), prefix...)
|
||||
}
|
||||
|
||||
// Dump prints the files of ResFS.
|
||||
func (fs *ResFS) Dump() {
|
||||
var info os.FileInfo
|
||||
fs.tree.Iterator(func(key, value interface{}) bool {
|
||||
info = value.(*File).FileInfo()
|
||||
intlog.Printf(
|
||||
context.TODO(),
|
||||
"%v %8s %s",
|
||||
gtime.New(info.ModTime()).ISO8601(),
|
||||
gfile.FormatSize(info.Size()),
|
||||
key,
|
||||
)
|
||||
return true
|
||||
})
|
||||
intlog.Printf(context.TODO(), "TOTAL FILES: %d", fs.tree.Size())
|
||||
}
|
||||
150
os/gres/gres_fs_std.go
Normal file
150
os/gres/gres_fs_std.go
Normal file
@ -0,0 +1,150 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
||||
//
|
||||
// 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 (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
// StdFS implements the FS interface using the standard library fs.FS.
|
||||
type StdFS struct {
|
||||
fs fs.FS
|
||||
}
|
||||
|
||||
// NewStdFS creates and returns a new StdFS.
|
||||
func NewStdFS(fs fs.FS) *StdFS {
|
||||
return &StdFS{
|
||||
fs: fs,
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the file with given path.
|
||||
func (fs *StdFS) Get(path string) *File {
|
||||
f, err := fs.fs.Open(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read the content
|
||||
content, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
file := &File{
|
||||
name: info.Name(),
|
||||
path: path,
|
||||
file: info,
|
||||
resource: content,
|
||||
fs: fs,
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
// IsEmpty checks and returns whether the resource is empty.
|
||||
func (fs *StdFS) IsEmpty() bool {
|
||||
if dir, ok := fs.fs.(interface {
|
||||
ReadDir(name string) ([]os.DirEntry, error)
|
||||
}); ok {
|
||||
entries, err := dir.ReadDir(".")
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
return len(entries) == 0
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// ScanDir returns the files under the given path,
|
||||
// the parameter `path` should be a folder type.
|
||||
func (fs *StdFS) ScanDir(path string, pattern string, recursive ...bool) []*File {
|
||||
var (
|
||||
files = make([]*File, 0)
|
||||
isRecursive = len(recursive) > 0 && recursive[0]
|
||||
)
|
||||
err := fs.walkDir(path, func(path string, d os.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !d.IsDir() {
|
||||
matched, err := filepath.Match(pattern, filepath.Base(path))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if matched {
|
||||
if file := fs.Get(path); file != nil {
|
||||
files = append(files, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !isRecursive && d.IsDir() && path != "." {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
// walkDir walks the file tree rooted at path, calling fn for each file or
|
||||
// directory in the tree, including path.
|
||||
func (fs *StdFS) walkDir(path string, fn func(path string, d os.DirEntry, err error) error) error {
|
||||
if dir, ok := fs.fs.(interface {
|
||||
ReadDir(name string) ([]os.DirEntry, error)
|
||||
}); ok {
|
||||
entries, err := dir.ReadDir(path)
|
||||
if err != nil {
|
||||
err = fn(path, nil, err)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
var (
|
||||
fileName = entry.Name()
|
||||
filePath = filepath.Join(path, fileName)
|
||||
)
|
||||
err = fn(filePath, entry, nil)
|
||||
if err != nil {
|
||||
if gerror.Is(err, filepath.SkipDir) {
|
||||
if entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
if entry.IsDir() {
|
||||
err = fs.walkDir(filePath, fn)
|
||||
if err != nil {
|
||||
if gerror.Is(err, filepath.SkipDir) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return gerror.New("filesystem does not implement ReadDir")
|
||||
}
|
||||
@ -173,7 +173,14 @@ func UnpackContent(content string) ([]*File, error) {
|
||||
}
|
||||
array := make([]*File, len(reader.File))
|
||||
for i, file := range reader.File {
|
||||
array[i] = &File{file: file}
|
||||
array[i] = &File{
|
||||
name: file.Name,
|
||||
path: file.Name,
|
||||
file: file.FileInfo(),
|
||||
reader: nil,
|
||||
resource: nil,
|
||||
fs: nil,
|
||||
}
|
||||
}
|
||||
return array, nil
|
||||
}
|
||||
|
||||
@ -8,90 +8,35 @@ package gres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/container/gtree"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// Resource implements the FS interface.
|
||||
type Resource struct {
|
||||
tree *gtree.BTree
|
||||
fs FS
|
||||
}
|
||||
|
||||
const (
|
||||
defaultTreeM = 100
|
||||
)
|
||||
|
||||
// New creates and returns a new resource object.
|
||||
func New() *Resource {
|
||||
return NewWithFS(NewResFS())
|
||||
}
|
||||
|
||||
// NewWithFS sets the underlying file system implementation.
|
||||
func NewWithFS(fs FS) *Resource {
|
||||
return &Resource{
|
||||
tree: gtree.NewBTree(defaultTreeM, func(v1, v2 interface{}) int {
|
||||
return strings.Compare(v1.(string), v2.(string))
|
||||
}),
|
||||
fs: fs,
|
||||
}
|
||||
}
|
||||
|
||||
// Add unpacks and adds the `content` into current resource object.
|
||||
// The unnecessary parameter `prefix` indicates the prefix
|
||||
// for each file storing into current resource object.
|
||||
func (r *Resource) Add(content string, prefix ...string) error {
|
||||
files, err := UnpackContent(content)
|
||||
if err != nil {
|
||||
intlog.Printf(context.TODO(), "Add resource files failed: %v", err)
|
||||
return err
|
||||
}
|
||||
namePrefix := ""
|
||||
if len(prefix) > 0 {
|
||||
namePrefix = prefix[0]
|
||||
}
|
||||
for i := 0; i < len(files); i++ {
|
||||
files[i].resource = r
|
||||
r.tree.Set(namePrefix+files[i].file.Name, files[i])
|
||||
}
|
||||
intlog.Printf(context.TODO(), "Add %d files to resource manager", r.tree.Size())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load loads, unpacks and adds the data from `path` into current resource object.
|
||||
// The unnecessary parameter `prefix` indicates the prefix
|
||||
// for each file storing into current resource object.
|
||||
func (r *Resource) Load(path string, prefix ...string) error {
|
||||
realPath, err := gfile.Search(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.Add(gfile.GetContents(realPath), prefix...)
|
||||
}
|
||||
|
||||
// Get returns the file with given path.
|
||||
func (r *Resource) Get(path string) *File {
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
path = strings.ReplaceAll(path, "\\", "/")
|
||||
path = strings.ReplaceAll(path, "//", "/")
|
||||
if path != "/" {
|
||||
for path[len(path)-1] == '/' {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
}
|
||||
result := r.tree.Get(path)
|
||||
if result != nil {
|
||||
return result.(*File)
|
||||
}
|
||||
return nil
|
||||
return r.fs.Get(path)
|
||||
}
|
||||
|
||||
// GetWithIndex searches file with `path`, if the file is directory
|
||||
// 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 {
|
||||
// Necessary for double char '/' replacement in prefix.
|
||||
path = strings.ReplaceAll(path, "\\", "/")
|
||||
@ -101,11 +46,11 @@ func (r *Resource) GetWithIndex(path string, indexFiles []string) *File {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
}
|
||||
if file := r.Get(path); file != nil {
|
||||
if file := r.fs.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 {
|
||||
if f = r.fs.Get(path + "/" + name); f != nil {
|
||||
return f
|
||||
}
|
||||
}
|
||||
@ -126,163 +71,52 @@ func (r *Resource) GetContent(path string) []byte {
|
||||
|
||||
// Contains checks whether the `path` exists in current resource object.
|
||||
func (r *Resource) Contains(path string) bool {
|
||||
return r.Get(path) != nil
|
||||
return r.Get(path) == nil
|
||||
}
|
||||
|
||||
// IsEmpty checks and returns whether the resource manager is empty.
|
||||
func (r *Resource) IsEmpty() bool {
|
||||
return r.tree.IsEmpty()
|
||||
return r.fs.IsEmpty()
|
||||
}
|
||||
|
||||
// ScanDir returns the files under the given path, the parameter `path` should be a folder type.
|
||||
//
|
||||
// The pattern parameter `pattern` supports multiple file name patterns,
|
||||
// using the ',' symbol to separate multiple patterns.
|
||||
//
|
||||
// It scans directory recursively if given parameter `recursive` is true.
|
||||
//
|
||||
// Note that the returned files does not contain given parameter `path`.
|
||||
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)
|
||||
return r.fs.ScanDir(path, pattern, recursive...)
|
||||
}
|
||||
|
||||
// ScanDirFile returns all sub-files with absolute paths of given `path`,
|
||||
// It scans directory recursively if given parameter `recursive` is true.
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// The pattern parameter `pattern` supports multiple file name patterns,
|
||||
// using the ',' symbol to separate multiple patterns.
|
||||
//
|
||||
// It scans directory recursively if given parameter `recursive` is true.
|
||||
func (r *Resource) doScanDir(path string, pattern string, recursive bool, onlyFile bool) []*File {
|
||||
path = strings.ReplaceAll(path, "\\", "/")
|
||||
path = strings.ReplaceAll(path, "//", "/")
|
||||
if path != "/" {
|
||||
for path[len(path)-1] == '/' {
|
||||
path = path[:len(path)-1]
|
||||
}
|
||||
}
|
||||
var (
|
||||
name = ""
|
||||
files = make([]*File, 0)
|
||||
length = len(path)
|
||||
patterns = strings.Split(pattern, ",")
|
||||
result = make([]*File, 0)
|
||||
files = r.fs.ScanDir(path, pattern, recursive...)
|
||||
)
|
||||
for i := 0; i < len(patterns); i++ {
|
||||
patterns[i] = strings.TrimSpace(patterns[i])
|
||||
}
|
||||
// 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
|
||||
}
|
||||
if onlyFile && value.(*File).FileInfo().IsDir() {
|
||||
return true
|
||||
}
|
||||
name = key.(string)
|
||||
if len(name) <= length {
|
||||
return true
|
||||
}
|
||||
if path != name[:length] {
|
||||
return false
|
||||
}
|
||||
// To avoid of, eg: /i18n and /i18n-dir
|
||||
if !first && name[length] != '/' {
|
||||
return true
|
||||
}
|
||||
if !recursive {
|
||||
if strings.IndexByte(name[length+1:], '/') != -1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
var (
|
||||
err error
|
||||
name string
|
||||
path string
|
||||
exportOption ExportOption
|
||||
files []*File
|
||||
)
|
||||
|
||||
if r.Get(src).FileInfo().IsDir() {
|
||||
files = r.doScanDir(src, "*", true, false)
|
||||
} else {
|
||||
files = append(files, r.Get(src))
|
||||
}
|
||||
|
||||
if len(option) > 0 {
|
||||
exportOption = option[0]
|
||||
}
|
||||
for _, file := range files {
|
||||
name = file.Name()
|
||||
if exportOption.RemovePrefix != "" {
|
||||
name = gstr.TrimLeftStr(name, exportOption.RemovePrefix)
|
||||
}
|
||||
name = gstr.Trim(name, `\/`)
|
||||
if name == "" {
|
||||
if file.FileInfo().IsDir() {
|
||||
continue
|
||||
}
|
||||
path = gfile.Join(dst, name)
|
||||
if file.FileInfo().IsDir() {
|
||||
err = gfile.Mkdir(path)
|
||||
} else {
|
||||
err = gfile.PutBytes(path, file.Content())
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result = append(result, file)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Export exports and saves specified path `src` and all its sub files
|
||||
// to specified system path `dst` recursively.
|
||||
func (r *Resource) Export(src, dst string, option ...ExportOption) error {
|
||||
if file := r.Get(src); file != nil {
|
||||
return file.Export(dst, option...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dump prints the files of current resource object.
|
||||
func (r *Resource) Dump() {
|
||||
var info os.FileInfo
|
||||
r.tree.Iterator(func(key, value interface{}) bool {
|
||||
info = value.(*File).FileInfo()
|
||||
fmt.Printf(
|
||||
"%v %8s %s\n",
|
||||
gtime.New(info.ModTime()).ISO8601(),
|
||||
gfile.FormatSize(info.Size()),
|
||||
key,
|
||||
)
|
||||
return true
|
||||
})
|
||||
fmt.Printf("TOTAL FILES: %d\n", r.tree.Size())
|
||||
var ctx = context.TODO()
|
||||
if r.IsEmpty() {
|
||||
intlog.Printf(ctx, "empty resource")
|
||||
} else {
|
||||
for _, v := range r.ScanDir("/", "*", true) {
|
||||
intlog.Printf(ctx, "%s %d", v.Path(), v.FileInfo().Size())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user