From 684b2c724cea143d2f16abb5992f99256718a23c Mon Sep 17 00:00:00 2001 From: paladz <453256728@qq.com> Date: Wed, 1 Nov 2017 17:00:17 +0800 Subject: [PATCH] add rjeczalik/notify to vendor --- vendor/github.com/rjeczalik/notify/.gitignore | 92 ++ vendor/github.com/rjeczalik/notify/.travis.yml | 28 + vendor/github.com/rjeczalik/notify/AUTHORS | 10 + vendor/github.com/rjeczalik/notify/LICENSE | 21 + vendor/github.com/rjeczalik/notify/README.md | 21 + vendor/github.com/rjeczalik/notify/appveyor.yml | 23 + vendor/github.com/rjeczalik/notify/debug.go | 11 + vendor/github.com/rjeczalik/notify/debug_debug.go | 43 + vendor/github.com/rjeczalik/notify/doc.go | 43 + vendor/github.com/rjeczalik/notify/event.go | 143 ++++ vendor/github.com/rjeczalik/notify/event_fen.go | 57 ++ .../github.com/rjeczalik/notify/event_fsevents.go | 71 ++ .../github.com/rjeczalik/notify/event_inotify.go | 75 ++ vendor/github.com/rjeczalik/notify/event_kqueue.go | 59 ++ .../github.com/rjeczalik/notify/event_readdcw.go | 118 +++ vendor/github.com/rjeczalik/notify/event_stub.go | 31 + vendor/github.com/rjeczalik/notify/event_test.go | 33 + .../github.com/rjeczalik/notify/event_trigger.go | 22 + .../rjeczalik/notify/example_fsevents_test.go | 128 +++ .../rjeczalik/notify/example_inotify_test.go | 84 ++ .../rjeczalik/notify/example_readdcw_test.go | 40 + vendor/github.com/rjeczalik/notify/example_test.go | 87 ++ vendor/github.com/rjeczalik/notify/node.go | 272 ++++++ vendor/github.com/rjeczalik/notify/notify.go | 74 ++ .../rjeczalik/notify/notify_inotify_test.go | 37 + .../rjeczalik/notify/notify_readdcw_test.go | 60 ++ vendor/github.com/rjeczalik/notify/notify_test.go | 103 +++ .../rjeczalik/notify/sync_readdcw_test.go | 33 + .../github.com/rjeczalik/notify/sync_unix_test.go | 18 + .../github.com/rjeczalik/notify/testdata/vfs.txt | 56 ++ vendor/github.com/rjeczalik/notify/testing_test.go | 951 +++++++++++++++++++++ vendor/github.com/rjeczalik/notify/tree.go | 22 + .../rjeczalik/notify/tree_nonrecursive.go | 292 +++++++ .../rjeczalik/notify/tree_nonrecursive_test.go | 543 ++++++++++++ .../github.com/rjeczalik/notify/tree_recursive.go | 354 ++++++++ .../rjeczalik/notify/tree_recursive_test.go | 524 ++++++++++++ vendor/github.com/rjeczalik/notify/util.go | 150 ++++ .../rjeczalik/notify/util_darwin_test.go | 41 + vendor/github.com/rjeczalik/notify/util_test.go | 142 +++ .../github.com/rjeczalik/notify/util_unix_test.go | 125 +++ vendor/github.com/rjeczalik/notify/watcher.go | 85 ++ vendor/github.com/rjeczalik/notify/watcher_fen.go | 168 ++++ .../github.com/rjeczalik/notify/watcher_fen_cgo.go | 141 +++ .../rjeczalik/notify/watcher_fen_test.go | 92 ++ .../rjeczalik/notify/watcher_fsevents.go | 319 +++++++ .../rjeczalik/notify/watcher_fsevents_cgo.go | 190 ++++ .../rjeczalik/notify/watcher_fsevents_test.go | 111 +++ .../github.com/rjeczalik/notify/watcher_inotify.go | 397 +++++++++ .../rjeczalik/notify/watcher_inotify_test.go | 132 +++ .../github.com/rjeczalik/notify/watcher_kqueue.go | 193 +++++ .../rjeczalik/notify/watcher_kqueue_test.go | 111 +++ .../github.com/rjeczalik/notify/watcher_readdcw.go | 574 +++++++++++++ .../rjeczalik/notify/watcher_readdcw_test.go | 67 ++ .../rjeczalik/notify/watcher_recursive_test.go | 102 +++ vendor/github.com/rjeczalik/notify/watcher_stub.go | 23 + vendor/github.com/rjeczalik/notify/watcher_test.go | 32 + .../github.com/rjeczalik/notify/watcher_trigger.go | 432 ++++++++++ .../rjeczalik/notify/watcher_trigger_test.go | 21 + vendor/github.com/rjeczalik/notify/watchpoint.go | 103 +++ .../rjeczalik/notify/watchpoint_other.go | 23 + .../rjeczalik/notify/watchpoint_readdcw.go | 38 + .../github.com/rjeczalik/notify/watchpoint_test.go | 162 ++++ 62 files changed, 8553 insertions(+) create mode 100644 vendor/github.com/rjeczalik/notify/.gitignore create mode 100644 vendor/github.com/rjeczalik/notify/.travis.yml create mode 100644 vendor/github.com/rjeczalik/notify/AUTHORS create mode 100644 vendor/github.com/rjeczalik/notify/LICENSE create mode 100644 vendor/github.com/rjeczalik/notify/README.md create mode 100644 vendor/github.com/rjeczalik/notify/appveyor.yml create mode 100644 vendor/github.com/rjeczalik/notify/debug.go create mode 100644 vendor/github.com/rjeczalik/notify/debug_debug.go create mode 100644 vendor/github.com/rjeczalik/notify/doc.go create mode 100644 vendor/github.com/rjeczalik/notify/event.go create mode 100644 vendor/github.com/rjeczalik/notify/event_fen.go create mode 100644 vendor/github.com/rjeczalik/notify/event_fsevents.go create mode 100644 vendor/github.com/rjeczalik/notify/event_inotify.go create mode 100644 vendor/github.com/rjeczalik/notify/event_kqueue.go create mode 100644 vendor/github.com/rjeczalik/notify/event_readdcw.go create mode 100644 vendor/github.com/rjeczalik/notify/event_stub.go create mode 100644 vendor/github.com/rjeczalik/notify/event_test.go create mode 100644 vendor/github.com/rjeczalik/notify/event_trigger.go create mode 100644 vendor/github.com/rjeczalik/notify/example_fsevents_test.go create mode 100644 vendor/github.com/rjeczalik/notify/example_inotify_test.go create mode 100644 vendor/github.com/rjeczalik/notify/example_readdcw_test.go create mode 100644 vendor/github.com/rjeczalik/notify/example_test.go create mode 100644 vendor/github.com/rjeczalik/notify/node.go create mode 100644 vendor/github.com/rjeczalik/notify/notify.go create mode 100644 vendor/github.com/rjeczalik/notify/notify_inotify_test.go create mode 100644 vendor/github.com/rjeczalik/notify/notify_readdcw_test.go create mode 100644 vendor/github.com/rjeczalik/notify/notify_test.go create mode 100644 vendor/github.com/rjeczalik/notify/sync_readdcw_test.go create mode 100644 vendor/github.com/rjeczalik/notify/sync_unix_test.go create mode 100644 vendor/github.com/rjeczalik/notify/testdata/vfs.txt create mode 100644 vendor/github.com/rjeczalik/notify/testing_test.go create mode 100644 vendor/github.com/rjeczalik/notify/tree.go create mode 100644 vendor/github.com/rjeczalik/notify/tree_nonrecursive.go create mode 100644 vendor/github.com/rjeczalik/notify/tree_nonrecursive_test.go create mode 100644 vendor/github.com/rjeczalik/notify/tree_recursive.go create mode 100644 vendor/github.com/rjeczalik/notify/tree_recursive_test.go create mode 100644 vendor/github.com/rjeczalik/notify/util.go create mode 100644 vendor/github.com/rjeczalik/notify/util_darwin_test.go create mode 100644 vendor/github.com/rjeczalik/notify/util_test.go create mode 100644 vendor/github.com/rjeczalik/notify/util_unix_test.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_fen.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_fen_cgo.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_fen_test.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_fsevents.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_fsevents_cgo.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_fsevents_test.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_inotify.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_inotify_test.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_kqueue.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_kqueue_test.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_readdcw.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_readdcw_test.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_recursive_test.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_stub.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_test.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_trigger.go create mode 100644 vendor/github.com/rjeczalik/notify/watcher_trigger_test.go create mode 100644 vendor/github.com/rjeczalik/notify/watchpoint.go create mode 100644 vendor/github.com/rjeczalik/notify/watchpoint_other.go create mode 100644 vendor/github.com/rjeczalik/notify/watchpoint_readdcw.go create mode 100644 vendor/github.com/rjeczalik/notify/watchpoint_test.go diff --git a/vendor/github.com/rjeczalik/notify/.gitignore b/vendor/github.com/rjeczalik/notify/.gitignore new file mode 100644 index 00000000..32c6eb98 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/.gitignore @@ -0,0 +1,92 @@ +# 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 +*~ + +### JetBrains files ### +.idea/ +*.iml \ No newline at end of file diff --git a/vendor/github.com/rjeczalik/notify/.travis.yml b/vendor/github.com/rjeczalik/notify/.travis.yml new file mode 100644 index 00000000..0f38a353 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/.travis.yml @@ -0,0 +1,28 @@ +language: go + +go: + - 1.7.5 + +os: + - linux + - osx + +matrix: + include: + - os: osx + go: 1.7.5 + env: + - GOFLAGS="-tags kqueue" + +env: + global: + - GOBIN=$HOME/bin + - PATH=$HOME/bin:$PATH + +install: + - go get -t -v ./... + +script: + - "(go version | grep -q 1.4) || go tool vet -all ." + - go install $GOFLAGS ./... + - go test -v -race $GOFLAGS ./... diff --git a/vendor/github.com/rjeczalik/notify/AUTHORS b/vendor/github.com/rjeczalik/notify/AUTHORS new file mode 100644 index 00000000..9262eae6 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/LICENSE b/vendor/github.com/rjeczalik/notify/LICENSE new file mode 100644 index 00000000..3e678817 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/README.md b/vendor/github.com/rjeczalik/notify/README.md new file mode 100644 index 00000000..02a5f329 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/README.md @@ -0,0 +1,21 @@ +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) +- [github.com/cortesi/devd](https://github.com/cortesi/devd) +- [github.com/cortesi/modd](https://github.com/cortesi/modd) +- [github.com/syncthing/syncthing-inotify](https://github.com/syncthing/syncthing-inotify) diff --git a/vendor/github.com/rjeczalik/notify/appveyor.yml b/vendor/github.com/rjeczalik/notify/appveyor.yml new file mode 100644 index 00000000..8e762d05 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/appveyor.yml @@ -0,0 +1,23 @@ +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 -v -t ./... + +build_script: + - go tool vet -all . + - go build ./... + - go test -v -race ./... + +test: off + +deploy: off diff --git a/vendor/github.com/rjeczalik/notify/debug.go b/vendor/github.com/rjeczalik/notify/debug.go new file mode 100644 index 00000000..bd9bc468 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/debug_debug.go b/vendor/github.com/rjeczalik/notify/debug_debug.go new file mode 100644 index 00000000..f0622917 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/doc.go b/vendor/github.com/rjeczalik/notify/doc.go new file mode 100644 index 00000000..60543c08 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/doc.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. + +// Package notify implements access to filesystem events. +// +// Notify is a high-level abstraction over filesystem watchers like inotify, +// kqueue, FSEvents, FEN or ReadDirectoryChangesW. Watcher implementations are +// split into two groups: ones that natively support recursive notifications +// (FSEvents and ReadDirectoryChangesW) and ones that do not (inotify, kqueue, FEN). +// 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 +// a 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. +// Both the platform-independent (see Constants) and specific events can be used. +// Refer to the event_*.go source files for information about the available +// events. +// +// 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 +// - FEN: port_get +// +// 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, recursive and +// non-recursive ones as well. +package notify diff --git a/vendor/github.com/rjeczalik/notify/event.go b/vendor/github.com/rjeczalik/notify/event.go new file mode 100644 index 00000000..c1288419 --- /dev/null +++ b/vendor/github.com/rjeczalik/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 *unix.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/vendor/github.com/rjeczalik/notify/event_fen.go b/vendor/github.com/rjeczalik/notify/event_fen.go new file mode 100644 index 00000000..767f04fa --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/event_fen.go @@ -0,0 +1,57 @@ +// 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 solaris + +package notify + +const ( + osSpecificCreate Event = 0x00000100 << 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 ( + // FileAccess is an event reported when monitored file/directory was accessed. + FileAccess = fileAccess + // FileModified is an event reported when monitored file/directory was modified. + FileModified = fileModified + // FileAttrib is an event reported when monitored file/directory's ATTRIB + // was changed. + FileAttrib = fileAttrib + // FileDelete is an event reported when monitored file/directory was deleted. + FileDelete = fileDelete + // FileRenameTo to is an event reported when monitored file/directory was renamed. + FileRenameTo = fileRenameTo + // FileRenameFrom is an event reported when monitored file/directory was renamed. + FileRenameFrom = fileRenameFrom + // FileTrunc is an event reported when monitored file/directory was truncated. + FileTrunc = fileTrunc + // FileNoFollow is an flag to indicate not to follow symbolic links. + FileNoFollow = fileNoFollow + // Unmounted is an event reported when monitored filesystem was unmounted. + Unmounted = unmounted + // MountedOver is an event reported when monitored file/directory was mounted on. + MountedOver = mountedOver +) + +var osestr = map[Event]string{ + FileAccess: "notify.FileAccess", + FileModified: "notify.FileModified", + FileAttrib: "notify.FileAttrib", + FileDelete: "notify.FileDelete", + FileRenameTo: "notify.FileRenameTo", + FileRenameFrom: "notify.FileRenameFrom", + FileTrunc: "notify.FileTrunc", + FileNoFollow: "notify.FileNoFollow", + Unmounted: "notify.Unmounted", + MountedOver: "notify.MountedOver", +} diff --git a/vendor/github.com/rjeczalik/notify/event_fsevents.go b/vendor/github.com/rjeczalik/notify/event_fsevents.go new file mode 100644 index 00000000..6ded80b2 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/event_inotify.go b/vendor/github.com/rjeczalik/notify/event_inotify.go new file mode 100644 index 00000000..1f32bb73 --- /dev/null +++ b/vendor/github.com/rjeczalik/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 "golang.org/x/sys/unix" + +// 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(unix.IN_ACCESS) // File was accessed + InModify = Event(unix.IN_MODIFY) // File was modified + InAttrib = Event(unix.IN_ATTRIB) // Metadata changed + InCloseWrite = Event(unix.IN_CLOSE_WRITE) // Writtable file was closed + InCloseNowrite = Event(unix.IN_CLOSE_NOWRITE) // Unwrittable file closed + InOpen = Event(unix.IN_OPEN) // File was opened + InMovedFrom = Event(unix.IN_MOVED_FROM) // File was moved from X + InMovedTo = Event(unix.IN_MOVED_TO) // File was moved to Y + InCreate = Event(unix.IN_CREATE) // Subfile was created + InDelete = Event(unix.IN_DELETE) // Subfile was deleted + InDeleteSelf = Event(unix.IN_DELETE_SELF) // Self was deleted + InMoveSelf = Event(unix.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(unix.IN_DONT_FOLLOW) + inExclUnlink = Event(unix.IN_EXCL_UNLINK) + inMaskAdd = Event(unix.IN_MASK_ADD) + inOneshot = Event(unix.IN_ONESHOT) + inOnlydir = Event(unix.IN_ONLYDIR) +) + +type event struct { + sys unix.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&unix.IN_ISDIR != 0, nil } diff --git a/vendor/github.com/rjeczalik/notify/event_kqueue.go b/vendor/github.com/rjeczalik/notify/event_kqueue.go new file mode 100644 index 00000000..422ac87f --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/event_kqueue.go @@ -0,0 +1,59 @@ +// 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 "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 event 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", +} diff --git a/vendor/github.com/rjeczalik/notify/event_readdcw.go b/vendor/github.com/rjeczalik/notify/event_readdcw.go new file mode 100644 index 00000000..f7b82de0 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/event_readdcw.go @@ -0,0 +1,118 @@ +// 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 +// On Windows the following events can be passed to Watch. A different set of +// events (see actions below) are received on the channel passed to Watch. +// For more information refer to +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx +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 +// The following events are returned on the channel passed to Watch, but cannot +// be passed to Watch itself (see filters above). You can find a table showing +// the relation between actions and filteres at +// https://github.com/rjeczalik/notify/issues/10#issuecomment-66179535 +// The msdn documentation on actions is part of +// https://msdn.microsoft.com/en-us/library/windows/desktop/aa364391(v=vs.85).aspx +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/vendor/github.com/rjeczalik/notify/event_stub.go b/vendor/github.com/rjeczalik/notify/event_stub.go new file mode 100644 index 00000000..faac7c7c --- /dev/null +++ b/vendor/github.com/rjeczalik/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,!solaris + +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/vendor/github.com/rjeczalik/notify/event_test.go b/vendor/github.com/rjeczalik/notify/event_test.go new file mode 100644 index 00000000..2803509f --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/event_trigger.go b/vendor/github.com/rjeczalik/notify/event_trigger.go new file mode 100644 index 00000000..94470fd3 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/event_trigger.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. + +// +build darwin,kqueue dragonfly freebsd netbsd openbsd solaris + +package notify + +type event struct { + p string + e Event + d bool + pe interface{} +} + +func (e *event) Event() Event { return e.e } + +func (e *event) Path() string { return e.p } + +func (e *event) Sys() interface{} { return e.pe } + +func (e *event) isDir() (bool, error) { return e.d, nil } diff --git a/vendor/github.com/rjeczalik/notify/example_fsevents_test.go b/vendor/github.com/rjeczalik/notify/example_fsevents_test.go new file mode 100644 index 00000000..53d1b342 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/example_inotify_test.go b/vendor/github.com/rjeczalik/notify/example_inotify_test.go new file mode 100644 index 00000000..e386012c --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/example_inotify_test.go @@ -0,0 +1,84 @@ +// 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" + + "golang.org/x/sys/unix" + + "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().(*unix.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/vendor/github.com/rjeczalik/notify/example_readdcw_test.go b/vendor/github.com/rjeczalik/notify/example_readdcw_test.go new file mode 100644 index 00000000..e9e05bef --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/example_test.go b/vendor/github.com/rjeczalik/notify/example_test.go new file mode 100644 index 00000000..3c8df71d --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/node.go b/vendor/github.com/rjeczalik/notify/node.go new file mode 100644 index 00000000..29c1bb20 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/node.go @@ -0,0 +1,272 @@ +// 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" + "fmt" + "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 fmt.Errorf("error while traversing %q: %v", nd.Name, 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/vendor/github.com/rjeczalik/notify/notify.go b/vendor/github.com/rjeczalik/notify/notify.go new file mode 100644 index 00000000..7d2eec3a --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/notify.go @@ -0,0 +1,74 @@ +// 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 persistent 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. +// +// The c almost always is a buffered channel. Watch will not block sending to c +// - the caller must ensure that c has sufficient buffer space to keep up with +// the expected event rate. +// +// 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 persistent 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 guaranteed that c will +// receive no more signals. +func Stop(c chan<- EventInfo) { + defaultTree.Stop(c) +} diff --git a/vendor/github.com/rjeczalik/notify/notify_inotify_test.go b/vendor/github.com/rjeczalik/notify/notify_inotify_test.go new file mode 100644 index 00000000..c5984d33 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/notify_readdcw_test.go b/vendor/github.com/rjeczalik/notify/notify_readdcw_test.go new file mode 100644 index 00000000..944b5338 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/notify_test.go b/vendor/github.com/rjeczalik/notify/notify_test.go new file mode 100644 index 00000000..58c6ae75 --- /dev/null +++ b/vendor/github.com/rjeczalik/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 solaris + +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 explicitly 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/vendor/github.com/rjeczalik/notify/sync_readdcw_test.go b/vendor/github.com/rjeczalik/notify/sync_readdcw_test.go new file mode 100644 index 00000000..f2997543 --- /dev/null +++ b/vendor/github.com/rjeczalik/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 privileges, 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/vendor/github.com/rjeczalik/notify/sync_unix_test.go b/vendor/github.com/rjeczalik/notify/sync_unix_test.go new file mode 100644 index 00000000..5e2c5dde --- /dev/null +++ b/vendor/github.com/rjeczalik/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 "golang.org/x/sys/unix" + +func Sync() { + unix.Sync() +} + +// UpdateWait is required only by windows watcher implementation. On other +// platforms this function is no-op. +func UpdateWait() { +} diff --git a/vendor/github.com/rjeczalik/notify/testdata/vfs.txt b/vendor/github.com/rjeczalik/notify/testdata/vfs.txt new file mode 100644 index 00000000..88afbc6e --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/testing_test.go b/vendor/github.com/rjeczalik/notify/testing_test.go new file mode 100644 index 00000000..ea18c4fa --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/testing_test.go @@ -0,0 +1,951 @@ +// 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 string + +func init() { + var err error + if wd, err = os.Getwd(); err != nil { + panic("Getwd()=" + err.Error()) + } +} + +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 HasEventInfo(want, got Event, p string) error { + if got&want != want { + return fmt.Errorf("want Event=%v; got %v (path=%s)", want, + got, p) + } + 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) +} + +func (w *W) aggregate(ei []EventInfo, pf string) (evs map[string]Event) { + evs = make(map[string]Event) + for _, cas := range ei { + p := cas.Path() + if pf != "" { + p = filepath.Join(pf, p) + } + evs[p] |= cas.Event() + } + return +} + +func (w *W) ExpectAllFunc(cases []WCase) { + UpdateWait() // Wait some time before starting the test. + for i, cas := range cases { + exp := w.aggregate(cas.Events, w.root) + dbgprintf("ExpectAll: i=%d\n", i) + cas.Action() + Sync() + got := w.aggregate(drainall(w.C), "") + for ep, ee := range exp { + ge, ok := got[ep] + if !ok { + w.Fatalf("missing events for %q (%v)", ep, ee) + continue + } + delete(got, ep) + if err := HasEventInfo(ee, ge, ep); err != nil { + w.Fatalf("ExpectAll received an event which does not match "+ + "the expected ones for %q: want %v; got %v", ep, ee, ge) + continue + } + } + if len(got) != 0 { + w.Fatalf("ExpectAll received unexpected events: %v", got) + } + } +} + +// ExpectAll requires all requested events to be send. +// It does not require events to be send in the same order or in the same +// chunks (e.g. NoteWrite and NoteExtend reported as independent events are +// treated the same as one NoteWrite|NoteExtend event). +func (w *W) ExpectAll(cases []WCase) { + w.ExpectAllFunc(cases) +} + +// 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 convenient 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/vendor/github.com/rjeczalik/notify/tree.go b/vendor/github.com/rjeczalik/notify/tree.go new file mode 100644 index 00000000..cd6afd60 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/tree_nonrecursive.go b/vendor/github.com/rjeczalik/notify/tree_nonrecursive.go new file mode 100644 index 00000000..dfa72d1d --- /dev/null +++ b/vendor/github.com/rjeczalik/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) 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: + t.w.Watch(nd.Name, diff[1]) + default: + t.w.Rewatch(nd.Name, diff[0], diff[1]) + } + return nil + } +} + +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/vendor/github.com/rjeczalik/notify/tree_nonrecursive_test.go b/vendor/github.com/rjeczalik/notify/tree_nonrecursive_test.go new file mode 100644 index 00000000..37625bfc --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/tree_recursive.go b/vendor/github.com/rjeczalik/notify/tree_recursive.go new file mode 100644 index 00000000..7f00dfe3 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/tree_recursive_test.go b/vendor/github.com/rjeczalik/notify/tree_recursive_test.go new file mode 100644 index 00000000..329fca06 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/util.go b/vendor/github.com/rjeczalik/notify/util.go new file mode 100644 index 00000000..67e01fbb --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/util.go @@ -0,0 +1,150 @@ +// 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, j, depth := 1, 0, 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 { + j, i = i, len(p) + } else { + j, i = 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 + } + if filepath.IsAbs(s) { + p = "/" + s + p[i:] + } else { + p = p[:j] + 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/vendor/github.com/rjeczalik/notify/util_darwin_test.go b/vendor/github.com/rjeczalik/notify/util_darwin_test.go new file mode 100644 index 00000000..8c95db37 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/util_test.go b/vendor/github.com/rjeczalik/notify/util_test.go new file mode 100644 index 00000000..dcf6616e --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/util_test.go @@ -0,0 +1,142 @@ +// 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 ( + "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) { + 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/vendor/github.com/rjeczalik/notify/util_unix_test.go b/vendor/github.com/rjeczalik/notify/util_unix_test.go new file mode 100644 index 00000000..b944db2a --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/util_unix_test.go @@ -0,0 +1,125 @@ +// 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) + } +} + +// issue #83 +func TestCanonical_RelativeSymlink(t *testing.T) { + dir, err := ioutil.TempDir(wd, "") + if err != nil { + t.Fatalf("TempDir()=%v", err) + } + var ( + path = filepath.Join(dir, filepath.FromSlash("a/b/c/d/e/f")) + realpath = filepath.Join(dir, filepath.FromSlash("a/b/x/y/z/d/e/f")) + rel = filepath.FromSlash("x/y/z/../z/../z") + chdir = filepath.Join(dir, filepath.FromSlash("a/b")) + ) + defer os.RemoveAll(dir) + if err = os.MkdirAll(realpath, 0755); err != nil { + t.Fatalf("MkdirAll()=%v", err) + } + if err := os.Chdir(chdir); err != nil { + t.Fatalf("Chdir()=%v", err) + } + if err := nonil(os.Symlink(rel, "c"), os.Chdir(wd)); err != nil { + t.Fatalf("Symlink()=%v", err) + } + got, err := canonical(path) + if err != nil { + t.Fatalf("canonical(%s)=%v", path, err) + } + if got != realpath { + t.Fatalf("want canonical()=%s; got %s", realpath, got) + } +} diff --git a/vendor/github.com/rjeczalik/notify/watcher.go b/vendor/github.com/rjeczalik/notify/watcher.go new file mode 100644 index 00000000..34148eff --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/watcher_fen.go b/vendor/github.com/rjeczalik/notify/watcher_fen.go new file mode 100644 index 00000000..dd069f2a --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/watcher_fen.go @@ -0,0 +1,168 @@ +// 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 solaris + +package notify + +import ( + "fmt" + "os" + "syscall" +) + +// newTrigger returns implementation of trigger. +func newTrigger(pthLkp map[string]*watched) trigger { + return &fen{ + pthLkp: pthLkp, + cf: newCfen(), + } +} + +// fen is a structure implementing trigger for FEN. +type fen struct { + // p is a FEN port identifier + p int + // pthLkp is a structure mapping monitored files/dir with data about them, + // shared with parent trg structure + pthLkp map[string]*watched + // cf wraps C operations for FEN + cf cfen +} + +// watched is a data structure representing watched file/directory. +type watched struct { + // p is a path to watched file/directory + p string + // fi provides information about watched file/dir + fi os.FileInfo + // eDir represents events watched directly + eDir Event + // eNonDir represents events watched indirectly + eNonDir Event +} + +// Stop implements trigger. +func (f *fen) Stop() error { + return f.cf.portAlert(f.p) +} + +// Close implements trigger. +func (f *fen) Close() (err error) { + return syscall.Close(f.p) +} + +// NewWatched implements trigger. +func (*fen) NewWatched(p string, fi os.FileInfo) (*watched, error) { + return &watched{p: p, fi: fi}, nil +} + +// Record implements trigger. +func (f *fen) Record(w *watched) { + f.pthLkp[w.p] = w +} + +// Del implements trigger. +func (f *fen) Del(w *watched) { + delete(f.pthLkp, w.p) +} + +func inter2pe(n interface{}) PortEvent { + pe, ok := n.(PortEvent) + if !ok { + panic(fmt.Sprintf("fen: type should be PortEvent, %T instead", n)) + } + return pe +} + +// Watched implements trigger. +func (f *fen) Watched(n interface{}) (*watched, int64, error) { + pe := inter2pe(n) + fo, ok := pe.PortevObject.(*FileObj) + if !ok || fo == nil { + panic(fmt.Sprintf("fen: type should be *FileObj, %T instead", fo)) + } + w, ok := f.pthLkp[fo.Name] + if !ok { + return nil, 0, errNotWatched + } + return w, int64(pe.PortevEvents), nil +} + +// init initializes FEN. +func (f *fen) Init() (err error) { + f.p, err = f.cf.portCreate() + return +} + +func fi2fo(fi os.FileInfo, p string) FileObj { + st, ok := fi.Sys().(*syscall.Stat_t) + if !ok { + panic(fmt.Sprintf("fen: type should be *syscall.Stat_t, %T instead", st)) + } + return FileObj{Name: p, Atim: st.Atim, Mtim: st.Mtim, Ctim: st.Ctim} +} + +// Unwatch implements trigger. +func (f *fen) Unwatch(w *watched) error { + return f.cf.portDissociate(f.p, FileObj{Name: w.p}) +} + +// Watch implements trigger. +func (f *fen) Watch(fi os.FileInfo, w *watched, e int64) error { + return f.cf.portAssociate(f.p, fi2fo(fi, w.p), int(e)) +} + +// Wait implements trigger. +func (f *fen) Wait() (interface{}, error) { + var ( + pe PortEvent + err error + ) + err = f.cf.portGet(f.p, &pe) + return pe, err +} + +// IsStop implements trigger. +func (f *fen) IsStop(n interface{}, err error) bool { + return err == syscall.EBADF || inter2pe(n).PortevSource == srcAlert +} + +func init() { + encode = func(e Event, dir bool) (o int64) { + // Create event is not supported by FEN. Instead FileModified event will + // be registered. If this event will be reported on dir which is to be + // monitored for Create, dir will be rescanned and Create events will + // be generated and returned for new files. In case of files, + // if not requested FileModified event is reported, it will be ignored. + o = int64(e &^ Create) + if (e&Create != 0 && dir) || e&Write != 0 { + o = (o &^ int64(Write)) | int64(FileModified) + } + // Following events are 'exception events' and as such cannot be requested + // explicitly for monitoring or filtered out. If the will be reported + // by FEN and not subscribed with by user, they will be filtered out by + // watcher's logic. + o &= int64(^Rename & ^Remove &^ FileDelete &^ FileRenameTo &^ + FileRenameFrom &^ Unmounted &^ MountedOver) + return + } + nat2not = map[Event]Event{ + FileModified: Write, + FileRenameFrom: Rename, + FileDelete: Remove, + FileAccess: Event(0), + FileAttrib: Event(0), + FileRenameTo: Event(0), + FileTrunc: Event(0), + FileNoFollow: Event(0), + Unmounted: Event(0), + MountedOver: Event(0), + } + not2nat = map[Event]Event{ + Write: FileModified, + Rename: FileRenameFrom, + Remove: FileDelete, + } +} diff --git a/vendor/github.com/rjeczalik/notify/watcher_fen_cgo.go b/vendor/github.com/rjeczalik/notify/watcher_fen_cgo.go new file mode 100644 index 00000000..8ec8ead3 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/watcher_fen_cgo.go @@ -0,0 +1,141 @@ +// 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 solaris + +package notify + +// #include +// #include +// #include +// struct file_obj* newFo() { return (struct file_obj*) malloc(sizeof(struct file_obj)); } +// port_event_t* newPe() { return (port_event_t*) malloc(sizeof(port_event_t)); } +// uintptr_t conv(struct file_obj* fo) { return (uintptr_t) fo; } +// struct file_obj* dconv(uintptr_t fo) { return (struct file_obj*) fo; } +import "C" + +import ( + "syscall" + "unsafe" +) + +const ( + fileAccess = Event(C.FILE_ACCESS) + fileModified = Event(C.FILE_MODIFIED) + fileAttrib = Event(C.FILE_ATTRIB) + fileDelete = Event(C.FILE_DELETE) + fileRenameTo = Event(C.FILE_RENAME_TO) + fileRenameFrom = Event(C.FILE_RENAME_FROM) + fileTrunc = Event(C.FILE_TRUNC) + fileNoFollow = Event(C.FILE_NOFOLLOW) + unmounted = Event(C.UNMOUNTED) + mountedOver = Event(C.MOUNTEDOVER) +) + +// PortEvent is a notify's equivalent of port_event_t. +type PortEvent struct { + PortevEvents int // PortevEvents is an equivalent of portev_events. + PortevSource uint8 // PortevSource is an equivalent of portev_source. + PortevPad uint8 // Portevpad is an equivalent of portev_pad. + PortevObject interface{} // PortevObject is an equivalent of portev_object. + PortevUser uintptr // PortevUser is an equivalent of portev_user. +} + +// FileObj is a notify's equivalent of file_obj. +type FileObj struct { + Atim syscall.Timespec // Atim is an equivalent of fo_atime. + Mtim syscall.Timespec // Mtim is an equivalent of fo_mtime. + Ctim syscall.Timespec // Ctim is an equivalent of fo_ctime. + Pad [3]uintptr // Pad is an equivalent of fo_pad. + Name string // Name is an equivalent of fo_name. +} + +type cfen struct { + p2pe map[string]*C.port_event_t + p2fo map[string]*C.struct_file_obj +} + +func newCfen() cfen { + return cfen{ + p2pe: make(map[string]*C.port_event_t), + p2fo: make(map[string]*C.struct_file_obj), + } +} + +func unix2C(sec int64, nsec int64) (C.time_t, C.long) { + return C.time_t(sec), C.long(nsec) +} + +func (c *cfen) portAssociate(p int, fo FileObj, e int) (err error) { + cfo := C.newFo() + cfo.fo_atime.tv_sec, cfo.fo_atime.tv_nsec = unix2C(fo.Atim.Unix()) + cfo.fo_mtime.tv_sec, cfo.fo_mtime.tv_nsec = unix2C(fo.Mtim.Unix()) + cfo.fo_ctime.tv_sec, cfo.fo_ctime.tv_nsec = unix2C(fo.Ctim.Unix()) + cfo.fo_name = C.CString(fo.Name) + c.p2fo[fo.Name] = cfo + _, err = C.port_associate(C.int(p), srcFile, C.conv(cfo), C.int(e), nil) + return +} + +func (c *cfen) portDissociate(port int, fo FileObj) (err error) { + cfo, ok := c.p2fo[fo.Name] + if !ok { + return errNotWatched + } + _, err = C.port_dissociate(C.int(port), srcFile, C.conv(cfo)) + C.free(unsafe.Pointer(cfo.fo_name)) + C.free(unsafe.Pointer(cfo)) + delete(c.p2fo, fo.Name) + return +} + +const srcAlert = C.PORT_SOURCE_ALERT +const srcFile = C.PORT_SOURCE_FILE +const alertSet = C.PORT_ALERT_SET + +func cfo2fo(cfo *C.struct_file_obj) *FileObj { + // Currently remaining attributes are not used. + if cfo == nil { + return nil + } + var fo FileObj + fo.Name = C.GoString(cfo.fo_name) + return &fo +} + +func (c *cfen) portGet(port int, pe *PortEvent) (err error) { + cpe := C.newPe() + if _, err = C.port_get(C.int(port), cpe, nil); err != nil { + C.free(unsafe.Pointer(cpe)) + return + } + pe.PortevEvents, pe.PortevSource, pe.PortevPad = + int(cpe.portev_events), uint8(cpe.portev_source), uint8(cpe.portev_pad) + pe.PortevObject = cfo2fo(C.dconv(cpe.portev_object)) + pe.PortevUser = uintptr(cpe.portev_user) + C.free(unsafe.Pointer(cpe)) + return +} + +func (c *cfen) portCreate() (int, error) { + p, err := C.port_create() + return int(p), err +} + +func (c *cfen) portAlert(p int) (err error) { + _, err = C.port_alert(C.int(p), alertSet, C.int(666), nil) + return +} + +func (c *cfen) free() { + for i := range c.p2fo { + C.free(unsafe.Pointer(c.p2fo[i].fo_name)) + C.free(unsafe.Pointer(c.p2fo[i])) + } + for i := range c.p2pe { + C.free(unsafe.Pointer(c.p2pe[i])) + } + c.p2fo = make(map[string]*C.struct_file_obj) + c.p2pe = make(map[string]*C.port_event_t) +} diff --git a/vendor/github.com/rjeczalik/notify/watcher_fen_test.go b/vendor/github.com/rjeczalik/notify/watcher_fen_test.go new file mode 100644 index 00000000..7f811ae2 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/watcher_fen_test.go @@ -0,0 +1,92 @@ +// 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 solaris + +package notify + +import ( + "os" + "path/filepath" + "testing" +) + +func fremove(w *W, path string, files []string) WCase { + cas := remove(w, path) + cas.Events[0] = &Call{P: path, E: FileDelete} + for _, f := range files { + cas.Events = append(cas.Events, &Call{P: f, E: FileDelete}) + } + return cas +} + +func fwrite(w *W, path string, p []byte) WCase { + cas := write(w, path, p) + path = cas.Events[0].Path() + cas.Events[0] = &Call{P: path, E: FileModified} + return cas +} + +func frename(w *W, path string, files []string) WCase { + const ext = ".notify" + cas := 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 + ext, E: osSpecificCreate}, + &Call{P: path, E: FileRenameFrom}, + }, + } + for _, f := range files { + cas.Events = append(cas.Events, &Call{P: f, E: FileRenameFrom}) + } + return cas +} + +var events = []Event{ + FileModified, + FileAttrib, + FileRenameFrom, + osSpecificCreate, + FileDelete, +} + +func TestWatcherFen(t *testing.T) { + w := NewWatcherTest(t, "testdata/vfs.txt", events...) + defer w.Close() + + cases := [...]WCase{ + fremove(w, "src/github.com/ppknap/link/include/coost/link", []string{ + "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/stdhelpers", + "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/detail", + "src/github.com/ppknap/link/include/coost/link/link.hpp", + }, + ), + fwrite(w, "src/github.com/rjeczalik/fs/fs.go", []byte("XD")), + fremove(w, "src/github.com/ppknap/link/README.md", nil), + frename(w, "src/github.com/rjeczalik/fs/fs.go", nil), + frename(w, "src/github.com/rjeczalik/fs/cmd/gotree", []string{ + "src/github.com/rjeczalik/fs/cmd/gotree/go.go", + "src/github.com/rjeczalik/fs/cmd/gotree/main.go", + }, + ), + } + + w.ExpectAll(cases[:]) +} diff --git a/vendor/github.com/rjeczalik/notify/watcher_fsevents.go b/vendor/github.com/rjeczalik/notify/watcher_fsevents.go new file mode 100644 index 00000000..9062c17c --- /dev/null +++ b/vendor/github.com/rjeczalik/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 + continue + } + 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/vendor/github.com/rjeczalik/notify/watcher_fsevents_cgo.go b/vendor/github.com/rjeczalik/notify/watcher_fsevents_cgo.go new file mode 100644 index 00000000..5be64632 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/watcher_fsevents_cgo.go @@ -0,0 +1,190 @@ +// 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(uintptr_t, uintptr_t, size_t, uintptr_t, uintptr_t, uintptr_t); + +static FSEventStreamRef EventStreamCreate(FSEventStreamContext * context, uintptr_t info, CFArrayRef paths, FSEventStreamEventId since, CFTimeInterval latency, FSEventStreamCreateFlags flags) { + context->info = (void*) info; + return FSEventStreamCreate(NULL, (FSEventStreamCallback) gostream, context, paths, since, latency, flags); +} + +#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(_, info uintptr, 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)), + }) + } + + } + streamFuncs.get(info)(ev) +} + +// StreamFunc is a callback called when stream receives file events. +type streamFunc func([]FSEvent) + +var streamFuncs = streamFuncRegistry{m: map[uintptr]streamFunc{}} + +type streamFuncRegistry struct { + mu sync.Mutex + m map[uintptr]streamFunc + i uintptr +} + +func (r *streamFuncRegistry) get(id uintptr) streamFunc { + r.mu.Lock() + defer r.mu.Unlock() + return r.m[id] +} + +func (r *streamFuncRegistry) add(fn streamFunc) uintptr { + r.mu.Lock() + defer r.mu.Unlock() + r.i++ + r.m[r.i] = fn + return r.i +} + +func (r *streamFuncRegistry) delete(id uintptr) { + r.mu.Lock() + defer r.mu.Unlock() + delete(r.m, id) +} + +// Stream represents single watch-point which listens for events scheduled by +// the global runloop. +type stream struct { + path string + ref C.FSEventStreamRef + info uintptr +} + +// NewStream creates a stream for given path, listening for file events and +// calling fn upon receiving any. +func newStream(path string, fn streamFunc) *stream { + return &stream{ + path: path, + info: streamFuncs.add(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) + ctx := C.FSEventStreamContext{} + ref := C.EventStreamCreate(&ctx, C.uintptr_t(s.info), 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 + streamFuncs.delete(s.info) +} diff --git a/vendor/github.com/rjeczalik/notify/watcher_fsevents_test.go b/vendor/github.com/rjeczalik/notify/watcher_fsevents_test.go new file mode 100644 index 00000000..1d608778 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/watcher_inotify.go b/vendor/github.com/rjeczalik/notify/watcher_inotify.go new file mode 100644 index 00000000..da016f52 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/watcher_inotify.go @@ -0,0 +1,397 @@ +// 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" + "unsafe" + + "golang.org/x/sys/unix" +) + +// 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 * (unix.SizeofInotifyEvent + unix.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 []unix.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([]unix.EpollEvent, 0), + c: c, + } + runtime.SetFinalizer(i, func(i *inotify) { + i.epollclose() + if i.fd != invalidDescriptor { + unix.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(unix.IN_ALL_EVENTS)) != 0 { + return errors.New("notify: unknown event") + } + if err = i.lazyinit(); err != nil { + return + } + iwd, err := unix.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 := unix.InotifyInit1(unix.IN_CLOEXEC) + if err != nil { + return err + } + i.fd = int32(fd) + if err = i.epollinit(); err != nil { + _, _ = i.epollclose(), unix.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 = unix.EpollCreate1(0); err != nil { + return + } + if err = unix.Pipe(i.pipefd); err != nil { + return + } + i.epes = []unix.EpollEvent{ + {Events: unix.EPOLLIN, Fd: i.fd}, + {Events: unix.EPOLLIN, Fd: int32(i.pipefd[0])}, + } + if err = unix.EpollCtl(i.epfd, unix.EPOLL_CTL_ADD, int(i.fd), &i.epes[0]); err != nil { + return + } + return unix.EpollCtl(i.epfd, unix.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 = unix.Close(i.epfd); err == nil { + i.epfd = invalidDescriptor + } + } + for n, fd := range i.pipefd { + if fd != invalidDescriptor { + switch e := unix.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([]unix.EpollEvent, 1) + fd := atomic.LoadInt32(&i.fd) + for { + switch _, err := unix.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 = unix.Close(int(fd)); err != nil && err != unix.EINTR { + panic("notify: close(2) error " + err.Error()) + } + atomic.StoreInt32(&i.fd, invalidDescriptor) + if err = i.epollclose(); err != nil && err != unix.EINTR { + panic("notify: epollclose error " + err.Error()) + } + close(esch) + return + } + case unix.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 := unix.Read(int(i.fd), i.buffer[:]) + if err != nil || n < unix.SizeofInotifyEvent { + return + } + var sys *unix.InotifyEvent + nmin := n - unix.SizeofInotifyEvent + for pos, path := 0, ""; pos <= nmin; { + sys = (*unix.InotifyEvent)(unsafe.Pointer(&i.buffer[pos])) + pos += unix.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: unix.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&(unix.IN_IGNORED|unix.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: unix.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 = unix.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 := unix.InotifyRmWatch(int(i.fd), uint32(iwd)); e != nil && err == nil { + err = e + } + delete(i.m, iwd) + } + switch _, errwrite := unix.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/vendor/github.com/rjeczalik/notify/watcher_inotify_test.go b/vendor/github.com/rjeczalik/notify/watcher_inotify_test.go new file mode 100644 index 00000000..5feadafc --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/watcher_kqueue.go b/vendor/github.com/rjeczalik/notify/watcher_kqueue.go new file mode 100644 index 00000000..6d500b70 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/watcher_kqueue.go @@ -0,0 +1,193 @@ +// 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 ( + "fmt" + "os" + "syscall" +) + +// newTrigger returns implementation of trigger. +func newTrigger(pthLkp map[string]*watched) trigger { + return &kq{ + pthLkp: pthLkp, + idLkp: make(map[int]*watched), + } +} + +// kq is a structure implementing trigger for kqueue. +type kq struct { + // 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 structure mapping monitored files/dir with data about them, + // shared with parent trg structure + pthLkp map[string]*watched +} + +// 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 +} + +// Stop implements trigger. +func (k *kq) Stop() (err error) { + // trigger event used to interrupt Kevent call. + _, err = syscall.Write(k.pipefds[1], []byte{0x00}) + return +} + +// Close implements trigger. +func (k *kq) Close() error { + return syscall.Close(k.fd) +} + +// NewWatched implements trigger. +func (*kq) NewWatched(p string, fi os.FileInfo) (*watched, error) { + fd, err := syscall.Open(p, syscall.O_NONBLOCK|syscall.O_RDONLY, 0) + if err != nil { + return nil, err + } + return &watched{fd: fd, p: p, fi: fi}, nil +} + +// Record implements trigger. +func (k *kq) Record(w *watched) { + k.idLkp[w.fd], k.pthLkp[w.p] = w, w +} + +// Del implements trigger. +func (k *kq) Del(w *watched) { + syscall.Close(w.fd) + delete(k.idLkp, w.fd) + delete(k.pthLkp, w.p) +} + +func inter2kq(n interface{}) syscall.Kevent_t { + kq, ok := n.(syscall.Kevent_t) + if !ok { + panic(fmt.Sprintf("kqueue: type should be Kevent_t, %T instead", n)) + } + return kq +} + +// Init implements trigger. +func (k *kq) 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 nonil(err, k.Close()) + } + var kevn [1]syscall.Kevent_t + syscall.SetKevent(&kevn[0], k.pipefds[0], syscall.EVFILT_READ, syscall.EV_ADD) + if _, err = syscall.Kevent(k.fd, kevn[:], nil, nil); err != nil { + return nonil(err, k.Close()) + } + return +} + +// Unwatch implements trigger. +func (k *kq) Unwatch(w *watched) (err error) { + var kevn [1]syscall.Kevent_t + syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE, syscall.EV_DELETE) + + _, err = syscall.Kevent(k.fd, kevn[:], nil, nil) + return +} + +// Watch implements trigger. +func (k *kq) Watch(fi os.FileInfo, w *watched, e int64) (err error) { + var kevn [1]syscall.Kevent_t + syscall.SetKevent(&kevn[0], w.fd, syscall.EVFILT_VNODE, + syscall.EV_ADD|syscall.EV_CLEAR) + kevn[0].Fflags = uint32(e) + + _, err = syscall.Kevent(k.fd, kevn[:], nil, nil) + return +} + +// Wait implements trigger. +func (k *kq) Wait() (interface{}, error) { + var ( + kevn [1]syscall.Kevent_t + err error + ) + kevn[0] = syscall.Kevent_t{} + _, err = syscall.Kevent(k.fd, nil, kevn[:], nil) + + return kevn[0], err +} + +// Watched implements trigger. +func (k *kq) Watched(n interface{}) (*watched, int64, error) { + kevn, ok := n.(syscall.Kevent_t) + if !ok { + panic(fmt.Sprintf("kq: type should be syscall.Kevent_t, %T instead", kevn)) + } + if _, ok = k.idLkp[int(kevn.Ident)]; !ok { + return nil, 0, errNotWatched + } + return k.idLkp[int(kevn.Ident)], int64(kevn.Fflags), nil +} + +// IsStop implements trigger. +func (k *kq) IsStop(n interface{}, err error) bool { + return int(inter2kq(n).Ident) == k.pipefds[0] +} + +func init() { + encode = func(e Event, dir bool) (o int64) { + // Create event is not supported by kqueue. Instead NoteWrite event will + // be registered for a directory. If this event will be reported on dir + // which is to be monitored for Create, dir will be rescanned + // and Create events will be generated and returned for new files. + // In case of files, if not requested NoteRename event is reported, + // it will be ignored. + o = int64(e &^ Create) + if (e&Create != 0 && dir) || e&Write != 0 { + o = (o &^ int64(Write)) | int64(NoteWrite) + } + if e&Rename != 0 { + o = (o &^ int64(Rename)) | int64(NoteRename) + } + if e&Remove != 0 { + o = (o &^ int64(Remove)) | int64(NoteDelete) + } + return + } + nat2not = map[Event]Event{ + NoteWrite: Write, + NoteRename: Rename, + NoteDelete: Remove, + NoteExtend: Event(0), + NoteAttrib: Event(0), + NoteRevoke: Event(0), + NoteLink: Event(0), + } + not2nat = map[Event]Event{ + Write: NoteWrite, + Rename: NoteRename, + Remove: NoteDelete, + } +} diff --git a/vendor/github.com/rjeczalik/notify/watcher_kqueue_test.go b/vendor/github.com/rjeczalik/notify/watcher_kqueue_test.go new file mode 100644 index 00000000..8c07dc10 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/watcher_kqueue_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 dragonfly freebsd netbsd openbsd + +package notify + +import ( + "os" + "path/filepath" + "testing" +) + +func kqremove(w *W, path string, files []string) WCase { + cas := remove(w, path) + cas.Events[0] = &Call{P: path, E: NoteDelete} + for _, f := range files { + cas.Events = append(cas.Events, &Call{P: f, E: NoteDelete}) + } + return cas +} + +func kqwrite(w *W, path string, p []byte) WCase { + cas := write(w, path, p) + path = cas.Events[0].Path() + cas.Events[0] = &Call{P: path, E: NoteExtend | NoteWrite} + return cas +} + +func kqrename(w *W, path string, files []string) WCase { + const ext = ".notify" + cas := 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 + ext, E: osSpecificCreate}, + &Call{P: path, E: NoteRename}, + }, + } + for _, f := range files { + cas.Events = append(cas.Events, &Call{P: f, E: NoteRename}) + } + return cas +} + +func kqlink(w *W, path string) WCase { + const ext = ".notify" + return WCase{ + Action: func() { + file := filepath.Join(w.root, path) + if err := os.Link(file, file+ext); err != nil { + w.Fatalf("Link(%q, %q)=%v", path, path+ext, err) + } + }, + Events: []EventInfo{ + &Call{P: path, E: NoteLink}, + &Call{P: path + ext, E: osSpecificCreate}, + }, + } +} + +var events = []Event{ + NoteWrite, + NoteAttrib, + NoteRename, + osSpecificCreate, + NoteDelete, + NoteExtend, + NoteLink, +} + +func TestWatcherKqueue(t *testing.T) { + w := NewWatcherTest(t, "testdata/vfs.txt", events...) + defer w.Close() + + cases := [...]WCase{ + kqremove(w, "src/github.com/ppknap/link/include/coost/link", []string{ + "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/stdhelpers", + "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/detail", + "src/github.com/ppknap/link/include/coost/link/link.hpp", + }, + ), + kqwrite(w, "src/github.com/rjeczalik/fs/fs.go", []byte("XD")), + kqremove(w, "src/github.com/ppknap/link/README.md", nil), + kqlink(w, "src/github.com/rjeczalik/fs/LICENSE"), + kqrename(w, "src/github.com/rjeczalik/fs/fs.go", nil), + kqrename(w, "src/github.com/rjeczalik/fs/cmd/gotree", []string{ + "src/github.com/rjeczalik/fs/cmd/gotree/go.go", + "src/github.com/rjeczalik/fs/cmd/gotree/main.go", + }, + ), + } + + w.ExpectAll(cases[:]) +} diff --git a/vendor/github.com/rjeczalik/notify/watcher_readdcw.go b/vendor/github.com/rjeczalik/notify/watcher_readdcw.go new file mode 100644 index 00000000..5923bfdd --- /dev/null +++ b/vendor/github.com/rjeczalik/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_LONG_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/vendor/github.com/rjeczalik/notify/watcher_readdcw_test.go b/vendor/github.com/rjeczalik/notify/watcher_readdcw_test.go new file mode 100644 index 00000000..ea15b4a4 --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/watcher_recursive_test.go b/vendor/github.com/rjeczalik/notify/watcher_recursive_test.go new file mode 100644 index 00000000..3b1f148f --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/watcher_stub.go b/vendor/github.com/rjeczalik/notify/watcher_stub.go new file mode 100644 index 00000000..68b9c135 --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/watcher_stub.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 !darwin,!linux,!freebsd,!dragonfly,!netbsd,!openbsd,!windows +// +build !kqueue,!solaris + +package notify + +import "errors" + +type stub struct{ error } + +// newWatcher stub. +func newWatcher(chan<- EventInfo) watcher { + 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/vendor/github.com/rjeczalik/notify/watcher_test.go b/vendor/github.com/rjeczalik/notify/watcher_test.go new file mode 100644 index 00000000..911cba4b --- /dev/null +++ b/vendor/github.com/rjeczalik/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 solaris + +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/vendor/github.com/rjeczalik/notify/watcher_trigger.go b/vendor/github.com/rjeczalik/notify/watcher_trigger.go new file mode 100644 index 00000000..d079d59b --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/watcher_trigger.go @@ -0,0 +1,432 @@ +// 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 solaris + +// watcher_trigger is used for FEN and kqueue which behave similarly: +// only files and dirs can be watched directly, but not files inside dirs. +// As a result Create events have to be generated by implementation when +// after Write event is returned for watched dir, it is rescanned and Create +// event is returned for new files and these are automatically added +// to watchlist. In case of removal of watched directory, native system returns +// events for all files, but for Rename, they also need to be generated. +// As a result native system works as something like trigger for rescan, +// but contains additional data about dir in which changes occurred. For files +// detailed data is returned. +// Usage of watcher_trigger requires: +// - trigger implementation, +// - encode func, +// - not2nat, nat2not maps. +// Required manual operations on filesystem can lead to loss of precision. + +package notify + +import ( + "os" + "path/filepath" + "strings" + "sync" + "syscall" +) + +// trigger is to be implemented by platform implementation like FEN or kqueue. +type trigger interface { + // Close closes watcher's main native file descriptor. + Close() error + // Stop waiting for new events. + Stop() error + // Create new instance of watched. + NewWatched(string, os.FileInfo) (*watched, error) + // Record internally new *watched instance. + Record(*watched) + // Del removes internal copy of *watched instance. + Del(*watched) + // Watched returns *watched instance and native events for native type. + Watched(interface{}) (*watched, int64, error) + // Init initializes native watcher call. + Init() error + // Watch starts watching provided file/dir. + Watch(os.FileInfo, *watched, int64) error + // Unwatch stops watching provided file/dir. + Unwatch(*watched) error + // Wait for new events. + Wait() (interface{}, error) + // IsStop checks if Wait finished because of request watcher's stop. + IsStop(n interface{}, err error) bool +} + +// encode Event to native representation. Implementation is to be provided by +// platform specific implementation. +var encode func(Event, bool) int64 + +var ( + // nat2not matches native events to notify's ones. To be initialized by + // platform dependent implementation. + nat2not map[Event]Event + // not2nat matches notify's events to native ones. To be initialized by + // platform dependent implementation. + not2nat map[Event]Event +) + +// trg is a main structure implementing watcher. +type trg struct { + sync.Mutex + // s is a channel used to stop monitoring. + s chan struct{} + // c is a channel used to pass events further. + c chan<- EventInfo + // pthLkp is a data structure mapping file names with data about watching + // represented by them files/directories. + pthLkp map[string]*watched + // t is a platform dependent implementation of trigger. + t trigger +} + +// newWatcher returns new watcher's implementation. +func newWatcher(c chan<- EventInfo) watcher { + t := &trg{ + s: make(chan struct{}, 1), + pthLkp: make(map[string]*watched, 0), + c: c, + } + t.t = newTrigger(t.pthLkp) + if err := t.t.Init(); err != nil { + panic(err) + } + go t.monitor() + return t +} + +// Close implements watcher. +func (t *trg) Close() (err error) { + t.Lock() + if err = t.t.Stop(); err != nil { + t.Unlock() + return + } + <-t.s + var e error + for _, w := range t.pthLkp { + if e = t.unwatch(w.p, w.fi); e != nil { + dbgprintf("trg: unwatch %q failed: %q\n", w.p, e) + err = nonil(err, e) + } + } + if e = t.t.Close(); e != nil { + dbgprintf("trg: closing native watch failed: %q\n", e) + err = nonil(err, e) + } + t.Unlock() + return +} + +// send reported events one by one through chan. +func (t *trg) send(evn []event) { + for i := range evn { + t.c <- &evn[i] + } +} + +// singlewatch starts to watch given p file/directory. +func (t *trg) singlewatch(p string, e Event, direct mode, fi os.FileInfo) (err error) { + w, ok := t.pthLkp[p] + if !ok { + if w, err = t.t.NewWatched(p, fi); err != nil { + return + } + } + switch direct { + case dir: + w.eDir |= e + case ndir: + w.eNonDir |= e + case both: + w.eDir |= e + w.eNonDir |= e + } + if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil { + return + } + if !ok { + t.t.Record(w) + return nil + } + return errAlreadyWatched +} + +// decode converts event received from native to notify.Event +// representation taking into account requested events (w). +func decode(o int64, w Event) (e Event) { + for f, n := range nat2not { + if o&int64(f) != 0 { + if w&f != 0 { + e |= f + } + if w&n != 0 { + e |= n + } + } + } + + return +} + +func (t *trg) watch(p string, e Event, fi os.FileInfo) error { + if err := t.singlewatch(p, e, dir, fi); err != nil { + if err != errAlreadyWatched { + return nil + } + } + if fi.IsDir() { + err := t.walk(p, func(fi os.FileInfo) (err error) { + if err = t.singlewatch(filepath.Join(p, fi.Name()), e, ndir, + fi); err != nil { + if err != errAlreadyWatched { + return + } + } + return nil + }) + if err != nil { + return err + } + } + return nil +} + +// walk runs f func on each file/dir from p directory. +func (t *trg) walk(p string, fn 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 := fn(ls[i]); err != nil { + return err + } + } + return nil +} + +func (t *trg) unwatch(p string, fi os.FileInfo) error { + if fi.IsDir() { + err := t.walk(p, func(fi os.FileInfo) error { + err := t.singleunwatch(filepath.Join(p, fi.Name()), ndir) + if err != errNotWatched { + return err + } + return nil + }) + if err != nil { + return err + } + } + return t.singleunwatch(p, dir) +} + +// Watch implements Watcher interface. +func (t *trg) Watch(p string, e Event) error { + fi, err := os.Stat(p) + if err != nil { + return err + } + t.Lock() + err = t.watch(p, e, fi) + t.Unlock() + return err +} + +// Unwatch implements Watcher interface. +func (t *trg) Unwatch(p string) error { + fi, err := os.Stat(p) + if err != nil { + return err + } + t.Lock() + err = t.unwatch(p, fi) + t.Unlock() + return err +} + +// Rewatch implements Watcher interface. +// +// TODO(rjeczalik): This is a naive hack. Rewrite might help. +func (t *trg) Rewatch(p string, _, e Event) error { + fi, err := os.Stat(p) + if err != nil { + return err + } + t.Lock() + if err = t.unwatch(p, fi); err == nil { + // TODO(rjeczalik): If watch fails then we leave trigger in inconsistent + // state. Handle? Panic? Native version of rewatch? + err = t.watch(p, e, fi) + } + t.Unlock() + return nil +} + +func (*trg) file(w *watched, n interface{}, e Event) (evn []event) { + evn = append(evn, event{w.p, e, w.fi.IsDir(), n}) + return +} + +func (t *trg) dir(w *watched, n interface{}, e, ge 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 native impl. + // However events for rename must be generated for all monitored files + // inside of moved directory, because native impl does not report it independently + // for each file descriptor being moved in result of move action on + // parent directory. + if (ge & (not2nat[Rename] | not2nat[Remove])) != 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 & ^not2nat[Write], true, n}) + if ge¬2nat[Rename] != 0 { + for p := range t.pthLkp { + if strings.HasPrefix(p, w.p+string(os.PathSeparator)) { + if err := t.singleunwatch(p, both); err != nil && err != errNotWatched && + !os.IsNotExist(err) { + dbgprintf("trg: failed stop watching moved file (%q): %q\n", + p, err) + } + if (w.eDir|w.eNonDir)&(not2nat[Rename]|Rename) != 0 { + evn = append(evn, event{ + p, (w.eDir | w.eNonDir) & e &^ Write &^ not2nat[Write], + w.fi.IsDir(), nil, + }) + } + } + } + } + t.t.Del(w) + return + } + if (ge & not2nat[Write]) != 0 { + switch err := t.walk(w.p, func(fi os.FileInfo) error { + p := filepath.Join(w.p, fi.Name()) + switch err := t.singlewatch(p, w.eDir, ndir, fi); { + case os.IsNotExist(err) && ((w.eDir & Remove) != 0): + evn = append(evn, event{p, Remove, fi.IsDir(), n}) + case err == errAlreadyWatched: + case err != nil: + dbgprintf("trg: watching %q failed: %q", p, err) + case (w.eDir & Create) != 0: + evn = append(evn, event{p, Create, fi.IsDir(), n}) + default: + } + return nil + }); { + case os.IsNotExist(err): + return + case err != nil: + dbgprintf("trg: dir processing failed: %q", err) + default: + } + } + return +} + +type mode uint + +const ( + dir mode = iota + ndir + both +) + +// unwatch stops watching p file/directory. +func (t *trg) singleunwatch(p string, direct mode) error { + w, ok := t.pthLkp[p] + if !ok { + return errNotWatched + } + switch direct { + case dir: + w.eDir = 0 + case ndir: + w.eNonDir = 0 + case both: + w.eDir, w.eNonDir = 0, 0 + } + if err := t.t.Unwatch(w); err != nil { + return err + } + if w.eNonDir|w.eDir != 0 { + mod := dir + if w.eNonDir == 0 { + mod = ndir + } + if err := t.singlewatch(p, w.eNonDir|w.eDir, mod, + w.fi); err != nil && err != errAlreadyWatched { + return err + } + } else { + t.t.Del(w) + } + return nil +} + +func (t *trg) monitor() { + var ( + n interface{} + err error + ) + for { + switch n, err = t.t.Wait(); { + case err == syscall.EINTR: + case t.t.IsStop(n, err): + t.s <- struct{}{} + return + case err != nil: + dbgprintf("trg: failed to read events: %q\n", err) + default: + t.send(t.process(n)) + } + } +} + +// process event returned by native call. +func (t *trg) process(n interface{}) (evn []event) { + t.Lock() + w, ge, err := t.t.Watched(n) + if err != nil { + t.Unlock() + dbgprintf("trg: %v event lookup failed: %q", Event(ge), err) + return + } + + e := decode(ge, w.eDir|w.eNonDir) + if ge&int64(not2nat[Remove]|not2nat[Rename]) == 0 { + switch fi, err := os.Stat(w.p); { + case err != nil: + default: + if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir, fi.IsDir())); err != nil { + dbgprintf("trg: %q is no longer watched: %q", w.p, err) + t.t.Del(w) + } + } + } + if e == Event(0) && (!w.fi.IsDir() || (ge&int64(not2nat[Write])) == 0) { + t.Unlock() + return + } + + if w.fi.IsDir() { + evn = append(evn, t.dir(w, n, e, Event(ge))...) + } else { + evn = append(evn, t.file(w, n, e)...) + } + if Event(ge)&(not2nat[Remove]|not2nat[Rename]) != 0 { + t.t.Del(w) + } + t.Unlock() + return +} diff --git a/vendor/github.com/rjeczalik/notify/watcher_trigger_test.go b/vendor/github.com/rjeczalik/notify/watcher_trigger_test.go new file mode 100644 index 00000000..e59cbbcd --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/watcher_trigger_test.go @@ -0,0 +1,21 @@ +// Copyright (c) 2014-2017 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 solaris + +package notify + +import "testing" + +func TestWatcherCreateOnly(t *testing.T) { + w := NewWatcherTest(t, "testdata/vfs.txt", Create) + defer w.Close() + + cases := [...]WCase{ + create(w, "dir/"), + create(w, "dir2/"), + } + + w.ExpectAny(cases[:]) +} diff --git a/vendor/github.com/rjeczalik/notify/watchpoint.go b/vendor/github.com/rjeczalik/notify/watchpoint.go new file mode 100644 index 00000000..5afc914f --- /dev/null +++ b/vendor/github.com/rjeczalik/notify/watchpoint.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. + +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 + dbgprintf("dropped %s on %q: receiver too slow", ei.Event(), ei.Path()) + } + } + } +} + +func (wp watchpoint) Total() Event { + return wp[nil] &^ internal +} + +func (wp watchpoint) IsRecursive() bool { + return wp[nil]&recursive != 0 +} diff --git a/vendor/github.com/rjeczalik/notify/watchpoint_other.go b/vendor/github.com/rjeczalik/notify/watchpoint_other.go new file mode 100644 index 00000000..9bb381db --- /dev/null +++ b/vendor/github.com/rjeczalik/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 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 +} diff --git a/vendor/github.com/rjeczalik/notify/watchpoint_readdcw.go b/vendor/github.com/rjeczalik/notify/watchpoint_readdcw.go new file mode 100644 index 00000000..9fd1e1df --- /dev/null +++ b/vendor/github.com/rjeczalik/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/vendor/github.com/rjeczalik/notify/watchpoint_test.go b/vendor/github.com/rjeczalik/notify/watchpoint_test.go new file mode 100644 index 00000000..e35483d1 --- /dev/null +++ b/vendor/github.com/rjeczalik/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.11.0