syncthing/cmd/syncthing/gui.go

1381 lines
40 KiB
Go
Raw Normal View History

2014-11-16 13:13:20 -07:00
// Copyright (C) 2014 The Syncthing Authors.
2014-09-29 12:43:32 -07:00
//
2015-03-07 13:36:35 -07:00
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
2014-06-01 13:50:14 -07:00
2014-03-02 15:58:14 -07:00
package main
import (
"bytes"
"compress/gzip"
"crypto/tls"
2014-03-02 15:58:14 -07:00
"encoding/json"
"fmt"
2014-03-02 15:58:14 -07:00
"io/ioutil"
"mime"
"net"
2014-03-02 15:58:14 -07:00
"net/http"
"os"
"path/filepath"
2015-04-07 12:45:22 -07:00
"reflect"
2014-03-02 15:58:14 -07:00
"runtime"
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
"sort"
2014-07-13 12:07:24 -07:00
"strconv"
2014-07-05 12:40:29 -07:00
"strings"
2014-03-02 15:58:14 -07:00
"time"
2015-11-21 01:48:57 -07:00
"github.com/rcrowley/go-metrics"
2015-08-06 02:29:25 -07:00
"github.com/syncthing/syncthing/lib/auto"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/discover"
"github.com/syncthing/syncthing/lib/events"
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
"github.com/syncthing/syncthing/lib/logger"
"github.com/syncthing/syncthing/lib/model"
2015-08-06 02:29:25 -07:00
"github.com/syncthing/syncthing/lib/osutil"
2015-09-22 10:38:46 -07:00
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/stats"
2015-08-06 02:29:25 -07:00
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/tlsutil"
2015-08-06 02:29:25 -07:00
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/vitrun/qart/qr"
"golang.org/x/crypto/bcrypt"
2014-03-02 15:58:14 -07:00
)
var (
2015-04-28 13:32:10 -07:00
configInSync = true
startTime = time.Now()
2014-03-02 15:58:14 -07:00
)
2015-12-23 08:31:12 -07:00
type apiService struct {
id protocol.DeviceID
cfg configIntf
httpsCertFile string
httpsKeyFile string
assetDir string
themes []string
model modelIntf
eventSub events.BufferedSubscription
discoverer discover.CachingMux
connectionsService connectionsIntf
fss *folderSummaryService
systemConfigMut sync.Mutex // serializes posts to /rest/system/config
stop chan struct{} // signals intentional stop
configChanged chan struct{} // signals intentional listener close due to config change
started chan struct{} // signals startup complete, for testing only
listener net.Listener
listenerMut sync.Mutex
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
guiErrors logger.Recorder
systemLog logger.Recorder
2015-04-28 14:12:19 -07:00
}
type modelIntf interface {
GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{}
Completion(device protocol.DeviceID, folder string) float64
Override(folder string)
NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated, int)
NeedSize(folder string) (nfiles int, bytes int64)
ConnectionStats() map[string]interface{}
DeviceStatistics() map[string]stats.DeviceStatistics
FolderStatistics() map[string]stats.FolderStatistics
CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool)
CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool)
ResetFolder(folder string)
Availability(folder, file string, version protocol.Vector, block protocol.BlockInfo) []model.Availability
GetIgnores(folder string) ([]string, []string, error)
SetIgnores(folder string, content []string) error
PauseDevice(device protocol.DeviceID)
ResumeDevice(device protocol.DeviceID)
DelayScan(folder string, next time.Duration)
ScanFolder(folder string) error
ScanFolders() map[string]error
ScanFolderSubs(folder string, subs []string) error
BringToFront(folder, file string)
ConnectedTo(deviceID protocol.DeviceID) bool
GlobalSize(folder string) (nfiles, deleted int, bytes int64)
LocalSize(folder string) (nfiles, deleted int, bytes int64)
CurrentLocalVersion(folder string) (int64, bool)
RemoteLocalVersion(folder string) (int64, bool)
State(folder string) (string, time.Time, error)
}
type configIntf interface {
GUI() config.GUIConfiguration
Raw() config.Configuration
Options() config.OptionsConfiguration
Replace(cfg config.Configuration) config.CommitResponse
Subscribe(c config.Committer)
Folders() map[string]config.FolderConfiguration
Devices() map[protocol.DeviceID]config.DeviceConfiguration
Save() error
ListenAddresses() []string
}
type connectionsIntf interface {
Status() map[string]interface{}
}
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, error) {
2015-12-23 08:31:12 -07:00
service := &apiService{
id: id,
cfg: cfg,
httpsCertFile: httpsCertFile,
httpsKeyFile: httpsKeyFile,
assetDir: assetDir,
model: m,
eventSub: eventSub,
discoverer: discoverer,
connectionsService: connectionsService,
systemConfigMut: sync.NewMutex(),
stop: make(chan struct{}),
configChanged: make(chan struct{}),
listenerMut: sync.NewMutex(),
guiErrors: errors,
systemLog: systemLog,
2015-04-28 14:12:19 -07:00
}
2016-01-10 08:37:31 -07:00
seen := make(map[string]struct{})
// Load themes from compiled in assets.
2016-01-10 08:37:31 -07:00
for file := range auto.Assets() {
theme := strings.Split(file, "/")[0]
if _, ok := seen[theme]; !ok {
seen[theme] = struct{}{}
service.themes = append(service.themes, theme)
}
}
if assetDir != "" {
// Load any extra themes from the asset override dir.
for _, dir := range dirNames(assetDir) {
if _, ok := seen[dir]; !ok {
seen[dir] = struct{}{}
service.themes = append(service.themes, dir)
}
}
}
2016-01-10 08:37:31 -07:00
2015-04-28 14:12:19 -07:00
var err error
2015-12-23 08:31:12 -07:00
service.listener, err = service.getListener(cfg.GUI())
return service, err
2015-04-28 14:12:19 -07:00
}
2014-09-12 12:28:47 -07:00
2015-12-23 08:31:12 -07:00
func (s *apiService) getListener(guiCfg config.GUIConfiguration) (net.Listener, error) {
cert, err := tls.LoadX509KeyPair(s.httpsCertFile, s.httpsKeyFile)
2014-09-12 12:28:47 -07:00
if err != nil {
l.Infoln("Loading HTTPS certificate:", err)
l.Infoln("Creating new HTTPS certificate")
// When generating the HTTPS certificate, use the system host name per
// default. If that isn't available, use the "syncthing" default.
var name string
name, err = os.Hostname()
if err != nil {
name = tlsDefaultCommonName
}
cert, err = tlsutil.NewCertificate(s.httpsCertFile, s.httpsKeyFile, name, httpsRSABits)
2014-09-12 12:28:47 -07:00
}
if err != nil {
2015-04-28 14:12:19 -07:00
return nil, err
2014-09-12 12:28:47 -07:00
}
tlsCfg := &tls.Config{
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS10, // No SSLv3
CipherSuites: []uint16{
// No RC4
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA,
tls.TLS_RSA_WITH_3DES_EDE_CBC_SHA,
},
}
rawListener, err := net.Listen("tcp", guiCfg.Address())
2014-09-12 12:28:47 -07:00
if err != nil {
2015-04-28 14:12:19 -07:00
return nil, err
}
2015-04-28 14:12:19 -07:00
listener := &tlsutil.DowngradingListener{
Listener: rawListener,
TLSConfig: tlsCfg,
}
2015-04-28 14:12:19 -07:00
return listener, nil
}
func sendJSON(w http.ResponseWriter, jsonObject interface{}) {
w.Header().Set("Content-Type", "application/json; charset=utf-8")
// Marshalling might fail, in which case we should return a 500 with the
// actual error.
bs, err := json.Marshal(jsonObject)
if err != nil {
// This Marshal() can't fail though.
bs, _ = json.Marshal(map[string]string{"error": err.Error()})
http.Error(w, string(bs), http.StatusInternalServerError)
return
}
w.Write(bs)
}
2015-12-23 08:31:12 -07:00
func (s *apiService) Serve() {
s.listenerMut.Lock()
listener := s.listener
s.listenerMut.Unlock()
if listener == nil {
// Not much we can do here other than exit quickly. The supervisor
// will log an error at some point.
return
}
2014-07-05 12:40:29 -07:00
// The GET handlers
getRestMux := http.NewServeMux()
2015-04-28 14:12:19 -07:00
getRestMux.HandleFunc("/rest/db/completion", s.getDBCompletion) // device folder
getRestMux.HandleFunc("/rest/db/file", s.getDBFile) // folder file
getRestMux.HandleFunc("/rest/db/ignores", s.getDBIgnores) // folder
getRestMux.HandleFunc("/rest/db/need", s.getDBNeed) // folder [perpage] [page]
getRestMux.HandleFunc("/rest/db/status", s.getDBStatus) // folder
getRestMux.HandleFunc("/rest/db/browse", s.getDBBrowse) // folder [prefix] [dirsonly] [levels]
getRestMux.HandleFunc("/rest/events", s.getEvents) // since [limit]
getRestMux.HandleFunc("/rest/stats/device", s.getDeviceStats) // -
getRestMux.HandleFunc("/rest/stats/folder", s.getFolderStats) // -
getRestMux.HandleFunc("/rest/svc/deviceid", s.getDeviceID) // id
getRestMux.HandleFunc("/rest/svc/lang", s.getLang) // -
getRestMux.HandleFunc("/rest/svc/report", s.getReport) // -
getRestMux.HandleFunc("/rest/system/browse", s.getSystemBrowse) // current
getRestMux.HandleFunc("/rest/system/config", s.getSystemConfig) // -
getRestMux.HandleFunc("/rest/system/config/insync", s.getSystemConfigInsync) // -
getRestMux.HandleFunc("/rest/system/connections", s.getSystemConnections) // -
getRestMux.HandleFunc("/rest/system/discovery", s.getSystemDiscovery) // -
getRestMux.HandleFunc("/rest/system/error", s.getSystemError) // -
getRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
getRestMux.HandleFunc("/rest/system/status", s.getSystemStatus) // -
getRestMux.HandleFunc("/rest/system/upgrade", s.getSystemUpgrade) // -
getRestMux.HandleFunc("/rest/system/version", s.getSystemVersion) // -
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
getRestMux.HandleFunc("/rest/system/debug", s.getSystemDebug) // -
getRestMux.HandleFunc("/rest/system/log", s.getSystemLog) // [since]
getRestMux.HandleFunc("/rest/system/log.txt", s.getSystemLogTxt) // [since]
2014-07-05 12:40:29 -07:00
// The POST handlers
postRestMux := http.NewServeMux()
2015-04-28 14:12:19 -07:00
postRestMux.HandleFunc("/rest/db/prio", s.postDBPrio) // folder file [perpage] [page]
postRestMux.HandleFunc("/rest/db/ignores", s.postDBIgnores) // folder
postRestMux.HandleFunc("/rest/db/override", s.postDBOverride) // folder
postRestMux.HandleFunc("/rest/db/scan", s.postDBScan) // folder [sub...] [delay]
2015-04-28 14:12:19 -07:00
postRestMux.HandleFunc("/rest/system/config", s.postSystemConfig) // <body>
postRestMux.HandleFunc("/rest/system/error", s.postSystemError) // <body>
postRestMux.HandleFunc("/rest/system/error/clear", s.postSystemErrorClear) // -
postRestMux.HandleFunc("/rest/system/ping", s.restPing) // -
postRestMux.HandleFunc("/rest/system/reset", s.postSystemReset) // [folder]
postRestMux.HandleFunc("/rest/system/restart", s.postSystemRestart) // -
postRestMux.HandleFunc("/rest/system/shutdown", s.postSystemShutdown) // -
postRestMux.HandleFunc("/rest/system/upgrade", s.postSystemUpgrade) // -
2015-08-23 12:56:10 -07:00
postRestMux.HandleFunc("/rest/system/pause", s.postSystemPause) // device
postRestMux.HandleFunc("/rest/system/resume", s.postSystemResume) // device
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
postRestMux.HandleFunc("/rest/system/debug", s.postSystemDebug) // [enable] [disable]
// Debug endpoints, not for general use
2015-04-28 14:12:19 -07:00
getRestMux.HandleFunc("/rest/debug/peerCompletion", s.getPeerCompletion)
2015-11-21 01:48:57 -07:00
getRestMux.HandleFunc("/rest/debug/httpmetrics", s.getSystemHTTPMetrics)
2014-07-05 12:40:29 -07:00
// A handler that splits requests between the two above and disables
// caching
2015-11-21 01:48:57 -07:00
restMux := noCacheMiddleware(metricsMiddleware(getPostHandler(getRestMux, postRestMux)))
2014-07-05 12:40:29 -07:00
// The main routing handler
mux := http.NewServeMux()
mux.Handle("/rest/", restMux)
2015-04-28 14:12:19 -07:00
mux.HandleFunc("/qr/", s.getQR)
2014-07-05 12:40:29 -07:00
// Serve compiled in assets unless an asset directory was set (for development)
2016-01-10 08:37:31 -07:00
assets := &embeddedStatic{
theme: s.cfg.GUI().Theme,
lastModified: time.Now().Truncate(time.Second), // must truncate, for the wire precision is 1s
2016-01-10 08:37:31 -07:00
mut: sync.NewRWMutex(),
assetDir: s.assetDir,
assets: auto.Assets(),
}
mux.Handle("/", assets)
// Handle the special meta.js path
mux.HandleFunc("/meta.js", s.getJSMetadata)
2016-01-10 08:37:31 -07:00
s.cfg.Subscribe(assets)
guiCfg := s.cfg.GUI()
// Wrap everything in CSRF protection. The /rest prefix should be
// protected, other requests will grant cookies.
handler := csrfMiddleware(s.id.String()[:5], "/rest", guiCfg, mux)
// Add our version and ID as a header to responses
handler = withDetailsMiddleware(s.id, handler)
2014-07-05 12:40:29 -07:00
// Wrap everything in basic auth, if user/password is set.
if len(guiCfg.User) > 0 && len(guiCfg.Password) > 0 {
handler = basicAuthAndSessionMiddleware("sessionid-"+s.id.String()[:5], guiCfg, handler)
2014-07-05 12:40:29 -07:00
}
2014-09-14 15:18:05 -07:00
// Redirect to HTTPS if we are supposed to
if guiCfg.UseTLS() {
2014-09-14 15:18:05 -07:00
handler = redirectToHTTPSMiddleware(handler)
}
2014-09-12 12:28:47 -07:00
// Add the CORS handling
handler = corsMiddleware(handler)
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
handler = debugMiddleware(handler)
2015-04-07 12:45:22 -07:00
srv := http.Server{
Handler: handler,
ReadTimeout: 10 * time.Second,
}
2015-12-23 08:31:12 -07:00
s.fss = newFolderSummaryService(s.cfg, s.model)
defer s.fss.Stop()
2015-04-28 14:12:19 -07:00
s.fss.ServeBackground()
2016-03-06 15:04:12 -07:00
l.Infoln("GUI and API listening on", listener.Addr())
l.Infoln("Access the GUI via the following URL:", guiCfg.URL())
if s.started != nil {
// only set when run by the tests
close(s.started)
}
err := srv.Serve(listener)
// The return could be due to an intentional close. Wait for the stop
// signal before returning. IF there is no stop signal within a second, we
// assume it was unintentional and log the error before retrying.
select {
case <-s.stop:
case <-s.configChanged:
case <-time.After(time.Second):
l.Warnln("API:", err)
}
2015-04-28 14:12:19 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) Stop() {
s.listenerMut.Lock()
listener := s.listener
s.listenerMut.Unlock()
close(s.stop)
// listener may be nil here if we've had a config change to a broken
// configuration, in which case we shouldn't try to close it.
if listener != nil {
listener.Close()
}
}
2015-12-23 08:31:12 -07:00
func (s *apiService) String() string {
return fmt.Sprintf("apiService@%p", s)
}
2015-12-23 08:31:12 -07:00
func (s *apiService) VerifyConfiguration(from, to config.Configuration) error {
return nil
}
2015-12-23 08:31:12 -07:00
func (s *apiService) CommitConfiguration(from, to config.Configuration) bool {
if to.GUI == from.GUI {
return true
}
// Order here is important. We must close the listener to stop Serve(). We
// must create a new listener before Serve() starts again. We can't create
// a new listener on the same port before the previous listener is closed.
// To assist in this little dance the Serve() method will wait for a
// signal on the configChanged channel after the listener has closed.
s.listenerMut.Lock()
defer s.listenerMut.Unlock()
2015-04-28 14:12:19 -07:00
s.listener.Close()
var err error
s.listener, err = s.getListener(to.GUI)
if err != nil {
// Ideally this should be a verification error, but we check it by
// creating a new listener which requires shutting down the previous
// one first, which is too destructive for the VerifyConfiguration
// method.
return false
}
s.configChanged <- struct{}{}
return true
2014-03-02 15:58:14 -07:00
}
2014-07-05 12:40:29 -07:00
func getPostHandler(get, post http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
get.ServeHTTP(w, r)
case "POST":
post.ServeHTTP(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
})
2014-03-02 15:58:14 -07:00
}
2015-04-07 12:45:22 -07:00
func debugMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t0 := time.Now()
h.ServeHTTP(w, r)
if shouldDebugHTTP() {
ms := 1000 * time.Since(t0).Seconds()
// The variable `w` is most likely a *http.response, which we can't do
// much with since it's a non exported type. We can however peek into
// it with reflection to get at the status code and number of bytes
// written.
var status, written int64
if rw := reflect.Indirect(reflect.ValueOf(w)); rw.IsValid() && rw.Kind() == reflect.Struct {
if rf := rw.FieldByName("status"); rf.IsValid() && rf.Kind() == reflect.Int {
status = rf.Int()
}
if rf := rw.FieldByName("written"); rf.IsValid() && rf.Kind() == reflect.Int64 {
written = rf.Int()
}
2015-04-07 12:45:22 -07:00
}
httpl.Debugf("http: %s %q: status %d, %d bytes in %.02f ms", r.Method, r.URL.String(), status, written, ms)
2015-04-07 12:45:22 -07:00
}
})
}
func corsMiddleware(next http.Handler) http.Handler {
// Handle CORS headers and CORS OPTIONS request.
// CORS OPTIONS request are typically sent by browser during AJAX preflight
// when the browser initiate a POST request.
//
// As the OPTIONS request is unauthorized, this handler must be the first
// of the chain (hence added at the end).
//
// See https://www.w3.org/TR/cors/ for details.
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Process OPTIONS requests
if r.Method == "OPTIONS" {
// Only GET/POST Methods are supported
w.Header().Set("Access-Control-Allow-Methods", "GET, POST")
// Only this custom header can be set
w.Header().Set("Access-Control-Allow-Headers", "X-API-Key")
// The request is meant to be cached 10 minutes
w.Header().Set("Access-Control-Max-Age", "600")
// Indicate that no content will be returned
w.WriteHeader(204)
return
}
// For everything else, pass to the next handler
next.ServeHTTP(w, r)
return
})
}
2015-11-21 01:48:57 -07:00
func metricsMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t := metrics.GetOrRegisterTimer(r.URL.Path, nil)
t0 := time.Now()
h.ServeHTTP(w, r)
t.UpdateSince(t0)
})
}
2014-09-14 15:18:05 -07:00
func redirectToHTTPSMiddleware(h http.Handler) http.Handler {
2014-09-12 12:28:47 -07:00
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2014-09-14 15:18:05 -07:00
if r.TLS == nil {
// Redirect HTTP requests to HTTPS
r.URL.Host = r.Host
2014-09-12 12:28:47 -07:00
r.URL.Scheme = "https"
http.Redirect(w, r, r.URL.String(), http.StatusTemporaryRedirect)
2014-09-12 12:28:47 -07:00
} else {
h.ServeHTTP(w, r)
}
})
}
2014-07-05 12:40:29 -07:00
func noCacheMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store")
w.Header().Set("Expires", time.Now().UTC().Format(http.TimeFormat))
w.Header().Set("Pragma", "no-cache")
2014-07-05 12:40:29 -07:00
h.ServeHTTP(w, r)
})
}
func withDetailsMiddleware(id protocol.DeviceID, h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Syncthing-Version", Version)
w.Header().Set("X-Syncthing-ID", id.String())
h.ServeHTTP(w, r)
})
}
2015-12-23 08:31:12 -07:00
func (s *apiService) restPing(w http.ResponseWriter, r *http.Request) {
sendJSON(w, map[string]string{"ping": "pong"})
}
func (s *apiService) getJSMetadata(w http.ResponseWriter, r *http.Request) {
meta, _ := json.Marshal(map[string]string{
"deviceID": s.id.String(),
})
w.Header().Set("Content-Type", "application/javascript")
fmt.Fprintf(w, "var metadata = %s;\n", meta)
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getSystemVersion(w http.ResponseWriter, r *http.Request) {
sendJSON(w, map[string]string{
"version": Version,
"codename": Codename,
"longVersion": LongVersion,
"os": runtime.GOOS,
"arch": runtime.GOARCH,
})
2014-03-02 15:58:14 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getSystemDebug(w http.ResponseWriter, r *http.Request) {
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
names := l.Facilities()
enabled := l.FacilityDebugging()
sort.Strings(enabled)
sendJSON(w, map[string]interface{}{
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
"facilities": names,
"enabled": enabled,
})
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postSystemDebug(w http.ResponseWriter, r *http.Request) {
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
w.Header().Set("Content-Type", "application/json; charset=utf-8")
q := r.URL.Query()
for _, f := range strings.Split(q.Get("enable"), ",") {
if f == "" || l.ShouldDebug(f) {
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
continue
}
l.SetDebug(f, true)
l.Infof("Enabled debug data for %q", f)
}
for _, f := range strings.Split(q.Get("disable"), ",") {
if f == "" || !l.ShouldDebug(f) {
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
continue
}
l.SetDebug(f, false)
l.Infof("Disabled debug data for %q", f)
}
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getDBBrowse(w http.ResponseWriter, r *http.Request) {
2015-02-07 03:52:42 -07:00
qs := r.URL.Query()
folder := qs.Get("folder")
prefix := qs.Get("prefix")
dirsonly := qs.Get("dirsonly") != ""
levels, err := strconv.Atoi(qs.Get("levels"))
if err != nil {
levels = -1
}
sendJSON(w, s.model.GlobalDirectoryTree(folder, prefix, levels, dirsonly))
2015-02-07 03:52:42 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getDBCompletion(w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var folder = qs.Get("folder")
var deviceStr = qs.Get("device")
device, err := protocol.DeviceIDFromString(deviceStr)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
sendJSON(w, map[string]float64{
2015-04-28 14:12:19 -07:00
"completion": s.model.Completion(device, folder),
})
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getDBStatus(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
sendJSON(w, folderSummary(s.cfg, s.model, folder))
}
func folderSummary(cfg configIntf, m modelIntf, folder string) map[string]interface{} {
2014-03-02 15:58:14 -07:00
var res = make(map[string]interface{})
res["invalid"] = cfg.Folders()[folder].Invalid
globalFiles, globalDeleted, globalBytes := m.GlobalSize(folder)
2014-03-02 15:58:14 -07:00
res["globalFiles"], res["globalDeleted"], res["globalBytes"] = globalFiles, globalDeleted, globalBytes
localFiles, localDeleted, localBytes := m.LocalSize(folder)
2014-03-02 15:58:14 -07:00
res["localFiles"], res["localDeleted"], res["localBytes"] = localFiles, localDeleted, localBytes
needFiles, needBytes := m.NeedSize(folder)
res["needFiles"], res["needBytes"] = needFiles, needBytes
2014-03-02 15:58:14 -07:00
res["inSyncFiles"], res["inSyncBytes"] = globalFiles-needFiles, globalBytes-needBytes
2014-03-02 15:58:14 -07:00
var err error
res["state"], res["stateChanged"], err = m.State(folder)
if err != nil {
res["error"] = err.Error()
}
lv, _ := m.CurrentLocalVersion(folder)
rv, _ := m.RemoteLocalVersion(folder)
res["version"] = lv + rv
ignorePatterns, _, _ := m.GetIgnores(folder)
res["ignorePatterns"] = false
for _, line := range ignorePatterns {
if len(line) > 0 && !strings.HasPrefix(line, "//") {
res["ignorePatterns"] = true
break
}
}
return res
2014-03-02 15:58:14 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postDBOverride(w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var folder = qs.Get("folder")
2015-04-28 14:12:19 -07:00
go s.model.Override(folder)
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getDBNeed(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
page, err := strconv.Atoi(qs.Get("page"))
if err != nil || page < 1 {
page = 1
}
perpage, err := strconv.Atoi(qs.Get("perpage"))
if err != nil || perpage < 1 {
perpage = 1 << 16
}
2015-04-28 14:12:19 -07:00
progress, queued, rest, total := s.model.NeedFolderFiles(folder, page, perpage)
// Convert the struct to a more loose structure, and inject the size.
sendJSON(w, map[string]interface{}{
2015-04-28 14:12:19 -07:00
"progress": s.toNeedSlice(progress),
"queued": s.toNeedSlice(queued),
"rest": s.toNeedSlice(rest),
"total": total,
"page": page,
"perpage": perpage,
})
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getSystemConnections(w http.ResponseWriter, r *http.Request) {
sendJSON(w, s.model.ConnectionStats())
2014-03-02 15:58:14 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getDeviceStats(w http.ResponseWriter, r *http.Request) {
sendJSON(w, s.model.DeviceStatistics())
2014-08-21 15:46:34 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getFolderStats(w http.ResponseWriter, r *http.Request) {
sendJSON(w, s.model.FolderStatistics())
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getDBFile(w http.ResponseWriter, r *http.Request) {
2015-03-17 10:51:50 -07:00
qs := r.URL.Query()
folder := qs.Get("folder")
file := qs.Get("file")
gf, gfOk := s.model.CurrentGlobalFile(folder, file)
lf, lfOk := s.model.CurrentFolderFile(folder, file)
if !(gfOk || lfOk) {
// This file for sure does not exist.
http.Error(w, "No such object in the index", http.StatusNotFound)
return
}
2015-03-17 10:51:50 -07:00
av := s.model.Availability(folder, file, protocol.Vector{}, protocol.BlockInfo{})
sendJSON(w, map[string]interface{}{
2015-04-20 06:37:04 -07:00
"global": jsonFileInfo(gf),
"local": jsonFileInfo(lf),
2015-03-17 10:51:50 -07:00
"availability": av,
})
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getSystemConfig(w http.ResponseWriter, r *http.Request) {
sendJSON(w, s.cfg.Raw())
2014-03-02 15:58:14 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postSystemConfig(w http.ResponseWriter, r *http.Request) {
s.systemConfigMut.Lock()
defer s.systemConfigMut.Unlock()
to, err := config.ReadJSON(r.Body, myID)
r.Body.Close()
2014-03-02 15:58:14 -07:00
if err != nil {
l.Warnln("decoding posted config:", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
2014-12-08 08:36:15 -07:00
}
if to.GUI.Password != s.cfg.GUI().Password {
if to.GUI.Password != "" {
hash, err := bcrypt.GenerateFromPassword([]byte(to.GUI.Password), 0)
if err != nil {
2014-12-08 08:36:15 -07:00
l.Warnln("bcrypting password:", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
2014-12-08 08:36:15 -07:00
return
}
2014-12-08 08:36:15 -07:00
to.GUI.Password = string(hash)
2014-06-11 11:04:23 -07:00
}
2014-12-08 08:36:15 -07:00
}
2014-06-11 11:04:23 -07:00
// Fixup usage reporting settings
if curAcc := s.cfg.Options().URAccepted; to.Options.URAccepted > curAcc {
2014-12-08 08:36:15 -07:00
// UR was enabled
to.Options.URAccepted = usageReportVersion
to.Options.URUniqueID = rand.String(8)
} else if to.Options.URAccepted < curAcc {
2014-12-08 08:36:15 -07:00
// UR was disabled
to.Options.URAccepted = -1
to.Options.URUniqueID = ""
2014-03-02 15:58:14 -07:00
}
2014-12-08 08:36:15 -07:00
// Activate and save
resp := s.cfg.Replace(to)
configInSync = !resp.RequiresRestart
s.cfg.Save()
2014-03-02 15:58:14 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getSystemConfigInsync(w http.ResponseWriter, r *http.Request) {
sendJSON(w, map[string]bool{"configInSync": configInSync})
2014-03-02 15:58:14 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postSystemRestart(w http.ResponseWriter, r *http.Request) {
2015-04-28 14:12:19 -07:00
s.flushResponse(`{"ok": "restarting"}`, w)
go restart()
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postSystemReset(w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
folder := qs.Get("folder")
if len(folder) > 0 {
if _, ok := s.cfg.Folders()[folder]; !ok {
http.Error(w, "Invalid folder ID", 500)
return
2015-06-04 05:35:03 -07:00
}
}
if len(folder) == 0 {
// Reset all folders.
for folder := range s.cfg.Folders() {
s.model.ResetFolder(folder)
}
2015-04-28 14:12:19 -07:00
s.flushResponse(`{"ok": "resetting database"}`, w)
} else {
// Reset a specific folder, assuming it's supposed to exist.
s.model.ResetFolder(folder)
s.flushResponse(`{"ok": "resetting folder `+folder+`"}`, w)
}
go restart()
2014-03-02 15:58:14 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postSystemShutdown(w http.ResponseWriter, r *http.Request) {
2015-04-28 14:12:19 -07:00
s.flushResponse(`{"ok": "shutting down"}`, w)
2014-05-11 16:16:27 -07:00
go shutdown()
}
2015-12-23 08:31:12 -07:00
func (s *apiService) flushResponse(resp string, w http.ResponseWriter) {
2015-04-28 14:12:19 -07:00
w.Write([]byte(resp + "\n"))
2014-05-12 17:15:18 -07:00
f := w.(http.Flusher)
f.Flush()
}
2014-04-14 03:02:40 -07:00
var cpuUsagePercent [10]float64 // The last ten seconds
2015-04-28 13:32:10 -07:00
var cpuUsageLock = sync.NewRWMutex()
2014-03-02 15:58:14 -07:00
2015-12-23 08:31:12 -07:00
func (s *apiService) getSystemStatus(w http.ResponseWriter, r *http.Request) {
2014-03-02 15:58:14 -07:00
var m runtime.MemStats
runtime.ReadMemStats(&m)
tilde, _ := osutil.ExpandTilde("~")
2014-03-02 15:58:14 -07:00
res := make(map[string]interface{})
res["myID"] = myID.String()
2014-03-02 15:58:14 -07:00
res["goroutines"] = runtime.NumGoroutine()
res["alloc"] = m.Alloc
res["sys"] = m.Sys - m.HeapReleased
res["tilde"] = tilde
if s.cfg.Options().LocalAnnEnabled || s.cfg.Options().GlobalAnnEnabled {
res["discoveryEnabled"] = true
discoErrors := make(map[string]string)
discoMethods := 0
for disco, err := range s.discoverer.ChildErrors() {
discoMethods++
if err != nil {
discoErrors[disco] = err.Error()
}
}
res["discoveryMethods"] = discoMethods
res["discoveryErrors"] = discoErrors
}
res["connectionServiceStatus"] = s.connectionsService.Status()
2014-03-02 15:58:14 -07:00
cpuUsageLock.RLock()
2014-04-14 03:02:40 -07:00
var cpusum float64
for _, p := range cpuUsagePercent {
cpusum += p
}
2014-03-02 15:58:14 -07:00
cpuUsageLock.RUnlock()
2015-02-25 15:30:24 -07:00
res["cpuPercent"] = cpusum / float64(len(cpuUsagePercent)) / float64(runtime.NumCPU())
res["pathSeparator"] = string(filepath.Separator)
2015-04-03 11:00:13 -07:00
res["uptime"] = int(time.Since(startTime).Seconds())
res["startTime"] = startTime
2016-01-10 08:37:31 -07:00
res["themes"] = s.themes
2014-03-02 15:58:14 -07:00
sendJSON(w, res)
2014-03-02 15:58:14 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getSystemError(w http.ResponseWriter, r *http.Request) {
sendJSON(w, map[string][]logger.Line{
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
"errors": s.guiErrors.Since(time.Time{}),
})
2014-03-02 15:58:14 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postSystemError(w http.ResponseWriter, r *http.Request) {
2014-07-05 12:40:29 -07:00
bs, _ := ioutil.ReadAll(r.Body)
r.Body.Close()
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
l.Warnln(string(bs))
2014-03-02 15:58:14 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postSystemErrorClear(w http.ResponseWriter, r *http.Request) {
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
s.guiErrors.Clear()
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getSystemLog(w http.ResponseWriter, r *http.Request) {
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
q := r.URL.Query()
since, err := time.Parse(time.RFC3339, q.Get("since"))
l.Debugln(err)
sendJSON(w, map[string][]logger.Line{
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
"messages": s.systemLog.Since(since),
})
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getSystemLogTxt(w http.ResponseWriter, r *http.Request) {
Implement facility based logger, debugging via REST API This implements a new debug/trace infrastructure based on a slightly hacked up logger. Instead of the traditional "if debug { ... }" I've rewritten the logger to have no-op Debugln and Debugf, unless debugging has been enabled for a given "facility". The "facility" is just a string, typically a package name. This will be slightly slower than before; but not that much as it's mostly a function call that returns immediately. For the cases where it matters (the Debugln takes a hex.Dump() of something for example, and it's not in a very occasional "if err != nil" branch) there is an l.ShouldDebug(facility) that is fast enough to be used like the old "if debug". The point of all this is that we can now toggle debugging for the various packages on and off at runtime. There's a new method /rest/system/debug that can be POSTed a set of facilities to enable and disable debug for, or GET from to get a list of facilities with descriptions and their current debug status. Similarly a /rest/system/log?since=... can grab the latest log entries, up to 250 of them (hardcoded constant in main.go) plus the initial few. Not implemented in this commit (but planned) is a simple debug GUI available on /debug that shows the current log in an easily pasteable format and has checkboxes to enable the various debug facilities. The debug instructions to a user then becomes "visit this URL, check these boxes, reproduce your problem, copy and paste the log". The actual log viewer on the hypothetical /debug URL can poll regularly for new log entries and this bypass the 250 line limit. The existing STTRACE=foo variable is still obeyed and just sets the start state of the system.
2015-10-03 08:25:21 -07:00
q := r.URL.Query()
since, err := time.Parse(time.RFC3339, q.Get("since"))
l.Debugln(err)
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
for _, line := range s.systemLog.Since(since) {
fmt.Fprintf(w, "%s: %s\n", line.When.Format(time.RFC3339), line.Message)
2014-03-02 15:58:14 -07:00
}
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getSystemHTTPMetrics(w http.ResponseWriter, r *http.Request) {
2015-11-21 01:48:57 -07:00
stats := make(map[string]interface{})
metrics.Each(func(name string, intf interface{}) {
if m, ok := intf.(*metrics.StandardTimer); ok {
pct := m.Percentiles([]float64{0.50, 0.95, 0.99})
for i := range pct {
pct[i] /= 1e6 // ns to ms
}
stats[name] = map[string]interface{}{
"count": m.Count(),
"sumMs": m.Sum() / 1e6, // ns to ms
"ratesPerS": []float64{m.Rate1(), m.Rate5(), m.Rate15()},
"percentilesMs": pct,
}
}
})
bs, _ := json.MarshalIndent(stats, "", " ")
w.Write(bs)
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getSystemDiscovery(w http.ResponseWriter, r *http.Request) {
devices := make(map[string]discover.CacheEntry)
2015-09-12 12:59:15 -07:00
if s.discoverer != nil {
// Device ids can't be marshalled as keys so we need to manually
// rebuild this map using strings. Discoverer may be nil if discovery
// has not started yet.
for device, entry := range s.discoverer.Cache() {
devices[device.String()] = entry
}
}
sendJSON(w, devices)
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getReport(w http.ResponseWriter, r *http.Request) {
sendJSON(w, reportData(s.cfg, s.model))
2014-06-11 11:04:23 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getDBIgnores(w http.ResponseWriter, r *http.Request) {
2014-09-15 15:12:29 -07:00
qs := r.URL.Query()
2015-04-28 14:12:19 -07:00
ignores, patterns, err := s.model.GetIgnores(qs.Get("folder"))
2014-09-15 15:12:29 -07:00
if err != nil {
http.Error(w, err.Error(), 500)
return
}
sendJSON(w, map[string][]string{
"ignore": ignores,
"expanded": patterns,
2014-09-15 15:12:29 -07:00
})
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postDBIgnores(w http.ResponseWriter, r *http.Request) {
2014-09-15 15:12:29 -07:00
qs := r.URL.Query()
bs, err := ioutil.ReadAll(r.Body)
2014-09-15 15:12:29 -07:00
r.Body.Close()
if err != nil {
http.Error(w, err.Error(), 500)
return
}
2014-09-19 13:02:53 -07:00
var data map[string][]string
err = json.Unmarshal(bs, &data)
2014-09-15 15:12:29 -07:00
if err != nil {
http.Error(w, err.Error(), 500)
return
}
2015-04-28 14:12:19 -07:00
err = s.model.SetIgnores(qs.Get("folder"), data["ignore"])
2014-09-15 15:12:29 -07:00
if err != nil {
http.Error(w, err.Error(), 500)
return
}
2015-04-28 14:12:19 -07:00
s.getDBIgnores(w, r)
2014-09-15 15:12:29 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getEvents(w http.ResponseWriter, r *http.Request) {
2014-07-13 12:07:24 -07:00
qs := r.URL.Query()
sinceStr := qs.Get("since")
limitStr := qs.Get("limit")
since, _ := strconv.Atoi(sinceStr)
limit, _ := strconv.Atoi(limitStr)
2015-04-28 14:12:19 -07:00
s.fss.gotEventRequest()
// Flush before blocking, to indicate that we've received the request and
// that it should not be retried. Must set Content-Type header before
// flushing.
w.Header().Set("Content-Type", "application/json; charset=utf-8")
f := w.(http.Flusher)
f.Flush()
evs := s.eventSub.Since(since, nil)
if 0 < limit && limit < len(evs) {
evs = evs[len(evs)-limit:]
}
2014-07-13 12:07:24 -07:00
sendJSON(w, evs)
2014-07-13 12:07:24 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getSystemUpgrade(w http.ResponseWriter, r *http.Request) {
if noUpgrade {
http.Error(w, upgrade.ErrUpgradeUnsupported.Error(), 500)
return
}
rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version)
2014-07-14 01:45:29 -07:00
if err != nil {
http.Error(w, err.Error(), 500)
return
}
res := make(map[string]interface{})
res["running"] = Version
res["latest"] = rel.Tag
2015-04-22 05:41:08 -07:00
res["newer"] = upgrade.CompareVersions(rel.Tag, Version) == upgrade.Newer
res["majorNewer"] = upgrade.CompareVersions(rel.Tag, Version) == upgrade.MajorNewer
2014-07-14 01:45:29 -07:00
sendJSON(w, res)
2014-07-14 01:45:29 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getDeviceID(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
idStr := qs.Get("id")
id, err := protocol.DeviceIDFromString(idStr)
if err == nil {
sendJSON(w, map[string]string{
"id": id.String(),
})
} else {
sendJSON(w, map[string]string{
"error": err.Error(),
})
}
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getLang(w http.ResponseWriter, r *http.Request) {
2014-07-26 13:30:29 -07:00
lang := r.Header.Get("Accept-Language")
var langs []string
for _, l := range strings.Split(lang, ",") {
parts := strings.SplitN(l, ";", 2)
langs = append(langs, strings.ToLower(strings.TrimSpace(parts[0])))
2014-07-26 13:30:29 -07:00
}
sendJSON(w, langs)
2014-07-26 13:30:29 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postSystemUpgrade(w http.ResponseWriter, r *http.Request) {
rel, err := upgrade.LatestRelease(s.cfg.Options().ReleasesURL, Version)
2014-07-14 01:45:29 -07:00
if err != nil {
l.Warnln("getting latest release:", err)
2014-07-14 01:45:29 -07:00
http.Error(w, err.Error(), 500)
return
}
2015-04-22 05:41:08 -07:00
if upgrade.CompareVersions(rel.Tag, Version) > upgrade.Equal {
2014-12-08 08:36:15 -07:00
err = upgrade.To(rel)
2014-07-31 07:01:23 -07:00
if err != nil {
l.Warnln("upgrading:", err)
2014-07-31 07:01:23 -07:00
http.Error(w, err.Error(), 500)
return
}
2015-04-28 14:12:19 -07:00
s.flushResponse(`{"ok": "restarting"}`, w)
l.Infoln("Upgrading")
stop <- exitUpgrading
2014-07-31 07:01:23 -07:00
}
2014-07-14 01:45:29 -07:00
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postSystemPause(w http.ResponseWriter, r *http.Request) {
2015-08-23 12:56:10 -07:00
var qs = r.URL.Query()
var deviceStr = qs.Get("device")
device, err := protocol.DeviceIDFromString(deviceStr)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
s.model.PauseDevice(device)
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postSystemResume(w http.ResponseWriter, r *http.Request) {
2015-08-23 12:56:10 -07:00
var qs = r.URL.Query()
var deviceStr = qs.Get("device")
device, err := protocol.DeviceIDFromString(deviceStr)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
s.model.ResumeDevice(device)
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postDBScan(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
if folder != "" {
2015-05-03 05:18:50 -07:00
nextStr := qs.Get("next")
next, err := strconv.Atoi(nextStr)
if err == nil {
s.model.DelayScan(folder, time.Duration(next)*time.Second)
}
subs := qs["sub"]
2015-05-03 05:18:50 -07:00
err = s.model.ScanFolderSubs(folder, subs)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
} else {
2015-04-28 14:12:19 -07:00
errors := s.model.ScanFolders()
if len(errors) > 0 {
http.Error(w, "Error scanning folders", 500)
sendJSON(w, errors)
return
}
}
}
2015-12-23 08:31:12 -07:00
func (s *apiService) postDBPrio(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
file := qs.Get("file")
2015-04-28 14:12:19 -07:00
s.model.BringToFront(folder, file)
s.getDBNeed(w, r)
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getQR(w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var text = qs.Get("text")
2014-07-05 12:40:29 -07:00
code, err := qr.Encode(text, qr.M)
if err != nil {
http.Error(w, "Invalid", 500)
return
}
w.Header().Set("Content-Type", "image/png")
w.Write(code.PNG())
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
tot := map[string]float64{}
count := map[string]float64{}
for _, folder := range s.cfg.Folders() {
for _, device := range folder.DeviceIDs() {
deviceStr := device.String()
2015-04-28 14:12:19 -07:00
if s.model.ConnectedTo(device) {
tot[deviceStr] += s.model.Completion(device, folder.ID)
} else {
tot[deviceStr] = 0
}
count[deviceStr]++
}
}
comp := map[string]int{}
for device := range tot {
comp[device] = int(tot[device] / count[device])
}
sendJSON(w, comp)
}
2015-12-23 08:31:12 -07:00
func (s *apiService) getSystemBrowse(w http.ResponseWriter, r *http.Request) {
2014-11-16 12:30:49 -07:00
qs := r.URL.Query()
current := qs.Get("current")
search, _ := osutil.ExpandTilde(current)
pathSeparator := string(os.PathSeparator)
if strings.HasSuffix(current, pathSeparator) && !strings.HasSuffix(search, pathSeparator) {
search = search + pathSeparator
}
subdirectories, _ := osutil.Glob(search + "*")
2014-11-16 12:30:49 -07:00
ret := make([]string, 0, 10)
for _, subdirectory := range subdirectories {
info, err := os.Stat(subdirectory)
if err == nil && info.IsDir() {
ret = append(ret, subdirectory+pathSeparator)
2014-11-16 12:30:49 -07:00
if len(ret) > 9 {
break
}
}
}
sendJSON(w, ret)
2014-11-16 12:30:49 -07:00
}
2015-04-28 14:12:19 -07:00
type embeddedStatic struct {
2016-01-10 08:37:31 -07:00
theme string
lastModified time.Time
mut sync.RWMutex
assetDir string
assets map[string][]byte
2015-04-28 14:12:19 -07:00
}
2015-04-28 14:12:19 -07:00
func (s embeddedStatic) ServeHTTP(w http.ResponseWriter, r *http.Request) {
file := r.URL.Path
2015-04-28 14:12:19 -07:00
if file[0] == '/' {
file = file[1:]
}
2015-04-28 14:12:19 -07:00
if len(file) == 0 {
file = "index.html"
}
s.mut.RLock()
theme := s.theme
modified := s.lastModified
s.mut.RUnlock()
// Check for an override for the current theme.
2015-04-28 14:12:19 -07:00
if s.assetDir != "" {
p := filepath.Join(s.assetDir, s.theme, filepath.FromSlash(file))
if _, err := os.Stat(p); err == nil {
2015-04-28 14:12:19 -07:00
http.ServeFile(w, r, p)
return
}
2015-04-28 14:12:19 -07:00
}
// Check for a compiled in asset for the current theme.
2016-01-10 08:37:31 -07:00
bs, ok := s.assets[theme+"/"+file]
2015-04-28 14:12:19 -07:00
if !ok {
// Check for an overridden default asset.
if s.assetDir != "" {
p := filepath.Join(s.assetDir, config.DefaultTheme, filepath.FromSlash(file))
if _, err := os.Stat(p); err == nil {
http.ServeFile(w, r, p)
return
}
}
// Check for a compiled in default asset.
2016-01-10 08:37:31 -07:00
bs, ok = s.assets[config.DefaultTheme+"/"+file]
if !ok {
http.NotFound(w, r)
return
}
2015-04-28 14:12:19 -07:00
}
2014-07-05 12:40:29 -07:00
modifiedSince, err := http.ParseTime(r.Header.Get("If-Modified-Since"))
if err == nil && !modified.After(modifiedSince) {
w.WriteHeader(http.StatusNotModified)
return
}
2015-04-28 14:12:19 -07:00
mtype := s.mimeTypeForFile(file)
if len(mtype) != 0 {
w.Header().Set("Content-Type", mtype)
}
if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
w.Header().Set("Content-Encoding", "gzip")
} else {
// ungzip if browser not send gzip accepted header
var gr *gzip.Reader
gr, _ = gzip.NewReader(bytes.NewReader(bs))
bs, _ = ioutil.ReadAll(gr)
gr.Close()
}
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(bs)))
w.Header().Set("Last-Modified", modified.UTC().Format(http.TimeFormat))
w.Header().Set("Cache-Control", "public")
2015-04-28 14:12:19 -07:00
w.Write(bs)
}
2015-04-28 14:12:19 -07:00
func (s embeddedStatic) mimeTypeForFile(file string) string {
// We use a built in table of the common types since the system
// TypeByExtension might be unreliable. But if we don't know, we delegate
// to the system.
ext := filepath.Ext(file)
switch ext {
case ".htm", ".html":
return "text/html"
case ".css":
return "text/css"
case ".js":
return "application/javascript"
case ".json":
return "application/json"
case ".png":
return "image/png"
case ".ttf":
return "application/x-font-ttf"
2014-09-03 23:47:23 -07:00
case ".woff":
return "application/x-font-woff"
case ".svg":
return "image/svg+xml"
default:
return mime.TypeByExtension(ext)
}
}
2016-01-10 08:37:31 -07:00
// VerifyConfiguration implements the config.Committer interface
func (s *embeddedStatic) VerifyConfiguration(from, to config.Configuration) error {
return nil
}
// CommitConfiguration implements the config.Committer interface
func (s *embeddedStatic) CommitConfiguration(from, to config.Configuration) bool {
s.mut.Lock()
if s.theme != to.GUI.Theme {
s.theme = to.GUI.Theme
s.lastModified = time.Now()
}
s.mut.Unlock()
return true
}
func (s *embeddedStatic) String() string {
return fmt.Sprintf("embeddedStatic@%p", s)
}
2015-12-23 08:31:12 -07:00
func (s *apiService) toNeedSlice(fs []db.FileInfoTruncated) []jsonDBFileInfo {
2015-04-20 06:37:04 -07:00
res := make([]jsonDBFileInfo, len(fs))
for i, f := range fs {
res[i] = jsonDBFileInfo(f)
}
return res
}
// Type wrappers for nice JSON serialization
type jsonFileInfo protocol.FileInfo
func (f jsonFileInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": f.Name,
"size": protocol.FileInfo(f).Size(),
"flags": fmt.Sprintf("%#o", f.Flags),
"modified": time.Unix(f.Modified, 0),
"localVersion": f.LocalVersion,
"numBlocks": len(f.Blocks),
"version": jsonVersionVector(f.Version),
})
}
type jsonDBFileInfo db.FileInfoTruncated
func (f jsonDBFileInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(map[string]interface{}{
"name": f.Name,
"size": db.FileInfoTruncated(f).Size(),
"flags": fmt.Sprintf("%#o", f.Flags),
"modified": time.Unix(f.Modified, 0),
"localVersion": f.LocalVersion,
"version": jsonVersionVector(f.Version),
})
}
type jsonVersionVector protocol.Vector
func (v jsonVersionVector) MarshalJSON() ([]byte, error) {
res := make([]string, len(v))
for i, c := range v {
res[i] = fmt.Sprintf("%v:%d", c.ID, c.Value)
}
2015-04-20 06:37:04 -07:00
return json.Marshal(res)
}
func dirNames(dir string) []string {
fd, err := os.Open(dir)
if err != nil {
return nil
}
defer fd.Close()
fis, err := fd.Readdir(-1)
if err != nil {
return nil
}
var dirs []string
for _, fi := range fis {
if fi.IsDir() {
dirs = append(dirs, filepath.Base(fi.Name()))
}
}
sort.Strings(dirs)
return dirs
}