1
go get github.com/fsnotify/fsnotify
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
package main;

import (
"github.com/fsnotify/fsnotify"
"log"
"fmt"
)

func main() {
//创建一个监控对象
watch, err := fsnotify.NewWatcher();
if err != nil {
log.Fatal(err);
}
defer watch.Close();

//添加要监控的对象,文件或文件夹
err = watch.Add("./tmp");
if err != nil {
log.Fatal(err);
}
//我们另启一个goroutine来处理监控对象的事件
go func() {
for {
select {
case ev := <-watch.Events:
{
//判断事件发生的类型,如下5种
// Create 创建
// Write 写入
// Remove 删除
// Rename 重命名
// Chmod 修改权限
if ev.Op&fsnotify.Create == fsnotify.Create {
log.Println("创建文件 : ", ev.Name);
}
if ev.Op&fsnotify.Write == fsnotify.Write {
log.Println("写入文件 : ", ev.Name);
}
if ev.Op&fsnotify.Remove == fsnotify.Remove {
log.Println("删除文件 : ", ev.Name);
}
if ev.Op&fsnotify.Rename == fsnotify.Rename {
log.Println("重命名文件 : ", ev.Name);
}
if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
log.Println("修改权限 : ", ev.Name);
}
}
case err := <-watch.Errors:
{
log.Println("error : ", err);
return;
}
}
}
}();

//循环
select {};
}

fsnotify有一个问题,它无法递归的帮我们捕捉子目录、孙子目录的操作事件,这需要我们自已来实现。

还有一个问题就是当们修改文件夹名称时,fsnotify中event.Name仍然是原来的文件名,这就需要我们在重命名事件中,先移除之前的监控,然后添加新的监控。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package main;

import (
"github.com/fsnotify/fsnotify"
"fmt"
"path/filepath"
"os"
)

type Watch struct {
watch *fsnotify.Watcher;
}

//监控目录
func (w *Watch) watchDir(dir string) {
//通过Walk来遍历目录下的所有子目录
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
//这里判断是否为目录,只需监控目录即可
//目录下的文件也在监控范围内,不需要我们一个一个加
if info.IsDir() {
path, err := filepath.Abs(path);
if err != nil {
return err;
}
err = w.watch.Add(path);
if err != nil {
return err;
}
fmt.Println("监控 : ", path);
}
return nil;
});

go func() {
for {
select {
case ev := <-w.watch.Events:
{
if ev.Op&fsnotify.Create == fsnotify.Create {
fmt.Println("创建文件 : ", ev.Name);
//这里获取新创建文件的信息,如果是目录,则加入监控中
fi, err := os.Stat(ev.Name);
if err == nil && fi.IsDir() {
w.watch.Add(ev.Name);
fmt.Println("添加监控 : ", ev.Name);
}
}
if ev.Op&fsnotify.Write == fsnotify.Write {
fmt.Println("写入文件 : ", ev.Name);
}
if ev.Op&fsnotify.Remove == fsnotify.Remove {
fmt.Println("删除文件 : ", ev.Name);
//如果删除文件是目录,则移除监控
fi, err := os.Stat(ev.Name);
if err == nil && fi.IsDir() {
w.watch.Remove(ev.Name);
fmt.Println("删除监控 : ", ev.Name);
}
}
if ev.Op&fsnotify.Rename == fsnotify.Rename {
fmt.Println("重命名文件 : ", ev.Name);
//如果重命名文件是目录,则移除监控
//注意这里无法使用os.Stat来判断是否是目录了
//因为重命名后,go已经无法找到原文件来获取信息了
//所以这里就简单粗爆的直接remove好了
w.watch.Remove(ev.Name);
}
if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
fmt.Println("修改权限 : ", ev.Name);
}
}
case err := <-w.watch.Errors:
{
fmt.Println("error : ", err);
return;
}
}
}
}();
}

func main() {
watch, _ := fsnotify.NewWatcher()
w := Watch{
watch: watch,
}
w.watchDir("./tmp");
select {};
}

server.go代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
package main;

import (
"github.com/fsnotify/fsnotify"
"log"
"fmt"
"os/exec"
"regexp"
"strconv"
"bytes"
"errors"
"os"
"path/filepath"
)

const (
confFilePath = "./conf";
)

//获取进程ID
func getPid(processName string) (int, error) {
//通过wmic process get name,processid | findstr server.exe获取进程ID
buf := bytes.Buffer{};
cmd := exec.Command("wmic", "process", "get", "name,processid");
cmd.Stdout = &buf;
cmd.Run();
cmd2 := exec.Command("findstr", processName);
cmd2.Stdin = &buf;
data, _ := cmd2.CombinedOutput();
if len(data) == 0 {
return -1, errors.New("not find");
}
info := string(data);
//这里通过正则把进程id提取出来
reg := regexp.MustCompile(`[0-9]+`);
pid := reg.FindString(info);
return strconv.Atoi(pid);
}

