1// SPDX-FileCopyrightText: Kitsune Ral <Kitsune-Ral@users.sf.net>
2// SPDX-FileCopyrightText: Tobias Fella <fella@posteo.de>
3// SPDX-License-Identifier: LGPL-2.1-or-later
4
5#include "accountregistry.h"
6
7#include "connection.h"
8#include "logging_categories_p.h"
9#include "settings.h"
10
11#include <QtCore/QCoreApplication>
12
13// TODO: remove in 0.9
14QT_IGNORE_DEPRECATIONS(
15 QUOTIENT_API Quotient::AccountRegistry Quotient::Accounts{};)
16
17using namespace Quotient;
18
19struct Q_DECL_HIDDEN AccountRegistry::Private {
20 QStringList m_accountsLoading;
21};
22
23AccountRegistry::AccountRegistry(QObject* parent)
24 : QAbstractListModel(parent), d(makeImpl<Private>())
25{}
26
27void AccountRegistry::add(Connection* a)
28{
29 Q_ASSERT(a != nullptr);
30 if (get(userId: a->userId()) != nullptr) {
31 qWarning(catFunc: MAIN) << "Attempt to add another connection for the same user "
32 "id; skipping";
33 return;
34 }
35 beginInsertRows(parent: QModelIndex(), first: size(), last: size());
36 push_back(t: a);
37 connect(sender: a, signal: &Connection::loggedOut, context: this, slot: [this, a] { drop(a); });
38 qDebug(catFunc: MAIN) << "Added" << a->objectName() << "to the account registry";
39 endInsertRows();
40 emit accountCountChanged();
41}
42
43void AccountRegistry::drop(Connection* a)
44{
45 if (const auto idx = indexOf(t: a); idx != -1) {
46 beginRemoveRows(parent: QModelIndex(), first: idx, last: idx);
47 remove(i: idx);
48 qDebug(catFunc: MAIN) << "Removed" << a->objectName()
49 << "from the account registry";
50 endRemoveRows();
51 }
52 Q_ASSERT(!contains(a));
53}
54
55bool AccountRegistry::isLoggedIn(const QString &userId) const
56{
57 const auto conn = get(userId);
58 return conn != nullptr && conn->isLoggedIn();
59}
60
61QVariant AccountRegistry::data(const QModelIndex& index, int role) const
62{
63 if (!index.isValid() || index.row() >= count())
64 return {};
65
66 switch (role) {
67 case AccountRole:
68 return QVariant::fromValue(value: at(i: index.row()));
69 case UserIdRole:
70 return QVariant::fromValue(value: at(i: index.row())->userId());
71 default:
72 return {};
73 }
74}
75
76int AccountRegistry::rowCount(const QModelIndex& parent) const
77{
78 return parent.isValid() ? 0 : count();
79}
80
81QHash<int, QByteArray> AccountRegistry::roleNames() const
82{
83 return { { AccountRole, QByteArrayLiteral("connection") },
84 { UserIdRole, QByteArrayLiteral("userId") } };
85}
86
87Connection* AccountRegistry::get(const QString& userId) const
88{
89 for (const auto& connection : accounts()) {
90 if (connection->userId() == userId)
91 return connection;
92 }
93 return nullptr;
94}
95
96void AccountRegistry::invokeLogin()
97{
98 const auto accounts = SettingsGroup("Accounts"_ls).childGroups();
99 for (const auto& accountId : accounts) {
100 AccountSettings account { accountId };
101
102 if (account.homeserver().isEmpty())
103 continue;
104
105 d->m_accountsLoading += accountId;
106 emit accountsLoadingChanged();
107
108 qCDebug(MAIN) << "Reading access token from keychain for" << accountId;
109 auto accessTokenLoadingJob =
110 new QKeychain::ReadPasswordJob(qAppName(), this);
111 accessTokenLoadingJob->setKey(accountId);
112 connect(sender: accessTokenLoadingJob, signal: &QKeychain::Job::finished, context: this,
113 slot: [accountId, this, accessTokenLoadingJob]() {
114 if (accessTokenLoadingJob->error()
115 != QKeychain::Error::NoError) {
116 emit keychainError(error: accessTokenLoadingJob->error());
117 d->m_accountsLoading.removeAll(t: accountId);
118 emit accountsLoadingChanged();
119 return;
120 }
121
122 AccountSettings account { accountId };
123 auto connection = new Connection(account.homeserver());
124 connect(sender: connection, signal: &Connection::connected, context: this,
125 slot: [connection, this, accountId] {
126 connection->loadState();
127 connection->setLazyLoading(true);
128
129 connection->syncLoop();
130
131 d->m_accountsLoading.removeAll(t: accountId);
132 emit accountsLoadingChanged();
133 });
134 connect(sender: connection, signal: &Connection::loginError, context: this,
135 slot: [this, connection, accountId](const QString& error,
136 const QString& details) {
137 emit loginError(connection, message: error, details);
138
139 d->m_accountsLoading.removeAll(t: accountId);
140 emit accountsLoadingChanged();
141 });
142 connect(sender: connection, signal: &Connection::resolveError, context: this,
143 slot: [this, connection, accountId](const QString& error) {
144 emit resolveError(connection, error);
145
146 d->m_accountsLoading.removeAll(t: accountId);
147 emit accountsLoadingChanged();
148 });
149 connection->assumeIdentity(
150 mxId: account.userId(),
151 accessToken: QString::fromUtf8(ba: accessTokenLoadingJob->binaryData()));
152 add(a: connection);
153 });
154 accessTokenLoadingJob->start();
155 }
156}
157
158QStringList AccountRegistry::accountsLoading() const
159{
160 return d->m_accountsLoading;
161}
162