mirror of
https://github.com/octoleo/syncthing.git
synced 2025-01-31 10:58:27 +00:00
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/4455 LGTM: AudriusButkevicius, calmh
This commit is contained in:
parent
a9c221189b
commit
dc42db444b
@ -7,6 +7,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
@ -14,6 +15,11 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
)
|
||||
|
||||
var (
|
||||
errPathMissing = errors.New("folder path missing")
|
||||
errMarkerMissing = errors.New("folder marker missing")
|
||||
)
|
||||
|
||||
type FolderConfiguration struct {
|
||||
ID string `xml:"id,attr" json:"id"`
|
||||
Label string `xml:"label,attr" json:"label"`
|
||||
@ -82,32 +88,44 @@ func (f FolderConfiguration) Filesystem() fs.Filesystem {
|
||||
}
|
||||
|
||||
func (f *FolderConfiguration) CreateMarker() error {
|
||||
if !f.HasMarker() {
|
||||
permBits := fs.FileMode(0777)
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows has no umask so we must chose a safer set of bits to
|
||||
// begin with.
|
||||
permBits = 0700
|
||||
}
|
||||
fs := f.Filesystem()
|
||||
err := fs.Mkdir(".stfolder", permBits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dir, err := fs.Open("."); err != nil {
|
||||
l.Debugln("folder marker: open . failed:", err)
|
||||
} else if err := dir.Sync(); err != nil {
|
||||
l.Debugln("folder marker: fsync . failed:", err)
|
||||
}
|
||||
fs.Hide(".stfolder")
|
||||
if err := f.CheckPath(); err != errMarkerMissing {
|
||||
return err
|
||||
}
|
||||
|
||||
permBits := fs.FileMode(0777)
|
||||
if runtime.GOOS == "windows" {
|
||||
// Windows has no umask so we must chose a safer set of bits to
|
||||
// begin with.
|
||||
permBits = 0700
|
||||
}
|
||||
fs := f.Filesystem()
|
||||
err := fs.Mkdir(".stfolder", permBits)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dir, err := fs.Open("."); err != nil {
|
||||
l.Debugln("folder marker: open . failed:", err)
|
||||
} else if err := dir.Sync(); err != nil {
|
||||
l.Debugln("folder marker: fsync . failed:", err)
|
||||
}
|
||||
fs.Hide(".stfolder")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FolderConfiguration) HasMarker() bool {
|
||||
_, err := f.Filesystem().Stat(".stfolder")
|
||||
return err == nil
|
||||
// CheckPath returns nil if the folder root exists and contains the marker file
|
||||
func (f *FolderConfiguration) CheckPath() error {
|
||||
fi, err := f.Filesystem().Stat(".")
|
||||
if err != nil || !fi.IsDir() {
|
||||
return errPathMissing
|
||||
}
|
||||
|
||||
_, err = f.Filesystem().Stat(".stfolder")
|
||||
if err != nil {
|
||||
return errMarkerMissing
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FolderConfiguration) CreateRoot() (err error) {
|
||||
@ -184,3 +202,7 @@ func (l FolderDeviceConfigurationList) Swap(a, b int) {
|
||||
func (l FolderDeviceConfigurationList) Len() int {
|
||||
return len(l)
|
||||
}
|
||||
|
||||
func (f *FolderConfiguration) CheckFreeSpace() (err error) {
|
||||
return checkFreeSpace(f.MinDiskFree, f.Filesystem())
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
)
|
||||
|
||||
type Size struct {
|
||||
@ -73,3 +75,24 @@ func (s Size) String() string {
|
||||
func (Size) ParseDefault(s string) (interface{}, error) {
|
||||
return ParseSize(s)
|
||||
}
|
||||
|
||||
func checkFreeSpace(req Size, fs fs.Filesystem) error {
|
||||
val := req.BaseValue()
|
||||
if val <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
usage, err := fs.Usage(".")
|
||||
if req.Percentage() {
|
||||
freePct := (float64(usage.Free) / float64(usage.Total)) * 100
|
||||
if err == nil && freePct < val {
|
||||
return fmt.Errorf("insufficient space in %v %v: %f %% < %v", fs.Type(), fs.URI(), freePct, req)
|
||||
}
|
||||
} else {
|
||||
if err == nil && float64(usage.Free) < val {
|
||||
return fmt.Errorf("insufficient space in %v %v: %v < %v", fs.Type(), fs.URI(), usage.Free, req)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -8,9 +8,11 @@ package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
@ -468,3 +470,9 @@ func (w *Wrapper) MyName() string {
|
||||
cfg, _ := w.Device(myID)
|
||||
return cfg.Name
|
||||
}
|
||||
|
||||
// CheckHomeFreeSpace returns nil if the home disk has the required amount of
|
||||
// free space, or if home disk free space checking is disabled.
|
||||
func (w *Wrapper) CheckHomeFreeSpace() error {
|
||||
return checkFreeSpace(w.Options().MinHomeDiskFree, fs.NewFilesystem(fs.FilesystemTypeBasic, filepath.Dir(w.ConfigPath())))
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ type folder struct {
|
||||
initialScanFinished chan struct{}
|
||||
watchCancel context.CancelFunc
|
||||
watchChan chan []string
|
||||
ignoresUpdated chan struct{} // The ignores changed, we need to restart watcher
|
||||
restartWatchChan chan struct{}
|
||||
}
|
||||
|
||||
func newFolder(model *Model, cfg config.FolderConfiguration) folder {
|
||||
@ -43,12 +43,25 @@ func newFolder(model *Model, cfg config.FolderConfiguration) folder {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *folder) IndexUpdated() {
|
||||
}
|
||||
func (f *folder) BringToFront(string) {}
|
||||
|
||||
func (f *folder) DelayScan(next time.Duration) {
|
||||
f.scan.Delay(next)
|
||||
}
|
||||
|
||||
func (f *folder) IndexUpdated() {
|
||||
}
|
||||
|
||||
func (f *folder) IgnoresUpdated() {
|
||||
if f.FSWatcherEnabled {
|
||||
f.scheduleWatchRestart()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *folder) Jobs() ([]string, []string) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *folder) Scan(subdirs []string) error {
|
||||
<-f.initialScanFinished
|
||||
return f.scan.Scan(subdirs)
|
||||
@ -58,13 +71,34 @@ func (f *folder) Stop() {
|
||||
f.cancel()
|
||||
}
|
||||
|
||||
func (f *folder) Jobs() ([]string, []string) {
|
||||
return nil, nil
|
||||
func (f *folder) BlockStats() map[string]int {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *folder) BringToFront(string) {}
|
||||
// CheckHealth checks the folder for common errors, updates the folder state
|
||||
// and returns the current folder error, or nil if the folder is healthy.
|
||||
func (f *folder) CheckHealth() error {
|
||||
err := f.getHealthError()
|
||||
f.setError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *folder) getHealthError() error {
|
||||
// Check for folder errors, with the most serious and specific first and
|
||||
// generic ones like out of space on the home disk later.
|
||||
|
||||
if err := f.CheckPath(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.CheckFreeSpace(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.model.cfg.CheckHomeFreeSpace(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func (f *folder) BlockStats() map[string]int {
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -72,7 +106,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
if err := f.model.internalScanFolderSubdirs(f.ctx, f.folderID, subDirs); err != nil {
|
||||
// Potentially sets the error twice, once in the scanner just
|
||||
// by doing a check, and once here, if the error returned is
|
||||
// the same one as returned by CheckFolderHealth, though
|
||||
// the same one as returned by CheckHealth, though
|
||||
// duplicate set is handled by setError.
|
||||
f.setError(err)
|
||||
return err
|
||||
@ -97,7 +131,7 @@ func (f *folder) scanTimerFired() {
|
||||
f.scan.Reschedule()
|
||||
}
|
||||
|
||||
func (f *folder) startWatcher() {
|
||||
func (f *folder) startWatch() {
|
||||
ctx, cancel := context.WithCancel(f.ctx)
|
||||
f.model.fmut.RLock()
|
||||
ignores := f.model.folderIgnores[f.folderID]
|
||||
@ -113,18 +147,45 @@ func (f *folder) startWatcher() {
|
||||
}
|
||||
}
|
||||
|
||||
func (f *folder) restartWatcher() {
|
||||
func (f *folder) restartWatch() {
|
||||
f.watchCancel()
|
||||
f.startWatcher()
|
||||
f.startWatch()
|
||||
f.Scan(nil)
|
||||
}
|
||||
|
||||
func (f *folder) IgnoresUpdated() {
|
||||
func (f *folder) scheduleWatchRestart() {
|
||||
select {
|
||||
case f.ignoresUpdated <- struct{}{}:
|
||||
case f.restartWatchChan <- struct{}{}:
|
||||
default:
|
||||
// We might be busy doing a pull and thus not reading from this
|
||||
// channel. The channel is 1-buffered, so one notification will be
|
||||
// queued to ensure we recheck after the pull.
|
||||
}
|
||||
}
|
||||
|
||||
func (f *folder) setError(err error) {
|
||||
_, _, oldErr := f.getState()
|
||||
if (err != nil && oldErr != nil && oldErr.Error() == err.Error()) || (err == nil && oldErr == nil) {
|
||||
return
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if oldErr == nil {
|
||||
l.Warnf("Error on folder %s: %v", f.Description(), err)
|
||||
} else {
|
||||
l.Infof("Error on folder %s changed: %q -> %q", f.Description(), oldErr, err)
|
||||
}
|
||||
} else {
|
||||
l.Infoln("Cleared error on folder", f.Description())
|
||||
}
|
||||
|
||||
if f.FSWatcherEnabled {
|
||||
if err != nil {
|
||||
f.watchCancel()
|
||||
} else {
|
||||
f.scheduleWatchRestart()
|
||||
}
|
||||
}
|
||||
|
||||
f.stateTracker.setError(err)
|
||||
}
|
||||
|
@ -94,49 +94,32 @@ func (s *stateTracker) getState() (current folderState, changed time.Time, err e
|
||||
return
|
||||
}
|
||||
|
||||
// setError sets the folder state to FolderError with the specified error.
|
||||
// setError sets the folder state to FolderError with the specified error or
|
||||
// to FolderIdle if the error is nil
|
||||
func (s *stateTracker) setError(err error) {
|
||||
s.mut.Lock()
|
||||
if s.current != FolderError || s.err.Error() != err.Error() {
|
||||
eventData := map[string]interface{}{
|
||||
"folder": s.folderID,
|
||||
"to": FolderError.String(),
|
||||
"from": s.current.String(),
|
||||
"error": err.Error(),
|
||||
}
|
||||
defer s.mut.Unlock()
|
||||
|
||||
if !s.changed.IsZero() {
|
||||
eventData["duration"] = time.Since(s.changed).Seconds()
|
||||
}
|
||||
eventData := map[string]interface{}{
|
||||
"folder": s.folderID,
|
||||
"from": s.current.String(),
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
eventData["error"] = err.Error()
|
||||
s.current = FolderError
|
||||
s.err = err
|
||||
s.changed = time.Now()
|
||||
|
||||
events.Default.Log(events.StateChanged, eventData)
|
||||
}
|
||||
s.mut.Unlock()
|
||||
}
|
||||
|
||||
// clearError sets the folder state to FolderIdle and clears the error
|
||||
func (s *stateTracker) clearError() {
|
||||
s.mut.Lock()
|
||||
if s.current == FolderError {
|
||||
eventData := map[string]interface{}{
|
||||
"folder": s.folderID,
|
||||
"to": FolderIdle.String(),
|
||||
"from": s.current.String(),
|
||||
}
|
||||
|
||||
if !s.changed.IsZero() {
|
||||
eventData["duration"] = time.Since(s.changed).Seconds()
|
||||
}
|
||||
|
||||
} else {
|
||||
s.current = FolderIdle
|
||||
s.err = nil
|
||||
s.changed = time.Now()
|
||||
|
||||
events.Default.Log(events.StateChanged, eventData)
|
||||
}
|
||||
s.mut.Unlock()
|
||||
|
||||
eventData["to"] = s.current.String()
|
||||
|
||||
if !s.changed.IsZero() {
|
||||
eventData["duration"] = time.Since(s.changed).Seconds()
|
||||
}
|
||||
|
||||
s.err = err
|
||||
s.changed = time.Now()
|
||||
|
||||
events.Default.Log(events.StateChanged, eventData)
|
||||
}
|
||||
|
@ -54,10 +54,10 @@ type service interface {
|
||||
Serve()
|
||||
Stop()
|
||||
BlockStats() map[string]int
|
||||
CheckHealth() error
|
||||
|
||||
getState() (folderState, time.Time, error)
|
||||
setState(state folderState)
|
||||
clearError()
|
||||
setError(err error)
|
||||
}
|
||||
|
||||
@ -107,14 +107,12 @@ var (
|
||||
)
|
||||
|
||||
var (
|
||||
errFolderPathMissing = errors.New("folder path missing")
|
||||
errFolderMarkerMissing = errors.New("folder marker missing")
|
||||
errDeviceUnknown = errors.New("unknown device")
|
||||
errDevicePaused = errors.New("device is paused")
|
||||
errDeviceIgnored = errors.New("device is ignored")
|
||||
errFolderPaused = errors.New("folder is paused")
|
||||
errFolderMissing = errors.New("no such folder")
|
||||
errNetworkNotAllowed = errors.New("network not allowed")
|
||||
errDeviceUnknown = errors.New("unknown device")
|
||||
errDevicePaused = errors.New("device is paused")
|
||||
errDeviceIgnored = errors.New("device is ignored")
|
||||
errFolderPaused = errors.New("folder is paused")
|
||||
errFolderMissing = errors.New("no such folder")
|
||||
errNetworkNotAllowed = errors.New("network not allowed")
|
||||
)
|
||||
|
||||
// NewModel creates and starts a new model. The model starts in read-only mode,
|
||||
@ -1361,7 +1359,7 @@ func (m *Model) GetIgnores(folder string) ([]string, []string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if err := m.checkFolderPath(cfg); err != nil {
|
||||
if err := cfg.CheckPath(); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
@ -1791,7 +1789,7 @@ func (m *Model) ScanFolders() map[string]error {
|
||||
|
||||
// Potentially sets the error twice, once in the scanner just
|
||||
// by doing a check, and once here, if the error returned is
|
||||
// the same one as returned by CheckFolderHealth, though
|
||||
// the same one as returned by CheckHealth, though
|
||||
// duplicate set is handled by setError.
|
||||
m.fmut.RLock()
|
||||
srv := m.folderRunners[folder]
|
||||
@ -1871,16 +1869,13 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
||||
return errFolderMissing
|
||||
}
|
||||
|
||||
if err := m.CheckFolderHealth(folder); err != nil {
|
||||
runner.setError(err)
|
||||
l.Infof("Stopping folder %s due to error: %s", folderCfg.Description(), err)
|
||||
if err := runner.CheckHealth(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
|
||||
err = fmt.Errorf("loading ignores: %v", err)
|
||||
runner.setError(err)
|
||||
l.Infof("Stopping folder %s due to error: %s", folderCfg.Description(), err)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -1914,7 +1909,7 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
||||
// The error we get here is likely an OS level error, which might not be
|
||||
// as readable as our health check errors. Check if we can get a health
|
||||
// check error first, and use that if it's available.
|
||||
if ferr := m.CheckFolderHealth(folder); ferr != nil {
|
||||
if ferr := runner.CheckHealth(); ferr != nil {
|
||||
err = ferr
|
||||
}
|
||||
runner.setError(err)
|
||||
@ -1926,8 +1921,8 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
||||
|
||||
for f := range fchan {
|
||||
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
|
||||
if err := m.CheckFolderHealth(folder); err != nil {
|
||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), err)
|
||||
if err := runner.CheckHealth(); err != nil {
|
||||
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
|
||||
return err
|
||||
}
|
||||
m.updateLocalsFromScanning(folder, batch)
|
||||
@ -1938,8 +1933,8 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
||||
batchSizeBytes += f.ProtoSize()
|
||||
}
|
||||
|
||||
if err := m.CheckFolderHealth(folder); err != nil {
|
||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), err)
|
||||
if err := runner.CheckHealth(); err != nil {
|
||||
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
|
||||
return err
|
||||
} else if len(batch) > 0 {
|
||||
m.updateLocalsFromScanning(folder, batch)
|
||||
@ -1961,7 +1956,7 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
||||
fset.WithPrefixedHaveTruncated(protocol.LocalDeviceID, sub, func(fi db.FileIntf) bool {
|
||||
f := fi.(db.FileInfoTruncated)
|
||||
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
|
||||
if err := m.CheckFolderHealth(folder); err != nil {
|
||||
if err := runner.CheckHealth(); err != nil {
|
||||
iterError = err
|
||||
return false
|
||||
}
|
||||
@ -2020,13 +2015,13 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
||||
})
|
||||
|
||||
if iterError != nil {
|
||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), iterError)
|
||||
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
|
||||
return iterError
|
||||
}
|
||||
}
|
||||
|
||||
if err := m.CheckFolderHealth(folder); err != nil {
|
||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), err)
|
||||
if err := runner.CheckHealth(); err != nil {
|
||||
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
|
||||
return err
|
||||
} else if len(batch) > 0 {
|
||||
m.updateLocalsFromScanning(folder, batch)
|
||||
@ -2344,112 +2339,6 @@ func (m *Model) BringToFront(folder, file string) {
|
||||
}
|
||||
}
|
||||
|
||||
// CheckFolderHealth checks the folder for common errors and returns the
|
||||
// current folder error, or nil if the folder is healthy.
|
||||
func (m *Model) CheckFolderHealth(id string) error {
|
||||
folder, ok := m.cfg.Folders()[id]
|
||||
if !ok {
|
||||
return errFolderMissing
|
||||
}
|
||||
|
||||
// Check for folder errors, with the most serious and specific first and
|
||||
// generic ones like out of space on the home disk later. Note the
|
||||
// inverted error flow (err==nil checks) here.
|
||||
|
||||
err := m.checkFolderPath(folder)
|
||||
if err == nil {
|
||||
err = m.checkFolderFreeSpace(folder)
|
||||
}
|
||||
if err == nil {
|
||||
err = m.checkHomeDiskFree()
|
||||
}
|
||||
|
||||
// Set or clear the error on the runner, which also does logging and
|
||||
// generates events and stuff.
|
||||
m.runnerExchangeError(folder, err)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// checkFolderPath returns nil if the folder path exists and has the marker file.
|
||||
func (m *Model) checkFolderPath(folder config.FolderConfiguration) error {
|
||||
fs := folder.Filesystem()
|
||||
|
||||
if fi, err := fs.Stat("."); err != nil || !fi.IsDir() {
|
||||
return errFolderPathMissing
|
||||
}
|
||||
|
||||
if !folder.HasMarker() {
|
||||
return errFolderMarkerMissing
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkFolderFreeSpace returns nil if the folder has the required amount of
|
||||
// free space, or if folder free space checking is disabled.
|
||||
func (m *Model) checkFolderFreeSpace(folder config.FolderConfiguration) error {
|
||||
return m.checkFreeSpace(folder.MinDiskFree, folder.Filesystem())
|
||||
}
|
||||
|
||||
// checkHomeDiskFree returns nil if the home disk has the required amount of
|
||||
// free space, or if home disk free space checking is disabled.
|
||||
func (m *Model) checkHomeDiskFree() error {
|
||||
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, filepath.Dir(m.cfg.ConfigPath()))
|
||||
return m.checkFreeSpace(m.cfg.Options().MinHomeDiskFree, fs)
|
||||
}
|
||||
|
||||
func (m *Model) checkFreeSpace(req config.Size, fs fs.Filesystem) error {
|
||||
val := req.BaseValue()
|
||||
if val <= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
usage, err := fs.Usage(".")
|
||||
if req.Percentage() {
|
||||
freePct := (float64(usage.Free) / float64(usage.Total)) * 100
|
||||
if err == nil && freePct < val {
|
||||
return fmt.Errorf("insufficient space in %v %v: %f %% < %v", fs.Type(), fs.URI(), freePct, req)
|
||||
}
|
||||
} else {
|
||||
if err == nil && float64(usage.Free) < val {
|
||||
return fmt.Errorf("insufficient space in %v %v: %v < %v", fs.Type(), fs.URI(), usage.Free, req)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// runnerExchangeError sets the given error (which way be nil) on the folder
|
||||
// runner. If the error differs from any previous error, logging and events
|
||||
// happen.
|
||||
func (m *Model) runnerExchangeError(folder config.FolderConfiguration, err error) {
|
||||
m.fmut.RLock()
|
||||
runner, runnerExists := m.folderRunners[folder.ID]
|
||||
m.fmut.RUnlock()
|
||||
|
||||
var oldErr error
|
||||
if runnerExists {
|
||||
_, _, oldErr = runner.getState()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if oldErr != nil && oldErr.Error() != err.Error() {
|
||||
l.Infof("Folder %s error changed: %q -> %q", folder.Description(), oldErr, err)
|
||||
} else if oldErr == nil {
|
||||
l.Warnf("Stopping folder %s - %v", folder.Description(), err)
|
||||
}
|
||||
if runnerExists {
|
||||
runner.setError(err)
|
||||
}
|
||||
} else if oldErr != nil {
|
||||
l.Infof("Folder %q error is cleared, restarting", folder.ID)
|
||||
if runnerExists {
|
||||
runner.clearError()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) ResetFolder(folder string) {
|
||||
l.Infof("Cleaning data for folder %q", folder)
|
||||
db.DropFolder(m.db, folder)
|
||||
|
@ -2028,7 +2028,7 @@ func TestScanNoDatabaseWrite(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestIssue2782(t *testing.T) {
|
||||
// CheckFolderHealth should accept a symlinked folder, when using tilde-expanded path.
|
||||
// CheckHealth should accept a symlinked folder, when using tilde-expanded path.
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
t.Skip("not reliable on Windows")
|
||||
@ -2070,7 +2070,10 @@ func TestIssue2782(t *testing.T) {
|
||||
t.Error("scan error:", err)
|
||||
}
|
||||
|
||||
if err := m.CheckFolderHealth("default"); err != nil {
|
||||
m.fmut.Lock()
|
||||
runner, _ := m.folderRunners["default"]
|
||||
m.fmut.Unlock()
|
||||
if err := runner.CheckHealth(); err != nil {
|
||||
t.Error("health check error:", err)
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ func (f *sendOnlyFolder) Serve() {
|
||||
}()
|
||||
|
||||
if f.FSWatcherEnabled {
|
||||
f.startWatcher()
|
||||
f.startWatch()
|
||||
}
|
||||
|
||||
for {
|
||||
@ -43,10 +43,8 @@ func (f *sendOnlyFolder) Serve() {
|
||||
case <-f.ctx.Done():
|
||||
return
|
||||
|
||||
case <-f.ignoresUpdated:
|
||||
if f.FSWatcherEnabled {
|
||||
f.restartWatcher()
|
||||
}
|
||||
case <-f.restartWatchChan:
|
||||
f.restartWatch()
|
||||
|
||||
case <-f.scan.timer.C:
|
||||
l.Debugln(f, "Scanning subdirectories")
|
||||
|
@ -165,7 +165,7 @@ func (f *sendReceiveFolder) Serve() {
|
||||
var prevIgnoreHash string
|
||||
|
||||
if f.FSWatcherEnabled {
|
||||
f.startWatcher()
|
||||
f.startWatch()
|
||||
}
|
||||
|
||||
for {
|
||||
@ -178,12 +178,6 @@ func (f *sendReceiveFolder) Serve() {
|
||||
f.pullTimer.Reset(0)
|
||||
l.Debugln(f, "remote index updated, rescheduling pull")
|
||||
|
||||
case <-f.ignoresUpdated:
|
||||
if f.FSWatcherEnabled {
|
||||
f.restartWatcher()
|
||||
}
|
||||
f.IndexUpdated()
|
||||
|
||||
case <-f.pullTimer.C:
|
||||
select {
|
||||
case <-f.initialScanFinished:
|
||||
@ -214,8 +208,8 @@ func (f *sendReceiveFolder) Serve() {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := f.model.CheckFolderHealth(f.folderID); err != nil {
|
||||
l.Infoln("Skipping pull of", f.Description(), "due to folder error:", err)
|
||||
if err := f.CheckHealth(); err != nil {
|
||||
l.Debugln("Skipping pull of", f.Description(), "due to folder error:", err)
|
||||
f.pullTimer.Reset(f.sleep)
|
||||
continue
|
||||
}
|
||||
@ -292,6 +286,9 @@ func (f *sendReceiveFolder) Serve() {
|
||||
case fsEvents := <-f.watchChan:
|
||||
l.Debugln(f, "filesystem notification rescan")
|
||||
f.scanSubdirs(fsEvents)
|
||||
|
||||
case <-f.restartWatchChan:
|
||||
f.restartWatch()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1696,6 +1693,11 @@ func (f *sendReceiveFolder) currentErrors() []fileError {
|
||||
return errors
|
||||
}
|
||||
|
||||
func (f *sendReceiveFolder) IgnoresUpdated() {
|
||||
f.folder.IgnoresUpdated()
|
||||
f.IndexUpdated()
|
||||
}
|
||||
|
||||
// A []fileError is sent as part of an event and will be JSON serialized.
|
||||
type fileError struct {
|
||||
Path string `json:"path"`
|
||||
|
Loading…
x
Reference in New Issue
Block a user