mirror of
https://github.com/octoleo/syncthing.git
synced 2025-01-03 15:17:25 +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
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
@ -14,6 +15,11 @@ import (
|
|||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
errPathMissing = errors.New("folder path missing")
|
||||||
|
errMarkerMissing = errors.New("folder marker missing")
|
||||||
|
)
|
||||||
|
|
||||||
type FolderConfiguration struct {
|
type FolderConfiguration struct {
|
||||||
ID string `xml:"id,attr" json:"id"`
|
ID string `xml:"id,attr" json:"id"`
|
||||||
Label string `xml:"label,attr" json:"label"`
|
Label string `xml:"label,attr" json:"label"`
|
||||||
@ -82,32 +88,44 @@ func (f FolderConfiguration) Filesystem() fs.Filesystem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *FolderConfiguration) CreateMarker() error {
|
func (f *FolderConfiguration) CreateMarker() error {
|
||||||
if !f.HasMarker() {
|
if err := f.CheckPath(); err != errMarkerMissing {
|
||||||
permBits := fs.FileMode(0777)
|
return err
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *FolderConfiguration) HasMarker() bool {
|
// CheckPath returns nil if the folder root exists and contains the marker file
|
||||||
_, err := f.Filesystem().Stat(".stfolder")
|
func (f *FolderConfiguration) CheckPath() error {
|
||||||
return err == nil
|
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) {
|
func (f *FolderConfiguration) CreateRoot() (err error) {
|
||||||
@ -184,3 +202,7 @@ func (l FolderDeviceConfigurationList) Swap(a, b int) {
|
|||||||
func (l FolderDeviceConfigurationList) Len() int {
|
func (l FolderDeviceConfigurationList) Len() int {
|
||||||
return len(l)
|
return len(l)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *FolderConfiguration) CheckFreeSpace() (err error) {
|
||||||
|
return checkFreeSpace(f.MinDiskFree, f.Filesystem())
|
||||||
|
}
|
||||||
|
@ -10,6 +10,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/syncthing/syncthing/lib/fs"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Size struct {
|
type Size struct {
|
||||||
@ -73,3 +75,24 @@ func (s Size) String() string {
|
|||||||
func (Size) ParseDefault(s string) (interface{}, error) {
|
func (Size) ParseDefault(s string) (interface{}, error) {
|
||||||
return ParseSize(s)
|
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 (
|
import (
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/syncthing/syncthing/lib/events"
|
"github.com/syncthing/syncthing/lib/events"
|
||||||
|
"github.com/syncthing/syncthing/lib/fs"
|
||||||
"github.com/syncthing/syncthing/lib/osutil"
|
"github.com/syncthing/syncthing/lib/osutil"
|
||||||
"github.com/syncthing/syncthing/lib/protocol"
|
"github.com/syncthing/syncthing/lib/protocol"
|
||||||
"github.com/syncthing/syncthing/lib/rand"
|
"github.com/syncthing/syncthing/lib/rand"
|
||||||
@ -468,3 +470,9 @@ func (w *Wrapper) MyName() string {
|
|||||||
cfg, _ := w.Device(myID)
|
cfg, _ := w.Device(myID)
|
||||||
return cfg.Name
|
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{}
|
initialScanFinished chan struct{}
|
||||||
watchCancel context.CancelFunc
|
watchCancel context.CancelFunc
|
||||||
watchChan chan []string
|
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 {
|
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) {
|
func (f *folder) DelayScan(next time.Duration) {
|
||||||
f.scan.Delay(next)
|
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 {
|
func (f *folder) Scan(subdirs []string) error {
|
||||||
<-f.initialScanFinished
|
<-f.initialScanFinished
|
||||||
return f.scan.Scan(subdirs)
|
return f.scan.Scan(subdirs)
|
||||||
@ -58,13 +71,34 @@ func (f *folder) Stop() {
|
|||||||
f.cancel()
|
f.cancel()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *folder) Jobs() ([]string, []string) {
|
func (f *folder) BlockStats() map[string]int {
|
||||||
return nil, nil
|
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
|
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 {
|
if err := f.model.internalScanFolderSubdirs(f.ctx, f.folderID, subDirs); err != nil {
|
||||||
// Potentially sets the error twice, once in the scanner just
|
// Potentially sets the error twice, once in the scanner just
|
||||||
// by doing a check, and once here, if the error returned is
|
// 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.
|
// duplicate set is handled by setError.
|
||||||
f.setError(err)
|
f.setError(err)
|
||||||
return err
|
return err
|
||||||
@ -97,7 +131,7 @@ func (f *folder) scanTimerFired() {
|
|||||||
f.scan.Reschedule()
|
f.scan.Reschedule()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *folder) startWatcher() {
|
func (f *folder) startWatch() {
|
||||||
ctx, cancel := context.WithCancel(f.ctx)
|
ctx, cancel := context.WithCancel(f.ctx)
|
||||||
f.model.fmut.RLock()
|
f.model.fmut.RLock()
|
||||||
ignores := f.model.folderIgnores[f.folderID]
|
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.watchCancel()
|
||||||
f.startWatcher()
|
f.startWatch()
|
||||||
f.Scan(nil)
|
f.Scan(nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *folder) IgnoresUpdated() {
|
func (f *folder) scheduleWatchRestart() {
|
||||||
select {
|
select {
|
||||||
case f.ignoresUpdated <- struct{}{}:
|
case f.restartWatchChan <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
// We might be busy doing a pull and thus not reading from this
|
// We might be busy doing a pull and thus not reading from this
|
||||||
// channel. The channel is 1-buffered, so one notification will be
|
// channel. The channel is 1-buffered, so one notification will be
|
||||||
// queued to ensure we recheck after the pull.
|
// 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
|
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) {
|
func (s *stateTracker) setError(err error) {
|
||||||
s.mut.Lock()
|
s.mut.Lock()
|
||||||
if s.current != FolderError || s.err.Error() != err.Error() {
|
defer s.mut.Unlock()
|
||||||
eventData := map[string]interface{}{
|
|
||||||
"folder": s.folderID,
|
|
||||||
"to": FolderError.String(),
|
|
||||||
"from": s.current.String(),
|
|
||||||
"error": err.Error(),
|
|
||||||
}
|
|
||||||
|
|
||||||
if !s.changed.IsZero() {
|
eventData := map[string]interface{}{
|
||||||
eventData["duration"] = time.Since(s.changed).Seconds()
|
"folder": s.folderID,
|
||||||
}
|
"from": s.current.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
eventData["error"] = err.Error()
|
||||||
s.current = FolderError
|
s.current = FolderError
|
||||||
s.err = err
|
} else {
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
s.current = FolderIdle
|
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()
|
Serve()
|
||||||
Stop()
|
Stop()
|
||||||
BlockStats() map[string]int
|
BlockStats() map[string]int
|
||||||
|
CheckHealth() error
|
||||||
|
|
||||||
getState() (folderState, time.Time, error)
|
getState() (folderState, time.Time, error)
|
||||||
setState(state folderState)
|
setState(state folderState)
|
||||||
clearError()
|
|
||||||
setError(err error)
|
setError(err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,14 +107,12 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
errFolderPathMissing = errors.New("folder path missing")
|
errDeviceUnknown = errors.New("unknown device")
|
||||||
errFolderMarkerMissing = errors.New("folder marker missing")
|
errDevicePaused = errors.New("device is paused")
|
||||||
errDeviceUnknown = errors.New("unknown device")
|
errDeviceIgnored = errors.New("device is ignored")
|
||||||
errDevicePaused = errors.New("device is paused")
|
errFolderPaused = errors.New("folder is paused")
|
||||||
errDeviceIgnored = errors.New("device is ignored")
|
errFolderMissing = errors.New("no such folder")
|
||||||
errFolderPaused = errors.New("folder is paused")
|
errNetworkNotAllowed = errors.New("network not allowed")
|
||||||
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,
|
// 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
|
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
|
// Potentially sets the error twice, once in the scanner just
|
||||||
// by doing a check, and once here, if the error returned is
|
// 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.
|
// duplicate set is handled by setError.
|
||||||
m.fmut.RLock()
|
m.fmut.RLock()
|
||||||
srv := m.folderRunners[folder]
|
srv := m.folderRunners[folder]
|
||||||
@ -1871,16 +1869,13 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
|||||||
return errFolderMissing
|
return errFolderMissing
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.CheckFolderHealth(folder); err != nil {
|
if err := runner.CheckHealth(); err != nil {
|
||||||
runner.setError(err)
|
|
||||||
l.Infof("Stopping folder %s due to error: %s", folderCfg.Description(), err)
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
|
if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
|
||||||
err = fmt.Errorf("loading ignores: %v", err)
|
err = fmt.Errorf("loading ignores: %v", err)
|
||||||
runner.setError(err)
|
runner.setError(err)
|
||||||
l.Infof("Stopping folder %s due to error: %s", folderCfg.Description(), err)
|
|
||||||
return 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
|
// 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
|
// as readable as our health check errors. Check if we can get a health
|
||||||
// check error first, and use that if it's available.
|
// 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
|
err = ferr
|
||||||
}
|
}
|
||||||
runner.setError(err)
|
runner.setError(err)
|
||||||
@ -1926,8 +1921,8 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
|||||||
|
|
||||||
for f := range fchan {
|
for f := range fchan {
|
||||||
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
|
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
|
||||||
if err := m.CheckFolderHealth(folder); err != nil {
|
if err := runner.CheckHealth(); err != nil {
|
||||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), err)
|
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
m.updateLocalsFromScanning(folder, batch)
|
m.updateLocalsFromScanning(folder, batch)
|
||||||
@ -1938,8 +1933,8 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
|||||||
batchSizeBytes += f.ProtoSize()
|
batchSizeBytes += f.ProtoSize()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.CheckFolderHealth(folder); err != nil {
|
if err := runner.CheckHealth(); err != nil {
|
||||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), err)
|
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
|
||||||
return err
|
return err
|
||||||
} else if len(batch) > 0 {
|
} else if len(batch) > 0 {
|
||||||
m.updateLocalsFromScanning(folder, batch)
|
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 {
|
fset.WithPrefixedHaveTruncated(protocol.LocalDeviceID, sub, func(fi db.FileIntf) bool {
|
||||||
f := fi.(db.FileInfoTruncated)
|
f := fi.(db.FileInfoTruncated)
|
||||||
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
|
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
|
||||||
if err := m.CheckFolderHealth(folder); err != nil {
|
if err := runner.CheckHealth(); err != nil {
|
||||||
iterError = err
|
iterError = err
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -2020,13 +2015,13 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
|
|||||||
})
|
})
|
||||||
|
|
||||||
if iterError != nil {
|
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
|
return iterError
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := m.CheckFolderHealth(folder); err != nil {
|
if err := runner.CheckHealth(); err != nil {
|
||||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), err)
|
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
|
||||||
return err
|
return err
|
||||||
} else if len(batch) > 0 {
|
} else if len(batch) > 0 {
|
||||||
m.updateLocalsFromScanning(folder, batch)
|
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) {
|
func (m *Model) ResetFolder(folder string) {
|
||||||
l.Infof("Cleaning data for folder %q", folder)
|
l.Infof("Cleaning data for folder %q", folder)
|
||||||
db.DropFolder(m.db, folder)
|
db.DropFolder(m.db, folder)
|
||||||
|
@ -2028,7 +2028,7 @@ func TestScanNoDatabaseWrite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestIssue2782(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" {
|
if runtime.GOOS == "windows" {
|
||||||
t.Skip("not reliable on Windows")
|
t.Skip("not reliable on Windows")
|
||||||
@ -2070,7 +2070,10 @@ func TestIssue2782(t *testing.T) {
|
|||||||
t.Error("scan error:", err)
|
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)
|
t.Error("health check error:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ func (f *sendOnlyFolder) Serve() {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if f.FSWatcherEnabled {
|
if f.FSWatcherEnabled {
|
||||||
f.startWatcher()
|
f.startWatch()
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -43,10 +43,8 @@ func (f *sendOnlyFolder) Serve() {
|
|||||||
case <-f.ctx.Done():
|
case <-f.ctx.Done():
|
||||||
return
|
return
|
||||||
|
|
||||||
case <-f.ignoresUpdated:
|
case <-f.restartWatchChan:
|
||||||
if f.FSWatcherEnabled {
|
f.restartWatch()
|
||||||
f.restartWatcher()
|
|
||||||
}
|
|
||||||
|
|
||||||
case <-f.scan.timer.C:
|
case <-f.scan.timer.C:
|
||||||
l.Debugln(f, "Scanning subdirectories")
|
l.Debugln(f, "Scanning subdirectories")
|
||||||
|
@ -165,7 +165,7 @@ func (f *sendReceiveFolder) Serve() {
|
|||||||
var prevIgnoreHash string
|
var prevIgnoreHash string
|
||||||
|
|
||||||
if f.FSWatcherEnabled {
|
if f.FSWatcherEnabled {
|
||||||
f.startWatcher()
|
f.startWatch()
|
||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
@ -178,12 +178,6 @@ func (f *sendReceiveFolder) Serve() {
|
|||||||
f.pullTimer.Reset(0)
|
f.pullTimer.Reset(0)
|
||||||
l.Debugln(f, "remote index updated, rescheduling pull")
|
l.Debugln(f, "remote index updated, rescheduling pull")
|
||||||
|
|
||||||
case <-f.ignoresUpdated:
|
|
||||||
if f.FSWatcherEnabled {
|
|
||||||
f.restartWatcher()
|
|
||||||
}
|
|
||||||
f.IndexUpdated()
|
|
||||||
|
|
||||||
case <-f.pullTimer.C:
|
case <-f.pullTimer.C:
|
||||||
select {
|
select {
|
||||||
case <-f.initialScanFinished:
|
case <-f.initialScanFinished:
|
||||||
@ -214,8 +208,8 @@ func (f *sendReceiveFolder) Serve() {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := f.model.CheckFolderHealth(f.folderID); err != nil {
|
if err := f.CheckHealth(); err != nil {
|
||||||
l.Infoln("Skipping pull of", f.Description(), "due to folder error:", err)
|
l.Debugln("Skipping pull of", f.Description(), "due to folder error:", err)
|
||||||
f.pullTimer.Reset(f.sleep)
|
f.pullTimer.Reset(f.sleep)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -292,6 +286,9 @@ func (f *sendReceiveFolder) Serve() {
|
|||||||
case fsEvents := <-f.watchChan:
|
case fsEvents := <-f.watchChan:
|
||||||
l.Debugln(f, "filesystem notification rescan")
|
l.Debugln(f, "filesystem notification rescan")
|
||||||
f.scanSubdirs(fsEvents)
|
f.scanSubdirs(fsEvents)
|
||||||
|
|
||||||
|
case <-f.restartWatchChan:
|
||||||
|
f.restartWatch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1696,6 +1693,11 @@ func (f *sendReceiveFolder) currentErrors() []fileError {
|
|||||||
return errors
|
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.
|
// A []fileError is sent as part of an event and will be JSON serialized.
|
||||||
type fileError struct {
|
type fileError struct {
|
||||||
Path string `json:"path"`
|
Path string `json:"path"`
|
||||||
|
Loading…
Reference in New Issue
Block a user