├── 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 | [](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 | [](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 |
--------------------------------------------------------------------------------