From a05fa80470f83fde9d25bda3f3ade351e417f1e0 Mon Sep 17 00:00:00 2001 From: Lode Hoste Date: Fri, 13 Nov 2015 23:38:17 +0100 Subject: [PATCH 1/3] Use CGO to build Syncthing with support for DNS (fixes #57) --- .gitignore | 3 + .gitmodules | 6 + build-syncthing.sh | 78 - build.gradle | 22 +- ext/golang/go | 1 + ext/golang/go1.4 | 1 + ...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 +++++++++++++++++ .../src/github.com/syncthing/syncthing | 2 +- make-all.bash | 13 + make-go.bash | 115 + make-syncthing.bash | 96 + 15 files changed, 9883 insertions(+), 100 deletions(-) delete mode 100755 build-syncthing.sh create mode 160000 ext/golang/go create mode 160000 ext/golang/go1.4 create mode 100644 ext/patches/golang/386/0001-cmd-runtime-TLS-setup-for-android-386.patch create mode 100644 ext/patches/golang/386/0002-runtime-add-syscalls-needed-for-android-386-logging.patch create mode 100644 ext/patches/golang/386/0003-cmd-enable-android-386-build-buildmode-pie-by-defaul.patch create mode 100644 ext/patches/golang/386/0004-cmd-allow-buildmode-c-shared-for-android-386.patch create mode 100644 ext/patches/syncthing-inotify/godeps/0001-add-godeps.patch create mode 100755 make-all.bash create mode 100755 make-go.bash create mode 100755 make-syncthing.bash diff --git a/.gitignore b/.gitignore index ebec1dea..d14d352c 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,9 @@ proguard/ gradle/wrapper/gradle/ gradle/wrapper/gradlew* +# Go native dependencies +ext/golang/dist + # Syncthing native dependencies ext/syncthing/pkg/ ext/syncthing/src/code.google.com/ diff --git a/.gitmodules b/.gitmodules index 01fed3c4..7194fa36 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "ext/syncthing/src/github.com/syncthing/syncthing"] path = ext/syncthing/src/github.com/syncthing/syncthing url = https://github.com/syncthing/syncthing.git +[submodule "ext/golang/go"] + path = ext/golang/go + url = https://github.com/golang/go.git +[submodule "ext/golang/go1.4"] + path = ext/golang/go1.4 + url = https://github.com/golang/go.git diff --git a/build-syncthing.sh b/build-syncthing.sh deleted file mode 100755 index 022132bc..00000000 --- a/build-syncthing.sh +++ /dev/null @@ -1,78 +0,0 @@ -#!/bin/bash -e - -# Build the syncthing library -ORIG=$(pwd) -mkdir -p bin - -# Load submodules -if [ ! -e "ext/syncthing/src/github.com/syncthing/syncthing/.git" ]; then - git submodule update --init --recursive -fi - -# Check for GOLANG installation -if [ -z $GOROOT ] || [[ $(go version) != go\ version\ go1.4* ]] ; then - mkdir -p "build" - tmpgo='build/go' - if [ ! -f "$tmpgo/bin/go" ]; then - # Download GOLANG v1.4.2 - wget -O go.src.tar.gz http://golang.org/dl/go1.4.2.src.tar.gz - sha1=$(sha1sum go.src.tar.gz) - if [ "$sha1" != "460caac03379f746c473814a65223397e9c9a2f6 go.src.tar.gz" ]; then - echo "go.src.tar.gz SHA1 checksum does not match!" - exit 1 - fi - mkdir -p $tmpgo - tar -xzf go.src.tar.gz --strip=1 -C $tmpgo - rm go.src.tar.gz - # Build GO for host - pushd $tmpgo/src - GO386=387 GOARM=5 ./make.bash --no-clean - popd - fi - # Add GO to the environment - export GOROOT="$(pwd)/$tmpgo" -fi - -# Add GO compiler to PATH -export PATH=$GOROOT/bin:$PATH -# Disable CGO (no dynamic linking) -export CGO_ENABLED=0 - -# Check whether GOLANG is compiled with cross-compilation for 386 -if [ ! -f $GOROOT/bin/linux_386/go ]; then - pushd $GOROOT/src - # Build GO for cross-compilation - GOOS=linux GOARCH=386 GO386=387 ./make.bash --no-clean - popd -fi - -# Check whether GOLANG is compiled with cross-compilation for arm -if [ ! -f $GOROOT/bin/linux_arm/go ]; then - pushd $GOROOT/src - # Build GO for cross-compilation - GOOS=linux GOARCH=arm GOARM=5 ./make.bash --no-clean - popd -fi - -# Setup GOPATH -cd "ext/syncthing/" -export GOPATH="$(pwd)" - -# Install godep -$GOROOT/bin/go get github.com/tools/godep -export PATH="$(pwd)/bin":$PATH - -# Setup syncthing and clean -export ENVIRONMENT=android -cd src/github.com/syncthing/syncthing -$GOROOT/bin/go run build.go clean - -# X86 -GO386=387 $GOROOT/bin/go run build.go -goos linux -goarch 386 -no-upgrade build -mv syncthing $ORIG/bin/syncthing-x86 -$GOROOT/bin/go run build.go clean - -# ARM -GOARM=5 $GOROOT/bin/go run build.go -goos linux -goarch arm -no-upgrade build -mv syncthing $ORIG/bin/syncthing-armeabi -$GOROOT/bin/go run build.go clean diff --git a/build.gradle b/build.gradle index 48d9007b..f0a6fb8a 100644 --- a/build.gradle +++ b/build.gradle @@ -27,10 +27,6 @@ dependencies { androidTestCompile 'com.squareup.okhttp:mockwebserver:2.4.0' } -preBuild { - dependsOn 'buildNative' -} - project.archivesBaseName = 'syncthing' android { @@ -104,25 +100,9 @@ android { task buildNative(type: Exec) { outputs.upToDateWhen { false } - executable = './build-syncthing.sh' + executable = './make-all.bash' } -task copyNative(type: Copy) { - def lib_dir = "libs/" - new File(lib_dir).mkdirs() - def st_dir = "bin/"; - from st_dir + 'syncthing-x86', st_dir + 'syncthing-armeabi'; - into lib_dir - rename('syncthing-x86', 'x86/libsyncthing.so') - rename('syncthing-armeabi', 'armeabi/libsyncthing.so') -} -buildNative.finalizedBy copyNative - -task cleanBin(type: Delete) { - delete 'bin/' -} -copyNative.finalizedBy cleanBin - task cleanNative(type: Delete) { delete 'bin/' delete 'build/' diff --git a/ext/golang/go b/ext/golang/go new file mode 160000 index 00000000..525d4bd5 --- /dev/null +++ b/ext/golang/go @@ -0,0 +1 @@ +Subproject commit 525d4bd5203ce0bc6d36add058041dcdfb979161 diff --git a/ext/golang/go1.4 b/ext/golang/go1.4 new file mode 160000 index 00000000..50eb39bb --- /dev/null +++ b/ext/golang/go1.4 @@ -0,0 +1 @@ +Subproject commit 50eb39bb23e8b03e823c38e844f0410d0b5325d2 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 new file mode 100644 index 00000000..2ab2e16f --- /dev/null +++ b/ext/patches/golang/386/0001-cmd-runtime-TLS-setup-for-android-386.patch @@ -0,0 +1,267 @@ +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 new file mode 100644 index 00000000..70d976f7 --- /dev/null +++ b/ext/patches/golang/386/0002-runtime-add-syscalls-needed-for-android-386-logging.patch @@ -0,0 +1,54 @@ +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 new file mode 100644 index 00000000..97bb7dc1 --- /dev/null +++ b/ext/patches/golang/386/0003-cmd-enable-android-386-build-buildmode-pie-by-defaul.patch @@ -0,0 +1,53 @@ +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 new file mode 100644 index 00000000..a2e663bd --- /dev/null +++ b/ext/patches/golang/386/0004-cmd-allow-buildmode-c-shared-for-android-386.patch @@ -0,0 +1,99 @@ +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 new file mode 100644 index 00000000..91e6cd36 --- /dev/null +++ b/ext/patches/syncthing-inotify/godeps/0001-add-godeps.patch @@ -0,0 +1,9173 @@ +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/ext/syncthing/src/github.com/syncthing/syncthing b/ext/syncthing/src/github.com/syncthing/syncthing index 3e25b579..56f1c295 160000 --- a/ext/syncthing/src/github.com/syncthing/syncthing +++ b/ext/syncthing/src/github.com/syncthing/syncthing @@ -1 +1 @@ -Subproject commit 3e25b579bc4843b6cde69e867e8b6e5e6ea304c1 +Subproject commit 56f1c295b609e402c994e93fb963636d89247ca2 diff --git a/make-all.bash b/make-all.bash new file mode 100755 index 00000000..45d63327 --- /dev/null +++ b/make-all.bash @@ -0,0 +1,13 @@ +#!/bin/bash -e + +# Build the syncthing library + +./make-go.bash arm +./make-syncthing.bash arm + +./make-go.bash 386 +./make-syncthing.bash 386 + +./make-go.bash amd64 +./make-syncthing.bash amd64 + diff --git a/make-go.bash b/make-go.bash new file mode 100755 index 00000000..27df6718 --- /dev/null +++ b/make-go.bash @@ -0,0 +1,115 @@ +#!/usr/bin/env bash + +set -e + +RESET=1 + +MYDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +if [ -z "$ANDROID_NDK" ]; then + echo "Error: unspecified ANDROID_NDK" + exit 1 +fi + +if [ -z "$GOROOT_BOOTSTRAP" ]; then + # We need Go 1.4 to bootstrap Go 1.5 + if [ -z $GOROOT ] || [[ $(go version) != go\ version\ go1.4* ]] ; then + git submodule update --init ext/golang/go1.4 + # Build Go 1.4 for host + pushd ext/golang/go1.4/src + ./make.bash --no-clean + popd + # Add Go 1.4 to the environment + export GOROOT="$(pwd)/ext/golang/go1.4" + fi + # Add Go 1.4 compiler to PATH + export GOROOT_BOOTSTRAP=$GOROOT +fi + +case "$1" in + arm) + if [ ! -d "${MYDIR}/build/ndk-$1" ]; then + sh ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh --platform=android-9 --toolchain=arm-linux-androideabi-4.9 --install-dir=${MYDIR}/build/ndk-$1 + fi + export CC_FOR_TARGET=${MYDIR}/build/ndk-arm/bin/arm-linux-androideabi-gcc + export CXX_FOR_TARGET=${MYDIR}/build/ndk-arm/bin/arm-linux-androideabi-g++ + export CGO_ENABLED=1 + export GOOS=android + export GOARCH=arm + export GOARM=5 + ;; + 386) + if [ ! -d "${MYDIR}/build/ndk-$1" ]; then + sh ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh --platform=android-9 --toolchain=x86-4.9 --install-dir=${MYDIR}/build/ndk-$1 + fi + export CC_FOR_TARGET=${MYDIR}/build/ndk-386/bin/i686-linux-android-gcc + export CXX_FOR_TARGET=${MYDIR}/build/ndk-386/bin/i686-linux-android-g++ + export CGO_ENABLED=1 + export GOOS=android + export GOARCH=386 + export GO386=387 + ;; + amd64) + if [ ! -d "${MYDIR}/build/ndk-$1" ]; then + sh ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh --platform=android-21 --toolchain=x86_64-4.9 --install-dir=${MYDIR}/build/ndk-$1 + fi + export CC_FOR_TARGET=${MYDIR}/build/ndk-amd64/bin/x86_64-linux-android-gcc + export CXX_FOR_TARGET=${MYDIR}/build/ndk-amd64/bin/x86_64-linux-android-g++ + export CGO_ENABLED=1 + export GOOS=android + export GOARCH=amd64 + ;; + *) + echo "Must specify either arm or 386 or amd64" + exit 1 +esac + +#TODO figure out why --depth 1 never works right +if [ $RESET -eq 1 ]; then + git submodule update --init ext/golang/go +fi + +unset GOPATH + +export GOROOT_FINAL=${MYDIR}/ext/golang/dist/go-${GOOS}-${GOARCH} + +if [ -d "$GOROOT_FINAL" ]; then + rm -r "$GOROOT_FINAL" +fi +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 +rm -r ../pkg +set -e + +if [ ! -e ../VERSION ]; then + echo "$(git describe --tags)" > ../VERSION +fi + +./make.bash --no-banner +cp -a ../bin "${GOROOT_FINAL}"/ +cp -a ../pkg "${GOROOT_FINAL}"/ +cp -a ../src "${GOROOT_FINAL}"/ + +if [[ $RESET -eq 1 && -e ./make.bash ]]; then + pushd ../ + git clean -f + popd +fi + +popd + +if [ $RESET -eq 1 ]; then + git submodule update --init ext/golang/go +fi + +echo "Complete" + diff --git a/make-syncthing.bash b/make-syncthing.bash new file mode 100755 index 00000000..1626adaa --- /dev/null +++ b/make-syncthing.bash @@ -0,0 +1,96 @@ +#!/usr/bin/env bash + +set -e + +RESET=1 + +MYDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +if [ -z "$ANDROID_NDK" ]; then + echo "Error: unspecified ANDROID_NDK" + exit 1 +fi + +case "$1" in + arm) + if [ ! -d "${MYDIR}/build/ndk-$1" ]; then + sh ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh --platform=android-9 --toolchain=arm-linux-androideabi-4.9 --install-dir=${MYDIR}/build/ndk-$1 + fi + export CC=${MYDIR}/build/ndk-$1/bin/arm-linux-androideabi-gcc + export CXX=${MYDIR}/build/ndk-$1/bin/arm-linux-androideabi-g++ + export CGO_ENABLED=1 + export GOOS=android + export GOARCH=arm + export GOARM=5 + export TARGETDIR=${MYDIR}/libs/armeabi + ;; + 386) + if [ ! -d "${MYDIR}/build/ndk-$1" ]; then + sh ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh --platform=android-9 --toolchain=x86-4.9 --install-dir=${MYDIR}/build/ndk-$1 + fi + export CC_FOR_TARGET=${MYDIR}/build/ndk-$1/bin/i686-linux-android-gcc + export CXX_FOR_TARGET=${MYDIR}/build/ndk-$1/bin/i686-linux-android-g++ + export CGO_ENABLED=1 + export GOOS=android + export GOARCH=386 + export GO386=387 + export TARGETDIR=${MYDIR}/libs/x86 + ;; + amd64) + if [ ! -d "${MYDIR}/build/ndk-$1" ]; then + sh ${ANDROID_NDK}/build/tools/make-standalone-toolchain.sh --platform=android-9 --toolchain=x86_64-4.9 --install-dir=${MYDIR}/build/ndk-$1 + fi + export CC_FOR_TARGET=${MYDIR}/build/ndk-$1/bin/x86_64-linux-android-gcc + export CXX_FOR_TARGET=${MYDIR}/build/ndk-$1/bin/x86_64-linux-android-g++ + export CGO_ENABLED=1 + export GOOS=android + export GOARCH=amd64 + export TARGETDIR=${MYDIR}/libs/x86_64 + ;; + *) + echo "Must specify either arm or 386 or amd64" + exit 1 +esac + +unset GOPATH #Set by build.go +export GOROOT=${MYDIR}/ext/golang/dist/go-${GOOS}-${GOARCH} +export PATH=${GOROOT}/bin:${PATH} + +if [ ! -x ${GOROOT}/bin/${GOOS}_${GOARCH}/go ]; then + echo Need to build go for ${GOOS}-${GOARCH} + exit 1 +fi + +if [ $RESET -eq 1 ]; then + git submodule update --init ext/syncthing/src/github.com/syncthing/syncthing +fi + +pushd ext/syncthing/src/github.com/syncthing/syncthing + +_GOOS=$GOOS +unset GOOS +_GOARCH=$GOARCH +unset GOARCH + +go run build.go -goos=${_GOOS} -goarch=${_GOARCH} clean +go run build.go -goos=${_GOOS} -goarch=${_GOARCH} -no-upgrade build + +export GOOS=$_GOOS +export GOARCH=$_GOARCH + +mkdir -p ${TARGETDIR} +mv syncthing ${TARGETDIR}/libsyncthing.so +chmod 644 ${TARGETDIR}/libsyncthing.so + +if [[ RESET -eq 1 && -e ./build.go ]]; then + git clean -f +fi + +popd + +if [ $RESET -eq 1 ]; then + git submodule update --init ext/syncthing/src/github.com/syncthing/syncthing +fi + +echo "Build Complete" + From 4e1979036a5a7765e36b34d3bbc4a8f9c5eabce8 Mon Sep 17 00:00:00 2001 From: Lode Hoste Date: Sun, 15 Nov 2015 18:05:49 +0100 Subject: [PATCH 2/3] Remove manual DNS resolution of default servers --- .../syncthingandroid/util/ConfigXml.java | 49 ------------------- 1 file changed, 49 deletions(-) diff --git a/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java b/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java index 4bd8cf89..118fde84 100644 --- a/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java +++ b/src/main/java/com/nutomic/syncthingandroid/util/ConfigXml.java @@ -40,21 +40,6 @@ public class ConfigXml { private static final String TAG = "ConfigXml"; - private static final String[] REPLACE_DISCOVERY_SERVERS = new String[] { - "default", - "udp4://194.126.249.5:22026", - "udp6://[2001:470:28:4d6::5]:22026" - }; - - private static final String[] DISCOVERY_SERVER_IPS = new String[] { - "https://194.126.249.5/?id=SR7AARM-TCBUZ5O-VFAXY4D-CECGSDE-3Q6IZ4G-XG7AH75-OBIXJQV-QJ6NLQA", - "https://45.55.230.38/?id=AQEHEO2-XOS7QRA-X2COH5K-PO6OPVA-EWOSEGO-KZFMD32-XJ4ZV46-CUUVKAS", - "https://128.199.95.124/?id=7WT2BVR-FX62ZOW-TNVVW25-6AHFJGD-XEXQSBW-VO3MPL2-JBTLL4T-P4572Q4", - "https://[2001:470:28:4d6::5]/?id=SR7AARM-TCBUZ5O-VFAXY4D-CECGSDE-3Q6IZ4G-XG7AH75-OBIXJQV-QJ6NLQA", - "https://[2604:a880:800:10::182:a001]/?id=AQEHEO2-XOS7QRA-X2COH5K-PO6OPVA-EWOSEGO-KZFMD32-XJ4ZV46-CUUVKAS", - "https://[2400:6180:0:d0::d9:d001]/?id=7WT2BVR-FX62ZOW-TNVVW25-6AHFJGD-XEXQSBW-VO3MPL2-JBTLL4T-P4572Q4", - }; - /** * File in the config folder that contains configuration. */ @@ -123,9 +108,6 @@ public class ConfigXml { /** * Updates the config file. *

- * Coming from 0.2.0 and earlier, globalAnnounceServer value "announce.syncthing.net:22025" is - * replaced with "194.126.249.5:22025" (as domain resolve is broken). - *

* Coming from 0.3.0 and earlier, the ignorePerms flag is set to true on every folder. */ @SuppressWarnings("SdCardPath") @@ -135,10 +117,6 @@ public class ConfigXml { Element options = (Element) mConfig.getDocumentElement() .getElementsByTagName("options").item(0); - if (replaceAnnounceServers(options)) { - changed = true; - } - NodeList folders = mConfig.getDocumentElement().getElementsByTagName("folder"); for (int i = 0; i < folders.getLength(); i++) { Element r = (Element) folders.item(i); @@ -181,33 +159,6 @@ public class ConfigXml { } } - /** - * Replaces the announce servers in {@link #REPLACE_DISCOVERY_SERVERS} with those in - * {@link #DISCOVERY_SERVER_IPS}. - */ - private boolean replaceAnnounceServers(Element options) { - // Hardcode default globalAnnounceServer ip. - NodeList globalAnnounceServers = options.getElementsByTagName("globalAnnounceServer"); - boolean needUpdateAnnounceServers = false; - for (int i = 0; i < globalAnnounceServers.getLength(); i++) { - Node announce = globalAnnounceServers.item(i); - if (Arrays.asList(REPLACE_DISCOVERY_SERVERS).contains(announce.getTextContent())) { - options.removeChild(announce); - needUpdateAnnounceServers = true; - } - } - - if (needUpdateAnnounceServers) { - Log.i(TAG, "Replacing globalAnnounceServer address with ip"); - for (String server : DISCOVERY_SERVER_IPS) { - Element newAnnounce = mConfig.createElement("globalAnnounceServer"); - newAnnounce.setTextContent(server); - options.appendChild(newAnnounce); - } - return true; - } - return false; - } /** * Set 'hashers' (see https://github.com/syncthing/syncthing-android/issues/384) on the * given folder. From 572ccb9e84202e736e43c1ab3b4f965e2c21e0ca Mon Sep 17 00:00:00 2001 From: Lode Hoste Date: Sun, 15 Nov 2015 18:10:34 +0100 Subject: [PATCH 3/3] Update build instructions --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 50d38b0d..133580c9 100644 --- a/README.md +++ b/README.md @@ -21,10 +21,13 @@ Translations can be updated using the [Transifex client](http://docs.transifex.c ### Requirements - Android SDK Platform (for the `compileSdkVersion` specified in [build.gradle](build.gradle)) +- Android NDK Platform - Android Support Repository ### Build instructions +Set the `ANDROID_NDK` environment variable to the Android NDK folder (e.g. `export ANDROID_NDK=/opt/android_ndk`). +Build Go and Syncthing using `./make-all.bash`. Use `./gradlew assembleDebug` in the project directory to compile the APK. To check for updated gradle dependencies, run `gradle dependencyUpdates`. Additionally, the git submodule in `ext/syncthing/src/github.com/syncthing/syncthing` may need to be updated. @@ -33,8 +36,8 @@ To check for updated gradle dependencies, run `gradle dependencyUpdates`. Additi ### Building on Windows To build the Syncthing app on Windows we need to include the native Syncthing binaries: -- Download the `syncthing-linux-386` and `syncthing-linux-arm` archives from [Syncthing releases](https://github.com/syncthing/syncthing/releases) and extract them. In each there is a `syncthing` executable. Rename and place both of these to `libs/x86/libsyncthing.so` and `libs/armeabi/libsyncthing.so` respectively. -You will also need to replace the standard `build.gradle` file with [this one](https://gist.github.com/Moter8/9cfc191434d1989d86be). +- Download the `syncthing-linux-386` and `syncthing-linux-arm` archives from [Syncthing releases](https://github.com/syncthing/syncthing/releases) and extract them. In each there is a `syncthing` executable. Rename and place both of these to `libs/x86/libsyncthing.so` and `libs/armeabi/libsyncthing.so` respectively. +Use `./gradlew assembleDebug` in the project directory to compile the APK. # License