Op 的方法
String
var ops = map[Op]string{
Create: "CREATE",
Write: "WRITE",
Remove: "REMOVE",
Rename: "RENAME",
Chmod: "CHMOD",
Move: "MOVE",
}
// String prints the string version of the Op consts
func (e Op) String() string {
if op, found := ops[e]; found {
return op
}
return "???"
}
对 ops 中的六种操作返回对应的 string ,对于没有的就返回 “???”
Event 的方法
String
// An Event describes an event that is received when files or directory
// changes occur. It includes the os.FileInfo of the changed file or
// directory and the type of event that's occurred and the full path of the file.
type Event struct {
Op
Path string
os.FileInfo
}
// String returns a string depending on what type of event occurred and the
// file name associated with the event.
func (e Event) String() string {
if e.FileInfo != nil {
pathType := "FILE"
if e.IsDir() {
pathType = "DIRECTORY"
}
return fmt.Sprintf("%s %q %s [%s]", pathType, e.Name(), e.Op, e.Path)
}
return "???"
}
也是返回 Event 对应的字符串
Watcher 的方法
SetMaxEvents
// SetMaxEvents controls the maximum amount of events that are sent on
// the Event channel per watching cycle. If max events is less than 1, there is
// no limit, which is the default.
func (w *Watcher) SetMaxEvents(delta int) {
w.mu.Lock()
w.maxEvents = delta
w.mu.Unlock()
}
这里的 mu 是互斥锁,可以保证同一时间只有一个 goroutine 在修改 maxEvents 值,后面很多方法也都这样做了。
这个方法设置了此 Watcher 每次有改动时 print 出来的事件的最大数量,比如设置为 1 ,然后在 Watching 的目录下新建一个文件,会有这样的结果
只打印出了一条信息,但如果不设置限制或者限制大于1,则
推荐保持默认,如有特殊需求则自行测试
IgnoreHiddenFiles
// IgnoreHiddenFiles sets the watcher to ignore any file or directory
// that starts with a dot.
func (w *Watcher) IgnoreHiddenFiles(ignore bool) {
w.mu.Lock()
w.ignoreHidden = ignore
w.mu.Unlock()
}
顾名思义,设置是否忽略隐藏文件
隐藏文件即文件名第一个字符是.
的文件,也就是作者所说的 dotFile ,如.dotFile
选择隐藏后就不会将隐藏文件纳入 Watching 列表
FilterOps
// FilterOps filters which event op types should be returned
// when an event occurs.
func (w *Watcher) FilterOps(ops ...Op) {
w.mu.Lock()
w.ops = make(map[Op]struct{})
for _, op := range ops {
w.ops[op] = struct{}{}
}
w.mu.Unlock()
}
选择要监视的操作,即 Op 那六个常量操作中的一个或多个
进行 Watching ,有某个操作触发了,会先检查这个操作是否被监测,如果没有被监测,就跳过,如果被监测,再打印出这个操作的信息
如果不进行设置,就默认监测全部六个操作
Ignore
// Ignore adds paths that should be ignored.
//
// For files that are already added, Ignore removes them.
func (w *Watcher) Ignore(paths ...string) (err error) {
for _, path := range paths {
path, err = filepath.Abs(path)
if err != nil {
return err
}
// Remove any of the paths that were already added.
if err := w.RemoveRecursive(path); err != nil {
return err
}
w.mu.Lock()
w.ignored[path] = struct{}{}
w.mu.Unlock()
}
return nil
}
将参数中的路径对应的文件或文件夹加入忽略名单,忽略名单中的文件及文件夹不会被监测
List
func (w *Watcher) list(name string) (map[string]os.FileInfo, error) {
fileList := make(map[string]os.FileInfo)
// Make sure name exists.
stat, err := os.Stat(name)
if err != nil {
return nil, err
}
fileList[name] = stat
// If it's not a directory, just return.
if !stat.IsDir() {
return fileList, nil
}
// It's a directory.
fInfoList, err := ioutil.ReadDir(name)
if err != nil {
return nil, err
}
// Add all of the files in the directory to the file list as long
// as they aren't on the ignored list or are hidden files if ignoreHidden
// is set to true.
for _, fInfo := range fInfoList {
path := filepath.Join(name, fInfo.Name())
_, ignored := w.ignored[path]
if ignored || (w.ignoreHidden && strings.HasPrefix(fInfo.Name(), ".")) {
continue
}
fileList[path] = fInfo
}
return fileList, nil
}
返回指定路径下的所有文件夹和文件的一个 map 表,忽略列表中的不会加入 map 中
ListRecursive
func (w *Watcher) listRecursive(name string) (map[string]os.FileInfo, error) {
fileList := make(map[string]os.FileInfo)
return fileList, filepath.Walk(name, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
// If path is ignored and it's a directory, skip the directory. If it's
// ignored and it's a single file, skip the file.
_, ignored := w.ignored[path]
if ignored || (w.ignoreHidden && strings.HasPrefix(info.Name(), ".")) {
if info.IsDir() {
return filepath.SkipDir
}
return nil
}
// Add the path and it's info to the file list.
fileList[path] = info
return nil
})
}
跟List方法功能类似,不过是递归的,遇到文件夹会将文件夹中的内容再列出来,直到列完,当然忽略列表中的也不会列出。
作者并没有直接写递归,而是调用了filepath.Walk
方法去递归遍历指定路径下的所有文件
Add
// Add adds either a single file or directory to the file list.
func (w *Watcher) Add(name string) (err error) {
w.mu.Lock()
defer w.mu.Unlock()
name, err = filepath.Abs(name)
if err != nil {
return err
}
// If name is on the ignored list or if hidden files are
// ignored and name is a hidden file or directory, simply return.
_, ignored := w.ignored[name]
if ignored || (w.ignoreHidden && strings.HasPrefix(name, ".")) {
return nil
}
// Add the directory's contents to the files list.
fileList, err := w.list(name)
if err != nil {
return err
}
for k, v := range fileList {
w.files[k] = v
}
// Add the name to the names list.
w.names[name] = false
return nil
}
这个方法将参数对应的文件或文件夹加入监测名单,如果在忽略名单中就不加入监测名单,非递归
AddRecursive
// AddRecursive adds either a single file or directory recursively to the file list.
func (w *Watcher) AddRecursive(name string) (err error) {
w.mu.Lock()
defer w.mu.Unlock()
name, err = filepath.Abs(name)
if err != nil {
return err
}
fileList, err := w.listRecursive(name)
if err != nil {
return err
}
for k, v := range fileList {
w.files[k] = v
}
// Add the name to the names list.
w.names[name] = true
return nil
}
这个方法将参数对应的文件或文件夹加入监测名单,如果在忽略名单中就不加入监测名单,递归
Remove
// Remove removes either a single file or directory from the file's list.
func (w *Watcher) Remove(name string) (err error) {
w.mu.Lock()
defer w.mu.Unlock()
name, err = filepath.Abs(name)
if err != nil {
return err
}
// Remove the name from w's names list.
delete(w.names, name)
// If name is a single file, remove it and return.
info, found := w.files[name]
if !found {
return nil // Doesn't exist, just return.
}
if !info.IsDir() {
delete(w.files, name)
return nil
}
// Delete the actual directory from w.files
delete(w.files, name)
// If it's a directory, delete all of it's contents from w.files.
for path := range w.files {
if filepath.Dir(path) == name {
delete(w.files, path)
}
}
return nil
}
移出监测名单,非递归
RemoveRecursive
// RemoveRecursive removes either a single file or a directory recursively from
// the file's list.
func (w *Watcher) RemoveRecursive(name string) (err error) {
w.mu.Lock()
defer w.mu.Unlock()
name, err = filepath.Abs(name)
if err != nil {
return err
}
// Remove the name from w's names list.
delete(w.names, name)
// If name is a single file, remove it and return.
info, found := w.files[name]
if !found {
return nil // Doesn't exist, just return.
}
if !info.IsDir() {
delete(w.files, name)
return nil
}
// If it's a directory, delete all of it's contents recursively
// from w.files.
for path := range w.files {
if strings.HasPrefix(path, name) {
delete(w.files, path)
}
}
return nil
}
移出监测名单,递归
WatchedFiles
// WatchedFiles returns a map of files added to a Watcher.
func (w *Watcher) WatchedFiles() map[string]os.FileInfo {
w.mu.Lock()
defer w.mu.Unlock()
return w.files
}
这个方法返回 Watcher 对象的 files 属性,也就是被检测的文件列表
TriggerEvent
// TriggerEvent is a method that can be used to trigger an event, separate to
// the file watching process.
func (w *Watcher) TriggerEvent(eventType Op, file os.FileInfo) {
w.Wait()
if file == nil {
file = &fileInfo{name: "triggered event", modTime: time.Now()}
}
w.Event <- Event{Op: eventType, Path: "-", FileInfo: file}
}
Trigger ,也就是扳机的意思,这个方法用于触发一个事件,比如 REMOVE 或者 CREATE
retrieveFileList
func (w *Watcher) retrieveFileList() map[string]os.FileInfo {
w.mu.Lock()
defer w.mu.Unlock()
fileList := make(map[string]os.FileInfo)
var list map[string]os.FileInfo
var err error
for name, recursive := range w.names {
if recursive {
list, err = w.listRecursive(name)
if err != nil {
if os.IsNotExist(err) {
w.Error <- ErrWatchedFileDeleted
w.mu.Unlock()
w.RemoveRecursive(name)
w.mu.Lock()
} else {
w.Error <- err
}
}
} else {
list, err = w.list(name)
if err != nil {
if os.IsNotExist(err) {
w.Error <- ErrWatchedFileDeleted
w.mu.Unlock()
w.Remove(name)
w.mu.Lock()
} else {
w.Error <- err
}
}
}
// Add the file's to the file list.
for k, v := range list {
fileList[k] = v
}
}
return fileList
}
这个方法并没有暴露出去,只供内部调用,具体做的事情也很简单,就是去获取被监测的文件及文件夹的当前状态
pollEvents
func (w *Watcher) pollEvents(files map[string]os.FileInfo, evt chan Event,
cancel chan struct{}) {
w.mu.Lock()
defer w.mu.Unlock()
// Store create and remove events for use to check for rename events.
creates := make(map[string]os.FileInfo)
removes := make(map[string]os.FileInfo)
// Check for removed files.
for path, info := range w.files {
if _, found := files[path]; !found {
removes[path] = info
}
}
// Check for created files, writes and chmods.
for path, info := range files {
oldInfo, found := w.files[path]
if !found {
// A file was created.
creates[path] = info
continue
}
if oldInfo.ModTime() != info.ModTime() {
select {
case <-cancel:
return
case evt <- Event{Write, path, info}:
}
}
if oldInfo.Mode() != info.Mode() {
select {
case <-cancel:
return
case evt <- Event{Chmod, path, info}:
}
}
}
// Check for renames and moves.
for path1, info1 := range removes {
for path2, info2 := range creates {
if sameFile(info1, info2) {
e := Event{
Op: Move,
Path: fmt.Sprintf("%s -> %s", path1, path2),
FileInfo: info1,
}
// If they are from the same directory, it's a rename
// instead of a move event.
if filepath.Dir(path1) == filepath.Dir(path2) {
e.Op = Rename
}
delete(removes, path1)
delete(creates, path2)
select {
case <-cancel:
return
case evt <- e:
}
}
}
}
// Send all the remaining create and remove events.
for path, info := range creates {
select {
case <-cancel:
return
case evt <- Event{Create, path, info}:
}
}
for path, info := range removes {
select {
case <-cancel:
return
case evt <- Event{Remove, path, info}:
}
}
}
这个方法也没有暴露,只供 Start 方法调用,功能就是去比较传入的参数中的文件 map 表(也就是当前状态)和 Watcher 对象的 files 属性中存的 map 表(也就是初始状态),如果有不同的地方就讲这个时间放入参数的 Event 通道中
Start
// Start begins the polling cycle which repeats every specified
// duration until Close is called.
func (w *Watcher) Start(d time.Duration) error {
// Return an error if d is less than 1 nanosecond.
if d < time.Nanosecond {
return ErrDurationTooShort
}
// Make sure the Watcher is not already running.
w.mu.Lock()
if w.running {
w.mu.Unlock()
return ErrWatcherRunning
}
w.running = true
w.mu.Unlock()
// Unblock w.Wait().
w.wg.Done()
for {
// done lets the inner polling cycle loop know when the
// current cycle's method has finished executing.
done := make(chan struct{})
// Any events that are found are first piped to evt before
// being sent to the main Event channel.
evt := make(chan Event)
// Retrieve the file list for all watched file's and dirs.
fileList := w.retrieveFileList()
// cancel can be used to cancel the current event polling function.
cancel := make(chan struct{})
// Look for events.
go func() {
w.pollEvents(fileList, evt, cancel)
done <- struct{}{}
}()
// numEvents holds the number of events for the current cycle.
numEvents := 0
inner:
for {
select {
case <-w.close:
close(cancel)
close(w.Closed)
return nil
case event := <-evt:
if len(w.ops) > 0 { // Filter Ops.
_, found := w.ops[event.Op]
if !found {
continue
}
}
numEvents++
if w.maxEvents > 0 && numEvents > w.maxEvents {
close(cancel)
break inner
}
w.Event <- event
case <-done: // Current cycle is finished.
break inner
}
}
// Update the file's list.
w.mu.Lock()
w.files = fileList
w.mu.Unlock()
// Sleep and then continue to the next loop iteration.
time.Sleep(d)
}
}
这就是 Watcher 启动的方法了,而做的事情也很简单,接收一个time.Duration
对象作为参数,每隔一段时间(也就是这个 Duration ),进行一次检测,也就是调用 retrieveFiles 读取一次被监测文件的当前状态,然后去跟之前保存的初始状态做对比,出现不同就将这个操作输出
Wait
// Wait blocks until the watcher is started.
func (w *Watcher) Wait() {
w.wg.Wait()
}
Watcher 对象中的 wg 是一个 sync.WaitGroup 对象,Wait 方法就是去等待 Done 动作,具体可以自己去看 sync.WaitGroup 的包解析
Close
// Close stops a Watcher and unlocks its mutex, then sends a close signal.
func (w *Watcher) Close() {
w.mu.Lock()
if !w.running {
w.mu.Unlock()
return
}
w.running = false
w.files = make(map[string]os.FileInfo)
w.names = make(map[string]bool)
w.mu.Unlock()
// Send a close signal to the Start method.
w.close <- struct{}{}
}
很直白,就是关闭一个 Watcher
小结
分析了 Watcher 的所有方法,发现其实做的事情并不复杂,能够把源码都读懂就能很清楚的知道它具体所做的事情了,就是一直去对比当前状态和初始状态的异同,然后抛出不同的事件提示用户
当然只是文字讲可能会比较枯燥,接下来用流程图来解释,可能会更加明白一点