2017-10-20 14:52:55 +00:00
// 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"
2018-03-14 13:48:22 +00:00
"github.com/syncthing/notify"
2017-10-20 14:52:55 +00:00
)
// 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
}