From 91163b8bedb2d569691591afcd6459853c5591a8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Thu, 6 Apr 2023 14:27:46 -0400
Subject: [PATCH] migration: avoid race conditions for account status

Setting an account to migrating is asynchronous. So the status
of the account should be checked via dedicated signals and not
just at the startup, else the migration view is not shown.

Also removing two accounts to migrate wasn't working correctly.

Change-Id: I0bb716982ec5fe76aaba3ad4cb6f47b1ef0aa710
GitLab: #1062
---
 extras/ci/client-qt-gnulinux/Jenkinsfile |  2 +-
 src/app/AccountMigrationView.qml         | 13 +---
 src/app/MainApplicationWindow.qml        | 29 ++++---
 src/app/ViewCoordinator.qml              |  1 +
 src/app/constant/JamiQmlUtils.qml        |  1 -
 src/app/currentaccounttomigrate.cpp      | 96 ++++++++++++++++--------
 src/app/currentaccounttomigrate.h        |  6 ++
 7 files changed, 92 insertions(+), 56 deletions(-)

diff --git a/extras/ci/client-qt-gnulinux/Jenkinsfile b/extras/ci/client-qt-gnulinux/Jenkinsfile
index 1c4e4f256..2d5f1961e 100644
--- a/extras/ci/client-qt-gnulinux/Jenkinsfile
+++ b/extras/ci/client-qt-gnulinux/Jenkinsfile
@@ -119,7 +119,7 @@ pipeline {
                                 // Run tests
                                 exec_cmd("""
                                     cd ${dockerTopDir}/build/tests
-                                    HOME=/tmp ctest -j${cpuCount} -V -C Release
+                                    HOME=/tmp ctest -V -C Release -j${cpuCount}
                                 """)
                             }
                         }
diff --git a/src/app/AccountMigrationView.qml b/src/app/AccountMigrationView.qml
index 37a59468f..c3c5f6bf9 100644
--- a/src/app/AccountMigrationView.qml
+++ b/src/app/AccountMigrationView.qml
@@ -28,7 +28,7 @@ import net.jami.Constants 1.1
 import "commoncomponents"
 
 // Account Migration Dialog for migrating account
