@cryptotaxi247 / kubo / commits / 038ec6a5b

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 +[![Coverage](http://gocover.io/_badge/github.com/go-fsnotify/fsnotify)](http://gocover.io/github.com/go-fsnotify/fsnotify) [![GoDoc](https://godoc.org/gopkg.in/fsnotify.v1?status.svg)](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 +}