├── README.md ├── conf.go ├── deploy.go ├── fsnotify ├── example_test.go ├── fsnotify.go ├── fsnotify_bsd.go ├── fsnotify_linux.go ├── fsnotify_open_bsd.go ├── fsnotify_open_darwin.go ├── fsnotify_symlink_test.go ├── fsnotify_test.go └── fsnotify_windows.go ├── less.go ├── new.go ├── run.go ├── util.go └── watch.go /README.md: -------------------------------------------------------------------------------- 1 | # less 2 | 3 | Less is a command line tool facilitating development with lessgo framework. It is modified from bee. 4 | 5 | ## Requirements 6 | 7 | - Go version >= 1.3. 8 | 9 | 10 | ## Installation 11 | 12 | Begin by installing `less` using `go get` command. 13 | 14 | ```bash 15 | go get github.com/henrylee2cn/less 16 | ``` 17 | 18 | Then you can add `less` binary to PATH environment variable in your `~/.bashrc` or `~/.bash_profile` file: 19 | 20 | ```bash 21 | export PATH=$PATH:/bin 22 | ``` 23 | 24 | > If you already have `less` installed, updating `less` is simple: 25 | 26 | ```bash 27 | go get -u github.com/henrylee2cn/less 28 | ``` 29 | 30 | ## Basic commands 31 | 32 | Less provides a variety of commands which can be helpful at various stage of development. The top level commands include: 33 | ```base 34 | new create an application base on lessgo framework 35 | run run the app which can hot compile 36 | ``` 37 | 38 | ## less new 39 | 40 | Creating a new lessgo web application is no big deal, too. 41 | 42 | ```bash 43 | $ less new myapp 44 | [INFO] Creating application... 45 | /home/zheng/gopath/src/myapp/ 46 | /home/zheng/gopath/src/myapp/common/ 47 | /home/zheng/gopath/src/myapp/middleware/ 48 | /home/zheng/gopath/src/myapp/static/ 49 | /home/zheng/gopath/src/myapp/static/tpl/ 50 | /home/zheng/gopath/src/myapp/static/css/ 51 | /home/zheng/gopath/src/myapp/static/js/ 52 | /home/zheng/gopath/src/myapp/static/img/ 53 | /home/zheng/gopath/src/myapp/static/plugin/ 54 | /home/zheng/gopath/src/myapp/uploads/ 55 | /home/zheng/gopath/src/myapp/router/ 56 | /home/zheng/gopath/src/myapp/sys_handler/ 57 | /home/zheng/gopath/src/myapp/sys_handler/admin/ 58 | /home/zheng/gopath/src/myapp/sys_handler/admin/login/ 59 | /home/zheng/gopath/src/myapp/sys_model/ 60 | /home/zheng/gopath/src/myapp/sys_model/admin/ 61 | /home/zheng/gopath/src/myapp/sys_view/ 62 | /home/zheng/gopath/src/myapp/sys_view/admin/ 63 | /home/zheng/gopath/src/myapp/sys_view/admin/login/ 64 | /home/zheng/gopath/src/myapp/biz_handler/ 65 | /home/zheng/gopath/src/myapp/biz_handler/home/ 66 | /home/zheng/gopath/src/myapp/biz_model/ 67 | /home/zheng/gopath/src/myapp/biz_view/ 68 | /home/zheng/gopath/src/myapp/biz_view/home/ 69 | /home/zheng/gopath/src/myapp/middleware/test.go 70 | /home/zheng/gopath/src/myapp/static/img/favicon.ico 71 | /home/zheng/gopath/src/myapp/static/js/jquery.js 72 | /home/zheng/gopath/src/myapp/static/js/jquery.min.map 73 | /home/zheng/gopath/src/myapp/router/sys_router.go 74 | /home/zheng/gopath/src/myapp/router/biz_router.go 75 | /home/zheng/gopath/src/myapp/sys_handler/admin/index.go 76 | /home/zheng/gopath/src/myapp/sys_handler/admin/login/index.go 77 | /home/zheng/gopath/src/myapp/sys_model/admin/login.go 78 | /home/zheng/gopath/src/myapp/sys_view/admin/login/index.tpl 79 | /home/zheng/gopath/src/myapp/biz_handler/home/index.go 80 | /home/zheng/gopath/src/myapp/biz_handler/home/websocket.go 81 | /home/zheng/gopath/src/myapp/biz_view/home/index.tpl 82 | /home/zheng/gopath/src/myapp/biz_view/home/websocket.js 83 | /home/zheng/gopath/src/myapp/biz_view/home/jquery.githubRepoWidget2.js 84 | /home/zheng/gopath/src/myapp/biz_view/home/index.css 85 | /home/zheng/gopath/src/myapp/main.go 86 | 2016/08/08 15:45:47 [SUCC] New application successfully created! 87 | ``` 88 | 89 | ## less run 90 | 91 | To run the application we just created, navigate to the application folder and execute `less run`. 92 | 93 | ```bash 94 | $ cd myapp 95 | $ less run 96 | ``` 97 | 98 | 99 | ## Help 100 | 101 | If you happend to forget the usage of a command, you can always find the usage information by `less help `. 102 | 103 | For instance, to get more information about the `run` command: 104 | 105 | ```bash 106 | $ less help run 107 | usage: less run [appname] [watchall] [-main=*.go] 108 | 109 | start the appname throw exec.Command 110 | 111 | then start a inotify watch for current dir 112 | 113 | when the file has changed less will auto go build and restart the app 114 | 115 | file changed 116 | | 117 | check if it's go file 118 | | 119 | yes no 120 | | | 121 | go build do nothing 122 | | 123 | restart app 124 | 125 | ``` 126 | 127 | ## lessgo project 128 | 129 | ``` 130 | ─Project 项目开发目录 131 | ├─config 配置文件目录 132 | │ ├─app.config 系统应用配置文件 133 | │ └─db.config 数据库配置文件 134 | ├─common 后端公共目录 135 | │ └─... 如utils等其他 136 | ├─middleware 后端公共中间件目录 137 | ├─static 前端公共目录 (url: /static) 138 | │ ├─tpl 公共tpl模板目录 139 | │ ├─js 公共js目录 (url: /static/js) 140 | │ ├─css 公共css目录 (url: /static/css) 141 | │ ├─img 公共img目录 (url: /static/img) 142 | │ └─plugin 公共js插件 (url: /static/plugin) 143 | ├─uploads 默认上传下载目录 144 | ├─router 源码路由配置 145 | │ ├─sys_router.go 系统模块路由文件 146 | │ ├─biz_router.go 业务模块路由文件 147 | ├─sys_handler 系统模块后端目录 148 | │ ├─xxx 子模块目录 149 | │ │ ├─example.go example操作 150 | │ │ └─... xxx的子模块目录 151 | │ └─... 其他子模块目录 152 | ├─sys_model 系统模块数据模型目录 153 | ├─sys_view 系统模块前端目录 (url: /sys) 154 | │ ├─xxx 与sys_handler对应的子模块目录 (url: /sys/xxx) 155 | │ │ ├─example.tpl 相应操作的模板文件 156 | │ │ ├─example2.html 无需绑定操作的静态html文件 157 | │ │ ├─xxx.css css文件(可有多个) 158 | │ │ ├─xxx.js js文件(可有多个) 159 | │ │ └─... xxx的子模块目录 160 | ├─biz_handler 业务模块后端目录 161 | │ ├─xxx 子模块目录 162 | │ │ ├─example.go example操作 163 | │ │ └─... xxx的子模块目录 164 | │ └─... 其他子模块目录 165 | ├─biz_model 业务模块数据模型目录 166 | ├─biz_view 业务模块前端目录 (url: /biz) 167 | │ ├─xxx 与biz_handler对应的子模块目录 (url: /biz/xxx) 168 | │ │ ├─example.tpl 相应操作的模板文件 169 | │ │ ├─example2.html 无需绑定操作的静态html文件 170 | │ │ ├─xxx.css css文件(可有多个) 171 | │ │ ├─xxx.js js文件(可有多个) 172 | │ │ └─... xxx的子模块目录 173 | ├─database 默认数据库文件存储目录 174 | ├─logger 运行日志输出目录 175 | └─main.go 应用入口文件 176 | ``` 177 | 178 | -------------------------------------------------------------------------------- /conf.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 bee authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/json" 19 | "os" 20 | ) 21 | 22 | const CONF_VER = 0 23 | 24 | var defaultConf = `{ 25 | "version": 0, 26 | "gopm": { 27 | "enable": false, 28 | "install": false 29 | }, 30 | "go_install": false, 31 | "watch_ext": [], 32 | "dir_structure": { 33 | "watch_all": false, 34 | "controllers": "", 35 | "models": "", 36 | "others": [] 37 | }, 38 | "cmd_args": [], 39 | "envs": [], 40 | "database": { 41 | "driver": "mysql" 42 | } 43 | } 44 | ` 45 | var conf struct { 46 | Version int 47 | // gopm support 48 | Gopm struct { 49 | Enable bool 50 | Install bool 51 | } 52 | // Indicates whether execute "go install" before "go build". 53 | GoInstall bool `json:"go_install"` 54 | WatchExt []string `json:"watch_ext"` 55 | DirStruct struct { 56 | WatchAll bool `json:"watch_all"` 57 | Controllers string 58 | Models string 59 | Others []string // Other directories. 60 | } `json:"dir_structure"` 61 | CmdArgs []string `json:"cmd_args"` 62 | Envs []string 63 | Bale struct { 64 | Import string 65 | Dirs []string 66 | IngExt []string `json:"ignore_ext"` 67 | } 68 | Database struct { 69 | Driver string 70 | Conn string 71 | } 72 | } 73 | 74 | // loadConfig loads customized configuration. 75 | func loadConfig() error { 76 | f, err := os.Open("less.json") 77 | if err != nil { 78 | // Use default. 79 | err = json.Unmarshal([]byte(defaultConf), &conf) 80 | if err != nil { 81 | return err 82 | } 83 | } else { 84 | defer f.Close() 85 | ColorLog("[INFO] Detected less.json\n") 86 | d := json.NewDecoder(f) 87 | err = d.Decode(&conf) 88 | if err != nil { 89 | return err 90 | } 91 | } 92 | 93 | // Check format version. 94 | if conf.Version != CONF_VER { 95 | ColorLog("[WARN] Your less.json is out-of-date, please update!\n") 96 | ColorLog("[HINT] Compare less.json under less source code path and yours\n") 97 | } 98 | 99 | // Set variables. 100 | if len(conf.DirStruct.Controllers) == 0 { 101 | conf.DirStruct.Controllers = "controllers" 102 | } 103 | if len(conf.DirStruct.Models) == 0 { 104 | conf.DirStruct.Models = "models" 105 | } 106 | 107 | // Append watch exts. 108 | watchExts = append(watchExts, conf.WatchExt...) 109 | return nil 110 | } 111 | -------------------------------------------------------------------------------- /deploy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "html/template" 7 | "io" 8 | "log" 9 | "os" 10 | "strings" 11 | ) 12 | 13 | const version = "1.3.0" 14 | 15 | type Command struct { 16 | // Run runs the command. 17 | // The args are the arguments after the command name. 18 | Run func(cmd *Command, args []string) int 19 | 20 | // UsageLine is the one-line usage message. 21 | // The first word in the line is taken to be the command name. 22 | UsageLine string 23 | 24 | // Short is the short description shown in the 'go help' output. 25 | Short template.HTML 26 | 27 | // Long is the long message shown in the 'go help ' output. 28 | Long template.HTML 29 | 30 | // Flag is a set of flags specific to this command. 31 | Flag flag.FlagSet 32 | 33 | // CustomFlags indicates that the command will do its own 34 | // flag parsing. 35 | CustomFlags bool 36 | } 37 | 38 | // Name returns the command's name: the first word in the usage line. 39 | func (c *Command) Name() string { 40 | name := c.UsageLine 41 | i := strings.Index(name, " ") 42 | if i >= 0 { 43 | name = name[:i] 44 | } 45 | return name 46 | } 47 | 48 | func (c *Command) Usage() { 49 | fmt.Fprintf(os.Stderr, "usage: %s\n\n", c.UsageLine) 50 | fmt.Fprintf(os.Stderr, "%s\n", strings.TrimSpace(string(c.Long))) 51 | os.Exit(2) 52 | } 53 | 54 | // Runnable reports whether the command can be run; otherwise 55 | // it is a documentation pseudo-command such as importpath. 56 | func (c *Command) Runnable() bool { 57 | return c.Run != nil 58 | } 59 | 60 | var commands = []*Command{ 61 | cmdNew, 62 | cmdRun, 63 | } 64 | 65 | func Deploy() { 66 | flag.Usage = usage 67 | flag.Parse() 68 | log.SetFlags(0) 69 | 70 | args := flag.Args() 71 | if len(args) < 1 { 72 | usage() 73 | } 74 | 75 | if args[0] == "help" { 76 | help(args[1:]) 77 | return 78 | } 79 | 80 | for _, cmd := range commands { 81 | if cmd.Name() == args[0] && cmd.Run != nil { 82 | cmd.Flag.Usage = func() { cmd.Usage() } 83 | if cmd.CustomFlags { 84 | args = args[1:] 85 | } else { 86 | cmd.Flag.Parse(args[1:]) 87 | args = cmd.Flag.Args() 88 | } 89 | os.Exit(cmd.Run(cmd, args)) 90 | return 91 | } 92 | } 93 | 94 | fmt.Fprintf(os.Stderr, "less: unknown subcommand %q\nRun 'less help' for usage.\n", args[0]) 95 | os.Exit(2) 96 | } 97 | 98 | var usageTemplate = `this is a tool for managing less framework. 99 | 100 | Usage: 101 | 102 | less command [arguments] 103 | 104 | The commands are: 105 | {{range .}}{{if .Runnable}} 106 | {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} 107 | 108 | Use "less help [command]" for more information about a command. 109 | 110 | Additional help topics: 111 | {{range .}}{{if not .Runnable}} 112 | {{.Name | printf "%-11s"}} {{.Short}}{{end}}{{end}} 113 | 114 | Use "less help [topic]" for more information about that topic. 115 | 116 | ` 117 | 118 | var helpTemplate = `{{if .Runnable}}usage: less {{.UsageLine}} 119 | 120 | {{end}}{{.Long | trim}} 121 | ` 122 | 123 | func usage() { 124 | tmpl(os.Stdout, usageTemplate, commands) 125 | os.Exit(2) 126 | } 127 | 128 | func tmpl(w io.Writer, text string, data interface{}) { 129 | t := template.New("top") 130 | t.Funcs(template.FuncMap{"trim": func(s template.HTML) template.HTML { 131 | return template.HTML(strings.TrimSpace(string(s))) 132 | }}) 133 | template.Must(t.Parse(text)) 134 | if err := t.Execute(w, data); err != nil { 135 | panic(err) 136 | } 137 | } 138 | 139 | func help(args []string) { 140 | if len(args) == 0 { 141 | usage() 142 | // not exit 2: succeeded at 'go help'. 143 | return 144 | } 145 | if len(args) != 1 { 146 | fmt.Fprintf(os.Stdout, "usage: less help command\n\nToo many arguments given.\n") 147 | os.Exit(2) // failed at 'less help' 148 | } 149 | 150 | arg := args[0] 151 | 152 | for _, cmd := range commands { 153 | if cmd.Name() == arg { 154 | tmpl(os.Stdout, helpTemplate, cmd) 155 | // not exit 2: succeeded at 'go help cmd'. 156 | return 157 | } 158 | } 159 | 160 | fmt.Fprintf(os.Stdout, "Unknown help topic %#q. Run 'less help'.\n", arg) 161 | os.Exit(2) // failed at 'less help cmd' 162 | } 163 | -------------------------------------------------------------------------------- /fsnotify/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fsnotify_test 6 | 7 | import ( 8 | "log" 9 | 10 | "github.com/howeyc/fsnotify" 11 | ) 12 | 13 | func ExampleNewWatcher() { 14 | watcher, err := fsnotify.NewWatcher() 15 | if err != nil { 16 | log.Fatal(err) 17 | } 18 | 19 | go func() { 20 | for { 21 | select { 22 | case ev := <-watcher.Event: 23 | log.Println("event:", ev) 24 | case err := <-watcher.Error: 25 | log.Println("error:", err) 26 | } 27 | } 28 | }() 29 | 30 | err = watcher.Watch("/tmp/foo") 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /fsnotify/fsnotify.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package fsnotify implements file system notification. 6 | package fsnotify 7 | 8 | import "fmt" 9 | 10 | const ( 11 | FSN_CREATE = 1 12 | FSN_MODIFY = 2 13 | FSN_DELETE = 4 14 | FSN_RENAME = 8 15 | 16 | FSN_ALL = FSN_MODIFY | FSN_DELETE | FSN_RENAME | FSN_CREATE 17 | ) 18 | 19 | // Purge events from interal chan to external chan if passes filter 20 | func (w *Watcher) purgeEvents() { 21 | for ev := range w.internalEvent { 22 | sendEvent := false 23 | w.fsnmut.Lock() 24 | fsnFlags := w.fsnFlags[ev.Name] 25 | w.fsnmut.Unlock() 26 | 27 | if (fsnFlags&FSN_CREATE == FSN_CREATE) && ev.IsCreate() { 28 | sendEvent = true 29 | } 30 | 31 | if (fsnFlags&FSN_MODIFY == FSN_MODIFY) && ev.IsModify() { 32 | sendEvent = true 33 | } 34 | 35 | if (fsnFlags&FSN_DELETE == FSN_DELETE) && ev.IsDelete() { 36 | sendEvent = true 37 | } 38 | 39 | if (fsnFlags&FSN_RENAME == FSN_RENAME) && ev.IsRename() { 40 | sendEvent = true 41 | } 42 | 43 | if sendEvent { 44 | w.Event <- ev 45 | } 46 | 47 | // If there's no file, then no more events for user 48 | // BSD must keep watch for internal use (watches DELETEs to keep track 49 | // what files exist for create events) 50 | if ev.IsDelete() { 51 | w.fsnmut.Lock() 52 | delete(w.fsnFlags, ev.Name) 53 | w.fsnmut.Unlock() 54 | } 55 | } 56 | 57 | close(w.Event) 58 | } 59 | 60 | // Watch a given file path 61 | func (w *Watcher) Watch(path string) error { 62 | return w.WatchFlags(path, FSN_ALL) 63 | } 64 | 65 | // Watch a given file path for a particular set of notifications (FSN_MODIFY etc.) 66 | func (w *Watcher) WatchFlags(path string, flags uint32) error { 67 | w.fsnmut.Lock() 68 | w.fsnFlags[path] = flags 69 | w.fsnmut.Unlock() 70 | return w.watch(path) 71 | } 72 | 73 | // Remove a watch on a file 74 | func (w *Watcher) RemoveWatch(path string) error { 75 | w.fsnmut.Lock() 76 | delete(w.fsnFlags, path) 77 | w.fsnmut.Unlock() 78 | return w.removeWatch(path) 79 | } 80 | 81 | // String formats the event e in the form 82 | // "filename: DELETE|MODIFY|..." 83 | func (e *FileEvent) String() string { 84 | var events string = "" 85 | 86 | if e.IsCreate() { 87 | events += "|" + "CREATE" 88 | } 89 | 90 | if e.IsDelete() { 91 | events += "|" + "DELETE" 92 | } 93 | 94 | if e.IsModify() { 95 | events += "|" + "MODIFY" 96 | } 97 | 98 | if e.IsRename() { 99 | events += "|" + "RENAME" 100 | } 101 | 102 | if e.IsAttrib() { 103 | events += "|" + "ATTRIB" 104 | } 105 | 106 | if len(events) > 0 { 107 | events = events[1:] 108 | } 109 | 110 | return fmt.Sprintf("%q: %s", e.Name, events) 111 | } 112 | -------------------------------------------------------------------------------- /fsnotify/fsnotify_bsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build freebsd openbsd netbsd dragonfly darwin 6 | 7 | package fsnotify 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "io/ioutil" 13 | "os" 14 | "path/filepath" 15 | "sync" 16 | "syscall" 17 | ) 18 | 19 | const ( 20 | // Flags (from ) 21 | sys_NOTE_DELETE = 0x0001 /* vnode was removed */ 22 | sys_NOTE_WRITE = 0x0002 /* data contents changed */ 23 | sys_NOTE_EXTEND = 0x0004 /* size increased */ 24 | sys_NOTE_ATTRIB = 0x0008 /* attributes changed */ 25 | sys_NOTE_LINK = 0x0010 /* link count changed */ 26 | sys_NOTE_RENAME = 0x0020 /* vnode was renamed */ 27 | sys_NOTE_REVOKE = 0x0040 /* vnode access was revoked */ 28 | 29 | // Watch all events 30 | sys_NOTE_ALLEVENTS = sys_NOTE_DELETE | sys_NOTE_WRITE | sys_NOTE_ATTRIB | sys_NOTE_RENAME 31 | 32 | // Block for 100 ms on each call to kevent 33 | keventWaitTime = 100e6 34 | ) 35 | 36 | type FileEvent struct { 37 | mask uint32 // Mask of events 38 | Name string // File name (optional) 39 | create bool // set by fsnotify package if found new file 40 | } 41 | 42 | // IsCreate reports whether the FileEvent was triggered by a creation 43 | func (e *FileEvent) IsCreate() bool { return e.create } 44 | 45 | // IsDelete reports whether the FileEvent was triggered by a delete 46 | func (e *FileEvent) IsDelete() bool { return (e.mask & sys_NOTE_DELETE) == sys_NOTE_DELETE } 47 | 48 | // IsModify reports whether the FileEvent was triggered by a file modification 49 | func (e *FileEvent) IsModify() bool { 50 | return ((e.mask&sys_NOTE_WRITE) == sys_NOTE_WRITE || (e.mask&sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB) 51 | } 52 | 53 | // IsRename reports whether the FileEvent was triggered by a change name 54 | func (e *FileEvent) IsRename() bool { return (e.mask & sys_NOTE_RENAME) == sys_NOTE_RENAME } 55 | 56 | // IsAttrib reports whether the FileEvent was triggered by a change in the file metadata. 57 | func (e *FileEvent) IsAttrib() bool { 58 | return (e.mask & sys_NOTE_ATTRIB) == sys_NOTE_ATTRIB 59 | } 60 | 61 | type Watcher struct { 62 | mu sync.Mutex // Mutex for the Watcher itself. 63 | kq int // File descriptor (as returned by the kqueue() syscall) 64 | watches map[string]int // Map of watched file descriptors (key: path) 65 | wmut sync.Mutex // Protects access to watches. 66 | fsnFlags map[string]uint32 // Map of watched files to flags used for filter 67 | fsnmut sync.Mutex // Protects access to fsnFlags. 68 | enFlags map[string]uint32 // Map of watched files to evfilt note flags used in kqueue 69 | enmut sync.Mutex // Protects access to enFlags. 70 | paths map[int]string // Map of watched paths (key: watch descriptor) 71 | finfo map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor) 72 | pmut sync.Mutex // Protects access to paths and finfo. 73 | fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events) 74 | femut sync.Mutex // Protects access to fileExists. 75 | externalWatches map[string]bool // Map of watches added by user of the library. 76 | ewmut sync.Mutex // Protects access to externalWatches. 77 | Error chan error // Errors are sent on this channel 78 | internalEvent chan *FileEvent // Events are queued on this channel 79 | Event chan *FileEvent // Events are returned on this channel 80 | done chan bool // Channel for sending a "quit message" to the reader goroutine 81 | isClosed bool // Set to true when Close() is first called 82 | } 83 | 84 | // NewWatcher creates and returns a new kevent instance using kqueue(2) 85 | func NewWatcher() (*Watcher, error) { 86 | fd, errno := syscall.Kqueue() 87 | if fd == -1 { 88 | return nil, os.NewSyscallError("kqueue", errno) 89 | } 90 | w := &Watcher{ 91 | kq: fd, 92 | watches: make(map[string]int), 93 | fsnFlags: make(map[string]uint32), 94 | enFlags: make(map[string]uint32), 95 | paths: make(map[int]string), 96 | finfo: make(map[int]os.FileInfo), 97 | fileExists: make(map[string]bool), 98 | externalWatches: make(map[string]bool), 99 | internalEvent: make(chan *FileEvent), 100 | Event: make(chan *FileEvent), 101 | Error: make(chan error), 102 | done: make(chan bool, 1), 103 | } 104 | 105 | go w.readEvents() 106 | go w.purgeEvents() 107 | return w, nil 108 | } 109 | 110 | // Close closes a kevent watcher instance 111 | // It sends a message to the reader goroutine to quit and removes all watches 112 | // associated with the kevent instance 113 | func (w *Watcher) Close() error { 114 | w.mu.Lock() 115 | if w.isClosed { 116 | w.mu.Unlock() 117 | return nil 118 | } 119 | w.isClosed = true 120 | w.mu.Unlock() 121 | 122 | // Send "quit" message to the reader goroutine 123 | w.done <- true 124 | w.wmut.Lock() 125 | ws := w.watches 126 | w.wmut.Unlock() 127 | for path := range ws { 128 | w.removeWatch(path) 129 | } 130 | 131 | return nil 132 | } 133 | 134 | // AddWatch adds path to the watched file set. 135 | // The flags are interpreted as described in kevent(2). 136 | func (w *Watcher) addWatch(path string, flags uint32) error { 137 | w.mu.Lock() 138 | if w.isClosed { 139 | w.mu.Unlock() 140 | return errors.New("kevent instance already closed") 141 | } 142 | w.mu.Unlock() 143 | 144 | watchDir := false 145 | 146 | w.wmut.Lock() 147 | watchfd, found := w.watches[path] 148 | w.wmut.Unlock() 149 | if !found { 150 | fi, errstat := os.Lstat(path) 151 | if errstat != nil { 152 | return errstat 153 | } 154 | 155 | // don't watch socket 156 | if fi.Mode()&os.ModeSocket == os.ModeSocket { 157 | return nil 158 | } 159 | 160 | // Follow Symlinks 161 | // Unfortunately, Linux can add bogus symlinks to watch list without 162 | // issue, and Windows can't do symlinks period (AFAIK). To maintain 163 | // consistency, we will act like everything is fine. There will simply 164 | // be no file events for broken symlinks. 165 | // Hence the returns of nil on errors. 166 | if fi.Mode()&os.ModeSymlink == os.ModeSymlink { 167 | path, err := filepath.EvalSymlinks(path) 168 | if err != nil { 169 | return nil 170 | } 171 | 172 | fi, errstat = os.Lstat(path) 173 | if errstat != nil { 174 | return nil 175 | } 176 | } 177 | 178 | fd, errno := syscall.Open(path, open_FLAGS, 0700) 179 | if fd == -1 { 180 | return errno 181 | } 182 | watchfd = fd 183 | 184 | w.wmut.Lock() 185 | w.watches[path] = watchfd 186 | w.wmut.Unlock() 187 | 188 | w.pmut.Lock() 189 | w.paths[watchfd] = path 190 | w.finfo[watchfd] = fi 191 | w.pmut.Unlock() 192 | } 193 | // Watch the directory if it has not been watched before. 194 | w.pmut.Lock() 195 | w.enmut.Lock() 196 | if w.finfo[watchfd].IsDir() && 197 | (flags&sys_NOTE_WRITE) == sys_NOTE_WRITE && 198 | (!found || (w.enFlags[path]&sys_NOTE_WRITE) != sys_NOTE_WRITE) { 199 | watchDir = true 200 | } 201 | w.enmut.Unlock() 202 | w.pmut.Unlock() 203 | 204 | w.enmut.Lock() 205 | w.enFlags[path] = flags 206 | w.enmut.Unlock() 207 | 208 | var kbuf [1]syscall.Kevent_t 209 | watchEntry := &kbuf[0] 210 | watchEntry.Fflags = flags 211 | syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_ADD|syscall.EV_CLEAR) 212 | entryFlags := watchEntry.Flags 213 | success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil) 214 | if success == -1 { 215 | return errno 216 | } else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR { 217 | return errors.New("kevent add error") 218 | } 219 | 220 | if watchDir { 221 | errdir := w.watchDirectoryFiles(path) 222 | if errdir != nil { 223 | return errdir 224 | } 225 | } 226 | return nil 227 | } 228 | 229 | // Watch adds path to the watched file set, watching all events. 230 | func (w *Watcher) watch(path string) error { 231 | w.ewmut.Lock() 232 | w.externalWatches[path] = true 233 | w.ewmut.Unlock() 234 | return w.addWatch(path, sys_NOTE_ALLEVENTS) 235 | } 236 | 237 | // RemoveWatch removes path from the watched file set. 238 | func (w *Watcher) removeWatch(path string) error { 239 | w.wmut.Lock() 240 | watchfd, ok := w.watches[path] 241 | w.wmut.Unlock() 242 | if !ok { 243 | return errors.New(fmt.Sprintf("can't remove non-existent kevent watch for: %s", path)) 244 | } 245 | var kbuf [1]syscall.Kevent_t 246 | watchEntry := &kbuf[0] 247 | syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_DELETE) 248 | entryFlags := watchEntry.Flags 249 | success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil) 250 | if success == -1 { 251 | return os.NewSyscallError("kevent_rm_watch", errno) 252 | } else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR { 253 | return errors.New("kevent rm error") 254 | } 255 | syscall.Close(watchfd) 256 | w.wmut.Lock() 257 | delete(w.watches, path) 258 | w.wmut.Unlock() 259 | w.enmut.Lock() 260 | delete(w.enFlags, path) 261 | w.enmut.Unlock() 262 | w.pmut.Lock() 263 | delete(w.paths, watchfd) 264 | fInfo := w.finfo[watchfd] 265 | delete(w.finfo, watchfd) 266 | w.pmut.Unlock() 267 | 268 | // Find all watched paths that are in this directory that are not external. 269 | if fInfo.IsDir() { 270 | var pathsToRemove []string 271 | w.pmut.Lock() 272 | for _, wpath := range w.paths { 273 | wdir, _ := filepath.Split(wpath) 274 | if filepath.Clean(wdir) == filepath.Clean(path) { 275 | w.ewmut.Lock() 276 | if !w.externalWatches[wpath] { 277 | pathsToRemove = append(pathsToRemove, wpath) 278 | } 279 | w.ewmut.Unlock() 280 | } 281 | } 282 | w.pmut.Unlock() 283 | for _, p := range pathsToRemove { 284 | // Since these are internal, not much sense in propagating error 285 | // to the user, as that will just confuse them with an error about 286 | // a path they did not explicitly watch themselves. 287 | w.removeWatch(p) 288 | } 289 | } 290 | 291 | return nil 292 | } 293 | 294 | // readEvents reads from the kqueue file descriptor, converts the 295 | // received events into Event objects and sends them via the Event channel 296 | func (w *Watcher) readEvents() { 297 | var ( 298 | eventbuf [10]syscall.Kevent_t // Event buffer 299 | events []syscall.Kevent_t // Received events 300 | twait *syscall.Timespec // Time to block waiting for events 301 | n int // Number of events returned from kevent 302 | errno error // Syscall errno 303 | ) 304 | events = eventbuf[0:0] 305 | twait = new(syscall.Timespec) 306 | *twait = syscall.NsecToTimespec(keventWaitTime) 307 | 308 | for { 309 | // See if there is a message on the "done" channel 310 | var done bool 311 | select { 312 | case done = <-w.done: 313 | default: 314 | } 315 | 316 | // If "done" message is received 317 | if done { 318 | errno := syscall.Close(w.kq) 319 | if errno != nil { 320 | w.Error <- os.NewSyscallError("close", errno) 321 | } 322 | close(w.internalEvent) 323 | close(w.Error) 324 | return 325 | } 326 | 327 | // Get new events 328 | if len(events) == 0 { 329 | n, errno = syscall.Kevent(w.kq, nil, eventbuf[:], twait) 330 | 331 | // EINTR is okay, basically the syscall was interrupted before 332 | // timeout expired. 333 | if errno != nil && errno != syscall.EINTR { 334 | w.Error <- os.NewSyscallError("kevent", errno) 335 | continue 336 | } 337 | 338 | // Received some events 339 | if n > 0 { 340 | events = eventbuf[0:n] 341 | } 342 | } 343 | 344 | // Flush the events we received to the events channel 345 | for len(events) > 0 { 346 | fileEvent := new(FileEvent) 347 | watchEvent := &events[0] 348 | fileEvent.mask = uint32(watchEvent.Fflags) 349 | w.pmut.Lock() 350 | fileEvent.Name = w.paths[int(watchEvent.Ident)] 351 | fileInfo := w.finfo[int(watchEvent.Ident)] 352 | w.pmut.Unlock() 353 | if fileInfo != nil && fileInfo.IsDir() && !fileEvent.IsDelete() { 354 | // Double check to make sure the directory exist. This can happen when 355 | // we do a rm -fr on a recursively watched folders and we receive a 356 | // modification event first but the folder has been deleted and later 357 | // receive the delete event 358 | if _, err := os.Lstat(fileEvent.Name); os.IsNotExist(err) { 359 | // mark is as delete event 360 | fileEvent.mask |= sys_NOTE_DELETE 361 | } 362 | } 363 | 364 | if fileInfo != nil && fileInfo.IsDir() && fileEvent.IsModify() && !fileEvent.IsDelete() { 365 | w.sendDirectoryChangeEvents(fileEvent.Name) 366 | } else { 367 | // Send the event on the events channel 368 | w.internalEvent <- fileEvent 369 | } 370 | 371 | // Move to next event 372 | events = events[1:] 373 | 374 | if fileEvent.IsRename() { 375 | w.removeWatch(fileEvent.Name) 376 | w.femut.Lock() 377 | delete(w.fileExists, fileEvent.Name) 378 | w.femut.Unlock() 379 | } 380 | if fileEvent.IsDelete() { 381 | w.removeWatch(fileEvent.Name) 382 | w.femut.Lock() 383 | delete(w.fileExists, fileEvent.Name) 384 | w.femut.Unlock() 385 | 386 | // Look for a file that may have overwritten this 387 | // (ie mv f1 f2 will delete f2 then create f2) 388 | fileDir, _ := filepath.Split(fileEvent.Name) 389 | fileDir = filepath.Clean(fileDir) 390 | w.wmut.Lock() 391 | _, found := w.watches[fileDir] 392 | w.wmut.Unlock() 393 | if found { 394 | // make sure the directory exist before we watch for changes. When we 395 | // do a recursive watch and perform rm -fr, the parent directory might 396 | // have gone missing, ignore the missing directory and let the 397 | // upcoming delete event remove the watch form the parent folder 398 | if _, err := os.Lstat(fileDir); !os.IsNotExist(err) { 399 | w.sendDirectoryChangeEvents(fileDir) 400 | } 401 | } 402 | } 403 | } 404 | } 405 | } 406 | 407 | func (w *Watcher) watchDirectoryFiles(dirPath string) error { 408 | // Get all files 409 | files, err := ioutil.ReadDir(dirPath) 410 | if err != nil { 411 | return err 412 | } 413 | 414 | // Search for new files 415 | for _, fileInfo := range files { 416 | filePath := filepath.Join(dirPath, fileInfo.Name()) 417 | 418 | // Inherit fsnFlags from parent directory 419 | w.fsnmut.Lock() 420 | if flags, found := w.fsnFlags[dirPath]; found { 421 | w.fsnFlags[filePath] = flags 422 | } else { 423 | w.fsnFlags[filePath] = FSN_ALL 424 | } 425 | w.fsnmut.Unlock() 426 | 427 | if fileInfo.IsDir() == false { 428 | // Watch file to mimic linux fsnotify 429 | e := w.addWatch(filePath, sys_NOTE_ALLEVENTS) 430 | if e != nil { 431 | return e 432 | } 433 | } else { 434 | // If the user is currently watching directory 435 | // we want to preserve the flags used 436 | w.enmut.Lock() 437 | currFlags, found := w.enFlags[filePath] 438 | w.enmut.Unlock() 439 | var newFlags uint32 = sys_NOTE_DELETE 440 | if found { 441 | newFlags |= currFlags 442 | } 443 | 444 | // Linux gives deletes if not explicitly watching 445 | e := w.addWatch(filePath, newFlags) 446 | if e != nil { 447 | return e 448 | } 449 | } 450 | w.femut.Lock() 451 | w.fileExists[filePath] = true 452 | w.femut.Unlock() 453 | } 454 | 455 | return nil 456 | } 457 | 458 | // sendDirectoryEvents searches the directory for newly created files 459 | // and sends them over the event channel. This functionality is to have 460 | // the BSD version of fsnotify match linux fsnotify which provides a 461 | // create event for files created in a watched directory. 462 | func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { 463 | // Get all files 464 | files, err := ioutil.ReadDir(dirPath) 465 | if err != nil { 466 | w.Error <- err 467 | } 468 | 469 | // Search for new files 470 | for _, fileInfo := range files { 471 | filePath := filepath.Join(dirPath, fileInfo.Name()) 472 | w.femut.Lock() 473 | _, doesExist := w.fileExists[filePath] 474 | w.femut.Unlock() 475 | if !doesExist { 476 | // Inherit fsnFlags from parent directory 477 | w.fsnmut.Lock() 478 | if flags, found := w.fsnFlags[dirPath]; found { 479 | w.fsnFlags[filePath] = flags 480 | } else { 481 | w.fsnFlags[filePath] = FSN_ALL 482 | } 483 | w.fsnmut.Unlock() 484 | 485 | // Send create event 486 | fileEvent := new(FileEvent) 487 | fileEvent.Name = filePath 488 | fileEvent.create = true 489 | w.internalEvent <- fileEvent 490 | } 491 | w.femut.Lock() 492 | w.fileExists[filePath] = true 493 | w.femut.Unlock() 494 | } 495 | w.watchDirectoryFiles(dirPath) 496 | } 497 | -------------------------------------------------------------------------------- /fsnotify/fsnotify_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build linux 6 | 7 | package fsnotify 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "os" 13 | "strings" 14 | "sync" 15 | "syscall" 16 | "unsafe" 17 | ) 18 | 19 | const ( 20 | // Options for inotify_init() are not exported 21 | // sys_IN_CLOEXEC uint32 = syscall.IN_CLOEXEC 22 | // sys_IN_NONBLOCK uint32 = syscall.IN_NONBLOCK 23 | 24 | // Options for AddWatch 25 | sys_IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW 26 | sys_IN_ONESHOT uint32 = syscall.IN_ONESHOT 27 | sys_IN_ONLYDIR uint32 = syscall.IN_ONLYDIR 28 | 29 | // The "sys_IN_MASK_ADD" option is not exported, as AddWatch 30 | // adds it automatically, if there is already a watch for the given path 31 | // sys_IN_MASK_ADD uint32 = syscall.IN_MASK_ADD 32 | 33 | // Events 34 | sys_IN_ACCESS uint32 = syscall.IN_ACCESS 35 | sys_IN_ALL_EVENTS uint32 = syscall.IN_ALL_EVENTS 36 | sys_IN_ATTRIB uint32 = syscall.IN_ATTRIB 37 | sys_IN_CLOSE uint32 = syscall.IN_CLOSE 38 | sys_IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE 39 | sys_IN_CLOSE_WRITE uint32 = syscall.IN_CLOSE_WRITE 40 | sys_IN_CREATE uint32 = syscall.IN_CREATE 41 | sys_IN_DELETE uint32 = syscall.IN_DELETE 42 | sys_IN_DELETE_SELF uint32 = syscall.IN_DELETE_SELF 43 | sys_IN_MODIFY uint32 = syscall.IN_MODIFY 44 | sys_IN_MOVE uint32 = syscall.IN_MOVE 45 | sys_IN_MOVED_FROM uint32 = syscall.IN_MOVED_FROM 46 | sys_IN_MOVED_TO uint32 = syscall.IN_MOVED_TO 47 | sys_IN_MOVE_SELF uint32 = syscall.IN_MOVE_SELF 48 | sys_IN_OPEN uint32 = syscall.IN_OPEN 49 | 50 | sys_AGNOSTIC_EVENTS = sys_IN_MOVED_TO | sys_IN_MOVED_FROM | sys_IN_CREATE | sys_IN_ATTRIB | sys_IN_MODIFY | sys_IN_MOVE_SELF | sys_IN_DELETE | sys_IN_DELETE_SELF 51 | 52 | // Special events 53 | sys_IN_ISDIR uint32 = syscall.IN_ISDIR 54 | sys_IN_IGNORED uint32 = syscall.IN_IGNORED 55 | sys_IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW 56 | sys_IN_UNMOUNT uint32 = syscall.IN_UNMOUNT 57 | ) 58 | 59 | type FileEvent struct { 60 | mask uint32 // Mask of events 61 | cookie uint32 // Unique cookie associating related events (for rename(2)) 62 | Name string // File name (optional) 63 | } 64 | 65 | // IsCreate reports whether the FileEvent was triggered by a creation 66 | func (e *FileEvent) IsCreate() bool { 67 | return (e.mask&sys_IN_CREATE) == sys_IN_CREATE || (e.mask&sys_IN_MOVED_TO) == sys_IN_MOVED_TO 68 | } 69 | 70 | // IsDelete reports whether the FileEvent was triggered by a delete 71 | func (e *FileEvent) IsDelete() bool { 72 | return (e.mask&sys_IN_DELETE_SELF) == sys_IN_DELETE_SELF || (e.mask&sys_IN_DELETE) == sys_IN_DELETE 73 | } 74 | 75 | // IsModify reports whether the FileEvent was triggered by a file modification or attribute change 76 | func (e *FileEvent) IsModify() bool { 77 | return ((e.mask&sys_IN_MODIFY) == sys_IN_MODIFY || (e.mask&sys_IN_ATTRIB) == sys_IN_ATTRIB) 78 | } 79 | 80 | // IsRename reports whether the FileEvent was triggered by a change name 81 | func (e *FileEvent) IsRename() bool { 82 | return ((e.mask&sys_IN_MOVE_SELF) == sys_IN_MOVE_SELF || (e.mask&sys_IN_MOVED_FROM) == sys_IN_MOVED_FROM) 83 | } 84 | 85 | // IsAttrib reports whether the FileEvent was triggered by a change in the file metadata. 86 | func (e *FileEvent) IsAttrib() bool { 87 | return (e.mask & sys_IN_ATTRIB) == sys_IN_ATTRIB 88 | } 89 | 90 | type watch struct { 91 | wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) 92 | flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) 93 | } 94 | 95 | type Watcher struct { 96 | mu sync.Mutex // Map access 97 | fd int // File descriptor (as returned by the inotify_init() syscall) 98 | watches map[string]*watch // Map of inotify watches (key: path) 99 | fsnFlags map[string]uint32 // Map of watched files to flags used for filter 100 | fsnmut sync.Mutex // Protects access to fsnFlags. 101 | paths map[int]string // Map of watched paths (key: watch descriptor) 102 | Error chan error // Errors are sent on this channel 103 | internalEvent chan *FileEvent // Events are queued on this channel 104 | Event chan *FileEvent // Events are returned on this channel 105 | done chan bool // Channel for sending a "quit message" to the reader goroutine 106 | isClosed bool // Set to true when Close() is first called 107 | } 108 | 109 | // NewWatcher creates and returns a new inotify instance using inotify_init(2) 110 | func NewWatcher() (*Watcher, error) { 111 | fd, errno := syscall.InotifyInit() 112 | if fd == -1 { 113 | return nil, os.NewSyscallError("inotify_init", errno) 114 | } 115 | w := &Watcher{ 116 | fd: fd, 117 | watches: make(map[string]*watch), 118 | fsnFlags: make(map[string]uint32), 119 | paths: make(map[int]string), 120 | internalEvent: make(chan *FileEvent), 121 | Event: make(chan *FileEvent), 122 | Error: make(chan error), 123 | done: make(chan bool, 1), 124 | } 125 | 126 | go w.readEvents() 127 | go w.purgeEvents() 128 | return w, nil 129 | } 130 | 131 | // Close closes an inotify watcher instance 132 | // It sends a message to the reader goroutine to quit and removes all watches 133 | // associated with the inotify instance 134 | func (w *Watcher) Close() error { 135 | if w.isClosed { 136 | return nil 137 | } 138 | w.isClosed = true 139 | 140 | // Remove all watches 141 | for path := range w.watches { 142 | w.RemoveWatch(path) 143 | } 144 | 145 | // Send "quit" message to the reader goroutine 146 | w.done <- true 147 | 148 | return nil 149 | } 150 | 151 | // AddWatch adds path to the watched file set. 152 | // The flags are interpreted as described in inotify_add_watch(2). 153 | func (w *Watcher) addWatch(path string, flags uint32) error { 154 | if w.isClosed { 155 | return errors.New("inotify instance already closed") 156 | } 157 | 158 | w.mu.Lock() 159 | watchEntry, found := w.watches[path] 160 | w.mu.Unlock() 161 | if found { 162 | watchEntry.flags |= flags 163 | flags |= syscall.IN_MASK_ADD 164 | } 165 | wd, errno := syscall.InotifyAddWatch(w.fd, path, flags) 166 | if wd == -1 { 167 | return errno 168 | } 169 | 170 | w.mu.Lock() 171 | w.watches[path] = &watch{wd: uint32(wd), flags: flags} 172 | w.paths[wd] = path 173 | w.mu.Unlock() 174 | 175 | return nil 176 | } 177 | 178 | // Watch adds path to the watched file set, watching all events. 179 | func (w *Watcher) watch(path string) error { 180 | return w.addWatch(path, sys_AGNOSTIC_EVENTS) 181 | } 182 | 183 | // RemoveWatch removes path from the watched file set. 184 | func (w *Watcher) removeWatch(path string) error { 185 | w.mu.Lock() 186 | defer w.mu.Unlock() 187 | watch, ok := w.watches[path] 188 | if !ok { 189 | return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path)) 190 | } 191 | success, errno := syscall.InotifyRmWatch(w.fd, watch.wd) 192 | if success == -1 { 193 | return os.NewSyscallError("inotify_rm_watch", errno) 194 | } 195 | delete(w.watches, path) 196 | return nil 197 | } 198 | 199 | // readEvents reads from the inotify file descriptor, converts the 200 | // received events into Event objects and sends them via the Event channel 201 | func (w *Watcher) readEvents() { 202 | var ( 203 | buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events 204 | n int // Number of bytes read with read() 205 | errno error // Syscall errno 206 | ) 207 | 208 | for { 209 | // See if there is a message on the "done" channel 210 | select { 211 | case <-w.done: 212 | syscall.Close(w.fd) 213 | close(w.internalEvent) 214 | close(w.Error) 215 | return 216 | default: 217 | } 218 | 219 | n, errno = syscall.Read(w.fd, buf[:]) 220 | 221 | // If EOF is received 222 | if n == 0 { 223 | syscall.Close(w.fd) 224 | close(w.internalEvent) 225 | close(w.Error) 226 | return 227 | } 228 | 229 | if n < 0 { 230 | w.Error <- os.NewSyscallError("read", errno) 231 | continue 232 | } 233 | if n < syscall.SizeofInotifyEvent { 234 | w.Error <- errors.New("inotify: short read in readEvents()") 235 | continue 236 | } 237 | 238 | var offset uint32 = 0 239 | // We don't know how many events we just read into the buffer 240 | // While the offset points to at least one whole event... 241 | for offset <= uint32(n-syscall.SizeofInotifyEvent) { 242 | // Point "raw" to the event in the buffer 243 | raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset])) 244 | event := new(FileEvent) 245 | event.mask = uint32(raw.Mask) 246 | event.cookie = uint32(raw.Cookie) 247 | nameLen := uint32(raw.Len) 248 | // If the event happened to the watched directory or the watched file, the kernel 249 | // doesn't append the filename to the event, but we would like to always fill the 250 | // the "Name" field with a valid filename. We retrieve the path of the watch from 251 | // the "paths" map. 252 | w.mu.Lock() 253 | event.Name = w.paths[int(raw.Wd)] 254 | w.mu.Unlock() 255 | watchedName := event.Name 256 | if nameLen > 0 { 257 | // Point "bytes" at the first byte of the filename 258 | bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent])) 259 | // The filename is padded with NUL bytes. TrimRight() gets rid of those. 260 | event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") 261 | } 262 | 263 | // Send the events that are not ignored on the events channel 264 | if !event.ignoreLinux() { 265 | // Setup FSNotify flags (inherit from directory watch) 266 | w.fsnmut.Lock() 267 | if _, fsnFound := w.fsnFlags[event.Name]; !fsnFound { 268 | if fsnFlags, watchFound := w.fsnFlags[watchedName]; watchFound { 269 | w.fsnFlags[event.Name] = fsnFlags 270 | } else { 271 | w.fsnFlags[event.Name] = FSN_ALL 272 | } 273 | } 274 | w.fsnmut.Unlock() 275 | 276 | w.internalEvent <- event 277 | } 278 | 279 | // Move to the next event in the buffer 280 | offset += syscall.SizeofInotifyEvent + nameLen 281 | } 282 | } 283 | } 284 | 285 | // Certain types of events can be "ignored" and not sent over the Event 286 | // channel. Such as events marked ignore by the kernel, or MODIFY events 287 | // against files that do not exist. 288 | func (e *FileEvent) ignoreLinux() bool { 289 | // Ignore anything the inotify API says to ignore 290 | if e.mask&sys_IN_IGNORED == sys_IN_IGNORED { 291 | return true 292 | } 293 | 294 | // If the event is not a DELETE or RENAME, the file must exist. 295 | // Otherwise the event is ignored. 296 | // *Note*: this was put in place because it was seen that a MODIFY 297 | // event was sent after the DELETE. This ignores that MODIFY and 298 | // assumes a DELETE will come or has come if the file doesn't exist. 299 | if !(e.IsDelete() || e.IsRename()) { 300 | _, statErr := os.Lstat(e.Name) 301 | return os.IsNotExist(statErr) 302 | } 303 | return false 304 | } 305 | -------------------------------------------------------------------------------- /fsnotify/fsnotify_open_bsd.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build freebsd openbsd netbsd dragonfly 6 | 7 | package fsnotify 8 | 9 | import "syscall" 10 | 11 | const open_FLAGS = syscall.O_NONBLOCK | syscall.O_RDONLY 12 | -------------------------------------------------------------------------------- /fsnotify/fsnotify_open_darwin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build darwin 6 | 7 | package fsnotify 8 | 9 | import "syscall" 10 | 11 | const open_FLAGS = syscall.O_EVTONLY 12 | -------------------------------------------------------------------------------- /fsnotify/fsnotify_symlink_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build freebsd openbsd netbsd darwin linux 6 | 7 | package fsnotify 8 | 9 | import ( 10 | "os" 11 | "path/filepath" 12 | "testing" 13 | "time" 14 | ) 15 | 16 | func TestFsnotifyFakeSymlink(t *testing.T) { 17 | watcher := newWatcher(t) 18 | 19 | // Create directory to watch 20 | testDir := tempMkdir(t) 21 | defer os.RemoveAll(testDir) 22 | 23 | var errorsReceived counter 24 | // Receive errors on the error channel on a separate goroutine 25 | go func() { 26 | for errors := range watcher.Error { 27 | t.Logf("Received error: %s", errors) 28 | errorsReceived.increment() 29 | } 30 | }() 31 | 32 | // Count the CREATE events received 33 | var createEventsReceived, otherEventsReceived counter 34 | go func() { 35 | for ev := range watcher.Event { 36 | t.Logf("event received: %s", ev) 37 | if ev.IsCreate() { 38 | createEventsReceived.increment() 39 | } else { 40 | otherEventsReceived.increment() 41 | } 42 | } 43 | }() 44 | 45 | addWatch(t, watcher, testDir) 46 | 47 | if err := os.Symlink(filepath.Join(testDir, "zzz"), filepath.Join(testDir, "zzznew")); err != nil { 48 | t.Fatalf("Failed to create bogus symlink: %s", err) 49 | } 50 | t.Logf("Created bogus symlink") 51 | 52 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 53 | time.Sleep(500 * time.Millisecond) 54 | 55 | // Should not be error, just no events for broken links (watching nothing) 56 | if errorsReceived.value() > 0 { 57 | t.Fatal("fsnotify errors have been received.") 58 | } 59 | if otherEventsReceived.value() > 0 { 60 | t.Fatal("fsnotify other events received on the broken link") 61 | } 62 | 63 | // Except for 1 create event (for the link itself) 64 | if createEventsReceived.value() == 0 { 65 | t.Fatal("fsnotify create events were not received after 500 ms") 66 | } 67 | if createEventsReceived.value() > 1 { 68 | t.Fatal("fsnotify more create events received than expected") 69 | } 70 | 71 | // Try closing the fsnotify instance 72 | t.Log("calling Close()") 73 | watcher.Close() 74 | } 75 | -------------------------------------------------------------------------------- /fsnotify/fsnotify_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2010 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package fsnotify 6 | 7 | import ( 8 | "io/ioutil" 9 | "os" 10 | "os/exec" 11 | "path/filepath" 12 | "runtime" 13 | "sync/atomic" 14 | "testing" 15 | "time" 16 | ) 17 | 18 | // An atomic counter 19 | type counter struct { 20 | val int32 21 | } 22 | 23 | func (c *counter) increment() { 24 | atomic.AddInt32(&c.val, 1) 25 | } 26 | 27 | func (c *counter) value() int32 { 28 | return atomic.LoadInt32(&c.val) 29 | } 30 | 31 | func (c *counter) reset() { 32 | atomic.StoreInt32(&c.val, 0) 33 | } 34 | 35 | // tempMkdir makes a temporary directory 36 | func tempMkdir(t *testing.T) string { 37 | dir, err := ioutil.TempDir("", "fsnotify") 38 | if err != nil { 39 | t.Fatalf("failed to create test directory: %s", err) 40 | } 41 | return dir 42 | } 43 | 44 | // newWatcher initializes an fsnotify Watcher instance. 45 | func newWatcher(t *testing.T) *Watcher { 46 | watcher, err := NewWatcher() 47 | if err != nil { 48 | t.Fatalf("NewWatcher() failed: %s", err) 49 | } 50 | return watcher 51 | } 52 | 53 | // addWatch adds a watch for a directory 54 | func addWatch(t *testing.T, watcher *Watcher, dir string) { 55 | if err := watcher.Watch(dir); err != nil { 56 | t.Fatalf("watcher.Watch(%q) failed: %s", dir, err) 57 | } 58 | } 59 | 60 | func TestFsnotifyMultipleOperations(t *testing.T) { 61 | watcher := newWatcher(t) 62 | 63 | // Receive errors on the error channel on a separate goroutine 64 | go func() { 65 | for err := range watcher.Error { 66 | t.Fatalf("error received: %s", err) 67 | } 68 | }() 69 | 70 | // Create directory to watch 71 | testDir := tempMkdir(t) 72 | defer os.RemoveAll(testDir) 73 | 74 | // Create directory that's not watched 75 | testDirToMoveFiles := tempMkdir(t) 76 | defer os.RemoveAll(testDirToMoveFiles) 77 | 78 | testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile") 79 | testFileRenamed := filepath.Join(testDirToMoveFiles, "TestFsnotifySeqRename.testfile") 80 | 81 | addWatch(t, watcher, testDir) 82 | 83 | // Receive events on the event channel on a separate goroutine 84 | eventstream := watcher.Event 85 | var createReceived, modifyReceived, deleteReceived, renameReceived counter 86 | done := make(chan bool) 87 | go func() { 88 | for event := range eventstream { 89 | // Only count relevant events 90 | if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) { 91 | t.Logf("event received: %s", event) 92 | if event.IsDelete() { 93 | deleteReceived.increment() 94 | } 95 | if event.IsModify() { 96 | modifyReceived.increment() 97 | } 98 | if event.IsCreate() { 99 | createReceived.increment() 100 | } 101 | if event.IsRename() { 102 | renameReceived.increment() 103 | } 104 | } else { 105 | t.Logf("unexpected event received: %s", event) 106 | } 107 | } 108 | done <- true 109 | }() 110 | 111 | // Create a file 112 | // This should add at least one event to the fsnotify event queue 113 | var f *os.File 114 | f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 115 | if err != nil { 116 | t.Fatalf("creating test file failed: %s", err) 117 | } 118 | f.Sync() 119 | 120 | time.Sleep(time.Millisecond) 121 | f.WriteString("data") 122 | f.Sync() 123 | f.Close() 124 | 125 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 126 | 127 | if err := testRename(testFile, testFileRenamed); err != nil { 128 | t.Fatalf("rename failed: %s", err) 129 | } 130 | 131 | // Modify the file outside of the watched dir 132 | f, err = os.Open(testFileRenamed) 133 | if err != nil { 134 | t.Fatalf("open test renamed file failed: %s", err) 135 | } 136 | f.WriteString("data") 137 | f.Sync() 138 | f.Close() 139 | 140 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 141 | 142 | // Recreate the file that was moved 143 | f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 144 | if err != nil { 145 | t.Fatalf("creating test file failed: %s", err) 146 | } 147 | f.Close() 148 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 149 | 150 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 151 | time.Sleep(500 * time.Millisecond) 152 | cReceived := createReceived.value() 153 | if cReceived != 2 { 154 | t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2) 155 | } 156 | mReceived := modifyReceived.value() 157 | if mReceived != 1 { 158 | t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1) 159 | } 160 | dReceived := deleteReceived.value() 161 | rReceived := renameReceived.value() 162 | if dReceived+rReceived != 1 { 163 | t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", rReceived+dReceived, 1) 164 | } 165 | 166 | // Try closing the fsnotify instance 167 | t.Log("calling Close()") 168 | watcher.Close() 169 | t.Log("waiting for the event channel to become closed...") 170 | select { 171 | case <-done: 172 | t.Log("event channel closed") 173 | case <-time.After(2 * time.Second): 174 | t.Fatal("event stream was not closed after 2 seconds") 175 | } 176 | } 177 | 178 | func TestFsnotifyMultipleCreates(t *testing.T) { 179 | watcher := newWatcher(t) 180 | 181 | // Receive errors on the error channel on a separate goroutine 182 | go func() { 183 | for err := range watcher.Error { 184 | t.Fatalf("error received: %s", err) 185 | } 186 | }() 187 | 188 | // Create directory to watch 189 | testDir := tempMkdir(t) 190 | defer os.RemoveAll(testDir) 191 | 192 | testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile") 193 | 194 | addWatch(t, watcher, testDir) 195 | 196 | // Receive events on the event channel on a separate goroutine 197 | eventstream := watcher.Event 198 | var createReceived, modifyReceived, deleteReceived counter 199 | done := make(chan bool) 200 | go func() { 201 | for event := range eventstream { 202 | // Only count relevant events 203 | if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) { 204 | t.Logf("event received: %s", event) 205 | if event.IsDelete() { 206 | deleteReceived.increment() 207 | } 208 | if event.IsCreate() { 209 | createReceived.increment() 210 | } 211 | if event.IsModify() { 212 | modifyReceived.increment() 213 | } 214 | } else { 215 | t.Logf("unexpected event received: %s", event) 216 | } 217 | } 218 | done <- true 219 | }() 220 | 221 | // Create a file 222 | // This should add at least one event to the fsnotify event queue 223 | var f *os.File 224 | f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 225 | if err != nil { 226 | t.Fatalf("creating test file failed: %s", err) 227 | } 228 | f.Sync() 229 | 230 | time.Sleep(time.Millisecond) 231 | f.WriteString("data") 232 | f.Sync() 233 | f.Close() 234 | 235 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 236 | 237 | os.Remove(testFile) 238 | 239 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 240 | 241 | // Recreate the file 242 | f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 243 | if err != nil { 244 | t.Fatalf("creating test file failed: %s", err) 245 | } 246 | f.Close() 247 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 248 | 249 | // Modify 250 | f, err = os.OpenFile(testFile, os.O_WRONLY, 0666) 251 | if err != nil { 252 | t.Fatalf("creating test file failed: %s", err) 253 | } 254 | f.Sync() 255 | 256 | time.Sleep(time.Millisecond) 257 | f.WriteString("data") 258 | f.Sync() 259 | f.Close() 260 | 261 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 262 | 263 | // Modify 264 | f, err = os.OpenFile(testFile, os.O_WRONLY, 0666) 265 | if err != nil { 266 | t.Fatalf("creating test file failed: %s", err) 267 | } 268 | f.Sync() 269 | 270 | time.Sleep(time.Millisecond) 271 | f.WriteString("data") 272 | f.Sync() 273 | f.Close() 274 | 275 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 276 | 277 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 278 | time.Sleep(500 * time.Millisecond) 279 | cReceived := createReceived.value() 280 | if cReceived != 2 { 281 | t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2) 282 | } 283 | mReceived := modifyReceived.value() 284 | if mReceived < 3 { 285 | t.Fatalf("incorrect number of modify events received after 500 ms (%d vs atleast %d)", mReceived, 3) 286 | } 287 | dReceived := deleteReceived.value() 288 | if dReceived != 1 { 289 | t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", dReceived, 1) 290 | } 291 | 292 | // Try closing the fsnotify instance 293 | t.Log("calling Close()") 294 | watcher.Close() 295 | t.Log("waiting for the event channel to become closed...") 296 | select { 297 | case <-done: 298 | t.Log("event channel closed") 299 | case <-time.After(2 * time.Second): 300 | t.Fatal("event stream was not closed after 2 seconds") 301 | } 302 | } 303 | 304 | func TestFsnotifyDirOnly(t *testing.T) { 305 | watcher := newWatcher(t) 306 | 307 | // Create directory to watch 308 | testDir := tempMkdir(t) 309 | defer os.RemoveAll(testDir) 310 | 311 | // Create a file before watching directory 312 | // This should NOT add any events to the fsnotify event queue 313 | testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile") 314 | { 315 | var f *os.File 316 | f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666) 317 | if err != nil { 318 | t.Fatalf("creating test file failed: %s", err) 319 | } 320 | f.Sync() 321 | f.Close() 322 | } 323 | 324 | addWatch(t, watcher, testDir) 325 | 326 | // Receive errors on the error channel on a separate goroutine 327 | go func() { 328 | for err := range watcher.Error { 329 | t.Fatalf("error received: %s", err) 330 | } 331 | }() 332 | 333 | testFile := filepath.Join(testDir, "TestFsnotifyDirOnly.testfile") 334 | 335 | // Receive events on the event channel on a separate goroutine 336 | eventstream := watcher.Event 337 | var createReceived, modifyReceived, deleteReceived counter 338 | done := make(chan bool) 339 | go func() { 340 | for event := range eventstream { 341 | // Only count relevant events 342 | if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileAlreadyExists) { 343 | t.Logf("event received: %s", event) 344 | if event.IsDelete() { 345 | deleteReceived.increment() 346 | } 347 | if event.IsModify() { 348 | modifyReceived.increment() 349 | } 350 | if event.IsCreate() { 351 | createReceived.increment() 352 | } 353 | } else { 354 | t.Logf("unexpected event received: %s", event) 355 | } 356 | } 357 | done <- true 358 | }() 359 | 360 | // Create a file 361 | // This should add at least one event to the fsnotify event queue 362 | var f *os.File 363 | f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 364 | if err != nil { 365 | t.Fatalf("creating test file failed: %s", err) 366 | } 367 | f.Sync() 368 | 369 | time.Sleep(time.Millisecond) 370 | f.WriteString("data") 371 | f.Sync() 372 | f.Close() 373 | 374 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 375 | 376 | os.Remove(testFile) 377 | os.Remove(testFileAlreadyExists) 378 | 379 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 380 | time.Sleep(500 * time.Millisecond) 381 | cReceived := createReceived.value() 382 | if cReceived != 1 { 383 | t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 1) 384 | } 385 | mReceived := modifyReceived.value() 386 | if mReceived != 1 { 387 | t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1) 388 | } 389 | dReceived := deleteReceived.value() 390 | if dReceived != 2 { 391 | t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2) 392 | } 393 | 394 | // Try closing the fsnotify instance 395 | t.Log("calling Close()") 396 | watcher.Close() 397 | t.Log("waiting for the event channel to become closed...") 398 | select { 399 | case <-done: 400 | t.Log("event channel closed") 401 | case <-time.After(2 * time.Second): 402 | t.Fatal("event stream was not closed after 2 seconds") 403 | } 404 | } 405 | 406 | func TestFsnotifyDeleteWatchedDir(t *testing.T) { 407 | watcher := newWatcher(t) 408 | defer watcher.Close() 409 | 410 | // Create directory to watch 411 | testDir := tempMkdir(t) 412 | defer os.RemoveAll(testDir) 413 | 414 | // Create a file before watching directory 415 | testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile") 416 | { 417 | var f *os.File 418 | f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666) 419 | if err != nil { 420 | t.Fatalf("creating test file failed: %s", err) 421 | } 422 | f.Sync() 423 | f.Close() 424 | } 425 | 426 | addWatch(t, watcher, testDir) 427 | 428 | // Add a watch for testFile 429 | addWatch(t, watcher, testFileAlreadyExists) 430 | 431 | // Receive errors on the error channel on a separate goroutine 432 | go func() { 433 | for err := range watcher.Error { 434 | t.Fatalf("error received: %s", err) 435 | } 436 | }() 437 | 438 | // Receive events on the event channel on a separate goroutine 439 | eventstream := watcher.Event 440 | var deleteReceived counter 441 | go func() { 442 | for event := range eventstream { 443 | // Only count relevant events 444 | if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFileAlreadyExists) { 445 | t.Logf("event received: %s", event) 446 | if event.IsDelete() { 447 | deleteReceived.increment() 448 | } 449 | } else { 450 | t.Logf("unexpected event received: %s", event) 451 | } 452 | } 453 | }() 454 | 455 | os.RemoveAll(testDir) 456 | 457 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 458 | time.Sleep(500 * time.Millisecond) 459 | dReceived := deleteReceived.value() 460 | if dReceived < 2 { 461 | t.Fatalf("did not receive at least %d delete events, received %d after 500 ms", 2, dReceived) 462 | } 463 | } 464 | 465 | func TestFsnotifySubDir(t *testing.T) { 466 | watcher := newWatcher(t) 467 | 468 | // Create directory to watch 469 | testDir := tempMkdir(t) 470 | defer os.RemoveAll(testDir) 471 | 472 | testFile1 := filepath.Join(testDir, "TestFsnotifyFile1.testfile") 473 | testSubDir := filepath.Join(testDir, "sub") 474 | testSubDirFile := filepath.Join(testDir, "sub/TestFsnotifyFile1.testfile") 475 | 476 | // Receive errors on the error channel on a separate goroutine 477 | go func() { 478 | for err := range watcher.Error { 479 | t.Fatalf("error received: %s", err) 480 | } 481 | }() 482 | 483 | // Receive events on the event channel on a separate goroutine 484 | eventstream := watcher.Event 485 | var createReceived, deleteReceived counter 486 | done := make(chan bool) 487 | go func() { 488 | for event := range eventstream { 489 | // Only count relevant events 490 | if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testSubDir) || event.Name == filepath.Clean(testFile1) { 491 | t.Logf("event received: %s", event) 492 | if event.IsCreate() { 493 | createReceived.increment() 494 | } 495 | if event.IsDelete() { 496 | deleteReceived.increment() 497 | } 498 | } else { 499 | t.Logf("unexpected event received: %s", event) 500 | } 501 | } 502 | done <- true 503 | }() 504 | 505 | addWatch(t, watcher, testDir) 506 | 507 | // Create sub-directory 508 | if err := os.Mkdir(testSubDir, 0777); err != nil { 509 | t.Fatalf("failed to create test sub-directory: %s", err) 510 | } 511 | 512 | // Create a file 513 | var f *os.File 514 | f, err := os.OpenFile(testFile1, os.O_WRONLY|os.O_CREATE, 0666) 515 | if err != nil { 516 | t.Fatalf("creating test file failed: %s", err) 517 | } 518 | f.Sync() 519 | f.Close() 520 | 521 | // Create a file (Should not see this! we are not watching subdir) 522 | var fs *os.File 523 | fs, err = os.OpenFile(testSubDirFile, os.O_WRONLY|os.O_CREATE, 0666) 524 | if err != nil { 525 | t.Fatalf("creating test file failed: %s", err) 526 | } 527 | fs.Sync() 528 | fs.Close() 529 | 530 | time.Sleep(200 * time.Millisecond) 531 | 532 | // Make sure receive deletes for both file and sub-directory 533 | os.RemoveAll(testSubDir) 534 | os.Remove(testFile1) 535 | 536 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 537 | time.Sleep(500 * time.Millisecond) 538 | cReceived := createReceived.value() 539 | if cReceived != 2 { 540 | t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2) 541 | } 542 | dReceived := deleteReceived.value() 543 | if dReceived != 2 { 544 | t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2) 545 | } 546 | 547 | // Try closing the fsnotify instance 548 | t.Log("calling Close()") 549 | watcher.Close() 550 | t.Log("waiting for the event channel to become closed...") 551 | select { 552 | case <-done: 553 | t.Log("event channel closed") 554 | case <-time.After(2 * time.Second): 555 | t.Fatal("event stream was not closed after 2 seconds") 556 | } 557 | } 558 | 559 | func TestFsnotifyRename(t *testing.T) { 560 | watcher := newWatcher(t) 561 | 562 | // Create directory to watch 563 | testDir := tempMkdir(t) 564 | defer os.RemoveAll(testDir) 565 | 566 | addWatch(t, watcher, testDir) 567 | 568 | // Receive errors on the error channel on a separate goroutine 569 | go func() { 570 | for err := range watcher.Error { 571 | t.Fatalf("error received: %s", err) 572 | } 573 | }() 574 | 575 | testFile := filepath.Join(testDir, "TestFsnotifyEvents.testfile") 576 | testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed") 577 | 578 | // Receive events on the event channel on a separate goroutine 579 | eventstream := watcher.Event 580 | var renameReceived counter 581 | done := make(chan bool) 582 | go func() { 583 | for event := range eventstream { 584 | // Only count relevant events 585 | if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) { 586 | if event.IsRename() { 587 | renameReceived.increment() 588 | } 589 | t.Logf("event received: %s", event) 590 | } else { 591 | t.Logf("unexpected event received: %s", event) 592 | } 593 | } 594 | done <- true 595 | }() 596 | 597 | // Create a file 598 | // This should add at least one event to the fsnotify event queue 599 | var f *os.File 600 | f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 601 | if err != nil { 602 | t.Fatalf("creating test file failed: %s", err) 603 | } 604 | f.Sync() 605 | 606 | f.WriteString("data") 607 | f.Sync() 608 | f.Close() 609 | 610 | // Add a watch for testFile 611 | addWatch(t, watcher, testFile) 612 | 613 | if err := testRename(testFile, testFileRenamed); err != nil { 614 | t.Fatalf("rename failed: %s", err) 615 | } 616 | 617 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 618 | time.Sleep(500 * time.Millisecond) 619 | if renameReceived.value() == 0 { 620 | t.Fatal("fsnotify rename events have not been received after 500 ms") 621 | } 622 | 623 | // Try closing the fsnotify instance 624 | t.Log("calling Close()") 625 | watcher.Close() 626 | t.Log("waiting for the event channel to become closed...") 627 | select { 628 | case <-done: 629 | t.Log("event channel closed") 630 | case <-time.After(2 * time.Second): 631 | t.Fatal("event stream was not closed after 2 seconds") 632 | } 633 | 634 | os.Remove(testFileRenamed) 635 | } 636 | 637 | func TestFsnotifyRenameToCreate(t *testing.T) { 638 | watcher := newWatcher(t) 639 | 640 | // Create directory to watch 641 | testDir := tempMkdir(t) 642 | defer os.RemoveAll(testDir) 643 | 644 | // Create directory to get file 645 | testDirFrom := tempMkdir(t) 646 | defer os.RemoveAll(testDirFrom) 647 | 648 | addWatch(t, watcher, testDir) 649 | 650 | // Receive errors on the error channel on a separate goroutine 651 | go func() { 652 | for err := range watcher.Error { 653 | t.Fatalf("error received: %s", err) 654 | } 655 | }() 656 | 657 | testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile") 658 | testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed") 659 | 660 | // Receive events on the event channel on a separate goroutine 661 | eventstream := watcher.Event 662 | var createReceived counter 663 | done := make(chan bool) 664 | go func() { 665 | for event := range eventstream { 666 | // Only count relevant events 667 | if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) { 668 | if event.IsCreate() { 669 | createReceived.increment() 670 | } 671 | t.Logf("event received: %s", event) 672 | } else { 673 | t.Logf("unexpected event received: %s", event) 674 | } 675 | } 676 | done <- true 677 | }() 678 | 679 | // Create a file 680 | // This should add at least one event to the fsnotify event queue 681 | var f *os.File 682 | f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 683 | if err != nil { 684 | t.Fatalf("creating test file failed: %s", err) 685 | } 686 | f.Sync() 687 | f.Close() 688 | 689 | if err := testRename(testFile, testFileRenamed); err != nil { 690 | t.Fatalf("rename failed: %s", err) 691 | } 692 | 693 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 694 | time.Sleep(500 * time.Millisecond) 695 | if createReceived.value() == 0 { 696 | t.Fatal("fsnotify create events have not been received after 500 ms") 697 | } 698 | 699 | // Try closing the fsnotify instance 700 | t.Log("calling Close()") 701 | watcher.Close() 702 | t.Log("waiting for the event channel to become closed...") 703 | select { 704 | case <-done: 705 | t.Log("event channel closed") 706 | case <-time.After(2 * time.Second): 707 | t.Fatal("event stream was not closed after 2 seconds") 708 | } 709 | 710 | os.Remove(testFileRenamed) 711 | } 712 | 713 | func TestFsnotifyRenameToOverwrite(t *testing.T) { 714 | switch runtime.GOOS { 715 | case "plan9", "windows": 716 | t.Skipf("skipping test on %q (os.Rename over existing file does not create event).", runtime.GOOS) 717 | } 718 | 719 | watcher := newWatcher(t) 720 | 721 | // Create directory to watch 722 | testDir := tempMkdir(t) 723 | defer os.RemoveAll(testDir) 724 | 725 | // Create directory to get file 726 | testDirFrom := tempMkdir(t) 727 | defer os.RemoveAll(testDirFrom) 728 | 729 | testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile") 730 | testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed") 731 | 732 | // Create a file 733 | var fr *os.File 734 | fr, err := os.OpenFile(testFileRenamed, os.O_WRONLY|os.O_CREATE, 0666) 735 | if err != nil { 736 | t.Fatalf("creating test file failed: %s", err) 737 | } 738 | fr.Sync() 739 | fr.Close() 740 | 741 | addWatch(t, watcher, testDir) 742 | 743 | // Receive errors on the error channel on a separate goroutine 744 | go func() { 745 | for err := range watcher.Error { 746 | t.Fatalf("error received: %s", err) 747 | } 748 | }() 749 | 750 | // Receive events on the event channel on a separate goroutine 751 | eventstream := watcher.Event 752 | var eventReceived counter 753 | done := make(chan bool) 754 | go func() { 755 | for event := range eventstream { 756 | // Only count relevant events 757 | if event.Name == filepath.Clean(testFileRenamed) { 758 | eventReceived.increment() 759 | t.Logf("event received: %s", event) 760 | } else { 761 | t.Logf("unexpected event received: %s", event) 762 | } 763 | } 764 | done <- true 765 | }() 766 | 767 | // Create a file 768 | // This should add at least one event to the fsnotify event queue 769 | var f *os.File 770 | f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 771 | if err != nil { 772 | t.Fatalf("creating test file failed: %s", err) 773 | } 774 | f.Sync() 775 | f.Close() 776 | 777 | if err := testRename(testFile, testFileRenamed); err != nil { 778 | t.Fatalf("rename failed: %s", err) 779 | } 780 | 781 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 782 | time.Sleep(500 * time.Millisecond) 783 | if eventReceived.value() == 0 { 784 | t.Fatal("fsnotify events have not been received after 500 ms") 785 | } 786 | 787 | // Try closing the fsnotify instance 788 | t.Log("calling Close()") 789 | watcher.Close() 790 | t.Log("waiting for the event channel to become closed...") 791 | select { 792 | case <-done: 793 | t.Log("event channel closed") 794 | case <-time.After(2 * time.Second): 795 | t.Fatal("event stream was not closed after 2 seconds") 796 | } 797 | 798 | os.Remove(testFileRenamed) 799 | } 800 | 801 | func TestRemovalOfWatch(t *testing.T) { 802 | // Create directory to watch 803 | testDir := tempMkdir(t) 804 | defer os.RemoveAll(testDir) 805 | 806 | // Create a file before watching directory 807 | testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile") 808 | { 809 | var f *os.File 810 | f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666) 811 | if err != nil { 812 | t.Fatalf("creating test file failed: %s", err) 813 | } 814 | f.Sync() 815 | f.Close() 816 | } 817 | 818 | watcher := newWatcher(t) 819 | defer watcher.Close() 820 | 821 | addWatch(t, watcher, testDir) 822 | if err := watcher.RemoveWatch(testDir); err != nil { 823 | t.Fatalf("Could not remove the watch: %v\n", err) 824 | } 825 | 826 | go func() { 827 | select { 828 | case ev := <-watcher.Event: 829 | t.Fatalf("We received event: %v\n", ev) 830 | case <-time.After(500 * time.Millisecond): 831 | t.Log("No event received, as expected.") 832 | } 833 | }() 834 | 835 | time.Sleep(200 * time.Millisecond) 836 | // Modify the file outside of the watched dir 837 | f, err := os.Open(testFileAlreadyExists) 838 | if err != nil { 839 | t.Fatalf("Open test file failed: %s", err) 840 | } 841 | f.WriteString("data") 842 | f.Sync() 843 | f.Close() 844 | if err := os.Chmod(testFileAlreadyExists, 0700); err != nil { 845 | t.Fatalf("chmod failed: %s", err) 846 | } 847 | time.Sleep(400 * time.Millisecond) 848 | } 849 | 850 | func TestFsnotifyAttrib(t *testing.T) { 851 | if runtime.GOOS == "windows" { 852 | t.Skip("attributes don't work on Windows.") 853 | } 854 | 855 | watcher := newWatcher(t) 856 | 857 | // Create directory to watch 858 | testDir := tempMkdir(t) 859 | defer os.RemoveAll(testDir) 860 | 861 | // Receive errors on the error channel on a separate goroutine 862 | go func() { 863 | for err := range watcher.Error { 864 | t.Fatalf("error received: %s", err) 865 | } 866 | }() 867 | 868 | testFile := filepath.Join(testDir, "TestFsnotifyAttrib.testfile") 869 | 870 | // Receive events on the event channel on a separate goroutine 871 | eventstream := watcher.Event 872 | // The modifyReceived counter counts IsModify events that are not IsAttrib, 873 | // and the attribReceived counts IsAttrib events (which are also IsModify as 874 | // a consequence). 875 | var modifyReceived counter 876 | var attribReceived counter 877 | done := make(chan bool) 878 | go func() { 879 | for event := range eventstream { 880 | // Only count relevant events 881 | if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) { 882 | if event.IsModify() { 883 | modifyReceived.increment() 884 | } 885 | if event.IsAttrib() { 886 | attribReceived.increment() 887 | } 888 | t.Logf("event received: %s", event) 889 | } else { 890 | t.Logf("unexpected event received: %s", event) 891 | } 892 | } 893 | done <- true 894 | }() 895 | 896 | // Create a file 897 | // This should add at least one event to the fsnotify event queue 898 | var f *os.File 899 | f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 900 | if err != nil { 901 | t.Fatalf("creating test file failed: %s", err) 902 | } 903 | f.Sync() 904 | 905 | f.WriteString("data") 906 | f.Sync() 907 | f.Close() 908 | 909 | // Add a watch for testFile 910 | addWatch(t, watcher, testFile) 911 | 912 | if err := os.Chmod(testFile, 0700); err != nil { 913 | t.Fatalf("chmod failed: %s", err) 914 | } 915 | 916 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 917 | // Creating/writing a file changes also the mtime, so IsAttrib should be set to true here 918 | time.Sleep(500 * time.Millisecond) 919 | if modifyReceived.value() == 0 { 920 | t.Fatal("fsnotify modify events have not received after 500 ms") 921 | } 922 | if attribReceived.value() == 0 { 923 | t.Fatal("fsnotify attribute events have not received after 500 ms") 924 | } 925 | 926 | // Modifying the contents of the file does not set the attrib flag (although eg. the mtime 927 | // might have been modified). 928 | modifyReceived.reset() 929 | attribReceived.reset() 930 | 931 | f, err = os.OpenFile(testFile, os.O_WRONLY, 0) 932 | if err != nil { 933 | t.Fatalf("reopening test file failed: %s", err) 934 | } 935 | 936 | f.WriteString("more data") 937 | f.Sync() 938 | f.Close() 939 | 940 | time.Sleep(500 * time.Millisecond) 941 | 942 | if modifyReceived.value() != 1 { 943 | t.Fatal("didn't receive a modify event after changing test file contents") 944 | } 945 | 946 | if attribReceived.value() != 0 { 947 | t.Fatal("did receive an unexpected attrib event after changing test file contents") 948 | } 949 | 950 | modifyReceived.reset() 951 | attribReceived.reset() 952 | 953 | // Doing a chmod on the file should trigger an event with the "attrib" flag set (the contents 954 | // of the file are not changed though) 955 | if err := os.Chmod(testFile, 0600); err != nil { 956 | t.Fatalf("chmod failed: %s", err) 957 | } 958 | 959 | time.Sleep(500 * time.Millisecond) 960 | 961 | if attribReceived.value() != 1 { 962 | t.Fatal("didn't receive an attribute change after 500ms") 963 | } 964 | 965 | // Try closing the fsnotify instance 966 | t.Log("calling Close()") 967 | watcher.Close() 968 | t.Log("waiting for the event channel to become closed...") 969 | select { 970 | case <-done: 971 | t.Log("event channel closed") 972 | case <-time.After(1e9): 973 | t.Fatal("event stream was not closed after 1 second") 974 | } 975 | 976 | os.Remove(testFile) 977 | } 978 | 979 | func TestFsnotifyClose(t *testing.T) { 980 | watcher := newWatcher(t) 981 | watcher.Close() 982 | 983 | var done int32 984 | go func() { 985 | watcher.Close() 986 | atomic.StoreInt32(&done, 1) 987 | }() 988 | 989 | time.Sleep(50e6) // 50 ms 990 | if atomic.LoadInt32(&done) == 0 { 991 | t.Fatal("double Close() test failed: second Close() call didn't return") 992 | } 993 | 994 | testDir := tempMkdir(t) 995 | defer os.RemoveAll(testDir) 996 | 997 | if err := watcher.Watch(testDir); err == nil { 998 | t.Fatal("expected error on Watch() after Close(), got nil") 999 | } 1000 | } 1001 | 1002 | func testRename(file1, file2 string) error { 1003 | switch runtime.GOOS { 1004 | case "windows", "plan9": 1005 | return os.Rename(file1, file2) 1006 | default: 1007 | cmd := exec.Command("mv", file1, file2) 1008 | return cmd.Run() 1009 | } 1010 | } 1011 | -------------------------------------------------------------------------------- /fsnotify/fsnotify_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2011 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // +build windows 6 | 7 | package fsnotify 8 | 9 | import ( 10 | "errors" 11 | "fmt" 12 | "os" 13 | "path/filepath" 14 | "runtime" 15 | "sync" 16 | "syscall" 17 | "unsafe" 18 | ) 19 | 20 | const ( 21 | // Options for AddWatch 22 | sys_FS_ONESHOT = 0x80000000 23 | sys_FS_ONLYDIR = 0x1000000 24 | 25 | // Events 26 | sys_FS_ACCESS = 0x1 27 | sys_FS_ALL_EVENTS = 0xfff 28 | sys_FS_ATTRIB = 0x4 29 | sys_FS_CLOSE = 0x18 30 | sys_FS_CREATE = 0x100 31 | sys_FS_DELETE = 0x200 32 | sys_FS_DELETE_SELF = 0x400 33 | sys_FS_MODIFY = 0x2 34 | sys_FS_MOVE = 0xc0 35 | sys_FS_MOVED_FROM = 0x40 36 | sys_FS_MOVED_TO = 0x80 37 | sys_FS_MOVE_SELF = 0x800 38 | 39 | // Special events 40 | sys_FS_IGNORED = 0x8000 41 | sys_FS_Q_OVERFLOW = 0x4000 42 | ) 43 | 44 | const ( 45 | // TODO(nj): Use syscall.ERROR_MORE_DATA from ztypes_windows in Go 1.3+ 46 | sys_ERROR_MORE_DATA syscall.Errno = 234 47 | ) 48 | 49 | // Event is the type of the notification messages 50 | // received on the watcher's Event channel. 51 | type FileEvent struct { 52 | mask uint32 // Mask of events 53 | cookie uint32 // Unique cookie associating related events (for rename) 54 | Name string // File name (optional) 55 | } 56 | 57 | // IsCreate reports whether the FileEvent was triggered by a creation 58 | func (e *FileEvent) IsCreate() bool { return (e.mask & sys_FS_CREATE) == sys_FS_CREATE } 59 | 60 | // IsDelete reports whether the FileEvent was triggered by a delete 61 | func (e *FileEvent) IsDelete() bool { 62 | return ((e.mask&sys_FS_DELETE) == sys_FS_DELETE || (e.mask&sys_FS_DELETE_SELF) == sys_FS_DELETE_SELF) 63 | } 64 | 65 | // IsModify reports whether the FileEvent was triggered by a file modification or attribute change 66 | func (e *FileEvent) IsModify() bool { 67 | return ((e.mask&sys_FS_MODIFY) == sys_FS_MODIFY || (e.mask&sys_FS_ATTRIB) == sys_FS_ATTRIB) 68 | } 69 | 70 | // IsRename reports whether the FileEvent was triggered by a change name 71 | func (e *FileEvent) IsRename() bool { 72 | return ((e.mask&sys_FS_MOVE) == sys_FS_MOVE || (e.mask&sys_FS_MOVE_SELF) == sys_FS_MOVE_SELF || (e.mask&sys_FS_MOVED_FROM) == sys_FS_MOVED_FROM || (e.mask&sys_FS_MOVED_TO) == sys_FS_MOVED_TO) 73 | } 74 | 75 | // IsAttrib reports whether the FileEvent was triggered by a change in the file metadata. 76 | func (e *FileEvent) IsAttrib() bool { 77 | return (e.mask & sys_FS_ATTRIB) == sys_FS_ATTRIB 78 | } 79 | 80 | const ( 81 | opAddWatch = iota 82 | opRemoveWatch 83 | ) 84 | 85 | const ( 86 | provisional uint64 = 1 << (32 + iota) 87 | ) 88 | 89 | type input struct { 90 | op int 91 | path string 92 | flags uint32 93 | reply chan error 94 | } 95 | 96 | type inode struct { 97 | handle syscall.Handle 98 | volume uint32 99 | index uint64 100 | } 101 | 102 | type watch struct { 103 | ov syscall.Overlapped 104 | ino *inode // i-number 105 | path string // Directory path 106 | mask uint64 // Directory itself is being watched with these notify flags 107 | names map[string]uint64 // Map of names being watched and their notify flags 108 | rename string // Remembers the old name while renaming a file 109 | buf [4096]byte 110 | } 111 | 112 | type indexMap map[uint64]*watch 113 | type watchMap map[uint32]indexMap 114 | 115 | // A Watcher waits for and receives event notifications 116 | // for a specific set of files and directories. 117 | type Watcher struct { 118 | mu sync.Mutex // Map access 119 | port syscall.Handle // Handle to completion port 120 | watches watchMap // Map of watches (key: i-number) 121 | fsnFlags map[string]uint32 // Map of watched files to flags used for filter 122 | fsnmut sync.Mutex // Protects access to fsnFlags. 123 | input chan *input // Inputs to the reader are sent on this channel 124 | internalEvent chan *FileEvent // Events are queued on this channel 125 | Event chan *FileEvent // Events are returned on this channel 126 | Error chan error // Errors are sent on this channel 127 | isClosed bool // Set to true when Close() is first called 128 | quit chan chan<- error 129 | cookie uint32 130 | } 131 | 132 | // NewWatcher creates and returns a Watcher. 133 | func NewWatcher() (*Watcher, error) { 134 | port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0) 135 | if e != nil { 136 | return nil, os.NewSyscallError("CreateIoCompletionPort", e) 137 | } 138 | w := &Watcher{ 139 | port: port, 140 | watches: make(watchMap), 141 | fsnFlags: make(map[string]uint32), 142 | input: make(chan *input, 1), 143 | Event: make(chan *FileEvent, 50), 144 | internalEvent: make(chan *FileEvent), 145 | Error: make(chan error), 146 | quit: make(chan chan<- error, 1), 147 | } 148 | go w.readEvents() 149 | go w.purgeEvents() 150 | return w, nil 151 | } 152 | 153 | // Close closes a Watcher. 154 | // It sends a message to the reader goroutine to quit and removes all watches 155 | // associated with the watcher. 156 | func (w *Watcher) Close() error { 157 | if w.isClosed { 158 | return nil 159 | } 160 | w.isClosed = true 161 | 162 | // Send "quit" message to the reader goroutine 163 | ch := make(chan error) 164 | w.quit <- ch 165 | if err := w.wakeupReader(); err != nil { 166 | return err 167 | } 168 | return <-ch 169 | } 170 | 171 | // AddWatch adds path to the watched file set. 172 | func (w *Watcher) AddWatch(path string, flags uint32) error { 173 | if w.isClosed { 174 | return errors.New("watcher already closed") 175 | } 176 | in := &input{ 177 | op: opAddWatch, 178 | path: filepath.Clean(path), 179 | flags: flags, 180 | reply: make(chan error), 181 | } 182 | w.input <- in 183 | if err := w.wakeupReader(); err != nil { 184 | return err 185 | } 186 | return <-in.reply 187 | } 188 | 189 | // Watch adds path to the watched file set, watching all events. 190 | func (w *Watcher) watch(path string) error { 191 | return w.AddWatch(path, sys_FS_ALL_EVENTS) 192 | } 193 | 194 | // RemoveWatch removes path from the watched file set. 195 | func (w *Watcher) removeWatch(path string) error { 196 | in := &input{ 197 | op: opRemoveWatch, 198 | path: filepath.Clean(path), 199 | reply: make(chan error), 200 | } 201 | w.input <- in 202 | if err := w.wakeupReader(); err != nil { 203 | return err 204 | } 205 | return <-in.reply 206 | } 207 | 208 | func (w *Watcher) wakeupReader() error { 209 | e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil) 210 | if e != nil { 211 | return os.NewSyscallError("PostQueuedCompletionStatus", e) 212 | } 213 | return nil 214 | } 215 | 216 | func getDir(pathname string) (dir string, err error) { 217 | attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname)) 218 | if e != nil { 219 | return "", os.NewSyscallError("GetFileAttributes", e) 220 | } 221 | if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { 222 | dir = pathname 223 | } else { 224 | dir, _ = filepath.Split(pathname) 225 | dir = filepath.Clean(dir) 226 | } 227 | return 228 | } 229 | 230 | func getIno(path string) (ino *inode, err error) { 231 | h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path), 232 | syscall.FILE_LIST_DIRECTORY, 233 | syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 234 | nil, syscall.OPEN_EXISTING, 235 | syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0) 236 | if e != nil { 237 | return nil, os.NewSyscallError("CreateFile", e) 238 | } 239 | var fi syscall.ByHandleFileInformation 240 | if e = syscall.GetFileInformationByHandle(h, &fi); e != nil { 241 | syscall.CloseHandle(h) 242 | return nil, os.NewSyscallError("GetFileInformationByHandle", e) 243 | } 244 | ino = &inode{ 245 | handle: h, 246 | volume: fi.VolumeSerialNumber, 247 | index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow), 248 | } 249 | return ino, nil 250 | } 251 | 252 | // Must run within the I/O thread. 253 | func (m watchMap) get(ino *inode) *watch { 254 | if i := m[ino.volume]; i != nil { 255 | return i[ino.index] 256 | } 257 | return nil 258 | } 259 | 260 | // Must run within the I/O thread. 261 | func (m watchMap) set(ino *inode, watch *watch) { 262 | i := m[ino.volume] 263 | if i == nil { 264 | i = make(indexMap) 265 | m[ino.volume] = i 266 | } 267 | i[ino.index] = watch 268 | } 269 | 270 | // Must run within the I/O thread. 271 | func (w *Watcher) addWatch(pathname string, flags uint64) error { 272 | dir, err := getDir(pathname) 273 | if err != nil { 274 | return err 275 | } 276 | if flags&sys_FS_ONLYDIR != 0 && pathname != dir { 277 | return nil 278 | } 279 | ino, err := getIno(dir) 280 | if err != nil { 281 | return err 282 | } 283 | w.mu.Lock() 284 | watchEntry := w.watches.get(ino) 285 | w.mu.Unlock() 286 | if watchEntry == nil { 287 | if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil { 288 | syscall.CloseHandle(ino.handle) 289 | return os.NewSyscallError("CreateIoCompletionPort", e) 290 | } 291 | watchEntry = &watch{ 292 | ino: ino, 293 | path: dir, 294 | names: make(map[string]uint64), 295 | } 296 | w.mu.Lock() 297 | w.watches.set(ino, watchEntry) 298 | w.mu.Unlock() 299 | flags |= provisional 300 | } else { 301 | syscall.CloseHandle(ino.handle) 302 | } 303 | if pathname == dir { 304 | watchEntry.mask |= flags 305 | } else { 306 | watchEntry.names[filepath.Base(pathname)] |= flags 307 | } 308 | if err = w.startRead(watchEntry); err != nil { 309 | return err 310 | } 311 | if pathname == dir { 312 | watchEntry.mask &= ^provisional 313 | } else { 314 | watchEntry.names[filepath.Base(pathname)] &= ^provisional 315 | } 316 | return nil 317 | } 318 | 319 | // Must run within the I/O thread. 320 | func (w *Watcher) remWatch(pathname string) error { 321 | dir, err := getDir(pathname) 322 | if err != nil { 323 | return err 324 | } 325 | ino, err := getIno(dir) 326 | if err != nil { 327 | return err 328 | } 329 | w.mu.Lock() 330 | watch := w.watches.get(ino) 331 | w.mu.Unlock() 332 | if watch == nil { 333 | return fmt.Errorf("can't remove non-existent watch for: %s", pathname) 334 | } 335 | if pathname == dir { 336 | w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED) 337 | watch.mask = 0 338 | } else { 339 | name := filepath.Base(pathname) 340 | w.sendEvent(watch.path+"\\"+name, watch.names[name]&sys_FS_IGNORED) 341 | delete(watch.names, name) 342 | } 343 | return w.startRead(watch) 344 | } 345 | 346 | // Must run within the I/O thread. 347 | func (w *Watcher) deleteWatch(watch *watch) { 348 | for name, mask := range watch.names { 349 | if mask&provisional == 0 { 350 | w.sendEvent(watch.path+"\\"+name, mask&sys_FS_IGNORED) 351 | } 352 | delete(watch.names, name) 353 | } 354 | if watch.mask != 0 { 355 | if watch.mask&provisional == 0 { 356 | w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED) 357 | } 358 | watch.mask = 0 359 | } 360 | } 361 | 362 | // Must run within the I/O thread. 363 | func (w *Watcher) startRead(watch *watch) error { 364 | if e := syscall.CancelIo(watch.ino.handle); e != nil { 365 | w.Error <- os.NewSyscallError("CancelIo", e) 366 | w.deleteWatch(watch) 367 | } 368 | mask := toWindowsFlags(watch.mask) 369 | for _, m := range watch.names { 370 | mask |= toWindowsFlags(m) 371 | } 372 | if mask == 0 { 373 | if e := syscall.CloseHandle(watch.ino.handle); e != nil { 374 | w.Error <- os.NewSyscallError("CloseHandle", e) 375 | } 376 | w.mu.Lock() 377 | delete(w.watches[watch.ino.volume], watch.ino.index) 378 | w.mu.Unlock() 379 | return nil 380 | } 381 | e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0], 382 | uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0) 383 | if e != nil { 384 | err := os.NewSyscallError("ReadDirectoryChanges", e) 385 | if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 { 386 | // Watched directory was probably removed 387 | if w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF) { 388 | if watch.mask&sys_FS_ONESHOT != 0 { 389 | watch.mask = 0 390 | } 391 | } 392 | err = nil 393 | } 394 | w.deleteWatch(watch) 395 | w.startRead(watch) 396 | return err 397 | } 398 | return nil 399 | } 400 | 401 | // readEvents reads from the I/O completion port, converts the 402 | // received events into Event objects and sends them via the Event channel. 403 | // Entry point to the I/O thread. 404 | func (w *Watcher) readEvents() { 405 | var ( 406 | n, key uint32 407 | ov *syscall.Overlapped 408 | ) 409 | runtime.LockOSThread() 410 | 411 | for { 412 | e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE) 413 | watch := (*watch)(unsafe.Pointer(ov)) 414 | 415 | if watch == nil { 416 | select { 417 | case ch := <-w.quit: 418 | w.mu.Lock() 419 | var indexes []indexMap 420 | for _, index := range w.watches { 421 | indexes = append(indexes, index) 422 | } 423 | w.mu.Unlock() 424 | for _, index := range indexes { 425 | for _, watch := range index { 426 | w.deleteWatch(watch) 427 | w.startRead(watch) 428 | } 429 | } 430 | var err error 431 | if e := syscall.CloseHandle(w.port); e != nil { 432 | err = os.NewSyscallError("CloseHandle", e) 433 | } 434 | close(w.internalEvent) 435 | close(w.Error) 436 | ch <- err 437 | return 438 | case in := <-w.input: 439 | switch in.op { 440 | case opAddWatch: 441 | in.reply <- w.addWatch(in.path, uint64(in.flags)) 442 | case opRemoveWatch: 443 | in.reply <- w.remWatch(in.path) 444 | } 445 | default: 446 | } 447 | continue 448 | } 449 | 450 | switch e { 451 | case sys_ERROR_MORE_DATA: 452 | if watch == nil { 453 | w.Error <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer") 454 | } else { 455 | // The i/o succeeded but the buffer is full. 456 | // In theory we should be building up a full packet. 457 | // In practice we can get away with just carrying on. 458 | n = uint32(unsafe.Sizeof(watch.buf)) 459 | } 460 | case syscall.ERROR_ACCESS_DENIED: 461 | // Watched directory was probably removed 462 | w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF) 463 | w.deleteWatch(watch) 464 | w.startRead(watch) 465 | continue 466 | case syscall.ERROR_OPERATION_ABORTED: 467 | // CancelIo was called on this handle 468 | continue 469 | default: 470 | w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e) 471 | continue 472 | case nil: 473 | } 474 | 475 | var offset uint32 476 | for { 477 | if n == 0 { 478 | w.internalEvent <- &FileEvent{mask: sys_FS_Q_OVERFLOW} 479 | w.Error <- errors.New("short read in readEvents()") 480 | break 481 | } 482 | 483 | // Point "raw" to the event in the buffer 484 | raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset])) 485 | buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName)) 486 | name := syscall.UTF16ToString(buf[:raw.FileNameLength/2]) 487 | fullname := watch.path + "\\" + name 488 | 489 | var mask uint64 490 | switch raw.Action { 491 | case syscall.FILE_ACTION_REMOVED: 492 | mask = sys_FS_DELETE_SELF 493 | case syscall.FILE_ACTION_MODIFIED: 494 | mask = sys_FS_MODIFY 495 | case syscall.FILE_ACTION_RENAMED_OLD_NAME: 496 | watch.rename = name 497 | case syscall.FILE_ACTION_RENAMED_NEW_NAME: 498 | if watch.names[watch.rename] != 0 { 499 | watch.names[name] |= watch.names[watch.rename] 500 | delete(watch.names, watch.rename) 501 | mask = sys_FS_MOVE_SELF 502 | } 503 | } 504 | 505 | sendNameEvent := func() { 506 | if w.sendEvent(fullname, watch.names[name]&mask) { 507 | if watch.names[name]&sys_FS_ONESHOT != 0 { 508 | delete(watch.names, name) 509 | } 510 | } 511 | } 512 | if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME { 513 | sendNameEvent() 514 | } 515 | if raw.Action == syscall.FILE_ACTION_REMOVED { 516 | w.sendEvent(fullname, watch.names[name]&sys_FS_IGNORED) 517 | delete(watch.names, name) 518 | } 519 | if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) { 520 | if watch.mask&sys_FS_ONESHOT != 0 { 521 | watch.mask = 0 522 | } 523 | } 524 | if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME { 525 | fullname = watch.path + "\\" + watch.rename 526 | sendNameEvent() 527 | } 528 | 529 | // Move to the next event in the buffer 530 | if raw.NextEntryOffset == 0 { 531 | break 532 | } 533 | offset += raw.NextEntryOffset 534 | 535 | // Error! 536 | if offset >= n { 537 | w.Error <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.") 538 | break 539 | } 540 | } 541 | 542 | if err := w.startRead(watch); err != nil { 543 | w.Error <- err 544 | } 545 | } 546 | } 547 | 548 | func (w *Watcher) sendEvent(name string, mask uint64) bool { 549 | if mask == 0 { 550 | return false 551 | } 552 | event := &FileEvent{mask: uint32(mask), Name: name} 553 | if mask&sys_FS_MOVE != 0 { 554 | if mask&sys_FS_MOVED_FROM != 0 { 555 | w.cookie++ 556 | } 557 | event.cookie = w.cookie 558 | } 559 | select { 560 | case ch := <-w.quit: 561 | w.quit <- ch 562 | case w.Event <- event: 563 | } 564 | return true 565 | } 566 | 567 | func toWindowsFlags(mask uint64) uint32 { 568 | var m uint32 569 | if mask&sys_FS_ACCESS != 0 { 570 | m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS 571 | } 572 | if mask&sys_FS_MODIFY != 0 { 573 | m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE 574 | } 575 | if mask&sys_FS_ATTRIB != 0 { 576 | m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES 577 | } 578 | if mask&(sys_FS_MOVE|sys_FS_CREATE|sys_FS_DELETE) != 0 { 579 | m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME 580 | } 581 | return m 582 | } 583 | 584 | func toFSnotifyFlags(action uint32) uint64 { 585 | switch action { 586 | case syscall.FILE_ACTION_ADDED: 587 | return sys_FS_CREATE 588 | case syscall.FILE_ACTION_REMOVED: 589 | return sys_FS_DELETE 590 | case syscall.FILE_ACTION_MODIFIED: 591 | return sys_FS_MODIFY 592 | case syscall.FILE_ACTION_RENAMED_OLD_NAME: 593 | return sys_FS_MOVED_FROM 594 | case syscall.FILE_ACTION_RENAMED_NEW_NAME: 595 | return sys_FS_MOVED_TO 596 | } 597 | return 0 598 | } 599 | -------------------------------------------------------------------------------- /less.go: -------------------------------------------------------------------------------- 1 | // Lessgo 部署工具,支持新建项目,支持热编译并运行程序。 2 | package main 3 | 4 | func main() { 5 | Deploy() 6 | } 7 | -------------------------------------------------------------------------------- /run.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 bee authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "io/ioutil" 20 | "os" 21 | path "path/filepath" 22 | "runtime" 23 | "strings" 24 | ) 25 | 26 | var cmdRun = &Command{ 27 | UsageLine: "run [appname] [watchall] [-main=*.go]", 28 | Short: "run the app and start a Web server for development", 29 | Long: ` 30 | Run command will supervise the file system of the lessgo project using inotify, 31 | it will recompile and restart the app after any modifications. 32 | 33 | `, 34 | } 35 | 36 | type ListOpts []string 37 | 38 | func (opts *ListOpts) String() string { 39 | return fmt.Sprint(*opts) 40 | } 41 | 42 | func (opts *ListOpts) Set(value string) error { 43 | *opts = append(*opts, value) 44 | return nil 45 | } 46 | 47 | type docValue string 48 | 49 | func (d *docValue) String() string { 50 | return fmt.Sprint(*d) 51 | } 52 | 53 | func (d *docValue) Set(value string) error { 54 | *d = docValue(value) 55 | return nil 56 | } 57 | 58 | var mainFiles ListOpts 59 | 60 | // The flags list of the paths excluded from watching 61 | var excludedPaths strFlags 62 | 63 | func init() { 64 | cmdRun.Run = runApp 65 | cmdRun.Flag.Var(&mainFiles, "main", "specify main go files") 66 | cmdRun.Flag.Var(&excludedPaths, "e", "Excluded paths[].") 67 | } 68 | 69 | var appname string 70 | 71 | func runApp(cmd *Command, args []string) int { 72 | exit := make(chan bool) 73 | crupath, _ := os.Getwd() 74 | 75 | if len(args) == 0 || args[0] == "watchall" { 76 | appname = path.Base(crupath) 77 | ColorLog("[INFO] Uses '%s' as 'appname'\n", appname) 78 | } else { 79 | appname = args[0] 80 | ColorLog("[INFO] Uses '%s' as 'appname'\n", appname) 81 | if strings.HasSuffix(appname, ".go") && isExist(path.Join(crupath, appname)) { 82 | ColorLog("[WARN] The appname has conflic with crupath's file, do you want to build appname as %s\n", appname) 83 | ColorLog("[INFO] Do you want to overwrite it? [yes|no]] ") 84 | if !askForConfirmation() { 85 | return 0 86 | } 87 | } 88 | } 89 | Debugf("current path:%s\n", crupath) 90 | 91 | var paths []string 92 | 93 | readAppDirectories(crupath, &paths) 94 | 95 | // Because monitor files has some issues, we watch current directory 96 | // and ignore non-go files. 97 | gps := GetGOPATHs() 98 | if len(gps) == 0 { 99 | ColorLog("[ERRO] Fail to start[ %s ]\n", "$GOPATH is not set or empty") 100 | os.Exit(2) 101 | } 102 | gopath := gps[0] 103 | for _, p := range conf.DirStruct.Others { 104 | paths = append(paths, strings.Replace(p, "$GOPATH", gopath, -1)) 105 | } 106 | 107 | files := []string{} 108 | for _, arg := range mainFiles { 109 | if len(arg) > 0 { 110 | files = append(files, arg) 111 | } 112 | } 113 | 114 | NewWatcher(paths, files, false) 115 | Autobuild(files, false) 116 | 117 | for { 118 | select { 119 | case <-exit: 120 | runtime.Goexit() 121 | } 122 | } 123 | return 0 124 | } 125 | 126 | func readAppDirectories(directory string, paths *[]string) { 127 | fileInfos, err := ioutil.ReadDir(directory) 128 | if err != nil { 129 | return 130 | } 131 | 132 | useDirectory := false 133 | for _, fileInfo := range fileInfos { 134 | if strings.HasSuffix(fileInfo.Name(), "docs") { 135 | continue 136 | } 137 | 138 | if isExcluded(fileInfo) { 139 | continue 140 | } 141 | 142 | if fileInfo.IsDir() == true && fileInfo.Name()[0] != '.' { 143 | readAppDirectories(directory+"/"+fileInfo.Name(), paths) 144 | continue 145 | } 146 | 147 | if useDirectory == true { 148 | continue 149 | } 150 | 151 | if path.Ext(fileInfo.Name()) == ".go" { 152 | *paths = append(*paths, directory) 153 | useDirectory = true 154 | } 155 | } 156 | 157 | return 158 | } 159 | 160 | // If a file is excluded 161 | func isExcluded(fileInfo os.FileInfo) bool { 162 | for _, p := range excludedPaths { 163 | if strings.HasSuffix(fileInfo.Name(), p) { 164 | ColorLog("[INFO] Excluding from watching [ %s ]\n", fileInfo.Name()) 165 | return true 166 | } 167 | } 168 | return false 169 | } 170 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 bee authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | "log" 20 | "os" 21 | "path/filepath" 22 | "runtime" 23 | "strings" 24 | "time" 25 | ) 26 | 27 | // Go is a basic promise implementation: it wraps calls a function in a goroutine 28 | // and returns a channel which will later return the function's return value. 29 | func Go(f func() error) chan error { 30 | ch := make(chan error) 31 | go func() { 32 | ch <- f() 33 | }() 34 | return ch 35 | } 36 | 37 | // if os.env DEBUG set, debug is on 38 | func Debugf(format string, a ...interface{}) { 39 | if os.Getenv("DEBUG") != "" { 40 | _, file, line, ok := runtime.Caller(1) 41 | if !ok { 42 | file = "" 43 | line = -1 44 | } else { 45 | file = filepath.Base(file) 46 | } 47 | fmt.Fprintf(os.Stderr, fmt.Sprintf("[debug] %s:%d %s\n", file, line, format), a...) 48 | } 49 | } 50 | 51 | const ( 52 | Gray = uint8(iota + 90) 53 | Red 54 | Green 55 | Yellow 56 | Blue 57 | Magenta 58 | //NRed = uint8(31) // Normal 59 | EndColor = "\033[0m" 60 | 61 | INFO = "INFO" 62 | TRAC = "TRAC" 63 | ERRO = "ERRO" 64 | WARN = "WARN" 65 | SUCC = "SUCC" 66 | ) 67 | 68 | // ColorLog colors log and print to stdout. 69 | // See color rules in function 'ColorLogS'. 70 | func ColorLog(format string, a ...interface{}) { 71 | fmt.Print(ColorLogS(format, a...)) 72 | } 73 | 74 | // ColorLogS colors log and return colored content. 75 | // Log format: [ error ]. 76 | // Level: TRAC -> blue; ERRO -> red; WARN -> Magenta; SUCC -> green; others -> default. 77 | // Content: default; path: yellow; error -> red. 78 | // Level has to be surrounded by "[" and "]". 79 | // Highlights have to be surrounded by "# " and " #"(space), "#" will be deleted. 80 | // Paths have to be surrounded by "( " and " )"(space). 81 | // Errors have to be surrounded by "[ " and " ]"(space). 82 | // Note: it hasn't support windows yet, contribute is welcome. 83 | func ColorLogS(format string, a ...interface{}) string { 84 | log := fmt.Sprintf(format, a...) 85 | 86 | var clog string 87 | 88 | if runtime.GOOS != "windows" { 89 | // Level. 90 | i := strings.Index(log, "]") 91 | if log[0] == '[' && i > -1 { 92 | clog += "[" + getColorLevel(log[1:i]) + "]" 93 | } 94 | 95 | log = log[i+1:] 96 | 97 | // Error. 98 | log = strings.Replace(log, "[ ", fmt.Sprintf("[\033[%dm", Red), -1) 99 | log = strings.Replace(log, " ]", EndColor+"]", -1) 100 | 101 | // Path. 102 | log = strings.Replace(log, "( ", fmt.Sprintf("(\033[%dm", Yellow), -1) 103 | log = strings.Replace(log, " )", EndColor+")", -1) 104 | 105 | // Highlights. 106 | log = strings.Replace(log, "# ", fmt.Sprintf("\033[%dm", Gray), -1) 107 | log = strings.Replace(log, " #", EndColor, -1) 108 | 109 | log = clog + log 110 | 111 | } else { 112 | // Level. 113 | i := strings.Index(log, "]") 114 | if log[0] == '[' && i > -1 { 115 | clog += "[" + log[1:i] + "]" 116 | } 117 | 118 | log = log[i+1:] 119 | 120 | // Error. 121 | log = strings.Replace(log, "[ ", "[", -1) 122 | log = strings.Replace(log, " ]", "]", -1) 123 | 124 | // Path. 125 | log = strings.Replace(log, "( ", "(", -1) 126 | log = strings.Replace(log, " )", ")", -1) 127 | 128 | // Highlights. 129 | log = strings.Replace(log, "# ", "", -1) 130 | log = strings.Replace(log, " #", "", -1) 131 | 132 | log = clog + log 133 | } 134 | 135 | return time.Now().Format("2006/01/02 15:04:05 ") + log 136 | } 137 | 138 | // getColorLevel returns colored level string by given level. 139 | func getColorLevel(level string) string { 140 | level = strings.ToUpper(level) 141 | switch level { 142 | case INFO: 143 | return fmt.Sprintf("\033[%dm%s\033[0m", Blue, level) 144 | case TRAC: 145 | return fmt.Sprintf("\033[%dm%s\033[0m", Blue, level) 146 | case ERRO: 147 | return fmt.Sprintf("\033[%dm%s\033[0m", Red, level) 148 | case WARN: 149 | return fmt.Sprintf("\033[%dm%s\033[0m", Magenta, level) 150 | case SUCC: 151 | return fmt.Sprintf("\033[%dm%s\033[0m", Green, level) 152 | default: 153 | return level 154 | } 155 | return level 156 | } 157 | 158 | // IsExist returns whether a file or directory exists. 159 | func isExist(path string) bool { 160 | _, err := os.Stat(path) 161 | return err == nil || os.IsExist(err) 162 | } 163 | 164 | // GetGOPATHs returns all paths in GOPATH variable. 165 | func GetGOPATHs() []string { 166 | gopath := os.Getenv("GOPATH") 167 | var paths []string 168 | if runtime.GOOS == "windows" { 169 | gopath = strings.Replace(gopath, "\\", "/", -1) 170 | paths = strings.Split(gopath, ";") 171 | } else { 172 | paths = strings.Split(gopath, ":") 173 | } 174 | return paths 175 | } 176 | 177 | // askForConfirmation uses Scanln to parse user input. A user must type in "yes" or "no" and 178 | // then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as 179 | // confirmations. If the input is not recognized, it will ask again. The function does not return 180 | // until it gets a valid response from the user. Typically, you should use fmt to print out a question 181 | // before calling askForConfirmation. E.g. fmt.Println("WARNING: Are you sure? (yes/no)") 182 | func askForConfirmation() bool { 183 | var response string 184 | _, err := fmt.Scanln(&response) 185 | if err != nil { 186 | log.Fatal(err) 187 | } 188 | okayResponses := []string{"y", "Y", "yes", "Yes", "YES"} 189 | nokayResponses := []string{"n", "N", "no", "No", "NO"} 190 | if containsString(okayResponses, response) { 191 | return true 192 | } else if containsString(nokayResponses, response) { 193 | return false 194 | } else { 195 | fmt.Println("Please type yes or no and then press enter:") 196 | return askForConfirmation() 197 | } 198 | } 199 | 200 | func containsString(slice []string, element string) bool { 201 | for _, elem := range slice { 202 | if elem == element { 203 | return true 204 | } 205 | } 206 | return false 207 | } 208 | 209 | // snake string, XxYy to xx_yy 210 | func snakeString(s string) string { 211 | data := make([]byte, 0, len(s)*2) 212 | j := false 213 | num := len(s) 214 | for i := 0; i < num; i++ { 215 | d := s[i] 216 | if i > 0 && d >= 'A' && d <= 'Z' && j { 217 | data = append(data, '_') 218 | } 219 | if d != '_' { 220 | j = true 221 | } 222 | data = append(data, d) 223 | } 224 | return strings.ToLower(string(data[:len(data)])) 225 | } 226 | 227 | func camelString(s string) string { 228 | data := make([]byte, 0, len(s)) 229 | j := false 230 | k := false 231 | num := len(s) - 1 232 | for i := 0; i <= num; i++ { 233 | d := s[i] 234 | if k == false && d >= 'A' && d <= 'Z' { 235 | k = true 236 | } 237 | if d >= 'a' && d <= 'z' && (j || k == false) { 238 | d = d - 32 239 | j = false 240 | k = true 241 | } 242 | if k && d == '_' && num > i && s[i+1] >= 'a' && s[i+1] <= 'z' { 243 | j = true 244 | continue 245 | } 246 | data = append(data, d) 247 | } 248 | return string(data[:len(data)]) 249 | } 250 | 251 | // The string flag list, implemented flag.Value interface 252 | type strFlags []string 253 | 254 | func (s *strFlags) String() string { 255 | return fmt.Sprintf("%d", *s) 256 | } 257 | 258 | func (s *strFlags) Set(value string) error { 259 | *s = append(*s, value) 260 | return nil 261 | } 262 | -------------------------------------------------------------------------------- /watch.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 bee authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"): you may 4 | // not use this file except in compliance with the License. You may obtain 5 | // a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations 13 | // under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "bytes" 19 | "fmt" 20 | "os" 21 | "os/exec" 22 | "runtime" 23 | "strings" 24 | "sync" 25 | "time" 26 | 27 | "github.com/henrylee2cn/less/fsnotify" 28 | ) 29 | 30 | var ( 31 | cmd *exec.Cmd 32 | state sync.Mutex 33 | eventTime = make(map[string]int64) 34 | scheduleTime time.Time 35 | ) 36 | var started = make(chan bool) 37 | 38 | func NewWatcher(paths []string, files []string, isgenerate bool) { 39 | watcher, err := fsnotify.NewWatcher() 40 | if err != nil { 41 | ColorLog("[ERRO] Fail to create new Watcher[ %s ]\n", err) 42 | os.Exit(2) 43 | } 44 | 45 | go func() { 46 | for { 47 | select { 48 | case e := <-watcher.Event: 49 | isbuild := true 50 | 51 | // Skip TMP files for Sublime Text. 52 | if checkTMPFile(e.Name) { 53 | continue 54 | } 55 | if !checkIfWatchExt(e.Name) { 56 | continue 57 | } 58 | 59 | mt := getFileModTime(e.Name) 60 | if t := eventTime[e.Name]; mt == t { 61 | ColorLog("[SKIP] # %s #\n", e.String()) 62 | isbuild = false 63 | } 64 | 65 | eventTime[e.Name] = mt 66 | 67 | if isbuild { 68 | ColorLog("[EVEN] %s\n", e) 69 | go func() { 70 | // Wait 1s before autobuild util there is no file change. 71 | scheduleTime = time.Now().Add(1 * time.Second) 72 | for { 73 | time.Sleep(scheduleTime.Sub(time.Now())) 74 | if time.Now().After(scheduleTime) { 75 | break 76 | } 77 | return 78 | } 79 | 80 | Autobuild(files, isgenerate) 81 | }() 82 | } 83 | case err := <-watcher.Error: 84 | ColorLog("[WARN] %s\n", err.Error()) // No need to exit here 85 | } 86 | } 87 | }() 88 | 89 | ColorLog("[INFO] Initializing watcher...\n") 90 | for _, path := range paths { 91 | ColorLog("[TRAC] Directory( %s )\n", path) 92 | err = watcher.Watch(path) 93 | if err != nil { 94 | ColorLog("[ERRO] Fail to watch directory[ %s ]\n", err) 95 | os.Exit(2) 96 | } 97 | } 98 | 99 | } 100 | 101 | // getFileModTime retuens unix timestamp of `os.File.ModTime` by given path. 102 | func getFileModTime(path string) int64 { 103 | path = strings.Replace(path, "\\", "/", -1) 104 | f, err := os.Open(path) 105 | if err != nil { 106 | ColorLog("[ERRO] Fail to open file[ %s ]\n", err) 107 | return time.Now().Unix() 108 | } 109 | defer f.Close() 110 | 111 | fi, err := f.Stat() 112 | if err != nil { 113 | ColorLog("[ERRO] Fail to get file information[ %s ]\n", err) 114 | return time.Now().Unix() 115 | } 116 | 117 | return fi.ModTime().Unix() 118 | } 119 | 120 | func Autobuild(files []string, isgenerate bool) { 121 | state.Lock() 122 | defer state.Unlock() 123 | 124 | ColorLog("[INFO] Start building...\n") 125 | path, _ := os.Getwd() 126 | os.Chdir(path) 127 | 128 | cmdName := "go" 129 | if conf.Gopm.Enable { 130 | cmdName = "gopm" 131 | } 132 | 133 | var err error 134 | // For applications use full import path like "github.com/.../.." 135 | // are able to use "go install" to reduce build time. 136 | if conf.GoInstall || conf.Gopm.Install { 137 | icmd := exec.Command("go", "list", "./...") 138 | buf := bytes.NewBuffer([]byte("")) 139 | icmd.Stdout = buf 140 | err = icmd.Run() 141 | if err == nil { 142 | list := strings.Split(buf.String(), "\n")[1:] 143 | for _, pkg := range list { 144 | if len(pkg) == 0 { 145 | continue 146 | } 147 | icmd = exec.Command(cmdName, "install", pkg) 148 | icmd.Stdout = os.Stdout 149 | icmd.Stderr = os.Stderr 150 | err = icmd.Run() 151 | if err != nil { 152 | break 153 | } 154 | } 155 | } 156 | } 157 | 158 | if isgenerate { 159 | icmd := exec.Command("less", "generate", "docs") 160 | icmd.Stdout = os.Stdout 161 | icmd.Stderr = os.Stderr 162 | icmd.Run() 163 | ColorLog("============== generate docs ===================\n") 164 | } 165 | 166 | if err == nil { 167 | appName := appname 168 | if runtime.GOOS == "windows" { 169 | appName += ".exe" 170 | } 171 | 172 | args := []string{"build"} 173 | args = append(args, "-o", appName) 174 | args = append(args, files...) 175 | 176 | bcmd := exec.Command(cmdName, args...) 177 | bcmd.Stdout = os.Stdout 178 | bcmd.Stderr = os.Stderr 179 | err = bcmd.Run() 180 | } 181 | 182 | if err != nil { 183 | ColorLog("[ERRO] ============== Build failed ===================\n") 184 | return 185 | } 186 | ColorLog("[SUCC] Build was successful\n") 187 | Restart(appname) 188 | } 189 | 190 | func Kill() { 191 | defer func() { 192 | if e := recover(); e != nil { 193 | fmt.Println("Kill.recover -> ", e) 194 | } 195 | }() 196 | if cmd != nil && cmd.Process != nil { 197 | err := cmd.Process.Kill() 198 | if err != nil { 199 | fmt.Println("Kill -> ", err) 200 | } 201 | } 202 | } 203 | 204 | func Restart(appname string) { 205 | Debugf("kill running process") 206 | Kill() 207 | go Start(appname) 208 | } 209 | 210 | func Start(appname string) { 211 | ColorLog("[INFO] Restarting %s ...\n", appname) 212 | if strings.Index(appname, "./") == -1 { 213 | appname = "./" + appname 214 | } 215 | 216 | cmd = exec.Command(appname) 217 | cmd.Stdout = os.Stdout 218 | cmd.Stderr = os.Stderr 219 | cmd.Args = append([]string{appname}, conf.CmdArgs...) 220 | cmd.Env = append(os.Environ(), conf.Envs...) 221 | 222 | go cmd.Run() 223 | ColorLog("[INFO] %s is running...\n", appname) 224 | started <- true 225 | } 226 | 227 | // checkTMPFile returns true if the event was for TMP files. 228 | func checkTMPFile(name string) bool { 229 | if strings.HasSuffix(strings.ToLower(name), ".tmp") { 230 | return true 231 | } 232 | return false 233 | } 234 | 235 | var watchExts = []string{".go"} 236 | 237 | // checkIfWatchExt returns true if the name HasSuffix . 238 | func checkIfWatchExt(name string) bool { 239 | for _, s := range watchExts { 240 | if strings.HasSuffix(name, s) { 241 | return true 242 | } 243 | } 244 | return false 245 | } 246 | --------------------------------------------------------------------------------