diff --git a/cmd/syncthing/summarysvc.go b/cmd/syncthing/summarysvc.go index 9120e524e..8f252e4ab 100644 --- a/cmd/syncthing/summarysvc.go +++ b/cmd/syncthing/summarysvc.go @@ -18,9 +18,10 @@ import ( // The folderSummarySvc adds summary information events (FolderSummary and // FolderCompletion) into the event stream at certain intervals. type folderSummarySvc struct { - model *model.Model - srv suture.Service - stop chan struct{} + model *model.Model + srv suture.Service + stop chan struct{} + immediate chan string // For keeping track of folders to recalculate for foldersMut sync.Mutex @@ -32,6 +33,7 @@ func (c *folderSummarySvc) Serve() { srv.Add(serviceFunc(c.listenForUpdates)) srv.Add(serviceFunc(c.calculateSummaries)) + c.immediate = make(chan string) c.stop = make(chan struct{}) c.folders = make(map[string]struct{}) c.srv = srv @@ -50,7 +52,7 @@ func (c *folderSummarySvc) Stop() { // listenForUpdates subscribes to the event bus and makes note of folders that // need their data recalculated. func (c *folderSummarySvc) listenForUpdates() { - sub := events.Default.Subscribe(events.LocalIndexUpdated | events.RemoteIndexUpdated) + sub := events.Default.Subscribe(events.LocalIndexUpdated | events.RemoteIndexUpdated | events.StateChanged) defer events.Default.Unsubscribe(sub) for { @@ -63,9 +65,29 @@ func (c *folderSummarySvc) listenForUpdates() { data := ev.Data.(map[string]interface{}) folder := data["folder"].(string) - c.foldersMut.Lock() - c.folders[folder] = struct{}{} - c.foldersMut.Unlock() + + if ev.Type == events.StateChanged && data["to"].(string) == "idle" && data["from"].(string) == "syncing" { + // The folder changed to idle from syncing. We should do an + // immediate refresh to update the GUI. The send to + // c.immediate must be nonblocking so that we can continue + // handling events. + + select { + case c.immediate <- folder: + c.foldersMut.Lock() + delete(c.folders, folder) + c.foldersMut.Unlock() + + default: + } + } else { + // This folder needs to be refreshed whenever we do the next + // refresh. + + c.foldersMut.Lock() + c.folders[folder] = struct{}{} + c.foldersMut.Unlock() + } case <-c.stop: return @@ -82,48 +104,9 @@ func (c *folderSummarySvc) calculateSummaries() { for { select { case <-pump.C: - // We only recalculate sumamries if someone is listening to events - // (a request to /rest/events has been made within the last - // pingEventInterval). - - lastEventRequestMut.Lock() - // XXX: Reaching out to a global var here is very ugly :( Should - // we make the gui stuff a proper object with methods on it that - // we can query about this kind of thing? - last := lastEventRequest - lastEventRequestMut.Unlock() - t0 := time.Now() - if time.Since(last) < pingEventInterval { - for _, folder := range c.foldersToHandle() { - // The folder summary contains how many bytes, files etc - // are in the folder and how in sync we are. - data := folderSummary(c.model, folder) - events.Default.Log(events.FolderSummary, map[string]interface{}{ - "folder": folder, - "summary": data, - }) - - for _, devCfg := range cfg.Folders()[folder].Devices { - if devCfg.DeviceID.Equals(myID) { - // We already know about ourselves. - continue - } - if !c.model.ConnectedTo(devCfg.DeviceID) { - // We're not interested in disconnected devices. - continue - } - - // Get completion percentage of this folder for the - // remote device. - comp := c.model.Completion(devCfg.DeviceID, folder) - events.Default.Log(events.FolderCompletion, map[string]interface{}{ - "folder": folder, - "device": devCfg.DeviceID.String(), - "completion": comp, - }) - } - } + for _, folder := range c.foldersToHandle() { + c.sendSummary(folder) } // We don't want to spend all our time calculating summaries. Lets @@ -132,6 +115,9 @@ func (c *folderSummarySvc) calculateSummaries() { wait := 2*time.Since(t0) + pumpInterval pump.Reset(wait) + case folder := <-c.immediate: + c.sendSummary(folder) + case <-c.stop: return } @@ -141,6 +127,20 @@ func (c *folderSummarySvc) calculateSummaries() { // foldersToHandle returns the list of folders needing a summary update, and // clears the list. func (c *folderSummarySvc) foldersToHandle() []string { + // We only recalculate sumamries if someone is listening to events + // (a request to /rest/events has been made within the last + // pingEventInterval). + + lastEventRequestMut.Lock() + // XXX: Reaching out to a global var here is very ugly :( Should + // we make the gui stuff a proper object with methods on it that + // we can query about this kind of thing? + last := lastEventRequest + lastEventRequestMut.Unlock() + if time.Since(last) < pingEventInterval { + return nil + } + c.foldersMut.Lock() res := make([]string, 0, len(c.folders)) for folder := range c.folders { @@ -151,6 +151,37 @@ func (c *folderSummarySvc) foldersToHandle() []string { return res } +// sendSummary send the summary events for a single folder +func (c *folderSummarySvc) sendSummary(folder string) { + // The folder summary contains how many bytes, files etc + // are in the folder and how in sync we are. + data := folderSummary(c.model, folder) + events.Default.Log(events.FolderSummary, map[string]interface{}{ + "folder": folder, + "summary": data, + }) + + for _, devCfg := range cfg.Folders()[folder].Devices { + if devCfg.DeviceID.Equals(myID) { + // We already know about ourselves. + continue + } + if !c.model.ConnectedTo(devCfg.DeviceID) { + // We're not interested in disconnected devices. + continue + } + + // Get completion percentage of this folder for the + // remote device. + comp := c.model.Completion(devCfg.DeviceID, folder) + events.Default.Log(events.FolderCompletion, map[string]interface{}{ + "folder": folder, + "device": devCfg.DeviceID.String(), + "completion": comp, + }) + } +} + // serviceFunc wraps a function to create a suture.Service without stop // functionality. type serviceFunc func()