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
18using namespace Quotient;
19
20class ConnectionData::Private {
21public:
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
44ConnectionData::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
76ConnectionData::~ConnectionData()
77{
78 d->rateLimiter.disconnect();
79 d->rateLimiter.stop();
80}
81
82void 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
95void 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
102QByteArray ConnectionData::accessToken() const { return d->accessToken; }
103
104QUrl ConnectionData::baseUrl() const { return d->baseUrl; }
105
106NetworkAccessManager* ConnectionData::nam() const
107{
108 return NetworkAccessManager::instance();
109}
110
111void 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
119void ConnectionData::setToken(QByteArray token)
120{
121 d->accessToken = std::move(token);
122}
123
124const QString& ConnectionData::deviceId() const { return d->deviceId; }
125
126const QString& ConnectionData::userId() const { return d->userId; }
127
128bool 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
134void ConnectionData::setDeviceId(const QString& deviceId)
135{
136 d->deviceId = deviceId;
137}
138
139void 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
150void ConnectionData::setNeedsToken(const QString& requestName)
151{
152 d->needToken.push_back(x: requestName);
153}
154
155QString ConnectionData::lastEvent() const { return d->lastEvent; }
156
157void ConnectionData::setLastEvent(QString identifier)
158{
159 d->lastEvent = std::move(identifier);
160}
161
162QString ConnectionData::generateTxnId() const
163{
164 return d->deviceId + QString::number(d->txnBase)
165 + QString::number(++d->txnCounter);
166}
167