1 | // SPDX-FileCopyrightText: 2015 Felix Rohrbach <kde@fxrh.de> |
2 | // SPDX-FileCopyrightText: 2016 Kitsune Ral <Kitsune-Ral@users.sf.net> |
3 | // SPDX-License-Identifier: LGPL-2.1-or-later |
4 | |
5 | #include "connectiondata.h" |
6 | |
7 | #include "logging_categories_p.h" |
8 | #include "networkaccessmanager.h" |
9 | |
10 | #include "jobs/basejob.h" |
11 | |
12 | #include <QtCore/QPointer> |
13 | #include <QtCore/QTimer> |
14 | |
15 | #include <array> |
16 | #include <queue> |
17 | |
18 | using namespace Quotient; |
19 | |
20 | class ConnectionData::Private { |
21 | public: |
22 | explicit Private(QUrl url) : baseUrl(std::move(url)) |
23 | { |
24 | rateLimiter.setSingleShot(true); |
25 | } |
26 | |
27 | QUrl baseUrl; |
28 | QByteArray accessToken; |
29 | QString lastEvent; |
30 | QString userId; |
31 | QString deviceId; |
32 | std::vector<QString> needToken; |
33 | |
34 | mutable unsigned int txnCounter = 0; |
35 | const qint64 txnBase = QDateTime::currentMSecsSinceEpoch(); |
36 | |
37 | QString id() const { return userId + u'/' + deviceId; } |
38 | |
39 | using job_queue_t = std::queue<QPointer<BaseJob>>; |
40 | std::array<job_queue_t, 2> jobs; // 0 - foreground, 1 - background |
41 | QTimer rateLimiter; |
42 | }; |
43 | |
44 | ConnectionData::ConnectionData(QUrl baseUrl) |
45 | : d(makeImpl<Private>(args: std::move(baseUrl))) |
46 | { |
47 | // Each lambda invocation below takes no more than one job from the |
48 | // queues (first foreground, then background) and resumes it; then |
49 | // restarts the rate limiter timer with duration 0, effectively yielding |
50 | // to the event loop and then resuming until both queues are empty. |
51 | QObject::connect(sender: &d->rateLimiter, signal: &QTimer::timeout, slot: [this] { |
52 | // TODO: Consider moving out all job->sendRequest() invocations to |
53 | // a dedicated thread |
54 | d->rateLimiter.setInterval(0); |
55 | for (auto& q : d->jobs) |
56 | while (!q.empty()) { |
57 | const auto job = q.front(); |
58 | q.pop(); |
59 | if (!job || job->error() == BaseJob::Abandoned) |
60 | continue; |
61 | if (job->error() != BaseJob::Pending) { |
62 | qCCritical(MAIN) |
63 | << "Job" << job |
64 | << "is in the wrong status:" << job->status(); |
65 | Q_ASSERT(false); |
66 | job->setStatus(BaseJob::Pending); |
67 | } |
68 | job->sendRequest(); |
69 | d->rateLimiter.start(); |
70 | return; |
71 | } |
72 | qCDebug(MAIN) << d->id() << "job queues are empty" ; |
73 | }); |
74 | } |
75 | |
76 | ConnectionData::~ConnectionData() |
77 | { |
78 | d->rateLimiter.disconnect(); |
79 | d->rateLimiter.stop(); |
80 | } |
81 | |
82 | void ConnectionData::submit(BaseJob* job) |
83 | { |
84 | job->setStatus(BaseJob::Pending); |
85 | if (!d->rateLimiter.isActive()) { |
86 | QTimer::singleShot(interval: 0, receiver: job, slot: &BaseJob::sendRequest); |
87 | return; |
88 | } |
89 | d->jobs[size_t(job->isBackground())].emplace(args&: job); |
90 | qCDebug(MAIN) << job << "queued," << d->jobs.front().size() << "(fg) +" |
91 | << d->jobs.back().size() << "(bg) total jobs in" << d->id() |
92 | << "queues" ; |
93 | } |
94 | |
95 | void ConnectionData::limitRate(std::chrono::milliseconds nextCallAfter) |
96 | { |
97 | qCDebug(MAIN) << "Jobs for" << (d->userId + u'/' + d->deviceId) |
98 | << "suspended for" << nextCallAfter.count() << "ms" ; |
99 | d->rateLimiter.start(value: nextCallAfter); |
100 | } |
101 | |
102 | QByteArray ConnectionData::accessToken() const { return d->accessToken; } |
103 | |
104 | QUrl ConnectionData::baseUrl() const { return d->baseUrl; } |
105 | |
106 | NetworkAccessManager* ConnectionData::nam() const |
107 | { |
108 | return NetworkAccessManager::instance(); |
109 | } |
110 | |
111 | void ConnectionData::setBaseUrl(QUrl baseUrl) |
112 | { |
113 | d->baseUrl = std::move(baseUrl); |
114 | qCDebug(MAIN) << "updated baseUrl to" << d->baseUrl; |
115 | if (!d->userId.isEmpty() && d->baseUrl.isValid()) |
116 | NetworkAccessManager::addBaseUrl(accountId: d->userId, homeserver: d->baseUrl); |
117 | } |
118 | |
119 | void ConnectionData::setToken(QByteArray token) |
120 | { |
121 | d->accessToken = std::move(token); |
122 | } |
123 | |
124 | const QString& ConnectionData::deviceId() const { return d->deviceId; } |
125 | |
126 | const QString& ConnectionData::userId() const { return d->userId; } |
127 | |
128 | bool ConnectionData::needsToken(const QString& requestName) const |
129 | { |
130 | return std::find(first: d->needToken.cbegin(), last: d->needToken.cend(), val: requestName) |
131 | != d->needToken.cend(); |
132 | } |
133 | |
134 | void ConnectionData::setDeviceId(const QString& deviceId) |
135 | { |
136 | d->deviceId = deviceId; |
137 | } |
138 | |
139 | void ConnectionData::setUserId(const QString& userId) |
140 | { |
141 | if (d->baseUrl.isValid()) { |
142 | if (d->userId != userId) |
143 | NetworkAccessManager::dropBaseUrl(accountId: d->userId); |
144 | if (!userId.isEmpty()) |
145 | NetworkAccessManager::addBaseUrl(accountId: userId, homeserver: d->baseUrl); |
146 | } |
147 | d->userId = userId; |
148 | } |
149 | |
150 | void ConnectionData::setNeedsToken(const QString& requestName) |
151 | { |
152 | d->needToken.push_back(x: requestName); |
153 | } |
154 | |
155 | QString ConnectionData::lastEvent() const { return d->lastEvent; } |
156 | |
157 | void ConnectionData::setLastEvent(QString identifier) |
158 | { |
159 | d->lastEvent = std::move(identifier); |
160 | } |
161 | |
162 | QString ConnectionData::generateTxnId() const |
163 | { |
164 | return d->deviceId + QString::number(d->txnBase) |
165 | + QString::number(++d->txnCounter); |
166 | } |
167 | |