diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index 6fe6398d..45721169 100644 --- a/cmd/syncthing/gui.go +++ b/cmd/syncthing/gui.go @@ -53,6 +53,7 @@ type apiService struct { statics *staticsServer model modelIntf eventSub events.BufferedSubscription + diskEventSub events.BufferedSubscription discoverer discover.CachingMux connectionsService connectionsIntf fss *folderSummaryService @@ -113,7 +114,7 @@ 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 { +func newAPIService(id protocol.DeviceID, cfg configIntf, httpsCertFile, httpsKeyFile, assetDir string, m modelIntf, eventSub events.BufferedSubscription, diskEventSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService connectionsIntf, errors, systemLog logger.Recorder) *apiService { service := &apiService{ id: id, cfg: cfg, @@ -122,6 +123,7 @@ func newAPIService(id protocol.DeviceID, cfg configIntf, httpsCertFile, httpsKey statics: newStaticsServer(cfg.GUI().Theme, assetDir), model: m, eventSub: eventSub, + diskEventSub: diskEventSub, discoverer: discoverer, connectionsService: connectionsService, systemConfigMut: sync.NewMutex(), @@ -229,7 +231,8 @@ func (s *apiService) Serve() { 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/events", s.getIndexEvents) // since [limit] + getRestMux.HandleFunc("/rest/events/disk", s.getDiskEvents) // since [limit] getRestMux.HandleFunc("/rest/stats/device", s.getDeviceStats) // - getRestMux.HandleFunc("/rest/stats/folder", s.getFolderStats) // - getRestMux.HandleFunc("/rest/svc/deviceid", s.getDeviceID) // id @@ -993,15 +996,22 @@ func (s *apiService) postDBIgnores(w http.ResponseWriter, r *http.Request) { s.getDBIgnores(w, r) } -func (s *apiService) getEvents(w http.ResponseWriter, r *http.Request) { +func (s *apiService) getIndexEvents(w http.ResponseWriter, r *http.Request) { + s.fss.gotEventRequest() + s.getEvents(w, r, s.eventSub) +} + +func (s *apiService) getDiskEvents(w http.ResponseWriter, r *http.Request) { + s.getEvents(w, r, s.diskEventSub) +} + +func (s *apiService) getEvents(w http.ResponseWriter, r *http.Request, eventSub events.BufferedSubscription) { qs := r.URL.Query() sinceStr := qs.Get("since") limitStr := qs.Get("limit") since, _ := strconv.Atoi(sinceStr) limit, _ := strconv.Atoi(limitStr) - 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. @@ -1009,7 +1019,7 @@ func (s *apiService) getEvents(w http.ResponseWriter, r *http.Request) { f := w.(http.Flusher) f.Flush() - evs := s.eventSub.Since(since, nil) + evs := eventSub.Since(since, nil) if 0 < limit && limit < len(evs) { evs = evs[len(evs)-limit:] } diff --git a/cmd/syncthing/gui_test.go b/cmd/syncthing/gui_test.go index 1bc26c8d..961e66ca 100644 --- a/cmd/syncthing/gui_test.go +++ b/cmd/syncthing/gui_test.go @@ -70,7 +70,7 @@ func TestStopAfterBrokenConfig(t *testing.T) { } w := config.Wrap("/dev/null", cfg) - srv := newAPIService(protocol.LocalDeviceID, w, "../../test/h1/https-cert.pem", "../../test/h1/https-key.pem", "", nil, nil, nil, nil, nil, nil) + srv := newAPIService(protocol.LocalDeviceID, w, "../../test/h1/https-cert.pem", "../../test/h1/https-key.pem", "", nil, nil, nil, nil, nil, nil, nil) srv.started = make(chan string) sup := suture.NewSimple("test") @@ -469,6 +469,7 @@ func startHTTP(cfg *mockedConfig) (string, error) { httpsKeyFile := "../../test/h1/https-key.pem" assetDir := "../../gui" eventSub := new(mockedEventSub) + diskEventSub := new(mockedEventSub) discoverer := new(mockedCachingMux) connections := new(mockedConnections) errorLog := new(mockedLoggerRecorder) @@ -477,7 +478,7 @@ func startHTTP(cfg *mockedConfig) (string, error) { // Instantiate the API service svc := newAPIService(protocol.LocalDeviceID, cfg, httpsCertFile, httpsKeyFile, assetDir, model, - eventSub, discoverer, connections, errorLog, systemLog) + eventSub, diskEventSub, discoverer, connections, errorLog, systemLog) svc.started = addrChan // Actually start the API service diff --git a/cmd/syncthing/main.go b/cmd/syncthing/main.go index 563bafaa..b56bf479 100644 --- a/cmd/syncthing/main.go +++ b/cmd/syncthing/main.go @@ -552,6 +552,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) { // events. The LocalChangeDetected event might overwhelm the event // receiver in some situations so we will not subscribe to it here. apiSub := events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents&^events.LocalChangeDetected), 1000) + diskSub := events.NewBufferedSubscription(events.Default.Subscribe(events.LocalChangeDetected), 1000) if len(os.Getenv("GOMAXPROCS")) == 0 { runtime.GOMAXPROCS(runtime.NumCPU()) @@ -752,7 +753,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) { // GUI - setupGUI(mainService, cfg, m, apiSub, cachedDiscovery, connectionsService, errors, systemLog, runtimeOptions) + setupGUI(mainService, cfg, m, apiSub, diskSub, cachedDiscovery, connectionsService, errors, systemLog, runtimeOptions) if runtimeOptions.cpuProfile { f, err := os.Create(fmt.Sprintf("cpu-%d.pprof", os.Getpid())) @@ -928,7 +929,7 @@ func startAuditing(mainService *suture.Supervisor) { l.Infoln("Audit log in", auditFile) } -func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService *connections.Service, errors, systemLog logger.Recorder, runtimeOptions RuntimeOptions) { +func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Model, apiSub events.BufferedSubscription, diskSub events.BufferedSubscription, discoverer discover.CachingMux, connectionsService *connections.Service, errors, systemLog logger.Recorder, runtimeOptions RuntimeOptions) { guiCfg := cfg.GUI() if !guiCfg.Enabled { @@ -939,7 +940,7 @@ func setupGUI(mainService *suture.Supervisor, cfg *config.Wrapper, m *model.Mode l.Warnln("Insecure admin access is enabled.") } - api := newAPIService(myID, cfg, locations[locHTTPSCertFile], locations[locHTTPSKeyFile], runtimeOptions.assetDir, m, apiSub, discoverer, connectionsService, errors, systemLog) + api := newAPIService(myID, cfg, locations[locHTTPSCertFile], locations[locHTTPSKeyFile], runtimeOptions.assetDir, m, apiSub, diskSub, discoverer, connectionsService, errors, systemLog) cfg.Subscribe(api) mainService.Add(api) diff --git a/lib/model/model.go b/lib/model/model.go index dabb53f2..fa4176e5 100644 --- a/lib/model/model.go +++ b/lib/model/model.go @@ -1465,12 +1465,12 @@ func sendIndexTo(minSequence int64, conn protocol.Connection, folder string, fs func (m *Model) updateLocalsFromScanning(folder string, fs []protocol.FileInfo) { m.updateLocals(folder, fs) - // Fire the LocalChangeDetected event to notify listeners about local - // updates. m.fmut.RLock() - path := m.folderCfgs[folder].Path() + folderCfg := m.folderCfgs[folder] m.fmut.RUnlock() - m.localChangeDetected(folder, path, fs) + + // Fire the LocalChangeDetected event to notify listeners about local updates. + m.localChangeDetected(folderCfg, fs) } func (m *Model) updateLocalsFromPulling(folder string, fs []protocol.FileInfo) { @@ -1500,9 +1500,8 @@ func (m *Model) updateLocals(folder string, fs []protocol.FileInfo) { }) } -func (m *Model) localChangeDetected(folder, path string, files []protocol.FileInfo) { - // For windows paths, strip unwanted chars from the front - path = strings.Replace(path, `\\?\`, "", 1) +func (m *Model) localChangeDetected(folderCfg config.FolderConfiguration, files []protocol.FileInfo) { + path := strings.Replace(folderCfg.Path(), `\\?\`, "", 1) for _, file := range files { objType := "file" @@ -1526,14 +1525,16 @@ func (m *Model) localChangeDetected(folder, path string, files []protocol.FileIn action = "deleted" } - // The full file path, adjusted to the local path separator character. + // The full file path, adjusted to the local path separator character. Also + // for windows paths, strip unwanted chars from the front. path := filepath.Join(path, filepath.FromSlash(file.Name)) events.Default.Log(events.LocalChangeDetected, map[string]string{ - "folder": folder, - "action": action, - "type": objType, - "path": path, + "folderID": folderCfg.ID, + "label": folderCfg.Label, + "action": action, + "type": objType, + "path": path, }) } } diff --git a/test/http_test.go b/test/http_test.go index f5dc8244..51b40841 100644 --- a/test/http_test.go +++ b/test/http_test.go @@ -27,6 +27,7 @@ var jsonEndpoints = []string{ "/rest/db/status?folder=default", "/rest/db/browse?folder=default", "/rest/events?since=-1&limit=5", + "/rest/events/disk?since=-1&limit=5", "/rest/stats/device", "/rest/stats/folder", "/rest/svc/deviceid?id=I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU",