You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
355 lines
8.7 KiB
355 lines
8.7 KiB
7 years ago
|
// 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
|
||
|
}
|