From b190ee9fe61138e248bbb4636453cd3637fa210d Mon Sep 17 00:00:00 2001 From: John Date: Sat, 17 Nov 2018 02:39:23 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E6=AD=A5=E5=AE=8C=E6=88=90=E7=AC=AC?= =?UTF-8?q?=E4=B8=80=E9=98=B6=E6=AE=B5=E7=9A=84=E6=80=A7=E8=83=BD=E6=94=B9?= =?UTF-8?q?=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- g/net/ghttp/ghttp_response.go | 2 +- g/net/ghttp/ghttp_server.go | 12 ++ g/net/ghttp/ghttp_server_handler.go | 41 ++++-- g/net/ghttp/ghttp_server_router.go | 4 +- g/os/gcache/gcache_mem_cache_item.go | 3 +- g/os/gcfg/gcfg.go | 6 +- g/os/gfile/gfile.go | 8 +- g/os/gfsnotify/gfsnotify.go | 92 ++++--------- g/os/gfsnotify/gfsnotify_filefunc.go | 42 ++++-- g/os/gfsnotify/gfsnotify_watcher.go | 162 +++++++++------------- g/os/gfsnotify/gfsnotify_watcher_loop.go | 164 +++++++++++------------ g/os/gspath/gspath.go | 32 +++-- g/os/gview/gview.go | 2 +- geg/net/ghttp/server/https/https_http.go | 2 +- geg/net/ghttp/server/static.go | 2 +- geg/os/gcfg/gcfg1.go | 4 +- geg/os/gcfg/gcfg2.go | 4 +- geg/os/gfsnotify/fsnotify.go | 2 +- geg/os/gfsnotify/gfsnotify.go | 4 +- geg/os/gspath/gspath.go | 15 ++- geg/other/test.go | 5 +- 22 files changed, 295 insertions(+), 315 deletions(-) diff --git a/.gitignore b/.gitignore index 679820f45..55bb05beb 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,6 @@ gitpush.sh pkg/ bin/ cbuild -*/.DS_Store +**/.DS_Store .vscode/ go.sum diff --git a/g/net/ghttp/ghttp_response.go b/g/net/ghttp/ghttp_response.go index 4cb9b164f..f6a095ef6 100644 --- a/g/net/ghttp/ghttp_response.go +++ b/g/net/ghttp/ghttp_response.go @@ -159,7 +159,7 @@ func (r *Response) ServeFile(path string) { r.request.isFileServe = true // 首先判断是否给定的path已经是一个绝对路径 if !gfile.Exists(path) { - path = r.Server.paths.Search(path) + path, _ = r.Server.paths.Search(path) } if path == "" { r.WriteStatus(http.StatusNotFound) diff --git a/g/net/ghttp/ghttp_server.go b/g/net/ghttp/ghttp_server.go index 58e3636d8..008448994 100644 --- a/g/net/ghttp/ghttp_server.go +++ b/g/net/ghttp/ghttp_server.go @@ -237,6 +237,18 @@ func (s *Server) Start() error { } } + // 配置相关相对路径处理 + if !gfile.Exists(s.config.HTTPSCertPath) { + if t, _ := s.paths.Search(s.config.HTTPSCertPath); t != "" { + s.config.HTTPSCertPath = t + } + } + if !gfile.Exists(s.config.HTTPSKeyPath) { + if t, _ := s.paths.Search(s.config.HTTPSKeyPath); t != "" { + s.config.HTTPSKeyPath = t + } + } + // gzip压缩文件类型 //if s.config.GzipContentTypes != nil { // for _, v := range s.config.GzipContentTypes { diff --git a/g/net/ghttp/ghttp_server_handler.go b/g/net/ghttp/ghttp_server_handler.go index e0f396f3a..48b258c60 100644 --- a/g/net/ghttp/ghttp_server_handler.go +++ b/g/net/ghttp/ghttp_server_handler.go @@ -11,7 +11,6 @@ import ( "fmt" "gitee.com/johng/gf/g/encoding/ghtml" "gitee.com/johng/gf/g/os/gfile" - "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/os/gtime" "net/http" "net/url" @@ -55,12 +54,28 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) { s.callHookHandler(HOOK_AFTER_CLOSE, request) }() + // ============================================================ + // 优先级控制: + // 静态文件 > 动态服务 > 静态目录 + // ============================================================ + // 优先执行静态文件检索(检测是否存在对应的静态文件,包括index files处理) - staticFile := s.paths.Search(r.URL.Path, s.config.IndexFiles...) + staticFile, isStaticDir := s.paths.Search(r.URL.Path, s.config.IndexFiles...) if staticFile != "" { request.isFileRequest = true } - glog.Info(staticFile) + + // 动态服务检索 + handler := (*handlerItem)(nil) + if !request.IsFileRequest() || isStaticDir { + if parsedItem := s.getServeHandlerWithCache(request); parsedItem != nil { + handler = parsedItem.handler + for k, v := range parsedItem.values { + request.routerVars[k] = v + } + request.Router = parsedItem.handler.router + } + } // 事件 - BeforeServe s.callHookHandler(HOOK_BEFORE_SERVE, request) @@ -68,22 +83,20 @@ func (s *Server)handleRequest(w http.ResponseWriter, r *http.Request) { // 执行静态文件服务/回调控制器/执行对象/方法 if !request.IsExited() { // 需要再次判断文件是否真实存在,因为文件检索可能使用了缓存,从健壮性考虑这里需要二次判断 - if request.IsFileRequest() && gfile.Exists(staticFile) && gfile.SelfPath() != staticFile { + if request.IsFileRequest() && !isStaticDir /* && gfile.Exists(staticFile) */ && gfile.SelfPath() != staticFile { + // 静态文件 s.serveFile(request, staticFile) } else { - // 动态服务检索 - handler := (*handlerItem)(nil) - if parsedItem := s.getServeHandlerWithCache(request); parsedItem != nil { - handler = parsedItem.handler - for k, v := range parsedItem.values { - request.routerVars[k] = v - } - request.Router = parsedItem.handler.router - } if handler != nil { + // 动态服务 s.callServeHandler(handler, request) } else { - request.Response.WriteStatus(http.StatusNotFound) + if isStaticDir { + // 静态目录 + s.serveFile(request, staticFile) + } else { + request.Response.WriteStatus(http.StatusNotFound) + } } } } diff --git a/g/net/ghttp/ghttp_server_router.go b/g/net/ghttp/ghttp_server_router.go index f2a5a0a36..8087479d7 100644 --- a/g/net/ghttp/ghttp_server_router.go +++ b/g/net/ghttp/ghttp_server_router.go @@ -71,8 +71,8 @@ func (s *Server) setHandler(pattern string, handler *handlerItem, hook ... strin } regkey := s.hookHandlerKey(hookName, method, uri, domain) caller := s.getHandlerRegisterCallerLine(handler) - if line, ok := s.routesMap[regkey]; ok { - s := fmt.Sprintf(`duplicated route registry "%s" in %s , former in %s`, pattern, caller, line) + if item, ok := s.routesMap[regkey]; ok { + s := fmt.Sprintf(`duplicated route registry "%s", already registered in %s`, pattern, item.file) glog.Errorfln(s) return errors.New(s) } else { diff --git a/g/os/gcache/gcache_mem_cache_item.go b/g/os/gcache/gcache_mem_cache_item.go index 564e26da4..b7b301b8b 100644 --- a/g/os/gcache/gcache_mem_cache_item.go +++ b/g/os/gcache/gcache_mem_cache_item.go @@ -10,7 +10,8 @@ import "gitee.com/johng/gf/g/os/gtime" // 判断缓存项是否已过期 func (item *memCacheItem) IsExpired() bool { - if item.e > gtime.Millisecond() { + // 注意这里应当包含等于,试想一下缓存时间只有最小粒度为1毫秒的情况 + if item.e >= gtime.Millisecond() { return false } return true diff --git a/g/os/gcfg/gcfg.go b/g/os/gcfg/gcfg.go index 08530608e..631b6391b 100644 --- a/g/os/gcfg/gcfg.go +++ b/g/os/gcfg/gcfg.go @@ -53,7 +53,8 @@ func (c *Config) filePath(file...string) string { if len(file) > 0 { name = file[0] } - return c.paths.Search(name) + path, _ := c.paths.Search(name) + return path } // 设置配置管理器的配置文件存放目录绝对路径 @@ -92,7 +93,8 @@ func (c *Config) GetFilePath(file...string) string { if len(file) > 0 { name = file[0] } - return c.paths.Search(name) + path, _ := c.paths.Search(name) + return path } // 设置配置管理对象的默认文件名称 diff --git a/g/os/gfile/gfile.go b/g/os/gfile/gfile.go index b5c3e0ec5..188e01c13 100644 --- a/g/os/gfile/gfile.go +++ b/g/os/gfile/gfile.go @@ -255,15 +255,15 @@ func ScanDir(path string, pattern string, recursive ... bool) ([]string, error) // 内部检索目录方法,支持递归,返回没有排序的文件绝对路径列表结果。 // pattern参数支持多个文件名称模式匹配,使用','符号分隔多个模式。 func doScanDir(path string, pattern string, recursive ... bool) ([]string, error) { - var list []string + list := ([]string)(nil) // 打开目录 - dfile, err := os.Open(path) + file, err := os.Open(path) if err != nil { return nil, err } - defer dfile.Close() + defer file.Close() // 读取目录下的文件列表 - names, err := dfile.Readdirnames(-1) + names, err := file.Readdirnames(-1) if err != nil { return nil, err } diff --git a/g/os/gfsnotify/gfsnotify.go b/g/os/gfsnotify/gfsnotify.go index ee34b908a..c141f7eff 100644 --- a/g/os/gfsnotify/gfsnotify.go +++ b/g/os/gfsnotify/gfsnotify.go @@ -11,36 +11,29 @@ import ( "container/list" "errors" "fmt" - "gitee.com/johng/gf/g/container/glist" "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/container/gqueue" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/os/gcache" - "gitee.com/johng/gf/g/os/gcmd" - "gitee.com/johng/gf/g/os/genv" - "gitee.com/johng/gf/g/util/gconv" "gitee.com/johng/gf/third/github.com/fsnotify/fsnotify" ) // 监听管理对象 type Watcher struct { - watchers []*fsnotify.Watcher // 底层fsnotify对象,支持多个,以避免单个inotify对象监听队列上限问题 + watcher *fsnotify.Watcher // 底层fsnotify对象 events *gqueue.Queue // 过滤后的事件通知,不会出现重复事件 cache *gcache.Cache // 缓存对象,主要用于事件重复过滤 - callbacks *gmap.StringInterfaceMap // 注册的所有绝对路径机器对应的回调函数列表map - recursivePaths *gmap.StringInterfaceMap // 支持递归监听的目录绝对路径及其对应的回调函数列表map + callbacks *gmap.StringInterfaceMap // 注册的所有绝对路径(文件/目录)及其对应的回调函数列表map closeChan chan struct{} // 关闭事件 } // 注册的监听回调方法 type Callback struct { - Id int // 唯一ID - Func func(event *Event) // 回调方法 - Path string // 监听的文件/目录 - addr string // Func对应的内存地址,用以判断回调的重复 - elem *list.Element // 指向监听链表中的元素项位置 - parent *Callback // 父级callback,有这个属性表示该callback为被自动管理的callback - subs *glist.List // 子级回调对象指针列表 + Id int // 唯一ID + Func func(event *Event) // 回调方法 + Path string // 监听的文件/目录 + elem *list.Element // 指向回调函数链表中的元素项位置(便于删除) + recursive bool // 当目录时,是否递归监听(使用在子文件/目录回溯查找回调函数时) } // 监听事件对象 @@ -65,77 +58,45 @@ const ( const ( REPEAT_EVENT_FILTER_INTERVAL = 1 // (毫秒)重复事件过滤间隔 - DEFAULT_WATCHER_COUNT = 1 // 默认创建的监控对象数量(使用哈希取模) - gDEFAULT_PKG_WATCHER_COUNT = 4 // 默认创建的包监控对象数量(使用哈希取模) ) var ( // 默认的Watcher对象 - defaultWatcher *Watcher + defaultWatcher, _ = New() // 默认的watchers是否初始化,使用时才创建 - watcherInited = gtype.NewBool() + watcherInited = gtype.NewBool() // 回调方法ID与对象指针的映射哈希表,用于根据ID快速查找回调对象 - callbackIdMap = gmap.NewIntInterfaceMap() + callbackIdMap = gmap.NewIntInterfaceMap() // 回调函数的ID生成器(原子操作) callbackIdGenerator = gtype.NewInt() ) -// 初始化创建watcher对象,用于包默认管理监听 -func initWatcher() { - if !watcherInited.Set(true) { - pkgWatcherCount := gconv.Int(genv.Get("GF_INOTIFY_COUNT")) - if pkgWatcherCount == 0 { - pkgWatcherCount = gconv.Int(gcmd.Option.Get("gf.inotify-count")) - } - if pkgWatcherCount == 0 { - pkgWatcherCount = gDEFAULT_PKG_WATCHER_COUNT - } - if w, err := New(pkgWatcherCount); err == nil { - defaultWatcher = w - } else { - panic(err) - } - } -} - // 创建监听管理对象,主要注意的是创建监听对象会占用系统的inotify句柄数量,受到 fs.inotify.max_user_instances 的限制 -func New(inotifyCount...int) (*Watcher, error) { - count := DEFAULT_WATCHER_COUNT - if len(inotifyCount) > 0 { - count = inotifyCount[0] - } +func New() (*Watcher, error) { w := &Watcher { - cache : gcache.New(), - watchers : make([]*fsnotify.Watcher, count), - events : gqueue.New(), - closeChan : make(chan struct{}), - callbacks : gmap.NewStringInterfaceMap(), - recursivePaths : gmap.NewStringInterfaceMap(), + cache : gcache.New(), + events : gqueue.New(), + closeChan : make(chan struct{}), + callbacks : gmap.NewStringInterfaceMap(), } - for i := 0; i < count; i++ { - if watcher, err := fsnotify.NewWatcher(); err == nil { - w.watchers[i] = watcher - } else { - // 出错,关闭已创建的底层watcher对象 - for j := 0; j < i; j++ { - w.watchers[j].Close() - } - return nil, err - } + if watcher, err := fsnotify.NewWatcher(); err == nil { + w.watcher = watcher + } else { + return nil, err } w.startWatchLoop() w.startEventLoop() return w, nil } -// 添加对指定文件/目录的监听,并给定回调函数;如果给定的是一个目录,默认递归监控。 +// 添加对指定文件/目录的监听,并给定回调函数;如果给定的是一个目录,默认非递归监控。 func Add(path string, callbackFunc func(event *Event), recursive...bool) (callback *Callback, err error) { - return watcher().Add(path, callbackFunc, recursive...) + return defaultWatcher.Add(path, callbackFunc, recursive...) } // 递归移除对指定文件/目录的所有监听回调 func Remove(path string) error { - return watcher().Remove(path) + return defaultWatcher.Remove(path) } // 根据指定的回调函数ID,移出指定的inotify回调函数 @@ -147,11 +108,6 @@ func RemoveCallback(callbackId int) error { if callback == nil { return errors.New(fmt.Sprintf(`callback for id %d not found`, callbackId)) } - return watcher().RemoveCallback(callbackId) -} - -// 获得默认的包watcher -func watcher() *Watcher { - initWatcher() - return defaultWatcher + defaultWatcher.RemoveCallback(callbackId) + return nil } diff --git a/g/os/gfsnotify/gfsnotify_filefunc.go b/g/os/gfsnotify/gfsnotify_filefunc.go index 876ab0bb4..97f810263 100644 --- a/g/os/gfsnotify/gfsnotify_filefunc.go +++ b/g/os/gfsnotify/gfsnotify_filefunc.go @@ -49,9 +49,35 @@ func fileIsDir(path string) bool { return s.IsDir() } +// 返回制定目录其子级所有的目录绝对路径(包含自身) +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) { + if array := fileAllDirs(path); len(array) > 0 { + list = append(list, array...) + } + } + } + return +} + // 打开目录,并返回其下一级文件列表(绝对路径),按照文件名称大小写进行排序,支持目录递归遍历。 func fileScanDir(path string, pattern string, recursive ... bool) ([]string, error) { - list, err := fileDoScanDir(path, pattern, recursive...) + list, err := doFileScanDir(path, pattern, recursive...) if err != nil { return nil, err } @@ -63,16 +89,16 @@ func fileScanDir(path string, pattern string, recursive ... bool) ([]string, err // 内部检索目录方法,支持递归,返回没有排序的文件绝对路径列表结果。 // pattern参数支持多个文件名称模式匹配,使用','符号分隔多个模式。 -func fileDoScanDir(path string, pattern string, recursive ... bool) ([]string, error) { - var list []string +func doFileScanDir(path string, pattern string, recursive ... bool) ([]string, error) { + list := ([]string)(nil) // 打开目录 - dfile, err := os.Open(path) + file, err := os.Open(path) if err != nil { return nil, err } - defer dfile.Close() + defer file.Close() // 读取目录下的文件列表 - names, err := dfile.Readdirnames(-1) + names, err := file.Readdirnames(-1) if err != nil { return nil, err } @@ -80,7 +106,7 @@ func fileDoScanDir(path string, pattern string, recursive ... bool) ([]string, e for _, name := range names { path := fmt.Sprintf("%s%s%s", path, string(filepath.Separator), name) if fileIsDir(path) && len(recursive) > 0 && recursive[0] { - array, _ := fileDoScanDir(path, pattern, true) + array, _ := doFileScanDir(path, pattern, true) if len(array) > 0 { list = append(list, array...) } @@ -93,4 +119,4 @@ func fileDoScanDir(path string, pattern string, recursive ... bool) ([]string, e } } return list, nil -} \ No newline at end of file +} diff --git a/g/os/gfsnotify/gfsnotify_watcher.go b/g/os/gfsnotify/gfsnotify_watcher.go index 1c4cabcd2..4e39f09cb 100644 --- a/g/os/gfsnotify/gfsnotify_watcher.go +++ b/g/os/gfsnotify/gfsnotify_watcher.go @@ -10,74 +10,45 @@ import ( "errors" "fmt" "gitee.com/johng/gf/g/container/glist" - "gitee.com/johng/gf/g/encoding/ghash" - "gitee.com/johng/gf/third/github.com/fsnotify/fsnotify" ) -// 添加监控,path参数支持文件或者目录路径,recursive为非必需参数,默认为递归添加监控(当path为目录时)。 +// 添加监控,path参数支持文件或者目录路径,recursive为非必需参数,默认为非递归监控(当path为目录时)。 // 如果添加目录,这里只会返回目录的callback,按照callback删除时会递归删除。 func (w *Watcher) Add(path string, callbackFunc func(event *Event), recursive...bool) (callback *Callback, err error) { - return w.addWithCallbackFunc(nil, path, callbackFunc, recursive...) -} - -// 添加监控,path参数支持文件或者目录路径,recursive为非必需参数,默认为递归添加监控(当path为目录时)。 -// 如果添加目录,这里只会返回目录的callback,按照callback删除时会递归删除。 -func (w *Watcher) addWithCallbackFunc(parentCallback *Callback, path string, callbackFunc func(event *Event), recursive...bool) (callback *Callback, err error) { // 首先添加这个文件/目录 - callback, err = w.doAddWithCallbackFunc(path, callbackFunc, parentCallback) + callback, err = w.addWithCallbackFunc(path, callbackFunc, recursive...) if err != nil { return nil, err } - // 其次递归添加其下的文件/目录 + // 如果需要递归,那么递归添加其下的子级目录, + // 注意!! + // 1、这里只递归添加**目录**, 而非文件,因为监控了目录即监控了其下一级的文件; + // 2、这里只是添加底层监控对象对**子级所有目录**的监控,没有任何回调函数的设置,在事件产生时会回溯查找父级的回调函数; if fileIsDir(path) && (len(recursive) == 0 || recursive[0]) { - // 追加递归监控的回调到recursivePaths中 - w.recursivePaths.LockFunc(func(m map[string]interface{}) { - list := (*glist.List)(nil) - if v, ok := m[path]; !ok { - list = glist.New() - m[path] = list - } else { - list = v.(*glist.List) + for _, subPath := range fileAllDirs(path) { + if fileIsDir(subPath) { + w.watcher.Add(subPath) } - list.PushBack(callback) - }) - // 递归添加监控 - paths, _ := fileScanDir(path, "*", true) - for _, v := range paths { - w.doAddWithCallbackFunc(v, callbackFunc, callback) } } return } // 添加对指定文件/目录的监听,并给定回调函数 -func (w *Watcher) doAddWithCallbackFunc(path string, callbackFunc func(event *Event), parentCallback *Callback) (callback *Callback, err error) { +func (w *Watcher) addWithCallbackFunc(path string, callbackFunc func(event *Event), recursive...bool) (callback *Callback, err error) { // 这里统一转换为当前系统的绝对路径,便于统一监控文件名称 if t := fileRealPath(path); t == "" { return nil, errors.New(fmt.Sprintf(`"%s" does not exist`, path)) } else { path = t } - // 添加成功后会注册该callback id到全局的哈希表,并绑定到父级的注册回调中 - defer func() { - if err == nil { - if parentCallback == nil { - // 只有主callback才记录到id map中,因为子callback是自动管理的无需添加到全局id映射map中 - callbackIdMap.Set(callback.Id, callback) - } - if parentCallback != nil { - // 添加到直属父级的subs属性中,建立关联关系,便于后续删除 - parentCallback.subs.PushBack(callback) - } - } - }() callback = &Callback { Id : callbackIdGenerator.Add(1), Func : callbackFunc, Path : path, - addr : fmt.Sprintf("%p", callbackFunc)[2:], - subs : glist.New(), - parent : parentCallback, + } + if len(recursive) > 0 { + callback.recursive = recursive[0] } // 注册回调函数 w.callbacks.LockFunc(func(m map[string]interface{}) { @@ -91,93 +62,80 @@ func (w *Watcher) doAddWithCallbackFunc(path string, callbackFunc func(event *Ev callback.elem = list.PushBack(callback) }) // 添加底层监听 - w.watcher(path).Add(path) + w.watcher.Add(path) + // 添加成功后会注册该callback id到全局的哈希表 + callbackIdMap.Set(callback.Id, callback) return } -// 根据path查询对应的底层watcher对象 -func (w *Watcher) watcher(path string) *fsnotify.Watcher { - return w.watchers[ghash.BKDRHash([]byte(path)) % uint32(len(w.watchers))] -} - // 关闭监听管理对象 func (w *Watcher) Close() { - for _, watcher := range w.watchers { - watcher.Close() - } w.events.Close() + w.watcher.Close() close(w.closeChan) } // 递归移除对指定文件/目录的所有监听回调 func (w *Watcher) Remove(path string) error { - if fileIsDir(path) && fileExists(path) { - paths, _ := fileScanDir(path, "*", true) - paths = append(paths, path) - for _, v := range paths { - if err := w.removeWatch(v); err != nil { - return err - } - } - return nil - } else { - return w.removeWatch(path) - } -} - -// 移除对指定文件/目录的所有监听 -func (w *Watcher) removeWatch(path string) error { - // 首先移除所有该path的回调注册 - if r := w.callbacks.Get(path); r != nil { + // 首先移除path注册的回调注册,以及callbackIdMap中的ID + if r := w.callbacks.Remove(path); r != nil { list := r.(*glist.List) for { if r := list.PopFront(); r != nil { - w.removeCallback(r.(*Callback)) + callbackIdMap.Remove(r.(*Callback).Id) } else { break } } } - // 其次移除该path的监听注册 - w.callbacks.Remove(path) + // 其次递归判断所有的子级是否可删除监听 + if subPaths, err := fileScanDir(path, "*", true); err == nil && len(subPaths) > 0 { + for _, subPath := range subPaths { + if w.checkPathCanBeRemoved(subPath) { + w.watcher.Remove(subPath) + } + } + } // 最后移除底层的监听 - return w.watcher(path).Remove(path) + return w.watcher.Remove(path) +} + +// 判断给定的路径是否可以删除监听(只有所有回调函数都没有了才能删除) +func (w *Watcher) checkPathCanBeRemoved(path string) bool { + // 首先检索path对应的回调函数 + if v := w.callbacks.Get(path); v != nil { + return false + } + // 其次查找父级目录有无回调注册 + dirPath := fileDir(path) + if v := w.callbacks.Get(dirPath); v != nil { + return false + } + // 最后回溯查找递归回调函数 + for { + parentDirPath := fileDir(dirPath) + if parentDirPath == dirPath { + break + } + if v := w.callbacks.Get(parentDirPath); v != nil { + return false + } + dirPath = parentDirPath + } + return true } // 根据指定的回调函数ID,移出指定的inotify回调函数 -func (w *Watcher) RemoveCallback(callbackId int) error { +func (w *Watcher) RemoveCallback(callbackId int) { callback := (*Callback)(nil) if r := callbackIdMap.Get(callbackId); r != nil { callback = r.(*Callback) } - if callback == nil { - return errors.New(fmt.Sprintf(`callback for id %d not found`, callbackId)) + if callback != nil { + if r := w.callbacks.Get(callback.Path); r != nil { + r.(*glist.List).Remove(callback.elem) + } + callbackIdMap.Remove(callbackId) } - w.removeCallback(callback) - return nil } -// (递归)移除对指定文件/目录的所有监听 -func (w *Watcher) removeCallback(callback *Callback) error { - if r := w.callbacks.Get(callback.Path); r != nil { - list := r.(*glist.List) - list.Remove(callback.elem) - // 如果存在子级callback,那么也一并递归删除 - if callback.subs.Len() > 0 { - for { - if r := callback.subs.PopFront(); r != nil { - w.removeCallback(r.(*Callback)) - } else { - break - } - } - } - // 如果该文件/目录的所有回调都被删除,那么移除底层的监听 - if list.Len() == 0 { - return w.watcher(callback.Path).Remove(callback.Path) - } - } else { - return errors.New(fmt.Sprintf(`callbacks not found for "%s"`, callback.Path)) - } - return nil -} diff --git a/g/os/gfsnotify/gfsnotify_watcher_loop.go b/g/os/gfsnotify/gfsnotify_watcher_loop.go index e13aea861..76b526fcb 100644 --- a/g/os/gfsnotify/gfsnotify_watcher_loop.go +++ b/g/os/gfsnotify/gfsnotify_watcher_loop.go @@ -9,67 +9,70 @@ package gfsnotify import ( "fmt" "gitee.com/johng/gf/g/container/glist" - "sync" - "time" ) // 监听循环 func (w *Watcher) startWatchLoop() { - for i := 0; i < len(w.watchers); i++ { - go func(i int) { - for { - select { - // 关闭事件 - case <- w.closeChan: return + go func() { + for { + select { + // 关闭事件 + case <- w.closeChan: return - // 监听事件 - case ev := <- w.watchers[i].Events: - //fmt.Println("ev:", ev.String()) - w.cache.SetIfNotExist(ev.String(), func() interface{} { - w.events.Push(&Event{ - event : ev, - Path : ev.Name, - Op : Op(ev.Op), - Watcher : w, - }) - return struct {}{} - }, REPEAT_EVENT_FILTER_INTERVAL) + // 监听事件 + case ev := <- w.watcher.Events: + //fmt.Println("ev:", ev.String()) + w.cache.SetIfNotExist(ev.String(), func() interface{} { + w.events.Push(&Event{ + event : ev, + Path : ev.Name, + Op : Op(ev.Op), + Watcher : w, + }) + return struct {}{} + }, REPEAT_EVENT_FILTER_INTERVAL) - case err := <- w.watchers[i].Errors: - fmt.Errorf("error: %s\n" + err.Error()); - } + case err := <- w.watcher.Errors: + fmt.Errorf("error: %s\n" + err.Error()); } - }(i) - } + } + }() } -// 获得真正监听的文件路径及回调函数列表,假如是临时文件或者新增文件,是无法搜索都监听回调的。 -// 判断规则: -// 1、在 callbacks 中应当有回调注册函数(否则监听根本没意义); -// 2、如果该path下不存在回调注册函数,则按照path长度从右往左递减,直到减到目录地址为止(不包含); -// 3、如果仍旧无法匹配回调函数,那么忽略,否则使用查找到的新path覆盖掉event的path; -// 解决问题: -// 1、部分IDE修改文件时生成的临时文件,如: /index.html -> /index.html__jdold__; -func (w *Watcher) getWatchPathAndCallbacks(path string) (watchPath string, callbacks *glist.List) { - if path == "" { - return "", nil +// 获得文件路径的监听回调,包括层级的监听回调。 +func (w *Watcher) getCallbacks(path string) (callbacks []*Callback) { + // 首先检索path对应的回调函数 + if v := w.callbacks.Get(path); v != nil { + for _, v := range v.(*glist.List).FrontAll() { + callback := v.(*Callback) + callbacks = append(callbacks, callback) + } } + // 其次查找父级目录有无回调注册 dirPath := fileDir(path) - for { - if v := w.callbacks.Get(path); v != nil { - return path, v.(*glist.List) - } - path = path[0 : len(path) - 1] - // 递减到上一级目录为止 - if path == dirPath { - break - } - // 如果不能再继续递减,那么退出 - if len(path) == 0 { - break + if v := w.callbacks.Get(dirPath); v != nil { + for _, v := range v.(*glist.List).FrontAll() { + callback := v.(*Callback) + callbacks = append(callbacks, callback) } } - return "", nil + // 最后回溯查找递归回调函数 + for { + parentDirPath := fileDir(dirPath) + if parentDirPath == dirPath { + break + } + if v := w.callbacks.Get(parentDirPath); v != nil { + for _, v := range v.(*glist.List).FrontAll() { + callback := v.(*Callback) + if callback.recursive { + callbacks = append(callbacks, callback) + } + } + } + dirPath = parentDirPath + } + return } // 事件循环 @@ -78,63 +81,54 @@ func (w *Watcher) startEventLoop() { for { if v := w.events.Pop(); v != nil { event := v.(*Event) - // watchPath是注册回调的路径,可能和event.Path不一样 - watchPath, callbacks := w.getWatchPathAndCallbacks(event.Path) - if callbacks == nil { + // 如果该路径一个回调也没有,那么没有必要执行后续逻辑,删除对该文件的监听 + callbacks := w.getCallbacks(event.Path) + if len(callbacks) == 0 { + w.watcher.Remove(event.Path) continue } - fmt.Println("event:", event.String(), watchPath, fileExists(watchPath)) switch { // 如果是删除操作,那么需要判断是否文件真正不存在了,如果存在,那么将此事件认为“假删除” case event.IsRemove(): - if fileExists(watchPath) { - // 重新添加监控(底层fsnotify会自动删除掉监控,这里重新添加回去) - // 注意这里调用的是底层fsnotify添加监控,只会产生回调事件,并不会使回调函数重复注册 - w.watcher(watchPath).Add(event.Path) + if fileExists(event.Path) { + // 底层重新添加监控(不用担心重复添加) + w.watcher.Add(event.Path) // 修改事件操作为重命名(相当于重命名为自身名称,最终名称没变) event.Op = RENAME - } else { - // 删除之前需要执行一遍回调,否则Remove之后就无法执行了 - // 由于是异步回调,这里保证所有回调都开始执行后再执行删除 - wg := sync.WaitGroup{} - for _, v := range callbacks.FrontAll() { - wg.Add(1) - go func(callback *Callback) { - wg.Done() - callback.Func(event) - }(v.(*Callback)) - } - wg.Wait() - time.Sleep(time.Second) - // 如果是真实删除,那么递归删除监控信息 - fmt.Println("remove", watchPath) - w.Remove(watchPath) } // 如果是重命名操作,那么需要判断是否文件真正不存在了,如果存在,那么将此事件认为“假命名” // (特别是某些编辑器在编辑文件时会先对文件RENAME再CHMOD) case event.IsRename(): - if fileExists(watchPath) { - // 重新添加监控 - w.watcher(watchPath).Add(watchPath) - } else if watchPath != event.Path && fileExists(event.Path) { - for _, v := range callbacks.FrontAll() { - callback := v.(*Callback) - w.addWithCallbackFunc(callback, event.Path, callback.Func) - } + if fileExists(event.Path) { + // 底层有可能去掉了监控, 这里重新添加监控(不用担心重复添加) + w.watcher.Add(event.Path) + // 修改事件操作为修改属性 + event.Op = CHMOD } // 创建文件/目录 case event.IsCreate(): - for _, v := range callbacks.FrontAll() { - callback := v.(*Callback) - w.addWithCallbackFunc(callback, event.Path, callback.Func) + // ========================================= + // 注意这里只是添加底层监听,并没有注册任何的回调函数, + // 默认的回调函数为父级的递归回调 + // ========================================= + if fileIsDir(event.Path) { + // 递归添加 + for _, subPath := range fileAllDirs(event.Path) { + if fileIsDir(subPath) { + w.watcher.Add(subPath) + } + } + } else { + // 添加文件监听 + w.watcher.Add(event.Path) } } // 执行回调处理,异步处理 - for _, v := range callbacks.FrontAll() { - go v.(*Callback).Func(event) + for _, callback := range callbacks { + go callback.Func(event) } } else { diff --git a/g/os/gspath/gspath.go b/g/os/gspath/gspath.go index 07502f06d..e97f65459 100644 --- a/g/os/gspath/gspath.go +++ b/g/os/gspath/gspath.go @@ -15,9 +15,9 @@ import ( "gitee.com/johng/gf/g/container/gmap" "gitee.com/johng/gf/g/os/gfile" "gitee.com/johng/gf/g/os/gfsnotify" - "gitee.com/johng/gf/g/os/glog" "gitee.com/johng/gf/g/util/gstr" "runtime" + "sort" "strings" ) @@ -33,7 +33,6 @@ type SPathCacheItem struct { isDir bool // 是否目录 } - // 创建一个搜索对象 func New () *SPath { return &SPath { @@ -46,7 +45,7 @@ func New () *SPath { func (sp *SPath) Set(path string) (realPath string, err error) { realPath = gfile.RealPath(path) if realPath == "" { - realPath = sp.Search(path) + realPath, _ = sp.Search(path) if realPath == "" { realPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path) } @@ -80,7 +79,7 @@ func (sp *SPath) Set(path string) (realPath string, err error) { func (sp *SPath) Add(path string) (realPath string, err error) { realPath = gfile.RealPath(path) if realPath == "" { - realPath = sp.Search(path) + realPath, _ = sp.Search(path) if realPath == "" { realPath = gfile.RealPath(gfile.Pwd() + gfile.Separator + path) } @@ -107,22 +106,32 @@ func (sp *SPath) Add(path string) (realPath string, err error) { } // 给定的name只是相对文件路径,找不到该文件时,返回空字符串; -// 当给定indexFiles时,如果name时一个目录,那么会进一步检索其下对应的indexFiles文件是否存在,存在则返回indexFile 绝对路径; +// 当给定indexFiles时,如果name时一个目录,那么会进一步检索其下对应的indexFiles文件是否存在,存在则返回indexFile绝对路径; // 否则返回name目录绝对路径。 -func (sp *SPath) Search(name string, indexFiles...string) string { +func (sp *SPath) Search(name string, indexFiles...string) (path string, isDir bool) { name = sp.formatCacheName(name) if v := sp.cache.Get(name); v != nil { item := v.(*SPathCacheItem) if len(indexFiles) > 0 && item.isDir { for _, file := range indexFiles { if v := sp.cache.Get(name + "/" + file); v != nil { - return v.(*SPathCacheItem).path + item := v.(*SPathCacheItem) + return item.path, item.isDir } } } - return item.path + return item.path, item.isDir } - return "" + return "", false +} + +// 返回当前对象缓存的所有路径列表 +func (sp *SPath) AllPaths() []string { + paths := sp.cache.Keys() + if len(paths) > 0 { + sort.Strings(paths) + } + return paths } // 当前的搜索路径数量 @@ -140,6 +149,7 @@ func (sp *SPath) formatCacheName(name string) string { if name == "" { return "/" } + name = strings.TrimLeft(name, ".") if runtime.GOOS != "linux" { name = gstr.Replace(name, "\\", "/") } @@ -180,7 +190,7 @@ func (sp *SPath) addToCache(filePath, dirPath string) { // 这里需要注意的点是,由于添加监听是递归添加的,那么假如删除一个目录,那么该目录下的文件(包括目录)也会产生一条删除事件,总共会产生N条事件。 func (sp *SPath) addMonitorByPath(path string) { gfsnotify.Add(path, func(event *gfsnotify.Event) { - glog.Debug(event.String()) + //glog.Debug(event.String()) switch { case event.IsRemove(): sp.cache.Remove(sp.nameFromPath(event.Path, path)) @@ -193,7 +203,7 @@ func (sp *SPath) addMonitorByPath(path string) { case event.IsCreate(): sp.addToCache(event.Path, path) } - }) + }, true) } // 删除监听(递归) diff --git a/g/os/gview/gview.go b/g/os/gview/gview.go index 5f3e4cd54..2b23ac058 100644 --- a/g/os/gview/gview.go +++ b/g/os/gview/gview.go @@ -141,7 +141,7 @@ func (view *View) Assign(key string, value interface{}) { // 解析模板,返回解析后的内容 func (view *View) Parse(file string, params Params, funcmap...map[string]interface{}) ([]byte, error) { - path := view.paths.Search(file) + path, _ := view.paths.Search(file) if path == "" { return nil, errors.New("tpl \"" + file + "\" not found") } diff --git a/geg/net/ghttp/server/https/https_http.go b/geg/net/ghttp/server/https/https_http.go index 3a42b01a7..7961b0c40 100644 --- a/geg/net/ghttp/server/https/https_http.go +++ b/geg/net/ghttp/server/https/https_http.go @@ -10,7 +10,7 @@ func main() { s.BindHandler("/", func(r *ghttp.Request){ r.Response.Writeln("您可以同时通过HTTP和HTTPS方式看到该内容!") }) - s.EnableHTTPS("/home/john/temp/server.crt", "/home/john/temp/server.key") + s.EnableHTTPS("./server.crt", "./server.key") s.SetHTTPSPort(8198, 8199) s.SetPort(8200, 8300) s.EnableAdmin() diff --git a/geg/net/ghttp/server/static.go b/geg/net/ghttp/server/static.go index 0bb323b16..ca23ddf19 100644 --- a/geg/net/ghttp/server/static.go +++ b/geg/net/ghttp/server/static.go @@ -6,7 +6,7 @@ import "gitee.com/johng/gf/g" func main() { s := g.Server() s.SetIndexFolder(true) - s.SetServerRoot("/Users/john/Documents") + s.SetServerRoot("/Users/john/Temp") s.SetPort(8199) s.Run() } diff --git a/geg/os/gcfg/gcfg1.go b/geg/os/gcfg/gcfg1.go index a32caccdf..05fbb6ba2 100644 --- a/geg/os/gcfg/gcfg1.go +++ b/geg/os/gcfg/gcfg1.go @@ -2,11 +2,11 @@ package main import ( "fmt" - "gitee.com/johng/gf/g/os/gcfg" + "gitee.com/johng/gf/g" ) func main() { - c := gcfg.New("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/os/gcfg") + c := g.Config() redisConfig := c.GetArray("redis-cache", "redis.toml") memConfig := c.GetArray("", "memcache.yml") fmt.Println(redisConfig) diff --git a/geg/os/gcfg/gcfg2.go b/geg/os/gcfg/gcfg2.go index 4616e48fe..c985508f1 100644 --- a/geg/os/gcfg/gcfg2.go +++ b/geg/os/gcfg/gcfg2.go @@ -2,11 +2,11 @@ package main import ( "fmt" - "gitee.com/johng/gf/g/os/gcfg" + "gitee.com/johng/gf/g" ) func main() { - c := gcfg.New("/home/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/os/gcfg") + c := g.Config() fmt.Println(c.GetArray("memcache")) } diff --git a/geg/os/gfsnotify/fsnotify.go b/geg/os/gfsnotify/fsnotify.go index 5aaa5ad5f..262b3262e 100644 --- a/geg/os/gfsnotify/fsnotify.go +++ b/geg/os/gfsnotify/fsnotify.go @@ -15,7 +15,7 @@ func main() { defer watch.Close() //添加要监控的对象,文件或文件夹 //err = watch.Add("D:\\Workspace\\Go\\GOPATH\\src\\gitee.com\\johng\\gf\\geg\\other\\test.go") - err = watch.Add("/Users/john/Temp/1/2") + err = watch.Add("/Users/john/Workspace/Go/GOPATH/src/gitee.com/johng/gf/geg/other/test.go") if err != nil { log.Fatal(err) } diff --git a/geg/os/gfsnotify/gfsnotify.go b/geg/os/gfsnotify/gfsnotify.go index 880c09189..154e72f62 100644 --- a/geg/os/gfsnotify/gfsnotify.go +++ b/geg/os/gfsnotify/gfsnotify.go @@ -7,10 +7,10 @@ import ( func main() { //path := "D:\\Workspace\\Go\\GOPATH\\src\\gitee.com\\johng\\gf\\geg\\other\\test.go" - path := "/Users/john/Temp/1/2/3" + path := "/Users/john/Temp/" _, err := gfsnotify.Add(path, func(event *gfsnotify.Event) { glog.Println(event) - }) + }, true) // 移除对该path的监听 //gfsnotify.Remove(path) diff --git a/geg/os/gspath/gspath.go b/geg/os/gspath/gspath.go index b5424611b..c0475d45e 100644 --- a/geg/os/gspath/gspath.go +++ b/geg/os/gspath/gspath.go @@ -2,8 +2,10 @@ package main import ( "fmt" + "gitee.com/johng/gf/g" "gitee.com/johng/gf/g/os/gspath" "gitee.com/johng/gf/g/os/gtime" + "time" ) func main() { @@ -12,8 +14,13 @@ func main() { rp, err := sp.Add(path) fmt.Println(err) fmt.Println(rp) - fmt.Println(gtime.FuncCost(func() { - sp.Search("1") - })) - fmt.Println(sp.Search("1", "index.html")) + + gtime.SetInterval(5*time.Second, func() bool { + g.Dump(sp.AllPaths()) + return true + }) + + select { + + } } diff --git a/geg/other/test.go b/geg/other/test.go index 7d89a2daf..f600d12cd 100644 --- a/geg/other/test.go +++ b/geg/other/test.go @@ -1,9 +1,10 @@ package main import ( - "fmt" + "fmt" + "gitee.com/johng/gf/g/os/gfile" ) func main() { - fmt.Println("123"[2:]) + fmt.Println(gfile.TempDir()) }