//启动进程
func startProcess(exePath string, args []string) error {
attr := &os.ProcAttr{
//files指定新进程继承的活动文件对象
//前三个分别为,标准输入、标准输出、标准错误输出
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
//新进程的环境变量
Env: os.Environ(),
}

p, err := os.StartProcess(exePath, args, attr);
if err != nil {
return err;
}
fmt.Println(exePath, "进程启动");
p.Wait();
return nil;
}

func main() {
//创建一个监控对象
watch, err := fsnotify.NewWatcher();
if err != nil {
log.Fatal(err);
}
defer watch.Close();
//添加要监控的文件
err = watch.Add(confFilePath);
if err != nil {
log.Fatal(err);
}
//我们另启一个goroutine来处理监控对象的事件
go func() {
for {
select {
case ev := <-watch.Events:
{
//我们只需关心文件的修改
if ev.Op&fsnotify.Write == fsnotify.Write {
fmt.Println(ev.Name, "文件写入");
//查找进程
pid, err := getPid("server.exe");
//获取运行文件的绝对路径
exePath, _ := filepath.Abs("./server.exe")
if err != nil {
//启动进程
go startProcess(exePath, []string{});
} else {
//找到进程,并退出
process, err := os.FindProcess(pid);
if err == nil {
//让进程退出
process.Kill();
fmt.Println(exePath, "进程退出");
}
//启动进程
go startProcess(exePath, []string{});
}
}
}
case err := <-watch.Errors:
{
fmt.Println("error : ", err);
return;
}
}
}
}();

//循环
select {};
}

多级监控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
package FSNotify

import (
"fmt"
"github.com/fsnotify/fsnotify"
"os"
"path/filepath"
)

type NotifyFile struct {
watch *fsnotify.Watcher
}

func NewNotifyFile() *NotifyFile {
w := new(NotifyFile)
w.watch, _ = fsnotify.NewWatcher()
return w
}

//监控目录
func (this *NotifyFile) WatchDir(dir string) {
//通过Walk来遍历目录下的所有子目录
filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
//判断是否为目录,监控目录,目录下文件也在监控范围内,不需要加
if info.IsDir() {
path, err := filepath.Abs(path)
if err != nil {
return err
}
err = this.watch.Add(path)
if err != nil {
return err
}
fmt.Println("监控 : ", path)
}
return nil
})

go this.WatchEvent()
}

