aboutsummaryrefslogblamecommitdiffstats
path: root/Godeps/_workspace/src/github.com/rjeczalik/notify/tree_recursive.go
blob: 7f00dfe35a2ce6d1e5bfb3b45ce697c68afaa9e9 (plain) (tree)

































































































































































































































































































































































                                                                                                      
// 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
}