This commit is contained in:
John Guo
2024-12-09 23:43:29 +08:00
parent 5afc7f8aa1
commit efec967bec
10 changed files with 155 additions and 210 deletions

View File

@ -8,6 +8,9 @@
package gres
var (
// Default resource file system.
defaultFS = NewResFS()
// Default resource object.
defaultResource = Instance()
)
@ -16,18 +19,18 @@ var (
// The unnecessary parameter `prefix` indicates the prefix
// for each file storing into current resource object.
func Add(content string, prefix ...string) error {
return defaultResource.Add(content, prefix...)
return defaultFS.Add(content, prefix...)
}
// Load loads, unpacks and adds the data from `path` into the default resource object.
// The unnecessary parameter `prefix` indicates the prefix
// for each file storing into current resource object.
func Load(path string, prefix ...string) error {
return defaultResource.Load(path, prefix...)
return defaultFS.Load(path, prefix...)
}
// Get returns the file with given path.
func Get(path string) *File {
func Get(path string) File {
return defaultResource.Get(path)
}
@ -35,7 +38,7 @@ func Get(path string) *File {
// it then does index files searching under this directory.
//
// GetWithIndex is usually used for http static file service.
func GetWithIndex(path string, indexFiles []string) *File {
func GetWithIndex(path string, indexFiles []string) File {
return defaultResource.GetWithIndex(path, indexFiles)
}
@ -60,7 +63,7 @@ func IsEmpty() bool {
// using the ',' symbol to separate multiple patterns.
//
// It scans directory recursively if given parameter `recursive` is true.
func ScanDir(path string, pattern string, recursive ...bool) []*File {
func ScanDir(path string, pattern string, recursive ...bool) []File {
return defaultResource.ScanDir(path, pattern, recursive...)
}
@ -68,7 +71,7 @@ func ScanDir(path string, pattern string, recursive ...bool) []*File {
// It scans directory recursively if given parameter `recursive` is true.
//
// Note that it returns only files, exclusive of directories.
func ScanDirFile(path string, pattern string, recursive ...bool) []*File {
func ScanDirFile(path string, pattern string, recursive ...bool) []File {
return defaultResource.ScanDirFile(path, pattern, recursive...)
}

View File

@ -11,104 +11,71 @@ import (
"io"
"io/fs"
"os"
"sync"
"time"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gfile"
"github.com/gogf/gf/v2/text/gstr"
)
// A File provides access to a single file.
// The File interface is the minimum implementation required of the file.
// Directory files should also implement [ReadDirFile].
// A file may implement [io.ReaderAt] or [io.Seeker] as optimizations.
type File interface {
Name() string
Path() string
Content() []byte
FileInfo() os.FileInfo
Export(dst string, option ...ExportOption) error
// For http.File implementation.
Readdir(count int) ([]os.FileInfo, error)
io.ReadSeekCloser
}
// File implements the interface fs.File.
type File struct {
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
type localFile struct {
name string // Name is the file name
path string // Path is the file path
content []byte // file content
file os.FileInfo // FileInfo is the underlying file info
fs FS // FS is the file system that contains this file
mu sync.Mutex // mu protects concurrent access to the file
}
// Name returns the name of the file
func (f *File) Name() string {
func (f *localFile) Name() string {
return f.name
}
// Path returns the path of the file
func (f *File) Path() string {
func (f *localFile) Path() string {
return f.path
}
// 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)
}
return nil
}
// 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 {
func (f *localFile) FileInfo() os.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
func (f *localFile) Content() []byte {
return f.content
}
// Export exports and saves all its sub files to specified system path `dst` recursively.
func (f *File) Export(dst string, option ...ExportOption) error {
func (f *localFile) Export(dst string, option ...ExportOption) error {
var (
err error
name string
path string
exportOption ExportOption
exportFiles []*File
exportFiles []File
)
if f.FileInfo().IsDir() {
exportFiles = f.fs.ScanDir(f.path, "*", true)
} else {
@ -128,7 +95,7 @@ func (f *File) Export(dst string, option ...ExportOption) error {
continue
}
path = gfile.Join(dst, name)
if exportFile.FileInfo().IsDir() {
if f.FileInfo().IsDir() {
err = gfile.Mkdir(path)
} else {
err = gfile.PutBytes(path, exportFile.Content())
@ -140,8 +107,51 @@ func (f *File) Export(dst string, option ...ExportOption) error {
return nil
}
// Close implements interface of http.File.
func (f *localFile) Close() error {
return nil
}
// Readdir implements Readdir interface of http.File.
func (f *localFile) Readdir(count int) ([]os.FileInfo, error) {
files := f.fs.ScanDir(f.Name(), "*", false)
if len(files) > 0 {
if count <= 0 || count > len(files) {
count = len(files)
}
infos := make([]os.FileInfo, count)
for k, v := range files {
infos[k] = v.FileInfo()
}
return infos, nil
}
return nil, nil
}
// Read implements the io.Reader interface.
func (f *localFile) Read(b []byte) (n int, err error) {
reader := bytes.NewReader(f.Content())
if n, err = reader.Read(b); err != nil {
err = gerror.Wrapf(err, `read content failed`)
}
return
}
// Seek implements the io.Seeker interface.
func (f *localFile) Seek(offset int64, whence int) (n int64, err error) {
reader := bytes.NewReader(f.Content())
if n, err = reader.Seek(offset, whence); err != nil {
err = gerror.Wrapf(err, `seek failed for offset %d, whence %d`, offset, whence)
}
return
}
func (f *localFile) getReader() (io.ReadSeeker, error) {
return bytes.NewReader(f.Content()), nil
}
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
func (f *File) MarshalJSON() ([]byte, error) {
func (f *localFile) MarshalJSON() ([]byte, error) {
info := f.FileInfo()
return gjson.Marshal(map[string]interface{}{
"name": f.name,
@ -155,7 +165,7 @@ func (f *File) MarshalJSON() ([]byte, error) {
// fileInfo is the internal implementation of os.FileInfo interface.
type fileInfo struct {
file *File
file *localFile
}
// Name returns the base name of the file.

View File

@ -9,14 +9,14 @@ 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
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
ScanDir(path string, pattern string, recursive ...bool) []File
}
// ExportOption contains options for Export.

View File

@ -8,6 +8,7 @@ package gres
import (
"io/fs"
"sort"
)
// MixedFS implements the FS interface by combining StdFS and ResFS.
@ -17,6 +18,8 @@ type MixedFS struct {
resFS *ResFS
}
var _ FS = (*MixedFS)(nil)
// NewMixedFS creates and returns a new MixedFS.
func NewMixedFS(stdFs fs.FS, resFS *ResFS) *MixedFS {
return &MixedFS{
@ -26,7 +29,7 @@ func NewMixedFS(stdFs fs.FS, resFS *ResFS) *MixedFS {
}
// Get returns the file with given path.
func (fs *MixedFS) Get(path string) *File {
func (fs *MixedFS) Get(path string) File {
if file := fs.resFS.Get(path); file != nil {
return file
}
@ -40,15 +43,15 @@ func (fs *MixedFS) IsEmpty() bool {
// 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 {
func (fs *MixedFS) ScanDir(path string, pattern string, recursive ...bool) []File {
var (
filesMap = make(map[string]*File)
files = make([]*File, 0)
filesMap = make(map[string]File)
files = make([]File, 0)
)
// Get files from ResFS
defaultFiles := fs.resFS.ScanDir(path, pattern, recursive...)
for _, file := range defaultFiles {
resFiles := fs.resFS.ScanDir(path, pattern, recursive...)
for _, file := range resFiles {
if _, exists := filesMap[file.Path()]; !exists {
filesMap[file.Path()] = file
}
@ -56,32 +59,21 @@ func (fs *MixedFS) ScanDir(path string, pattern string, recursive ...bool) []*Fi
// 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)
// Convert map to slice and sort by path
paths := make([]string, 0, len(filesMap))
for filePath := range filesMap {
paths = append(paths, filePath)
}
sort.Strings(paths)
// Build sorted result
for _, filePath := range paths {
files = append(files, filesMap[filePath])
}
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
}

View File

@ -23,6 +23,8 @@ type ResFS struct {
tree *gtree.BTree // The tree storing all resource files.
}
var _ FS = (*ResFS)(nil)
const (
defaultTreeM = 100
)
@ -37,7 +39,7 @@ func NewResFS() *ResFS {
}
// Get returns the file with given path.
func (fs *ResFS) Get(path string) *File {
func (fs *ResFS) Get(path string) File {
if path == "" {
return nil
}
@ -50,7 +52,7 @@ func (fs *ResFS) Get(path string) *File {
}
result := fs.tree.Get(path)
if result != nil {
return result.(*File)
return result.(File)
}
return nil
}
@ -62,7 +64,7 @@ func (fs *ResFS) IsEmpty() bool {
// 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 {
func (fs *ResFS) ScanDir(path string, pattern string, recursive ...bool) []File {
isRecursive := false
if len(recursive) > 0 {
isRecursive = recursive[0]
@ -77,7 +79,7 @@ func (fs *ResFS) ScanDir(path string, pattern string, recursive ...bool) []*File
// 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 {
func (fs *ResFS) doScanDir(path string, pattern string, recursive bool) []File {
path = strings.ReplaceAll(path, "\\", "/")
path = strings.ReplaceAll(path, "//", "/")
if path != "/" {
@ -86,7 +88,7 @@ func (fs *ResFS) doScanDir(path string, pattern string, recursive bool) []*File
}
}
var (
files = make([]*File, 0)
files = make([]File, 0)
patterns = strings.Split(pattern, ",")
)
for i := 0; i < len(patterns); i++ {
@ -102,7 +104,7 @@ func (fs *ResFS) doScanDir(path string, pattern string, recursive bool) []*File
// Walk through the tree to find matching files
fs.tree.IteratorAsc(func(key, value interface{}) bool {
var (
file = value.(*File)
file = value.(File)
filePath = key.(string)
)
@ -144,7 +146,7 @@ func (fs *ResFS) Add(content string, prefix ...string) error {
namePrefix = prefix[0]
}
for i := 0; i < len(files); i++ {
files[i].fs = fs
files[i].(*localFile).fs = fs
fs.tree.Set(namePrefix+files[i].Path(), files[i])
}
intlog.Printf(context.TODO(), "Add %d files to resource manager", fs.tree.Size())
@ -164,7 +166,7 @@ func (fs *ResFS) Load(path string, prefix ...string) error {
func (fs *ResFS) Dump() {
var info os.FileInfo
fs.tree.Iterator(func(key, value interface{}) bool {
info = value.(*File).FileInfo()
info = value.(File).FileInfo()
intlog.Printf(
context.TODO(),
"%v %8s %s",

View File

@ -20,6 +20,8 @@ type StdFS struct {
fs fs.FS
}
var _ FS = (*StdFS)(nil)
// NewStdFS creates and returns a new StdFS.
func NewStdFS(fs fs.FS) *StdFS {
return &StdFS{
@ -28,7 +30,7 @@ func NewStdFS(fs fs.FS) *StdFS {
}
// Get returns the file with given path.
func (fs *StdFS) Get(path string) *File {
func (fs *StdFS) Get(path string) File {
f, err := fs.fs.Open(path)
if err != nil {
return nil
@ -37,6 +39,7 @@ func (fs *StdFS) Get(path string) *File {
info, err := f.Stat()
if err != nil {
panic(err)
return nil
}
@ -46,12 +49,12 @@ func (fs *StdFS) Get(path string) *File {
return nil
}
file := &File{
name: info.Name(),
path: path,
file: info,
resource: content,
fs: fs,
file := &localFile{
name: info.Name(),
path: path,
file: info,
content: content,
fs: fs,
}
return file
}
@ -72,9 +75,9 @@ func (fs *StdFS) IsEmpty() bool {
// 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 {
func (fs *StdFS) ScanDir(path string, pattern string, recursive ...bool) []File {
var (
files = make([]*File, 0)
files = make([]File, 0)
isRecursive = len(recursive) > 0 && recursive[0]
)
err := fs.walkDir(path, func(path string, d os.DirEntry, err error) error {

View File

@ -11,6 +11,7 @@ import (
"bytes"
"encoding/hex"
"fmt"
"sync"
"github.com/gogf/gf/v2/encoding/gbase64"
"github.com/gogf/gf/v2/encoding/gcompress"
@ -129,7 +130,7 @@ func PackToGoFileWithOption(srcPath, goFilePath, pkgName string, option Option)
}
// Unpack unpacks the content specified by `path` to []*File.
func Unpack(path string) ([]*File, error) {
func Unpack(path string) ([]File, error) {
realPath, err := gfile.Search(path)
if err != nil {
return nil, err
@ -137,8 +138,8 @@ func Unpack(path string) ([]*File, error) {
return UnpackContent(gfile.GetContents(realPath))
}
// UnpackContent unpacks the content to []*File.
func UnpackContent(content string) ([]*File, error) {
// UnpackContent unpacks the content to []File.
func UnpackContent(content string) ([]File, error) {
var (
err error
data []byte
@ -171,15 +172,15 @@ func UnpackContent(content string) ([]*File, error) {
err = gerror.Wrapf(err, `create zip reader failed`)
return nil, err
}
array := make([]*File, len(reader.File))
array := make([]File, len(reader.File))
for i, file := range reader.File {
array[i] = &File{
name: file.Name,
path: file.Name,
file: file.FileInfo(),
reader: nil,
resource: nil,
fs: nil,
array[i] = &localFile{
name: file.Name,
path: file.Name,
content: data,
file: file.FileInfo(),
fs: nil,
mu: sync.Mutex{},
}
}
return array, nil

View File

@ -1,71 +0,0 @@
// 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 (
"bytes"
"os"
"github.com/gogf/gf/v2/errors/gerror"
)
// Close implements interface of http.File.
func (f *File) Close() error {
return nil
}
// Readdir implements Readdir interface of http.File.
func (f *File) Readdir(count int) ([]os.FileInfo, error) {
files := f.resource.ScanDir(f.Name(), "*", false)
if len(files) > 0 {
if count <= 0 || count > len(files) {
count = len(files)
}
infos := make([]os.FileInfo, count)
for k, v := range files {
infos[k] = v.FileInfo()
}
return infos, nil
}
return nil, nil
}
// Stat implements Stat interface of http.File.
func (f *File) Stat() (os.FileInfo, error) {
return f.FileInfo(), nil
}
// Read implements the io.Reader interface.
func (f *File) Read(b []byte) (n int, err error) {
reader, err := f.getReader()
if err != nil {
return 0, err
}
if n, err = reader.Read(b); err != nil {
err = gerror.Wrapf(err, `read content failed`)
}
return
}
// Seek implements the io.Seeker interface.
func (f *File) Seek(offset int64, whence int) (n int64, err error) {
reader, err := f.getReader()
if err != nil {
return 0, err
}
if n, err = reader.Seek(offset, whence); err != nil {
err = gerror.Wrapf(err, `seek failed for offset %d, whence %d`, offset, whence)
}
return
}
func (f *File) getReader() (*bytes.Reader, error) {
if f.reader == nil {
f.reader = bytes.NewReader(f.Content())
}
return f.reader, nil
}

View File

@ -26,6 +26,6 @@ func Instance(name ...string) *Resource {
key = name[0]
}
return instances.GetOrSetFuncLock(key, func() interface{} {
return New()
return NewWithFS(defaultFS)
}).(*Resource)
}

View File

@ -30,14 +30,19 @@ func NewWithFS(fs FS) *Resource {
}
}
// SetFS sets the underlying file system implementation.
func (r *Resource) SetFS(fs FS) {
r.fs = fs
}
// Get returns the file with given path.
func (r *Resource) Get(path string) *File {
func (r *Resource) Get(path string) File {
return r.fs.Get(path)
}
// GetWithIndex searches file with `path`, if the file is directory
// it then does index files searching under this directory.
func (r *Resource) GetWithIndex(path string, indexFiles []string) *File {
func (r *Resource) GetWithIndex(path string, indexFiles []string) File {
// Necessary for double char '/' replacement in prefix.
path = strings.ReplaceAll(path, "\\", "/")
path = strings.ReplaceAll(path, "//", "/")
@ -48,7 +53,7 @@ func (r *Resource) GetWithIndex(path string, indexFiles []string) *File {
}
if file := r.fs.Get(path); file != nil {
if len(indexFiles) > 0 && file.FileInfo().IsDir() {
var f *File
var f File
for _, name := range indexFiles {
if f = r.fs.Get(path + "/" + name); f != nil {
return f
@ -80,15 +85,15 @@ func (r *Resource) IsEmpty() bool {
}
// ScanDir returns the files under the given path, the parameter `path` should be a folder type.
func (r *Resource) ScanDir(path string, pattern string, recursive ...bool) []*File {
func (r *Resource) ScanDir(path string, pattern string, recursive ...bool) []File {
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.
func (r *Resource) ScanDirFile(path string, pattern string, recursive ...bool) []*File {
func (r *Resource) ScanDirFile(path string, pattern string, recursive ...bool) []File {
var (
result = make([]*File, 0)
result = make([]File, 0)
files = r.fs.ScanDir(path, pattern, recursive...)
)
for _, file := range files {