mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
improve gfsnotify/gview and container
This commit is contained in:
@ -4,28 +4,36 @@ import (
|
||||
"github.com/gogf/gf/frame/g"
|
||||
"github.com/gogf/gf/net/ghttp"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"io"
|
||||
)
|
||||
|
||||
// 执行文件上传处理,上传到系统临时目录 /tmp
|
||||
// Upload uploads file to /tmp .
|
||||
func Upload(r *ghttp.Request) {
|
||||
if f, h, e := r.FormFile("upload-file"); e == nil {
|
||||
defer f.Close()
|
||||
name := gfile.Basename(h.Filename)
|
||||
buffer := make([]byte, h.Size)
|
||||
f.Read(buffer)
|
||||
gfile.PutBytes("/tmp/"+name, buffer)
|
||||
r.Response.Write(name + " uploaded successly")
|
||||
} else {
|
||||
r.Response.Write(e.Error())
|
||||
f, h, e := r.FormFile("upload-file")
|
||||
if e != nil {
|
||||
r.Response.Write(e)
|
||||
}
|
||||
defer f.Close()
|
||||
savePath := "/tmp/" + gfile.Basename(h.Filename)
|
||||
file, err := gfile.Create(savePath)
|
||||
if err != nil {
|
||||
r.Response.Write(err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
if _, err := io.Copy(file, f); err != nil {
|
||||
r.Response.Write(err)
|
||||
return
|
||||
}
|
||||
r.Response.Write("upload successfully")
|
||||
}
|
||||
|
||||
// 展示文件上传页面
|
||||
// UploadShow shows uploading page.
|
||||
func UploadShow(r *ghttp.Request) {
|
||||
r.Response.Write(`
|
||||
<html>
|
||||
<head>
|
||||
<title>上传文件</title>
|
||||
<title>GF UploadFile Demo</title>
|
||||
</head>
|
||||
<body>
|
||||
<form enctype="multipart/form-data" action="/upload" method="post">
|
||||
@ -39,8 +47,10 @@ func UploadShow(r *ghttp.Request) {
|
||||
|
||||
func main() {
|
||||
s := g.Server()
|
||||
s.BindHandler("/upload", Upload)
|
||||
s.BindHandler("/upload/show", UploadShow)
|
||||
s.Group("/upload", func(g *ghttp.RouterGroup) {
|
||||
g.ALL("/", Upload)
|
||||
g.ALL("/show", UploadShow)
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.Run()
|
||||
}
|
||||
|
||||
@ -12,8 +12,8 @@ import (
|
||||
func main() {
|
||||
gres.Dump()
|
||||
|
||||
v := g.View()
|
||||
v.SetPath("template/layout1")
|
||||
//v := g.View()
|
||||
//v.SetPath("template/layout1")
|
||||
|
||||
s := g.Server()
|
||||
s.SetIndexFolder(true)
|
||||
@ -22,8 +22,8 @@ func main() {
|
||||
fmt.Println(r.URL.Path, r.IsFileRequest())
|
||||
})
|
||||
s.BindHandler("/template", func(r *ghttp.Request) {
|
||||
r.Response.WriteTpl("layout.html")
|
||||
r.Response.WriteTpl("layout1/layout.html")
|
||||
})
|
||||
s.SetPort(8199)
|
||||
s.SetPort(8198)
|
||||
s.Run()
|
||||
}
|
||||
|
||||
@ -194,7 +194,9 @@ func (m *AnyAnyMap) doSetWithLockCheck(key interface{}, value interface{}) inter
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
}
|
||||
m.data[key] = value
|
||||
if value != nil {
|
||||
m.data[key] = value
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@ -181,12 +181,11 @@ func (m *IntStrMap) Pops(size int) map[int]string {
|
||||
// It returns value with given <key>.
|
||||
func (m *IntStrMap) doSetWithLockCheck(key int, value string) string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if v, ok := m.data[key]; ok {
|
||||
m.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
m.mu.Unlock()
|
||||
return value
|
||||
}
|
||||
|
||||
@ -223,7 +222,9 @@ func (m *IntStrMap) GetOrSetFuncLock(key int, f func() string) string {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
if v != "" {
|
||||
m.data[key] = v
|
||||
}
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
|
||||
@ -181,12 +181,11 @@ func (m *StrStrMap) Pops(size int) map[string]string {
|
||||
// It returns value with given <key>.
|
||||
func (m *StrStrMap) doSetWithLockCheck(key string, value string) string {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
if v, ok := m.data[key]; ok {
|
||||
m.mu.Unlock()
|
||||
return v
|
||||
}
|
||||
m.data[key] = value
|
||||
m.mu.Unlock()
|
||||
return value
|
||||
}
|
||||
|
||||
@ -225,7 +224,9 @@ func (m *StrStrMap) GetOrSetFuncLock(key string, f func() string) string {
|
||||
return v
|
||||
}
|
||||
v = f()
|
||||
m.data[key] = v
|
||||
if v != "" {
|
||||
m.data[key] = v
|
||||
}
|
||||
return v
|
||||
} else {
|
||||
return v
|
||||
|
||||
@ -246,7 +246,9 @@ func (m *ListMap) doSetWithLockCheck(key interface{}, value interface{}) interfa
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
}
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
if value != nil {
|
||||
m.data[key] = m.list.PushBack(&gListMapNode{key, value})
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@ -109,7 +109,9 @@ func (set *Set) doAddWithLockCheck(item interface{}, value interface{}) interfac
|
||||
item = value
|
||||
}
|
||||
}
|
||||
set.data[item] = struct{}{}
|
||||
if item != nil {
|
||||
set.data[item] = struct{}{}
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
|
||||
@ -103,7 +103,9 @@ func (set *StrSet) doAddWithLockCheck(item string, value interface{}) string {
|
||||
item = value.(string)
|
||||
}
|
||||
}
|
||||
set.data[item] = struct{}{}
|
||||
if item != "" {
|
||||
set.data[item] = struct{}{}
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
|
||||
@ -130,7 +130,9 @@ func (tree *AVLTree) doSetWithLockCheck(key interface{}, value interface{}) inte
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
}
|
||||
tree.put(key, value, nil, &tree.root)
|
||||
if value != nil {
|
||||
tree.put(key, value, nil, &tree.root)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@ -128,7 +128,9 @@ func (tree *BTree) doSetWithLockCheck(key interface{}, value interface{}) interf
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
}
|
||||
tree.doSet(key, value)
|
||||
if value != nil {
|
||||
tree.doSet(key, value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@ -170,7 +170,9 @@ func (tree *RedBlackTree) doSetWithLockCheck(key interface{}, value interface{})
|
||||
if f, ok := value.(func() interface{}); ok {
|
||||
value = f()
|
||||
}
|
||||
tree.doSet(key, value)
|
||||
if value != nil {
|
||||
tree.doSet(key, value)
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
|
||||
@ -252,16 +252,28 @@ func (c *Config) FilePath(file ...string) (path string) {
|
||||
if len(file) > 0 {
|
||||
name = file[0]
|
||||
}
|
||||
c.paths.RLockFunc(func(array []string) {
|
||||
for _, prefix := range array {
|
||||
// Firstly checking the resource manager.
|
||||
for _, v := range resourceTryFiles {
|
||||
if file := gres.Get(prefix + v + name); file != nil {
|
||||
path = file.Name()
|
||||
return
|
||||
// Searching resource manager.
|
||||
if !gres.IsEmpty() {
|
||||
for _, v := range resourceTryFiles {
|
||||
if file := gres.Get(v + name); file != nil {
|
||||
path = file.Name()
|
||||
return
|
||||
}
|
||||
}
|
||||
c.paths.RLockFunc(func(array []string) {
|
||||
for _, prefix := range array {
|
||||
for _, v := range resourceTryFiles {
|
||||
if file := gres.Get(prefix + v + name); file != nil {
|
||||
path = file.Name()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
// Secondly checking the file system.
|
||||
})
|
||||
}
|
||||
// Searching the file system.
|
||||
c.paths.RLockFunc(func(array []string) {
|
||||
for _, prefix := range array {
|
||||
if path, _ = gspath.Search(prefix, name); path != "" {
|
||||
return
|
||||
}
|
||||
@ -270,15 +282,6 @@ func (c *Config) FilePath(file ...string) (path string) {
|
||||
}
|
||||
}
|
||||
})
|
||||
// Checking the configuration file in default paths.
|
||||
if path == "" && !gres.IsEmpty() {
|
||||
for _, v := range resourceTryFiles {
|
||||
if file := gres.Get(v + name); file != nil {
|
||||
path = file.Name()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@ -252,6 +252,7 @@ func CopyDir(src string, dst string) (err error) {
|
||||
}
|
||||
|
||||
// DirNames returns sub-file names of given directory <path>.
|
||||
// Note that the returned names are NOT absolute paths.
|
||||
func DirNames(path string) ([]string, error) {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
|
||||
@ -56,6 +56,8 @@ func ScanDirFile(path string, pattern string, recursive ...bool) ([]string, erro
|
||||
// using the ',' symbol to separate multiple patterns.
|
||||
//
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
//
|
||||
// It returns only files except folders if <onlyFile> is true.
|
||||
func doScanDir(path string, pattern string, recursive bool, onlyFile bool) ([]string, error) {
|
||||
list := ([]string)(nil)
|
||||
file, err := os.Open(path)
|
||||
|
||||
@ -5,13 +5,13 @@
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gfsnotify provides a platform-independent interface for file system notifications.
|
||||
//
|
||||
// 文件监控.
|
||||
package gfsnotify
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gset"
|
||||
"time"
|
||||
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/gogf/gf/container/glist"
|
||||
@ -21,36 +21,37 @@ import (
|
||||
"github.com/gogf/gf/os/gcache"
|
||||
)
|
||||
|
||||
// 监听管理对象
|
||||
// Watcher is the monitor for file changes.
|
||||
type Watcher struct {
|
||||
watcher *fsnotify.Watcher // 底层fsnotify对象
|
||||
events *gqueue.Queue // 过滤后的事件通知,不会出现重复事件
|
||||
cache *gcache.Cache // 缓存对象,主要用于事件重复过滤
|
||||
callbacks *gmap.StrAnyMap // 注册的所有绝对路径(文件/目录)及其对应的回调函数列表map
|
||||
closeChan chan struct{} // 关闭事件
|
||||
watcher *fsnotify.Watcher // Underlying fsnotify object.
|
||||
events *gqueue.Queue // Used for internal event management.
|
||||
cache *gcache.Cache // Used for repeated event filter.
|
||||
nameSet *gset.StrSet // Used for AddOnce feature.
|
||||
callbacks *gmap.StrAnyMap // Path(file/folder) to callbacks mapping.
|
||||
closeChan chan struct{} // Used for watcher closing notification.
|
||||
}
|
||||
|
||||
// 注册的监听回调方法
|
||||
// Callback is the callback function for Watcher.
|
||||
type Callback struct {
|
||||
Id int // 唯一ID
|
||||
Func func(event *Event) // 回调方法
|
||||
Path string // 监听的文件/目录
|
||||
elem *glist.Element // 指向回调函数链表中的元素项位置(便于删除)
|
||||
recursive bool // 当目录时,是否递归监听(使用在子文件/目录回溯查找回调函数时)
|
||||
Id int // Unique id for callback object.
|
||||
Func func(event *Event) // Callback function.
|
||||
Path string // Bound file path (absolute).
|
||||
name string // Registered name for AddOnce.
|
||||
elem *glist.Element // Element in the callbacks of watcher.
|
||||
recursive bool // Is bound to path recursively or not.
|
||||
}
|
||||
|
||||
// 监听事件对象
|
||||
// Event is the event produced by underlying fsnotify.
|
||||
type Event struct {
|
||||
event fsnotify.Event // 底层事件对象
|
||||
Path string // 文件绝对路径
|
||||
Op Op // 触发监听的文件操作
|
||||
Watcher *Watcher // 事件对应的监听对象
|
||||
event fsnotify.Event // Underlying event.
|
||||
Path string // Absolute file path.
|
||||
Op Op // File operation.
|
||||
Watcher *Watcher // Parent watcher.
|
||||
}
|
||||
|
||||
// 按位进行识别的操作集合
|
||||
// Op is the bits union for file operations.
|
||||
type Op uint32
|
||||
|
||||
// 必须放到一个const分组里面
|
||||
const (
|
||||
CREATE Op = 1 << iota
|
||||
WRITE
|
||||
@ -60,26 +61,24 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
REPEAT_EVENT_FILTER_INTERVAL = 1 // (毫秒)重复事件过滤间隔
|
||||
gFSNOTIFY_EVENT_EXIT = "exit" // 是否退出回调执行
|
||||
REPEAT_EVENT_FILTER_DURATION = time.Millisecond // Duration for repeated event filter.
|
||||
gFSNOTIFY_EVENT_EXIT = "exit" // Custom exit event for internal usage.
|
||||
)
|
||||
|
||||
var (
|
||||
// 默认的Watcher对象
|
||||
defaultWatcher, _ = New()
|
||||
// 默认的watchers是否初始化,使用时才创建
|
||||
watcherInited = gtype.NewBool()
|
||||
// 回调方法ID与对象指针的映射哈希表,用于根据ID快速查找回调对象
|
||||
callbackIdMap = gmap.NewIntAnyMap(true)
|
||||
// 回调函数的ID生成器(原子操作)
|
||||
callbackIdGenerator = gtype.NewInt()
|
||||
defaultWatcher, _ = New() // Default watcher.
|
||||
callbackIdMap = gmap.NewIntAnyMap(true) // Id to callback mapping.
|
||||
callbackIdGenerator = gtype.NewInt() // Atomic id generator for callback.
|
||||
)
|
||||
|
||||
// 创建监听管理对象,主要注意的是创建监听对象会占用系统的inotify句柄数量,受到 fs.inotify.max_user_instances 的限制
|
||||
// New creates and returns a new watcher.
|
||||
// Note that the watcher number is limited by the file handle setting of the system.
|
||||
// Eg: fs.inotify.max_user_instances system variable in linux systems.
|
||||
func New() (*Watcher, error) {
|
||||
w := &Watcher{
|
||||
cache: gcache.New(),
|
||||
events: gqueue.New(),
|
||||
nameSet: gset.NewStrSet(true),
|
||||
closeChan: make(chan struct{}),
|
||||
callbacks: gmap.NewStrAnyMap(true),
|
||||
}
|
||||
@ -93,17 +92,27 @@ func New() (*Watcher, error) {
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// 添加对指定文件/目录的监听,并给定回调函数;如果给定的是一个目录,默认递归监控。
|
||||
// Add monitors <path> using default watcher with callback function <callbackFunc>.
|
||||
// The optional parameter <recursive> specifies whether monitoring the <path> recursively, which is true in default.
|
||||
func Add(path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) {
|
||||
return defaultWatcher.Add(path, callbackFunc, recursive...)
|
||||
}
|
||||
|
||||
// 递归移除对指定文件/目录的所有监听回调
|
||||
// AddOnce monitors <path> using default watcher with callback function <callbackFunc> only once using unique name <name>.
|
||||
// If AddOnce is called multiple times with the same <name> parameter, <path> is only added to monitor once. It returns error
|
||||
// if it's called twice with the same <name>.
|
||||
//
|
||||
// The optional parameter <recursive> specifies whether monitoring the <path> recursively, which is true in default.
|
||||
func AddOnce(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) {
|
||||
return defaultWatcher.AddOnce(name, path, callbackFunc, recursive...)
|
||||
}
|
||||
|
||||
// Remove removes all monitoring callbacks of given <path> from watcher recursively.
|
||||
func Remove(path string) error {
|
||||
return defaultWatcher.Remove(path)
|
||||
}
|
||||
|
||||
// 根据指定的回调函数ID,移出指定的inotify回调函数
|
||||
// RemoveCallback removes specified callback with given id from watcher.
|
||||
func RemoveCallback(callbackId int) error {
|
||||
callback := (*Callback)(nil)
|
||||
if r := callbackIdMap.Get(callbackId); r != nil {
|
||||
@ -116,7 +125,7 @@ func RemoveCallback(callbackId int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 在回调方法中调用该方法退出回调注册
|
||||
// Exit is only used in the callback function, which can be used to remove current callback from the watcher.
|
||||
func Exit() {
|
||||
panic(gFSNOTIFY_EVENT_EXIT)
|
||||
}
|
||||
|
||||
@ -6,31 +6,32 @@
|
||||
|
||||
package gfsnotify
|
||||
|
||||
// String returns current event as string.
|
||||
func (e *Event) String() string {
|
||||
return e.event.String()
|
||||
}
|
||||
|
||||
// 文件/目录创建
|
||||
// IsCreate checks whether current event contains file/folder create event.
|
||||
func (e *Event) IsCreate() bool {
|
||||
return e.Op == 1 || e.Op&CREATE == CREATE
|
||||
}
|
||||
|
||||
// 文件/目录修改
|
||||
// IsWrite checks whether current event contains file/folder write event.
|
||||
func (e *Event) IsWrite() bool {
|
||||
return e.Op&WRITE == WRITE
|
||||
}
|
||||
|
||||
// 文件/目录删除
|
||||
// IsRemove checks whether current event contains file/folder remove event.
|
||||
func (e *Event) IsRemove() bool {
|
||||
return e.Op&REMOVE == REMOVE
|
||||
}
|
||||
|
||||
// 文件/目录重命名
|
||||
// IsRename checks whether current event contains file/folder rename event.
|
||||
func (e *Event) IsRename() bool {
|
||||
return e.Op&RENAME == RENAME
|
||||
}
|
||||
|
||||
// 文件/目录修改权限
|
||||
// IsChmod checks whether current event contains file/folder chmod event.
|
||||
func (e *Event) IsChmod() bool {
|
||||
return e.Op&CHMOD == CHMOD
|
||||
}
|
||||
|
||||
@ -14,13 +14,19 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// 获取指定文件路径的目录地址绝对路径
|
||||
// fileDir returns all but the last element of path, typically the path's directory.
|
||||
// After dropping the final element, Dir calls Clean on the path and trailing
|
||||
// slashes are removed.
|
||||
// If the path is empty, Dir returns ".".
|
||||
// If the path consists entirely of separators, Dir returns a single separator.
|
||||
// The returned path does not end in a separator unless it is the root directory.
|
||||
func fileDir(path string) string {
|
||||
return filepath.Dir(path)
|
||||
}
|
||||
|
||||
// 将所给定的路径转换为绝对路径
|
||||
// 并判断文件路径是否存在,如果文件不存在,那么返回空字符串
|
||||
// fileRealPath converts the given <path> to its absolute path
|
||||
// and checks if the file path exists.
|
||||
// If the file does not exist, return an empty string.
|
||||
func fileRealPath(path string) string {
|
||||
p, err := filepath.Abs(path)
|
||||
if err != nil {
|
||||
@ -32,7 +38,7 @@ func fileRealPath(path string) string {
|
||||
return p
|
||||
}
|
||||
|
||||
// 判断所给路径文件/文件夹是否存在
|
||||
// fileExists checks whether given <path> exist.
|
||||
func fileExists(path string) bool {
|
||||
if _, err := os.Stat(path); !os.IsNotExist(err) {
|
||||
return true
|
||||
@ -40,7 +46,7 @@ func fileExists(path string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// 判断所给路径是否为文件夹
|
||||
// fileIsDir checks whether given <path> a directory.
|
||||
func fileIsDir(path string) bool {
|
||||
s, err := os.Stat(path)
|
||||
if err != nil {
|
||||
@ -49,21 +55,18 @@ func fileIsDir(path string) bool {
|
||||
return s.IsDir()
|
||||
}
|
||||
|
||||
// 返回制定目录其子级所有的目录绝对路径(包含自身)
|
||||
// fileAllDirs returns all sub-folders including itself of given <path> recursively.
|
||||
func fileAllDirs(path string) (list []string) {
|
||||
list = []string{path}
|
||||
// 打开目录
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return list
|
||||
}
|
||||
defer file.Close()
|
||||
// 读取目录下的文件列表
|
||||
names, err := file.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return list
|
||||
}
|
||||
// 是否递归遍历
|
||||
for _, name := range names {
|
||||
path := fmt.Sprintf("%s%s%s", path, string(filepath.Separator), name)
|
||||
if fileIsDir(path) {
|
||||
@ -75,7 +78,8 @@ func fileAllDirs(path string) (list []string) {
|
||||
return
|
||||
}
|
||||
|
||||
// 打开目录,并返回其下一级文件列表(绝对路径),按照文件名称大小写进行排序,支持目录递归遍历。
|
||||
// fileScanDir returns all sub-files with absolute paths of given <path>,
|
||||
// It scans directory recursively if given parameter <recursive> is true.
|
||||
func fileScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
|
||||
list, err := doFileScanDir(path, pattern, recursive...)
|
||||
if err != nil {
|
||||
@ -87,34 +91,36 @@ func fileScanDir(path string, pattern string, recursive ...bool) ([]string, erro
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// 内部检索目录方法,支持递归,返回没有排序的文件绝对路径列表结果。
|
||||
// pattern参数支持多个文件名称模式匹配,使用','符号分隔多个模式。
|
||||
// doFileScanDir 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 doFileScanDir(path string, pattern string, recursive ...bool) ([]string, error) {
|
||||
list := ([]string)(nil)
|
||||
// 打开目录
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
// 读取目录下的文件列表
|
||||
names, err := file.Readdirnames(-1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 是否递归遍历
|
||||
filePath := ""
|
||||
for _, name := range names {
|
||||
path := fmt.Sprintf("%s%s%s", path, string(filepath.Separator), name)
|
||||
if fileIsDir(path) && len(recursive) > 0 && recursive[0] {
|
||||
array, _ := doFileScanDir(path, pattern, true)
|
||||
filePath = fmt.Sprintf("%s%s%s", path, string(filepath.Separator), name)
|
||||
if fileIsDir(filePath) && len(recursive) > 0 && recursive[0] {
|
||||
array, _ := doFileScanDir(filePath, pattern, true)
|
||||
if len(array) > 0 {
|
||||
list = append(list, array...)
|
||||
}
|
||||
}
|
||||
// 满足pattern才加入结果列表
|
||||
for _, p := range strings.Split(pattern, ",") {
|
||||
if match, err := filepath.Match(strings.TrimSpace(p), name); err == nil && match {
|
||||
list = append(list, path)
|
||||
list = append(list, filePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,50 +9,69 @@ package gfsnotify
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
|
||||
"github.com/gogf/gf/container/glist"
|
||||
)
|
||||
|
||||
// 添加监控,path参数支持文件或者目录路径,recursive为非必需参数,默认为递归监控(当path为目录时)。
|
||||
// 如果添加目录,这里只会返回目录的callback,按照callback删除时会递归删除。
|
||||
// Add monitors <path> with callback function <callbackFunc> to the watcher.
|
||||
// The optional parameter <recursive> specifies whether monitoring the <path> recursively, which is true in default.
|
||||
func (w *Watcher) Add(path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) {
|
||||
// 首先添加这个文件/目录
|
||||
callback, err = w.addWithCallbackFunc(path, callbackFunc, recursive...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 如果需要递归,那么递归添加其下的子级目录,
|
||||
// 注意!!
|
||||
// 1、这里只递归添加**目录**, 而非文件,因为监控了目录即监控了其下一级的文件;
|
||||
// 2、这里只是添加底层监控对象对**子级所有目录**的监控,没有任何回调函数的设置,在事件产生时会回溯查找父级的回调函数;
|
||||
if fileIsDir(path) && (len(recursive) == 0 || recursive[0]) {
|
||||
for _, subPath := range fileAllDirs(path) {
|
||||
if fileIsDir(subPath) {
|
||||
w.watcher.Add(subPath)
|
||||
return w.AddOnce("", path, callbackFunc, recursive...)
|
||||
}
|
||||
|
||||
// AddOnce monitors <path> with callback function <callbackFunc> only once using unique name <name> to the watcher.
|
||||
// If AddOnce is called multiple times with the same <name> parameter, <path> is only added to monitor once. It returns error
|
||||
// if it's called twice with the same <name>.
|
||||
//
|
||||
// The optional parameter <recursive> specifies whether monitoring the <path> recursively, which is true in default.
|
||||
func (w *Watcher) AddOnce(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) {
|
||||
w.nameSet.AddIfNotExistFuncLock(name, func() string {
|
||||
// Firstly add the path to watcher.
|
||||
callback, err = w.addWithCallbackFunc(name, path, callbackFunc, recursive...)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
// If it's recursive adding, it then adds all sub-folders to the monitor.
|
||||
// NOTE:
|
||||
// 1. It only recursively adds **folders** to the monitor, NOT files,
|
||||
// because if the folders are monitored and their sub-files are also monitored.
|
||||
// 2. It bounds no callbacks to the folders, because it will search the callbacks
|
||||
// from its parent recursively if any event produced.
|
||||
if fileIsDir(path) && (len(recursive) == 0 || recursive[0]) {
|
||||
for _, subPath := range fileAllDirs(path) {
|
||||
if fileIsDir(subPath) {
|
||||
if err := w.watcher.Add(subPath); err != nil {
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return name
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 添加对指定文件/目录的监听,并给定回调函数
|
||||
func (w *Watcher) addWithCallbackFunc(path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) {
|
||||
// 这里统一转换为当前系统的绝对路径,便于统一监控文件名称
|
||||
// addWithCallbackFunc adds the path to underlying monitor, creates and returns a callback object.
|
||||
func (w *Watcher) addWithCallbackFunc(name, path string, callbackFunc func(event *Event), recursive ...bool) (callback *Callback, err error) {
|
||||
// Check and convert the given path to absolute path.
|
||||
if t := fileRealPath(path); t == "" {
|
||||
return nil, errors.New(fmt.Sprintf(`"%s" does not exist`, path))
|
||||
} else {
|
||||
path = t
|
||||
}
|
||||
// Create callback object.
|
||||
callback = &Callback{
|
||||
Id: callbackIdGenerator.Add(1),
|
||||
Func: callbackFunc,
|
||||
Path: path,
|
||||
name: name,
|
||||
recursive: true,
|
||||
}
|
||||
if len(recursive) > 0 {
|
||||
callback.recursive = recursive[0]
|
||||
}
|
||||
// 注册回调函数
|
||||
// Register the callback to watcher.
|
||||
w.callbacks.LockFunc(func(m map[string]interface{}) {
|
||||
list := (*glist.List)(nil)
|
||||
if v, ok := m[path]; !ok {
|
||||
@ -63,23 +82,29 @@ func (w *Watcher) addWithCallbackFunc(path string, callbackFunc func(event *Even
|
||||
}
|
||||
callback.elem = list.PushBack(callback)
|
||||
})
|
||||
// 添加底层监听
|
||||
w.watcher.Add(path)
|
||||
// 添加成功后会注册该callback id到全局的哈希表
|
||||
// Add the path to underlying monitor.
|
||||
if err := w.watcher.Add(path); err != nil {
|
||||
intlog.Error(err)
|
||||
}
|
||||
// Add the callback to global callback map.
|
||||
callbackIdMap.Set(callback.Id, callback)
|
||||
|
||||
intlog.Print("addWithCallbackFunc", name, path, callback.recursive)
|
||||
return
|
||||
}
|
||||
|
||||
// 关闭监听管理对象
|
||||
// Close closes the watcher.
|
||||
func (w *Watcher) Close() {
|
||||
w.events.Close()
|
||||
w.watcher.Close()
|
||||
if err := w.watcher.Close(); err != nil {
|
||||
intlog.Error(err)
|
||||
}
|
||||
close(w.closeChan)
|
||||
}
|
||||
|
||||
// 递归移除对指定文件/目录的所有监听回调
|
||||
// Remove removes monitor and all callbacks associated with the <path> recursively.
|
||||
func (w *Watcher) Remove(path string) error {
|
||||
// 首先移除path注册的回调注册,以及callbackIdMap中的ID
|
||||
// Firstly remove the callbacks of the path.
|
||||
if r := w.callbacks.Remove(path); r != nil {
|
||||
list := r.(*glist.List)
|
||||
for {
|
||||
@ -90,36 +115,39 @@ func (w *Watcher) Remove(path string) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
// 其次递归判断所有的子级是否可删除监听
|
||||
// Secondly remove monitor of all sub-files which have no callbacks.
|
||||
if subPaths, err := fileScanDir(path, "*", true); err == nil && len(subPaths) > 0 {
|
||||
for _, subPath := range subPaths {
|
||||
if w.checkPathCanBeRemoved(subPath) {
|
||||
w.watcher.Remove(subPath)
|
||||
if err := w.watcher.Remove(subPath); err != nil {
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 最后移除底层的监听
|
||||
// Lastly remove the monitor of the path from underlying monitor.
|
||||
return w.watcher.Remove(path)
|
||||
}
|
||||
|
||||
// 判断给定的路径是否可以删除监听(只有所有回调函数都没有了才能删除)
|
||||
// checkPathCanBeRemoved checks whether the given path have no callbacks bound.
|
||||
func (w *Watcher) checkPathCanBeRemoved(path string) bool {
|
||||
// 首先检索path对应的回调函数
|
||||
// Firstly check the callbacks in the watcher directly.
|
||||
if v := w.callbacks.Get(path); v != nil {
|
||||
return false
|
||||
}
|
||||
// 其次查找父级目录有无回调注册
|
||||
// Secondly check its parent whether has callbacks.
|
||||
dirPath := fileDir(path)
|
||||
if v := w.callbacks.Get(dirPath); v != nil {
|
||||
if v := w.callbacks.Get(dirPath); v != nil && v.(*Callback).recursive {
|
||||
return false
|
||||
}
|
||||
// 最后回溯查找递归回调函数
|
||||
// Recursively check its parent.
|
||||
parentDirPath := ""
|
||||
for {
|
||||
parentDirPath := fileDir(dirPath)
|
||||
parentDirPath = fileDir(dirPath)
|
||||
if parentDirPath == dirPath {
|
||||
break
|
||||
}
|
||||
if v := w.callbacks.Get(parentDirPath); v != nil {
|
||||
if v := w.callbacks.Get(parentDirPath); v != nil && v.(*Callback).recursive {
|
||||
return false
|
||||
}
|
||||
dirPath = parentDirPath
|
||||
@ -127,7 +155,7 @@ func (w *Watcher) checkPathCanBeRemoved(path string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// 根据指定的回调函数ID,移出指定的inotify回调函数
|
||||
// RemoveCallback removes callback with given callback id from watcher.
|
||||
func (w *Watcher) RemoveCallback(callbackId int) {
|
||||
callback := (*Callback)(nil)
|
||||
if r := callbackIdMap.Get(callbackId); r != nil {
|
||||
@ -138,5 +166,8 @@ func (w *Watcher) RemoveCallback(callbackId int) {
|
||||
r.(*glist.List).Remove(callback.elem)
|
||||
}
|
||||
callbackIdMap.Remove(callbackId)
|
||||
if callback.name != "" {
|
||||
w.nameSet.Remove(callback.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,20 +8,22 @@ package gfsnotify
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/glist"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
)
|
||||
|
||||
// 监听循环
|
||||
// startWatchLoop starts the loop for event listening fro underlying inotify monitor.
|
||||
func (w *Watcher) startWatchLoop() {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
// 关闭事件
|
||||
// Close event.
|
||||
case <-w.closeChan:
|
||||
return
|
||||
|
||||
// 监听事件
|
||||
// Event listening.
|
||||
case ev := <-w.watcher.Events:
|
||||
//fmt.Println("ev:", ev.String())
|
||||
//intlog.Print(ev.String())
|
||||
// Filter the repeated event in custom duration.
|
||||
w.cache.SetIfNotExist(ev.String(), func() interface{} {
|
||||
w.events.Push(&Event{
|
||||
event: ev,
|
||||
@ -30,33 +32,36 @@ func (w *Watcher) startWatchLoop() {
|
||||
Watcher: w,
|
||||
})
|
||||
return struct{}{}
|
||||
}, REPEAT_EVENT_FILTER_INTERVAL)
|
||||
}, REPEAT_EVENT_FILTER_DURATION)
|
||||
|
||||
case <-w.watcher.Errors:
|
||||
//fmt.Fprintf(os.Stderr, "[gfsnotify] error: %s\n", err.Error())
|
||||
case err := <-w.watcher.Errors:
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 获得文件路径的监听回调,包括层级的监听回调。
|
||||
// getCallbacks searches and returns all callbacks with given <path>.
|
||||
// It also searches its parent for callbacks if they're recursive.
|
||||
func (w *Watcher) getCallbacks(path string) (callbacks []*Callback) {
|
||||
// 首先检索path对应的回调函数
|
||||
// Firstly add the callbacks of itself.
|
||||
if v := w.callbacks.Get(path); v != nil {
|
||||
for _, v := range v.(*glist.List).FrontAll() {
|
||||
callback := v.(*Callback)
|
||||
callbacks = append(callbacks, callback)
|
||||
}
|
||||
}
|
||||
// 其次查找父级目录有无回调注册
|
||||
// Secondly searches its parent for callbacks.
|
||||
dirPath := fileDir(path)
|
||||
if v := w.callbacks.Get(dirPath); v != nil {
|
||||
for _, v := range v.(*glist.List).FrontAll() {
|
||||
callback := v.(*Callback)
|
||||
callbacks = append(callbacks, callback)
|
||||
if callback.recursive {
|
||||
callbacks = append(callbacks, callback)
|
||||
}
|
||||
}
|
||||
}
|
||||
// 最后回溯查找递归回调函数
|
||||
// Lastly searches the parent recursively for callbacks.
|
||||
for {
|
||||
parentDirPath := fileDir(dirPath)
|
||||
if parentDirPath == dirPath {
|
||||
@ -75,62 +80,74 @@ func (w *Watcher) getCallbacks(path string) (callbacks []*Callback) {
|
||||
return
|
||||
}
|
||||
|
||||
// 事件循环(核心逻辑)
|
||||
// startEventLoop is the core event handler.
|
||||
func (w *Watcher) startEventLoop() {
|
||||
go func() {
|
||||
for {
|
||||
if v := w.events.Pop(); v != nil {
|
||||
event := v.(*Event)
|
||||
// 如果该路径一个回调也没有,那么没有必要执行后续逻辑,删除对该文件的监听
|
||||
// If there's no any callback of this path, it removes it from monitor.
|
||||
callbacks := w.getCallbacks(event.Path)
|
||||
if len(callbacks) == 0 {
|
||||
w.watcher.Remove(event.Path)
|
||||
if err := w.watcher.Remove(event.Path); err != nil {
|
||||
intlog.Error(err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
// 如果是删除操作,那么需要判断是否文件真正不存在了,如果存在,那么将此事件认为“假删除”
|
||||
case event.IsRemove():
|
||||
// It should check again the existence of the path.
|
||||
// It adds it back to the monitor if it still exists.
|
||||
if fileExists(event.Path) {
|
||||
// 底层重新添加监控(不用担心重复添加)
|
||||
w.watcher.Add(event.Path)
|
||||
// 修改事件操作为重命名(相当于重命名为自身名称,最终名称没变)
|
||||
// It adds the path back to monitor.
|
||||
// We need no worry about the repeat adding.
|
||||
if err := w.watcher.Add(event.Path); err != nil {
|
||||
intlog.Error(err)
|
||||
}
|
||||
// Change the event to RENAME, which means it renames itself to its origin name.
|
||||
event.Op = RENAME
|
||||
}
|
||||
|
||||
// 如果是重命名操作,那么需要判断是否文件真正不存在了,如果存在,那么将此事件认为“假命名”
|
||||
// (特别是某些编辑器在编辑文件时会先对文件RENAME再CHMOD)
|
||||
case event.IsRename():
|
||||
// It should check again the existence of the path.
|
||||
// It adds it back to the monitor if it still exists.
|
||||
// Especially Some editors might do RENAME and then CHMOD when it's editing file.
|
||||
if fileExists(event.Path) {
|
||||
// 底层有可能去掉了监控, 这里重新添加监控(不用担心重复添加)
|
||||
w.watcher.Add(event.Path)
|
||||
// 修改事件操作为修改属性
|
||||
// It might lost the monitoring for the path, so we add the path back to monitor.
|
||||
// We need no worry about the repeat adding.
|
||||
if err := w.watcher.Add(event.Path); err != nil {
|
||||
intlog.Error(err)
|
||||
}
|
||||
// Change the event to CHMOD.
|
||||
event.Op = CHMOD
|
||||
}
|
||||
|
||||
// 创建文件/目录
|
||||
case event.IsCreate():
|
||||
// =========================================
|
||||
// 注意这里只是添加底层监听,并没有注册任何的回调函数,
|
||||
// 默认的回调函数为父级的递归回调
|
||||
// Note that it here just adds the path to monitor without any callback registering,
|
||||
// because its parent already has the callbacks.
|
||||
// =========================================
|
||||
if fileIsDir(event.Path) {
|
||||
// 递归添加
|
||||
// If it's a folder, it then does adding recursively to monitor.
|
||||
for _, subPath := range fileAllDirs(event.Path) {
|
||||
if fileIsDir(subPath) {
|
||||
w.watcher.Add(subPath)
|
||||
if err := w.watcher.Add(subPath); err != nil {
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 添加文件监听
|
||||
w.watcher.Add(event.Path)
|
||||
// If it's a file, it directly adds it to monitor.
|
||||
if err := w.watcher.Add(event.Path); err != nil {
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
// 执行回调处理,异步处理
|
||||
// Calling the callbacks in order.
|
||||
for _, v := range callbacks {
|
||||
go func(callback *Callback) {
|
||||
defer func() {
|
||||
// 是否退出监控
|
||||
if err := recover(); err != nil {
|
||||
switch err {
|
||||
case gFSNOTIFY_EVENT_EXIT:
|
||||
|
||||
@ -4,8 +4,6 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// go test *.go -bench=".*" -benchmem
|
||||
|
||||
package gfsnotify_test
|
||||
|
||||
import (
|
||||
|
||||
@ -13,6 +13,7 @@ package gview
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"sync"
|
||||
|
||||
"github.com/gogf/gf/i18n/gi18n"
|
||||
@ -29,12 +30,13 @@ import (
|
||||
|
||||
// View object for template engine.
|
||||
type View struct {
|
||||
mu sync.RWMutex
|
||||
paths *garray.StrArray // Searching path array.
|
||||
data map[string]interface{} // Global template variables.
|
||||
funcMap map[string]interface{} // Global template function map.
|
||||
i18nManager *gi18n.Manager // I18n manager for this view.
|
||||
delimiters []string // Customized template delimiters.
|
||||
mu sync.RWMutex
|
||||
paths *garray.StrArray // Searching path array, NOT concurrent safe for performance purpose.
|
||||
data map[string]interface{} // Global template variables.
|
||||
funcMap map[string]interface{} // Global template function map.
|
||||
fileCacheMap *gmap.StrAnyMap // File cache map.
|
||||
i18nManager *gi18n.Manager // I18n manager for this view.
|
||||
delimiters []string // Customized template delimiters.
|
||||
}
|
||||
|
||||
// Params is type for template params.
|
||||
@ -65,11 +67,12 @@ func ParseContent(content string, params Params) (string, error) {
|
||||
// The parameter <path> specifies the template directory path to load template files.
|
||||
func New(path ...string) *View {
|
||||
view := &View{
|
||||
paths: garray.NewStrArray(true),
|
||||
data: make(map[string]interface{}),
|
||||
funcMap: make(map[string]interface{}),
|
||||
i18nManager: gi18n.Instance(),
|
||||
delimiters: make([]string, 2),
|
||||
paths: garray.NewStrArray(),
|
||||
data: make(map[string]interface{}),
|
||||
funcMap: make(map[string]interface{}),
|
||||
fileCacheMap: gmap.NewStrAnyMap(true),
|
||||
i18nManager: gi18n.Instance(),
|
||||
delimiters: make([]string, 2),
|
||||
}
|
||||
if len(path) > 0 && len(path[0]) > 0 {
|
||||
view.SetPath(path[0])
|
||||
|
||||
@ -10,22 +10,23 @@ import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/gogf/gf/encoding/ghash"
|
||||
"github.com/gogf/gf/internal/intlog"
|
||||
"github.com/gogf/gf/os/gfcache"
|
||||
"github.com/gogf/gf/os/gfsnotify"
|
||||
"github.com/gogf/gf/os/gmlock"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/gogf/gf/os/gfcache"
|
||||
|
||||
"github.com/gogf/gf/os/gres"
|
||||
|
||||
"github.com/gogf/gf/container/gmap"
|
||||
"github.com/gogf/gf/encoding/ghash"
|
||||
"github.com/gogf/gf/os/gfile"
|
||||
"github.com/gogf/gf/os/gfsnotify"
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"github.com/gogf/gf/os/gmlock"
|
||||
"github.com/gogf/gf/os/gspath"
|
||||
"github.com/gogf/gf/text/gstr"
|
||||
"github.com/gogf/gf/util/gconv"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -40,6 +41,154 @@ var (
|
||||
resourceTryFiles = []string{"template/", "template", "/template", "/template/"}
|
||||
)
|
||||
|
||||
// ParseContent parses given template file <file>
|
||||
// with given template parameters <params> and function map <funcMap>
|
||||
// and returns the parsed string content.
|
||||
func (view *View) Parse(file string, params ...Params) (result string, err error) {
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
|
||||
var tpl *template.Template
|
||||
// It here uses map cache to enhance the performance.
|
||||
r := view.fileCacheMap.GetOrSetFuncLock(file, func() interface{} {
|
||||
var path, folder string
|
||||
var resource *gres.File
|
||||
// Searching the absolute file path for <file>.
|
||||
path, folder, resource, err = view.searchFile(file)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// Get the template object instance for <folder>.
|
||||
tpl, err = view.getTemplate(folder, fmt.Sprintf(`*%s`, gfile.Ext(path)))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// Using memory lock to ensure concurrent safety for template parsing.
|
||||
gmlock.LockFunc("gview.Parse:"+folder, func() {
|
||||
if resource != nil {
|
||||
tpl, err = tpl.Parse(gconv.UnsafeBytesToStr(resource.Content()))
|
||||
} else {
|
||||
tpl, err = tpl.Parse(gfcache.GetContents(path))
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
// Monitor template files changes using fsnotify asynchronously.
|
||||
if resource == nil {
|
||||
if _, err := gfsnotify.AddOnce("gview.Parse:"+folder, folder, func(event *gfsnotify.Event) {
|
||||
// CLEAR THEM ALL.
|
||||
view.fileCacheMap.Clear()
|
||||
templates.Clear()
|
||||
gfsnotify.Exit()
|
||||
}); err != nil {
|
||||
intlog.Error(err)
|
||||
}
|
||||
}
|
||||
return tpl
|
||||
})
|
||||
if r == nil {
|
||||
return
|
||||
}
|
||||
tpl = r.(*template.Template)
|
||||
// Note that the template variable assignment cannot change the value
|
||||
// of the existing <params> or view.data because both variables are pointers.
|
||||
// It needs to merge the values of the two maps into a new map.
|
||||
var variables map[string]interface{}
|
||||
length := len(view.data)
|
||||
if len(params) > 0 {
|
||||
length += len(params[0])
|
||||
}
|
||||
if length > 0 {
|
||||
variables = make(map[string]interface{}, length)
|
||||
}
|
||||
if len(view.data) > 0 {
|
||||
if len(params) > 0 {
|
||||
if variables == nil {
|
||||
variables = make(map[string]interface{})
|
||||
}
|
||||
for k, v := range params[0] {
|
||||
variables[k] = v
|
||||
}
|
||||
for k, v := range view.data {
|
||||
variables[k] = v
|
||||
}
|
||||
} else {
|
||||
variables = view.data
|
||||
}
|
||||
} else {
|
||||
if len(params) > 0 {
|
||||
variables = params[0]
|
||||
}
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if err := tpl.Execute(buffer, variables); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// TODO any graceful plan to replace "<no value>"?
|
||||
result = gstr.Replace(buffer.String(), "<no value>", "")
|
||||
result = view.i18nTranslate(result, variables)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseContent parses given template content <content>
|
||||
// with given template parameters <params> and function map <funcMap>
|
||||
// and returns the parsed content in []byte.
|
||||
func (view *View) ParseContent(content string, params ...Params) (string, error) {
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
err := (error)(nil)
|
||||
tpl := templates.GetOrSetFuncLock(gCONTENT_TEMPLATE_NAME, func() interface{} {
|
||||
return template.New(gCONTENT_TEMPLATE_NAME).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
}).(*template.Template)
|
||||
// Using memory lock to ensure concurrent safety for content parsing.
|
||||
hash := strconv.FormatUint(ghash.DJBHash64([]byte(content)), 10)
|
||||
gmlock.LockFunc("gview.ParseContent:"+hash, func() {
|
||||
tpl, err = tpl.Parse(content)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Note that the template variable assignment cannot change the value
|
||||
// of the existing <params> or view.data because both variables are pointers.
|
||||
// It needs to merge the values of the two maps into a new map.
|
||||
var variables map[string]interface{}
|
||||
length := len(view.data)
|
||||
if len(params) > 0 {
|
||||
length += len(params[0])
|
||||
}
|
||||
if length > 0 {
|
||||
variables = make(map[string]interface{}, length)
|
||||
}
|
||||
if len(view.data) > 0 {
|
||||
if len(params) > 0 {
|
||||
if variables == nil {
|
||||
variables = make(map[string]interface{})
|
||||
}
|
||||
for k, v := range params[0] {
|
||||
variables[k] = v
|
||||
}
|
||||
for k, v := range view.data {
|
||||
variables[k] = v
|
||||
}
|
||||
} else {
|
||||
variables = view.data
|
||||
}
|
||||
} else {
|
||||
if len(params) > 0 {
|
||||
variables = params[0]
|
||||
}
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if err := tpl.Execute(buffer, variables); err != nil {
|
||||
return "", err
|
||||
}
|
||||
// TODO any graceful plan to replace "<no value>"?
|
||||
result := gstr.Replace(buffer.String(), "<no value>", "")
|
||||
result = view.i18nTranslate(result, variables)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// getTemplate returns the template object associated with given template folder <path>.
|
||||
// It uses template cache to enhance performance, that is, it will return the same template object
|
||||
// with the same given <path>. It will also refresh the template cache
|
||||
@ -48,16 +197,19 @@ func (view *View) getTemplate(path string, pattern string) (tpl *template.Templa
|
||||
r := templates.GetOrSetFuncLock(path, func() interface{} {
|
||||
tpl = template.New(path).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
// Firstly checking the resource manager.
|
||||
if files := gres.ScanDirFile(path, pattern, true); len(files) > 0 {
|
||||
var err error
|
||||
for _, v := range files {
|
||||
_, err = tpl.New(v.FileInfo().Name()).Parse(string(v.Content()))
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
if !gres.IsEmpty() {
|
||||
if files := gres.ScanDirFile(path, pattern, true); len(files) > 0 {
|
||||
var err error
|
||||
for _, v := range files {
|
||||
_, err = tpl.New(v.FileInfo().Name()).Parse(string(v.Content()))
|
||||
if err != nil {
|
||||
glog.Error(err)
|
||||
}
|
||||
}
|
||||
return tpl
|
||||
}
|
||||
return tpl
|
||||
}
|
||||
|
||||
// Secondly checking the file system.
|
||||
files := ([]string)(nil)
|
||||
files, err = gfile.ScanDir(path, pattern, true)
|
||||
@ -67,10 +219,6 @@ func (view *View) getTemplate(path string, pattern string) (tpl *template.Templa
|
||||
if tpl, err = tpl.ParseFiles(files...); err != nil {
|
||||
return nil
|
||||
}
|
||||
_, _ = gfsnotify.Add(path, func(event *gfsnotify.Event) {
|
||||
templates.Remove(path)
|
||||
gfsnotify.Exit()
|
||||
})
|
||||
return tpl
|
||||
})
|
||||
if r != nil {
|
||||
@ -80,24 +228,34 @@ func (view *View) getTemplate(path string, pattern string) (tpl *template.Templa
|
||||
}
|
||||
|
||||
// searchFile returns the found absolute path for <file>, and its template folder path.
|
||||
func (view *View) searchFile(file string) (path string, folder string, err error) {
|
||||
func (view *View) searchFile(file string) (path string, folder string, resource *gres.File, err error) {
|
||||
// Firstly checking the resource manager.
|
||||
view.paths.RLockFunc(func(array []string) {
|
||||
f := (*gres.File)(nil)
|
||||
for _, v := range array {
|
||||
v = strings.TrimRight(v, "/")
|
||||
if f = gres.Get(v + "/" + file); f != nil {
|
||||
path = f.Name()
|
||||
folder = gfile.Dir(path)
|
||||
break
|
||||
}
|
||||
if f = gres.Get(v + "/template/" + file); f != nil {
|
||||
path = f.Name()
|
||||
folder = gfile.Dir(path)
|
||||
break
|
||||
if !gres.IsEmpty() {
|
||||
for _, v := range resourceTryFiles {
|
||||
if resource = gres.Get(v + file); resource != nil {
|
||||
path = resource.Name()
|
||||
folder = v
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
view.paths.RLockFunc(func(array []string) {
|
||||
for _, v := range array {
|
||||
v = strings.TrimRight(v, "/"+gfile.Separator)
|
||||
if resource = gres.Get(v + "/" + file); resource != nil {
|
||||
path = resource.Name()
|
||||
folder = v
|
||||
break
|
||||
}
|
||||
if resource = gres.Get(v + "/template/" + file); resource != nil {
|
||||
path = resource.Name()
|
||||
folder = v + "/template"
|
||||
break
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Secondly checking the file system.
|
||||
if path == "" {
|
||||
view.paths.RLockFunc(func(array []string) {
|
||||
@ -114,16 +272,7 @@ func (view *View) searchFile(file string) (path string, folder string, err error
|
||||
}
|
||||
})
|
||||
}
|
||||
// Checking the configuration file in default paths.
|
||||
if path == "" && !gres.IsEmpty() {
|
||||
for _, v := range resourceTryFiles {
|
||||
if file := gres.Get(v + file); file != nil {
|
||||
path = file.Name()
|
||||
folder = gfile.Dir(path)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error checking.
|
||||
if path == "" {
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
@ -152,118 +301,3 @@ func (view *View) searchFile(file string) (path string, folder string, err error
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ParseContent parses given template file <file>
|
||||
// with given template parameters <params> and function map <funcMap>
|
||||
// and returns the parsed string content.
|
||||
func (view *View) Parse(file string, params ...Params) (parsed string, err error) {
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
path, folder, err := view.searchFile(file)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
tpl, err := view.getTemplate(folder, fmt.Sprintf(`*%s`, gfile.Ext(path)))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Using memory lock to ensure concurrent safety for template parsing.
|
||||
gmlock.LockFunc("gview-parsing:"+folder, func() {
|
||||
if file := gres.Get(path); file != nil {
|
||||
tpl, err = tpl.Parse(string(file.Content()))
|
||||
} else {
|
||||
tpl, err = tpl.Parse(gfcache.GetContents(path))
|
||||
}
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Note that the template variable assignment cannot change the value
|
||||
// of the existing <params> or view.data because both variables are pointers.
|
||||
// It's need to merge the values of the two maps into a new map.
|
||||
variables := (map[string]interface{})(nil)
|
||||
length := len(view.data)
|
||||
if len(params) > 0 {
|
||||
length += len(params[0])
|
||||
}
|
||||
if length > 0 {
|
||||
variables = make(map[string]interface{}, length)
|
||||
}
|
||||
if len(view.data) > 0 {
|
||||
if len(params) > 0 {
|
||||
for k, v := range params[0] {
|
||||
variables[k] = v
|
||||
}
|
||||
for k, v := range view.data {
|
||||
variables[k] = v
|
||||
}
|
||||
} else {
|
||||
variables = view.data
|
||||
}
|
||||
} else {
|
||||
if len(params) > 0 {
|
||||
variables = params[0]
|
||||
}
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if err := tpl.Execute(buffer, variables); err != nil {
|
||||
return "", err
|
||||
}
|
||||
result := gstr.Replace(buffer.String(), "<no value>", "")
|
||||
result = view.i18nTranslate(result, variables)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ParseContent parses given template content <content>
|
||||
// with given template parameters <params> and function map <funcMap>
|
||||
// and returns the parsed content in []byte.
|
||||
func (view *View) ParseContent(content string, params ...Params) (string, error) {
|
||||
view.mu.RLock()
|
||||
defer view.mu.RUnlock()
|
||||
err := (error)(nil)
|
||||
tpl := templates.GetOrSetFuncLock(gCONTENT_TEMPLATE_NAME, func() interface{} {
|
||||
return template.New(gCONTENT_TEMPLATE_NAME).Delims(view.delimiters[0], view.delimiters[1]).Funcs(view.funcMap)
|
||||
}).(*template.Template)
|
||||
// Using memory lock to ensure concurrent safety for content parsing.
|
||||
hash := gconv.String(ghash.DJBHash64([]byte(content)))
|
||||
gmlock.LockFunc("gview-parsing-content:"+hash, func() {
|
||||
tpl, err = tpl.Parse(content)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// Note that the template variable assignment cannot change the value
|
||||
// of the existing <params> or view.data because both variables are pointers.
|
||||
// It's need to merge the values of the two maps into a new map.
|
||||
variables := (map[string]interface{})(nil)
|
||||
length := len(view.data)
|
||||
if len(params) > 0 {
|
||||
length += len(params[0])
|
||||
}
|
||||
if length > 0 {
|
||||
variables = make(map[string]interface{}, length)
|
||||
}
|
||||
if len(view.data) > 0 {
|
||||
if len(params) > 0 {
|
||||
for k, v := range params[0] {
|
||||
variables[k] = v
|
||||
}
|
||||
for k, v := range view.data {
|
||||
variables[k] = v
|
||||
}
|
||||
} else {
|
||||
variables = view.data
|
||||
}
|
||||
} else {
|
||||
if len(params) > 0 {
|
||||
variables = params[0]
|
||||
}
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
if err := tpl.Execute(buffer, variables); err != nil {
|
||||
return "", err
|
||||
}
|
||||
result := gstr.Replace(buffer.String(), "<no value>", "")
|
||||
result = view.i18nTranslate(result, variables)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user