improve gfsnotify/gview and container

This commit is contained in:
John
2019-10-31 23:37:33 +08:00
parent 4204125dce
commit 66355354fc
22 changed files with 482 additions and 351 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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])

View File

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