Add progress bar for first sync

This commit is contained in:
Tulir Asokan 2020-04-19 18:06:45 +03:00
parent 6a907354e2
commit a66b02ae8b
8 changed files with 145 additions and 49 deletions

View File

@ -273,15 +273,3 @@ func (config *Config) SaveRoom(_ *mautrix.Room) {
func (config *Config) LoadRoom(_ id.RoomID) *mautrix.Room { func (config *Config) LoadRoom(_ id.RoomID) *mautrix.Room {
panic("LoadRoom is not supported") panic("LoadRoom is not supported")
} }
func (config *Config) GetRoom(roomID id.RoomID) *rooms.Room {
return config.Rooms.GetOrCreate(roomID)
}
func (config *Config) DisableUnloading() {
config.Rooms.DisableUnloading()
}
func (config *Config) EnableUnloading() {
config.Rooms.EnableUnloading()
}

4
go.mod
View File

@ -21,7 +21,7 @@ require (
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e
gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2
gopkg.in/yaml.v2 v2.2.8 gopkg.in/yaml.v2 v2.2.8
maunium.net/go/mautrix v0.2.0-beta.1 maunium.net/go/mautrix v0.2.0-beta.2
maunium.net/go/mauview v0.1.0-beta.1 maunium.net/go/mauview v0.1.0
maunium.net/go/tcell v0.1.0 maunium.net/go/tcell v0.1.0
) )

4
go.sum
View File

@ -75,7 +75,11 @@ gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
maunium.net/go/mautrix v0.2.0-beta.1 h1:gIFsA8HquYie36FVdoaEcARb2xca/H0FB7hrV84ZBb4= maunium.net/go/mautrix v0.2.0-beta.1 h1:gIFsA8HquYie36FVdoaEcARb2xca/H0FB7hrV84ZBb4=
maunium.net/go/mautrix v0.2.0-beta.1/go.mod h1:WeTUYKrM3/4LZK2bXQ9NRIXnRWKsa+6+OA1gw0nf5G8= maunium.net/go/mautrix v0.2.0-beta.1/go.mod h1:WeTUYKrM3/4LZK2bXQ9NRIXnRWKsa+6+OA1gw0nf5G8=
maunium.net/go/mautrix v0.2.0-beta.2 h1:h9yR/d1bYzlSs8qC5ylTgYzrgpm/T24sEqXKfo7UJ0Y=
maunium.net/go/mautrix v0.2.0-beta.2/go.mod h1:WeTUYKrM3/4LZK2bXQ9NRIXnRWKsa+6+OA1gw0nf5G8=
maunium.net/go/mauview v0.1.0-beta.1 h1:hRprD6NTi5Mw7i97DKmgs/TzFQeNpGPytPoswNlU/Ww= maunium.net/go/mauview v0.1.0-beta.1 h1:hRprD6NTi5Mw7i97DKmgs/TzFQeNpGPytPoswNlU/Ww=
maunium.net/go/mauview v0.1.0-beta.1/go.mod h1:og9WbzmWe9SNYNyOFlCv8qa9zMcOvG2nzRJ5vYyud9U= maunium.net/go/mauview v0.1.0-beta.1/go.mod h1:og9WbzmWe9SNYNyOFlCv8qa9zMcOvG2nzRJ5vYyud9U=
maunium.net/go/mauview v0.1.0 h1:x2WdkKI2zdriJuPAB0CKlwmnHGE7W9xfM5z6RgG+IIg=
maunium.net/go/mauview v0.1.0/go.mod h1:og9WbzmWe9SNYNyOFlCv8qa9zMcOvG2nzRJ5vYyud9U=
maunium.net/go/tcell v0.1.0 h1:XzsEoGCvOw5nac+tlkSLzQcliLYTN4PrtA7ar2ptjSM= maunium.net/go/tcell v0.1.0 h1:XzsEoGCvOw5nac+tlkSLzQcliLYTN4PrtA7ar2ptjSM=
maunium.net/go/tcell v0.1.0/go.mod h1:Ru7KmI5AU7xHUx6hGltgJvknrS+8jlGGMKK15pZuc9k= maunium.net/go/tcell v0.1.0/go.mod h1:Ru7KmI5AU7xHUx6hGltgJvknrS+8jlGGMKK15pZuc9k=

View File

