folderstate.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. // Copyright (C) 2015 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. "log/slog"
  9. "sync"
  10. "time"
  11. "github.com/syncthing/syncthing/internal/slogutil"
  12. "github.com/syncthing/syncthing/lib/events"
  13. )
  14. type folderState int
  15. const (
  16. FolderIdle folderState = iota
  17. FolderScanning
  18. FolderScanWaiting
  19. FolderSyncWaiting
  20. FolderSyncPreparing
  21. FolderSyncing
  22. FolderCleaning
  23. FolderCleanWaiting
  24. FolderError
  25. )
  26. func (s folderState) String() string {
  27. switch s {
  28. case FolderIdle:
  29. return "idle"
  30. case FolderScanning:
  31. return "scanning"
  32. case FolderScanWaiting:
  33. return "scan-waiting"
  34. case FolderSyncWaiting:
  35. return "sync-waiting"
  36. case FolderSyncPreparing:
  37. return "sync-preparing"
  38. case FolderSyncing:
  39. return "syncing"
  40. case FolderCleaning:
  41. return "cleaning"
  42. case FolderCleanWaiting:
  43. return "clean-waiting"
  44. case FolderError:
  45. return "error"
  46. default:
  47. return "unknown"
  48. }
  49. }
  50. type remoteFolderState int
  51. const (
  52. remoteFolderUnknown remoteFolderState = iota
  53. remoteFolderNotSharing
  54. remoteFolderPaused
  55. remoteFolderValid
  56. )
  57. func (s remoteFolderState) String() string {
  58. switch s {
  59. case remoteFolderUnknown:
  60. return "unknown"
  61. case remoteFolderNotSharing:
  62. return "notSharing"
  63. case remoteFolderPaused:
  64. return "paused"
  65. case remoteFolderValid:
  66. return "valid"
  67. default:
  68. return "unknown"
  69. }
  70. }
  71. func (s remoteFolderState) MarshalText() ([]byte, error) {
  72. return []byte(s.String()), nil
  73. }
  74. type stateTracker struct {
  75. folderID string
  76. evLogger events.Logger
  77. mut sync.Mutex
  78. current folderState
  79. err error
  80. changed time.Time
  81. }
  82. func newStateTracker(id string, evLogger events.Logger) stateTracker {
  83. return stateTracker{
  84. folderID: id,
  85. evLogger: evLogger,
  86. }
  87. }
  88. // setState sets the new folder state, for states other than FolderError.
  89. func (s *stateTracker) setState(newState folderState) {
  90. if newState == FolderError {
  91. panic("must use setError")
  92. }
  93. s.mut.Lock()
  94. defer s.mut.Unlock()
  95. if newState == s.current {
  96. return
  97. }
  98. defer func() {
  99. metricFolderState.WithLabelValues(s.folderID).Set(float64(s.current))
  100. }()
  101. eventData := map[string]interface{}{
  102. "folder": s.folderID,
  103. "to": newState.String(),
  104. "from": s.current.String(),
  105. }
  106. if !s.changed.IsZero() {
  107. eventData["duration"] = time.Since(s.changed).Seconds()
  108. }
  109. slog.Debug("Folder changed state", "folder", s.folderID, "state", newState, "from", s.current)
  110. s.current = newState
  111. s.changed = time.Now().Truncate(time.Second)
  112. s.evLogger.Log(events.StateChanged, eventData)
  113. }
  114. // getState returns the current state, the time when it last changed, and the
  115. // current error or nil.
  116. func (s *stateTracker) getState() (current folderState, changed time.Time, err error) {
  117. s.mut.Lock()
  118. current, changed, err = s.current, s.changed, s.err
  119. s.mut.Unlock()
  120. return
  121. }
  122. // setError sets the folder state to FolderError with the specified error or
  123. // to FolderIdle if the error is nil
  124. func (s *stateTracker) setError(err error) {
  125. s.mut.Lock()
  126. defer s.mut.Unlock()
  127. defer func() {
  128. metricFolderState.WithLabelValues(s.folderID).Set(float64(s.current))
  129. }()
  130. eventData := map[string]interface{}{
  131. "folder": s.folderID,
  132. "from": s.current.String(),
  133. }
  134. if err != nil && s.current != FolderError {
  135. slog.Warn("Folder is in error state", slog.String("folder", s.folderID), slogutil.Error(err))
  136. } else if err == nil && s.current == FolderError {
  137. slog.Info("Folder error state was cleared", slog.String("folder", s.folderID))
  138. }
  139. if err != nil {
  140. eventData["error"] = err.Error()
  141. s.current = FolderError
  142. } else {
  143. s.current = FolderIdle
  144. }
  145. eventData["to"] = s.current.String()
  146. if !s.changed.IsZero() {
  147. eventData["duration"] = time.Since(s.changed).Seconds()
  148. }
  149. s.err = err
  150. s.changed = time.Now().Truncate(time.Second)
  151. s.evLogger.Log(events.StateChanged, eventData)
  152. }