Browse code

Revert "pkg: remove unused filenotify"

This reverts commit ee99b5f2e96aafa982487aadbb78478898ae0c71.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>

Brian Goff authored on 2016/02/24 11:05:16
Showing 4 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,40 @@
0
+// Package filenotify provides a mechanism for watching file(s) for changes.
1
+// Generally leans on fsnotify, but provides a poll-based notifier which fsnotify does not support.
2
+// These are wrapped up in a common interface so that either can be used interchangeably in your code.
3
+package filenotify
4
+
5
+import "gopkg.in/fsnotify.v1"
6
+
7
+// FileWatcher is an interface for implementing file notification watchers
8
+type FileWatcher interface {
9
+	Events() <-chan fsnotify.Event
10
+	Errors() <-chan error
11
+	Add(name string) error
12
+	Remove(name string) error
13
+	Close() error
14
+}
15
+
16
+// New tries to use an fs-event watcher, and falls back to the poller if there is an error
17
+func New() (FileWatcher, error) {
18
+	if watcher, err := NewEventWatcher(); err == nil {
19
+		return watcher, nil
20
+	}
21
+	return NewPollingWatcher(), nil
22
+}
23
+
24
+// NewPollingWatcher returns a poll-based file watcher
25
+func NewPollingWatcher() FileWatcher {
26
+	return &filePoller{
27
+		events: make(chan fsnotify.Event),
28
+		errors: make(chan error),
29
+	}
30
+}
31
+
32
+// NewEventWatcher returns an fs-event based file watcher
33
+func NewEventWatcher() (FileWatcher, error) {
34
+	watcher, err := fsnotify.NewWatcher()
35
+	if err != nil {
36
+		return nil, err
37
+	}
38
+	return &fsNotifyWatcher{watcher}, nil
39
+}
0 40
new file mode 100644
... ...
@@ -0,0 +1,18 @@
0
+package filenotify
1
+
2
+import "gopkg.in/fsnotify.v1"
3
+
4
+// fsNotify wraps the fsnotify package to satisfy the FileNotifer interface
5
+type fsNotifyWatcher struct {
6
+	*fsnotify.Watcher
7
+}
8
+
9
+// GetEvents returns the fsnotify event channel receiver
10
+func (w *fsNotifyWatcher) Events() <-chan fsnotify.Event {
11
+	return w.Watcher.Events
12
+}
13
+
14
+// GetErrors returns the fsnotify error channel receiver
15
+func (w *fsNotifyWatcher) Errors() <-chan error {
16
+	return w.Watcher.Errors
17
+}
0 18
new file mode 100644
... ...
@@ -0,0 +1,205 @@
0
+package filenotify
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"os"
6
+	"sync"
7
+	"time"
8
+
9
+	"github.com/Sirupsen/logrus"
10
+
11
+	"gopkg.in/fsnotify.v1"
12
+)
13
+
14
+var (
15
+	// errPollerClosed is returned when the poller is closed
16
+	errPollerClosed = errors.New("poller is closed")
17
+	// errNoSuchPoller is returned when trying to remove a watch that doesn't exist
18
+	errNoSuchWatch = errors.New("poller does not exist")
19
+)
20
+
21
+// watchWaitTime is the time to wait between file poll loops
22
+const watchWaitTime = 200 * time.Millisecond
23
+
24
+// filePoller is used to poll files for changes, especially in cases where fsnotify
25
+// can't be run (e.g. when inotify handles are exhausted)
26
+// filePoller satisfies the FileWatcher interface
27
+type filePoller struct {
28
+	// watches is the list of files currently being polled, close the associated channel to stop the watch
29
+	watches map[string]chan struct{}
30
+	// events is the channel to listen to for watch events
31
+	events chan fsnotify.Event
32
+	// errors is the channel to listen to for watch errors
33
+	errors chan error
34
+	// mu locks the poller for modification
35
+	mu sync.Mutex
36
+	// closed is used to specify when the poller has already closed
37
+	closed bool
38
+}
39
+
40
+// Add adds a filename to the list of watches
41
+// once added the file is polled for changes in a separate goroutine
42
+func (w *filePoller) Add(name string) error {
43
+	w.mu.Lock()
44
+	defer w.mu.Unlock()
45
+
46
+	if w.closed == true {
47
+		return errPollerClosed
48
+	}
49
+
50
+	f, err := os.Open(name)
51
+	if err != nil {
52
+		return err
53
+	}
54
+	fi, err := os.Stat(name)
55
+	if err != nil {
56
+		return err
57
+	}
58
+
59
+	if w.watches == nil {
60
+		w.watches = make(map[string]chan struct{})
61
+	}
62
+	if _, exists := w.watches[name]; exists {
63
+		return fmt.Errorf("watch exists")
64
+	}
65
+	chClose := make(chan struct{})
66
+	w.watches[name] = chClose
67
+
68
+	go w.watch(f, fi, chClose)
69
+	return nil
70
+}
71
+
72
+// Remove stops and removes watch with the specified name
73
+func (w *filePoller) Remove(name string) error {
74
+	w.mu.Lock()
75
+	defer w.mu.Unlock()
76
+	return w.remove(name)
77
+}
78
+
79
+func (w *filePoller) remove(name string) error {
80
+	if w.closed == true {
81
+		return errPollerClosed
82
+	}
83
+
84
+	chClose, exists := w.watches[name]
85
+	if !exists {
86
+		return errNoSuchWatch
87
+	}
88
+	close(chClose)
89
+	delete(w.watches, name)
90
+	return nil
91
+}
92
+
93
+// Events returns the event channel
94
+// This is used for notifications on events about watched files
95
+func (w *filePoller) Events() <-chan fsnotify.Event {
96
+	return w.events
97
+}
98
+
99
+// Errors returns the errors channel
100
+// This is used for notifications about errors on watched files
101
+func (w *filePoller) Errors() <-chan error {
102
+	return w.errors
103
+}
104
+
105
+// Close closes the poller
106
+// All watches are stopped, removed, and the poller cannot be added to
107
+func (w *filePoller) Close() error {
108
+	w.mu.Lock()
109
+	defer w.mu.Unlock()
110
+
111
+	if w.closed {
112
+		return nil
113
+	}
114
+
115
+	w.closed = true
116
+	for name := range w.watches {
117
+		w.remove(name)
118
+		delete(w.watches, name)
119
+	}
120
+	close(w.events)
121
+	close(w.errors)
122
+	return nil
123
+}
124
+
125
+// sendEvent publishes the specified event to the events channel
126
+func (w *filePoller) sendEvent(e fsnotify.Event, chClose <-chan struct{}) error {
127
+	select {
128
+	case w.events <- e:
129
+	case <-chClose:
130
+		return fmt.Errorf("closed")
131
+	}
132
+	return nil
133
+}
134
+
135
+// sendErr publishes the specified error to the errors channel
136
+func (w *filePoller) sendErr(e error, chClose <-chan struct{}) error {
137
+	select {
138
+	case w.errors <- e:
139
+	case <-chClose:
140
+		return fmt.Errorf("closed")
141
+	}
142
+	return nil
143
+}
144
+
145
+// watch is responsible for polling the specified file for changes
146
+// upon finding changes to a file or errors, sendEvent/sendErr is called
147
+func (w *filePoller) watch(f *os.File, lastFi os.FileInfo, chClose chan struct{}) {
148
+	for {
149
+		time.Sleep(watchWaitTime)
150
+		select {
151
+		case <-chClose:
152
+			logrus.Debugf("watch for %s closed", f.Name())
153
+			return
154
+		default:
155
+		}
156
+
157
+		fi, err := os.Stat(f.Name())
158
+		if err != nil {
159
+			// if we got an error here and lastFi is not set, we can presume that nothing has changed
160
+			// This should be safe since before `watch()` is called, a stat is performed, there is any error `watch` is not called
161
+			if lastFi == nil {
162
+				continue
163
+			}
164
+			// If it doesn't exist at this point, it must have been removed
165
+			// no need to send the error here since this is a valid operation
166
+			if os.IsNotExist(err) {
167
+				if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Remove, Name: f.Name()}, chClose); err != nil {
168
+					return
169
+				}
170
+				lastFi = nil
171
+				continue
172
+			}
173
+			// at this point, send the error
174
+			if err := w.sendErr(err, chClose); err != nil {
175
+				return
176
+			}
177
+			continue
178
+		}
179
+
180
+		if lastFi == nil {
181
+			if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Create, Name: fi.Name()}, chClose); err != nil {
182
+				return
183
+			}
184
+			lastFi = fi
185
+			continue
186
+		}
187
+
188
+		if fi.Mode() != lastFi.Mode() {
189
+			if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Chmod, Name: fi.Name()}, chClose); err != nil {
190
+				return
191
+			}
192
+			lastFi = fi
193
+			continue
194
+		}
195
+
196
+		if fi.ModTime() != lastFi.ModTime() || fi.Size() != lastFi.Size() {
197
+			if err := w.sendEvent(fsnotify.Event{Op: fsnotify.Write, Name: fi.Name()}, chClose); err != nil {
198
+				return
199
+			}
200
+			lastFi = fi
201
+			continue
202
+		}
203
+	}
204
+}
0 205
new file mode 100644
... ...
@@ -0,0 +1,137 @@
0
+package filenotify
1
+
2
+import (
3
+	"fmt"
4
+	"io/ioutil"
5
+	"os"
6
+	"runtime"
7
+	"testing"
8
+	"time"
9
+
10
+	"gopkg.in/fsnotify.v1"
11
+)
12
+
13
+func TestPollerAddRemove(t *testing.T) {
14
+	w := NewPollingWatcher()
15
+
16
+	if err := w.Add("no-such-file"); err == nil {
17
+		t.Fatal("should have gotten error when adding a non-existent file")
18
+	}
19
+	if err := w.Remove("no-such-file"); err == nil {
20
+		t.Fatal("should have gotten error when removing non-existent watch")
21
+	}
22
+
23
+	f, err := ioutil.TempFile("", "asdf")
24
+	if err != nil {
25
+		t.Fatal(err)
26
+	}
27
+	defer os.RemoveAll(f.Name())
28
+
29
+	if err := w.Add(f.Name()); err != nil {
30
+		t.Fatal(err)
31
+	}
32
+
33
+	if err := w.Remove(f.Name()); err != nil {
34
+		t.Fatal(err)
35
+	}
36
+}
37
+
38
+func TestPollerEvent(t *testing.T) {
39
+	if runtime.GOOS == "windows" {
40
+		t.Skip("No chmod on Windows")
41
+	}
42
+	w := NewPollingWatcher()
43
+
44
+	f, err := ioutil.TempFile("", "test-poller")
45
+	if err != nil {
46
+		t.Fatal("error creating temp file")
47
+	}
48
+	defer os.RemoveAll(f.Name())
49
+	f.Close()
50
+
51
+	if err := w.Add(f.Name()); err != nil {
52
+		t.Fatal(err)
53
+	}
54
+
55
+	select {
56
+	case <-w.Events():
57
+		t.Fatal("got event before anything happened")
58
+	case <-w.Errors():
59
+		t.Fatal("got error before anything happened")
60
+	default:
61
+	}
62
+
63
+	if err := ioutil.WriteFile(f.Name(), []byte("hello"), 644); err != nil {
64
+		t.Fatal(err)
65
+	}
66
+	if err := assertEvent(w, fsnotify.Write); err != nil {
67
+		t.Fatal(err)
68
+	}
69
+
70
+	if err := os.Chmod(f.Name(), 600); err != nil {
71
+		t.Fatal(err)
72
+	}
73
+	if err := assertEvent(w, fsnotify.Chmod); err != nil {
74
+		t.Fatal(err)
75
+	}
76
+
77
+	if err := os.Remove(f.Name()); err != nil {
78
+		t.Fatal(err)
79
+	}
80
+	if err := assertEvent(w, fsnotify.Remove); err != nil {
81
+		t.Fatal(err)
82
+	}
83
+}
84
+
85
+func TestPollerClose(t *testing.T) {
86
+	w := NewPollingWatcher()
87
+	if err := w.Close(); err != nil {
88
+		t.Fatal(err)
89
+	}
90
+	// test double-close
91
+	if err := w.Close(); err != nil {
92
+		t.Fatal(err)
93
+	}
94
+
95
+	select {
96
+	case _, open := <-w.Events():
97
+		if open {
98
+			t.Fatal("event chan should be closed")
99
+		}
100
+	default:
101
+		t.Fatal("event chan should be closed")
102
+	}
103
+
104
+	select {
105
+	case _, open := <-w.Errors():
106
+		if open {
107
+			t.Fatal("errors chan should be closed")
108
+		}
109
+	default:
110
+		t.Fatal("errors chan should be closed")
111
+	}
112
+
113
+	f, err := ioutil.TempFile("", "asdf")
114
+	if err != nil {
115
+		t.Fatal(err)
116
+	}
117
+	defer os.RemoveAll(f.Name())
118
+	if err := w.Add(f.Name()); err == nil {
119
+		t.Fatal("should have gotten error adding watch for closed watcher")
120
+	}
121
+}
122
+
123
+func assertEvent(w FileWatcher, eType fsnotify.Op) error {
124
+	var err error
125
+	select {
126
+	case e := <-w.Events():
127
+		if e.Op != eType {
128
+			err = fmt.Errorf("got wrong event type, expected %q: %v", eType, e)
129
+		}
130
+	case e := <-w.Errors():
131
+		err = fmt.Errorf("got unexpected error waiting for events %v: %v", eType, e)
132
+	case <-time.After(watchWaitTime * 3):
133
+		err = fmt.Errorf("timeout waiting for event %v", eType)
134
+	}
135
+	return err
136
+}