contract.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. // Copyright (C) 2020 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 contract
  7. import (
  8. "database/sql/driver"
  9. "encoding/json"
  10. "errors"
  11. "reflect"
  12. "strconv"
  13. "time"
  14. "github.com/syncthing/syncthing/lib/util"
  15. )
  16. type Report struct {
  17. // Generated
  18. Received time.Time `json:"-"` // Only from DB
  19. Date string `json:"date,omitempty"`
  20. Address string `json:"address,omitempty"`
  21. // v1 fields
  22. UniqueID string `json:"uniqueID,omitempty" since:"1"`
  23. Version string `json:"version,omitempty" since:"1"`
  24. LongVersion string `json:"longVersion,omitempty" since:"1"`
  25. Platform string `json:"platform,omitempty" since:"1"`
  26. NumFolders int `json:"numFolders,omitempty" since:"1"`
  27. NumDevices int `json:"numDevices,omitempty" since:"1"`
  28. TotFiles int `json:"totFiles,omitempty" since:"1"`
  29. FolderMaxFiles int `json:"folderMaxFiles,omitempty" since:"1"`
  30. TotMiB int `json:"totMiB,omitempty" since:"1"`
  31. FolderMaxMiB int `json:"folderMaxMiB,omitempty" since:"1"`
  32. MemoryUsageMiB int `json:"memoryUsageMiB,omitempty" since:"1"`
  33. SHA256Perf float64 `json:"sha256Perf,omitempty" since:"1"`
  34. HashPerf float64 `json:"hashPerf,omitempty" since:"1"` // Was previously not stored server-side
  35. MemorySize int `json:"memorySize,omitempty" since:"1"`
  36. // v2 fields
  37. URVersion int `json:"urVersion,omitempty" since:"2"`
  38. NumCPU int `json:"numCPU,omitempty" since:"2"`
  39. FolderUses struct {
  40. SendOnly int `json:"sendonly,omitempty" since:"2"`
  41. SendReceive int `json:"sendreceive,omitempty" since:"2"` // Was previously not stored server-side
  42. ReceiveOnly int `json:"receiveonly,omitempty" since:"2"`
  43. IgnorePerms int `json:"ignorePerms,omitempty" since:"2"`
  44. IgnoreDelete int `json:"ignoreDelete,omitempty" since:"2"`
  45. AutoNormalize int `json:"autoNormalize,omitempty" since:"2"`
  46. SimpleVersioning int `json:"simpleVersioning,omitempty" since:"2"`
  47. ExternalVersioning int `json:"externalVersioning,omitempty" since:"2"`
  48. StaggeredVersioning int `json:"staggeredVersioning,omitempty" since:"2"`
  49. TrashcanVersioning int `json:"trashcanVersioning,omitempty" since:"2"`
  50. } `json:"folderUses,omitempty" since:"2"`
  51. DeviceUses struct {
  52. Introducer int `json:"introducer,omitempty" since:"2"`
  53. CustomCertName int `json:"customCertName,omitempty" since:"2"`
  54. CompressAlways int `json:"compressAlways,omitempty" since:"2"`
  55. CompressMetadata int `json:"compressMetadata,omitempty" since:"2"`
  56. CompressNever int `json:"compressNever,omitempty" since:"2"`
  57. DynamicAddr int `json:"dynamicAddr,omitempty" since:"2"`
  58. StaticAddr int `json:"staticAddr,omitempty" since:"2"`
  59. } `json:"deviceUses,omitempty" since:"2"`
  60. Announce struct {
  61. GlobalEnabled bool `json:"globalEnabled,omitempty" since:"2"`
  62. LocalEnabled bool `json:"localEnabled,omitempty" since:"2"`
  63. DefaultServersDNS int `json:"defaultServersDNS,omitempty" since:"2"`
  64. DefaultServersIP int `json:"defaultServersIP,omitempty" since:"2"` // Deprecated and not provided client-side anymore
  65. OtherServers int `json:"otherServers,omitempty" since:"2"`
  66. } `json:"announce,omitempty" since:"2"`
  67. Relays struct {
  68. Enabled bool `json:"enabled,omitempty" since:"2"`
  69. DefaultServers int `json:"defaultServers,omitempty" since:"2"`
  70. OtherServers int `json:"otherServers,omitempty" since:"2"`
  71. } `json:"relays,omitempty" since:"2"`
  72. UsesRateLimit bool `json:"usesRateLimit,omitempty" since:"2"`
  73. UpgradeAllowedManual bool `json:"upgradeAllowedManual,omitempty" since:"2"`
  74. UpgradeAllowedAuto bool `json:"upgradeAllowedAuto,omitempty" since:"2"`
  75. // V2.5 fields (fields that were in v2 but never added to the database
  76. UpgradeAllowedPre bool `json:"upgradeAllowedPre,omitempty" since:"2"`
  77. RescanIntvs []int `json:"rescanIntvs,omitempty" since:"2"`
  78. // v3 fields
  79. Uptime int `json:"uptime,omitempty" since:"3"`
  80. NATType string `json:"natType,omitempty" since:"3"`
  81. AlwaysLocalNets bool `json:"alwaysLocalNets,omitempty" since:"3"`
  82. CacheIgnoredFiles bool `json:"cacheIgnoredFiles,omitempty" since:"3"`
  83. OverwriteRemoteDeviceNames bool `json:"overwriteRemoteDeviceNames,omitempty" since:"3"`
  84. ProgressEmitterEnabled bool `json:"progressEmitterEnabled,omitempty" since:"3"`
  85. CustomDefaultFolderPath bool `json:"customDefaultFolderPath,omitempty" since:"3"`
  86. WeakHashSelection string `json:"weakHashSelection,omitempty" since:"3"` // Deprecated and not provided client-side anymore
  87. CustomTrafficClass bool `json:"customTrafficClass,omitempty" since:"3"`
  88. CustomTempIndexMinBlocks bool `json:"customTempIndexMinBlocks,omitempty" since:"3"`
  89. TemporariesDisabled bool `json:"temporariesDisabled,omitempty" since:"3"`
  90. TemporariesCustom bool `json:"temporariesCustom,omitempty" since:"3"`
  91. LimitBandwidthInLan bool `json:"limitBandwidthInLan,omitempty" since:"3"`
  92. CustomReleaseURL bool `json:"customReleaseURL,omitempty" since:"3"`
  93. RestartOnWakeup bool `json:"restartOnWakeup,omitempty" since:"3"`
  94. CustomStunServers bool `json:"customStunServers,omitempty" since:"3"`
  95. FolderUsesV3 struct {
  96. ScanProgressDisabled int `json:"scanProgressDisabled,omitempty" since:"3"`
  97. ConflictsDisabled int `json:"conflictsDisabled,omitempty" since:"3"`
  98. ConflictsUnlimited int `json:"conflictsUnlimited,omitempty" since:"3"`
  99. ConflictsOther int `json:"conflictsOther,omitempty" since:"3"`
  100. DisableSparseFiles int `json:"disableSparseFiles,omitempty" since:"3"`
  101. DisableTempIndexes int `json:"disableTempIndexes,omitempty" since:"3"`
  102. AlwaysWeakHash int `json:"alwaysWeakHash,omitempty" since:"3"`
  103. CustomWeakHashThreshold int `json:"customWeakHashThreshold,omitempty" since:"3"`
  104. FsWatcherEnabled int `json:"fsWatcherEnabled,omitempty" since:"3"`
  105. PullOrder map[string]int `json:"pullOrder,omitempty" since:"3"`
  106. FilesystemType map[string]int `json:"filesystemType,omitempty" since:"3"`
  107. FsWatcherDelays []int `json:"fsWatcherDelays,omitempty" since:"3"`
  108. CustomMarkerName int `json:"customMarkerName,omitempty" since:"3"`
  109. CopyOwnershipFromParent int `json:"copyOwnershipFromParent,omitempty" since:"3"`
  110. ModTimeWindowS []int `json:"modTimeWindowS,omitempty" since:"3"`
  111. MaxConcurrentWrites []int `json:"maxConcurrentWrites,omitempty" since:"3"`
  112. DisableFsync int `json:"disableFsync,omitempty" since:"3"`
  113. BlockPullOrder map[string]int `json:"blockPullOrder,omitempty" since:"3"`
  114. CopyRangeMethod map[string]int `json:"copyRangeMethod,omitempty" since:"3"`
  115. } `json:"folderUsesV3,omitempty" since:"3"`
  116. GUIStats struct {
  117. Enabled int `json:"enabled,omitempty" since:"3"`
  118. UseTLS int `json:"useTLS,omitempty" since:"3"`
  119. UseAuth int `json:"useAuth,omitempty" since:"3"`
  120. InsecureAdminAccess int `json:"insecureAdminAccess,omitempty" since:"3"`
  121. Debugging int `json:"debugging,omitempty" since:"3"`
  122. InsecureSkipHostCheck int `json:"insecureSkipHostCheck,omitempty" since:"3"`
  123. InsecureAllowFrameLoading int `json:"insecureAllowFrameLoading,omitempty" since:"3"`
  124. ListenLocal int `json:"listenLocal,omitempty" since:"3"`
  125. ListenUnspecified int `json:"listenUnspecified,omitempty" since:"3"`
  126. Theme map[string]int `json:"theme,omitempty" since:"3"`
  127. } `json:"guiStats,omitempty" since:"3"`
  128. BlockStats struct {
  129. Total int `json:"total,omitempty" since:"3"`
  130. Renamed int `json:"renamed,omitempty" since:"3"`
  131. Reused int `json:"reused,omitempty" since:"3"`
  132. Pulled int `json:"pulled,omitempty" since:"3"`
  133. CopyOrigin int `json:"copyOrigin,omitempty" since:"3"`
  134. CopyOriginShifted int `json:"copyOriginShifted,omitempty" since:"3"`
  135. CopyElsewhere int `json:"copyElsewhere,omitempty" since:"3"`
  136. } `json:"blockStats,omitempty" since:"3"`
  137. TransportStats map[string]int `json:"transportStats,omitempty" since:"3"`
  138. IgnoreStats struct {
  139. Lines int `json:"lines,omitempty" since:"3"`
  140. Inverts int `json:"inverts,omitempty" since:"3"`
  141. Folded int `json:"folded,omitempty" since:"3"`
  142. Deletable int `json:"deletable,omitempty" since:"3"`
  143. Rooted int `json:"rooted,omitempty" since:"3"`
  144. Includes int `json:"includes,omitempty" since:"3"`
  145. EscapedIncludes int `json:"escapedIncludes,omitempty" since:"3"`
  146. DoubleStars int `json:"doubleStars,omitempty" since:"3"`
  147. Stars int `json:"stars,omitempty" since:"3"`
  148. } `json:"ignoreStats,omitempty" since:"3"`
  149. // V3 fields added late in the RC
  150. WeakHashEnabled bool `json:"weakHashEnabled,omitempty" since:"3"` // Deprecated and not provided client-side anymore
  151. }
  152. func New() *Report {
  153. r := &Report{}
  154. util.FillNil(r)
  155. return r
  156. }
  157. func (r *Report) Validate() error {
  158. if r.UniqueID == "" || r.Version == "" || r.Platform == "" {
  159. return errors.New("missing required field")
  160. }
  161. if len(r.Date) != 8 {
  162. return errors.New("date not initialized")
  163. }
  164. // Some fields may not be null.
  165. if r.RescanIntvs == nil {
  166. r.RescanIntvs = []int{}
  167. }
  168. if r.FolderUsesV3.FsWatcherDelays == nil {
  169. r.FolderUsesV3.FsWatcherDelays = []int{}
  170. }
  171. return nil
  172. }
  173. func (r *Report) ClearForVersion(version int) error {
  174. return clear(r, version)
  175. }
  176. func (r *Report) FieldPointers() []interface{} {
  177. // All the fields of the Report, in the same order as the database fields.
  178. return []interface{}{
  179. &r.Received, &r.UniqueID, &r.Version, &r.LongVersion, &r.Platform,
  180. &r.NumFolders, &r.NumDevices, &r.TotFiles, &r.FolderMaxFiles,
  181. &r.TotMiB, &r.FolderMaxMiB, &r.MemoryUsageMiB, &r.SHA256Perf,
  182. &r.MemorySize, &r.Date,
  183. // V2
  184. &r.URVersion, &r.NumCPU, &r.FolderUses.SendOnly, &r.FolderUses.IgnorePerms,
  185. &r.FolderUses.IgnoreDelete, &r.FolderUses.AutoNormalize, &r.DeviceUses.Introducer,
  186. &r.DeviceUses.CustomCertName, &r.DeviceUses.CompressAlways,
  187. &r.DeviceUses.CompressMetadata, &r.DeviceUses.CompressNever,
  188. &r.DeviceUses.DynamicAddr, &r.DeviceUses.StaticAddr,
  189. &r.Announce.GlobalEnabled, &r.Announce.LocalEnabled,
  190. &r.Announce.DefaultServersDNS, &r.Announce.DefaultServersIP,
  191. &r.Announce.OtherServers, &r.Relays.Enabled, &r.Relays.DefaultServers,
  192. &r.Relays.OtherServers, &r.UsesRateLimit, &r.UpgradeAllowedManual,
  193. &r.UpgradeAllowedAuto, &r.FolderUses.SimpleVersioning,
  194. &r.FolderUses.ExternalVersioning, &r.FolderUses.StaggeredVersioning,
  195. &r.FolderUses.TrashcanVersioning,
  196. // V2.5
  197. &r.UpgradeAllowedPre,
  198. // V3
  199. &r.Uptime, &r.NATType, &r.AlwaysLocalNets, &r.CacheIgnoredFiles,
  200. &r.OverwriteRemoteDeviceNames, &r.ProgressEmitterEnabled, &r.CustomDefaultFolderPath,
  201. &r.WeakHashSelection, &r.CustomTrafficClass, &r.CustomTempIndexMinBlocks,
  202. &r.TemporariesDisabled, &r.TemporariesCustom, &r.LimitBandwidthInLan,
  203. &r.CustomReleaseURL, &r.RestartOnWakeup, &r.CustomStunServers,
  204. &r.FolderUsesV3.ScanProgressDisabled, &r.FolderUsesV3.ConflictsDisabled,
  205. &r.FolderUsesV3.ConflictsUnlimited, &r.FolderUsesV3.ConflictsOther,
  206. &r.FolderUsesV3.DisableSparseFiles, &r.FolderUsesV3.DisableTempIndexes,
  207. &r.FolderUsesV3.AlwaysWeakHash, &r.FolderUsesV3.CustomWeakHashThreshold,
  208. &r.FolderUsesV3.FsWatcherEnabled,
  209. &r.GUIStats.Enabled, &r.GUIStats.UseTLS, &r.GUIStats.UseAuth,
  210. &r.GUIStats.InsecureAdminAccess,
  211. &r.GUIStats.Debugging, &r.GUIStats.InsecureSkipHostCheck,
  212. &r.GUIStats.InsecureAllowFrameLoading, &r.GUIStats.ListenLocal,
  213. &r.GUIStats.ListenUnspecified,
  214. &r.BlockStats.Total, &r.BlockStats.Renamed,
  215. &r.BlockStats.Reused, &r.BlockStats.Pulled, &r.BlockStats.CopyOrigin,
  216. &r.BlockStats.CopyOriginShifted, &r.BlockStats.CopyElsewhere,
  217. &r.IgnoreStats.Lines, &r.IgnoreStats.Inverts, &r.IgnoreStats.Folded,
  218. &r.IgnoreStats.Deletable, &r.IgnoreStats.Rooted, &r.IgnoreStats.Includes,
  219. &r.IgnoreStats.EscapedIncludes, &r.IgnoreStats.DoubleStars, &r.IgnoreStats.Stars,
  220. // V3 added late in the RC
  221. &r.WeakHashEnabled,
  222. &r.Address,
  223. // Receive only folders
  224. &r.FolderUses.ReceiveOnly,
  225. }
  226. }
  227. func (r *Report) FieldNames() []string {
  228. // The database fields that back this struct in PostgreSQL
  229. return []string{
  230. // V1
  231. "Received",
  232. "UniqueID",
  233. "Version",
  234. "LongVersion",
  235. "Platform",
  236. "NumFolders",
  237. "NumDevices",
  238. "TotFiles",
  239. "FolderMaxFiles",
  240. "TotMiB",
  241. "FolderMaxMiB",
  242. "MemoryUsageMiB",
  243. "SHA256Perf",
  244. "MemorySize",
  245. "Date",
  246. // V2
  247. "ReportVersion",
  248. "NumCPU",
  249. "FolderRO",
  250. "FolderIgnorePerms",
  251. "FolderIgnoreDelete",
  252. "FolderAutoNormalize",
  253. "DeviceIntroducer",
  254. "DeviceCustomCertName",
  255. "DeviceCompressAlways",
  256. "DeviceCompressMetadata",
  257. "DeviceCompressNever",
  258. "DeviceDynamicAddr",
  259. "DeviceStaticAddr",
  260. "AnnounceGlobalEnabled",
  261. "AnnounceLocalEnabled",
  262. "AnnounceDefaultServersDNS",
  263. "AnnounceDefaultServersIP",
  264. "AnnounceOtherServers",
  265. "RelayEnabled",
  266. "RelayDefaultServers",
  267. "RelayOtherServers",
  268. "RateLimitEnabled",
  269. "UpgradeAllowedManual",
  270. "UpgradeAllowedAuto",
  271. // v0.12.19+
  272. "FolderSimpleVersioning",
  273. "FolderExternalVersioning",
  274. "FolderStaggeredVersioning",
  275. "FolderTrashcanVersioning",
  276. // V2.5
  277. "UpgradeAllowedPre",
  278. // V3
  279. "Uptime",
  280. "NATType",
  281. "AlwaysLocalNets",
  282. "CacheIgnoredFiles",
  283. "OverwriteRemoteDeviceNames",
  284. "ProgressEmitterEnabled",
  285. "CustomDefaultFolderPath",
  286. "WeakHashSelection",
  287. "CustomTrafficClass",
  288. "CustomTempIndexMinBlocks",
  289. "TemporariesDisabled",
  290. "TemporariesCustom",
  291. "LimitBandwidthInLan",
  292. "CustomReleaseURL",
  293. "RestartOnWakeup",
  294. "CustomStunServers",
  295. "FolderScanProgressDisabled",
  296. "FolderConflictsDisabled",
  297. "FolderConflictsUnlimited",
  298. "FolderConflictsOther",
  299. "FolderDisableSparseFiles",
  300. "FolderDisableTempIndexes",
  301. "FolderAlwaysWeakHash",
  302. "FolderCustomWeakHashThreshold",
  303. "FolderFsWatcherEnabled",
  304. "GUIEnabled",
  305. "GUIUseTLS",
  306. "GUIUseAuth",
  307. "GUIInsecureAdminAccess",
  308. "GUIDebugging",
  309. "GUIInsecureSkipHostCheck",
  310. "GUIInsecureAllowFrameLoading",
  311. "GUIListenLocal",
  312. "GUIListenUnspecified",
  313. "BlocksTotal",
  314. "BlocksRenamed",
  315. "BlocksReused",
  316. "BlocksPulled",
  317. "BlocksCopyOrigin",
  318. "BlocksCopyOriginShifted",
  319. "BlocksCopyElsewhere",
  320. "IgnoreLines",
  321. "IgnoreInverts",
  322. "IgnoreFolded",
  323. "IgnoreDeletable",
  324. "IgnoreRooted",
  325. "IgnoreIncludes",
  326. "IgnoreEscapedIncludes",
  327. "IgnoreDoubleStars",
  328. "IgnoreStars",
  329. // V3 added late in the RC
  330. "WeakHashEnabled",
  331. "Address",
  332. // Receive only folders
  333. "FolderRecvOnly",
  334. }
  335. }
  336. func (r Report) Value() (driver.Value, error) {
  337. // This needs to be string, yet we read back bytes..
  338. bs, err := json.Marshal(r)
  339. return string(bs), err
  340. }
  341. func (r *Report) Scan(value interface{}) error {
  342. // Zero out the previous value
  343. // JSON un-marshaller does not touch fields that are not in the payload, so we carry over values from a previous
  344. // scan.
  345. *r = Report{}
  346. b, ok := value.([]byte)
  347. if !ok {
  348. return errors.New("type assertion to []byte failed")
  349. }
  350. return json.Unmarshal(b, &r)
  351. }
  352. func clear(v interface{}, since int) error {
  353. s := reflect.ValueOf(v).Elem()
  354. t := s.Type()
  355. for i := 0; i < s.NumField(); i++ {
  356. f := s.Field(i)
  357. tag := t.Field(i).Tag
  358. v := tag.Get("since")
  359. if len(v) == 0 {
  360. f.Set(reflect.Zero(f.Type()))
  361. continue
  362. }
  363. vn, err := strconv.Atoi(v)
  364. if err != nil {
  365. return err
  366. }
  367. if vn > since {
  368. f.Set(reflect.Zero(f.Type()))
  369. continue
  370. }
  371. // Dive deeper
  372. if f.Kind() == reflect.Ptr {
  373. f = f.Elem()
  374. }
  375. if f.Kind() == reflect.Struct {
  376. if err := clear(f.Addr().Interface(), since); err != nil {
  377. return err
  378. }
  379. }
  380. }
  381. return nil
  382. }