1
0
Fork 0
mirror of https://github.com/syncthing/syncthing-android.git synced 2024-12-25 20:31:29 +00:00
syncthing-android/ext/patches/syncthing-inotify/godeps/0001-add-godeps.patch

9173 lines
258 KiB
Diff
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

From cf0c69c5bc84cdcefe887245d4a093f41999caf2 Mon Sep 17 00:00:00 2001
From: Andrew Sutherland <dr3wsuth3rland@gmail.com>
Date: Sat, 10 Oct 2015 15:59:08 -0500
Subject: [PATCH] add godeps
---
Godeps/Godeps.json | 14 +
Godeps/Readme | 5 +
Godeps/_workspace/.gitignore | 2 +
.../src/github.com/cenkalti/backoff/.gitignore | 22 +
.../src/github.com/cenkalti/backoff/.travis.yml | 2 +
.../src/github.com/cenkalti/backoff/LICENSE | 20 +
.../src/github.com/cenkalti/backoff/README.md | 116 +++
.../cenkalti/backoff/adv_example_test.go | 117 +++
.../src/github.com/cenkalti/backoff/backoff.go | 59 ++
.../github.com/cenkalti/backoff/backoff_test.go | 27 +
.../github.com/cenkalti/backoff/example_test.go | 51 ++
.../src/github.com/cenkalti/backoff/exponential.go | 151 ++++
.../cenkalti/backoff/exponential_test.go | 108 +++
.../src/github.com/cenkalti/backoff/retry.go | 46 ++
.../src/github.com/cenkalti/backoff/retry_test.go | 34 +
.../src/github.com/cenkalti/backoff/ticker.go | 79 ++
.../src/github.com/cenkalti/backoff/ticker_test.go | 45 ++
.../src/github.com/zillode/notify/.gitignore | 88 ++
.../src/github.com/zillode/notify/.travis.yml | 35 +
.../src/github.com/zillode/notify/AUTHORS | 10 +
.../src/github.com/zillode/notify/LICENSE | 21 +
.../src/github.com/zillode/notify/README.md | 18 +
.../src/github.com/zillode/notify/appveyor.yml | 24 +
.../src/github.com/zillode/notify/debug.go | 11 +
.../src/github.com/zillode/notify/debug_debug.go | 43 +
.../src/github.com/zillode/notify/doc.go | 39 +
.../src/github.com/zillode/notify/event.go | 143 ++++
.../github.com/zillode/notify/event_fsevents.go | 71 ++
.../src/github.com/zillode/notify/event_inotify.go | 75 ++
.../src/github.com/zillode/notify/event_kqueue.go | 89 ++
.../src/github.com/zillode/notify/event_readdcw.go | 108 +++
.../src/github.com/zillode/notify/event_stub.go | 31 +
.../src/github.com/zillode/notify/event_test.go | 33 +
.../zillode/notify/example_fsevents_test.go | 128 +++
.../zillode/notify/example_inotify_test.go | 83 ++
.../zillode/notify/example_readdcw_test.go | 40 +
.../src/github.com/zillode/notify/example_test.go | 87 ++
.../src/github.com/zillode/notify/node.go | 271 +++++++
.../src/github.com/zillode/notify/notify.go | 70 ++
.../zillode/notify/notify_inotify_test.go | 37 +
.../zillode/notify/notify_readdcw_test.go | 60 ++
.../src/github.com/zillode/notify/notify_test.go | 103 +++
.../github.com/zillode/notify/sync_readdcw_test.go | 33 +
.../github.com/zillode/notify/sync_unix_test.go | 18 +
.../src/github.com/zillode/notify/testdata/vfs.txt | 56 ++
.../src/github.com/zillode/notify/testing_test.go | 895 +++++++++++++++++++++
.../src/github.com/zillode/notify/tree.go | 22 +
.../github.com/zillode/notify/tree_nonrecursive.go | 292 +++++++
.../zillode/notify/tree_nonrecursive_test.go | 543 +++++++++++++
.../github.com/zillode/notify/tree_recursive.go | 354 ++++++++
.../zillode/notify/tree_recursive_test.go | 524 ++++++++++++
.../src/github.com/zillode/notify/util.go | 146 ++++
.../github.com/zillode/notify/util_darwin_test.go | 41 +
.../src/github.com/zillode/notify/util_test.go | 147 ++++
.../github.com/zillode/notify/util_unix_test.go | 94 +++
.../src/github.com/zillode/notify/watcher.go | 85 ++
.../github.com/zillode/notify/watcher_fsevents.go | 319 ++++++++
.../zillode/notify/watcher_fsevents_cgo.go | 156 ++++
.../zillode/notify/watcher_fsevents_test.go | 111 +++
.../github.com/zillode/notify/watcher_inotify.go | 396 +++++++++
.../zillode/notify/watcher_inotify_test.go | 132 +++
.../github.com/zillode/notify/watcher_kqueue.go | 413 ++++++++++
.../zillode/notify/watcher_kqueue_test.go | 72 ++
.../github.com/zillode/notify/watcher_readdcw.go | 574 +++++++++++++
.../zillode/notify/watcher_readdcw_test.go | 67 ++
.../zillode/notify/watcher_recursive_test.go | 102 +++
.../src/github.com/zillode/notify/watcher_stub.go | 21 +
.../src/github.com/zillode/notify/watcher_test.go | 32 +
.../src/github.com/zillode/notify/watchpoint.go | 102 +++
.../github.com/zillode/notify/watchpoint_other.go | 23 +
.../zillode/notify/watchpoint_readdcw.go | 38 +
.../github.com/zillode/notify/watchpoint_test.go | 162 ++++
72 files changed, 8586 insertions(+)
create mode 100644 Godeps/Godeps.json
create mode 100644 Godeps/Readme
create mode 100644 Godeps/_workspace/.gitignore
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/.gitignore
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/.travis.yml
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/LICENSE
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/README.md
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/adv_example_test.go
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/backoff.go
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/backoff_test.go
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/example_test.go
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/exponential.go
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/exponential_test.go
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/retry.go
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/retry_test.go
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/ticker.go
create mode 100644 Godeps/_workspace/src/github.com/cenkalti/backoff/ticker_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/.gitignore
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/.travis.yml
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/AUTHORS
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/LICENSE
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/README.md
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/appveyor.yml
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/debug.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/debug_debug.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/doc.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/event.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/event_fsevents.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/event_inotify.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/event_kqueue.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/event_readdcw.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/event_stub.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/event_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/example_fsevents_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/example_inotify_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/example_readdcw_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/example_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/node.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/notify.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/notify_inotify_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/notify_readdcw_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/notify_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/sync_readdcw_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/sync_unix_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/testdata/vfs.txt
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/testing_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/tree.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/tree_nonrecursive.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/tree_nonrecursive_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/tree_recursive.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/tree_recursive_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/util.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/util_darwin_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/util_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/util_unix_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watcher.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watcher_fsevents.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watcher_fsevents_cgo.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watcher_fsevents_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watcher_inotify.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watcher_inotify_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watcher_kqueue.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watcher_kqueue_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watcher_readdcw.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watcher_readdcw_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watcher_recursive_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watcher_stub.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watcher_test.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watchpoint.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watchpoint_other.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watchpoint_readdcw.go
create mode 100644 Godeps/_workspace/src/github.com/zillode/notify/watchpoint_test.go
diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json
new file mode 100644
index 0000000..79c42a5
--- /dev/null
+++ b/Godeps/Godeps.json
@@ -0,0 +1,14 @@
+{
+ "ImportPath": "github.com/syncthing/syncthing-inotify",
+ "GoVersion": "go1.5.1",
+ "Deps": [
+ {
+ "ImportPath": "github.com/cenkalti/backoff",
+ "Rev": "4dc77674aceaabba2c7e3da25d4c823edfb73f99"
+ },
+ {
+ "ImportPath": "github.com/zillode/notify",
+ "Rev": "f06b1e3b795091f2e1414067b08e5f07332cdb05"
+ }
+ ]
+}
diff --git a/Godeps/Readme b/Godeps/Readme
new file mode 100644
index 0000000..4cdaa53
--- /dev/null
+++ b/Godeps/Readme
@@ -0,0 +1,5 @@
+This directory tree is generated automatically by godep.
+
+Please do not edit.
+
+See https://github.com/tools/godep for more information.
diff --git a/Godeps/_workspace/.gitignore b/Godeps/_workspace/.gitignore
new file mode 100644
index 0000000..f037d68
--- /dev/null
+++ b/Godeps/_workspace/.gitignore
@@ -0,0 +1,2 @@
+/pkg
+/bin
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/.gitignore b/Godeps/_workspace/src/github.com/cenkalti/backoff/.gitignore
new file mode 100644
index 0000000..0026861
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/.gitignore
@@ -0,0 +1,22 @@
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/.travis.yml b/Godeps/_workspace/src/github.com/cenkalti/backoff/.travis.yml
new file mode 100644
index 0000000..ce9cb62
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/.travis.yml
@@ -0,0 +1,2 @@
+language: go
+go: 1.3.3
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/LICENSE b/Godeps/_workspace/src/github.com/cenkalti/backoff/LICENSE
new file mode 100644
index 0000000..89b8179
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Cenk Altı
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/README.md b/Godeps/_workspace/src/github.com/cenkalti/backoff/README.md
new file mode 100644
index 0000000..020b8fb
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/README.md
@@ -0,0 +1,116 @@
+# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis]
+
+This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client].
+
+[Exponential backoff][exponential backoff wiki]
+is an algorithm that uses feedback to multiplicatively decrease the rate of some process,
+in order to gradually find an acceptable rate.
+The retries exponentially increase and stop increasing when a certain threshold is met.
+
+## How To
+
+We define two functions, `Retry()` and `RetryNotify()`.
+They receive an `Operation` to execute, a `BackOff` algorithm,
+and an optional `Notify` error handler.
+
+The operation will be executed, and will be retried on failure with delay
+as given by the backoff algorithm. The backoff algorithm can also decide when to stop
+retrying.
+In addition, the notify error handler will be called after each failed attempt,
+except for the last time, whose error should be handled by the caller.
+
+```go
+// An Operation is executing by Retry() or RetryNotify().
+// The operation will be retried using a backoff policy if it returns an error.
+type Operation func() error
+
+// Notify is a notify-on-error function. It receives an operation error and
+// backoff delay if the operation failed (with an error).
+//
+// NOTE that if the backoff policy stated to stop retrying,
+// the notify function isn't called.
+type Notify func(error, time.Duration)
+
+func Retry(Operation, BackOff) error
+func RetryNotify(Operation, BackOff, Notify)
+```
+
+## Examples
+
+See more advanced examples in the [godoc][advanced example].
+
+### Retry
+
+Simple retry helper that uses the default exponential backoff algorithm:
+
+```go
+operation := func() error {
+ // An operation that might fail.
+ return nil // or return errors.New("some error")
+}
+
+err := Retry(operation, NewExponentialBackOff())
+if err != nil {
+ // Handle error.
+ return err
+}
+
+// Operation is successful.
+return nil
+```
+
+### Ticker
+
+```go
+operation := func() error {
+ // An operation that might fail
+ return nil // or return errors.New("some error")
+}
+
+b := NewExponentialBackOff()
+ticker := NewTicker(b)
+
+var err error
+
+// Ticks will continue to arrive when the previous operation is still running,
+// so operations that take a while to fail could run in quick succession.
+for range ticker.C {
+ if err = operation(); err != nil {
+ log.Println(err, "will retry...")
+ continue
+ }
+
+ ticker.Stop()
+ break
+}
+
+if err != nil {
+ // Operation has failed.
+ return err
+}
+
+// Operation is successful.
+return nil
+```
+
+## Getting Started
+
+```bash
+# install
+$ go get github.com/cenkalti/backoff
+
+# test
+$ cd $GOPATH/src/github.com/cenkalti/backoff
+$ go get -t ./...
+$ go test -v -cover
+```
+
+[godoc]: https://godoc.org/github.com/cenkalti/backoff
+[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png
+[travis]: https://travis-ci.org/cenkalti/backoff
+[travis image]: https://travis-ci.org/cenkalti/backoff.png
+
+[google-http-java-client]: https://github.com/google/google-http-java-client
+[exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff
+
+[advanced example]: https://godoc.org/github.com/cenkalti/backoff#example_
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/adv_example_test.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/adv_example_test.go
new file mode 100644
index 0000000..3fe6783
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/adv_example_test.go
@@ -0,0 +1,117 @@
+package backoff
+
+import (
+ "io/ioutil"
+ "log"
+ "net/http"
+ "time"
+)
+
+// This is an example that demonstrates how this package could be used
+// to perform various advanced operations.
+//
+// It executes an HTTP GET request with exponential backoff,
+// while errors are logged and failed responses are closed, as required by net/http package.
+//
+// Note we define a condition function which is used inside the operation to
+// determine whether the operation succeeded or failed.
+func Example() error {
+ res, err := GetWithRetry(
+ "http://localhost:9999",
+ ErrorIfStatusCodeIsNot(http.StatusOK),
+ NewExponentialBackOff())
+
+ if err != nil {
+ // Close response body of last (failed) attempt.
+ // The Last attempt isn't handled by the notify-on-error function,
+ // which closes the body of all the previous attempts.
+ if e := res.Body.Close(); e != nil {
+ log.Printf("error closing last attempt's response body: %s", e)
+ }
+ log.Printf("too many failed request attempts: %s", err)
+ return err
+ }
+ defer res.Body.Close() // The response's Body must be closed.
+
+ // Read body
+ _, _ = ioutil.ReadAll(res.Body)
+
+ // Do more stuff
+ return nil
+}
+
+// GetWithRetry is a helper function that performs an HTTP GET request
+// to the given URL, and retries with the given backoff using the given condition function.
+//
+// It also uses a notify-on-error function which logs
+// and closes the response body of the failed request.
+func GetWithRetry(url string, condition Condition, bck BackOff) (*http.Response, error) {
+ var res *http.Response
+ err := RetryNotify(
+ func() error {
+ var err error
+ res, err = http.Get(url)
+ if err != nil {
+ return err
+ }
+ return condition(res)
+ },
+ bck,
+ LogAndClose())
+
+ return res, err
+}
+
+// Condition is a retry condition function.
+// It receives a response, and returns an error
+// if the response failed the condition.
+type Condition func(*http.Response) error
+
+// ErrorIfStatusCodeIsNot returns a retry condition function.
+// The condition returns an error
+// if the given response's status code is not the given HTTP status code.
+func ErrorIfStatusCodeIsNot(status int) Condition {
+ return func(res *http.Response) error {
+ if res.StatusCode != status {
+ return NewError(res)
+ }
+ return nil
+ }
+}
+
+// Error is returned on ErrorIfX() condition functions throughout this package.
+type Error struct {
+ Response *http.Response
+}
+
+func NewError(res *http.Response) *Error {
+ // Sanity check
+ if res == nil {
+ panic("response object is nil")
+ }
+ return &Error{Response: res}
+}
+func (err *Error) Error() string { return "request failed" }
+
+// LogAndClose is a notify-on-error function.
+// It logs the error and closes the response body.
+func LogAndClose() Notify {
+ return func(err error, wait time.Duration) {
+ switch e := err.(type) {
+ case *Error:
+ defer e.Response.Body.Close()
+
+ b, err := ioutil.ReadAll(e.Response.Body)
+ var body string
+ if err != nil {
+ body = "can't read body"
+ } else {
+ body = string(b)
+ }
+
+ log.Printf("%s: %s", e.Response.Status, body)
+ default:
+ log.Println(err)
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/backoff.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/backoff.go
new file mode 100644
index 0000000..61bd6df
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/backoff.go
@@ -0,0 +1,59 @@
+// Package backoff implements backoff algorithms for retrying operations.
+//
+// Also has a Retry() helper for retrying operations that may fail.
+package backoff
+
+import "time"
+
+// BackOff is a backoff policy for retrying an operation.
+type BackOff interface {
+ // NextBackOff returns the duration to wait before retrying the operation,
+ // or backoff.Stop to indicate that no more retries should be made.
+ //
+ // Example usage:
+ //
+ // duration := backoff.NextBackOff();
+ // if (duration == backoff.Stop) {
+ // // Do not retry operation.
+ // } else {
+ // // Sleep for duration and retry operation.
+ // }
+ //
+ NextBackOff() time.Duration
+
+ // Reset to initial state.
+ Reset()
+}
+
+// Indicates that no more retries should be made for use in NextBackOff().
+const Stop time.Duration = -1
+
+// ZeroBackOff is a fixed backoff policy whose backoff time is always zero,
+// meaning that the operation is retried immediately without waiting, indefinitely.
+type ZeroBackOff struct{}
+
+func (b *ZeroBackOff) Reset() {}
+
+func (b *ZeroBackOff) NextBackOff() time.Duration { return 0 }
+
+// StopBackOff is a fixed backoff policy that always returns backoff.Stop for
+// NextBackOff(), meaning that the operation should never be retried.
+type StopBackOff struct{}
+
+func (b *StopBackOff) Reset() {}
+
+func (b *StopBackOff) NextBackOff() time.Duration { return Stop }
+
+// ConstantBackOff is a backoff policy that always returns the same backoff delay.
+// This is in contrast to an exponential backoff policy,
+// which returns a delay that grows longer as you call NextBackOff() over and over again.
+type ConstantBackOff struct {
+ Interval time.Duration
+}
+
+func (b *ConstantBackOff) Reset() {}
+func (b *ConstantBackOff) NextBackOff() time.Duration { return b.Interval }
+
+func NewConstantBackOff(d time.Duration) *ConstantBackOff {
+ return &ConstantBackOff{Interval: d}
+}
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/backoff_test.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/backoff_test.go
new file mode 100644
index 0000000..91f27c4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/backoff_test.go
@@ -0,0 +1,27 @@
+package backoff
+
+import (
+ "testing"
+ "time"
+)
+
+func TestNextBackOffMillis(t *testing.T) {
+ subtestNextBackOff(t, 0, new(ZeroBackOff))
+ subtestNextBackOff(t, Stop, new(StopBackOff))
+}
+
+func subtestNextBackOff(t *testing.T, expectedValue time.Duration, backOffPolicy BackOff) {
+ for i := 0; i < 10; i++ {
+ next := backOffPolicy.NextBackOff()
+ if next != expectedValue {
+ t.Errorf("got: %d expected: %d", next, expectedValue)
+ }
+ }
+}
+
+func TestConstantBackOff(t *testing.T) {
+ backoff := NewConstantBackOff(time.Second)
+ if backoff.NextBackOff() != time.Second {
+ t.Error("invalid interval")
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/example_test.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/example_test.go
new file mode 100644
index 0000000..0d1852e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/example_test.go
@@ -0,0 +1,51 @@
+package backoff
+
+import "log"
+
+func ExampleRetry() error {
+ operation := func() error {
+ // An operation that might fail.
+ return nil // or return errors.New("some error")
+ }
+
+ err := Retry(operation, NewExponentialBackOff())
+ if err != nil {
+ // Handle error.
+ return err
+ }
+
+ // Operation is successful.
+ return nil
+}
+
+func ExampleTicker() error {
+ operation := func() error {
+ // An operation that might fail
+ return nil // or return errors.New("some error")
+ }
+
+ b := NewExponentialBackOff()
+ ticker := NewTicker(b)
+
+ var err error
+
+ // Ticks will continue to arrive when the previous operation is still running,
+ // so operations that take a while to fail could run in quick succession.
+ for _ = range ticker.C {
+ if err = operation(); err != nil {
+ log.Println(err, "will retry...")
+ continue
+ }
+
+ ticker.Stop()
+ break
+ }
+
+ if err != nil {
+ // Operation has failed.
+ return err
+ }
+
+ // Operation is successful.
+ return nil
+}
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/exponential.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/exponential.go
new file mode 100644
index 0000000..cc2a164
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/exponential.go
@@ -0,0 +1,151 @@
+package backoff
+
+import (
+ "math/rand"
+ "time"
+)
+
+/*
+ExponentialBackOff is a backoff implementation that increases the backoff
+period for each retry attempt using a randomization function that grows exponentially.
+
+NextBackOff() is calculated using the following formula:
+
+ randomized interval =
+ RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
+
+In other words NextBackOff() will range between the randomization factor
+percentage below and above the retry interval.
+
+For example, given the following parameters:
+
+ RetryInterval = 2
+ RandomizationFactor = 0.5
+ Multiplier = 2
+
+the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
+multiplied by the exponential, that is, between 2 and 6 seconds.
+
+Note: MaxInterval caps the RetryInterval and not the randomized interval.
+
+If the time elapsed since an ExponentialBackOff instance is created goes past the
+MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
+
+The elapsed time can be reset by calling Reset().
+
+Example: Given the following default arguments, for 10 tries the sequence will be,
+and assuming we go over the MaxElapsedTime on the 10th try:
+
+ Request # RetryInterval (seconds) Randomized Interval (seconds)
+
+ 1 0.5 [0.25, 0.75]
+ 2 0.75 [0.375, 1.125]
+ 3 1.125 [0.562, 1.687]
+ 4 1.687 [0.8435, 2.53]
+ 5 2.53 [1.265, 3.795]
+ 6 3.795 [1.897, 5.692]
+ 7 5.692 [2.846, 8.538]
+ 8 8.538 [4.269, 12.807]
+ 9 12.807 [6.403, 19.210]
+ 10 19.210 backoff.Stop
+
+Note: Implementation is not thread-safe.
+*/
+type ExponentialBackOff struct {
+ InitialInterval time.Duration
+ RandomizationFactor float64
+ Multiplier float64
+ MaxInterval time.Duration
+ // After MaxElapsedTime the ExponentialBackOff stops.
+ // It never stops if MaxElapsedTime == 0.
+ MaxElapsedTime time.Duration
+ Clock Clock
+
+ currentInterval time.Duration
+ startTime time.Time
+}
+
+// Clock is an interface that returns current time for BackOff.
+type Clock interface {
+ Now() time.Time
+}
+
+// Default values for ExponentialBackOff.
+const (
+ DefaultInitialInterval = 500 * time.Millisecond
+ DefaultRandomizationFactor = 0.5
+ DefaultMultiplier = 1.5
+ DefaultMaxInterval = 60 * time.Second
+ DefaultMaxElapsedTime = 15 * time.Minute
+)
+
+// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
+func NewExponentialBackOff() *ExponentialBackOff {
+ b := &ExponentialBackOff{
+ InitialInterval: DefaultInitialInterval,
+ RandomizationFactor: DefaultRandomizationFactor,
+ Multiplier: DefaultMultiplier,
+ MaxInterval: DefaultMaxInterval,
+ MaxElapsedTime: DefaultMaxElapsedTime,
+ Clock: SystemClock,
+ }
+ b.Reset()
+ return b
+}
+
+type systemClock struct{}
+
+func (t systemClock) Now() time.Time {
+ return time.Now()
+}
+
+// SystemClock implements Clock interface that uses time.Now().
+var SystemClock = systemClock{}
+
+// Reset the interval back to the initial retry interval and restarts the timer.
+func (b *ExponentialBackOff) Reset() {
+ b.currentInterval = b.InitialInterval
+ b.startTime = b.Clock.Now()
+}
+
+// NextBackOff calculates the next backoff interval using the formula:
+// Randomized interval = RetryInterval +/- (RandomizationFactor * RetryInterval)
+func (b *ExponentialBackOff) NextBackOff() time.Duration {
+ // Make sure we have not gone over the maximum elapsed time.
+ if b.MaxElapsedTime != 0 && b.GetElapsedTime() > b.MaxElapsedTime {
+ return Stop
+ }
+ defer b.incrementCurrentInterval()
+ return getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
+}
+
+// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
+// is created and is reset when Reset() is called.
+//
+// The elapsed time is computed using time.Now().UnixNano().
+func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
+ return b.Clock.Now().Sub(b.startTime)
+}
+
+// Increments the current interval by multiplying it with the multiplier.
+func (b *ExponentialBackOff) incrementCurrentInterval() {
+ // Check for overflow, if overflow is detected set the current interval to the max interval.
+ if float64(b.currentInterval) >= float64(b.MaxInterval)/b.Multiplier {
+ b.currentInterval = b.MaxInterval
+ } else {
+ b.currentInterval = time.Duration(float64(b.currentInterval) * b.Multiplier)
+ }
+}
+
+// Returns a random value from the following interval:
+// [randomizationFactor * currentInterval, randomizationFactor * currentInterval].
+func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
+ var delta = randomizationFactor * float64(currentInterval)
+ var minInterval = float64(currentInterval) - delta
+ var maxInterval = float64(currentInterval) + delta
+
+ // Get a random value from the range [minInterval, maxInterval].
+ // The formula used below has a +1 because if the minInterval is 1 and the maxInterval is 3 then
+ // we want a 33% chance for selecting either 1, 2 or 3.
+ return time.Duration(minInterval + (random * (maxInterval - minInterval + 1)))
+}
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/exponential_test.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/exponential_test.go
new file mode 100644
index 0000000..11b95e4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/exponential_test.go
@@ -0,0 +1,108 @@
+package backoff
+
+import (
+ "math"
+ "testing"
+ "time"
+)
+
+func TestBackOff(t *testing.T) {
+ var (
+ testInitialInterval = 500 * time.Millisecond
+ testRandomizationFactor = 0.1
+ testMultiplier = 2.0
+ testMaxInterval = 5 * time.Second
+ testMaxElapsedTime = 15 * time.Minute
+ )
+
+ exp := NewExponentialBackOff()
+ exp.InitialInterval = testInitialInterval
+ exp.RandomizationFactor = testRandomizationFactor
+ exp.Multiplier = testMultiplier
+ exp.MaxInterval = testMaxInterval
+ exp.MaxElapsedTime = testMaxElapsedTime
+ exp.Reset()
+
+ var expectedResults = []time.Duration{500, 1000, 2000, 4000, 5000, 5000, 5000, 5000, 5000, 5000}
+ for i, d := range expectedResults {
+ expectedResults[i] = d * time.Millisecond
+ }
+
+ for _, expected := range expectedResults {
+ assertEquals(t, expected, exp.currentInterval)
+ // Assert that the next backoff falls in the expected range.
+ var minInterval = expected - time.Duration(testRandomizationFactor*float64(expected))
+ var maxInterval = expected + time.Duration(testRandomizationFactor*float64(expected))
+ var actualInterval = exp.NextBackOff()
+ if !(minInterval <= actualInterval && actualInterval <= maxInterval) {
+ t.Error("error")
+ }
+ }
+}
+
+func TestGetRandomizedInterval(t *testing.T) {
+ // 33% chance of being 1.
+ assertEquals(t, 1, getRandomValueFromInterval(0.5, 0, 2))
+ assertEquals(t, 1, getRandomValueFromInterval(0.5, 0.33, 2))
+ // 33% chance of being 2.
+ assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.34, 2))
+ assertEquals(t, 2, getRandomValueFromInterval(0.5, 0.66, 2))
+ // 33% chance of being 3.
+ assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.67, 2))
+ assertEquals(t, 3, getRandomValueFromInterval(0.5, 0.99, 2))
+}
+
+type TestClock struct {
+ i time.Duration
+ start time.Time
+}
+
+func (c *TestClock) Now() time.Time {
+ t := c.start.Add(c.i)
+ c.i += time.Second
+ return t
+}
+
+func TestGetElapsedTime(t *testing.T) {
+ var exp = NewExponentialBackOff()
+ exp.Clock = &TestClock{}
+ exp.Reset()
+
+ var elapsedTime = exp.GetElapsedTime()
+ if elapsedTime != time.Second {
+ t.Errorf("elapsedTime=%d", elapsedTime)
+ }
+}
+
+func TestMaxElapsedTime(t *testing.T) {
+ var exp = NewExponentialBackOff()
+ exp.Clock = &TestClock{start: time.Time{}.Add(10000 * time.Second)}
+ // Change the currentElapsedTime to be 0 ensuring that the elapsed time will be greater
+ // than the max elapsed time.
+ exp.startTime = time.Time{}
+ assertEquals(t, Stop, exp.NextBackOff())
+}
+
+func TestBackOffOverflow(t *testing.T) {
+ var (
+ testInitialInterval time.Duration = math.MaxInt64 / 2
+ testMaxInterval time.Duration = math.MaxInt64
+ testMultiplier = 2.1
+ )
+
+ exp := NewExponentialBackOff()
+ exp.InitialInterval = testInitialInterval
+ exp.Multiplier = testMultiplier
+ exp.MaxInterval = testMaxInterval
+ exp.Reset()
+
+ exp.NextBackOff()
+ // Assert that when an overflow is possible the current varerval time.Duration is set to the max varerval time.Duration .
+ assertEquals(t, testMaxInterval, exp.currentInterval)
+}
+
+func assertEquals(t *testing.T, expected, value time.Duration) {
+ if expected != value {
+ t.Errorf("got: %d, expected: %d", value, expected)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/retry.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/retry.go
new file mode 100644
index 0000000..f01f2bb
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/retry.go
@@ -0,0 +1,46 @@
+package backoff
+
+import "time"
+
+// An Operation is executing by Retry() or RetryNotify().
+// The operation will be retried using a backoff policy if it returns an error.
+type Operation func() error
+
+// Notify is a notify-on-error function. It receives an operation error and
+// backoff delay if the operation failed (with an error).
+//
+// NOTE that if the backoff policy stated to stop retrying,
+// the notify function isn't called.
+type Notify func(error, time.Duration)
+
+// Retry the function f until it does not return error or BackOff stops.
+// f is guaranteed to be run at least once.
+// It is the caller's responsibility to reset b after Retry returns.
+//
+// Retry sleeps the goroutine for the duration returned by BackOff after a
+// failed operation returns.
+func Retry(o Operation, b BackOff) error { return RetryNotify(o, b, nil) }
+
+// RetryNotify calls notify function with the error and wait duration
+// for each failed attempt before sleep.
+func RetryNotify(operation Operation, b BackOff, notify Notify) error {
+ var err error
+ var next time.Duration
+
+ b.Reset()
+ for {
+ if err = operation(); err == nil {
+ return nil
+ }
+
+ if next = b.NextBackOff(); next == Stop {
+ return err
+ }
+
+ if notify != nil {
+ notify(err, next)
+ }
+
+ time.Sleep(next)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/retry_test.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/retry_test.go
new file mode 100644
index 0000000..c0d25ab
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/retry_test.go
@@ -0,0 +1,34 @@
+package backoff
+
+import (
+ "errors"
+ "log"
+ "testing"
+)
+
+func TestRetry(t *testing.T) {
+ const successOn = 3
+ var i = 0
+
+ // This function is successfull on "successOn" calls.
+ f := func() error {
+ i++
+ log.Printf("function is called %d. time\n", i)
+
+ if i == successOn {
+ log.Println("OK")
+ return nil
+ }
+
+ log.Println("error")
+ return errors.New("error")
+ }
+
+ err := Retry(f, NewExponentialBackOff())
+ if err != nil {
+ t.Errorf("unexpected error: %s", err.Error())
+ }
+ if i != successOn {
+ t.Errorf("invalid number of retries: %d", i)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/ticker.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/ticker.go
new file mode 100644
index 0000000..7a5ff4e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/ticker.go
@@ -0,0 +1,79 @@
+package backoff
+
+import (
+ "runtime"
+ "sync"
+ "time"
+)
+
+// Ticker holds a channel that delivers `ticks' of a clock at times reported by a BackOff.
+//
+// Ticks will continue to arrive when the previous operation is still running,
+// so operations that take a while to fail could run in quick succession.
+type Ticker struct {
+ C <-chan time.Time
+ c chan time.Time
+ b BackOff
+ stop chan struct{}
+ stopOnce sync.Once
+}
+
+// NewTicker returns a new Ticker containing a channel that will send the time at times
+// specified by the BackOff argument. Ticker is guaranteed to tick at least once.
+// The channel is closed when Stop method is called or BackOff stops.
+func NewTicker(b BackOff) *Ticker {
+ c := make(chan time.Time)
+ t := &Ticker{
+ C: c,
+ c: c,
+ b: b,
+ stop: make(chan struct{}),
+ }
+ go t.run()
+ runtime.SetFinalizer(t, (*Ticker).Stop)
+ return t
+}
+
+// Stop turns off a ticker. After Stop, no more ticks will be sent.
+func (t *Ticker) Stop() {
+ t.stopOnce.Do(func() { close(t.stop) })
+}
+
+func (t *Ticker) run() {
+ c := t.c
+ defer close(c)
+ t.b.Reset()
+
+ // Ticker is guaranteed to tick at least once.
+ afterC := t.send(time.Now())
+
+ for {
+ if afterC == nil {
+ return
+ }
+
+ select {
+ case tick := <-afterC:
+ afterC = t.send(tick)
+ case <-t.stop:
+ t.c = nil // Prevent future ticks from being sent to the channel.
+ return
+ }
+ }
+}
+
+func (t *Ticker) send(tick time.Time) <-chan time.Time {
+ select {
+ case t.c <- tick:
+ case <-t.stop:
+ return nil
+ }
+
+ next := t.b.NextBackOff()
+ if next == Stop {
+ t.Stop()
+ return nil
+ }
+
+ return time.After(next)
+}
diff --git a/Godeps/_workspace/src/github.com/cenkalti/backoff/ticker_test.go b/Godeps/_workspace/src/github.com/cenkalti/backoff/ticker_test.go
new file mode 100644
index 0000000..7c392df
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/cenkalti/backoff/ticker_test.go
@@ -0,0 +1,45 @@
+package backoff
+
+import (
+ "errors"
+ "log"
+ "testing"
+)
+
+func TestTicker(t *testing.T) {
+ const successOn = 3
+ var i = 0
+
+ // This function is successfull on "successOn" calls.
+ f := func() error {
+ i++
+ log.Printf("function is called %d. time\n", i)
+
+ if i == successOn {
+ log.Println("OK")
+ return nil
+ }
+
+ log.Println("error")
+ return errors.New("error")
+ }
+
+ b := NewExponentialBackOff()
+ ticker := NewTicker(b)
+
+ var err error
+ for _ = range ticker.C {
+ if err = f(); err != nil {
+ t.Log(err)
+ continue
+ }
+
+ break
+ }
+ if err != nil {
+ t.Errorf("unexpected error: %s", err.Error())
+ }
+ if i != successOn {
+ t.Errorf("invalid number of retries: %d", i)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/.gitignore b/Godeps/_workspace/src/github.com/zillode/notify/.gitignore
new file mode 100644
index 0000000..86d4fa8
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/.gitignore
@@ -0,0 +1,88 @@
+# Created by https://www.gitignore.io
+
+### OSX ###
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear on external disk
+.Spotlight-V100
+.Trashes
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+
+### Windows ###
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+
+### Linux ###
+*~
+
+# KDE directory preferences
+.directory
+
+
+### Go ###
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+
+
+### vim ###
+[._]*.s[a-w][a-z]
+[._]s[a-w][a-z]
+*.un~
+Session.vim
+.netrwhist
+*~
+
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/.travis.yml b/Godeps/_workspace/src/github.com/zillode/notify/.travis.yml
new file mode 100644
index 0000000..f287755
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/.travis.yml
@@ -0,0 +1,35 @@
+language: go
+
+go:
+ - 1.4.2
+
+os:
+ - linux
+ - osx
+
+matrix:
+ include:
+ - os: osx
+ go: 1.4.2
+ env:
+ - GOFLAGS="-tags kqueue"
+
+env:
+ global:
+ - secure: JfdLa3hcnvIwT13kbH2fr4c+4V9H4yO4GZsqDckODmxILlEtKcCLM5eCi09D21uTuaMUfZDIFNPN4mpDhbeEoM6Aoro4Ht+mX0vlpFUOS5bRqhP0l59PKEXqKFfh2IIlqEOmQ9XU6XvqWezTirN1OFUPMIMO9qSM/HuKhbX5wJQ=
+ - GOBIN=$HOME/bin
+ - PATH=$HOME/bin:$PATH
+
+install:
+ - if [ "$(curl -LIsS -w %{http_code} coveralls.io | tail -1)" = "200" ] && [ "$TRAVIS_OS_NAME" = "linux" ]; then export COVERALLS=1; fi
+ - go get golang.org/x/tools/cmd/vet
+ - if [ "$COVERALLS" = "1" ]; then go get golang.org/x/tools/cmd/cover github.com/mattn/goveralls github.com/modocache/gover; fi
+ - go get -t -v ./...
+
+script:
+ - go tool vet -all .
+ - go install $GOFLAGS ./...
+ - go test -v -race $GOFLAGS ./...
+ - if [ "$COVERALLS" = "1" ]; then go list -f '{{if len .TestGoFiles}}go test -coverprofile={{.Dir}}/.coverprofile {{.ImportPath}}{{end}}' ./... | xargs -i sh -c {}; fi
+ - if [ "$COVERALLS" = "1" ]; then gover; fi
+ - if [ "$COVERALLS" = "1" ]; then goveralls -coverprofile=gover.coverprofile -service=travis-ci -repotoken $COVERALLS_TOKEN; fi
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/AUTHORS b/Godeps/_workspace/src/github.com/zillode/notify/AUTHORS
new file mode 100644
index 0000000..9262eae
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/AUTHORS
@@ -0,0 +1,10 @@
+# List of individuals who contributed to the Notify package.
+#
+# The up-to-date list of the authors one may obtain with:
+#
+# ~ $ git shortlog -es | cut -f2 | rev | uniq -f1 | rev
+#
+
+Pawel Blaszczyk <blaszczykpb@gmail.com>
+Pawel Knap <pawelknap88@gmail.com>
+Rafal Jeczalik <rjeczalik@gmail.com>
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/LICENSE b/Godeps/_workspace/src/github.com/zillode/notify/LICENSE
new file mode 100644
index 0000000..3e67881
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014-2015 The Notify Authors
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/README.md b/Godeps/_workspace/src/github.com/zillode/notify/README.md
new file mode 100644
index 0000000..adbeb3f
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/README.md
@@ -0,0 +1,18 @@
+notify [![GoDoc](https://godoc.org/github.com/rjeczalik/notify?status.svg)](https://godoc.org/github.com/rjeczalik/notify) [![Build Status](https://img.shields.io/travis/rjeczalik/notify/master.svg)](https://travis-ci.org/rjeczalik/notify "inotify + FSEvents + kqueue") [![Build status](https://img.shields.io/appveyor/ci/rjeczalik/notify-246.svg)](https://ci.appveyor.com/project/rjeczalik/notify-246 "ReadDirectoryChangesW") [![Coverage Status](https://img.shields.io/coveralls/rjeczalik/notify/master.svg)](https://coveralls.io/r/rjeczalik/notify?branch=master)
+======
+
+Filesystem event notification library on steroids. (under active development)
+
+*Documentation*
+
+[godoc.org/github.com/rjeczalik/notify](https://godoc.org/github.com/rjeczalik/notify)
+
+*Installation*
+
+```
+~ $ go get -u github.com/rjeczalik/notify
+```
+
+*Projects using notify*
+
+- [github.com/rjeczalik/cmd/notify](https://godoc.org/github.com/rjeczalik/cmd/notify)
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/appveyor.yml b/Godeps/_workspace/src/github.com/zillode/notify/appveyor.yml
new file mode 100644
index 0000000..16d09ac
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/appveyor.yml
@@ -0,0 +1,24 @@
+version: "{build}"
+
+os: Windows Server 2012 R2
+
+clone_folder: c:\projects\src\github.com\rjeczalik\notify
+
+environment:
+ PATH: c:\projects\bin;%PATH%
+ GOPATH: c:\projects
+ NOTIFY_TIMEOUT: 5s
+
+install:
+ - go version
+ - go get golang.org/x/tools/cmd/vet
+ - go get -v -t ./...
+
+build_script:
+ - go tool vet -all .
+ - go build ./...
+ - go test -v -race ./...
+
+test: off
+
+deploy: off
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/debug.go b/Godeps/_workspace/src/github.com/zillode/notify/debug.go
new file mode 100644
index 0000000..bd9bc46
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/debug.go
@@ -0,0 +1,11 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build !debug
+
+package notify
+
+func dbgprint(...interface{}) {}
+
+func dbgprintf(string, ...interface{}) {}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/debug_debug.go b/Godeps/_workspace/src/github.com/zillode/notify/debug_debug.go
new file mode 100644
index 0000000..f062291
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/debug_debug.go
@@ -0,0 +1,43 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build debug
+
+package notify
+
+import (
+ "fmt"
+ "os"
+ "runtime"
+ "strings"
+)
+
+func dbgprint(v ...interface{}) {
+ fmt.Printf("[D] ")
+ fmt.Print(v...)
+ fmt.Printf("\n\n")
+}
+
+func dbgprintf(format string, v ...interface{}) {
+ fmt.Printf("[D] ")
+ fmt.Printf(format, v...)
+ fmt.Printf("\n\n")
+}
+
+func dbgcallstack(max int) []string {
+ pc, stack := make([]uintptr, max), make([]string, 0, max)
+ runtime.Callers(2, pc)
+ for _, pc := range pc {
+ if f := runtime.FuncForPC(pc); f != nil {
+ fname := f.Name()
+ idx := strings.LastIndex(fname, string(os.PathSeparator))
+ if idx != -1 {
+ stack = append(stack, fname[idx+1:])
+ } else {
+ stack = append(stack, fname)
+ }
+ }
+ }
+ return stack
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/doc.go b/Godeps/_workspace/src/github.com/zillode/notify/doc.go
new file mode 100644
index 0000000..e26014e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/doc.go
@@ -0,0 +1,39 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// Package notify implements access to filesystem events.
+//
+// Notify is a high-level abstraction over filesystem watchers like inotify,
+// kqueue, FSEvents or ReadDirectoryChangesW. Watcher implementations are
+// split into two groups: ones that natively support recursive notifications
+// (FSEvents and ReadDirectoryChangesW) and ones that do not (inotify and kqueue).
+// For more details see watcher and recursiveWatcher interfaces in watcher.go
+// source file.
+//
+// On top of filesystem watchers notify maintains a watchpoint tree, which provides
+// strategy for creating and closing filesystem watches and dispatching filesystem
+// events to user channels.
+//
+// An event set is just an event list joint using bitwise OR operator
+// into a single event value.
+//
+// A filesystem watch or just a watch is platform-specific entity which represents
+// a single path registered for notifications for specific event set. Setting a watch
+// means using platform-specific API calls for creating / initializing said watch.
+// For each watcher the API call is:
+//
+// - FSEvents: FSEventStreamCreate
+// - inotify: notify_add_watch
+// - kqueue: kevent
+// - ReadDirectoryChangesW: CreateFile+ReadDirectoryChangesW
+//
+// To rewatch means to either shrink or expand an event set that was previously
+// registered during watch operation for particular filesystem watch.
+//
+// A watchpoint is a list of user channel and event set pairs for particular
+// path (watchpoint tree's node). A single watchpoint can contain multiple
+// different user channels registered to listen for one or more events. A single
+// user channel can be registered in one or more watchpoints, recurisve and
+// non-recursive ones as well.
+package notify
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/event.go b/Godeps/_workspace/src/github.com/zillode/notify/event.go
new file mode 100644
index 0000000..e045edc
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/event.go
@@ -0,0 +1,143 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+ "fmt"
+ "strings"
+)
+
+// Event represents the type of filesystem action.
+//
+// Number of available event values is dependent on the target system or the
+// watcher implmenetation used (e.g. it's possible to use either kqueue or
+// FSEvents on Darwin).
+//
+// Please consult documentation for your target platform to see list of all
+// available events.
+type Event uint32
+
+// Create, Remove, Write and Rename are the only event values guaranteed to be
+// present on all platforms.
+const (
+ Create = osSpecificCreate
+ Remove = osSpecificRemove
+ Write = osSpecificWrite
+ Rename = osSpecificRename
+
+ // All is handful alias for all platform-independent event values.
+ All = Create | Remove | Write | Rename
+)
+
+const internal = recursive | omit
+
+// String implements fmt.Stringer interface.
+func (e Event) String() string {
+ var s []string
+ for _, strmap := range []map[Event]string{estr, osestr} {
+ for ev, str := range strmap {
+ if e&ev == ev {
+ s = append(s, str)
+ }
+ }
+ }
+ return strings.Join(s, "|")
+}
+
+// EventInfo describes an event reported by the underlying filesystem notification
+// subsystem.
+//
+// It always describes single event, even if the OS reported a coalesced action.
+// Reported path is absolute and clean.
+//
+// For non-recursive watchpoints its base is always equal to the path passed
+// to corresponding Watch call.
+//
+// The value of Sys if system-dependent and can be nil.
+//
+// Sys
+//
+// Under Darwin (FSEvents) Sys() always returns a non-nil *notify.FSEvent value,
+// which is defined as:
+//
+// type FSEvent struct {
+// Path string // real path of the file or directory
+// ID uint64 // ID of the event (FSEventStreamEventId)
+// Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
+// }
+//
+// For possible values of Flags see Darwin godoc for notify or FSEvents
+// documentation for FSEventStreamEventFlags constants:
+//
+// https://developer.apple.com/library/mac/documentation/Darwin/Reference/FSEvents_Ref/index.html#//apple_ref/doc/constant_group/FSEventStreamEventFlags
+//
+// Under Linux (inotify) Sys() always returns a non-nil *syscall.InotifyEvent
+// value, defined as:
+//
+// type InotifyEvent struct {
+// Wd int32 // Watch descriptor
+// Mask uint32 // Mask describing event
+// Cookie uint32 // Unique cookie associating related events (for rename(2))
+// Len uint32 // Size of name field
+// Name [0]uint8 // Optional null-terminated name
+// }
+//
+// More information about inotify masks and the usage of inotify_event structure
+// can be found at:
+//
+// http://man7.org/linux/man-pages/man7/inotify.7.html
+//
+// Under Darwin, DragonFlyBSD, FreeBSD, NetBSD, OpenBSD (kqueue) Sys() always
+// returns a non-nil *notify.Kevent value, which is defined as:
+//
+// type Kevent struct {
+// Kevent *syscall.Kevent_t // Kevent is a kqueue specific structure
+// FI os.FileInfo // FI describes file/dir
+// }
+//
+// More information about syscall.Kevent_t can be found at:
+//
+// https://www.freebsd.org/cgi/man.cgi?query=kqueue
+//
+// Under Windows (ReadDirectoryChangesW) Sys() always returns nil. The documentation
+// of watcher's WinAPI function can be found at:
+//
+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465%28v=vs.85%29.aspx
+type EventInfo interface {
+ Event() Event // event value for the filesystem action
+ Path() string // real path of the file or directory
+ Sys() interface{} // underlying data source (can return nil)
+}
+
+type isDirer interface {
+ isDir() (bool, error)
+}
+
+var _ fmt.Stringer = (*event)(nil)
+var _ isDirer = (*event)(nil)
+
+// String implements fmt.Stringer interface.
+func (e *event) String() string {
+ return e.Event().String() + `: "` + e.Path() + `"`
+}
+
+var estr = map[Event]string{
+ Create: "notify.Create",
+ Remove: "notify.Remove",
+ Write: "notify.Write",
+ Rename: "notify.Rename",
+ // Display name for recursive event is added only for debugging
+ // purposes. It's an internal event after all and won't be exposed to the
+ // user. Having Recursive event printable is helpful, e.g. for reading
+ // testing failure messages:
+ //
+ // --- FAIL: TestWatchpoint (0.00 seconds)
+ // watchpoint_test.go:64: want diff=[notify.Remove notify.Create|notify.Remove];
+ // got [notify.Remove notify.Remove|notify.Create] (i=1)
+ //
+ // Yup, here the diff have Recursive event inside. Go figure.
+ recursive: "recursive",
+ omit: "omit",
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/event_fsevents.go b/Godeps/_workspace/src/github.com/zillode/notify/event_fsevents.go
new file mode 100644
index 0000000..6ded80b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/event_fsevents.go
@@ -0,0 +1,71 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,!kqueue
+
+package notify
+
+const (
+ osSpecificCreate = Event(FSEventsCreated)
+ osSpecificRemove = Event(FSEventsRemoved)
+ osSpecificWrite = Event(FSEventsModified)
+ osSpecificRename = Event(FSEventsRenamed)
+ // internal = Event(0x100000)
+ // recursive is used to distinguish recursive eventsets from non-recursive ones
+ recursive = Event(0x200000)
+ // omit is used for dispatching internal events; only those events are sent
+ // for which both the event and the watchpoint has omit in theirs event sets.
+ omit = Event(0x400000)
+)
+
+// FSEvents specific event values.
+const (
+ FSEventsMustScanSubDirs Event = 0x00001
+ FSEventsUserDropped = 0x00002
+ FSEventsKernelDropped = 0x00004
+ FSEventsEventIdsWrapped = 0x00008
+ FSEventsHistoryDone = 0x00010
+ FSEventsRootChanged = 0x00020
+ FSEventsMount = 0x00040
+ FSEventsUnmount = 0x00080
+ FSEventsCreated = 0x00100
+ FSEventsRemoved = 0x00200
+ FSEventsInodeMetaMod = 0x00400
+ FSEventsRenamed = 0x00800
+ FSEventsModified = 0x01000
+ FSEventsFinderInfoMod = 0x02000
+ FSEventsChangeOwner = 0x04000
+ FSEventsXattrMod = 0x08000
+ FSEventsIsFile = 0x10000
+ FSEventsIsDir = 0x20000
+ FSEventsIsSymlink = 0x40000
+)
+
+var osestr = map[Event]string{
+ FSEventsMustScanSubDirs: "notify.FSEventsMustScanSubDirs",
+ FSEventsUserDropped: "notify.FSEventsUserDropped",
+ FSEventsKernelDropped: "notify.FSEventsKernelDropped",
+ FSEventsEventIdsWrapped: "notify.FSEventsEventIdsWrapped",
+ FSEventsHistoryDone: "notify.FSEventsHistoryDone",
+ FSEventsRootChanged: "notify.FSEventsRootChanged",
+ FSEventsMount: "notify.FSEventsMount",
+ FSEventsUnmount: "notify.FSEventsUnmount",
+ FSEventsInodeMetaMod: "notify.FSEventsInodeMetaMod",
+ FSEventsFinderInfoMod: "notify.FSEventsFinderInfoMod",
+ FSEventsChangeOwner: "notify.FSEventsChangeOwner",
+ FSEventsXattrMod: "notify.FSEventsXattrMod",
+ FSEventsIsFile: "notify.FSEventsIsFile",
+ FSEventsIsDir: "notify.FSEventsIsDir",
+ FSEventsIsSymlink: "notify.FSEventsIsSymlink",
+}
+
+type event struct {
+ fse FSEvent
+ event Event
+}
+
+func (ei *event) Event() Event { return ei.event }
+func (ei *event) Path() string { return ei.fse.Path }
+func (ei *event) Sys() interface{} { return &ei.fse }
+func (ei *event) isDir() (bool, error) { return ei.fse.Flags&FSEventsIsDir != 0, nil }
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/event_inotify.go b/Godeps/_workspace/src/github.com/zillode/notify/event_inotify.go
new file mode 100644
index 0000000..82954a9
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/event_inotify.go
@@ -0,0 +1,75 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build linux
+
+package notify
+
+import "syscall"
+
+// Platform independent event values.
+const (
+ osSpecificCreate Event = 0x100000 << iota
+ osSpecificRemove
+ osSpecificWrite
+ osSpecificRename
+ // internal
+ // recursive is used to distinguish recursive eventsets from non-recursive ones
+ recursive
+ // omit is used for dispatching internal events; only those events are sent
+ // for which both the event and the watchpoint has omit in theirs event sets.
+ omit
+)
+
+// Inotify specific masks are legal, implemented events that are guaranteed to
+// work with notify package on linux-based systems.
+const (
+ InAccess = Event(syscall.IN_ACCESS) // File was accessed
+ InModify = Event(syscall.IN_MODIFY) // File was modified
+ InAttrib = Event(syscall.IN_ATTRIB) // Metadata changed
+ InCloseWrite = Event(syscall.IN_CLOSE_WRITE) // Writtable file was closed
+ InCloseNowrite = Event(syscall.IN_CLOSE_NOWRITE) // Unwrittable file closed
+ InOpen = Event(syscall.IN_OPEN) // File was opened
+ InMovedFrom = Event(syscall.IN_MOVED_FROM) // File was moved from X
+ InMovedTo = Event(syscall.IN_MOVED_TO) // File was moved to Y
+ InCreate = Event(syscall.IN_CREATE) // Subfile was created
+ InDelete = Event(syscall.IN_DELETE) // Subfile was deleted
+ InDeleteSelf = Event(syscall.IN_DELETE_SELF) // Self was deleted
+ InMoveSelf = Event(syscall.IN_MOVE_SELF) // Self was moved
+)
+
+var osestr = map[Event]string{
+ InAccess: "notify.InAccess",
+ InModify: "notify.InModify",
+ InAttrib: "notify.InAttrib",
+ InCloseWrite: "notify.InCloseWrite",
+ InCloseNowrite: "notify.InCloseNowrite",
+ InOpen: "notify.InOpen",
+ InMovedFrom: "notify.InMovedFrom",
+ InMovedTo: "notify.InMovedTo",
+ InCreate: "notify.InCreate",
+ InDelete: "notify.InDelete",
+ InDeleteSelf: "notify.InDeleteSelf",
+ InMoveSelf: "notify.InMoveSelf",
+}
+
+// Inotify behavior events are not **currently** supported by notify package.
+const (
+ inDontFollow = Event(syscall.IN_DONT_FOLLOW)
+ inExclUnlink = Event(syscall.IN_EXCL_UNLINK)
+ inMaskAdd = Event(syscall.IN_MASK_ADD)
+ inOneshot = Event(syscall.IN_ONESHOT)
+ inOnlydir = Event(syscall.IN_ONLYDIR)
+)
+
+type event struct {
+ sys syscall.InotifyEvent
+ path string
+ event Event
+}
+
+func (e *event) Event() Event { return e.event }
+func (e *event) Path() string { return e.path }
+func (e *event) Sys() interface{} { return &e.sys }
+func (e *event) isDir() (bool, error) { return e.sys.Mask&syscall.IN_ISDIR != 0, nil }
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/event_kqueue.go b/Godeps/_workspace/src/github.com/zillode/notify/event_kqueue.go
new file mode 100644
index 0000000..b5fee08
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/event_kqueue.go
@@ -0,0 +1,89 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,kqueue dragonfly freebsd netbsd openbsd
+
+package notify
+
+import (
+ "os"
+ "syscall"
+)
+
+// TODO(pblaszczyk): ensure in runtime notify built-in event values do not
+// overlap with platform-defined ones.
+
+// Platform independent event values.
+const (
+ osSpecificCreate Event = 0x0100 << iota
+ osSpecificRemove
+ osSpecificWrite
+ osSpecificRename
+ // internal
+ // recursive is used to distinguish recursive eventsets from non-recursive ones
+ recursive
+ // omit is used for dispatching internal events; only those events are sent
+ // for which both the event and the watchpoint has omit in theirs event sets.
+ omit
+)
+
+const (
+ // NoteDelete is an even reported when the unlink() system call was called
+ // on the file referenced by the descriptor.
+ NoteDelete = Event(syscall.NOTE_DELETE)
+ // NoteWrite is an event reported when a write occurred on the file
+ // referenced by the descriptor.
+ NoteWrite = Event(syscall.NOTE_WRITE)
+ // NoteExtend is an event reported when the file referenced by the
+ // descriptor was extended.
+ NoteExtend = Event(syscall.NOTE_EXTEND)
+ // NoteAttrib is an event reported when the file referenced
+ // by the descriptor had its attributes changed.
+ NoteAttrib = Event(syscall.NOTE_ATTRIB)
+ // NoteLink is an event reported when the link count on the file changed.
+ NoteLink = Event(syscall.NOTE_LINK)
+ // NoteRename is an event reported when the file referenced
+ // by the descriptor was renamed.
+ NoteRename = Event(syscall.NOTE_RENAME)
+ // NoteRevoke is an event reported when access to the file was revoked via
+ // revoke(2) or the underlying file system was unmounted.
+ NoteRevoke = Event(syscall.NOTE_REVOKE)
+)
+
+var osestr = map[Event]string{
+ NoteDelete: "notify.NoteDelete",
+ NoteWrite: "notify.NoteWrite",
+ NoteExtend: "notify.NoteExtend",
+ NoteAttrib: "notify.NoteAttrib",
+ NoteLink: "notify.NoteLink",
+ NoteRename: "notify.NoteRename",
+ NoteRevoke: "notify.NoteRevoke",
+}
+
+// event is a struct storing reported event's data.
+type event struct {
+ // p is an absolute path to file for which event is reported.
+ p string
+ // e specifies type of a reported event.
+ e Event
+ // kq specifies event's additional information.
+ kq Kevent
+}
+
+// Event returns type of a reported event.
+func (e *event) Event() Event { return e.e }
+
+// Path returns path to file/directory for which event is reported.
+func (e *event) Path() string { return e.p }
+
+// Sys returns platform specific object describing reported event.
+func (e *event) Sys() interface{} { return &e.kq }
+
+func (e *event) isDir() (bool, error) { return e.kq.FI.IsDir(), nil }
+
+// Kevent represents a single event.
+type Kevent struct {
+ Kevent *syscall.Kevent_t // Kevent is a kqueue specific structure.
+ FI os.FileInfo // FI describes file/dir.
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/event_readdcw.go b/Godeps/_workspace/src/github.com/zillode/notify/event_readdcw.go
new file mode 100644
index 0000000..11ead9e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/event_readdcw.go
@@ -0,0 +1,108 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify
+
+import (
+ "os"
+ "path/filepath"
+ "syscall"
+)
+
+// Platform independent event values.
+const (
+ osSpecificCreate Event = 1 << (20 + iota)
+ osSpecificRemove
+ osSpecificWrite
+ osSpecificRename
+ // recursive is used to distinguish recursive eventsets from non-recursive ones
+ recursive
+ // omit is used for dispatching internal events; only those events are sent
+ // for which both the event and the watchpoint has omit in theirs event sets.
+ omit
+ // dirmarker TODO(pknap)
+ dirmarker
+)
+
+// ReadDirectoryChangesW filters.
+const (
+ FileNotifyChangeFileName = Event(syscall.FILE_NOTIFY_CHANGE_FILE_NAME)
+ FileNotifyChangeDirName = Event(syscall.FILE_NOTIFY_CHANGE_DIR_NAME)
+ FileNotifyChangeAttributes = Event(syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES)
+ FileNotifyChangeSize = Event(syscall.FILE_NOTIFY_CHANGE_SIZE)
+ FileNotifyChangeLastWrite = Event(syscall.FILE_NOTIFY_CHANGE_LAST_WRITE)
+ FileNotifyChangeLastAccess = Event(syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS)
+ FileNotifyChangeCreation = Event(syscall.FILE_NOTIFY_CHANGE_CREATION)
+ FileNotifyChangeSecurity = Event(syscallFileNotifyChangeSecurity)
+)
+
+const (
+ fileNotifyChangeAll = 0x17f // logical sum of all FileNotifyChange* events.
+ fileNotifyChangeModified = fileNotifyChangeAll &^ (FileNotifyChangeFileName | FileNotifyChangeDirName)
+)
+
+// according to: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx
+// this flag should be declared in: http://golang.org/src/pkg/syscall/ztypes_windows.go
+const syscallFileNotifyChangeSecurity = 0x00000100
+
+// ReadDirectoryChangesW actions.
+const (
+ FileActionAdded = Event(syscall.FILE_ACTION_ADDED) << 12
+ FileActionRemoved = Event(syscall.FILE_ACTION_REMOVED) << 12
+ FileActionModified = Event(syscall.FILE_ACTION_MODIFIED) << 14
+ FileActionRenamedOldName = Event(syscall.FILE_ACTION_RENAMED_OLD_NAME) << 15
+ FileActionRenamedNewName = Event(syscall.FILE_ACTION_RENAMED_NEW_NAME) << 16
+)
+
+const fileActionAll = 0x7f000 // logical sum of all FileAction* events.
+
+var osestr = map[Event]string{
+ FileNotifyChangeFileName: "notify.FileNotifyChangeFileName",
+ FileNotifyChangeDirName: "notify.FileNotifyChangeDirName",
+ FileNotifyChangeAttributes: "notify.FileNotifyChangeAttributes",
+ FileNotifyChangeSize: "notify.FileNotifyChangeSize",
+ FileNotifyChangeLastWrite: "notify.FileNotifyChangeLastWrite",
+ FileNotifyChangeLastAccess: "notify.FileNotifyChangeLastAccess",
+ FileNotifyChangeCreation: "notify.FileNotifyChangeCreation",
+ FileNotifyChangeSecurity: "notify.FileNotifyChangeSecurity",
+
+ FileActionAdded: "notify.FileActionAdded",
+ FileActionRemoved: "notify.FileActionRemoved",
+ FileActionModified: "notify.FileActionModified",
+ FileActionRenamedOldName: "notify.FileActionRenamedOldName",
+ FileActionRenamedNewName: "notify.FileActionRenamedNewName",
+}
+
+const (
+ fTypeUnknown uint8 = iota
+ fTypeFile
+ fTypeDirectory
+)
+
+// TODO(ppknap) : doc.
+type event struct {
+ pathw []uint16
+ name string
+ ftype uint8
+ action uint32
+ filter uint32
+ e Event
+}
+
+func (e *event) Event() Event { return e.e }
+func (e *event) Path() string { return filepath.Join(syscall.UTF16ToString(e.pathw), e.name) }
+func (e *event) Sys() interface{} { return e.ftype }
+
+func (e *event) isDir() (bool, error) {
+ if e.ftype != fTypeUnknown {
+ return e.ftype == fTypeDirectory, nil
+ }
+ fi, err := os.Stat(e.Path())
+ if err != nil {
+ return false, err
+ }
+ return fi.IsDir(), nil
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/event_stub.go b/Godeps/_workspace/src/github.com/zillode/notify/event_stub.go
new file mode 100644
index 0000000..919bce2
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/event_stub.go
@@ -0,0 +1,31 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build !darwin,!linux,!freebsd,!dragonfly,!netbsd,!openbsd,!windows
+// +build !kqueue
+
+package notify
+
+// Platform independent event values.
+const (
+ osSpecificCreate Event = 1 << iota
+ osSpecificRemove
+ osSpecificWrite
+ osSpecificRename
+ // internal
+ // recursive is used to distinguish recursive eventsets from non-recursive ones
+ recursive
+ // omit is used for dispatching internal events; only those events are sent
+ // for which both the event and the watchpoint has omit in theirs event sets.
+ omit
+)
+
+var osestr = map[Event]string{}
+
+type event struct{}
+
+func (e *event) Event() (_ Event) { return }
+func (e *event) Path() (_ string) { return }
+func (e *event) Sys() (_ interface{}) { return }
+func (e *event) isDir() (_ bool, _ error) { return }
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/event_test.go b/Godeps/_workspace/src/github.com/zillode/notify/event_test.go
new file mode 100644
index 0000000..2803509
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/event_test.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+ "sort"
+ "strings"
+ "testing"
+)
+
+// S is a workaround for random event strings concatenation order.
+func s(s string) string {
+ z := strings.Split(s, "|")
+ sort.StringSlice(z).Sort()
+ return strings.Join(z, "|")
+}
+
+// This test is not safe to run in parallel with others.
+func TestEventString(t *testing.T) {
+ cases := map[Event]string{
+ Create: "notify.Create",
+ Create | Remove: "notify.Create|notify.Remove",
+ Create | Remove | Write: "notify.Create|notify.Remove|notify.Write",
+ Create | Write | Rename: "notify.Create|notify.Rename|notify.Write",
+ }
+ for e, str := range cases {
+ if s := s(e.String()); s != str {
+ t.Errorf("want s=%s; got %s (e=%#x)", str, s, e)
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/example_fsevents_test.go b/Godeps/_workspace/src/github.com/zillode/notify/example_fsevents_test.go
new file mode 100644
index 0000000..53d1b34
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/example_fsevents_test.go
@@ -0,0 +1,128 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,!kqueue
+
+package notify_test
+
+import (
+ "log"
+
+ "github.com/rjeczalik/notify"
+)
+
+// This example shows how to use FSEvents-specifc event values.
+func ExampleWatch_darwin() {
+ // Make the channel buffered to ensure no event is dropped. Notify will drop
+ // an event if the receiver is not able to keep up the sending pace.
+ c := make(chan notify.EventInfo, 1)
+
+ // Set up a watchpoint listening for FSEvents-specific events within a
+ // current working directory. Dispatch each FSEventsChangeOwner and FSEventsMount
+ // events separately to c.
+ if err := notify.Watch(".", c, notify.FSEventsChangeOwner, notify.FSEventsMount); err != nil {
+ log.Fatal(err)
+ }
+ defer notify.Stop(c)
+
+ // Block until an event is received.
+ switch ei := <-c; ei.Event() {
+ case notify.FSEventsChangeOwner:
+ log.Println("The owner of", ei.Path(), "has changed.")
+ case notify.FSEventsMount:
+ log.Println("The path", ei.Path(), "has been mounted.")
+ }
+}
+
+// This example shows how to work with EventInfo's underlying FSEvent struct.
+// Investigating notify.(*FSEvent).Flags field we are able to say whether
+// the event's path is a file or a directory and many more.
+func ExampleWatch_darwinDirFileSymlink() {
+ var must = func(err error) {
+ if err != nil {
+ log.Fatal(err)
+ }
+ }
+ var stop = func(c ...chan<- notify.EventInfo) {
+ for _, c := range c {
+ notify.Stop(c)
+ }
+ }
+
+ // Make the channels buffered to ensure no event is dropped. Notify will drop
+ // an event if the receiver is not able to keep up the sending pace.
+ dir := make(chan notify.EventInfo, 1)
+ file := make(chan notify.EventInfo, 1)
+ symlink := make(chan notify.EventInfo, 1)
+ all := make(chan notify.EventInfo, 1)
+
+ // Set up a single watchpoint listening for FSEvents-specific events on
+ // multiple user-provided channels.
+ must(notify.Watch(".", dir, notify.FSEventsIsDir))
+ must(notify.Watch(".", file, notify.FSEventsIsFile))
+ must(notify.Watch(".", symlink, notify.FSEventsIsSymlink))
+ must(notify.Watch(".", all, notify.All))
+ defer stop(dir, file, symlink, all)
+
+ // Block until an event is received.
+ select {
+ case ei := <-dir:
+ log.Println("The directory", ei.Path(), "has changed")
+ case ei := <-file:
+ log.Println("The file", ei.Path(), "has changed")
+ case ei := <-symlink:
+ log.Println("The symlink", ei.Path(), "has changed")
+ case ei := <-all:
+ var kind string
+
+ // Investigate underlying *notify.FSEvent struct to access more
+ // information about the event.
+ switch flags := ei.Sys().(*notify.FSEvent).Flags; {
+ case flags&notify.FSEventsIsFile != 0:
+ kind = "file"
+ case flags&notify.FSEventsIsDir != 0:
+ kind = "dir"
+ case flags&notify.FSEventsIsSymlink != 0:
+ kind = "symlink"
+ }
+
+ log.Printf("The %s under path %s has been %sd\n", kind, ei.Path(), ei.Event())
+ }
+}
+
+// FSEvents may report multiple filesystem actions with one, coalesced event.
+// Notify unscoalesces such event and dispatches series of single events
+// back to the user.
+//
+// This example shows how to coalesce events by investigating notify.(*FSEvent).ID
+// field, for the science.
+func ExampleWatch_darwinCoalesce() {
+ // Make the channels buffered to ensure no event is dropped. Notify will drop
+ // an event if the receiver is not able to keep up the sending pace.
+ c := make(chan notify.EventInfo, 4)
+
+ // Set up a watchpoint listetning for events within current working directory.
+ // Dispatch all platform-independent separately to c.
+ if err := notify.Watch(".", c, notify.All); err != nil {
+ log.Fatal(err)
+ }
+ defer notify.Stop(c)
+
+ var id uint64
+ var coalesced []notify.EventInfo
+
+ for ei := range c {
+ switch n := ei.Sys().(*notify.FSEvent).ID; {
+ case id == 0:
+ id = n
+ coalesced = []notify.EventInfo{ei}
+ case id == n:
+ coalesced = append(coalesced, ei)
+ default:
+ log.Printf("FSEvents reported a filesystem action with the following"+
+ " coalesced events %v groupped by %d ID\n", coalesced, id)
+ return
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/example_inotify_test.go b/Godeps/_workspace/src/github.com/zillode/notify/example_inotify_test.go
new file mode 100644
index 0000000..e6a68e7
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/example_inotify_test.go
@@ -0,0 +1,83 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build linux
+
+package notify_test
+
+import (
+ "log"
+ "syscall"
+
+ "github.com/rjeczalik/notify"
+)
+
+// This example shows how to watch changes made on file-system by text editor
+// when saving a file. Usually, either InCloseWrite or InMovedTo (when swapping
+// with a temporary file) event is created.
+func ExampleWatch_linux() {
+ // Make the channel buffered to ensure no event is dropped. Notify will drop
+ // an event if the receiver is not able to keep up the sending pace.
+ c := make(chan notify.EventInfo, 1)
+
+ // Set up a watchpoint listening for inotify-specific events within a
+ // current working directory. Dispatch each InCloseWrite and InMovedTo
+ // events separately to c.
+ if err := notify.Watch(".", c, notify.InCloseWrite, notify.InMovedTo); err != nil {
+ log.Fatal(err)
+ }
+ defer notify.Stop(c)
+
+ // Block until an event is received.
+ switch ei := <-c; ei.Event() {
+ case notify.InCloseWrite:
+ log.Println("Editing of", ei.Path(), "file is done.")
+ case notify.InMovedTo:
+ log.Println("File", ei.Path(), "was swapped/moved into the watched directory.")
+ }
+}
+
+// This example shows how to use Sys() method from EventInfo interface to tie
+// two separate events generated by rename(2) function.
+func ExampleWatch_linuxMove() {
+ // Make the channel buffered to ensure no event is dropped. Notify will drop
+ // an event if the receiver is not able to keep up the sending pace.
+ c := make(chan notify.EventInfo, 2)
+
+ // Set up a watchpoint listening for inotify-specific events within a
+ // current working directory. Dispatch each InMovedFrom and InMovedTo
+ // events separately to c.
+ if err := notify.Watch(".", c, notify.InMovedFrom, notify.InMovedTo); err != nil {
+ log.Fatal(err)
+ }
+ defer notify.Stop(c)
+
+ // Inotify reports move filesystem action by sending two events tied with
+ // unique cookie value (uint32): one of the events is of InMovedFrom type
+ // carrying move source path, while the second one is of InMoveTo type
+ // carrying move destination path.
+ moves := make(map[uint32]struct {
+ From string
+ To string
+ })
+
+ // Wait for moves.
+ for ei := range c {
+ cookie := ei.Sys().(*syscall.InotifyEvent).Cookie
+
+ info := moves[cookie]
+ switch ei.Event() {
+ case notify.InMovedFrom:
+ info.From = ei.Path()
+ case notify.InMovedTo:
+ info.To = ei.Path()
+ }
+ moves[cookie] = info
+
+ if cookie != 0 && info.From != "" && info.To != "" {
+ log.Println("File:", info.From, "was renamed to", info.To)
+ delete(moves, cookie)
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/example_readdcw_test.go b/Godeps/_workspace/src/github.com/zillode/notify/example_readdcw_test.go
new file mode 100644
index 0000000..e9e05be
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/example_readdcw_test.go
@@ -0,0 +1,40 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify_test
+
+import (
+ "log"
+
+ "github.com/rjeczalik/notify"
+)
+
+// This example shows how to watch directory-name changes in the working directory subtree.
+func ExampleWatch_windows() {
+ // Make the channel buffered to ensure no event is dropped. Notify will drop
+ // an event if the receiver is not able to keep up the sending pace.
+ c := make(chan notify.EventInfo, 4)
+
+ // Since notify package behaves exactly like ReadDirectoryChangesW function,
+ // we must register notify.FileNotifyChangeDirName filter and wait for one
+ // of FileAction* events.
+ if err := notify.Watch("./...", c, notify.FileNotifyChangeDirName); err != nil {
+ log.Fatal(err)
+ }
+ defer notify.Stop(c)
+
+ // Wait for actions.
+ for ei := range c {
+ switch ei.Event() {
+ case notify.FileActionAdded, notify.FileActionRenamedNewName:
+ log.Println("Created:", ei.Path())
+ case notify.FileActionRemoved, notify.FileActionRenamedOldName:
+ log.Println("Removed:", ei.Path())
+ case notify.FileActionModified:
+ panic("notify: unexpected action")
+ }
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/example_test.go b/Godeps/_workspace/src/github.com/zillode/notify/example_test.go
new file mode 100644
index 0000000..3c8df71
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/example_test.go
@@ -0,0 +1,87 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify_test
+
+import (
+ "log"
+ "path/filepath"
+ "time"
+
+ "github.com/rjeczalik/notify"
+)
+
+// This is a basic example showing how to work with notify.Watch function.
+func ExampleWatch() {
+ // Make the channel buffered to ensure no event is dropped. Notify will drop
+ // an event if the receiver is not able to keep up the sending pace.
+ c := make(chan notify.EventInfo, 1)
+
+ // Set up a watchpoint listening on events within current working directory.
+ // Dispatch each create and remove events separately to c.
+ if err := notify.Watch(".", c, notify.Create, notify.Remove); err != nil {
+ log.Fatal(err)
+ }
+ defer notify.Stop(c)
+
+ // Block until an event is received.
+ ei := <-c
+ log.Println("Got event:", ei)
+}
+
+// This example shows how to set up a recursive watchpoint.
+func ExampleWatch_recursive() {
+ // Make the channel buffered to ensure no event is dropped. Notify will drop
+ // an event if the receiver is not able to keep up the sending pace.
+ c := make(chan notify.EventInfo, 1)
+
+ // Set up a watchpoint listening for events within a directory tree rooted
+ // at current working directory. Dispatch remove events to c.
+ if err := notify.Watch("./...", c, notify.Remove); err != nil {
+ log.Fatal(err)
+ }
+ defer notify.Stop(c)
+
+ // Block until an event is received.
+ ei := <-c
+ log.Println("Got event:", ei)
+}
+
+// This example shows why it is important to not create leaks by stoping
+// a channel when it's no longer being used.
+func ExampleStop() {
+ waitfor := func(path string, e notify.Event, timeout time.Duration) bool {
+ dir, file := filepath.Split(path)
+ c := make(chan notify.EventInfo, 1)
+
+ if err := notify.Watch(dir, c, e); err != nil {
+ log.Fatal(err)
+ }
+ // Clean up watchpoint associated with c. If Stop was not called upon
+ // return the channel would be leaked as notify holds the only reference
+ // to it and does not release it on its own.
+ defer notify.Stop(c)
+
+ t := time.After(timeout)
+
+ for {
+ select {
+ case ei := <-c:
+ if filepath.Base(ei.Path()) == file {
+ return true
+ }
+ case <-t:
+ return false
+ }
+ }
+ }
+
+ if waitfor("index.lock", notify.Create, 5*time.Second) {
+ log.Println("The git repository was locked")
+ }
+
+ if waitfor("index.lock", notify.Remove, 5*time.Second) {
+ log.Println("The git repository was unlocked")
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/node.go b/Godeps/_workspace/src/github.com/zillode/notify/node.go
new file mode 100644
index 0000000..4302071
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/node.go
@@ -0,0 +1,271 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+ "errors"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+)
+
+var errSkip = errors.New("notify: skip")
+
+type walkPathFunc func(nd node, isbase bool) error
+
+type walkFunc func(node) error
+
+func errnotexist(name string) error {
+ return &os.PathError{
+ Op: "Node",
+ Path: name,
+ Err: os.ErrNotExist,
+ }
+}
+
+type node struct {
+ Name string
+ Watch watchpoint
+ Child map[string]node
+}
+
+func newnode(name string) node {
+ return node{
+ Name: name,
+ Watch: make(watchpoint),
+ Child: make(map[string]node),
+ }
+}
+
+func (nd node) addchild(name, base string) node {
+ child, ok := nd.Child[base]
+ if !ok {
+ child = newnode(name)
+ nd.Child[base] = child
+ }
+ return child
+}
+
+func (nd node) Add(name string) node {
+ i := indexbase(nd.Name, name)
+ if i == -1 {
+ return node{}
+ }
+ for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
+ nd = nd.addchild(name[:i+j], name[i:i+j])
+ i += j + 1
+ }
+ return nd.addchild(name, name[i:])
+}
+
+func (nd node) AddDir(fn walkFunc) error {
+ stack := []node{nd}
+Traverse:
+ for n := len(stack); n != 0; n = len(stack) {
+ nd, stack = stack[n-1], stack[:n-1]
+ switch err := fn(nd); err {
+ case nil:
+ case errSkip:
+ continue Traverse
+ default:
+ return err
+ }
+ // TODO(rjeczalik): tolerate open failures - add failed names to
+ // AddDirError and notify users which names are not added to the tree.
+ fi, err := ioutil.ReadDir(nd.Name)
+ if err != nil {
+ return err
+ }
+ for _, fi := range fi {
+ if fi.Mode()&(os.ModeSymlink|os.ModeDir) == os.ModeDir {
+ name := filepath.Join(nd.Name, fi.Name())
+ stack = append(stack, nd.addchild(name, name[len(nd.Name)+1:]))
+ }
+ }
+ }
+ return nil
+}
+
+func (nd node) Get(name string) (node, error) {
+ i := indexbase(nd.Name, name)
+ if i == -1 {
+ return node{}, errnotexist(name)
+ }
+ ok := false
+ for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
+ if nd, ok = nd.Child[name[i:i+j]]; !ok {
+ return node{}, errnotexist(name)
+ }
+ i += j + 1
+ }
+ if nd, ok = nd.Child[name[i:]]; !ok {
+ return node{}, errnotexist(name)
+ }
+ return nd, nil
+}
+
+func (nd node) Del(name string) error {
+ i := indexbase(nd.Name, name)
+ if i == -1 {
+ return errnotexist(name)
+ }
+ stack := []node{nd}
+ ok := false
+ for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
+ if nd, ok = nd.Child[name[i:i+j]]; !ok {
+ return errnotexist(name[:i+j])
+ }
+ stack = append(stack, nd)
+ }
+ if nd, ok = nd.Child[name[i:]]; !ok {
+ return errnotexist(name)
+ }
+ nd.Child = nil
+ nd.Watch = nil
+ for name, i = base(nd.Name), len(stack); i != 0; name, i = base(nd.Name), i-1 {
+ nd = stack[i-1]
+ if nd := nd.Child[name]; len(nd.Watch) > 1 || len(nd.Child) != 0 {
+ break
+ } else {
+ nd.Child = nil
+ nd.Watch = nil
+ }
+ delete(nd.Child, name)
+ }
+ return nil
+}
+
+func (nd node) Walk(fn walkFunc) error {
+ stack := []node{nd}
+Traverse:
+ for n := len(stack); n != 0; n = len(stack) {
+ nd, stack = stack[n-1], stack[:n-1]
+ switch err := fn(nd); err {
+ case nil:
+ case errSkip:
+ continue Traverse
+ default:
+ return err
+ }
+ for name, nd := range nd.Child {
+ if name == "" {
+ // Node storing inactive watchpoints has empty name, skip it
+ // form traversing. Root node has also an empty name, but it
+ // never has a parent node.
+ continue
+ }
+ stack = append(stack, nd)
+ }
+ }
+ return nil
+}
+
+func (nd node) WalkPath(name string, fn walkPathFunc) error {
+ i := indexbase(nd.Name, name)
+ if i == -1 {
+ return errnotexist(name)
+ }
+ ok := false
+ for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
+ switch err := fn(nd, false); err {
+ case nil:
+ case errSkip:
+ return nil
+ default:
+ return err
+ }
+ if nd, ok = nd.Child[name[i:i+j]]; !ok {
+ return errnotexist(name[:i+j])
+ }
+ i += j + 1
+ }
+ switch err := fn(nd, false); err {
+ case nil:
+ case errSkip:
+ return nil
+ default:
+ return err
+ }
+ if nd, ok = nd.Child[name[i:]]; !ok {
+ return errnotexist(name)
+ }
+ switch err := fn(nd, true); err {
+ case nil, errSkip:
+ return nil
+ default:
+ return err
+ }
+}
+
+type root struct {
+ nd node
+}
+
+func (r root) addroot(name string) node {
+ if vol := filepath.VolumeName(name); vol != "" {
+ root, ok := r.nd.Child[vol]
+ if !ok {
+ root = r.nd.addchild(vol, vol)
+ }
+ return root
+ }
+ return r.nd
+}
+
+func (r root) root(name string) (node, error) {
+ if vol := filepath.VolumeName(name); vol != "" {
+ nd, ok := r.nd.Child[vol]
+ if !ok {
+ return node{}, errnotexist(name)
+ }
+ return nd, nil
+ }
+ return r.nd, nil
+}
+
+func (r root) Add(name string) node {
+ return r.addroot(name).Add(name)
+}
+
+func (r root) AddDir(dir string, fn walkFunc) error {
+ return r.Add(dir).AddDir(fn)
+}
+
+func (r root) Del(name string) error {
+ nd, err := r.root(name)
+ if err != nil {
+ return err
+ }
+ return nd.Del(name)
+}
+
+func (r root) Get(name string) (node, error) {
+ nd, err := r.root(name)
+ if err != nil {
+ return node{}, err
+ }
+ if nd.Name != name {
+ if nd, err = nd.Get(name); err != nil {
+ return node{}, err
+ }
+ }
+ return nd, nil
+}
+
+func (r root) Walk(name string, fn walkFunc) error {
+ nd, err := r.Get(name)
+ if err != nil {
+ return err
+ }
+ return nd.Walk(fn)
+}
+
+func (r root) WalkPath(name string, fn walkPathFunc) error {
+ nd, err := r.root(name)
+ if err != nil {
+ return err
+ }
+ return nd.WalkPath(name, fn)
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/notify.go b/Godeps/_workspace/src/github.com/zillode/notify/notify.go
new file mode 100644
index 0000000..4ffca02
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/notify.go
@@ -0,0 +1,70 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// BUG(rjeczalik): Notify does not collect watchpoints, when underlying watches
+// were removed by their os-specific watcher implementations. Instead users are
+// advised to listen on persistant paths to have guarantee they receive events
+// for the whole lifetime of their applications (to discuss see #69).
+
+// BUG(ppknap): Linux (inotify) does not support watcher behavior masks like
+// InOneshot, InOnlydir etc. Instead users are advised to perform the filtering
+// themselves (to discuss see #71).
+
+// BUG(ppknap): Notify was not tested for short path name support under Windows
+// (ReadDirectoryChangesW).
+
+// BUG(ppknap): Windows (ReadDirectoryChangesW) cannot recognize which notification
+// triggers FileActionModified event. (to discuss see #75).
+
+package notify
+
+var defaultTree = newTree()
+
+// Watch sets up a watchpoint on path listening for events given by the events
+// argument.
+//
+// File or directory given by the path must exist, otherwise Watch will fail
+// with non-nil error. Notify resolves, for its internal purpose, any symlinks
+// the provided path may contain, so it may fail if the symlinks form a cycle.
+// It does so, since not all watcher implementations treat passed paths as-is.
+// E.g. FSEvents reports a real path for every event, setting a watchpoint
+// on /tmp will report events with paths rooted at /private/tmp etc.
+//
+// It is allowed to pass the same channel multiple times with different event
+// list or different paths. Calling Watch with different event lists for a single
+// watchpoint expands its event set. The only way to shrink it, is to call
+// Stop on its channel.
+//
+// Calling Watch with empty event list does expand nor shrink watchpoint's event
+// set. If c is the first channel to listen for events on the given path, Watch
+// will seamlessly create a watch on the filesystem.
+//
+// Notify dispatches copies of single filesystem event to all channels registered
+// for each path. If a single filesystem event contains multiple coalesced events,
+// each of them is dispatched separately. E.g. the following filesystem change:
+//
+// ~ $ echo Hello > Notify.txt
+//
+// dispatches two events - notify.Create and notify.Write. However, it may depend
+// on the underlying watcher implementation whether OS reports both of them.
+//
+// Windows and recursive watches
+//
+// If a directory which path was used to create recursive watch under Windows
+// gets deleted, the OS will not report such event. It is advised to keep in
+// mind this limitation while setting recursive watchpoints for your application,
+// e.g. use persistant paths like %userprofile% or watch additionally parent
+// directory of a recursive watchpoint in order to receive delete events for it.
+func Watch(path string, c chan<- EventInfo, events ...Event) error {
+ return defaultTree.Watch(path, c, events...)
+}
+
+// Stop removes all watchpoints registered for c. All underlying watches are
+// also removed, for which c was the last channel listening for events.
+//
+// Stop does not close c. When Stop returns, it is guranteed that c will
+// receive no more signals.
+func Stop(c chan<- EventInfo) {
+ defaultTree.Stop(c)
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/notify_inotify_test.go b/Godeps/_workspace/src/github.com/zillode/notify/notify_inotify_test.go
new file mode 100644
index 0000000..c5984d3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/notify_inotify_test.go
@@ -0,0 +1,37 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build linux
+
+package notify
+
+import "testing"
+
+func TestNotifySystemAndGlobalMix(t *testing.T) {
+ n := NewNotifyTest(t, "testdata/vfs.txt")
+ defer n.Close()
+
+ ch := NewChans(2)
+
+ n.Watch("src/github.com/rjeczalik/fs", ch[0], Create)
+ n.Watch("src/github.com/rjeczalik/fs", ch[1], InCreate)
+
+ cases := []NCase{
+ {
+ Event: icreate(n.W(), "src/github.com/rjeczalik/fs/.main.cc.swr"),
+ Receiver: Chans{ch[0], ch[1]},
+ },
+ }
+
+ n.ExpectNotifyEvents(cases, ch)
+}
+
+func TestUnknownEvent(t *testing.T) {
+ n := NewNotifyTest(t, "testdata/vfs.txt")
+ defer n.Close()
+
+ ch := NewChans(1)
+
+ n.WatchErr("src/github.com/rjeczalik/fs", ch[0], nil, inExclUnlink)
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/notify_readdcw_test.go b/Godeps/_workspace/src/github.com/zillode/notify/notify_readdcw_test.go
new file mode 100644
index 0000000..944b533
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/notify_readdcw_test.go
@@ -0,0 +1,60 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify
+
+import "testing"
+
+func TestNotifySystemSpecificEvent(t *testing.T) {
+ n := NewNotifyTest(t, "testdata/vfs.txt")
+ defer n.Close()
+
+ ch := NewChans(1)
+
+ n.Watch("src/github.com/rjeczalik/fs", ch[0], FileNotifyChangeFileName, FileNotifyChangeSize)
+
+ cases := []NCase{
+ {
+ Event: rremove(n.W(), "src/github.com/rjeczalik/fs/fs.go"),
+ Receiver: Chans{ch[0]},
+ },
+ {
+ Event: rwrite(n.W(), "src/github.com/rjeczalik/fs/README.md", []byte("XD")),
+ Receiver: Chans{ch[0]},
+ },
+ }
+
+ n.ExpectNotifyEvents(cases, ch)
+}
+
+func TestUnknownEvent(t *testing.T) {
+ n := NewNotifyTest(t, "testdata/vfs.txt")
+ defer n.Close()
+
+ ch := NewChans(1)
+
+ n.WatchErr("src/github.com/rjeczalik/fs", ch[0], nil, FileActionAdded)
+}
+
+func TestNotifySystemAndGlobalMix(t *testing.T) {
+ n := NewNotifyTest(t, "testdata/vfs.txt")
+ defer n.Close()
+
+ ch := NewChans(3)
+
+ n.Watch("src/github.com/rjeczalik/fs", ch[0], Create)
+ n.Watch("src/github.com/rjeczalik/fs", ch[1], FileNotifyChangeFileName)
+ n.Watch("src/github.com/rjeczalik/fs", ch[2], FileNotifyChangeDirName)
+
+ cases := []NCase{
+ {
+ Event: rcreate(n.W(), "src/github.com/rjeczalik/fs/.main.cc.swr"),
+ Receiver: Chans{ch[0], ch[1]},
+ },
+ }
+
+ n.ExpectNotifyEvents(cases, ch)
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/notify_test.go b/Godeps/_workspace/src/github.com/zillode/notify/notify_test.go
new file mode 100644
index 0000000..c8bc9d4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/notify_test.go
@@ -0,0 +1,103 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin linux freebsd dragonfly netbsd openbsd windows
+
+package notify
+
+import "testing"
+
+func TestNotifyExample(t *testing.T) {
+ n := NewNotifyTest(t, "testdata/vfs.txt")
+ defer n.Close()
+
+ ch := NewChans(3)
+
+ // Watch-points can be set explicitely via Watch/Stop calls...
+ n.Watch("src/github.com/rjeczalik/fs", ch[0], Write)
+ n.Watch("src/github.com/pblaszczyk/qttu", ch[0], Write)
+ n.Watch("src/github.com/pblaszczyk/qttu/...", ch[1], Create)
+ n.Watch("src/github.com/rjeczalik/fs/cmd/...", ch[2], Remove)
+
+ cases := []NCase{
+ // i=0
+ {
+ Event: write(n.W(), "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
+ Receiver: Chans{ch[0]},
+ },
+ // TODO(rjeczalik): #62
+ // i=1
+ // {
+ // Event: write(n.W(), "src/github.com/pblaszczyk/qttu/README.md", []byte("XD")),
+ // Receiver: Chans{ch[0]},
+ // },
+ // i=2
+ {
+ Event: write(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/go.go", []byte("XD")),
+ Receiver: nil,
+ },
+ // i=3
+ {
+ Event: create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swp"),
+ Receiver: Chans{ch[1]},
+ },
+ // i=4
+ {
+ Event: create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swo"),
+ Receiver: Chans{ch[1]},
+ },
+ // i=5
+ {
+ Event: remove(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/go.go"),
+ Receiver: Chans{ch[2]},
+ },
+ }
+
+ n.ExpectNotifyEvents(cases, ch)
+
+ // ...or using Call structures.
+ stops := [...]Call{
+ // i=0
+ {
+ F: FuncStop,
+ C: ch[0],
+ },
+ // i=1
+ {
+ F: FuncStop,
+ C: ch[1],
+ },
+ }
+
+ n.Call(stops[:]...)
+
+ cases = []NCase{
+ // i=0
+ {
+ Event: write(n.W(), "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
+ Receiver: nil,
+ },
+ // i=1
+ {
+ Event: write(n.W(), "src/github.com/pblaszczyk/qttu/README.md", []byte("XD")),
+ Receiver: nil,
+ },
+ // i=2
+ {
+ Event: create(n.W(), "src/github.com/pblaszczyk/qttu/src/.main.cc.swr"),
+ Receiver: nil,
+ },
+ // i=3
+ {
+ Event: remove(n.W(), "src/github.com/rjeczalik/fs/cmd/gotree/main.go"),
+ Receiver: Chans{ch[2]},
+ },
+ }
+
+ n.ExpectNotifyEvents(cases, ch)
+}
+
+func TestStop(t *testing.T) {
+ t.Skip("TODO(rjeczalik)")
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/sync_readdcw_test.go b/Godeps/_workspace/src/github.com/zillode/notify/sync_readdcw_test.go
new file mode 100644
index 0000000..1a7f830
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/sync_readdcw_test.go
@@ -0,0 +1,33 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify
+
+import (
+ "syscall"
+ "time"
+ "unsafe"
+)
+
+var modkernel32 = syscall.NewLazyDLL("kernel32.dll")
+var procSetSystemFileCacheSize = modkernel32.NewProc("SetSystemFileCacheSize")
+var zero = uintptr(1<<(unsafe.Sizeof(uintptr(0))*8) - 1)
+
+func Sync() {
+ // TODO(pknap): does not work without admin privilages, but I'm going
+ // to hack it.
+ // r, _, err := procSetSystemFileCacheSize.Call(none, none, 0)
+ // if r == 0 {
+ // dbgprint("SetSystemFileCacheSize error:", err)
+ // }
+}
+
+// UpdateWait pauses the program for some minimal amount of time. This function
+// is required only by implementations which work asynchronously. It gives
+// watcher structure time to update its internal state.
+func UpdateWait() {
+ time.Sleep(50 * time.Millisecond)
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/sync_unix_test.go b/Godeps/_workspace/src/github.com/zillode/notify/sync_unix_test.go
new file mode 100644
index 0000000..9804c6c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/sync_unix_test.go
@@ -0,0 +1,18 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build !windows
+
+package notify
+
+import "syscall"
+
+func Sync() {
+ syscall.Sync()
+}
+
+// UpdateWait is required only by windows watcher implementation. On other
+// platforms this function is no-op.
+func UpdateWait() {
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/testdata/vfs.txt b/Godeps/_workspace/src/github.com/zillode/notify/testdata/vfs.txt
new file mode 100644
index 0000000..88afbc6
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/testdata/vfs.txt
@@ -0,0 +1,56 @@
+src/github.com/pblaszczyk/qttu/.travis.yml
+src/github.com/pblaszczyk/qttu/include/qttu/detail/registrator.hh
+src/github.com/pblaszczyk/qttu/include/qttu/detail/registry.hh
+src/github.com/pblaszczyk/qttu/include/qttu/runner.hh
+src/github.com/pblaszczyk/qttu/LICENSE
+src/github.com/pblaszczyk/qttu/qttu.pri
+src/github.com/pblaszczyk/qttu/qttu.pro
+src/github.com/pblaszczyk/qttu/README.md
+src/github.com/pblaszczyk/qttu/src/main.cc
+src/github.com/pblaszczyk/qttu/src/reg.cc
+src/github.com/ppknap/link/.travis.yml
+src/github.com/ppknap/link/include/coost/link/definitions.hpp
+src/github.com/ppknap/link/include/coost/link/detail/bundle.hpp
+src/github.com/ppknap/link/include/coost/link/detail/container_invoker.hpp
+src/github.com/ppknap/link/include/coost/link/detail/container_value_trait.hpp
+src/github.com/ppknap/link/include/coost/link/detail/dummy_type.hpp
+src/github.com/ppknap/link/include/coost/link/detail/function_trait.hpp
+src/github.com/ppknap/link/include/coost/link/detail/immediate_invoker.hpp
+src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/always_same.hpp
+src/github.com/ppknap/link/include/coost/link/detail/stdhelpers/make_unique.hpp
+src/github.com/ppknap/link/include/coost/link/detail/vertex.hpp
+src/github.com/ppknap/link/include/coost/link/detail/wire.hpp
+src/github.com/ppknap/link/include/coost/link/link.hpp
+src/github.com/ppknap/link/include/coost/link.hpp
+src/github.com/ppknap/link/Jamroot.jam
+src/github.com/ppknap/link/LICENSE.md
+src/github.com/ppknap/link/README.md
+src/github.com/ppknap/link/test/counter_helper.hpp
+src/github.com/ppknap/link/test/Jamfile.jam
+src/github.com/ppknap/link/test/test_circular_calls.cpp
+src/github.com/ppknap/link/test/test_container.cpp
+src/github.com/ppknap/link/test/test_copy.cpp
+src/github.com/ppknap/link/test/test_destructor.cpp
+src/github.com/ppknap/link/test/test_immediate.cpp
+src/github.com/ppknap/link/test/test_initialize.cpp
+src/github.com/rjeczalik/fs/.travis.yml
+src/github.com/rjeczalik/fs/appveyor.yml
+src/github.com/rjeczalik/fs/cmd/gotree/go.go
+src/github.com/rjeczalik/fs/cmd/gotree/main.go
+src/github.com/rjeczalik/fs/cmd/mktree/main.go
+src/github.com/rjeczalik/fs/fs.go
+src/github.com/rjeczalik/fs/fsutil/fixture_test.go
+src/github.com/rjeczalik/fs/fsutil/fsutil.go
+src/github.com/rjeczalik/fs/fsutil/fsutil_test.go
+src/github.com/rjeczalik/fs/fsutil/rel.go
+src/github.com/rjeczalik/fs/fsutil/rel_test.go
+src/github.com/rjeczalik/fs/fsutil/tee.go
+src/github.com/rjeczalik/fs/fsutil/tee_test.go
+src/github.com/rjeczalik/fs/LICENSE
+src/github.com/rjeczalik/fs/memfs/memfs.go
+src/github.com/rjeczalik/fs/memfs/memfs_test.go
+src/github.com/rjeczalik/fs/memfs/tree.go
+src/github.com/rjeczalik/fs/memfs/tree_test.go
+src/github.com/rjeczalik/fs/memfs/util.go
+src/github.com/rjeczalik/fs/memfs/util_test.go
+src/github.com/rjeczalik/fs/README.md
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/testing_test.go b/Godeps/_workspace/src/github.com/zillode/notify/testing_test.go
new file mode 100644
index 0000000..9ef41e5
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/testing_test.go
@@ -0,0 +1,895 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+ "bufio"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "sort"
+ "strconv"
+ "strings"
+ "testing"
+ "time"
+)
+
+// NOTE(rjeczalik): some useful environment variables:
+//
+// - NOTIFY_DEBUG gives some extra information about generated events
+// - NOTIFY_TIMEOUT allows for changing default wait time for watcher's
+// events
+// - NOTIFY_TMP allows for changing location of temporary directory trees
+// created for test purpose
+
+var wd = func() string {
+ s, err := os.Getwd()
+ if err != nil {
+ panic(err)
+ }
+ return s
+}()
+
+func timeout() time.Duration {
+ if s := os.Getenv("NOTIFY_TIMEOUT"); s != "" {
+ if t, err := time.ParseDuration(s); err == nil {
+ return t
+ }
+ }
+ return 2 * time.Second
+}
+
+func vfs() (string, string) {
+ if s := os.Getenv("NOTIFY_TMP"); s != "" {
+ return filepath.Split(s)
+ }
+ return "testdata", ""
+}
+
+func isDir(path string) bool {
+ r := path[len(path)-1]
+ return r == '\\' || r == '/'
+}
+
+func tmpcreateall(tmp string, path string) error {
+ isdir := isDir(path)
+ path = filepath.Join(tmp, filepath.FromSlash(path))
+ if isdir {
+ if err := os.MkdirAll(path, 0755); err != nil {
+ return err
+ }
+ } else {
+ if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
+ return err
+ }
+ f, err := os.Create(path)
+ if err != nil {
+ return err
+ }
+ if err := nonil(f.Sync(), f.Close()); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func tmpcreate(root, path string) (bool, error) {
+ isdir := isDir(path)
+ path = filepath.Join(root, filepath.FromSlash(path))
+ if isdir {
+ if err := os.Mkdir(path, 0755); err != nil {
+ return false, err
+ }
+ } else {
+ f, err := os.Create(path)
+ if err != nil {
+ return false, err
+ }
+ if err := nonil(f.Sync(), f.Close()); err != nil {
+ return false, err
+ }
+ }
+ return isdir, nil
+}
+
+func tmptree(root, list string) (string, error) {
+ f, err := os.Open(list)
+ if err != nil {
+ return "", err
+ }
+ defer f.Close()
+ if root == "" {
+ if root, err = ioutil.TempDir(vfs()); err != nil {
+ return "", err
+ }
+ }
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ if err := tmpcreateall(root, scanner.Text()); err != nil {
+ return "", err
+ }
+ }
+ if err := scanner.Err(); err != nil {
+ return "", err
+ }
+ return root, nil
+}
+
+func callern(n int) string {
+ _, file, line, ok := runtime.Caller(n)
+ if !ok {
+ return "<unknown>"
+ }
+ return filepath.Base(file) + ":" + strconv.Itoa(line)
+}
+
+func caller() string {
+ return callern(3)
+}
+
+type WCase struct {
+ Action func()
+ Events []EventInfo
+}
+
+func (cas WCase) String() string {
+ s := make([]string, 0, len(cas.Events))
+ for _, ei := range cas.Events {
+ s = append(s, "Event("+ei.Event().String()+")@"+filepath.FromSlash(ei.Path()))
+ }
+ return strings.Join(s, ", ")
+}
+
+type W struct {
+ Watcher watcher
+ C chan EventInfo
+ Timeout time.Duration
+
+ t *testing.T
+ root string
+}
+
+func newWatcherTest(t *testing.T, tree string) *W {
+ root, err := tmptree("", filepath.FromSlash(tree))
+ if err != nil {
+ t.Fatalf(`tmptree("", %q)=%v`, tree, err)
+ }
+ Sync()
+ return &W{
+ t: t,
+ root: root,
+ }
+}
+
+func NewWatcherTest(t *testing.T, tree string, events ...Event) *W {
+ w := newWatcherTest(t, tree)
+ if len(events) == 0 {
+ events = []Event{Create, Remove, Write, Rename}
+ }
+ if rw, ok := w.watcher().(recursiveWatcher); ok {
+ if err := rw.RecursiveWatch(w.root, joinevents(events)); err != nil {
+ t.Fatalf("RecursiveWatch(%q, All)=%v", w.root, err)
+ }
+ } else {
+ fn := func(path string, fi os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ if fi.IsDir() {
+ if err := w.watcher().Watch(path, joinevents(events)); err != nil {
+ return err
+ }
+ }
+ return nil
+ }
+ if err := filepath.Walk(w.root, fn); err != nil {
+ t.Fatalf("Walk(%q, fn)=%v", w.root, err)
+ }
+ }
+ drainall(w.C)
+ return w
+}
+
+func (w *W) clean(path string) string {
+ path, isrec, err := cleanpath(filepath.Join(w.root, path))
+ if err != nil {
+ w.Fatalf("cleanpath(%q)=%v", path, err)
+ }
+ if isrec {
+ path = path + "..."
+ }
+ return path
+}
+
+func (w *W) Fatal(v interface{}) {
+ w.t.Fatalf("%s: %v", caller(), v)
+}
+
+func (w *W) Fatalf(format string, v ...interface{}) {
+ w.t.Fatalf("%s: %s", caller(), fmt.Sprintf(format, v...))
+}
+
+func (w *W) Watch(path string, e Event) {
+ if err := w.watcher().Watch(w.clean(path), e); err != nil {
+ w.Fatalf("Watch(%s, %v)=%v", path, e, err)
+ }
+}
+
+func (w *W) Rewatch(path string, olde, newe Event) {
+ if err := w.watcher().Rewatch(w.clean(path), olde, newe); err != nil {
+ w.Fatalf("Rewatch(%s, %v, %v)=%v", path, olde, newe, err)
+ }
+}
+
+func (w *W) Unwatch(path string) {
+ if err := w.watcher().Unwatch(w.clean(path)); err != nil {
+ w.Fatalf("Unwatch(%s)=%v", path, err)
+ }
+}
+
+func (w *W) RecursiveWatch(path string, e Event) {
+ rw, ok := w.watcher().(recursiveWatcher)
+ if !ok {
+ w.Fatal("watcher does not implement recursive watching on this platform")
+ }
+ if err := rw.RecursiveWatch(w.clean(path), e); err != nil {
+ w.Fatalf("RecursiveWatch(%s, %v)=%v", path, e, err)
+ }
+}
+
+func (w *W) RecursiveRewatch(oldp, newp string, olde, newe Event) {
+ rw, ok := w.watcher().(recursiveWatcher)
+ if !ok {
+ w.Fatal("watcher does not implement recursive watching on this platform")
+ }
+ if err := rw.RecursiveRewatch(w.clean(oldp), w.clean(newp), olde, newe); err != nil {
+ w.Fatalf("RecursiveRewatch(%s, %s, %v, %v)=%v", oldp, newp, olde, newe, err)
+ }
+}
+
+func (w *W) RecursiveUnwatch(path string) {
+ rw, ok := w.watcher().(recursiveWatcher)
+ if !ok {
+ w.Fatal("watcher does not implement recursive watching on this platform")
+ }
+ if err := rw.RecursiveUnwatch(w.clean(path)); err != nil {
+ w.Fatalf("RecursiveUnwatch(%s)=%v", path, err)
+ }
+}
+
+func (w *W) initwatcher(buffer int) {
+ c := make(chan EventInfo, buffer)
+ w.Watcher = newWatcher(c)
+ w.C = c
+}
+
+func (w *W) watcher() watcher {
+ if w.Watcher == nil {
+ w.initwatcher(512)
+ }
+ return w.Watcher
+}
+
+func (w *W) c() chan EventInfo {
+ if w.C == nil {
+ w.initwatcher(512)
+ }
+ return w.C
+}
+
+func (w *W) timeout() time.Duration {
+ if w.Timeout != 0 {
+ return w.Timeout
+ }
+ return timeout()
+}
+
+func (w *W) Close() error {
+ defer os.RemoveAll(w.root)
+ if err := w.watcher().Close(); err != nil {
+ w.Fatalf("w.Watcher.Close()=%v", err)
+ }
+ return nil
+}
+
+func EqualEventInfo(want, got EventInfo) error {
+ if got.Event() != want.Event() {
+ return fmt.Errorf("want Event()=%v; got %v (path=%s)", want.Event(),
+ got.Event(), want.Path())
+ }
+ path := strings.TrimRight(filepath.FromSlash(want.Path()), `/\`)
+ if !strings.HasSuffix(got.Path(), path) {
+ return fmt.Errorf("want Path()=%s; got %s (event=%v)", path, got.Path(),
+ want.Event())
+ }
+ return nil
+}
+
+func EqualCall(want, got Call) error {
+ if want.F != got.F {
+ return fmt.Errorf("want F=%v; got %v (want.P=%q, got.P=%q)", want.F, got.F, want.P, got.P)
+ }
+ if got.E != want.E {
+ return fmt.Errorf("want E=%v; got %v (want.P=%q, got.P=%q)", want.E, got.E, want.P, got.P)
+ }
+ if got.NE != want.NE {
+ return fmt.Errorf("want NE=%v; got %v (want.P=%q, got.P=%q)", want.NE, got.NE, want.P, got.P)
+ }
+ if want.C != got.C {
+ return fmt.Errorf("want C=%p; got %p (want.P=%q, got.P=%q)", want.C, got.C, want.P, got.P)
+ }
+ if want := filepath.FromSlash(want.P); !strings.HasSuffix(got.P, want) {
+ return fmt.Errorf("want P=%s; got %s", want, got.P)
+ }
+ if want := filepath.FromSlash(want.NP); !strings.HasSuffix(got.NP, want) {
+ return fmt.Errorf("want NP=%s; got %s", want, got.NP)
+ }
+ return nil
+}
+
+func create(w *W, path string) WCase {
+ return WCase{
+ Action: func() {
+ isdir, err := tmpcreate(w.root, filepath.FromSlash(path))
+ if err != nil {
+ w.Fatalf("tmpcreate(%q, %q)=%v", w.root, path, err)
+ }
+ if isdir {
+ dbgprintf("[FS] os.Mkdir(%q)\n", path)
+ } else {
+ dbgprintf("[FS] os.Create(%q)\n", path)
+ }
+ },
+ Events: []EventInfo{
+ &Call{P: path, E: Create},
+ },
+ }
+}
+
+func remove(w *W, path string) WCase {
+ return WCase{
+ Action: func() {
+ if err := os.RemoveAll(filepath.Join(w.root, filepath.FromSlash(path))); err != nil {
+ w.Fatal(err)
+ }
+ dbgprintf("[FS] os.Remove(%q)\n", path)
+ },
+ Events: []EventInfo{
+ &Call{P: path, E: Remove},
+ },
+ }
+}
+
+func rename(w *W, oldpath, newpath string) WCase {
+ return WCase{
+ Action: func() {
+ err := os.Rename(filepath.Join(w.root, filepath.FromSlash(oldpath)),
+ filepath.Join(w.root, filepath.FromSlash(newpath)))
+ if err != nil {
+ w.Fatal(err)
+ }
+ dbgprintf("[FS] os.Rename(%q, %q)\n", oldpath, newpath)
+ },
+ Events: []EventInfo{
+ &Call{P: newpath, E: Rename},
+ },
+ }
+}
+
+func write(w *W, path string, p []byte) WCase {
+ return WCase{
+ Action: func() {
+ f, err := os.OpenFile(filepath.Join(w.root, filepath.FromSlash(path)),
+ os.O_WRONLY, 0644)
+ if err != nil {
+ w.Fatalf("OpenFile(%q)=%v", path, err)
+ }
+ if _, err := f.Write(p); err != nil {
+ w.Fatalf("Write(%q)=%v", path, err)
+ }
+ if err := nonil(f.Sync(), f.Close()); err != nil {
+ w.Fatalf("Sync(%q)/Close(%q)=%v", path, path, err)
+ }
+ dbgprintf("[FS] Write(%q)\n", path)
+ },
+ Events: []EventInfo{
+ &Call{P: path, E: Write},
+ },
+ }
+}
+
+func drainall(c chan EventInfo) (ei []EventInfo) {
+ time.Sleep(50 * time.Millisecond)
+ for {
+ select {
+ case e := <-c:
+ ei = append(ei, e)
+ runtime.Gosched()
+ default:
+ return
+ }
+ }
+}
+
+type WCaseFunc func(i int, cas WCase, ei EventInfo) error
+
+func (w *W) ExpectAnyFunc(cases []WCase, fn WCaseFunc) {
+ UpdateWait() // Wait some time before starting the test.
+Test:
+ for i, cas := range cases {
+ dbgprintf("ExpectAny: i=%d\n", i)
+ cas.Action()
+ Sync()
+ switch cas.Events {
+ case nil:
+ if ei := drainall(w.C); len(ei) != 0 {
+ w.Fatalf("unexpected dangling events: %v (i=%d)", ei, i)
+ }
+ default:
+ select {
+ case ei := <-w.C:
+ dbgprintf("received: path=%q, event=%v, sys=%v (i=%d)", ei.Path(),
+ ei.Event(), ei.Sys(), i)
+ for j, want := range cas.Events {
+ if err := EqualEventInfo(want, ei); err != nil {
+ dbgprint(err, j)
+ continue
+ }
+ if fn != nil {
+ if err := fn(i, cas, ei); err != nil {
+ w.Fatalf("ExpectAnyFunc(%d, %v)=%v", i, ei, err)
+ }
+ }
+ drainall(w.C) // TODO(rjeczalik): revisit
+ continue Test
+ }
+ w.Fatalf("ExpectAny received an event which does not match any of "+
+ "the expected ones (i=%d): want one of %v; got %v", i, cas.Events, ei)
+ case <-time.After(w.timeout()):
+ w.Fatalf("timed out after %v waiting for one of %v (i=%d)", w.timeout(),
+ cas.Events, i)
+ }
+ drainall(w.C) // TODO(rjeczalik): revisit
+ }
+ }
+}
+
+func (w *W) ExpectAny(cases []WCase) {
+ w.ExpectAnyFunc(cases, nil)
+}
+
+// FuncType represents enums for Watcher interface.
+type FuncType string
+
+const (
+ FuncWatch = FuncType("Watch")
+ FuncUnwatch = FuncType("Unwatch")
+ FuncRewatch = FuncType("Rewatch")
+ FuncRecursiveWatch = FuncType("RecursiveWatch")
+ FuncRecursiveUnwatch = FuncType("RecursiveUnwatch")
+ FuncRecursiveRewatch = FuncType("RecursiveRewatch")
+ FuncStop = FuncType("Stop")
+)
+
+type Chans []chan EventInfo
+
+func NewChans(n int) Chans {
+ ch := make([]chan EventInfo, n)
+ for i := range ch {
+ ch[i] = make(chan EventInfo, buffer)
+ }
+ return ch
+}
+
+func (c Chans) Foreach(fn func(chan<- EventInfo, node)) {
+ for i, ch := range c {
+ fn(ch, node{Name: strconv.Itoa(i)})
+ }
+}
+
+func (c Chans) Drain() (ei []EventInfo) {
+ n := len(c)
+ stop := make(chan struct{})
+ eich := make(chan EventInfo, n*buffer)
+ go func() {
+ defer close(eich)
+ cases := make([]reflect.SelectCase, n+1)
+ for i := range c {
+ cases[i].Chan = reflect.ValueOf(c[i])
+ cases[i].Dir = reflect.SelectRecv
+ }
+ cases[n].Chan = reflect.ValueOf(stop)
+ cases[n].Dir = reflect.SelectRecv
+ for {
+ i, v, ok := reflect.Select(cases)
+ if i == n {
+ return
+ }
+ if !ok {
+ panic("(Chans).Drain(): unexpected chan close")
+ }
+ eich <- v.Interface().(EventInfo)
+ }
+ }()
+ <-time.After(50 * time.Duration(n) * time.Millisecond)
+ close(stop)
+ for e := range eich {
+ ei = append(ei, e)
+ }
+ return
+}
+
+// Call represents single call to Watcher issued by the Tree
+// and recorded by a spy Watcher mock.
+type Call struct {
+ F FuncType // denotes type of function to call, for both watcher and notifier interface
+ C chan EventInfo // user channel being an argument to either Watch or Stop function
+ P string // regular Path argument and old path from RecursiveRewatch call
+ NP string // new Path argument from RecursiveRewatch call
+ E Event // regular Event argument and old Event from a Rewatch call
+ NE Event // new Event argument from Rewatch call
+ S interface{} // when Call is used as EventInfo, S is a value of Sys()
+ Dir bool // when Call is used as EventInfo, Dir is a value of isDir()
+}
+
+// Call implements the EventInfo interface.
+func (c *Call) Event() Event { return c.E }
+func (c *Call) Path() string { return c.P }
+func (c *Call) String() string { return fmt.Sprintf("%#v", c) }
+func (c *Call) Sys() interface{} { return c.S }
+func (c *Call) isDir() (bool, error) { return c.Dir, nil }
+
+// CallSlice is a conveniance wrapper for a slice of Call values, which allows
+// to sort them in ascending order.
+type CallSlice []Call
+
+// CallSlice implements sort.Interface inteface.
+func (cs CallSlice) Len() int { return len(cs) }
+func (cs CallSlice) Less(i, j int) bool { return cs[i].P < cs[j].P }
+func (cs CallSlice) Swap(i, j int) { cs[i], cs[j] = cs[j], cs[i] }
+func (cs CallSlice) Sort() { sort.Sort(cs) }
+
+// Spy is a mock for Watcher interface, which records every call.
+type Spy []Call
+
+func (s *Spy) Close() (_ error) { return }
+
+func (s *Spy) Watch(p string, e Event) (_ error) {
+ dbgprintf("%s: (*Spy).Watch(%q, %v)", caller(), p, e)
+ *s = append(*s, Call{F: FuncWatch, P: p, E: e})
+ return
+}
+
+func (s *Spy) Unwatch(p string) (_ error) {
+ dbgprintf("%s: (*Spy).Unwatch(%q)", caller(), p)
+ *s = append(*s, Call{F: FuncUnwatch, P: p})
+ return
+}
+
+func (s *Spy) Rewatch(p string, olde, newe Event) (_ error) {
+ dbgprintf("%s: (*Spy).Rewatch(%q, %v, %v)", caller(), p, olde, newe)
+ *s = append(*s, Call{F: FuncRewatch, P: p, E: olde, NE: newe})
+ return
+}
+
+func (s *Spy) RecursiveWatch(p string, e Event) (_ error) {
+ dbgprintf("%s: (*Spy).RecursiveWatch(%q, %v)", caller(), p, e)
+ *s = append(*s, Call{F: FuncRecursiveWatch, P: p, E: e})
+ return
+}
+
+func (s *Spy) RecursiveUnwatch(p string) (_ error) {
+ dbgprintf("%s: (*Spy).RecursiveUnwatch(%q)", caller(), p)
+ *s = append(*s, Call{F: FuncRecursiveUnwatch, P: p})
+ return
+}
+
+func (s *Spy) RecursiveRewatch(oldp, newp string, olde, newe Event) (_ error) {
+ dbgprintf("%s: (*Spy).RecursiveRewatch(%q, %q, %v, %v)", caller(), oldp, newp, olde, newe)
+ *s = append(*s, Call{F: FuncRecursiveRewatch, P: oldp, NP: newp, E: olde, NE: newe})
+ return
+}
+
+type RCase struct {
+ Call Call
+ Record []Call
+}
+
+type TCase struct {
+ Event Call
+ Receiver Chans
+}
+
+type NCase struct {
+ Event WCase
+ Receiver Chans
+}
+
+type N struct {
+ Timeout time.Duration
+
+ t *testing.T
+ tree tree
+ w *W
+ spy *Spy
+ c chan EventInfo
+ j int // spy offset
+
+ realroot string
+}
+
+func newN(t *testing.T, tree string) *N {
+ n := &N{
+ t: t,
+ w: newWatcherTest(t, tree),
+ }
+ realroot, err := canonical(n.w.root)
+ if err != nil {
+ t.Fatalf("%s: unexpected fixture failure: %v", caller(), err)
+ }
+ n.realroot = realroot
+ return n
+}
+
+func newTreeN(t *testing.T, tree string) *N {
+ c := make(chan EventInfo, buffer)
+ n := newN(t, tree)
+ n.spy = &Spy{}
+ n.w.Watcher = n.spy
+ n.w.C = c
+ n.c = c
+ return n
+}
+
+func NewNotifyTest(t *testing.T, tree string) *N {
+ n := newN(t, tree)
+ if rw, ok := n.w.watcher().(recursiveWatcher); ok {
+ n.tree = newRecursiveTree(rw, n.w.c())
+ } else {
+ n.tree = newNonrecursiveTree(n.w.watcher(), n.w.c(), nil)
+ }
+ return n
+}
+
+func NewRecursiveTreeTest(t *testing.T, tree string) *N {
+ n := newTreeN(t, tree)
+ n.tree = newRecursiveTree(n.spy, n.c)
+ return n
+}
+
+func NewNonrecursiveTreeTest(t *testing.T, tree string) *N {
+ n := newTreeN(t, tree)
+ n.tree = newNonrecursiveTree(n.spy, n.c, nil)
+ return n
+}
+
+func NewNonrecursiveTreeTestC(t *testing.T, tree string) (*N, chan EventInfo) {
+ rec := make(chan EventInfo, buffer)
+ recinternal := make(chan EventInfo, buffer)
+ recuser := make(chan EventInfo, buffer)
+ go func() {
+ for ei := range rec {
+ select {
+ case recinternal <- ei:
+ default:
+ t.Fatalf("failed to send ei to recinternal: not ready")
+ }
+ select {
+ case recuser <- ei:
+ default:
+ t.Fatalf("failed to send ei to recuser: not ready")
+ }
+ }
+ }()
+ n := newTreeN(t, tree)
+ tr := newNonrecursiveTree(n.spy, n.c, recinternal)
+ tr.rec = rec
+ n.tree = tr
+ return n, recuser
+}
+
+func (n *N) timeout() time.Duration {
+ if n.Timeout != 0 {
+ return n.Timeout
+ }
+ return n.w.timeout()
+}
+
+func (n *N) W() *W {
+ return n.w
+}
+
+func (n *N) Close() error {
+ defer os.RemoveAll(n.w.root)
+ if err := n.tree.Close(); err != nil {
+ n.w.Fatalf("(notifier).Close()=%v", err)
+ }
+ return nil
+}
+
+func (n *N) Watch(path string, c chan<- EventInfo, events ...Event) {
+ UpdateWait() // we need to wait on Windows because of its asynchronous watcher.
+ path = filepath.Join(n.w.root, path)
+ if err := n.tree.Watch(path, c, events...); err != nil {
+ n.t.Errorf("Watch(%s, %p, %v)=%v", path, c, events, err)
+ }
+}
+
+func (n *N) WatchErr(path string, c chan<- EventInfo, err error, events ...Event) {
+ path = filepath.Join(n.w.root, path)
+ switch e := n.tree.Watch(path, c, events...); {
+ case err == nil && e == nil:
+ n.t.Errorf("Watch(%s, %p, %v)=nil", path, c, events)
+ case err != nil && e != err:
+ n.t.Errorf("Watch(%s, %p, %v)=%v != %v", path, c, events, e, err)
+ }
+}
+
+func (n *N) Stop(c chan<- EventInfo) {
+ n.tree.Stop(c)
+}
+
+func (n *N) Call(calls ...Call) {
+ for i := range calls {
+ switch calls[i].F {
+ case FuncWatch:
+ n.Watch(calls[i].P, calls[i].C, calls[i].E)
+ case FuncStop:
+ n.Stop(calls[i].C)
+ default:
+ panic("unsupported call type: " + string(calls[i].F))
+ }
+ }
+}
+
+func (n *N) expectDry(ch Chans, i int) {
+ if ei := ch.Drain(); len(ei) != 0 {
+ n.w.Fatalf("unexpected dangling events: %v (i=%d)", ei, i)
+ }
+}
+
+func (n *N) ExpectRecordedCalls(cases []RCase) {
+ for i, cas := range cases {
+ dbgprintf("ExpectRecordedCalls: i=%d\n", i)
+ n.Call(cas.Call)
+ record := (*n.spy)[n.j:]
+ if len(cas.Record) == 0 && len(record) == 0 {
+ continue
+ }
+ n.j = len(*n.spy)
+ if len(record) != len(cas.Record) {
+ n.t.Fatalf("%s: want len(record)=%d; got %d [%+v] (i=%d)", caller(),
+ len(cas.Record), len(record), record, i)
+ }
+ CallSlice(record).Sort()
+ for j := range cas.Record {
+ if err := EqualCall(cas.Record[j], record[j]); err != nil {
+ n.t.Fatalf("%s: %v (i=%d, j=%d)", caller(), err, i, j)
+ }
+ }
+ }
+}
+
+func (n *N) collect(ch Chans) <-chan []EventInfo {
+ done := make(chan []EventInfo)
+ go func() {
+ cases := make([]reflect.SelectCase, len(ch))
+ unique := make(map[<-chan EventInfo]EventInfo, len(ch))
+ for i := range ch {
+ cases[i].Chan = reflect.ValueOf(ch[i])
+ cases[i].Dir = reflect.SelectRecv
+ }
+ for i := len(cases); i != 0; i = len(cases) {
+ j, v, ok := reflect.Select(cases)
+ if !ok {
+ n.t.Fatal("unexpected chan close")
+ }
+ ch := cases[j].Chan.Interface().(chan EventInfo)
+ got := v.Interface().(EventInfo)
+ if ei, ok := unique[ch]; ok {
+ n.t.Fatalf("duplicated event %v (previous=%v) received on collect", got, ei)
+ }
+ unique[ch] = got
+ cases[j], cases = cases[i-1], cases[:i-1]
+ }
+ collected := make([]EventInfo, 0, len(ch))
+ for _, ch := range unique {
+ collected = append(collected, ch)
+ }
+ done <- collected
+ }()
+ return done
+}
+
+func (n *N) abs(rel Call) *Call {
+ rel.P = filepath.Join(n.realroot, filepath.FromSlash(rel.P))
+ if !filepath.IsAbs(rel.P) {
+ rel.P = filepath.Join(wd, rel.P)
+ }
+ return &rel
+}
+
+func (n *N) ExpectTreeEvents(cases []TCase, all Chans) {
+ for i, cas := range cases {
+ dbgprintf("ExpectTreeEvents: i=%d\n", i)
+ // Ensure there're no dangling event left by previous test-case.
+ n.expectDry(all, i)
+ n.c <- n.abs(cas.Event)
+ switch cas.Receiver {
+ case nil:
+ n.expectDry(all, i)
+ default:
+ ch := n.collect(cas.Receiver)
+ select {
+ case collected := <-ch:
+ for _, got := range collected {
+ if err := EqualEventInfo(&cas.Event, got); err != nil {
+ n.w.Fatalf("%s: %s (i=%d)", caller(), err, i)
+ }
+ }
+ case <-time.After(n.timeout()):
+ n.w.Fatalf("ExpectTreeEvents has timed out after %v waiting for"+
+ " %v on %s (i=%d)", n.timeout(), cas.Event.E, cas.Event.P, i)
+ }
+
+ }
+ }
+ n.expectDry(all, -1)
+}
+
+func (n *N) ExpectNotifyEvents(cases []NCase, all Chans) {
+ UpdateWait() // Wait some time before starting the test.
+ for i, cas := range cases {
+ dbgprintf("ExpectNotifyEvents: i=%d\n", i)
+ cas.Event.Action()
+ Sync()
+ switch cas.Receiver {
+ case nil:
+ n.expectDry(all, i)
+ default:
+ ch := n.collect(cas.Receiver)
+ select {
+ case collected := <-ch:
+ Compare:
+ for j, ei := range collected {
+ dbgprintf("received: path=%q, event=%v, sys=%v (i=%d, j=%d)", ei.Path(),
+ ei.Event(), ei.Sys(), i, j)
+ for _, want := range cas.Event.Events {
+ if err := EqualEventInfo(want, ei); err != nil {
+ dbgprint(err, j)
+ continue
+ }
+ continue Compare
+ }
+ n.w.Fatalf("ExpectNotifyEvents received an event which does not"+
+ " match any of the expected ones (i=%d): want one of %v; got %v", i,
+ cas.Event.Events, ei)
+ }
+ case <-time.After(n.timeout()):
+ n.w.Fatalf("ExpectNotifyEvents did not receive any of the expected events [%v] "+
+ "after %v (i=%d)", cas.Event, n.timeout(), i)
+ }
+ }
+ }
+ n.expectDry(all, -1)
+}
+
+func (n *N) Walk(fn walkFunc) {
+ switch t := n.tree.(type) {
+ case *recursiveTree:
+ if err := t.root.Walk("", fn); err != nil {
+ n.w.Fatal(err)
+ }
+ case *nonrecursiveTree:
+ if err := t.root.Walk("", fn); err != nil {
+ n.w.Fatal(err)
+ }
+ default:
+ n.t.Fatal("unknown tree type")
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/tree.go b/Godeps/_workspace/src/github.com/zillode/notify/tree.go
new file mode 100644
index 0000000..cd6afd6
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/tree.go
@@ -0,0 +1,22 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+const buffer = 128
+
+type tree interface {
+ Watch(string, chan<- EventInfo, ...Event) error
+ Stop(chan<- EventInfo)
+ Close() error
+}
+
+func newTree() tree {
+ c := make(chan EventInfo, buffer)
+ w := newWatcher(c)
+ if rw, ok := w.(recursiveWatcher); ok {
+ return newRecursiveTree(rw, c)
+ }
+ return newNonrecursiveTree(w, c, make(chan EventInfo, buffer))
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/tree_nonrecursive.go b/Godeps/_workspace/src/github.com/zillode/notify/tree_nonrecursive.go
new file mode 100644
index 0000000..788a17b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/tree_nonrecursive.go
@@ -0,0 +1,292 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import "sync"
+
+// nonrecursiveTree TODO(rjeczalik)
+type nonrecursiveTree struct {
+ rw sync.RWMutex // protects root
+ root root
+ w watcher
+ c chan EventInfo
+ rec chan EventInfo
+}
+
+// newNonrecursiveTree TODO(rjeczalik)
+func newNonrecursiveTree(w watcher, c, rec chan EventInfo) *nonrecursiveTree {
+ if rec == nil {
+ rec = make(chan EventInfo, buffer)
+ }
+ t := &nonrecursiveTree{
+ root: root{nd: newnode("")},
+ w: w,
+ c: c,
+ rec: rec,
+ }
+ go t.dispatch(c)
+ go t.internal(rec)
+ return t
+}
+
+// dispatch TODO(rjeczalik)
+func (t *nonrecursiveTree) dispatch(c <-chan EventInfo) {
+ for ei := range c {
+ dbgprintf("dispatching %v on %q", ei.Event(), ei.Path())
+ go func(ei EventInfo) {
+ var nd node
+ var isrec bool
+ dir, base := split(ei.Path())
+ fn := func(it node, isbase bool) error {
+ isrec = isrec || it.Watch.IsRecursive()
+ if isbase {
+ nd = it
+ } else {
+ it.Watch.Dispatch(ei, recursive)
+ }
+ return nil
+ }
+ t.rw.RLock()
+ // Notify recursive watchpoints found on the path.
+ if err := t.root.WalkPath(dir, fn); err != nil {
+ dbgprint("dispatch did not reach leaf:", err)
+ t.rw.RUnlock()
+ return
+ }
+ // Notify parent watchpoint.
+ nd.Watch.Dispatch(ei, 0)
+ isrec = isrec || nd.Watch.IsRecursive()
+ // If leaf watchpoint exists, notify it.
+ if nd, ok := nd.Child[base]; ok {
+ isrec = isrec || nd.Watch.IsRecursive()
+ nd.Watch.Dispatch(ei, 0)
+ }
+ t.rw.RUnlock()
+ // If the event describes newly leaf directory created within
+ if !isrec || ei.Event() != Create {
+ return
+ }
+ if ok, err := ei.(isDirer).isDir(); !ok || err != nil {
+ return
+ }
+ t.rec <- ei
+ }(ei)
+ }
+}
+
+// internal TODO(rjeczalik)
+func (t *nonrecursiveTree) internal(rec <-chan EventInfo) {
+ for ei := range rec {
+ var nd node
+ var eset = internal
+ t.rw.Lock()
+ t.root.WalkPath(ei.Path(), func(it node, _ bool) error {
+ if e := it.Watch[t.rec]; e != 0 && e > eset {
+ eset = e
+ }
+ nd = it
+ return nil
+ })
+ if eset == internal {
+ t.rw.Unlock()
+ continue
+ }
+ err := nd.Add(ei.Path()).AddDir(t.recFunc(eset))
+ t.rw.Unlock()
+ if err != nil {
+ dbgprintf("internal(%p) error: %v", rec, err)
+ }
+ }
+}
+
+// watchAdd TODO(rjeczalik)
+func (t *nonrecursiveTree) watchAdd(nd node, c chan<- EventInfo, e Event) eventDiff {
+ if e&recursive != 0 {
+ diff := nd.Watch.Add(t.rec, e|Create|omit)
+ nd.Watch.Add(c, e)
+ return diff
+ }
+ return nd.Watch.Add(c, e)
+}
+
+// watchDelMin TODO(rjeczalik)
+func (t *nonrecursiveTree) watchDelMin(min Event, nd node, c chan<- EventInfo, e Event) eventDiff {
+ old, ok := nd.Watch[t.rec]
+ if ok {
+ nd.Watch[t.rec] = min
+ }
+ diff := nd.Watch.Del(c, e)
+ if ok {
+ switch old &^= diff[0] &^ diff[1]; {
+ case old|internal == internal:
+ delete(nd.Watch, t.rec)
+ if set, ok := nd.Watch[nil]; ok && len(nd.Watch) == 1 && set == 0 {
+ delete(nd.Watch, nil)
+ }
+ default:
+ nd.Watch.Add(t.rec, old|Create)
+ switch {
+ case diff == none:
+ case diff[1]|Create == diff[0]:
+ diff = none
+ default:
+ diff[1] |= Create
+ }
+ }
+ }
+ return diff
+}
+
+// watchDel TODO(rjeczalik)
+func (t *nonrecursiveTree) watchDel(nd node, c chan<- EventInfo, e Event) eventDiff {
+ return t.watchDelMin(0, nd, c, e)
+}
+
+// Watch TODO(rjeczalik)
+func (t *nonrecursiveTree) Watch(path string, c chan<- EventInfo, events ...Event) error {
+ if c == nil {
+ panic("notify: Watch using nil channel")
+ }
+ // Expanding with empty event set is a nop.
+ if len(events) == 0 {
+ return nil
+ }
+ path, isrec, err := cleanpath(path)
+ if err != nil {
+ return err
+ }
+ eset := joinevents(events)
+ t.rw.Lock()
+ defer t.rw.Unlock()
+ nd := t.root.Add(path)
+ if isrec {
+ return t.watchrec(nd, c, eset|recursive)
+ }
+ return t.watch(nd, c, eset)
+}
+
+func (t *nonrecursiveTree) watch(nd node, c chan<- EventInfo, e Event) (err error) {
+ diff := nd.Watch.Add(c, e)
+ switch {
+ case diff == none:
+ return nil
+ case diff[1] == 0:
+ // TODO(rjeczalik): cleanup this panic after implementation is stable
+ panic("eset is empty: " + nd.Name)
+ case diff[0] == 0:
+ err = t.w.Watch(nd.Name, diff[1])
+ default:
+ err = t.w.Rewatch(nd.Name, diff[0], diff[1])
+ }
+ if err != nil {
+ nd.Watch.Del(c, diff.Event())
+ return err
+ }
+ return nil
+}
+
+func (t *nonrecursiveTree) recFunc(e Event) walkFunc {
+ return func(nd node) (err error) {
+ switch diff := nd.Watch.Add(t.rec, e|omit|Create); {
+ case diff == none:
+ case diff[1] == 0:
+ // TODO(rjeczalik): cleanup this panic after implementation is stable
+ panic("eset is empty: " + nd.Name)
+ case diff[0] == 0:
+ err = t.w.Watch(nd.Name, diff[1])
+ default:
+ err = t.w.Rewatch(nd.Name, diff[0], diff[1])
+ }
+ return
+ }
+}
+
+func (t *nonrecursiveTree) watchrec(nd node, c chan<- EventInfo, e Event) error {
+ var traverse func(walkFunc) error
+ // Non-recursive tree listens on Create event for every recursive
+ // watchpoint in order to automagically set a watch for every
+ // created directory.
+ switch diff := nd.Watch.dryAdd(t.rec, e|Create); {
+ case diff == none:
+ t.watchAdd(nd, c, e)
+ nd.Watch.Add(t.rec, e|omit|Create)
+ return nil
+ case diff[1] == 0:
+ // TODO(rjeczalik): cleanup this panic after implementation is stable
+ panic("eset is empty: " + nd.Name)
+ case diff[0] == 0:
+ // TODO(rjeczalik): BFS into directories and skip subtree as soon as first
+ // recursive watchpoint is encountered.
+ traverse = nd.AddDir
+ default:
+ traverse = nd.Walk
+ }
+ // TODO(rjeczalik): account every path that failed to be (re)watched
+ // and retry.
+ if err := traverse(t.recFunc(e)); err != nil {
+ return err
+ }
+ t.watchAdd(nd, c, e)
+ return nil
+}
+
+type walkWatchpointFunc func(Event, node) error
+
+func (t *nonrecursiveTree) walkWatchpoint(nd node, fn walkWatchpointFunc) error {
+ type minode struct {
+ min Event
+ nd node
+ }
+ mnd := minode{nd: nd}
+ stack := []minode{mnd}
+Traverse:
+ for n := len(stack); n != 0; n = len(stack) {
+ mnd, stack = stack[n-1], stack[:n-1]
+ // There must be no recursive watchpoints if the node has no watchpoints
+ // itself (every node in subtree rooted at recursive watchpoints must
+ // have at least nil (total) and t.rec watchpoints).
+ if len(mnd.nd.Watch) != 0 {
+ switch err := fn(mnd.min, mnd.nd); err {
+ case nil:
+ case errSkip:
+ continue Traverse
+ default:
+ return err
+ }
+ }
+ for _, nd := range mnd.nd.Child {
+ stack = append(stack, minode{mnd.nd.Watch[t.rec], nd})
+ }
+ }
+ return nil
+}
+
+// Stop TODO(rjeczalik)
+func (t *nonrecursiveTree) Stop(c chan<- EventInfo) {
+ fn := func(min Event, nd node) error {
+ // TODO(rjeczalik): aggregate watcher errors and retry; in worst case
+ // forward to the user.
+ switch diff := t.watchDelMin(min, nd, c, all); {
+ case diff == none:
+ return nil
+ case diff[1] == 0:
+ t.w.Unwatch(nd.Name)
+ default:
+ t.w.Rewatch(nd.Name, diff[0], diff[1])
+ }
+ return nil
+ }
+ t.rw.Lock()
+ err := t.walkWatchpoint(t.root.nd, fn) // TODO(rjeczalik): store max root per c
+ t.rw.Unlock()
+ dbgprintf("Stop(%p) error: %v\n", c, err)
+}
+
+// Close TODO(rjeczalik)
+func (t *nonrecursiveTree) Close() error {
+ err := t.w.Close()
+ close(t.c)
+ return err
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/tree_nonrecursive_test.go b/Godeps/_workspace/src/github.com/zillode/notify/tree_nonrecursive_test.go
new file mode 100644
index 0000000..37625bf
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/tree_nonrecursive_test.go
@@ -0,0 +1,543 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestNonrecursiveTree(t *testing.T) {
+ n := NewNonrecursiveTreeTest(t, "testdata/vfs.txt")
+ defer n.Close()
+
+ ch := NewChans(5)
+
+ watches := [...]RCase{
+ // i=0
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/fs.go",
+ C: ch[0],
+ E: Rename,
+ },
+ Record: []Call{
+ {
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/fs.go",
+ E: Rename,
+ },
+ },
+ },
+ // i=1
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/cmd/...",
+ C: ch[1],
+ E: Remove,
+ },
+ Record: []Call{
+ {
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/cmd",
+ E: Create | Remove,
+ },
+ {
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/cmd/gotree",
+ E: Create | Remove,
+ },
+ {
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/cmd/mktree",
+ E: Create | Remove,
+ },
+ },
+ },
+ // i=2
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/cmd/...",
+ C: ch[2],
+ E: Rename,
+ },
+ Record: []Call{
+ {
+ F: FuncRewatch,
+ P: "src/github.com/rjeczalik/fs/cmd",
+ E: Create | Remove,
+ NE: Create | Remove | Rename,
+ },
+ {
+ F: FuncRewatch,
+ P: "src/github.com/rjeczalik/fs/cmd/gotree",
+ E: Create | Remove,
+ NE: Create | Remove | Rename,
+ },
+ {
+ F: FuncRewatch,
+ P: "src/github.com/rjeczalik/fs/cmd/mktree",
+ E: Create | Remove,
+ NE: Create | Remove | Rename,
+ },
+ },
+ },
+ // i=3
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/cmd/mktree/...",
+ C: ch[2],
+ E: Write,
+ },
+ Record: []Call{
+ {
+ F: FuncRewatch,
+ P: "src/github.com/rjeczalik/fs/cmd/mktree",
+ E: Create | Remove | Rename,
+ NE: Create | Remove | Rename | Write,
+ },
+ },
+ },
+ // i=4
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu/include",
+ C: ch[3],
+ E: Create,
+ },
+ Record: []Call{
+ {
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu/include",
+ E: Create,
+ },
+ },
+ },
+ // i=5
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu/include/qttu/detail/...",
+ C: ch[3],
+ E: Write,
+ },
+ Record: []Call{
+ {
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu/include/qttu/detail",
+ E: Create | Write,
+ },
+ },
+ },
+ // i=6
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu/include/...",
+ C: ch[0],
+ E: Rename,
+ },
+ Record: []Call{
+ {
+ F: FuncRewatch,
+ P: "src/github.com/pblaszczyk/qttu/include",
+ E: Create,
+ NE: Create | Rename,
+ },
+ {
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu/include/qttu",
+ E: Create | Rename,
+ },
+ {
+ F: FuncRewatch,
+ P: "src/github.com/pblaszczyk/qttu/include/qttu/detail",
+ E: Create | Write,
+ NE: Create | Write | Rename,
+ },
+ },
+ },
+ // i=7
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/...",
+ C: ch[1],
+ E: Write,
+ },
+ Record: []Call{
+ {
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk",
+ E: Create | Write,
+ },
+ {
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu",
+ E: Create | Write,
+ },
+ {
+ F: FuncRewatch,
+ P: "src/github.com/pblaszczyk/qttu/include",
+ E: Create | Rename,
+ NE: Create | Rename | Write,
+ },
+ {
+ F: FuncRewatch,
+ P: "src/github.com/pblaszczyk/qttu/include/qttu",
+ E: Create | Rename,
+ NE: Create | Rename | Write,
+ },
+ {
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu/src",
+ E: Create | Write,
+ },
+ },
+ },
+ // i=8
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu/include/...",
+ C: ch[4],
+ E: Write,
+ },
+ Record: nil,
+ },
+ // i=9
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu",
+ C: ch[3],
+ E: Remove,
+ },
+ Record: []Call{
+ {
+ F: FuncRewatch,
+ P: "src/github.com/pblaszczyk/qttu",
+ E: Create | Write,
+ NE: Create | Write | Remove,
+ },
+ },
+ },
+ }
+
+ n.ExpectRecordedCalls(watches[:])
+
+ events := [...]TCase{
+ // i=0
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Rename},
+ Receiver: Chans{ch[0]},
+ },
+ // i=1
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Create},
+ Receiver: nil,
+ },
+ // i=2
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/cmd.go", E: Remove},
+ Receiver: Chans{ch[1]},
+ },
+ // i=3
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/doc.go", E: Write},
+ Receiver: nil,
+ },
+ // i=4
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/mktree/main.go", E: Write},
+ Receiver: Chans{ch[2]},
+ },
+ // i=5
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/mktree/tree.go", E: Create},
+ Receiver: nil,
+ },
+ // i=6
+ {
+ Event: Call{P: "src/github.com/pblaszczyk/qttu/include/.lock", E: Create},
+ Receiver: Chans{ch[3]},
+ },
+ // i=7
+ {
+ Event: Call{P: "src/github.com/pblaszczyk/qttu/include/qttu/detail/registry.hh", E: Write},
+ Receiver: Chans{ch[3], ch[1], ch[4]},
+ },
+ // i=8
+ {
+ Event: Call{P: "src/github.com/pblaszczyk/qttu/include/qttu", E: Remove},
+ Receiver: nil,
+ },
+ // i=9
+ {
+ Event: Call{P: "src/github.com/pblaszczyk/qttu/include", E: Remove},
+ Receiver: Chans{ch[3]},
+ },
+ }
+
+ n.ExpectTreeEvents(events[:], ch)
+
+ stops := [...]RCase{
+ // i=0
+ {
+ Call: Call{
+ F: FuncStop,
+ C: ch[4],
+ },
+ Record: nil,
+ },
+ // i=1
+ {
+ Call: Call{
+ F: FuncStop,
+ C: ch[3],
+ },
+ Record: []Call{
+ {
+ F: FuncRewatch,
+ P: "src/github.com/pblaszczyk/qttu",
+ E: Create | Write | Remove,
+ NE: Create | Write,
+ },
+ },
+ },
+ // i=2
+ {
+ Call: Call{
+ F: FuncStop,
+ C: ch[2],
+ },
+ Record: []Call{
+ {
+ F: FuncRewatch,
+ P: "src/github.com/rjeczalik/fs/cmd",
+ E: Create | Remove | Rename,
+ NE: Create | Remove,
+ },
+ {
+ F: FuncRewatch,
+ P: "src/github.com/rjeczalik/fs/cmd/gotree",
+ E: Create | Remove | Rename,
+ NE: Create | Remove,
+ },
+ {
+ F: FuncRewatch,
+ P: "src/github.com/rjeczalik/fs/cmd/mktree",
+ E: Create | Remove | Rename | Write,
+ NE: Create | Remove,
+ },
+ },
+ },
+ // i=3
+ {
+ Call: Call{
+ F: FuncStop,
+ C: ch[1],
+ },
+ Record: []Call{
+ {
+ F: FuncUnwatch,
+ P: "src/github.com/pblaszczyk",
+ },
+ {
+ F: FuncUnwatch,
+ P: "src/github.com/pblaszczyk/qttu",
+ },
+ {
+ F: FuncRewatch,
+ P: "src/github.com/pblaszczyk/qttu/include",
+ E: Create | Rename | Write,
+ NE: Create | Rename,
+ },
+ {
+ F: FuncRewatch,
+ P: "src/github.com/pblaszczyk/qttu/include/qttu",
+ E: Create | Rename | Write,
+ NE: Create | Rename,
+ },
+ {
+ F: FuncRewatch,
+ P: "src/github.com/pblaszczyk/qttu/include/qttu/detail",
+ E: Create | Rename | Write,
+ NE: Create | Rename,
+ },
+ {
+ F: FuncUnwatch,
+ P: "src/github.com/pblaszczyk/qttu/src",
+ },
+ {
+ F: FuncUnwatch,
+ P: "src/github.com/rjeczalik/fs/cmd",
+ },
+ {
+ F: FuncUnwatch,
+ P: "src/github.com/rjeczalik/fs/cmd/gotree",
+ },
+ {
+ F: FuncUnwatch,
+ P: "src/github.com/rjeczalik/fs/cmd/mktree",
+ },
+ },
+ },
+ // i=4
+ {
+ Call: Call{
+ F: FuncStop,
+ C: ch[0],
+ },
+ Record: []Call{
+ {
+ F: FuncUnwatch,
+ P: "src/github.com/pblaszczyk/qttu/include",
+ },
+ {
+ F: FuncUnwatch,
+ P: "src/github.com/pblaszczyk/qttu/include/qttu",
+ },
+ {
+ F: FuncUnwatch,
+ P: "src/github.com/pblaszczyk/qttu/include/qttu/detail",
+ },
+ {
+ F: FuncUnwatch,
+ P: "src/github.com/rjeczalik/fs/fs.go",
+ },
+ },
+ },
+ }
+
+ n.ExpectRecordedCalls(stops[:])
+
+ n.Walk(func(nd node) error {
+ if len(nd.Watch) != 0 {
+ return fmt.Errorf("unexpected watchpoint: name=%s, eventset=%v (len=%d)",
+ nd.Name, nd.Watch.Total(), len(nd.Watch))
+ }
+ return nil
+ })
+}
+
+func TestNonrecursiveTreeInternal(t *testing.T) {
+ n, c := NewNonrecursiveTreeTestC(t, "testdata/vfs.txt")
+ defer n.Close()
+
+ ch := NewChans(5)
+
+ watches := [...]RCase{
+ // i=0
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/cmd/...",
+ C: ch[0],
+ E: Remove,
+ },
+ Record: []Call{
+ {
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/cmd",
+ E: Create | Remove,
+ },
+ {
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/cmd/gotree",
+ E: Create | Remove,
+ },
+ {
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/cmd/mktree",
+ E: Create | Remove,
+ },
+ },
+ },
+ // i=1
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/ppknap/link/include/coost/...",
+ C: ch[1],
+ E: Create,
+ },
+ Record: []Call{
+ {
+ F: FuncWatch,
+ P: "src/github.com/ppknap/link/include/coost",
+ E: Create,
+ },
+ {
+ F: FuncWatch,
+ P: "src/github.com/ppknap/link/include/coost/link",
+ E: Create,
+ },
+ {
+ F: FuncWatch,
+ P: "src/github.com/ppknap/link/include/coost/link/detail",
+ E: Create,
+ },
+ {
+ F: FuncWatch,
+ P: "src/github.com/ppknap/link/include/coost/link/detail/stdhelpers",
+ E: Create,
+ },
+ },
+ },
+ }
+
+ n.ExpectRecordedCalls(watches[:])
+
+ events := [...]TCase{
+ // i=0
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/dir", E: Create, Dir: true},
+ Receiver: Chans{c},
+ },
+ // i=1
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/dir/another", E: Create, Dir: true},
+ Receiver: Chans{c},
+ },
+ // i=2
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/file", E: Create, Dir: false},
+ Receiver: nil,
+ },
+ // i=3
+ {
+ Event: Call{P: "src/github.com/ppknap/link/include/coost/dir", E: Create, Dir: true},
+ Receiver: Chans{ch[1], c},
+ },
+ // i=4
+ {
+ Event: Call{P: "src/github.com/ppknap/link/include/coost/dir/another", E: Create, Dir: true},
+ Receiver: Chans{ch[1], c},
+ },
+ // i=5
+ {
+ Event: Call{P: "src/github.com/ppknap/link/include/coost/file", E: Create, Dir: false},
+ Receiver: Chans{ch[1]},
+ },
+ // i=6
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/mktree", E: Remove},
+ Receiver: Chans{ch[0]},
+ },
+ // i=7
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/rmtree", E: Create, Dir: true},
+ Receiver: Chans{c},
+ },
+ }
+
+ n.ExpectTreeEvents(events[:], ch)
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/tree_recursive.go b/Godeps/_workspace/src/github.com/zillode/notify/tree_recursive.go
new file mode 100644
index 0000000..7f00dfe
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/tree_recursive.go
@@ -0,0 +1,354 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import "sync"
+
+// watchAdd TODO(rjeczalik)
+func watchAdd(nd node, c chan<- EventInfo, e Event) eventDiff {
+ diff := nd.Watch.Add(c, e)
+ if wp := nd.Child[""].Watch; len(wp) != 0 {
+ e = wp.Total()
+ diff[0] |= e
+ diff[1] |= e
+ if diff[0] == diff[1] {
+ return none
+ }
+ }
+ return diff
+}
+
+// watchAddInactive TODO(rjeczalik)
+func watchAddInactive(nd node, c chan<- EventInfo, e Event) eventDiff {
+ wp := nd.Child[""].Watch
+ if wp == nil {
+ wp = make(watchpoint)
+ nd.Child[""] = node{Watch: wp}
+ }
+ diff := wp.Add(c, e)
+ e = nd.Watch.Total()
+ diff[0] |= e
+ diff[1] |= e
+ if diff[0] == diff[1] {
+ return none
+ }
+ return diff
+}
+
+// watchCopy TODO(rjeczalik)
+func watchCopy(src, dst node) {
+ for c, e := range src.Watch {
+ if c == nil {
+ continue
+ }
+ watchAddInactive(dst, c, e)
+ }
+ if wpsrc := src.Child[""].Watch; len(wpsrc) != 0 {
+ wpdst := dst.Child[""].Watch
+ for c, e := range wpsrc {
+ if c == nil {
+ continue
+ }
+ wpdst.Add(c, e)
+ }
+ }
+}
+
+// watchDel TODO(rjeczalik)
+func watchDel(nd node, c chan<- EventInfo, e Event) eventDiff {
+ diff := nd.Watch.Del(c, e)
+ if wp := nd.Child[""].Watch; len(wp) != 0 {
+ diffInactive := wp.Del(c, e)
+ e = wp.Total()
+ // TODO(rjeczalik): add e if e != all?
+ diff[0] |= diffInactive[0] | e
+ diff[1] |= diffInactive[1] | e
+ if diff[0] == diff[1] {
+ return none
+ }
+ }
+ return diff
+}
+
+// watchTotal TODO(rjeczalik)
+func watchTotal(nd node) Event {
+ e := nd.Watch.Total()
+ if wp := nd.Child[""].Watch; len(wp) != 0 {
+ e |= wp.Total()
+ }
+ return e
+}
+
+// watchIsRecursive TODO(rjeczalik)
+func watchIsRecursive(nd node) bool {
+ ok := nd.Watch.IsRecursive()
+ // TODO(rjeczalik): add a test for len(wp) != 0 change the condition.
+ if wp := nd.Child[""].Watch; len(wp) != 0 {
+ // If a watchpoint holds inactive watchpoints, it means it's a parent
+ // one, which is recursive by nature even though it may be not recursive
+ // itself.
+ ok = true
+ }
+ return ok
+}
+
+// recursiveTree TODO(rjeczalik)
+type recursiveTree struct {
+ rw sync.RWMutex // protects root
+ root root
+ // TODO(rjeczalik): merge watcher + recursiveWatcher after #5 and #6
+ w interface {
+ watcher
+ recursiveWatcher
+ }
+ c chan EventInfo
+}
+
+// newRecursiveTree TODO(rjeczalik)
+func newRecursiveTree(w recursiveWatcher, c chan EventInfo) *recursiveTree {
+ t := &recursiveTree{
+ root: root{nd: newnode("")},
+ w: struct {
+ watcher
+ recursiveWatcher
+ }{w.(watcher), w},
+ c: c,
+ }
+ go t.dispatch()
+ return t
+}
+
+// dispatch TODO(rjeczalik)
+func (t *recursiveTree) dispatch() {
+ for ei := range t.c {
+ dbgprintf("dispatching %v on %q", ei.Event(), ei.Path())
+ go func(ei EventInfo) {
+ nd, ok := node{}, false
+ dir, base := split(ei.Path())
+ fn := func(it node, isbase bool) error {
+ if isbase {
+ nd = it
+ } else {
+ it.Watch.Dispatch(ei, recursive)
+ }
+ return nil
+ }
+ t.rw.RLock()
+ defer t.rw.RUnlock()
+ // Notify recursive watchpoints found on the path.
+ if err := t.root.WalkPath(dir, fn); err != nil {
+ dbgprint("dispatch did not reach leaf:", err)
+ return
+ }
+ // Notify parent watchpoint.
+ nd.Watch.Dispatch(ei, 0)
+ // If leaf watchpoint exists, notify it.
+ if nd, ok = nd.Child[base]; ok {
+ nd.Watch.Dispatch(ei, 0)
+ }
+ }(ei)
+ }
+}
+
+// Watch TODO(rjeczalik)
+func (t *recursiveTree) Watch(path string, c chan<- EventInfo, events ...Event) error {
+ if c == nil {
+ panic("notify: Watch using nil channel")
+ }
+ // Expanding with empty event set is a nop.
+ if len(events) == 0 {
+ return nil
+ }
+ path, isrec, err := cleanpath(path)
+ if err != nil {
+ return err
+ }
+ eventset := joinevents(events)
+ if isrec {
+ eventset |= recursive
+ }
+ t.rw.Lock()
+ defer t.rw.Unlock()
+ // case 1: cur is a child
+ //
+ // Look for parent watch which already covers the given path.
+ parent := node{}
+ self := false
+ err = t.root.WalkPath(path, func(nd node, isbase bool) error {
+ if watchTotal(nd) != 0 {
+ parent = nd
+ self = isbase
+ return errSkip
+ }
+ return nil
+ })
+ cur := t.root.Add(path) // add after the walk, so it's less to traverse
+ if err == nil && parent.Watch != nil {
+ // Parent watch found. Register inactive watchpoint, so we have enough
+ // information to shrink the eventset on eventual Stop.
+ // return t.resetwatchpoint(parent, parent, c, eventset|inactive)
+ var diff eventDiff
+ if self {
+ diff = watchAdd(cur, c, eventset)
+ } else {
+ diff = watchAddInactive(parent, c, eventset)
+ }
+ switch {
+ case diff == none:
+ // the parent watchpoint already covers requested subtree with its
+ // eventset
+ case diff[0] == 0:
+ // TODO(rjeczalik): cleanup this panic after implementation is stable
+ panic("dangling watchpoint: " + parent.Name)
+ default:
+ if isrec || watchIsRecursive(parent) {
+ err = t.w.RecursiveRewatch(parent.Name, parent.Name, diff[0], diff[1])
+ } else {
+ err = t.w.Rewatch(parent.Name, diff[0], diff[1])
+ }
+ if err != nil {
+ watchDel(parent, c, diff.Event())
+ return err
+ }
+ watchAdd(cur, c, eventset)
+ // TODO(rjeczalik): account top-most path for c
+ return nil
+ }
+ if !self {
+ watchAdd(cur, c, eventset)
+ }
+ return nil
+ }
+ // case 2: cur is new parent
+ //
+ // Look for children nodes, unwatch n-1 of them and rewatch the last one.
+ var children []node
+ fn := func(nd node) error {
+ if len(nd.Watch) == 0 {
+ return nil
+ }
+ children = append(children, nd)
+ return errSkip
+ }
+ switch must(cur.Walk(fn)); len(children) {
+ case 0:
+ // no child watches, cur holds a new watch
+ case 1:
+ watchAdd(cur, c, eventset) // TODO(rjeczalik): update cache c subtree root?
+ watchCopy(children[0], cur)
+ err = t.w.RecursiveRewatch(children[0].Name, cur.Name, watchTotal(children[0]),
+ watchTotal(cur))
+ if err != nil {
+ // Clean inactive watchpoint. The c chan did not exist before.
+ cur.Child[""] = node{}
+ delete(cur.Watch, c)
+ return err
+ }
+ return nil
+ default:
+ watchAdd(cur, c, eventset)
+ // Copy children inactive watchpoints to the new parent.
+ for _, nd := range children {
+ watchCopy(nd, cur)
+ }
+ // Watch parent subtree.
+ if err = t.w.RecursiveWatch(cur.Name, watchTotal(cur)); err != nil {
+ // Clean inactive watchpoint. The c chan did not exist before.
+ cur.Child[""] = node{}
+ delete(cur.Watch, c)
+ return err
+ }
+ // Unwatch children subtrees.
+ var e error
+ for _, nd := range children {
+ if watchIsRecursive(nd) {
+ e = t.w.RecursiveUnwatch(nd.Name)
+ } else {
+ e = t.w.Unwatch(nd.Name)
+ }
+ if e != nil {
+ err = nonil(err, e)
+ // TODO(rjeczalik): child is still watched, warn all its watchpoints
+ // about possible duplicate events via Error event
+ }
+ }
+ return err
+ }
+ // case 3: cur is new, alone node
+ switch diff := watchAdd(cur, c, eventset); {
+ case diff == none:
+ // TODO(rjeczalik): cleanup this panic after implementation is stable
+ panic("watch requested but no parent watchpoint found: " + cur.Name)
+ case diff[0] == 0:
+ if isrec {
+ err = t.w.RecursiveWatch(cur.Name, diff[1])
+ } else {
+ err = t.w.Watch(cur.Name, diff[1])
+ }
+ if err != nil {
+ watchDel(cur, c, diff.Event())
+ return err
+ }
+ default:
+ // TODO(rjeczalik): cleanup this panic after implementation is stable
+ panic("watch requested but no parent watchpoint found: " + cur.Name)
+ }
+ return nil
+}
+
+// Stop TODO(rjeczalik)
+//
+// TODO(rjeczalik): Split parent watchpoint - transfer watches to children
+// if parent is no longer needed. This carries a risk that underlying
+// watcher calls could fail - reconsider if it's worth the effort.
+func (t *recursiveTree) Stop(c chan<- EventInfo) {
+ var err error
+ fn := func(nd node) (e error) {
+ diff := watchDel(nd, c, all)
+ switch {
+ case diff == none && watchTotal(nd) == 0:
+ // TODO(rjeczalik): There's no watchpoints deeper in the tree,
+ // probably we should remove the nodes as well.
+ return nil
+ case diff == none:
+ // Removing c from nd does not require shrinking its eventset.
+ case diff[1] == 0:
+ if watchIsRecursive(nd) {
+ e = t.w.RecursiveUnwatch(nd.Name)
+ } else {
+ e = t.w.Unwatch(nd.Name)
+ }
+ default:
+ if watchIsRecursive(nd) {
+ e = t.w.RecursiveRewatch(nd.Name, nd.Name, diff[0], diff[1])
+ } else {
+ e = t.w.Rewatch(nd.Name, diff[0], diff[1])
+ }
+ }
+ fn := func(nd node) error {
+ watchDel(nd, c, all)
+ return nil
+ }
+ err = nonil(err, e, nd.Walk(fn))
+ // TODO(rjeczalik): if e != nil store dummy chan in nd.Watch just to
+ // retry un/rewatching next time and/or let the user handle the failure
+ // vie Error event?
+ return errSkip
+ }
+ t.rw.Lock()
+ e := t.root.Walk("", fn) // TODO(rjeczalik): use max root per c
+ t.rw.Unlock()
+ if e != nil {
+ err = nonil(err, e)
+ }
+ dbgprintf("Stop(%p) error: %v\n", c, err)
+}
+
+// Close TODO(rjeczalik)
+func (t *recursiveTree) Close() error {
+ err := t.w.Close()
+ close(t.c)
+ return err
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/tree_recursive_test.go b/Godeps/_workspace/src/github.com/zillode/notify/tree_recursive_test.go
new file mode 100644
index 0000000..329fca0
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/tree_recursive_test.go
@@ -0,0 +1,524 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import "testing"
+
+func TestRecursiveTree(t *testing.T) {
+ n := NewRecursiveTreeTest(t, "testdata/vfs.txt")
+ defer n.Close()
+
+ ch := NewChans(5)
+
+ watches := [...]RCase{
+ // i=0
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/fs.go",
+ C: ch[0],
+ E: Create,
+ },
+ Record: []Call{
+ {
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/fs.go",
+ E: Create,
+ },
+ },
+ },
+ // i=1
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/cmd/...",
+ C: ch[1],
+ E: Remove,
+ },
+ Record: []Call{
+ {
+ F: FuncRecursiveWatch,
+ P: "src/github.com/rjeczalik/fs/cmd",
+ E: Remove,
+ },
+ },
+ },
+ // i=2
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs",
+ C: ch[2],
+ E: Rename,
+ },
+ Record: []Call{
+ {
+ F: FuncRecursiveWatch,
+ P: "src/github.com/rjeczalik/fs",
+ E: Create | Remove | Rename,
+ },
+ {
+ F: FuncRecursiveUnwatch,
+ P: "src/github.com/rjeczalik/fs/cmd",
+ },
+ {
+ F: FuncUnwatch,
+ P: "src/github.com/rjeczalik/fs/fs.go",
+ },
+ },
+ },
+ // i=3
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/ppknap/link/README.md",
+ C: ch[0],
+ E: Create,
+ },
+ Record: []Call{
+ {
+ F: FuncWatch,
+ P: "src/github.com/ppknap/link/README.md",
+ E: Create,
+ },
+ },
+ },
+ // i=4
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/ppknap/link/include/...",
+ C: ch[3],
+ E: Remove,
+ },
+ Record: []Call{
+ {
+ F: FuncRecursiveWatch,
+ P: "src/github.com/ppknap/link/include",
+ E: Remove,
+ },
+ },
+ },
+ // i=5
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/ppknap/link/include",
+ C: ch[0],
+ E: Write,
+ },
+ Record: []Call{
+ {
+ F: FuncRecursiveRewatch,
+ P: "src/github.com/ppknap/link/include",
+ NP: "src/github.com/ppknap/link/include",
+ E: Remove,
+ NE: Remove | Write,
+ },
+ },
+ },
+ // i=6
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/ppknap/link/test/Jamfile.jam",
+ C: ch[0],
+ E: Rename,
+ },
+ Record: []Call{
+ {
+ F: FuncWatch,
+ P: "src/github.com/ppknap/link/test/Jamfile.jam",
+ E: Rename,
+ },
+ },
+ },
+ // i=7
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/ppknap/link/test/Jamfile.jam",
+ C: ch[0],
+ E: Create,
+ },
+ Record: []Call{
+ {
+ F: FuncRewatch,
+ P: "src/github.com/ppknap/link/test/Jamfile.jam",
+ E: Rename,
+ NE: Rename | Create,
+ },
+ },
+ },
+ // i=8
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/ppknap/...",
+ C: ch[0],
+ E: Create,
+ },
+ Record: []Call{
+ {
+ F: FuncRecursiveWatch,
+ P: "src/github.com/ppknap",
+ E: Create | Remove | Write | Rename,
+ },
+ {
+ F: FuncUnwatch,
+ P: "src/github.com/ppknap/link/README.md",
+ },
+ {
+ F: FuncRecursiveUnwatch,
+ P: "src/github.com/ppknap/link/include",
+ },
+ {
+ F: FuncUnwatch,
+ P: "src/github.com/ppknap/link/test/Jamfile.jam",
+ },
+ },
+ },
+ // i=9
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/README.md",
+ C: ch[0],
+ E: Rename,
+ },
+ Record: nil,
+ },
+ // i=10
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/cmd/gotree",
+ C: ch[2],
+ E: Create | Remove,
+ },
+ Record: nil,
+ },
+ // i=11
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu/src/main.cc",
+ C: ch[0],
+ E: Create,
+ },
+ Record: []Call{
+ {
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu/src/main.cc",
+ E: Create,
+ },
+ },
+ },
+ // i=12
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu/src/main.cc",
+ C: ch[0],
+ E: Remove,
+ },
+ Record: []Call{
+ {
+ F: FuncRewatch,
+ P: "src/github.com/pblaszczyk/qttu/src/main.cc",
+ E: Create,
+ NE: Create | Remove,
+ },
+ },
+ },
+ // i=13
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu/src/main.cc",
+ C: ch[0],
+ E: Create | Remove,
+ },
+ Record: nil,
+ },
+ // i=14
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu/src",
+ C: ch[0],
+ E: Create,
+ },
+ Record: []Call{
+ {
+ F: FuncRecursiveRewatch,
+ P: "src/github.com/pblaszczyk/qttu/src/main.cc",
+ NP: "src/github.com/pblaszczyk/qttu/src",
+ E: Create | Remove,
+ NE: Create | Remove,
+ },
+ },
+ },
+ // i=15
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/pblaszczyk/qttu",
+ C: ch[4],
+ E: Write,
+ },
+ Record: []Call{
+ {
+ F: FuncRecursiveRewatch,
+ P: "src/github.com/pblaszczyk/qttu/src",
+ NP: "src/github.com/pblaszczyk/qttu",
+ E: Create | Remove,
+ NE: Create | Remove | Write,
+ },
+ },
+ },
+ // i=16
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/fs.go",
+ C: ch[3],
+ E: Rename,
+ },
+ Record: nil,
+ },
+ }
+
+ n.ExpectRecordedCalls(watches[:])
+
+ events := [...]TCase{
+ // i=0
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Rename},
+ Receiver: Chans{ch[2], ch[3]},
+ },
+ // i=1
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Create},
+ Receiver: Chans{ch[0]},
+ },
+ // i=2
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/fs.go/file", E: Create},
+ Receiver: Chans{ch[0]},
+ },
+ // i=3
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs", E: Rename},
+ Receiver: Chans{ch[2]},
+ },
+ // i=4
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/fs_test.go", E: Rename},
+ Receiver: Chans{ch[2]},
+ },
+ // i=5
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/mktree/main.go", E: Remove},
+ Receiver: Chans{ch[1]},
+ },
+ // i=6
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/gotree", E: Remove},
+ Receiver: Chans{ch[1], ch[2]},
+ },
+ // i=7
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd", E: Remove},
+ Receiver: Chans{ch[1]},
+ },
+ // i=8
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/fs.go/file", E: Write},
+ Receiver: nil,
+ },
+ // i=9
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/fs.go/file", E: Write},
+ Receiver: nil,
+ },
+ // i=10
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs", E: Remove},
+ Receiver: nil,
+ },
+ // i=11
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd", E: Rename},
+ Receiver: Chans{ch[2]},
+ },
+ // i=12
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/mktree/main.go", E: Write},
+ Receiver: nil,
+ },
+ // i=13
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/gotree", E: Rename},
+ Receiver: nil,
+ },
+ // i=14
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/file", E: Rename},
+ Receiver: nil,
+ },
+ // i=15
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/fs.go", E: Rename},
+ Receiver: Chans{ch[2], ch[3]},
+ },
+ }
+
+ n.ExpectTreeEvents(events[:], ch)
+
+ stops := [...]RCase{
+ // i=0
+ {
+ Call: Call{
+ F: FuncStop,
+ C: ch[1],
+ },
+ Record: nil,
+ },
+ {
+ Call: Call{
+ F: FuncStop,
+ C: ch[4],
+ },
+ Record: []Call{
+ {
+ F: FuncRecursiveRewatch,
+ P: "src/github.com/pblaszczyk/qttu",
+ NP: "src/github.com/pblaszczyk/qttu",
+ E: Create | Remove | Write,
+ NE: Create | Remove,
+ },
+ },
+ },
+ }
+
+ n.ExpectRecordedCalls(stops[:])
+}
+
+func TestRecursiveTreeWatchInactiveMerge(t *testing.T) {
+ n := NewRecursiveTreeTest(t, "testdata/vfs.txt")
+ defer n.Close()
+
+ ch := NewChans(1)
+
+ watches := [...]RCase{
+ // i=0
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs",
+ C: ch[0],
+ E: Create,
+ },
+ Record: []Call{
+ {
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs",
+ E: Create,
+ },
+ },
+ },
+ // i=1
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs/cmd/gotree/...",
+ C: ch[0],
+ E: Remove,
+ },
+ Record: []Call{
+ {
+ F: FuncRecursiveRewatch,
+ P: "src/github.com/rjeczalik/fs",
+ NP: "src/github.com/rjeczalik/fs",
+ E: Create,
+ NE: Create | Remove,
+ },
+ },
+ },
+ }
+
+ n.ExpectRecordedCalls(watches[:])
+
+ events := [...]TCase{
+ // i=0
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/.fs.go.swp", E: Create},
+ Receiver: Chans{ch[0]},
+ },
+ // i=1
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/.fs.go.swp", E: Remove},
+ Receiver: nil,
+ },
+ // i=2
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs", E: Remove},
+ Receiver: nil,
+ },
+ // i=3
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/cmd/gotree/main.go", E: Remove},
+ Receiver: Chans{ch[0]},
+ },
+ }
+
+ n.ExpectTreeEvents(events[:], ch)
+}
+
+func TestRecursiveTree_Windows(t *testing.T) {
+ n := NewRecursiveTreeTest(t, "testdata/vfs.txt")
+ defer n.Close()
+
+ const ChangeFileName = Event(0x1)
+
+ ch := NewChans(1)
+
+ watches := [...]RCase{
+ // i=0
+ {
+ Call: Call{
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs",
+ C: ch[0],
+ E: ChangeFileName,
+ },
+ Record: []Call{
+ {
+ F: FuncWatch,
+ P: "src/github.com/rjeczalik/fs",
+ E: ChangeFileName,
+ },
+ },
+ },
+ }
+
+ n.ExpectRecordedCalls(watches[:])
+
+ events := [...]TCase{
+ // i=0
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs", E: ChangeFileName},
+ Receiver: Chans{ch[0]},
+ },
+ // i=1
+ {
+ Event: Call{P: "src/github.com/rjeczalik/fs/fs.go", E: ChangeFileName},
+ Receiver: Chans{ch[0]},
+ },
+ }
+
+ n.ExpectTreeEvents(events[:], ch)
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/util.go b/Godeps/_workspace/src/github.com/zillode/notify/util.go
new file mode 100644
index 0000000..bbc84c3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/util.go
@@ -0,0 +1,146 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+ "errors"
+ "os"
+ "path/filepath"
+ "strings"
+)
+
+const all = ^Event(0)
+const sep = string(os.PathSeparator)
+
+var errDepth = errors.New("exceeded allowed iteration count (circular symlink?)")
+
+func min(i, j int) int {
+ if i > j {
+ return j
+ }
+ return i
+}
+
+func max(i, j int) int {
+ if i < j {
+ return j
+ }
+ return i
+}
+
+// must panics if err is non-nil.
+func must(err error) {
+ if err != nil {
+ panic(err)
+ }
+}
+
+// nonil gives first non-nil error from the given arguments.
+func nonil(err ...error) error {
+ for _, err := range err {
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func cleanpath(path string) (realpath string, isrec bool, err error) {
+ if strings.HasSuffix(path, "...") {
+ isrec = true
+ path = path[:len(path)-3]
+ }
+ if path, err = filepath.Abs(path); err != nil {
+ return "", false, err
+ }
+ if path, err = canonical(path); err != nil {
+ return "", false, err
+ }
+ return path, isrec, nil
+}
+
+// canonical resolves any symlink in the given path and returns it in a clean form.
+// It expects the path to be absolute. It fails to resolve circular symlinks by
+// maintaining a simple iteration limit.
+func canonical(p string) (string, error) {
+ p, err := filepath.Abs(p)
+ if err != nil {
+ return "", err
+ }
+ for i, depth := 1, 1; i < len(p); i, depth = i+1, depth+1 {
+ if depth > 128 {
+ return "", &os.PathError{Op: "canonical", Path: p, Err: errDepth}
+ }
+ if j := strings.IndexRune(p[i:], '/'); j == -1 {
+ i = len(p)
+ } else {
+ i = i + j
+ }
+ fi, err := os.Lstat(p[:i])
+ if err != nil {
+ return "", err
+ }
+ if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
+ s, err := os.Readlink(p[:i])
+ if err != nil {
+ return "", err
+ }
+ p = "/" + s + p[i:]
+ i = 1 // no guarantee s is canonical, start all over
+ }
+ }
+ return filepath.Clean(p), nil
+}
+
+func joinevents(events []Event) (e Event) {
+ if len(events) == 0 {
+ e = All
+ } else {
+ for _, event := range events {
+ e |= event
+ }
+ }
+ return
+}
+
+func split(s string) (string, string) {
+ if i := lastIndexSep(s); i != -1 {
+ return s[:i], s[i+1:]
+ }
+ return "", s
+}
+
+func base(s string) string {
+ if i := lastIndexSep(s); i != -1 {
+ return s[i+1:]
+ }
+ return s
+}
+
+func indexbase(root, name string) int {
+ if n, m := len(root), len(name); m >= n && name[:n] == root &&
+ (n == m || name[n] == os.PathSeparator) {
+ return min(n+1, m)
+ }
+ return -1
+}
+
+func indexSep(s string) int {
+ for i := 0; i < len(s); i++ {
+ if s[i] == os.PathSeparator {
+ return i
+ }
+ }
+ return -1
+}
+
+func lastIndexSep(s string) int {
+ for i := len(s) - 1; i >= 0; i-- {
+ if s[i] == os.PathSeparator {
+ return i
+ }
+ }
+ return -1
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/util_darwin_test.go b/Godeps/_workspace/src/github.com/zillode/notify/util_darwin_test.go
new file mode 100644
index 0000000..8c95db3
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/util_darwin_test.go
@@ -0,0 +1,41 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin
+
+package notify
+
+import (
+ "os"
+ "testing"
+)
+
+func TestCanonicalDarwin(t *testing.T) {
+ cases := [...]caseCanonical{
+ {"/etc", "/private/etc"},
+ {"/etc/defaults", "/private/etc/defaults"},
+ {"/etc/hosts", "/private/etc/hosts"},
+ {"/tmp", "/private/tmp"},
+ {"/var", "/private/var"},
+ }
+ testCanonical(t, cases[:])
+}
+
+func TestCanonicalDarwinMultiple(t *testing.T) {
+ etcsym, err := symlink("/etc", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ tmpsym, err := symlink("/tmp", "")
+ if err != nil {
+ t.Fatal(nonil(err, os.Remove(etcsym)))
+ }
+ defer removeall(etcsym, tmpsym)
+ cases := [...]caseCanonical{
+ {etcsym, "/private/etc"},
+ {etcsym + "/hosts", "/private/etc/hosts"},
+ {tmpsym, "/private/tmp"},
+ }
+ testCanonical(t, cases[:])
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/util_test.go b/Godeps/_workspace/src/github.com/zillode/notify/util_test.go
new file mode 100644
index 0000000..43af3e7
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/util_test.go
@@ -0,0 +1,147 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+type caseCanonical struct {
+ path string
+ full string
+}
+
+func testCanonical(t *testing.T, cases []caseCanonical) {
+ for i, cas := range cases {
+ full, err := canonical(cas.path)
+ if err != nil {
+ t.Errorf("want err=nil; got %v (i=%d)", err, i)
+ continue
+ }
+ if full != cas.full {
+ t.Errorf("want full=%q; got %q (i=%d)", cas.full, full, i)
+ continue
+ }
+ }
+}
+
+func TestCanonicalNoSymlink(t *testing.T) {
+ wd, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("os.Getwd()=%v", err)
+ }
+ td := filepath.Join(wd, "testdata")
+ cases := [...]caseCanonical{
+ {".", wd},
+ {"testdata", td},
+ {filepath.Join("testdata", ".."), wd},
+ }
+ testCanonical(t, cases[:])
+}
+
+func TestJoinevents(t *testing.T) {
+ cases := [...]struct {
+ evs []Event
+ ev Event
+ }{
+ 0: {nil, All},
+ 1: {[]Event{}, All},
+ 2: {[]Event{Create}, Create},
+ 3: {[]Event{Rename}, Rename},
+ 4: {[]Event{Create, Write, Remove}, Create | Write | Remove},
+ }
+ for i, cas := range cases {
+ if ev := joinevents(cas.evs); ev != cas.ev {
+ t.Errorf("want event=%v; got %v (i=%d)", cas.ev, ev, i)
+ }
+ }
+}
+
+func TestTreeSplit(t *testing.T) {
+ cases := [...]struct {
+ path string
+ dir string
+ base string
+ }{
+ {"/github.com/rjeczalik/fakerpc", "/github.com/rjeczalik", "fakerpc"},
+ {"/home/rjeczalik/src", "/home/rjeczalik", "src"},
+ {"/Users/pknap/porn/gopher.avi", "/Users/pknap/porn", "gopher.avi"},
+ {"C:/Documents and Users", "C:", "Documents and Users"},
+ {"C:/Documents and Users/pblaszczyk/wiertarka.exe", "C:/Documents and Users/pblaszczyk", "wiertarka.exe"},
+ {"/home/(╯°□°)╯︵ ┻━┻", "/home", "(╯°□°)╯︵ ┻━┻"},
+ }
+ for i, cas := range cases {
+ dir, base := split(filepath.FromSlash(cas.path))
+ if want := filepath.FromSlash(cas.dir); dir != want {
+ t.Errorf("want dir=%s; got %s (i=%d)", want, dir, i)
+ }
+ if want := filepath.FromSlash(cas.base); base != want {
+ t.Errorf("want base=%s; got %s (i=%d)", want, base, i)
+ }
+ }
+}
+
+func TestTreeBase(t *testing.T) {
+ cases := [...]struct {
+ path string
+ base string
+ }{
+ {"/github.com/rjeczalik/fakerpc", "fakerpc"},
+ {"/home/rjeczalik/src", "src"},
+ {"/Users/pknap/porn/gopher.avi", "gopher.avi"},
+ {"C:/Documents and Users", "Documents and Users"},
+ {"C:/Documents and Users/pblaszczyk/wiertarka.exe", "wiertarka.exe"},
+ {"/home/(╯°□°)╯︵ ┻━┻", "(╯°□°)╯︵ ┻━┻"},
+ }
+ for i, cas := range cases {
+ if base := base(filepath.FromSlash(cas.path)); base != cas.base {
+ t.Errorf("want base=%s; got %s (i=%d)", cas.base, base, i)
+ }
+ }
+}
+
+func TestTreeIndexSep(t *testing.T) {
+ cases := [...]struct {
+ path string
+ n int
+ }{
+ {"github.com/rjeczalik/fakerpc", 10},
+ {"home/rjeczalik/src", 4},
+ {"Users/pknap/porn/gopher.avi", 5},
+ {"C:/Documents and Users", 2},
+ {"Documents and Users/pblaszczyk/wiertarka.exe", 19},
+ {"(╯°□°)╯︵ ┻━┻/Downloads", 30},
+ }
+ for i, cas := range cases {
+ if n := indexSep(filepath.FromSlash(cas.path)); n != cas.n {
+ t.Errorf("want n=%d; got %d (i=%d)", cas.n, n, i)
+ }
+ }
+}
+
+func TestTreeLastIndexSep(t *testing.T) {
+ cases := [...]struct {
+ path string
+ n int
+ }{
+ {"github.com/rjeczalik/fakerpc", 20},
+ {"home/rjeczalik/src", 14},
+ {"Users/pknap/porn/gopher.avi", 16},
+ {"C:/Documents and Users", 2},
+ {"Documents and Users/pblaszczyk/wiertarka.exe", 30},
+ {"/home/(╯°□°)╯︵ ┻━┻", 5},
+ }
+ for i, cas := range cases {
+ if n := lastIndexSep(filepath.FromSlash(cas.path)); n != cas.n {
+ t.Errorf("want n=%d; got %d (i=%d)", cas.n, n, i)
+ }
+ }
+}
+
+func TestCleanpath(t *testing.T) {
+ t.Skip("TODO(rjeczalik)")
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/util_unix_test.go b/Godeps/_workspace/src/github.com/zillode/notify/util_unix_test.go
new file mode 100644
index 0000000..0011506
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/util_unix_test.go
@@ -0,0 +1,94 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build !windows
+
+package notify
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func tmpfile(s string) (string, error) {
+ f, err := ioutil.TempFile(filepath.Split(s))
+ if err != nil {
+ return "", err
+ }
+ if err = nonil(f.Sync(), f.Close()); err != nil {
+ return "", err
+ }
+ return f.Name(), nil
+}
+
+func symlink(src, dst string) (string, error) {
+ name, err := tmpfile(dst)
+ if err != nil {
+ return "", err
+ }
+ if err = nonil(os.Remove(name), os.Symlink(src, name)); err != nil {
+ return "", err
+ }
+ return name, nil
+}
+
+func removeall(s ...string) {
+ for _, s := range s {
+ os.Remove(s)
+ }
+}
+
+func TestCanonical(t *testing.T) {
+ wd, err := os.Getwd()
+ if err != nil {
+ t.Fatalf("os.Getwd()=%v", err)
+ }
+ wdsym, err := symlink(wd, "")
+ if err != nil {
+ t.Fatalf(`symlink(%q, "")=%v`, wd, err)
+ }
+ td := filepath.Join(wd, "testdata")
+ tdsym, err := symlink(td, td)
+ if err != nil {
+ t.Errorf("symlink(%q, %q)=%v", td, td, nonil(err, os.Remove(wdsym)))
+ }
+ defer removeall(wdsym, tdsym)
+ vfstxt := filepath.Join(td, "vfs.txt")
+ cases := [...]caseCanonical{
+ {wdsym, wd},
+ {tdsym, td},
+ {filepath.Join(wdsym, "notify.go"), filepath.Join(wd, "notify.go")},
+ {filepath.Join(tdsym, "vfs.txt"), vfstxt},
+ {filepath.Join(wdsym, filepath.Base(tdsym), "vfs.txt"), vfstxt},
+ }
+ testCanonical(t, cases[:])
+}
+
+func TestCanonicalCircular(t *testing.T) {
+ tmp1, err := tmpfile("circular")
+ if err != nil {
+ t.Fatal(err)
+ }
+ tmp2, err := tmpfile("circular")
+ if err != nil {
+ t.Fatal(nonil(err, os.Remove(tmp1)))
+ }
+ defer removeall(tmp1, tmp2)
+ // Symlink tmp1 -> tmp2.
+ if err = nonil(os.Remove(tmp1), os.Symlink(tmp2, tmp1)); err != nil {
+ t.Fatal(err)
+ }
+ // Symlnik tmp2 -> tmp1.
+ if err = nonil(os.Remove(tmp2), os.Symlink(tmp1, tmp2)); err != nil {
+ t.Fatal(err)
+ }
+ if _, err = canonical(tmp1); err == nil {
+ t.Fatalf("want canonical(%q)!=nil", tmp1)
+ }
+ if _, ok := err.(*os.PathError); !ok {
+ t.Fatalf("want canonical(%q)=os.PathError; got %T", tmp1, err)
+ }
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watcher.go b/Godeps/_workspace/src/github.com/zillode/notify/watcher.go
new file mode 100644
index 0000000..34148ef
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watcher.go
@@ -0,0 +1,85 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import "errors"
+
+var (
+ errAlreadyWatched = errors.New("path is already watched")
+ errNotWatched = errors.New("path is not being watched")
+ errInvalidEventSet = errors.New("invalid event set provided")
+)
+
+// Watcher is a intermediate interface for wrapping inotify, ReadDirChangesW,
+// FSEvents, kqueue and poller implementations.
+//
+// The watcher implementation is expected to do its own mapping between paths and
+// create watchers if underlying event notification does not support it. For
+// the ease of implementation it is guaranteed that paths provided via Watch and
+// Unwatch methods are absolute and clean.
+type watcher interface {
+ // Watch requests a watcher creation for the given path and given event set.
+ Watch(path string, event Event) error
+
+ // Unwatch requests a watcher deletion for the given path and given event set.
+ Unwatch(path string) error
+
+ // Rewatch provides a functionality for modifying existing watch-points, like
+ // expanding its event set.
+ //
+ // Rewatch modifies existing watch-point under for the given path. It passes
+ // the existing event set currently registered for the given path, and the
+ // new, requested event set.
+ //
+ // It is guaranteed that Tree will not pass to Rewatch zero value for any
+ // of its arguments. If old == new and watcher can be upgraded to
+ // recursiveWatcher interface, a watch for the corresponding path is expected
+ // to be changed from recursive to the non-recursive one.
+ Rewatch(path string, old, new Event) error
+
+ // Close unwatches all paths that are registered. When Close returns, it
+ // is expected it will report no more events.
+ Close() error
+}
+
+// RecursiveWatcher is an interface for a Watcher for those OS, which do support
+// recursive watching over directories.
+type recursiveWatcher interface {
+ RecursiveWatch(path string, event Event) error
+
+ // RecursiveUnwatch removes a recursive watch-point given by the path. For
+ // native recursive implementation there is no difference in functionality
+ // between Unwatch and RecursiveUnwatch, however for those platforms, that
+ // requires emulation for recursive watch-points, the implementation differs.
+ RecursiveUnwatch(path string) error
+
+ // RecursiveRewatcher provides a functionality for modifying and/or relocating
+ // existing recursive watch-points.
+ //
+ // To relocate a watch-point means to unwatch oldpath and set a watch-point on
+ // newpath.
+ //
+ // To modify a watch-point means either to expand or shrink its event set.
+ //
+ // Tree can want to either relocate, modify or relocate and modify a watch-point
+ // via single RecursiveRewatch call.
+ //
+ // If oldpath == newpath, the watch-point is expected to change its event set value
+ // from oldevent to newevent.
+ //
+ // If oldevent == newevent, the watch-point is expected to relocate from oldpath
+ // to the newpath.
+ //
+ // If oldpath != newpath and oldevent != newevent, the watch-point is expected
+ // to relocate from oldpath to the newpath first and then change its event set
+ // value from oldevent to the newevent. In other words the end result must be
+ // a watch-point set on newpath with newevent value of its event set.
+ //
+ // It is guaranteed that Tree will not pass to RecurisveRewatcha zero value
+ // for any of its arguments. If oldpath == newpath and oldevent == newevent,
+ // a watch for the corresponding path is expected to be changed for
+ // non-recursive to the recursive one.
+ RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watcher_fsevents.go b/Godeps/_workspace/src/github.com/zillode/notify/watcher_fsevents.go
new file mode 100644
index 0000000..5433491
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watcher_fsevents.go
@@ -0,0 +1,319 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,!kqueue
+
+package notify
+
+import (
+ "errors"
+ "strings"
+ "sync/atomic"
+)
+
+// TODO(rjeczalik): get rid of calls to canonical, it's tree responsibility
+
+const (
+ failure = uint32(FSEventsMustScanSubDirs | FSEventsUserDropped | FSEventsKernelDropped)
+ filter = uint32(FSEventsCreated | FSEventsRemoved | FSEventsRenamed |
+ FSEventsModified | FSEventsInodeMetaMod)
+)
+
+// FSEvent represents single file event. It is created out of values passed by
+// FSEvents to FSEventStreamCallback function.
+type FSEvent struct {
+ Path string // real path of the file or directory
+ ID uint64 // ID of the event (FSEventStreamEventId)
+ Flags uint32 // joint FSEvents* flags (FSEventStreamEventFlags)
+}
+
+// splitflags separates event flags from single set into slice of flags.
+func splitflags(set uint32) (e []uint32) {
+ for i := uint32(1); set != 0; i, set = i<<1, set>>1 {
+ if (set & 1) != 0 {
+ e = append(e, i)
+ }
+ }
+ return
+}
+
+// watch represents a filesystem watchpoint. It is a higher level abstraction
+// over FSEvents' stream, which implements filtering of file events based
+// on path and event set. It emulates non-recursive watch-point by filtering out
+// events which paths are more than 1 level deeper than the watched path.
+type watch struct {
+ // prev stores last event set per path in order to filter out old flags
+ // for new events, which appratenly FSEvents likes to retain. It's a disgusting
+ // hack, it should be researched how to get rid of it.
+ prev map[string]uint32
+ c chan<- EventInfo
+ stream *stream
+ path string
+ events uint32
+ isrec int32
+ flushed bool
+}
+
+// Example format:
+//
+// ~ $ (trigger command) # (event set) -> (effective event set)
+//
+// Heuristics:
+//
+// 1. Create event is removed when it was present in previous event set.
+// Example:
+//
+// ~ $ echo > file # Create|Write -> Create|Write
+// ~ $ echo > file # Create|Write|InodeMetaMod -> Write|InodeMetaMod
+//
+// 2. Remove event is removed if it was present in previouse event set.
+// Example:
+//
+// ~ $ touch file # Create -> Create
+// ~ $ rm file # Create|Remove -> Remove
+// ~ $ touch file # Create|Remove -> Create
+//
+// 3. Write event is removed if not followed by InodeMetaMod on existing
+// file. Example:
+//
+// ~ $ echo > file # Create|Write -> Create|Write
+// ~ $ chmod +x file # Create|Write|ChangeOwner -> ChangeOwner
+//
+// 4. Write&InodeMetaMod is removed when effective event set contain Remove event.
+// Example:
+//
+// ~ $ echo > file # Write|InodeMetaMod -> Write|InodeMetaMod
+// ~ $ rm file # Remove|Write|InodeMetaMod -> Remove
+//
+func (w *watch) strip(base string, set uint32) uint32 {
+ const (
+ write = FSEventsModified | FSEventsInodeMetaMod
+ both = FSEventsCreated | FSEventsRemoved
+ )
+ switch w.prev[base] {
+ case FSEventsCreated:
+ set &^= FSEventsCreated
+ if set&FSEventsRemoved != 0 {
+ w.prev[base] = FSEventsRemoved
+ set &^= write
+ }
+ case FSEventsRemoved:
+ set &^= FSEventsRemoved
+ if set&FSEventsCreated != 0 {
+ w.prev[base] = FSEventsCreated
+ }
+ default:
+ switch set & both {
+ case FSEventsCreated:
+ w.prev[base] = FSEventsCreated
+ case FSEventsRemoved:
+ w.prev[base] = FSEventsRemoved
+ set &^= write
+ }
+ }
+ dbgprintf("split()=%v\n", Event(set))
+ return set
+}
+
+// Dispatch is a stream function which forwards given file events for the watched
+// path to underlying FileInfo channel.
+func (w *watch) Dispatch(ev []FSEvent) {
+ events := atomic.LoadUint32(&w.events)
+ isrec := (atomic.LoadInt32(&w.isrec) == 1)
+ for i := range ev {
+ if ev[i].Flags&FSEventsHistoryDone != 0 {
+ w.flushed = true
+ continue
+ }
+ if !w.flushed {
+ continue
+ }
+ dbgprintf("%v (0x%x) (%s, i=%d, ID=%d, len=%d)\n", Event(ev[i].Flags),
+ ev[i].Flags, ev[i].Path, i, ev[i].ID, len(ev))
+ if ev[i].Flags&failure != 0 {
+ // TODO(rjeczalik): missing error handling
+ panic("unhandled error: " + Event(ev[i].Flags).String())
+ }
+ if !strings.HasPrefix(ev[i].Path, w.path) {
+ continue
+ }
+ n := len(w.path)
+ base := ""
+ if len(ev[i].Path) > n {
+ if ev[i].Path[n] != '/' {
+ continue
+ }
+ base = ev[i].Path[n+1:]
+ if !isrec && strings.IndexByte(base, '/') != -1 {
+ continue
+ }
+ }
+ // TODO(rjeczalik): get diff only from filtered events?
+ e := w.strip(string(base), ev[i].Flags) & events
+ if e == 0 {
+ continue
+ }
+ for _, e := range splitflags(e) {
+ dbgprintf("%d: single event: %v", ev[i].ID, Event(e))
+ w.c <- &event{
+ fse: ev[i],
+ event: Event(e),
+ }
+ }
+ }
+}
+
+// Stop closes underlying FSEvents stream and stops dispatching events.
+func (w *watch) Stop() {
+ w.stream.Stop()
+ // TODO(rjeczalik): make (*stream).Stop flush synchronously undelivered events,
+ // so the following hack can be removed. It should flush all the streams
+ // concurrently as we care not to block too much here.
+ atomic.StoreUint32(&w.events, 0)
+ atomic.StoreInt32(&w.isrec, 0)
+}
+
+// fsevents implements Watcher and RecursiveWatcher interfaces backed by FSEvents
+// framework.
+type fsevents struct {
+ watches map[string]*watch
+ c chan<- EventInfo
+}
+
+func newWatcher(c chan<- EventInfo) watcher {
+ return &fsevents{
+ watches: make(map[string]*watch),
+ c: c,
+ }
+}
+
+func (fse *fsevents) watch(path string, event Event, isrec int32) (err error) {
+ if path, err = canonical(path); err != nil {
+ return err
+ }
+ if _, ok := fse.watches[path]; ok {
+ return errAlreadyWatched
+ }
+ w := &watch{
+ prev: make(map[string]uint32),
+ c: fse.c,
+ path: path,
+ events: uint32(event),
+ isrec: isrec,
+ }
+ w.stream = newStream(path, w.Dispatch)
+ if err = w.stream.Start(); err != nil {
+ return err
+ }
+ fse.watches[path] = w
+ return nil
+}
+
+func (fse *fsevents) unwatch(path string) (err error) {
+ if path, err = canonical(path); err != nil {
+ return
+ }
+ w, ok := fse.watches[path]
+ if !ok {
+ return errNotWatched
+ }
+ w.stream.Stop()
+ delete(fse.watches, path)
+ return nil
+}
+
+// Watch implements Watcher interface. It fails with non-nil error when setting
+// the watch-point by FSEvents fails or with errAlreadyWatched error when
+// the given path is already watched.
+func (fse *fsevents) Watch(path string, event Event) error {
+ return fse.watch(path, event, 0)
+}
+
+// Unwatch implements Watcher interface. It fails with errNotWatched when
+// the given path is not being watched.
+func (fse *fsevents) Unwatch(path string) error {
+ return fse.unwatch(path)
+}
+
+// Rewatch implements Watcher interface. It fails with errNotWatched when
+// the given path is not being watched or with errInvalidEventSet when oldevent
+// does not match event set the watch-point currently holds.
+func (fse *fsevents) Rewatch(path string, oldevent, newevent Event) error {
+ w, ok := fse.watches[path]
+ if !ok {
+ return errNotWatched
+ }
+ if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
+ return errInvalidEventSet
+ }
+ atomic.StoreInt32(&w.isrec, 0)
+ return nil
+}
+
+// RecursiveWatch implements RecursiveWatcher interface. It fails with non-nil
+// error when setting the watch-point by FSEvents fails or with errAlreadyWatched
+// error when the given path is already watched.
+func (fse *fsevents) RecursiveWatch(path string, event Event) error {
+ return fse.watch(path, event, 1)
+}
+
+// RecursiveUnwatch implements RecursiveWatcher interface. It fails with
+// errNotWatched when the given path is not being watched.
+//
+// TODO(rjeczalik): fail if w.isrec == 0?
+func (fse *fsevents) RecursiveUnwatch(path string) error {
+ return fse.unwatch(path)
+}
+
+// RecrusiveRewatch implements RecursiveWatcher interface. It fails:
+//
+// * with errNotWatched when the given path is not being watched
+// * with errInvalidEventSet when oldevent does not match the current event set
+// * with errAlreadyWatched when watch-point given by the oldpath was meant to
+// be relocated to newpath, but the newpath is already watched
+// * a non-nil error when setting the watch-point with FSEvents fails
+//
+// TODO(rjeczalik): Improve handling of watch-point relocation? See two TODOs
+// that follows.
+func (fse *fsevents) RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error {
+ switch [2]bool{oldpath == newpath, oldevent == newevent} {
+ case [2]bool{true, true}:
+ w, ok := fse.watches[oldpath]
+ if !ok {
+ return errNotWatched
+ }
+ atomic.StoreInt32(&w.isrec, 1)
+ return nil
+ case [2]bool{true, false}:
+ w, ok := fse.watches[oldpath]
+ if !ok {
+ return errNotWatched
+ }
+ if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
+ return errors.New("invalid event state diff")
+ }
+ atomic.StoreInt32(&w.isrec, 1)
+ return nil
+ default:
+ // TODO(rjeczalik): rewatch newpath only if exists?
+ // TODO(rjeczalik): migrate w.prev to new watch?
+ if _, ok := fse.watches[newpath]; ok {
+ return errAlreadyWatched
+ }
+ if err := fse.Unwatch(oldpath); err != nil {
+ return err
+ }
+ // TODO(rjeczalik): revert unwatch if watch fails?
+ return fse.watch(newpath, newevent, 1)
+ }
+}
+
+// Close unwatches all watch-points.
+func (fse *fsevents) Close() error {
+ for _, w := range fse.watches {
+ w.Stop()
+ }
+ fse.watches = nil
+ return nil
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watcher_fsevents_cgo.go b/Godeps/_workspace/src/github.com/zillode/notify/watcher_fsevents_cgo.go
new file mode 100644
index 0000000..c88819e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watcher_fsevents_cgo.go
@@ -0,0 +1,156 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,!kqueue
+
+package notify
+
+// #include <CoreServices/CoreServices.h>
+//
+// typedef void (*CFRunLoopPerformCallBack)(void*);
+//
+// void gosource(void *);
+// void gostream(void*, void*, size_t, uintptr_t, uintptr_t, uintptr_t);
+//
+// #cgo LDFLAGS: -framework CoreServices
+import "C"
+
+import (
+ "errors"
+ "os"
+ "sync"
+ "sync/atomic"
+ "time"
+ "unsafe"
+)
+
+var nilstream C.FSEventStreamRef
+
+// Default arguments for FSEventStreamCreate function.
+var (
+ latency C.CFTimeInterval
+ flags = C.FSEventStreamCreateFlags(C.kFSEventStreamCreateFlagFileEvents | C.kFSEventStreamCreateFlagNoDefer)
+ since = uint64(C.FSEventsGetCurrentEventId())
+)
+
+var runloop C.CFRunLoopRef // global runloop which all streams are registered with
+var wg sync.WaitGroup // used to wait until the runloop starts
+
+// source is used for synchronization purposes - it signals when runloop has
+// started and is ready via the wg. It also serves purpose of a dummy source,
+// thanks to it the runloop does not return as it also has at least one source
+// registered.
+var source = C.CFRunLoopSourceCreate(nil, 0, &C.CFRunLoopSourceContext{
+ perform: (C.CFRunLoopPerformCallBack)(C.gosource),
+})
+
+// Errors returned when FSEvents functions fail.
+var (
+ errCreate = os.NewSyscallError("FSEventStreamCreate", errors.New("NULL"))
+ errStart = os.NewSyscallError("FSEventStreamStart", errors.New("false"))
+)
+
+// initializes the global runloop and ensures any created stream awaits its
+// readiness.
+func init() {
+ wg.Add(1)
+ go func() {
+ runloop = C.CFRunLoopGetCurrent()
+ C.CFRunLoopAddSource(runloop, source, C.kCFRunLoopDefaultMode)
+ C.CFRunLoopRun()
+ panic("runloop has just unexpectedly stopped")
+ }()
+ C.CFRunLoopSourceSignal(source)
+}
+
+//export gosource
+func gosource(unsafe.Pointer) {
+ time.Sleep(time.Second)
+ wg.Done()
+}
+
+//export gostream
+func gostream(_, ctx unsafe.Pointer, n C.size_t, paths, flags, ids uintptr) {
+ const (
+ offchar = unsafe.Sizeof((*C.char)(nil))
+ offflag = unsafe.Sizeof(C.FSEventStreamEventFlags(0))
+ offid = unsafe.Sizeof(C.FSEventStreamEventId(0))
+ )
+ if n == 0 {
+ return
+ }
+ ev := make([]FSEvent, 0, int(n))
+ for i := uintptr(0); i < uintptr(n); i++ {
+ switch flags := *(*uint32)(unsafe.Pointer((flags + i*offflag))); {
+ case flags&uint32(FSEventsEventIdsWrapped) != 0:
+ atomic.StoreUint64(&since, uint64(C.FSEventsGetCurrentEventId()))
+ default:
+ ev = append(ev, FSEvent{
+ Path: C.GoString(*(**C.char)(unsafe.Pointer(paths + i*offchar))),
+ Flags: flags,
+ ID: *(*uint64)(unsafe.Pointer(ids + i*offid)),
+ })
+ }
+
+ }
+ (*(*streamFunc)(ctx))(ev)
+}
+
+// StreamFunc is a callback called when stream receives file events.
+type streamFunc func([]FSEvent)
+
+// Stream represents single watch-point which listens for events scheduled by
+// the global runloop.
+type stream struct {
+ path string
+ ref C.FSEventStreamRef
+ ctx C.FSEventStreamContext
+}
+
+// NewStream creates a stream for given path, listening for file events and
+// calling fn upon receving any.
+func newStream(path string, fn streamFunc) *stream {
+ return &stream{
+ path: path,
+ ctx: C.FSEventStreamContext{
+ info: unsafe.Pointer(&fn),
+ },
+ }
+}
+
+// Start creates a FSEventStream for the given path and schedules it with
+// global runloop. It's a nop if the stream was already started.
+func (s *stream) Start() error {
+ if s.ref != nilstream {
+ return nil
+ }
+ wg.Wait()
+ p := C.CFStringCreateWithCStringNoCopy(nil, C.CString(s.path), C.kCFStringEncodingUTF8, nil)
+ path := C.CFArrayCreate(nil, (*unsafe.Pointer)(unsafe.Pointer(&p)), 1, nil)
+ ref := C.FSEventStreamCreate(nil, (C.FSEventStreamCallback)(C.gostream),
+ &s.ctx, path, C.FSEventStreamEventId(atomic.LoadUint64(&since)), latency, flags)
+ if ref == nilstream {
+ return errCreate
+ }
+ C.FSEventStreamScheduleWithRunLoop(ref, runloop, C.kCFRunLoopDefaultMode)
+ if C.FSEventStreamStart(ref) == C.Boolean(0) {
+ C.FSEventStreamInvalidate(ref)
+ return errStart
+ }
+ C.CFRunLoopWakeUp(runloop)
+ s.ref = ref
+ return nil
+}
+
+// Stop stops underlying FSEventStream and unregisters it from global runloop.
+func (s *stream) Stop() {
+ if s.ref == nilstream {
+ return
+ }
+ wg.Wait()
+ C.FSEventStreamStop(s.ref)
+ C.FSEventStreamInvalidate(s.ref)
+ C.CFRunLoopWakeUp(runloop)
+ s.ref = nilstream
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watcher_fsevents_test.go b/Godeps/_workspace/src/github.com/zillode/notify/watcher_fsevents_test.go
new file mode 100644
index 0000000..1d60877
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watcher_fsevents_test.go
@@ -0,0 +1,111 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,!kqueue
+
+package notify
+
+import (
+ "reflect"
+ "testing"
+)
+
+func TestSplitflags(t *testing.T) {
+ cases := [...]struct {
+ set uint32
+ flags []uint32
+ }{
+ {0, nil},
+ {0xD, []uint32{0x1, 0x4, 0x8}},
+ {0x0010 | 0x0040 | 0x0080 | 0x01000, []uint32{0x0010, 0x0040, 0x0080, 0x01000}},
+ {0x40000 | 0x00100 | 0x00200, []uint32{0x00100, 0x00200, 0x40000}},
+ }
+ for i, cas := range cases {
+ if flags := splitflags(cas.set); !reflect.DeepEqual(flags, cas.flags) {
+ t.Errorf("want flags=%v; got %v (i=%d)", cas.flags, flags, i)
+ }
+ }
+}
+
+func TestWatchStrip(t *testing.T) {
+ const (
+ create = uint32(FSEventsCreated)
+ remove = uint32(FSEventsRemoved)
+ rename = uint32(FSEventsRenamed)
+ write = uint32(FSEventsModified)
+ inode = uint32(FSEventsInodeMetaMod)
+ owner = uint32(FSEventsChangeOwner)
+ )
+ cases := [...][]struct {
+ path string
+ flag uint32
+ diff uint32
+ }{
+ // 1.
+ {
+ {"file", create | write, create | write},
+ {"file", create | write | inode, write | inode},
+ },
+ // 2.
+ {
+ {"file", create, create},
+ {"file", create | remove, remove},
+ {"file", create | remove, create},
+ },
+ // 3.
+ {
+ {"file", create | write, create | write},
+ {"file", create | write | owner, write | owner},
+ },
+ // 4.
+ {
+ {"file", create | write, create | write},
+ {"file", write | inode, write | inode},
+ {"file", remove | write | inode, remove},
+ },
+ {
+ {"file", remove | write | inode, remove},
+ },
+ }
+Test:
+ for i, cas := range cases {
+ if len(cas) == 0 {
+ t.Log("skipped")
+ continue
+ }
+ w := &watch{prev: make(map[string]uint32)}
+ for j, cas := range cas {
+ if diff := w.strip(cas.path, cas.flag); diff != cas.diff {
+ t.Errorf("want diff=%v; got %v (i=%d, j=%d)", Event(cas.diff),
+ Event(diff), i, j)
+ continue Test
+ }
+ }
+ }
+}
+
+// Test for cases 3) and 5) with shadowed write&create events.
+//
+// See comment for (flagdiff).diff method.
+func TestWatcherShadowedWriteCreate(t *testing.T) {
+ w := NewWatcherTest(t, "testdata/vfs.txt")
+ defer w.Close()
+
+ cases := [...]WCase{
+ // i=0
+ create(w, "src/github.com/rjeczalik/fs/.fs.go.swp"),
+ // i=1
+ write(w, "src/github.com/rjeczalik/fs/.fs.go.swp", []byte("XD")),
+ // i=2
+ write(w, "src/github.com/rjeczalik/fs/.fs.go.swp", []byte("XD")),
+ // i=3
+ remove(w, "src/github.com/rjeczalik/fs/.fs.go.swp"),
+ // i=4
+ create(w, "src/github.com/rjeczalik/fs/.fs.go.swp"),
+ // i=5
+ write(w, "src/github.com/rjeczalik/fs/.fs.go.swp", []byte("XD")),
+ }
+
+ w.ExpectAny(cases[:5]) // BUG(rjeczalik): #62
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watcher_inotify.go b/Godeps/_workspace/src/github.com/zillode/notify/watcher_inotify.go
new file mode 100644
index 0000000..8c0c27b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watcher_inotify.go
@@ -0,0 +1,396 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build linux
+
+package notify
+
+import (
+ "bytes"
+ "errors"
+ "path/filepath"
+ "runtime"
+ "sync"
+ "sync/atomic"
+ "syscall"
+ "unsafe"
+)
+
+// eventBufferSize defines the size of the buffer given to read(2) function. One
+// should not depend on this value, since it was arbitrary chosen and may be
+// changed in the future.
+const eventBufferSize = 64 * (syscall.SizeofInotifyEvent + syscall.PathMax + 1)
+
+// consumersCount defines the number of consumers in producer-consumer based
+// implementation. Each consumer is run in a separate goroutine and has read
+// access to watched files map.
+const consumersCount = 2
+
+const invalidDescriptor = -1
+
+// watched is a pair of file path and inotify mask used as a value in
+// watched files map.
+type watched struct {
+ path string
+ mask uint32
+}
+
+// inotify implements Watcher interface.
+type inotify struct {
+ sync.RWMutex // protects inotify.m map
+ m map[int32]*watched // watch descriptor to watched object
+ fd int32 // inotify file descriptor
+ pipefd []int // pipe's read and write descriptors
+ epfd int // epoll descriptor
+ epes []syscall.EpollEvent // epoll events
+ buffer [eventBufferSize]byte // inotify event buffer
+ wg sync.WaitGroup // wait group used to close main loop
+ c chan<- EventInfo // event dispatcher channel
+}
+
+// NewWatcher creates new non-recursive inotify backed by inotify.
+func newWatcher(c chan<- EventInfo) watcher {
+ i := &inotify{
+ m: make(map[int32]*watched),
+ fd: invalidDescriptor,
+ pipefd: []int{invalidDescriptor, invalidDescriptor},
+ epfd: invalidDescriptor,
+ epes: make([]syscall.EpollEvent, 0),
+ c: c,
+ }
+ runtime.SetFinalizer(i, func(i *inotify) {
+ i.epollclose()
+ if i.fd != invalidDescriptor {
+ syscall.Close(int(i.fd))
+ }
+ })
+ return i
+}
+
+// Watch implements notify.watcher interface.
+func (i *inotify) Watch(path string, e Event) error {
+ return i.watch(path, e)
+}
+
+// Rewatch implements notify.watcher interface.
+func (i *inotify) Rewatch(path string, _, newevent Event) error {
+ return i.watch(path, newevent)
+}
+
+// watch adds a new watcher to the set of watched objects or modifies the existing
+// one. If called for the first time, this function initializes inotify filesystem
+// monitor and starts producer-consumers goroutines.
+func (i *inotify) watch(path string, e Event) (err error) {
+ if e&^(All|Event(syscall.IN_ALL_EVENTS)) != 0 {
+ return errors.New("notify: unknown event")
+ }
+ if err = i.lazyinit(); err != nil {
+ return
+ }
+ iwd, err := syscall.InotifyAddWatch(int(i.fd), path, encode(e))
+ if err != nil {
+ return
+ }
+ i.RLock()
+ wd := i.m[int32(iwd)]
+ i.RUnlock()
+ if wd == nil {
+ i.Lock()
+ if i.m[int32(iwd)] == nil {
+ i.m[int32(iwd)] = &watched{path: path, mask: uint32(e)}
+ }
+ i.Unlock()
+ } else {
+ i.Lock()
+ wd.mask = uint32(e)
+ i.Unlock()
+ }
+ return nil
+}
+
+// lazyinit sets up all required file descriptors and starts 1+consumersCount
+// goroutines. The producer goroutine blocks until file-system notifications
+// occur. Then, all events are read from system buffer and sent to consumer
+// goroutines which construct valid notify events. This method uses
+// Double-Checked Locking optimization.
+func (i *inotify) lazyinit() error {
+ if atomic.LoadInt32(&i.fd) == invalidDescriptor {
+ i.Lock()
+ defer i.Unlock()
+ if atomic.LoadInt32(&i.fd) == invalidDescriptor {
+ fd, err := syscall.InotifyInit()
+ if err != nil {
+ return err
+ }
+ i.fd = int32(fd)
+ if err = i.epollinit(); err != nil {
+ _, _ = i.epollclose(), syscall.Close(int(fd)) // Ignore errors.
+ i.fd = invalidDescriptor
+ return err
+ }
+ esch := make(chan []*event)
+ go i.loop(esch)
+ i.wg.Add(consumersCount)
+ for n := 0; n < consumersCount; n++ {
+ go i.send(esch)
+ }
+ }
+ }
+ return nil
+}
+
+// epollinit opens an epoll file descriptor and creates a pipe which will be
+// used to wake up the epoll_wait(2) function. Then, file descriptor associated
+// with inotify event queue and the read end of the pipe are added to epoll set.
+// Note that `fd` member must be set before this function is called.
+func (i *inotify) epollinit() (err error) {
+ if i.epfd, err = syscall.EpollCreate(2); err != nil {
+ return
+ }
+ if err = syscall.Pipe(i.pipefd); err != nil {
+ return
+ }
+ i.epes = []syscall.EpollEvent{
+ {Events: syscall.EPOLLIN, Fd: i.fd},
+ {Events: syscall.EPOLLIN, Fd: int32(i.pipefd[0])},
+ }
+ if err = syscall.EpollCtl(i.epfd, syscall.EPOLL_CTL_ADD, int(i.fd), &i.epes[0]); err != nil {
+ return
+ }
+ return syscall.EpollCtl(i.epfd, syscall.EPOLL_CTL_ADD, i.pipefd[0], &i.epes[1])
+}
+
+// epollclose closes the file descriptor created by the call to epoll_create(2)
+// and two file descriptors opened by pipe(2) function.
+func (i *inotify) epollclose() (err error) {
+ if i.epfd != invalidDescriptor {
+ if err = syscall.Close(i.epfd); err == nil {
+ i.epfd = invalidDescriptor
+ }
+ }
+ for n, fd := range i.pipefd {
+ if fd != invalidDescriptor {
+ switch e := syscall.Close(fd); {
+ case e != nil && err == nil:
+ err = e
+ case e == nil:
+ i.pipefd[n] = invalidDescriptor
+ }
+ }
+ }
+ return
+}
+
+// loop blocks until either inotify or pipe file descriptor is ready for I/O.
+// All read operations triggered by filesystem notifications are forwarded to
+// one of the event's consumers. If pipe fd became ready, loop function closes
+// all file descriptors opened by lazyinit method and returns afterwards.
+func (i *inotify) loop(esch chan<- []*event) {
+ epes := make([]syscall.EpollEvent, 1)
+ fd := atomic.LoadInt32(&i.fd)
+ for {
+ switch _, err := syscall.EpollWait(i.epfd, epes, -1); err {
+ case nil:
+ switch epes[0].Fd {
+ case fd:
+ esch <- i.read()
+ epes[0].Fd = 0
+ case int32(i.pipefd[0]):
+ i.Lock()
+ defer i.Unlock()
+ if err = syscall.Close(int(fd)); err != nil && err != syscall.EINTR {
+ panic("notify: close(2) error " + err.Error())
+ }
+ atomic.StoreInt32(&i.fd, invalidDescriptor)
+ if err = i.epollclose(); err != nil && err != syscall.EINTR {
+ panic("notify: epollclose error " + err.Error())
+ }
+ close(esch)
+ return
+ }
+ case syscall.EINTR:
+ continue
+ default: // We should never reach this line.
+ panic("notify: epoll_wait(2) error " + err.Error())
+ }
+ }
+}
+
+// read reads events from an inotify file descriptor. It does not handle errors
+// returned from read(2) function since they are not critical to watcher logic.
+func (i *inotify) read() (es []*event) {
+ n, err := syscall.Read(int(i.fd), i.buffer[:])
+ if err != nil || n < syscall.SizeofInotifyEvent {
+ return
+ }
+ var sys *syscall.InotifyEvent
+ nmin := n - syscall.SizeofInotifyEvent
+ for pos, path := 0, ""; pos <= nmin; {
+ sys = (*syscall.InotifyEvent)(unsafe.Pointer(&i.buffer[pos]))
+ pos += syscall.SizeofInotifyEvent
+ if path = ""; sys.Len > 0 {
+ endpos := pos + int(sys.Len)
+ path = string(bytes.TrimRight(i.buffer[pos:endpos], "\x00"))
+ pos = endpos
+ }
+ es = append(es, &event{
+ sys: syscall.InotifyEvent{
+ Wd: sys.Wd,
+ Mask: sys.Mask,
+ Cookie: sys.Cookie,
+ },
+ path: path,
+ })
+ }
+ return
+}
+
+// send is a consumer function which sends events to event dispatcher channel.
+// It is run in a separate goroutine in order to not block loop method when
+// possibly expensive write operations are performed on inotify map.
+func (i *inotify) send(esch <-chan []*event) {
+ for es := range esch {
+ for _, e := range i.transform(es) {
+ if e != nil {
+ i.c <- e
+ }
+ }
+ }
+ i.wg.Done()
+}
+
+// transform prepares events read from inotify file descriptor for sending to
+// user. It removes invalid events and these which are no longer present in
+// inotify map. This method may also split one raw event into two different ones
+// when system-dependent result is required.
+func (i *inotify) transform(es []*event) []*event {
+ var multi []*event
+ i.RLock()
+ for idx, e := range es {
+ if e.sys.Mask&(syscall.IN_IGNORED|syscall.IN_Q_OVERFLOW) != 0 {
+ es[idx] = nil
+ continue
+ }
+ wd, ok := i.m[e.sys.Wd]
+ if !ok || e.sys.Mask&encode(Event(wd.mask)) == 0 {
+ es[idx] = nil
+ continue
+ }
+ if e.path == "" {
+ e.path = wd.path
+ } else {
+ e.path = filepath.Join(wd.path, e.path)
+ }
+ multi = append(multi, decode(Event(wd.mask), e))
+ if e.event == 0 {
+ es[idx] = nil
+ }
+ }
+ i.RUnlock()
+ es = append(es, multi...)
+ return es
+}
+
+// encode converts notify system-independent events to valid inotify mask
+// which can be passed to inotify_add_watch(2) function.
+func encode(e Event) uint32 {
+ if e&Create != 0 {
+ e = (e ^ Create) | InCreate | InMovedTo
+ }
+ if e&Remove != 0 {
+ e = (e ^ Remove) | InDelete | InDeleteSelf
+ }
+ if e&Write != 0 {
+ e = (e ^ Write) | InModify
+ }
+ if e&Rename != 0 {
+ e = (e ^ Rename) | InMovedFrom | InMoveSelf
+ }
+ return uint32(e)
+}
+
+// decode uses internally stored mask to distinguish whether system-independent
+// or system-dependent event is requested. The first one is created by modifying
+// `e` argument. decode method sets e.event value to 0 when an event should be
+// skipped. System-dependent event is set as the function's return value which
+// can be nil when the event should not be passed on.
+func decode(mask Event, e *event) (syse *event) {
+ if sysmask := uint32(mask) & e.sys.Mask; sysmask != 0 {
+ syse = &event{sys: syscall.InotifyEvent{
+ Wd: e.sys.Wd,
+ Mask: e.sys.Mask,
+ Cookie: e.sys.Cookie,
+ }, event: Event(sysmask), path: e.path}
+ }
+ imask := encode(mask)
+ switch {
+ case mask&Create != 0 && imask&uint32(InCreate|InMovedTo)&e.sys.Mask != 0:
+ e.event = Create
+ case mask&Remove != 0 && imask&uint32(InDelete|InDeleteSelf)&e.sys.Mask != 0:
+ e.event = Remove
+ case mask&Write != 0 && imask&uint32(InModify)&e.sys.Mask != 0:
+ e.event = Write
+ case mask&Rename != 0 && imask&uint32(InMovedFrom|InMoveSelf)&e.sys.Mask != 0:
+ e.event = Rename
+ default:
+ e.event = 0
+ }
+ return
+}
+
+// Unwatch implements notify.watcher interface. It looks for watch descriptor
+// related to registered path and if found, calls inotify_rm_watch(2) function.
+// This method is allowed to return EINVAL error when concurrently requested to
+// delete identical path.
+func (i *inotify) Unwatch(path string) (err error) {
+ iwd := int32(invalidDescriptor)
+ i.RLock()
+ for iwdkey, wd := range i.m {
+ if wd.path == path {
+ iwd = iwdkey
+ break
+ }
+ }
+ i.RUnlock()
+ if iwd == invalidDescriptor {
+ return errors.New("notify: path " + path + " is already watched")
+ }
+ fd := atomic.LoadInt32(&i.fd)
+ if _, err = syscall.InotifyRmWatch(int(fd), uint32(iwd)); err != nil {
+ return
+ }
+ i.Lock()
+ delete(i.m, iwd)
+ i.Unlock()
+ return nil
+}
+
+// Close implements notify.watcher interface. It removes all existing watch
+// descriptors and wakes up producer goroutine by sending data to the write end
+// of the pipe. The function waits for a signal from producer which means that
+// all operations on current monitoring instance are done.
+func (i *inotify) Close() (err error) {
+ i.Lock()
+ if fd := atomic.LoadInt32(&i.fd); fd == invalidDescriptor {
+ i.Unlock()
+ return nil
+ }
+ for iwd := range i.m {
+ if _, e := syscall.InotifyRmWatch(int(i.fd), uint32(iwd)); e != nil && err == nil {
+ err = e
+ }
+ delete(i.m, iwd)
+ }
+ switch _, errwrite := syscall.Write(i.pipefd[1], []byte{0x00}); {
+ case errwrite != nil && err == nil:
+ err = errwrite
+ fallthrough
+ case errwrite != nil:
+ i.Unlock()
+ default:
+ i.Unlock()
+ i.wg.Wait()
+ }
+ return
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watcher_inotify_test.go b/Godeps/_workspace/src/github.com/zillode/notify/watcher_inotify_test.go
new file mode 100644
index 0000000..5feadaf
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watcher_inotify_test.go
@@ -0,0 +1,132 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build linux
+
+package notify
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+)
+
+func icreate(w *W, path string) WCase {
+ cas := create(w, path)
+ cas.Events = append(cas.Events,
+ &Call{P: path, E: InCreate},
+ )
+ return cas
+}
+
+func iremove(w *W, path string) WCase {
+ cas := remove(w, path)
+ cas.Events = append(cas.Events,
+ &Call{P: path, E: InDelete},
+ )
+ return cas
+}
+
+func iopen(w *W, path string) WCase {
+ return WCase{
+ Action: func() {
+ f, err := os.OpenFile(filepath.Join(w.root, path), os.O_RDWR, 0644)
+ if err != nil {
+ w.Fatalf("OpenFile(%q)=%v", path, err)
+ }
+ if err := f.Close(); err != nil {
+ w.Fatalf("Close(%q)=%v", path, err)
+ }
+ },
+ Events: []EventInfo{
+ &Call{P: path, E: InAccess},
+ &Call{P: path, E: InOpen},
+ &Call{P: path, E: InCloseNowrite},
+ },
+ }
+}
+
+func iread(w *W, path string, p []byte) WCase {
+ return WCase{
+ Action: func() {
+ f, err := os.OpenFile(filepath.Join(w.root, path), os.O_RDWR, 0644)
+ if err != nil {
+ w.Fatalf("OpenFile(%q)=%v", path, err)
+ }
+ if _, err := f.Read(p); err != nil {
+ w.Fatalf("Read(%q)=%v", path, err)
+ }
+ if err := f.Close(); err != nil {
+ w.Fatalf("Close(%q)=%v", path, err)
+ }
+ },
+ Events: []EventInfo{
+ &Call{P: path, E: InAccess},
+ &Call{P: path, E: InOpen},
+ &Call{P: path, E: InModify},
+ &Call{P: path, E: InCloseNowrite},
+ },
+ }
+}
+
+func iwrite(w *W, path string, p []byte) WCase {
+ cas := write(w, path, p)
+ path = cas.Events[0].Path()
+ cas.Events = append(cas.Events,
+ &Call{P: path, E: InAccess},
+ &Call{P: path, E: InOpen},
+ &Call{P: path, E: InModify},
+ &Call{P: path, E: InCloseWrite},
+ )
+ return cas
+}
+
+func irename(w *W, path string) WCase {
+ const ext = ".notify"
+ return WCase{
+ Action: func() {
+ file := filepath.Join(w.root, path)
+ if err := os.Rename(file, file+ext); err != nil {
+ w.Fatalf("Rename(%q, %q)=%v", path, path+ext, err)
+ }
+ },
+ Events: []EventInfo{
+ &Call{P: path, E: InMovedFrom},
+ &Call{P: path + ext, E: InMovedTo},
+ &Call{P: path, E: InOpen},
+ &Call{P: path, E: InAccess},
+ &Call{P: path, E: InCreate},
+ },
+ }
+}
+
+var events = []Event{
+ InAccess,
+ InModify,
+ InAttrib,
+ InCloseWrite,
+ InCloseNowrite,
+ InOpen,
+ InMovedFrom,
+ InMovedTo,
+ InCreate,
+ InDelete,
+ InDeleteSelf,
+ InMoveSelf,
+}
+
+func TestWatcherInotify(t *testing.T) {
+ w := NewWatcherTest(t, "testdata/vfs.txt", events...)
+ defer w.Close()
+
+ cases := [...]WCase{
+ iopen(w, "src/github.com/rjeczalik/fs/fs.go"),
+ iwrite(w, "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
+ iread(w, "src/github.com/rjeczalik/fs/fs.go", []byte("XD")),
+ iremove(w, "src/github.com/ppknap/link/README.md"),
+ irename(w, "src/github.com/rjeczalik/fs/LICENSE"),
+ }
+
+ w.ExpectAny(cases[:])
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watcher_kqueue.go b/Godeps/_workspace/src/github.com/zillode/notify/watcher_kqueue.go
new file mode 100644
index 0000000..e7e2e4d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watcher_kqueue.go
@@ -0,0 +1,413 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,kqueue dragonfly freebsd netbsd openbsd
+
+package notify
+
+import (
+ "os"
+ "path/filepath"
+ "sync"
+ "syscall"
+)
+
+// newWatcher returns kqueue Watcher implementation.
+func newWatcher(c chan<- EventInfo) watcher {
+ k := &kqueue{
+ idLkp: make(map[int]*watched, 0),
+ pthLkp: make(map[string]*watched, 0),
+ c: c,
+ s: make(chan struct{}, 1),
+ }
+ if err := k.init(); err != nil {
+ panic(err)
+ }
+ go k.monitor()
+ return k
+}
+
+// Close closes all still open file descriptors and kqueue.
+func (k *kqueue) Close() (err error) {
+ // trigger event used to interrup Kevent call.
+ if _, err = syscall.Write(k.pipefds[1], []byte{0x00}); err != nil {
+ return
+ }
+ <-k.s
+ k.Lock()
+ var e error
+ for _, w := range k.idLkp {
+ if e = k.unwatch(w.p, w.fi); e != nil && err == nil {
+ dbgprintf("kqueue: unwatch %q failed: %q", w.p, e)
+ err = e
+ }
+ }
+ if e := error(syscall.Close(k.fd)); e != nil && err == nil {
+ dbgprintf("kqueue: closing kqueu fd failed: %q", e)
+ err = e
+ }
+ k.idLkp, k.pthLkp = nil, nil
+ k.Unlock()
+ return
+}
+
+// sendEvents sends reported events one by one through chan.
+func (k *kqueue) sendEvents(evn []event) {
+ for i := range evn {
+ k.c <- &evn[i]
+ }
+}
+
+// encode converts requested events to kqueue representation.
+func encode(e Event) (o uint32) {
+ o = uint32(e &^ Create)
+ if e&Write != 0 {
+ o = (o &^ uint32(Write)) | uint32(NoteWrite)
+ }
+ if e&Rename != 0 {
+ o = (o &^ uint32(Rename)) | uint32(NoteRename)
+ }
+ if e&Remove != 0 {
+ o = (o &^ uint32(Remove)) | uint32(NoteDelete)
+ }
+ return
+}
+
+// decode converts event received from kqueue to notify.Event
+// representation taking into account requested events (w).
+func decode(o uint32, w Event) (e Event) {
+ // TODO(someone) : fix me
+ if o&uint32(NoteWrite) != 0 {
+ if w&NoteWrite != 0 {
+ e |= NoteWrite
+ } else {
+ e |= Write
+ }
+ }
+ if o&uint32(NoteRename) != 0 {
+ if w&NoteRename != 0 {
+ e |= NoteRename
+ } else {
+ e |= Rename
+ }
+ }
+ if o&uint32(NoteDelete) != 0 {
+ if w&NoteDelete != 0 {
+ e |= NoteDelete
+ } else {
+ e |= Remove
+ }
+ }
+ e |= Event(o) & w
+ return
+}
+
+// del closes fd for watched and removes it from internal cache of monitored
+// files/directories.
+func (k *kqueue) del(w watched) {
+ syscall.Close(w.fd)
+ delete(k.idLkp, w.fd)
+ delete(k.pthLkp, w.p)
+}
+
+// monitor reads reported kqueue events and forwards them further after
+// performing additional processing. If read event concerns directory,
+// it generates Create/Remove event and sent them further instead of directory
+// event. This event is detected based on reading contents of analyzed
+// directory. If no changes in file list are detected, no event is send further.
+// Reading directory structure is less accurate than kqueue and can lead
+// to lack of detection of all events.
+func (k *kqueue) monitor() {
+ var (
+ kevn [1]syscall.Kevent_t
+ n int
+ err error
+ )
+ for {
+ kevn[0] = syscall.Kevent_t{}
+ switch n, err = syscall.Kevent(k.fd, nil, kevn[:], nil); {
+ case err == syscall.EINTR:
+ case err != nil:
+ dbgprintf("kqueue: failed to read events: %q\n", err)
+ case int(kevn[0].Ident) == k.pipefds[0]:
+ k.s <- struct{}{}
+ return
+ case n > 0:
+ k.sendEvents(k.process(kevn[0]))
+ }
+ }
+}
+
+func (k *kqueue) dir(w watched, kevn syscall.Kevent_t, e Event) (evn []event) {
+ // If it's dir and delete we have to send it and continue, because
+ // other processing relies on opening (in this case not existing) dir.
+ // Events for contents of this dir are reported by kqueue.
+ if (Event(kevn.Fflags) & NoteDelete) != 0 {
+ // Write is reported also for Remove on directory. Because of that
+ // we have to filter it out explicitly.
+ evn = append(evn, event{w.p,
+ e & ^Write & ^NoteWrite, Kevent{&kevn, w.fi}})
+ k.del(w)
+ return
+ }
+ if (Event(kevn.Fflags) & NoteWrite) != 0 {
+ switch err := k.walk(w.p, func(fi os.FileInfo) error {
+ p := filepath.Join(w.p, fi.Name())
+ switch err := k.singlewatch(p, w.eDir, false, fi); {
+ case os.IsNotExist(err) && ((w.eDir & Remove) != 0):
+ evn = append(evn, event{p, Remove, Kevent{nil, fi}})
+ case err == errAlreadyWatched:
+ case err != nil:
+ dbgprintf("kqueue: watching %q failed: %q", p, err)
+ case (w.eDir & Create) != 0:
+ evn = append(evn, event{p, Create, Kevent{nil, fi}})
+ }
+ return nil
+ }); {
+ // If file is already watched, kqueue will return remove event.
+ case os.IsNotExist(err):
+ return
+ case err != nil:
+ dbgprintf("kqueue: dir processing failed: %q", err)
+ default:
+ }
+ }
+ return
+}
+
+func (*kqueue) file(w watched, kevn syscall.Kevent_t, e Event) (evn []event) {
+ evn = append(evn, event{w.p, e, Kevent{&kevn, w.fi}})
+ return
+}
+
+// process event returned by Kevent call.
+func (k *kqueue) process(kevn syscall.Kevent_t) (evn []event) {
+ k.Lock()
+ w := k.idLkp[int(kevn.Ident)]
+ if w == nil {
+ k.Unlock()
+ dbgprintf("kqueue: %v event for not registered fd", kevn)
+ return
+ }
+ e := decode(kevn.Fflags, w.eDir|w.eNonDir)
+ if w.fi.IsDir() {
+ evn = k.dir(*w, kevn, e)
+ } else {
+ evn = k.file(*w, kevn, e)
+ }
+ if (Event(kevn.Fflags) & NoteDelete) != 0 {
+ k.del(*w)
+ }
+ k.Unlock()
+ return
+}
+
+// kqueue is a type holding data for kqueue watcher.
+type kqueue struct {
+ sync.Mutex
+ // fd is a kqueue file descriptor
+ fd int
+ // pipefds are file descriptors used to stop `Kevent` call.
+ pipefds [2]int
+ // idLkp is a data structure mapping file descriptors with data about watching
+ // represented by them files/directories.
+ idLkp map[int]*watched
+ // pthLkp is a data structure mapping file names with data about watching
+ // represented by them files/directories.
+ pthLkp map[string]*watched
+ // c is a channel used to pass events further.
+ c chan<- EventInfo
+ // s is a channel used to stop monitoring.
+ s chan struct{}
+}
+
+// watched is a data structure representing watched file/directory.
+type watched struct {
+ // p is a path to watched file/directory.
+ p string
+ // fd is a file descriptor for watched file/directory.
+ fd int
+ // fi provides information about watched file/dir.
+ fi os.FileInfo
+ // eDir represents events watched directly.
+ eDir Event
+ // eNonDir represents events watched indirectly.
+ eNonDir Event
+}
+
+// init initializes kqueue.
+func (k *kqueue) init() (err error) {
+ if k.fd, err = syscall.Kqueue(); err != nil {
+ return
+ }
+ // Creates pipe used to stop `Kevent` call by registering it,
+ // watching read end and writing to other end of it.
+ if err = syscall.Pipe(k.pipefds[:]); err != nil {
+ return
+ }
+ var kevn [1]syscall.Kevent_t
+ syscall.SetKevent(&kevn[0], k.pipefds[0], syscall.EVFILT_READ, syscall.EV_ADD)
+ _, err = syscall.Kevent(k.fd, kevn[:], nil, nil)
+ return
+}
+
+func (k *kqueue) watch(p string, e Event, fi os.FileInfo) error {
+ if err := k.singlewatch(p, e, true, fi); err != nil {
+ if err != errAlreadyWatched {
+ return nil
+ }
+ }
+ if fi.IsDir() {
+ err := k.walk(p, func(fi os.FileInfo) (err error) {
+ if err = k.singlewatch(filepath.Join(p, fi.Name()), e, false,
+ fi); err != nil {
+ if err != errAlreadyWatched {
+ return
+ }
+ }
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// watch starts to watch given p file/directory.
+func (k *kqueue) singlewatch(p string, e Event, direct bool,
+ fi os.FileInfo) error {
+ w, ok := k.pthLkp[p]
+ if !ok {
+ fd, err := syscall.Open(p, syscall.O_NONBLOCK|syscall.O_RDONLY, 0)
+ if err != nil {
+ return err
+ }
+ w = &watched{fd: fd, p: p, fi: fi}
+ }
+ if direct {
+ w.eDir |= e
+ } else {
+ w.eNonDir |= e
+ }
+ var kevn [1]syscall.Kevent_t
+ syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE,
+ syscall.EV_ADD|syscall.EV_CLEAR)
+ kevn[0].Fflags = encode(w.eDir | w.eNonDir)
+ if _, err := syscall.Kevent(k.fd, kevn[:], nil, nil); err != nil {
+ return err
+ }
+ if !ok {
+ k.idLkp[w.fd], k.pthLkp[w.p] = w, w
+ return nil
+ }
+ return errAlreadyWatched
+}
+
+// unwatch stops watching p file/directory.
+func (k *kqueue) singleunwatch(p string, direct bool) error {
+ w := k.pthLkp[p]
+ if w == nil {
+ return errNotWatched
+ }
+ if direct {
+ w.eDir = 0
+ } else {
+ w.eNonDir = 0
+ }
+ var kevn [1]syscall.Kevent_t
+ syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE, syscall.EV_DELETE)
+ if _, err := syscall.Kevent(k.fd, kevn[:], nil, nil); err != nil {
+ return err
+ }
+ if w.eNonDir&w.eDir != 0 {
+ if err := k.singlewatch(p, w.eNonDir|w.eDir, w.eNonDir == 0,
+ w.fi); err != nil {
+ return err
+ }
+ } else {
+ k.del(*w)
+ }
+ return nil
+}
+
+// walk runs f func on each file/dir from p directory.
+func (k *kqueue) walk(p string, f func(os.FileInfo) error) error {
+ fp, err := os.Open(p)
+ if err != nil {
+ return err
+ }
+ ls, err := fp.Readdir(0)
+ fp.Close()
+ if err != nil {
+ return err
+ }
+ for i := range ls {
+ if err := f(ls[i]); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func (k *kqueue) unwatch(p string, fi os.FileInfo) error {
+ if fi.IsDir() {
+ err := k.walk(p, func(fi os.FileInfo) error {
+ if !fi.IsDir() {
+ err := k.singleunwatch(filepath.Join(p, fi.Name()), false)
+ if err != errNotWatched {
+ return err
+ }
+ return nil
+ }
+ return nil
+ })
+ if err != nil {
+ return err
+ }
+ }
+ return k.singleunwatch(p, true)
+}
+
+// Watch implements Watcher interface.
+func (k *kqueue) Watch(p string, e Event) error {
+ fi, err := os.Stat(p)
+ if err != nil {
+ return err
+ }
+ k.Lock()
+ err = k.watch(p, e, fi)
+ k.Unlock()
+ return nil
+}
+
+// Unwatch implements Watcher interface.
+func (k *kqueue) Unwatch(p string) error {
+ fi, err := os.Stat(p)
+ if err != nil {
+ return err
+ }
+ k.Lock()
+ err = k.unwatch(p, fi)
+ k.Unlock()
+ return nil
+}
+
+// Rewatch implements Watcher interface.
+//
+// TODO(rjeczalik): This is a naive hack. Rewrite might help.
+func (k *kqueue) Rewatch(p string, _, e Event) error {
+ fi, err := os.Stat(p)
+ if err != nil {
+ return err
+ }
+ k.Lock()
+ if err = k.unwatch(p, fi); err == nil {
+ // TODO(rjeczalik): If watch fails then we leave kqueue in inconsistent
+ // state. Handle? Panic? Native version of rewatch?
+ err = k.watch(p, e, fi)
+ }
+ k.Unlock()
+ return nil
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watcher_kqueue_test.go b/Godeps/_workspace/src/github.com/zillode/notify/watcher_kqueue_test.go
new file mode 100644
index 0000000..b4a57e8
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watcher_kqueue_test.go
@@ -0,0 +1,72 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build ignore
+
+// TODO(rjeczalik): rewrite with test_test.go
+
+package notify_test
+
+import (
+ "os"
+ "os/exec"
+ "testing"
+
+ "github.com/rjeczalik/notify"
+ "github.com/rjeczalik/notify/test"
+)
+
+var kqueueActions = test.Actions{
+ notify.NoteAttrib: func(p string) (err error) {
+ _, err = exec.Command("touch", p).CombinedOutput()
+ return
+ },
+ notify.NoteExtend: func(p string) (err error) {
+ //TODO: Extend and no Write
+ var f *os.File
+ if f, err = os.OpenFile(p, os.O_RDWR|os.O_APPEND, 0755); err != nil {
+ return
+ }
+ defer f.Close()
+ if _, err = f.WriteString(" "); err != nil {
+ return
+ }
+ return
+ },
+ notify.NoteLink: func(p string) error {
+ return os.Link(p, p+"link")
+ },
+ /*TODO: notify.NoteRevoke: func(p string) error {
+ if err := syscall.Revoke(p); err != nil {
+ fmt.Printf("this is the error %v\n", err)
+ return err
+ }
+ return nil
+ },*/
+}
+
+func ExpectKqueueEvents(t *testing.T, wr notify.watcher, e notify.Event,
+ ei map[notify.EventInfo][]notify.Event) {
+ w := test.W(t, kqueueActions)
+ defer w.Close()
+ if err := w.WatchAll(wr, e); err != nil {
+ t.Fatal(err)
+ }
+ defer w.UnwatchAll(wr)
+ w.ExpectEvents(wr, ei)
+}
+
+func TestKqueue(t *testing.T) {
+ ei := map[notify.EventInfo][]notify.Event{
+ test.EI("github.com/rjeczalik/fs/fs.go",
+ notify.NoteAttrib): {notify.NoteAttrib},
+ test.EI("github.com/rjeczalik/fs/fs.go",
+ notify.NoteExtend): {notify.NoteExtend | notify.NoteWrite},
+ test.EI("github.com/rjeczalik/fs/fs.go",
+ notify.NoteLink): {notify.NoteLink},
+ }
+ ExpectKqueueEvents(t, notify.NewWatcher(), notify.NoteDelete|
+ notify.NoteWrite|notify.NoteExtend|notify.NoteAttrib|notify.NoteLink|
+ notify.NoteRename|notify.NoteRevoke, ei)
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watcher_readdcw.go b/Godeps/_workspace/src/github.com/zillode/notify/watcher_readdcw.go
new file mode 100644
index 0000000..e8359bb
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watcher_readdcw.go
@@ -0,0 +1,574 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify
+
+import (
+ "errors"
+ "runtime"
+ "sync"
+ "sync/atomic"
+ "syscall"
+ "unsafe"
+)
+
+// readBufferSize defines the size of an array in which read statuses are stored.
+// The buffer have to be DWORD-aligned and, if notify is used in monitoring a
+// directory over the network, its size must not be greater than 64KB. Each of
+// watched directories uses its own buffer for storing events.
+const readBufferSize = 4096
+
+// Since all operations which go through the Windows completion routine are done
+// asynchronously, filter may set one of the constants belor. They were defined
+// in order to distinguish whether current folder should be re-registered in
+// ReadDirectoryChangesW function or some control operations need to be executed.
+const (
+ stateRewatch uint32 = 1 << (28 + iota)
+ stateUnwatch
+ stateCPClose
+)
+
+// Filter used in current implementation was split into four segments:
+// - bits 0-11 store ReadDirectoryChangesW filters,
+// - bits 12-19 store File notify actions,
+// - bits 20-27 store notify specific events and flags,
+// - bits 28-31 store states which are used in loop's FSM.
+// Constants below are used as masks to retrieve only specific filter parts.
+const (
+ onlyNotifyChanges uint32 = 0x00000FFF
+ onlyNGlobalEvents uint32 = 0x0FF00000
+ onlyMachineStates uint32 = 0xF0000000
+)
+
+// grip represents a single watched directory. It stores the data required by
+// ReadDirectoryChangesW function. Only the filter, recursive, and handle members
+// may by modified by watcher implementation. Rest of the them have to remain
+// constant since they are used by Windows completion routine. This indicates that
+// grip can be removed only when all operations on the file handle are finished.
+type grip struct {
+ handle syscall.Handle
+ filter uint32
+ recursive bool
+ pathw []uint16
+ buffer [readBufferSize]byte
+ parent *watched
+ ovlapped *overlappedEx
+}
+
+// overlappedEx stores information used in asynchronous input and output.
+// Additionally, overlappedEx contains a pointer to 'grip' item which is used in
+// order to gather the structure in which the overlappedEx object was created.
+type overlappedEx struct {
+ syscall.Overlapped
+ parent *grip
+}
+
+// newGrip creates a new file handle that can be used in overlapped operations.
+// Then, the handle is associated with I/O completion port 'cph' and its value
+// is stored in newly created 'grip' object.
+func newGrip(cph syscall.Handle, parent *watched, filter uint32) (*grip, error) {
+ g := &grip{
+ handle: syscall.InvalidHandle,
+ filter: filter,
+ recursive: parent.recursive,
+ pathw: parent.pathw,
+ parent: parent,
+ ovlapped: &overlappedEx{},
+ }
+ if err := g.register(cph); err != nil {
+ return nil, err
+ }
+ g.ovlapped.parent = g
+ return g, nil
+}
+
+// NOTE : Thread safe
+func (g *grip) register(cph syscall.Handle) (err error) {
+ if g.handle, err = syscall.CreateFile(
+ &g.pathw[0],
+ syscall.FILE_LIST_DIRECTORY,
+ syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
+ nil,
+ syscall.OPEN_EXISTING,
+ syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED,
+ 0,
+ ); err != nil {
+ return
+ }
+ if _, err = syscall.CreateIoCompletionPort(g.handle, cph, 0, 0); err != nil {
+ syscall.CloseHandle(g.handle)
+ return
+ }
+ return g.readDirChanges()
+}
+
+// readDirChanges tells the system to store file change information in grip's
+// buffer. Directory changes that occur between calls to this function are added
+// to the buffer and then, returned with the next call.
+func (g *grip) readDirChanges() error {
+ return syscall.ReadDirectoryChanges(
+ g.handle,
+ &g.buffer[0],
+ uint32(unsafe.Sizeof(g.buffer)),
+ g.recursive,
+ encode(g.filter),
+ nil,
+ (*syscall.Overlapped)(unsafe.Pointer(g.ovlapped)),
+ 0,
+ )
+}
+
+// encode transforms a generic filter, which contains platform independent and
+// implementation specific bit fields, to value that can be used as NotifyFilter
+// parameter in ReadDirectoryChangesW function.
+func encode(filter uint32) uint32 {
+ e := Event(filter & (onlyNGlobalEvents | onlyNotifyChanges))
+ if e&dirmarker != 0 {
+ return uint32(FileNotifyChangeDirName)
+ }
+ if e&Create != 0 {
+ e = (e ^ Create) | FileNotifyChangeFileName
+ }
+ if e&Remove != 0 {
+ e = (e ^ Remove) | FileNotifyChangeFileName
+ }
+ if e&Write != 0 {
+ e = (e ^ Write) | FileNotifyChangeAttributes | FileNotifyChangeSize |
+ FileNotifyChangeCreation | FileNotifyChangeSecurity
+ }
+ if e&Rename != 0 {
+ e = (e ^ Rename) | FileNotifyChangeFileName
+ }
+ return uint32(e)
+}
+
+// watched is made in order to check whether an action comes from a directory or
+// file. This approach requires two file handlers per single monitored folder. The
+// second grip handles actions which include creating or deleting a directory. If
+// these processes are not monitored, only the first grip is created.
+type watched struct {
+ filter uint32
+ recursive bool
+ count uint8
+ pathw []uint16
+ digrip [2]*grip
+}
+
+// newWatched creates a new watched instance. It splits the filter variable into
+// two parts. The first part is responsible for watching all events which can be
+// created for a file in watched directory structure and the second one watches
+// only directory Create/Remove actions. If all operations succeed, the Create
+// message is sent to I/O completion port queue for further processing.
+func newWatched(cph syscall.Handle, filter uint32, recursive bool,
+ path string) (wd *watched, err error) {
+ wd = &watched{
+ filter: filter,
+ recursive: recursive,
+ }
+ if wd.pathw, err = syscall.UTF16FromString(path); err != nil {
+ return
+ }
+ if err = wd.recreate(cph); err != nil {
+ return
+ }
+ return wd, nil
+}
+
+// TODO : doc
+func (wd *watched) recreate(cph syscall.Handle) (err error) {
+ filefilter := wd.filter &^ uint32(FileNotifyChangeDirName)
+ if err = wd.updateGrip(0, cph, filefilter == 0, filefilter); err != nil {
+ return
+ }
+ dirfilter := wd.filter & uint32(FileNotifyChangeDirName|Create|Remove)
+ if err = wd.updateGrip(1, cph, dirfilter == 0, wd.filter|uint32(dirmarker)); err != nil {
+ return
+ }
+ wd.filter &^= onlyMachineStates
+ return
+}
+
+// TODO : doc
+func (wd *watched) updateGrip(idx int, cph syscall.Handle, reset bool,
+ newflag uint32) (err error) {
+ if reset {
+ wd.digrip[idx] = nil
+ } else {
+ if wd.digrip[idx] == nil {
+ if wd.digrip[idx], err = newGrip(cph, wd, newflag); err != nil {
+ wd.closeHandle()
+ return
+ }
+ } else {
+ wd.digrip[idx].filter = newflag
+ wd.digrip[idx].recursive = wd.recursive
+ if err = wd.digrip[idx].register(cph); err != nil {
+ wd.closeHandle()
+ return
+ }
+ }
+ wd.count++
+ }
+ return
+}
+
+// closeHandle closes handles that are stored in digrip array. Function always
+// tries to close all of the handlers before it exits, even when there are errors
+// returned from the operating system kernel.
+func (wd *watched) closeHandle() (err error) {
+ for _, g := range wd.digrip {
+ if g != nil && g.handle != syscall.InvalidHandle {
+ switch suberr := syscall.CloseHandle(g.handle); {
+ case suberr == nil:
+ g.handle = syscall.InvalidHandle
+ case err == nil:
+ err = suberr
+ }
+ }
+ }
+ return
+}
+
+// watcher implements Watcher interface. It stores a set of watched directories.
+// All operations which remove watched objects from map `m` must be performed in
+// loop goroutine since these structures are used internally by operating system.
+type readdcw struct {
+ sync.Mutex
+ m map[string]*watched
+ cph syscall.Handle
+ start bool
+ wg sync.WaitGroup
+ c chan<- EventInfo
+}
+
+// NewWatcher creates new non-recursive watcher backed by ReadDirectoryChangesW.
+func newWatcher(c chan<- EventInfo) watcher {
+ r := &readdcw{
+ m: make(map[string]*watched),
+ cph: syscall.InvalidHandle,
+ c: c,
+ }
+ runtime.SetFinalizer(r, func(r *readdcw) {
+ if r.cph != syscall.InvalidHandle {
+ syscall.CloseHandle(r.cph)
+ }
+ })
+ return r
+}
+
+// Watch implements notify.Watcher interface.
+func (r *readdcw) Watch(path string, event Event) error {
+ return r.watch(path, event, false)
+}
+
+// RecursiveWatch implements notify.RecursiveWatcher interface.
+func (r *readdcw) RecursiveWatch(path string, event Event) error {
+ return r.watch(path, event, true)
+}
+
+// watch inserts a directory to the group of watched folders. If watched folder
+// already exists, function tries to rewatch it with new filters(NOT VALID). Moreover,
+// watch starts the main event loop goroutine when called for the first time.
+func (r *readdcw) watch(path string, event Event, recursive bool) (err error) {
+ if event&^(All|fileNotifyChangeAll) != 0 {
+ return errors.New("notify: unknown event")
+ }
+ r.Lock()
+ wd, ok := r.m[path]
+ r.Unlock()
+ if !ok {
+ if err = r.lazyinit(); err != nil {
+ return
+ }
+ r.Lock()
+ if wd, ok = r.m[path]; ok {
+ r.Unlock()
+ return
+ }
+ if wd, err = newWatched(r.cph, uint32(event), recursive, path); err != nil {
+ r.Unlock()
+ return
+ }
+ r.m[path] = wd
+ r.Unlock()
+ }
+ return nil
+}
+
+// lazyinit creates an I/O completion port and starts the main event processing
+// loop. This method uses Double-Checked Locking optimization.
+func (r *readdcw) lazyinit() (err error) {
+ invalid := uintptr(syscall.InvalidHandle)
+ if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid {
+ r.Lock()
+ defer r.Unlock()
+ if atomic.LoadUintptr((*uintptr)(&r.cph)) == invalid {
+ cph := syscall.InvalidHandle
+ if cph, err = syscall.CreateIoCompletionPort(cph, 0, 0, 0); err != nil {
+ return
+ }
+ r.cph, r.start = cph, true
+ go r.loop()
+ }
+ }
+ return
+}
+
+// TODO(pknap) : doc
+func (r *readdcw) loop() {
+ var n, key uint32
+ var overlapped *syscall.Overlapped
+ for {
+ err := syscall.GetQueuedCompletionStatus(r.cph, &n, &key, &overlapped, syscall.INFINITE)
+ if key == stateCPClose {
+ r.Lock()
+ handle := r.cph
+ r.cph = syscall.InvalidHandle
+ r.Unlock()
+ syscall.CloseHandle(handle)
+ r.wg.Done()
+ return
+ }
+ if overlapped == nil {
+ // TODO: check key == rewatch delete or 0(panic)
+ continue
+ }
+ overEx := (*overlappedEx)(unsafe.Pointer(overlapped))
+ if n == 0 {
+ r.loopstate(overEx)
+ } else {
+ r.loopevent(n, overEx)
+ if err = overEx.parent.readDirChanges(); err != nil {
+ // TODO: error handling
+ }
+ }
+ }
+}
+
+// TODO(pknap) : doc
+func (r *readdcw) loopstate(overEx *overlappedEx) {
+ filter := atomic.LoadUint32(&overEx.parent.parent.filter)
+ if filter&onlyMachineStates == 0 {
+ return
+ }
+ if overEx.parent.parent.count--; overEx.parent.parent.count == 0 {
+ switch filter & onlyMachineStates {
+ case stateRewatch:
+ r.Lock()
+ overEx.parent.parent.recreate(r.cph)
+ r.Unlock()
+ case stateUnwatch:
+ r.Lock()
+ delete(r.m, syscall.UTF16ToString(overEx.parent.pathw))
+ r.Unlock()
+ case stateCPClose:
+ default:
+ panic(`notify: windows loopstate logic error`)
+ }
+ }
+}
+
+// TODO(pknap) : doc
+func (r *readdcw) loopevent(n uint32, overEx *overlappedEx) {
+ events := []*event{}
+ var currOffset uint32
+ for {
+ raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&overEx.parent.buffer[currOffset]))
+ name := syscall.UTF16ToString((*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))[:raw.FileNameLength>>1])
+ events = append(events, &event{
+ pathw: overEx.parent.pathw,
+ filter: overEx.parent.filter,
+ action: raw.Action,
+ name: name,
+ })
+ if raw.NextEntryOffset == 0 {
+ break
+ }
+ if currOffset += raw.NextEntryOffset; currOffset >= n {
+ break
+ }
+ }
+ r.send(events)
+}
+
+// TODO(pknap) : doc
+func (r *readdcw) send(es []*event) {
+ for _, e := range es {
+ var syse Event
+ if e.e, syse = decode(e.filter, e.action); e.e == 0 && syse == 0 {
+ continue
+ }
+ switch {
+ case e.action == syscall.FILE_ACTION_MODIFIED:
+ e.ftype = fTypeUnknown
+ case e.filter&uint32(dirmarker) != 0:
+ e.ftype = fTypeDirectory
+ default:
+ e.ftype = fTypeFile
+ }
+ switch {
+ case e.e == 0:
+ e.e = syse
+ case syse != 0:
+ r.c <- &event{
+ pathw: e.pathw,
+ name: e.name,
+ ftype: e.ftype,
+ action: e.action,
+ filter: e.filter,
+ e: syse,
+ }
+ }
+ r.c <- e
+ }
+}
+
+// Rewatch implements notify.Rewatcher interface.
+func (r *readdcw) Rewatch(path string, oldevent, newevent Event) error {
+ return r.rewatch(path, uint32(oldevent), uint32(newevent), false)
+}
+
+// RecursiveRewatch implements notify.RecursiveRewatcher interface.
+func (r *readdcw) RecursiveRewatch(oldpath, newpath string, oldevent,
+ newevent Event) error {
+ if oldpath != newpath {
+ if err := r.unwatch(oldpath); err != nil {
+ return err
+ }
+ return r.watch(newpath, newevent, true)
+ }
+ return r.rewatch(newpath, uint32(oldevent), uint32(newevent), true)
+}
+
+// TODO : (pknap) doc.
+func (r *readdcw) rewatch(path string, oldevent, newevent uint32, recursive bool) (err error) {
+ if Event(newevent)&^(All|fileNotifyChangeAll) != 0 {
+ return errors.New("notify: unknown event")
+ }
+ var wd *watched
+ r.Lock()
+ if wd, err = r.nonStateWatched(path); err != nil {
+ r.Unlock()
+ return
+ }
+ if wd.filter&(onlyNotifyChanges|onlyNGlobalEvents) != oldevent {
+ panic(`notify: windows re-watcher logic error`)
+ }
+ wd.filter = stateRewatch | newevent
+ wd.recursive, recursive = recursive, wd.recursive
+ if err = wd.closeHandle(); err != nil {
+ wd.filter = oldevent
+ wd.recursive = recursive
+ r.Unlock()
+ return
+ }
+ r.Unlock()
+ return
+}
+
+// TODO : pknap
+func (r *readdcw) nonStateWatched(path string) (wd *watched, err error) {
+ wd, ok := r.m[path]
+ if !ok || wd == nil {
+ err = errors.New(`notify: ` + path + ` path is unwatched`)
+ return
+ }
+ if filter := atomic.LoadUint32(&wd.filter); filter&onlyMachineStates != 0 {
+ err = errors.New(`notify: another re/unwatching operation in progress`)
+ return
+ }
+ return
+}
+
+// Unwatch implements notify.Watcher interface.
+func (r *readdcw) Unwatch(path string) error {
+ return r.unwatch(path)
+}
+
+// RecursiveUnwatch implements notify.RecursiveWatcher interface.
+func (r *readdcw) RecursiveUnwatch(path string) error {
+ return r.unwatch(path)
+}
+
+// TODO : pknap
+func (r *readdcw) unwatch(path string) (err error) {
+ var wd *watched
+ r.Lock()
+ if wd, err = r.nonStateWatched(path); err != nil {
+ r.Unlock()
+ return
+ }
+ wd.filter |= stateUnwatch
+ if err = wd.closeHandle(); err != nil {
+ wd.filter &^= stateUnwatch
+ r.Unlock()
+ return
+ }
+ r.Unlock()
+ return
+}
+
+// Close resets the whole watcher object, closes all existing file descriptors,
+// and sends stateCPClose state as completion key to the main watcher's loop.
+func (r *readdcw) Close() (err error) {
+ r.Lock()
+ if !r.start {
+ r.Unlock()
+ return nil
+ }
+ for _, wd := range r.m {
+ wd.filter &^= onlyMachineStates
+ wd.filter |= stateCPClose
+ if e := wd.closeHandle(); e != nil && err == nil {
+ err = e
+ }
+ }
+ r.start = false
+ r.Unlock()
+ r.wg.Add(1)
+ if e := syscall.PostQueuedCompletionStatus(r.cph, 0, stateCPClose, nil); e != nil && err == nil {
+ return e
+ }
+ r.wg.Wait()
+ return
+}
+
+// decode creates a notify event from both non-raw filter and action which was
+// returned from completion routine. Function may return Event(0) in case when
+// filter was replaced by a new value which does not contain fields that are
+// valid with passed action.
+func decode(filter, action uint32) (Event, Event) {
+ switch action {
+ case syscall.FILE_ACTION_ADDED:
+ return gensys(filter, Create, FileActionAdded)
+ case syscall.FILE_ACTION_REMOVED:
+ return gensys(filter, Remove, FileActionRemoved)
+ case syscall.FILE_ACTION_MODIFIED:
+ return gensys(filter, Write, FileActionModified)
+ case syscall.FILE_ACTION_RENAMED_OLD_NAME:
+ return gensys(filter, Rename, FileActionRenamedOldName)
+ case syscall.FILE_ACTION_RENAMED_NEW_NAME:
+ return gensys(filter, Rename, FileActionRenamedNewName)
+ }
+ panic(`notify: cannot decode internal mask`)
+}
+
+// gensys decides whether the Windows action, system-independent event or both
+// of them should be returned. Since the grip's filter may be atomically changed
+// during watcher lifetime, it is possible that neither Windows nor notify masks
+// are watched by the user when this function is called.
+func gensys(filter uint32, ge, se Event) (gene, syse Event) {
+ isdir := filter&uint32(dirmarker) != 0
+ if isdir && filter&uint32(FileNotifyChangeDirName) != 0 ||
+ !isdir && filter&uint32(FileNotifyChangeFileName) != 0 ||
+ filter&uint32(fileNotifyChangeModified) != 0 {
+ syse = se
+ }
+ if filter&uint32(ge) != 0 {
+ gene = ge
+ }
+ return
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watcher_readdcw_test.go b/Godeps/_workspace/src/github.com/zillode/notify/watcher_readdcw_test.go
new file mode 100644
index 0000000..ea15b4a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watcher_readdcw_test.go
@@ -0,0 +1,67 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify
+
+import "testing"
+
+// TODO(ppknap) : remove notify.Create event.
+func rcreate(w *W, path string) WCase {
+ cas := create(w, path)
+ cas.Events = append(cas.Events,
+ &Call{P: path, E: FileActionAdded},
+ )
+ return cas
+}
+
+// TODO(ppknap) : remove notify.Remove event.
+func rremove(w *W, path string) WCase {
+ cas := remove(w, path)
+ cas.Events = append(cas.Events,
+ &Call{P: path, E: FileActionRemoved},
+ )
+ return cas
+}
+
+// TODO(ppknap) : remove notify.Rename event.
+func rrename(w *W, oldpath, newpath string) WCase {
+ cas := rename(w, oldpath, newpath)
+ cas.Events = append(cas.Events,
+ &Call{P: oldpath, E: FileActionRenamedOldName},
+ &Call{P: newpath, E: FileActionRenamedNewName},
+ )
+ return cas
+}
+
+// TODO(ppknap) : remove notify.Write event.
+func rwrite(w *W, path string, p []byte) WCase {
+ cas := write(w, path, p)
+ cas.Events = append(cas.Events,
+ &Call{P: path, E: FileActionModified},
+ )
+ return cas
+}
+
+var events = []Event{
+ FileNotifyChangeFileName,
+ FileNotifyChangeDirName,
+ FileNotifyChangeSize,
+}
+
+func TestWatcherReadDirectoryChangesW(t *testing.T) {
+ w := NewWatcherTest(t, "testdata/vfs.txt", events...)
+ defer w.Close()
+
+ cases := [...]WCase{
+ rcreate(w, "src/github.com/rjeczalik/fs/fs_windows.go"),
+ rcreate(w, "src/github.com/rjeczalik/fs/subdir/"),
+ rremove(w, "src/github.com/rjeczalik/fs/fs.go"),
+ rrename(w, "src/github.com/rjeczalik/fs/LICENSE", "src/github.com/rjeczalik/fs/COPYLEFT"),
+ rwrite(w, "src/github.com/rjeczalik/fs/cmd/gotree/go.go", []byte("XD")),
+ }
+
+ w.ExpectAny(cases[:])
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watcher_recursive_test.go b/Godeps/_workspace/src/github.com/zillode/notify/watcher_recursive_test.go
new file mode 100644
index 0000000..3b1f148
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watcher_recursive_test.go
@@ -0,0 +1,102 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin,!kqueue windows
+
+package notify
+
+import (
+ "fmt"
+ "testing"
+)
+
+// noevent stripts test-case from expected event list, used when action is not
+// expected to trigger any events.
+func noevent(cas WCase) WCase {
+ return WCase{Action: cas.Action}
+}
+
+func TestWatcherRecursiveRewatch(t *testing.T) {
+ w := newWatcherTest(t, "testdata/vfs.txt")
+ defer w.Close()
+
+ cases := []WCase{
+ create(w, "src/github.com/rjeczalik/file"),
+ create(w, "src/github.com/rjeczalik/dir/"),
+ noevent(create(w, "src/github.com/rjeczalik/fs/dir/")),
+ noevent(create(w, "src/github.com/dir/")),
+ noevent(write(w, "src/github.com/rjeczalik/file", []byte("XD"))),
+ noevent(rename(w, "src/github.com/rjeczalik/fs/LICENSE", "src/LICENSE")),
+ }
+
+ w.Watch("src/github.com/rjeczalik", Create)
+ w.ExpectAny(cases)
+
+ cases = []WCase{
+ create(w, "src/github.com/rjeczalik/fs/file"),
+ create(w, "src/github.com/rjeczalik/fs/cmd/gotree/file"),
+ create(w, "src/github.com/rjeczalik/fs/cmd/dir/"),
+ create(w, "src/github.com/rjeczalik/fs/cmd/gotree/dir/"),
+ noevent(write(w, "src/github.com/rjeczalik/fs/file", []byte("XD"))),
+ noevent(create(w, "src/github.com/anotherdir/")),
+ }
+
+ w.RecursiveRewatch("src/github.com/rjeczalik", "src/github.com/rjeczalik", Create, Create)
+ w.ExpectAny(cases)
+
+ cases = []WCase{
+ create(w, "src/github.com/rjeczalik/1"),
+ create(w, "src/github.com/rjeczalik/2/"),
+ noevent(create(w, "src/github.com/rjeczalik/fs/cmd/1")),
+ noevent(create(w, "src/github.com/rjeczalik/fs/1/")),
+ noevent(write(w, "src/github.com/rjeczalik/fs/file", []byte("XD"))),
+ }
+
+ w.Rewatch("src/github.com/rjeczalik", Create, Create)
+ w.ExpectAny(cases)
+}
+
+// TODO(rjeczalik): move to watcher_test.go after #5
+func TestIsDirCreateEvent(t *testing.T) {
+ w := NewWatcherTest(t, "testdata/vfs.txt")
+ defer w.Close()
+
+ cases := [...]WCase{
+ // i=0
+ create(w, "src/github.com/jszwec/"),
+ // i=1
+ create(w, "src/github.com/jszwec/gojunitxml/"),
+ // i=2
+ create(w, "src/github.com/jszwec/gojunitxml/README.md"),
+ // i=3
+ create(w, "src/github.com/jszwec/gojunitxml/LICENSE"),
+ // i=4
+ create(w, "src/github.com/jszwec/gojunitxml/cmd/"),
+ }
+
+ dirs := [...]bool{
+ true, // i=0
+ true, // i=1
+ false, // i=2
+ false, // i=3
+ true, // i=4
+ }
+
+ fn := func(i int, _ WCase, ei EventInfo) error {
+ d, ok := ei.(isDirer)
+ if !ok {
+ return fmt.Errorf("received EventInfo does not implement isDirer")
+ }
+ switch ok, err := d.isDir(); {
+ case err != nil:
+ return err
+ case ok != dirs[i]:
+ return fmt.Errorf("want ok=%v; got %v", dirs[i], ok)
+ default:
+ return nil
+ }
+ }
+
+ w.ExpectAnyFunc(cases[:], fn)
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watcher_stub.go b/Godeps/_workspace/src/github.com/zillode/notify/watcher_stub.go
new file mode 100644
index 0000000..df55102
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watcher_stub.go
@@ -0,0 +1,21 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build !darwin,!linux,!freebsd,!dragonfly,!netbsd,!openbsd,!windows
+// +build !kqueue
+
+package notify
+
+type stub struct{ error }
+
+// NewWatcher stub.
+func newWatcher(chan<- EventInfo) stub {
+ return stub{errors.New("notify: not implemented")}
+}
+
+// Following methods implement notify.watcher interface.
+func (s stub) Watch(string, Event) error { return s }
+func (s stub) Rewatch(string, Event, Event) error { return s }
+func (s stub) Unwatch(string) (err error) { return s }
+func (s stub) Close() error { return s }
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watcher_test.go b/Godeps/_workspace/src/github.com/zillode/notify/watcher_test.go
new file mode 100644
index 0000000..45633e1
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watcher_test.go
@@ -0,0 +1,32 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build darwin linux freebsd dragonfly netbsd openbsd windows
+
+package notify
+
+import "testing"
+
+// NOTE Set DEBUG env var for extra debugging info.
+
+func TestWatcher(t *testing.T) {
+ w := NewWatcherTest(t, "testdata/vfs.txt")
+ defer w.Close()
+
+ cases := [...]WCase{
+ create(w, "src/github.com/ppknap/link/include/coost/.link.hpp.swp"),
+ create(w, "src/github.com/rjeczalik/fs/fs_test.go"),
+ create(w, "src/github.com/rjeczalik/fs/binfs/"),
+ create(w, "src/github.com/rjeczalik/fs/binfs.go"),
+ create(w, "src/github.com/rjeczalik/fs/binfs_test.go"),
+ remove(w, "src/github.com/rjeczalik/fs/binfs/"),
+ create(w, "src/github.com/rjeczalik/fs/binfs/"),
+ create(w, "src/github.com/rjeczalik/fs/virfs"),
+ remove(w, "src/github.com/rjeczalik/fs/virfs"),
+ create(w, "file"),
+ create(w, "dir/"),
+ }
+
+ w.ExpectAny(cases[:])
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watchpoint.go b/Godeps/_workspace/src/github.com/zillode/notify/watchpoint.go
new file mode 100644
index 0000000..e034013
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watchpoint.go
@@ -0,0 +1,102 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+// EventDiff describes a change to an event set - EventDiff[0] is an old state,
+// while EventDiff[1] is a new state. If event set has not changed (old == new),
+// functions typically return the None value.
+type eventDiff [2]Event
+
+func (diff eventDiff) Event() Event {
+ return diff[1] &^ diff[0]
+}
+
+// Watchpoint
+//
+// The nil key holds total event set - logical sum for all registered events.
+// It speeds up computing EventDiff for Add method.
+//
+// The rec key holds an event set for a watchpoints created by RecursiveWatch
+// for a Watcher implementation which is not natively recursive.
+type watchpoint map[chan<- EventInfo]Event
+
+// None is an empty event diff, think null object.
+var none eventDiff
+
+// rec is just a placeholder
+var rec = func() (ch chan<- EventInfo) {
+ ch = make(chan<- EventInfo)
+ close(ch)
+ return
+}()
+
+func (wp watchpoint) dryAdd(ch chan<- EventInfo, e Event) eventDiff {
+ if e &^= internal; wp[ch]&e == e {
+ return none
+ }
+ total := wp[ch] &^ internal
+ return eventDiff{total, total | e}
+}
+
+// Add assumes neither c nor e are nil or zero values.
+func (wp watchpoint) Add(c chan<- EventInfo, e Event) (diff eventDiff) {
+ wp[c] |= e
+ diff[0] = wp[nil]
+ diff[1] = diff[0] | e
+ wp[nil] = diff[1] &^ omit
+ // Strip diff from internal events.
+ diff[0] &^= internal
+ diff[1] &^= internal
+ if diff[0] == diff[1] {
+ return none
+ }
+ return
+}
+
+func (wp watchpoint) Del(c chan<- EventInfo, e Event) (diff eventDiff) {
+ wp[c] &^= e
+ if wp[c] == 0 {
+ delete(wp, c)
+ }
+ diff[0] = wp[nil]
+ delete(wp, nil)
+ if len(wp) != 0 {
+ // Recalculate total event set.
+ for _, e := range wp {
+ diff[1] |= e
+ }
+ wp[nil] = diff[1] &^ omit
+ }
+ // Strip diff from internal events.
+ diff[0] &^= internal
+ diff[1] &^= internal
+ if diff[0] == diff[1] {
+ return none
+ }
+ return
+}
+
+func (wp watchpoint) Dispatch(ei EventInfo, extra Event) {
+ e := eventmask(ei, extra)
+ if !matches(wp[nil], e) {
+ return
+ }
+ for ch, eset := range wp {
+ if ch != nil && matches(eset, e) {
+ select {
+ case ch <- ei:
+ default: // Drop event if receiver is too slow
+ }
+ }
+ }
+}
+
+func (wp watchpoint) Total() Event {
+ return wp[nil] &^ internal
+}
+
+func (wp watchpoint) IsRecursive() bool {
+ return wp[nil]&recursive != 0
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watchpoint_other.go b/Godeps/_workspace/src/github.com/zillode/notify/watchpoint_other.go
new file mode 100644
index 0000000..881631c
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watchpoint_other.go
@@ -0,0 +1,23 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build !windows
+
+package notify
+
+// eventmask uses ei to create a new event which contains internal flags used by
+// notify package logic.
+func eventmask(ei EventInfo, extra Event) Event {
+ return ei.Event() | extra
+}
+
+// matches reports a match only when:
+//
+// - for user events, when event is present in the given set
+// - for internal events, when additionaly both event and set have omit bit set
+//
+// Internal events must not be sent to user channels and vice versa.
+func matches(set, event Event) bool {
+ return (set&omit)^(event&omit) == 0 && set&event == event
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watchpoint_readdcw.go b/Godeps/_workspace/src/github.com/zillode/notify/watchpoint_readdcw.go
new file mode 100644
index 0000000..9fd1e1d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watchpoint_readdcw.go
@@ -0,0 +1,38 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify
+
+// eventmask uses ei to create a new event which contains internal flags used by
+// notify package logic. If one of FileAction* masks is detected, this function
+// adds corresponding FileNotifyChange* values. This allows non registered
+// FileAction* events to be passed on.
+func eventmask(ei EventInfo, extra Event) (e Event) {
+ if e = ei.Event() | extra; e&fileActionAll != 0 {
+ if ev, ok := ei.(*event); ok {
+ switch ev.ftype {
+ case fTypeFile:
+ e |= FileNotifyChangeFileName
+ case fTypeDirectory:
+ e |= FileNotifyChangeDirName
+ case fTypeUnknown:
+ e |= fileNotifyChangeModified
+ }
+ return e &^ fileActionAll
+ }
+ }
+ return
+}
+
+// matches reports a match only when:
+//
+// - for user events, when event is present in the given set
+// - for internal events, when additionally both event and set have omit bit set
+//
+// Internal events must not be sent to user channels and vice versa.
+func matches(set, event Event) bool {
+ return (set&omit)^(event&omit) == 0 && (set&event == event || set&fileNotifyChangeModified&event != 0)
+}
diff --git a/Godeps/_workspace/src/github.com/zillode/notify/watchpoint_test.go b/Godeps/_workspace/src/github.com/zillode/notify/watchpoint_test.go
new file mode 100644
index 0000000..e35483d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/zillode/notify/watchpoint_test.go
@@ -0,0 +1,162 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+ "fmt"
+ "reflect"
+ "testing"
+)
+
+func call(wp watchpoint, fn interface{}, args []interface{}) eventDiff {
+ vals := []reflect.Value{reflect.ValueOf(wp)}
+ for _, arg := range args {
+ vals = append(vals, reflect.ValueOf(arg))
+ }
+ res := reflect.ValueOf(fn).Call(vals)
+ if n := len(res); n != 1 {
+ panic(fmt.Sprintf("unexpected len(res)=%d", n))
+ }
+ diff, ok := res[0].Interface().(eventDiff)
+ if !ok {
+ panic(fmt.Sprintf("want typeof(diff)=EventDiff; got %T", res[0].Interface()))
+ }
+ return diff
+}
+
+func TestWatchpoint(t *testing.T) {
+ ch := NewChans(5)
+ all := All | recursive
+ cases := [...]struct {
+ fn interface{}
+ args []interface{}
+ diff eventDiff
+ total Event
+ }{
+ // i=0
+ {
+ watchpoint.Add,
+ []interface{}{ch[0], Remove},
+ eventDiff{0, Remove},
+ Remove,
+ },
+ // i=1
+ {
+ watchpoint.Add,
+ []interface{}{ch[1], Create | Remove | recursive},
+ eventDiff{Remove, Remove | Create},
+ Create | Remove | recursive,
+ },
+ // i=2
+ {
+ watchpoint.Add,
+ []interface{}{ch[2], Create | Rename},
+ eventDiff{Create | Remove, Create | Remove | Rename},
+ Create | Remove | Rename | recursive,
+ },
+ // i=3
+ {
+ watchpoint.Add,
+ []interface{}{ch[0], Write | recursive},
+ eventDiff{Create | Remove | Rename, Create | Remove | Rename | Write},
+ Create | Remove | Rename | Write | recursive,
+ },
+ // i=4
+ {
+ watchpoint.Add,
+ []interface{}{ch[2], Remove | recursive},
+ none,
+ Create | Remove | Rename | Write | recursive,
+ },
+ // i=5
+ {
+ watchpoint.Del,
+ []interface{}{ch[0], all},
+ eventDiff{Create | Remove | Rename | Write, Create | Remove | Rename},
+ Create | Remove | Rename | recursive,
+ },
+ // i=6
+ {
+ watchpoint.Del,
+ []interface{}{ch[2], all},
+ eventDiff{Create | Remove | Rename, Create | Remove},
+ Create | Remove | recursive,
+ },
+ // i=7
+ {
+ watchpoint.Add,
+ []interface{}{ch[3], Create | Remove},
+ none,
+ Create | Remove | recursive,
+ },
+ // i=8
+ {
+ watchpoint.Del,
+ []interface{}{ch[1], all},
+ none,
+ Create | Remove,
+ },
+ // i=9
+ {
+ watchpoint.Add,
+ []interface{}{ch[3], recursive | Write},
+ eventDiff{Create | Remove, Create | Remove | Write},
+ Create | Remove | Write | recursive,
+ },
+ // i=10
+ {
+ watchpoint.Del,
+ []interface{}{ch[3], Create},
+ eventDiff{Create | Remove | Write, Remove | Write},
+ Remove | Write | recursive,
+ },
+ // i=11
+ {
+ watchpoint.Add,
+ []interface{}{ch[3], Create | Rename},
+ eventDiff{Remove | Write, Create | Remove | Rename | Write},
+ Create | Remove | Rename | Write | recursive,
+ },
+ // i=12
+ {
+ watchpoint.Add,
+ []interface{}{ch[2], Remove | Write},
+ none,
+ Create | Remove | Rename | Write | recursive,
+ },
+ // i=13
+ {
+ watchpoint.Del,
+ []interface{}{ch[3], Create | Remove | Write},
+ eventDiff{Create | Remove | Rename | Write, Remove | Rename | Write},
+ Remove | Rename | Write | recursive,
+ },
+ // i=14
+ {
+ watchpoint.Del,
+ []interface{}{ch[2], Remove},
+ eventDiff{Remove | Rename | Write, Rename | Write},
+ Rename | Write | recursive,
+ },
+ // i=15
+ {
+ watchpoint.Del,
+ []interface{}{ch[3], Rename | recursive},
+ eventDiff{Rename | Write, Write},
+ Write,
+ },
+ }
+ wp := watchpoint{}
+ for i, cas := range cases {
+ if diff := call(wp, cas.fn, cas.args); diff != cas.diff {
+ t.Errorf("want diff=%v; got %v (i=%d)", cas.diff, diff, i)
+ continue
+ }
+ if total := wp[nil]; total != cas.total {
+ t.Errorf("want total=%v; got %v (i=%d)", cas.total, total, i)
+ continue
+ }
+ }
+}
--
2.5.2