structs.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  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 db
  7. import (
  8. "bytes"
  9. "fmt"
  10. "strings"
  11. "google.golang.org/protobuf/proto"
  12. "github.com/syncthing/syncthing/internal/gen/dbproto"
  13. "github.com/syncthing/syncthing/lib/protocol"
  14. )
  15. type CountsSet struct {
  16. Counts []Counts
  17. Created int64 // unix nanos
  18. }
  19. type Counts struct {
  20. Files int
  21. Directories int
  22. Symlinks int
  23. Deleted int
  24. Bytes int64
  25. Sequence int64 // zero for the global state
  26. DeviceID protocol.DeviceID // device ID for remote devices, or special values for local/global
  27. LocalFlags uint32 // the local flag for this count bucket
  28. }
  29. func (c Counts) toWire() *dbproto.Counts {
  30. return &dbproto.Counts{
  31. Files: int32(c.Files),
  32. Directories: int32(c.Directories),
  33. Symlinks: int32(c.Symlinks),
  34. Deleted: int32(c.Deleted),
  35. Bytes: c.Bytes,
  36. Sequence: c.Sequence,
  37. DeviceId: c.DeviceID[:],
  38. LocalFlags: c.LocalFlags,
  39. }
  40. }
  41. func countsFromWire(w *dbproto.Counts) Counts {
  42. return Counts{
  43. Files: int(w.Files),
  44. Directories: int(w.Directories),
  45. Symlinks: int(w.Symlinks),
  46. Deleted: int(w.Deleted),
  47. Bytes: w.Bytes,
  48. Sequence: w.Sequence,
  49. DeviceID: protocol.DeviceID(w.DeviceId),
  50. LocalFlags: w.LocalFlags,
  51. }
  52. }
  53. func (c Counts) Add(other Counts) Counts {
  54. return Counts{
  55. Files: c.Files + other.Files,
  56. Directories: c.Directories + other.Directories,
  57. Symlinks: c.Symlinks + other.Symlinks,
  58. Deleted: c.Deleted + other.Deleted,
  59. Bytes: c.Bytes + other.Bytes,
  60. Sequence: c.Sequence + other.Sequence,
  61. DeviceID: protocol.EmptyDeviceID,
  62. LocalFlags: c.LocalFlags | other.LocalFlags,
  63. }
  64. }
  65. func (c Counts) TotalItems() int {
  66. return c.Files + c.Directories + c.Symlinks + c.Deleted
  67. }
  68. func (c Counts) String() string {
  69. var flags strings.Builder
  70. if c.LocalFlags&needFlag != 0 {
  71. flags.WriteString("Need")
  72. }
  73. if c.LocalFlags&protocol.FlagLocalIgnored != 0 {
  74. flags.WriteString("Ignored")
  75. }
  76. if c.LocalFlags&protocol.FlagLocalMustRescan != 0 {
  77. flags.WriteString("Rescan")
  78. }
  79. if c.LocalFlags&protocol.FlagLocalReceiveOnly != 0 {
  80. flags.WriteString("Recvonly")
  81. }
  82. if c.LocalFlags&protocol.FlagLocalUnsupported != 0 {
  83. flags.WriteString("Unsupported")
  84. }
  85. if c.LocalFlags != 0 {
  86. flags.WriteString(fmt.Sprintf("(%x)", c.LocalFlags))
  87. }
  88. if flags.Len() == 0 {
  89. flags.WriteString("---")
  90. }
  91. return fmt.Sprintf("{Device:%v, Files:%d, Dirs:%d, Symlinks:%d, Del:%d, Bytes:%d, Seq:%d, Flags:%s}", c.DeviceID, c.Files, c.Directories, c.Symlinks, c.Deleted, c.Bytes, c.Sequence, flags.String())
  92. }
  93. // Equal compares the numbers only, not sequence/dev/flags.
  94. func (c Counts) Equal(o Counts) bool {
  95. return c.Files == o.Files && c.Directories == o.Directories && c.Symlinks == o.Symlinks && c.Deleted == o.Deleted && c.Bytes == o.Bytes
  96. }
  97. // update brings the VersionList up to date with file. It returns the updated
  98. // VersionList, a device that has the global/newest version, a device that previously
  99. // had the global/newest version, a boolean indicating if the global version has
  100. // changed and if any error occurred (only possible in db interaction).
  101. func vlUpdate(vl *dbproto.VersionList, folder, device []byte, file protocol.FileInfo, t readOnlyTransaction) (*dbproto.FileVersion, *dbproto.FileVersion, *dbproto.FileVersion, bool, bool, bool, error) {
  102. if len(vl.Versions) == 0 {
  103. nv := newFileVersion(device, file.FileVersion(), file.IsInvalid(), file.IsDeleted())
  104. vl.Versions = append(vl.Versions, nv)
  105. return nv, nil, nil, false, false, true, nil
  106. }
  107. // Get the current global (before updating)
  108. oldFV, haveOldGlobal := vlGetGlobal(vl)
  109. oldFV = fvCopy(oldFV)
  110. // Remove ourselves first
  111. removedFV, haveRemoved, _ := vlPop(vl, device)
  112. // Find position and insert the file
  113. err := vlInsert(vl, folder, device, file, t)
  114. if err != nil {
  115. return nil, nil, nil, false, false, false, err
  116. }
  117. newFV, _ := vlGetGlobal(vl) // We just inserted something above, can't be empty
  118. if !haveOldGlobal {
  119. return newFV, nil, removedFV, false, haveRemoved, true, nil
  120. }
  121. globalChanged := true
  122. if fvIsInvalid(oldFV) == fvIsInvalid(newFV) && protocol.VectorFromWire(oldFV.Version).Equal(protocol.VectorFromWire(newFV.Version)) {
  123. globalChanged = false
  124. }
  125. return newFV, oldFV, removedFV, true, haveRemoved, globalChanged, nil
  126. }
  127. func vlInsert(vl *dbproto.VersionList, folder, device []byte, file protocol.FileInfo, t readOnlyTransaction) error {
  128. var added bool
  129. var err error
  130. i := 0
  131. for ; i < len(vl.Versions); i++ {
  132. // Insert our new version
  133. added, err = vlCheckInsertAt(vl, i, folder, device, file, t)
  134. if err != nil {
  135. return err
  136. }
  137. if added {
  138. break
  139. }
  140. }
  141. if i == len(vl.Versions) {
  142. // Append to the end
  143. vl.Versions = append(vl.Versions, newFileVersion(device, file.FileVersion(), file.IsInvalid(), file.IsDeleted()))
  144. }
  145. return nil
  146. }
  147. func vlInsertAt(vl *dbproto.VersionList, i int, v *dbproto.FileVersion) {
  148. vl.Versions = append(vl.Versions, &dbproto.FileVersion{})
  149. copy(vl.Versions[i+1:], vl.Versions[i:])
  150. vl.Versions[i] = v
  151. }
  152. // pop removes the given device from the VersionList and returns the FileVersion
  153. // before removing the device, whether it was found/removed at all and whether
  154. // the global changed in the process.
  155. func vlPop(vl *dbproto.VersionList, device []byte) (*dbproto.FileVersion, bool, bool) {
  156. invDevice, i, j, ok := vlFindDevice(vl, device)
  157. if !ok {
  158. return nil, false, false
  159. }
  160. globalPos := vlFindGlobal(vl)
  161. fv := vl.Versions[i]
  162. if fvDeviceCount(fv) == 1 {
  163. vlPopVersionAt(vl, i)
  164. return fv, true, globalPos == i
  165. }
  166. oldFV := fvCopy(fv)
  167. if invDevice {
  168. vl.Versions[i].InvalidDevices = popDeviceAt(vl.Versions[i].InvalidDevices, j)
  169. return oldFV, true, false
  170. }
  171. vl.Versions[i].Devices = popDeviceAt(vl.Versions[i].Devices, j)
  172. // If the last valid device of the previous global was removed above,
  173. // the global changed.
  174. return oldFV, true, len(vl.Versions[i].Devices) == 0 && globalPos == i
  175. }
  176. // Get returns a FileVersion that contains the given device and whether it has
  177. // been found at all.
  178. func vlGet(vl *dbproto.VersionList, device []byte) (*dbproto.FileVersion, bool) {
  179. _, i, _, ok := vlFindDevice(vl, device)
  180. if !ok {
  181. return &dbproto.FileVersion{}, false
  182. }
  183. return vl.Versions[i], true
  184. }
  185. // GetGlobal returns the current global FileVersion. The returned FileVersion
  186. // may be invalid, if all FileVersions are invalid. Returns false only if
  187. // VersionList is empty.
  188. func vlGetGlobal(vl *dbproto.VersionList) (*dbproto.FileVersion, bool) {
  189. i := vlFindGlobal(vl)
  190. if i == -1 {
  191. return nil, false
  192. }
  193. return vl.Versions[i], true
  194. }
  195. // findGlobal returns the first version that isn't invalid, or if all versions are
  196. // invalid just the first version (i.e. 0) or -1, if there's no versions at all.
  197. func vlFindGlobal(vl *dbproto.VersionList) int {
  198. for i := range vl.Versions {
  199. if !fvIsInvalid(vl.Versions[i]) {
  200. return i
  201. }
  202. }
  203. if len(vl.Versions) == 0 {
  204. return -1
  205. }
  206. return 0
  207. }
  208. // findDevice returns whether the device is in InvalidVersions or Versions and
  209. // in InvalidDevices or Devices (true for invalid), the positions in the version
  210. // and device slices and whether it has been found at all.
  211. func vlFindDevice(vl *dbproto.VersionList, device []byte) (bool, int, int, bool) {
  212. for i, v := range vl.Versions {
  213. if j := deviceIndex(v.Devices, device); j != -1 {
  214. return false, i, j, true
  215. }
  216. if j := deviceIndex(v.InvalidDevices, device); j != -1 {
  217. return true, i, j, true
  218. }
  219. }
  220. return false, -1, -1, false
  221. }
  222. func vlPopVersionAt(vl *dbproto.VersionList, i int) {
  223. vl.Versions = append(vl.Versions[:i], vl.Versions[i+1:]...)
  224. }
  225. // checkInsertAt determines if the given device and associated file should be
  226. // inserted into the FileVersion at position i or into a new FileVersion at
  227. // position i.
  228. func vlCheckInsertAt(vl *dbproto.VersionList, i int, folder, device []byte, file protocol.FileInfo, t readOnlyTransaction) (bool, error) {
  229. fv := vl.Versions[i]
  230. ordering := protocol.VectorFromWire(fv.Version).Compare(file.FileVersion())
  231. if ordering == protocol.Equal {
  232. if !file.IsInvalid() {
  233. fv.Devices = append(fv.Devices, device)
  234. } else {
  235. fv.InvalidDevices = append(fv.InvalidDevices, device)
  236. }
  237. return true, nil
  238. }
  239. existingDevice, _ := fvFirstDevice(fv)
  240. insert, err := shouldInsertBefore(ordering, folder, existingDevice, fvIsInvalid(fv), file, t)
  241. if err != nil {
  242. return false, err
  243. }
  244. if insert {
  245. vlInsertAt(vl, i, newFileVersion(device, file.FileVersion(), file.IsInvalid(), file.IsDeleted()))
  246. return true, nil
  247. }
  248. return false, nil
  249. }
  250. // shouldInsertBefore determines whether the file comes before an existing
  251. // entry, given the version ordering (existing compared to new one), existing
  252. // device and if the existing version is invalid.
  253. func shouldInsertBefore(ordering protocol.Ordering, folder, existingDevice []byte, existingInvalid bool, file protocol.FileInfo, t readOnlyTransaction) (bool, error) {
  254. switch ordering {
  255. case protocol.Lesser:
  256. // The version at this point in the list is lesser
  257. // ("older") than us. We insert ourselves in front of it.
  258. return true, nil
  259. case protocol.ConcurrentLesser, protocol.ConcurrentGreater:
  260. // The version in conflict with us.
  261. // Check if we can shortcut due to one being invalid.
  262. if existingInvalid != file.IsInvalid() {
  263. return existingInvalid, nil
  264. }
  265. // We must pull the actual file metadata to determine who wins.
  266. // If we win, we insert ourselves in front of the loser here.
  267. // (The "Lesser" and "Greater" in the condition above is just
  268. // based on the device IDs in the version vector, which is not
  269. // the only thing we use to determine the winner.)
  270. of, ok, err := t.getFile(folder, existingDevice, []byte(file.FileName()))
  271. if err != nil {
  272. return false, err
  273. }
  274. // A surprise missing file entry here is counted as a win for us.
  275. if !ok {
  276. return true, nil
  277. }
  278. if file.WinsConflict(of) {
  279. return true, nil
  280. }
  281. }
  282. return false, nil
  283. }
  284. func deviceIndex(devices [][]byte, device []byte) int {
  285. for i, dev := range devices {
  286. if bytes.Equal(device, dev) {
  287. return i
  288. }
  289. }
  290. return -1
  291. }
  292. func popDeviceAt(devices [][]byte, i int) [][]byte {
  293. return append(devices[:i], devices[i+1:]...)
  294. }
  295. func newFileVersion(device []byte, version protocol.Vector, invalid, deleted bool) *dbproto.FileVersion {
  296. fv := &dbproto.FileVersion{
  297. Version: version.ToWire(),
  298. Deleted: deleted,
  299. }
  300. if invalid {
  301. fv.InvalidDevices = [][]byte{device}
  302. } else {
  303. fv.Devices = [][]byte{device}
  304. }
  305. return fv
  306. }
  307. func fvFirstDevice(fv *dbproto.FileVersion) ([]byte, bool) {
  308. if len(fv.Devices) != 0 {
  309. return fv.Devices[0], true
  310. }
  311. if len(fv.InvalidDevices) != 0 {
  312. return fv.InvalidDevices[0], true
  313. }
  314. return nil, false
  315. }
  316. func fvIsInvalid(fv *dbproto.FileVersion) bool {
  317. return fv == nil || len(fv.Devices) == 0
  318. }
  319. func fvDeviceCount(fv *dbproto.FileVersion) int {
  320. return len(fv.Devices) + len(fv.InvalidDevices)
  321. }
  322. func fvCopy(fv *dbproto.FileVersion) *dbproto.FileVersion {
  323. return proto.Clone(fv).(*dbproto.FileVersion)
  324. }