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 constsfunc (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 {OpPath stringos.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 = deltaw.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 = ignorew.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] = inforeturn 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] = falsereturn 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] = truereturn 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.filesdelete(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.FileInfovar err errorfor name, recursive := range w.names {if recursive {list, err = w.listRecursive(name)if err != nil {if os.IsNotExist(err) {w.Error <- ErrWatchedFileDeletedw.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 <- ErrWatchedFileDeletedw.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] = infocontinue}if oldInfo.ModTime() != info.ModTime() {select {case <-cancel:returncase evt <- Event{Write, path, info}:}}if oldInfo.Mode() != info.Mode() {select {case <-cancel:returncase 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:returncase evt <- e:}}}}// Send all the remaining create and remove events.for path, info := range creates {select {case <-cancel:returncase evt <- Event{Create, path, info}:}}for path, info := range removes {select {case <-cancel:returncase 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 = truew.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 := 0inner:for {select {case <-w.close:close(cancel)close(w.Closed)return nilcase 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 <- eventcase <-done: // Current cycle is finished.break inner}}// Update the file's list.w.mu.Lock()w.files = fileListw.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 的所有方法,发现其实做的事情并不复杂,能够把源码都读懂就能很清楚的知道它具体所做的事情了,就是一直去对比当前状态和初始状态的异同,然后抛出不同的事件提示用户
当然只是文字讲可能会比较枯燥,接下来用流程图来解释,可能会更加明白一点
