main.go 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270
  1. package main
  2. import (
  3. "bytes"
  4. "crypto/tls"
  5. "database/sql"
  6. "database/sql/driver"
  7. "encoding/json"
  8. "errors"
  9. "fmt"
  10. "html/template"
  11. "io"
  12. "io/ioutil"
  13. "log"
  14. "net/http"
  15. "os"
  16. "regexp"
  17. "sort"
  18. "strings"
  19. "sync"
  20. "time"
  21. "github.com/lib/pq"
  22. )
  23. var (
  24. keyFile = getEnvDefault("UR_KEY_FILE", "key.pem")
  25. certFile = getEnvDefault("UR_CRT_FILE", "crt.pem")
  26. dbConn = getEnvDefault("UR_DB_URL", "postgres://user:password@localhost/ur?sslmode=disable")
  27. listenAddr = getEnvDefault("UR_LISTEN", "0.0.0.0:8443")
  28. tpl *template.Template
  29. compilerRe = regexp.MustCompile(`\(([A-Za-z0-9()., -]+) \w+-\w+(?:| android| default)\) ([\[email protected]]+)`)
  30. )
  31. var funcs = map[string]interface{}{
  32. "commatize": commatize,
  33. "number": number,
  34. }
  35. func getEnvDefault(key, def string) string {
  36. if val := os.Getenv(key); val != "" {
  37. return val
  38. }
  39. return def
  40. }
  41. type IntMap map[string]int
  42. func (p IntMap) Value() (driver.Value, error) {
  43. return json.Marshal(p)
  44. }
  45. func (p *IntMap) Scan(src interface{}) error {
  46. source, ok := src.([]byte)
  47. if !ok {
  48. return errors.New("Type assertion .([]byte) failed.")
  49. }
  50. var i interface{}
  51. err := json.Unmarshal(source, &i)
  52. if err != nil {
  53. return err
  54. }
  55. *p, ok = i.(map[string]int)
  56. if !ok {
  57. return errors.New("Type assertion .(map[string]int) failed.")
  58. }
  59. return nil
  60. }
  61. type report struct {
  62. Received time.Time // Only from DB
  63. UniqueID string
  64. Version string
  65. LongVersion string
  66. Platform string
  67. NumFolders int
  68. NumDevices int
  69. TotFiles int
  70. FolderMaxFiles int
  71. TotMiB int
  72. FolderMaxMiB int
  73. MemoryUsageMiB int
  74. SHA256Perf float64
  75. MemorySize int
  76. // v2 fields
  77. URVersion int
  78. NumCPU int
  79. FolderUses struct {
  80. ReadOnly int
  81. IgnorePerms int
  82. IgnoreDelete int
  83. AutoNormalize int
  84. SimpleVersioning int
  85. ExternalVersioning int
  86. StaggeredVersioning int
  87. TrashcanVersioning int
  88. }
  89. DeviceUses struct {
  90. Introducer int
  91. CustomCertName int
  92. CompressAlways int
  93. CompressMetadata int
  94. CompressNever int
  95. DynamicAddr int
  96. StaticAddr int
  97. }
  98. Announce struct {
  99. GlobalEnabled bool
  100. LocalEnabled bool
  101. DefaultServersDNS int
  102. DefaultServersIP int
  103. OtherServers int
  104. }
  105. Relays struct {
  106. Enabled bool
  107. DefaultServers int
  108. OtherServers int
  109. }
  110. UsesRateLimit bool
  111. UpgradeAllowedManual bool
  112. UpgradeAllowedAuto bool
  113. // V2.5 fields (fields that were in v2 but never added to the database
  114. UpgradeAllowedPre bool
  115. RescanIntvs pq.Int64Array
  116. // v3 fields
  117. Uptime int
  118. NATType string
  119. AlwaysLocalNets bool
  120. CacheIgnoredFiles bool
  121. OverwriteRemoteDeviceNames bool
  122. ProgressEmitterEnabled bool
  123. CustomDefaultFolderPath bool
  124. WeakHashSelection string
  125. CustomTrafficClass bool
  126. CustomTempIndexMinBlocks bool
  127. TemporariesDisabled bool
  128. TemporariesCustom bool
  129. LimitBandwidthInLan bool
  130. CustomReleaseURL bool
  131. RestartOnWakeup bool
  132. CustomStunServers bool
  133. FolderUsesV3 struct {
  134. ScanProgressDisabled int
  135. ConflictsDisabled int
  136. ConflictsUnlimited int
  137. ConflictsOther int
  138. DisableSparseFiles int
  139. DisableTempIndexes int
  140. AlwaysWeakHash int
  141. CustomWeakHashThreshold int
  142. FsWatcherEnabled int
  143. PullOrder IntMap
  144. FilesystemType IntMap
  145. FsWatcherDelays pq.Int64Array
  146. }
  147. GUIStats struct {
  148. Enabled int
  149. UseTLS int
  150. UseAuth int
  151. InsecureAdminAccess int
  152. Debugging int
  153. InsecureSkipHostCheck int
  154. InsecureAllowFrameLoading int
  155. ListenLocal int
  156. ListenUnspecified int
  157. Theme IntMap
  158. }
  159. BlockStats struct {
  160. Total int
  161. Renamed int
  162. Reused int
  163. Pulled int
  164. CopyOrigin int
  165. CopyOriginShifted int
  166. CopyElsewhere int
  167. }
  168. TransportStats IntMap
  169. IgnoreStats struct {
  170. Lines int
  171. Inverts int
  172. Folded int
  173. Deletable int
  174. Rooted int
  175. Includes int
  176. EscapedIncludes int
  177. DoubleStars int
  178. Stars int
  179. }
  180. // Generated
  181. Date string
  182. }
  183. func (r *report) Validate() error {
  184. if r.UniqueID == "" || r.Version == "" || r.Platform == "" {
  185. return fmt.Errorf("missing required field")
  186. }
  187. if len(r.Date) != 8 {
  188. return fmt.Errorf("date not initialized")
  189. }
  190. return nil
  191. }
  192. func (r *report) FieldPointers() []interface{} {
  193. // All the fields of the report, in the same order as the database fields.
  194. return []interface{}{
  195. &r.Received, &r.UniqueID, &r.Version, &r.LongVersion, &r.Platform,
  196. &r.NumFolders, &r.NumDevices, &r.TotFiles, &r.FolderMaxFiles,
  197. &r.TotMiB, &r.FolderMaxMiB, &r.MemoryUsageMiB, &r.SHA256Perf,
  198. &r.MemorySize, &r.Date,
  199. // V2
  200. &r.URVersion, &r.NumCPU, &r.FolderUses.ReadOnly, &r.FolderUses.IgnorePerms,
  201. &r.FolderUses.IgnoreDelete, &r.FolderUses.AutoNormalize, &r.DeviceUses.Introducer,
  202. &r.DeviceUses.CustomCertName, &r.DeviceUses.CompressAlways,
  203. &r.DeviceUses.CompressMetadata, &r.DeviceUses.CompressNever,
  204. &r.DeviceUses.DynamicAddr, &r.DeviceUses.StaticAddr,
  205. &r.Announce.GlobalEnabled, &r.Announce.LocalEnabled,
  206. &r.Announce.DefaultServersDNS, &r.Announce.DefaultServersIP,
  207. &r.Announce.OtherServers, &r.Relays.Enabled, &r.Relays.DefaultServers,
  208. &r.Relays.OtherServers, &r.UsesRateLimit, &r.UpgradeAllowedManual,
  209. &r.UpgradeAllowedAuto, &r.FolderUses.SimpleVersioning,
  210. &r.FolderUses.ExternalVersioning, &r.FolderUses.StaggeredVersioning,
  211. &r.FolderUses.TrashcanVersioning,
  212. // V2.5
  213. &r.UpgradeAllowedPre, &r.RescanIntvs,
  214. // V3
  215. &r.Uptime, &r.NATType, &r.AlwaysLocalNets, &r.CacheIgnoredFiles,
  216. &r.OverwriteRemoteDeviceNames, &r.ProgressEmitterEnabled, &r.CustomDefaultFolderPath,
  217. &r.WeakHashSelection, &r.CustomTrafficClass, &r.CustomTempIndexMinBlocks,
  218. &r.TemporariesDisabled, &r.TemporariesCustom, &r.LimitBandwidthInLan,
  219. &r.CustomReleaseURL, &r.RestartOnWakeup, &r.CustomStunServers,
  220. &r.FolderUsesV3.ScanProgressDisabled, &r.FolderUsesV3.ConflictsDisabled,
  221. &r.FolderUsesV3.ConflictsUnlimited, &r.FolderUsesV3.ConflictsOther,
  222. &r.FolderUsesV3.DisableSparseFiles, &r.FolderUsesV3.DisableTempIndexes,
  223. &r.FolderUsesV3.AlwaysWeakHash, &r.FolderUsesV3.CustomWeakHashThreshold,
  224. &r.FolderUsesV3.FsWatcherEnabled,
  225. &r.FolderUsesV3.PullOrder, &r.FolderUsesV3.FilesystemType,
  226. &r.FolderUsesV3.FsWatcherDelays,
  227. &r.GUIStats.Enabled, &r.GUIStats.UseTLS, &r.GUIStats.UseAuth,
  228. &r.GUIStats.InsecureAdminAccess,
  229. &r.GUIStats.Debugging, &r.GUIStats.InsecureSkipHostCheck,
  230. &r.GUIStats.InsecureAllowFrameLoading, &r.GUIStats.ListenLocal,
  231. &r.GUIStats.ListenUnspecified, &r.GUIStats.Theme,
  232. &r.BlockStats.Total, &r.BlockStats.Renamed,
  233. &r.BlockStats.Reused, &r.BlockStats.Pulled, &r.BlockStats.CopyOrigin,
  234. &r.BlockStats.CopyOriginShifted, &r.BlockStats.CopyElsewhere,
  235. &r.TransportStats,
  236. &r.IgnoreStats.Lines, &r.IgnoreStats.Inverts, &r.IgnoreStats.Folded,
  237. &r.IgnoreStats.Deletable, &r.IgnoreStats.Rooted, &r.IgnoreStats.Includes,
  238. &r.IgnoreStats.EscapedIncludes, &r.IgnoreStats.DoubleStars, &r.IgnoreStats.Stars,
  239. }
  240. }
  241. func (r *report) FieldNames() []string {
  242. // The database fields that back this struct in PostgreSQL
  243. return []string{
  244. // V1
  245. "Received",
  246. "UniqueID",
  247. "Version",
  248. "LongVersion",
  249. "Platform",
  250. "NumFolders",
  251. "NumDevices",
  252. "TotFiles",
  253. "FolderMaxFiles",
  254. "TotMiB",
  255. "FolderMaxMiB",
  256. "MemoryUsageMiB",
  257. "SHA256Perf",
  258. "MemorySize",
  259. "Date",
  260. // V2
  261. "ReportVersion",
  262. "NumCPU",
  263. "FolderRO",
  264. "FolderIgnorePerms",
  265. "FolderIgnoreDelete",
  266. "FolderAutoNormalize",
  267. "DeviceIntroducer",
  268. "DeviceCustomCertName",
  269. "DeviceCompressAlways",
  270. "DeviceCompressMetadata",
  271. "DeviceCompressNever",
  272. "DeviceDynamicAddr",
  273. "DeviceStaticAddr",
  274. "AnnounceGlobalEnabled",
  275. "AnnounceLocalEnabled",
  276. "AnnounceDefaultServersDNS",
  277. "AnnounceDefaultServersIP",
  278. "AnnounceOtherServers",
  279. "RelayEnabled",
  280. "RelayDefaultServers",
  281. "RelayOtherServers",
  282. "RateLimitEnabled",
  283. "UpgradeAllowedManual",
  284. "UpgradeAllowedAuto",
  285. // v0.12.19+
  286. "FolderSimpleVersioning",
  287. "FolderExternalVersioning",
  288. "FolderStaggeredVersioning",
  289. "FolderTrashcanVersioning",
  290. // V2.5
  291. "UpgradeAllowedPre",
  292. "RescanIntvs",
  293. // V3
  294. "Uptime",
  295. "NATType",
  296. "AlwaysLocalNets",
  297. "CacheIgnoredFiles",
  298. "OverwriteRemoteDeviceNames",
  299. "ProgressEmitterEnabled",
  300. "CustomDefaultFolderPath",
  301. "WeakHashSelection",
  302. "CustomTrafficClass",
  303. "CustomTempIndexMinBlocks",
  304. "TemporariesDisabled",
  305. "TemporariesCustom",
  306. "LimitBandwidthInLan",
  307. "CustomReleaseURL",
  308. "RestartOnWakeup",
  309. "CustomStunServers",
  310. "FolderScanProgressDisabled",
  311. "FolderConflictsDisabled",
  312. "FolderConflictsUnlimited",
  313. "FolderConflictsOther",
  314. "FolderDisableSparseFiles",
  315. "FolderDisableTempIndexes",
  316. "FolderAlwaysWeakHash",
  317. "FolderCustomWeakHashThreshold",
  318. "FolderFsWatcherEnabled",
  319. "FolderPullOrder",
  320. "FolderFilesystemType",
  321. "FolderFsWatcherDelays",
  322. "GUIEnabled",
  323. "GUIUseTLS",
  324. "GUIUseAuth",
  325. "GUIInsecureAdminAccess",
  326. "GUIDebugging",
  327. "GUIInsecureSkipHostCheck",
  328. "GUIInsecureAllowFrameLoading",
  329. "GUIListenLocal",
  330. "GUIListenUnspecified",
  331. "GUITheme",
  332. "BlocksTotal",
  333. "BlocksRenamed",
  334. "BlocksReused",
  335. "BlocksPulled",
  336. "BlocksCopyOrigin",
  337. "BlocksCopyOriginShifted",
  338. "BlocksCopyElsewhere",
  339. "Transport",
  340. "IgnoreLines",
  341. "IgnoreInverts",
  342. "IgnoreFolded",
  343. "IgnoreDeletable",
  344. "IgnoreRooted",
  345. "IgnoreIncludes",
  346. "IgnoreEscapedIncludes",
  347. "IgnoreDoubleStars",
  348. "IgnoreStars",
  349. }
  350. }
  351. func setupDB(db *sql.DB) error {
  352. _, err := db.Exec(`CREATE TABLE IF NOT EXISTS Reports (
  353. Received TIMESTAMP NOT NULL,
  354. UniqueID VARCHAR(32) NOT NULL,
  355. Version VARCHAR(32) NOT NULL,
  356. LongVersion VARCHAR(256) NOT NULL,
  357. Platform VARCHAR(32) NOT NULL,
  358. NumFolders INTEGER NOT NULL,
  359. NumDevices INTEGER NOT NULL,
  360. TotFiles INTEGER NOT NULL,
  361. FolderMaxFiles INTEGER NOT NULL,
  362. TotMiB INTEGER NOT NULL,
  363. FolderMaxMiB INTEGER NOT NULL,
  364. MemoryUsageMiB INTEGER NOT NULL,
  365. SHA256Perf DOUBLE PRECISION NOT NULL,
  366. MemorySize INTEGER NOT NULL,
  367. Date VARCHAR(8) NOT NULL
  368. )`)
  369. if err != nil {
  370. return err
  371. }
  372. var t string
  373. row := db.QueryRow(`SELECT 'UniqueIDIndex'::regclass`)
  374. if err := row.Scan(&t); err != nil {
  375. if _, err = db.Exec(`CREATE UNIQUE INDEX UniqueIDIndex ON Reports (Date, UniqueID)`); err != nil {
  376. return err
  377. }
  378. }
  379. row = db.QueryRow(`SELECT 'ReceivedIndex'::regclass`)
  380. if err := row.Scan(&t); err != nil {
  381. if _, err = db.Exec(`CREATE INDEX ReceivedIndex ON Reports (Received)`); err != nil {
  382. return err
  383. }
  384. }
  385. // V2
  386. row = db.QueryRow(`SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'reports') AND attname = 'reportversion'`)
  387. if err := row.Scan(&t); err != nil {
  388. // The ReportVersion column doesn't exist; add the new columns.
  389. _, err = db.Exec(`ALTER TABLE Reports
  390. ADD COLUMN ReportVersion INTEGER NOT NULL DEFAULT 0,
  391. ADD COLUMN NumCPU INTEGER NOT NULL DEFAULT 0,
  392. ADD COLUMN FolderRO INTEGER NOT NULL DEFAULT 0,
  393. ADD COLUMN FolderIgnorePerms INTEGER NOT NULL DEFAULT 0,
  394. ADD COLUMN FolderIgnoreDelete INTEGER NOT NULL DEFAULT 0,
  395. ADD COLUMN FolderAutoNormalize INTEGER NOT NULL DEFAULT 0,
  396. ADD COLUMN DeviceIntroducer INTEGER NOT NULL DEFAULT 0,
  397. ADD COLUMN DeviceCustomCertName INTEGER NOT NULL DEFAULT 0,
  398. ADD COLUMN DeviceCompressAlways INTEGER NOT NULL DEFAULT 0,
  399. ADD COLUMN DeviceCompressMetadata INTEGER NOT NULL DEFAULT 0,
  400. ADD COLUMN DeviceCompressNever INTEGER NOT NULL DEFAULT 0,
  401. ADD COLUMN DeviceDynamicAddr INTEGER NOT NULL DEFAULT 0,
  402. ADD COLUMN DeviceStaticAddr INTEGER NOT NULL DEFAULT 0,
  403. ADD COLUMN AnnounceGlobalEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  404. ADD COLUMN AnnounceLocalEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  405. ADD COLUMN AnnounceDefaultServersDNS INTEGER NOT NULL DEFAULT 0,
  406. ADD COLUMN AnnounceDefaultServersIP INTEGER NOT NULL DEFAULT 0,
  407. ADD COLUMN AnnounceOtherServers INTEGER NOT NULL DEFAULT 0,
  408. ADD COLUMN RelayEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  409. ADD COLUMN RelayDefaultServers INTEGER NOT NULL DEFAULT 0,
  410. ADD COLUMN RelayOtherServers INTEGER NOT NULL DEFAULT 0,
  411. ADD COLUMN RateLimitEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  412. ADD COLUMN UpgradeAllowedManual BOOLEAN NOT NULL DEFAULT FALSE,
  413. ADD COLUMN UpgradeAllowedAuto BOOLEAN NOT NULL DEFAULT FALSE,
  414. ADD COLUMN FolderSimpleVersioning INTEGER NOT NULL DEFAULT 0,
  415. ADD COLUMN FolderExternalVersioning INTEGER NOT NULL DEFAULT 0,
  416. ADD COLUMN FolderStaggeredVersioning INTEGER NOT NULL DEFAULT 0,
  417. ADD COLUMN FolderTrashcanVersioning INTEGER NOT NULL DEFAULT 0
  418. `)
  419. if err != nil {
  420. return err
  421. }
  422. }
  423. row = db.QueryRow(`SELECT 'ReportVersionIndex'::regclass`)
  424. if err := row.Scan(&t); err != nil {
  425. if _, err = db.Exec(`CREATE INDEX ReportVersionIndex ON Reports (ReportVersion)`); err != nil {
  426. return err
  427. }
  428. }
  429. // V2.5
  430. row = db.QueryRow(`SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'reports') AND attname = 'upgradeallowedpre'`)
  431. if err := row.Scan(&t); err != nil {
  432. // The ReportVersion column doesn't exist; add the new columns.
  433. _, err = db.Exec(`ALTER TABLE Reports
  434. ADD COLUMN UpgradeAllowedPre BOOLEAN NOT NULL DEFAULT FALSE,
  435. ADD COLUMN RescanIntvs INT[] NOT NULL DEFAULT '{}'
  436. `)
  437. if err != nil {
  438. return err
  439. }
  440. }
  441. // V3
  442. row = db.QueryRow(`SELECT attname FROM pg_attribute WHERE attrelid = (SELECT oid FROM pg_class WHERE relname = 'reports') AND attname = 'uptime'`)
  443. if err := row.Scan(&t); err != nil {
  444. // The Uptime column doesn't exist; add the new columns.
  445. _, err = db.Exec(`ALTER TABLE Reports
  446. ADD COLUMN Uptime INTEGER NOT NULL DEFAULT 0,
  447. ADD COLUMN NATType VARCHAR(32) NOT NULL DEFAULT '',
  448. ADD COLUMN AlwaysLocalNets BOOLEAN NOT NULL DEFAULT FALSE,
  449. ADD COLUMN CacheIgnoredFiles BOOLEAN NOT NULL DEFAULT FALSE,
  450. ADD COLUMN OverwriteRemoteDeviceNames BOOLEAN NOT NULL DEFAULT FALSE,
  451. ADD COLUMN ProgressEmitterEnabled BOOLEAN NOT NULL DEFAULT FALSE,
  452. ADD COLUMN CustomDefaultFolderPath BOOLEAN NOT NULL DEFAULT FALSE,
  453. ADD COLUMN WeakHashSelection VARCHAR(32) NOT NULL DEFAULT '',
  454. ADD COLUMN CustomTrafficClass BOOLEAN NOT NULL DEFAULT FALSE,
  455. ADD COLUMN CustomTempIndexMinBlocks BOOLEAN NOT NULL DEFAULT FALSE,
  456. ADD COLUMN TemporariesDisabled BOOLEAN NOT NULL DEFAULT FALSE,
  457. ADD COLUMN TemporariesCustom BOOLEAN NOT NULL DEFAULT FALSE,
  458. ADD COLUMN LimitBandwidthInLan BOOLEAN NOT NULL DEFAULT FALSE,
  459. ADD COLUMN CustomReleaseURL BOOLEAN NOT NULL DEFAULT FALSE,
  460. ADD COLUMN RestartOnWakeup BOOLEAN NOT NULL DEFAULT FALSE,
  461. ADD COLUMN CustomStunServers BOOLEAN NOT NULL DEFAULT FALSE,
  462. ADD COLUMN FolderScanProgressDisabled INTEGER NOT NULL DEFAULT 0,
  463. ADD COLUMN FolderConflictsDisabled INTEGER NOT NULL DEFAULT 0,
  464. ADD COLUMN FolderConflictsUnlimited INTEGER NOT NULL DEFAULT 0,
  465. ADD COLUMN FolderConflictsOther INTEGER NOT NULL DEFAULT 0,
  466. ADD COLUMN FolderDisableSparseFiles INTEGER NOT NULL DEFAULT 0,
  467. ADD COLUMN FolderDisableTempIndexes INTEGER NOT NULL DEFAULT 0,
  468. ADD COLUMN FolderAlwaysWeakHash INTEGER NOT NULL DEFAULT 0,
  469. ADD COLUMN FolderCustomWeakHashThreshold INTEGER NOT NULL DEFAULT 0,
  470. ADD COLUMN FolderFsWatcherEnabled INTEGER NOT NULL DEFAULT 0,
  471. ADD COLUMN FolderPullOrder JSONB NOT NULL DEFAULT '{}',
  472. ADD COLUMN FolderFilesystemType JSONB NOT NULL DEFAULT '{}',
  473. ADD COLUMN FolderFsWatcherDelays INT[] NOT NULL DEFAULT '{}',
  474. ADD COLUMN GUIEnabled INTEGER NOT NULL DEFAULT 0,
  475. ADD COLUMN GUIUseTLS INTEGER NOT NULL DEFAULT 0,
  476. ADD COLUMN GUIUseAuth INTEGER NOT NULL DEFAULT 0,
  477. ADD COLUMN GUIInsecureAdminAccess INTEGER NOT NULL DEFAULT 0,
  478. ADD COLUMN GUIDebugging INTEGER NOT NULL DEFAULT 0,
  479. ADD COLUMN GUIInsecureSkipHostCheck INTEGER NOT NULL DEFAULT 0,
  480. ADD COLUMN GUIInsecureAllowFrameLoading INTEGER NOT NULL DEFAULT 0,
  481. ADD COLUMN GUIListenLocal INTEGER NOT NULL DEFAULT 0,
  482. ADD COLUMN GUIListenUnspecified INTEGER NOT NULL DEFAULT 0,
  483. ADD COLUMN GUITheme JSONB NOT NULL DEFAULT '{}',
  484. ADD COLUMN BlocksTotal INTEGER NOT NULL DEFAULT 0,
  485. ADD COLUMN BlocksRenamed INTEGER NOT NULL DEFAULT 0,
  486. ADD COLUMN BlocksReused INTEGER NOT NULL DEFAULT 0,
  487. ADD COLUMN BlocksPulled INTEGER NOT NULL DEFAULT 0,
  488. ADD COLUMN BlocksCopyOrigin INTEGER NOT NULL DEFAULT 0,
  489. ADD COLUMN BlocksCopyOriginShifted INTEGER NOT NULL DEFAULT 0,
  490. ADD COLUMN BlocksCopyElsewhere INTEGER NOT NULL DEFAULT 0,
  491. ADD COLUMN Transport JSONB NOT NULL DEFAULT '{}',
  492. ADD COLUMN IgnoreLines INTEGER NOT NULL DEFAULT 0,
  493. ADD COLUMN IgnoreInverts INTEGER NOT NULL DEFAULT 0,
  494. ADD COLUMN IgnoreFolded INTEGER NOT NULL DEFAULT 0,
  495. ADD COLUMN IgnoreDeletable INTEGER NOT NULL DEFAULT 0,
  496. ADD COLUMN IgnoreRooted INTEGER NOT NULL DEFAULT 0,
  497. ADD COLUMN IgnoreIncludes INTEGER NOT NULL DEFAULT 0,
  498. ADD COLUMN IgnoreEscapedIncludes INTEGER NOT NULL DEFAULT 0,
  499. ADD COLUMN IgnoreDoubleStars INTEGER NOT NULL DEFAULT 0,
  500. ADD COLUMN IgnoreStars INTEGER NOT NULL DEFAULT 0
  501. `)
  502. if err != nil {
  503. return err
  504. }
  505. }
  506. return nil
  507. }
  508. func insertReport(db *sql.DB, r report) error {
  509. r.Received = time.Now().UTC()
  510. fields := r.FieldPointers()
  511. params := make([]string, len(fields))
  512. for i := range params {
  513. params[i] = fmt.Sprintf("$%d", i+1)
  514. }
  515. query := "INSERT INTO Reports (" + strings.Join(r.FieldNames(), ", ") + ") VALUES (" + strings.Join(params, ", ") + ")"
  516. _, err := db.Exec(query, fields...)
  517. return err
  518. }
  519. type withDBFunc func(*sql.DB, http.ResponseWriter, *http.Request)
  520. func withDB(db *sql.DB, f withDBFunc) http.HandlerFunc {
  521. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  522. f(db, w, r)
  523. })
  524. }
  525. func main() {
  526. log.SetFlags(log.Ltime | log.Ldate)
  527. log.SetOutput(os.Stdout)
  528. // Template
  529. fd, err := os.Open("static/index.html")
  530. if err != nil {
  531. log.Fatalln("template:", err)
  532. }
  533. bs, err := ioutil.ReadAll(fd)
  534. if err != nil {
  535. log.Fatalln("template:", err)
  536. }
  537. fd.Close()
  538. tpl = template.Must(template.New("index.html").Funcs(funcs).Parse(string(bs)))
  539. // DB
  540. db, err := sql.Open("postgres", dbConn)
  541. if err != nil {
  542. log.Fatalln("database:", err)
  543. }
  544. err = setupDB(db)
  545. if err != nil {
  546. log.Fatalln("database:", err)
  547. }
  548. // TLS
  549. cert, err := tls.LoadX509KeyPair(certFile, keyFile)
  550. if err != nil {
  551. log.Fatalln("tls:", err)
  552. }
  553. cfg := &tls.Config{
  554. Certificates: []tls.Certificate{cert},
  555. SessionTicketsDisabled: true,
  556. }
  557. // HTTPS
  558. listener, err := tls.Listen("tcp", listenAddr, cfg)
  559. if err != nil {
  560. log.Fatalln("https:", err)
  561. }
  562. srv := http.Server{
  563. ReadTimeout: 5 * time.Second,
  564. WriteTimeout: 5 * time.Second,
  565. }
  566. http.HandleFunc("/", withDB(db, rootHandler))
  567. http.HandleFunc("/newdata", withDB(db, newDataHandler))
  568. http.HandleFunc("/summary.json", withDB(db, summaryHandler))
  569. http.HandleFunc("/movement.json", withDB(db, movementHandler))
  570. http.HandleFunc("/performance.json", withDB(db, performanceHandler))
  571. http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
  572. err = srv.Serve(listener)
  573. if err != nil {
  574. log.Fatalln("https:", err)
  575. }
  576. }
  577. var (
  578. cacheData []byte
  579. cacheTime time.Time
  580. cacheMut sync.Mutex
  581. )
  582. const maxCacheTime = 5 * 60 * time.Second
  583. func rootHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  584. if r.URL.Path == "/" || r.URL.Path == "/index.html" {
  585. cacheMut.Lock()
  586. defer cacheMut.Unlock()
  587. if time.Since(cacheTime) > maxCacheTime {
  588. rep := getReport(db)
  589. buf := new(bytes.Buffer)
  590. err := tpl.Execute(buf, rep)
  591. if err != nil {
  592. log.Println(err)
  593. http.Error(w, "Template Error", http.StatusInternalServerError)
  594. return
  595. }
  596. cacheData = buf.Bytes()
  597. cacheTime = time.Now()
  598. }
  599. w.Header().Set("Content-Type", "text/html; charset=utf-8")
  600. w.Write(cacheData)
  601. } else {
  602. http.Error(w, "Not found", 404)
  603. return
  604. }
  605. }
  606. func newDataHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  607. defer r.Body.Close()
  608. var rep report
  609. rep.Date = time.Now().UTC().Format("20060102")
  610. lr := &io.LimitedReader{R: r.Body, N: 40 * 1024}
  611. if err := json.NewDecoder(lr).Decode(&rep); err != nil {
  612. log.Println("json decode:", err)
  613. http.Error(w, "JSON Decode Error", http.StatusInternalServerError)
  614. return
  615. }
  616. if err := rep.Validate(); err != nil {
  617. log.Println("validate:", err)
  618. log.Printf("%#v", rep)
  619. http.Error(w, "Validation Error", http.StatusInternalServerError)
  620. return
  621. }
  622. if err := insertReport(db, rep); err != nil {
  623. log.Println("insert:", err)
  624. log.Printf("%#v", rep)
  625. http.Error(w, "Database Error", http.StatusInternalServerError)
  626. return
  627. }
  628. }
  629. func summaryHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  630. s, err := getSummary(db)
  631. if err != nil {
  632. log.Println("summaryHandler:", err)
  633. http.Error(w, "Database Error", http.StatusInternalServerError)
  634. return
  635. }
  636. bs, err := s.MarshalJSON()
  637. if err != nil {
  638. log.Println("summaryHandler:", err)
  639. http.Error(w, "JSON Encode Error", http.StatusInternalServerError)
  640. return
  641. }
  642. w.Header().Set("Content-Type", "application/json")
  643. w.Write(bs)
  644. }
  645. func movementHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  646. s, err := getMovement(db)
  647. if err != nil {
  648. log.Println("movementHandler:", err)
  649. http.Error(w, "Database Error", http.StatusInternalServerError)
  650. return
  651. }
  652. bs, err := json.Marshal(s)
  653. if err != nil {
  654. log.Println("movementHandler:", err)
  655. http.Error(w, "JSON Encode Error", http.StatusInternalServerError)
  656. return
  657. }
  658. w.Header().Set("Content-Type", "application/json")
  659. w.Write(bs)
  660. }
  661. func performanceHandler(db *sql.DB, w http.ResponseWriter, r *http.Request) {
  662. s, err := getPerformance(db)
  663. if err != nil {
  664. log.Println("performanceHandler:", err)
  665. http.Error(w, "Database Error", http.StatusInternalServerError)
  666. return
  667. }
  668. bs, err := json.Marshal(s)
  669. if err != nil {
  670. log.Println("performanceHandler:", err)
  671. http.Error(w, "JSON Encode Error", http.StatusInternalServerError)
  672. return
  673. }
  674. w.Header().Set("Content-Type", "application/json")
  675. w.Write(bs)
  676. }
  677. type category struct {
  678. Values [4]float64
  679. Key string
  680. Descr string
  681. Unit string
  682. Binary bool
  683. }
  684. type feature struct {
  685. Key string
  686. Pct float64
  687. }
  688. func getReport(db *sql.DB) map[string]interface{} {
  689. nodes := 0
  690. var versions []string
  691. var platforms []string
  692. var numFolders []int
  693. var numDevices []int
  694. var totFiles []int
  695. var maxFiles []int
  696. var totMiB []int
  697. var maxMiB []int
  698. var memoryUsage []int
  699. var sha256Perf []float64
  700. var memorySize []int
  701. var compilers []string
  702. var builders []string
  703. v2Reports := 0
  704. features := map[string]float64{
  705. "Rate limiting": 0,
  706. "Upgrades allowed (automatic)": 0,
  707. "Upgrades allowed (manual)": 0,
  708. "Folders, automatic normalization": 0,
  709. "Folders, ignore deletes": 0,
  710. "Folders, ignore permissions": 0,
  711. "Folders, master mode": 0,
  712. "Folders, simple versioning": 0,
  713. "Folders, external versioning": 0,
  714. "Folders, staggered versioning": 0,
  715. "Folders, trashcan versioning": 0,
  716. "Devices, compress always": 0,
  717. "Devices, compress metadata": 0,
  718. "Devices, compress nothing": 0,
  719. "Devices, custom certificate": 0,
  720. "Devices, dynamic addresses": 0,
  721. "Devices, static addresses": 0,
  722. "Devices, introducer": 0,
  723. "Relaying, enabled": 0,
  724. "Relaying, default relays": 0,
  725. "Relaying, other relays": 0,
  726. "Discovery, global enabled": 0,
  727. "Discovery, local enabled": 0,
  728. "Discovery, default servers (using DNS)": 0,
  729. "Discovery, default servers (using IP)": 0,
  730. "Discovery, other servers": 0,
  731. }
  732. var numCPU []int
  733. var rep report
  734. rows, err := db.Query(`SELECT ` + strings.Join(rep.FieldNames(), ",") + ` FROM Reports WHERE Received > now() - '1 day'::INTERVAL`)
  735. if err != nil {
  736. log.Println("sql:", err)
  737. return nil
  738. }
  739. defer rows.Close()
  740. for rows.Next() {
  741. err := rows.Scan(rep.FieldPointers()...)
  742. if err != nil {
  743. log.Println("sql:", err)
  744. return nil
  745. }
  746. nodes++
  747. versions = append(versions, transformVersion(rep.Version))
  748. platforms = append(platforms, rep.Platform)
  749. if m := compilerRe.FindStringSubmatch(rep.LongVersion); len(m) == 3 {
  750. compilers = append(compilers, m[1])
  751. builders = append(builders, m[2])
  752. }
  753. if rep.NumFolders > 0 {
  754. numFolders = append(numFolders, rep.NumFolders)
  755. }
  756. if rep.NumDevices > 0 {
  757. numDevices = append(numDevices, rep.NumDevices)
  758. }
  759. if rep.TotFiles > 0 {
  760. totFiles = append(totFiles, rep.TotFiles)
  761. }
  762. if rep.FolderMaxFiles > 0 {
  763. maxFiles = append(maxFiles, rep.FolderMaxFiles)
  764. }
  765. if rep.TotMiB > 0 {
  766. totMiB = append(totMiB, rep.TotMiB*(1<<20))
  767. }
  768. if rep.FolderMaxMiB > 0 {
  769. maxMiB = append(maxMiB, rep.FolderMaxMiB*(1<<20))
  770. }
  771. if rep.MemoryUsageMiB > 0 {
  772. memoryUsage = append(memoryUsage, rep.MemoryUsageMiB*(1<<20))
  773. }
  774. if rep.SHA256Perf > 0 {
  775. sha256Perf = append(sha256Perf, rep.SHA256Perf*(1<<20))
  776. }
  777. if rep.MemorySize > 0 {
  778. memorySize = append(memorySize, rep.MemorySize*(1<<20))
  779. }
  780. if rep.URVersion >= 2 {
  781. v2Reports++
  782. numCPU = append(numCPU, rep.NumCPU)
  783. if rep.UsesRateLimit {
  784. features["Rate limiting"]++
  785. }
  786. if rep.UpgradeAllowedAuto {
  787. features["Upgrades allowed (automatic)"]++
  788. }
  789. if rep.UpgradeAllowedManual {
  790. features["Upgrades allowed (manual)"]++
  791. }
  792. if rep.FolderUses.AutoNormalize > 0 {
  793. features["Folders, automatic normalization"]++
  794. }
  795. if rep.FolderUses.IgnoreDelete > 0 {
  796. features["Folders, ignore deletes"]++
  797. }
  798. if rep.FolderUses.IgnorePerms > 0 {
  799. features["Folders, ignore permissions"]++
  800. }
  801. if rep.FolderUses.ReadOnly > 0 {
  802. features["Folders, master mode"]++
  803. }
  804. if rep.FolderUses.SimpleVersioning > 0 {
  805. features["Folders, simple versioning"]++
  806. }
  807. if rep.FolderUses.ExternalVersioning > 0 {
  808. features["Folders, external versioning"]++
  809. }
  810. if rep.FolderUses.StaggeredVersioning > 0 {
  811. features["Folders, staggered versioning"]++
  812. }
  813. if rep.FolderUses.TrashcanVersioning > 0 {
  814. features["Folders, trashcan versioning"]++
  815. }
  816. if rep.DeviceUses.CompressAlways > 0 {
  817. features["Devices, compress always"]++
  818. }
  819. if rep.DeviceUses.CompressMetadata > 0 {
  820. features["Devices, compress metadata"]++
  821. }
  822. if rep.DeviceUses.CompressNever > 0 {
  823. features["Devices, compress nothing"]++
  824. }
  825. if rep.DeviceUses.CustomCertName > 0 {
  826. features["Devices, custom certificate"]++
  827. }
  828. if rep.DeviceUses.DynamicAddr > 0 {
  829. features["Devices, dynamic addresses"]++
  830. }
  831. if rep.DeviceUses.StaticAddr > 0 {
  832. features["Devices, static addresses"]++
  833. }
  834. if rep.DeviceUses.Introducer > 0 {
  835. features["Devices, introducer"]++
  836. }
  837. if rep.Relays.Enabled {
  838. features["Relaying, enabled"]++
  839. }
  840. if rep.Relays.DefaultServers > 0 {
  841. features["Relaying, default relays"]++
  842. }
  843. if rep.Relays.OtherServers > 0 {
  844. features["Relaying, other relays"]++
  845. }
  846. if rep.Announce.GlobalEnabled {
  847. features["Discovery, global enabled"]++
  848. }
  849. if rep.Announce.LocalEnabled {
  850. features["Discovery, local enabled"]++
  851. }
  852. if rep.Announce.DefaultServersDNS > 0 {
  853. features["Discovery, default servers (using DNS)"]++
  854. }
  855. if rep.Announce.DefaultServersIP > 0 {
  856. features["Discovery, default servers (using IP)"]++
  857. }
  858. if rep.Announce.DefaultServersIP > 0 {
  859. features["Discovery, other servers"]++
  860. }
  861. }
  862. }
  863. var categories []category
  864. categories = append(categories, category{
  865. Values: statsForInts(totFiles),
  866. Descr: "Files Managed per Device",
  867. })
  868. categories = append(categories, category{
  869. Values: statsForInts(maxFiles),
  870. Descr: "Files in Largest Folder",
  871. })
  872. categories = append(categories, category{
  873. Values: statsForInts(totMiB),
  874. Descr: "Data Managed per Device",
  875. Unit: "B",
  876. Binary: true,
  877. })
  878. categories = append(categories, category{
  879. Values: statsForInts(maxMiB),
  880. Descr: "Data in Largest Folder",
  881. Unit: "B",
  882. Binary: true,
  883. })
  884. categories = append(categories, category{
  885. Values: statsForInts(numDevices),
  886. Descr: "Number of Devices in Cluster",
  887. })
  888. categories = append(categories, category{
  889. Values: statsForInts(numFolders),
  890. Descr: "Number of Folders Configured",
  891. })
  892. categories = append(categories, category{
  893. Values: statsForInts(memoryUsage),
  894. Descr: "Memory Usage",
  895. Unit: "B",
  896. Binary: true,
  897. })
  898. categories = append(categories, category{
  899. Values: statsForInts(memorySize),
  900. Descr: "System Memory",
  901. Unit: "B",
  902. Binary: true,
  903. })
  904. categories = append(categories, category{
  905. Values: statsForFloats(sha256Perf),
  906. Descr: "SHA-256 Hashing Performance",
  907. Unit: "B/s",
  908. Binary: true,
  909. })
  910. categories = append(categories, category{
  911. Values: statsForInts(numCPU),
  912. Descr: "Number of CPU cores",
  913. })
  914. var featureList []feature
  915. var featureNames []string
  916. for key := range features {
  917. featureNames = append(featureNames, key)
  918. }
  919. sort.Strings(featureNames)
  920. if v2Reports > 0 {
  921. for _, key := range featureNames {
  922. featureList = append(featureList, feature{
  923. Key: key,
  924. Pct: (100 * features[key]) / float64(v2Reports),
  925. })
  926. }
  927. sort.Sort(sort.Reverse(sortableFeatureList(featureList)))
  928. }
  929. r := make(map[string]interface{})
  930. r["nodes"] = nodes
  931. r["v2nodes"] = v2Reports
  932. r["categories"] = categories
  933. r["versions"] = group(byVersion, analyticsFor(versions, 2000), 5)
  934. r["platforms"] = group(byPlatform, analyticsFor(platforms, 2000), 5)
  935. r["compilers"] = group(byCompiler, analyticsFor(compilers, 2000), 3)
  936. r["builders"] = analyticsFor(builders, 12)
  937. r["features"] = featureList
  938. return r
  939. }
  940. func ensureDir(dir string, mode int) {
  941. fi, err := os.Stat(dir)
  942. if os.IsNotExist(err) {
  943. os.MkdirAll(dir, 0700)
  944. } else if mode >= 0 && err == nil && int(fi.Mode()&0777) != mode {
  945. os.Chmod(dir, os.FileMode(mode))
  946. }
  947. }
  948. var plusRe = regexp.MustCompile(`\+.*$`)
  949. // transformVersion returns a version number formatted correctly, with all
  950. // development versions aggregated into one.
  951. func transformVersion(v string) string {
  952. if v == "unknown-dev" {
  953. return v
  954. }
  955. if !strings.HasPrefix(v, "v") {
  956. v = "v" + v
  957. }
  958. v = plusRe.ReplaceAllString(v, " (+dev)")
  959. return v
  960. }
  961. type summary struct {
  962. versions map[string]int // version string to count index
  963. max map[string]int // version string to max users per day
  964. rows map[string][]int // date to list of counts
  965. }
  966. func newSummary() summary {
  967. return summary{
  968. versions: make(map[string]int),
  969. max: make(map[string]int),
  970. rows: make(map[string][]int),
  971. }
  972. }
  973. func (s *summary) setCount(date, version string, count int) {
  974. idx, ok := s.versions[version]
  975. if !ok {
  976. idx = len(s.versions)
  977. s.versions[version] = idx
  978. }
  979. if s.max[version] < count {
  980. s.max[version] = count
  981. }
  982. row := s.rows[date]
  983. if len(row) <= idx {
  984. old := row
  985. row = make([]int, idx+1)
  986. copy(row, old)
  987. s.rows[date] = row
  988. }
  989. row[idx] = count
  990. }
  991. func (s *summary) MarshalJSON() ([]byte, error) {
  992. var versions []string
  993. for v := range s.versions {
  994. versions = append(versions, v)
  995. }
  996. sort.Strings(versions)
  997. var filtered []string
  998. for _, v := range versions {
  999. if s.max[v] > 50 {
  1000. filtered = append(filtered, v)
  1001. }
  1002. }
  1003. versions = filtered
  1004. headerRow := []interface{}{"Day"}
  1005. for _, v := range versions {
  1006. headerRow = append(headerRow, v)
  1007. }
  1008. var table [][]interface{}
  1009. table = append(table, headerRow)
  1010. var dates []string
  1011. for k := range s.rows {
  1012. dates = append(dates, k)
  1013. }
  1014. sort.Strings(dates)
  1015. for _, date := range dates {
  1016. row := []interface{}{date}
  1017. for _, ver := range versions {
  1018. idx := s.versions[ver]
  1019. if len(s.rows[date]) > idx && s.rows[date][idx] > 0 {
  1020. row = append(row, s.rows[date][idx])
  1021. } else {
  1022. row = append(row, nil)
  1023. }
  1024. }
  1025. table = append(table, row)
  1026. }
  1027. return json.Marshal(table)
  1028. }
  1029. func getSummary(db *sql.DB) (summary, error) {
  1030. s := newSummary()
  1031. rows, err := db.Query(`SELECT Day, Version, Count FROM VersionSummary WHERE Day > now() - '2 year'::INTERVAL;`)
  1032. if err != nil {
  1033. return summary{}, err
  1034. }
  1035. defer rows.Close()
  1036. for rows.Next() {
  1037. var day time.Time
  1038. var ver string
  1039. var num int
  1040. err := rows.Scan(&day, &ver, &num)
  1041. if err != nil {
  1042. return summary{}, err
  1043. }
  1044. if ver == "v0.0" {
  1045. // ?
  1046. continue
  1047. }
  1048. // SUPER UGLY HACK to avoid having to do sorting properly
  1049. if len(ver) == 4 { // v0.x
  1050. ver = ver[:3] + "0" + ver[3:] // now v0.0x
  1051. }
  1052. s.setCount(day.Format("2006-01-02"), ver, num)
  1053. }
  1054. return s, nil
  1055. }
  1056. func getMovement(db *sql.DB) ([][]interface{}, error) {
  1057. rows, err := db.Query(`SELECT Day, Added, Removed, Bounced FROM UserMovement WHERE Day > now() - '2 year'::INTERVAL ORDER BY Day`)
  1058. if err != nil {
  1059. return nil, err
  1060. }
  1061. defer rows.Close()
  1062. res := [][]interface{}{
  1063. {"Day", "Joined", "Left", "Bounced"},
  1064. }
  1065. for rows.Next() {
  1066. var day time.Time
  1067. var added, removed, bounced int
  1068. err := rows.Scan(&day, &added, &removed, &bounced)
  1069. if err != nil {
  1070. return nil, err
  1071. }
  1072. row := []interface{}{day.Format("2006-01-02"), added, -removed, bounced}
  1073. if removed == 0 {
  1074. row[2] = nil
  1075. }
  1076. if bounced == 0 {
  1077. row[3] = nil
  1078. }
  1079. res = append(res, row)
  1080. }
  1081. return res, nil
  1082. }
  1083. func getPerformance(db *sql.DB) ([][]interface{}, error) {
  1084. rows, err := db.Query(`SELECT Day, TotFiles, TotMiB, SHA256Perf, MemorySize, MemoryUsageMiB FROM Performance WHERE Day > '2014-06-20'::TIMESTAMP ORDER BY Day`)
  1085. if err != nil {
  1086. return nil, err
  1087. }
  1088. defer rows.Close()
  1089. res := [][]interface{}{
  1090. {"Day", "TotFiles", "TotMiB", "SHA256Perf", "MemorySize", "MemoryUsageMiB"},
  1091. }
  1092. for rows.Next() {
  1093. var day time.Time
  1094. var sha256Perf float64
  1095. var totFiles, totMiB, memorySize, memoryUsage int
  1096. err := rows.Scan(&day, &totFiles, &totMiB, &sha256Perf, &memorySize, &memoryUsage)
  1097. if err != nil {
  1098. return nil, err
  1099. }
  1100. row := []interface{}{day.Format("2006-01-02"), totFiles, totMiB, float64(int(sha256Perf*10)) / 10, memorySize, memoryUsage}
  1101. res = append(res, row)
  1102. }
  1103. return res, nil
  1104. }
  1105. type sortableFeatureList []feature
  1106. func (l sortableFeatureList) Len() int {
  1107. return len(l)
  1108. }
  1109. func (l sortableFeatureList) Swap(a, b int) {
  1110. l[a], l[b] = l[b], l[a]
  1111. }
  1112. func (l sortableFeatureList) Less(a, b int) bool {
  1113. if l[a].Pct != l[b].Pct {
  1114. return l[a].Pct < l[b].Pct
  1115. }
  1116. return l[a].Key > l[b].Key
  1117. }