Given that we've taken on the resposibility of maintaining this forked package I've added it to the Syncthing organization. We still vendor it like an external package, because it's convenient to keep it as a fork of upstream to easier merge and file pull requests towards them.
356 lines
8.8 KiB
Go
356 lines
8.8 KiB
Go
// 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,
|
|
doNotWatch func(string) bool, 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
|
|
}
|