folderconfiguration.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  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 http://mozilla.org/MPL/2.0/.
  6. package config
  7. import (
  8. "os"
  9. "path/filepath"
  10. "runtime"
  11. "strings"
  12. "github.com/syncthing/syncthing/lib/osutil"
  13. "github.com/syncthing/syncthing/lib/protocol"
  14. )
  15. type FolderConfiguration struct {
  16. ID string `xml:"id,attr" json:"id"`
  17. Label string `xml:"label,attr" json:"label"`
  18. RawPath string `xml:"path,attr" json:"path"`
  19. Devices []FolderDeviceConfiguration `xml:"device" json:"devices"`
  20. ReadOnly bool `xml:"ro,attr" json:"readOnly"`
  21. RescanIntervalS int `xml:"rescanIntervalS,attr" json:"rescanIntervalS"`
  22. IgnorePerms bool `xml:"ignorePerms,attr" json:"ignorePerms"`
  23. AutoNormalize bool `xml:"autoNormalize,attr" json:"autoNormalize"`
  24. MinDiskFreePct float64 `xml:"minDiskFreePct" json:"minDiskFreePct"`
  25. Versioning VersioningConfiguration `xml:"versioning" json:"versioning"`
  26. Copiers int `xml:"copiers" json:"copiers"` // This defines how many files are handled concurrently.
  27. Pullers int `xml:"pullers" json:"pullers"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
  28. Hashers int `xml:"hashers" json:"hashers"` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
  29. Order PullOrder `xml:"order" json:"order"`
  30. IgnoreDelete bool `xml:"ignoreDelete" json:"ignoreDelete"`
  31. 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)
  32. PullerSleepS int `xml:"pullerSleepS" json:"pullerSleepS"`
  33. PullerPauseS int `xml:"pullerPauseS" json:"pullerPauseS"`
  34. MaxConflicts int `xml:"maxConflicts" json:"maxConflicts"`
  35. DisableSparseFiles bool `xml:"disableSparseFiles" json:"disableSparseFiles"`
  36. Invalid string `xml:"-" json:"invalid"` // Set at runtime when there is an error, not saved
  37. cachedPath string
  38. }
  39. type FolderDeviceConfiguration struct {
  40. DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
  41. }
  42. func NewFolderConfiguration(id, path string) FolderConfiguration {
  43. f := FolderConfiguration{
  44. ID: id,
  45. RawPath: path,
  46. }
  47. f.prepare()
  48. return f
  49. }
  50. func (f FolderConfiguration) Copy() FolderConfiguration {
  51. c := f
  52. c.Devices = make([]FolderDeviceConfiguration, len(f.Devices))
  53. copy(c.Devices, f.Devices)
  54. c.Versioning = f.Versioning.Copy()
  55. return c
  56. }
  57. func (f FolderConfiguration) Path() string {
  58. // This is intentionally not a pointer method, because things like
  59. // cfg.Folders["default"].Path() should be valid.
  60. if f.cachedPath == "" {
  61. l.Infoln("bug: uncached path call (should only happen in tests)")
  62. return f.cleanedPath()
  63. }
  64. return f.cachedPath
  65. }
  66. func (f *FolderConfiguration) CreateMarker() error {
  67. if !f.HasMarker() {
  68. marker := filepath.Join(f.Path(), ".stfolder")
  69. fd, err := os.Create(marker)
  70. if err != nil {
  71. return err
  72. }
  73. fd.Close()
  74. osutil.HideFile(marker)
  75. }
  76. return nil
  77. }
  78. func (f *FolderConfiguration) HasMarker() bool {
  79. _, err := os.Stat(filepath.Join(f.Path(), ".stfolder"))
  80. if err != nil {
  81. return false
  82. }
  83. return true
  84. }
  85. func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID {
  86. deviceIDs := make([]protocol.DeviceID, len(f.Devices))
  87. for i, n := range f.Devices {
  88. deviceIDs[i] = n.DeviceID
  89. }
  90. return deviceIDs
  91. }
  92. func (f *FolderConfiguration) prepare() {
  93. if len(f.RawPath) == 0 {
  94. f.Invalid = "no directory configured"
  95. return
  96. }
  97. // The reason it's done like this:
  98. // C: -> C:\ -> C:\ (issue that this is trying to fix)
  99. // C:\somedir -> C:\somedir\ -> C:\somedir
  100. // C:\somedir\ -> C:\somedir\\ -> C:\somedir
  101. // This way in the tests, we get away without OS specific separators
  102. // in the test configs.
  103. f.RawPath = filepath.Dir(f.RawPath + string(filepath.Separator))
  104. // If we're not on Windows, we want the path to end with a slash to
  105. // penetrate symlinks. On Windows, paths must not end with a slash.
  106. if runtime.GOOS != "windows" && f.RawPath[len(f.RawPath)-1] != filepath.Separator {
  107. f.RawPath = f.RawPath + string(filepath.Separator)
  108. }
  109. f.cachedPath = f.cleanedPath()
  110. if f.ID == "" {
  111. f.ID = "default"
  112. }
  113. if f.RescanIntervalS > MaxRescanIntervalS {
  114. f.RescanIntervalS = MaxRescanIntervalS
  115. } else if f.RescanIntervalS < 0 {
  116. f.RescanIntervalS = 0
  117. }
  118. }
  119. func (f *FolderConfiguration) cleanedPath() string {
  120. cleaned := f.RawPath
  121. // Attempt tilde expansion; leave unchanged in case of error
  122. if path, err := osutil.ExpandTilde(cleaned); err == nil {
  123. cleaned = path
  124. }
  125. // Attempt absolutification; leave unchanged in case of error
  126. if !filepath.IsAbs(cleaned) {
  127. // Abs() looks like a fairly expensive syscall on Windows, while
  128. // IsAbs() is a whole bunch of string mangling. I think IsAbs() may be
  129. // somewhat faster in the general case, hence the outer if...
  130. if path, err := filepath.Abs(cleaned); err == nil {
  131. cleaned = path
  132. }
  133. }
  134. // Attempt to enable long filename support on Windows. We may still not
  135. // have an absolute path here if the previous steps failed.
  136. if runtime.GOOS == "windows" && filepath.IsAbs(cleaned) && !strings.HasPrefix(f.RawPath, `\\`) {
  137. return `\\?\` + cleaned
  138. }
  139. return cleaned
  140. }
  141. type FolderDeviceConfigurationList []FolderDeviceConfiguration
  142. func (l FolderDeviceConfigurationList) Less(a, b int) bool {
  143. return l[a].DeviceID.Compare(l[b].DeviceID) == -1
  144. }
  145. func (l FolderDeviceConfigurationList) Swap(a, b int) {
  146. l[a], l[b] = l[b], l[a]
  147. }
  148. func (l FolderDeviceConfigurationList) Len() int {
  149. return len(l)
  150. }