-
Notifications
You must be signed in to change notification settings - Fork 0
/
watch.go
205 lines (185 loc) · 4.5 KB
/
watch.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
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
package pslog
import (
"bytes"
"fmt"
"os"
"path/filepath"
"runtime/debug"
"gitee.com/xuesongtao/gotool/base"
plg "gitee.com/xuesongtao/ps-log/log"
fs "github.com/fsnotify/fsnotify"
tw "github.com/olekukonko/tablewriter"
)
// Watch 监听的文件
type Watch struct {
fileMap map[string]*WatchFileInfo // key: file path
watcher *fs.Watcher // 监听
}
// WatchFileInfo
type WatchFileInfo struct {
IsDir bool // 是否为目录
Dir string // 原始目录路径
Path string // 原始添加的文件路径, 这里可能是文件路径或目录路径
// 动态参数
Op fs.Op
ChangedFilename string // 被监听到的文件名, 绝对路径
}
func (w *WatchFileInfo) copy() *WatchFileInfo {
return &WatchFileInfo{
IsDir: w.IsDir,
Dir: w.Dir,
Path: w.Path,
// IsRename: w.IsRename,
// ChangedFilename: w.ChangedFilename,
}
}
// NewWatch 监听
func NewWatch() (*Watch, error) {
watcher, err := fs.NewWatcher()
if err != nil {
return nil, fmt.Errorf("fs.NewWatcher is failed, err:%v", err)
}
obj := &Watch{
fileMap: make(map[string]*WatchFileInfo),
watcher: watcher,
}
return obj, nil
}
// Add 添加待 watch 的路径
// 说明:
// 1. 自动去重
// 2. paths 中可以为目录和文件
// 3. 建议使用绝对路径
func (w *Watch) Add(paths ...string) error {
for _, path := range paths {
path = filepath.Clean(path)
st, err := os.Lstat(path)
if err != nil {
return fmt.Errorf("os.Lstat is failed, err: %v", err)
}
if _, ok := w.fileMap[path]; ok {
continue
}
watchFileInfo := &WatchFileInfo{IsDir: false, Path: path, Dir: path}
if st.IsDir() {
watchFileInfo.IsDir = true
} else {
watchFileInfo.Dir = filepath.Dir(path)
}
// 保存和监听
w.fileMap[path] = watchFileInfo
// 只监听目录
if err := w.watcher.Add(watchFileInfo.Dir); err != nil {
return fmt.Errorf("w.watcher.Add is failed, err: %v", err)
}
}
return nil
}
// Remove 移除待 watch 的路径
func (w *Watch) Remove(paths ...string) error {
for _, path := range paths {
path = filepath.Clean(path)
info, ok := w.fileMap[path]
if ok && info.IsDir {
if err := w.watcher.Remove(info.Dir); err != nil {
return fmt.Errorf("w.watcher.Remove is failed, err: %v", err)
}
}
delete(w.fileMap, path)
}
return nil
}
// Close
func (w *Watch) Close() {
w.watcher.Close()
// w.fileMap = nil
}
// Watch 文件异步监听
func (w *Watch) Watch(busCh chan *WatchFileInfo) {
go func() {
defer func() {
if err := recover(); err != nil {
plg.Error("Watch recover err:", debug.Stack())
}
close(busCh)
w.fileMap = nil
}()
for {
select {
case err, ok := <-w.watcher.Errors:
if !ok {
plg.Info("err channel is closed")
return
}
plg.Error("watch err:", err)
case event, ok := <-w.watcher.Events:
if !ok {
plg.Info("event channel is closed")
return
}
if !w.inEvenOps(event.Op, fs.Write, fs.Rename) {
continue
}
watchFileInfo := w.getWatchFileInfo(event.Name)
if watchFileInfo == nil {
continue
}
watchFileInfo.Op = event.Op
watchFileInfo.ChangedFilename = event.Name
// plg.Infof("filename: %q, op: %s, watch: %s", event.Name, event.Op.String(), base.GetJson2Dump(watchFileInfo))
// plg.Infof("filename: %q, op: %s", event.Name, event.Op.String())
busCh <- watchFileInfo
}
}
}()
}
func (w *Watch) getWatchFileInfo(filename string) *WatchFileInfo {
var (
ok bool
watchFileInfo *WatchFileInfo
)
// 这里查找2次, filename 为文件全路径
// 如果根据 filename 没有查询到, 再按照 filename 目录查询下
for i := 0; i < 2; i++ {
watchFileInfo, ok = w.fileMap[filename]
if !ok {
filename = filepath.Dir(filename)
continue
}
break
}
return watchFileInfo.copy()
}
func (w *Watch) inEvenOps(target fs.Op, ins ...fs.Op) bool {
for _, in := range ins {
if in == target {
return true
}
}
return false
}
// WatchList 查询监听的所有 path
// 格式:
// ----------------------
// | WATCH-PATH | DIR |
// ----------------------
// | xxxx | true |
// -----------------------
func (w *Watch) WatchList() string {
header := []string{"WATCH-PATH", "DIR"}
buffer := new(bytes.Buffer)
buffer.WriteByte('\n')
table := tw.NewWriter(buffer)
table.SetHeader(header)
table.SetRowLine(true)
table.SetCenterSeparator("|")
for path, watchFileInfo := range w.fileMap {
data := []string{
path,
base.ToString(watchFileInfo.IsDir),
}
table.Append(data)
}
table.Render()
return buffer.String()
}