(file string, callback func())
| 74 | } |
| 75 | |
| 76 | func WatchFile(file string, callback func()) io.Closer { |
| 77 | watcher, err := fsnotify.NewWatcher() |
| 78 | if err != nil { |
| 79 | log.Error("failed to create file watcher for %s: %v", file, err) |
| 80 | return nil |
| 81 | } |
| 82 | |
| 83 | // we have to watch the entire directory to pick up renames/atomic saves in a cross-platform way |
| 84 | filename := filepath.Clean(file) |
| 85 | dirPath, _ := filepath.Split(filename) |
| 86 | if err := watcher.Add(dirPath); err != nil { |
| 87 | log.Error("failed to add directory to watcher: %v", err) |
| 88 | return nil |
| 89 | } |
| 90 | |
| 91 | var eventsWG sync.WaitGroup |
| 92 | var debounceTimer *time.Timer |
| 93 | debounceTime := 100 * time.Millisecond |
| 94 | eventsWG.Add(1) |
| 95 | |
| 96 | go func() { |
| 97 | defer CatchPanic() |
| 98 | defer watcher.Close() |
| 99 | defer eventsWG.Done() |
| 100 | |
| 101 | for { |
| 102 | select { |
| 103 | case event, ok := <-watcher.Events: |
| 104 | if !ok { // 'Events' channel is closed |
| 105 | return |
| 106 | } |
| 107 | |
| 108 | // callback fires when file is modified or created |
| 109 | if filepath.Clean(event.Name) == filename { |
| 110 | if event.Has(fsnotify.Write) || event.Has(fsnotify.Create) { |
| 111 | if callback != nil { |
| 112 | if debounceTimer != nil { |
| 113 | debounceTimer.Stop() |
| 114 | } |
| 115 | debounceTimer = time.AfterFunc(debounceTime, func() { callback() }) |
| 116 | } |
| 117 | } else if event.Has(fsnotify.Remove) { |
| 118 | return |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | case err, ok := <-watcher.Errors: |
| 123 | if ok { // 'Errors' channel is not closed |
| 124 | log.Error("file watcher error: %v", err) |
| 125 | } |
| 126 | return |
| 127 | } |
| 128 | } |
| 129 | }() |
| 130 | |
| 131 | log.Info("start watching file %s", filename) |
| 132 | return &fileWatcher{filename, watcher, &eventsWG} |
| 133 | } |
nothing calls this directly
no test coverage detected