-Rectangle {
+BaseView {
     id: root
 
     enum AccountMigrationStep {
@@ -54,8 +54,6 @@ Rectangle {
         CurrentAccountToMigrate.removeCurrentAccountToMigrate()
     }
 
-    color: JamiTheme.backgroundColor
-
     Timer {
         id: timerFailureReturn
 
@@ -94,15 +92,6 @@ Rectangle {
             stackedWidget.currentIndex =
                     AccountMigrationView.AccountMigrationStep.PasswordEnter
         }
-
-        function onAllMigrationsFinished() {
-            if (UtilsAdapter.getAccountListSize() === 0)
-                root.loaderSourceChangeRequested(
-                            MainApplicationWindow.LoadedSource.WizardView)
-            else
-                root.loaderSourceChangeRequested(
-                            MainApplicationWindow.LoadedSource.MainView)
-        }
     }
 
     ColumnLayout {
diff --git a/src/app/MainApplicationWindow.qml b/src/app/MainApplicationWindow.qml
index b8f10e34d..46d995cd6 100644
--- a/src/app/MainApplicationWindow.qml
+++ b/src/app/MainApplicationWindow.qml
@@ -78,10 +78,6 @@ ApplicationWindow {
         }
     }
 
-    function startAccountMigration() {
-        setMainLoaderSource(JamiQmlUtils.accountMigrationViewLoadPath)
-    }
-
     function setMainLoaderSource(source) {
         if (checkLoadedSource() === MainApplicationWindow.LoadedSource.MainView) {
             cleanupMainView()
@@ -142,12 +138,29 @@ ApplicationWindow {
             }
         }
 
+        Connections {
+            id: connectionMigrationEnded
+
+            target: CurrentAccountToMigrate
+
+            function onAccountNeedsMigration(accountId) {
+                viewCoordinator.present("AccountMigrationView")
+            }
+
+            function onAllMigrationsFinished() {
+                viewCoordinator.dismiss("AccountMigrationView")
+                startClient()
+            }
+        }
+
         Connections {
             target: mainApplicationLoader.item
 
             function onLoaderSourceChangeRequested(sourceToLoad) {
                 if (sourceToLoad === MainApplicationWindow.LoadedSource.WizardView)
                     setMainLoaderSource(JamiQmlUtils.wizardViewLoadPath)
+                else if (sourceToLoad === MainApplicationWindow.LoadedSource.AccountMigrationView)
+                    setMainLoaderSource(JamiQmlUtils.accountMigrationViewLoadPath)
                 else
                     setMainLoaderSource(JamiQmlUtils.mainViewLoadPath)
             }
@@ -180,6 +193,8 @@ ApplicationWindow {
                 })
                 // Set the viewCoordinator's root item.
                 viewCoordinator.init(item)
+                if (CurrentAccountToMigrate.accountToMigrateListSize > 0)
+                    viewCoordinator.present("AccountMigrationView")
             }
             if (Qt.platform.os.toString() === "osx") {
                 MainApplication.setEventFilter()
@@ -248,11 +263,7 @@ ApplicationWindow {
     onClosing: root.close()
 
     Component.onCompleted: {
-        if (CurrentAccountToMigrate.accountToMigrateListSize <= 0)
-            startClient()
-        else
-            startAccountMigration()
-
+        startClient()
         if (Qt.platform.os.toString()  !== "windows" && Qt.platform.os.toString()  !== "osx")
             DBusErrorHandler.setActive(true)
     }
diff --git a/src/app/ViewCoordinator.qml b/src/app/ViewCoordinator.qml
index c4c9148d8..59e9925ea 100644
--- a/src/app/ViewCoordinator.qml
+++ b/src/app/ViewCoordinator.qml
@@ -39,6 +39,7 @@ QtObject {
         "ConversationView": "mainview/ConversationView.qml",
         "NewSwarmPage": "mainview/components/NewSwarmPage.qml",
         "WizardView": "wizardview/WizardView.qml",
+        "AccountMigrationView": "AccountMigrationView.qml",
         "SettingsView": "settingsview/SettingsView.qml",
         "SettingsSidePanel": "settingsview/SettingsSidePanel.qml",
     }
diff --git a/src/app/constant/JamiQmlUtils.qml b/src/app/constant/JamiQmlUtils.qml
index 4d051a980..82334258e 100644
--- a/src/app/constant/JamiQmlUtils.qml
+++ b/src/app/constant/JamiQmlUtils.qml
@@ -28,7 +28,6 @@ Item {
 
     readonly property string mainViewLoadPath: "qrc:/mainview/MainView.qml"
     readonly property string wizardViewLoadPath: "qrc:/wizardview/WizardView.qml"
-    readonly property string accountMigrationViewLoadPath: "qrc:/AccountMigrationView.qml"
     readonly property string base64StringTitle: "data:image/png;base64,"
 
     property var accountCreationInputParaObject: ({})
diff --git a/src/app/currentaccounttomigrate.cpp b/src/app/currentaccounttomigrate.cpp
index 93e34e87c..8292f68be 100644
--- a/src/app/currentaccounttomigrate.cpp
+++ b/src/app/currentaccounttomigrate.cpp
@@ -40,28 +40,22 @@ CurrentAccountToMigrate::CurrentAccountToMigrate(LRCInstance* instance, QObject*
     }
 
     if (accountToMigrateList_.size()) {
-        migrationEndedConnection_ = connect(
-            &lrcInstance_->accountModel(),
-            &lrc::api::AccountModel::migrationEnded,
-            this,
-            [this](const QString& accountId, bool ok) {
-                if (ok && accountToMigrateList_.removeOne(accountId)) {
-                    updateData();
-                }
-
-                if (accountToMigrateList_.isEmpty()) {
-                    disconnect(migrationEndedConnection_);
-                    Q_EMIT allMigrationsFinished();
-
-                    return;
-                }
-
-                Q_EMIT migrationEnded(ok);
-            },
-            Qt::ConnectionType::QueuedConnection);
-
+        connectMigrationEnded();
         updateData();
     }
+
+    connect(&lrcInstance_->accountModel(),
+            &AccountModel::accountStatusChanged,
+            this,
+            &CurrentAccountToMigrate::slotAccountStatusChanged);
+    connect(&lrcInstance_->accountModel(),
+            &AccountModel::accountAdded,
+            this,
+            &CurrentAccountToMigrate::slotAccountStatusChanged);
+    connect(&lrcInstance_->accountModel(),
+            &AccountModel::accountRemoved,
+            this,
+            &CurrentAccountToMigrate::slotAccountRemoved);
 }
 
 CurrentAccountToMigrate::~CurrentAccountToMigrate() {}
@@ -69,19 +63,6 @@ CurrentAccountToMigrate::~CurrentAccountToMigrate() {}
 void
 CurrentAccountToMigrate::removeCurrentAccountToMigrate()
 {
-    if (accountToMigrateList_.removeOne(get_accountId())) {
-        updateData();
-    }
-
-    Utils::oneShotConnect(&lrcInstance_->accountModel(),
-                          &lrc::api::AccountModel::accountRemoved,
-                          [this] {
-                              if (accountToMigrateList_.isEmpty())
-                                  Q_EMIT allMigrationsFinished();
-                              else
-                                  Q_EMIT currentAccountToMigrateRemoved();
-                          });
-
     lrcInstance_->accountModel().removeAccount(get_accountId());
 }
 
@@ -102,3 +83,52 @@ CurrentAccountToMigrate::updateData()
     set_username(avatarInfo.confProperties.username);
     set_alias(lrcInstance_->accountModel().getAccountInfo(accountId).profileInfo.alias);
 }
+
+void
+CurrentAccountToMigrate::connectMigrationEnded()
+{
+    migrationEndedConnection_ = connect(
+        &lrcInstance_->accountModel(),
+        &lrc::api::AccountModel::migrationEnded,
+        this,
+        [this](const QString& accountId, bool ok) {
+            if (ok && accountToMigrateList_.removeOne(accountId)) {
+                updateData();
+            }
+
+            if (accountToMigrateList_.isEmpty()) {
+                disconnect(migrationEndedConnection_);
+                Q_EMIT allMigrationsFinished();
+                return;
+            }
+
+            Q_EMIT migrationEnded(ok);
+        },
+        Qt::ConnectionType::QueuedConnection);
+}
+
+void
+CurrentAccountToMigrate::slotAccountStatusChanged(const QString& accountId)
+{
+    try {
+        auto& accountInfo = lrcInstance_->getAccountInfo(accountId);
+        if (accountInfo.status == lrc::api::account::Status::ERROR_NEED_MIGRATION) {
+            accountToMigrateList_.append(accountId);
+            updateData();
+            connectMigrationEnded();
+            Q_EMIT accountNeedsMigration(accountId);
+        }
+    } catch (...) {
+    }
+}
+
+void
+CurrentAccountToMigrate::slotAccountRemoved(const QString& accountId)
+{
+    if (accountToMigrateList_.removeOne(accountId))
+        updateData();
+    if (accountToMigrateList_.isEmpty())
+        Q_EMIT allMigrationsFinished();
+    else
+        Q_EMIT currentAccountToMigrateRemoved();
+}
\ No newline at end of file
diff --git a/src/app/currentaccounttomigrate.h b/src/app/currentaccounttomigrate.h
index 611fb52bb..8f1bbd0ec 100644
--- a/src/app/currentaccounttomigrate.h
+++ b/src/app/currentaccounttomigrate.h
@@ -45,8 +45,14 @@ Q_SIGNALS:
     void migrationEnded(bool success);
     void allMigrationsFinished();
     void currentAccountToMigrateRemoved();
+    void accountNeedsMigration(const QString& accountId);
+
+private Q_SLOTS:
+    void slotAccountStatusChanged(const QString& accountId);
+    void slotAccountRemoved(const QString& accountId);
 
 private:
+    void connectMigrationEnded();
     void updateData();
 
     LRCInstance* lrcInstance_;
-- 
GitLab