├── src ├── fsnotify │ ├── .travis.yml │ ├── example_test.go │ ├── README.md │ ├── fsnotify_symlink_test.go │ ├── fsnotify.go │ ├── fsnotify_linux.go │ ├── fsnotify_bsd.go │ ├── fsnotify_windows.go │ └── fsnotify_test.go ├── simplejson │ ├── .travis.yml │ ├── simplejson_test.go │ ├── README.md │ └── simplejson.go ├── autogo │ └── main.go ├── project │ ├── project_linux.go │ ├── project_windows.go │ └── project.go ├── files │ └── files.go └── config │ └── config.go ├── clean.sh ├── clean.bat ├── config ├── conf_example.json └── projects.json ├── .gitignore ├── install.bat ├── install.sh ├── templates ├── make_win.tpl ├── make_linux.tpl └── error.html ├── LICENCE └── README.md /src/fsnotify/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | -------------------------------------------------------------------------------- /src/simplejson/.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | install: 3 | - go get github.com/bmizerany/assert 4 | -------------------------------------------------------------------------------- /clean.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ ! -f clean ]; then 4 | echo 'clean must be run within its container folder' 1>&2 5 | exit 1 6 | fi 7 | 8 | rm -rf bin/* 9 | rm -rf pkg/linux_amd64/* 10 | 11 | echo 'finished' -------------------------------------------------------------------------------- /clean.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | 5 | if exist clean.bat goto ok 6 | echo clean.bat must be run from its folder 7 | goto end 8 | 9 | :ok 10 | 11 | del /q bin\* 12 | del /q pkg\windows_386\* 13 | 14 | :end 15 | echo finished 16 | -------------------------------------------------------------------------------- /config/conf_example.json: -------------------------------------------------------------------------------- 1 | // 配置示例 2 | // 假如,有一个项目名称是studygolang,根目录和autogo在同一个目录下。src下有一个blog文件夹,存放了main包(main.go)。可以如下配置: 3 | [ 4 | { 5 | "name": "test", 6 | "root": "../test", 7 | "go_way": "install", 8 | "main": "blog/main.go" 9 | } 10 | ] -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | -------------------------------------------------------------------------------- /install.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | 5 | if exist install.bat goto ok 6 | echo install.bat must be run from its folder 7 | goto end 8 | 9 | :ok 10 | 11 | set OLDGOPATH=%GOPATH% 12 | set GOPATH=%~dp0; 13 | 14 | gofmt -tabs=false -tabwidth=4 -w src 15 | 16 | go install autogo 17 | 18 | set GOPATH=%OLDGOPATH% 19 | 20 | :end 21 | echo finished 22 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ ! -f install.sh ]; then 4 | echo 'install.sh must be run within its container folder' 1>&2 5 | exit 1 6 | fi 7 | 8 | CURDIR=`pwd` 9 | OLDGOPATH="$GOPATH" 10 | export GOPATH="$CURDIR" 11 | 12 | gofmt -tabs=false -tabwidth=4 -w src 13 | 14 | go install autogo 15 | 16 | export GOPATH="$OLDGOPATH" 17 | 18 | echo 'finished' -------------------------------------------------------------------------------- /templates/make_win.tpl: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | setlocal 4 | 5 | if exist install.bat goto ok 6 | echo install.bat must be run from its folder 7 | goto end 8 | 9 | :ok 10 | 11 | set OLDGOPATH=%GOPATH% 12 | set GOPATH=%~dp0;{{range .Depends}}{{.}};{{end}} 13 | 14 | ::打开代码格式化可能会导致监控两次 15 | ::gofmt -tabs=false -tabwidth=4 -w src 16 | 17 | go {{.GoWay}} {{.Options}} {{.MainFile}} 18 | 19 | set GOPATH=%OLDGOPATH% 20 | 21 | :end 22 | echo finished 23 | -------------------------------------------------------------------------------- /templates/make_linux.tpl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ ! -f install.sh ]; then 4 | echo 'install.sh must be run within its container folder' 1>&2 5 | exit 1 6 | fi 7 | 8 | CURDIR=`pwd` 9 | OLDGOPATH="$GOPATH" 10 | export GOPATH="$CURDIR:{{range .Depends}}{{.}}:{{end}}" 11 | 12 | # 打开代码格式化可能会导致监控两次 13 | # gofmt -tabs=false -tabwidth=4 -w src 14 | 15 | go {{.GoWay}} {{.Options}} {{.MainFile}} 16 | 17 | export GOPATH="$OLDGOPATH" 18 | 19 | echo 'finished' -------------------------------------------------------------------------------- /src/autogo/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 polaris(studygolang.com). All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "config" 9 | "flag" 10 | "runtime" 11 | ) 12 | 13 | var configFile string 14 | 15 | func init() { 16 | runtime.GOMAXPROCS(runtime.NumCPU()) 17 | flag.StringVar(&configFile, "f", "config/projects.json", "配置文件:需要监听哪些工程") 18 | flag.Parse() 19 | } 20 | 21 | func main() { 22 | config.Load(configFile) 23 | config.Watch(configFile) 24 | select {} 25 | } 26 | -------------------------------------------------------------------------------- /src/fsnotify/example_test.go: -------------------------------------------------------------------------------- 1 | package fsnotify_test 2 | 3 | import ( 4 | "github.com/howeyc/fsnotify" 5 | "log" 6 | ) 7 | 8 | func ExampleNewWatcher() { 9 | watcher, err := fsnotify.NewWatcher() 10 | if err != nil { 11 | log.Fatal(err) 12 | } 13 | 14 | go func() { 15 | for { 16 | select { 17 | case ev := <-watcher.Event: 18 | log.Println("event:", ev) 19 | case err := <-watcher.Error: 20 | log.Println("error:", err) 21 | } 22 | } 23 | }() 24 | 25 | err = watcher.Watch("/tmp/foo") 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/project/project_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 polaris(studygolang.com). All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package project 6 | 7 | import ( 8 | "os/exec" 9 | ) 10 | 11 | var ( 12 | makeTplFile = "templates/make_linux.tpl" 13 | installFileName = "install.sh" 14 | binanryFileSuffix = "" 15 | 16 | installCmd = "./" + installFileName // 编译时传给Command的名称 17 | ) 18 | 19 | // Stop 停止该Project 20 | func (this *Project) Stop() error { 21 | cmd := exec.Command("killall", this.MainFile) 22 | if err := cmd.Run(); err != nil { 23 | return err 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /src/project/project_windows.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 polaris(studygolang.com). All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package project 6 | 7 | import ( 8 | "os/exec" 9 | ) 10 | 11 | var ( 12 | makeTplFile = "templates/make_win.tpl" 13 | installFileName = "install.bat" 14 | binanryFileSuffix = ".exe" 15 | 16 | installCmd = installFileName // 编译时传给Command的名称 17 | ) 18 | 19 | // Stop 停止该Project 20 | func (this *Project) Stop() error { 21 | cmd := exec.Command("taskkill", "/F", "/IM", this.MainFile+binanryFileSuffix) 22 | if err := cmd.Run(); err != nil { 23 | return err 24 | } 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /config/projects.json: -------------------------------------------------------------------------------- 1 | // autogo监控的项目配置信息。 2 | // 可以同时监控多个项目,每个项目的配置是一个json object 3 | [ 4 | { 5 | // 项目名称(必须) 6 | "name": "", 7 | 8 | // 项目的根路径,可以是相对路径或绝对路径(必须) 9 | "root": "", 10 | 11 | // go编译运行方式,可以是run、build或insall。(可选,默认为install) 12 | "go_way": "", 13 | 14 | // 项目的运行方式:执行完后自动退出还是会一直运行(可选,默认为true) 15 | "deamon": true, 16 | 17 | // main 项目main函数所在文件路径,相对src。可选。对于go_way不同,对该配置的要求也不一样 18 | // 1)当go_way为run时,该配置有时(有值),直接go run 该值;否则要求src目录下的main包文件,名字为项目名; 19 | // 2)当go_way为build时,该配置有时(有值),必须是"dir/filename.go"这种形式;没有时,要求main包src根目录中。生成的可执行文件名总是项目名 20 | // 3)当go_way为install时,该配置有时(有值),必须是"dir/filename.go"这种形式,生成的可执行文件名是dir;没有时,要求main包在一个名称为项目名的文件夹中中,生成的可执行文件名是项目名称; 21 | "main": "", 22 | 23 | // 依赖其他项目(一般只是库) 24 | "depends": [] 25 | } 26 | ] 27 | // 可以查看conf_example.json配置示例 -------------------------------------------------------------------------------- /templates/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 编译出错了! 6 | 7 | 13 | 14 | 15 |
16 |
17 |

~~o(>_<)o ~~主人,编译出错了哦!

18 |
19 |
20 |

错误详细信息:

21 |

22 | {{.}} 23 |

24 |
25 |
26 | 29 |
30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2010 Sencha Inc. 4 | Copyright (c) 2011 LearnBoost 5 | Copyright (c) 2011 TJ Holowaychuk 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining 8 | a copy of this software and associated documentation files (the 9 | 'Software'), to deal in the Software without restriction, including 10 | without limitation the rights to use, copy, modify, merge, publish, 11 | distribute, sublicense, and/or sell copies of the Software, and to 12 | permit persons to whom the Software is furnished to do so, subject to 13 | the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be 16 | included in all copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 19 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 20 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 21 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 23 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 24 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /src/files/files.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 polaris(studygolang.com). All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // files包封装了一下文件操作函数,方便进行文件操作 6 | package files 7 | 8 | import ( 9 | "os" 10 | ) 11 | 12 | // 检查文件或目录是否存在 13 | // 如果由 filename 指定的文件或目录存在则返回 true,否则返回 false 14 | func Exist(filename string) bool { 15 | _, err := os.Stat(filename) 16 | return err == nil || os.IsExist(err) 17 | } 18 | 19 | // 列出指定路径中的文件和目录 20 | // 如果目录不存在,则返回空slice 21 | func ScanDir(directory string) []string { 22 | file, err := os.Open(directory) 23 | if err != nil { 24 | return []string{} 25 | } 26 | names, err := file.Readdirnames(-1) 27 | if err != nil { 28 | return []string{} 29 | } 30 | return names 31 | } 32 | 33 | // 判断给定文件名是否是一个目录 34 | // 如果文件名存在并且为目录则返回 true。如果 filename 是一个相对路径,则按照当前工作目录检查其相对路径。 35 | func IsDir(filename string) bool { 36 | return isFileOrDir(filename, true) 37 | } 38 | 39 | // 判断给定文件名是否为一个正常的文件 40 | // 如果文件存在且为正常的文件则返回 true 41 | func IsFile(filename string) bool { 42 | return isFileOrDir(filename, false) 43 | } 44 | 45 | // 判断是文件还是目录,根据decideDir为true表示判断是否为目录;否则判断是否为文件 46 | func isFileOrDir(filename string, decideDir bool) bool { 47 | fileInfo, err := os.Stat(filename) 48 | if err != nil { 49 | return false 50 | } 51 | isDir := fileInfo.IsDir() 52 | if decideDir { 53 | return isDir 54 | } 55 | return !isDir 56 | } 57 | -------------------------------------------------------------------------------- /src/fsnotify/README.md: -------------------------------------------------------------------------------- 1 | # File system notifications for Go 2 | 3 | [GoDoc](http://go.pkgdoc.org/github.com/howeyc/fsnotify) 4 | 5 | Cross platform, works on: 6 | * Windows 7 | * Linux 8 | * BSD 9 | * OSX 10 | 11 | Example: 12 | ```go 13 | watcher, err := fsnotify.NewWatcher() 14 | if err != nil { 15 | log.Fatal(err) 16 | } 17 | 18 | // Process events 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") 31 | if err != nil { 32 | log.Fatal(err) 33 | } 34 | 35 | /* ... do stuff ... */ 36 | watcher.Close() 37 | ``` 38 | 39 | For each event: 40 | * Name 41 | * IsCreate() 42 | * IsDelete() 43 | * IsModify() 44 | * IsRename() 45 | 46 | Notes: 47 | * When a file is renamed to another directory is it still being watched? 48 | * No (it shouldn't be, unless you are watching where it was moved to). 49 | * When I watch a directory, are all subdirectories watched as well? 50 | * No, you must add watches for any directory you want to watch. 51 | * Do I have to watch the Error and Event channels in a separate goroutine? 52 | * As of now, yes. Looking into making this single-thread friendly. 53 | 54 | [![Build Status](https://secure.travis-ci.org/howeyc/fsnotify.png?branch=master)](http://travis-ci.org/howeyc/fsnotify) 55 | 56 | -------------------------------------------------------------------------------- /src/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 | "testing" 12 | ) 13 | 14 | func TestFsnotifyFakeSymlink(t *testing.T) { 15 | // Create an fsnotify watcher instance and initialize it 16 | watcher, err := NewWatcher() 17 | if err != nil { 18 | t.Fatalf("NewWatcher() failed: %s", err) 19 | } 20 | 21 | const testDir string = "_test" 22 | 23 | // Create directory to watch 24 | if os.Mkdir(testDir, 0777) != nil { 25 | t.Fatalf("Failed to create test directory: %s", err) 26 | } 27 | defer os.RemoveAll(testDir) 28 | 29 | if os.Symlink("_test/zzz", "_test/zzznew") != nil { 30 | t.Fatalf("Failed to create bogus symlink: %s", err) 31 | } 32 | t.Logf("Created bogus symlink") 33 | 34 | var errorsReceived = 0 35 | // Receive errors on the error channel on a separate goroutine 36 | go func() { 37 | for errors := range watcher.Error { 38 | t.Logf("Received error: %s", errors) 39 | errorsReceived++ 40 | } 41 | }() 42 | 43 | // Add a watch for testDir 44 | err = watcher.Watch(testDir) 45 | if err != nil { 46 | t.Fatalf("Watcher.Watch() failed: %s", err) 47 | } 48 | 49 | // Should not be error, just no events for broken links (watching nothing) 50 | if errorsReceived > 0 { 51 | t.Fatal("fsnotify errors have been received.") 52 | } 53 | 54 | // Try closing the fsnotify instance 55 | t.Log("calling Close()") 56 | watcher.Close() 57 | } 58 | -------------------------------------------------------------------------------- /src/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "fmt" 5 | "fsnotify" 6 | "log" 7 | "project" 8 | "simplejson" 9 | "time" 10 | ) 11 | 12 | // Watch 监控配置文件 13 | func Watch(configFile string) error { 14 | watcher, err := fsnotify.NewWatcher() 15 | if err != nil { 16 | return err 17 | } 18 | eventNum := make(chan int) 19 | go func() { 20 | for { 21 | i := 0 22 | GetEvent: 23 | for { 24 | select { 25 | case <-watcher.Event: 26 | i++ 27 | case <-time.After(200e6): 28 | break GetEvent 29 | } 30 | } 31 | if i > 0 { 32 | eventNum <- i 33 | } 34 | } 35 | }() 36 | 37 | go func() { 38 | for { 39 | select { 40 | case <-eventNum: 41 | log.Println("[INFO] ReloadConfig...") 42 | Load(configFile) 43 | } 44 | } 45 | }() 46 | 47 | return watcher.Watch(configFile) 48 | } 49 | 50 | // Load加载解析配置文件 51 | func Load(configFile string) error { 52 | allConfig, err := simplejson.ParseFile(configFile) 53 | if err != nil { 54 | log.Println("[ERROR] 配置文件格式错误", err) 55 | return err 56 | } 57 | middleJs, err := allConfig.Array() 58 | if err != nil { 59 | log.Println("[ERROR] 配置文件格式错误", err) 60 | return err 61 | } 62 | for i, length := 0, len(middleJs); i < length; i++ { 63 | oneProject := allConfig.GetIndex(i) 64 | name := oneProject.Get("name").MustString() 65 | root := oneProject.Get("root").MustString() 66 | goWay := oneProject.Get("go_way").MustString() 67 | deamon := oneProject.Get("deamon").MustBool(true) 68 | mainFile := oneProject.Get("main").MustString() 69 | depends := oneProject.GetStringSlice("depends") 70 | err = project.Watch(name, root, goWay, mainFile, deamon, depends...) 71 | if err != nil { 72 | log.Println("[ERROR] 监控Project:", name, " 出错。详细信息如下:") 73 | fmt.Println(err) 74 | } 75 | } 76 | return err 77 | } 78 | -------------------------------------------------------------------------------- /src/fsnotify/fsnotify.go: -------------------------------------------------------------------------------- 1 | package fsnotify 2 | 3 | import "fmt" 4 | 5 | const ( 6 | FSN_CREATE = 1 7 | FSN_MODIFY = 2 8 | FSN_DELETE = 4 9 | FSN_RENAME = 8 10 | 11 | FSN_ALL = FSN_MODIFY | FSN_DELETE | FSN_RENAME | FSN_CREATE 12 | ) 13 | 14 | // Purge events from interal chan to external chan if passes filter 15 | func (w *Watcher) purgeEvents() { 16 | for ev := range w.internalEvent { 17 | sendEvent := false 18 | fsnFlags := w.fsnFlags[ev.Name] 19 | 20 | if (fsnFlags&FSN_CREATE == FSN_CREATE) && ev.IsCreate() { 21 | sendEvent = true 22 | } 23 | 24 | if (fsnFlags&FSN_MODIFY == FSN_MODIFY) && ev.IsModify() { 25 | sendEvent = true 26 | } 27 | 28 | if (fsnFlags&FSN_DELETE == FSN_DELETE) && ev.IsDelete() { 29 | sendEvent = true 30 | } 31 | 32 | if (fsnFlags&FSN_RENAME == FSN_RENAME) && ev.IsRename() { 33 | //w.RemoveWatch(ev.Name) 34 | sendEvent = true 35 | } 36 | 37 | if sendEvent { 38 | w.Event <- ev 39 | } 40 | } 41 | 42 | close(w.Event) 43 | } 44 | 45 | // Watch a given file path 46 | func (w *Watcher) Watch(path string) error { 47 | w.fsnFlags[path] = FSN_ALL 48 | return w.watch(path) 49 | } 50 | 51 | // Watch a given file path for a particular set of notifications (FSN_MODIFY etc.) 52 | func (w *Watcher) WatchFlags(path string, flags uint32) error { 53 | w.fsnFlags[path] = flags 54 | return w.watch(path) 55 | } 56 | 57 | // Remove a watch on a file 58 | func (w *Watcher) RemoveWatch(path string) error { 59 | delete(w.fsnFlags, path) 60 | return w.removeWatch(path) 61 | } 62 | 63 | // String formats the event e in the form 64 | // "filename: DELETE|MODIFY|..." 65 | func (e *FileEvent) String() string { 66 | var events string = "" 67 | 68 | if e.IsCreate() { 69 | events += "|" + "CREATE" 70 | } 71 | 72 | if e.IsDelete() { 73 | events += "|" + "DELETE" 74 | } 75 | 76 | if e.IsModify() { 77 | events += "|" + "MODIFY" 78 | } 79 | 80 | if e.IsRename() { 81 | events += "|" + "RENAME" 82 | } 83 | 84 | if len(events) > 0 { 85 | events = events[1:] 86 | } 87 | 88 | return fmt.Sprintf("%q: %s", e.Name, events) 89 | } 90 | -------------------------------------------------------------------------------- /src/simplejson/simplejson_test.go: -------------------------------------------------------------------------------- 1 | package simplejson 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/bmizerany/assert" 6 | "io/ioutil" 7 | "log" 8 | "strconv" 9 | "testing" 10 | ) 11 | 12 | func TestSimplejson(t *testing.T) { 13 | var ok bool 14 | var err error 15 | 16 | log.SetOutput(ioutil.Discard) 17 | 18 | js, err := NewJson([]byte(`{ 19 | "test": { 20 | "array": [1, "2", 3], 21 | "arraywithsubs": [{"subkeyone": 1}, 22 | {"subkeytwo": 2, "subkeythree": 3}], 23 | "int": 10, 24 | "float": 5.150, 25 | "bignum": 9223372036854775807, 26 | "string": "simplejson", 27 | "bool": true 28 | } 29 | }`)) 30 | 31 | assert.NotEqual(t, nil, js) 32 | assert.Equal(t, nil, err) 33 | 34 | _, ok = js.CheckGet("test") 35 | assert.Equal(t, true, ok) 36 | 37 | _, ok = js.CheckGet("missing_key") 38 | assert.Equal(t, false, ok) 39 | 40 | arr, _ := js.Get("test").Get("array").Array() 41 | assert.NotEqual(t, nil, arr) 42 | for i, v := range arr { 43 | var iv int 44 | switch v.(type) { 45 | case float64: 46 | iv = int(v.(float64)) 47 | case string: 48 | iv, _ = strconv.Atoi(v.(string)) 49 | } 50 | assert.Equal(t, i+1, iv) 51 | } 52 | 53 | aws := js.Get("test").Get("arraywithsubs") 54 | assert.NotEqual(t, nil, aws) 55 | var awsval int 56 | awsval, _ = aws.GetIndex(0).Get("subkeyone").Int() 57 | assert.Equal(t, 1, awsval) 58 | awsval, _ = aws.GetIndex(1).Get("subkeytwo").Int() 59 | assert.Equal(t, 2, awsval) 60 | awsval, _ = aws.GetIndex(1).Get("subkeythree").Int() 61 | assert.Equal(t, 3, awsval) 62 | 63 | i, _ := js.Get("test").Get("int").Int() 64 | assert.Equal(t, 10, i) 65 | 66 | f, _ := js.Get("test").Get("float").Float64() 67 | assert.Equal(t, 5.150, f) 68 | 69 | s, _ := js.Get("test").Get("string").String() 70 | assert.Equal(t, "simplejson", s) 71 | 72 | b, _ := js.Get("test").Get("bool").Bool() 73 | assert.Equal(t, true, b) 74 | 75 | mi := js.Get("test").Get("int").MustInt() 76 | assert.Equal(t, 10, mi) 77 | 78 | mi2 := js.Get("test").Get("missing_int").MustInt(5150) 79 | assert.Equal(t, 5150, mi2) 80 | 81 | ms := js.Get("test").Get("string").MustString() 82 | assert.Equal(t, "simplejson", ms) 83 | 84 | ms2 := js.Get("test").Get("missing_string").MustString("fyea") 85 | assert.Equal(t, "fyea", ms2) 86 | } 87 | 88 | func TestStdlibInterfaces(t *testing.T) { 89 | val := new(struct { 90 | Name string `json:"name"` 91 | Params *Json `json:"params"` 92 | }) 93 | val2 := new(struct { 94 | Name string `json:"name"` 95 | Params *Json `json:"params"` 96 | }) 97 | 98 | raw := `{"name":"myobject","params":{"string":"simplejson"}}` 99 | 100 | assert.Equal(t, nil, json.Unmarshal([]byte(raw), val)) 101 | 102 | assert.Equal(t, "myobject", val.Name) 103 | assert.NotEqual(t, nil, val.Params.data) 104 | s, _ := val.Params.Get("string").String() 105 | assert.Equal(t, "simplejson", s) 106 | 107 | p, err := json.Marshal(val) 108 | assert.Equal(t, nil, err) 109 | assert.Equal(t, nil, json.Unmarshal(p, val2)) 110 | assert.Equal(t, val, val2) // stable 111 | } 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | autogo 2 | ====== 3 | 4 | Go语言是静态语言,修改源代码总是需要编译、运行,如果用Go做Web开发,修改一点就要编译、运行,然后才能看结果,很痛苦。 5 | autogo就是为了让Go开发更方便。在开发阶段,做到修改之后,立马看到修改的效果,如果编译出错,能够及时显示错误信息! 6 | 7 | 使用说明 8 | ====== 9 | 10 | 1、下载 11 | 将源代码git clone到任意一个位置 12 | 13 | 2、修改config/project.json文件 14 | 该文件的作用是配置需要被autogo管理的项目,一个项目是一个json对象({}),包括name、root和depends, 15 | 其中depends是可选的,name是项目最终生成可执行文件的文件名(也就是main包所在的目录);root是项目的根目录。 16 | 17 | 3、执行install.sh(linux)/install.bat(windows),编译autogo 18 | 19 | 4、运行autogo:bin/autogo 20 | 注意,运行autogo时,当前目录要切换到autogo所在目录 21 | 22 | 注:为了方便编译出错时看到错误详细信息,当有错误时autogo会在项目中新建一个文件,将错误信息写入其中。 23 | 因此建议测阶段,在被监控的项目中加入如下一段代码(在所有访问的入口处): 24 | 25 | errFile := "_log_/error.html" 26 | _, err := os.Stat(errFile) 27 | if err == nil || os.IsExist(err) { 28 | content, _ := ioutil.ReadFile(errFile) 29 | fmt.Fprintln(rw, string(content)) 30 | return 31 | } 32 | 这样,当程序编译出错时,刷新页面会看到类似如下的错误提示: 33 | 34 | ~~o(>_<)o ~~主人,编译出错了哦! 35 | 36 | 错误详细信息: 37 | 38 | # test src\test\main.go:5: imported and not used: "io" 39 | 40 | 例子程序 41 | ====== 42 | 43 | 1、在任意目录新建一个test工程。目录结构如下: 44 | 45 | test 46 | └───src 47 | └───test 48 | └───main.go 49 | 2、main.go的代码如下: 50 | 51 | import ( 52 | "fmt" 53 | "io/ioutil" 54 | "log" 55 | "net/http" 56 | "os" 57 | "runtime" 58 | ) 59 | func init() { 60 | runtime.GOMAXPROCS(runtime.NumCPU()) 61 | } 62 | 63 | func main() { 64 | http.HandleFunc("/", mainHandle) 65 | 66 | log.Fatal(http.ListenAndServe(":8080", nil)) 67 | } 68 | 69 | func mainHandle(rw http.ResponseWriter, req *http.Request) { 70 | // 当编译出错时提示错误信息;开发阶段使用 71 | errFile := "_log_/error.html" 72 | _, err := os.Stat(errFile) 73 | if err == nil || os.IsExist(err) { 74 | content, _ := ioutil.ReadFile(errFile) 75 | fmt.Fprintln(rw, string(content)) 76 | return 77 | } 78 | fmt.Fprintln(rw, "Hello, World!") 79 | // 这里可以统一路由转发 80 | } 81 | 82 | 3、在autogo的config/project.json中将该项目加进去 83 | 84 | [ 85 | { 86 | "name": "test", 87 | "root": "../test", 88 | "go_way": "install", 89 | "deamon": true, 90 | "main": "test/test.go" 91 | "depends": [] 92 | } 93 | ] 94 | root可以是相对路径或决定路径, 95 | go_way go编译运行方式,可以是run、build或insall,可选,默认为install 96 | deamon 项目的运行方式:执行完后自动退出还是会一直运行 97 | main 项目main函数所在文件路径,相对src 98 | depends 依赖的其他gopath 99 | 100 | 101 | 4、启动autogo(如果autogo没编译,先通过make编译)。注意,启动autogo应该cd到autogo所在根目录执行bin/autogo启动。 102 | 103 | 5、在浏览器中访问:http://localhost:8080,就可以看到Hello World!了。 104 | 改动test中的main.go,故意出错,然后刷新页面看到效果了有木有! 105 | 106 | 版本更新历史 107 | ===== 108 | 109 | 2012-12-20 autogo 2.0发布 110 | ``` 111 | 1、优化编译、运行过程(只会执行一次) 112 | 2、支持多种goway方式:go run、build、install,这样对于测试项目也支持了 113 | 3、修复了 被监控项目如果有问题 autogo启动不了的情况 114 | 4、调整了代码结构 115 | ``` 116 | 117 | 2012-12-18 autogo 1.0发布 118 | 119 | 使用的第三方库 120 | ====== 121 | 122 | 为了方便,autogo中直接包含了第三方库,不需要另外下载。 123 | 124 | 1、[fsnotify](https://github.com/howeyc/fsnotify),File system notifications 125 | 126 | 2、[simplejson](https://github.com/bitly/go-simplejson),解析JSON,我做了一些改动 127 | 128 | 感谢 129 | ===== 130 | 131 | johntech 132 | 133 | [ohlinux](https://github.com/ohlinux) 134 | 135 | LICENCE 136 | ====== 137 | 138 | The MIT [License](https://github.com/polaris1119/autogo/master/LICENSE) 139 | -------------------------------------------------------------------------------- /src/simplejson/README.md: -------------------------------------------------------------------------------- 1 | ### go-simplejson 2 | 3 | a Go package to interact with arbitrary JSON 4 | 5 | [![Build Status](https://secure.travis-ci.org/bitly/go-simplejson.png)](http://travis-ci.org/bitly/go-simplejson) 6 | 7 | ### importing 8 | 9 | import simplejson github.com/bitly/go-simplejson 10 | 11 | ### go doc 12 | 13 | ```go 14 | FUNCTIONS 15 | 16 | func Version() string 17 | returns the current implementation version 18 | 19 | TYPES 20 | 21 | type Json struct { 22 | // contains filtered or unexported fields 23 | } 24 | 25 | func NewJson(body []byte) (*Json, error) 26 | NewJson returns a pointer to a new `Json` object after unmarshaling 27 | `body` bytes 28 | 29 | func (j *Json) Array() ([]interface{}, error) 30 | Array type asserts to an `array` 31 | 32 | func (j *Json) Bool() (bool, error) 33 | Bool type asserts to `bool` 34 | 35 | func (j *Json) Bytes() ([]byte, error) 36 | Bytes type asserts to `[]byte` 37 | 38 | func (j *Json) CheckGet(key string) (*Json, bool) 39 | CheckGet returns a pointer to a new `Json` object and a `bool` 40 | identifying success or failure 41 | 42 | useful for chained operations when success is important: 43 | 44 | if data, ok := js.Get("top_level").CheckGet("inner"); ok { 45 | log.Println(data) 46 | } 47 | 48 | func (j *Json) Encode() ([]byte, error) 49 | Encode returns its marshaled data as `[]byte` 50 | 51 | func (j *Json) Float64() (float64, error) 52 | Float64 type asserts to `float64` 53 | 54 | func (j *Json) Get(key string) *Json 55 | Get returns a pointer to a new `Json` object for `key` in its `map` 56 | representation 57 | 58 | useful for chaining operations (to traverse a nested JSON): 59 | 60 | js.Get("top_level").Get("dict").Get("value").Int() 61 | 62 | func (j *Json) GetIndex(index int) *Json 63 | GetIndex resturns a pointer to a new `Json` object for `index` in its 64 | `array` representation 65 | 66 | this is the analog to Get when accessing elements of a json array 67 | instead of a json object: 68 | 69 | js.Get("top_level").Get("array").GetIndex(1).Get("key").Int() 70 | 71 | func (j *Json) Int() (int, error) 72 | Int type asserts to `float64` then converts to `int` 73 | 74 | func (j *Json) Int64() (int64, error) 75 | Int type asserts to `float64` then converts to `int64` 76 | 77 | func (j *Json) Map() (map[string]interface{}, error) 78 | Map type asserts to `map` 79 | 80 | func (j *Json) MarshalJSON() ([]byte, error) 81 | Implements the json.Marshaler interface. 82 | 83 | func (j *Json) MustFloat64(args ...float64) float64 84 | MustFloat64 guarantees the return of a `float64` (with optional default) 85 | 86 | useful when you explicitly want a `float64` in a single value return 87 | context: 88 | 89 | myFunc(js.Get("param1").MustFloat64(), js.Get("optional_param").MustFloat64(5.150)) 90 | 91 | func (j *Json) MustInt(args ...int) int 92 | MustInt guarantees the return of an `int` (with optional default) 93 | 94 | useful when you explicitly want an `int` in a single value return 95 | context: 96 | 97 | myFunc(js.Get("param1").MustInt(), js.Get("optional_param").MustInt(5150)) 98 | 99 | func (j *Json) MustString(args ...string) string 100 | MustString guarantees the return of a `string` (with optional default) 101 | 102 | useful when you explicitly want a `string` in a single value return 103 | context: 104 | 105 | myFunc(js.Get("param1").MustString(), js.Get("optional_param").MustString("my_default")) 106 | 107 | func (j *Json) String() (string, error) 108 | String type asserts to `string` 109 | 110 | func (j *Json) UnmarshalJSON(p []byte) error 111 | Implements the json.Unmarshaler interface. 112 | ``` 113 | -------------------------------------------------------------------------------- /src/simplejson/simplejson.go: -------------------------------------------------------------------------------- 1 | package simplejson 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "io/ioutil" 8 | "log" 9 | "strings" 10 | ) 11 | 12 | // returns the current implementation version 13 | func Version() string { 14 | return "0.5" 15 | } 16 | 17 | type Json struct { 18 | data interface{} 19 | } 20 | 21 | // 解析json文件,过滤掉注释(只支持注释在单独一行,以"//"注释) 22 | func ParseFile(filename string) (*Json, error) { 23 | stream, err := ioutil.ReadFile(filename) 24 | if err != nil { 25 | return nil, err 26 | } 27 | content := string(stream) 28 | var builder bytes.Buffer 29 | lines := strings.Split(content, "\n") 30 | for _, line := range lines { 31 | line = strings.TrimSpace(line) 32 | if line == "" || strings.HasPrefix(line, "//") { 33 | continue 34 | } 35 | builder.WriteString(line) 36 | } 37 | return NewJson(builder.Bytes()) 38 | } 39 | 40 | // NewJson returns a pointer to a new `Json` object 41 | // after unmarshaling `body` bytes 42 | func NewJson(body []byte) (*Json, error) { 43 | j := new(Json) 44 | err := j.UnmarshalJSON(body) 45 | if err != nil { 46 | return nil, err 47 | } 48 | return j, nil 49 | } 50 | 51 | // Encode returns its marshaled data as `[]byte` 52 | func (j *Json) Encode() ([]byte, error) { 53 | return j.MarshalJSON() 54 | } 55 | 56 | // Implements the json.Unmarshaler interface. 57 | func (j *Json) UnmarshalJSON(p []byte) error { 58 | return json.Unmarshal(p, &j.data) 59 | } 60 | 61 | // Implements the json.Marshaler interface. 62 | func (j *Json) MarshalJSON() ([]byte, error) { 63 | return json.Marshal(&j.data) 64 | } 65 | 66 | // Get returns a pointer to a new `Json` object 67 | // for `key` in its `map` representation 68 | // 69 | // useful for chaining operations (to traverse a nested JSON): 70 | // js.Get("top_level").Get("dict").Get("value").Int() 71 | func (j *Json) Get(key string) *Json { 72 | m, err := j.Map() 73 | if err == nil { 74 | if val, ok := m[key]; ok { 75 | return &Json{val} 76 | } 77 | } 78 | return &Json{nil} 79 | } 80 | 81 | // GetStringSlice returns slice of string. 82 | func (j *Json) GetStringSlice(key string) []string { 83 | currentJson := j.Get(key) 84 | a, err := currentJson.Array() 85 | stringSlice := make([]string, len(a)) 86 | if err == nil { 87 | for i, v := range a { 88 | if tmp, ok := v.(string); ok { 89 | stringSlice[i] = tmp 90 | } 91 | } 92 | } 93 | return stringSlice 94 | } 95 | 96 | // GetIndex resturns a pointer to a new `Json` object 97 | // for `index` in its `array` representation 98 | // 99 | // this is the analog to Get when accessing elements of 100 | // a json array instead of a json object: 101 | // js.Get("top_level").Get("array").GetIndex(1).Get("key").Int() 102 | func (j *Json) GetIndex(index int) *Json { 103 | a, err := j.Array() 104 | if err == nil { 105 | if len(a) > index { 106 | return &Json{a[index]} 107 | } 108 | } 109 | return &Json{nil} 110 | } 111 | 112 | // CheckGet returns a pointer to a new `Json` object and 113 | // a `bool` identifying success or failure 114 | // 115 | // useful for chained operations when success is important: 116 | // if data, ok := js.Get("top_level").CheckGet("inner"); ok { 117 | // log.Println(data) 118 | // } 119 | func (j *Json) CheckGet(key string) (*Json, bool) { 120 | m, err := j.Map() 121 | if err == nil { 122 | if val, ok := m[key]; ok { 123 | return &Json{val}, true 124 | } 125 | } 126 | return nil, false 127 | } 128 | 129 | // Map type asserts to `map` 130 | func (j *Json) Map() (map[string]interface{}, error) { 131 | if m, ok := (j.data).(map[string]interface{}); ok { 132 | return m, nil 133 | } 134 | return nil, errors.New("type assertion to map[string]interface{} failed") 135 | } 136 | 137 | // Array type asserts to an `array` 138 | func (j *Json) Array() ([]interface{}, error) { 139 | if a, ok := (j.data).([]interface{}); ok { 140 | return a, nil 141 | } 142 | return nil, errors.New("type assertion to []interface{} failed") 143 | } 144 | 145 | // Bool type asserts to `bool` 146 | func (j *Json) Bool() (bool, error) { 147 | if s, ok := (j.data).(bool); ok { 148 | return s, nil 149 | } 150 | return false, errors.New("type assertion to bool failed") 151 | } 152 | 153 | // String type asserts to `string` 154 | func (j *Json) String() (string, error) { 155 | if s, ok := (j.data).(string); ok { 156 | return s, nil 157 | } 158 | return "", errors.New("type assertion to string failed") 159 | } 160 | 161 | // Float64 type asserts to `float64` 162 | func (j *Json) Float64() (float64, error) { 163 | if i, ok := (j.data).(float64); ok { 164 | return i, nil 165 | } 166 | return -1, errors.New("type assertion to float64 failed") 167 | } 168 | 169 | // Int type asserts to `float64` then converts to `int` 170 | func (j *Json) Int() (int, error) { 171 | if f, ok := (j.data).(float64); ok { 172 | return int(f), nil 173 | } 174 | 175 | return -1, errors.New("type assertion to float64 failed") 176 | } 177 | 178 | // Int type asserts to `float64` then converts to `int64` 179 | func (j *Json) Int64() (int64, error) { 180 | if f, ok := (j.data).(float64); ok { 181 | return int64(f), nil 182 | } 183 | 184 | return -1, errors.New("type assertion to float64 failed") 185 | } 186 | 187 | // Bytes type asserts to `[]byte` 188 | func (j *Json) Bytes() ([]byte, error) { 189 | if s, ok := (j.data).(string); ok { 190 | return []byte(s), nil 191 | } 192 | return nil, errors.New("type assertion to []byte failed") 193 | } 194 | 195 | // MustString guarantees the return of a `string` (with optional default) 196 | // 197 | // useful when you explicitly want a `string` in a single value return context: 198 | // myFunc(js.Get("param1").MustString(), js.Get("optional_param").MustString("my_default")) 199 | func (j *Json) MustString(args ...string) string { 200 | var def string 201 | 202 | switch len(args) { 203 | case 0: 204 | break 205 | case 1: 206 | def = args[0] 207 | default: 208 | log.Panicf("MustString() received too many arguments %d", len(args)) 209 | } 210 | 211 | s, err := j.String() 212 | if err == nil { 213 | return s 214 | } 215 | 216 | return def 217 | } 218 | 219 | // MustInt guarantees the return of an `int` (with optional default) 220 | // 221 | // useful when you explicitly want an `int` in a single value return context: 222 | // myFunc(js.Get("param1").MustInt(), js.Get("optional_param").MustInt(5150)) 223 | func (j *Json) MustInt(args ...int) int { 224 | var def int 225 | 226 | switch len(args) { 227 | case 0: 228 | break 229 | case 1: 230 | def = args[0] 231 | default: 232 | log.Panicf("MustInt() received too many arguments %d", len(args)) 233 | } 234 | 235 | i, err := j.Int() 236 | if err == nil { 237 | return i 238 | } 239 | 240 | return def 241 | } 242 | 243 | // MustBool guarantees the return of an `bool` (with optional default) 244 | // 245 | // useful when you explicitly want an `bool` in a single value return context: 246 | // myFunc(js.Get("param1").MustBool(), js.Get("optional_param").MustBool(false)) 247 | func (j *Json) MustBool(args ...bool) bool { 248 | var def bool 249 | 250 | switch len(args) { 251 | case 0: 252 | break 253 | case 1: 254 | def = args[0] 255 | default: 256 | log.Panicf("MustBool() received too many arguments %d", len(args)) 257 | } 258 | 259 | i, err := j.Bool() 260 | if err == nil { 261 | return i 262 | } 263 | 264 | return def 265 | } 266 | 267 | // MustFloat64 guarantees the return of a `float64` (with optional default) 268 | // 269 | // useful when you explicitly want a `float64` in a single value return context: 270 | // myFunc(js.Get("param1").MustFloat64(), js.Get("optional_param").MustFloat64(5.150)) 271 | func (j *Json) MustFloat64(args ...float64) float64 { 272 | var def float64 273 | 274 | switch len(args) { 275 | case 0: 276 | break 277 | case 1: 278 | def = args[0] 279 | default: 280 | log.Panicf("MustFloat64() received too many arguments %d", len(args)) 281 | } 282 | 283 | i, err := j.Float64() 284 | if err == nil { 285 | return i 286 | } 287 | 288 | return def 289 | } 290 | -------------------------------------------------------------------------------- /src/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 implements a wrapper for the Linux inotify system. 8 | package fsnotify 9 | 10 | import ( 11 | "errors" 12 | "fmt" 13 | "os" 14 | "strings" 15 | "sync" 16 | "syscall" 17 | "unsafe" 18 | ) 19 | 20 | type FileEvent struct { 21 | mask uint32 // Mask of events 22 | cookie uint32 // Unique cookie associating related events (for rename(2)) 23 | Name string // File name (optional) 24 | } 25 | 26 | // IsCreate reports whether the FileEvent was triggerd by a creation 27 | func (e *FileEvent) IsCreate() bool { 28 | return (e.mask&IN_CREATE) == IN_CREATE || (e.mask&IN_MOVED_TO) == IN_MOVED_TO 29 | } 30 | 31 | // IsDelete reports whether the FileEvent was triggerd by a delete 32 | func (e *FileEvent) IsDelete() bool { 33 | return (e.mask&IN_DELETE_SELF) == IN_DELETE_SELF || (e.mask&IN_DELETE) == IN_DELETE 34 | } 35 | 36 | // IsModify reports whether the FileEvent was triggerd by a file modification or attribute change 37 | func (e *FileEvent) IsModify() bool { 38 | return ((e.mask&IN_MODIFY) == IN_MODIFY || (e.mask&IN_ATTRIB) == IN_ATTRIB) 39 | } 40 | 41 | // IsRename reports whether the FileEvent was triggerd by a change name 42 | func (e *FileEvent) IsRename() bool { 43 | return ((e.mask&IN_MOVE_SELF) == IN_MOVE_SELF || (e.mask&IN_MOVED_FROM) == IN_MOVED_FROM) 44 | } 45 | 46 | type watch struct { 47 | wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall) 48 | flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags) 49 | } 50 | 51 | type Watcher struct { 52 | mu sync.Mutex // Map access 53 | fd int // File descriptor (as returned by the inotify_init() syscall) 54 | watches map[string]*watch // Map of inotify watches (key: path) 55 | fsnFlags map[string]uint32 // Map of watched files to flags used for filter 56 | paths map[int]string // Map of watched paths (key: watch descriptor) 57 | Error chan error // Errors are sent on this channel 58 | internalEvent chan *FileEvent // Events are queued on this channel 59 | Event chan *FileEvent // Events are returned on this channel 60 | done chan bool // Channel for sending a "quit message" to the reader goroutine 61 | isClosed bool // Set to true when Close() is first called 62 | } 63 | 64 | // NewWatcher creates and returns a new inotify instance using inotify_init(2) 65 | func NewWatcher() (*Watcher, error) { 66 | fd, errno := syscall.InotifyInit() 67 | if fd == -1 { 68 | return nil, os.NewSyscallError("inotify_init", errno) 69 | } 70 | w := &Watcher{ 71 | fd: fd, 72 | watches: make(map[string]*watch), 73 | fsnFlags: make(map[string]uint32), 74 | paths: make(map[int]string), 75 | internalEvent: make(chan *FileEvent), 76 | Event: make(chan *FileEvent), 77 | Error: make(chan error), 78 | done: make(chan bool, 1), 79 | } 80 | 81 | go w.readEvents() 82 | go w.purgeEvents() 83 | return w, nil 84 | } 85 | 86 | // Close closes an inotify watcher instance 87 | // It sends a message to the reader goroutine to quit and removes all watches 88 | // associated with the inotify instance 89 | func (w *Watcher) Close() error { 90 | if w.isClosed { 91 | return nil 92 | } 93 | w.isClosed = true 94 | 95 | // Remove all watches 96 | for path := range w.watches { 97 | w.RemoveWatch(path) 98 | } 99 | 100 | // Send "quit" message to the reader goroutine 101 | w.done <- true 102 | 103 | return nil 104 | } 105 | 106 | // AddWatch adds path to the watched file set. 107 | // The flags are interpreted as described in inotify_add_watch(2). 108 | func (w *Watcher) addWatch(path string, flags uint32) error { 109 | if w.isClosed { 110 | return errors.New("inotify instance already closed") 111 | } 112 | 113 | watchEntry, found := w.watches[path] 114 | if found { 115 | watchEntry.flags |= flags 116 | flags |= syscall.IN_MASK_ADD 117 | } 118 | wd, errno := syscall.InotifyAddWatch(w.fd, path, flags) 119 | if wd == -1 { 120 | return errno 121 | } 122 | 123 | w.mu.Lock() 124 | w.watches[path] = &watch{wd: uint32(wd), flags: flags} 125 | w.paths[wd] = path 126 | w.mu.Unlock() 127 | 128 | return nil 129 | } 130 | 131 | // Watch adds path to the watched file set, watching all events. 132 | func (w *Watcher) watch(path string) error { 133 | return w.addWatch(path, OS_AGNOSTIC_EVENTS) 134 | } 135 | 136 | // RemoveWatch removes path from the watched file set. 137 | func (w *Watcher) removeWatch(path string) error { 138 | watch, ok := w.watches[path] 139 | if !ok { 140 | return errors.New(fmt.Sprintf("can't remove non-existent inotify watch for: %s", path)) 141 | } 142 | success, errno := syscall.InotifyRmWatch(w.fd, watch.wd) 143 | if success == -1 { 144 | return os.NewSyscallError("inotify_rm_watch", errno) 145 | } 146 | delete(w.watches, path) 147 | return nil 148 | } 149 | 150 | // readEvents reads from the inotify file descriptor, converts the 151 | // received events into Event objects and sends them via the Event channel 152 | func (w *Watcher) readEvents() { 153 | var ( 154 | buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events 155 | n int // Number of bytes read with read() 156 | errno error // Syscall errno 157 | ) 158 | 159 | for { 160 | n, errno = syscall.Read(w.fd, buf[0:]) 161 | // See if there is a message on the "done" channel 162 | var done bool 163 | select { 164 | case done = <-w.done: 165 | default: 166 | } 167 | 168 | // If EOF or a "done" message is received 169 | if n == 0 || done { 170 | syscall.Close(w.fd) 171 | close(w.internalEvent) 172 | close(w.Error) 173 | return 174 | } 175 | 176 | if n < 0 { 177 | w.Error <- os.NewSyscallError("read", errno) 178 | continue 179 | } 180 | if n < syscall.SizeofInotifyEvent { 181 | w.Error <- errors.New("inotify: short read in readEvents()") 182 | continue 183 | } 184 | 185 | var offset uint32 = 0 186 | // We don't know how many events we just read into the buffer 187 | // While the offset points to at least one whole event... 188 | for offset <= uint32(n-syscall.SizeofInotifyEvent) { 189 | // Point "raw" to the event in the buffer 190 | raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset])) 191 | event := new(FileEvent) 192 | event.mask = uint32(raw.Mask) 193 | event.cookie = uint32(raw.Cookie) 194 | nameLen := uint32(raw.Len) 195 | // If the event happened to the watched directory or the watched file, the kernel 196 | // doesn't append the filename to the event, but we would like to always fill the 197 | // the "Name" field with a valid filename. We retrieve the path of the watch from 198 | // the "paths" map. 199 | w.mu.Lock() 200 | event.Name = w.paths[int(raw.Wd)] 201 | w.mu.Unlock() 202 | watchedName := event.Name 203 | if nameLen > 0 { 204 | // Point "bytes" at the first byte of the filename 205 | bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent])) 206 | // The filename is padded with NUL bytes. TrimRight() gets rid of those. 207 | event.Name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000") 208 | } 209 | 210 | // Setup FSNotify flags (inherit from directory watch) 211 | fsnFlags := w.fsnFlags[watchedName] 212 | _, fsnFound := w.fsnFlags[event.Name] 213 | if !fsnFound { 214 | w.fsnFlags[event.Name] = fsnFlags 215 | } 216 | 217 | // Send the events that are not ignored on the events channel 218 | if (event.mask & IN_IGNORED) == 0 { 219 | w.internalEvent <- event 220 | } 221 | 222 | // Move to the next event in the buffer 223 | offset += syscall.SizeofInotifyEvent + nameLen 224 | } 225 | } 226 | } 227 | 228 | const ( 229 | // Options for inotify_init() are not exported 230 | // IN_CLOEXEC uint32 = syscall.IN_CLOEXEC 231 | // IN_NONBLOCK uint32 = syscall.IN_NONBLOCK 232 | 233 | // Options for AddWatch 234 | IN_DONT_FOLLOW uint32 = syscall.IN_DONT_FOLLOW 235 | IN_ONESHOT uint32 = syscall.IN_ONESHOT 236 | IN_ONLYDIR uint32 = syscall.IN_ONLYDIR 237 | 238 | // The "IN_MASK_ADD" option is not exported, as AddWatch 239 | // adds it automatically, if there is already a watch for the given path 240 | // IN_MASK_ADD uint32 = syscall.IN_MASK_ADD 241 | 242 | // Events 243 | IN_ACCESS uint32 = syscall.IN_ACCESS 244 | IN_ALL_EVENTS uint32 = syscall.IN_ALL_EVENTS 245 | IN_ATTRIB uint32 = syscall.IN_ATTRIB 246 | IN_CLOSE uint32 = syscall.IN_CLOSE 247 | IN_CLOSE_NOWRITE uint32 = syscall.IN_CLOSE_NOWRITE 248 | IN_CLOSE_WRITE uint32 = syscall.IN_CLOSE_WRITE 249 | IN_CREATE uint32 = syscall.IN_CREATE 250 | IN_DELETE uint32 = syscall.IN_DELETE 251 | IN_DELETE_SELF uint32 = syscall.IN_DELETE_SELF 252 | IN_MODIFY uint32 = syscall.IN_MODIFY 253 | IN_MOVE uint32 = syscall.IN_MOVE 254 | IN_MOVED_FROM uint32 = syscall.IN_MOVED_FROM 255 | IN_MOVED_TO uint32 = syscall.IN_MOVED_TO 256 | IN_MOVE_SELF uint32 = syscall.IN_MOVE_SELF 257 | IN_OPEN uint32 = syscall.IN_OPEN 258 | 259 | OS_AGNOSTIC_EVENTS = IN_MOVED_TO | IN_MOVED_FROM | IN_CREATE | IN_ATTRIB | IN_MODIFY | IN_MOVE_SELF | IN_DELETE | IN_DELETE_SELF 260 | 261 | // Special events 262 | IN_ISDIR uint32 = syscall.IN_ISDIR 263 | IN_IGNORED uint32 = syscall.IN_IGNORED 264 | IN_Q_OVERFLOW uint32 = syscall.IN_Q_OVERFLOW 265 | IN_UNMOUNT uint32 = syscall.IN_UNMOUNT 266 | ) 267 | -------------------------------------------------------------------------------- /src/project/project.go: -------------------------------------------------------------------------------- 1 | // Copyright 2012 polaris(studygolang.com). All rights reserved. 2 | // Use of this source code is governed by a MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // project包负责管理需要自动编译、运行的项目(Project) 6 | package project 7 | 8 | import ( 9 | "bytes" 10 | "errors" 11 | "files" 12 | "fmt" 13 | "fsnotify" 14 | "log" 15 | "os" 16 | "os/exec" 17 | "path/filepath" 18 | "strings" 19 | "text/template" 20 | "time" 21 | ) 22 | 23 | const pathSeparator = string(os.PathSeparator) 24 | 25 | var ( 26 | errorTplFile = "templates/error.html" 27 | 28 | tpl *template.Template 29 | 30 | successFlag = "finished" 31 | 32 | PrjRootErr = errors.New("project can't be found'!") 33 | ) 34 | 35 | func init() { 36 | tpl = template.Must(template.ParseFiles(errorTplFile)) 37 | } 38 | 39 | // Watch 监听项目 40 | // 41 | // name:项目名称(最后生成的可执行程序名,不包括后缀); 42 | // root: 项目根目录 43 | // goWay: 编译项目的方式,run、build还是install 44 | // deamon: 项目是否是一直运行的(即不手动退出,程序不会终止,一般会有死循环,比如Web服务) 45 | // mainFile:main包的main函数所在文件路径(相对于src目录) 46 | // depends:是依赖的其他GOPATH路径下的项目,可以不传 47 | func Watch(name, root, goWay, mainFile string, deamon bool, depends ...string) error { 48 | prj, err := New(name, root, goWay, mainFile, deamon, depends...) 49 | if err != nil { 50 | return err 51 | } 52 | if err = prj.CreateMakeFile(); err != nil { 53 | log.Println("create make file error:", err) 54 | return err 55 | } 56 | defer prj.Watch() 57 | if goWay == "run" { 58 | return prj.Run() 59 | } 60 | if err = prj.Compile(); err != nil { 61 | return err 62 | } 63 | if err = prj.Start(); err != nil { 64 | return err 65 | } 66 | if deamon { 67 | log.Println("[INFO] 项目", name, "启动完成") 68 | } 69 | return nil 70 | } 71 | 72 | type Project struct { 73 | name string // 项目名称 74 | Root string // 项目的根路径 75 | binAbsolutePath string // 执行文件路径(绝对路径) 76 | execArgs []string // 程序执行的参数 77 | srcAbsolutePath string // 源程序文件路径(绝对路径) 78 | errAbsolutePath string // 编译语法错误存放位置 79 | 80 | GoWay string // 项目编译方式:run、build还是install 81 | deamon bool // 程序是否一直运行(比如Web服务) 82 | Options string // 编译选项 83 | 84 | // 对于run、build而言,是main包中的main函数所在路径(包括文件名) 85 | // 对于install而言,是main包所在目录(可能多级),如果没配置,则等于name 86 | MainFile string 87 | Depends []string // 依赖其他项目(一般只是库) 88 | 89 | process *os.Process 90 | } 91 | 92 | // New 要求被监听项目必须有src目录(按Go习惯建目录) 93 | func New(name, root, goWay, mainFile string, deamon bool, depends ...string) (*Project, error) { 94 | if !files.IsDir(root) { 95 | return nil, PrjRootErr 96 | } 97 | if filepath.IsAbs(mainFile) { 98 | return nil, errors.New("main配置项必须是相对于项目src的相对路径!") 99 | } 100 | root, err := filepath.Abs(root) 101 | if err != nil { 102 | return nil, err 103 | } 104 | binAbsolutePath := filepath.Join(root, "bin") 105 | 106 | options := "" 107 | switch goWay { 108 | case "run": 109 | if mainFile == "" { 110 | mainFile = name + ".go" 111 | } 112 | mainFile = filepath.Join("src", mainFile) 113 | case "build": 114 | if mainFile == "" { 115 | mainFile = name + ".go" 116 | } 117 | mainFile = filepath.Join("src", mainFile) 118 | if !files.Exist(binAbsolutePath) { 119 | if err = os.Mkdir(binAbsolutePath, 0777); err != nil { 120 | return nil, err 121 | } 122 | } 123 | output := filepath.Join("bin", name+binanryFileSuffix) 124 | options = "-o " + output 125 | case "install": 126 | fallthrough 127 | default: 128 | if mainFile == "" { 129 | mainFile = name 130 | } else { 131 | mainFile = filepath.Dir(mainFile) 132 | } 133 | } 134 | return &Project{ 135 | name: name, 136 | Root: root, 137 | binAbsolutePath: binAbsolutePath, 138 | srcAbsolutePath: filepath.Join(root, "src"), 139 | errAbsolutePath: filepath.Join(root, "_log_"), 140 | GoWay: goWay, 141 | deamon: deamon, 142 | MainFile: mainFile, 143 | Options: options, 144 | Depends: depends, 145 | }, nil 146 | } 147 | 148 | // Watch 监听该项目,源码有改动会重新编译运行 149 | func (this *Project) Watch() error { 150 | watcher, err := fsnotify.NewWatcher() 151 | if err != nil { 152 | return err 153 | } 154 | eventNum := make(chan int) 155 | go func() { 156 | for { 157 | i := 0 158 | GetEvent: 159 | for { 160 | select { 161 | case <-watcher.Event: 162 | i++ 163 | // 修改可能会有多次modify事件 164 | case <-time.After(500e6): 165 | break GetEvent 166 | } 167 | } 168 | if i > 0 { 169 | eventNum <- i 170 | } 171 | } 172 | }() 173 | 174 | go func() { 175 | for { 176 | var err error 177 | select { 178 | case <-eventNum: 179 | if this.GoWay == "run" { 180 | if err = this.Run(); err != nil { 181 | log.Println("run error,详细信息如下:") 182 | fmt.Println(err) 183 | } 184 | break 185 | } 186 | if err = this.Compile(); err != nil { 187 | log.Println("complie error,详细信息如下:") 188 | fmt.Println(err) 189 | break 190 | } 191 | if this.deamon { 192 | if err = this.Stop(); err != nil { 193 | log.Println("stop error,详细信息如下:") 194 | fmt.Println(err) 195 | } 196 | } 197 | if err = this.Start(); err != nil { 198 | log.Println("start error,详细信息如下:") 199 | fmt.Println(err) 200 | } 201 | } 202 | if this.deamon && err == nil { 203 | log.Println("重启完成!") 204 | } 205 | } 206 | }() 207 | 208 | addWatch(watcher, this.srcAbsolutePath) 209 | return nil 210 | } 211 | 212 | // addWatch 使用fsnotify,监听src目录以及子目录 213 | func addWatch(watcher *fsnotify.Watcher, dir string) { 214 | watcher.Watch(dir) 215 | for _, filename := range files.ScanDir(dir) { 216 | childDir := filepath.Join(dir, filename) 217 | if files.IsDir(childDir) { 218 | addWatch(watcher, childDir) 219 | } 220 | } 221 | } 222 | 223 | // SetDepends 设置依赖的项目,被依赖的项目一般是tools 224 | func (this *Project) SetDepends(depends ...string) { 225 | for _, depend := range depends { 226 | this.Depends = append(this.Depends, depend) 227 | } 228 | } 229 | 230 | // ChangetoRoot 切换到当前Project的根目录 231 | func (this *Project) ChangeToRoot() error { 232 | if err := os.Chdir(this.Root); err != nil { 233 | log.Println(err) 234 | return err 235 | } 236 | return nil 237 | } 238 | 239 | // CreateMakeFile 创建make文件(在当前工程根目录),这里的make文件和makefile不一样 240 | // 这里的make文件只是方便编译当前工程而不依赖于GOPATH 241 | func (this *Project) CreateMakeFile() error { 242 | // 获得当前目录 243 | path, err := os.Getwd() 244 | if err != nil { 245 | return err 246 | } 247 | this.ChangeToRoot() 248 | file, err := os.Create(filepath.Join(this.Root, installFileName)) 249 | if err != nil { 250 | os.Chdir(path) 251 | return err 252 | } 253 | os.Chdir(path) 254 | defer file.Close() 255 | tpl := template.Must(template.ParseFiles(makeTplFile)) 256 | tpl.Execute(file, this) 257 | return nil 258 | } 259 | 260 | // Run 当GoWay==run时,直接通过该方法,而不需要先Compile然后Start 261 | func (this *Project) Run() error { 262 | path, err := os.Getwd() 263 | if err != nil { 264 | return err 265 | } 266 | this.ChangeToRoot() 267 | defer os.Chdir(path) 268 | os.Chmod(installFileName, 0755) 269 | cmd := exec.Command(installCmd) 270 | var stdout bytes.Buffer 271 | var stderr bytes.Buffer 272 | cmd.Stdout = &stdout 273 | cmd.Stderr = &stderr 274 | if err = cmd.Start(); err != nil { 275 | return err 276 | } 277 | // TODO:据说time.Sleep会内存泄露 278 | select { 279 | case <-time.After(300e6): 280 | } 281 | output := strings.TrimSpace(stdout.String()) 282 | errOutput := strings.TrimSpace(stderr.String()) 283 | errFile := filepath.Join(this.errAbsolutePath, "error.html") 284 | if this.deamon { 285 | if output == "" { 286 | // 删除可能的错误文件夹和文件 287 | if files.Exist(errFile) { 288 | os.RemoveAll(this.errAbsolutePath) 289 | } 290 | this.process = cmd.Process 291 | return nil 292 | } 293 | } else { 294 | log.Println("=====================") 295 | log.Println("[INFO] 项目", this.name, "的运行结果:") 296 | for _, val := range strings.Split(errOutput, "\n") { 297 | fmt.Println(val) 298 | } 299 | log.Println("=====================") 300 | return nil 301 | } 302 | 303 | if strings.Contains(errOutput, "listen tcp") { 304 | if err = this.process.Kill(); err == nil { 305 | this.Run() 306 | } 307 | return nil 308 | } 309 | 310 | // 往项目中写入错误信息 311 | if !files.Exist(this.errAbsolutePath) { 312 | if err = os.Mkdir(this.errAbsolutePath, 0777); err != nil { 313 | log.Println("can't create errAbsolutePath: ", err) 314 | } 315 | } 316 | file, err := os.Create(errFile) 317 | if err != nil { 318 | return err 319 | } 320 | defer file.Close() 321 | tpl.Execute(file, errOutput) 322 | return errors.New(errOutput) 323 | } 324 | 325 | // Compile 编译当前Project。 326 | func (this *Project) Compile() error { 327 | path, err := os.Getwd() 328 | if err != nil { 329 | return err 330 | } 331 | this.ChangeToRoot() 332 | defer os.Chdir(path) 333 | // 删除bin中的文件 334 | if this.GoWay == "build" { 335 | binFile := this.getExeFilePath() 336 | if files.Exist(binFile) { 337 | os.Remove(binFile) 338 | } 339 | } 340 | os.Chmod(installFileName, 0755) 341 | cmd := exec.Command(installCmd) 342 | var stdout bytes.Buffer 343 | cmd.Stdout = &stdout 344 | if err = cmd.Run(); err != nil { 345 | return err 346 | } 347 | output := strings.TrimSpace(stdout.String()) 348 | errFile := filepath.Join(this.errAbsolutePath, "error.html") 349 | if successFlag == output { 350 | // 删除可能的错误文件夹和文件 351 | if files.Exist(errFile) { 352 | os.RemoveAll(this.errAbsolutePath) 353 | } 354 | return nil 355 | } 356 | 357 | // 往项目中写入错误信息 358 | if !files.Exist(this.errAbsolutePath) { 359 | if err = os.Mkdir(this.errAbsolutePath, 0777); err != nil { 360 | log.Println("can't create errAbsolutePath: ", err) 361 | } 362 | } 363 | file, err := os.Create(errFile) 364 | if err != nil { 365 | return err 366 | } 367 | defer file.Close() 368 | output = strings.Replace(output, "finished", "", -1) 369 | tpl.Execute(file, output) 370 | return errors.New(output) 371 | } 372 | 373 | // Start 启动该Project 374 | func (this *Project) Start() error { 375 | path, err := os.Getwd() 376 | if err != nil { 377 | return err 378 | } 379 | this.ChangeToRoot() 380 | defer os.Chdir(path) 381 | cmd := exec.Command(this.getExeFilePath(), this.execArgs...) 382 | var stdout bytes.Buffer 383 | cmd.Stdout = &stdout 384 | err = cmd.Start() 385 | if this.deamon { 386 | return err 387 | } 388 | 389 | if err = cmd.Wait(); err != nil { 390 | return errors.New("启动失败!") 391 | } 392 | output := strings.TrimSpace(stdout.String()) 393 | log.Println("=====================") 394 | log.Println("[INFO] 项目", this.name, "的运行结果:") 395 | for _, val := range strings.Split(output, "\n") { 396 | fmt.Println(val) 397 | } 398 | log.Println("=====================") 399 | return nil 400 | } 401 | 402 | // 重新启动该Project 403 | func (this *Project) Restart() error { 404 | if err := this.Stop(); err != nil { 405 | log.Println("stop project error! 信息信息如下:") 406 | fmt.Println(err) 407 | return err 408 | } 409 | return this.Start() 410 | } 411 | 412 | // getExeFilePath 获得可执行文件路径(项目) 413 | func (this *Project) getExeFilePath() string { 414 | return filepath.Join(this.binAbsolutePath, this.MainFile+binanryFileSuffix) 415 | } 416 | -------------------------------------------------------------------------------- /src/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 darwin 6 | 7 | //Package fsnotify implements filesystem notification. 8 | package fsnotify 9 | 10 | import ( 11 | "errors" 12 | "fmt" 13 | "io/ioutil" 14 | "os" 15 | "path/filepath" 16 | "syscall" 17 | ) 18 | 19 | type FileEvent struct { 20 | mask uint32 // Mask of events 21 | Name string // File name (optional) 22 | create bool // set by fsnotify package if found new file 23 | } 24 | 25 | // IsCreate reports whether the FileEvent was triggerd by a creation 26 | func (e *FileEvent) IsCreate() bool { return e.create } 27 | 28 | // IsDelete reports whether the FileEvent was triggerd by a delete 29 | func (e *FileEvent) IsDelete() bool { return (e.mask & NOTE_DELETE) == NOTE_DELETE } 30 | 31 | // IsModify reports whether the FileEvent was triggerd by a file modification 32 | func (e *FileEvent) IsModify() bool { 33 | return ((e.mask&NOTE_WRITE) == NOTE_WRITE || (e.mask&NOTE_ATTRIB) == NOTE_ATTRIB) 34 | } 35 | 36 | // IsRename reports whether the FileEvent was triggerd by a change name 37 | func (e *FileEvent) IsRename() bool { return (e.mask & NOTE_RENAME) == NOTE_RENAME } 38 | 39 | type Watcher struct { 40 | kq int // File descriptor (as returned by the kqueue() syscall) 41 | watches map[string]int // Map of watched file diescriptors (key: path) 42 | fsnFlags map[string]uint32 // Map of watched files to flags used for filter 43 | enFlags map[string]uint32 // Map of watched files to evfilt note flags used in kqueue 44 | paths map[int]string // Map of watched paths (key: watch descriptor) 45 | finfo map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor) 46 | fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events) 47 | Error chan error // Errors are sent on this channel 48 | internalEvent chan *FileEvent // Events are queued on this channel 49 | Event chan *FileEvent // Events are returned on this channel 50 | done chan bool // Channel for sending a "quit message" to the reader goroutine 51 | isClosed bool // Set to true when Close() is first called 52 | kbuf [1]syscall.Kevent_t // An event buffer for Add/Remove watch 53 | } 54 | 55 | // NewWatcher creates and returns a new kevent instance using kqueue(2) 56 | func NewWatcher() (*Watcher, error) { 57 | fd, errno := syscall.Kqueue() 58 | if fd == -1 { 59 | return nil, os.NewSyscallError("kqueue", errno) 60 | } 61 | w := &Watcher{ 62 | kq: fd, 63 | watches: make(map[string]int), 64 | fsnFlags: make(map[string]uint32), 65 | enFlags: make(map[string]uint32), 66 | paths: make(map[int]string), 67 | finfo: make(map[int]os.FileInfo), 68 | fileExists: make(map[string]bool), 69 | internalEvent: make(chan *FileEvent), 70 | Event: make(chan *FileEvent), 71 | Error: make(chan error), 72 | done: make(chan bool, 1), 73 | } 74 | 75 | go w.readEvents() 76 | go w.purgeEvents() 77 | return w, nil 78 | } 79 | 80 | // Close closes a kevent watcher instance 81 | // It sends a message to the reader goroutine to quit and removes all watches 82 | // associated with the kevent instance 83 | func (w *Watcher) Close() error { 84 | if w.isClosed { 85 | return nil 86 | } 87 | w.isClosed = true 88 | 89 | // Send "quit" message to the reader goroutine 90 | w.done <- true 91 | for path := range w.watches { 92 | w.removeWatch(path) 93 | } 94 | 95 | return nil 96 | } 97 | 98 | // AddWatch adds path to the watched file set. 99 | // The flags are interpreted as described in kevent(2). 100 | func (w *Watcher) addWatch(path string, flags uint32) error { 101 | if w.isClosed { 102 | return errors.New("kevent instance already closed") 103 | } 104 | 105 | watchDir := false 106 | 107 | watchfd, found := w.watches[path] 108 | if !found { 109 | fi, errstat := os.Lstat(path) 110 | if errstat != nil { 111 | return errstat 112 | } 113 | 114 | // don't watch socket 115 | if fi.Mode()&os.ModeSocket == os.ModeSocket { 116 | return nil 117 | } 118 | 119 | // Follow Symlinks 120 | // Unfortunately, Linux can add bogus symlinks to watch list without 121 | // issue, and Windows can't do symlinks period (AFAIK). To maintain 122 | // consistency, we will act like everything is fine. There will simply 123 | // be no file events for broken symlinks. 124 | // Hence the returns of nil on errors. 125 | if fi.Mode()&os.ModeSymlink == os.ModeSymlink { 126 | path, err := filepath.EvalSymlinks(path) 127 | if err != nil { 128 | return nil 129 | } 130 | 131 | fi, errstat = os.Lstat(path) 132 | if errstat != nil { 133 | return nil 134 | } 135 | } 136 | 137 | fd, errno := syscall.Open(path, syscall.O_NONBLOCK|syscall.O_RDONLY, 0700) 138 | if fd == -1 { 139 | return errno 140 | } 141 | watchfd = fd 142 | 143 | w.watches[path] = watchfd 144 | w.paths[watchfd] = path 145 | 146 | w.finfo[watchfd] = fi 147 | } 148 | // Watch the directory if it has not been watched before. 149 | if w.finfo[watchfd].IsDir() && 150 | (flags&NOTE_WRITE) == NOTE_WRITE && 151 | (!found || (w.enFlags[path]&NOTE_WRITE) != NOTE_WRITE) { 152 | watchDir = true 153 | } 154 | 155 | w.enFlags[path] = flags 156 | watchEntry := &w.kbuf[0] 157 | watchEntry.Fflags = flags 158 | syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_ADD|syscall.EV_CLEAR) 159 | 160 | wd, errno := syscall.Kevent(w.kq, w.kbuf[:], nil, nil) 161 | if wd == -1 { 162 | return errno 163 | } else if (watchEntry.Flags & syscall.EV_ERROR) == syscall.EV_ERROR { 164 | return errors.New("kevent add error") 165 | } 166 | 167 | if watchDir { 168 | errdir := w.watchDirectoryFiles(path) 169 | if errdir != nil { 170 | return errdir 171 | } 172 | } 173 | return nil 174 | } 175 | 176 | // Watch adds path to the watched file set, watching all events. 177 | func (w *Watcher) watch(path string) error { 178 | return w.addWatch(path, NOTE_ALLEVENTS) 179 | } 180 | 181 | // RemoveWatch removes path from the watched file set. 182 | func (w *Watcher) removeWatch(path string) error { 183 | watchfd, ok := w.watches[path] 184 | if !ok { 185 | return errors.New(fmt.Sprintf("can't remove non-existent kevent watch for: %s", path)) 186 | } 187 | watchEntry := &w.kbuf[0] 188 | syscall.SetKevent(watchEntry, w.watches[path], syscall.EVFILT_VNODE, syscall.EV_DELETE) 189 | success, errno := syscall.Kevent(w.kq, w.kbuf[:], nil, nil) 190 | if success == -1 { 191 | return os.NewSyscallError("kevent_rm_watch", errno) 192 | } else if (watchEntry.Flags & syscall.EV_ERROR) == syscall.EV_ERROR { 193 | return errors.New("kevent rm error") 194 | } 195 | syscall.Close(watchfd) 196 | delete(w.watches, path) 197 | return nil 198 | } 199 | 200 | // readEvents reads from the kqueue file descriptor, converts the 201 | // received events into Event objects and sends them via the Event channel 202 | func (w *Watcher) readEvents() { 203 | var ( 204 | eventbuf [10]syscall.Kevent_t // Event buffer 205 | events []syscall.Kevent_t // Received events 206 | twait *syscall.Timespec // Time to block waiting for events 207 | n int // Number of events returned from kevent 208 | errno error // Syscall errno 209 | ) 210 | events = eventbuf[0:0] 211 | twait = new(syscall.Timespec) 212 | *twait = syscall.NsecToTimespec(keventWaitTime) 213 | 214 | for { 215 | // See if there is a message on the "done" channel 216 | var done bool 217 | select { 218 | case done = <-w.done: 219 | default: 220 | } 221 | 222 | // If "done" message is received 223 | if done { 224 | errno := syscall.Close(w.kq) 225 | if errno != nil { 226 | w.Error <- os.NewSyscallError("close", errno) 227 | } 228 | close(w.internalEvent) 229 | close(w.Error) 230 | return 231 | } 232 | 233 | // Get new events 234 | if len(events) == 0 { 235 | n, errno = syscall.Kevent(w.kq, nil, eventbuf[:], twait) 236 | 237 | // EINTR is okay, basically the syscall was interrupted before 238 | // timeout expired. 239 | if errno != nil && errno != syscall.EINTR { 240 | w.Error <- os.NewSyscallError("kevent", errno) 241 | continue 242 | } 243 | 244 | // Received some events 245 | if n > 0 { 246 | events = eventbuf[0:n] 247 | } 248 | } 249 | 250 | // Flush the events we recieved to the events channel 251 | for len(events) > 0 { 252 | fileEvent := new(FileEvent) 253 | watchEvent := &events[0] 254 | fileEvent.mask = uint32(watchEvent.Fflags) 255 | fileEvent.Name = w.paths[int(watchEvent.Ident)] 256 | 257 | fileInfo := w.finfo[int(watchEvent.Ident)] 258 | if fileInfo.IsDir() && !fileEvent.IsDelete() { 259 | // Double check to make sure the directory exist. This can happen when 260 | // we do a rm -fr on a recursively watched folders and we receive a 261 | // modification event first but the folder has been deleted and later 262 | // receive the delete event 263 | if _, err := os.Lstat(fileEvent.Name); os.IsNotExist(err) { 264 | // mark is as delete event 265 | fileEvent.mask |= NOTE_DELETE 266 | } 267 | } 268 | 269 | if fileInfo.IsDir() && fileEvent.IsModify() && !fileEvent.IsDelete() { 270 | w.sendDirectoryChangeEvents(fileEvent.Name) 271 | } else { 272 | // Send the event on the events channel 273 | w.internalEvent <- fileEvent 274 | } 275 | 276 | // Move to next event 277 | events = events[1:] 278 | 279 | if fileEvent.IsRename() { 280 | w.removeWatch(fileEvent.Name) 281 | delete(w.fileExists, fileEvent.Name) 282 | } 283 | if fileEvent.IsDelete() { 284 | w.removeWatch(fileEvent.Name) 285 | delete(w.fileExists, fileEvent.Name) 286 | 287 | // Look for a file that may have overwritten this 288 | // (ie mv f1 f2 will delete f2 then create f2) 289 | fileDir, _ := filepath.Split(fileEvent.Name) 290 | fileDir = filepath.Clean(fileDir) 291 | if _, found := w.watches[fileDir]; found { 292 | // make sure the directory exist before we watch for changes. When we 293 | // do a recursive watch and perform rm -fr, the parent directory might 294 | // have gone missing, ignore the missing directory and let the 295 | // upcoming delete event remove the watch form the parent folder 296 | if _, err := os.Lstat(fileDir); !os.IsNotExist(err) { 297 | w.sendDirectoryChangeEvents(fileDir) 298 | } 299 | } 300 | } 301 | } 302 | } 303 | } 304 | 305 | func (w *Watcher) watchDirectoryFiles(dirPath string) error { 306 | // Get all files 307 | files, err := ioutil.ReadDir(dirPath) 308 | if err != nil { 309 | return err 310 | } 311 | 312 | // Search for new files 313 | for _, fileInfo := range files { 314 | filePath := filepath.Join(dirPath, fileInfo.Name()) 315 | if fileInfo.IsDir() == false { 316 | // Watch file to mimic linux fsnotify 317 | e := w.addWatch(filePath, NOTE_DELETE|NOTE_WRITE|NOTE_RENAME) 318 | w.fsnFlags[filePath] = FSN_ALL 319 | if e != nil { 320 | return e 321 | } 322 | } else { 323 | // If the user is currently waching directory 324 | // we want to preserve the flags used 325 | currFlags, found := w.enFlags[filePath] 326 | var newFlags uint32 = NOTE_DELETE 327 | if found { 328 | newFlags |= currFlags 329 | } 330 | 331 | // Linux gives deletes if not explicitly watching 332 | e := w.addWatch(filePath, newFlags) 333 | w.fsnFlags[filePath] = FSN_ALL 334 | if e != nil { 335 | return e 336 | } 337 | } 338 | w.fileExists[filePath] = true 339 | } 340 | 341 | return nil 342 | } 343 | 344 | // sendDirectoryEvents searches the directory for newly created files 345 | // and sends them over the event channel. This functionality is to have 346 | // the BSD version of fsnotify mach linux fsnotify which provides a 347 | // create event for files created in a watched directory. 348 | func (w *Watcher) sendDirectoryChangeEvents(dirPath string) { 349 | // Get all files 350 | files, err := ioutil.ReadDir(dirPath) 351 | if err != nil { 352 | w.Error <- err 353 | } 354 | 355 | // Search for new files 356 | for _, fileInfo := range files { 357 | filePath := filepath.Join(dirPath, fileInfo.Name()) 358 | _, doesExist := w.fileExists[filePath] 359 | if doesExist == false { 360 | w.fsnFlags[filePath] = FSN_ALL 361 | // Send create event 362 | fileEvent := new(FileEvent) 363 | fileEvent.Name = filePath 364 | fileEvent.create = true 365 | w.internalEvent <- fileEvent 366 | } 367 | w.fileExists[filePath] = true 368 | } 369 | w.watchDirectoryFiles(dirPath) 370 | } 371 | 372 | const ( 373 | // Flags (from ) 374 | NOTE_DELETE = 0x0001 /* vnode was removed */ 375 | NOTE_WRITE = 0x0002 /* data contents changed */ 376 | NOTE_EXTEND = 0x0004 /* size increased */ 377 | NOTE_ATTRIB = 0x0008 /* attributes changed */ 378 | NOTE_LINK = 0x0010 /* link count changed */ 379 | NOTE_RENAME = 0x0020 /* vnode was renamed */ 380 | NOTE_REVOKE = 0x0040 /* vnode access was revoked */ 381 | 382 | // Watch all events 383 | NOTE_ALLEVENTS = NOTE_DELETE | NOTE_WRITE | NOTE_ATTRIB | NOTE_RENAME 384 | 385 | // Block for 100 ms on each call to kevent 386 | keventWaitTime = 100e6 387 | ) 388 | -------------------------------------------------------------------------------- /src/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 allows the user to receive 8 | // file system event notifications on Windows. 9 | package fsnotify 10 | 11 | import ( 12 | "errors" 13 | "fmt" 14 | "os" 15 | "path/filepath" 16 | "runtime" 17 | "syscall" 18 | "unsafe" 19 | ) 20 | 21 | // Event is the type of the notification messages 22 | // received on the watcher's Event channel. 23 | type FileEvent struct { 24 | mask uint32 // Mask of events 25 | cookie uint32 // Unique cookie associating related events (for rename) 26 | Name string // File name (optional) 27 | } 28 | 29 | // IsCreate reports whether the FileEvent was triggerd by a creation 30 | func (e *FileEvent) IsCreate() bool { return (e.mask & FS_CREATE) == FS_CREATE } 31 | 32 | // IsDelete reports whether the FileEvent was triggerd by a delete 33 | func (e *FileEvent) IsDelete() bool { 34 | return ((e.mask&FS_DELETE) == FS_DELETE || (e.mask&FS_DELETE_SELF) == FS_DELETE_SELF) 35 | } 36 | 37 | // IsModify reports whether the FileEvent was triggerd by a file modification or attribute change 38 | func (e *FileEvent) IsModify() bool { 39 | return ((e.mask&FS_MODIFY) == FS_MODIFY || (e.mask&FS_ATTRIB) == FS_ATTRIB) 40 | } 41 | 42 | // IsRename reports whether the FileEvent was triggerd by a change name 43 | func (e *FileEvent) IsRename() bool { 44 | return ((e.mask&FS_MOVE) == FS_MOVE || (e.mask&FS_MOVE_SELF) == FS_MOVE_SELF || (e.mask&FS_MOVED_FROM) == FS_MOVED_FROM || (e.mask&FS_MOVED_TO) == FS_MOVED_TO) 45 | } 46 | 47 | const ( 48 | opAddWatch = iota 49 | opRemoveWatch 50 | ) 51 | 52 | const ( 53 | provisional uint64 = 1 << (32 + iota) 54 | ) 55 | 56 | type input struct { 57 | op int 58 | path string 59 | flags uint32 60 | reply chan error 61 | } 62 | 63 | type inode struct { 64 | handle syscall.Handle 65 | volume uint32 66 | index uint64 67 | } 68 | 69 | type watch struct { 70 | ov syscall.Overlapped 71 | ino *inode // i-number 72 | path string // Directory path 73 | mask uint64 // Directory itself is being watched with these notify flags 74 | names map[string]uint64 // Map of names being watched and their notify flags 75 | rename string // Remembers the old name while renaming a file 76 | buf [4096]byte 77 | } 78 | 79 | type indexMap map[uint64]*watch 80 | type watchMap map[uint32]indexMap 81 | 82 | // A Watcher waits for and receives event notifications 83 | // for a specific set of files and directories. 84 | type Watcher struct { 85 | port syscall.Handle // Handle to completion port 86 | watches watchMap // Map of watches (key: i-number) 87 | fsnFlags map[string]uint32 // Map of watched files to flags used for filter 88 | input chan *input // Inputs to the reader are sent on this channel 89 | internalEvent chan *FileEvent // Events are queued on this channel 90 | Event chan *FileEvent // Events are returned on this channel 91 | Error chan error // Errors are sent on this channel 92 | isClosed bool // Set to true when Close() is first called 93 | quit chan chan<- error 94 | cookie uint32 95 | } 96 | 97 | // NewWatcher creates and returns a Watcher. 98 | func NewWatcher() (*Watcher, error) { 99 | port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0) 100 | if e != nil { 101 | return nil, os.NewSyscallError("CreateIoCompletionPort", e) 102 | } 103 | w := &Watcher{ 104 | port: port, 105 | watches: make(watchMap), 106 | fsnFlags: make(map[string]uint32), 107 | input: make(chan *input, 1), 108 | Event: make(chan *FileEvent, 50), 109 | internalEvent: make(chan *FileEvent), 110 | Error: make(chan error), 111 | quit: make(chan chan<- error, 1), 112 | } 113 | go w.readEvents() 114 | go w.purgeEvents() 115 | return w, nil 116 | } 117 | 118 | // Close closes a Watcher. 119 | // It sends a message to the reader goroutine to quit and removes all watches 120 | // associated with the watcher. 121 | func (w *Watcher) Close() error { 122 | if w.isClosed { 123 | return nil 124 | } 125 | w.isClosed = true 126 | 127 | // Send "quit" message to the reader goroutine 128 | ch := make(chan error) 129 | w.quit <- ch 130 | if err := w.wakeupReader(); err != nil { 131 | return err 132 | } 133 | return <-ch 134 | } 135 | 136 | // AddWatch adds path to the watched file set. 137 | func (w *Watcher) AddWatch(path string, flags uint32) error { 138 | if w.isClosed { 139 | return errors.New("watcher already closed") 140 | } 141 | in := &input{ 142 | op: opAddWatch, 143 | path: filepath.Clean(path), 144 | flags: flags, 145 | reply: make(chan error), 146 | } 147 | w.input <- in 148 | if err := w.wakeupReader(); err != nil { 149 | return err 150 | } 151 | return <-in.reply 152 | } 153 | 154 | // Watch adds path to the watched file set, watching all events. 155 | func (w *Watcher) watch(path string) error { 156 | return w.AddWatch(path, FS_ALL_EVENTS) 157 | } 158 | 159 | // RemoveWatch removes path from the watched file set. 160 | func (w *Watcher) removeWatch(path string) error { 161 | in := &input{ 162 | op: opRemoveWatch, 163 | path: filepath.Clean(path), 164 | reply: make(chan error), 165 | } 166 | w.input <- in 167 | if err := w.wakeupReader(); err != nil { 168 | return err 169 | } 170 | return <-in.reply 171 | } 172 | 173 | func (w *Watcher) wakeupReader() error { 174 | e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil) 175 | if e != nil { 176 | return os.NewSyscallError("PostQueuedCompletionStatus", e) 177 | } 178 | return nil 179 | } 180 | 181 | func getDir(pathname string) (dir string, err error) { 182 | attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname)) 183 | if e != nil { 184 | return "", os.NewSyscallError("GetFileAttributes", e) 185 | } 186 | if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { 187 | dir = pathname 188 | } else { 189 | dir, _ = filepath.Split(pathname) 190 | dir = filepath.Clean(dir) 191 | } 192 | return 193 | } 194 | 195 | func getIno(path string) (ino *inode, err error) { 196 | h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path), 197 | syscall.FILE_LIST_DIRECTORY, 198 | syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE, 199 | nil, syscall.OPEN_EXISTING, 200 | syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0) 201 | if e != nil { 202 | return nil, os.NewSyscallError("CreateFile", e) 203 | } 204 | var fi syscall.ByHandleFileInformation 205 | if e = syscall.GetFileInformationByHandle(h, &fi); e != nil { 206 | syscall.CloseHandle(h) 207 | return nil, os.NewSyscallError("GetFileInformationByHandle", e) 208 | } 209 | ino = &inode{ 210 | handle: h, 211 | volume: fi.VolumeSerialNumber, 212 | index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow), 213 | } 214 | return ino, nil 215 | } 216 | 217 | // Must run within the I/O thread. 218 | func (m watchMap) get(ino *inode) *watch { 219 | if i := m[ino.volume]; i != nil { 220 | return i[ino.index] 221 | } 222 | return nil 223 | } 224 | 225 | // Must run within the I/O thread. 226 | func (m watchMap) set(ino *inode, watch *watch) { 227 | i := m[ino.volume] 228 | if i == nil { 229 | i = make(indexMap) 230 | m[ino.volume] = i 231 | } 232 | i[ino.index] = watch 233 | } 234 | 235 | // Must run within the I/O thread. 236 | func (w *Watcher) addWatch(pathname string, flags uint64) error { 237 | dir, err := getDir(pathname) 238 | if err != nil { 239 | return err 240 | } 241 | if flags&FS_ONLYDIR != 0 && pathname != dir { 242 | return nil 243 | } 244 | ino, err := getIno(dir) 245 | if err != nil { 246 | return err 247 | } 248 | watchEntry := w.watches.get(ino) 249 | if watchEntry == nil { 250 | if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil { 251 | syscall.CloseHandle(ino.handle) 252 | return os.NewSyscallError("CreateIoCompletionPort", e) 253 | } 254 | watchEntry = &watch{ 255 | ino: ino, 256 | path: dir, 257 | names: make(map[string]uint64), 258 | } 259 | w.watches.set(ino, watchEntry) 260 | flags |= provisional 261 | } else { 262 | syscall.CloseHandle(ino.handle) 263 | } 264 | if pathname == dir { 265 | watchEntry.mask |= flags 266 | } else { 267 | watchEntry.names[filepath.Base(pathname)] |= flags 268 | } 269 | if err = w.startRead(watchEntry); err != nil { 270 | return err 271 | } 272 | if pathname == dir { 273 | watchEntry.mask &= ^provisional 274 | } else { 275 | watchEntry.names[filepath.Base(pathname)] &= ^provisional 276 | } 277 | return nil 278 | } 279 | 280 | // Must run within the I/O thread. 281 | func (w *Watcher) remWatch(pathname string) error { 282 | dir, err := getDir(pathname) 283 | if err != nil { 284 | return err 285 | } 286 | ino, err := getIno(dir) 287 | if err != nil { 288 | return err 289 | } 290 | watch := w.watches.get(ino) 291 | if watch == nil { 292 | return fmt.Errorf("can't remove non-existent watch for: %s", pathname) 293 | } 294 | if pathname == dir { 295 | w.sendEvent(watch.path, watch.mask&FS_IGNORED) 296 | watch.mask = 0 297 | } else { 298 | name := filepath.Base(pathname) 299 | w.sendEvent(watch.path+"/"+name, watch.names[name]&FS_IGNORED) 300 | delete(watch.names, name) 301 | } 302 | return w.startRead(watch) 303 | } 304 | 305 | // Must run within the I/O thread. 306 | func (w *Watcher) deleteWatch(watch *watch) { 307 | for name, mask := range watch.names { 308 | if mask&provisional == 0 { 309 | w.sendEvent(watch.path+"/"+name, mask&FS_IGNORED) 310 | } 311 | delete(watch.names, name) 312 | } 313 | if watch.mask != 0 { 314 | if watch.mask&provisional == 0 { 315 | w.sendEvent(watch.path, watch.mask&FS_IGNORED) 316 | } 317 | watch.mask = 0 318 | } 319 | } 320 | 321 | // Must run within the I/O thread. 322 | func (w *Watcher) startRead(watch *watch) error { 323 | if e := syscall.CancelIo(watch.ino.handle); e != nil { 324 | w.Error <- os.NewSyscallError("CancelIo", e) 325 | w.deleteWatch(watch) 326 | } 327 | mask := toWindowsFlags(watch.mask) 328 | for _, m := range watch.names { 329 | mask |= toWindowsFlags(m) 330 | } 331 | if mask == 0 { 332 | if e := syscall.CloseHandle(watch.ino.handle); e != nil { 333 | w.Error <- os.NewSyscallError("CloseHandle", e) 334 | } 335 | delete(w.watches[watch.ino.volume], watch.ino.index) 336 | return nil 337 | } 338 | e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0], 339 | uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0) 340 | if e != nil { 341 | err := os.NewSyscallError("ReadDirectoryChanges", e) 342 | if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 { 343 | // Watched directory was probably removed 344 | if w.sendEvent(watch.path, watch.mask&FS_DELETE_SELF) { 345 | if watch.mask&FS_ONESHOT != 0 { 346 | watch.mask = 0 347 | } 348 | } 349 | err = nil 350 | } 351 | w.deleteWatch(watch) 352 | w.startRead(watch) 353 | return err 354 | } 355 | return nil 356 | } 357 | 358 | // readEvents reads from the I/O completion port, converts the 359 | // received events into Event objects and sends them via the Event channel. 360 | // Entry point to the I/O thread. 361 | func (w *Watcher) readEvents() { 362 | var ( 363 | n, key uint32 364 | ov *syscall.Overlapped 365 | ) 366 | runtime.LockOSThread() 367 | 368 | for { 369 | e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE) 370 | watch := (*watch)(unsafe.Pointer(ov)) 371 | 372 | if watch == nil { 373 | select { 374 | case ch := <-w.quit: 375 | for _, index := range w.watches { 376 | for _, watch := range index { 377 | w.deleteWatch(watch) 378 | w.startRead(watch) 379 | } 380 | } 381 | var err error 382 | if e := syscall.CloseHandle(w.port); e != nil { 383 | err = os.NewSyscallError("CloseHandle", e) 384 | } 385 | close(w.internalEvent) 386 | close(w.Error) 387 | ch <- err 388 | return 389 | case in := <-w.input: 390 | switch in.op { 391 | case opAddWatch: 392 | in.reply <- w.addWatch(in.path, uint64(in.flags)) 393 | case opRemoveWatch: 394 | in.reply <- w.remWatch(in.path) 395 | } 396 | default: 397 | } 398 | continue 399 | } 400 | 401 | switch e { 402 | case syscall.ERROR_ACCESS_DENIED: 403 | // Watched directory was probably removed 404 | w.sendEvent(watch.path, watch.mask&FS_DELETE_SELF) 405 | w.deleteWatch(watch) 406 | w.startRead(watch) 407 | continue 408 | case syscall.ERROR_OPERATION_ABORTED: 409 | // CancelIo was called on this handle 410 | continue 411 | default: 412 | w.Error <- os.NewSyscallError("GetQueuedCompletionPort", e) 413 | continue 414 | case nil: 415 | } 416 | 417 | var offset uint32 418 | for { 419 | if n == 0 { 420 | w.internalEvent <- &FileEvent{mask: FS_Q_OVERFLOW} 421 | w.Error <- errors.New("short read in readEvents()") 422 | break 423 | } 424 | 425 | // Point "raw" to the event in the buffer 426 | raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset])) 427 | buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName)) 428 | name := syscall.UTF16ToString(buf[:raw.FileNameLength/2]) 429 | fullname := watch.path + "/" + name 430 | 431 | var mask uint64 432 | switch raw.Action { 433 | case syscall.FILE_ACTION_REMOVED: 434 | mask = FS_DELETE_SELF 435 | case syscall.FILE_ACTION_MODIFIED: 436 | mask = FS_MODIFY 437 | case syscall.FILE_ACTION_RENAMED_OLD_NAME: 438 | watch.rename = name 439 | case syscall.FILE_ACTION_RENAMED_NEW_NAME: 440 | if watch.names[watch.rename] != 0 { 441 | watch.names[name] |= watch.names[watch.rename] 442 | delete(watch.names, watch.rename) 443 | mask = FS_MOVE_SELF 444 | } 445 | } 446 | 447 | sendNameEvent := func() { 448 | if w.sendEvent(fullname, watch.names[name]&mask) { 449 | if watch.names[name]&FS_ONESHOT != 0 { 450 | delete(watch.names, name) 451 | } 452 | } 453 | } 454 | if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME { 455 | sendNameEvent() 456 | } 457 | if raw.Action == syscall.FILE_ACTION_REMOVED { 458 | w.sendEvent(fullname, watch.names[name]&FS_IGNORED) 459 | delete(watch.names, name) 460 | } 461 | if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) { 462 | if watch.mask&FS_ONESHOT != 0 { 463 | watch.mask = 0 464 | } 465 | } 466 | if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME { 467 | fullname = watch.path + "/" + watch.rename 468 | sendNameEvent() 469 | } 470 | 471 | // Move to the next event in the buffer 472 | if raw.NextEntryOffset == 0 { 473 | break 474 | } 475 | offset += raw.NextEntryOffset 476 | } 477 | 478 | if err := w.startRead(watch); err != nil { 479 | w.Error <- err 480 | } 481 | } 482 | } 483 | 484 | func (w *Watcher) sendEvent(name string, mask uint64) bool { 485 | if mask == 0 { 486 | return false 487 | } 488 | event := &FileEvent{mask: uint32(mask), Name: name} 489 | if mask&FS_MOVE != 0 { 490 | if mask&FS_MOVED_FROM != 0 { 491 | w.cookie++ 492 | } 493 | event.cookie = w.cookie 494 | } 495 | select { 496 | case ch := <-w.quit: 497 | w.quit <- ch 498 | case w.Event <- event: 499 | } 500 | return true 501 | } 502 | 503 | func toWindowsFlags(mask uint64) uint32 { 504 | var m uint32 505 | if mask&FS_ACCESS != 0 { 506 | m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS 507 | } 508 | if mask&FS_MODIFY != 0 { 509 | m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE 510 | } 511 | if mask&FS_ATTRIB != 0 { 512 | m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES 513 | } 514 | if mask&(FS_MOVE|FS_CREATE|FS_DELETE) != 0 { 515 | m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME 516 | } 517 | return m 518 | } 519 | 520 | func toFSnotifyFlags(action uint32) uint64 { 521 | switch action { 522 | case syscall.FILE_ACTION_ADDED: 523 | return FS_CREATE 524 | case syscall.FILE_ACTION_REMOVED: 525 | return FS_DELETE 526 | case syscall.FILE_ACTION_MODIFIED: 527 | return FS_MODIFY 528 | case syscall.FILE_ACTION_RENAMED_OLD_NAME: 529 | return FS_MOVED_FROM 530 | case syscall.FILE_ACTION_RENAMED_NEW_NAME: 531 | return FS_MOVED_TO 532 | } 533 | return 0 534 | } 535 | 536 | const ( 537 | // Options for AddWatch 538 | FS_ONESHOT = 0x80000000 539 | FS_ONLYDIR = 0x1000000 540 | 541 | // Events 542 | FS_ACCESS = 0x1 543 | FS_ALL_EVENTS = 0xfff 544 | FS_ATTRIB = 0x4 545 | FS_CLOSE = 0x18 546 | FS_CREATE = 0x100 547 | FS_DELETE = 0x200 548 | FS_DELETE_SELF = 0x400 549 | FS_MODIFY = 0x2 550 | FS_MOVE = 0xc0 551 | FS_MOVED_FROM = 0x40 552 | FS_MOVED_TO = 0x80 553 | FS_MOVE_SELF = 0x800 554 | 555 | // Special events 556 | FS_IGNORED = 0x8000 557 | FS_Q_OVERFLOW = 0x4000 558 | ) 559 | 560 | var eventBits = []struct { 561 | Value uint32 562 | Name string 563 | }{ 564 | {FS_ACCESS, "FS_ACCESS"}, 565 | {FS_ATTRIB, "FS_ATTRIB"}, 566 | {FS_CREATE, "FS_CREATE"}, 567 | {FS_DELETE, "FS_DELETE"}, 568 | {FS_DELETE_SELF, "FS_DELETE_SELF"}, 569 | {FS_MODIFY, "FS_MODIFY"}, 570 | {FS_MOVED_FROM, "FS_MOVED_FROM"}, 571 | {FS_MOVED_TO, "FS_MOVED_TO"}, 572 | {FS_MOVE_SELF, "FS_MOVE_SELF"}, 573 | {FS_IGNORED, "FS_IGNORED"}, 574 | {FS_Q_OVERFLOW, "FS_Q_OVERFLOW"}, 575 | } 576 | -------------------------------------------------------------------------------- /src/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 | "os" 9 | "os/exec" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | func TestFsnotifyMultipleOperations(t *testing.T) { 15 | // Create an fsnotify watcher instance and initialize it 16 | watcher, err := NewWatcher() 17 | if err != nil { 18 | t.Fatalf("NewWatcher() failed: %s", err) 19 | } 20 | 21 | // Receive errors on the error channel on a separate goroutine 22 | go func() { 23 | for err := range watcher.Error { 24 | t.Fatalf("error received: %s", err) 25 | } 26 | }() 27 | 28 | const testDir string = "_test" 29 | const testDirToMoveFiles string = "_test2" 30 | 31 | // Create directory to watch 32 | if os.Mkdir(testDir, 0777) != nil { 33 | t.Fatalf("Failed to create test directory: %s", err) 34 | } 35 | defer os.RemoveAll(testDir) 36 | 37 | // Create directory to that's not watched 38 | if os.Mkdir(testDirToMoveFiles, 0777) != nil { 39 | t.Fatalf("Failed to create test directory: %s", err) 40 | } 41 | defer os.RemoveAll(testDirToMoveFiles) 42 | 43 | const testFile string = "_test/TestFsnotifySeq.testfile" 44 | const testFileRenamed string = "_test2/TestFsnotifySeqRename.testfile" 45 | 46 | // Add a watch for testDir 47 | err = watcher.Watch(testDir) 48 | if err != nil { 49 | t.Fatalf("Watcher.Watch() failed: %s", err) 50 | } 51 | // Receive events on the event channel on a separate goroutine 52 | eventstream := watcher.Event 53 | var createReceived = 0 54 | var modifyReceived = 0 55 | var deleteReceived = 0 56 | var renameReceived = 0 57 | done := make(chan bool) 58 | go func() { 59 | for event := range eventstream { 60 | // Only count relevant events 61 | if event.Name == testDir || event.Name == testFile { 62 | t.Logf("event received: %s", event) 63 | if event.IsDelete() { 64 | deleteReceived++ 65 | } 66 | if event.IsModify() { 67 | modifyReceived++ 68 | } 69 | if event.IsCreate() { 70 | createReceived++ 71 | } 72 | if event.IsRename() { 73 | renameReceived++ 74 | } 75 | } else { 76 | t.Logf("unexpected event received: %s", event) 77 | } 78 | } 79 | done <- true 80 | }() 81 | 82 | // Create a file 83 | // This should add at least one event to the fsnotify event queue 84 | var f *os.File 85 | f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 86 | if err != nil { 87 | t.Fatalf("creating test file failed: %s", err) 88 | } 89 | f.Sync() 90 | 91 | time.Sleep(time.Millisecond) 92 | f.WriteString("data") 93 | f.Sync() 94 | f.Close() 95 | 96 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 97 | 98 | cmd := exec.Command("mv", testFile, testFileRenamed) 99 | err = cmd.Run() 100 | if err != nil { 101 | t.Fatalf("rename failed: %s", err) 102 | } 103 | 104 | // Modify the file outside of the watched dir 105 | f, err = os.Open(testFileRenamed) 106 | if err != nil { 107 | t.Fatalf("open test renamed file failed: %s", err) 108 | } 109 | f.WriteString("data") 110 | f.Sync() 111 | f.Close() 112 | 113 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 114 | 115 | // Recreate the file that was moved 116 | f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 117 | if err != nil { 118 | t.Fatalf("creating test file failed: %s", err) 119 | } 120 | f.Close() 121 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 122 | 123 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 124 | time.Sleep(500 * time.Millisecond) 125 | if createReceived != 2 { 126 | t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", createReceived, 2) 127 | } 128 | if modifyReceived != 1 { 129 | t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", modifyReceived, 1) 130 | } 131 | if deleteReceived+renameReceived != 1 { 132 | t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", renameReceived+deleteReceived, 1) 133 | } 134 | 135 | // Try closing the fsnotify instance 136 | t.Log("calling Close()") 137 | watcher.Close() 138 | t.Log("waiting for the event channel to become closed...") 139 | select { 140 | case <-done: 141 | t.Log("event channel closed") 142 | case <-time.After(2 * time.Second): 143 | t.Fatal("event stream was not closed after 2 seconds") 144 | } 145 | } 146 | 147 | func TestFsnotifyMultipleCreates(t *testing.T) { 148 | // Create an fsnotify watcher instance and initialize it 149 | watcher, err := NewWatcher() 150 | if err != nil { 151 | t.Fatalf("NewWatcher() failed: %s", err) 152 | } 153 | 154 | // Receive errors on the error channel on a separate goroutine 155 | go func() { 156 | for err := range watcher.Error { 157 | t.Fatalf("error received: %s", err) 158 | } 159 | }() 160 | 161 | const testDir string = "_test" 162 | 163 | // Create directory to watch 164 | if os.Mkdir(testDir, 0777) != nil { 165 | t.Fatalf("Failed to create test directory: %s", err) 166 | } 167 | defer os.RemoveAll(testDir) 168 | 169 | const testFile string = "_test/TestFsnotifySeq.testfile" 170 | 171 | // Add a watch for testDir 172 | err = watcher.Watch(testDir) 173 | if err != nil { 174 | t.Fatalf("Watcher.Watch() failed: %s", err) 175 | } 176 | // Receive events on the event channel on a separate goroutine 177 | eventstream := watcher.Event 178 | var createReceived = 0 179 | var modifyReceived = 0 180 | var deleteReceived = 0 181 | done := make(chan bool) 182 | go func() { 183 | for event := range eventstream { 184 | // Only count relevant events 185 | if event.Name == testDir || event.Name == testFile { 186 | t.Logf("event received: %s", event) 187 | if event.IsDelete() { 188 | deleteReceived++ 189 | } 190 | if event.IsCreate() { 191 | createReceived++ 192 | } 193 | if event.IsModify() { 194 | modifyReceived++ 195 | } 196 | } else { 197 | t.Logf("unexpected event received: %s", event) 198 | } 199 | } 200 | done <- true 201 | }() 202 | 203 | // Create a file 204 | // This should add at least one event to the fsnotify event queue 205 | var f *os.File 206 | f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 207 | if err != nil { 208 | t.Fatalf("creating test file failed: %s", err) 209 | } 210 | f.Sync() 211 | 212 | time.Sleep(time.Millisecond) 213 | f.WriteString("data") 214 | f.Sync() 215 | f.Close() 216 | 217 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 218 | 219 | os.Remove(testFile) 220 | 221 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 222 | 223 | // Recreate the 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.Close() 229 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 230 | 231 | // Modify 232 | f, err = os.OpenFile(testFile, os.O_WRONLY, 0666) 233 | if err != nil { 234 | t.Fatalf("creating test file failed: %s", err) 235 | } 236 | f.Sync() 237 | 238 | time.Sleep(time.Millisecond) 239 | f.WriteString("data") 240 | f.Sync() 241 | f.Close() 242 | 243 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 244 | 245 | // Modify 246 | f, err = os.OpenFile(testFile, os.O_WRONLY, 0666) 247 | if err != nil { 248 | t.Fatalf("creating test file failed: %s", err) 249 | } 250 | f.Sync() 251 | 252 | time.Sleep(time.Millisecond) 253 | f.WriteString("data") 254 | f.Sync() 255 | f.Close() 256 | 257 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 258 | 259 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 260 | time.Sleep(500 * time.Millisecond) 261 | if createReceived != 2 { 262 | t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", createReceived, 2) 263 | } 264 | if modifyReceived != 3 { 265 | t.Fatalf("incorrect number of modify events received after 500 ms (%d vs atleast %d)", modifyReceived, 3) 266 | } 267 | if deleteReceived != 1 { 268 | t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", deleteReceived, 1) 269 | } 270 | 271 | // Try closing the fsnotify instance 272 | t.Log("calling Close()") 273 | watcher.Close() 274 | t.Log("waiting for the event channel to become closed...") 275 | select { 276 | case <-done: 277 | t.Log("event channel closed") 278 | case <-time.After(2 * time.Second): 279 | t.Fatal("event stream was not closed after 2 seconds") 280 | } 281 | } 282 | 283 | func TestFsnotifyDirOnly(t *testing.T) { 284 | // Create an fsnotify watcher instance and initialize it 285 | watcher, err := NewWatcher() 286 | if err != nil { 287 | t.Fatalf("NewWatcher() failed: %s", err) 288 | } 289 | 290 | const testDir string = "_test" 291 | 292 | // Create directory to watch 293 | if os.Mkdir(testDir, 0777) != nil { 294 | t.Fatalf("Failed to create test directory: %s", err) 295 | } 296 | defer os.RemoveAll(testDir) 297 | 298 | // Create a file before watching directory 299 | // This should NOT add any events to the fsnotify event queue 300 | const testFileAlreadyExists string = "_test/TestFsnotifyEventsExisting.testfile" 301 | { 302 | var f *os.File 303 | f, err = os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666) 304 | if err != nil { 305 | t.Fatalf("creating test file failed: %s", err) 306 | } 307 | f.Sync() 308 | f.Close() 309 | } 310 | 311 | // Add a watch for testDir 312 | err = watcher.Watch(testDir) 313 | if err != nil { 314 | t.Fatalf("Watcher.Watch() failed: %s", err) 315 | } 316 | 317 | // Receive errors on the error channel on a separate goroutine 318 | go func() { 319 | for err := range watcher.Error { 320 | t.Fatalf("error received: %s", err) 321 | } 322 | }() 323 | 324 | const testFile string = "_test/TestFsnotifyDirOnly.testfile" 325 | 326 | // Receive events on the event channel on a separate goroutine 327 | eventstream := watcher.Event 328 | var createReceived = 0 329 | var modifyReceived = 0 330 | var deleteReceived = 0 331 | done := make(chan bool) 332 | go func() { 333 | for event := range eventstream { 334 | // Only count relevant events 335 | if event.Name == testDir || event.Name == testFile || event.Name == testFileAlreadyExists { 336 | t.Logf("event received: %s", event) 337 | if event.IsDelete() { 338 | deleteReceived++ 339 | } 340 | if event.IsModify() { 341 | modifyReceived++ 342 | } 343 | if event.IsCreate() { 344 | createReceived++ 345 | } 346 | } else { 347 | t.Logf("unexpected event received: %s", event) 348 | } 349 | } 350 | done <- true 351 | }() 352 | 353 | // Create a file 354 | // This should add at least one event to the fsnotify event queue 355 | var f *os.File 356 | f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 357 | if err != nil { 358 | t.Fatalf("creating test file failed: %s", err) 359 | } 360 | f.Sync() 361 | 362 | time.Sleep(time.Millisecond) 363 | f.WriteString("data") 364 | f.Sync() 365 | f.Close() 366 | 367 | time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete 368 | 369 | os.Remove(testFile) 370 | os.Remove(testFileAlreadyExists) 371 | 372 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 373 | time.Sleep(500 * time.Millisecond) 374 | if createReceived != 1 { 375 | t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", createReceived, 1) 376 | } 377 | if modifyReceived != 1 { 378 | t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", modifyReceived, 1) 379 | } 380 | if deleteReceived != 2 { 381 | t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", deleteReceived, 2) 382 | } 383 | 384 | // Try closing the fsnotify instance 385 | t.Log("calling Close()") 386 | watcher.Close() 387 | t.Log("waiting for the event channel to become closed...") 388 | select { 389 | case <-done: 390 | t.Log("event channel closed") 391 | case <-time.After(2 * time.Second): 392 | t.Fatal("event stream was not closed after 2 seconds") 393 | } 394 | } 395 | 396 | func TestFsnotifyDeleteWatchedDir(t *testing.T) { 397 | // Create an fsnotify watcher instance and initialize it 398 | watcher, err := NewWatcher() 399 | if err != nil { 400 | t.Fatalf("NewWatcher() failed: %s", err) 401 | } 402 | defer watcher.Close() 403 | 404 | const testDir string = "_test" 405 | 406 | // Create directory to watch 407 | if os.Mkdir(testDir, 0777) != nil { 408 | t.Fatalf("Failed to create test directory: %s", err) 409 | } 410 | 411 | // Create a file before watching directory 412 | const testFileAlreadyExists string = "_test/TestFsnotifyEventsExisting.testfile" 413 | { 414 | var f *os.File 415 | f, err = os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666) 416 | if err != nil { 417 | t.Fatalf("creating test file failed: %s", err) 418 | } 419 | f.Sync() 420 | f.Close() 421 | } 422 | 423 | // Add a watch for testDir 424 | err = watcher.Watch(testDir) 425 | if err != nil { 426 | t.Fatalf("Watcher.Watch() failed: %s", err) 427 | } 428 | 429 | // Add a watch for testFile 430 | err = watcher.Watch(testFileAlreadyExists) 431 | if err != nil { 432 | t.Fatalf("Watcher.Watch() failed: %s", err) 433 | } 434 | 435 | // Receive errors on the error channel on a separate goroutine 436 | go func() { 437 | for err := range watcher.Error { 438 | t.Fatalf("error received: %s", err) 439 | } 440 | }() 441 | 442 | // Receive events on the event channel on a separate goroutine 443 | eventstream := watcher.Event 444 | var deleteReceived = 0 445 | go func() { 446 | for event := range eventstream { 447 | // Only count relevant events 448 | if event.Name == testDir || event.Name == testFileAlreadyExists { 449 | t.Logf("event received: %s", event) 450 | if event.IsDelete() { 451 | deleteReceived++ 452 | } 453 | } else { 454 | t.Logf("unexpected event received: %s", event) 455 | } 456 | } 457 | }() 458 | 459 | os.RemoveAll(testDir) 460 | 461 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 462 | time.Sleep(500 * time.Millisecond) 463 | if deleteReceived < 2 { 464 | t.Fatalf("did not receive at least %d delete events, received %d after 500 ms", 2, deleteReceived) 465 | } 466 | } 467 | 468 | func TestFsnotifySubDir(t *testing.T) { 469 | // Create an fsnotify watcher instance and initialize it 470 | watcher, err := NewWatcher() 471 | if err != nil { 472 | t.Fatalf("NewWatcher() failed: %s", err) 473 | } 474 | 475 | const testDir string = "_test" 476 | const testFile1 string = "_test/TestFsnotifyFile1.testfile" 477 | const testSubDir string = "_test/sub" 478 | const testSubDirFile string = "_test/sub/TestFsnotifyFile1.testfile" 479 | 480 | // Create directory to watch 481 | if os.Mkdir(testDir, 0777) != nil { 482 | t.Fatalf("Failed to create test directory: %s", err) 483 | } 484 | defer os.RemoveAll(testDir) 485 | 486 | // Receive errors on the error channel on a separate goroutine 487 | go func() { 488 | for err := range watcher.Error { 489 | t.Fatalf("error received: %s", err) 490 | } 491 | }() 492 | 493 | // Receive events on the event channel on a separate goroutine 494 | eventstream := watcher.Event 495 | var createReceived = 0 496 | var deleteReceived = 0 497 | done := make(chan bool) 498 | go func() { 499 | for event := range eventstream { 500 | // Only count relevant events 501 | if event.Name == testDir || event.Name == testSubDir || event.Name == testFile1 { 502 | t.Logf("event received: %s", event) 503 | if event.IsCreate() { 504 | createReceived++ 505 | } 506 | if event.IsDelete() { 507 | deleteReceived++ 508 | } 509 | } else { 510 | t.Logf("unexpected event received: %s", event) 511 | } 512 | } 513 | done <- true 514 | }() 515 | 516 | // Add a watch for testDir 517 | err = watcher.Watch(testDir) 518 | if err != nil { 519 | t.Fatalf("Watcher.Watch() failed: %s", err) 520 | } 521 | 522 | // Create sub-directory 523 | if os.Mkdir(testSubDir, 0777) != nil { 524 | t.Fatalf("Failed to create test sub-directory: %s", err) 525 | } 526 | 527 | // Create a file 528 | var f *os.File 529 | f, err = os.OpenFile(testFile1, os.O_WRONLY|os.O_CREATE, 0666) 530 | if err != nil { 531 | t.Fatalf("creating test file failed: %s", err) 532 | } 533 | f.Sync() 534 | f.Close() 535 | 536 | // Create a file (Should not see this! we are not watching subdir) 537 | var fs *os.File 538 | fs, err = os.OpenFile(testSubDirFile, os.O_WRONLY|os.O_CREATE, 0666) 539 | if err != nil { 540 | t.Fatalf("creating test file failed: %s", err) 541 | } 542 | fs.Sync() 543 | fs.Close() 544 | 545 | // Make sure receive deletes for both file and sub-directory 546 | os.RemoveAll(testSubDir) 547 | os.Remove(testFile1) 548 | 549 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 550 | time.Sleep(500 * time.Millisecond) 551 | if createReceived != 2 { 552 | t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", createReceived, 2) 553 | } 554 | if deleteReceived != 2 { 555 | t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", deleteReceived, 2) 556 | } 557 | 558 | // Try closing the fsnotify instance 559 | t.Log("calling Close()") 560 | watcher.Close() 561 | t.Log("waiting for the event channel to become closed...") 562 | select { 563 | case <-done: 564 | t.Log("event channel closed") 565 | case <-time.After(2 * time.Second): 566 | t.Fatal("event stream was not closed after 2 seconds") 567 | } 568 | } 569 | 570 | func TestFsnotifyRename(t *testing.T) { 571 | // Create an fsnotify watcher instance and initialize it 572 | watcher, err := NewWatcher() 573 | if err != nil { 574 | t.Fatalf("NewWatcher() failed: %s", err) 575 | } 576 | 577 | const testDir string = "_test" 578 | 579 | // Create directory to watch 580 | if os.Mkdir(testDir, 0777) != nil { 581 | t.Fatalf("Failed to create test directory: %s", err) 582 | } 583 | defer os.RemoveAll(testDir) 584 | 585 | // Add a watch for testDir 586 | err = watcher.Watch(testDir) 587 | if err != nil { 588 | t.Fatalf("Watcher.Watch() failed: %s", err) 589 | } 590 | 591 | // Receive errors on the error channel on a separate goroutine 592 | go func() { 593 | for err := range watcher.Error { 594 | t.Fatalf("error received: %s", err) 595 | } 596 | }() 597 | 598 | const testFile string = "_test/TestFsnotifyEvents.testfile" 599 | const testFileRenamed string = "_test/TestFsnotifyEvents.testfileRenamed" 600 | 601 | // Receive events on the event channel on a separate goroutine 602 | eventstream := watcher.Event 603 | var renameReceived = 0 604 | done := make(chan bool) 605 | go func() { 606 | for event := range eventstream { 607 | // Only count relevant events 608 | if event.Name == testDir || event.Name == testFile || event.Name == testFileRenamed { 609 | if event.IsRename() { 610 | renameReceived++ 611 | } 612 | t.Logf("event received: %s", event) 613 | } else { 614 | t.Logf("unexpected event received: %s", event) 615 | } 616 | } 617 | done <- true 618 | }() 619 | 620 | // Create a file 621 | // This should add at least one event to the fsnotify event queue 622 | var f *os.File 623 | f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 624 | if err != nil { 625 | t.Fatalf("creating test file failed: %s", err) 626 | } 627 | f.Sync() 628 | 629 | f.WriteString("data") 630 | f.Sync() 631 | f.Close() 632 | 633 | // Add a watch for testFile 634 | err = watcher.Watch(testFile) 635 | if err != nil { 636 | t.Fatalf("Watcher.Watch() failed: %s", err) 637 | } 638 | 639 | cmd := exec.Command("mv", testFile, testFileRenamed) 640 | err = cmd.Run() 641 | if err != nil { 642 | t.Fatalf("rename failed: %s", err) 643 | } 644 | 645 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 646 | time.Sleep(500 * time.Millisecond) 647 | if renameReceived == 0 { 648 | t.Fatal("fsnotify rename events have not been received after 500 ms") 649 | } 650 | 651 | // Try closing the fsnotify instance 652 | t.Log("calling Close()") 653 | watcher.Close() 654 | t.Log("waiting for the event channel to become closed...") 655 | select { 656 | case <-done: 657 | t.Log("event channel closed") 658 | case <-time.After(2 * time.Second): 659 | t.Fatal("event stream was not closed after 2 seconds") 660 | } 661 | 662 | os.Remove(testFileRenamed) 663 | } 664 | 665 | func TestFsnotifyRenameToCreate(t *testing.T) { 666 | // Create an fsnotify watcher instance and initialize it 667 | watcher, err := NewWatcher() 668 | if err != nil { 669 | t.Fatalf("NewWatcher() failed: %s", err) 670 | } 671 | 672 | const testDir string = "_test" 673 | const testDirFrom string = "_testfrom" 674 | 675 | // Create directory to watch 676 | if os.Mkdir(testDir, 0777) != nil { 677 | t.Fatalf("Failed to create test directory: %s", err) 678 | } 679 | defer os.RemoveAll(testDir) 680 | 681 | // Create directory to get file 682 | if os.Mkdir(testDirFrom, 0777) != nil { 683 | t.Fatalf("Failed to create test directory: %s", err) 684 | } 685 | defer os.RemoveAll(testDirFrom) 686 | 687 | // Add a watch for testDir 688 | err = watcher.Watch(testDir) 689 | if err != nil { 690 | t.Fatalf("Watcher.Watch() failed: %s", err) 691 | } 692 | 693 | // Receive errors on the error channel on a separate goroutine 694 | go func() { 695 | for err := range watcher.Error { 696 | t.Fatalf("error received: %s", err) 697 | } 698 | }() 699 | 700 | const testFile string = "_testfrom/TestFsnotifyEvents.testfile" 701 | const testFileRenamed string = "_test/TestFsnotifyEvents.testfileRenamed" 702 | 703 | // Receive events on the event channel on a separate goroutine 704 | eventstream := watcher.Event 705 | var createReceived = 0 706 | done := make(chan bool) 707 | go func() { 708 | for event := range eventstream { 709 | // Only count relevant events 710 | if event.Name == testDir || event.Name == testFile || event.Name == testFileRenamed { 711 | if event.IsCreate() { 712 | createReceived++ 713 | } 714 | t.Logf("event received: %s", event) 715 | } else { 716 | t.Logf("unexpected event received: %s", event) 717 | } 718 | } 719 | done <- true 720 | }() 721 | 722 | // Create a file 723 | // This should add at least one event to the fsnotify event queue 724 | var f *os.File 725 | f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 726 | if err != nil { 727 | t.Fatalf("creating test file failed: %s", err) 728 | } 729 | f.Sync() 730 | f.Close() 731 | 732 | cmd := exec.Command("mv", testFile, testFileRenamed) 733 | err = cmd.Run() 734 | if err != nil { 735 | t.Fatalf("rename failed: %s", err) 736 | } 737 | 738 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 739 | time.Sleep(500 * time.Millisecond) 740 | if createReceived == 0 { 741 | t.Fatal("fsnotify create events have not been received after 500 ms") 742 | } 743 | 744 | // Try closing the fsnotify instance 745 | t.Log("calling Close()") 746 | watcher.Close() 747 | t.Log("waiting for the event channel to become closed...") 748 | select { 749 | case <-done: 750 | t.Log("event channel closed") 751 | case <-time.After(2 * time.Second): 752 | t.Fatal("event stream was not closed after 2 seconds") 753 | } 754 | 755 | os.Remove(testFileRenamed) 756 | } 757 | 758 | func TestFsnotifyRenameToOverwrite(t *testing.T) { 759 | // Create an fsnotify watcher instance and initialize it 760 | watcher, err := NewWatcher() 761 | if err != nil { 762 | t.Fatalf("NewWatcher() failed: %s", err) 763 | } 764 | 765 | const testDir string = "_test" 766 | const testDirFrom string = "_testfrom" 767 | 768 | const testFile string = "_testfrom/TestFsnotifyEvents.testfile" 769 | const testFileRenamed string = "_test/TestFsnotifyEvents.testfileRenamed" 770 | 771 | // Create directory to watch 772 | if os.Mkdir(testDir, 0777) != nil { 773 | t.Fatalf("Failed to create test directory: %s", err) 774 | } 775 | defer os.RemoveAll(testDir) 776 | 777 | // Create directory to get file 778 | if os.Mkdir(testDirFrom, 0777) != nil { 779 | t.Fatalf("Failed to create test directory: %s", err) 780 | } 781 | defer os.RemoveAll(testDirFrom) 782 | 783 | // Create a file 784 | var fr *os.File 785 | fr, err = os.OpenFile(testFileRenamed, os.O_WRONLY|os.O_CREATE, 0666) 786 | if err != nil { 787 | t.Fatalf("creating test file failed: %s", err) 788 | } 789 | fr.Sync() 790 | fr.Close() 791 | 792 | // Add a watch for testDir 793 | err = watcher.Watch(testDir) 794 | if err != nil { 795 | t.Fatalf("Watcher.Watch() failed: %s", err) 796 | } 797 | 798 | // Receive errors on the error channel on a separate goroutine 799 | go func() { 800 | for err := range watcher.Error { 801 | t.Fatalf("error received: %s", err) 802 | } 803 | }() 804 | 805 | // Receive events on the event channel on a separate goroutine 806 | eventstream := watcher.Event 807 | var eventReceived = 0 808 | done := make(chan bool) 809 | go func() { 810 | for event := range eventstream { 811 | // Only count relevant events 812 | if event.Name == testFileRenamed { 813 | eventReceived++ 814 | t.Logf("event received: %s", event) 815 | } else { 816 | t.Logf("unexpected event received: %s", event) 817 | } 818 | } 819 | done <- true 820 | }() 821 | 822 | // Create a file 823 | // This should add at least one event to the fsnotify event queue 824 | var f *os.File 825 | f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 826 | if err != nil { 827 | t.Fatalf("creating test file failed: %s", err) 828 | } 829 | f.Sync() 830 | f.Close() 831 | 832 | cmd := exec.Command("mv", testFile, testFileRenamed) 833 | err = cmd.Run() 834 | if err != nil { 835 | t.Fatalf("rename failed: %s", err) 836 | } 837 | 838 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 839 | time.Sleep(500 * time.Millisecond) 840 | if eventReceived == 0 { 841 | t.Fatal("fsnotify events have not been received after 500 ms") 842 | } 843 | 844 | // Try closing the fsnotify instance 845 | t.Log("calling Close()") 846 | watcher.Close() 847 | t.Log("waiting for the event channel to become closed...") 848 | select { 849 | case <-done: 850 | t.Log("event channel closed") 851 | case <-time.After(2 * time.Second): 852 | t.Fatal("event stream was not closed after 2 seconds") 853 | } 854 | 855 | os.Remove(testFileRenamed) 856 | } 857 | 858 | func TestFsnotifyAttrib(t *testing.T) { 859 | // Create an fsnotify watcher instance and initialize it 860 | watcher, err := NewWatcher() 861 | if err != nil { 862 | t.Fatalf("NewWatcher() failed: %s", err) 863 | } 864 | 865 | const testDir string = "_test" 866 | 867 | // Create directory to watch 868 | if os.Mkdir(testDir, 0777) != nil { 869 | t.Fatalf("Failed to create test directory: %s", err) 870 | } 871 | defer os.RemoveAll(testDir) 872 | 873 | // Receive errors on the error channel on a separate goroutine 874 | go func() { 875 | for err := range watcher.Error { 876 | t.Fatalf("error received: %s", err) 877 | } 878 | }() 879 | 880 | const testFile string = "_test/TestFsnotifyAttrib.testfile" 881 | 882 | // Receive events on the event channel on a separate goroutine 883 | eventstream := watcher.Event 884 | var attribReceived = 0 885 | done := make(chan bool) 886 | go func() { 887 | for event := range eventstream { 888 | // Only count relevant events 889 | if event.Name == testDir || event.Name == testFile { 890 | if event.IsModify() { 891 | attribReceived++ 892 | } 893 | t.Logf("event received: %s", event) 894 | } else { 895 | t.Logf("unexpected event received: %s", event) 896 | } 897 | } 898 | done <- true 899 | }() 900 | 901 | // Create a file 902 | // This should add at least one event to the fsnotify event queue 903 | var f *os.File 904 | f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666) 905 | if err != nil { 906 | t.Fatalf("creating test file failed: %s", err) 907 | } 908 | f.Sync() 909 | 910 | f.WriteString("data") 911 | f.Sync() 912 | f.Close() 913 | 914 | // Add a watch for testFile 915 | err = watcher.Watch(testFile) 916 | if err != nil { 917 | t.Fatalf("Watcher.Watch() failed: %s", err) 918 | } 919 | 920 | cmd := exec.Command("chmod", "0700", testFile) 921 | err = cmd.Run() 922 | if err != nil { 923 | t.Fatalf("chmod failed: %s", err) 924 | } 925 | 926 | // We expect this event to be received almost immediately, but let's wait 500 ms to be sure 927 | time.Sleep(500 * time.Millisecond) 928 | if attribReceived == 0 { 929 | t.Fatal("fsnotify attribute events have not received after 500 ms") 930 | } 931 | 932 | // Try closing the fsnotify instance 933 | t.Log("calling Close()") 934 | watcher.Close() 935 | t.Log("waiting for the event channel to become closed...") 936 | select { 937 | case <-done: 938 | t.Log("event channel closed") 939 | case <-time.After(1e9): 940 | t.Fatal("event stream was not closed after 1 second") 941 | } 942 | 943 | os.Remove(testFile) 944 | } 945 | 946 | func TestFsnotifyClose(t *testing.T) { 947 | watcher, _ := NewWatcher() 948 | watcher.Close() 949 | 950 | done := false 951 | go func() { 952 | watcher.Close() 953 | done = true 954 | }() 955 | 956 | time.Sleep(50e6) // 50 ms 957 | if !done { 958 | t.Fatal("double Close() test failed: second Close() call didn't return") 959 | } 960 | 961 | err := watcher.Watch("_test") 962 | if err == nil { 963 | t.Fatal("expected error on Watch() after Close(), got nil") 964 | } 965 | } 966 | --------------------------------------------------------------------------------