syncthing/lib/fs/basicfs_watch.go
Jakob Borg bea3c01772 vendor: github.com/Zillode/notify is now github.com/syncthing/notify (#4813)
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.
2018-03-14 14:48:22 +01:00

117 lines
3.3 KiB
Go

// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
// +build !solaris,!darwin solaris,cgo darwin,cgo
package fs
import (
"context"
"errors"
"path/filepath"
"github.com/syncthing/notify"
)
// Notify does not block on sending to channel, so the channel must be buffered.
// The actual number is magic.
// Not meant to be changed, but must be changeable for tests
var backendBuffer = 500
func (f *BasicFilesystem) Watch(name string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, error) {
absName, err := f.rooted(name)
if err != nil {
return nil, err
}
absShouldIgnore := func(absPath string) bool {
return ignore.ShouldIgnore(f.unrootedChecked(absPath))
}
outChan := make(chan Event)
backendChan := make(chan notify.EventInfo, backendBuffer)
eventMask := subEventMask
if !ignorePerms {
eventMask |= permEventMask
}
if err := notify.WatchWithFilter(filepath.Join(absName, "..."), backendChan, absShouldIgnore, eventMask); err != nil {
notify.Stop(backendChan)
if reachedMaxUserWatches(err) {
err = errors.New("failed to install inotify handler. Please increase inotify limits, see https://github.com/syncthing/syncthing-inotify#troubleshooting-for-folders-with-many-files-on-linux for more information")
}
return nil, err
}
go f.watchLoop(name, absName, backendChan, outChan, ignore, ctx)
return outChan, nil
}
func (f *BasicFilesystem) watchLoop(name string, absName string, backendChan chan notify.EventInfo, outChan chan<- Event, ignore Matcher, ctx context.Context) {
for {
// Detect channel overflow
if len(backendChan) == backendBuffer {
outer:
for {
select {
case <-backendChan:
default:
break outer
}
}
// When next scheduling a scan, do it on the entire folder as events have been lost.
outChan <- Event{Name: name, Type: NonRemove}
l.Debugln(f.Type(), f.URI(), "Watch: Event overflow, send \".\"")
}
select {
case ev := <-backendChan:
relPath := f.unrootedChecked(ev.Path())
if ignore.ShouldIgnore(relPath) {
l.Debugln(f.Type(), f.URI(), "Watch: Ignoring", relPath)
continue
}
evType := f.eventType(ev.Event())
select {
case outChan <- Event{Name: relPath, Type: evType}:
l.Debugln(f.Type(), f.URI(), "Watch: Sending", relPath, evType)
case <-ctx.Done():
notify.Stop(backendChan)
l.Debugln(f.Type(), f.URI(), "Watch: Stopped")
return
}
case <-ctx.Done():
notify.Stop(backendChan)
l.Debugln(f.Type(), f.URI(), "Watch: Stopped")
return
}
}
}
func (f *BasicFilesystem) eventType(notifyType notify.Event) EventType {
if notifyType&rmEventMask != 0 {
return Remove
}
return NonRemove
}
// unrootedChecked returns the path relative to the folder root (same as
// unrooted). It panics if the given path is not a subpath and handles the
// special case when the given path is the folder root without a trailing
// pathseparator.
func (f *BasicFilesystem) unrootedChecked(absPath string) string {
if absPath+string(PathSeparator) == f.root {
return "."
}
relPath := f.unrooted(absPath)
if relPath == absPath {
panic("bug: Notify backend is processing a change outside of the watched path: " + absPath)
}
return relPath
}