func (this *NotifyFile) WatchEvent() {
for {
select {
case ev := <-this.watch.Events:
{
if ev.Op&fsnotify.Create == fsnotify.Create {
fmt.Println("创建文件 : ", ev.Name)
//获取新创建文件的信息,如果是目录,则加入监控中
file, err := os.Stat(ev.Name)
if err == nil && file.IsDir() {
this.watch.Add(ev.Name)
fmt.Println("添加监控 : ", ev.Name)
}
}

if ev.Op&fsnotify.Write == fsnotify.Write {
//fmt.Println("写入文件 : ", ev.Name)
}

if ev.Op&fsnotify.Remove == fsnotify.Remove {
fmt.Println("删除文件 : ", ev.Name)
//如果删除文件是目录,则移除监控
fi, err := os.Stat(ev.Name)
if err == nil && fi.IsDir() {
this.watch.Remove(ev.Name)
fmt.Println("删除监控 : ", ev.Name)
}
}

if ev.Op&fsnotify.Rename == fsnotify.Rename {
//如果重命名文件是目录,则移除监控 ,注意这里无法使用os.Stat来判断是否是目录了
//因为重命名后,go已经无法找到原文件来获取信息了,所以简单粗爆直接remove
fmt.Println("重命名文件 : ", ev.Name)
this.watch.Remove(ev.Name)
}
if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
fmt.Println("修改权限 : ", ev.Name)
}
}
case err := <-this.watch.Errors:
{
fmt.Println("error : ", err)
return
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import (
"CGO/FSNotify"
)

func main() {

watch := FSNotify.NewNotifyFile()
watch.WatchDir("G:\\Ferry")
select {}
return
}

自写的监控和回调机制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
package fsnotify_test

import (
"fmt"
"log"
"os"
"path/filepath"
"sync"
"testing"
"time"

"github.com/fsnotify/fsnotify"
)

var eventMap *EventMap

type WatchEvents struct {
events []fsnotify.Event
}

type EventMap struct {
*sync.Mutex
m map[string]*WatchEvents
}

func NewEventMap() *EventMap {
return &EventMap{
Mutex: &sync.Mutex{},
m: make(map[string]*WatchEvents, 5),
}
}

func (em *EventMap) Append(event fsnotify.Event) {
em.Lock()
defer em.Unlock()
name := event.Op.String()
if _, ok := em.m[name]; !ok {
em.m[name] = new(WatchEvents)
}
em.m[name].events = append(em.m[name].events, event)
}

func (em *EventMap) GetFileList() []string {
var fileList []string
files := make(map[string]struct{}, 0)
em.Lock()
defer em.Unlock()
for _, events := range em.m {
for _, event := range events.events {
if _, exist := files[event.Name]; !exist {
files[event.Name] = struct{}{}
fileList = append(fileList, event.Name)
}
}
}
return fileList
}

func (em *EventMap) Clear() {
em.Lock()
defer em.Unlock()
em.m = map[string]*WatchEvents{}
}

func (em *EventMap) Copy() *EventMap {
ret := NewEventMap()
for k, v := range em.m {
ret.m[k] = v
}
return ret
}

type FileWatcher struct {
watch *fsnotify.Watcher
eventInputChan chan fsnotify.Event
eventOutputChan chan *EventMap // 消息整合

path string
waitingPushTime time.Duration
callbackFunc func(ch chan *EventMap)
}

func NewNotifyFile(path string, waitingPushTime time.Duration, callbackFunc func(ch chan *EventMap)) *FileWatcher {
watch, err := fsnotify.NewWatcher()
if err != nil {
log.Fatal(err)
}

fw := &FileWatcher{
watch: watch,
eventInputChan: make(chan fsnotify.Event, 100),
eventOutputChan: make(chan *EventMap, 1), // 在waitingPushTime内只发一条event

path: path,
waitingPushTime: waitingPushTime,
callbackFunc: callbackFunc,
}
go fw.callback()
return fw
}

func (fw *FileWatcher) callback() {
fw.callbackFunc(fw.eventOutputChan)
}

func (fw *FileWatcher) pushEventMapToOutputChan() {
ticker := time.NewTicker(fw.waitingPushTime)
go func() {
for range ticker.C {
e := eventMap.Copy()
fw.eventOutputChan <- e
eventMap.Clear()
}
}()
}

func (fw *FileWatcher) collectEventToMap() {
for {
select {
case event := <-fw.eventInputChan:
eventMap.Append(event)
}
}
}

func (fw *FileWatcher) WatchDir() {
filepath.Walk(fw.path, func(path string, info os.FileInfo, err error) error {
// 因为目录下文件也在监控范围内,不需要加文件
if info.IsDir() {
path, err := filepath.Abs(path)
if err != nil {
return err
}
err = fw.watch.Add(path)
if err != nil {
return err
}
log.Println("[INFO] add watch: ", path)
}
return nil
})
go fw.pushEventMapToOutputChan()
go fw.collectEventToMap()
go fw.watchEvent()
}

func (fw *FileWatcher) watchEvent() {
for {
select {
case ev := <-fw.watch.Events:
fw.eventInputChan <- ev
if ev.Op&fsnotify.Create == fsnotify.Create {
fw.onCreate(ev)
}
if ev.Op&fsnotify.Write == fsnotify.Write {
fw.onWrite(ev)
}
if ev.Op&fsnotify.Remove == fsnotify.Remove {
fw.onRemove(ev)
}
if ev.Op&fsnotify.Rename == fsnotify.Rename {
fw.onRename(ev)
}
if ev.Op&fsnotify.Chmod == fsnotify.Chmod {
fw.onChmod(ev)
}
case err := <-fw.watch.Errors:
fw.onError(err)
return
}
}
}

func (fw *FileWatcher) onCreate(event fsnotify.Event) {
log.Println("[INFO] create: ", event.Name)
// 获取新创建文件的信息,如果是目录,则加入监控中
file, err := os.Stat(event.Name)
if err == nil && file.IsDir() {
if err := fw.watch.Add(event.Name); err != nil {
log.Println("[WARN] add watch failed: ", event.Name)
return
}
log.Println("[INFO] add watch: ", event.Name)
}
}

func (fw *FileWatcher) onWrite(event fsnotify.Event) {
log.Println("[INFO] write: ", event.Name)
}

func (fw *FileWatcher) onRemove(event fsnotify.Event) {
log.Println("[INFO] remove: ", event.Name)
// 如果删除文件是目录,则移除监控
fi, err := os.Stat(event.Name)
if err == nil && fi.IsDir() {
if err := fw.watch.Remove(event.Name); err != nil {
log.Println("[WARN] delete watch failed: ", event.Name)
return
}
log.Println("[INFO] delete watch: ", event.Name)
}
}

func (fw *FileWatcher) onRename(event fsnotify.Event) {
log.Println("[INFO] rename: ", event.Name)
// 如果重命名文件是目录,则移除监控 ,注意这里无法使用os.Stat来判断是否是目录了
// 因为重命名后,go已经无法找到原文件来获取信息了,所以简单粗爆直接remove
if err := fw.watch.Remove(event.Name); err != nil {
log.Println("[WARN] delete watch failed: ", event.Name)
}
}

func (fw *FileWatcher) onChmod(event fsnotify.Event) {
log.Println("[INFO] chmod: ", event.Name)
}

func (fw *FileWatcher) onError(err error) {
log.Println("[ERROR] watcher on error: ", err)
}

func init() {
eventMap = NewEventMap()
}

func TestFsNotify(t *testing.T) {
waitingTime := 10 * time.Second
path := "/Users/heyingliang/Dropbox/root/md"
f := func(ch chan *EventMap) {
for em := range ch {
files := em.GetFileList()
fmt.Println(time.Now(), files)
}
}
watcher := NewNotifyFile(path, waitingTime, f)
watcher.WatchDir()
select {}
}