qTbot 0.2.114.81e7cc4
qTbot is base back end library for your c++ Qt projects.
ibot.cpp
Go to the documentation of this file.
1//#
2//# Copyright (C) 2023-2025 QuasarApp.
3//# Distributed under the GPLv3 software license, see the accompanying
4//# Everyone is permitted to copy and distribute verbatim copies
5//# of this license document, but changing it is not allowed.
6//#
7
8#include "httpexception.h"
9#include "ibot.h"
10#include "qstandardpaths.h"
11
12#include <QNetworkReply>
13#include <QPromise>
14
15namespace qTbot {
16
18 _manager = new QNetworkAccessManager();
19 _manager->setAutoDeleteReplies(true);
20 _manager->setTransferTimeout(60000); // one minute
21
22 _requestExecutor = new QTimer(this);
23 _requestExecutor->setInterval(1000 / 20); // 20 times per second.
24
25 connect(_requestExecutor, &QTimer::timeout, this , &IBot::handleEcxecuteRequest);
26}
27
29 delete _manager;
30}
31
33 setToken({});
34}
35
36const QByteArray &IBot::token() const {
37 return _token;
38}
39
40void IBot::setToken(const QByteArray &newToken) {
41 _token = newToken;
42 _startTime = QDateTime::currentDateTime();
43
44}
45
46void IBot::incomeNewUpdate(const QSharedPointer<iUpdate> &message) {
47 if (!message->isValid())
48 return;
49
50 auto id = message->updateId();
51
52 if (!_processed.contains(id)) {
53
54 _processed.insert(id);
55 _notProcessedUpdates[id] = message;
56
57 handleIncomeNewUpdate(message);
58 }
59}
60
61QNetworkReply* IBot::sendRquestImpl(const QSharedPointer<iRequest> &rquest) {
62 if (!rquest)
63 return {};
64
65 auto && url = makeUrl(rquest);
66
67#ifdef QTBOT_PRINT_RQUESTS
68 qDebug() << url;
69#endif
70
71 QNetworkReply* networkReplay = nullptr;
72
73 switch (rquest->method()) {
74 case iRequest::Get: {
75 networkReplay = _manager->get(QNetworkRequest(url));
76
77 break;
78 }
79
80 case iRequest::Post:
81 // req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
82 // reply = m_nam.post(req, params.toByteArray());
83
84 // break;
86 QNetworkRequest netRequest(url);
87
88 auto httpData = rquest->argsToMultipartFormData();
89 if (httpData) {
90 networkReplay = _manager->post(netRequest, httpData.data());
91 connect(networkReplay, &QNetworkReply::destroyed, [httpData](){});
92
93 } else {
94 return {};
95 }
96
97 break;
98 }
99
100 return networkReplay;
101}
102
103QDateTime IBot::startTime() const {
104 return _startTime;
105}
106
107unsigned long long IBot::totalSentRequests() const {
108 return _totalRequest;
109}
110
112 return _parallelActiveNetworkThreads;
113}
114
115void IBot::setParallelActiveNetworkThreads(int newParallelActiveNetworkThreads) {
116 _parallelActiveNetworkThreads = newParallelActiveNetworkThreads;
117}
118
119void IBot::setCurrentParallelActiveNetworkThreads(int newParallelActiveNetworkThreads) {
120 bool wasBusy = _currentParallelActiveNetworkThreads == _parallelActiveNetworkThreads;
121 static bool lastMessageWasFree = false;
122
123
124 _currentParallelActiveNetworkThreads = newParallelActiveNetworkThreads;
125
126 if (_currentParallelActiveNetworkThreads == _parallelActiveNetworkThreads) {
127 qInfo() << "All network threads are busy!";
128 lastMessageWasFree = false;
129
130 } else if (wasBusy) {
131 qInfo() << "Network threads are free! available: " << _currentParallelActiveNetworkThreads << " from " << _parallelActiveNetworkThreads;
132 lastMessageWasFree = false;
133
134 } else if (_currentParallelActiveNetworkThreads == 0 && !lastMessageWasFree) {
135 qInfo() << "All network threads are free!";
136 lastMessageWasFree = true;
137 }
138}
139
141 return _requestExecutor->interval() * 1000;
142}
143
144void IBot::setReqestLimitPerSecond(int newReqestLimitPerSecond) {
145 _requestExecutor->setInterval(1000 / newReqestLimitPerSecond);
146}
147
148QFuture<QByteArray>
149IBot::sendRequest(const QSharedPointer<iRequest> &rquest) {
150 auto&& responce = QSharedPointer<QPromise<QByteArray>>::create();
151 responce->start();
152
153 {
154 QMutexLocker lock(&_mutex);
155 _requestQueue.insert(makeKey(rquest->priority()),
156 RequestData{rquest, "", responce});
157 }
158
159 if (!_requestExecutor->isActive()) {
160 handleEcxecuteRequest();
161 _requestExecutor->start();
162
163 }
164
165 return responce->future();
166}
167
168QFuture<QByteArray>
169IBot::sendRequest(const QSharedPointer<iRequest> &rquest,
170 const QString &pathToResult) {
171 auto&& responce = QSharedPointer<QPromise<QByteArray>>::create();
172 responce->start();
173
174 {
175 QMutexLocker lock(&_mutex);
176 _requestQueue.insert(makeKey(rquest->priority()),
177 RequestData{rquest, pathToResult, responce});
178 }
179
180
181 if (!_requestExecutor->isActive()) {
182 handleEcxecuteRequest();
183 _requestExecutor->start();
184
185 }
186
187 return responce->future();
188
189}
190
191void IBot::markUpdateAsProcessed(const QSharedPointer<iUpdate> &message) {
192 _notProcessedUpdates.remove(message->updateId());
193}
194
195void IBot::markUpdateAsUnprocessed(const QSharedPointer<iUpdate> &message) {
196 return markUpdateAsUnprocessed(message->updateId());
197}
198
199void IBot::markUpdateAsUnprocessed(unsigned long long messageID) {
200 _processed.remove(messageID);
201}
202
204 return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
205}
206
207void IBot::handleIncomeNewUpdate(const QSharedPointer<iUpdate> & message) {
208 emit sigReceiveUpdate(message);
209}
210
211void IBot::handleEcxecuteRequest() {
212 QMutexLocker lock(&_mutex);
213
214 if (!_requestQueue.size()) {
215 _requestExecutor->stop();
216 return;
217 }
218
219 if (_currentParallelActiveNetworkThreads > _parallelActiveNetworkThreads) {
220 return;
221 }
222
223 auto&& requestData = _requestQueue.take(_requestQueue.firstKey());
224
225 if (requestData.responceFilePath.size()) {
226 sendRequestPrivate(requestData.request, requestData.responceFilePath, requestData.responce);
227 return;
228 }
229
230 sendRequestPrivate(requestData.request, requestData.responce);
231}
232
233unsigned long long IBot::makeKey(iRequest::RequestPriority priority) {
234 unsigned long long key = _totalRequest;
235 _totalRequest++;
236 key = key | (static_cast<unsigned long long>(iRequest::RequestPriority::MaxPriorityValue - priority) << 56);
237 return key;
238}
239
240void IBot::sendRequestPrivate(const QSharedPointer<iRequest> &rquest,
241 const QSharedPointer<QPromise<QByteArray> > &promise) {
242
243 QNetworkReply* networkReplay = sendRquestImpl(rquest);
244 if (!networkReplay) {
245 return;
246 }
247
248 setCurrentParallelActiveNetworkThreads(_currentParallelActiveNetworkThreads + 1);
249
250 connect(networkReplay, &QNetworkReply::finished, [this, networkReplay, promise](){
251 if (networkReplay->error() == QNetworkReply::NoError) {
252 promise->addResult(networkReplay->readAll());
253 promise->finish();
254
255 } else {
256 QByteArray msg = networkReplay->errorString().toLatin1() + networkReplay->readAll();
257 promise->setException(HttpException(networkReplay->error(), msg));
258#ifdef QTBOT_PRINT_ERRORS
259 qCritical() << msg;
260#endif
261
262 }
263
264 setCurrentParallelActiveNetworkThreads(_currentParallelActiveNetworkThreads - 1);
265
266 });
267
268 auto && setProggress = [promise](qint64 bytesCurrent, qint64 bytesTotal){
269
270 if (promise->future().progressMaximum() != bytesTotal)
271 promise->setProgressRange(0, bytesTotal);
272
273 promise->setProgressValue(bytesCurrent);
274 };
275
276 connect(networkReplay, &QNetworkReply::downloadProgress, setProggress);
277 connect(networkReplay, &QNetworkReply::uploadProgress, setProggress);
278}
279
280void IBot::sendRequestPrivate(const QSharedPointer<iRequest> &rquest,
281 const QString &pathToResult,
282 const QSharedPointer<QPromise<QByteArray>> & promise) {
283 auto&& file = QSharedPointer<QFile>::create(pathToResult);
284
285 if (!file->open(QIODeviceBase::WriteOnly | QIODevice::Truncate)) {
286 qCritical() << "Fail to wrote data into " << pathToResult;
287 return;
288 }
289
290 QNetworkReply* networkReplay = sendRquestImpl(rquest);
291 if (!networkReplay) {
292 return;
293 }
294
295 setCurrentParallelActiveNetworkThreads(_currentParallelActiveNetworkThreads + 1);
296 connect(networkReplay, &QNetworkReply::finished, [this, promise, networkReplay, pathToResult](){
297
298 if (networkReplay->error() == QNetworkReply::NoError) {
299 QByteArray msg = networkReplay->errorString().toLatin1();
300 promise->setException(HttpException(networkReplay->error(), msg));
301#ifdef QTBOT_PRINT_ERRORS
302 qCritical() << msg;
303#endif
304 } else {
305 promise->addResult(pathToResult.toUtf8()); // wil not work with UTF 8 path names
306 promise->finish();
307 }
308 setCurrentParallelActiveNetworkThreads(_currentParallelActiveNetworkThreads - 1);
309 });
310
311 connect(networkReplay, &QNetworkReply::readyRead, [networkReplay, promise, pathToResult, file](){
312 if (networkReplay->error() == QNetworkReply::NoError) {
313 file->write(networkReplay->readAll());
314 }
315
316 });
317
318 auto && setProggress = [promise](qint64 bytesCurrent, qint64 bytesTotal){
319
320 if (promise->future().progressMaximum() != bytesTotal)
321 promise->setProgressRange(0, bytesTotal);
322
323 promise->setProgressValue(bytesCurrent);
324 };
325
326 connect(networkReplay, &QNetworkReply::downloadProgress, setProggress);
327 connect(networkReplay, &QNetworkReply::uploadProgress, setProggress);
328
329}
330
331QSet<unsigned long long> IBot::processed() const {
332 return _processed;
333}
334
335void IBot::setProcessed(const QSet<unsigned long long> &newProcessed) {
336 _processed = newProcessed;
337}
338
339const QString &IBot::name() const {
340 return _name;
341}
342
343void IBot::setName(const QString &newName) {
344 _name = newName;
345}
346
347QSharedPointer<iUpdate> IBot::takeNextUnreadUpdate() {
348 if (_notProcessedUpdates.size()) {
349 auto toRemove = std::move(*_notProcessedUpdates.begin());
350 _notProcessedUpdates.erase(_notProcessedUpdates.cbegin());
351 return toRemove;
352 }
353
354 return nullptr;
355}
356
357
358}
virtual QString makeUrl(const QSharedPointer< iRequest > &request) const =0
makeUrl This method prepare a prefix url for http requests.
void setName(const QString &newName)
setName This method sets new value for the IBot::name field.
Definition ibot.cpp:343
void sigReceiveUpdate(const QSharedPointer< qTbot::iUpdate > &)
sigReceiveUpdate emit when but receive any updates from users.
const QString & name() const
name This is name of the bot. usualy it fields will be received from the server after autication.
Definition ibot.cpp:339
unsigned long long totalSentRequests() const
totalSentRequests This is total prepared requests count of bot from the start.
Definition ibot.cpp:107
void markUpdateAsUnprocessed(const QSharedPointer< iUpdate > &message)
markMessageAsUnprocessed This method add the message into a not processed messages store.
Definition ibot.cpp:195
void markUpdateAsProcessed(const QSharedPointer< iUpdate > &message)
markMessageAsProcessed This method remove message from the not processed messages store.
Definition ibot.cpp:191
const QByteArray & token() const
token This is token value for authication on the remote server (bot)
Definition ibot.cpp:36
int parallelActiveNetworkThreads() const
parallelActiveNetworkThreads
Definition ibot.cpp:111
virtual void handleIncomeNewUpdate(const QSharedPointer< iUpdate > &)
handleIncomeNewUpdate This method just emit the sigReceiveUpdate signal.
Definition ibot.cpp:207
int reqestLimitPerSecond() const
reqestLimitPerSecond this is request performence limitation. by default is 20 requests per second
Definition ibot.cpp:140
void setReqestLimitPerSecond(int newReqestLimitPerSecond)
setReqestLimitPerSecond this method sets new limitation of bot performance.
Definition ibot.cpp:144
virtual QString defaultFileStorageLocation() const
defaultFileStorageLocation This method return default file storage location.
Definition ibot.cpp:203
void incomeNewUpdate(const QSharedPointer< iUpdate > &message)
incomeNewUpdate This method save incomed messages into store.
Definition ibot.cpp:46
void setParallelActiveNetworkThreads(int newParallelActiveNetworkThreads)
setParallelActiveNetworkThreads
Definition ibot.cpp:115
QSet< unsigned long long > processed() const
processed This method return list of processed mesages.
Definition ibot.cpp:331
QFuture< QByteArray > sendRequest(const QSharedPointer< iRequest > &rquest)
sendRequest This method sent custom requests to the server.
Definition ibot.cpp:149
virtual void setProcessed(const QSet< unsigned long long > &newProcessed)
setProcessed This method sets new list of processed mesages.
Definition ibot.cpp:335
void setToken(const QByteArray &newToken)
setToken This is setter of the IBot::token value.
Definition ibot.cpp:40
virtual void logout()
login This method remove login token of bot.
Definition ibot.cpp:32
QDateTime startTime() const
startTime this is time when bol wil started.
Definition ibot.cpp:103
QSharedPointer< iUpdate > takeNextUnreadUpdate()
takeNextUnreadUpdate This method take a unread update and mark them as read.
Definition ibot.cpp:347
@ Post
general post request
Definition irequest.h:44
@ Upload
this is post request to upload a big data to telegram
Definition irequest.h:46
@ Get
general ger request, all request data sent as a url line
Definition irequest.h:42
RequestPriority
The RequestPriority enum.
Definition irequest.h:52
The RequestData class is simple wrapper of request object with path of responce. If Path of responce ...
Definition ibot.h:58