folderconfiguration.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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 config
  7. import (
  8. "fmt"
  9. "os"
  10. "path/filepath"
  11. "runtime"
  12. "strings"
  13. "github.com/syncthing/syncthing/lib/osutil"
  14. "github.com/syncthing/syncthing/lib/protocol"
  15. )
  16. type FolderConfiguration struct {
  17. ID string `xml:"id,attr" json:"id"`
  18. Label string `xml:"label,attr" json:"label"`
  19. RawPath string `xml:"path,attr" json:"path"`
  20. Type FolderType `xml:"type,attr" json:"type"`
  21. Devices []FolderDeviceConfiguration `xml:"device" json:"devices"`
  22. RescanIntervalS int `xml:"rescanIntervalS,attr" json:"rescanIntervalS"`
  23. IgnorePerms bool `xml:"ignorePerms,attr" json:"ignorePerms"`
  24. AutoNormalize bool `xml:"autoNormalize,attr" json:"autoNormalize"`
  25. MinDiskFree Size `xml:"minDiskFree" json:"minDiskFree"`
  26. Versioning VersioningConfiguration `xml:"versioning" json:"versioning"`
  27. Copiers int `xml:"copiers" json:"copiers"` // This defines how many files are handled concurrently.
  28. Pullers int `xml:"pullers" json:"pullers"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
  29. Hashers int `xml:"hashers" json:"hashers"` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
  30. Order PullOrder `xml:"order" json:"order"`
  31. IgnoreDelete bool `xml:"ignoreDelete" json:"ignoreDelete"`
  32. ScanProgressIntervalS int `xml:"scanProgressIntervalS" json:"scanProgressIntervalS"` // Set to a negative value to disable. Value of 0 will get replaced with value of 2 (default value)
  33. PullerSleepS int `xml:"pullerSleepS" json:"pullerSleepS"`
  34. PullerPauseS int `xml:"pullerPauseS" json:"pullerPauseS"`
  35. MaxConflicts int `xml:"maxConflicts" json:"maxConflicts"`
  36. DisableSparseFiles bool `xml:"disableSparseFiles" json:"disableSparseFiles"`
  37. DisableTempIndexes bool `xml:"disableTempIndexes" json:"disableTempIndexes"`
  38. Fsync bool `xml:"fsync" json:"fsync"`
  39. Paused bool `xml:"paused" json:"paused"`
  40. WeakHashThresholdPct int `xml:"weakHashThresholdPct" json:"weakHashThresholdPct"` // Use weak hash if more than X percent of the file has changed. Set to -1 to always use weak hash.
  41. cachedPath string
  42. DeprecatedReadOnly bool `xml:"ro,attr,omitempty" json:"-"`
  43. DeprecatedMinDiskFreePct float64 `xml:"minDiskFreePct,omitempty" json:"-"`
  44. }
  45. type FolderDeviceConfiguration struct {
  46. DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
  47. IntroducedBy protocol.DeviceID `xml:"introducedBy,attr" json:"introducedBy"`
  48. }
  49. func NewFolderConfiguration(id, path string) FolderConfiguration {
  50. f := FolderConfiguration{
  51. ID: id,
  52. RawPath: path,
  53. }
  54. f.prepare()
  55. return f
  56. }
  57. func (f FolderConfiguration) Copy() FolderConfiguration {
  58. c := f
  59. c.Devices = make([]FolderDeviceConfiguration, len(f.Devices))
  60. copy(c.Devices, f.Devices)
  61. c.Versioning = f.Versioning.Copy()
  62. return c
  63. }
  64. func (f FolderConfiguration) Path() string {
  65. // This is intentionally not a pointer method, because things like
  66. // cfg.Folders["default"].Path() should be valid.
  67. if f.cachedPath == "" && f.RawPath != "" {
  68. l.Infoln("bug: uncached path call (should only happen in tests)")
  69. return f.cleanedPath()
  70. }
  71. return f.cachedPath
  72. }
  73. func (f *FolderConfiguration) CreateMarker() error {
  74. if !f.HasMarker() {
  75. marker := filepath.Join(f.Path(), ".stfolder")
  76. fd, err := os.Create(marker)
  77. if err != nil {
  78. return err
  79. }
  80. fd.Close()
  81. if err := osutil.SyncDir(filepath.Dir(marker)); err != nil {
  82. l.Infof("fsync %q failed: %v", filepath.Dir(marker), err)
  83. }
  84. osutil.HideFile(marker)
  85. }
  86. return nil
  87. }
  88. func (f *FolderConfiguration) HasMarker() bool {
  89. _, err := os.Stat(filepath.Join(f.Path(), ".stfolder"))
  90. return err == nil
  91. }
  92. func (f *FolderConfiguration) CreateRoot() (err error) {
  93. // Directory permission bits. Will be filtered down to something
  94. // sane by umask on Unixes.
  95. permBits := os.FileMode(0777)
  96. if runtime.GOOS == "windows" {
  97. // Windows has no umask so we must chose a safer set of bits to
  98. // begin with.
  99. permBits = 0700
  100. }
  101. if _, err = os.Stat(f.Path()); os.IsNotExist(err) {
  102. if err = osutil.MkdirAll(f.Path(), permBits); err != nil {
  103. l.Warnf("Creating directory for %v: %v",
  104. f.Description(), err)
  105. }
  106. }
  107. return err
  108. }
  109. func (f FolderConfiguration) Description() string {
  110. if f.Label == "" {
  111. return f.ID
  112. }
  113. return fmt.Sprintf("%q (%s)", f.Label, f.ID)
  114. }
  115. func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID {
  116. deviceIDs := make([]protocol.DeviceID, len(f.Devices))
  117. for i, n := range f.Devices {
  118. deviceIDs[i] = n.DeviceID
  119. }
  120. return deviceIDs
  121. }
  122. func (f *FolderConfiguration) prepare() {
  123. if f.RawPath != "" {
  124. // The reason it's done like this:
  125. // C: -> C:\ -> C:\ (issue that this is trying to fix)
  126. // C:\somedir -> C:\somedir\ -> C:\somedir
  127. // C:\somedir\ -> C:\somedir\\ -> C:\somedir
  128. // This way in the tests, we get away without OS specific separators
  129. // in the test configs.
  130. f.RawPath = filepath.Dir(f.RawPath + string(filepath.Separator))
  131. // If we're not on Windows, we want the path to end with a slash to
  132. // penetrate symlinks. On Windows, paths must not end with a slash.
  133. if runtime.GOOS != "windows" && f.RawPath[len(f.RawPath)-1] != filepath.Separator {
  134. f.RawPath = f.RawPath + string(filepath.Separator)
  135. }
  136. }
  137. f.cachedPath = f.cleanedPath()
  138. if f.RescanIntervalS > MaxRescanIntervalS {
  139. f.RescanIntervalS = MaxRescanIntervalS
  140. } else if f.RescanIntervalS < 0 {
  141. f.RescanIntervalS = 0
  142. }
  143. if f.Versioning.Params == nil {
  144. f.Versioning.Params = make(map[string]string)
  145. }
  146. if f.WeakHashThresholdPct == 0 {
  147. f.WeakHashThresholdPct = 25
  148. }
  149. }
  150. func (f *FolderConfiguration) cleanedPath() string {
  151. if f.RawPath == "" {
  152. return ""
  153. }
  154. cleaned := f.RawPath
  155. // Attempt tilde expansion; leave unchanged in case of error
  156. if path, err := osutil.ExpandTilde(cleaned); err == nil {
  157. cleaned = path
  158. }
  159. // Attempt absolutification; leave unchanged in case of error
  160. if !filepath.IsAbs(cleaned) {
  161. // Abs() looks like a fairly expensive syscall on Windows, while
  162. // IsAbs() is a whole bunch of string mangling. I think IsAbs() may be
  163. // somewhat faster in the general case, hence the outer if...
  164. if path, err := filepath.Abs(cleaned); err == nil {
  165. cleaned = path
  166. }
  167. }
  168. // Attempt to enable long filename support on Windows. We may still not
  169. // have an absolute path here if the previous steps failed.
  170. if runtime.GOOS == "windows" && filepath.IsAbs(cleaned) && !strings.HasPrefix(f.RawPath, `\\`) {
  171. return `\\?\` + cleaned
  172. }
  173. // If we're not on Windows, we want the path to end with a slash to
  174. // penetrate symlinks. On Windows, paths must not end with a slash.
  175. if runtime.GOOS != "windows" && cleaned[len(cleaned)-1] != filepath.Separator {
  176. cleaned = cleaned + string(filepath.Separator)
  177. }
  178. return cleaned
  179. }
  180. type FolderDeviceConfigurationList []FolderDeviceConfiguration
  181. func (l FolderDeviceConfigurationList) Less(a, b int) bool {
  182. return l[a].DeviceID.Compare(l[b].DeviceID) == -1
  183. }
  184. func (l FolderDeviceConfigurationList) Swap(a, b int) {
  185. l[a], l[b] = l[b], l[a]
  186. }
  187. func (l FolderDeviceConfigurationList) Len() int {
  188. return len(l)
  189. }