Immediately recalculate summary when folder state changes syncing->idle

This commit is contained in:
Jakob Borg 2015-03-29 10:25:41 +02:00
parent 454e688c3d
commit e4dba99cc0

View File

@ -18,9 +18,10 @@ import (
// The folderSummarySvc adds summary information events (FolderSummary and // The folderSummarySvc adds summary information events (FolderSummary and
// FolderCompletion) into the event stream at certain intervals. // FolderCompletion) into the event stream at certain intervals.
type folderSummarySvc struct { type folderSummarySvc struct {
model *model.Model model *model.Model
srv suture.Service srv suture.Service
stop chan struct{} stop chan struct{}
immediate chan string
// For keeping track of folders to recalculate for // For keeping track of folders to recalculate for
foldersMut sync.Mutex foldersMut sync.Mutex
@ -32,6 +33,7 @@ func (c *folderSummarySvc) Serve() {
srv.Add(serviceFunc(c.listenForUpdates)) srv.Add(serviceFunc(c.listenForUpdates))
srv.Add(serviceFunc(c.calculateSummaries)) srv.Add(serviceFunc(c.calculateSummaries))
c.immediate = make(chan string)
c.stop = make(chan struct{}) c.stop = make(chan struct{})
c.folders = make(map[string]struct{}) c.folders = make(map[string]struct{})
c.srv = srv c.srv = srv
@ -50,7 +52,7 @@ func (c *folderSummarySvc) Stop() {
// listenForUpdates subscribes to the event bus and makes note of folders that // listenForUpdates subscribes to the event bus and makes note of folders that
// need their data recalculated. // need their data recalculated.
func (c *folderSummarySvc) listenForUpdates() { 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) defer events.Default.Unsubscribe(sub)
for { for {
@ -63,9 +65,29 @@ func (c *folderSummarySvc) listenForUpdates() {
data := ev.Data.(map[string]interface{}) data := ev.Data.(map[string]interface{})
folder := data["folder"].(string) folder := data["folder"].(string)
c.foldersMut.Lock()
c.folders[folder] = struct{}{} if ev.Type == events.StateChanged && data["to"].(string) == "idle" && data["from"].(string) == "syncing" {
c.foldersMut.Unlock() // 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: case <-c.stop:
return return
@ -82,48 +104,9 @@ func (c *folderSummarySvc) calculateSummaries() {
for { for {
select { select {
case <-pump.C: 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() t0 := time.Now()
if time.Since(last) < pingEventInterval { for _, folder := range c.foldersToHandle() {
for _, folder := range c.foldersToHandle() { c.sendSummary(folder)
// 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,
})
}
}
} }
// We don't want to spend all our time calculating summaries. Lets // 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 wait := 2*time.Since(t0) + pumpInterval
pump.Reset(wait) pump.Reset(wait)
case folder := <-c.immediate:
c.sendSummary(folder)
case <-c.stop: case <-c.stop:
return return
} }
@ -141,6 +127,20 @@ func (c *folderSummarySvc) calculateSummaries() {
// foldersToHandle returns the list of folders needing a summary update, and // foldersToHandle returns the list of folders needing a summary update, and
// clears the list. // clears the list.
func (c *folderSummarySvc) foldersToHandle() []string { 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() c.foldersMut.Lock()
res := make([]string, 0, len(c.folders)) res := make([]string, 0, len(c.folders))
for folder := range c.folders { for folder := range c.folders {
@ -151,6 +151,37 @@ func (c *folderSummarySvc) foldersToHandle() []string {
return res 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 // serviceFunc wraps a function to create a suture.Service without stop
// functionality. // functionality.
type serviceFunc func() type serviceFunc func()