From 234b27eba916bde76babbd89a4dea43233d1f1be Mon Sep 17 00:00:00 2001 From: Lode Hoste Date: Sat, 16 Jan 2016 14:42:23 +0100 Subject: [PATCH] Remove Go 386 patches (fixed upstream since Go 1.6beta2) --- ...md-runtime-TLS-setup-for-android-386.patch | 267 - ...calls-needed-for-android-386-logging.patch | 54 - ...id-386-build-buildmode-pie-by-defaul.patch | 53 - ...w-buildmode-c-shared-for-android-386.patch | 99 - .../godeps/0001-add-godeps.patch | 9173 ----------------- make-go.bash | 4 - 6 files changed, 9650 deletions(-) delete mode 100644 ext/patches/golang/386/0001-cmd-runtime-TLS-setup-for-android-386.patch delete mode 100644 ext/patches/golang/386/0002-runtime-add-syscalls-needed-for-android-386-logging.patch delete mode 100644 ext/patches/golang/386/0003-cmd-enable-android-386-build-buildmode-pie-by-defaul.patch delete mode 100644 ext/patches/golang/386/0004-cmd-allow-buildmode-c-shared-for-android-386.patch delete mode 100644 ext/patches/syncthing-inotify/godeps/0001-add-godeps.patch diff --git a/ext/patches/golang/386/0001-cmd-runtime-TLS-setup-for-android-386.patch b/ext/patches/golang/386/0001-cmd-runtime-TLS-setup-for-android-386.patch deleted file mode 100644 index 2ab2e16f..00000000 --- a/ext/patches/golang/386/0001-cmd-runtime-TLS-setup-for-android-386.patch +++ /dev/null @@ -1,267 +0,0 @@ -From df553e646c597ab7fb26e3ffa2b730c04ab069f8 Mon Sep 17 00:00:00 2001 -From: Hyang-Ah Hana Kim -Date: Thu, 5 Nov 2015 16:32:27 -0500 -Subject: [PATCH 1/4] cmd,runtime: TLS setup for android/386 - -Same ugly hack as https://go-review.googlesource.com/15991. - -Update golang/go#9327. - -Change-Id: I58284e83268a15de95eabc833c3e01bf1e3faa2e ---- - src/cmd/internal/obj/x86/asm6.go | 3 ++ - src/cmd/link/internal/ld/sym.go | 15 +++++-- - src/runtime/cgo/gcc_android_386.c | 87 +++++++++++++++++++++++++++++++++++++++ - src/runtime/cgo/gcc_linux_386.c | 12 ++++++ - src/runtime/rt0_android_386.s | 39 ++++++++++++++++++ - src/runtime/sys_linux_386.s | 10 +++++ - 6 files changed, 162 insertions(+), 4 deletions(-) - create mode 100644 src/runtime/cgo/gcc_android_386.c - create mode 100644 src/runtime/rt0_android_386.s - -diff --git a/src/cmd/internal/obj/x86/asm6.go b/src/cmd/internal/obj/x86/asm6.go -index 919e00b..13ab240 100644 ---- a/src/cmd/internal/obj/x86/asm6.go -+++ b/src/cmd/internal/obj/x86/asm6.go -@@ -1975,6 +1975,9 @@ func prefixof(ctxt *obj.Link, p *obj.Prog, a *obj.Addr) int { - if p.Mode == 32 { - switch ctxt.Headtype { - default: -+ if isAndroid { -+ return 0x65 // GS -+ } - log.Fatalf("unknown TLS base register for %s", obj.Headstr(ctxt.Headtype)) - - case obj.Hdarwin, -diff --git a/src/cmd/link/internal/ld/sym.go b/src/cmd/link/internal/ld/sym.go -index da5776d..d91f138 100644 ---- a/src/cmd/link/internal/ld/sym.go -+++ b/src/cmd/link/internal/ld/sym.go -@@ -102,10 +102,17 @@ func linknew(arch *LinkArch) *Link { - obj.Hopenbsd, - obj.Hdragonfly, - obj.Hsolaris: -- if obj.Getgoos() == "android" && ctxt.Arch.Thechar == '6' { -- // Android/x86 constant - offset from 0(FS) to our -- // TLS slot. Explained in src/runtime/cgo/gcc_android_*.c -- ctxt.Tlsoffset = 0x1d0 -+ if obj.Getgoos() == "android" { -+ switch ctxt.Arch.Thechar { -+ case '6': -+ // Android/x86 constant - offset from 0(FS) to our -+ // TLS slot. Explained in src/runtime/cgo/gcc_android_*.c -+ ctxt.Tlsoffset = 0x1d0 -+ case '8': -+ ctxt.Tlsoffset = 0xf8 -+ default: -+ ctxt.Tlsoffset = -1 * ctxt.Arch.Ptrsize -+ } - } else { - ctxt.Tlsoffset = -1 * ctxt.Arch.Ptrsize - } -diff --git a/src/runtime/cgo/gcc_android_386.c b/src/runtime/cgo/gcc_android_386.c -new file mode 100644 -index 0000000..a82d7d0 ---- /dev/null -+++ b/src/runtime/cgo/gcc_android_386.c -@@ -0,0 +1,87 @@ -+// Copyright 2015 The Go Authors. All rights reserved. -+// Use of this source code is governed by a BSD-style -+// license that can be found in the LICENSE file. -+ -+#include /* for strerror */ -+#include -+#include -+#include "libcgo.h" -+ -+static void* threadentry(void*); -+static pthread_key_t k1; -+ -+#define magic1 (0x23581321U) -+ -+static void -+inittls(void) -+{ -+ uint32 x; -+ pthread_key_t tofree[128], k; -+ int i, ntofree; -+ -+ /* -+ * Same logic, code as gcc_android_amd64.c:/inittls. -+ * Note that this is a temporary hack that should be fixed soon. -+ * -+ * TODO: fix this. -+ * -+ * The linker and runtime hard-code this constant offset -+ * from %gs where we expect to find g. Disgusting. -+ * -+ * Known to src/cmd/link/internal/ld/sym.go:/0xf8 -+ * and to src/runtime/sys_linux_386.s:/0xf8 or /GOOS_android. -+ * TODO(hyangah): check 0xb0 works with API23+ -+ * -+ * As disgusting as on the darwin/386, darwin/amd64. -+ */ -+ ntofree = 0; -+ for(;;) { -+ if(pthread_key_create(&k, nil) < 0) { -+ fprintf(stderr, "runtime/cgo: pthread_key_create failed\n"); -+ abort(); -+ } -+ pthread_setspecific(k, (void*)magic1); -+ asm volatile("movl %%gs:0xf8, %0" : "=r"(x)); -+ pthread_setspecific(k, 0); -+ if (x == magic1) { -+ k1 = k; -+ break; -+ } -+ if(ntofree >= nelem(tofree)) { -+ fprintf(stderr, "runtime/cgo: could not obtain pthread_keys\n"); -+ fprintf(stderr, "\ttried"); -+ for(i=0; istacklo = (uintptr)&attr - size + 4096; - pthread_attr_destroy(&attr); -+ -+ if (x_cgo_inittls) { -+ x_cgo_inittls(); -+ } - } - - -@@ -57,6 +65,10 @@ _cgo_sys_thread_start(ThreadStart *ts) - static void* - threadentry(void *v) - { -+ if (x_cgo_threadentry) { -+ return x_cgo_threadentry(v); -+ } -+ - ThreadStart ts; - - ts = *(ThreadStart*)v; -diff --git a/src/runtime/rt0_android_386.s b/src/runtime/rt0_android_386.s -new file mode 100644 -index 0000000..bf44af3 ---- /dev/null -+++ b/src/runtime/rt0_android_386.s -@@ -0,0 +1,39 @@ -+// Copyright 2015 The Go Authors. All rights reserved. -+// Use of this source code is governed by a BSD-style -+// license that can be found in the LICENSE file. -+ -+#include "textflag.h" -+ -+TEXT _rt0_386_android(SB),NOSPLIT,$8 -+ MOVL 8(SP), AX // argc -+ LEAL 12(SP), BX // argv -+ MOVL AX, 0(SP) -+ MOVL BX, 4(SP) -+ CALL main(SB) -+ INT $3 -+ -+TEXT _rt0_386_android_lib(SB),NOSPLIT,$0 -+ PUSHL $_rt0_386_android_argv(SB) // argv -+ PUSHL $1 // argc -+ CALL _rt0_386_linux_lib(SB) -+ POPL AX -+ POPL AX -+ RET -+ -+DATA _rt0_386_android_argv+0x00(SB)/4,$_rt0_386_android_argv0(SB) -+DATA _rt0_386_android_argv+0x04(SB)/4,$0 -+DATA _rt0_386_android_argv+0x08(SB)/4,$0 -+DATA _rt0_386_android_argv+0x0c(SB)/4,$15 // AT_PLATFORM (auxvec.h) -+DATA _rt0_386_android_argv+0x10(SB)/4,$_rt0_386_android_auxv0(SB) -+DATA _rt0_386_android_argv+0x14(SB)/4,$0 -+DATA _rt0_386_android_argv+0x18(SB)/4,$0 -+DATA _rt0_386_android_argv+0x1c(SB)/4,$0 -+GLOBL _rt0_386_android_argv(SB),NOPTR,$0x20 -+ -+// TODO: AT_HWCAP necessary? If so, what value? -+ -+DATA _rt0_386_android_argv0(SB)/8, $"gojni" -+GLOBL _rt0_386_android_argv0(SB),RODATA,$8 -+ -+DATA _rt0_386_android_auxv0(SB)/8, $"i386" -+GLOBL _rt0_386_android_auxv0(SB),RODATA,$8 -diff --git a/src/runtime/sys_linux_386.s b/src/runtime/sys_linux_386.s -index 9e0e87c..a52c4b2 100644 ---- a/src/runtime/sys_linux_386.s -+++ b/src/runtime/sys_linux_386.s -@@ -405,6 +405,15 @@ TEXT runtime·setldt(SB),NOSPLIT,$32 - MOVL entry+0(FP), BX // entry - MOVL address+4(FP), CX // base address - -+#ifdef GOOS_android -+ /* -+ * Same as in sys_darwin_386.s:/ugliness, different constant. -+ * address currently holds m->tls, which must be %gs:0xf8. -+ * See cgo/gcc_android_386.c for the derivation of the constant. -+ */ -+ SUBL $0xf8, CX -+ MOVL CX, 0(CX) -+#else - /* - * When linking against the system libraries, - * we use its pthread_create and let it set up %gs -@@ -420,6 +429,7 @@ TEXT runtime·setldt(SB),NOSPLIT,$32 - */ - ADDL $0x4, CX // address - MOVL CX, 0(CX) -+#endif - - // set up user_desc - LEAL 16(SP), AX // struct user_desc --- -2.5.2 - diff --git a/ext/patches/golang/386/0002-runtime-add-syscalls-needed-for-android-386-logging.patch b/ext/patches/golang/386/0002-runtime-add-syscalls-needed-for-android-386-logging.patch deleted file mode 100644 index 70d976f7..00000000 --- a/ext/patches/golang/386/0002-runtime-add-syscalls-needed-for-android-386-logging.patch +++ /dev/null @@ -1,54 +0,0 @@ -From 4d38efc71f1a8733377cb0d0b06eeaee11ddb15c Mon Sep 17 00:00:00 2001 -From: Hyang-Ah Hana Kim -Date: Thu, 5 Nov 2015 16:35:24 -0500 -Subject: [PATCH 2/4] runtime: add syscalls needed for android/386 logging - -Update golang/go#9327. - -Change-Id: I27ef973190d9ae652411caf3739414b5d46ca7d2 ---- - src/runtime/sys_linux_386.s | 31 +++++++++++++++++++++++++++++++ - 1 file changed, 31 insertions(+) - -diff --git a/src/runtime/sys_linux_386.s b/src/runtime/sys_linux_386.s -index a52c4b2..eaf1d1e 100644 ---- a/src/runtime/sys_linux_386.s -+++ b/src/runtime/sys_linux_386.s -@@ -518,3 +518,34 @@ TEXT runtime·closeonexec(SB),NOSPLIT,$0 - MOVL $1, DX // FD_CLOEXEC - INVOKE_SYSINFO - RET -+ -+// int access(const char *name, int mode) -+TEXT runtime·access(SB),NOSPLIT,$0 -+ MOVL $33, AX // syscall - access -+ MOVL name+0(FP), BX -+ MOVL mode+4(FP), CX -+ INVOKE_SYSINFO -+ MOVL AX, ret+8(FP) -+ RET -+ -+// int connect(int fd, const struct sockaddr *addr, socklen_t addrlen) -+TEXT runtime·connect(SB),NOSPLIT,$0 -+ // connect is implemented as socketcall(NR_socket, 3, *(rest of args)) -+ // stack already should have fd, addr, addrlen. -+ MOVL $102, AX // syscall - socketcall -+ MOVL $3, BX // connect -+ LEAL fd+0(FP), CX -+ INVOKE_SYSINFO -+ MOVL AX, ret+12(FP) -+ RET -+ -+// int socket(int domain, int type, int protocol) -+TEXT runtime·socket(SB),NOSPLIT,$0 -+ // socket is implemented as socketcall(NR_socket, 1, *(rest of args)) -+ // stack already should have domain, type, protocol. -+ MOVL $102, AX // syscall - socketcall -+ MOVL $1, BX // socket -+ LEAL domain+0(FP), CX -+ INVOKE_SYSINFO -+ MOVL AX, ret+12(FP) -+ RET --- -2.5.2 - diff --git a/ext/patches/golang/386/0003-cmd-enable-android-386-build-buildmode-pie-by-defaul.patch b/ext/patches/golang/386/0003-cmd-enable-android-386-build-buildmode-pie-by-defaul.patch deleted file mode 100644 index 97bb7dc1..00000000 --- a/ext/patches/golang/386/0003-cmd-enable-android-386-build-buildmode-pie-by-defaul.patch +++ /dev/null @@ -1,53 +0,0 @@ -From 467be3ff84bd18698b2c7dce8695a210c19b705a Mon Sep 17 00:00:00 2001 -From: Hyang-Ah Hana Kim -Date: Thu, 5 Nov 2015 16:37:07 -0500 -Subject: [PATCH 3/4] cmd: enable android/386 build (buildmode=pie by default) - -no buildmode=c-shared yet. - -Update golang/go#9327. - -Change-Id: I9989d954d574807bac105da401c3463607fe8a99 ---- - src/cmd/compile/internal/gc/lex.go | 2 +- - src/cmd/go/build.go | 4 ++-- - 2 files changed, 3 insertions(+), 3 deletions(-) - -diff --git a/src/cmd/compile/internal/gc/lex.go b/src/cmd/compile/internal/gc/lex.go -index 9fe0f6c..31d41b4 100644 ---- a/src/cmd/compile/internal/gc/lex.go -+++ b/src/cmd/compile/internal/gc/lex.go -@@ -221,7 +221,7 @@ func Main() { - var flag_shared int - var flag_dynlink bool - switch Thearch.Thechar { -- case '5', '6', '7', '9': -+ case '5', '6', '7', '8', '9': - obj.Flagcount("shared", "generate code that can be linked into a shared library", &flag_shared) - } - if Thearch.Thechar == '6' { -diff --git a/src/cmd/go/build.go b/src/cmd/go/build.go -index e869b27..50d7a6d 100644 ---- a/src/cmd/go/build.go -+++ b/src/cmd/go/build.go -@@ -361,7 +361,7 @@ func buildModeInit() { - ldBuildmode = "c-shared" - case "default": - switch platform { -- case "android/arm", "android/amd64": -+ case "android/arm", "android/amd64", "android/386": - codegenArg = "-shared" - ldBuildmode = "pie" - default: -@@ -375,7 +375,7 @@ func buildModeInit() { - fatalf("-buildmode=pie not supported by gccgo") - } else { - switch platform { -- case "android/arm", "linux/amd64", "android/amd64": -+ case "android/arm", "linux/amd64", "android/amd64", "android/386": - codegenArg = "-shared" - default: - fatalf("-buildmode=pie not supported on %s\n", platform) --- -2.5.2 - diff --git a/ext/patches/golang/386/0004-cmd-allow-buildmode-c-shared-for-android-386.patch b/ext/patches/golang/386/0004-cmd-allow-buildmode-c-shared-for-android-386.patch deleted file mode 100644 index a2e663bd..00000000 --- a/ext/patches/golang/386/0004-cmd-allow-buildmode-c-shared-for-android-386.patch +++ /dev/null @@ -1,99 +0,0 @@ -From de38cf7c1a1e59009e917c3619ca1c6d1372b72c Mon Sep 17 00:00:00 2001 -From: Hyang-Ah Hana Kim -Date: Fri, 6 Nov 2015 17:28:58 -0500 -Subject: [PATCH 4/4] cmd: allow buildmode=c-shared for android/386 - -Update golang/go#9327. - -Change-Id: Iab7dad31cf6b9f9347c3f34faebb67ecb38b17fc ---- - misc/cgo/testcshared/test.bash | 4 +++- - src/cmd/dist/test.go | 4 +++- - src/cmd/go/build.go | 2 +- - src/cmd/link/internal/ld/lib.go | 24 +++++++++++++++++++++--- - 4 files changed, 28 insertions(+), 6 deletions(-) - -diff --git a/misc/cgo/testcshared/test.bash b/misc/cgo/testcshared/test.bash -index a641162..8963e4b 100755 ---- a/misc/cgo/testcshared/test.bash -+++ b/misc/cgo/testcshared/test.bash -@@ -81,7 +81,9 @@ GOPATH=$(pwd) go install -buildmode=c-shared $suffix libgo - GOPATH=$(pwd) go build -buildmode=c-shared $suffix -o libgo.$libext src/libgo/libgo.go - binpush libgo.$libext - --if [ "$goos" == "linux" ] || [ "$goos" == "android" ] ; then -+# TODO: fix text relocation bug in android/386. -+ -+if [ "$goos" == "linux" ] || [ "$goos" == "android" ] && [ "$goarch" != "386" ] ; then - if readelf -d libgo.$libext | grep TEXTREL >/dev/null; then - echo "libgo.$libext has TEXTREL set" - exit 1 -diff --git a/src/cmd/dist/test.go b/src/cmd/dist/test.go -index 6b0056a..8a7638c 100644 ---- a/src/cmd/dist/test.go -+++ b/src/cmd/dist/test.go -@@ -581,7 +581,9 @@ func (t *tester) supportedBuildmode(mode string) bool { - case "c-shared": - // TODO(hyangah): add linux-386. - switch pair { -- case "linux-amd64", "darwin-amd64", "android-arm", "linux-arm", "linux-arm64": -+ case "linux-amd64", "linux-arm", "linux-arm64", -+ "darwin-amd64", -+ "android-amd64", "android-386", "android-arm": - return true - } - return false -diff --git a/src/cmd/go/build.go b/src/cmd/go/build.go -index 50d7a6d..e7a9f13 100644 ---- a/src/cmd/go/build.go -+++ b/src/cmd/go/build.go -@@ -351,7 +351,7 @@ func buildModeInit() { - } else { - switch platform { - case "linux/amd64", "linux/arm", "linux/arm64", -- "android/amd64", "android/arm": -+ "android/amd64", "android/arm", "android/386": - codegenArg = "-shared" - case "darwin/amd64": - default: -diff --git a/src/cmd/link/internal/ld/lib.go b/src/cmd/link/internal/ld/lib.go -index f0e0511..d17825f 100644 ---- a/src/cmd/link/internal/ld/lib.go -+++ b/src/cmd/link/internal/ld/lib.go -@@ -317,12 +317,30 @@ func (mode *BuildMode) Set(s string) error { - } - *mode = BuildmodeCArchive - case "c-shared": -- if goarch != "amd64" && goarch != "arm" && goarch != "arm64" { -- return badmode() -+ switch goos { -+ case "android": -+ switch goarch { -+ case "386", "amd64", "arm", "arm64": -+ default: -+ return badmode() -+ } -+ default: -+ switch goarch { -+ case "amd64", "arm", "arm64": -+ default: -+ return badmode() -+ } - } - *mode = BuildmodeCShared - case "shared": -- if goos != "linux" || (goarch != "386" && goarch != "amd64" && goarch != "arm" && goarch != "arm64") { -+ switch goos { -+ case "linux": -+ switch goarch { -+ case "386", "amd64", "arm", "arm64": -+ default: -+ return badmode() -+ } -+ default: - return badmode() - } - *mode = BuildmodeShared --- -2.5.2 - diff --git a/ext/patches/syncthing-inotify/godeps/0001-add-godeps.patch b/ext/patches/syncthing-inotify/godeps/0001-add-godeps.patch deleted file mode 100644 index 91e6cd36..00000000 --- a/ext/patches/syncthing-inotify/godeps/0001-add-godeps.patch +++ /dev/null @@ -1,9173 +0,0 @@ -From cf0c69c5bc84cdcefe887245d4a093f41999caf2 Mon Sep 17 00:00:00 2001 -From: Andrew Sutherland -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 -+Pawel Knap -+Rafal Jeczalik -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 "" -+ } -+ 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 -+// -+// 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 - diff --git a/make-go.bash b/make-go.bash index 27df6718..e902cb9c 100755 --- a/make-go.bash +++ b/make-go.bash @@ -80,10 +80,6 @@ mkdir -p "$GOROOT_FINAL" pushd ext/golang/go/src -if [ "$GOARCH" = "386" ]; then - git am -3 ../../../patches/golang/386/* -fi - set +e ./clean.bash rm -r ../bin