1 // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
2 // Revel Framework source code and usage is governed by a MIT style
3 // license that can be found in the LICENSE file.
13 "gopkg.in/fsnotify/fsnotify.v1"
17 // Listener is an interface for receivers of filesystem events.
18 type Listener interface {
19 // Refresh is invoked by the watcher on relevant filesystem events.
20 // If the listener returns an error, it is served to the user on the current request.
24 // DiscerningListener allows the receiver to selectively watch files.
25 type DiscerningListener interface {
27 WatchDir(info os.FileInfo) bool
28 WatchFile(basename string) bool
31 // Watcher allows listeners to register to be notified of changes under a given
34 serial bool // true to process events in serial
35 watchers []*fsnotify.Watcher // Parallel arrays of watcher/listener pairs.
36 listeners []Listener // List of listeners for watcher
37 forceRefresh bool // True to force the refresh
38 lastError int // The last error found
39 notifyMutex sync.Mutex // The mutext to serialize watches
40 refreshTimer *time.Timer // The timer to countdown the next refresh
41 timerMutex *sync.Mutex // A mutex to prevent concurrent updates
42 refreshChannel chan *Error // The error channel to listen to when waiting for a refresh
43 refreshChannelCount int // The number of clients listening on the channel
44 refreshTimerMS time.Duration // The number of milliseconds between refreshing builds
47 func NewWatcher() *Watcher {
51 refreshTimerMS: time.Duration(Config.IntDefault("watch.rebuild.delay", 10)),
52 timerMutex: &sync.Mutex{},
53 refreshChannel: make(chan *Error, 10),
54 refreshChannelCount: 0,
58 // Listen registers for events within the given root directories (recursively).
59 func (w *Watcher) Listen(listener Listener, roots ...string) {
60 watcher, err := fsnotify.NewWatcher()
62 utilLog.Fatal("Watcher: Failed to create watcher", "error", err)
65 // Replace the unbuffered Event channel with a buffered one.
66 // Otherwise multiple change events only come out one at a time, across
67 // multiple page views. (There appears no way to "pump" the events out of
69 // This causes a notification when you do a check in go, since you are modifying a buffer in use
70 watcher.Events = make(chan fsnotify.Event, 100)
71 watcher.Errors = make(chan error, 10)
73 // Walk through all files / directories under the root, adding each to watcher.
74 for _, p := range roots {
75 // is the directory / file a symlink?
77 if err == nil && f.Mode()&os.ModeSymlink == os.ModeSymlink {
79 realPath, err = filepath.EvalSymlinks(p)
88 utilLog.Error("Watcher: Failed to stat watched path, code will continue but auto updates will not work", "path", p, "error", err)
92 // If it is a file, watch that specific file.
96 utilLog.Error("Watcher: Failed to watch, code will continue but auto updates will not work", "path", p, "error", err)
101 var watcherWalker func(path string, info os.FileInfo, err error) error
103 watcherWalker = func(path string, info os.FileInfo, err error) error {
105 utilLog.Error("Watcher: Error walking path:", "error", err)
110 if dl, ok := listener.(DiscerningListener); ok {
111 if !dl.WatchDir(info) {
112 return filepath.SkipDir
116 err := watcher.Add(path)
118 utilLog.Error("Watcher: Failed to watch this path, code will continue but auto updates will not work", "path", path, "error", err)
124 // Else, walk the directory tree.
125 err = Walk(p, watcherWalker)
127 utilLog.Error("Watcher: Failed to walk directory, code will continue but auto updates will not work", "path", p, "error", err)
131 if w.eagerRebuildEnabled() {
132 // Create goroutine to notify file changes in real time
133 go w.NotifyWhenUpdated(listener, watcher)
136 w.watchers = append(w.watchers, watcher)
137 w.listeners = append(w.listeners, listener)
140 // NotifyWhenUpdated notifies the watcher when a file event is received.
141 func (w *Watcher) NotifyWhenUpdated(listener Listener, watcher *fsnotify.Watcher) {
145 case ev := <-watcher.Events:
146 if w.rebuildRequired(ev, listener) {
147 // Serialize listener.Refresh() calls.
149 // Serialize listener.Refresh() calls.
152 if err := listener.Refresh(); err != nil {
153 utilLog.Error("Watcher: Listener refresh reported error:", "error", err)
155 w.notifyMutex.Unlock()
157 // Run refresh in parallel
159 w.notifyInProcess(listener)
163 case <-watcher.Errors:
169 // Notify causes the watcher to forward any change events to listeners.
170 // It returns the first (if any) error returned.
171 func (w *Watcher) Notify() *Error {
172 // Serialize Notify() calls.
174 defer w.notifyMutex.Unlock()
176 for i, watcher := range w.watchers {
177 listener := w.listeners[i]
179 // Pull all pending events / errors from the watcher.
183 case ev := <-watcher.Events:
184 if w.rebuildRequired(ev, listener) {
188 case <-watcher.Errors:
191 // No events left to pull
196 if w.forceRefresh || refresh || w.lastError == i {
199 err = listener.Refresh()
201 err = w.notifyInProcess(listener)
211 w.forceRefresh = false
216 // Build a queue for refresh notifications
217 // this will not return until one of the queue completes
218 func (w *Watcher) notifyInProcess(listener Listener) (err *Error) {
219 shouldReturn := false
220 // This code block ensures that either a timer is created
221 // or that a process would be added the the h.refreshChannel
224 defer w.timerMutex.Unlock()
225 // If we are in the process of a rebuild, forceRefresh will always be true
226 w.forceRefresh = true
227 if w.refreshTimer != nil {
228 utilLog.Info("Found existing timer running, resetting")
229 w.refreshTimer.Reset(time.Millisecond * w.refreshTimerMS)
231 w.refreshChannelCount++
233 w.refreshTimer = time.NewTimer(time.Millisecond * w.refreshTimerMS)
237 // If another process is already waiting for the timer this one
238 // only needs to return the output from the channel
240 return <-w.refreshChannel
242 utilLog.Info("Waiting for refresh timer to expire")
246 // Ensure the queue is properly dispatched even if a panic occurs
248 for x := 0; x < w.refreshChannelCount; x++ {
249 w.refreshChannel <- err
251 w.refreshChannelCount = 0
253 w.timerMutex.Unlock()
256 err = listener.Refresh()
258 utilLog.Info("Watcher: Recording error last build, setting rebuild on", "error", err)
261 w.forceRefresh = false
263 utilLog.Info("Rebuilt, result", "error", err)
267 // If watch.mode is set to eager, the application is rebuilt immediately
268 // when a source file is changed.
269 // This feature is available only in dev mode.
270 func (w *Watcher) eagerRebuildEnabled() bool {
271 return Config.BoolDefault("mode.dev", true) &&
272 Config.BoolDefault("watch", true) &&
273 Config.StringDefault("watch.mode", "normal") == "eager"
276 func (w *Watcher) rebuildRequired(ev fsnotify.Event, listener Listener) bool {
277 // Ignore changes to dotfiles.
278 if strings.HasPrefix(filepath.Base(ev.Name), ".") {
282 if dl, ok := listener.(DiscerningListener); ok {
283 if !dl.WatchFile(ev.Name) || ev.Op&fsnotify.Chmod == fsnotify.Chmod {
290 var WatchFilter = func(c *Controller, fc []Filter) {
291 if MainWatcher != nil {
292 err := MainWatcher.Notify()
294 c.Result = c.RenderError(err)