all: Add filesystem notification support
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3986
This commit is contained in:
committed by
Audrius Butkevicius
parent
c704ba9ef9
commit
f98c21b68e
116
lib/fs/basicfs_watch.go
Normal file
116
lib/fs/basicfs_watch.go
Normal file
@@ -0,0 +1,116 @@
|
||||
// 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/zillode/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
|
||||
}
|
||||
Reference in New Issue
Block a user