@ -40,6 +40,14 @@ type GomuksUI interface {
Finish() Finish()
} }
type SyncingModal interface {
SetIndeterminate()
SetMessage(string)
SetSteps(int)
Step()
Close()
}
type MainView interface { type MainView interface {
GetRoom(roomID id.RoomID) RoomView GetRoom(roomID id.RoomID) RoomView
AddRoom(room *rooms.Room) AddRoom(room *rooms.Room)
@ -50,6 +58,7 @@ type MainView interface {
UpdateTags(room *rooms.Room) UpdateTags(room *rooms.Room)
SetTyping(roomID id.RoomID, users []id.UserID) SetTyping(roomID id.RoomID, users []id.UserID)
OpenSyncingModal() SyncingModal
NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould) NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould)
} }

View File

@ -300,6 +300,13 @@ func init() {
gob.Register(&config.UserPreferences{}) gob.Register(&config.UserPreferences{})
} }
type StubSyncingModal struct{}
func (s StubSyncingModal) SetIndeterminate() {}
func (s StubSyncingModal) SetMessage(s2 string) {}
func (s StubSyncingModal) SetSteps(i int) {}
func (s StubSyncingModal) Step() {}
func (s StubSyncingModal) Close() {}
// OnLogin initializes the syncer and updates the room list. // OnLogin initializes the syncer and updates the room list.
func (c *Container) OnLogin() { func (c *Container) OnLogin() {
c.ui.OnLogin() c.ui.OnLogin()
@ -307,7 +314,7 @@ func (c *Container) OnLogin() {
c.client.Store = c.config c.client.Store = c.config
debug.Print("Initializing syncer") debug.Print("Initializing syncer")
c.syncer = NewGomuksSyncer(c.config) c.syncer = NewGomuksSyncer(c.config.Rooms)
c.syncer.OnEventType(event.EventMessage, c.HandleMessage) c.syncer.OnEventType(event.EventMessage, c.HandleMessage)
c.syncer.OnEventType(event.EventEncrypted, c.HandleMessage) c.syncer.OnEventType(event.EventEncrypted, c.HandleMessage)
c.syncer.OnEventType(event.EventSticker, c.HandleMessage) c.syncer.OnEventType(event.EventSticker, c.HandleMessage)
@ -324,6 +331,13 @@ func (c *Container) OnLogin() {
c.syncer.OnEventType(event.AccountDataPushRules, c.HandlePushRules) c.syncer.OnEventType(event.AccountDataPushRules, c.HandlePushRules)
c.syncer.OnEventType(event.AccountDataRoomTags, c.HandleTag) c.syncer.OnEventType(event.AccountDataRoomTags, c.HandleTag)
c.syncer.OnEventType(AccountDataGomuksPreferences, c.HandlePreferences) c.syncer.OnEventType(AccountDataGomuksPreferences, c.HandlePreferences)
c.syncer.Progress = c.ui.MainView().OpenSyncingModal()
c.syncer.Progress.SetMessage("Waiting for /sync response from server")
c.syncer.Progress.SetIndeterminate()
c.syncer.FirstDoneCallback = func() {
c.syncer.Progress.Close()
c.syncer.Progress = StubSyncingModal{}
}
c.syncer.InitDoneCallback = func() { c.syncer.InitDoneCallback = func() {
debug.Print("Initial sync done") debug.Print("Initial sync done")
c.config.AuthCache.InitialSyncDone = true c.config.AuthCache.InitialSyncDone = true

View File

@ -23,6 +23,7 @@ import (
"sync" "sync"
"time" "time"
ifc "maunium.net/go/gomuks/interface"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
"maunium.net/go/mautrix/event" "maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
@ -31,13 +32,6 @@ import (
"maunium.net/go/gomuks/matrix/rooms" "maunium.net/go/gomuks/matrix/rooms"
) )
type SyncerSession interface {
GetRoom(id id.RoomID) *rooms.Room
GetUserID() id.UserID
DisableUnloading()
EnableUnloading()
}
type EventSource int type EventSource int
const ( const (
@ -90,20 +84,19 @@ func (es EventSource) String() string {
type EventHandler func(source EventSource, event *event.Event) type EventHandler func(source EventSource, event *event.Event)
// GomuksSyncer is the default syncing implementation. You can either write your own syncer, or selectively
// replace parts of this default syncer (e.g. the ProcessResponse method). The default syncer uses the observer
// pattern to notify callers about incoming events. See GomuksSyncer.OnEventType for more information.
type GomuksSyncer struct { type GomuksSyncer struct {
Session SyncerSession rooms *rooms.RoomCache
listeners map[event.Type][]EventHandler // event type to listeners array listeners map[event.Type][]EventHandler // event type to listeners array
FirstSyncDone bool FirstSyncDone bool
InitDoneCallback func() InitDoneCallback func()
FirstDoneCallback func()
Progress ifc.SyncingModal
} }
// NewGomuksSyncer returns an instantiated GomuksSyncer // NewGomuksSyncer returns an instantiated GomuksSyncer
func NewGomuksSyncer(session SyncerSession) *GomuksSyncer { func NewGomuksSyncer(rooms *rooms.RoomCache) *GomuksSyncer {
return &GomuksSyncer{ return &GomuksSyncer{
Session: session, rooms: rooms,
listeners: make(map[event.Type][]EventHandler), listeners: make(map[event.Type][]EventHandler),
FirstSyncDone: false, FirstSyncDone: false,
} }
@ -112,43 +105,54 @@ func NewGomuksSyncer(session SyncerSession) *GomuksSyncer {
// ProcessResponse processes a Matrix sync response. // ProcessResponse processes a Matrix sync response.
func (s *GomuksSyncer) ProcessResponse(res *mautrix.RespSync, since string) (err error) { func (s *GomuksSyncer) ProcessResponse(res *mautrix.RespSync, since string) (err error) {
if since == "" { if since == "" {
s.Session.DisableUnloading() s.rooms.DisableUnloading()
} }
debug.Print("Received sync response") debug.Print("Received sync response")
steps := len(res.Rooms.Join) + len(res.Rooms.Invite) + len(res.Rooms.Leave)
s.Progress.SetSteps(steps + 2)
s.Progress.SetMessage("Processing global events")
s.processSyncEvents(nil, res.Presence.Events, EventSourcePresence) s.processSyncEvents(nil, res.Presence.Events, EventSourcePresence)
s.Progress.Step()
s.processSyncEvents(nil, res.AccountData.Events, EventSourceAccountData) s.processSyncEvents(nil, res.AccountData.Events, EventSourceAccountData)
s.Progress.Step()
wait := &sync.WaitGroup{} wait := &sync.WaitGroup{}
wait.Add(len(res.Rooms.Join)) wait.Add(steps)
callback := func() {
wait.Done()
s.Progress.Step()
}
s.Progress.SetMessage("Processing room events")
for roomID, roomData := range res.Rooms.Join { for roomID, roomData := range res.Rooms.Join {
go s.processJoinedRoom(roomID, roomData, wait) go s.processJoinedRoom(roomID, roomData, callback)
} }
wait.Add(len(res.Rooms.Invite))
for roomID, roomData := range res.Rooms.Invite { for roomID, roomData := range res.Rooms.Invite {
go s.processInvitedRoom(roomID, roomData, wait) go s.processInvitedRoom(roomID, roomData, callback)
} }
wait.Add(len(res.Rooms.Leave))
for roomID, roomData := range res.Rooms.Leave { for roomID, roomData := range res.Rooms.Leave {
go s.processLeftRoom(roomID, roomData, wait) go s.processLeftRoom(roomID, roomData, callback)
} }
wait.Wait() wait.Wait()
if since == "" && s.InitDoneCallback != nil { if since == "" && s.InitDoneCallback != nil {
s.InitDoneCallback() s.InitDoneCallback()
s.Session.EnableUnloading() s.rooms.EnableUnloading()
}
if !s.FirstSyncDone && s.FirstDoneCallback != nil {
s.FirstDoneCallback()
} }
s.FirstSyncDone = true s.FirstSyncDone = true
return return
} }
func (s *GomuksSyncer) processJoinedRoom(roomID id.RoomID, roomData mautrix.SyncJoinedRoom, wait *sync.WaitGroup) { func (s *GomuksSyncer) processJoinedRoom(roomID id.RoomID, roomData mautrix.SyncJoinedRoom, callback func()) {
defer debug.Recover() defer debug.Recover()
room := s.Session.GetRoom(roomID) room := s.rooms.GetOrCreate(roomID)
room.UpdateSummary(roomData.Summary) room.UpdateSummary(roomData.Summary)
s.processSyncEvents(room, roomData.State.Events, EventSourceJoin|EventSourceState) s.processSyncEvents(room, roomData.State.Events, EventSourceJoin|EventSourceState)
s.processSyncEvents(room, roomData.Timeline.Events, EventSourceJoin|EventSourceTimeline) s.processSyncEvents(room, roomData.Timeline.Events, EventSourceJoin|EventSourceTimeline)
@ -159,20 +163,20 @@ func (s *GomuksSyncer) processJoinedRoom(roomID id.RoomID, roomData mautrix.Sync
room.PrevBatch = roomData.Timeline.PrevBatch room.PrevBatch = roomData.Timeline.PrevBatch
} }
room.LastPrevBatch = roomData.Timeline.PrevBatch room.LastPrevBatch = roomData.Timeline.PrevBatch
wait.Done() callback()
} }
func (s *GomuksSyncer) processInvitedRoom(roomID id.RoomID, roomData mautrix.SyncInvitedRoom, wait *sync.WaitGroup) { func (s *GomuksSyncer) processInvitedRoom(roomID id.RoomID, roomData mautrix.SyncInvitedRoom, callback func()) {
defer debug.Recover() defer debug.Recover()
room := s.Session.GetRoom(roomID) room := s.rooms.GetOrCreate(roomID)
room.UpdateSummary(roomData.Summary) room.UpdateSummary(roomData.Summary)
s.processSyncEvents(room, roomData.State.Events, EventSourceInvite|EventSourceState) s.processSyncEvents(room, roomData.State.Events, EventSourceInvite|EventSourceState)
wait.Done() callback()
} }
func (s *GomuksSyncer) processLeftRoom(roomID id.RoomID, roomData mautrix.SyncLeftRoom, wait *sync.WaitGroup) { func (s *GomuksSyncer) processLeftRoom(roomID id.RoomID, roomData mautrix.SyncLeftRoom, callback func()) {
defer debug.Recover() defer debug.Recover()
room := s.Session.GetRoom(roomID) room := s.rooms.GetOrCreate(roomID)
room.HasLeft = true room.HasLeft = true
room.UpdateSummary(roomData.Summary) room.UpdateSummary(roomData.Summary)
s.processSyncEvents(room, roomData.State.Events, EventSourceLeave|EventSourceState) s.processSyncEvents(room, roomData.State.Events, EventSourceLeave|EventSourceState)
@ -182,7 +186,7 @@ func (s *GomuksSyncer) processLeftRoom(roomID id.RoomID, roomData mautrix.SyncLe
room.PrevBatch = roomData.Timeline.PrevBatch room.PrevBatch = roomData.Timeline.PrevBatch
} }
room.LastPrevBatch = roomData.Timeline.PrevBatch room.LastPrevBatch = roomData.Timeline.PrevBatch
wait.Done() callback()
} }
func (s *GomuksSyncer) processSyncEvents(room *rooms.Room, events []*event.Event, source EventSource) { func (s *GomuksSyncer) processSyncEvents(room *rooms.Room, events []*event.Event, source EventSource) {

71
ui/syncing-modal.go Normal file
View File

@ -0,0 +1,71 @@
// gomuks - A terminal Matrix client written in Go.
// Copyright (C) 2020 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package ui
import (
"time"
"maunium.net/go/mauview"
)
type SyncingModal struct {
parent *MainView
text *mauview.TextView
progress *mauview.ProgressBar
}
func NewSyncingModal(parent *MainView) (mauview.Component, *SyncingModal) {
sm := &SyncingModal{
parent: parent,
progress: mauview.NewProgressBar(),
text: mauview.NewTextView(),
}
return mauview.Center(
mauview.NewBox(
mauview.NewFlex().
SetDirection(mauview.FlexRow).
AddFixedComponent(sm.progress, 1).
AddFixedComponent(mauview.Center(sm.text, 40, 1), 1)).
SetTitle("Synchronizing"),
42, 4).
SetAlwaysFocusChild(true), sm
}
func (sm *SyncingModal) SetMessage(text string) {
sm.text.SetText(text)
}
func (sm *SyncingModal) SetIndeterminate() {
sm.progress.SetIndeterminate(true)
sm.parent.parent.app.SetRedrawTicker(100 * time.Millisecond)
sm.parent.parent.app.Redraw()
}
func (sm *SyncingModal) SetSteps(max int) {
sm.progress.SetMax(max)
sm.progress.SetIndeterminate(false)
sm.parent.parent.app.SetRedrawTicker(1 * time.Minute)
sm.parent.parent.Render()
}
func (sm *SyncingModal) Step() {
sm.progress.Increment(1)
}
func (sm *SyncingModal) Close() {
sm.parent.HideModal()
}

View File

@ -169,6 +169,12 @@ func (view *MainView) ShowBare(roomView *RoomView) {
}) })
} }
func (view *MainView) OpenSyncingModal() ifc.SyncingModal {
component, modal := NewSyncingModal(view)
view.ShowModal(component)
return modal
}
func (view *MainView) OnKeyEvent(event mauview.KeyEvent) bool { func (view *MainView) OnKeyEvent(event mauview.KeyEvent) bool {
view.BumpFocus(view.currentRoom) view.BumpFocus(view.currentRoom)