contract.go 15 KB

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