mirror of
https://github.com/syncthing/syncthing-android.git
synced 2024-12-26 12:51:28 +00:00
9174 lines
258 KiB
Diff
9174 lines
258 KiB
Diff
|
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¬ify.FSEventsIsFile != 0:
|
|||
|
+ kind = "file"
|
|||
|
+ case flags¬ify.FSEventsIsDir != 0:
|
|||
|
+ kind = "dir"
|
|||
|
+ case flags¬ify.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
|
|||
|
|