diff --git a/g/os/gfile/gfile_contents.go b/g/os/gfile/gfile_contents.go index 87738a114..716ee85f8 100644 --- a/g/os/gfile/gfile_contents.go +++ b/g/os/gfile/gfile_contents.go @@ -7,6 +7,7 @@ package gfile import ( + "gitee.com/johng/gf/g/os/gfpool" "io" "io/ioutil" "os" @@ -15,8 +16,8 @@ import ( const ( // 方法中涉及到读取的时候的缓冲大小 gREAD_BUFFER = 1024 - // 方法中涉及到文件指针池的默认缓存时间(秒) - gFILE_POOL_EXPIRE = 60 + // 方法中涉及到文件指针池的默认缓存时间(毫秒) + gFILE_POOL_EXPIRE = 60000 ) // (文本)读取文件内容 @@ -43,7 +44,7 @@ func putContents(path string, data []byte, flag int, perm int) error { } } // 创建/打开文件 - f, err := OpenWithFlagPerm(path, flag, perm) + f, err := gfpool.Open(path, flag, os.FileMode(perm), gFILE_POOL_EXPIRE) if err != nil { return err } @@ -82,11 +83,11 @@ func PutBinContentsAppend(path string, content []byte) error { } // 获得文件内容下一个指定字节的位置 -func GetNextCharOffset(file *os.File, char byte, start int64) int64 { +func GetNextCharOffset(reader io.ReaderAt, char byte, start int64) int64 { buffer := make([]byte, gREAD_BUFFER) offset := start for { - if n, err := file.ReadAt(buffer, offset); n > 0 { + if n, err := reader.ReadAt(buffer, offset); n > 0 { for i := 0; i < n; i++ { if buffer[i] == char { return int64(i) + offset @@ -102,7 +103,7 @@ func GetNextCharOffset(file *os.File, char byte, start int64) int64 { // 获得文件内容下一个指定字节的位置 func GetNextCharOffsetByPath(path string, char byte, start int64) int64 { - if f, err := OpenWithFlagPerm(path, os.O_RDONLY, gDEFAULT_PERM); err == nil { + if f, err := gfpool.Open(path, os.O_RDONLY, gDEFAULT_PERM, gFILE_POOL_EXPIRE); err == nil { defer f.Close() return GetNextCharOffset(f, char, start) } else { @@ -112,16 +113,16 @@ func GetNextCharOffsetByPath(path string, char byte, start int64) int64 { } // 获得文件内容直到下一个指定字节的位置(返回值包含该位置字符内容) -func GetBinContentsTilChar(file *os.File, char byte, start int64) ([]byte, int64) { - if offset := GetNextCharOffset(file, char, start); offset != -1 { - return GetBinContentsByTwoOffsets(file, start, offset + 1), offset +func GetBinContentsTilChar(reader io.ReaderAt, char byte, start int64) ([]byte, int64) { + if offset := GetNextCharOffset(reader, char, start); offset != -1 { + return GetBinContentsByTwoOffsets(reader, start, offset + 1), offset } return nil, -1 } // 获得文件内容直到下一个指定字节的位置(返回值包含该位置字符内容) func GetBinContentsTilCharByPath(path string, char byte, start int64) ([]byte, int64) { - if f, err := OpenWithFlagPerm(path, os.O_RDONLY, gDEFAULT_PERM); err == nil { + if f, err := gfpool.Open(path, os.O_RDONLY, gDEFAULT_PERM, gFILE_POOL_EXPIRE); err == nil { defer f.Close() return GetBinContentsTilChar(f, char, start) } else { @@ -131,9 +132,9 @@ func GetBinContentsTilCharByPath(path string, char byte, start int64) ([]byte, i } // 获得文件内容中两个offset之间的内容 [start, end) -func GetBinContentsByTwoOffsets(file *os.File, start int64, end int64) []byte { +func GetBinContentsByTwoOffsets(reader io.ReaderAt, start int64, end int64) []byte { buffer := make([]byte, end - start) - if _, err := file.ReadAt(buffer, start); err != nil { + if _, err := reader.ReadAt(buffer, start); err != nil { return nil } return buffer @@ -141,7 +142,7 @@ func GetBinContentsByTwoOffsets(file *os.File, start int64, end int64) []byte { // 获得文件内容中两个offset之间的内容 [start, end) func GetBinContentsByTwoOffsetsByPath(path string, start int64, end int64) []byte { - if f, err := OpenWithFlagPerm(path, os.O_RDONLY, gDEFAULT_PERM); err == nil { + if f, err := gfpool.Open(path, os.O_RDONLY, gDEFAULT_PERM, gFILE_POOL_EXPIRE); err == nil { defer f.Close() return GetBinContentsByTwoOffsets(f, start, end) } else { diff --git a/g/os/gfpool/gfpool.go b/g/os/gfpool/gfpool.go index 9e9b73c9f..95a8d7e8c 100644 --- a/g/os/gfpool/gfpool.go +++ b/g/os/gfpool/gfpool.go @@ -28,7 +28,7 @@ type Pool struct { // 文件指针池指针 type File struct { - os.File // 底层文件指针 + *os.File // 底层文件指针 mu sync.RWMutex // 互斥锁 pool *Pool // 所属池 poolid int // 所属池ID,如果池ID不同表示池已经重建,那么该文件指针也应当销毁,不能重新丢到原有的池中 @@ -81,8 +81,8 @@ func newFilePool(p *Pool, path string, flag int, perm os.FileMode, expire int) * if err != nil { return nil, err } - return &File{ - File : *file, + return &File { + File : file, pool : p, poolid : p.id.Val(), flag : flag, @@ -108,7 +108,7 @@ func (p *Pool) File() (*File, error) { if file, err := os.OpenFile(f.path, f.flag, f.perm); err != nil { return nil, err } else { - f.File = *file + f.File = file if stat, err = f.Stat(); err != nil { return nil, err } @@ -127,7 +127,9 @@ func (p *Pool) File() (*File, error) { return nil, err } } else { - f.Seek(0, 0) + if _, err := f.Seek(0, 0); err != nil { + return nil, err + } } if !p.inited.Set(true) { gfsnotify.Add(f.path, func(event *gfsnotify.Event) { diff --git a/g/os/gfpool/gfpool_test.go b/g/os/gfpool/gfpool_test.go index beef6771f..179705c50 100644 --- a/g/os/gfpool/gfpool_test.go +++ b/g/os/gfpool/gfpool_test.go @@ -5,17 +5,44 @@ import ( "os" ) -func Benchmark_os_Open_Close(b *testing.B) { +func Benchmark_os_Open_Close_ALLFlags(b *testing.B) { for i := 0; i < b.N; i++ { - f, _ := os.OpenFile("/tmp/bench-test", os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0766) + f, _ := os.OpenFile("/tmp/bench-test", os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666) f.Close() } } -func Benchmark_gfpool_Open_Close(b *testing.B) { +func Benchmark_gfpool_Open_Close_ALLFlags(b *testing.B) { for i := 0; i < b.N; i++ { - f, _ := Open("/tmp/bench-test", os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0766) + f, _ := Open("/tmp/bench-test", os.O_RDWR|os.O_CREATE|os.O_TRUNC|os.O_APPEND, 0666) f.Close() } } +func Benchmark_os_Open_Close_RDWR(b *testing.B) { + for i := 0; i < b.N; i++ { + f, _ := os.OpenFile("/tmp/bench-test", os.O_RDWR, 0666) + f.Close() + } +} + +func Benchmark_gfpool_Open_Close_RDWR(b *testing.B) { + for i := 0; i < b.N; i++ { + f, _ := Open("/tmp/bench-test", os.O_RDWR, 0666) + f.Close() + } +} + +func Benchmark_os_Open_Close_RDONLY(b *testing.B) { + for i := 0; i < b.N; i++ { + f, _ := os.OpenFile("/tmp/bench-test", os.O_RDONLY, 0666) + f.Close() + } +} + +func Benchmark_gfpool_Open_Close_RDONLY(b *testing.B) { + for i := 0; i < b.N; i++ { + f, _ := Open("/tmp/bench-test", os.O_RDONLY, 0666) + f.Close() + } +} \ No newline at end of file diff --git a/g/os/gfsnotify/gfsnotify_watcher.go b/g/os/gfsnotify/gfsnotify_watcher.go index ee6a32fcc..e10a64b4f 100644 --- a/g/os/gfsnotify/gfsnotify_watcher.go +++ b/g/os/gfsnotify/gfsnotify_watcher.go @@ -191,35 +191,75 @@ func (w *Watcher) startWatchLoop() { // 检索给定path的回调方法**列表** func (w *Watcher) getCallbacks(path string) *glist.List { - for path != "/" { + for { if l := w.callbacks.Get(path); l != nil { return l.(*glist.List) } else { - path = fileDir(path) + if p := fileDir(path); p != path { + path = p + } else { + break + } } } return nil } +// 获得真正监听的文件路径,判断规则: +// 1、在 callbacks 中应当有回调注册函数(否则监听根本没意义); +// 2、如果该path下不存在回调注册函数,则按照path长度从右往左递减,直到减到目录地址为止(不包含); +// 3、如果仍旧无法匹配回调函数,那么忽略,否则使用查找到的新path覆盖掉event的path; +func (w *Watcher) getWatchTruePath(path string) string { + if w.getCallbacks(path) != nil { + return path + } + dirPath := fileDir(path) + for { + path = path[0 : len(path) - 1] + if path == dirPath { + break + } + if w.getCallbacks(path) != nil { + return path + } + } + return "" +} + // 事件循环 func (w *Watcher) startEventLoop() { go func() { for { if v := w.events.Pop(); v != nil { event := v.(*Event) - // 如果是删除操作,那么需要判断是否文件真正不存在了 - if event.IsRemove() { - if fileExists(event.Path) { - // 如果是文件删除事件,判断该文件是否存在,如果存在,那么将此事件认为“假删除”, - // 并重新添加监控(底层fsnotify会自动删除掉监控,这里重新添加回去) - w.watcher.Add(event.Path) - // 修改事件操作为重命名(相当于重命名为自身名称,最终名称没变) - event.Op = RENAME - } else { - // 如果是真实删除,那么递归删除监控信息 - w.Remove(event.Path) - } + if path := w.getWatchTruePath(event.Path); path == "" { + continue + } else { + event.Path = path } + switch { + // 如果是删除操作,那么需要判断是否文件真正不存在了,如果存在,那么将此事件认为“假删除” + case event.IsRemove(): + if fileExists(event.Path) { + // 重新添加监控(底层fsnotify会自动删除掉监控,这里重新添加回去) + // 注意这里调用的是底层fsnotify添加监控,只会产生回调事件,并不会使回调函数重复注册 + w.watcher.Add(event.Path) + // 修改事件操作为重命名(相当于重命名为自身名称,最终名称没变) + event.Op = RENAME + } else { + // 如果是真实删除,那么递归删除监控信息 + w.Remove(event.Path) + } + + // 如果是删除操作,那么需要判断是否文件真正不存在了,如果存在,那么将此事件认为“假命名” + // (特别是某些编辑器在编辑文件时会先对文件RENAME再CHMOD) + case event.IsRename(): + if fileExists(event.Path) { + // 重新添加监控 + w.watcher.Add(event.Path) + } + } + callbacks := w.getCallbacks(event.Path) // 如果创建了新的目录,那么将这个目录递归添加到监控中 if event.IsCreate() && fileIsDir(event.Path) { diff --git a/g/os/glog/glog_logger.go b/g/os/glog/glog_logger.go index ed2ea114f..a5b1fa111 100644 --- a/g/os/glog/glog_logger.go +++ b/g/os/glog/glog_logger.go @@ -11,6 +11,7 @@ import ( "fmt" "gitee.com/johng/gf/g/container/gtype" "gitee.com/johng/gf/g/os/gfile" + "gitee.com/johng/gf/g/os/gfpool" "gitee.com/johng/gf/g/os/gmlock" "gitee.com/johng/gf/g/os/gtime" "gitee.com/johng/gf/g/util/gregex" @@ -38,6 +39,8 @@ type Logger struct { const ( gDEFAULT_FILE_FORMAT = `{Y-m-d}.log` gDEFAULT_FILE_POOL_FLAGS = os.O_CREATE|os.O_WRONLY|os.O_APPEND + gDEFAULT_FPOOL_PERM = os.FileMode(0666) + gDEFAULT_FPOOL_EXPIRE = 60000 ) var ( @@ -127,14 +130,14 @@ func (l *Logger) GetWriter() io.Writer { } // 获取默认的文件IO -func (l *Logger) getFilePointer() *os.File { +func (l *Logger) getFilePointer() *gfpool.File { if path := l.path.Val(); path != "" { // 文件名称中使用"{}"包含的内容使用gtime格式化 file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.file.Val(), func(s string) string { return gtime.Now().Format(strings.Trim(s, "{}")) }) fpath := path + gfile.Separator + file - if fp, err := gfile.OpenWithFlagPerm(fpath, gDEFAULT_FILE_POOL_FLAGS, 0666); err == nil { + if fp, err := gfpool.Open(fpath, gDEFAULT_FILE_POOL_FLAGS, gDEFAULT_FPOOL_PERM, gDEFAULT_FPOOL_EXPIRE); err == nil { return fp } else { fmt.Fprintln(os.Stderr, err) diff --git a/geg/net/ghttp/server/download/download.go b/geg/net/ghttp/server/download/download.go new file mode 100644 index 000000000..5c1d01abb --- /dev/null +++ b/geg/net/ghttp/server/download/download.go @@ -0,0 +1,20 @@ +package main + +import ( + "gitee.com/johng/gf/g" + "gitee.com/johng/gf/g/net/ghttp" +) + +func main() { + s := g.Server() + s.BindHandler("/download", func(r *ghttp.Request){ + r.Response.Header().Set("Content-Type", "text/html;charset=utf-8"); + r.Response.Header().Set("Content-type", "application/force-download"); + r.Response.Header().Set("Content-Type", "application/octet-stream"); + r.Response.Header().Set("Accept-Ranges", "bytes"); + r.Response.Header().Set("Content-Disposition", "attachment;filename=\"下载文件名称.txt\""); + r.Response.ServeFile("text.txt") + }) + s.SetPort(8199) + s.Run() +} \ No newline at end of file diff --git a/geg/net/ghttp/server/download/text.txt b/geg/net/ghttp/server/download/text.txt new file mode 100644 index 000000000..7e7566d8c --- /dev/null +++ b/geg/net/ghttp/server/download/text.txt @@ -0,0 +1 @@ +下载文件内容。 \ No newline at end of file diff --git a/geg/net/ghttp/server/template/tpl.go b/geg/net/ghttp/server/template/tpl.go deleted file mode 100644 index 87be74714..000000000 --- a/geg/net/ghttp/server/template/tpl.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "gitee.com/johng/gf/g" - "gitee.com/johng/gf/g/net/ghttp" -) - -func main() { - s := g.Server() - s.BindHandler("/", func(r *ghttp.Request){ - content :=`{{if (get "name")}} {{get "name"}} {{else}} NoName {{end}}` - r.Response.WriteTplContent(content, nil) - }) - s.SetPort(8199) - s.Run() -} \ No newline at end of file diff --git a/geg/net/ghttp/server/template/tpl1/index.html b/geg/net/ghttp/server/template/tpl1/index.html new file mode 100644 index 000000000..bebaf5f32 --- /dev/null +++ b/geg/net/ghttp/server/template/tpl1/index.html @@ -0,0 +1,11 @@ + + +
+ +