diff --git a/config/config.go b/config/config.go index 37058cd..098e99b 100644 --- a/config/config.go +++ b/config/config.go @@ -273,15 +273,3 @@ func (config *Config) SaveRoom(_ *mautrix.Room) { func (config *Config) LoadRoom(_ id.RoomID) *mautrix.Room { 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() -} diff --git a/go.mod b/go.mod index 8572a56..72e0fac 100644 --- a/go.mod +++ b/go.mod @@ -21,7 +21,7 @@ require ( golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e gopkg.in/toast.v1 v1.0.0-20180812000517-0a84660828b2 gopkg.in/yaml.v2 v2.2.8 - maunium.net/go/mautrix v0.2.0-beta.1 - maunium.net/go/mauview v0.1.0-beta.1 + maunium.net/go/mautrix v0.2.0-beta.2 + maunium.net/go/mauview v0.1.0 maunium.net/go/tcell v0.1.0 ) diff --git a/go.sum b/go.sum index 9f901d9..77da695 100644 --- a/go.sum +++ b/go.sum @@ -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= 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.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/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/go.mod h1:Ru7KmI5AU7xHUx6hGltgJvknrS+8jlGGMKK15pZuc9k= diff --git a/interface/ui.go b/interface/ui.go index 67cc35c..8699c79 100644 --- a/interface/ui.go +++ b/interface/ui.go @@ -40,6 +40,14 @@ type GomuksUI interface { Finish() } +type SyncingModal interface { + SetIndeterminate() + SetMessage(string) + SetSteps(int) + Step() + Close() +} + type MainView interface { GetRoom(roomID id.RoomID) RoomView AddRoom(room *rooms.Room) @@ -50,6 +58,7 @@ type MainView interface { UpdateTags(room *rooms.Room) SetTyping(roomID id.RoomID, users []id.UserID) + OpenSyncingModal() SyncingModal NotifyMessage(room *rooms.Room, message Message, should pushrules.PushActionArrayShould) } diff --git a/matrix/matrix.go b/matrix/matrix.go index 0a4c2e0..39f980d 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -300,6 +300,13 @@ func init() { 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. func (c *Container) OnLogin() { c.ui.OnLogin() @@ -307,7 +314,7 @@ func (c *Container) OnLogin() { c.client.Store = c.config 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.EventEncrypted, 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.AccountDataRoomTags, c.HandleTag) 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() { debug.Print("Initial sync done") c.config.AuthCache.InitialSyncDone = true diff --git a/matrix/sync.go b/matrix/sync.go index ed9d137..8004297 100644 --- a/matrix/sync.go +++ b/matrix/sync.go @@ -23,6 +23,7 @@ import ( "sync" "time" + ifc "maunium.net/go/gomuks/interface" "maunium.net/go/mautrix" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" @@ -31,13 +32,6 @@ import ( "maunium.net/go/gomuks/matrix/rooms" ) -type SyncerSession interface { - GetRoom(id id.RoomID) *rooms.Room - GetUserID() id.UserID - DisableUnloading() - EnableUnloading() -} - type EventSource int const ( @@ -90,20 +84,19 @@ func (es EventSource) String() string { 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 { - Session SyncerSession - listeners map[event.Type][]EventHandler // event type to listeners array - FirstSyncDone bool - InitDoneCallback func() + rooms *rooms.RoomCache + listeners map[event.Type][]EventHandler // event type to listeners array + FirstSyncDone bool + InitDoneCallback func() + FirstDoneCallback func() + Progress ifc.SyncingModal } // NewGomuksSyncer returns an instantiated GomuksSyncer -func NewGomuksSyncer(session SyncerSession) *GomuksSyncer { +func NewGomuksSyncer(rooms *rooms.RoomCache) *GomuksSyncer { return &GomuksSyncer{ - Session: session, + rooms: rooms, listeners: make(map[event.Type][]EventHandler), FirstSyncDone: false, } @@ -112,43 +105,54 @@ func NewGomuksSyncer(session SyncerSession) *GomuksSyncer { // ProcessResponse processes a Matrix sync response. func (s *GomuksSyncer) ProcessResponse(res *mautrix.RespSync, since string) (err error) { if since == "" { - s.Session.DisableUnloading() + s.rooms.DisableUnloading() } 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.Progress.Step() s.processSyncEvents(nil, res.AccountData.Events, EventSourceAccountData) + s.Progress.Step() 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 { - 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 { - 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 { - go s.processLeftRoom(roomID, roomData, wait) + go s.processLeftRoom(roomID, roomData, callback) } wait.Wait() if since == "" && s.InitDoneCallback != nil { s.InitDoneCallback() - s.Session.EnableUnloading() + s.rooms.EnableUnloading() + } + if !s.FirstSyncDone && s.FirstDoneCallback != nil { + s.FirstDoneCallback() } s.FirstSyncDone = true - 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() - room := s.Session.GetRoom(roomID) + room := s.rooms.GetOrCreate(roomID) room.UpdateSummary(roomData.Summary) s.processSyncEvents(room, roomData.State.Events, EventSourceJoin|EventSourceState) 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.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() - room := s.Session.GetRoom(roomID) + room := s.rooms.GetOrCreate(roomID) room.UpdateSummary(roomData.Summary) 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() - room := s.Session.GetRoom(roomID) + room := s.rooms.GetOrCreate(roomID) room.HasLeft = true room.UpdateSummary(roomData.Summary) 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.LastPrevBatch = roomData.Timeline.PrevBatch - wait.Done() + callback() } func (s *GomuksSyncer) processSyncEvents(room *rooms.Room, events []*event.Event, source EventSource) { diff --git a/ui/syncing-modal.go b/ui/syncing-modal.go new file mode 100644 index 0000000..9696d8a --- /dev/null +++ b/ui/syncing-modal.go @@ -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 . + +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() +} diff --git a/ui/view-main.go b/ui/view-main.go index e38756f..b2f4ff0 100644 --- a/ui/view-main.go +++ b/ui/view-main.go @@ -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 { view.BumpFocus(view.currentRoom)