folder.go 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at https://mozilla.org/MPL/2.0/.
  6. package model
  7. import (
  8. "context"
  9. "errors"
  10. "time"
  11. "github.com/syncthing/syncthing/lib/config"
  12. "github.com/syncthing/syncthing/lib/ignore"
  13. "github.com/syncthing/syncthing/lib/protocol"
  14. "github.com/syncthing/syncthing/lib/sync"
  15. "github.com/syncthing/syncthing/lib/watchaggregator"
  16. )
  17. var errWatchNotStarted error = errors.New("not started")
  18. type folder struct {
  19. stateTracker
  20. config.FolderConfiguration
  21. model *Model
  22. shortID protocol.ShortID
  23. ctx context.Context
  24. cancel context.CancelFunc
  25. scan folderScanner
  26. initialScanFinished chan struct{}
  27. pullScheduled chan struct{}
  28. watchCancel context.CancelFunc
  29. watchChan chan []string
  30. restartWatchChan chan struct{}
  31. watchErr error
  32. watchErrMut sync.Mutex
  33. }
  34. func newFolder(model *Model, cfg config.FolderConfiguration) folder {
  35. ctx, cancel := context.WithCancel(context.Background())
  36. return folder{
  37. stateTracker: newStateTracker(cfg.ID),
  38. FolderConfiguration: cfg,
  39. model: model,
  40. shortID: model.shortID,
  41. ctx: ctx,
  42. cancel: cancel,
  43. scan: newFolderScanner(cfg),
  44. initialScanFinished: make(chan struct{}),
  45. pullScheduled: make(chan struct{}, 1), // This needs to be 1-buffered so that we queue a pull if we're busy when it comes.
  46. watchCancel: func() {},
  47. watchErr: errWatchNotStarted,
  48. watchErrMut: sync.NewMutex(),
  49. }
  50. }
  51. func (f *folder) BringToFront(string) {}
  52. func (f *folder) DelayScan(next time.Duration) {
  53. f.scan.Delay(next)
  54. }
  55. func (f *folder) IgnoresUpdated() {
  56. if f.FSWatcherEnabled {
  57. f.scheduleWatchRestart()
  58. }
  59. }
  60. func (f *folder) SchedulePull() {
  61. select {
  62. case f.pullScheduled <- struct{}{}:
  63. default:
  64. // We might be busy doing a pull and thus not reading from this
  65. // channel. The channel is 1-buffered, so one notification will be
  66. // queued to ensure we recheck after the pull, but beyond that we must
  67. // make sure to not block index receiving.
  68. }
  69. }
  70. func (f *folder) Jobs() ([]string, []string) {
  71. return nil, nil
  72. }
  73. func (f *folder) Scan(subdirs []string) error {
  74. <-f.initialScanFinished
  75. return f.scan.Scan(subdirs)
  76. }
  77. func (f *folder) Stop() {
  78. f.cancel()
  79. }
  80. // CheckHealth checks the folder for common errors, updates the folder state
  81. // and returns the current folder error, or nil if the folder is healthy.
  82. func (f *folder) CheckHealth() error {
  83. err := f.getHealthError()
  84. f.setError(err)
  85. return err
  86. }
  87. func (f *folder) getHealthError() error {
  88. // Check for folder errors, with the most serious and specific first and
  89. // generic ones like out of space on the home disk later.
  90. if err := f.CheckPath(); err != nil {
  91. return err
  92. }
  93. if err := f.CheckFreeSpace(); err != nil {
  94. return err
  95. }
  96. if err := f.model.cfg.CheckHomeFreeSpace(); err != nil {
  97. return err
  98. }
  99. return nil
  100. }
  101. func (f *folder) scanSubdirs(subDirs []string) error {
  102. if err := f.model.internalScanFolderSubdirs(f.ctx, f.folderID, subDirs); err != nil {
  103. // Potentially sets the error twice, once in the scanner just
  104. // by doing a check, and once here, if the error returned is
  105. // the same one as returned by CheckHealth, though
  106. // duplicate set is handled by setError.
  107. f.setError(err)
  108. return err
  109. }
  110. return nil
  111. }
  112. func (f *folder) scanTimerFired() {
  113. err := f.scanSubdirs(nil)
  114. select {
  115. case <-f.initialScanFinished:
  116. default:
  117. status := "Completed"
  118. if err != nil {
  119. status = "Failed"
  120. }
  121. l.Infoln(status, "initial scan of", f.Type.String(), "folder", f.Description())
  122. close(f.initialScanFinished)
  123. }
  124. f.scan.Reschedule()
  125. }
  126. func (f *folder) WatchError() error {
  127. f.watchErrMut.Lock()
  128. defer f.watchErrMut.Unlock()
  129. return f.watchErr
  130. }
  131. // stopWatch immediately aborts watching and may be called asynchronously
  132. func (f *folder) stopWatch() {
  133. f.watchCancel()
  134. f.watchErrMut.Lock()
  135. f.watchErr = errWatchNotStarted
  136. f.watchErrMut.Unlock()
  137. }
  138. // scheduleWatchRestart makes sure watching is restarted from the main for loop
  139. // in a folder's Serve and thus may be called asynchronously (e.g. when ignores change).
  140. func (f *folder) scheduleWatchRestart() {
  141. select {
  142. case f.restartWatchChan <- struct{}{}:
  143. default:
  144. // We might be busy doing a pull and thus not reading from this
  145. // channel. The channel is 1-buffered, so one notification will be
  146. // queued to ensure we recheck after the pull.
  147. }
  148. }
  149. // restartWatch should only ever be called synchronously. If you want to use
  150. // this asynchronously, you should probably use scheduleWatchRestart instead.
  151. func (f *folder) restartWatch() {
  152. f.stopWatch()
  153. f.startWatch()
  154. f.Scan(nil)
  155. }
  156. // startWatch should only ever be called synchronously. If you want to use
  157. // this asynchronously, you should probably use scheduleWatchRestart instead.
  158. func (f *folder) startWatch() {
  159. ctx, cancel := context.WithCancel(f.ctx)
  160. f.model.fmut.RLock()
  161. ignores := f.model.folderIgnores[f.folderID]
  162. f.model.fmut.RUnlock()
  163. f.watchChan = make(chan []string)
  164. f.watchCancel = cancel
  165. go f.startWatchAsync(ctx, ignores)
  166. }
  167. // startWatchAsync tries to start the filesystem watching and retries every minute on failure.
  168. // It is a convenience function that should not be used except in startWatch.
  169. func (f *folder) startWatchAsync(ctx context.Context, ignores *ignore.Matcher) {
  170. timer := time.NewTimer(0)
  171. for {
  172. select {
  173. case <-timer.C:
  174. eventChan, err := f.Filesystem().Watch(".", ignores, ctx, f.IgnorePerms)
  175. f.watchErrMut.Lock()
  176. prevErr := f.watchErr
  177. f.watchErr = err
  178. f.watchErrMut.Unlock()
  179. if err != nil {
  180. if prevErr == errWatchNotStarted {
  181. l.Warnf("Failed to start filesystem watcher for folder %s: %v", f.Description(), err)
  182. } else {
  183. l.Debugf("Failed to start filesystem watcher for folder %s again: %v", f.Description(), err)
  184. }
  185. timer.Reset(time.Minute)
  186. continue
  187. }
  188. watchaggregator.Aggregate(eventChan, f.watchChan, f.FolderConfiguration, f.model.cfg, ctx)
  189. l.Debugln("Started filesystem watcher for folder", f.Description())
  190. return
  191. case <-ctx.Done():
  192. return
  193. }
  194. }
  195. }
  196. func (f *folder) setError(err error) {
  197. _, _, oldErr := f.getState()
  198. if (err != nil && oldErr != nil && oldErr.Error() == err.Error()) || (err == nil && oldErr == nil) {
  199. return
  200. }
  201. if err != nil {
  202. if oldErr == nil {
  203. l.Warnf("Error on folder %s: %v", f.Description(), err)
  204. } else {
  205. l.Infof("Error on folder %s changed: %q -> %q", f.Description(), oldErr, err)
  206. }
  207. } else {
  208. l.Infoln("Cleared error on folder", f.Description())
  209. }
  210. if f.FSWatcherEnabled {
  211. if err != nil {
  212. f.stopWatch()
  213. } else {
  214. f.scheduleWatchRestart()
  215. }
  216. }
  217. f.stateTracker.setError(err)
  218. }