diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4f3e8f53..168d97ad 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,7 +163,7 @@ jobs: run: | git clone --depth=1 -b $QUOTIENT_REF https://github.com/quotient-im/libQuotient cd libQuotient - cmake -S . -B build $CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=~/.local + cmake -S . -B build $CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=~/.local -DCMAKE_PREFIX_PATH=~/.local cmake --build build --target install if [[ '${{ matrix.composition }}' == 'dynamic' ]]; then QUOTIENT_SO_PATH=$(dirname $(find ~/.local/lib* -name libQuotient.so)) diff --git a/CMakeLists.txt b/CMakeLists.txt index e8933aa1..61cb33f6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,8 +105,6 @@ if (NOT USE_INTREE_LIBQMC) endif () endif () -find_package(${Qt}Keychain REQUIRED) - message( STATUS ) message( STATUS "=============================================================================" ) message( STATUS " Quaternion ${quaternion_VERSION} Build Information" ) @@ -140,13 +138,11 @@ if (USE_INTREE_LIBQMC) else () message( STATUS "Using libQuotient ${Quotient_VERSION} at ${Quotient_DIR}") endif () -message( STATUS "Using Qt Keychain ${${Qt}Keychain_VERSION} at ${${Qt}Keychain_DIR}") message( STATUS "=============================================================================" ) message( STATUS ) # Set up source files set(quaternion_SRCS - client/accountregistry.cpp client/quaternionroom.cpp client/htmlfilter.cpp client/imageprovider.cpp @@ -234,10 +230,6 @@ endif () target_link_libraries(${PROJECT_NAME} Quotient ${Qt}::Widgets ${Qt}::Quick ${Qt}::Qml ${Qt}::Gui ${Qt}::Network ${Qt}::QuickControls2 ${Qt}::QuickWidgets) -target_compile_definitions(${PROJECT_NAME} PRIVATE USE_KEYCHAIN) # Legacy, remove in 0.0.96+ -target_link_libraries(${PROJECT_NAME} ${QTKEYCHAIN_LIBRARIES}) -include_directories(${QTKEYCHAIN_INCLUDE_DIR}) - # macOS specific config for bundling if (APPLE) set_property(TARGET ${PROJECT_NAME} PROPERTY MACOSX_BUNDLE_INFO_PLIST diff --git a/client/accountregistry.cpp b/client/accountregistry.cpp deleted file mode 100644 index e2813258..00000000 --- a/client/accountregistry.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "accountregistry.h" - -#include - -void AccountRegistry::add(AccountRegistry::Account* a) -{ - if (contains(a)) - return; - push_back(a); - emit addedAccount(a); -} - -void AccountRegistry::drop(Account* a) -{ - emit aboutToDropAccount(a); - removeOne(a); - Q_ASSERT(!contains(a)); -} - -bool AccountRegistry::isLoggedIn(const QString &userId) const -{ - return std::any_of(cbegin(), cend(), - [&userId](Account* a) { return a->userId() == userId; }); -} diff --git a/client/accountregistry.h b/client/accountregistry.h deleted file mode 100644 index 32d70ace..00000000 --- a/client/accountregistry.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#include -#include - -namespace Quotient { -class Connection; -} - -class AccountRegistry : public QObject, private QVector { - Q_OBJECT -public: - using Account = Quotient::Connection; - using storage_type = QVector; - using const_iterator = storage_type::const_iterator; - using const_reference = storage_type::const_reference; - - const QVector& accounts() const { return *this; } - void add(Account* a); - void drop(Account* a); - bool isLoggedIn(const QString& userId) const; - const_iterator begin() const { return storage_type::begin(); } - const_iterator end() const { return storage_type::end(); } - const_reference front() const { return storage_type::front(); } - const_reference back() const { return storage_type::back(); } - using storage_type::isEmpty, storage_type::empty; - using storage_type::size, storage_type::count, storage_type::capacity; - using storage_type::cbegin, storage_type::cend; - using storage_type::contains; - -signals: - void addedAccount(Account* a); - void aboutToDropAccount(Account* a); -}; - diff --git a/client/accountselector.cpp b/client/accountselector.cpp index 6723c999..2b222ab3 100644 --- a/client/accountselector.cpp +++ b/client/accountselector.cpp @@ -2,34 +2,42 @@ #include -AccountSelector::AccountSelector(const AccountRegistry *registry, QWidget *parent) +using namespace Quotient; + +AccountSelector::AccountSelector(QWidget *parent) : QComboBox(parent) { connect(this, QOverload::of(&QComboBox::currentIndexChanged), this, [this] { emit currentAccountChanged(currentAccount()); }); - const auto& accounts = registry->accounts(); + const auto& accounts = Accounts.accounts(); for (auto* acc: accounts) addItem(acc->userId(), QVariant::fromValue(acc)); - connect(registry, &AccountRegistry::addedAccount, this, [this](Account* acc) { - if (const auto idx = indexOfAccount(acc); idx == -1) - addItem(acc->userId(), QVariant::fromValue(acc)); - else - qWarning() - << "AccountComboBox: refusing to add the same account twice"; + connect(&Accounts, &AccountRegistry::rowsInserted, this, [this](const QModelIndex&, int first, int last) { + for (int i = first; i < last; i++) { + auto acc = Accounts.accounts()[i]; + if (const auto idx = indexOfAccount(acc); idx == -1) + addItem(acc->userId(), QVariant::fromValue(acc)); + else + qWarning() + << "AccountComboBox: refusing to add the same account twice"; + } }); - connect(registry, &AccountRegistry::aboutToDropAccount, this, - [this](Account* acc) { - if (const auto idx = indexOfAccount(acc); idx != -1) - removeItem(idx); - else - qWarning() - << "AccountComboBox: account to drop not found, ignoring"; + connect(&Accounts, &AccountRegistry::rowsAboutToBeRemoved, this, + [this](const QModelIndex&, int first, int last) { + for (int i = first; i < last; i++) { + auto acc = Accounts.accounts()[i]; + if (const auto idx = indexOfAccount(acc); idx != -1) + removeItem(idx); + else + qWarning() + << "AccountComboBox: account to drop not found, ignoring"; + } }); } -void AccountSelector::setAccount(Account *newAccount) +void AccountSelector::setAccount(Connection *newAccount) { if (!newAccount) { setCurrentIndex(-1); @@ -45,15 +53,15 @@ void AccountSelector::setAccount(Account *newAccount) << "wasn't found in the full list of accounts"; } -AccountSelector::Account* AccountSelector::currentAccount() const +Connection* AccountSelector::currentAccount() const { - return currentData().value(); + return currentData().value(); } -int AccountSelector::indexOfAccount(Account* a) const +int AccountSelector::indexOfAccount(Connection* a) const { for (int i = 0; i < count(); ++i) - if (itemData(i).value() == a) + if (itemData(i).value() == a) return i; return -1; diff --git a/client/accountselector.h b/client/accountselector.h index be0d0f76..93c56d35 100644 --- a/client/accountselector.h +++ b/client/accountselector.h @@ -1,22 +1,25 @@ #pragma once -#include "accountregistry.h" +#include #include +namespace Quotient +{ + class Connection; +} + class AccountSelector : public QComboBox { Q_OBJECT public: - using Account = AccountRegistry::Account; - - AccountSelector(const AccountRegistry* registry, QWidget* parent = nullptr); + AccountSelector(QWidget* parent = nullptr); - void setAccount(Account* newAccount); - Account* currentAccount() const; - int indexOfAccount(Account* a) const; + void setAccount(Quotient::Connection* newAccount); + Quotient::Connection* currentAccount() const; + int indexOfAccount(Quotient::Connection* a) const; signals: - void currentAccountChanged(Account* newAccount); + void currentAccountChanged(Quotient::Connection* newAccount); }; diff --git a/client/mainwindow.cpp b/client/mainwindow.cpp index 86f9b5ed..af7b7b20 100644 --- a/client/mainwindow.cpp +++ b/client/mainwindow.cpp @@ -39,12 +39,6 @@ #include #include -#if QT_VERSION_MAJOR >= 6 -# include -#else -# include -#endif - #include #include #include @@ -81,6 +75,8 @@ using Quotient::Settings; using Quotient::AccountSettings; using Quotient::Uri; +using Quotient::Accounts; + MainWindow::MainWindow() { Connection::setRoomType(); @@ -128,18 +124,17 @@ MainWindow::MainWindow() busyLabel->show(); busyIndicator->start(); - QTimer::singleShot(0, this, &MainWindow::invokeLogin); + QMetaObject::invokeMethod(&Accounts, &Quotient::AccountRegistry::invokeLogin); + connect(&Accounts, &Quotient::AccountRegistry::rowsInserted, this, [this](const QModelIndex&, int first, int){ + addConnection(Accounts.accounts()[first]); + }); + connect(&Accounts, &Quotient::AccountRegistry::rowsAboutToBeRemoved, this, [this](const QModelIndex&, int first, int){ + roomListDock->deleteConnection(Accounts.accounts()[first]); + }); } MainWindow::~MainWindow() { - for (auto* acc: accountRegistry) - { - acc->saveState(); - acc->stopSync(); // Instead of deleting the connection, merely stop it - } - for (auto* acc: qAsConst(logoutOnExit)) - logout(acc); saveSettings(); } @@ -189,7 +184,7 @@ void MainWindow::createMenu() connectionMenu->addAction( QIcon::fromTheme("user-properties"), tr("User &profiles..."), this, [this, dlg = QPointer {}]() mutable { - summon(dlg, &accountRegistry, this); + summon(dlg, this); if (currentRoom) dlg->setAccount(currentRoom->connection()); }); @@ -341,7 +336,7 @@ void MainWindow::createMenu() tr("Create &new room..."), [this] { static QPointer dlg; - summon(dlg, &accountRegistry, this); + summon(dlg, this); }); createRoomAction->setShortcut(QKeySequence::New); createRoomAction->setDisabled(true); @@ -519,262 +514,13 @@ void MainWindow::saveSettings() const sg.sync(); } -template -inline QString accessTokenKey(const KeySourceT& source, bool legacyLocation) -{ - auto k = source.userId(); - if (!legacyLocation) { - if (source.deviceId().isEmpty()) - qWarning() << "Device id on the account is not set"; - else - k += '-' % source.deviceId(); - } - return k; -} - -template -inline std::unique_ptr makeKeychainJob(const QString& appName, - const QString& key, - bool legacyLocation = false) -{ - auto slotName = appName; - if (!legacyLocation) - slotName += " access token for " % key; - auto j = std::make_unique(slotName); - j->setAutoDelete(false); - j->setKey(key); - return j; -} - -template -inline QString accessTokenFileName(const KeySourceT& account, - bool legacyLocation = false) -{ - auto fileName = accessTokenKey(account, legacyLocation); - fileName.replace(':', '_'); - return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation) - % '/' % fileName; -} - -class AccessTokenFile : public QFile { // clazy:exclude=missing-qobject-macro - bool legacyLocation = false; - -public: - template - explicit AccessTokenFile(const KeySourceT& source, OpenMode mode = ReadOnly) - { - Q_ASSERT(mode == ReadOnly || mode == WriteOnly); - if (mode == WriteOnly) { - remove(accessTokenFileName(source, true)); - setFileName(accessTokenFileName(source, false)); - remove(); - const auto fileDir = QFileInfo(*this).dir(); - if (fileDir.exists() || fileDir.mkpath(".")) - open(QFile::WriteOnly); - return; - } - for (bool getLegacyLocation: { false, true }) { - setFileName(accessTokenFileName(source, getLegacyLocation)); - if (open(QFile::ReadOnly)) { - if (size() < 1024) { - qDebug() << "Found access token file at" << fileName(); - legacyLocation = getLegacyLocation; - return; - } - qWarning() << "File" << fileName() << "is" << size() - << "bytes long - too long for a token, ignoring it."; - } else - qWarning() << "Could not open access token file" << fileName(); - close(); - } - } - [[nodiscard]] bool isAtLegacyLocation() const { return legacyLocation; } -}; - -QByteArray MainWindow::loadAccessToken(const AccountSettings& account) -{ -#ifdef USE_KEYCHAIN - if (Settings().get("UI/use_keychain", true)) - return loadAccessTokenFromKeyChain(account); - - qDebug() << "Explicit opt-out from keychain by user setting"; -#endif - return AccessTokenFile(account).readAll(); -} - -#ifdef USE_KEYCHAIN -QByteArray MainWindow::loadAccessTokenFromKeyChain(const AccountSettings& account) -{ - using namespace QKeychain; - auto lastError = Error::OtherError; - bool legacyLocation = true; - do { - legacyLocation = !legacyLocation; // Start with non-legacy - - const auto& key = accessTokenKey(account, legacyLocation); - qDebug().noquote() << "Reading the access token from the keychain for" - << key; - auto job = - makeKeychainJob(qAppName(), key, legacyLocation); - QEventLoop loop; - connect(job.get(), &Job::finished, &loop, &QEventLoop::quit); - job->start(); - loop.exec(); - - if (job->error() == Error::NoError) { - auto token = job->binaryData(); - if (legacyLocation) { - qDebug() << "Migrating the token to the new keychain slot"; - if (saveAccessTokenToKeyChain(account, token, false)) { - auto* delJob = new DeletePasswordJob(qAppName()); - delJob->setAutoDelete(true); - delJob->setKey(accessTokenKey(account, true)); - connect(delJob, &Job::finished, this, [delJob] { - if (delJob->error() != Error::NoError) - qWarning().noquote() - << "Cleanup of the old keychain slot failed:" - << delJob->errorString(); - }); - delJob->start(); // Run async and move on - } - } - return token; - } - - qWarning().noquote() << "Could not read" << job->service() - << "from the keychain:" << job->errorString(); - lastError = job->error(); - } while (!legacyLocation); // Exit once the legacy round is through - - // Try token file - AccessTokenFile atf(account); - const auto& accessToken = atf.readAll(); - // Only offer migration if QtKeychain is usable but doesn't have the entry - if (lastError == Error::EntryNotFound && !accessToken.isEmpty() - && QMessageBox::warning( - this, tr("Access token file found"), - tr("Do you want to migrate the access token for %1 " - "from the file to the keychain?") - .arg(account.userId()), - QMessageBox::Yes | QMessageBox::No) - == QMessageBox::Yes) { - qInfo() << "Migrating the access token for" << account.userId() - << "from the file to the keychain"; - if (saveAccessTokenToKeyChain(account, accessToken, false)) { - if (!atf.remove()) - qWarning() - << "Could not remove the access token after migration"; - } else { - qWarning() << "Migration of the access token failed"; - QMessageBox::warning(this, tr("Couldn't migrate access token"), - tr("Quaternion couldn't migrate access token for %1 " - "from the file to the keychain.") - .arg(account.userId()), - QMessageBox::Close); - } - } - return accessToken; -} -#endif - -bool MainWindow::saveAccessToken(const AccountSettings& account, - const QByteArray& accessToken) -{ -#ifdef USE_KEYCHAIN - if (Settings().get("UI/use_keychain", true)) - return saveAccessTokenToKeyChain(account, accessToken); - - qDebug() << "Explicit opt-out from keychain by user setting"; -#endif // fall through to non QtKeychain-specific code - return saveAccessTokenToFile(account, accessToken); -} - -bool MainWindow::saveAccessTokenToFile(const AccountSettings& account, - const QByteArray& accessToken) -{ - // (Re-)Make a dedicated file for access_token. - AccessTokenFile accountTokenFile(account, QFile::WriteOnly); - if (!accountTokenFile.isOpen()) { - QMessageBox::warning(this, - tr("Couldn't open a file to save access token"), - tr("Quaternion couldn't open a file to write the" - " access token to. You're logged in but will have" - " to provide your password again when you restart" - " the application."), QMessageBox::Close); - } else { - // Try to restrict access rights to the file. The below is useless - // on Windows: FAT doesn't control access at all and NTFS is - // incompatible with the UNIX perms model used by Qt. If the attempt - // didn't have the effect, at least ask the user if it's fine to save - // the token to a file readable by others. - // TODO: use system-specific API to ensure proper access. - if ((accountTokenFile.setPermissions( - QFile::ReadOwner|QFile::WriteOwner) && - !(accountTokenFile.permissions() & - (QFile::ReadGroup|QFile::ReadOther))) - || - QMessageBox::warning(this, - tr("Couldn't set access token file permissions"), - tr("Quaternion couldn't restrict permissions on the" - " access token file. Do you still want to save" - " the access token to it?"), - QMessageBox::Yes|QMessageBox::No - ) == QMessageBox::Yes) - { - accountTokenFile.write(accessToken); - return true; - } - } - return false; -} - -#ifdef USE_KEYCHAIN -bool MainWindow::saveAccessTokenToKeyChain(const AccountSettings& account, - const QByteArray& accessToken, - bool writeToFile) -{ - using namespace QKeychain; - const auto key = accessTokenKey(account, false); - qDebug().noquote() << "Save the access token to the keychain for" << key; - auto job = makeKeychainJob(qAppName(), key); - job->setBinaryData(accessToken); - QEventLoop loop; - connect(job.get(), &Job::finished, &loop, &QEventLoop::quit); - job->start(); - loop.exec(); - - if (!job->error()) - return true; - - qWarning().noquote() << "Could not save access token to the keychain:" - << job->errorString(); - if (job->error() != Error::NoBackendAvailable - && job->error() != Error::NotImplemented - && job->error() != Error::OtherError) { - if (writeToFile) { - const auto button = QMessageBox::warning( - this, tr("Couldn't save access token"), - tr("Quaternion couldn't save the access token to the keychain." - " Do you want to save the access token to file %1?") - .arg(accessTokenFileName(account)), - QMessageBox::Yes | QMessageBox::No); - if (button == QMessageBox::Yes) - return saveAccessTokenToFile(account, accessToken); - } - return false; - } - return saveAccessTokenToFile(account, accessToken); -} -#endif - -void MainWindow::addConnection(Connection* c, const QString& deviceName) +void MainWindow::addConnection(Connection* c) { Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection"); using Room = Quotient::Room; c->setLazyLoading(true); - accountRegistry.add(c); roomListDock->addConnection(c); @@ -801,7 +547,7 @@ void MainWindow::addConnection(Connection* c, const QString& deviceName) connect(c, &Connection::syncError, this, [this, c](const QString& message, const QString& details) { QMessageBox msgBox(QMessageBox::Warning, tr("Sync failed"), - accountRegistry.size() > 1 + Accounts.size() > 1 ? tr("The last sync of account %1 has failed with error: %2") .arg(c->userId(), message) : tr("The last sync has failed with error: %1").arg(message), @@ -867,13 +613,11 @@ void MainWindow::addConnection(Connection* c, const QString& deviceName) // Update the menu QString accountCaption = c->userId(); - if (!deviceName.isEmpty()) - accountCaption += '/' % deviceName; QString menuCaption = accountCaption; - if (accountRegistry.size() < 10) - menuCaption.prepend('&' % QString::number(accountRegistry.size()) % ' '); + if (Accounts.size() < 10) + menuCaption.prepend('&' % QString::number(Accounts.size()) % ' '); auto logoutAction = - logoutMenu->addAction(menuCaption, [this, c] { logout(c); }); + logoutMenu->addAction(menuCaption, [this, c] { c->logout(); }); connect(c, &Connection::destroyed, logoutMenu, std::bind(&QMenu::removeAction, logoutMenu, logoutAction)); openRoomAction->setEnabled(true); @@ -887,10 +631,9 @@ void MainWindow::dropConnection(Connection* c) if (currentRoom && currentRoom->connection() == c) selectRoom(nullptr); - accountRegistry.drop(c); logoutOnExit.removeOne(c); - const auto noMoreAccounts = accountRegistry.isEmpty(); + const auto noMoreAccounts = Accounts.isEmpty(); openRoomAction->setDisabled(noMoreAccounts); createRoomAction->setDisabled(noMoreAccounts); joinAction->setDisabled(noMoreAccounts); @@ -924,7 +667,7 @@ void MainWindow::showLoginWindow(const QString& statusMessage) QStringList loggedOffAccounts; for (const auto& a: allKnownAccounts) // Skip already logged in accounts - if (!accountRegistry.isLoggedIn(AccountSettings(a).userId())) + if (!Accounts.isLoggedIn(AccountSettings(a).userId())) loggedOffAccounts.push_back(a); doOpenLoginDialog(new LoginDialog(statusMessage, this, loggedOffAccounts)); @@ -938,7 +681,6 @@ void MainWindow::showLoginWindow(const QString& statusMessage, reloginAccount->setParent(dialog); // => Delete with the dialog box doOpenLoginDialog(dialog); connect(dialog, &QDialog::rejected, this, [reloginAccount] { - AccessTokenFile(*reloginAccount).remove(); // XXX: Maybe even remove the account altogether as below? // Quotient::SettingsGroup("Accounts").remove(reloginAccount->userId()); }); @@ -959,8 +701,7 @@ void MainWindow::doOpenLoginDialog(LoginDialog* dialog) account.setDeviceId(connection->deviceId()); account.setDeviceName(dialog->deviceName()); if (dialog->keepLoggedIn()) { - if (!saveAccessToken(account, connection->accessToken())) - qWarning() << "Couldn't save access token"; + //TODO implement this in some other way } else logoutOnExit.push_back(connection); account.sync(); @@ -970,19 +711,6 @@ void MainWindow::doOpenLoginDialog(LoginDialog* dialog) firstSyncing.push_back(connection); showFirstSyncIndicator(); - - if (accountRegistry.isLoggedIn(connection->userId())) { - if (QMessageBox::warning( - this, tr("Logging in into a logged in account"), - tr("You're trying to log in into an account that's " - "already logged in. Do you want to continue?"), - QMessageBox::Yes, QMessageBox::No) - != QMessageBox::Yes) - return; - - deviceName += "-" + connection->deviceId(); - } - addConnection(connection, deviceName); }); connect(dialog, &QDialog::rejected, dialog, &QObject::deleteLater); } @@ -1073,42 +801,6 @@ void MainWindow::showAboutWindow() aboutDialog.exec(); } -void MainWindow::invokeLogin() -{ - using namespace Quotient; - const auto accounts = SettingsGroup("Accounts").childGroups(); - bool autoLoggedIn = false; - for(const auto& accountId: accounts) - { - AccountSettings account { accountId }; - if (!account.homeserver().isEmpty()) - { - auto accessToken = loadAccessToken(account); - if (accessToken.isEmpty()) - continue; // No autologin for this account - - autoLoggedIn = true; - auto c = new Connection(account.homeserver()); - firstSyncing.push_back(c); - auto deviceName = account.deviceName(); - connect(c, &Connection::connected, this, [this, c, deviceName] { - c->loadState(); - addConnection(c, deviceName); - }); - connect(c, &Connection::resolveError, this, [this, c] { - firstSyncOver(c); - statusBar()->showMessage( - tr("Failed to resolve server %1").arg(c->domain()), 4000); - }); - c->assumeIdentity(account.userId(), accessToken, account.deviceId()); - } - } - if (autoLoggedIn) - showFirstSyncIndicator(); - else - showLoginWindow(tr("Welcome to Quaternion")); -} - void MainWindow::loginError(Connection* c, const QString& message) { Q_ASSERT_X(c, __FUNCTION__, "Login error on a null connection"); @@ -1119,50 +811,6 @@ void MainWindow::loginError(Connection* c, const QString& message) showLoginWindow(message, c->userId()); } -void MainWindow::logout(Connection* c) -{ - Q_ASSERT_X(c, __FUNCTION__, "Logout on a null connection"); - - AccessTokenFile(*c).remove(); - -#ifdef USE_KEYCHAIN - if (Settings().get("UI/use_keychain", true)) - for (bool legacyLocation: { false, true }) { - using namespace QKeychain; - auto* job = new DeletePasswordJob(qAppName()); - job->setAutoDelete(true); - job->setKey(accessTokenKey(*c, legacyLocation)); - connect(job, &Job::finished, this, [this, job] { - switch (job->error()) { - case Error::EntryNotFound: - qDebug() << "Access token is not in the keychain, nothing " - "to delete"; - [[fallthrough]]; - case Error::NoError: - return; - // Actual errors follow - case Error::NoBackendAvailable: - case Error::NotImplemented: - case Error::OtherError: - break; - default: - QMessageBox::warning( - this, tr("Couldn't delete access token"), - tr("Quaternion couldn't delete the access " - "token from the keychain."), - QMessageBox::Close); - } - qWarning() - << "Could not delete access token from the keychain: " - << qUtf8Printable(job->errorString()); - }); - job->start(); - } -#endif - - c->logout(); -} - Quotient::UriResolveResult MainWindow::visitUser(Quotient::User* user, const QString& action) { @@ -1249,7 +897,7 @@ bool MainWindow::visitNonMatrix(const QUrl& url) MainWindow::Connection* MainWindow::getDefaultConnection() const { return currentRoom ? currentRoom->connection() : - accountRegistry.size() == 1 ? accountRegistry.front() : nullptr; + Accounts.size() == 1 ? Accounts.front() : nullptr; } void MainWindow::openResource(const QString& idOrUri, const QString& action) @@ -1346,13 +994,13 @@ void MainWindow::showStatusMessage(const QString& message, int timeout) MainWindow::Connection* MainWindow::chooseConnection(Connection* connection, const QString& prompt) { - Q_ASSERT(!accountRegistry.isEmpty()); - if (accountRegistry.size() == 1) - return accountRegistry.front(); + Q_ASSERT(!Accounts.isEmpty()); + if (Accounts.size() == 1) + return Accounts.front(); - QStringList names; names.reserve(accountRegistry.size()); + QStringList names; names.reserve(Accounts.size()); int defaultIdx = -1; - for (auto c: accountRegistry) + for (auto c: Accounts) { names.push_back(c->userId()); if (c == connection) @@ -1364,7 +1012,7 @@ MainWindow::Connection* MainWindow::chooseConnection(Connection* connection, if (!ok || choice.isEmpty()) return nullptr; - for (auto c: accountRegistry) + for (auto c: Accounts) if (c->userId() == choice) { connection = c; @@ -1376,7 +1024,7 @@ MainWindow::Connection* MainWindow::chooseConnection(Connection* connection, void MainWindow::openUserInput(bool forJoining) { - if (accountRegistry.isEmpty()) { + if (Accounts.accounts().isEmpty()) { showLoginWindow(tr("Please connect to a server")); return; } @@ -1398,14 +1046,14 @@ void MainWindow::openUserInput(bool forJoining) Dialog dlg(entry.dlgTitle, this, Dialog::NoStatusLine, entry.actionText, Dialog::NoExtraButtons); - auto* accountChooser = new AccountSelector(&accountRegistry); + auto* accountChooser = new AccountSelector(); auto* identifier = new QLineEdit(&dlg); auto* defaultConn = getDefaultConnection(); accountChooser->setAccount(defaultConn); // Lay out controls auto* layout = dlg.addLayout(); - if (accountRegistry.size() > 1) + if (Accounts.size() > 1) { layout->addRow(tr("Account"), accountChooser); accountChooser->setFocus(); diff --git a/client/mainwindow.h b/client/mainwindow.h index 45dd2aef..4f8304a7 100644 --- a/client/mainwindow.h +++ b/client/mainwindow.h @@ -26,6 +26,7 @@ #include #include +#include #include namespace Quotient { @@ -61,7 +62,7 @@ class MainWindow: public QMainWindow, public Quotient::UriResolverBase { MainWindow(); ~MainWindow() override; - void addConnection(Connection* c, const QString& deviceName); + void addConnection(Connection* c); void dropConnection(Connection* c); // For openUserInput() @@ -78,11 +79,8 @@ class MainWindow: public QMainWindow, public Quotient::UriResolverBase { void openRoomSettings(QuaternionRoom* r = nullptr); void selectRoom(Quotient::Room* r); void showStatusMessage(const QString& message, int timeout = 0); - void logout(Connection* c); private slots: - void invokeLogin(); - void loginError(Connection* c, const QString& message = {}); void networkError(Connection* c); void sslErrors(QNetworkReply* reply, const QList& errors); @@ -104,7 +102,6 @@ class MainWindow: public QMainWindow, public Quotient::UriResolverBase { bool visitNonMatrix(const QUrl& url) override; private: - AccountRegistry accountRegistry; QVector logoutOnExit; QVector firstSyncing; @@ -139,14 +136,6 @@ class MainWindow: public QMainWindow, public Quotient::UriResolverBase { void loadSettings(); void saveSettings() const; void doOpenLoginDialog(LoginDialog* dialog); - QByteArray loadAccessToken(const Quotient::AccountSettings& account); - QByteArray loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account); - bool saveAccessToken(const Quotient::AccountSettings& account, - const QByteArray& accessToken); - bool saveAccessTokenToFile(const Quotient::AccountSettings& account, - const QByteArray& accessToken); - bool saveAccessTokenToKeyChain(const Quotient::AccountSettings& account, - const QByteArray& accessToken, bool writeToFile = true); Connection* chooseConnection(Connection* connection, const QString& prompt); void showMillisToRecon(Connection* c); diff --git a/client/models/roomlistmodel.cpp b/client/models/roomlistmodel.cpp index 7f0aa696..6e2c57fd 100644 --- a/client/models/roomlistmodel.cpp +++ b/client/models/roomlistmodel.cpp @@ -49,8 +49,6 @@ void RoomListModel::addConnection(Quotient::Connection* connection) using namespace Quotient; m_connections.emplace_back(connection, this); - connect(connection, &Connection::loggedOut, this, - [this, connection] { deleteConnection(connection); }); connect(connection, &Connection::newRoom, this, &RoomListModel::addRoom); m_roomOrder->connectSignals(connection); diff --git a/client/profiledialog.cpp b/client/profiledialog.cpp index 9ddcb47f..a8c4fae4 100644 --- a/client/profiledialog.cpp +++ b/client/profiledialog.cpp @@ -147,24 +147,23 @@ void updateAvatarButton(Quotient::User* user, QPushButton* btn) } } -ProfileDialog::ProfileDialog(AccountRegistry* accounts, MainWindow* parent) +ProfileDialog::ProfileDialog(MainWindow* parent) : Dialog(tr("User profiles"), parent) , m_settings("UI/ProfileDialog") , m_avatar(new QPushButton) - , m_accountSelector(new AccountSelector(accounts)) + , m_accountSelector(new AccountSelector()) , m_displayName(new QLineEdit) , m_accessTokenLabel(new QLabel) , m_currentAccount(nullptr) { - Q_ASSERT(accounts != nullptr); auto* accountLayout = addLayout(); accountLayout->addRow(tr("Account"), m_accountSelector); connect(m_accountSelector, &AccountSelector::currentAccountChanged, this, &ProfileDialog::load); - connect(accounts, &AccountRegistry::aboutToDropAccount, this, - [this, accounts] { - if (accounts->size() == 1) + connect(&Quotient::Accounts, &Quotient::AccountRegistry::rowsAboutToBeRemoved, this, + [this] { + if (Quotient::Accounts.size() == 1) close(); // The last account is about to be dropped }); @@ -206,12 +205,12 @@ ProfileDialog::~ProfileDialog() m_settings.sync(); } -void ProfileDialog::setAccount(Account* newAccount) +void ProfileDialog::setAccount(Quotient::Connection* newAccount) { m_accountSelector->setAccount(newAccount); } -ProfileDialog::Account* ProfileDialog::account() const +Quotient::Connection* ProfileDialog::account() const { return m_currentAccount; } diff --git a/client/profiledialog.h b/client/profiledialog.h index 5b2772df..3c33ed81 100644 --- a/client/profiledialog.h +++ b/client/profiledialog.h @@ -35,19 +35,18 @@ class QLineEdit; namespace Quotient { class GetDevicesJob; + class Connection; } class ProfileDialog : public Dialog { Q_OBJECT public: - using Account = AccountRegistry::Account; - - explicit ProfileDialog(AccountRegistry* accounts, MainWindow* parent); + explicit ProfileDialog(MainWindow* parent); ~ProfileDialog() override; - void setAccount(Account* newAccount); - Account* account() const; + void setAccount(Quotient::Connection* newAccount); + Quotient::Connection* account() const; private slots: void load() override; @@ -64,7 +63,7 @@ private slots: QLineEdit* m_displayName; QLabel* m_accessTokenLabel; - Account* m_currentAccount; + Quotient::Connection* m_currentAccount; QString m_newAvatarPath; QPointer m_devicesJob; QVector m_devices; diff --git a/client/roomdialogs.cpp b/client/roomdialogs.cpp index c3872c58..9f9e6dcc 100644 --- a/client/roomdialogs.cpp +++ b/client/roomdialogs.cpp @@ -356,16 +356,16 @@ class InviteeList : public QListWidget } }; -CreateRoomDialog::CreateRoomDialog(const AccountRegistry* accounts, QWidget* parent) +CreateRoomDialog::CreateRoomDialog(QWidget* parent) : RoomDialogBase(tr("Create room"), tr("Create room"), nullptr, parent, NoExtraButtons) - , accountChooser(new AccountSelector(accounts)) + , accountChooser(new AccountSelector()) , version(nullptr) // Will be initialized below , nextInvitee(new NextInvitee) , inviteButton(new QPushButton(tr("Add", "Add a user to the list of invitees"))) , invitees(new QListWidget) { - Q_ASSERT(accounts && !accounts->isEmpty()); + Q_ASSERT(Quotient::Accounts.size() > 0); auto* versionBox = new QHBoxLayout; version = addVersionSelector(versionBox); @@ -419,7 +419,7 @@ CreateRoomDialog::CreateRoomDialog(const AccountRegistry* accounts, QWidget* par setPendingApplyMessage(tr("Creating the room, please wait")); - if (accounts->size() > 1) + if (Quotient::Accounts.size() > 1) accountChooser->setFocus(); else roomName->setFocus(); diff --git a/client/roomdialogs.h b/client/roomdialogs.h index dd24e8f9..ce40e148 100644 --- a/client/roomdialogs.h +++ b/client/roomdialogs.h @@ -26,7 +26,6 @@ namespace Quotient { class MainWindow; class QuaternionRoom; -class AccountRegistry; class AccountSelector; class QComboBox; @@ -90,8 +89,7 @@ class CreateRoomDialog : public RoomDialogBase { Q_OBJECT public: - CreateRoomDialog(const AccountRegistry* accounts, - QWidget* parent = nullptr); + CreateRoomDialog(QWidget* parent = nullptr); public slots: void updatePushButtons(); diff --git a/client/roomlistdock.cpp b/client/roomlistdock.cpp index 670352cf..b77fa2e4 100644 --- a/client/roomlistdock.cpp +++ b/client/roomlistdock.cpp @@ -224,6 +224,11 @@ void RoomListDock::addConnection(Quotient::Connection* connection) model->addConnection(connection); } +void RoomListDock::deleteConnection(Quotient::Connection* connection) +{ + model->deleteConnection(connection); +} + void RoomListDock::updateSortingMode() { // const auto sortMode = diff --git a/client/roomlistdock.h b/client/roomlistdock.h index ff9773d3..7975f067 100644 --- a/client/roomlistdock.h +++ b/client/roomlistdock.h @@ -39,6 +39,7 @@ class RoomListDock : public QDockWidget explicit RoomListDock(MainWindow* parent = nullptr); void addConnection(Quotient::Connection* connection); + void deleteConnection(Quotient::Connection* connection); public slots: void updateSortingMode();