This commit is contained in:
John Guo
2024-12-09 22:35:40 +08:00
parent 5e47590165
commit 5afc7f8aa1
8 changed files with 645 additions and 239 deletions

View File

@ -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.

View File

@ -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
View 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
View 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
View 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
View 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")
}

View File

@ -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
}

View File

@ -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())
}
}
}