mirror of
https://gitee.com/johng/gf
synced 2026-07-04 21:03:13 +08:00
up
This commit is contained in:
@ -287,22 +287,30 @@ func (s *Server) searchStaticFile(uri string) *staticFile {
|
||||
func (s *Server) serveFile(r *Request, f *staticFile, allowIndex ...bool) {
|
||||
// Use resource file from memory.
|
||||
if f.File != nil {
|
||||
httpFile, err := f.File.HttpFile()
|
||||
if err != nil {
|
||||
intlog.Errorf(r.Context(), "serving file failed: %+v", err)
|
||||
r.Response.WriteStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer httpFile.Close()
|
||||
if f.IsDir {
|
||||
if s.config.IndexFolder || (len(allowIndex) > 0 && allowIndex[0]) {
|
||||
s.listDir(r, f.File)
|
||||
s.listDir(r, httpFile)
|
||||
} else {
|
||||
r.Response.WriteStatus(http.StatusForbidden)
|
||||
}
|
||||
} else {
|
||||
info := f.File.FileInfo()
|
||||
r.Response.ServeContent(info.Name(), info.ModTime(), f.File)
|
||||
r.Response.ServeContent(info.Name(), info.ModTime(), httpFile)
|
||||
}
|
||||
return
|
||||
}
|
||||
// Use file from dist.
|
||||
file, err := os.Open(f.Path)
|
||||
if err != nil {
|
||||
r.Response.WriteStatus(http.StatusForbidden)
|
||||
intlog.Errorf(r.Context(), "open file failed: %+v", err)
|
||||
r.Response.WriteStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
@ -313,7 +321,12 @@ func (s *Server) serveFile(r *Request, f *staticFile, allowIndex ...bool) {
|
||||
// It ignores all custom buffer content and uses the file content.
|
||||
r.Response.ClearBuffer()
|
||||
|
||||
info, _ := file.Stat()
|
||||
info, err := file.Stat()
|
||||
if err != nil {
|
||||
intlog.Errorf(r.Context(), "getting file info failed: %+v", err)
|
||||
r.Response.WriteStatus(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if info.IsDir() {
|
||||
if s.config.IndexFolder || (len(allowIndex) > 0 && allowIndex[0]) {
|
||||
s.listDir(r, file)
|
||||
|
||||
@ -7,14 +7,44 @@
|
||||
// Package gres provides resource management and packing/unpacking feature between files and bytes.
|
||||
package gres
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gres/internal/defines"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/fs_mixed"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/fs_res"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/fs_std"
|
||||
)
|
||||
|
||||
type (
|
||||
FS = defines.FS
|
||||
File = defines.File
|
||||
// Deprecated: used PackOption instead.
|
||||
Option = defines.PackOption
|
||||
PackOption = defines.PackOption
|
||||
ExportOption = defines.ExportOption
|
||||
)
|
||||
|
||||
var (
|
||||
// Default resource file system.
|
||||
defaultFS = NewResFS()
|
||||
defaultFS = fs_res.NewFS()
|
||||
|
||||
// Default resource object.
|
||||
defaultResource = Instance()
|
||||
)
|
||||
|
||||
func NewResFS() *fs_res.FS {
|
||||
return fs_res.NewFS()
|
||||
}
|
||||
|
||||
func NewStdFS(fs fs.FS) *fs_std.FS {
|
||||
return fs_std.NewFS(fs)
|
||||
}
|
||||
|
||||
func NewMixedFS(resFS *fs_res.FS, stdFs fs.FS) *fs_mixed.FS {
|
||||
return fs_mixed.NewFS(resFS, stdFs)
|
||||
}
|
||||
|
||||
// Add unpacks and adds the `content` into the default resource object.
|
||||
// The unnecessary parameter `prefix` indicates the prefix
|
||||
// for each file storing into current resource object.
|
||||
|
||||
@ -1,210 +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"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"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 {
|
||||
http.File
|
||||
Name() string
|
||||
Path() string
|
||||
Content() []byte
|
||||
FileInfo() os.FileInfo
|
||||
Export(dst string, option ...ExportOption) error
|
||||
}
|
||||
|
||||
// File implements the interface fs.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 *localFile) Name() string {
|
||||
return f.name
|
||||
}
|
||||
|
||||
// Path returns the path of the file
|
||||
func (f *localFile) Path() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
// FileInfo returns an os.FileInfo describing this file
|
||||
func (f *localFile) FileInfo() os.FileInfo {
|
||||
return f.file
|
||||
}
|
||||
|
||||
// Stat returns the FileInfo structure describing file.
|
||||
func (f *localFile) Stat() (os.FileInfo, error) {
|
||||
return f.file, nil
|
||||
}
|
||||
|
||||
// Content returns the file content
|
||||
func (f *localFile) Content() []byte {
|
||||
return f.content
|
||||
}
|
||||
|
||||
// Export exports and saves all its sub files to specified system path `dst` recursively.
|
||||
func (f *localFile) Export(dst string, option ...ExportOption) error {
|
||||
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 f.FileInfo().IsDir() {
|
||||
err = gfile.Mkdir(path)
|
||||
} else {
|
||||
err = gfile.PutBytes(path, exportFile.Content())
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
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 *localFile) MarshalJSON() ([]byte, error) {
|
||||
info := f.FileInfo()
|
||||
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 *localFile
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
@ -1,25 +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
|
||||
|
||||
// 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.
|
||||
}
|
||||
@ -1,79 +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 (
|
||||
"io/fs"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
var _ FS = (*MixedFS)(nil)
|
||||
|
||||
// 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
|
||||
resFiles := fs.resFS.ScanDir(path, pattern, recursive...)
|
||||
for _, file := range resFiles {
|
||||
if _, exists := filesMap[file.Path()]; !exists {
|
||||
filesMap[file.Path()] = file
|
||||
}
|
||||
}
|
||||
|
||||
// Get files from StdFS
|
||||
stdFiles := fs.stdFS.ScanDir(path, pattern, recursive...)
|
||||
for _, file := range stdFiles {
|
||||
filesMap[file.Path()] = 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
|
||||
}
|
||||
@ -7,39 +7,10 @@
|
||||
package gres
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gbase64"
|
||||
"github.com/gogf/gf/v2/encoding/gcompress"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/fs_res"
|
||||
)
|
||||
|
||||
const (
|
||||
packedGoSourceTemplate = `
|
||||
package %s
|
||||
|
||||
import "github.com/gogf/gf/v2/os/gres"
|
||||
|
||||
func init() {
|
||||
if err := gres.Add("%s"); err != nil {
|
||||
panic("add binary content to resource manager failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
// Option contains the extra options for Pack functions.
|
||||
type Option struct {
|
||||
Prefix string // The file path prefix for each file item in resource manager.
|
||||
KeepPath bool // Keep the passed path when packing, usually for relative path.
|
||||
}
|
||||
|
||||
// Pack packs the path specified by `srcPaths` into bytes.
|
||||
// The unnecessary parameter `keyPrefix` indicates the prefix for each file
|
||||
// packed into the result bytes.
|
||||
@ -48,26 +19,13 @@ type Option struct {
|
||||
//
|
||||
// Deprecated: use PackWithOption instead.
|
||||
func Pack(srcPaths string, keyPrefix ...string) ([]byte, error) {
|
||||
option := Option{}
|
||||
option := PackOption{}
|
||||
if len(keyPrefix) > 0 && keyPrefix[0] != "" {
|
||||
option.Prefix = keyPrefix[0]
|
||||
}
|
||||
return PackWithOption(srcPaths, option)
|
||||
}
|
||||
|
||||
// PackWithOption packs the path specified by `srcPaths` into bytes.
|
||||
//
|
||||
// Note that parameter `srcPaths` supports multiple paths join with ','.
|
||||
func PackWithOption(srcPaths string, option Option) ([]byte, error) {
|
||||
var buffer = bytes.NewBuffer(nil)
|
||||
err := zipPathWriter(srcPaths, buffer, option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Gzip the data bytes to reduce the size.
|
||||
return gcompress.Gzip(buffer.Bytes(), 9)
|
||||
}
|
||||
|
||||
// PackToFile packs the path specified by `srcPaths` to target file `dstPath`.
|
||||
// The unnecessary parameter `keyPrefix` indicates the prefix for each file
|
||||
// packed into the result bytes.
|
||||
@ -83,17 +41,6 @@ func PackToFile(srcPaths, dstPath string, keyPrefix ...string) error {
|
||||
return gfile.PutBytes(dstPath, data)
|
||||
}
|
||||
|
||||
// PackToFileWithOption packs the path specified by `srcPaths` to target file `dstPath`.
|
||||
//
|
||||
// Note that parameter `srcPaths` supports multiple paths join with ','.
|
||||
func PackToFileWithOption(srcPaths, dstPath string, option Option) error {
|
||||
data, err := PackWithOption(srcPaths, option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gfile.PutBytes(dstPath, data)
|
||||
}
|
||||
|
||||
// PackToGoFile packs the path specified by `srcPaths` to target go file `goFilePath`
|
||||
// with given package name `pkgName`.
|
||||
//
|
||||
@ -104,124 +51,41 @@ func PackToFileWithOption(srcPaths, dstPath string, option Option) error {
|
||||
//
|
||||
// Deprecated: use PackToGoFileWithOption instead.
|
||||
func PackToGoFile(srcPath, goFilePath, pkgName string, keyPrefix ...string) error {
|
||||
data, err := Pack(srcPath, keyPrefix...)
|
||||
if err != nil {
|
||||
return err
|
||||
option := PackOption{}
|
||||
if len(keyPrefix) > 0 && keyPrefix[0] != "" {
|
||||
option.Prefix = keyPrefix[0]
|
||||
}
|
||||
return gfile.PutContents(
|
||||
goFilePath,
|
||||
fmt.Sprintf(gstr.TrimLeft(packedGoSourceTemplate), pkgName, gbase64.EncodeToString(data)),
|
||||
)
|
||||
return PackToGoFileWithOption(srcPath, goFilePath, pkgName, option)
|
||||
}
|
||||
|
||||
// PackWithOption packs the path specified by `srcPaths` into bytes.
|
||||
//
|
||||
// Note that parameter `srcPaths` supports multiple paths join with ','.
|
||||
func PackWithOption(srcPaths string, option PackOption) ([]byte, error) {
|
||||
return fs_res.PackWithOption(srcPaths, option)
|
||||
}
|
||||
|
||||
// PackToFileWithOption packs the path specified by `srcPaths` to target file `dstPath`.
|
||||
//
|
||||
// Note that parameter `srcPaths` supports multiple paths join with ','.
|
||||
func PackToFileWithOption(srcPaths, dstPath string, option PackOption) error {
|
||||
return fs_res.PackToFileWithOption(srcPaths, dstPath, option)
|
||||
}
|
||||
|
||||
// PackToGoFileWithOption packs the path specified by `srcPaths` to target go file `goFilePath`
|
||||
// with given package name `pkgName`.
|
||||
//
|
||||
// Note that parameter `srcPaths` supports multiple paths join with ','.
|
||||
func PackToGoFileWithOption(srcPath, goFilePath, pkgName string, option Option) error {
|
||||
data, err := PackWithOption(srcPath, option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gfile.PutContents(
|
||||
goFilePath,
|
||||
fmt.Sprintf(gstr.TrimLeft(packedGoSourceTemplate), pkgName, gbase64.EncodeToString(data)),
|
||||
)
|
||||
func PackToGoFileWithOption(srcPath, goFilePath, pkgName string, option PackOption) error {
|
||||
return fs_res.PackToGoFileWithOption(srcPath, goFilePath, pkgName, option)
|
||||
}
|
||||
|
||||
// Unpack unpacks the content specified by `path` to []*File.
|
||||
func Unpack(path string) ([]File, error) {
|
||||
realPath, err := gfile.Search(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return UnpackContent(gfile.GetContents(realPath))
|
||||
return fs_res.Unpack(path)
|
||||
}
|
||||
|
||||
// UnpackContent unpacks the content to []File.
|
||||
func UnpackContent(content string) ([]File, error) {
|
||||
var (
|
||||
err error
|
||||
data []byte
|
||||
)
|
||||
if isHexStr(content) {
|
||||
// It here keeps compatible with old version packing string using hex string.
|
||||
// TODO remove this support in the future.
|
||||
data, err = gcompress.UnGzip(hexStrToBytes(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if isBase64(content) {
|
||||
// New version packing string using base64.
|
||||
b, err := gbase64.DecodeString(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err = gcompress.UnGzip(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
data, err = gcompress.UnGzip([]byte(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
reader, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
|
||||
if err != nil {
|
||||
err = gerror.Wrapf(err, `create zip reader failed`)
|
||||
return nil, err
|
||||
}
|
||||
array := make([]File, len(reader.File))
|
||||
for i, file := range reader.File {
|
||||
array[i] = &localFile{
|
||||
name: file.Name,
|
||||
path: file.Name,
|
||||
content: data,
|
||||
file: file.FileInfo(),
|
||||
fs: nil,
|
||||
mu: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
return array, nil
|
||||
}
|
||||
|
||||
// isBase64 checks and returns whether given content `s` is base64 string.
|
||||
// It returns true if `s` is base64 string, or false if not.
|
||||
func isBase64(s string) bool {
|
||||
var r bool
|
||||
for i := 0; i < len(s); i++ {
|
||||
r = (s[i] >= '0' && s[i] <= '9') ||
|
||||
(s[i] >= 'a' && s[i] <= 'z') ||
|
||||
(s[i] >= 'A' && s[i] <= 'Z') ||
|
||||
(s[i] == '+' || s[i] == '-') ||
|
||||
(s[i] == '_' || s[i] == '/') || s[i] == '='
|
||||
if !r {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isHexStr checks and returns whether given content `s` is hex string.
|
||||
// It returns true if `s` is hex string, or false if not.
|
||||
func isHexStr(s string) bool {
|
||||
var r bool
|
||||
for i := 0; i < len(s); i++ {
|
||||
r = (s[i] >= '0' && s[i] <= '9') ||
|
||||
(s[i] >= 'a' && s[i] <= 'f') ||
|
||||
(s[i] >= 'A' && s[i] <= 'F')
|
||||
if !r {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// hexStrToBytes converts hex string content to []byte.
|
||||
func hexStrToBytes(s string) []byte {
|
||||
src := []byte(s)
|
||||
dst := make([]byte, hex.DecodedLen(len(src)))
|
||||
_, _ = hex.Decode(dst, src)
|
||||
return dst
|
||||
return fs_res.UnpackContent(content)
|
||||
}
|
||||
|
||||
@ -7,10 +7,12 @@
|
||||
package gres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// Resource implements the FS interface.
|
||||
@ -76,7 +78,7 @@ 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.
|
||||
@ -116,12 +118,19 @@ func (r *Resource) Export(src, dst string, option ...ExportOption) error {
|
||||
|
||||
// Dump prints the files of current resource object.
|
||||
func (r *Resource) Dump() {
|
||||
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())
|
||||
}
|
||||
var (
|
||||
count int
|
||||
info os.FileInfo
|
||||
)
|
||||
for _, file := range r.fs.ListAll() {
|
||||
count++
|
||||
info = file.FileInfo()
|
||||
fmt.Printf(
|
||||
"%v %8s %s\n",
|
||||
gtime.New(info.ModTime()).ISO8601(),
|
||||
gfile.FormatSize(info.Size()),
|
||||
file.Name(),
|
||||
)
|
||||
}
|
||||
fmt.Printf("TOTAL FILES: %d\n", count)
|
||||
}
|
||||
|
||||
@ -25,7 +25,7 @@ func Test_PackFolderToGoFile(t *testing.T) {
|
||||
srcPath = gtest.DataPath("files")
|
||||
goFilePath = gfile.Temp(gtime.TimestampNanoStr(), "testdata.go")
|
||||
pkgName = "testdata"
|
||||
err = gres.PackToGoFile(srcPath, goFilePath, pkgName)
|
||||
err = gres.PackToGoFileWithOption(srcPath, goFilePath, pkgName, gres.PackOption{})
|
||||
)
|
||||
t.AssertNil(err)
|
||||
_ = gfile.Remove(goFilePath)
|
||||
@ -33,7 +33,7 @@ func Test_PackFolderToGoFile(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_PackMultiFilesToGoFile(t *testing.T) {
|
||||
gres.Dump()
|
||||
// gres.Dump()
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
srcPath = gtest.DataPath("files")
|
||||
@ -42,11 +42,11 @@ func Test_PackMultiFilesToGoFile(t *testing.T) {
|
||||
array, err = gfile.ScanDir(srcPath, "*", false)
|
||||
)
|
||||
t.AssertNil(err)
|
||||
err = gres.PackToGoFile(strings.Join(array, ","), goFilePath, pkgName)
|
||||
err = gres.PackToGoFileWithOption(strings.Join(array, ","), goFilePath, pkgName, gres.PackOption{})
|
||||
t.AssertNil(err)
|
||||
defer gfile.Remove(goFilePath)
|
||||
|
||||
t.AssertNil(gfile.CopyFile(goFilePath, gtest.DataPath("data/data.go")))
|
||||
//t.AssertNil(gfile.CopyFile(goFilePath, gtest.DataPath("data/data.go")))
|
||||
})
|
||||
}
|
||||
|
||||
@ -140,13 +140,12 @@ func Test_Unpack(t *testing.T) {
|
||||
}
|
||||
|
||||
func Test_Basic(t *testing.T) {
|
||||
// gres.Dump()
|
||||
//gres.Dump()
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
t.Assert(gres.Get("none"), nil)
|
||||
t.Assert(gres.Contains("none"), false)
|
||||
t.Assert(gres.Contains("dir1"), true)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
path := "dir1/test1"
|
||||
file := gres.Get(path)
|
||||
@ -158,8 +157,11 @@ func Test_Basic(t *testing.T) {
|
||||
t.Assert(info.IsDir(), false)
|
||||
t.Assert(info.Name(), "test1")
|
||||
|
||||
r, err := file.Open()
|
||||
t.AssertNil(err)
|
||||
|
||||
b := make([]byte, 5)
|
||||
n, err := file.Read(b)
|
||||
n, err := r.Read(b)
|
||||
t.Assert(n, 5)
|
||||
t.AssertNil(err)
|
||||
t.Assert(string(b), "test1")
|
||||
@ -210,9 +212,9 @@ func Test_ScanDir(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
path := "dir1"
|
||||
files := gres.ScanDir(path, "*", false)
|
||||
t.AssertNE(files, nil)
|
||||
t.Assert(len(files), 2)
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
path := "dir1"
|
||||
files := gres.ScanDir(path, "*", true)
|
||||
@ -272,6 +274,7 @@ func Test_Export(t *testing.T) {
|
||||
name := `template-res/index.html`
|
||||
t.Assert(gfile.GetContents(gfile.Join(dst, name)), gres.GetContent(name))
|
||||
})
|
||||
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
src = `template-res`
|
||||
|
||||
47
os/gres/internal/defines/defines.go
Normal file
47
os/gres/internal/defines/defines.go
Normal file
@ -0,0 +1,47 @@
|
||||
package defines
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
// 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 returns the path of the file.
|
||||
Name() string
|
||||
Open() (io.ReadCloser, error)
|
||||
Content() []byte
|
||||
FileInfo() os.FileInfo
|
||||
Export(dst string, option ...ExportOption) error
|
||||
HttpFile() (http.File, error)
|
||||
}
|
||||
|
||||
// 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 FS 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
|
||||
|
||||
ListAll() []File
|
||||
}
|
||||
|
||||
// PackOption contains the extra options for Pack functions.
|
||||
type PackOption struct {
|
||||
Prefix string // The file path prefix for each file item in resource manager.
|
||||
KeepPath bool // Keep the passed path when packing, usually for relative path.
|
||||
}
|
||||
|
||||
// ExportOption contains options for Export.
|
||||
type ExportOption struct {
|
||||
RemovePrefix string // Remove the prefix from source file before export.
|
||||
}
|
||||
109
os/gres/internal/fs_mixed/fs_mixed.go
Normal file
109
os/gres/internal/fs_mixed/fs_mixed.go
Normal file
@ -0,0 +1,109 @@
|
||||
// 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 fs_mixed
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"sort"
|
||||
|
||||
"github.com/gogf/gf/v2/os/gres/internal/defines"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/fs_res"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/fs_std"
|
||||
)
|
||||
|
||||
// FS implements the FS interface by combining fs_res.FS and StdFS.
|
||||
// It prioritizes using fs_res.FS and falls back to StdFS when file not found.
|
||||
type FS struct {
|
||||
resFS *fs_res.FS
|
||||
stdFS *fs_std.FS
|
||||
}
|
||||
|
||||
var _ defines.FS = (*FS)(nil)
|
||||
|
||||
// NewFS creates and returns a new FS.
|
||||
func NewFS(resFS *fs_res.FS, stdFs fs.FS) *FS {
|
||||
return &FS{
|
||||
resFS: resFS,
|
||||
stdFS: fs_std.NewFS(stdFs),
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the file with given path.
|
||||
func (fs *FS) Get(path string) defines.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 *FS) 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 *FS) ScanDir(path string, pattern string, recursive ...bool) []defines.File {
|
||||
var (
|
||||
filesMap = make(map[string]defines.File)
|
||||
files = make([]defines.File, 0)
|
||||
)
|
||||
// Get files from StdFS
|
||||
stdFiles := fs.stdFS.ScanDir(path, pattern, recursive...)
|
||||
for _, file := range stdFiles {
|
||||
filesMap[file.Name()] = file
|
||||
}
|
||||
|
||||
// Get files from fs_res.FS
|
||||
resFiles := fs.resFS.ScanDir(path, pattern, recursive...)
|
||||
for _, file := range resFiles {
|
||||
if _, exists := filesMap[file.Name()]; !exists {
|
||||
filesMap[file.Name()] = 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
|
||||
}
|
||||
|
||||
func (fs *FS) ListAll() []defines.File {
|
||||
var (
|
||||
resAll = fs.resFS.ListAll()
|
||||
stdAll = fs.resFS.ListAll()
|
||||
filesMap = make(map[string]defines.File)
|
||||
files = make([]defines.File, 0)
|
||||
)
|
||||
for _, file := range stdAll {
|
||||
filesMap[file.Name()] = file
|
||||
}
|
||||
for _, file := range resAll {
|
||||
filesMap[file.Name()] = 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
|
||||
}
|
||||
@ -4,34 +4,33 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gres
|
||||
package fs_res
|
||||
|
||||
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"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/defines"
|
||||
)
|
||||
|
||||
// ResFS implements the FS interface using the default resource implementation.
|
||||
type ResFS struct {
|
||||
// FS implements the FS interface using the default resource implementation.
|
||||
type FS struct {
|
||||
tree *gtree.BTree // The tree storing all resource files.
|
||||
}
|
||||
|
||||
var _ FS = (*ResFS)(nil)
|
||||
var _ defines.FS = (*FS)(nil)
|
||||
|
||||
const (
|
||||
defaultTreeM = 100
|
||||
)
|
||||
|
||||
// NewResFS creates and returns a new ResFS.
|
||||
func NewResFS() *ResFS {
|
||||
return &ResFS{
|
||||
// NewFS creates and returns a new FS using resource manager.
|
||||
func NewFS() *FS {
|
||||
return &FS{
|
||||
tree: gtree.NewBTree(defaultTreeM, func(v1, v2 interface{}) int {
|
||||
return strings.Compare(v1.(string), v2.(string))
|
||||
}),
|
||||
@ -39,7 +38,7 @@ func NewResFS() *ResFS {
|
||||
}
|
||||
|
||||
// Get returns the file with given path.
|
||||
func (fs *ResFS) Get(path string) File {
|
||||
func (fs *FS) Get(path string) defines.File {
|
||||
if path == "" {
|
||||
return nil
|
||||
}
|
||||
@ -52,24 +51,24 @@ func (fs *ResFS) Get(path string) File {
|
||||
}
|
||||
result := fs.tree.Get(path)
|
||||
if result != nil {
|
||||
return result.(File)
|
||||
return result.(defines.File)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsEmpty checks and returns whether the resource is empty.
|
||||
func (fs *ResFS) IsEmpty() bool {
|
||||
func (fs *FS) 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 {
|
||||
func (fs *FS) ScanDir(path string, pattern string, recursive ...bool) []defines.File {
|
||||
isRecursive := false
|
||||
if len(recursive) > 0 {
|
||||
isRecursive = recursive[0]
|
||||
}
|
||||
return fs.doScanDir(path, pattern, isRecursive)
|
||||
return fs.doScanDir(path, pattern, isRecursive, false)
|
||||
}
|
||||
|
||||
// doScanDir is an internal method which scans directory
|
||||
@ -79,7 +78,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 *FS) doScanDir(path string, pattern string, recursive bool, onlyFile bool) []defines.File {
|
||||
path = strings.ReplaceAll(path, "\\", "/")
|
||||
path = strings.ReplaceAll(path, "//", "/")
|
||||
if path != "/" {
|
||||
@ -88,45 +87,47 @@ func (fs *ResFS) doScanDir(path string, pattern string, recursive bool) []File {
|
||||
}
|
||||
}
|
||||
var (
|
||||
files = make([]File, 0)
|
||||
name = ""
|
||||
files = make([]defines.File, 0)
|
||||
length = len(path)
|
||||
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) {
|
||||
// Used for type checking for first entry.
|
||||
first := true
|
||||
fs.tree.IteratorFrom(path, true, func(key, value interface{}) bool {
|
||||
if first {
|
||||
if !value.(defines.File).FileInfo().IsDir() {
|
||||
return false
|
||||
}
|
||||
first = false
|
||||
}
|
||||
if onlyFile && value.(defines.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
|
||||
}
|
||||
|
||||
// Skip if not recursive and file is in subdirectory
|
||||
if !recursive {
|
||||
relPath := strings.TrimPrefix(filePath, path)
|
||||
if strings.Contains(relPath, "/") {
|
||||
if strings.IndexByte(name[length+1:], '/') != -1 {
|
||||
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
|
||||
if match, err := filepath.Match(p, gfile.Basename(name)); err == nil && match {
|
||||
files = append(files, value.(defines.File))
|
||||
return true
|
||||
}
|
||||
}
|
||||
return true
|
||||
@ -134,8 +135,17 @@ func (fs *ResFS) doScanDir(path string, pattern string, recursive bool) []File {
|
||||
return files
|
||||
}
|
||||
|
||||
// Add adds the `content` into current ResFS with given `prefix`.
|
||||
func (fs *ResFS) Add(content string, prefix ...string) error {
|
||||
func (fs *FS) ListAll() []defines.File {
|
||||
files := make([]defines.File, 0)
|
||||
fs.tree.Iterator(func(key, value interface{}) bool {
|
||||
files = append(files, value.(defines.File))
|
||||
return true
|
||||
})
|
||||
return files
|
||||
}
|
||||
|
||||
// Add adds the `content` into current FS with given `prefix`.
|
||||
func (fs *FS) Add(content string, prefix ...string) error {
|
||||
files, err := UnpackContent(content)
|
||||
if err != nil {
|
||||
intlog.Printf(context.TODO(), "Add resource files failed: %v", err)
|
||||
@ -146,35 +156,18 @@ func (fs *ResFS) Add(content string, prefix ...string) error {
|
||||
namePrefix = prefix[0]
|
||||
}
|
||||
for i := 0; i < len(files); i++ {
|
||||
files[i].(*localFile).fs = fs
|
||||
fs.tree.Set(namePrefix+files[i].Path(), files[i])
|
||||
files[i].(*FileImp).fs = fs
|
||||
fs.tree.Set(namePrefix+files[i].Name(), 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 {
|
||||
// Load loads, unpacks and adds the data from `path` into FS.
|
||||
func (fs *FS) 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())
|
||||
}
|
||||
126
os/gres/internal/fs_res/fs_res_file.go
Normal file
126
os/gres/internal/fs_res/fs_res_file.go
Normal file
@ -0,0 +1,126 @@
|
||||
// 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 fs_res
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/defines"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// FileImp implements the interface fs.File.
|
||||
type FileImp struct {
|
||||
file *zip.File // File is the underlying file object
|
||||
fs defines.FS // FS is the file system that contains this file
|
||||
}
|
||||
|
||||
var _ defines.File = (*FileImp)(nil)
|
||||
|
||||
func (f *FileImp) Name() string {
|
||||
return f.file.Name
|
||||
}
|
||||
|
||||
// FileInfo returns an os.FileInfo describing this file
|
||||
func (f *FileImp) FileInfo() os.FileInfo {
|
||||
return f.file.FileInfo()
|
||||
}
|
||||
|
||||
// Stat returns the FileInfo structure describing file.
|
||||
func (f *FileImp) Stat() (os.FileInfo, error) {
|
||||
return f.FileInfo(), nil
|
||||
}
|
||||
|
||||
func (f *FileImp) Open() (io.ReadCloser, error) {
|
||||
return f.file.Open()
|
||||
}
|
||||
|
||||
func (f *FileImp) HttpFile() (http.File, error) {
|
||||
return NewHttpFile(f.fs, f.file)
|
||||
}
|
||||
|
||||
// Content returns the file content
|
||||
func (f *FileImp) Content() []byte {
|
||||
readCloser, err := f.file.Open()
|
||||
if err != nil {
|
||||
intlog.Error(context.Background(), err)
|
||||
return nil
|
||||
}
|
||||
defer readCloser.Close()
|
||||
content, err := io.ReadAll(readCloser)
|
||||
if err != nil {
|
||||
intlog.Error(context.Background(), err)
|
||||
return nil
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// Export exports and saves all its sub files to specified system path `dst` recursively.
|
||||
func (f *FileImp) Export(dst string, option ...defines.ExportOption) error {
|
||||
var (
|
||||
err error
|
||||
name string
|
||||
path string
|
||||
exportOption defines.ExportOption
|
||||
exportFiles []defines.File
|
||||
)
|
||||
if f.FileInfo().IsDir() {
|
||||
exportFiles = f.fs.ScanDir(f.Name(), "*", 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
|
||||
}
|
||||
|
||||
type jsonFileInfo struct {
|
||||
Name string
|
||||
Size int64
|
||||
Time time.Time
|
||||
IsDir bool
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (f *FileImp) MarshalJSON() ([]byte, error) {
|
||||
info := f.FileInfo()
|
||||
return gjson.Marshal(jsonFileInfo{
|
||||
Name: f.Name(),
|
||||
Size: info.Size(),
|
||||
Time: info.ModTime(),
|
||||
IsDir: info.IsDir(),
|
||||
})
|
||||
}
|
||||
86
os/gres/internal/fs_res/fs_res_file_http.go
Normal file
86
os/gres/internal/fs_res/fs_res_file_http.go
Normal file
@ -0,0 +1,86 @@
|
||||
// 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 fs_res
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/defines"
|
||||
)
|
||||
|
||||
// HttpFileImp implements the interface fs.File.
|
||||
type HttpFileImp struct {
|
||||
fs defines.FS // FS is the file system that contains this file
|
||||
zipFile *zip.File // File is the underlying file object
|
||||
readSeeker io.ReadSeeker // ReadCloser is the underlying file object
|
||||
}
|
||||
|
||||
var _ http.File = (*HttpFileImp)(nil)
|
||||
|
||||
func NewHttpFile(fs defines.FS, zipFile *zip.File) (*HttpFileImp, error) {
|
||||
readCloser, err := zipFile.Open()
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCodef(gcode.CodeOperationFailed, err, `open zip file failed`)
|
||||
}
|
||||
content, err := io.ReadAll(readCloser)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCodef(gcode.CodeOperationFailed, err, `read zip file content failed`)
|
||||
}
|
||||
return &HttpFileImp{
|
||||
readSeeker: bytes.NewReader(content),
|
||||
zipFile: zipFile,
|
||||
fs: fs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Stat returns the FileInfo structure describing file.
|
||||
func (f *HttpFileImp) Stat() (os.FileInfo, error) {
|
||||
return f.zipFile.FileInfo(), nil
|
||||
}
|
||||
|
||||
// Close implements interface of http.File.
|
||||
func (f *HttpFileImp) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Readdir implements Readdir interface of http.File.
|
||||
func (f *HttpFileImp) Readdir(count int) ([]os.FileInfo, error) {
|
||||
files := f.fs.ScanDir(f.zipFile.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 *HttpFileImp) Read(b []byte) (n int, err error) {
|
||||
if n, err = f.readSeeker.Read(b); err != nil {
|
||||
err = gerror.WrapCodef(gcode.CodeOperationFailed, err, `read content failed`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Seek implements the io.Seeker interface.
|
||||
func (f *HttpFileImp) Seek(offset int64, whence int) (n int64, err error) {
|
||||
if n, err = f.readSeeker.Seek(offset, whence); err != nil {
|
||||
err = gerror.Wrapf(err, `seek failed for offset %d, whence %d`, offset, whence)
|
||||
}
|
||||
return
|
||||
}
|
||||
164
os/gres/internal/fs_res/fs_res_func.go
Normal file
164
os/gres/internal/fs_res/fs_res_func.go
Normal file
@ -0,0 +1,164 @@
|
||||
package fs_res
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gbase64"
|
||||
"github.com/gogf/gf/v2/encoding/gcompress"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/defines"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
const (
|
||||
packedGoSourceTemplate = `
|
||||
package %s
|
||||
|
||||
import "github.com/gogf/gf/v2/os/gres"
|
||||
|
||||
func init() {
|
||||
if err := gres.Add("%s"); err != nil {
|
||||
panic("add binary content to resource manager failed: " + err.Error())
|
||||
}
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
// PackWithOption packs the path specified by `srcPaths` into bytes.
|
||||
//
|
||||
// Note that parameter `srcPaths` supports multiple paths join with ','.
|
||||
func PackWithOption(srcPaths string, option defines.PackOption) ([]byte, error) {
|
||||
var buffer = bytes.NewBuffer(nil)
|
||||
err := zipPathWriter(srcPaths, buffer, option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Gzip the data bytes to reduce the size.
|
||||
return gcompress.Gzip(buffer.Bytes(), 9)
|
||||
}
|
||||
|
||||
// PackToFileWithOption packs the path specified by `srcPaths` to target file `dstPath`.
|
||||
//
|
||||
// Note that parameter `srcPaths` supports multiple paths join with ','.
|
||||
func PackToFileWithOption(srcPaths, dstPath string, option defines.PackOption) error {
|
||||
data, err := PackWithOption(srcPaths, option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gfile.PutBytes(dstPath, data)
|
||||
}
|
||||
|
||||
// PackToGoFileWithOption packs the path specified by `srcPaths` to target go file `goFilePath`
|
||||
// with given package name `pkgName`.
|
||||
//
|
||||
// Note that parameter `srcPaths` supports multiple paths join with ','.
|
||||
func PackToGoFileWithOption(srcPath, goFilePath, pkgName string, option defines.PackOption) error {
|
||||
data, err := PackWithOption(srcPath, option)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return gfile.PutContents(
|
||||
goFilePath,
|
||||
fmt.Sprintf(gstr.TrimLeft(packedGoSourceTemplate), pkgName, gbase64.EncodeToString(data)),
|
||||
)
|
||||
}
|
||||
|
||||
// Unpack unpacks the content specified by `path` to []*File.
|
||||
func Unpack(path string) ([]defines.File, error) {
|
||||
realPath, err := gfile.Search(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return UnpackContent(gfile.GetContents(realPath))
|
||||
}
|
||||
|
||||
// UnpackContent unpacks the content to []File.
|
||||
func UnpackContent(content string) ([]defines.File, error) {
|
||||
var (
|
||||
err error
|
||||
data []byte
|
||||
)
|
||||
if isHexStr(content) {
|
||||
// It here keeps compatible with old version packing string using hex string.
|
||||
// TODO remove this support in the future.
|
||||
data, err = gcompress.UnGzip(hexStrToBytes(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else if isBase64(content) {
|
||||
// New version packing string using base64.
|
||||
b, err := gbase64.DecodeString(content)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data, err = gcompress.UnGzip(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
data, err = gcompress.UnGzip([]byte(content))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
reader, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
|
||||
if err != nil {
|
||||
err = gerror.Wrapf(err, `create zip reader failed`)
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
fs = NewFS()
|
||||
array = make([]defines.File, len(reader.File))
|
||||
)
|
||||
for i, file := range reader.File {
|
||||
array[i] = &FileImp{
|
||||
file: file,
|
||||
fs: fs,
|
||||
}
|
||||
}
|
||||
return array, nil
|
||||
}
|
||||
|
||||
// isBase64 checks and returns whether given content `s` is base64 string.
|
||||
// It returns true if `s` is base64 string, or false if not.
|
||||
func isBase64(s string) bool {
|
||||
var r bool
|
||||
for i := 0; i < len(s); i++ {
|
||||
r = (s[i] >= '0' && s[i] <= '9') ||
|
||||
(s[i] >= 'a' && s[i] <= 'z') ||
|
||||
(s[i] >= 'A' && s[i] <= 'Z') ||
|
||||
(s[i] == '+' || s[i] == '-') ||
|
||||
(s[i] == '_' || s[i] == '/') || s[i] == '='
|
||||
if !r {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isHexStr checks and returns whether given content `s` is hex string.
|
||||
// It returns true if `s` is hex string, or false if not.
|
||||
func isHexStr(s string) bool {
|
||||
var r bool
|
||||
for i := 0; i < len(s); i++ {
|
||||
r = (s[i] >= '0' && s[i] <= '9') ||
|
||||
(s[i] >= 'a' && s[i] <= 'f') ||
|
||||
(s[i] >= 'A' && s[i] <= 'F')
|
||||
if !r {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// hexStrToBytes converts hex string content to []byte.
|
||||
func hexStrToBytes(s string) []byte {
|
||||
src := []byte(s)
|
||||
dst := make([]byte, hex.DecodedLen(len(src)))
|
||||
_, _ = hex.Decode(dst, src)
|
||||
return dst
|
||||
}
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gres
|
||||
package fs_res
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/internal/fileinfo"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/defines"
|
||||
"github.com/gogf/gf/v2/text/gregex"
|
||||
)
|
||||
|
||||
@ -24,7 +25,7 @@ import (
|
||||
//
|
||||
// Note that the parameter `paths` can be either a directory or a file, which
|
||||
// supports multiple paths join with ','.
|
||||
func zipPathWriter(paths string, writer io.Writer, option ...Option) error {
|
||||
func zipPathWriter(paths string, writer io.Writer, option ...defines.PackOption) error {
|
||||
zipWriter := zip.NewWriter(writer)
|
||||
defer zipWriter.Close()
|
||||
for _, path := range strings.Split(paths, ",") {
|
||||
@ -40,11 +41,11 @@ func zipPathWriter(paths string, writer io.Writer, option ...Option) error {
|
||||
// The parameter `exclude` specifies the exclusive file path that is not compressed to `zipWriter`,
|
||||
// commonly the destination zip file path.
|
||||
// The unnecessary parameter `prefix` indicates the path prefix for zip file.
|
||||
func doZipPathWriter(srcPath string, zipWriter *zip.Writer, option ...Option) error {
|
||||
func doZipPathWriter(srcPath string, zipWriter *zip.Writer, option ...defines.PackOption) error {
|
||||
var (
|
||||
err error
|
||||
files []string
|
||||
usedOption Option
|
||||
usedOption defines.PackOption
|
||||
absolutePath string
|
||||
)
|
||||
if len(option) > 0 {
|
||||
@ -4,63 +4,46 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
package gres
|
||||
package fs_std
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/defines"
|
||||
)
|
||||
|
||||
// StdFS implements the FS interface using the standard library fs.FS.
|
||||
type StdFS struct {
|
||||
// FS implements the FS interface using the standard library fs.FS.
|
||||
type FS struct {
|
||||
fs fs.FS
|
||||
}
|
||||
|
||||
var _ FS = (*StdFS)(nil)
|
||||
var _ defines.FS = (*FS)(nil)
|
||||
|
||||
// NewStdFS creates and returns a new StdFS.
|
||||
func NewStdFS(fs fs.FS) *StdFS {
|
||||
return &StdFS{
|
||||
func NewFS(fs fs.FS) *FS {
|
||||
return &FS{
|
||||
fs: fs,
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns the file with given path.
|
||||
func (fs *StdFS) Get(path string) File {
|
||||
func (fs *FS) Get(path string) defines.File {
|
||||
f, err := fs.fs.Open(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Read the content
|
||||
content, err := io.ReadAll(f)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
file := &localFile{
|
||||
name: info.Name(),
|
||||
path: path,
|
||||
file: info,
|
||||
content: content,
|
||||
fs: fs,
|
||||
file := &FileImp{
|
||||
path: path,
|
||||
file: f,
|
||||
fs: fs,
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
// IsEmpty checks and returns whether the resource is empty.
|
||||
func (fs *StdFS) IsEmpty() bool {
|
||||
func (fs *FS) IsEmpty() bool {
|
||||
if dir, ok := fs.fs.(interface {
|
||||
ReadDir(name string) ([]os.DirEntry, error)
|
||||
}); ok {
|
||||
@ -75,9 +58,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 *FS) ScanDir(path string, pattern string, recursive ...bool) []defines.File {
|
||||
var (
|
||||
files = make([]File, 0)
|
||||
files = make([]defines.File, 0)
|
||||
isRecursive = len(recursive) > 0 && recursive[0]
|
||||
)
|
||||
err := fs.walkDir(path, func(path string, d os.DirEntry, err error) error {
|
||||
@ -109,7 +92,7 @@ func (fs *StdFS) ScanDir(path string, pattern string, recursive ...bool) []File
|
||||
|
||||
// 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 {
|
||||
func (fs *FS) 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 {
|
||||
@ -151,3 +134,7 @@ func (fs *StdFS) walkDir(path string, fn func(path string, d os.DirEntry, err er
|
||||
}
|
||||
return gerror.New("filesystem does not implement ReadDir")
|
||||
}
|
||||
|
||||
func (fs *FS) ListAll() []defines.File {
|
||||
return fs.ScanDir(".", "*", true)
|
||||
}
|
||||
132
os/gres/internal/fs_std/fs_std_file.go
Normal file
132
os/gres/internal/fs_std/fs_std_file.go
Normal file
@ -0,0 +1,132 @@
|
||||
// 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 fs_std
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/internal/intlog"
|
||||
"github.com/gogf/gf/v2/os/gfile"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/defines"
|
||||
"github.com/gogf/gf/v2/text/gstr"
|
||||
)
|
||||
|
||||
// FileImp implements the interface fs.File.
|
||||
type FileImp struct {
|
||||
path string
|
||||
file fs.File // File is the underlying file object
|
||||
fs defines.FS // FS is the file system that contains this file
|
||||
}
|
||||
|
||||
var _ defines.File = (*FileImp)(nil)
|
||||
|
||||
func (f *FileImp) Name() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
// FileInfo returns an os.FileInfo describing this file
|
||||
func (f *FileImp) FileInfo() os.FileInfo {
|
||||
info, err := f.Stat()
|
||||
if err != nil {
|
||||
intlog.Error(context.Background(), err)
|
||||
return nil
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
// Stat returns the FileInfo structure describing file.
|
||||
func (f *FileImp) Stat() (os.FileInfo, error) {
|
||||
return f.file.Stat()
|
||||
}
|
||||
|
||||
func (f *FileImp) Open() (io.ReadCloser, error) {
|
||||
return f.file, nil
|
||||
}
|
||||
|
||||
func (f *FileImp) HttpFile() (http.File, error) {
|
||||
return NewHttpFile(f.fs, f.file)
|
||||
}
|
||||
|
||||
// Content returns the file content
|
||||
func (f *FileImp) Content() []byte {
|
||||
readCloser, err := f.Open()
|
||||
if err != nil {
|
||||
intlog.Error(context.Background(), err)
|
||||
return nil
|
||||
}
|
||||
defer readCloser.Close()
|
||||
content, err := io.ReadAll(readCloser)
|
||||
if err != nil {
|
||||
intlog.Error(context.Background(), err)
|
||||
return nil
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
// Export exports and saves all its sub files to specified system path `dst` recursively.
|
||||
func (f *FileImp) Export(dst string, option ...defines.ExportOption) error {
|
||||
var (
|
||||
err error
|
||||
name string
|
||||
path string
|
||||
exportOption defines.ExportOption
|
||||
exportFiles []defines.File
|
||||
)
|
||||
if f.FileInfo().IsDir() {
|
||||
exportFiles = f.fs.ScanDir(f.Name(), "*", 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
|
||||
}
|
||||
|
||||
type jsonFileInfo struct {
|
||||
Name string
|
||||
Size int64
|
||||
Time time.Time
|
||||
IsDir bool
|
||||
}
|
||||
|
||||
// MarshalJSON implements the interface MarshalJSON for json.Marshal.
|
||||
func (f *FileImp) MarshalJSON() ([]byte, error) {
|
||||
info := f.FileInfo()
|
||||
return gjson.Marshal(jsonFileInfo{
|
||||
Name: f.Name(),
|
||||
Size: info.Size(),
|
||||
Time: info.ModTime(),
|
||||
IsDir: info.IsDir(),
|
||||
})
|
||||
}
|
||||
86
os/gres/internal/fs_std/fs_std_file_http.go
Normal file
86
os/gres/internal/fs_std/fs_std_file_http.go
Normal file
@ -0,0 +1,86 @@
|
||||
// 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 fs_std
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gogf/gf/v2/errors/gcode"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
"github.com/gogf/gf/v2/os/gres/internal/defines"
|
||||
)
|
||||
|
||||
// HttpFileImp implements the interface fs.File.
|
||||
type HttpFileImp struct {
|
||||
fs defines.FS // FS is the file system that contains this file
|
||||
fsFile fs.File // File is the underlying file object
|
||||
readSeeker io.ReadSeeker // ReadCloser is the underlying file object
|
||||
}
|
||||
|
||||
var _ http.File = (*HttpFileImp)(nil)
|
||||
|
||||
func NewHttpFile(fs defines.FS, fsFile fs.File) (*HttpFileImp, error) {
|
||||
content, err := io.ReadAll(fsFile)
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCodef(gcode.CodeOperationFailed, err, `read zip file content failed`)
|
||||
}
|
||||
return &HttpFileImp{
|
||||
readSeeker: bytes.NewReader(content),
|
||||
fsFile: fsFile,
|
||||
fs: fs,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Stat returns the FileInfo structure describing file.
|
||||
func (f *HttpFileImp) Stat() (os.FileInfo, error) {
|
||||
return f.fsFile.Stat()
|
||||
}
|
||||
|
||||
// Close implements interface of http.File.
|
||||
func (f *HttpFileImp) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Readdir implements Readdir interface of http.File.
|
||||
func (f *HttpFileImp) Readdir(count int) ([]os.FileInfo, error) {
|
||||
info, err := f.fsFile.Stat()
|
||||
if err != nil {
|
||||
return nil, gerror.WrapCodef(gcode.CodeOperationFailed, err, `get file info failed`)
|
||||
}
|
||||
files := f.fs.ScanDir(info.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 *HttpFileImp) Read(b []byte) (n int, err error) {
|
||||
if n, err = f.readSeeker.Read(b); err != nil {
|
||||
err = gerror.WrapCodef(gcode.CodeOperationFailed, err, `read content failed`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Seek implements the io.Seeker interface.
|
||||
func (f *HttpFileImp) Seek(offset int64, whence int) (n int64, err error) {
|
||||
if n, err = f.readSeeker.Seek(offset, whence); err != nil {
|
||||
err = gerror.Wrapf(err, `seek failed for offset %d, whence %d`, offset, whence)
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -121,7 +121,7 @@ func (view *View) ParseOption(ctx context.Context, option Option) (result string
|
||||
path string
|
||||
folder string
|
||||
content string
|
||||
resource *gres.File
|
||||
resource gres.File
|
||||
)
|
||||
// Searching the absolute file path for `file`.
|
||||
path, folder, resource, err = view.searchFile(ctx, option.File)
|
||||
@ -371,7 +371,7 @@ func (view *View) formatTemplateObjectCreatingError(filePath, tplName string, er
|
||||
|
||||
// searchFile returns the absolute path of the `file` and its template folder path.
|
||||
// The returned `folder` is the template folder path, not the folder of the template file `path`.
|
||||
func (view *View) searchFile(ctx context.Context, file string) (path string, folder string, resource *gres.File, err error) {
|
||||
func (view *View) searchFile(ctx context.Context, file string) (path string, folder string, resource gres.File, err error) {
|
||||
var tempPath string
|
||||
// Firstly, checking the resource manager.
|
||||
if !gres.IsEmpty() {
|
||||
|
||||
Reference in New Issue
Block a user