From 961742fa53c66c4a0e4a53cbafbb225718be25ed Mon Sep 17 00:00:00 2001 From: Jakub Date: Fri, 5 Nov 2021 12:31:32 +0100 Subject: [PATCH] GODT-1366: Simple lookup of index and select current user --- internal/events/events.go | 2 ++ .../frontend/qml/BridgeTest/UserControl.qml | 22 ++++++++----- internal/frontend/qml/Bridge_test.qml | 4 +-- internal/frontend/qml/ContentWrapper.qml | 11 +++++-- internal/frontend/qml/MainWindow.qml | 4 +++ internal/frontend/qt/frontend_users.go | 32 +++++++++++++++++-- internal/frontend/qt/qml_backend.go | 2 +- internal/frontend/qt/qml_users.go | 3 ++ internal/store/mocks/utils_mocks.go | 12 +++++++ internal/users/mocks/listener_mocks.go | 12 +++++++ internal/users/users.go | 2 +- pkg/listener/listener.go | 14 ++++++++ pkg/listener/listener_test.go | 3 ++ 13 files changed, 106 insertions(+), 17 deletions(-) diff --git a/internal/events/events.go b/internal/events/events.go index 0b087a48..a6f3a694 100644 --- a/internal/events/events.go +++ b/internal/events/events.go @@ -41,6 +41,7 @@ const ( NoActiveKeyForRecipientEvent = "noActiveKeyForRecipient" UpgradeApplicationEvent = "upgradeApplication" TLSCertIssue = "tlsCertPinningIssue" + UserChangeDone = "QMLUserChangedDone" // LogoutEventTimeout is the minimum time to permit between logout events being sent. LogoutEventTimeout = 3 * time.Minute @@ -55,4 +56,5 @@ func SetupEvents(listener listener.Listener) { listener.SetBuffer(UpgradeApplicationEvent) listener.SetBuffer(TLSCertIssue) listener.SetBuffer(UserRefreshEvent) + listener.Book(UserChangeDone) } diff --git a/internal/frontend/qml/BridgeTest/UserControl.qml b/internal/frontend/qml/BridgeTest/UserControl.qml index d714f325..01ccf259 100644 --- a/internal/frontend/qml/BridgeTest/UserControl.qml +++ b/internal/frontend/qml/BridgeTest/UserControl.qml @@ -236,13 +236,21 @@ ColumnLayout { } } - Button { - colorScheme: root.colorScheme - text: "Login Finished" + RowLayout { + Button { + colorScheme: root.colorScheme + text: "Login Finished" - onClicked: { - root.backend.loginFinished() - user.resetLoginRequests() + onClicked: { + root.backend.loginFinished(0+loginFinishedIndex.text) + user.resetLoginRequests() + } + } + TextField { + id: loginFinishedIndex + colorScheme: root.colorScheme + label: "Index:" + text: "0" } } @@ -251,7 +259,6 @@ ColumnLayout { colorScheme: root.colorScheme label: "used:" text: user && user.usedBytes ? user.usedBytes : 0 - validator: DoubleValidator {bottom: 1; top: 1024*1024*1024*1024*1024} onEditingFinished: { user.usedBytes = parseFloat(text) } @@ -261,7 +268,6 @@ ColumnLayout { colorScheme: root.colorScheme label: "total:" text: user && user.totalBytes ? user.totalBytes : 0 - validator: DoubleValidator {bottom: 1; top: 1024*1024*1024*1024*1024} onEditingFinished: { user.totalBytes = parseFloat(text) } diff --git a/internal/frontend/qml/Bridge_test.qml b/internal/frontend/qml/Bridge_test.qml index 9e48c00e..7a59b1cb 100644 --- a/internal/frontend/qml/Bridge_test.qml +++ b/internal/frontend/qml/Bridge_test.qml @@ -670,7 +670,7 @@ Window { signal login2PasswordRequested() signal login2PasswordError(string errorMsg) signal login2PasswordErrorAbort(string errorMsg) - signal loginFinished() + signal loginFinished(int index) signal internetOff() signal internetOn() @@ -847,7 +847,7 @@ Window { console.debug("<- login2PasswordErrorAbort") } onLoginFinished: { - console.debug("<- loginFinished") + console.debug("<- loginFinished", index) } onInternetOff: { diff --git a/internal/frontend/qml/ContentWrapper.qml b/internal/frontend/qml/ContentWrapper.qml index 14bd5614..11c2444a 100644 --- a/internal/frontend/qml/ContentWrapper.qml +++ b/internal/frontend/qml/ContentWrapper.qml @@ -159,7 +159,6 @@ Item { model: root.backend.users delegate: Item { - width: leftBar.width - 2*accounts._leftRightMargins implicitHeight: children[0].implicitHeight + children[0].anchors.topMargin + children[0].anchors.bottomMargin implicitWidth: children[0].implicitWidth + children[0].anchors.leftMargin + children[0].anchors.rightMargin @@ -359,7 +358,13 @@ Item { } } - function showAccount () { rightContent.currentIndex = 0 } + function showAccount(index) { + if (index !== undefined && index >= 0){ + accounts.currentIndex = index + } + rightContent.currentIndex = 0 + } + function showSignIn () { rightContent.currentIndex = 1 } function showGeneralSettings () { rightContent.currentIndex = 2 } function showPortSettings () { rightContent.currentIndex = 3 } @@ -371,7 +376,7 @@ Item { Connections { target: root.backend - onLoginFinished: rightContent.showAccount() + onLoginFinished: rightContent.showAccount(index) } } } diff --git a/internal/frontend/qml/MainWindow.qml b/internal/frontend/qml/MainWindow.qml index e47a738b..c46834fb 100644 --- a/internal/frontend/qml/MainWindow.qml +++ b/internal/frontend/qml/MainWindow.qml @@ -85,6 +85,10 @@ ApplicationWindow { onShowMainWindow: { root.showAndRise() } + + onLoginFinished: { + console.debug("Login finished", index) + } } StackLayout { diff --git a/internal/frontend/qt/frontend_users.go b/internal/frontend/qt/frontend_users.go index 24c328e8..73dd3e9c 100644 --- a/internal/frontend/qt/frontend_users.go +++ b/internal/frontend/qt/frontend_users.go @@ -22,7 +22,9 @@ package qt import ( "context" "encoding/base64" + "time" + "github.com/ProtonMail/proton-bridge/internal/events" "github.com/ProtonMail/proton-bridge/internal/users" "github.com/ProtonMail/proton-bridge/pkg/pmapi" ) @@ -138,14 +140,40 @@ func (f *FrontendQt) finishLogin() { return } - _, err := f.bridge.FinishLogin(f.authClient, f.auth, f.password) + done := make(chan string) + f.eventListener.Add(events.UserChangeDone, done) + defer f.eventListener.Remove(events.UserChangeDone, done) + + user, err := f.bridge.FinishLogin(f.authClient, f.auth, f.password) if err != nil && err != users.ErrUserAlreadyConnected { f.log.WithError(err).Errorf("Finish login failed") f.qml.Login2PasswordErrorAbort(err.Error()) return } - defer f.qml.LoginFinished() + // The user changed should be triggerd by FinishLogin but it is not + // guaranteed when this is going to happen. Therefore we should wait + // until we receive the signal from userChanged function. + f.waitForUserChangeDone(done, user.ID()) + + index := f.qml.Users().indexByID(user.ID()) + f.log.WithField("index", index).Debug("Login finished") + + defer f.qml.LoginFinished(index) +} + +func (f *FrontendQt) waitForUserChangeDone(done <-chan string, userID string) { + for { + select { + case changedID := <-done: + if changedID == userID { + return + } + case <-time.After(2 * time.Second): + f.log.WithField("ID", userID).Warning("Login finished but user not added within 2 seconds") + return + } + } } func (f *FrontendQt) loginAbort(username string) { diff --git a/internal/frontend/qt/qml_backend.go b/internal/frontend/qt/qml_backend.go index ad9f0bf6..01bd5065 100644 --- a/internal/frontend/qt/qml_backend.go +++ b/internal/frontend/qt/qml_backend.go @@ -64,7 +64,7 @@ type QMLBackend struct { _ func() `signal:"login2PasswordRequested"` _ func(errorMsg string) `signal:"login2PasswordError"` _ func(errorMsg string) `signal:"login2PasswordErrorAbort"` - _ func() `signal:"loginFinished"` + _ func(index int) `signal:"loginFinished"` _ func() `signal:"internetOff"` _ func() `signal:"internetOn"` diff --git a/internal/frontend/qt/qml_users.go b/internal/frontend/qt/qml_users.go index 3b5dded3..8aa4fb0d 100644 --- a/internal/frontend/qt/qml_users.go +++ b/internal/frontend/qt/qml_users.go @@ -23,6 +23,7 @@ package qt import ( "sync" + "github.com/ProtonMail/proton-bridge/internal/events" "github.com/ProtonMail/proton-bridge/internal/frontend/types" "github.com/therecipe/qt/core" ) @@ -174,6 +175,8 @@ func (um *QMLUserModel) load() { } func (um *QMLUserModel) userChanged(userID string) { + defer um.f.eventListener.Emit(events.UserChangeDone, userID) + index := um.indexByIDNotSafe(userID) user, err := um.f.bridge.GetUser(userID) diff --git a/internal/store/mocks/utils_mocks.go b/internal/store/mocks/utils_mocks.go index ec99aa03..2b881f1f 100644 --- a/internal/store/mocks/utils_mocks.go +++ b/internal/store/mocks/utils_mocks.go @@ -46,6 +46,18 @@ func (mr *MockListenerMockRecorder) Add(arg0, arg1 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockListener)(nil).Add), arg0, arg1) } +// Book mocks base method. +func (m *MockListener) Book(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Book", arg0) +} + +// Book indicates an expected call of Book. +func (mr *MockListenerMockRecorder) Book(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Book", reflect.TypeOf((*MockListener)(nil).Book), arg0) +} + // Emit mocks base method. func (m *MockListener) Emit(arg0, arg1 string) { m.ctrl.T.Helper() diff --git a/internal/users/mocks/listener_mocks.go b/internal/users/mocks/listener_mocks.go index ec99aa03..2b881f1f 100644 --- a/internal/users/mocks/listener_mocks.go +++ b/internal/users/mocks/listener_mocks.go @@ -46,6 +46,18 @@ func (mr *MockListenerMockRecorder) Add(arg0, arg1 interface{}) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockListener)(nil).Add), arg0, arg1) } +// Book mocks base method. +func (m *MockListener) Book(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Book", arg0) +} + +// Book indicates an expected call of Book. +func (mr *MockListenerMockRecorder) Book(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Book", reflect.TypeOf((*MockListener)(nil).Book), arg0) +} + // Emit mocks base method. func (m *MockListener) Emit(arg0, arg1 string) { m.ctrl.T.Helper() diff --git a/internal/users/users.go b/internal/users/users.go index 22e00853..cd9abfe4 100644 --- a/internal/users/users.go +++ b/internal/users/users.go @@ -219,7 +219,7 @@ func (u *Users) FinishLogin(client pmapi.Client, auth *pmapi.Auth, password []by logrus.WithError(err).Warn("Failed to delete new auth session") } - return nil, ErrUserAlreadyConnected + return user, ErrUserAlreadyConnected } // Update the user's credentials with the latest auth used to connect this user. diff --git a/pkg/listener/listener.go b/pkg/listener/listener.go index 48164d87..058ccb82 100644 --- a/pkg/listener/listener.go +++ b/pkg/listener/listener.go @@ -35,6 +35,7 @@ type Listener interface { Emit(eventName string, data string) SetBuffer(eventName string) RetryEmit(eventName string) + Book(eventName string) } type listener struct { @@ -56,6 +57,19 @@ func New() Listener { } } +// Book wil create the list of channels for specific eventName. This should be +// used when there is not always listening channel available and it should not +// be logged when no channel is awaiting an emitted event. +func (l *listener) Book(eventName string) { + if l.channels == nil { + l.channels = make(map[string][]chan<- string) + } + if _, ok := l.channels[eventName]; !ok { + l.channels[eventName] = []chan<- string{} + } + log.WithField("name", eventName).Debug("Channel booked") +} + // SetLimit sets the limit for the `eventName`. When the same event (name and data) // is emitted within last time duration (`limit`), event is dropped. Zero limit clears // the limit for the specific `eventName`. diff --git a/pkg/listener/listener_test.go b/pkg/listener/listener_test.go index f45800bf..7e9857d0 100644 --- a/pkg/listener/listener_test.go +++ b/pkg/listener/listener_test.go @@ -59,6 +59,9 @@ func TestAddAndRemove(t *testing.T) { channel := make(chan string) listener.Add("event", channel) + listener.Emit("event", "hello!") + checkChannelEmitted(t, channel, "hello!") + listener.Remove("event", channel) listener.Emit("event", "hello!")