aboutsummaryrefslogtreecommitdiffstats
path: root/Godeps/_workspace/src
diff options
context:
space:
mode:
Diffstat (limited to 'Godeps/_workspace/src')
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/.gitignore88
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/.travis.yml30
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/AUTHORS10
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/LICENSE21
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/README.md21
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/appveyor.yml24
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/debug.go11
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/debug_debug.go43
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/doc.go40
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/event.go143
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/event_fen.go46
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/event_fsevents.go71
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/event_inotify.go75
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/event_kqueue.go59
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/event_readdcw.go108
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/event_stub.go31
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/event_trigger.go22
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/node.go271
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/notify.go74
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/tree.go22
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/tree_nonrecursive.go292
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/tree_recursive.go354
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/util.go150
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/watcher.go85
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fen.go170
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fen_cgo.go141
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents.go319
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents_cgo.go190
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_inotify.go396
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_kqueue.go192
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_readdcw.go574
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_stub.go23
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_trigger.go438
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/watchpoint.go103
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/watchpoint_other.go23
-rw-r--r--Godeps/_workspace/src/github.com/rjeczalik/notify/watchpoint_readdcw.go38
36 files changed, 4698 insertions, 0 deletions
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/.gitignore b/Godeps/_workspace/src/github.com/rjeczalik/notify/.gitignore
new file mode 100644
index 000000000..86d4fa8b1
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/.gitignore
@@ -0,0 +1,88 @@
+# Created by https://www.gitignore.io
+
+### OSX ###
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+# Thumbnails
+._*
+
+# Files that might appear on external disk
+.Spotlight-V100
+.Trashes
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+
+### Windows ###
+# Windows image file caches
+Thumbs.db
+ehthumbs.db
+
+# Folder config file
+Desktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+
+### Linux ###
+*~
+
+# KDE directory preferences
+.directory
+
+
+### Go ###
+# Compiled Object files, Static and Dynamic libs (Shared Objects)
+*.o
+*.a
+*.so
+
+# Folders
+_obj
+_test
+
+# Architecture specific extensions/prefixes
+*.[568vq]
+[568vq].out
+
+*.cgo1.go
+*.cgo2.c
+_cgo_defun.c
+_cgo_gotypes.go
+_cgo_export.*
+
+_testmain.go
+
+*.exe
+*.test
+*.prof
+
+
+### vim ###
+[._]*.s[a-w][a-z]
+[._]s[a-w][a-z]
+*.un~
+Session.vim
+.netrwhist
+*~
+
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/.travis.yml b/Godeps/_workspace/src/github.com/rjeczalik/notify/.travis.yml
new file mode 100644
index 000000000..4f1f5f25e
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/.travis.yml
@@ -0,0 +1,30 @@
+language: go
+
+go:
+ - 1.4.3
+ - 1.6
+
+os:
+ - linux
+ - osx
+
+matrix:
+ include:
+ - os: osx
+ go: 1.6
+ env:
+ - GOFLAGS="-tags kqueue"
+
+env:
+ global:
+ - GOBIN=$HOME/bin
+ - PATH=$HOME/bin:$PATH
+
+install:
+ - go get golang.org/x/tools/cmd/vet
+ - go get -t -v ./...
+
+script:
+ - go tool vet -all .
+ - go install $GOFLAGS ./...
+ - go test -v -race $GOFLAGS ./...
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/AUTHORS b/Godeps/_workspace/src/github.com/rjeczalik/notify/AUTHORS
new file mode 100644
index 000000000..9262eae69
--- /dev/null
+++ b/Godeps/_workspace/src/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 <blaszczykpb@gmail.com>
+Pawel Knap <pawelknap88@gmail.com>
+Rafal Jeczalik <rjeczalik@gmail.com>
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/LICENSE b/Godeps/_workspace/src/github.com/rjeczalik/notify/LICENSE
new file mode 100644
index 000000000..3e678817b
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/rjeczalik/notify/README.md b/Godeps/_workspace/src/github.com/rjeczalik/notify/README.md
new file mode 100644
index 000000000..a728d1dd0
--- /dev/null
+++ b/Godeps/_workspace/src/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)
+
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/appveyor.yml b/Godeps/_workspace/src/github.com/rjeczalik/notify/appveyor.yml
new file mode 100644
index 000000000..16d09ac3b
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/appveyor.yml
@@ -0,0 +1,24 @@
+version: "{build}"
+
+os: Windows Server 2012 R2
+
+clone_folder: c:\projects\src\github.com\rjeczalik\notify
+
+environment:
+ PATH: c:\projects\bin;%PATH%
+ GOPATH: c:\projects
+ NOTIFY_TIMEOUT: 5s
+
+install:
+ - go version
+ - go get golang.org/x/tools/cmd/vet
+ - go get -v -t ./...
+
+build_script:
+ - go tool vet -all .
+ - go build ./...
+ - go test -v -race ./...
+
+test: off
+
+deploy: off
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/debug.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/debug.go
new file mode 100644
index 000000000..bd9bc468d
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/rjeczalik/notify/debug_debug.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/debug_debug.go
new file mode 100644
index 000000000..f0622917f
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/rjeczalik/notify/doc.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/doc.go
new file mode 100644
index 000000000..8a99ddda6
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/doc.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.
+
+// 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
+// strategy for creating and closing filesystem watches and dispatching filesystem
+// events to user channels.
+//
+// An event set is just an event list joint using bitwise OR operator
+// into a single event value.
+//
+// A filesystem watch or just a watch is platform-specific entity which represents
+// a single path registered for notifications for specific event set. Setting a watch
+// means using platform-specific API calls for creating / initializing said watch.
+// For each watcher the API call is:
+//
+// - FSEvents: FSEventStreamCreate
+// - inotify: notify_add_watch
+// - kqueue: kevent
+// - ReadDirectoryChangesW: CreateFile+ReadDirectoryChangesW
+// - 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, recurisve and
+// non-recursive ones as well.
+package notify
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/event.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/event.go
new file mode 100644
index 000000000..e045edcec
--- /dev/null
+++ b/Godeps/_workspace/src/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 *syscall.InotifyEvent
+// value, defined as:
+//
+// type InotifyEvent struct {
+// Wd int32 // Watch descriptor
+// Mask uint32 // Mask describing event
+// Cookie uint32 // Unique cookie associating related events (for rename(2))
+// Len uint32 // Size of name field
+// Name [0]uint8 // Optional null-terminated name
+// }
+//
+// More information about inotify masks and the usage of inotify_event structure
+// can be found at:
+//
+// http://man7.org/linux/man-pages/man7/inotify.7.html
+//
+// Under Darwin, DragonFlyBSD, FreeBSD, NetBSD, OpenBSD (kqueue) Sys() always
+// returns a non-nil *notify.Kevent value, which is defined as:
+//
+// type Kevent struct {
+// Kevent *syscall.Kevent_t // Kevent is a kqueue specific structure
+// FI os.FileInfo // FI describes file/dir
+// }
+//
+// More information about syscall.Kevent_t can be found at:
+//
+// https://www.freebsd.org/cgi/man.cgi?query=kqueue
+//
+// Under Windows (ReadDirectoryChangesW) Sys() always returns nil. The documentation
+// of watcher's WinAPI function can be found at:
+//
+// https://msdn.microsoft.com/en-us/library/windows/desktop/aa365465%28v=vs.85%29.aspx
+type EventInfo interface {
+ Event() Event // event value for the filesystem action
+ Path() string // real path of the file or directory
+ Sys() interface{} // underlying data source (can return nil)
+}
+
+type isDirer interface {
+ isDir() (bool, error)
+}
+
+var _ fmt.Stringer = (*event)(nil)
+var _ isDirer = (*event)(nil)
+
+// String implements fmt.Stringer interface.
+func (e *event) String() string {
+ return e.Event().String() + `: "` + e.Path() + `"`
+}
+
+var estr = map[Event]string{
+ Create: "notify.Create",
+ Remove: "notify.Remove",
+ Write: "notify.Write",
+ Rename: "notify.Rename",
+ // Display name for recursive event is added only for debugging
+ // purposes. It's an internal event after all and won't be exposed to the
+ // user. Having Recursive event printable is helpful, e.g. for reading
+ // testing failure messages:
+ //
+ // --- FAIL: TestWatchpoint (0.00 seconds)
+ // watchpoint_test.go:64: want diff=[notify.Remove notify.Create|notify.Remove];
+ // got [notify.Remove notify.Remove|notify.Create] (i=1)
+ //
+ // Yup, here the diff have Recursive event inside. Go figure.
+ recursive: "recursive",
+ omit: "omit",
+}
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/event_fen.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/event_fen.go
new file mode 100644
index 000000000..a3079385d
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/event_fen.go
@@ -0,0 +1,46 @@
+// 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 = fileAccess
+ FileModified = fileModified
+ FileAttrib = fileAttrib
+ FileDelete = fileDelete
+ FileRenameTo = fileRenameTo
+ FileRenameFrom = fileRenameFrom
+ FileTrunc = fileTrunc
+ FileNoFollow = fileNoFollow
+ Unmounted = unmounted
+ 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/Godeps/_workspace/src/github.com/rjeczalik/notify/event_fsevents.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/event_fsevents.go
new file mode 100644
index 000000000..6ded80b2c
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/rjeczalik/notify/event_inotify.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/event_inotify.go
new file mode 100644
index 000000000..82954a9b3
--- /dev/null
+++ b/Godeps/_workspace/src/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 "syscall"
+
+// Platform independent event values.
+const (
+ osSpecificCreate Event = 0x100000 << iota
+ osSpecificRemove
+ osSpecificWrite
+ osSpecificRename
+ // internal
+ // recursive is used to distinguish recursive eventsets from non-recursive ones
+ recursive
+ // omit is used for dispatching internal events; only those events are sent
+ // for which both the event and the watchpoint has omit in theirs event sets.
+ omit
+)
+
+// Inotify specific masks are legal, implemented events that are guaranteed to
+// work with notify package on linux-based systems.
+const (
+ InAccess = Event(syscall.IN_ACCESS) // File was accessed
+ InModify = Event(syscall.IN_MODIFY) // File was modified
+ InAttrib = Event(syscall.IN_ATTRIB) // Metadata changed
+ InCloseWrite = Event(syscall.IN_CLOSE_WRITE) // Writtable file was closed
+ InCloseNowrite = Event(syscall.IN_CLOSE_NOWRITE) // Unwrittable file closed
+ InOpen = Event(syscall.IN_OPEN) // File was opened
+ InMovedFrom = Event(syscall.IN_MOVED_FROM) // File was moved from X
+ InMovedTo = Event(syscall.IN_MOVED_TO) // File was moved to Y
+ InCreate = Event(syscall.IN_CREATE) // Subfile was created
+ InDelete = Event(syscall.IN_DELETE) // Subfile was deleted
+ InDeleteSelf = Event(syscall.IN_DELETE_SELF) // Self was deleted
+ InMoveSelf = Event(syscall.IN_MOVE_SELF) // Self was moved
+)
+
+var osestr = map[Event]string{
+ InAccess: "notify.InAccess",
+ InModify: "notify.InModify",
+ InAttrib: "notify.InAttrib",
+ InCloseWrite: "notify.InCloseWrite",
+ InCloseNowrite: "notify.InCloseNowrite",
+ InOpen: "notify.InOpen",
+ InMovedFrom: "notify.InMovedFrom",
+ InMovedTo: "notify.InMovedTo",
+ InCreate: "notify.InCreate",
+ InDelete: "notify.InDelete",
+ InDeleteSelf: "notify.InDeleteSelf",
+ InMoveSelf: "notify.InMoveSelf",
+}
+
+// Inotify behavior events are not **currently** supported by notify package.
+const (
+ inDontFollow = Event(syscall.IN_DONT_FOLLOW)
+ inExclUnlink = Event(syscall.IN_EXCL_UNLINK)
+ inMaskAdd = Event(syscall.IN_MASK_ADD)
+ inOneshot = Event(syscall.IN_ONESHOT)
+ inOnlydir = Event(syscall.IN_ONLYDIR)
+)
+
+type event struct {
+ sys syscall.InotifyEvent
+ path string
+ event Event
+}
+
+func (e *event) Event() Event { return e.event }
+func (e *event) Path() string { return e.path }
+func (e *event) Sys() interface{} { return &e.sys }
+func (e *event) isDir() (bool, error) { return e.sys.Mask&syscall.IN_ISDIR != 0, nil }
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/event_kqueue.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/event_kqueue.go
new file mode 100644
index 000000000..82e2d8cca
--- /dev/null
+++ b/Godeps/_workspace/src/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 even reported when the unlink() system call was called
+ // on the file referenced by the descriptor.
+ NoteDelete = Event(syscall.NOTE_DELETE)
+ // NoteWrite is an event reported when a write occurred on the file
+ // referenced by the descriptor.
+ NoteWrite = Event(syscall.NOTE_WRITE)
+ // NoteExtend is an event reported when the file referenced by the
+ // descriptor was extended.
+ NoteExtend = Event(syscall.NOTE_EXTEND)
+ // NoteAttrib is an event reported when the file referenced
+ // by the descriptor had its attributes changed.
+ NoteAttrib = Event(syscall.NOTE_ATTRIB)
+ // NoteLink is an event reported when the link count on the file changed.
+ NoteLink = Event(syscall.NOTE_LINK)
+ // NoteRename is an event reported when the file referenced
+ // by the descriptor was renamed.
+ NoteRename = Event(syscall.NOTE_RENAME)
+ // NoteRevoke is an event reported when access to the file was revoked via
+ // revoke(2) or the underlying file system was unmounted.
+ NoteRevoke = Event(syscall.NOTE_REVOKE)
+)
+
+var osestr = map[Event]string{
+ NoteDelete: "notify.NoteDelete",
+ NoteWrite: "notify.NoteWrite",
+ NoteExtend: "notify.NoteExtend",
+ NoteAttrib: "notify.NoteAttrib",
+ NoteLink: "notify.NoteLink",
+ NoteRename: "notify.NoteRename",
+ NoteRevoke: "notify.NoteRevoke",
+}
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/event_readdcw.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/event_readdcw.go
new file mode 100644
index 000000000..11ead9e29
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/event_readdcw.go
@@ -0,0 +1,108 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build windows
+
+package notify
+
+import (
+ "os"
+ "path/filepath"
+ "syscall"
+)
+
+// Platform independent event values.
+const (
+ osSpecificCreate Event = 1 << (20 + iota)
+ osSpecificRemove
+ osSpecificWrite
+ osSpecificRename
+ // recursive is used to distinguish recursive eventsets from non-recursive ones
+ recursive
+ // omit is used for dispatching internal events; only those events are sent
+ // for which both the event and the watchpoint has omit in theirs event sets.
+ omit
+ // dirmarker TODO(pknap)
+ dirmarker
+)
+
+// ReadDirectoryChangesW filters.
+const (
+ FileNotifyChangeFileName = Event(syscall.FILE_NOTIFY_CHANGE_FILE_NAME)
+ FileNotifyChangeDirName = Event(syscall.FILE_NOTIFY_CHANGE_DIR_NAME)
+ FileNotifyChangeAttributes = Event(syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES)
+ FileNotifyChangeSize = Event(syscall.FILE_NOTIFY_CHANGE_SIZE)
+ FileNotifyChangeLastWrite = Event(syscall.FILE_NOTIFY_CHANGE_LAST_WRITE)
+ FileNotifyChangeLastAccess = Event(syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS)
+ FileNotifyChangeCreation = Event(syscall.FILE_NOTIFY_CHANGE_CREATION)
+ FileNotifyChangeSecurity = Event(syscallFileNotifyChangeSecurity)
+)
+
+const (
+ fileNotifyChangeAll = 0x17f // logical sum of all FileNotifyChange* events.
+ fileNotifyChangeModified = fileNotifyChangeAll &^ (FileNotifyChangeFileName | FileNotifyChangeDirName)
+)
+
+// according to: http://msdn.microsoft.com/en-us/library/windows/desktop/aa365465(v=vs.85).aspx
+// this flag should be declared in: http://golang.org/src/pkg/syscall/ztypes_windows.go
+const syscallFileNotifyChangeSecurity = 0x00000100
+
+// ReadDirectoryChangesW actions.
+const (
+ FileActionAdded = Event(syscall.FILE_ACTION_ADDED) << 12
+ FileActionRemoved = Event(syscall.FILE_ACTION_REMOVED) << 12
+ FileActionModified = Event(syscall.FILE_ACTION_MODIFIED) << 14
+ FileActionRenamedOldName = Event(syscall.FILE_ACTION_RENAMED_OLD_NAME) << 15
+ FileActionRenamedNewName = Event(syscall.FILE_ACTION_RENAMED_NEW_NAME) << 16
+)
+
+const fileActionAll = 0x7f000 // logical sum of all FileAction* events.
+
+var osestr = map[Event]string{
+ FileNotifyChangeFileName: "notify.FileNotifyChangeFileName",
+ FileNotifyChangeDirName: "notify.FileNotifyChangeDirName",
+ FileNotifyChangeAttributes: "notify.FileNotifyChangeAttributes",
+ FileNotifyChangeSize: "notify.FileNotifyChangeSize",
+ FileNotifyChangeLastWrite: "notify.FileNotifyChangeLastWrite",
+ FileNotifyChangeLastAccess: "notify.FileNotifyChangeLastAccess",
+ FileNotifyChangeCreation: "notify.FileNotifyChangeCreation",
+ FileNotifyChangeSecurity: "notify.FileNotifyChangeSecurity",
+
+ FileActionAdded: "notify.FileActionAdded",
+ FileActionRemoved: "notify.FileActionRemoved",
+ FileActionModified: "notify.FileActionModified",
+ FileActionRenamedOldName: "notify.FileActionRenamedOldName",
+ FileActionRenamedNewName: "notify.FileActionRenamedNewName",
+}
+
+const (
+ fTypeUnknown uint8 = iota
+ fTypeFile
+ fTypeDirectory
+)
+
+// TODO(ppknap) : doc.
+type event struct {
+ pathw []uint16
+ name string
+ ftype uint8
+ action uint32
+ filter uint32
+ e Event
+}
+
+func (e *event) Event() Event { return e.e }
+func (e *event) Path() string { return filepath.Join(syscall.UTF16ToString(e.pathw), e.name) }
+func (e *event) Sys() interface{} { return e.ftype }
+
+func (e *event) isDir() (bool, error) {
+ if e.ftype != fTypeUnknown {
+ return e.ftype == fTypeDirectory, nil
+ }
+ fi, err := os.Stat(e.Path())
+ if err != nil {
+ return false, err
+ }
+ return fi.IsDir(), nil
+}
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/event_stub.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/event_stub.go
new file mode 100644
index 000000000..faac7c7cb
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/rjeczalik/notify/event_trigger.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/event_trigger.go
new file mode 100644
index 000000000..94470fd37
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/rjeczalik/notify/node.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/node.go
new file mode 100644
index 000000000..4302071bb
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/node.go
@@ -0,0 +1,271 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+package notify
+
+import (
+ "errors"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+)
+
+var errSkip = errors.New("notify: skip")
+
+type walkPathFunc func(nd node, isbase bool) error
+
+type walkFunc func(node) error
+
+func errnotexist(name string) error {
+ return &os.PathError{
+ Op: "Node",
+ Path: name,
+ Err: os.ErrNotExist,
+ }
+}
+
+type node struct {
+ Name string
+ Watch watchpoint
+ Child map[string]node
+}
+
+func newnode(name string) node {
+ return node{
+ Name: name,
+ Watch: make(watchpoint),
+ Child: make(map[string]node),
+ }
+}
+
+func (nd node) addchild(name, base string) node {
+ child, ok := nd.Child[base]
+ if !ok {
+ child = newnode(name)
+ nd.Child[base] = child
+ }
+ return child
+}
+
+func (nd node) Add(name string) node {
+ i := indexbase(nd.Name, name)
+ if i == -1 {
+ return node{}
+ }
+ for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
+ nd = nd.addchild(name[:i+j], name[i:i+j])
+ i += j + 1
+ }
+ return nd.addchild(name, name[i:])
+}
+
+func (nd node) AddDir(fn walkFunc) error {
+ stack := []node{nd}
+Traverse:
+ for n := len(stack); n != 0; n = len(stack) {
+ nd, stack = stack[n-1], stack[:n-1]
+ switch err := fn(nd); err {
+ case nil:
+ case errSkip:
+ continue Traverse
+ default:
+ return err
+ }
+ // TODO(rjeczalik): tolerate open failures - add failed names to
+ // AddDirError and notify users which names are not added to the tree.
+ fi, err := ioutil.ReadDir(nd.Name)
+ if err != nil {
+ return err
+ }
+ for _, fi := range fi {
+ if fi.Mode()&(os.ModeSymlink|os.ModeDir) == os.ModeDir {
+ name := filepath.Join(nd.Name, fi.Name())
+ stack = append(stack, nd.addchild(name, name[len(nd.Name)+1:]))
+ }
+ }
+ }
+ return nil
+}
+
+func (nd node) Get(name string) (node, error) {
+ i := indexbase(nd.Name, name)
+ if i == -1 {
+ return node{}, errnotexist(name)
+ }
+ ok := false
+ for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
+ if nd, ok = nd.Child[name[i:i+j]]; !ok {
+ return node{}, errnotexist(name)
+ }
+ i += j + 1
+ }
+ if nd, ok = nd.Child[name[i:]]; !ok {
+ return node{}, errnotexist(name)
+ }
+ return nd, nil
+}
+
+func (nd node) Del(name string) error {
+ i := indexbase(nd.Name, name)
+ if i == -1 {
+ return errnotexist(name)
+ }
+ stack := []node{nd}
+ ok := false
+ for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
+ if nd, ok = nd.Child[name[i:i+j]]; !ok {
+ return errnotexist(name[:i+j])
+ }
+ stack = append(stack, nd)
+ }
+ if nd, ok = nd.Child[name[i:]]; !ok {
+ return errnotexist(name)
+ }
+ nd.Child = nil
+ nd.Watch = nil
+ for name, i = base(nd.Name), len(stack); i != 0; name, i = base(nd.Name), i-1 {
+ nd = stack[i-1]
+ if nd := nd.Child[name]; len(nd.Watch) > 1 || len(nd.Child) != 0 {
+ break
+ } else {
+ nd.Child = nil
+ nd.Watch = nil
+ }
+ delete(nd.Child, name)
+ }
+ return nil
+}
+
+func (nd node) Walk(fn walkFunc) error {
+ stack := []node{nd}
+Traverse:
+ for n := len(stack); n != 0; n = len(stack) {
+ nd, stack = stack[n-1], stack[:n-1]
+ switch err := fn(nd); err {
+ case nil:
+ case errSkip:
+ continue Traverse
+ default:
+ return err
+ }
+ for name, nd := range nd.Child {
+ if name == "" {
+ // Node storing inactive watchpoints has empty name, skip it
+ // form traversing. Root node has also an empty name, but it
+ // never has a parent node.
+ continue
+ }
+ stack = append(stack, nd)
+ }
+ }
+ return nil
+}
+
+func (nd node) WalkPath(name string, fn walkPathFunc) error {
+ i := indexbase(nd.Name, name)
+ if i == -1 {
+ return errnotexist(name)
+ }
+ ok := false
+ for j := indexSep(name[i:]); j != -1; j = indexSep(name[i:]) {
+ switch err := fn(nd, false); err {
+ case nil:
+ case errSkip:
+ return nil
+ default:
+ return err
+ }
+ if nd, ok = nd.Child[name[i:i+j]]; !ok {
+ return errnotexist(name[:i+j])
+ }
+ i += j + 1
+ }
+ switch err := fn(nd, false); err {
+ case nil:
+ case errSkip:
+ return nil
+ default:
+ return err
+ }
+ if nd, ok = nd.Child[name[i:]]; !ok {
+ return errnotexist(name)
+ }
+ switch err := fn(nd, true); err {
+ case nil, errSkip:
+ return nil
+ default:
+ return err
+ }
+}
+
+type root struct {
+ nd node
+}
+
+func (r root) addroot(name string) node {
+ if vol := filepath.VolumeName(name); vol != "" {
+ root, ok := r.nd.Child[vol]
+ if !ok {
+ root = r.nd.addchild(vol, vol)
+ }
+ return root
+ }
+ return r.nd
+}
+
+func (r root) root(name string) (node, error) {
+ if vol := filepath.VolumeName(name); vol != "" {
+ nd, ok := r.nd.Child[vol]
+ if !ok {
+ return node{}, errnotexist(name)
+ }
+ return nd, nil
+ }
+ return r.nd, nil
+}
+
+func (r root) Add(name string) node {
+ return r.addroot(name).Add(name)
+}
+
+func (r root) AddDir(dir string, fn walkFunc) error {
+ return r.Add(dir).AddDir(fn)
+}
+
+func (r root) Del(name string) error {
+ nd, err := r.root(name)
+ if err != nil {
+ return err
+ }
+ return nd.Del(name)
+}
+
+func (r root) Get(name string) (node, error) {
+ nd, err := r.root(name)
+ if err != nil {
+ return node{}, err
+ }
+ if nd.Name != name {
+ if nd, err = nd.Get(name); err != nil {
+ return node{}, err
+ }
+ }
+ return nd, nil
+}
+
+func (r root) Walk(name string, fn walkFunc) error {
+ nd, err := r.Get(name)
+ if err != nil {
+ return err
+ }
+ return nd.Walk(fn)
+}
+
+func (r root) WalkPath(name string, fn walkPathFunc) error {
+ nd, err := r.root(name)
+ if err != nil {
+ return err
+ }
+ return nd.WalkPath(name, fn)
+}
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/notify.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/notify.go
new file mode 100644
index 000000000..dbf1e7bc2
--- /dev/null
+++ b/Godeps/_workspace/src/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 persistant paths to have guarantee they receive events
+// for the whole lifetime of their applications (to discuss see #69).
+
+// BUG(ppknap): Linux (inotify) does not support watcher behavior masks like
+// InOneshot, InOnlydir etc. Instead users are advised to perform the filtering
+// themselves (to discuss see #71).
+
+// BUG(ppknap): Notify was not tested for short path name support under Windows
+// (ReadDirectoryChangesW).
+
+// BUG(ppknap): Windows (ReadDirectoryChangesW) cannot recognize which notification
+// triggers FileActionModified event. (to discuss see #75).
+
+package notify
+
+var defaultTree = newTree()
+
+// Watch sets up a watchpoint on path listening for events given by the events
+// argument.
+//
+// File or directory given by the path must exist, otherwise Watch will fail
+// with non-nil error. Notify resolves, for its internal purpose, any symlinks
+// the provided path may contain, so it may fail if the symlinks form a cycle.
+// It does so, since not all watcher implementations treat passed paths as-is.
+// E.g. FSEvents reports a real path for every event, setting a watchpoint
+// on /tmp will report events with paths rooted at /private/tmp etc.
+//
+// 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 persistant paths like %userprofile% or watch additionally parent
+// directory of a recursive watchpoint in order to receive delete events for it.
+func Watch(path string, c chan<- EventInfo, events ...Event) error {
+ return defaultTree.Watch(path, c, events...)
+}
+
+// Stop removes all watchpoints registered for c. All underlying watches are
+// also removed, for which c was the last channel listening for events.
+//
+// Stop does not close c. When Stop returns, it is guranteed that c will
+// receive no more signals.
+func Stop(c chan<- EventInfo) {
+ defaultTree.Stop(c)
+}
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/tree.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/tree.go
new file mode 100644
index 000000000..cd6afd60d
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/rjeczalik/notify/tree_nonrecursive.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/tree_nonrecursive.go
new file mode 100644
index 000000000..dfa72d1d2
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/rjeczalik/notify/tree_recursive.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/tree_recursive.go
new file mode 100644
index 000000000..7f00dfe35
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/rjeczalik/notify/util.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/util.go
new file mode 100644
index 000000000..67e01fbbd
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher.go
new file mode 100644
index 000000000..34148eff3
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fen.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fen.go
new file mode 100644
index 000000000..60e9a36da
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fen.go
@@ -0,0 +1,170 @@
+// 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.port_alert(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.port_create()
+ 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.port_dissociate(f.p, FileObj{Name: w.p})
+}
+
+// Watch implements trigger.
+func (f *fen) Watch(fi os.FileInfo, w *watched, e int64) error {
+ return f.cf.port_associate(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.port_get(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) (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.
+ if e&Create != 0 {
+ o = (o &^ int64(Create)) | int64(FileModified)
+ }
+ if 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/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fen_cgo.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fen_cgo.go
new file mode 100644
index 000000000..58ac8e8c6
--- /dev/null
+++ b/Godeps/_workspace/src/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 <port.h>
+// #include <stdio.h>
+// #include <stdlib.h>
+// 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) port_associate(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) port_dissociate(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) port_get(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) port_create() (int, error) {
+ p, err := C.port_create()
+ return int(p), err
+}
+
+func (c *cfen) port_alert(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/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents.go
new file mode 100644
index 000000000..54334912e
--- /dev/null
+++ b/Godeps/_workspace/src/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
+ panic("unhandled error: " + Event(ev[i].Flags).String())
+ }
+ if !strings.HasPrefix(ev[i].Path, w.path) {
+ continue
+ }
+ n := len(w.path)
+ base := ""
+ if len(ev[i].Path) > n {
+ if ev[i].Path[n] != '/' {
+ continue
+ }
+ base = ev[i].Path[n+1:]
+ if !isrec && strings.IndexByte(base, '/') != -1 {
+ continue
+ }
+ }
+ // TODO(rjeczalik): get diff only from filtered events?
+ e := w.strip(string(base), ev[i].Flags) & events
+ if e == 0 {
+ continue
+ }
+ for _, e := range splitflags(e) {
+ dbgprintf("%d: single event: %v", ev[i].ID, Event(e))
+ w.c <- &event{
+ fse: ev[i],
+ event: Event(e),
+ }
+ }
+ }
+}
+
+// Stop closes underlying FSEvents stream and stops dispatching events.
+func (w *watch) Stop() {
+ w.stream.Stop()
+ // TODO(rjeczalik): make (*stream).Stop flush synchronously undelivered events,
+ // so the following hack can be removed. It should flush all the streams
+ // concurrently as we care not to block too much here.
+ atomic.StoreUint32(&w.events, 0)
+ atomic.StoreInt32(&w.isrec, 0)
+}
+
+// fsevents implements Watcher and RecursiveWatcher interfaces backed by FSEvents
+// framework.
+type fsevents struct {
+ watches map[string]*watch
+ c chan<- EventInfo
+}
+
+func newWatcher(c chan<- EventInfo) watcher {
+ return &fsevents{
+ watches: make(map[string]*watch),
+ c: c,
+ }
+}
+
+func (fse *fsevents) watch(path string, event Event, isrec int32) (err error) {
+ if path, err = canonical(path); err != nil {
+ return err
+ }
+ if _, ok := fse.watches[path]; ok {
+ return errAlreadyWatched
+ }
+ w := &watch{
+ prev: make(map[string]uint32),
+ c: fse.c,
+ path: path,
+ events: uint32(event),
+ isrec: isrec,
+ }
+ w.stream = newStream(path, w.Dispatch)
+ if err = w.stream.Start(); err != nil {
+ return err
+ }
+ fse.watches[path] = w
+ return nil
+}
+
+func (fse *fsevents) unwatch(path string) (err error) {
+ if path, err = canonical(path); err != nil {
+ return
+ }
+ w, ok := fse.watches[path]
+ if !ok {
+ return errNotWatched
+ }
+ w.stream.Stop()
+ delete(fse.watches, path)
+ return nil
+}
+
+// Watch implements Watcher interface. It fails with non-nil error when setting
+// the watch-point by FSEvents fails or with errAlreadyWatched error when
+// the given path is already watched.
+func (fse *fsevents) Watch(path string, event Event) error {
+ return fse.watch(path, event, 0)
+}
+
+// Unwatch implements Watcher interface. It fails with errNotWatched when
+// the given path is not being watched.
+func (fse *fsevents) Unwatch(path string) error {
+ return fse.unwatch(path)
+}
+
+// Rewatch implements Watcher interface. It fails with errNotWatched when
+// the given path is not being watched or with errInvalidEventSet when oldevent
+// does not match event set the watch-point currently holds.
+func (fse *fsevents) Rewatch(path string, oldevent, newevent Event) error {
+ w, ok := fse.watches[path]
+ if !ok {
+ return errNotWatched
+ }
+ if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
+ return errInvalidEventSet
+ }
+ atomic.StoreInt32(&w.isrec, 0)
+ return nil
+}
+
+// RecursiveWatch implements RecursiveWatcher interface. It fails with non-nil
+// error when setting the watch-point by FSEvents fails or with errAlreadyWatched
+// error when the given path is already watched.
+func (fse *fsevents) RecursiveWatch(path string, event Event) error {
+ return fse.watch(path, event, 1)
+}
+
+// RecursiveUnwatch implements RecursiveWatcher interface. It fails with
+// errNotWatched when the given path is not being watched.
+//
+// TODO(rjeczalik): fail if w.isrec == 0?
+func (fse *fsevents) RecursiveUnwatch(path string) error {
+ return fse.unwatch(path)
+}
+
+// RecrusiveRewatch implements RecursiveWatcher interface. It fails:
+//
+// * with errNotWatched when the given path is not being watched
+// * with errInvalidEventSet when oldevent does not match the current event set
+// * with errAlreadyWatched when watch-point given by the oldpath was meant to
+// be relocated to newpath, but the newpath is already watched
+// * a non-nil error when setting the watch-point with FSEvents fails
+//
+// TODO(rjeczalik): Improve handling of watch-point relocation? See two TODOs
+// that follows.
+func (fse *fsevents) RecursiveRewatch(oldpath, newpath string, oldevent, newevent Event) error {
+ switch [2]bool{oldpath == newpath, oldevent == newevent} {
+ case [2]bool{true, true}:
+ w, ok := fse.watches[oldpath]
+ if !ok {
+ return errNotWatched
+ }
+ atomic.StoreInt32(&w.isrec, 1)
+ return nil
+ case [2]bool{true, false}:
+ w, ok := fse.watches[oldpath]
+ if !ok {
+ return errNotWatched
+ }
+ if !atomic.CompareAndSwapUint32(&w.events, uint32(oldevent), uint32(newevent)) {
+ return errors.New("invalid event state diff")
+ }
+ atomic.StoreInt32(&w.isrec, 1)
+ return nil
+ default:
+ // TODO(rjeczalik): rewatch newpath only if exists?
+ // TODO(rjeczalik): migrate w.prev to new watch?
+ if _, ok := fse.watches[newpath]; ok {
+ return errAlreadyWatched
+ }
+ if err := fse.Unwatch(oldpath); err != nil {
+ return err
+ }
+ // TODO(rjeczalik): revert unwatch if watch fails?
+ return fse.watch(newpath, newevent, 1)
+ }
+}
+
+// Close unwatches all watch-points.
+func (fse *fsevents) Close() error {
+ for _, w := range fse.watches {
+ w.Stop()
+ }
+ fse.watches = nil
+ return nil
+}
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents_cgo.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_fsevents_cgo.go
new file mode 100644
index 000000000..ee9631a61
--- /dev/null
+++ b/Godeps/_workspace/src/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 <CoreServices/CoreServices.h>
+
+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 receving 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/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_inotify.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_inotify.go
new file mode 100644
index 000000000..3ceaa8f3a
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_inotify.go
@@ -0,0 +1,396 @@
+// Copyright (c) 2014-2015 The Notify Authors. All rights reserved.
+// Use of this source code is governed by the MIT license that can be
+// found in the LICENSE file.
+
+// +build linux
+
+package notify
+
+import (
+ "bytes"
+ "errors"
+ "path/filepath"
+ "runtime"
+ "sync"
+ "sync/atomic"
+ "syscall"
+ "unsafe"
+)
+
+// eventBufferSize defines the size of the buffer given to read(2) function. One
+// should not depend on this value, since it was arbitrary chosen and may be
+// changed in the future.
+const eventBufferSize = 64 * (syscall.SizeofInotifyEvent + syscall.PathMax + 1)
+
+// consumersCount defines the number of consumers in producer-consumer based
+// implementation. Each consumer is run in a separate goroutine and has read
+// access to watched files map.
+const consumersCount = 2
+
+const invalidDescriptor = -1
+
+// watched is a pair of file path and inotify mask used as a value in
+// watched files map.
+type watched struct {
+ path string
+ mask uint32
+}
+
+// inotify implements Watcher interface.
+type inotify struct {
+ sync.RWMutex // protects inotify.m map
+ m map[int32]*watched // watch descriptor to watched object
+ fd int32 // inotify file descriptor
+ pipefd []int // pipe's read and write descriptors
+ epfd int // epoll descriptor
+ epes []syscall.EpollEvent // epoll events
+ buffer [eventBufferSize]byte // inotify event buffer
+ wg sync.WaitGroup // wait group used to close main loop
+ c chan<- EventInfo // event dispatcher channel
+}
+
+// NewWatcher creates new non-recursive inotify backed by inotify.
+func newWatcher(c chan<- EventInfo) watcher {
+ i := &inotify{
+ m: make(map[int32]*watched),
+ fd: invalidDescriptor,
+ pipefd: []int{invalidDescriptor, invalidDescriptor},
+ epfd: invalidDescriptor,
+ epes: make([]syscall.EpollEvent, 0),
+ c: c,
+ }
+ runtime.SetFinalizer(i, func(i *inotify) {
+ i.epollclose()
+ if i.fd != invalidDescriptor {
+ syscall.Close(int(i.fd))
+ }
+ })
+ return i
+}
+
+// Watch implements notify.watcher interface.
+func (i *inotify) Watch(path string, e Event) error {
+ return i.watch(path, e)
+}
+
+// Rewatch implements notify.watcher interface.
+func (i *inotify) Rewatch(path string, _, newevent Event) error {
+ return i.watch(path, newevent)
+}
+
+// watch adds a new watcher to the set of watched objects or modifies the existing
+// one. If called for the first time, this function initializes inotify filesystem
+// monitor and starts producer-consumers goroutines.
+func (i *inotify) watch(path string, e Event) (err error) {
+ if e&^(All|Event(syscall.IN_ALL_EVENTS)) != 0 {
+ return errors.New("notify: unknown event")
+ }
+ if err = i.lazyinit(); err != nil {
+ return
+ }
+ iwd, err := syscall.InotifyAddWatch(int(i.fd), path, encode(e))
+ if err != nil {
+ return
+ }
+ i.RLock()
+ wd := i.m[int32(iwd)]
+ i.RUnlock()
+ if wd == nil {
+ i.Lock()
+ if i.m[int32(iwd)] == nil {
+ i.m[int32(iwd)] = &watched{path: path, mask: uint32(e)}
+ }
+ i.Unlock()
+ } else {
+ i.Lock()
+ wd.mask = uint32(e)
+ i.Unlock()
+ }
+ return nil
+}
+
+// lazyinit sets up all required file descriptors and starts 1+consumersCount
+// goroutines. The producer goroutine blocks until file-system notifications
+// occur. Then, all events are read from system buffer and sent to consumer
+// goroutines which construct valid notify events. This method uses
+// Double-Checked Locking optimization.
+func (i *inotify) lazyinit() error {
+ if atomic.LoadInt32(&i.fd) == invalidDescriptor {
+ i.Lock()
+ defer i.Unlock()
+ if atomic.LoadInt32(&i.fd) == invalidDescriptor {
+ fd, err := syscall.InotifyInit()
+ if err != nil {
+ return err
+ }
+ i.fd = int32(fd)
+ if err = i.epollinit(); err != nil {
+ _, _ = i.epollclose(), syscall.Close(int(fd)) // Ignore errors.
+ i.fd = invalidDescriptor
+ return err
+ }
+ esch := make(chan []*event)
+ go i.loop(esch)
+ i.wg.Add(consumersCount)
+ for n := 0; n < consumersCount; n++ {
+ go i.send(esch)
+ }
+ }
+ }
+ return nil
+}
+
+// epollinit opens an epoll file descriptor and creates a pipe which will be
+// used to wake up the epoll_wait(2) function. Then, file descriptor associated
+// with inotify event queue and the read end of the pipe are added to epoll set.
+// Note that `fd` member must be set before this function is called.
+func (i *inotify) epollinit() (err error) {
+ if i.epfd, err = syscall.EpollCreate1(0); err != nil {
+ return
+ }
+ if err = syscall.Pipe(i.pipefd); err != nil {
+ return
+ }
+ i.epes = []syscall.EpollEvent{
+ {Events: syscall.EPOLLIN, Fd: i.fd},
+ {Events: syscall.EPOLLIN, Fd: int32(i.pipefd[0])},
+ }
+ if err = syscall.EpollCtl(i.epfd, syscall.EPOLL_CTL_ADD, int(i.fd), &i.epes[0]); err != nil {
+ return
+ }
+ return syscall.EpollCtl(i.epfd, syscall.EPOLL_CTL_ADD, i.pipefd[0], &i.epes[1])
+}
+
+// epollclose closes the file descriptor created by the call to epoll_create(2)
+// and two file descriptors opened by pipe(2) function.
+func (i *inotify) epollclose() (err error) {
+ if i.epfd != invalidDescriptor {
+ if err = syscall.Close(i.epfd); err == nil {
+ i.epfd = invalidDescriptor
+ }
+ }
+ for n, fd := range i.pipefd {
+ if fd != invalidDescriptor {
+ switch e := syscall.Close(fd); {
+ case e != nil && err == nil:
+ err = e
+ case e == nil:
+ i.pipefd[n] = invalidDescriptor
+ }
+ }
+ }
+ return
+}
+
+// loop blocks until either inotify or pipe file descriptor is ready for I/O.
+// All read operations triggered by filesystem notifications are forwarded to
+// one of the event's consumers. If pipe fd became ready, loop function closes
+// all file descriptors opened by lazyinit method and returns afterwards.
+func (i *inotify) loop(esch chan<- []*event) {
+ epes := make([]syscall.EpollEvent, 1)
+ fd := atomic.LoadInt32(&i.fd)
+ for {
+ switch _, err := syscall.EpollWait(i.epfd, epes, -1); err {
+ case nil:
+ switch epes[0].Fd {
+ case fd:
+ esch <- i.read()
+ epes[0].Fd = 0
+ case int32(i.pipefd[0]):
+ i.Lock()
+ defer i.Unlock()
+ if err = syscall.Close(int(fd)); err != nil && err != syscall.EINTR {
+ panic("notify: close(2) error " + err.Error())
+ }
+ atomic.StoreInt32(&i.fd, invalidDescriptor)
+ if err = i.epollclose(); err != nil && err != syscall.EINTR {
+ panic("notify: epollclose error " + err.Error())
+ }
+ close(esch)
+ return
+ }
+ case syscall.EINTR:
+ continue
+ default: // We should never reach this line.
+ panic("notify: epoll_wait(2) error " + err.Error())
+ }
+ }
+}
+
+// read reads events from an inotify file descriptor. It does not handle errors
+// returned from read(2) function since they are not critical to watcher logic.
+func (i *inotify) read() (es []*event) {
+ n, err := syscall.Read(int(i.fd), i.buffer[:])
+ if err != nil || n < syscall.SizeofInotifyEvent {
+ return
+ }
+ var sys *syscall.InotifyEvent
+ nmin := n - syscall.SizeofInotifyEvent
+ for pos, path := 0, ""; pos <= nmin; {
+ sys = (*syscall.InotifyEvent)(unsafe.Pointer(&i.buffer[pos]))
+ pos += syscall.SizeofInotifyEvent
+ if path = ""; sys.Len > 0 {
+ endpos := pos + int(sys.Len)
+ path = string(bytes.TrimRight(i.buffer[pos:endpos], "\x00"))
+ pos = endpos
+ }
+ es = append(es, &event{
+ sys: syscall.InotifyEvent{
+ Wd: sys.Wd,
+ Mask: sys.Mask,
+ Cookie: sys.Cookie,
+ },
+ path: path,
+ })
+ }
+ return
+}
+
+// send is a consumer function which sends events to event dispatcher channel.
+// It is run in a separate goroutine in order to not block loop method when
+// possibly expensive write operations are performed on inotify map.
+func (i *inotify) send(esch <-chan []*event) {
+ for es := range esch {
+ for _, e := range i.transform(es) {
+ if e != nil {
+ i.c <- e
+ }
+ }
+ }
+ i.wg.Done()
+}
+
+// transform prepares events read from inotify file descriptor for sending to
+// user. It removes invalid events and these which are no longer present in
+// inotify map. This method may also split one raw event into two different ones
+// when system-dependent result is required.
+func (i *inotify) transform(es []*event) []*event {
+ var multi []*event
+ i.RLock()
+ for idx, e := range es {
+ if e.sys.Mask&(syscall.IN_IGNORED|syscall.IN_Q_OVERFLOW) != 0 {
+ es[idx] = nil
+ continue
+ }
+ wd, ok := i.m[e.sys.Wd]
+ if !ok || e.sys.Mask&encode(Event(wd.mask)) == 0 {
+ es[idx] = nil
+ continue
+ }
+ if e.path == "" {
+ e.path = wd.path
+ } else {
+ e.path = filepath.Join(wd.path, e.path)
+ }
+ multi = append(multi, decode(Event(wd.mask), e))
+ if e.event == 0 {
+ es[idx] = nil
+ }
+ }
+ i.RUnlock()
+ es = append(es, multi...)
+ return es
+}
+
+// encode converts notify system-independent events to valid inotify mask
+// which can be passed to inotify_add_watch(2) function.
+func encode(e Event) uint32 {
+ if e&Create != 0 {
+ e = (e ^ Create) | InCreate | InMovedTo
+ }
+ if e&Remove != 0 {
+ e = (e ^ Remove) | InDelete | InDeleteSelf
+ }
+ if e&Write != 0 {
+ e = (e ^ Write) | InModify
+ }
+ if e&Rename != 0 {
+ e = (e ^ Rename) | InMovedFrom | InMoveSelf
+ }
+ return uint32(e)
+}
+
+// decode uses internally stored mask to distinguish whether system-independent
+// or system-dependent event is requested. The first one is created by modifying
+// `e` argument. decode method sets e.event value to 0 when an event should be
+// skipped. System-dependent event is set as the function's return value which
+// can be nil when the event should not be passed on.
+func decode(mask Event, e *event) (syse *event) {
+ if sysmask := uint32(mask) & e.sys.Mask; sysmask != 0 {
+ syse = &event{sys: syscall.InotifyEvent{
+ Wd: e.sys.Wd,
+ Mask: e.sys.Mask,
+ Cookie: e.sys.Cookie,
+ }, event: Event(sysmask), path: e.path}
+ }
+ imask := encode(mask)
+ switch {
+ case mask&Create != 0 && imask&uint32(InCreate|InMovedTo)&e.sys.Mask != 0:
+ e.event = Create
+ case mask&Remove != 0 && imask&uint32(InDelete|InDeleteSelf)&e.sys.Mask != 0:
+ e.event = Remove
+ case mask&Write != 0 && imask&uint32(InModify)&e.sys.Mask != 0:
+ e.event = Write
+ case mask&Rename != 0 && imask&uint32(InMovedFrom|InMoveSelf)&e.sys.Mask != 0:
+ e.event = Rename
+ default:
+ e.event = 0
+ }
+ return
+}
+
+// Unwatch implements notify.watcher interface. It looks for watch descriptor
+// related to registered path and if found, calls inotify_rm_watch(2) function.
+// This method is allowed to return EINVAL error when concurrently requested to
+// delete identical path.
+func (i *inotify) Unwatch(path string) (err error) {
+ iwd := int32(invalidDescriptor)
+ i.RLock()
+ for iwdkey, wd := range i.m {
+ if wd.path == path {
+ iwd = iwdkey
+ break
+ }
+ }
+ i.RUnlock()
+ if iwd == invalidDescriptor {
+ return errors.New("notify: path " + path + " is already watched")
+ }
+ fd := atomic.LoadInt32(&i.fd)
+ if _, err = syscall.InotifyRmWatch(int(fd), uint32(iwd)); err != nil {
+ return
+ }
+ i.Lock()
+ delete(i.m, iwd)
+ i.Unlock()
+ return nil
+}
+
+// Close implements notify.watcher interface. It removes all existing watch
+// descriptors and wakes up producer goroutine by sending data to the write end
+// of the pipe. The function waits for a signal from producer which means that
+// all operations on current monitoring instance are done.
+func (i *inotify) Close() (err error) {
+ i.Lock()
+ if fd := atomic.LoadInt32(&i.fd); fd == invalidDescriptor {
+ i.Unlock()
+ return nil
+ }
+ for iwd := range i.m {
+ if _, e := syscall.InotifyRmWatch(int(i.fd), uint32(iwd)); e != nil && err == nil {
+ err = e
+ }
+ delete(i.m, iwd)
+ }
+ switch _, errwrite := syscall.Write(i.pipefd[1], []byte{0x00}); {
+ case errwrite != nil && err == nil:
+ err = errwrite
+ fallthrough
+ case errwrite != nil:
+ i.Unlock()
+ default:
+ i.Unlock()
+ i.wg.Wait()
+ }
+ return
+}
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_kqueue.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_kqueue.go
new file mode 100644
index 000000000..d5f7788c4
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_kqueue.go
@@ -0,0 +1,192 @@
+// 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) (o int64) {
+ // Create event is not supported by kqueue. Instead NoteWrite 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 NoteRename event is reported, it will be ignored.
+ o = int64(e &^ Create)
+ if 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/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_readdcw.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_readdcw.go
new file mode 100644
index 000000000..e8359bba4
--- /dev/null
+++ b/Godeps/_workspace/src/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_PATH]uint16)(unsafe.Pointer(&raw.FileName))[:raw.FileNameLength>>1])
+ events = append(events, &event{
+ pathw: overEx.parent.pathw,
+ filter: overEx.parent.filter,
+ action: raw.Action,
+ name: name,
+ })
+ if raw.NextEntryOffset == 0 {
+ break
+ }
+ if currOffset += raw.NextEntryOffset; currOffset >= n {
+ break
+ }
+ }
+ r.send(events)
+}
+
+// TODO(pknap) : doc
+func (r *readdcw) send(es []*event) {
+ for _, e := range es {
+ var syse Event
+ if e.e, syse = decode(e.filter, e.action); e.e == 0 && syse == 0 {
+ continue
+ }
+ switch {
+ case e.action == syscall.FILE_ACTION_MODIFIED:
+ e.ftype = fTypeUnknown
+ case e.filter&uint32(dirmarker) != 0:
+ e.ftype = fTypeDirectory
+ default:
+ e.ftype = fTypeFile
+ }
+ switch {
+ case e.e == 0:
+ e.e = syse
+ case syse != 0:
+ r.c <- &event{
+ pathw: e.pathw,
+ name: e.name,
+ ftype: e.ftype,
+ action: e.action,
+ filter: e.filter,
+ e: syse,
+ }
+ }
+ r.c <- e
+ }
+}
+
+// Rewatch implements notify.Rewatcher interface.
+func (r *readdcw) Rewatch(path string, oldevent, newevent Event) error {
+ return r.rewatch(path, uint32(oldevent), uint32(newevent), false)
+}
+
+// RecursiveRewatch implements notify.RecursiveRewatcher interface.
+func (r *readdcw) RecursiveRewatch(oldpath, newpath string, oldevent,
+ newevent Event) error {
+ if oldpath != newpath {
+ if err := r.unwatch(oldpath); err != nil {
+ return err
+ }
+ return r.watch(newpath, newevent, true)
+ }
+ return r.rewatch(newpath, uint32(oldevent), uint32(newevent), true)
+}
+
+// TODO : (pknap) doc.
+func (r *readdcw) rewatch(path string, oldevent, newevent uint32, recursive bool) (err error) {
+ if Event(newevent)&^(All|fileNotifyChangeAll) != 0 {
+ return errors.New("notify: unknown event")
+ }
+ var wd *watched
+ r.Lock()
+ if wd, err = r.nonStateWatched(path); err != nil {
+ r.Unlock()
+ return
+ }
+ if wd.filter&(onlyNotifyChanges|onlyNGlobalEvents) != oldevent {
+ panic(`notify: windows re-watcher logic error`)
+ }
+ wd.filter = stateRewatch | newevent
+ wd.recursive, recursive = recursive, wd.recursive
+ if err = wd.closeHandle(); err != nil {
+ wd.filter = oldevent
+ wd.recursive = recursive
+ r.Unlock()
+ return
+ }
+ r.Unlock()
+ return
+}
+
+// TODO : pknap
+func (r *readdcw) nonStateWatched(path string) (wd *watched, err error) {
+ wd, ok := r.m[path]
+ if !ok || wd == nil {
+ err = errors.New(`notify: ` + path + ` path is unwatched`)
+ return
+ }
+ if filter := atomic.LoadUint32(&wd.filter); filter&onlyMachineStates != 0 {
+ err = errors.New(`notify: another re/unwatching operation in progress`)
+ return
+ }
+ return
+}
+
+// Unwatch implements notify.Watcher interface.
+func (r *readdcw) Unwatch(path string) error {
+ return r.unwatch(path)
+}
+
+// RecursiveUnwatch implements notify.RecursiveWatcher interface.
+func (r *readdcw) RecursiveUnwatch(path string) error {
+ return r.unwatch(path)
+}
+
+// TODO : pknap
+func (r *readdcw) unwatch(path string) (err error) {
+ var wd *watched
+ r.Lock()
+ if wd, err = r.nonStateWatched(path); err != nil {
+ r.Unlock()
+ return
+ }
+ wd.filter |= stateUnwatch
+ if err = wd.closeHandle(); err != nil {
+ wd.filter &^= stateUnwatch
+ r.Unlock()
+ return
+ }
+ r.Unlock()
+ return
+}
+
+// Close resets the whole watcher object, closes all existing file descriptors,
+// and sends stateCPClose state as completion key to the main watcher's loop.
+func (r *readdcw) Close() (err error) {
+ r.Lock()
+ if !r.start {
+ r.Unlock()
+ return nil
+ }
+ for _, wd := range r.m {
+ wd.filter &^= onlyMachineStates
+ wd.filter |= stateCPClose
+ if e := wd.closeHandle(); e != nil && err == nil {
+ err = e
+ }
+ }
+ r.start = false
+ r.Unlock()
+ r.wg.Add(1)
+ if e := syscall.PostQueuedCompletionStatus(r.cph, 0, stateCPClose, nil); e != nil && err == nil {
+ return e
+ }
+ r.wg.Wait()
+ return
+}
+
+// decode creates a notify event from both non-raw filter and action which was
+// returned from completion routine. Function may return Event(0) in case when
+// filter was replaced by a new value which does not contain fields that are
+// valid with passed action.
+func decode(filter, action uint32) (Event, Event) {
+ switch action {
+ case syscall.FILE_ACTION_ADDED:
+ return gensys(filter, Create, FileActionAdded)
+ case syscall.FILE_ACTION_REMOVED:
+ return gensys(filter, Remove, FileActionRemoved)
+ case syscall.FILE_ACTION_MODIFIED:
+ return gensys(filter, Write, FileActionModified)
+ case syscall.FILE_ACTION_RENAMED_OLD_NAME:
+ return gensys(filter, Rename, FileActionRenamedOldName)
+ case syscall.FILE_ACTION_RENAMED_NEW_NAME:
+ return gensys(filter, Rename, FileActionRenamedNewName)
+ }
+ panic(`notify: cannot decode internal mask`)
+}
+
+// gensys decides whether the Windows action, system-independent event or both
+// of them should be returned. Since the grip's filter may be atomically changed
+// during watcher lifetime, it is possible that neither Windows nor notify masks
+// are watched by the user when this function is called.
+func gensys(filter uint32, ge, se Event) (gene, syse Event) {
+ isdir := filter&uint32(dirmarker) != 0
+ if isdir && filter&uint32(FileNotifyChangeDirName) != 0 ||
+ !isdir && filter&uint32(FileNotifyChangeFileName) != 0 ||
+ filter&uint32(fileNotifyChangeModified) != 0 {
+ syse = se
+ }
+ if filter&uint32(ge) != 0 {
+ gene = ge
+ }
+ return
+}
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_stub.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_stub.go
new file mode 100644
index 000000000..68b9c135b
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_trigger.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_trigger.go
new file mode 100644
index 000000000..f25a6fcbf
--- /dev/null
+++ b/Godeps/_workspace/src/github.com/rjeczalik/notify/watcher_trigger.go
@@ -0,0 +1,438 @@
+// 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) 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
+ }
+ var ee int64
+ // Native Write event is added to wait for Create events (Write event on
+ // directory triggers it's rescan).
+ if e&Create != 0 && fi.IsDir() {
+ ee = int64(not2nat[Write])
+ }
+ if err = t.t.Watch(fi, w, encode(w.eDir|w.eNonDir)|ee); 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 dirLiczba dostępnych dni urlopowych: 0ectory.
+ 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&not2nat[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 port_get 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))); err != nil {
+ dbgprintf("trg: %q is no longer watched: %q", w.p, err)
+ t.t.Del(w)
+ }
+ }
+ }
+ if e == Event(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/Godeps/_workspace/src/github.com/rjeczalik/notify/watchpoint.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watchpoint.go
new file mode 100644
index 000000000..5afc914f4
--- /dev/null
+++ b/Godeps/_workspace/src/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/Godeps/_workspace/src/github.com/rjeczalik/notify/watchpoint_other.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watchpoint_other.go
new file mode 100644
index 000000000..881631c99
--- /dev/null
+++ b/Godeps/_workspace/src/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 additionaly both event and set have omit bit set
+//
+// Internal events must not be sent to user channels and vice versa.
+func matches(set, event Event) bool {
+ return (set&omit)^(event&omit) == 0 && set&event == event
+}
diff --git a/Godeps/_workspace/src/github.com/rjeczalik/notify/watchpoint_readdcw.go b/Godeps/_workspace/src/github.com/rjeczalik/notify/watchpoint_readdcw.go
new file mode 100644
index 000000000..9fd1e1df3
--- /dev/null
+++ b/Godeps/_workspace/src/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)
+}