gui.go 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233
  1. // Copyright (C) 2014 The Syncthing Authors.
  2. //
  3. // This Source Code Form is subject to the terms of the Mozilla Public
  4. // License, v. 2.0. If a copy of the MPL was not distributed with this file,
  5. // You can obtain one at http://mozilla.org/MPL/2.0/.
  6. package main
  7. import (
  8. "crypto/tls"
  9. "encoding/json"
  10. "fmt"
  11. "io/ioutil"
  12. "net"
  13. "net/http"
  14. "os"
  15. "path/filepath"
  16. "reflect"
  17. "runtime"
  18. "sort"
  19. "strconv"
  20. "strings"
  21. "time"
  22. "github.com/rcrowley/go-metrics"
  23. "github.com/syncthing/syncthing/lib/config"
  24. "github.com/syncthing/syncthing/lib/db"
  25. "github.com/syncthing/syncthing/lib/discover"
  26. "github.com/syncthing/syncthing/lib/events"
  27. "github.com/syncthing/syncthing/lib/logger"
  28. "github.com/syncthing/syncthing/lib/model"
  29. "github.com/syncthing/syncthing/lib/osutil"
  30. "github.com/syncthing/syncthing/lib/protocol"
  31. "github.com/syncthing/syncthing/lib/rand"
  32. "github.com/syncthing/syncthing/lib/stats"
  33. "github.com/syncthing/syncthing/lib/sync"
  34. "github.com/syncthing/syncthing/lib/tlsutil"
  35. "github.com/syncthing/syncthing/lib/upgrade"
  36. "github.com/vitrun/qart/qr"
  37. "golang.org/x/crypto/bcrypt"
  38. )
  39. var (
  40. configInSync = true
  41. startTime = time.Now()
  42. )
  43. type apiService struct {
  44. id protocol.DeviceID
  45. cfg configIntf
  46. httpsCertFile string
  47. httpsKeyFile string
  48. statics *staticsServer
  49. model modelIntf
  50. eventSub events.BufferedSubscription
  51. discoverer discover.CachingMux
  52. connectionsService connectionsIntf
  53. fss *folderSummaryService
  54. systemConfigMut sync.Mutex // serializes posts to /rest/system/config
  55. stop chan struct{} // signals intentional stop
  56. configChanged chan struct{} // signals intentional listener close due to config change
  57. started chan string // signals startup complete by sending the listener address, for testing only
  58. startedOnce bool // the service has started successfully at least once
  59. guiErrors logger.Recorder
  60. systemLog logger.Recorder
  61. }
  62. type modelIntf interface {
  63. GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{}
  64. Completion(device protocol.DeviceID, folder string) float64
  65. Override(folder string)
  66. NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated, int)
  67. NeedSize(folder string) (nfiles int, bytes int64)
  68. ConnectionStats() map[string]interface{}
  69. DeviceStatistics() map[string]stats.DeviceStatistics
  70. FolderStatistics() map[string]stats.FolderStatistics
  71. CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool)
  72. CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool)
  73. ResetFolder(folder string)
  74. Availability(folder, file string, version protocol.Vector, block protocol.BlockInfo) []model.Availability
  75. GetIgnores(folder string) ([]string, []string, error)
  76. SetIgnores(folder string, content []string) error
  77. PauseDevice(device protocol.DeviceID)
  78. ResumeDevice(device protocol.DeviceID)
  79. DelayScan(folder string, next time.Duration)
  80. ScanFolder(folder string) error
  81. ScanFolders() map[string]error
  82. ScanFolderSubs(folder string, subs []string) error
  83. BringToFront(folder, file string)
  84. ConnectedTo(deviceID protocol.DeviceID) bool
  85. GlobalSize(folder string) (nfiles, deleted int, bytes int64)
  86. LocalSize(folder string) (nfiles, deleted int, bytes int64)
  87. CurrentLocalVersion(folder string) (int64, bool)
  88. RemoteLocalVersion(folder string) (int64, bool)
  89. State(folder string) (string, time.Time, error)
  90. }
  91. type configIntf interface {
  92. GUI() config.GUIConfiguration
  93. Raw() config.Configuration
  94. Options() config.OptionsConfiguration
  95. Replace(cfg config.Configuration) config.CommitResponse
  96. Subscribe(c config.Committer)
  97. Folders() map[string]config.FolderConfiguration
  98. Devices() map[protocol.DeviceID]config.DeviceConfiguration
  99. Save() error
  100. ListenAddresses() []string
  101. }
  102. type connectionsIntf interface {
  103. Status() map[string]interface{}
  104. }
  105. func newAPIService(id protocol.DeviceID, cfg configIntf, httpsCertFile, httpsKeyFile, assetDir string, m modelIntf, eventSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connectionsIntf, errors, systemLog logger.Recorder) *apiService {
  106. service := &apiService{
  107. id: id,
  108. cfg: cfg,
  109. httpsCertFile: httpsCertFile,
  110. httpsKeyFile: httpsKeyFile,
  111. statics: newStaticsServer(cfg.GUI().Theme, assetDir),
  112. model: m,
  113. eventSub: eventSub,
  114. discoverer: discoverer,
  115. connectionsService: connectionsService,
  116. systemConfigMut: sync.NewMutex(),
  117. stop: make(chan struct{}),
  118. configChanged: make(chan struct{}),
  119. guiErrors: errors,
  120. systemLog: systemLog,
  121. }
  122. return service
  123. }
  124. func (s *apiService) getListener(guiCfg config.GUIConfiguration) (net.Listener, error) {
  125. cert, err := tls.LoadX509KeyPair(s.httpsCertFile, s.httpsKeyFile)
  126. if err != nil {
  127. l.Infoln("Loading HTTPS certificate:", err)
  128. l.Infoln("Creating new HTTPS certificate")
  129. // When generating the HTTPS certificate, use the system host name per
  130. // default. If that isn't available, use the "syncthing" default.
  131. var name string
  132. name, err = os.Hostname()
  133. if err != nil {
  134. name = tlsDefaultCommonName
  135. }
  136. cert, err = tlsutil.NewCertificate(s.httpsCertFile, s.httpsKeyFile, name, httpsRSABits)
  137. }
  138. if err != nil {
  139. return nil, err
  140. }
  141. tlsCfg := &tls.Config{
  142. Certificates: []tls.Certificate{cert},
  143. MinVersion: tls.VersionTLS10, // No SSLv3
  144. CipherSuites: []uint16{
  145. // No RC4
  146. tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
  147. tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
  148. tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
  149. tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
  150. tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
  151. tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
  152. tls.TLS_RSA_WITH_AES_128_CBC_SHA,
  153. tls.TLS_RSA_WITH_AES_256_CBC_SHA,
  154. tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
  155. tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
  156. },
  157. }
  158. rawListener, err := net.Listen("tcp", guiCfg.Address())
  159. if err != nil {
  160. return nil, err
  161. }
  162. listener := &tlsutil.DowngradingListener{
  163. Listener: rawListener,
  164. TLSConfig: tlsCfg,
  165. }
  166. return listener, nil
  167. }
  168. func sendJSON(w http.ResponseWriter, jsonObject interface{}) {
  169. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  170. // Marshalling might fail, in which case we should return a 500 with the
  171. // actual error.
  172. bs, err := json.Marshal(jsonObject)
  173. if err != nil {
  174. // This Marshal() can't fail though.
  175. bs, _ = json.Marshal(map[string]string{"error": err.Error()})
  176. http.Error(w, string(bs), http.StatusInternalServerError)
  177. return
  178. }
  179. w.Write(bs)
  180. }
  181. func (s *apiService) Serve() {
  182. listener, err := s.getListener(s.cfg.GUI())
  183. if err != nil {
  184. if !s.startedOnce {
  185. // This is during initialization. A failure here should be fatal
  186. // as there will be no way for the user to communicate with us
  187. // otherwise anyway.
  188. l.Fatalln("Starting API/GUI:", err)
  189. }
  190. // We let this be a loud user-visible warning as it may be the only
  191. // indication they get that the GUI won't be available on startup.
  192. l.Warnln("Starting API/GUI:", err)
  193. return
  194. }
  195. s.startedOnce = true
  196. defer listener.Close()
  197. if listener == nil {
  198. // Not much we can do here other than exit quickly. The supervisor
  199. // will log an error at some point.
  200. return
  201. }
  202. // The GET handlers
  203. getRestMux := http.NewServeMux()
  204. getRestMux.HandleFunc("/rest/db/completion", s.getDBCompletion) // device folder
  205. getRestMux.HandleFunc("/rest/db/file", s.getDBFile) // folder file
  206. getRestMux.HandleFunc("/rest/db/ignores", s.getDBIgnores) // folder
  207. getRestMux.HandleFunc("/rest/db/need", s.getDBNeed) // folder [perpage] [page]
  208. getRestMux.HandleFunc("/rest/db/status", s.getDBStatus) // folder
  209. getRestMux.HandleFunc("/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels]
  210. getRestMux.HandleFunc("/rest/events", s.getEvents) // since [limit]
  211. getRestMux.HandleFunc("/rest/stats/device", s.getDeviceStats) // -
  212. getRestMux.HandleFunc("/rest/stats/folder", s.getFolderStats) // -
  213. getRestMux.HandleFunc("/rest/svc/deviceid", s.getDeviceID) // id
  214. getRestMux.HandleFunc("/rest/svc/lang", s.getLang) // -
  215. getRestMux.HandleFunc("/rest/svc/report", s.getReport) // -
  216. getRestMux.HandleFunc("/rest/svc/random/string", s.getRandomString) // [length]
  217. getRestMux.HandleFunc("/rest/system/browse", s.getSystemBrowse) // current
  218. getRestMux.HandleFunc("/rest/system/config", s.getSystemConfig) // -
  219. getRestMux.HandleFunc("/rest/system/config/insync", s.getSystemConfigInsync) // -
  220. getRestMux.HandleFunc("/rest/system/connections", s.getSystemConnections) // -
  221. getRestMux.HandleFunc("/rest/system/discovery", s.getSystemDiscovery) // -
  222. getRestMux.HandleFunc("/rest/system/error", s.getSystemError) // -
  223. getRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
  224. getRestMux.HandleFunc("/rest/system/status", s.getSystemStatus) // -
  225. getRestMux.HandleFunc("/rest/system/upgrade", s.getSystemUpgrade) // -
  226. getRestMux.HandleFunc("/rest/system/version", s.getSystemVersion) // -
  227. getRestMux.HandleFunc("/rest/system/debug", s.getSystemDebug) // -
  228. getRestMux.HandleFunc("/rest/system/log", s.getSystemLog) // [since]
  229. getRestMux.HandleFunc("/rest/system/log.txt", s.getSystemLogTxt) // [since]
  230. // The POST handlers
  231. postRestMux := http.NewServeMux()
  232. postRestMux.HandleFunc("/rest/db/prio", s.postDBPrio) // folder file [perpage] [page]
  233. postRestMux.HandleFunc("/rest/db/ignores", s.postDBIgnores) // folder
  234. postRestMux.HandleFunc("/rest/db/override", s.postDBOverride) // folder
  235. postRestMux.HandleFunc("/rest/db/scan", s.postDBScan) // folder [sub...] [delay]
  236. postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig) // <body>
  237. postRestMux.HandleFunc("/rest/system/error", s.postSystemError) // <body>
  238. postRestMux.HandleFunc("/rest/system/error/clear", s.postSystemErrorClear) // -
  239. postRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
  240. postRestMux.HandleFunc("/rest/system/reset", s.postSystemReset) // [folder]
  241. postRestMux.HandleFunc("/rest/system/restart", s.postSystemRestart) // -
  242. postRestMux.HandleFunc("/rest/system/shutdown", s.postSystemShutdown) // -
  243. postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade) // -
  244. postRestMux.HandleFunc("/rest/system/pause", s.postSystemPause) // device
  245. postRestMux.HandleFunc("/rest/system/resume", s.postSystemResume) // device
  246. postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable]
  247. // Debug endpoints, not for general use
  248. getRestMux.HandleFunc("/rest/debug/peerCompletion", s.getPeerCompletion)
  249. getRestMux.HandleFunc("/rest/debug/httpmetrics", s.getSystemHTTPMetrics)
  250. // A handler that splits requests between the two above and disables
  251. // caching
  252. restMux := noCacheMiddleware(metricsMiddleware(getPostHandler(getRestMux, postRestMux)))
  253. // The main routing handler
  254. mux := http.NewServeMux()
  255. mux.Handle("/rest/", restMux)
  256. mux.HandleFunc("/qr/", s.getQR)
  257. // Serve compiled in assets unless an asset directory was set (for development)
  258. mux.Handle("/", s.statics)
  259. // Handle the special meta.js path
  260. mux.HandleFunc("/meta.js", s.getJSMetadata)
  261. guiCfg := s.cfg.GUI()
  262. // Wrap everything in CSRF protection. The /rest prefix should be
  263. // protected, other requests will grant cookies.
  264. handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg, mux)
  265. // Add our version and ID as a header to responses
  266. handler = withDetailsMiddleware(s.id, handler)
  267. // Wrap everything in basic auth, if user/password is set.
  268. if len(guiCfg.User) > 0 && len(guiCfg.Password) > 0 {
  269. handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], guiCfg, handler)
  270. }
  271. // Redirect to HTTPS if we are supposed to
  272. if guiCfg.UseTLS() {
  273. handler = redirectToHTTPSMiddleware(handler)
  274. }
  275. // Add the CORS handling
  276. handler = corsMiddleware(handler)
  277. handler = debugMiddleware(handler)
  278. srv := http.Server{
  279. Handler: handler,
  280. ReadTimeout: 10 * time.Second,
  281. }
  282. s.fss = newFolderSummaryService(s.cfg, s.model)
  283. defer s.fss.Stop()
  284. s.fss.ServeBackground()
  285. l.Infoln("GUI and API listening on", listener.Addr())
  286. l.Infoln("Access the GUI via the following URL:", guiCfg.URL())
  287. if s.started != nil {
  288. // only set when run by the tests
  289. s.started <- listener.Addr().String()
  290. }
  291. // Serve in the background
  292. serveError := make(chan error, 1)
  293. go func() {
  294. serveError <- srv.Serve(listener)
  295. }()
  296. // Wait for stop, restart or error signals
  297. select {
  298. case <-s.stop:
  299. // Shutting down permanently
  300. l.Debugln("shutting down (stop)")
  301. case <-s.configChanged:
  302. // Soft restart due to configuration change
  303. l.Debugln("restarting (config changed)")
  304. case <-serveError:
  305. // Restart due to listen/serve failure
  306. l.Warnln("GUI/API:", err, "(restarting)")
  307. }
  308. }
  309. func (s *apiService) Stop() {
  310. close(s.stop)
  311. }
  312. func (s *apiService) String() string {
  313. return fmt.Sprintf("apiService@%p", s)
  314. }
  315. func (s *apiService) VerifyConfiguration(from, to config.Configuration) error {
  316. if _, err := net.ResolveTCPAddr("tcp", to.GUI.Address()); err != nil {
  317. return err
  318. }
  319. return nil
  320. }
  321. func (s *apiService) CommitConfiguration(from, to config.Configuration) bool {
  322. if to.GUI == from.GUI {
  323. return true
  324. }
  325. if to.GUI.Theme != from.GUI.Theme {
  326. s.statics.setTheme(to.GUI.Theme)
  327. }
  328. // Tell the serve loop to restart
  329. s.configChanged <- struct{}{}
  330. return true
  331. }
  332. func getPostHandler(get, post http.Handler) http.Handler {
  333. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  334. switch r.Method {
  335. case "GET":
  336. get.ServeHTTP(w, r)
  337. case "POST":
  338. post.ServeHTTP(w, r)
  339. default:
  340. http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
  341. }
  342. })
  343. }
  344. func debugMiddleware(h http.Handler) http.Handler {
  345. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  346. t0 := time.Now()
  347. h.ServeHTTP(w, r)
  348. if shouldDebugHTTP() {
  349. ms := 1000 * time.Since(t0).Seconds()
  350. // The variable `w` is most likely a *http.response, which we can't do
  351. // much with since it's a non exported type. We can however peek into
  352. // it with reflection to get at the status code and number of bytes
  353. // written.
  354. var status, written int64
  355. if rw := reflect.Indirect(reflect.ValueOf(w)); rw.IsValid() && rw.Kind() == reflect.Struct {
  356. if rf := rw.FieldByName("status"); rf.IsValid() && rf.Kind() == reflect.Int {
  357. status = rf.Int()
  358. }
  359. if rf := rw.FieldByName("written"); rf.IsValid() && rf.Kind() == reflect.Int64 {
  360. written = rf.Int()
  361. }
  362. }
  363. httpl.Debugf("http: %s %q: status %d, %d bytes in %.02f ms", r.Method, r.URL.String(), status, written, ms)
  364. }
  365. })
  366. }
  367. func corsMiddleware(next http.Handler) http.Handler {
  368. // Handle CORS headers and CORS OPTIONS request.
  369. // CORS OPTIONS request are typically sent by browser during AJAX preflight
  370. // when the browser initiate a POST request.
  371. //
  372. // As the OPTIONS request is unauthorized, this handler must be the first
  373. // of the chain (hence added at the end).
  374. //
  375. // See https://www.w3.org/TR/cors/ for details.
  376. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  377. // Process OPTIONS requests
  378. if r.Method == "OPTIONS" {
  379. // Only GET/POST Methods are supported
  380. w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
  381. // Only this custom header can be set
  382. w.Header().Set("Access-Control-Allow-Headers", "X-API-Key")
  383. // The request is meant to be cached 10 minutes
  384. w.Header().Set("Access-Control-Max-Age", "600")
  385. // Indicate that no content will be returned
  386. w.WriteHeader(204)
  387. return
  388. }
  389. // For everything else, pass to the next handler
  390. next.ServeHTTP(w, r)
  391. return
  392. })
  393. }
  394. func metricsMiddleware(h http.Handler) http.Handler {
  395. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  396. t := metrics.GetOrRegisterTimer(r.URL.Path, nil)
  397. t0 := time.Now()
  398. h.ServeHTTP(w, r)
  399. t.UpdateSince(t0)
  400. })
  401. }
  402. func redirectToHTTPSMiddleware(h http.Handler) http.Handler {
  403. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  404. if r.TLS == nil {
  405. // Redirect HTTP requests to HTTPS
  406. r.URL.Host = r.Host
  407. r.URL.Scheme = "https"
  408. http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect)
  409. } else {
  410. h.ServeHTTP(w, r)
  411. }
  412. })
  413. }
  414. func noCacheMiddleware(h http.Handler) http.Handler {
  415. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  416. w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store")
  417. w.Header().Set("Expires", time.Now().UTC().Format(http.TimeFormat))
  418. w.Header().Set("Pragma", "no-cache")
  419. h.ServeHTTP(w, r)
  420. })
  421. }
  422. func withDetailsMiddleware(id protocol.DeviceID, h http.Handler) http.Handler {
  423. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  424. w.Header().Set("X-Syncthing-Version", Version)
  425. w.Header().Set("X-Syncthing-ID", id.String())
  426. h.ServeHTTP(w, r)
  427. })
  428. }
  429. func (s *apiService) restPing(w http.ResponseWriter, r *http.Request) {
  430. sendJSON(w, map[string]string{"ping": "pong"})
  431. }
  432. func (s *apiService) getJSMetadata(w http.ResponseWriter, r *http.Request) {
  433. meta, _ := json.Marshal(map[string]string{
  434. "deviceID": s.id.String(),
  435. })
  436. w.Header().Set("Content-Type", "application/javascript")
  437. fmt.Fprintf(w, "var metadata = %s;\n", meta)
  438. }
  439. func (s *apiService) getSystemVersion(w http.ResponseWriter, r *http.Request) {
  440. sendJSON(w, map[string]string{
  441. "version": Version,
  442. "codename": Codename,
  443. "longVersion": LongVersion,
  444. "os": runtime.GOOS,
  445. "arch": runtime.GOARCH,
  446. })
  447. }
  448. func (s *apiService) getSystemDebug(w http.ResponseWriter, r *http.Request) {
  449. names := l.Facilities()
  450. enabled := l.FacilityDebugging()
  451. sort.Strings(enabled)
  452. sendJSON(w, map[string]interface{}{
  453. "facilities": names,
  454. "enabled": enabled,
  455. })
  456. }
  457. func (s *apiService) postSystemDebug(w http.ResponseWriter, r *http.Request) {
  458. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  459. q := r.URL.Query()
  460. for _, f := range strings.Split(q.Get("enable"), ",") {
  461. if f == "" || l.ShouldDebug(f) {
  462. continue
  463. }
  464. l.SetDebug(f, true)
  465. l.Infof("Enabled debug data for %q", f)
  466. }
  467. for _, f := range strings.Split(q.Get("disable"), ",") {
  468. if f == "" || !l.ShouldDebug(f) {
  469. continue
  470. }
  471. l.SetDebug(f, false)
  472. l.Infof("Disabled debug data for %q", f)
  473. }
  474. }
  475. func (s *apiService) getDBBrowse(w http.ResponseWriter, r *http.Request) {
  476. qs := r.URL.Query()
  477. folder := qs.Get("folder")
  478. prefix := qs.Get("prefix")
  479. dirsonly := qs.Get("dirsonly") != ""
  480. levels, err := strconv.Atoi(qs.Get("levels"))
  481. if err != nil {
  482. levels = -1
  483. }
  484. sendJSON(w, s.model.GlobalDirectoryTree(folder, prefix, levels, dirsonly))
  485. }
  486. func (s *apiService) getDBCompletion(w http.ResponseWriter, r *http.Request) {
  487. var qs = r.URL.Query()
  488. var folder = qs.Get("folder")
  489. var deviceStr = qs.Get("device")
  490. device, err := protocol.DeviceIDFromString(deviceStr)
  491. if err != nil {
  492. http.Error(w, err.Error(), 500)
  493. return
  494. }
  495. sendJSON(w, map[string]float64{
  496. "completion": s.model.Completion(device, folder),
  497. })
  498. }
  499. func (s *apiService) getDBStatus(w http.ResponseWriter, r *http.Request) {
  500. qs := r.URL.Query()
  501. folder := qs.Get("folder")
  502. sendJSON(w, folderSummary(s.cfg, s.model, folder))
  503. }
  504. func folderSummary(cfg configIntf, m modelIntf, folder string) map[string]interface{} {
  505. var res = make(map[string]interface{})
  506. res["invalid"] = cfg.Folders()[folder].Invalid
  507. globalFiles, globalDeleted, globalBytes := m.GlobalSize(folder)
  508. res["globalFiles"], res["globalDeleted"], res["globalBytes"] = globalFiles, globalDeleted, globalBytes
  509. localFiles, localDeleted, localBytes := m.LocalSize(folder)
  510. res["localFiles"], res["localDeleted"], res["localBytes"] = localFiles, localDeleted, localBytes
  511. needFiles, needBytes := m.NeedSize(folder)
  512. res["needFiles"], res["needBytes"] = needFiles, needBytes
  513. res["inSyncFiles"], res["inSyncBytes"] = globalFiles-needFiles, globalBytes-needBytes
  514. var err error
  515. res["state"], res["stateChanged"], err = m.State(folder)
  516. if err != nil {
  517. res["error"] = err.Error()
  518. }
  519. lv, _ := m.CurrentLocalVersion(folder)
  520. rv, _ := m.RemoteLocalVersion(folder)
  521. res["version"] = lv + rv
  522. ignorePatterns, _, _ := m.GetIgnores(folder)
  523. res["ignorePatterns"] = false
  524. for _, line := range ignorePatterns {
  525. if len(line) > 0 && !strings.HasPrefix(line, "//") {
  526. res["ignorePatterns"] = true
  527. break
  528. }
  529. }
  530. return res
  531. }
  532. func (s *apiService) postDBOverride(w http.ResponseWriter, r *http.Request) {
  533. var qs = r.URL.Query()
  534. var folder = qs.Get("folder")
  535. go s.model.Override(folder)
  536. }
  537. func (s *apiService) getDBNeed(w http.ResponseWriter, r *http.Request) {
  538. qs := r.URL.Query()
  539. folder := qs.Get("folder")
  540. page, err := strconv.Atoi(qs.Get("page"))
  541. if err != nil || page < 1 {
  542. page = 1
  543. }
  544. perpage, err := strconv.Atoi(qs.Get("perpage"))
  545. if err != nil || perpage < 1 {
  546. perpage = 1 << 16
  547. }
  548. progress, queued, rest, total := s.model.NeedFolderFiles(folder, page, perpage)
  549. // Convert the struct to a more loose structure, and inject the size.
  550. sendJSON(w, map[string]interface{}{
  551. "progress": s.toNeedSlice(progress),
  552. "queued": s.toNeedSlice(queued),
  553. "rest": s.toNeedSlice(rest),
  554. "total": total,
  555. "page": page,
  556. "perpage": perpage,
  557. })
  558. }
  559. func (s *apiService) getSystemConnections(w http.ResponseWriter, r *http.Request) {
  560. sendJSON(w, s.model.ConnectionStats())
  561. }
  562. func (s *apiService) getDeviceStats(w http.ResponseWriter, r *http.Request) {
  563. sendJSON(w, s.model.DeviceStatistics())
  564. }
  565. func (s *apiService) getFolderStats(w http.ResponseWriter, r *http.Request) {
  566. sendJSON(w, s.model.FolderStatistics())
  567. }
  568. func (s *apiService) getDBFile(w http.ResponseWriter, r *http.Request) {
  569. qs := r.URL.Query()
  570. folder := qs.Get("folder")
  571. file := qs.Get("file")
  572. gf, gfOk := s.model.CurrentGlobalFile(folder, file)
  573. lf, lfOk := s.model.CurrentFolderFile(folder, file)
  574. if !(gfOk || lfOk) {
  575. // This file for sure does not exist.
  576. http.Error(w, "No such object in the index", http.StatusNotFound)
  577. return
  578. }
  579. av := s.model.Availability(folder, file, protocol.Vector{}, protocol.BlockInfo{})
  580. sendJSON(w, map[string]interface{}{
  581. "global": jsonFileInfo(gf),
  582. "local": jsonFileInfo(lf),
  583. "availability": av,
  584. })
  585. }
  586. func (s *apiService) getSystemConfig(w http.ResponseWriter, r *http.Request) {
  587. sendJSON(w, s.cfg.Raw())
  588. }
  589. func (s *apiService) postSystemConfig(w http.ResponseWriter, r *http.Request) {
  590. s.systemConfigMut.Lock()
  591. defer s.systemConfigMut.Unlock()
  592. to, err := config.ReadJSON(r.Body, myID)
  593. r.Body.Close()
  594. if err != nil {
  595. l.Warnln("decoding posted config:", err)
  596. http.Error(w, err.Error(), http.StatusBadRequest)
  597. return
  598. }
  599. if to.GUI.Password != s.cfg.GUI().Password {
  600. if to.GUI.Password != "" {
  601. hash, err := bcrypt.GenerateFromPassword([]byte(to.GUI.Password), 0)
  602. if err != nil {
  603. l.Warnln("bcrypting password:", err)
  604. http.Error(w, err.Error(), http.StatusInternalServerError)
  605. return
  606. }
  607. to.GUI.Password = string(hash)
  608. }
  609. }
  610. // Fixup usage reporting settings
  611. if curAcc := s.cfg.Options().URAccepted; to.Options.URAccepted > curAcc {
  612. // UR was enabled
  613. to.Options.URAccepted = usageReportVersion
  614. to.Options.URUniqueID = rand.String(8)
  615. } else if to.Options.URAccepted < curAcc {
  616. // UR was disabled
  617. to.Options.URAccepted = -1
  618. to.Options.URUniqueID = ""
  619. }
  620. // Activate and save
  621. resp := s.cfg.Replace(to)
  622. configInSync = !resp.RequiresRestart
  623. s.cfg.Save()
  624. }
  625. func (s *apiService) getSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
  626. sendJSON(w, map[string]bool{"configInSync": configInSync})
  627. }
  628. func (s *apiService) postSystemRestart(w http.ResponseWriter, r *http.Request) {
  629. s.flushResponse(`{"ok": "restarting"}`, w)
  630. go restart()
  631. }
  632. func (s *apiService) postSystemReset(w http.ResponseWriter, r *http.Request) {
  633. var qs = r.URL.Query()
  634. folder := qs.Get("folder")
  635. if len(folder) > 0 {
  636. if _, ok := s.cfg.Folders()[folder]; !ok {
  637. http.Error(w, "Invalid folder ID", 500)
  638. return
  639. }
  640. }
  641. if len(folder) == 0 {
  642. // Reset all folders.
  643. for folder := range s.cfg.Folders() {
  644. s.model.ResetFolder(folder)
  645. }
  646. s.flushResponse(`{"ok": "resetting database"}`, w)
  647. } else {
  648. // Reset a specific folder, assuming it's supposed to exist.
  649. s.model.ResetFolder(folder)
  650. s.flushResponse(`{"ok": "resetting folder `+folder+`"}`, w)
  651. }
  652. go restart()
  653. }
  654. func (s *apiService) postSystemShutdown(w http.ResponseWriter, r *http.Request) {
  655. s.flushResponse(`{"ok": "shutting down"}`, w)
  656. go shutdown()
  657. }
  658. func (s *apiService) flushResponse(resp string, w http.ResponseWriter) {
  659. w.Write([]byte(resp + "\n"))
  660. f := w.(http.Flusher)
  661. f.Flush()
  662. }
  663. var cpuUsagePercent [10]float64 // The last ten seconds
  664. var cpuUsageLock = sync.NewRWMutex()
  665. func (s *apiService) getSystemStatus(w http.ResponseWriter, r *http.Request) {
  666. var m runtime.MemStats
  667. runtime.ReadMemStats(&m)
  668. tilde, _ := osutil.ExpandTilde("~")
  669. res := make(map[string]interface{})
  670. res["myID"] = myID.String()
  671. res["goroutines"] = runtime.NumGoroutine()
  672. res["alloc"] = m.Alloc
  673. res["sys"] = m.Sys - m.HeapReleased
  674. res["tilde"] = tilde
  675. if s.cfg.Options().LocalAnnEnabled || s.cfg.Options().GlobalAnnEnabled {
  676. res["discoveryEnabled"] = true
  677. discoErrors := make(map[string]string)
  678. discoMethods := 0
  679. for disco, err := range s.discoverer.ChildErrors() {
  680. discoMethods++
  681. if err != nil {
  682. discoErrors[disco] = err.Error()
  683. }
  684. }
  685. res["discoveryMethods"] = discoMethods
  686. res["discoveryErrors"] = discoErrors
  687. }
  688. res["connectionServiceStatus"] = s.connectionsService.Status()
  689. cpuUsageLock.RLock()
  690. var cpusum float64
  691. for _, p := range cpuUsagePercent {
  692. cpusum += p
  693. }
  694. cpuUsageLock.RUnlock()
  695. res["cpuPercent"] = cpusum / float64(len(cpuUsagePercent)) / float64(runtime.NumCPU())
  696. res["pathSeparator"] = string(filepath.Separator)
  697. res["uptime"] = int(time.Since(startTime).Seconds())
  698. res["startTime"] = startTime
  699. sendJSON(w, res)
  700. }
  701. func (s *apiService) getSystemError(w http.ResponseWriter, r *http.Request) {
  702. sendJSON(w, map[string][]logger.Line{
  703. "errors": s.guiErrors.Since(time.Time{}),
  704. })
  705. }
  706. func (s *apiService) postSystemError(w http.ResponseWriter, r *http.Request) {
  707. bs, _ := ioutil.ReadAll(r.Body)
  708. r.Body.Close()
  709. l.Warnln(string(bs))
  710. }
  711. func (s *apiService) postSystemErrorClear(w http.ResponseWriter, r *http.Request) {
  712. s.guiErrors.Clear()
  713. }
  714. func (s *apiService) getSystemLog(w http.ResponseWriter, r *http.Request) {
  715. q := r.URL.Query()
  716. since, err := time.Parse(time.RFC3339, q.Get("since"))
  717. l.Debugln(err)
  718. sendJSON(w, map[string][]logger.Line{
  719. "messages": s.systemLog.Since(since),
  720. })
  721. }
  722. func (s *apiService) getSystemLogTxt(w http.ResponseWriter, r *http.Request) {
  723. q := r.URL.Query()
  724. since, err := time.Parse(time.RFC3339, q.Get("since"))
  725. l.Debugln(err)
  726. w.Header().Set("Content-Type", "text/plain; charset=utf-8")
  727. for _, line := range s.systemLog.Since(since) {
  728. fmt.Fprintf(w, "%s: %s\n", line.When.Format(time.RFC3339), line.Message)
  729. }
  730. }
  731. func (s *apiService) getSystemHTTPMetrics(w http.ResponseWriter, r *http.Request) {
  732. stats := make(map[string]interface{})
  733. metrics.Each(func(name string, intf interface{}) {
  734. if m, ok := intf.(*metrics.StandardTimer); ok {
  735. pct := m.Percentiles([]float64{0.50, 0.95, 0.99})
  736. for i := range pct {
  737. pct[i] /= 1e6 // ns to ms
  738. }
  739. stats[name] = map[string]interface{}{
  740. "count": m.Count(),
  741. "sumMs": m.Sum() / 1e6, // ns to ms
  742. "ratesPerS": []float64{m.Rate1(), m.Rate5(), m.Rate15()},
  743. "percentilesMs": pct,
  744. }
  745. }
  746. })
  747. bs, _ := json.MarshalIndent(stats, "", " ")
  748. w.Write(bs)
  749. }
  750. func (s *apiService) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
  751. devices := make(map[string]discover.CacheEntry)
  752. if s.discoverer != nil {
  753. // Device ids can't be marshalled as keys so we need to manually
  754. // rebuild this map using strings. Discoverer may be nil if discovery
  755. // has not started yet.
  756. for device, entry := range s.discoverer.Cache() {
  757. devices[device.String()] = entry
  758. }
  759. }
  760. sendJSON(w, devices)
  761. }
  762. func (s *apiService) getReport(w http.ResponseWriter, r *http.Request) {
  763. sendJSON(w, reportData(s.cfg, s.model))
  764. }
  765. func (s *apiService) getRandomString(w http.ResponseWriter, r *http.Request) {
  766. length := 32
  767. if val, _ := strconv.Atoi(r.URL.Query().Get("length")); val > 0 {
  768. length = val
  769. }
  770. str := rand.String(length)
  771. sendJSON(w, map[string]string{"random": str})
  772. }
  773. func (s *apiService) getDBIgnores(w http.ResponseWriter, r *http.Request) {
  774. qs := r.URL.Query()
  775. ignores, patterns, err := s.model.GetIgnores(qs.Get("folder"))
  776. if err != nil {
  777. http.Error(w, err.Error(), 500)
  778. return
  779. }
  780. sendJSON(w, map[string][]string{
  781. "ignore": ignores,
  782. "expanded": patterns,
  783. })
  784. }
  785. func (s *apiService) postDBIgnores(w http.ResponseWriter, r *http.Request) {
  786. qs := r.URL.Query()
  787. bs, err := ioutil.ReadAll(r.Body)
  788. r.Body.Close()
  789. if err != nil {
  790. http.Error(w, err.Error(), 500)
  791. return
  792. }
  793. var data map[string][]string
  794. err = json.Unmarshal(bs, &data)
  795. if err != nil {
  796. http.Error(w, err.Error(), 500)
  797. return
  798. }
  799. err = s.model.SetIgnores(qs.Get("folder"), data["ignore"])
  800. if err != nil {
  801. http.Error(w, err.Error(), 500)
  802. return
  803. }
  804. s.getDBIgnores(w, r)
  805. }
  806. func (s *apiService) getEvents(w http.ResponseWriter, r *http.Request) {
  807. qs := r.URL.Query()
  808. sinceStr := qs.Get("since")
  809. limitStr := qs.Get("limit")
  810. since, _ := strconv.Atoi(sinceStr)
  811. limit, _ := strconv.Atoi(limitStr)
  812. s.fss.gotEventRequest()
  813. // Flush before blocking, to indicate that we've received the request and
  814. // that it should not be retried. Must set Content-Type header before
  815. // flushing.
  816. w.Header().Set("Content-Type", "application/json; charset=utf-8")
  817. f := w.(http.Flusher)
  818. f.Flush()
  819. evs := s.eventSub.Since(since, nil)
  820. if 0 < limit && limit < len(evs) {
  821. evs = evs[len(evs)-limit:]
  822. }
  823. sendJSON(w, evs)
  824. }
  825. func (s *apiService) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
  826. if noUpgrade {
  827. http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
  828. return
  829. }
  830. rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version)
  831. if err != nil {
  832. http.Error(w, err.Error(), 500)
  833. return
  834. }
  835. res := make(map[string]interface{})
  836. res["running"] = Version
  837. res["latest"] = rel.Tag
  838. res["newer"] = upgrade.CompareVersions(rel.Tag, Version) == upgrade.Newer
  839. res["majorNewer"] = upgrade.CompareVersions(rel.Tag, Version) == upgrade.MajorNewer
  840. sendJSON(w, res)
  841. }
  842. func (s *apiService) getDeviceID(w http.ResponseWriter, r *http.Request) {
  843. qs := r.URL.Query()
  844. idStr := qs.Get("id")
  845. id, err := protocol.DeviceIDFromString(idStr)
  846. if err == nil {
  847. sendJSON(w, map[string]string{
  848. "id": id.String(),
  849. })
  850. } else {
  851. sendJSON(w, map[string]string{
  852. "error": err.Error(),
  853. })
  854. }
  855. }
  856. func (s *apiService) getLang(w http.ResponseWriter, r *http.Request) {
  857. lang := r.Header.Get("Accept-Language")
  858. var langs []string
  859. for _, l := range strings.Split(lang, ",") {
  860. parts := strings.SplitN(l, ";", 2)
  861. langs = append(langs, strings.ToLower(strings.TrimSpace(parts[0])))
  862. }
  863. sendJSON(w, langs)
  864. }
  865. func (s *apiService) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
  866. rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version)
  867. if err != nil {
  868. l.Warnln("getting latest release:", err)
  869. http.Error(w, err.Error(), 500)
  870. return
  871. }
  872. if upgrade.CompareVersions(rel.Tag, Version) > upgrade.Equal {
  873. err = upgrade.To(rel)
  874. if err != nil {
  875. l.Warnln("upgrading:", err)
  876. http.Error(w, err.Error(), 500)
  877. return
  878. }
  879. s.flushResponse(`{"ok": "restarting"}`, w)
  880. l.Infoln("Upgrading")
  881. stop <- exitUpgrading
  882. }
  883. }
  884. func (s *apiService) postSystemPause(w http.ResponseWriter, r *http.Request) {
  885. var qs = r.URL.Query()
  886. var deviceStr = qs.Get("device")
  887. device, err := protocol.DeviceIDFromString(deviceStr)
  888. if err != nil {
  889. http.Error(w, err.Error(), 500)
  890. return
  891. }
  892. s.model.PauseDevice(device)
  893. }
  894. func (s *apiService) postSystemResume(w http.ResponseWriter, r *http.Request) {
  895. var qs = r.URL.Query()
  896. var deviceStr = qs.Get("device")
  897. device, err := protocol.DeviceIDFromString(deviceStr)
  898. if err != nil {
  899. http.Error(w, err.Error(), 500)
  900. return
  901. }
  902. s.model.ResumeDevice(device)
  903. }
  904. func (s *apiService) postDBScan(w http.ResponseWriter, r *http.Request) {
  905. qs := r.URL.Query()
  906. folder := qs.Get("folder")
  907. if folder != "" {
  908. nextStr := qs.Get("next")
  909. next, err := strconv.Atoi(nextStr)
  910. if err == nil {
  911. s.model.DelayScan(folder, time.Duration(next)*time.Second)
  912. }
  913. subs := qs["sub"]
  914. err = s.model.ScanFolderSubs(folder, subs)
  915. if err != nil {
  916. http.Error(w, err.Error(), 500)
  917. return
  918. }
  919. } else {
  920. errors := s.model.ScanFolders()
  921. if len(errors) > 0 {
  922. http.Error(w, "Error scanning folders", 500)
  923. sendJSON(w, errors)
  924. return
  925. }
  926. }
  927. }
  928. func (s *apiService) postDBPrio(w http.ResponseWriter, r *http.Request) {
  929. qs := r.URL.Query()
  930. folder := qs.Get("folder")
  931. file := qs.Get("file")
  932. s.model.BringToFront(folder, file)
  933. s.getDBNeed(w, r)
  934. }
  935. func (s *apiService) getQR(w http.ResponseWriter, r *http.Request) {
  936. var qs = r.URL.Query()
  937. var text = qs.Get("text")
  938. code, err := qr.Encode(text, qr.M)
  939. if err != nil {
  940. http.Error(w, "Invalid", 500)
  941. return
  942. }
  943. w.Header().Set("Content-Type", "image/png")
  944. w.Write(code.PNG())
  945. }
  946. func (s *apiService) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
  947. tot := map[string]float64{}
  948. count := map[string]float64{}
  949. for _, folder := range s.cfg.Folders() {
  950. for _, device := range folder.DeviceIDs() {
  951. deviceStr := device.String()
  952. if s.model.ConnectedTo(device) {
  953. tot[deviceStr] += s.model.Completion(device, folder.ID)
  954. } else {
  955. tot[deviceStr] = 0
  956. }
  957. count[deviceStr]++
  958. }
  959. }
  960. comp := map[string]int{}
  961. for device := range tot {
  962. comp[device] = int(tot[device] / count[device])
  963. }
  964. sendJSON(w, comp)
  965. }
  966. func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
  967. qs := r.URL.Query()
  968. current := qs.Get("current")
  969. if current == "" && runtime.GOOS == "windows" {
  970. if drives, err := osutil.GetDriveLetters(); err == nil {
  971. sendJSON(w, drives)
  972. } else {
  973. http.Error(w, err.Error(), 500)
  974. }
  975. return
  976. }
  977. search, _ := osutil.ExpandTilde(current)
  978. pathSeparator := string(os.PathSeparator)
  979. if strings.HasSuffix(current, pathSeparator) && !strings.HasSuffix(search, pathSeparator) {
  980. search = search + pathSeparator
  981. }
  982. subdirectories, _ := osutil.Glob(search + "*")
  983. ret := make([]string, 0, 10)
  984. for _, subdirectory := range subdirectories {
  985. info, err := os.Stat(subdirectory)
  986. if err == nil && info.IsDir() {
  987. ret = append(ret, subdirectory+pathSeparator)
  988. if len(ret) > 9 {
  989. break
  990. }
  991. }
  992. }
  993. sendJSON(w, ret)
  994. }
  995. func (s *apiService) toNeedSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
  996. res := make([]jsonDBFileInfo, len(fs))
  997. for i, f := range fs {
  998. res[i] = jsonDBFileInfo(f)
  999. }
  1000. return res
  1001. }
  1002. // Type wrappers for nice JSON serialization
  1003. type jsonFileInfo protocol.FileInfo
  1004. func (f jsonFileInfo) MarshalJSON() ([]byte, error) {
  1005. return json.Marshal(map[string]interface{}{
  1006. "name": f.Name,
  1007. "size": protocol.FileInfo(f).Size(),
  1008. "flags": fmt.Sprintf("%#o", f.Flags),
  1009. "modified": time.Unix(f.Modified, 0),
  1010. "localVersion": f.LocalVersion,
  1011. "numBlocks": len(f.Blocks),
  1012. "version": jsonVersionVector(f.Version),
  1013. })
  1014. }
  1015. type jsonDBFileInfo db.FileInfoTruncated
  1016. func (f jsonDBFileInfo) MarshalJSON() ([]byte, error) {
  1017. return json.Marshal(map[string]interface{}{
  1018. "name": f.Name,
  1019. "size": db.FileInfoTruncated(f).Size(),
  1020. "flags": fmt.Sprintf("%#o", f.Flags),
  1021. "modified": time.Unix(f.Modified, 0),
  1022. "localVersion": f.LocalVersion,
  1023. "version": jsonVersionVector(f.Version),
  1024. })
  1025. }
  1026. type jsonVersionVector protocol.Vector
  1027. func (v jsonVersionVector) MarshalJSON() ([]byte, error) {
  1028. res := make([]string, len(v))
  1029. for i, c := range v {
  1030. res[i] = fmt.Sprintf("%v:%d", c.ID, c.Value)
  1031. }
  1032. return json.Marshal(res)
  1033. }
  1034. func dirNames(dir string) []string {
  1035. fd, err := os.Open(dir)
  1036. if err != nil {
  1037. return nil
  1038. }
  1039. defer fd.Close()
  1040. fis, err := fd.Readdir(-1)
  1041. if err != nil {
  1042. return nil
  1043. }
  1044. var dirs []string
  1045. for _, fi := range fis {
  1046. if fi.IsDir() {
  1047. dirs = append(dirs, filepath.Base(fi.Name()))
  1048. }
  1049. }
  1050. sort.Strings(dirs)
  1051. return dirs
  1052. }