feat: IPFSWatch
Brian Tiger Chow committed
Jan 21, 2015 at 22:45 UTC
038ec6a5b3a4dc6732b7a5522afe07d4d520bf1e
19 files changed
+3148
Godeps/Godeps.json
+5
@@ -208,6 +208,11 @@
208
"ImportPath": "github.com/syndtr/gosnappy/snappy",
209
"Rev": "ce8acff4829e0c2458a67ead32390ac0a381c862"
210
},
211
+ {
212
+ "ImportPath": "gopkg.in/fsnotify.v1",
213
+ "Comment": "v1.1.0",
214
+ "Rev": "f582d920d11386e8ae15227bb5933a8f9b4c3dec"
215
+ },
216
{
217
"ImportPath": "gopkg.in/natefinch/lumberjack.v2",
218
"Comment": "v1.0-12-gd28785c",
Godeps/_workspace/src/gopkg.in/fsnotify.v1/.gitignore
new
+6
@@ -0,0 +1,6 @@
1
+# Setup a Global .gitignore for OS and editor generated files:
2
+# https://help.github.com/articles/ignoring-files
3
+# git config --global core.excludesfile ~/.gitignore_global
4
+
5
+.vagrant
6
+*.sublime-project
Godeps/_workspace/src/gopkg.in/fsnotify.v1/.travis.yml
new
+13
@@ -0,0 +1,13 @@
1
+language: go
2
+
3
+go:
4
+ - 1.2
5
+ - tip
6
+
7
+# not yet https://github.com/travis-ci/travis-ci/issues/2318
8
+os:
9
+ - linux
10
+ - osx
11
+
12
+notifications:
13
+ email: false
Godeps/_workspace/src/gopkg.in/fsnotify.v1/AUTHORS
new
+33
@@ -0,0 +1,33 @@
1
+# Names should be added to this file as
2
+# Name or Organization <email address>
3
+# The email address is not required for organizations.
4
+
5
+# You can update this list using the following command:
6
+#
7
+# $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
8
+
9
+# Please keep the list sorted.
10
+
11
+Adrien Bustany <adrien@bustany.org>
12
+Caleb Spare <cespare@gmail.com>
13
+Case Nelson <case@teammating.com>
14
+Chris Howey <howeyc@gmail.com> <chris@howey.me>
15
+Christoffer Buchholz <christoffer.buchholz@gmail.com>
16
+Dave Cheney <dave@cheney.net>
17
+Francisco Souza <f@souza.cc>
18
+Hari haran <hariharan.uno@gmail.com>
19
+John C Barstow
20
+Kelvin Fo <vmirage@gmail.com>
21
+Matt Layher <mdlayher@gmail.com>
22
+Nathan Youngman <git@nathany.com>
23
+Paul Hammond <paul@paulhammond.org>
24
+Pursuit92 <JoshChase@techpursuit.net>
25
+Rob Figueiredo <robfig@gmail.com>
26
+Soge Zhang <zhssoge@gmail.com>
27
+Tilak Sharma <tilaks@google.com>
28
+Travis Cline <travis.cline@gmail.com>
29
+Tudor Golubenco <tudor.g@gmail.com>
30
+Yukang <moorekang@gmail.com>
31
+bronze1man <bronze1man@gmail.com>
32
+debrando <denis.brandolini@gmail.com>
33
+henrikedwards <henrik.edwards@gmail.com>
Godeps/_workspace/src/gopkg.in/fsnotify.v1/CHANGELOG.md
new
+249
@@ -0,0 +1,249 @@
1
+# Changelog
2
+
3
+## v1.1.0 / 2014-12-12
4
+
5
+* kqueue: rework internals [#43](https://github.com/go-fsnotify/fsnotify/pull/43)
6
+ * add low-level functions
7
+ * only need to store flags on directories
8
+ * less mutexes [#13](https://github.com/go-fsnotify/fsnotify/issues/13)
9
+ * done can be an unbuffered channel
10
+ * remove calls to os.NewSyscallError
11
+* More efficient string concatenation for Event.String() [#52](https://github.com/go-fsnotify/fsnotify/pull/52) (thanks @mdlayher)
12
+* kqueue: fix regression in rework causing subdirectories to be watched [#48](https://github.com/go-fsnotify/fsnotify/issues/48)
13
+* kqueue: cleanup internal watch before sending remove event [#51](https://github.com/go-fsnotify/fsnotify/issues/48)
14
+
15
+## v1.0.4 / 2014-09-07
16
+
17
+* kqueue: add dragonfly to the build tags.
18
+* Rename source code files, rearrange code so exported APIs are at the top.
19
+* Add done channel to example code. [#37](https://github.com/go-fsnotify/fsnotify/pull/37) (thanks @chenyukang)
20
+
21
+## v1.0.3 / 2014-08-19
22
+
23
+* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/go-fsnotify/fsnotify/issues/36)
24
+
25
+## v1.0.2 / 2014-08-17
26
+
27
+* [Fix] Missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso)
28
+* [Fix] Make ./path and path equivalent. (thanks @zhsso)
29
+
30
+## v1.0.0 / 2014-08-15
31
+
32
+* [API] Remove AddWatch on Windows, use Add.
33
+* Improve documentation for exported identifiers. [#30](https://github.com/go-fsnotify/fsnotify/issues/30)
34
+* Minor updates based on feedback from golint.
35
+
36
+## dev / 2014-07-09
37
+
38
+* Moved to [github.com/go-fsnotify/fsnotify](https://github.com/go-fsnotify/fsnotify).
39
+* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
40
+
41
+## dev / 2014-07-04
42
+
43
+* kqueue: fix incorrect mutex used in Close()
44
+* Update example to demonstrate usage of Op.
45
+
46
+## dev / 2014-06-28
47
+
48
+* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/go-fsnotify/fsnotify/issues/4)
49
+* Fix for String() method on Event (thanks Alex Brainman)
50
+* Don't build on Plan 9 or Solaris (thanks @4ad)
51
+
52
+## dev / 2014-06-21
53
+
54
+* Events channel of type Event rather than *Event.
55
+* [internal] use syscall constants directly for inotify and kqueue.
56
+* [internal] kqueue: rename events to kevents and fileEvent to event.
57
+
58
+## dev / 2014-06-19
59
+
60
+* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
61
+* [internal] remove cookie from Event struct (unused).
62
+* [internal] Event struct has the same definition across every OS.
63
+* [internal] remove internal watch and removeWatch methods.
64
+
65
+## dev / 2014-06-12
66
+
67
+* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
68
+* [API] Pluralized channel names: Events and Errors.
69
+* [API] Renamed FileEvent struct to Event.
70
+* [API] Op constants replace methods like IsCreate().
71
+
72
+## dev / 2014-06-12
73
+
74
+* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
75
+
76
+## dev / 2014-05-23
77
+
78
+* [API] Remove current implementation of WatchFlags.
79
+ * current implementation doesn't take advantage of OS for efficiency
80
+ * provides little benefit over filtering events as they are received, but has extra bookkeeping and mutexes
81
+ * no tests for the current implementation
82
+ * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
83
+
84
+## v0.9.2 / 2014-08-17
85
+
86
+* [Backport] Fix missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso)
87
+
88
+## v0.9.1 / 2014-06-12
89
+
90
+* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
91
+
92
+## v0.9.0 / 2014-01-17
93
+
94
+* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
95
+* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
96
+* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
97
+
98
+## v0.8.12 / 2013-11-13
99
+
100
+* [API] Remove FD_SET and friends from Linux adapter
101
+
102
+## v0.8.11 / 2013-11-02
103
+
104
+* [Doc] Add Changelog [#72][] (thanks @nathany)
105
+* [Doc] Spotlight and double modify events on OS X [#62][] (reported by @paulhammond)
106
+
107
+## v0.8.10 / 2013-10-19
108
+
109
+* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
110
+* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
111
+* [Doc] specify OS-specific limits in README (thanks @debrando)
112
+
113
+## v0.8.9 / 2013-09-08
114
+
115
+* [Doc] Contributing (thanks @nathany)
116
+* [Doc] update package path in example code [#63][] (thanks @paulhammond)
117
+* [Doc] GoCI badge in README (Linux only) [#60][]
118
+* [Doc] Cross-platform testing with Vagrant [#59][] (thanks @nathany)
119
+
120
+## v0.8.8 / 2013-06-17
121
+
122
+* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
123
+
124
+## v0.8.7 / 2013-06-03
125
+
126
+* [API] Make syscall flags internal
127
+* [Fix] inotify: ignore event changes
128
+* [Fix] race in symlink test [#45][] (reported by @srid)
129
+* [Fix] tests on Windows
130
+* lower case error messages
131
+
132
+## v0.8.6 / 2013-05-23
133
+
134
+* kqueue: Use EVT_ONLY flag on Darwin
135
+* [Doc] Update README with full example
136
+
137
+## v0.8.5 / 2013-05-09
138
+
139
+* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
140
+
141
+## v0.8.4 / 2013-04-07
142
+
143
+* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
144
+
145
+## v0.8.3 / 2013-03-13
146
+
147
+* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
148
+* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
149
+
150
+## v0.8.2 / 2013-02-07
151
+
152
+* [Doc] add Authors
153
+* [Fix] fix data races for map access [#29][] (thanks @fsouza)
154
+
155
+## v0.8.1 / 2013-01-09
156
+
157
+* [Fix] Windows path separators
158
+* [Doc] BSD License
159
+
160
+## v0.8.0 / 2012-11-09
161
+
162
+* kqueue: directory watching improvements (thanks @vmirage)
163
+* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
164
+* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
165
+
166
+## v0.7.4 / 2012-10-09
167
+
168
+* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
169
+* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
170
+* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
171
+* [Fix] kqueue: modify after recreation of file
172
+
173
+## v0.7.3 / 2012-09-27
174
+
175
+* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
176
+* [Fix] kqueue: no longer get duplicate CREATE events
177
+
178
+## v0.7.2 / 2012-09-01
179
+
180
+* kqueue: events for created directories
181
+
182
+## v0.7.1 / 2012-07-14
183
+
184
+* [Fix] for renaming files
185
+
186
+## v0.7.0 / 2012-07-02
187
+
188
+* [Feature] FSNotify flags
189
+* [Fix] inotify: Added file name back to event path
190
+
191
+## v0.6.0 / 2012-06-06
192
+
193
+* kqueue: watch files after directory created (thanks @tmc)
194
+
195
+## v0.5.1 / 2012-05-22
196
+
197
+* [Fix] inotify: remove all watches before Close()
198
+
199
+## v0.5.0 / 2012-05-03
200
+
201
+* [API] kqueue: return errors during watch instead of sending over channel
202
+* kqueue: match symlink behavior on Linux
203
+* inotify: add `DELETE_SELF` (requested by @taralx)
204
+* [Fix] kqueue: handle EINTR (reported by @robfig)
205
+* [Doc] Godoc example [#1][] (thanks @davecheney)
206
+
207
+## v0.4.0 / 2012-03-30
208
+
209
+* Go 1 released: build with go tool
210
+* [Feature] Windows support using winfsnotify
211
+* Windows does not have attribute change notifications
212
+* Roll attribute notifications into IsModify
213
+
214
+## v0.3.0 / 2012-02-19
215
+
216
+* kqueue: add files when watch directory
217
+
218
+## v0.2.0 / 2011-12-30
219
+
220
+* update to latest Go weekly code
221
+
222
+## v0.1.0 / 2011-10-19
223
+
224
+* kqueue: add watch on file creation to match inotify
225
+* kqueue: create file event
226
+* inotify: ignore `IN_IGNORED` events
227
+* event String()
228
+* linux: common FileEvent functions
229
+* initial commit
230
+
231
+[#79]: https://github.com/howeyc/fsnotify/pull/79
232
+[#77]: https://github.com/howeyc/fsnotify/pull/77
233
+[#72]: https://github.com/howeyc/fsnotify/issues/72
234
+[#71]: https://github.com/howeyc/fsnotify/issues/71
235
+[#70]: https://github.com/howeyc/fsnotify/issues/70
236
+[#63]: https://github.com/howeyc/fsnotify/issues/63
237
+[#62]: https://github.com/howeyc/fsnotify/issues/62
238
+[#60]: https://github.com/howeyc/fsnotify/issues/60
239
+[#59]: https://github.com/howeyc/fsnotify/issues/59
240
+[#49]: https://github.com/howeyc/fsnotify/issues/49
241
+[#45]: https://github.com/howeyc/fsnotify/issues/45
242
+[#40]: https://github.com/howeyc/fsnotify/issues/40
243
+[#36]: https://github.com/howeyc/fsnotify/issues/36
244
+[#33]: https://github.com/howeyc/fsnotify/issues/33
245
+[#29]: https://github.com/howeyc/fsnotify/issues/29
246
+[#25]: https://github.com/howeyc/fsnotify/issues/25
247
+[#24]: https://github.com/howeyc/fsnotify/issues/24
248
+[#21]: https://github.com/howeyc/fsnotify/issues/21
249
+
Godeps/_workspace/src/gopkg.in/fsnotify.v1/CONTRIBUTING.md
new
+72
@@ -0,0 +1,72 @@
1
+# Contributing
2
+
3
+* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com).
4
+
5
+### Issues
6
+
7
+* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues).
8
+* Please indicate the platform you are running on.
9
+
10
+### Pull Requests
11
+
12
+fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/go-fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
13
+
14
+Please indicate that you have signed the CLA in your pull request.
15
+
16
+To hack on fsnotify, please use [the workflow outlined by Katrina Owen](https://blog.splice.com/contributing-open-source-git-repositories-go/), in short:
17
+
18
+1. Install as usual (`go get -u github.com/go-fsnotify/fsnotify`)
19
+2. Create your feature branch (`git checkout -b my-new-feature`)
20
+3. Ensure everything works and the tests pass (see below)
21
+4. Commit your changes (`git commit -am 'Add some feature'`)
22
+
23
+Contribute upstream:
24
+
25
+1. Fork fsnotify on GitHub
26
+2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
27
+3. Push to the branch (`git push fork my-new-feature`)
28
+4. Create a new Pull Request on GitHub
29
+
30
+If other team members need your patch before it is merged:
31
+
32
+1. Install as usual (`go get -u github.com/go-fsnotify/fsnotify`)
33
+2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
34
+3. Pull your revisions (`git fetch fork; git checkout -b my-new-feature fork/my-new-feature`)
35
+
36
+Notice: For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
37
+
38
+Note: The maintainers will update the CHANGELOG on your behalf. Please don't modify it in your pull request.
39
+
40
+### Testing
41
+
42
+fsnotify uses build tags to compile different code on Linux, BSD, OS X, and Windows.
43
+
44
+Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
45
+
46
+To aid in cross-platform testing there is a Vagrantfile for Linux and BSD.
47
+
48
+* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
49
+* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
50
+* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
51
+* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd go-fsnotify/fsnotify; go test'`.
52
+* When you're done, you will want to halt or destroy the Vagrant boxes.
53
+
54
+Notice: fsnotify file system events won't trigger in shared folders. The tests get around this limitation by using the /tmp directory.
55
+
56
+Right now there is no equivalent solution for Windows and OS X, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
57
+
58
+### Maintainers
59
+
60
+Help maintaining fsnotify is welcome. To be a maintainer:
61
+
62
+* Submit a pull request and sign the CLA as above.
63
+* You must be able to run the test suite on Mac, Windows, Linux and BSD.
64
+
65
+To keep master clean, the fsnotify project uses the "apply mail" workflow outlined in Nathaniel Talbott's post ["Merge pull request" Considered Harmful](http://blog.spreedly.com/2014/06/24/merge-pull-request-considered-harmful/#.VGa5yZPF_Zs).
66
+
67
+This requires installing [hub](https://github.com/github/hub). Both version 1 and 2 support `hub am -3`.
68
+
69
+All code changes should be internal pull requests.
70
+
71
+Releases are tagged using [Semantic Versioning](http://semver.org/), which makes them available through gopkg.in.
72
+
Godeps/_workspace/src/gopkg.in/fsnotify.v1/LICENSE
new
+28
@@ -0,0 +1,28 @@
1
+Copyright (c) 2012 The Go Authors. All rights reserved.
2
+Copyright (c) 2012 fsnotify Authors. All rights reserved.
3
+
4
+Redistribution and use in source and binary forms, with or without
5
+modification, are permitted provided that the following conditions are
6
+met:
7
+
8
+ * Redistributions of source code must retain the above copyright
9
+notice, this list of conditions and the following disclaimer.
10
+ * Redistributions in binary form must reproduce the above
11
+copyright notice, this list of conditions and the following disclaimer
12
+in the documentation and/or other materials provided with the
13
+distribution.
14
+ * Neither the name of Google Inc. nor the names of its
15
+contributors may be used to endorse or promote products derived from
16
+this software without specific prior written permission.
17
+
18
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Godeps/_workspace/src/gopkg.in/fsnotify.v1/README.md
new
+62
@@ -0,0 +1,62 @@
1
+# File system notifications for Go
2
+
3
+[](http://gocover.io/github.com/go-fsnotify/fsnotify) [](https://godoc.org/gopkg.in/fsnotify.v1)
4
+
5
+Go 1.3+ required.
6
+
7
+Cross platform: Windows, Linux, BSD and OS X.
8
+
9
+|Adapter |OS |Status |
10
+|----------|----------|----------|
11
+|inotify |Linux, Android\*|Supported|
12
+|kqueue |BSD, OS X, iOS\*|Supported|
13
+|ReadDirectoryChangesW|Windows|Supported|
14
+|FSEvents |OS X |[Planned](https://github.com/go-fsnotify/fsnotify/issues/11)|
15
+|FEN |Solaris 11 |[Planned](https://github.com/go-fsnotify/fsnotify/issues/12)|
16
+|fanotify |Linux 2.6.37+ | |
17
+|Polling |*All* |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/9)|
18
+| |Plan 9 | |
19
+
20
+\* Android and iOS are untested.
21
+
22
+Please see [the documentation](https://godoc.org/gopkg.in/fsnotify.v1) for usage. Consult the [Wiki](https://github.com/go-fsnotify/fsnotify/wiki) for the FAQ and further information.
23
+
24
+## API stability
25
+
26
+Two major versions of fsnotify exist.
27
+
28
+**[fsnotify.v0](https://gopkg.in/fsnotify.v0)** is API-compatible with [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify). Bugfixes *may* be backported, but I recommend upgrading to v1.
29
+
30
+```go
31
+import "gopkg.in/fsnotify.v0"
32
+```
33
+
34
+\* Refer to the package as fsnotify (without the .v0 suffix).
35
+
36
+**[fsnotify.v1](https://gopkg.in/fsnotify.v1)** provides [a new API](https://godoc.org/gopkg.in/fsnotify.v1) based on [this design document](http://goo.gl/MrYxyA). You can import v1 with:
37
+
38
+```go
39
+import "gopkg.in/fsnotify.v1"
40
+```
41
+
42
+Further API changes are [planned](https://github.com/go-fsnotify/fsnotify/milestones), but a new major revision will be tagged, so you can depend on the v1 API.
43
+
44
+**master** may have untagged changes. Use it to test the very latest code,
45
+but don't expect it to remain API-compatible:
46
+
47
+```go
48
+import "github.com/go-fsnotify/fsnotify"
49
+```
50
+
51
+## Contributing
52
+
53
+* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com).
54
+* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues). Please indicate the platform you are running on.
55
+
56
+fsnotify is derived from code in the [golang.org/x/exp](https://godoc.org/golang.org/x/exp) package and it may be included [in the standard library](https://github.com/go-fsnotify/fsnotify/issues/1) in the future. Therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so you need to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
57
+
58
+Please read [CONTRIBUTING](https://github.com/go-fsnotify/fsnotify/blob/master/CONTRIBUTING.md) before opening a pull request.
59
+
60
+## Example
61
+
62
+See [example_test.go](https://github.com/go-fsnotify/fsnotify/blob/master/example_test.go).
Godeps/_workspace/src/gopkg.in/fsnotify.v1/example_test.go
new
+42
@@ -0,0 +1,42 @@
1
+// Copyright 2012 The Go Authors. All rights reserved.
2
+// Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+// +build !plan9,!solaris
6
+
7
+package fsnotify_test
8
+
9
+import (
10
+ "log"
11
+
12
+ "github.com/jbenet/go-ipfs/Godeps/_workspace/src/gopkg.in/fsnotify.v1"
13
+)
14
+
15
+func ExampleNewWatcher() {
16
+ watcher, err := fsnotify.NewWatcher()
17
+ if err != nil {
18
+ log.Fatal(err)
19
+ }
20
+ defer watcher.Close()
21
+
22
+ done := make(chan bool)
23
+ go func() {
24
+ for {
25
+ select {
26
+ case event := <-watcher.Events:
27
+ log.Println("event:", event)
28
+ if event.Op&fsnotify.Write == fsnotify.Write {
29
+ log.Println("modified file:", event.Name)
30
+ }
31
+ case err := <-watcher.Errors:
32
+ log.Println("error:", err)
33
+ }
34
+ }
35
+ }()
36
+
37
+ err = watcher.Add("/tmp/foo")
38
+ if err != nil {
39
+ log.Fatal(err)
40
+ }
41
+ <-done
42
+}
Godeps/_workspace/src/gopkg.in/fsnotify.v1/fsnotify.go
new
+62
@@ -0,0 +1,62 @@
1
+// Copyright 2012 The Go Authors. All rights reserved.
2
+// Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+// +build !plan9,!solaris
6
+
7
+// Package fsnotify provides a platform-independent interface for file system notifications.
8
+package fsnotify
9
+
10
+import (
11
+ "bytes"
12
+ "fmt"
13
+)
14
+
15
+// Event represents a single file system notification.
16
+type Event struct {
17
+ Name string // Relative path to the file or directory.
18
+ Op Op // File operation that triggered the event.
19
+}
20
+
21
+// Op describes a set of file operations.
22
+type Op uint32
23
+
24
+// These are the generalized file operations that can trigger a notification.
25
+const (
26
+ Create Op = 1 << iota
27
+ Write
28
+ Remove
29
+ Rename
30
+ Chmod
31
+)
32
+
33
+// String returns a string representation of the event in the form
34
+// "file: REMOVE|WRITE|..."
35
+func (e Event) String() string {
36
+ // Use a buffer for efficient string concatenation
37
+ var buffer bytes.Buffer
38
+
39
+ if e.Op&Create == Create {
40
+ buffer.WriteString("|CREATE")
41
+ }
42
+ if e.Op&Remove == Remove {
43
+ buffer.WriteString("|REMOVE")
44
+ }
45
+ if e.Op&Write == Write {
46
+ buffer.WriteString("|WRITE")
47
+ }
48
+ if e.Op&Rename == Rename {
49
+ buffer.WriteString("|RENAME")
50
+ }
51
+ if e.Op&Chmod == Chmod {
52
+ buffer.WriteString("|CHMOD")
53
+ }
54
+
55
+ // If buffer remains empty, return no event names
56
+ if buffer.Len() == 0 {
57
+ return fmt.Sprintf("%q: ", e.Name)
58
+ }
59
+
60
+ // Return a list of event names, with leading pipe character stripped
61
+ return fmt.Sprintf("%q: %s", e.Name, buffer.String()[1:])
62
+}
Godeps/_workspace/src/gopkg.in/fsnotify.v1/inotify.go
new
+239
@@ -0,0 +1,239 @@
1
+// Copyright 2010 The Go Authors. All rights reserved.
2
+// Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+// +build linux
6
+
7
+package fsnotify
8
+
9
+import (
10
+ "errors"
11
+ "fmt"
12
+ "os"
13
+ "path/filepath"
14
+ "strings"
15
+ "sync"
16
+ "syscall"
17
+ "unsafe"
18
+)
19
+
20
+// Watcher watches a set of files, delivering events to a channel.
21
+type Watcher struct {
22
+ Events chan Event
23
+ Errors chan error
24
+ mu sync.Mutex // Map access
25
+ fd int // File descriptor (as returned by the inotify_init() syscall)
26
+ watches map[string]*watch // Map of inotify watches (key: path)
27
+ paths map[int]string // Map of watched paths (key: watch descriptor)
28
+ done chan bool // Channel for sending a "quit message" to the reader goroutine
29
+ isClosed bool // Set to true when Close() is first called
30
+}
31
+
32
+// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
33
+func NewWatcher() (*Watcher, error) {
34
+ fd, errno := syscall.InotifyInit()
35
+ if fd == -1 {
36
+ return nil, os.NewSyscallError("inotify_init", errno)
37
+ }
38
+ w := &Watcher{
39
+ fd: fd,
40
+ watches: make(map[string]*watch),
41
+ paths: make(map[int]string),
42
+ Events: make(chan Event),
43
+ Errors: make(chan error),
44
+ done: make(chan bool, 1),
45
+ }
46
+
47
+ go w.readEvents()
48
+ return w, nil
49
+}
50
+
51
+// Close removes all watches and closes the events channel.
52
+func (w *Watcher) Close() error {
53
+ if w.isClosed {
54
+ return nil
55
+ }
56
+ w.isClosed = true
57
+
58
+ // Remove all watches
59
+ for name := range w.watches {
60
+ w.Remove(name)
61
+ }
62
+
63
+ // Send "quit" message to the reader goroutine
64
+ w.done <- true
65
+
66
+ return nil
67
+}
68
+
69
+// Add starts watching the named file or directory (non-recursively).
70
+func (w *Watcher) Add(name string) error {
71
+ name = filepath.Clean(name)
72
+ if w.isClosed {
73
+ return errors.New("inotify instance already closed")
74
+ }
75
+
76
+ const agnosticEvents = syscall.IN_MOVED_TO | syscall.IN_MOVED_FROM |
77
+ syscall.IN_CREATE | syscall.IN_ATTRIB | syscall.IN_MODIFY |
78
+ syscall.IN_MOVE_SELF | syscall.IN_DELETE | syscall.IN_DELETE_SELF
79
+
80
+ var flags uint32 = agnosticEvents
81
+
82
+ w.mu.Lock()
83
+ watchEntry, found := w.watches[name]
84
+ w.mu.Unlock()
85
+ if found {
86
+ watchEntry.flags |= flags
87
+ flags |= syscall.IN_MASK_ADD
88
+ }
89
+ wd, errno := syscall.InotifyAddWatch(w.fd, name, flags)
90
+ if wd == -1 {
91
+ return os.NewSyscallError("inotify_add_watch", errno)
92
+ }
93
+
94
+ w.mu.Lock()
95
+ w.watches[name] = &watch{wd: uint32(wd), flags: flags}
96
+ w.paths[wd] = name
97
+ w.mu.Unlock()
98
+
99
+ return nil
100
+}
101
+
102
+// Remove stops watching the the named file or directory (non-recursively).
103
+func (w *Watcher) Remove(name string) error {
104
+ name = filepath.Clean(name)
105
+ w.mu.Lock()
106
+ defer w.mu.Unlock()
107
+ watch, ok := w.watches[name]
108
+ if !ok {
109
+ return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
110
+ }
111
+ success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
112
+ if success == -1 {
113
+ return os.NewSyscallError("inotify_rm_watch", errno)
114
+ }
115
+ delete(w.watches, name)
116
+ return nil
117
+}
118
+
119
+type watch struct {
120
+ wd uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
121
+ flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
122
+}
123
+
124
+// readEvents reads from the inotify file descriptor, converts the
125
+// received events into Event objects and sends them via the Events channel
126
+func (w *Watcher) readEvents() {
127
+ var (
128
+ buf [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
129
+ n int // Number of bytes read with read()
130
+ errno error // Syscall errno
131
+ )
132
+
133
+ for {
134
+ // See if there is a message on the "done" channel
135
+ select {
136
+ case <-w.done:
137
+ syscall.Close(w.fd)
138
+ close(w.Events)
139
+ close(w.Errors)
140
+ return
141
+ default:
142
+ }
143
+
144
+ n, errno = syscall.Read(w.fd, buf[:])
145
+
146
+ // If EOF is received
147
+ if n == 0 {
148
+ syscall.Close(w.fd)
149
+ close(w.Events)
150
+ close(w.Errors)
151
+ return
152
+ }
153
+
154
+ if n < 0 {
155
+ w.Errors <- os.NewSyscallError("read", errno)
156
+ continue
157
+ }
158
+ if n < syscall.SizeofInotifyEvent {
159
+ w.Errors <- errors.New("inotify: short read in readEvents()")
160
+ continue
161
+ }
162
+
163
+ var offset uint32
164
+ // We don't know how many events we just read into the buffer
165
+ // While the offset points to at least one whole event...
166
+ for offset <= uint32(n-syscall.SizeofInotifyEvent) {
167
+ // Point "raw" to the event in the buffer
168
+ raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
169
+
170
+ mask := uint32(raw.Mask)
171
+ nameLen := uint32(raw.Len)
172
+ // If the event happened to the watched directory or the watched file, the kernel
173
+ // doesn't append the filename to the event, but we would like to always fill the
174
+ // the "Name" field with a valid filename. We retrieve the path of the watch from
175
+ // the "paths" map.
176
+ w.mu.Lock()
177
+ name := w.paths[int(raw.Wd)]
178
+ w.mu.Unlock()
179
+ if nameLen > 0 {
180
+ // Point "bytes" at the first byte of the filename
181
+ bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
182
+ // The filename is padded with NULL bytes. TrimRight() gets rid of those.
183
+ name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
184
+ }
185
+
186
+ event := newEvent(name, mask)
187
+
188
+ // Send the events that are not ignored on the events channel
189
+ if !event.ignoreLinux(mask) {
190
+ w.Events <- event
191
+ }
192
+
193
+ // Move to the next event in the buffer
194
+ offset += syscall.SizeofInotifyEvent + nameLen
195
+ }
196
+ }
197
+}
198
+
199
+// Certain types of events can be "ignored" and not sent over the Events
200
+// channel. Such as events marked ignore by the kernel, or MODIFY events
201
+// against files that do not exist.
202
+func (e *Event) ignoreLinux(mask uint32) bool {
203
+ // Ignore anything the inotify API says to ignore
204
+ if mask&syscall.IN_IGNORED == syscall.IN_IGNORED {
205
+ return true
206
+ }
207
+
208
+ // If the event is not a DELETE or RENAME, the file must exist.
209
+ // Otherwise the event is ignored.
210
+ // *Note*: this was put in place because it was seen that a MODIFY
211
+ // event was sent after the DELETE. This ignores that MODIFY and
212
+ // assumes a DELETE will come or has come if the file doesn't exist.
213
+ if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
214
+ _, statErr := os.Lstat(e.Name)
215
+ return os.IsNotExist(statErr)
216
+ }
217
+ return false
218
+}
219
+
220
+// newEvent returns an platform-independent Event based on an inotify mask.
221
+func newEvent(name string, mask uint32) Event {
222
+ e := Event{Name: name}
223
+ if mask&syscall.IN_CREATE == syscall.IN_CREATE || mask&syscall.IN_MOVED_TO == syscall.IN_MOVED_TO {
224
+ e.Op |= Create
225
+ }
226
+ if mask&syscall.IN_DELETE_SELF == syscall.IN_DELETE_SELF || mask&syscall.IN_DELETE == syscall.IN_DELETE {
227
+ e.Op |= Remove
228
+ }
229
+ if mask&syscall.IN_MODIFY == syscall.IN_MODIFY {
230
+ e.Op |= Write
231
+ }
232
+ if mask&syscall.IN_MOVE_SELF == syscall.IN_MOVE_SELF || mask&syscall.IN_MOVED_FROM == syscall.IN_MOVED_FROM {
233
+ e.Op |= Rename
234
+ }
235
+ if mask&syscall.IN_ATTRIB == syscall.IN_ATTRIB {
236
+ e.Op |= Chmod
237
+ }
238
+ return e
239
+}
Godeps/_workspace/src/gopkg.in/fsnotify.v1/integration_test.go
new
+1120
@@ -0,0 +1,1120 @@
1
+// Copyright 2010 The Go Authors. All rights reserved.
2
+// Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+// +build !plan9,!solaris
6
+
7
+package fsnotify
8
+
9
+import (
10
+ "io/ioutil"
11
+ "os"
12
+ "os/exec"
13
+ "path/filepath"
14
+ "runtime"
15
+ "sync/atomic"
16
+ "testing"
17
+ "time"
18
+)
19
+
20
+// An atomic counter
21
+type counter struct {
22
+ val int32
23
+}
24
+
25
+func (c *counter) increment() {
26
+ atomic.AddInt32(&c.val, 1)
27
+}
28
+
29
+func (c *counter) value() int32 {
30
+ return atomic.LoadInt32(&c.val)
31
+}
32
+
33
+func (c *counter) reset() {
34
+ atomic.StoreInt32(&c.val, 0)
35
+}
36
+
37
+// tempMkdir makes a temporary directory
38
+func tempMkdir(t *testing.T) string {
39
+ dir, err := ioutil.TempDir("", "fsnotify")
40
+ if err != nil {
41
+ t.Fatalf("failed to create test directory: %s", err)
42
+ }
43
+ return dir
44
+}
45
+
46
+// newWatcher initializes an fsnotify Watcher instance.
47
+func newWatcher(t *testing.T) *Watcher {
48
+ watcher, err := NewWatcher()
49
+ if err != nil {
50
+ t.Fatalf("NewWatcher() failed: %s", err)
51
+ }
52
+ return watcher
53
+}
54
+
55
+// addWatch adds a watch for a directory
56
+func addWatch(t *testing.T, watcher *Watcher, dir string) {
57
+ if err := watcher.Add(dir); err != nil {
58
+ t.Fatalf("watcher.Add(%q) failed: %s", dir, err)
59
+ }
60
+}
61
+
62
+func TestFsnotifyMultipleOperations(t *testing.T) {
63
+ watcher := newWatcher(t)
64
+
65
+ // Receive errors on the error channel on a separate goroutine
66
+ go func() {
67
+ for err := range watcher.Errors {
68
+ t.Fatalf("error received: %s", err)
69
+ }
70
+ }()
71
+
72
+ // Create directory to watch
73
+ testDir := tempMkdir(t)
74
+ defer os.RemoveAll(testDir)
75
+
76
+ // Create directory that's not watched
77
+ testDirToMoveFiles := tempMkdir(t)
78
+ defer os.RemoveAll(testDirToMoveFiles)
79
+
80
+ testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile")
81
+ testFileRenamed := filepath.Join(testDirToMoveFiles, "TestFsnotifySeqRename.testfile")
82
+
83
+ addWatch(t, watcher, testDir)
84
+
85
+ // Receive events on the event channel on a separate goroutine
86
+ eventstream := watcher.Events
87
+ var createReceived, modifyReceived, deleteReceived, renameReceived counter
88
+ done := make(chan bool)
89
+ go func() {
90
+ for event := range eventstream {
91
+ // Only count relevant events
92
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
93
+ t.Logf("event received: %s", event)
94
+ if event.Op&Remove == Remove {
95
+ deleteReceived.increment()
96
+ }
97
+ if event.Op&Write == Write {
98
+ modifyReceived.increment()
99
+ }
100
+ if event.Op&Create == Create {
101
+ createReceived.increment()
102
+ }
103
+ if event.Op&Rename == Rename {
104
+ renameReceived.increment()
105
+ }
106
+ } else {
107
+ t.Logf("unexpected event received: %s", event)
108
+ }
109
+ }
110
+ done <- true
111
+ }()
112
+
113
+ // Create a file
114
+ // This should add at least one event to the fsnotify event queue
115
+ var f *os.File
116
+ f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
117
+ if err != nil {
118
+ t.Fatalf("creating test file failed: %s", err)
119
+ }
120
+ f.Sync()
121
+
122
+ time.Sleep(time.Millisecond)
123
+ f.WriteString("data")
124
+ f.Sync()
125
+ f.Close()
126
+
127
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
128
+
129
+ if err := testRename(testFile, testFileRenamed); err != nil {
130
+ t.Fatalf("rename failed: %s", err)
131
+ }
132
+
133
+ // Modify the file outside of the watched dir
134
+ f, err = os.Open(testFileRenamed)
135
+ if err != nil {
136
+ t.Fatalf("open test renamed file failed: %s", err)
137
+ }
138
+ f.WriteString("data")
139
+ f.Sync()
140
+ f.Close()
141
+
142
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
143
+
144
+ // Recreate the file that was moved
145
+ f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
146
+ if err != nil {
147
+ t.Fatalf("creating test file failed: %s", err)
148
+ }
149
+ f.Close()
150
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
151
+
152
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
153
+ time.Sleep(500 * time.Millisecond)
154
+ cReceived := createReceived.value()
155
+ if cReceived != 2 {
156
+ t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
157
+ }
158
+ mReceived := modifyReceived.value()
159
+ if mReceived != 1 {
160
+ t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1)
161
+ }
162
+ dReceived := deleteReceived.value()
163
+ rReceived := renameReceived.value()
164
+ if dReceived+rReceived != 1 {
165
+ t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", rReceived+dReceived, 1)
166
+ }
167
+
168
+ // Try closing the fsnotify instance
169
+ t.Log("calling Close()")
170
+ watcher.Close()
171
+ t.Log("waiting for the event channel to become closed...")
172
+ select {
173
+ case <-done:
174
+ t.Log("event channel closed")
175
+ case <-time.After(2 * time.Second):
176
+ t.Fatal("event stream was not closed after 2 seconds")
177
+ }
178
+}
179
+
180
+func TestFsnotifyMultipleCreates(t *testing.T) {
181
+ watcher := newWatcher(t)
182
+
183
+ // Receive errors on the error channel on a separate goroutine
184
+ go func() {
185
+ for err := range watcher.Errors {
186
+ t.Fatalf("error received: %s", err)
187
+ }
188
+ }()
189
+
190
+ // Create directory to watch
191
+ testDir := tempMkdir(t)
192
+ defer os.RemoveAll(testDir)
193
+
194
+ testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile")
195
+
196
+ addWatch(t, watcher, testDir)
197
+
198
+ // Receive events on the event channel on a separate goroutine
199
+ eventstream := watcher.Events
200
+ var createReceived, modifyReceived, deleteReceived counter
201
+ done := make(chan bool)
202
+ go func() {
203
+ for event := range eventstream {
204
+ // Only count relevant events
205
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
206
+ t.Logf("event received: %s", event)
207
+ if event.Op&Remove == Remove {
208
+ deleteReceived.increment()
209
+ }
210
+ if event.Op&Create == Create {
211
+ createReceived.increment()
212
+ }
213
+ if event.Op&Write == Write {
214
+ modifyReceived.increment()
215
+ }
216
+ } else {
217
+ t.Logf("unexpected event received: %s", event)
218
+ }
219
+ }
220
+ done <- true
221
+ }()
222
+
223
+ // Create a file
224
+ // This should add at least one event to the fsnotify event queue
225
+ var f *os.File
226
+ f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
227
+ if err != nil {
228
+ t.Fatalf("creating test file failed: %s", err)
229
+ }
230
+ f.Sync()
231
+
232
+ time.Sleep(time.Millisecond)
233
+ f.WriteString("data")
234
+ f.Sync()
235
+ f.Close()
236
+
237
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
238
+
239
+ os.Remove(testFile)
240
+
241
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
242
+
243
+ // Recreate the file
244
+ f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
245
+ if err != nil {
246
+ t.Fatalf("creating test file failed: %s", err)
247
+ }
248
+ f.Close()
249
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
250
+
251
+ // Modify
252
+ f, err = os.OpenFile(testFile, os.O_WRONLY, 0666)
253
+ if err != nil {
254
+ t.Fatalf("creating test file failed: %s", err)
255
+ }
256
+ f.Sync()
257
+
258
+ time.Sleep(time.Millisecond)
259
+ f.WriteString("data")
260
+ f.Sync()
261
+ f.Close()
262
+
263
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
264
+
265
+ // Modify
266
+ f, err = os.OpenFile(testFile, os.O_WRONLY, 0666)
267
+ if err != nil {
268
+ t.Fatalf("creating test file failed: %s", err)
269
+ }
270
+ f.Sync()
271
+
272
+ time.Sleep(time.Millisecond)
273
+ f.WriteString("data")
274
+ f.Sync()
275
+ f.Close()
276
+
277
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
278
+
279
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
280
+ time.Sleep(500 * time.Millisecond)
281
+ cReceived := createReceived.value()
282
+ if cReceived != 2 {
283
+ t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
284
+ }
285
+ mReceived := modifyReceived.value()
286
+ if mReceived < 3 {
287
+ t.Fatalf("incorrect number of modify events received after 500 ms (%d vs atleast %d)", mReceived, 3)
288
+ }
289
+ dReceived := deleteReceived.value()
290
+ if dReceived != 1 {
291
+ t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", dReceived, 1)
292
+ }
293
+
294
+ // Try closing the fsnotify instance
295
+ t.Log("calling Close()")
296
+ watcher.Close()
297
+ t.Log("waiting for the event channel to become closed...")
298
+ select {
299
+ case <-done:
300
+ t.Log("event channel closed")
301
+ case <-time.After(2 * time.Second):
302
+ t.Fatal("event stream was not closed after 2 seconds")
303
+ }
304
+}
305
+
306
+func TestFsnotifyDirOnly(t *testing.T) {
307
+ watcher := newWatcher(t)
308
+
309
+ // Create directory to watch
310
+ testDir := tempMkdir(t)
311
+ defer os.RemoveAll(testDir)
312
+
313
+ // Create a file before watching directory
314
+ // This should NOT add any events to the fsnotify event queue
315
+ testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
316
+ {
317
+ var f *os.File
318
+ f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
319
+ if err != nil {
320
+ t.Fatalf("creating test file failed: %s", err)
321
+ }
322
+ f.Sync()
323
+ f.Close()
324
+ }
325
+
326
+ addWatch(t, watcher, testDir)
327
+
328
+ // Receive errors on the error channel on a separate goroutine
329
+ go func() {
330
+ for err := range watcher.Errors {
331
+ t.Fatalf("error received: %s", err)
332
+ }
333
+ }()
334
+
335
+ testFile := filepath.Join(testDir, "TestFsnotifyDirOnly.testfile")
336
+
337
+ // Receive events on the event channel on a separate goroutine
338
+ eventstream := watcher.Events
339
+ var createReceived, modifyReceived, deleteReceived counter
340
+ done := make(chan bool)
341
+ go func() {
342
+ for event := range eventstream {
343
+ // Only count relevant events
344
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileAlreadyExists) {
345
+ t.Logf("event received: %s", event)
346
+ if event.Op&Remove == Remove {
347
+ deleteReceived.increment()
348
+ }
349
+ if event.Op&Write == Write {
350
+ modifyReceived.increment()
351
+ }
352
+ if event.Op&Create == Create {
353
+ createReceived.increment()
354
+ }
355
+ } else {
356
+ t.Logf("unexpected event received: %s", event)
357
+ }
358
+ }
359
+ done <- true
360
+ }()
361
+
362
+ // Create a file
363
+ // This should add at least one event to the fsnotify event queue
364
+ var f *os.File
365
+ f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
366
+ if err != nil {
367
+ t.Fatalf("creating test file failed: %s", err)
368
+ }
369
+ f.Sync()
370
+
371
+ time.Sleep(time.Millisecond)
372
+ f.WriteString("data")
373
+ f.Sync()
374
+ f.Close()
375
+
376
+ time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
377
+
378
+ os.Remove(testFile)
379
+ os.Remove(testFileAlreadyExists)
380
+
381
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
382
+ time.Sleep(500 * time.Millisecond)
383
+ cReceived := createReceived.value()
384
+ if cReceived != 1 {
385
+ t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 1)
386
+ }
387
+ mReceived := modifyReceived.value()
388
+ if mReceived != 1 {
389
+ t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1)
390
+ }
391
+ dReceived := deleteReceived.value()
392
+ if dReceived != 2 {
393
+ t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2)
394
+ }
395
+
396
+ // Try closing the fsnotify instance
397
+ t.Log("calling Close()")
398
+ watcher.Close()
399
+ t.Log("waiting for the event channel to become closed...")
400
+ select {
401
+ case <-done:
402
+ t.Log("event channel closed")
403
+ case <-time.After(2 * time.Second):
404
+ t.Fatal("event stream was not closed after 2 seconds")
405
+ }
406
+}
407
+
408
+func TestFsnotifyDeleteWatchedDir(t *testing.T) {
409
+ watcher := newWatcher(t)
410
+ defer watcher.Close()
411
+
412
+ // Create directory to watch
413
+ testDir := tempMkdir(t)
414
+ defer os.RemoveAll(testDir)
415
+
416
+ // Create a file before watching directory
417
+ testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
418
+ {
419
+ var f *os.File
420
+ f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
421
+ if err != nil {
422
+ t.Fatalf("creating test file failed: %s", err)
423
+ }
424
+ f.Sync()
425
+ f.Close()
426
+ }
427
+
428
+ addWatch(t, watcher, testDir)
429
+
430
+ // Add a watch for testFile
431
+ addWatch(t, watcher, testFileAlreadyExists)
432
+
433
+ // Receive errors on the error channel on a separate goroutine
434
+ go func() {
435
+ for err := range watcher.Errors {
436
+ t.Fatalf("error received: %s", err)
437
+ }
438
+ }()
439
+
440
+ // Receive events on the event channel on a separate goroutine
441
+ eventstream := watcher.Events
442
+ var deleteReceived counter
443
+ go func() {
444
+ for event := range eventstream {
445
+ // Only count relevant events
446
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFileAlreadyExists) {
447
+ t.Logf("event received: %s", event)
448
+ if event.Op&Remove == Remove {
449
+ deleteReceived.increment()
450
+ }
451
+ } else {
452
+ t.Logf("unexpected event received: %s", event)
453
+ }
454
+ }
455
+ }()
456
+
457
+ os.RemoveAll(testDir)
458
+
459
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
460
+ time.Sleep(500 * time.Millisecond)
461
+ dReceived := deleteReceived.value()
462
+ if dReceived < 2 {
463
+ t.Fatalf("did not receive at least %d delete events, received %d after 500 ms", 2, dReceived)
464
+ }
465
+}
466
+
467
+func TestFsnotifySubDir(t *testing.T) {
468
+ watcher := newWatcher(t)
469
+
470
+ // Create directory to watch
471
+ testDir := tempMkdir(t)
472
+ defer os.RemoveAll(testDir)
473
+
474
+ testFile1 := filepath.Join(testDir, "TestFsnotifyFile1.testfile")
475
+ testSubDir := filepath.Join(testDir, "sub")
476
+ testSubDirFile := filepath.Join(testDir, "sub/TestFsnotifyFile1.testfile")
477
+
478
+ // Receive errors on the error channel on a separate goroutine
479
+ go func() {
480
+ for err := range watcher.Errors {
481
+ t.Fatalf("error received: %s", err)
482
+ }
483
+ }()
484
+
485
+ // Receive events on the event channel on a separate goroutine
486
+ eventstream := watcher.Events
487
+ var createReceived, deleteReceived counter
488
+ done := make(chan bool)
489
+ go func() {
490
+ for event := range eventstream {
491
+ // Only count relevant events
492
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testSubDir) || event.Name == filepath.Clean(testFile1) {
493
+ t.Logf("event received: %s", event)
494
+ if event.Op&Create == Create {
495
+ createReceived.increment()
496
+ }
497
+ if event.Op&Remove == Remove {
498
+ deleteReceived.increment()
499
+ }
500
+ } else {
501
+ t.Logf("unexpected event received: %s", event)
502
+ }
503
+ }
504
+ done <- true
505
+ }()
506
+
507
+ addWatch(t, watcher, testDir)
508
+
509
+ // Create sub-directory
510
+ if err := os.Mkdir(testSubDir, 0777); err != nil {
511
+ t.Fatalf("failed to create test sub-directory: %s", err)
512
+ }
513
+
514
+ // Create a file
515
+ var f *os.File
516
+ f, err := os.OpenFile(testFile1, os.O_WRONLY|os.O_CREATE, 0666)
517
+ if err != nil {
518
+ t.Fatalf("creating test file failed: %s", err)
519
+ }
520
+ f.Sync()
521
+ f.Close()
522
+
523
+ // Create a file (Should not see this! we are not watching subdir)
524
+ var fs *os.File
525
+ fs, err = os.OpenFile(testSubDirFile, os.O_WRONLY|os.O_CREATE, 0666)
526
+ if err != nil {
527
+ t.Fatalf("creating test file failed: %s", err)
528
+ }
529
+ fs.Sync()
530
+ fs.Close()
531
+
532
+ time.Sleep(200 * time.Millisecond)
533
+
534
+ // Make sure receive deletes for both file and sub-directory
535
+ os.RemoveAll(testSubDir)
536
+ os.Remove(testFile1)
537
+
538
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
539
+ time.Sleep(500 * time.Millisecond)
540
+ cReceived := createReceived.value()
541
+ if cReceived != 2 {
542
+ t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
543
+ }
544
+ dReceived := deleteReceived.value()
545
+ if dReceived != 2 {
546
+ t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2)
547
+ }
548
+
549
+ // Try closing the fsnotify instance
550
+ t.Log("calling Close()")
551
+ watcher.Close()
552
+ t.Log("waiting for the event channel to become closed...")
553
+ select {
554
+ case <-done:
555
+ t.Log("event channel closed")
556
+ case <-time.After(2 * time.Second):
557
+ t.Fatal("event stream was not closed after 2 seconds")
558
+ }
559
+}
560
+
561
+func TestFsnotifyRename(t *testing.T) {
562
+ watcher := newWatcher(t)
563
+
564
+ // Create directory to watch
565
+ testDir := tempMkdir(t)
566
+ defer os.RemoveAll(testDir)
567
+
568
+ addWatch(t, watcher, testDir)
569
+
570
+ // Receive errors on the error channel on a separate goroutine
571
+ go func() {
572
+ for err := range watcher.Errors {
573
+ t.Fatalf("error received: %s", err)
574
+ }
575
+ }()
576
+
577
+ testFile := filepath.Join(testDir, "TestFsnotifyEvents.testfile")
578
+ testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
579
+
580
+ // Receive events on the event channel on a separate goroutine
581
+ eventstream := watcher.Events
582
+ var renameReceived counter
583
+ done := make(chan bool)
584
+ go func() {
585
+ for event := range eventstream {
586
+ // Only count relevant events
587
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) {
588
+ if event.Op&Rename == Rename {
589
+ renameReceived.increment()
590
+ }
591
+ t.Logf("event received: %s", event)
592
+ } else {
593
+ t.Logf("unexpected event received: %s", event)
594
+ }
595
+ }
596
+ done <- true
597
+ }()
598
+
599
+ // Create a file
600
+ // This should add at least one event to the fsnotify event queue
601
+ var f *os.File
602
+ f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
603
+ if err != nil {
604
+ t.Fatalf("creating test file failed: %s", err)
605
+ }
606
+ f.Sync()
607
+
608
+ f.WriteString("data")
609
+ f.Sync()
610
+ f.Close()
611
+
612
+ // Add a watch for testFile
613
+ addWatch(t, watcher, testFile)
614
+
615
+ if err := testRename(testFile, testFileRenamed); err != nil {
616
+ t.Fatalf("rename failed: %s", err)
617
+ }
618
+
619
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
620
+ time.Sleep(500 * time.Millisecond)
621
+ if renameReceived.value() == 0 {
622
+ t.Fatal("fsnotify rename events have not been received after 500 ms")
623
+ }
624
+
625
+ // Try closing the fsnotify instance
626
+ t.Log("calling Close()")
627
+ watcher.Close()
628
+ t.Log("waiting for the event channel to become closed...")
629
+ select {
630
+ case <-done:
631
+ t.Log("event channel closed")
632
+ case <-time.After(2 * time.Second):
633
+ t.Fatal("event stream was not closed after 2 seconds")
634
+ }
635
+
636
+ os.Remove(testFileRenamed)
637
+}
638
+
639
+func TestFsnotifyRenameToCreate(t *testing.T) {
640
+ watcher := newWatcher(t)
641
+
642
+ // Create directory to watch
643
+ testDir := tempMkdir(t)
644
+ defer os.RemoveAll(testDir)
645
+
646
+ // Create directory to get file
647
+ testDirFrom := tempMkdir(t)
648
+ defer os.RemoveAll(testDirFrom)
649
+
650
+ addWatch(t, watcher, testDir)
651
+
652
+ // Receive errors on the error channel on a separate goroutine
653
+ go func() {
654
+ for err := range watcher.Errors {
655
+ t.Fatalf("error received: %s", err)
656
+ }
657
+ }()
658
+
659
+ testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile")
660
+ testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
661
+
662
+ // Receive events on the event channel on a separate goroutine
663
+ eventstream := watcher.Events
664
+ var createReceived counter
665
+ done := make(chan bool)
666
+ go func() {
667
+ for event := range eventstream {
668
+ // Only count relevant events
669
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) {
670
+ if event.Op&Create == Create {
671
+ createReceived.increment()
672
+ }
673
+ t.Logf("event received: %s", event)
674
+ } else {
675
+ t.Logf("unexpected event received: %s", event)
676
+ }
677
+ }
678
+ done <- true
679
+ }()
680
+
681
+ // Create a file
682
+ // This should add at least one event to the fsnotify event queue
683
+ var f *os.File
684
+ f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
685
+ if err != nil {
686
+ t.Fatalf("creating test file failed: %s", err)
687
+ }
688
+ f.Sync()
689
+ f.Close()
690
+
691
+ if err := testRename(testFile, testFileRenamed); err != nil {
692
+ t.Fatalf("rename failed: %s", err)
693
+ }
694
+
695
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
696
+ time.Sleep(500 * time.Millisecond)
697
+ if createReceived.value() == 0 {
698
+ t.Fatal("fsnotify create events have not been received after 500 ms")
699
+ }
700
+
701
+ // Try closing the fsnotify instance
702
+ t.Log("calling Close()")
703
+ watcher.Close()
704
+ t.Log("waiting for the event channel to become closed...")
705
+ select {
706
+ case <-done:
707
+ t.Log("event channel closed")
708
+ case <-time.After(2 * time.Second):
709
+ t.Fatal("event stream was not closed after 2 seconds")
710
+ }
711
+
712
+ os.Remove(testFileRenamed)
713
+}
714
+
715
+func TestFsnotifyRenameToOverwrite(t *testing.T) {
716
+ switch runtime.GOOS {
717
+ case "plan9", "windows":
718
+ t.Skipf("skipping test on %q (os.Rename over existing file does not create event).", runtime.GOOS)
719
+ }
720
+
721
+ watcher := newWatcher(t)
722
+
723
+ // Create directory to watch
724
+ testDir := tempMkdir(t)
725
+ defer os.RemoveAll(testDir)
726
+
727
+ // Create directory to get file
728
+ testDirFrom := tempMkdir(t)
729
+ defer os.RemoveAll(testDirFrom)
730
+
731
+ testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile")
732
+ testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
733
+
734
+ // Create a file
735
+ var fr *os.File
736
+ fr, err := os.OpenFile(testFileRenamed, os.O_WRONLY|os.O_CREATE, 0666)
737
+ if err != nil {
738
+ t.Fatalf("creating test file failed: %s", err)
739
+ }
740
+ fr.Sync()
741
+ fr.Close()
742
+
743
+ addWatch(t, watcher, testDir)
744
+
745
+ // Receive errors on the error channel on a separate goroutine
746
+ go func() {
747
+ for err := range watcher.Errors {
748
+ t.Fatalf("error received: %s", err)
749
+ }
750
+ }()
751
+
752
+ // Receive events on the event channel on a separate goroutine
753
+ eventstream := watcher.Events
754
+ var eventReceived counter
755
+ done := make(chan bool)
756
+ go func() {
757
+ for event := range eventstream {
758
+ // Only count relevant events
759
+ if event.Name == filepath.Clean(testFileRenamed) {
760
+ eventReceived.increment()
761
+ t.Logf("event received: %s", event)
762
+ } else {
763
+ t.Logf("unexpected event received: %s", event)
764
+ }
765
+ }
766
+ done <- true
767
+ }()
768
+
769
+ // Create a file
770
+ // This should add at least one event to the fsnotify event queue
771
+ var f *os.File
772
+ f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
773
+ if err != nil {
774
+ t.Fatalf("creating test file failed: %s", err)
775
+ }
776
+ f.Sync()
777
+ f.Close()
778
+
779
+ if err := testRename(testFile, testFileRenamed); err != nil {
780
+ t.Fatalf("rename failed: %s", err)
781
+ }
782
+
783
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
784
+ time.Sleep(500 * time.Millisecond)
785
+ if eventReceived.value() == 0 {
786
+ t.Fatal("fsnotify events have not been received after 500 ms")
787
+ }
788
+
789
+ // Try closing the fsnotify instance
790
+ t.Log("calling Close()")
791
+ watcher.Close()
792
+ t.Log("waiting for the event channel to become closed...")
793
+ select {
794
+ case <-done:
795
+ t.Log("event channel closed")
796
+ case <-time.After(2 * time.Second):
797
+ t.Fatal("event stream was not closed after 2 seconds")
798
+ }
799
+
800
+ os.Remove(testFileRenamed)
801
+}
802
+
803
+func TestRemovalOfWatch(t *testing.T) {
804
+ // Create directory to watch
805
+ testDir := tempMkdir(t)
806
+ defer os.RemoveAll(testDir)
807
+
808
+ // Create a file before watching directory
809
+ testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
810
+ {
811
+ var f *os.File
812
+ f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
813
+ if err != nil {
814
+ t.Fatalf("creating test file failed: %s", err)
815
+ }
816
+ f.Sync()
817
+ f.Close()
818
+ }
819
+
820
+ watcher := newWatcher(t)
821
+ defer watcher.Close()
822
+
823
+ addWatch(t, watcher, testDir)
824
+ if err := watcher.Remove(testDir); err != nil {
825
+ t.Fatalf("Could not remove the watch: %v\n", err)
826
+ }
827
+
828
+ go func() {
829
+ select {
830
+ case ev := <-watcher.Events:
831
+ t.Fatalf("We received event: %v\n", ev)
832
+ case <-time.After(500 * time.Millisecond):
833
+ t.Log("No event received, as expected.")
834
+ }
835
+ }()
836
+
837
+ time.Sleep(200 * time.Millisecond)
838
+ // Modify the file outside of the watched dir
839
+ f, err := os.Open(testFileAlreadyExists)
840
+ if err != nil {
841
+ t.Fatalf("Open test file failed: %s", err)
842
+ }
843
+ f.WriteString("data")
844
+ f.Sync()
845
+ f.Close()
846
+ if err := os.Chmod(testFileAlreadyExists, 0700); err != nil {
847
+ t.Fatalf("chmod failed: %s", err)
848
+ }
849
+ time.Sleep(400 * time.Millisecond)
850
+}
851
+
852
+func TestFsnotifyAttrib(t *testing.T) {
853
+ if runtime.GOOS == "windows" {
854
+ t.Skip("attributes don't work on Windows.")
855
+ }
856
+
857
+ watcher := newWatcher(t)
858
+
859
+ // Create directory to watch
860
+ testDir := tempMkdir(t)
861
+ defer os.RemoveAll(testDir)
862
+
863
+ // Receive errors on the error channel on a separate goroutine
864
+ go func() {
865
+ for err := range watcher.Errors {
866
+ t.Fatalf("error received: %s", err)
867
+ }
868
+ }()
869
+
870
+ testFile := filepath.Join(testDir, "TestFsnotifyAttrib.testfile")
871
+
872
+ // Receive events on the event channel on a separate goroutine
873
+ eventstream := watcher.Events
874
+ // The modifyReceived counter counts IsModify events that are not IsAttrib,
875
+ // and the attribReceived counts IsAttrib events (which are also IsModify as
876
+ // a consequence).
877
+ var modifyReceived counter
878
+ var attribReceived counter
879
+ done := make(chan bool)
880
+ go func() {
881
+ for event := range eventstream {
882
+ // Only count relevant events
883
+ if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
884
+ if event.Op&Write == Write {
885
+ modifyReceived.increment()
886
+ }
887
+ if event.Op&Chmod == Chmod {
888
+ attribReceived.increment()
889
+ }
890
+ t.Logf("event received: %s", event)
891
+ } else {
892
+ t.Logf("unexpected event received: %s", event)
893
+ }
894
+ }
895
+ done <- true
896
+ }()
897
+
898
+ // Create a file
899
+ // This should add at least one event to the fsnotify event queue
900
+ var f *os.File
901
+ f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
902
+ if err != nil {
903
+ t.Fatalf("creating test file failed: %s", err)
904
+ }
905
+ f.Sync()
906
+
907
+ f.WriteString("data")
908
+ f.Sync()
909
+ f.Close()
910
+
911
+ // Add a watch for testFile
912
+ addWatch(t, watcher, testFile)
913
+
914
+ if err := os.Chmod(testFile, 0700); err != nil {
915
+ t.Fatalf("chmod failed: %s", err)
916
+ }
917
+
918
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
919
+ // Creating/writing a file changes also the mtime, so IsAttrib should be set to true here
920
+ time.Sleep(500 * time.Millisecond)
921
+ if modifyReceived.value() != 0 {
922
+ t.Fatal("received an unexpected modify event when creating a test file")
923
+ }
924
+ if attribReceived.value() == 0 {
925
+ t.Fatal("fsnotify attribute events have not received after 500 ms")
926
+ }
927
+
928
+ // Modifying the contents of the file does not set the attrib flag (although eg. the mtime
929
+ // might have been modified).
930
+ modifyReceived.reset()
931
+ attribReceived.reset()
932
+
933
+ f, err = os.OpenFile(testFile, os.O_WRONLY, 0)
934
+ if err != nil {
935
+ t.Fatalf("reopening test file failed: %s", err)
936
+ }
937
+
938
+ f.WriteString("more data")
939
+ f.Sync()
940
+ f.Close()
941
+
942
+ time.Sleep(500 * time.Millisecond)
943
+
944
+ if modifyReceived.value() != 1 {
945
+ t.Fatal("didn't receive a modify event after changing test file contents")
946
+ }
947
+
948
+ if attribReceived.value() != 0 {
949
+ t.Fatal("did receive an unexpected attrib event after changing test file contents")
950
+ }
951
+
952
+ modifyReceived.reset()
953
+ attribReceived.reset()
954
+
955
+ // Doing a chmod on the file should trigger an event with the "attrib" flag set (the contents
956
+ // of the file are not changed though)
957
+ if err := os.Chmod(testFile, 0600); err != nil {
958
+ t.Fatalf("chmod failed: %s", err)
959
+ }
960
+
961
+ time.Sleep(500 * time.Millisecond)
962
+
963
+ if attribReceived.value() != 1 {
964
+ t.Fatal("didn't receive an attribute change after 500ms")
965
+ }
966
+
967
+ // Try closing the fsnotify instance
968
+ t.Log("calling Close()")
969
+ watcher.Close()
970
+ t.Log("waiting for the event channel to become closed...")
971
+ select {
972
+ case <-done:
973
+ t.Log("event channel closed")
974
+ case <-time.After(1e9):
975
+ t.Fatal("event stream was not closed after 1 second")
976
+ }
977
+
978
+ os.Remove(testFile)
979
+}
980
+
981
+func TestFsnotifyClose(t *testing.T) {
982
+ watcher := newWatcher(t)
983
+ watcher.Close()
984
+
985
+ var done int32
986
+ go func() {
987
+ watcher.Close()
988
+ atomic.StoreInt32(&done, 1)
989
+ }()
990
+
991
+ time.Sleep(50e6) // 50 ms
992
+ if atomic.LoadInt32(&done) == 0 {
993
+ t.Fatal("double Close() test failed: second Close() call didn't return")
994
+ }
995
+
996
+ testDir := tempMkdir(t)
997
+ defer os.RemoveAll(testDir)
998
+
999
+ if err := watcher.Add(testDir); err == nil {
1000
+ t.Fatal("expected error on Watch() after Close(), got nil")
1001
+ }
1002
+}
1003
+
1004
+func TestFsnotifyFakeSymlink(t *testing.T) {
1005
+ if runtime.GOOS == "windows" {
1006
+ t.Skip("symlinks don't work on Windows.")
1007
+ }
1008
+
1009
+ watcher := newWatcher(t)
1010
+
1011
+ // Create directory to watch
1012
+ testDir := tempMkdir(t)
1013
+ defer os.RemoveAll(testDir)
1014
+
1015
+ var errorsReceived counter
1016
+ // Receive errors on the error channel on a separate goroutine
1017
+ go func() {
1018
+ for errors := range watcher.Errors {
1019
+ t.Logf("Received error: %s", errors)
1020
+ errorsReceived.increment()
1021
+ }
1022
+ }()
1023
+
1024
+ // Count the CREATE events received
1025
+ var createEventsReceived, otherEventsReceived counter
1026
+ go func() {
1027
+ for ev := range watcher.Events {
1028
+ t.Logf("event received: %s", ev)
1029
+ if ev.Op&Create == Create {
1030
+ createEventsReceived.increment()
1031
+ } else {
1032
+ otherEventsReceived.increment()
1033
+ }
1034
+ }
1035
+ }()
1036
+
1037
+ addWatch(t, watcher, testDir)
1038
+
1039
+ if err := os.Symlink(filepath.Join(testDir, "zzz"), filepath.Join(testDir, "zzznew")); err != nil {
1040
+ t.Fatalf("Failed to create bogus symlink: %s", err)
1041
+ }
1042
+ t.Logf("Created bogus symlink")
1043
+
1044
+ // We expect this event to be received almost immediately, but let's wait 500 ms to be sure
1045
+ time.Sleep(500 * time.Millisecond)
1046
+
1047
+ // Should not be error, just no events for broken links (watching nothing)
1048
+ if errorsReceived.value() > 0 {
1049
+ t.Fatal("fsnotify errors have been received.")
1050
+ }
1051
+ if otherEventsReceived.value() > 0 {
1052
+ t.Fatal("fsnotify other events received on the broken link")
1053
+ }
1054
+
1055
+ // Except for 1 create event (for the link itself)
1056
+ if createEventsReceived.value() == 0 {
1057
+ t.Fatal("fsnotify create events were not received after 500 ms")
1058
+ }
1059
+ if createEventsReceived.value() > 1 {
1060
+ t.Fatal("fsnotify more create events received than expected")
1061
+ }
1062
+
1063
+ // Try closing the fsnotify instance
1064
+ t.Log("calling Close()")
1065
+ watcher.Close()
1066
+}
1067
+
1068
+// TestConcurrentRemovalOfWatch tests that concurrent calls to RemoveWatch do not race.
1069
+// See https://codereview.appspot.com/103300045/
1070
+// go test -test.run=TestConcurrentRemovalOfWatch -test.cpu=1,1,1,1,1 -race
1071
+func TestConcurrentRemovalOfWatch(t *testing.T) {
1072
+ if runtime.GOOS != "darwin" {
1073
+ t.Skip("regression test for race only present on darwin")
1074
+ }
1075
+
1076
+ // Create directory to watch
1077
+ testDir := tempMkdir(t)
1078
+ defer os.RemoveAll(testDir)
1079
+
1080
+ // Create a file before watching directory
1081
+ testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
1082
+ {
1083
+ var f *os.File
1084
+ f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
1085
+ if err != nil {
1086
+ t.Fatalf("creating test file failed: %s", err)
1087
+ }
1088
+ f.Sync()
1089
+ f.Close()
1090
+ }
1091
+
1092
+ watcher := newWatcher(t)
1093
+ defer watcher.Close()
1094
+
1095
+ addWatch(t, watcher, testDir)
1096
+
1097
+ // Test that RemoveWatch can be invoked concurrently, with no data races.
1098
+ removed1 := make(chan struct{})
1099
+ go func() {
1100
+ defer close(removed1)
1101
+ watcher.Remove(testDir)
1102
+ }()
1103
+ removed2 := make(chan struct{})
1104
+ go func() {
1105
+ close(removed2)
1106
+ watcher.Remove(testDir)
1107
+ }()
1108
+ <-removed1
1109
+ <-removed2
1110
+}
1111
+
1112
+func testRename(file1, file2 string) error {
1113
+ switch runtime.GOOS {
1114
+ case "windows", "plan9":
1115
+ return os.Rename(file1, file2)
1116
+ default:
1117
+ cmd := exec.Command("mv", file1, file2)
1118
+ return cmd.Run()
1119
+ }
1120
+}
Godeps/_workspace/src/gopkg.in/fsnotify.v1/kqueue.go
new
+459
@@ -0,0 +1,459 @@
1
+// Copyright 2010 The Go Authors. All rights reserved.
2
+// Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+// +build freebsd openbsd netbsd dragonfly darwin
6
+
7
+package fsnotify
8
+
9
+import (
10
+ "errors"
11
+ "fmt"
12
+ "io/ioutil"
13
+ "os"
14
+ "path/filepath"
15
+ "sync"
16
+ "syscall"
17
+ "time"
18
+)
19
+
20
+// Watcher watches a set of files, delivering events to a channel.
21
+type Watcher struct {
22
+ Events chan Event
23
+ Errors chan error
24
+ done chan bool // Channel for sending a "quit message" to the reader goroutine
25
+
26
+ kq int // File descriptor (as returned by the kqueue() syscall).
27
+
28
+ mu sync.Mutex // Protects access to watcher data
29
+ watches map[string]int // Map of watched file descriptors (key: path).
30
+ externalWatches map[string]bool // Map of watches added by user of the library.
31
+ dirFlags map[string]uint32 // Map of watched directories to fflags used in kqueue.
32
+ paths map[int]pathInfo // Map file descriptors to path names for processing kqueue events.
33
+ fileExists map[string]bool // Keep track of if we know this file exists (to stop duplicate create events).
34
+ isClosed bool // Set to true when Close() is first called
35
+}
36
+
37
+type pathInfo struct {
38
+ name string
39
+ isDir bool
40
+}
41
+
42
+// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
43
+func NewWatcher() (*Watcher, error) {
44
+ kq, err := kqueue()
45
+ if err != nil {
46
+ return nil, err
47
+ }
48
+
49
+ w := &Watcher{
50
+ kq: kq,
51
+ watches: make(map[string]int),
52
+ dirFlags: make(map[string]uint32),
53
+ paths: make(map[int]pathInfo),
54
+ fileExists: make(map[string]bool),
55
+ externalWatches: make(map[string]bool),
56
+ Events: make(chan Event),
57
+ Errors: make(chan error),
58
+ done: make(chan bool),
59
+ }
60
+
61
+ go w.readEvents()
62
+ return w, nil
63
+}
64
+
65
+// Close removes all watches and closes the events channel.
66
+func (w *Watcher) Close() error {
67
+ w.mu.Lock()
68
+ if w.isClosed {
69
+ w.mu.Unlock()
70
+ return nil
71
+ }
72
+ w.isClosed = true
73
+ w.mu.Unlock()
74
+
75
+ // Send "quit" message to the reader goroutine:
76
+ w.done <- true
77
+
78
+ w.mu.Lock()
79
+ ws := w.watches
80
+ w.mu.Unlock()
81
+ for name := range ws {
82
+ w.Remove(name)
83
+ }
84
+
85
+ return nil
86
+}
87
+
88
+// Add starts watching the named file or directory (non-recursively).
89
+func (w *Watcher) Add(name string) error {
90
+ w.mu.Lock()
91
+ w.externalWatches[name] = true
92
+ w.mu.Unlock()
93
+ return w.addWatch(name, noteAllEvents)
94
+}
95
+
96
+// Remove stops watching the the named file or directory (non-recursively).
97
+func (w *Watcher) Remove(name string) error {
98
+ name = filepath.Clean(name)
99
+ w.mu.Lock()
100
+ watchfd, ok := w.watches[name]
101
+ w.mu.Unlock()
102
+ if !ok {
103
+ return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
104
+ }
105
+
106
+ const registerRemove = syscall.EV_DELETE
107
+ if err := register(w.kq, []int{watchfd}, registerRemove, 0); err != nil {
108
+ return err
109
+ }
110
+
111
+ syscall.Close(watchfd)
112
+
113
+ w.mu.Lock()
114
+ isDir := w.paths[watchfd].isDir
115
+ delete(w.watches, name)
116
+ delete(w.paths, watchfd)
117
+ delete(w.dirFlags, name)
118
+ w.mu.Unlock()
119
+
120
+ // Find all watched paths that are in this directory that are not external.
121
+ if isDir {
122
+ var pathsToRemove []string
123
+ w.mu.Lock()
124
+ for _, path := range w.paths {
125
+ wdir, _ := filepath.Split(path.name)
126
+ if filepath.Clean(wdir) == name {
127
+ if !w.externalWatches[path.name] {
128
+ pathsToRemove = append(pathsToRemove, path.name)
129
+ }
130
+ }
131
+ }
132
+ w.mu.Unlock()
133
+ for _, name := range pathsToRemove {
134
+ // Since these are internal, not much sense in propagating error
135
+ // to the user, as that will just confuse them with an error about
136
+ // a path they did not explicitly watch themselves.
137
+ w.Remove(name)
138
+ }
139
+ }
140
+
141
+ return nil
142
+}
143
+
144
+// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
145
+const noteAllEvents = syscall.NOTE_DELETE | syscall.NOTE_WRITE | syscall.NOTE_ATTRIB | syscall.NOTE_RENAME
146
+
147
+// keventWaitTime to block on each read from kevent
148
+var keventWaitTime = durationToTimespec(100 * time.Millisecond)
149
+
150
+// addWatch adds name to the watched file set.
151
+// The flags are interpreted as described in kevent(2).
152
+func (w *Watcher) addWatch(name string, flags uint32) error {
153
+ var isDir bool
154
+ // Make ./name and name equivalent
155
+ name = filepath.Clean(name)
156
+
157
+ w.mu.Lock()
158
+ if w.isClosed {
159
+ w.mu.Unlock()
160
+ return errors.New("kevent instance already closed")
161
+ }
162
+ watchfd, alreadyWatching := w.watches[name]
163
+ // We already have a watch, but we can still override flags.
164
+ if alreadyWatching {
165
+ isDir = w.paths[watchfd].isDir
166
+ }
167
+ w.mu.Unlock()
168
+
169
+ if !alreadyWatching {
170
+ fi, err := os.Lstat(name)
171
+ if err != nil {
172
+ return err
173
+ }
174
+
175
+ // Don't watch sockets.
176
+ if fi.Mode()&os.ModeSocket == os.ModeSocket {
177
+ return nil
178
+ }
179
+
180
+ // Follow Symlinks
181
+ // Unfortunately, Linux can add bogus symlinks to watch list without
182
+ // issue, and Windows can't do symlinks period (AFAIK). To maintain
183
+ // consistency, we will act like everything is fine. There will simply
184
+ // be no file events for broken symlinks.
185
+ // Hence the returns of nil on errors.
186
+ if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
187
+ name, err = filepath.EvalSymlinks(name)
188
+ if err != nil {
189
+ return nil
190
+ }
191
+
192
+ fi, err = os.Lstat(name)
193
+ if err != nil {
194
+ return nil
195
+ }
196
+ }
197
+
198
+ watchfd, err = syscall.Open(name, openMode, 0700)
199
+ if watchfd == -1 {
200
+ return err
201
+ }
202
+
203
+ isDir = fi.IsDir()
204
+ }
205
+
206
+ const registerAdd = syscall.EV_ADD | syscall.EV_CLEAR | syscall.EV_ENABLE
207
+ if err := register(w.kq, []int{watchfd}, registerAdd, flags); err != nil {
208
+ syscall.Close(watchfd)
209
+ return err
210
+ }
211
+
212
+ if !alreadyWatching {
213
+ w.mu.Lock()
214
+ w.watches[name] = watchfd
215
+ w.paths[watchfd] = pathInfo{name: name, isDir: isDir}
216
+ w.mu.Unlock()
217
+ }
218
+
219
+ if isDir {
220
+ // Watch the directory if it has not been watched before,
221
+ // or if it was watched before, but perhaps only a NOTE_DELETE (watchDirectoryFiles)
222
+ w.mu.Lock()
223
+ watchDir := (flags&syscall.NOTE_WRITE) == syscall.NOTE_WRITE &&
224
+ (!alreadyWatching || (w.dirFlags[name]&syscall.NOTE_WRITE) != syscall.NOTE_WRITE)
225
+ // Store flags so this watch can be updated later
226
+ w.dirFlags[name] = flags
227
+ w.mu.Unlock()
228
+
229
+ if watchDir {
230
+ if err := w.watchDirectoryFiles(name); err != nil {
231
+ return err
232
+ }
233
+ }
234
+ }
235
+ return nil
236
+}
237
+
238
+// readEvents reads from kqueue and converts the received kevents into
239
+// Event values that it sends down the Events channel.
240
+func (w *Watcher) readEvents() {
241
+ eventBuffer := make([]syscall.Kevent_t, 10)
242
+
243
+ for {
244
+ // See if there is a message on the "done" channel
245
+ select {
246
+ case <-w.done:
247
+ err := syscall.Close(w.kq)
248
+ if err != nil {
249
+ w.Errors <- err
250
+ }
251
+ close(w.Events)
252
+ close(w.Errors)
253
+ return
254
+ default:
255
+ }
256
+
257
+ // Get new events
258
+ kevents, err := read(w.kq, eventBuffer, &keventWaitTime)
259
+ // EINTR is okay, the syscall was interrupted before timeout expired.
260
+ if err != nil && err != syscall.EINTR {
261
+ w.Errors <- err
262
+ continue
263
+ }
264
+
265
+ // Flush the events we received to the Events channel
266
+ for len(kevents) > 0 {
267
+ kevent := &kevents[0]
268
+ watchfd := int(kevent.Ident)
269
+ mask := uint32(kevent.Fflags)
270
+ w.mu.Lock()
271
+ path := w.paths[watchfd]
272
+ w.mu.Unlock()
273
+ event := newEvent(path.name, mask)
274
+
275
+ if path.isDir && !(event.Op&Remove == Remove) {
276
+ // Double check to make sure the directory exists. This can happen when
277
+ // we do a rm -fr on a recursively watched folders and we receive a
278
+ // modification event first but the folder has been deleted and later
279
+ // receive the delete event
280
+ if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
281
+ // mark is as delete event
282
+ event.Op |= Remove
283
+ }
284
+ }
285
+
286
+ if event.Op&Rename == Rename || event.Op&Remove == Remove {
287
+ w.Remove(event.Name)
288
+ w.mu.Lock()
289
+ delete(w.fileExists, event.Name)
290
+ w.mu.Unlock()
291
+ }
292
+
293
+ if path.isDir && event.Op&Write == Write && !(event.Op&Remove == Remove) {
294
+ w.sendDirectoryChangeEvents(event.Name)
295
+ } else {
296
+ // Send the event on the Events channel
297
+ w.Events <- event
298
+ }
299
+
300
+ if event.Op&Remove == Remove {
301
+ // Look for a file that may have overwritten this.
302
+ // For example, mv f1 f2 will delete f2, then create f2.
303
+ fileDir, _ := filepath.Split(event.Name)
304
+ fileDir = filepath.Clean(fileDir)
305
+ w.mu.Lock()
306
+ _, found := w.watches[fileDir]
307
+ w.mu.Unlock()
308
+ if found {
309
+ // make sure the directory exists before we watch for changes. When we
310
+ // do a recursive watch and perform rm -fr, the parent directory might
311
+ // have gone missing, ignore the missing directory and let the
312
+ // upcoming delete event remove the watch from the parent directory.
313
+ if _, err := os.Lstat(fileDir); os.IsExist(err) {
314
+ w.sendDirectoryChangeEvents(fileDir)
315
+ // FIXME: should this be for events on files or just isDir?
316
+ }
317
+ }
318
+ }
319
+
320
+ // Move to next event
321
+ kevents = kevents[1:]
322
+ }
323
+ }
324
+}
325
+
326
+// newEvent returns an platform-independent Event based on kqueue Fflags.
327
+func newEvent(name string, mask uint32) Event {
328
+ e := Event{Name: name}
329
+ if mask&syscall.NOTE_DELETE == syscall.NOTE_DELETE {
330
+ e.Op |= Remove
331
+ }
332
+ if mask&syscall.NOTE_WRITE == syscall.NOTE_WRITE {
333
+ e.Op |= Write
334
+ }
335
+ if mask&syscall.NOTE_RENAME == syscall.NOTE_RENAME {
336
+ e.Op |= Rename
337
+ }
338
+ if mask&syscall.NOTE_ATTRIB == syscall.NOTE_ATTRIB {
339
+ e.Op |= Chmod
340
+ }
341
+ return e
342
+}
343
+
344
+func newCreateEvent(name string) Event {
345
+ return Event{Name: name, Op: Create}
346
+}
347
+
348
+// watchDirectoryFiles to mimic inotify when adding a watch on a directory
349
+func (w *Watcher) watchDirectoryFiles(dirPath string) error {
350
+ // Get all files
351
+ files, err := ioutil.ReadDir(dirPath)
352
+ if err != nil {
353
+ return err
354
+ }
355
+
356
+ for _, fileInfo := range files {
357
+ filePath := filepath.Join(dirPath, fileInfo.Name())
358
+ if err := w.internalWatch(filePath, fileInfo); err != nil {
359
+ return err
360
+ }
361
+
362
+ w.mu.Lock()
363
+ w.fileExists[filePath] = true
364
+ w.mu.Unlock()
365
+ }
366
+
367
+ return nil
368
+}
369
+
370
+// sendDirectoryEvents searches the directory for newly created files
371
+// and sends them over the event channel. This functionality is to have
372
+// the BSD version of fsnotify match Linux inotify which provides a
373
+// create event for files created in a watched directory.
374
+func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
375
+ // Get all files
376
+ files, err := ioutil.ReadDir(dirPath)
377
+ if err != nil {
378
+ w.Errors <- err
379
+ }
380
+
381
+ // Search for new files
382
+ for _, fileInfo := range files {
383
+ filePath := filepath.Join(dirPath, fileInfo.Name())
384
+ w.mu.Lock()
385
+ _, doesExist := w.fileExists[filePath]
386
+ w.mu.Unlock()
387
+ if !doesExist {
388
+ // Send create event
389
+ w.Events <- newCreateEvent(filePath)
390
+ }
391
+
392
+ // like watchDirectoryFiles (but without doing another ReadDir)
393
+ if err := w.internalWatch(filePath, fileInfo); err != nil {
394
+ return
395
+ }
396
+
397
+ w.mu.Lock()
398
+ w.fileExists[filePath] = true
399
+ w.mu.Unlock()
400
+ }
401
+}
402
+
403
+func (w *Watcher) internalWatch(name string, fileInfo os.FileInfo) error {
404
+ if fileInfo.IsDir() {
405
+ // mimic Linux providing delete events for subdirectories
406
+ // but preserve the flags used if currently watching subdirectory
407
+ w.mu.Lock()
408
+ flags := w.dirFlags[name]
409
+ w.mu.Unlock()
410
+
411
+ flags |= syscall.NOTE_DELETE
412
+ return w.addWatch(name, flags)
413
+ }
414
+
415
+ // watch file to mimic Linux inotify
416
+ return w.addWatch(name, noteAllEvents)
417
+}
418
+
419
+// kqueue creates a new kernel event queue and returns a descriptor.
420
+func kqueue() (kq int, err error) {
421
+ kq, err = syscall.Kqueue()
422
+ if kq == -1 {
423
+ return kq, err
424
+ }
425
+ return kq, nil
426
+}
427
+
428
+// register events with the queue
429
+func register(kq int, fds []int, flags int, fflags uint32) error {
430
+ changes := make([]syscall.Kevent_t, len(fds))
431
+
432
+ for i, fd := range fds {
433
+ // SetKevent converts int to the platform-specific types:
434
+ syscall.SetKevent(&changes[i], fd, syscall.EVFILT_VNODE, flags)
435
+ changes[i].Fflags = fflags
436
+ }
437
+
438
+ // register the events
439
+ success, err := syscall.Kevent(kq, changes, nil, nil)
440
+ if success == -1 {
441
+ return err
442
+ }
443
+ return nil
444
+}
445
+
446
+// read retrieves pending events, or waits until an event occurs.
447
+// A timeout of nil blocks indefinitely, while 0 polls the queue.
448
+func read(kq int, events []syscall.Kevent_t, timeout *syscall.Timespec) ([]syscall.Kevent_t, error) {
449
+ n, err := syscall.Kevent(kq, nil, events, timeout)
450
+ if err != nil {
451
+ return nil, err
452
+ }
453
+ return events[0:n], nil
454
+}
455
+
456
+// durationToTimespec prepares a timeout value
457
+func durationToTimespec(d time.Duration) syscall.Timespec {
458
+ return syscall.NsecToTimespec(d.Nanoseconds())
459
+}
Godeps/_workspace/src/gopkg.in/fsnotify.v1/open_mode_bsd.go
new
+11
@@ -0,0 +1,11 @@
1
+// Copyright 2013 The Go Authors. All rights reserved.
2
+// Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+// +build freebsd openbsd netbsd dragonfly
6
+
7
+package fsnotify
8
+
9
+import "syscall"
10
+
11
+const openMode = syscall.O_NONBLOCK | syscall.O_RDONLY
Godeps/_workspace/src/gopkg.in/fsnotify.v1/open_mode_darwin.go
new
+12
@@ -0,0 +1,12 @@
1
+// Copyright 2013 The Go Authors. All rights reserved.
2
+// Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+// +build darwin
6
+
7
+package fsnotify
8
+
9
+import "syscall"
10
+
11
+// note: this constant is not defined on BSD
12
+const openMode = syscall.O_EVTONLY
Godeps/_workspace/src/gopkg.in/fsnotify.v1/windows.go
new
+561
@@ -0,0 +1,561 @@
1
+// Copyright 2011 The Go Authors. All rights reserved.
2
+// Use of this source code is governed by a BSD-style
3
+// license that can be found in the LICENSE file.
4
+
5
+// +build windows
6
+
7
+package fsnotify
8
+
9
+import (
10
+ "errors"
11
+ "fmt"
12
+ "os"
13
+ "path/filepath"
14
+ "runtime"
15
+ "sync"
16
+ "syscall"
17
+ "unsafe"
18
+)
19
+
20
+// Watcher watches a set of files, delivering events to a channel.
21
+type Watcher struct {
22
+ Events chan Event
23
+ Errors chan error
24
+ isClosed bool // Set to true when Close() is first called
25
+ mu sync.Mutex // Map access
26
+ port syscall.Handle // Handle to completion port
27
+ watches watchMap // Map of watches (key: i-number)
28
+ input chan *input // Inputs to the reader are sent on this channel
29
+ quit chan chan<- error
30
+}
31
+
32
+// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
33
+func NewWatcher() (*Watcher, error) {
34
+ port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
35
+ if e != nil {
36
+ return nil, os.NewSyscallError("CreateIoCompletionPort", e)
37
+ }
38
+ w := &Watcher{
39
+ port: port,
40
+ watches: make(watchMap),
41
+ input: make(chan *input, 1),
42
+ Events: make(chan Event, 50),
43
+ Errors: make(chan error),
44
+ quit: make(chan chan<- error, 1),
45
+ }
46
+ go w.readEvents()
47
+ return w, nil
48
+}
49
+
50
+// Close removes all watches and closes the events channel.
51
+func (w *Watcher) Close() error {
52
+ if w.isClosed {
53
+ return nil
54
+ }
55
+ w.isClosed = true
56
+
57
+ // Send "quit" message to the reader goroutine
58
+ ch := make(chan error)
59
+ w.quit <- ch
60
+ if err := w.wakeupReader(); err != nil {
61
+ return err
62
+ }
63
+ return <-ch
64
+}
65
+
66
+// Add starts watching the named file or directory (non-recursively).
67
+func (w *Watcher) Add(name string) error {
68
+ if w.isClosed {
69
+ return errors.New("watcher already closed")
70
+ }
71
+ in := &input{
72
+ op: opAddWatch,
73
+ path: filepath.Clean(name),
74
+ flags: sys_FS_ALL_EVENTS,
75
+ reply: make(chan error),
76
+ }
77
+ w.input <- in
78
+ if err := w.wakeupReader(); err != nil {
79
+ return err
80
+ }
81
+ return <-in.reply
82
+}
83
+
84
+// Remove stops watching the the named file or directory (non-recursively).
85
+func (w *Watcher) Remove(name string) error {
86
+ in := &input{
87
+ op: opRemoveWatch,
88
+ path: filepath.Clean(name),
89
+ reply: make(chan error),
90
+ }
91
+ w.input <- in
92
+ if err := w.wakeupReader(); err != nil {
93
+ return err
94
+ }
95
+ return <-in.reply
96
+}
97
+
98
+const (
99
+ // Options for AddWatch
100
+ sys_FS_ONESHOT = 0x80000000
101
+ sys_FS_ONLYDIR = 0x1000000
102
+
103
+ // Events
104
+ sys_FS_ACCESS = 0x1
105
+ sys_FS_ALL_EVENTS = 0xfff
106
+ sys_FS_ATTRIB = 0x4
107
+ sys_FS_CLOSE = 0x18
108
+ sys_FS_CREATE = 0x100
109
+ sys_FS_DELETE = 0x200
110
+ sys_FS_DELETE_SELF = 0x400
111
+ sys_FS_MODIFY = 0x2
112
+ sys_FS_MOVE = 0xc0
113
+ sys_FS_MOVED_FROM = 0x40
114
+ sys_FS_MOVED_TO = 0x80
115
+ sys_FS_MOVE_SELF = 0x800
116
+
117
+ // Special events
118
+ sys_FS_IGNORED = 0x8000
119
+ sys_FS_Q_OVERFLOW = 0x4000
120
+)
121
+
122
+func newEvent(name string, mask uint32) Event {
123
+ e := Event{Name: name}
124
+ if mask&sys_FS_CREATE == sys_FS_CREATE || mask&sys_FS_MOVED_TO == sys_FS_MOVED_TO {
125
+ e.Op |= Create
126
+ }
127
+ if mask&sys_FS_DELETE == sys_FS_DELETE || mask&sys_FS_DELETE_SELF == sys_FS_DELETE_SELF {
128
+ e.Op |= Remove
129
+ }
130
+ if mask&sys_FS_MODIFY == sys_FS_MODIFY {
131
+ e.Op |= Write
132
+ }
133
+ if mask&sys_FS_MOVE == sys_FS_MOVE || mask&sys_FS_MOVE_SELF == sys_FS_MOVE_SELF || mask&sys_FS_MOVED_FROM == sys_FS_MOVED_FROM {
134
+ e.Op |= Rename
135
+ }
136
+ if mask&sys_FS_ATTRIB == sys_FS_ATTRIB {
137
+ e.Op |= Chmod
138
+ }
139
+ return e
140
+}
141
+
142
+const (
143
+ opAddWatch = iota
144
+ opRemoveWatch
145
+)
146
+
147
+const (
148
+ provisional uint64 = 1 << (32 + iota)
149
+)
150
+
151
+type input struct {
152
+ op int
153
+ path string
154
+ flags uint32
155
+ reply chan error
156
+}
157
+
158
+type inode struct {
159
+ handle syscall.Handle
160
+ volume uint32
161
+ index uint64
162
+}
163
+
164
+type watch struct {
165
+ ov syscall.Overlapped
166
+ ino *inode // i-number
167
+ path string // Directory path
168
+ mask uint64 // Directory itself is being watched with these notify flags
169
+ names map[string]uint64 // Map of names being watched and their notify flags
170
+ rename string // Remembers the old name while renaming a file
171
+ buf [4096]byte
172
+}
173
+
174
+type indexMap map[uint64]*watch
175
+type watchMap map[uint32]indexMap
176
+
177
+func (w *Watcher) wakeupReader() error {
178
+ e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
179
+ if e != nil {
180
+ return os.NewSyscallError("PostQueuedCompletionStatus", e)
181
+ }
182
+ return nil
183
+}
184
+
185
+func getDir(pathname string) (dir string, err error) {
186
+ attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
187
+ if e != nil {
188
+ return "", os.NewSyscallError("GetFileAttributes", e)
189
+ }
190
+ if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
191
+ dir = pathname
192
+ } else {
193
+ dir, _ = filepath.Split(pathname)
194
+ dir = filepath.Clean(dir)
195
+ }
196
+ return
197
+}
198
+
199
+func getIno(path string) (ino *inode, err error) {
200
+ h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
201
+ syscall.FILE_LIST_DIRECTORY,
202
+ syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
203
+ nil, syscall.OPEN_EXISTING,
204
+ syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
205
+ if e != nil {
206
+ return nil, os.NewSyscallError("CreateFile", e)
207
+ }
208
+ var fi syscall.ByHandleFileInformation
209
+ if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
210
+ syscall.CloseHandle(h)
211
+ return nil, os.NewSyscallError("GetFileInformationByHandle", e)
212
+ }
213
+ ino = &inode{
214
+ handle: h,
215
+ volume: fi.VolumeSerialNumber,
216
+ index: uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
217
+ }
218
+ return ino, nil
219
+}
220
+
221
+// Must run within the I/O thread.
222
+func (m watchMap) get(ino *inode) *watch {
223
+ if i := m[ino.volume]; i != nil {
224
+ return i[ino.index]
225
+ }
226
+ return nil
227
+}
228
+
229
+// Must run within the I/O thread.
230
+func (m watchMap) set(ino *inode, watch *watch) {
231
+ i := m[ino.volume]
232
+ if i == nil {
233
+ i = make(indexMap)
234
+ m[ino.volume] = i
235
+ }
236
+ i[ino.index] = watch
237
+}
238
+
239
+// Must run within the I/O thread.
240
+func (w *Watcher) addWatch(pathname string, flags uint64) error {
241
+ dir, err := getDir(pathname)
242
+ if err != nil {
243
+ return err
244
+ }
245
+ if flags&sys_FS_ONLYDIR != 0 && pathname != dir {
246
+ return nil
247
+ }
248
+ ino, err := getIno(dir)
249
+ if err != nil {
250
+ return err
251
+ }
252
+ w.mu.Lock()
253
+ watchEntry := w.watches.get(ino)
254
+ w.mu.Unlock()
255
+ if watchEntry == nil {
256
+ if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
257
+ syscall.CloseHandle(ino.handle)
258
+ return os.NewSyscallError("CreateIoCompletionPort", e)
259
+ }
260
+ watchEntry = &watch{
261
+ ino: ino,
262
+ path: dir,
263
+ names: make(map[string]uint64),
264
+ }
265
+ w.mu.Lock()
266
+ w.watches.set(ino, watchEntry)
267
+ w.mu.Unlock()
268
+ flags |= provisional
269
+ } else {
270
+ syscall.CloseHandle(ino.handle)
271
+ }
272
+ if pathname == dir {
273
+ watchEntry.mask |= flags
274
+ } else {
275
+ watchEntry.names[filepath.Base(pathname)] |= flags
276
+ }
277
+ if err = w.startRead(watchEntry); err != nil {
278
+ return err
279
+ }
280
+ if pathname == dir {
281
+ watchEntry.mask &= ^provisional
282
+ } else {
283
+ watchEntry.names[filepath.Base(pathname)] &= ^provisional
284
+ }
285
+ return nil
286
+}
287
+
288
+// Must run within the I/O thread.
289
+func (w *Watcher) remWatch(pathname string) error {
290
+ dir, err := getDir(pathname)
291
+ if err != nil {
292
+ return err
293
+ }
294
+ ino, err := getIno(dir)
295
+ if err != nil {
296
+ return err
297
+ }
298
+ w.mu.Lock()
299
+ watch := w.watches.get(ino)
300
+ w.mu.Unlock()
301
+ if watch == nil {
302
+ return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
303
+ }
304
+ if pathname == dir {
305
+ w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED)
306
+ watch.mask = 0
307
+ } else {
308
+ name := filepath.Base(pathname)
309
+ w.sendEvent(watch.path+"\\"+name, watch.names[name]&sys_FS_IGNORED)
310
+ delete(watch.names, name)
311
+ }
312
+ return w.startRead(watch)
313
+}
314
+
315
+// Must run within the I/O thread.
316
+func (w *Watcher) deleteWatch(watch *watch) {
317
+ for name, mask := range watch.names {
318
+ if mask&provisional == 0 {
319
+ w.sendEvent(watch.path+"\\"+name, mask&sys_FS_IGNORED)
320
+ }
321
+ delete(watch.names, name)
322
+ }
323
+ if watch.mask != 0 {
324
+ if watch.mask&provisional == 0 {
325
+ w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED)
326
+ }
327
+ watch.mask = 0
328
+ }
329
+}
330
+
331
+// Must run within the I/O thread.
332
+func (w *Watcher) startRead(watch *watch) error {
333
+ if e := syscall.CancelIo(watch.ino.handle); e != nil {
334
+ w.Errors <- os.NewSyscallError("CancelIo", e)
335
+ w.deleteWatch(watch)
336
+ }
337
+ mask := toWindowsFlags(watch.mask)
338
+ for _, m := range watch.names {
339
+ mask |= toWindowsFlags(m)
340
+ }
341
+ if mask == 0 {
342
+ if e := syscall.CloseHandle(watch.ino.handle); e != nil {
343
+ w.Errors <- os.NewSyscallError("CloseHandle", e)
344
+ }
345
+ w.mu.Lock()
346
+ delete(w.watches[watch.ino.volume], watch.ino.index)
347
+ w.mu.Unlock()
348
+ return nil
349
+ }
350
+ e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
351
+ uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
352
+ if e != nil {
353
+ err := os.NewSyscallError("ReadDirectoryChanges", e)
354
+ if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
355
+ // Watched directory was probably removed
356
+ if w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF) {
357
+ if watch.mask&sys_FS_ONESHOT != 0 {
358
+ watch.mask = 0
359
+ }
360
+ }
361
+ err = nil
362
+ }
363
+ w.deleteWatch(watch)
364
+ w.startRead(watch)
365
+ return err
366
+ }
367
+ return nil
368
+}
369
+
370
+// readEvents reads from the I/O completion port, converts the
371
+// received events into Event objects and sends them via the Events channel.
372
+// Entry point to the I/O thread.
373
+func (w *Watcher) readEvents() {
374
+ var (
375
+ n, key uint32
376
+ ov *syscall.Overlapped
377
+ )
378
+ runtime.LockOSThread()
379
+
380
+ for {
381
+ e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
382
+ watch := (*watch)(unsafe.Pointer(ov))
383
+
384
+ if watch == nil {
385
+ select {
386
+ case ch := <-w.quit:
387
+ w.mu.Lock()
388
+ var indexes []indexMap
389
+ for _, index := range w.watches {
390
+ indexes = append(indexes, index)
391
+ }
392
+ w.mu.Unlock()
393
+ for _, index := range indexes {
394
+ for _, watch := range index {
395
+ w.deleteWatch(watch)
396
+ w.startRead(watch)
397
+ }
398
+ }
399
+ var err error
400
+ if e := syscall.CloseHandle(w.port); e != nil {
401
+ err = os.NewSyscallError("CloseHandle", e)
402
+ }
403
+ close(w.Events)
404
+ close(w.Errors)
405
+ ch <- err
406
+ return
407
+ case in := <-w.input:
408
+ switch in.op {
409
+ case opAddWatch:
410
+ in.reply <- w.addWatch(in.path, uint64(in.flags))
411
+ case opRemoveWatch:
412
+ in.reply <- w.remWatch(in.path)
413
+ }
414
+ default:
415
+ }
416
+ continue
417
+ }
418
+
419
+ switch e {
420
+ case syscall.ERROR_MORE_DATA:
421
+ if watch == nil {
422
+ w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
423
+ } else {
424
+ // The i/o succeeded but the buffer is full.
425
+ // In theory we should be building up a full packet.
426
+ // In practice we can get away with just carrying on.
427
+ n = uint32(unsafe.Sizeof(watch.buf))
428
+ }
429
+ case syscall.ERROR_ACCESS_DENIED:
430
+ // Watched directory was probably removed
431
+ w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF)
432
+ w.deleteWatch(watch)
433
+ w.startRead(watch)
434
+ continue
435
+ case syscall.ERROR_OPERATION_ABORTED:
436
+ // CancelIo was called on this handle
437
+ continue
438
+ default:
439
+ w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
440
+ continue
441
+ case nil:
442
+ }
443
+
444
+ var offset uint32
445
+ for {
446
+ if n == 0 {
447
+ w.Events <- newEvent("", sys_FS_Q_OVERFLOW)
448
+ w.Errors <- errors.New("short read in readEvents()")
449
+ break
450
+ }
451
+
452
+ // Point "raw" to the event in the buffer
453
+ raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
454
+ buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
455
+ name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
456
+ fullname := watch.path + "\\" + name
457
+
458
+ var mask uint64
459
+ switch raw.Action {
460
+ case syscall.FILE_ACTION_REMOVED:
461
+ mask = sys_FS_DELETE_SELF
462
+ case syscall.FILE_ACTION_MODIFIED:
463
+ mask = sys_FS_MODIFY
464
+ case syscall.FILE_ACTION_RENAMED_OLD_NAME:
465
+ watch.rename = name
466
+ case syscall.FILE_ACTION_RENAMED_NEW_NAME:
467
+ if watch.names[watch.rename] != 0 {
468
+ watch.names[name] |= watch.names[watch.rename]
469
+ delete(watch.names, watch.rename)
470
+ mask = sys_FS_MOVE_SELF
471
+ }
472
+ }
473
+
474
+ sendNameEvent := func() {
475
+ if w.sendEvent(fullname, watch.names[name]&mask) {
476
+ if watch.names[name]&sys_FS_ONESHOT != 0 {
477
+ delete(watch.names, name)
478
+ }
479
+ }
480
+ }
481
+ if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
482
+ sendNameEvent()
483
+ }
484
+ if raw.Action == syscall.FILE_ACTION_REMOVED {
485
+ w.sendEvent(fullname, watch.names[name]&sys_FS_IGNORED)
486
+ delete(watch.names, name)
487
+ }
488
+ if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
489
+ if watch.mask&sys_FS_ONESHOT != 0 {
490
+ watch.mask = 0
491
+ }
492
+ }
493
+ if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
494
+ fullname = watch.path + "\\" + watch.rename
495
+ sendNameEvent()
496
+ }
497
+
498
+ // Move to the next event in the buffer
499
+ if raw.NextEntryOffset == 0 {
500
+ break
501
+ }
502
+ offset += raw.NextEntryOffset
503
+
504
+ // Error!
505
+ if offset >= n {
506
+ w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
507
+ break
508
+ }
509
+ }
510
+
511
+ if err := w.startRead(watch); err != nil {
512
+ w.Errors <- err
513
+ }
514
+ }
515
+}
516
+
517
+func (w *Watcher) sendEvent(name string, mask uint64) bool {
518
+ if mask == 0 {
519
+ return false
520
+ }
521
+ event := newEvent(name, uint32(mask))
522
+ select {
523
+ case ch := <-w.quit:
524
+ w.quit <- ch
525
+ case w.Events <- event:
526
+ }
527
+ return true
528
+}
529
+
530
+func toWindowsFlags(mask uint64) uint32 {
531
+ var m uint32
532
+ if mask&sys_FS_ACCESS != 0 {
533
+ m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
534
+ }
535
+ if mask&sys_FS_MODIFY != 0 {
536
+ m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
537
+ }
538
+ if mask&sys_FS_ATTRIB != 0 {
539
+ m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
540
+ }
541
+ if mask&(sys_FS_MOVE|sys_FS_CREATE|sys_FS_DELETE) != 0 {
542
+ m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
543
+ }
544
+ return m
545
+}
546
+
547
+func toFSnotifyFlags(action uint32) uint64 {
548
+ switch action {
549
+ case syscall.FILE_ACTION_ADDED:
550
+ return sys_FS_CREATE
551
+ case syscall.FILE_ACTION_REMOVED:
552
+ return sys_FS_DELETE
553
+ case syscall.FILE_ACTION_MODIFIED:
554
+ return sys_FS_MODIFY
555
+ case syscall.FILE_ACTION_RENAMED_OLD_NAME:
556
+ return sys_FS_MOVED_FROM
557
+ case syscall.FILE_ACTION_RENAMED_NEW_NAME:
558
+ return sys_FS_MOVED_TO
559
+ }
560
+ return 0
561
+}
cmd/ipfswatch/README.md
new
+7
@@ -0,0 +1,7 @@
1
+IPFSWatch monitors a directory and adds changes to IPFS
2
+
3
+```
4
+λ. ipfswatch --help
5
+ -path=".": the path to watch
6
+ -repo="": IPFS_PATH to use
7
+```
cmd/ipfswatch/ipfswatch_test.go
new
+13
@@ -0,0 +1,13 @@
1
+package main
2
+
3
+import (
4
+ "testing"
5
+
6
+ "github.com/jbenet/go-ipfs/thirdparty/assert"
7
+)
8
+
9
+func TestIsHidden(t *testing.T) {
10
+ assert.True(IsHidden("bar/.git"), t, "dirs beginning with . should be recognized as hidden")
11
+ assert.False(IsHidden("."), t, ". for current dir should not be considered hidden")
12
+ assert.False(IsHidden("bar/baz"), t, "normal dirs should not be hidden")
13
+}
cmd/ipfswatch/main.go
new
+154
@@ -0,0 +1,154 @@
1
+package main
2
+
3
+import (
4
+ "flag"
5
+ "log"
6
+ "os"
7
+ "path/filepath"
8
+
9
+ "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context"
10
+)
11
+import (
12
+ "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/mitchellh/go-homedir"
13
+ "github.com/jbenet/go-ipfs/Godeps/_workspace/src/gopkg.in/fsnotify.v1"
14
+ "github.com/jbenet/go-ipfs/core"
15
+ "github.com/jbenet/go-ipfs/core/coreunix"
16
+ "github.com/jbenet/go-ipfs/repo/config"
17
+ "github.com/jbenet/go-ipfs/repo/fsrepo"
18
+)
19
+
20
+var repoPath = flag.String("repo", os.Getenv("IPFS_PATH"), "IPFS_PATH to use")
21
+var watchPath = flag.String("path", ".", "the path to watch")
22
+
23
+func main() {
24
+ flag.Parse()
25
+
26
+ // precedence
27
+ // 1. --repo flag
28
+ // 2. IPFS_PATH environment variable
29
+ // 3. default repo path
30
+ ipfsPath := config.DefaultPathRoot
31
+ if *repoPath != "" {
32
+ ipfsPath = *repoPath
33
+ }
34
+
35
+ if err := run(ipfsPath, *watchPath); err != nil {
36
+ log.Fatal(err)
37
+ }
38
+}
39
+
40
+func run(ipfsPath, watchPath string) error {
41
+ log.Printf("running IPFSWatch on %s using repo at %s...", watchPath, ipfsPath)
42
+
43
+ ipfsPath, err := homedir.Expand(ipfsPath)
44
+ if err != nil {
45
+ return err
46
+ }
47
+ watcher, err := fsnotify.NewWatcher()
48
+ if err != nil {
49
+ return err
50
+ }
51
+ defer watcher.Close()
52
+
53
+ if err := addTree(watcher, watchPath); err != nil {
54
+ return err
55
+ }
56
+
57
+ r := fsrepo.At(ipfsPath)
58
+ if err := r.Open(); err != nil {
59
+ // TODO handle case: daemon running
60
+ // TODO handle case: repo doesn't exist or isn't initialized
61
+ return err
62
+ }
63
+ node, err := core.NewIPFSNode(context.Background(), core.Online(r))
64
+ if err != nil {
65
+ return err
66
+ }
67
+ defer node.Close()
68
+
69
+ for {
70
+ select {
71
+ case e := <-watcher.Events:
72
+ log.Printf("received event: %s", e)
73
+ isDir, err := IsDirectory(e.Name)
74
+ if err != nil {
75
+ continue
76
+ }
77
+ switch e.Op {
78
+ case fsnotify.Remove:
79
+ if isDir {
80
+ if err := watcher.Remove(e.Name); err != nil {
81
+ return err
82
+ }
83
+ }
84
+ default:
85
+ // all events except for Remove result in an IPFS.Add, but only
86
+ // directory creation triggers a new watch
87
+ switch e.Op {
88
+ case fsnotify.Create:
89
+ if isDir {
90
+ addTree(watcher, e.Name)
91
+ }
92
+ }
93
+ func() {
94
+ file, err := os.Open(e.Name)
95
+ if err != nil {
96
+ log.Println(err)
97
+ }
98
+ defer file.Close()
99
+ k, err := coreunix.Add(node, file)
100
+ if err != nil {
101
+ log.Println(err)
102
+ }
103
+ log.Printf("added %s... key: %s", e.Name, k)
104
+ }()
105
+ }
106
+ case err := <-watcher.Errors:
107
+ log.Println(err)
108
+ }
109
+ }
110
+ return nil
111
+}
112
+
113
+func addTree(w *fsnotify.Watcher, root string) error {
114
+ err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
115
+ isDir, err := IsDirectory(path)
116
+ if err != nil {
117
+ log.Println(err)
118
+ return nil
119
+ }
120
+ switch {
121
+ case isDir && IsHidden(path):
122
+ log.Println(path)
123
+ return filepath.SkipDir
124
+ case isDir:
125
+ log.Println(path)
126
+ if err := w.Add(path); err != nil {
127
+ return err
128
+ }
129
+ default:
130
+ return nil
131
+ }
132
+ return nil
133
+ })
134
+ if err != nil {
135
+ return err
136
+ }
137
+ return nil
138
+}
139
+
140
+func IsDirectory(path string) (bool, error) {
141
+ fileInfo, err := os.Stat(path)
142
+ return fileInfo.IsDir(), err
143
+}
144
+
145
+func IsHidden(path string) bool {
146
+ path = filepath.Base(path)
147
+ if path == "." || path == "" {
148
+ return false
149
+ }
150
+ if rune(path[0]) == rune('.') {
151
+ return true
152
+ }
153
+ return false
154
+}