diff --git a/.gitpod.Dockerfile b/.gitpod.Dockerfile new file mode 100644 index 0000000..b795e6c --- /dev/null +++ b/.gitpod.Dockerfile @@ -0,0 +1,12 @@ +FROM gitpod/workspace-full + +USER root + +# Install custom tools, runtime, etc. using apt-get +# For example, the command below would install "bastet" - a command line tetris clone: +# +# RUN apt-get update \ +# && apt-get install -y bastet \ +# && apt-get clean && rm -rf /var/cache/apt/* && rm -rf /var/lib/apt/lists/* && rm -rf /tmp/* +# +# More information: https://www.gitpod.io/docs/42_config_docker/ diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 0000000..6ed69c1 --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,7 @@ +image: + file: .gitpod.Dockerfile + +# List the start up tasks. You can start them in parallel in multiple terminals. See https://www.gitpod.io/docs/44_config_start_tasks/ +tasks: +- init: go get -d -v ./... + command: go build \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..6f21e1d --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ + +language: go + +go: + - 1.13.x + - master + +script: + - go build + - ./fileboy version + - ./fileboy help + - ./fileboy init + - cat filegirl.yaml + - ./fileboy exec + diff --git a/README.md b/README.md index d01fc39..08c5453 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ ## 项目说明 +[![Build Status](https://travis-ci.org/dengsgo/fileboy.svg?branch=master)](https://travis-ci.org/dengsgo/fileboy) [![Go Report Card](https://goreportcard.com/badge/github.com/dengsgo/fileboy)](https://goreportcard.com/report/github.com/dengsgo/fileboy) + fileboy,文件变更监听通知系统,使用 Go 编写。 适用于 Hot Reload (典型的如开发go项目,无需每次手动执行 go build;又比如前端 node 打包) 或者 系统监控的场景。 ___ diff --git a/fileboy.go b/fileboy.go index 48ac721..603af2a 100644 --- a/fileboy.go +++ b/fileboy.go @@ -29,12 +29,21 @@ var ( watcher *fsnotify.Watcher taskMan *TaskMan + + ioeventMapStr = map[fsnotify.Op]string{ + fsnotify.Write: "write", + fsnotify.Rename: "rename", + fsnotify.Remove: "remove", + fsnotify.Create: "create", + fsnotify.Chmod: "chmod", + } ) type changedFile struct { Name string Changed int64 Ext string + Event string } func parseConfig() { @@ -56,6 +65,7 @@ func parseConfig() { cfg.Monitor.TypesMap = map[string]bool{} cfg.Monitor.IncludeDirsMap = map[string]bool{} cfg.Monitor.ExceptDirsMap = map[string]bool{} + cfg.Monitor.IncludeDirsRec = map[string]bool{} // convert to map for _, v := range cfg.Monitor.Types { cfg.Monitor.TypesMap[v] = true @@ -70,26 +80,23 @@ func eventDispatcher(event fsnotify.Event) { !keyInMonitorTypesMap(ext, cfg) { return } - switch event.Op { - case - fsnotify.Write, - fsnotify.Rename: - log.Println("EVENT", event.Op.String(), ":", event.Name) - taskMan.Put(&changedFile{ - Name: relativePath(projectFolder, event.Name), - Changed: time.Now().UnixNano(), - Ext: ext, - }) - case fsnotify.Remove: - case fsnotify.Create: + + op := ioeventMapStr[event.Op] + if len(cfg.Monitor.Events) != 0 && !inStrArray(op, cfg.Monitor.Events) { + return } + log.Println("EVENT", event.Op.String(), ":", event.Name) + taskMan.Put(&changedFile{ + Name: relativePath(projectFolder, event.Name), + Changed: time.Now().UnixNano(), + Ext: ext, + Event: op, + }) } func addWatcher() { log.Println("collecting directory information...") - dirsMap := map[string]bool{ - projectFolder: true, - } + dirsMap := map[string]bool{} for _, dir := range cfg.Monitor.IncludeDirs { darr := dirParse2Array(dir) if len(darr) < 1 || len(darr) > 2 { @@ -107,6 +114,7 @@ func addWatcher() { listFile(projectFolder, func(d string) { dirsMap[d] = true }) + cfg.Monitor.IncludeDirsRec[projectFolder] = true break } else { dirsMap[projectFolder] = true @@ -118,6 +126,7 @@ func addWatcher() { listFile(md, func(d string) { dirsMap[d] = true }) + cfg.Monitor.IncludeDirsRec[md] = true } } @@ -136,7 +145,7 @@ func addWatcher() { log.Println("watcher add -> ", dir) err := watcher.Add(dir) if err != nil { - logAndExit(err) + logAndExit(PreError, err) } } log.Println("total monitored dirs: " + strconv.Itoa(len(dirsMap))) @@ -161,6 +170,9 @@ func initWatcher() { if !ok { return } + // directory structure changes, dynamically add, delete and monitor according to rules + // TODO // this method cannot be triggered when the parent folder of the change folder is not monitored + go watchChangeHandler(event) eventDispatcher(event) case err, ok := <-watcher.Errors: if !ok { @@ -173,6 +185,56 @@ func initWatcher() { addWatcher() } +func watchChangeHandler(event fsnotify.Event) { + if event.Op != fsnotify.Create && event.Op != fsnotify.Rename { + return + } + _, err := ioutil.ReadDir(event.Name) + if err != nil { + return + } + do := false + for rec := range cfg.Monitor.IncludeDirsRec { + if !strings.HasPrefix(event.Name, rec) { + continue + } + // check exceptDirs + has := false + for _, v := range cfg.Monitor.ExceptDirs { + if strings.HasPrefix(event.Name, projectFolder+"/"+v) { + has = true + } + } + if has { + continue + } + + _ = watcher.Remove(event.Name) + err := watcher.Add(event.Name) + if err == nil { + do = true + log.Println("watcher add -> ", event.Name) + } else { + log.Println(PreWarn, "watcher add faild:", event.Name, err) + } + } + + if do { + return + } + + // check map + if _, ok := cfg.Monitor.DirsMap[event.Name]; ok { + _ = watcher.Remove(event.Name) + err := watcher.Add(event.Name) + if err == nil { + log.Println("watcher add -> ", event.Name) + } else { + log.Println(PreWarn, "watcher add faild:", event.Name, err) + } + } +} + func parseArgs() { switch len(os.Args) { case 1: @@ -219,6 +281,7 @@ func show() { func main() { log.SetPrefix("[FileBoy]: ") log.SetFlags(2) + log.SetOutput(os.Stdout) show() var err error projectFolder, err = os.Getwd() diff --git a/filegirl.go b/filegirl.go index afa44d5..86cfea3 100644 --- a/filegirl.go +++ b/filegirl.go @@ -8,11 +8,14 @@ type FileGirl struct { Types []string `yaml:"types"` IncludeDirs []string `yaml:"includeDirs"` ExceptDirs []string `yaml:"exceptDirs"` + Events []string `yaml:"events"` // convert to TypesMap map[string]bool `yaml:"-"` IncludeDirsMap map[string]bool `yaml:"-"` ExceptDirsMap map[string]bool `yaml:"-"` DirsMap map[string]bool `yaml:"-"` + + IncludeDirsRec map[string]bool `yaml:"-"` } Command struct { Exec []string `yaml:"exec"` diff --git a/notifer.go b/notifer.go index e81be59..ae2e0af 100644 --- a/notifer.go +++ b/notifer.go @@ -14,6 +14,7 @@ type postParams struct { File string `json:"file"` Changed int64 `json:"changed"` Ext string `json:"ext"` + Event string `json:"event"` } type NetNotifier struct { @@ -42,6 +43,7 @@ func (n *NetNotifier) Put(cf *changedFile) { File: cf.Name, Changed: cf.Changed, Ext: cf.Ext, + Event: cf.Event, }) } diff --git a/raw.go b/raw.go index 7b20aa9..eaafbd1 100644 --- a/raw.go +++ b/raw.go @@ -32,6 +32,20 @@ monitor: types: - .go + # 监听的事件类型,发生此类事件才执行 command 中的命令 + # 没有该配置默认监听所有事件 + # write 写入文件事件 + # rename 重命名文件事件 + # remove 移除文件事件 + # create 创建文件事件 + # chmod 更新文件权限事件(类unix) + events: + - write + - rename + - remove + - create + - chmod + # 命令 command: # 监听的文件有更改会执行的命令 diff --git a/util.go b/util.go index c91d294..ff34066 100644 --- a/util.go +++ b/util.go @@ -25,12 +25,15 @@ func cmdParse2Array(s string, cf *changedFile) []string { } func strParseRealStr(s string, cf *changedFile) string { - return strings.Replace( - strings.Replace( - strings.Replace(s, "{{file}}", cf.Name, -1), - "{{ext}}", cf.Ext, -1, + return strings.ReplaceAll( + strings.ReplaceAll( + strings.ReplaceAll( + strings.ReplaceAll(s, "{{file}}", cf.Name), + "{{ext}}", cf.Ext, + ), + "{{changed}}", strconv.FormatInt(cf.Changed, 10), ), - "{{changed}}", strconv.FormatInt(cf.Changed, 10), -1, + "{{event}}", cf.Event, ) } @@ -64,6 +67,15 @@ func relativePath(folder, p string) string { return s } +func inStrArray(s string, arr []string) bool { + for _, v := range arr { + if s == v { + return true + } + } + return false +} + func logAndExit(v ...interface{}) { log.Println(v...) os.Exit(0)