diff --git a/CMakeLists.txt b/CMakeLists.txt index acaf411..85c7c2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ find_package(Qt6 COMPONENTS Test QUIET) include(submodules/CMake/QuasarApp.cmake) updateGitVars() -set(QTBOT_VERSION "0.${GIT_COMMIT_COUNT}.${GIT_COMMIT_HASH}") +set(QTBOT_VERSION "0.2.${GIT_COMMIT_COUNT}.${GIT_COMMIT_HASH}") set(QTBOT_PACKAGE_ID "quasarapp.core.qTbot") option(QTBOT_TESTS "This option disables or enables tests of the ${PROJECT_NAME} project" ON) diff --git a/src/example/main.cpp b/src/example/main.cpp index 2000b4e..a4234ce 100644 --- a/src/example/main.cpp +++ b/src/example/main.cpp @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -23,11 +24,11 @@ int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); qTbot::TelegramRestBot bot; + bot.setReqestLimitPerSecond(10); srand(time(0)); - QList > filesStack; - QObject::connect(&bot, &qTbot::TelegramRestBot::sigReceiveUpdate, [&bot, &filesStack](auto){ + QObject::connect(&bot, &qTbot::TelegramRestBot::sigReceiveUpdate, [&bot](auto){ while(auto&& update = bot.takeNextUnreadUpdate()) { if (auto&& tupdate = update.dynamicCast()) { @@ -36,41 +37,64 @@ int main(int argc, char *argv[]) { if (auto&& tmsg = tupdate->message()) { if (tmsg->contains(tmsg->Document)) { - filesStack.push_back(bot.getFile(tmsg->documents()->fileId(), qTbot::iFile::Local)); + bot.getFile(tmsg->documents()->fileId(), qTbot::ITelegramBot::Local).then([](const QByteArray& path){ + qInfo() << "file save into " << path; + }).onFailed([](const std::exception& exception){ + + qCritical() << "exception :" << exception.what(); + }); } if (tmsg->contains(tmsg->Image)) { - filesStack.push_back(bot.getFile(tmsg->image()->fileId(), qTbot::iFile::Local)); + bot.getFile(tmsg->image()->fileId(), qTbot::ITelegramBot::Local).then([](const QByteArray& path){ + qInfo() << "file save into " << path; + }).onFailed([](const std::exception& exception){ + + qCritical() << "exception :" << exception.what(); + });; } if (tmsg->contains(tmsg->Audio)) { - filesStack.push_back(bot.getFile(tmsg->audio()->fileId(), qTbot::iFile::Local)); + bot.getFile(tmsg->audio()->fileId(), qTbot::ITelegramBot::Local).then([](const QByteArray& path){ + qInfo() << "file save into " << path; + }).onFailed([](const std::exception& exception){ + + qCritical() << "exception :" << exception.what(); + });; } - bot.sendSpecificMessageWithKeyboard(qTbot::TelegramArgs{tmsg->chatId(), "I see it", tmsg->messageId()}, - {{{"test_button", [tmsg, &bot](const QString& queryId, const QVariant& msgId){ - static int index = 0; - - auto&& args = qTbot::TelegramArgs{tmsg->chatId(), - "I see it. Presed count: " + QString::number(index++), - tmsg->messageId(), - "", - false, - queryId}; - - auto&& keyboard = qTbot::KeyboardOnMessage{ - {{"test_button", [](auto , auto ){}}, - {"test_button 2", [](auto , auto ){}}}}; - - bot.editSpecificMessageWithKeyboard(msgId, - args, - keyboard - ); - }}}}); - - bot.sendSpecificMessageWithKeyboard(qTbot::TelegramArgs{tmsg->chatId(), "I see it", tmsg->messageId()}, - {{{"test_button"}, - {"test_button"},}}, true, true); + if (tmsg->text() == "spam") { + for (int i = 0 ; i < 1000; i++) { + bot.sendMessage(tmsg->chatId(), QString(" message N %0").arg(i), qTbot::iRequest::LowPriority); + } + } else { + bot.sendSpecificMessageWithKeyboard(qTbot::TelegramArgs{tmsg->chatId(), "I see it", tmsg->messageId()}, + {{{"test_button", [tmsg, &bot](const QString& queryId, const QVariant& msgId){ + static int index = 0; + + auto&& args = qTbot::TelegramArgs{tmsg->chatId(), + "I see it. Presed count: " + QString::number(index++), + tmsg->messageId(), + "", + false, + queryId}; + + auto&& keyboard = qTbot::KeyboardOnMessage{ + {{"test_button", [](auto , auto ){}}, + {"test_button 2", [](auto , auto ){}}}}; + + bot.editSpecificMessageWithKeyboard(msgId, + args, + keyboard + ); + }}}}); + + bot.sendSpecificMessageWithKeyboard(qTbot::TelegramArgs{tmsg->chatId(), "I see it", tmsg->messageId()}, + {{{"test_button"}, + {"test_button"},}}, true, true); + } + + } } @@ -78,6 +102,9 @@ int main(int argc, char *argv[]) { } }); - bot.login("6349356184:AAFotw9EC46sgAQrkGQ_jeHPyv3EAapZXcM"); + if (!bot.login("6349356184:AAFotw9EC46sgAQrkGQ_jeHPyv3EAapZXcM")) { + qCritical() << "failed to login!"; + return 1; + } return app.exec(); } diff --git a/src/qTbot/src/private/requests/telegramgetupdate.cpp b/src/qTbot/src/private/requests/telegramgetupdate.cpp index 069e085..29d82eb 100644 --- a/src/qTbot/src/private/requests/telegramgetupdate.cpp +++ b/src/qTbot/src/private/requests/telegramgetupdate.cpp @@ -13,6 +13,7 @@ namespace qTbot { TelegramGetUpdate::TelegramGetUpdate(unsigned long long offset): TelegramSingleRquest("getUpdates"){ addArg("offset", offset); addArg("timeout", 30); + setPriority(UngeredPriority); } } diff --git a/src/qTbot/src/private/requests/telegramsendmsg.cpp b/src/qTbot/src/private/requests/telegramsendmsg.cpp index 3867c26..d840885 100644 --- a/src/qTbot/src/private/requests/telegramsendmsg.cpp +++ b/src/qTbot/src/private/requests/telegramsendmsg.cpp @@ -18,6 +18,7 @@ TelegramSendMsg::TelegramSendMsg(const TelegramArgs& generalArgs, { QMap&& args = generalArgs.toMap(); + setPriority(generalArgs.requestPriority); for (auto it = extraObjects.begin(); it != extraObjects.end(); it = std::next(it)) { args[it.key()] = QJsonDocument(*it.value()).toJson(QJsonDocument::Compact); diff --git a/src/qTbot/src/public/qTbot/file.cpp b/src/qTbot/src/public/qTbot/file.cpp deleted file mode 100644 index 9f8fab9..0000000 --- a/src/qTbot/src/public/qTbot/file.cpp +++ /dev/null @@ -1,56 +0,0 @@ -//# -//# Copyright (C) 2023-2024 QuasarApp. -//# Distributed under the GPLv3 software license, see the accompanying -//# Everyone is permitted to copy and distribute verbatim copies -//# of this license document, but changing it is not allowed. -//# - -#include "file.h" - -namespace qTbot { - - -File::File(const QSharedPointer &replay, const QString &filePath): iFile(replay) { - _localFile.setFileName(filePath); - - if (!_localFile.isOpen()) { - _localFile.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Append); - } - -} - -File::File(const QString &filePath):File(nullptr, filePath) { - -} - -const QFile & File::localFile() const { - return _localFile; -} - -iFile::Type File::type() const { - return Type::Local; -} - -void File::handleReadReady() { - auto&& bytes = replay()->readAll(); - - if (bytes.size()) { - _localFile.write(bytes); - _localFile.flush(); - } -} - -void File::handleFinished() { - handleReadReady(); - _localFile.close(); - iFile::handleFinished(); -} - -void File::handleError(QNetworkReply::NetworkError error) { - iFile::handleError(error); - _localFile.close(); - - _localFile.remove(); -} - -} diff --git a/src/qTbot/src/public/qTbot/file.h b/src/qTbot/src/public/qTbot/file.h deleted file mode 100644 index 7aa55a8..0000000 --- a/src/qTbot/src/public/qTbot/file.h +++ /dev/null @@ -1,40 +0,0 @@ -//# -//# Copyright (C) 2023-2024 QuasarApp. -//# Distributed under the GPLv3 software license, see the accompanying -//# Everyone is permitted to copy and distribute verbatim copies -//# of this license document, but changing it is not allowed. -//# - - -#ifndef FILE_H -#define FILE_H - -#include "ifile.h" - -namespace qTbot { - -/** - * @brief The File class is implementations for local files. - */ -class QTBOT_EXPORT File: public iFile -{ - Q_OBJECT -public: - File(const QSharedPointer& replay, const QString &filePath); - File(const QString &filePath); - - const QFile & localFile() const; - - Type type() const override; - - // iFile interface -protected slots: - void handleReadReady() override; - void handleFinished() override; - void handleError(QNetworkReply::NetworkError error) override; -private: - QFile _localFile; - -}; -} -#endif // FILE_H diff --git a/src/qTbot/src/public/qTbot/filewaiter.cpp b/src/qTbot/src/public/qTbot/filewaiter.cpp deleted file mode 100644 index 72162f3..0000000 --- a/src/qTbot/src/public/qTbot/filewaiter.cpp +++ /dev/null @@ -1,34 +0,0 @@ -//# -//# Copyright (C) 2023-2024 QuasarApp. -//# Distributed under the GPLv3 software license, see the accompanying -//# Everyone is permitted to copy and distribute verbatim copies -//# of this license document, but changing it is not allowed. -//# -#include "filewaiter.h" -namespace qTbot { - -FileWaiter::FileWaiter() -{ - -} - -void FileWaiter::wait(const QSharedPointer &file) { - if (!file->isFinished()) { - auto address = reinterpret_cast(file.get()); - - _files[address] = file; - - - connect(file.get(), &qTbot::iFile::finishedChanged, this, &FileWaiter::handleFileFinished, - Qt::QueuedConnection); - } -} - -void FileWaiter::handleFileFinished() { - auto address = reinterpret_cast(sender()); - - _files.remove(address); - -} - -} diff --git a/src/qTbot/src/public/qTbot/filewaiter.h b/src/qTbot/src/public/qTbot/filewaiter.h deleted file mode 100644 index 4b0b9c3..0000000 --- a/src/qTbot/src/public/qTbot/filewaiter.h +++ /dev/null @@ -1,43 +0,0 @@ -//# -//# Copyright (C) 2023-2024 QuasarApp. -//# Distributed under the GPLv3 software license, see the accompanying -//# Everyone is permitted to copy and distribute verbatim copies -//# of this license document, but changing it is not allowed. -//# - - -#ifndef FILEWAITER_H -#define FILEWAITER_H - -#include "ifile.h" - -namespace qTbot { - -/** - * @brief The FileWaiter class. This is a simple storage for the shared pointer of files. - * All added files will be removed (shared object) after finish donwload or upload. - */ -class QTBOT_EXPORT FileWaiter: public QObject -{ - Q_OBJECT -public: - FileWaiter(); - - /** - * @brief wait This method add shared pointer of file in to local storage, and remove it from this when @a file change state to finish. - * @param file This is a processed file. - * @note The file will not added if the file alredey finished. - * @note This method not stop thread, it is just save a file until it is is progres - */ - void wait(const QSharedPointer& file); - -private slots: - void handleFileFinished(); - -private: - QHash> _files; - - -}; -} -#endif // FILEWAITER_H diff --git a/src/qTbot/src/public/qTbot/httpexception.cpp b/src/qTbot/src/public/qTbot/httpexception.cpp new file mode 100644 index 0000000..1c6fd24 --- /dev/null +++ b/src/qTbot/src/public/qTbot/httpexception.cpp @@ -0,0 +1,42 @@ +//# +//# Copyright (C) 2023-2024 QuasarApp. +//# Distributed under the GPLv3 software license, see the accompanying +//# Everyone is permitted to copy and distribute verbatim copies +//# of this license document, but changing it is not allowed. +//# + +#include "httpexception.h" + +namespace qTbot { + +HttpException::HttpException(QNetworkReply::NetworkError code, + const QByteArray &erroString) { + + _code = code; + + if (erroString.size()) { + _errText = erroString; + } else { + + _errText = QByteArray("Http request finished with code: "). + append(QString::number(code).toLatin1()); + } +} + +const char *HttpException::what() const noexcept { + return _errText.constData(); +} + +void HttpException::raise() const { + throw *this; +} + +QException *HttpException::clone() const { + return new HttpException(QNetworkReply::NetworkError(0), + _errText); +} + +QNetworkReply::NetworkError HttpException::code() const { + return _code; +} +} diff --git a/src/qTbot/src/public/qTbot/httpexception.h b/src/qTbot/src/public/qTbot/httpexception.h new file mode 100644 index 0000000..e1d7e0a --- /dev/null +++ b/src/qTbot/src/public/qTbot/httpexception.h @@ -0,0 +1,41 @@ +//# +//# Copyright (C) 2023-2024 QuasarApp. +//# Distributed under the GPLv3 software license, see the accompanying +//# Everyone is permitted to copy and distribute verbatim copies +//# of this license document, but changing it is not allowed. +//# + +#include +#include + + +#ifndef HTTPEXCEPTION_H +#define HTTPEXCEPTION_H + +namespace qTbot { + +/** + * @brief The HttpException class is base exaption that will raise on all errors of the HTTP protocol, + */ +class HttpException: public QException +{ +public: + HttpException(QNetworkReply::NetworkError code, const QByteArray& erroString = {}); + + // exception interface +public: + const char *what() const noexcept override; + + // QException interface +public: + void raise() const override; + QException *clone() const override; + + QNetworkReply::NetworkError code() const; + +private: + QByteArray _errText; + QNetworkReply::NetworkError _code; +}; +} +#endif // HTTPEXCEPTION_H diff --git a/src/qTbot/src/public/qTbot/ibot.cpp b/src/qTbot/src/public/qTbot/ibot.cpp index a1b72f2..9bd1828 100644 --- a/src/qTbot/src/public/qTbot/ibot.cpp +++ b/src/qTbot/src/public/qTbot/ibot.cpp @@ -5,16 +5,22 @@ //# of this license document, but changing it is not allowed. //# +#include "httpexception.h" #include "ibot.h" #include "qstandardpaths.h" #include +#include namespace qTbot { IBot::IBot() { _manager = new QNetworkAccessManager(); - _manager->setAutoDeleteReplies(false); + _manager->setAutoDeleteReplies(true); + _requestExecutor = new QTimer(this); + _requestExecutor->setInterval(1000 / 20); // 20 times per second. + + connect(_requestExecutor, &QTimer::timeout, this , &IBot::handleEcxecuteRequest); } IBot::~IBot() { @@ -31,6 +37,8 @@ const QByteArray &IBot::token() const { void IBot::setToken(const QByteArray &newToken) { _token = newToken; + _startTime = QDateTime::currentDateTime(); + } void IBot::incomeNewUpdate(const QSharedPointer &message) { @@ -48,12 +56,9 @@ void IBot::incomeNewUpdate(const QSharedPointer &message) { } } -QSharedPointer -IBot::sendRequest(const QSharedPointer &rquest) { +QNetworkReply* IBot::sendRquestImpl(const QSharedPointer &rquest) { if (!rquest) - return nullptr; - - doRemoveFinishedRequests(); + return {}; auto && url = makeUrl(rquest); @@ -61,62 +66,93 @@ IBot::sendRequest(const QSharedPointer &rquest) { qDebug() << url; #endif - QSharedPointer networkReplay; + QNetworkReply* networkReplay = nullptr; QSharedPointer httpData; switch (rquest->method()) { case iRequest::Get: { - auto reply = _manager->get(QNetworkRequest(url)); - - // we control replay object wia shared pointers. - reply->setParent(nullptr); + networkReplay = _manager->get(QNetworkRequest(url)); - networkReplay.reset(reply); break; } case iRequest::Post: -// req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); -// reply = m_nam.post(req, params.toByteArray()); + // req.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + // reply = m_nam.post(req, params.toByteArray()); -// break; + // break; case iRequest::Upload: QNetworkRequest netRequest(url); httpData = rquest->argsToMultipartFormData(); if (httpData) { - auto reply = _manager->post(netRequest, httpData.data()); + networkReplay = _manager->post(netRequest, httpData.data()); - // we control replay object wia shared pointers. - reply->setParent(nullptr); - - networkReplay.reset(reply); } else { - return nullptr; + return {}; } break; } - size_t address = reinterpret_cast(networkReplay.get()); - _replayStorage[address] = networkReplay; + return networkReplay; +} - connect(networkReplay.get(), &QNetworkReply::finished, this, - [this, address, httpData]() { - _toRemove.push_back(address); - }); +QDateTime IBot::startTime() const { + return _startTime; +} - connect(networkReplay.get(), &QNetworkReply::errorOccurred, this, - [this, address](QNetworkReply::NetworkError err){ - qWarning() << "The reqeust " << address << " finished with error code : " << err; - if (auto&& replay = _replayStorage.value(address)) { - qWarning() << replay->errorString(); - } +unsigned long long IBot::totalSentRequests() const { + return _totalRequest; +} - _toRemove.push_back(address); - }); +int IBot::parallelActiveNetworkThreads() const { + return _parallelActiveNetworkThreads; +} + +void IBot::setParallelActiveNetworkThreads(int newParallelActiveNetworkThreads) { + _parallelActiveNetworkThreads = newParallelActiveNetworkThreads; +} + +void IBot::setCurrentParallelActiveNetworkThreads(int newParallelActiveNetworkThreads) { + _currentParallelActiveNetworkThreads = newParallelActiveNetworkThreads; + qDebug () << "current network active requests count : " << _currentParallelActiveNetworkThreads; +} + +int IBot::reqestLimitPerSecond() const { + return _requestExecutor->interval() * 1000; +} + +void IBot::setReqestLimitPerSecond(int newReqestLimitPerSecond) { + _requestExecutor->setInterval(1000 / newReqestLimitPerSecond); +} + +QFuture +IBot::sendRequest(const QSharedPointer &rquest) { + auto&& responce = QSharedPointer>::create(); + responce->start(); + + + _requestQueue.insert(makeKey(rquest->priority()), + RequestData{rquest, "", responce}); + + _requestExecutor->start(); + + return responce->future(); +} + +QFuture +IBot::sendRequest(const QSharedPointer &rquest, + const QString &pathToResult) { + auto&& responce = QSharedPointer>::create(); + responce->start(); + _requestQueue.insert(makeKey(rquest->priority()), + RequestData{rquest, pathToResult, responce}); + + _requestExecutor->start(); + + return responce->future(); - return networkReplay; } void IBot::markUpdateAsProcessed(const QSharedPointer &message) { @@ -139,12 +175,113 @@ void IBot::handleIncomeNewUpdate(const QSharedPointer & message) { emit sigReceiveUpdate(message); } -void IBot::doRemoveFinishedRequests() { - for (auto address: std::as_const(_toRemove)) { - _replayStorage.remove(address); +void IBot::handleEcxecuteRequest() { + if (!_requestQueue.size()) { + _requestExecutor->stop(); + return; + } + + if (_currentParallelActiveNetworkThreads > _parallelActiveNetworkThreads) { + return; + } + + auto&& requestData = _requestQueue.take(_requestQueue.firstKey()); + + if (requestData.responceFilePath.size()) { + sendRequestPrivate(requestData.request, requestData.responceFilePath, requestData.responce); + return; + } + + sendRequestPrivate(requestData.request, requestData.responce); +} + +unsigned long long IBot::makeKey(iRequest::RequestPriority priority) { + unsigned long long key = _totalRequest; + _totalRequest++; + key = key | (static_cast(iRequest::RequestPriority::MaxPriorityValue - priority) << 56); + return key; +} + +void IBot::sendRequestPrivate(const QSharedPointer &rquest, + const QSharedPointer > &promise) { + + QNetworkReply* networkReplay = sendRquestImpl(rquest); + if (!networkReplay) { + return; + } + + setCurrentParallelActiveNetworkThreads(_currentParallelActiveNetworkThreads + 1); + + connect(networkReplay, &QNetworkReply::finished, [this, networkReplay, promise](){ + if (networkReplay->error() == QNetworkReply::NoError) { + promise->addResult(networkReplay->readAll()); + promise->finish(); + + } else { + promise->setException(HttpException(networkReplay->error(), networkReplay->errorString().toLatin1() + networkReplay->readAll())); + } + + setCurrentParallelActiveNetworkThreads(_currentParallelActiveNetworkThreads - 1); + + }); + + auto && setProggress = [promise](qint64 bytesCurrent, qint64 bytesTotal){ + + if (promise->future().progressMaximum() != bytesTotal) + promise->setProgressRange(0, bytesTotal); + + promise->setProgressValue(bytesCurrent); + }; + + connect(networkReplay, &QNetworkReply::downloadProgress, setProggress); + connect(networkReplay, &QNetworkReply::uploadProgress, setProggress); +} + +void IBot::sendRequestPrivate(const QSharedPointer &rquest, + const QString &pathToResult, + const QSharedPointer> & promise) { + auto&& file = QSharedPointer::create(pathToResult); + + if (!file->open(QIODeviceBase::WriteOnly | QIODevice::Truncate)) { + qCritical() << "Fail to wrote data into " << pathToResult; + return; + } + + QNetworkReply* networkReplay = sendRquestImpl(rquest); + if (!networkReplay) { + return; } - _toRemove.clear(); + setCurrentParallelActiveNetworkThreads(_currentParallelActiveNetworkThreads + 1); + connect(networkReplay, &QNetworkReply::finished, [this, promise, networkReplay, pathToResult](){ + + if (networkReplay->error() == QNetworkReply::NoError) { + promise->setException(HttpException(networkReplay->error(), networkReplay->errorString().toLatin1())); + } else { + promise->addResult(pathToResult.toUtf8()); // wil not work with UTF 8 path names + promise->finish(); + } + setCurrentParallelActiveNetworkThreads(_currentParallelActiveNetworkThreads - 1); + }); + + connect(networkReplay, &QNetworkReply::readyRead, [networkReplay, promise, pathToResult, file](){ + if (networkReplay->error() == QNetworkReply::NoError) { + file->write(networkReplay->readAll()); + } + + }); + + auto && setProggress = [promise](qint64 bytesCurrent, qint64 bytesTotal){ + + if (promise->future().progressMaximum() != bytesTotal) + promise->setProgressRange(0, bytesTotal); + + promise->setProgressValue(bytesCurrent); + }; + + connect(networkReplay, &QNetworkReply::downloadProgress, setProggress); + connect(networkReplay, &QNetworkReply::uploadProgress, setProggress); + } QSet IBot::processed() const { diff --git a/src/qTbot/src/public/qTbot/ibot.h b/src/qTbot/src/public/qTbot/ibot.h index e89d900..e89bd5e 100644 --- a/src/qTbot/src/public/qTbot/ibot.h +++ b/src/qTbot/src/public/qTbot/ibot.h @@ -13,16 +13,16 @@ #include "qTbot/iupdate.h" #include "qTbot/irequest.h" -#include "ifile.h" -#include "qfileinfo.h" - #include #include #include +#include +#include #include #include #include +#include namespace qTbot { @@ -38,6 +38,40 @@ class QTBOT_EXPORT IBot: public QObject IBot(); ~IBot(); + /** + * @brief The FileType enum is is file types, deffine how we should download a file - as a local object in file system or into virtual memory. + */ + enum FileType { + /// The Ram is a Virtual type of download files will save all file data into QFuture bytes array. + Ram, + + /// The Local file will saved in internal file storage. + /// This file type can use the filse system as cache. + /// and will doenload file with same id only one time. + Local + }; + + /** + * @brief The RequestData class is simple wrapper of request object with path of responce. + * If Path of responce is empty then responce will saved in RAM. + */ + struct RequestData { + /** + * @brief request saved request object. + */ + QSharedPointer request; + + /** + * @brief responceFilePath path to responce. + */ + QString responceFilePath = ""; + + /** + * @brief responce This is promise to responce of this requests that will sets back. + */ + QSharedPointer> responce; + }; + /** * @brief login This method get bae information of the bot from remote server. * @param token This is token value for login @@ -58,7 +92,7 @@ class QTBOT_EXPORT IBot: public QObject * * @note the specific implementations of this interface can have a different method for sending. */ - virtual bool sendMessage(const QVariant& chatId, const QString& text) = 0; + virtual bool sendMessage(const QVariant& chatId, const QString& text, iRequest::RequestPriority priority = iRequest::NormalPriority) = 0; /** * @brief deleteMessage This is main method to delete messages. @@ -74,10 +108,10 @@ class QTBOT_EXPORT IBot: public QObject * This function allows you to retrieve a file by its ID. * * @param fileId The ID of the file to retrieve. - * @param fileType This is a saving way, by Default will be used a iFile::Type::Ram + * @param fileType This is a saving way, by Default will be used a FileType::Ram * @return Returns true if the file retrieval operation was successfully initiated and false in case of an error. */ - virtual QSharedPointer getFile(const QString& fileId, iFile::Type fileType = iFile::Type::Ram) = 0; + virtual QFuture getFile(const QString& fileId, FileType fileType = Ram) = 0; /** * @brief send @a file . @@ -133,6 +167,43 @@ class QTBOT_EXPORT IBot: public QObject */ virtual void setProcessed(const QSet &newProcessed); + /** + * @brief reqestLimitPerSecond this is request performence limitation. by default is 20 requests per second + * @return + */ + int reqestLimitPerSecond() const; + + /** + * @brief setReqestLimitPerSecond this method sets new limitation of bot performance. + * @param newReqestLimitPerSecond this is a new value of performance. + */ + void setReqestLimitPerSecond(int newReqestLimitPerSecond); + + /** + * @brief parallelActiveNetworkThreads + * @return + */ + int parallelActiveNetworkThreads() const; + + /** + * @brief setParallelActiveNetworkThreads + * @param newParallelActiveNetworkThreads + */ + void setParallelActiveNetworkThreads(int newParallelActiveNetworkThreads); + + /** + * @brief totalSentRequests This is total prepared requests count of bot from the start. + * @see startTime method to get start date time. + * @return requests count. + */ + unsigned long long totalSentRequests() const; + + /** + * @brief startTime this is time when bol wil started. + * @return + */ + QDateTime startTime() const; + protected: /** @@ -163,7 +234,6 @@ class QTBOT_EXPORT IBot: public QObject return ptr; } - /** * @brief makeUrl This method prepare a prefix url for http requests. * @param request - This is request object for that will be prepared url. @@ -174,12 +244,21 @@ class QTBOT_EXPORT IBot: public QObject /** * @brief sendRequest This method sent custom requests to the server. * @param rquest This is message that will be sent to server. - * @return shared pointer to the request replay. - * @note The raplay will be removed from local storage only after error or finishing, If you want to save replay just make local copy of the shared pointer. + * @return future pointer to the request replay. */ - QSharedPointer + QFuture sendRequest(const QSharedPointer& rquest); + /** + * @brief sendRequest This method sent custom requests to the server. + * @param rquest This is message that will be sent to server. + * @return future pointer to the request replay. + * @note This is same as a default implementaion execpt save data location, + * this method will create new file that located @a pathToResult and save all received data to this location. + */ + QFuture + sendRequest(const QSharedPointer& rquest, const QString& pathToResult); + /** * @brief setToken This is setter of the IBot::token value. * @param newToken This is new value of the token. @@ -233,17 +312,34 @@ class QTBOT_EXPORT IBot: public QObject */ void sigStopRequire(); +private slots: + void handleEcxecuteRequest(); private: - void doRemoveFinishedRequests(); + unsigned long long makeKey(iRequest::RequestPriority priority); + void setCurrentParallelActiveNetworkThreads(int newParallelActiveNetworkThreads); + + void sendRequestPrivate(const QSharedPointer& rquest, + const QSharedPointer> & promiseResult); + + void sendRequestPrivate(const QSharedPointer& rquest, + const QString& pathToResult, + const QSharedPointer > &promiseResult); + + QNetworkReply *sendRquestImpl(const QSharedPointer &rquest); QByteArray _token; QString _name; QMap> _notProcessedUpdates; QSet _processed; QNetworkAccessManager *_manager = nullptr; + QTimer* _requestExecutor = nullptr; + unsigned long long _totalRequest = 0; + QDateTime _startTime; + QMap _requestQueue; + int _currentParallelActiveNetworkThreads = 0; + int _parallelActiveNetworkThreads = 5; + - QMap> _replayStorage; - QList _toRemove; }; diff --git a/src/qTbot/src/public/qTbot/ifile.cpp b/src/qTbot/src/public/qTbot/ifile.cpp deleted file mode 100644 index 2febf92..0000000 --- a/src/qTbot/src/public/qTbot/ifile.cpp +++ /dev/null @@ -1,125 +0,0 @@ -//# -//# Copyright (C) 2023-2024 QuasarApp. -//# Distributed under the GPLv3 software license, see the accompanying -//# Everyone is permitted to copy and distribute verbatim copies -//# of this license document, but changing it is not allowed. -//# - -#include "ifile.h" - -namespace qTbot { - -iFile::iFile(const QSharedPointer& replay) { - setDownloadRequest(replay); -} - -float iFile::uploadProgress() const { - return _uploadProgress; -} - -void iFile::setUploadProgress(float newUploadProgress) { - if (qFuzzyCompare(_uploadProgress, newUploadProgress)) - return; - - _uploadProgress = newUploadProgress; - emit uploadProgressChanged(); -} - -float iFile::downloadProgress() const { - return _downloadProgress; -} - -void iFile::setDownloadProgress(float newDownloadProgress) { - if (qFuzzyCompare(_downloadProgress, newDownloadProgress)) - return; - - _downloadProgress = newDownloadProgress; - emit downloadProgressChanged(); -} - -int iFile::error() const { - return _error; -} - -void iFile::setError(int newError) { - if (_error == newError) - return; - _error = newError; - emit errorChanged(); -} - -const QSharedPointer &iFile::replay() const { - return _replay; -} - -bool iFile::isFinished() const { - return _finished; -} - -void iFile::handleError(QNetworkReply::NetworkError error) { - setError(error); - setUploadProgress(0); - setDownloadProgress(0); - -} - -void iFile::handleUploadProgressChanged(qint64 bytesSent, qint64 bytesTotal) { - setUploadProgress(bytesSent / static_cast(bytesTotal)); -} - -void iFile::handleDownloadProgressChanged(qint64 bytesReceived, qint64 bytesTotal) { - setDownloadProgress(bytesReceived / static_cast(bytesTotal)); -} - -void iFile::setDownloadRequest(const QSharedPointer &replay) { - - if (_replay) { - disconnect(_replay.get(), &QNetworkReply::finished, - this, &iFile::handleFinished); - - disconnect(_replay.get(), &QNetworkReply::errorOccurred, - this, &iFile::handleError); - - disconnect(_replay.get(), &QNetworkReply::readyRead, - this, &iFile::handleReadReady); - - disconnect(_replay.get(), &QNetworkReply::uploadProgress, - this, &iFile::handleUploadProgressChanged); - - disconnect(_replay.get(), &QNetworkReply::downloadProgress, - this, &iFile::handleDownloadProgressChanged); - } - - _replay = replay; - - if (_replay) { - connect(replay.get(), &QNetworkReply::finished, - this, &iFile::handleFinished, Qt::DirectConnection); - - connect(replay.get(), &QNetworkReply::errorOccurred, - this, &iFile::handleError, Qt::DirectConnection); - - connect(replay.get(), &QNetworkReply::readyRead, - this, &iFile::handleReadReady, Qt::DirectConnection); - - connect(replay.get(), &QNetworkReply::uploadProgress, - this, &iFile::handleUploadProgressChanged, Qt::DirectConnection); - - connect(replay.get(), &QNetworkReply::downloadProgress, - this, &iFile::handleDownloadProgressChanged, Qt::DirectConnection); - } - -} - -void iFile::setFinished(bool newFinished) { - - if (newFinished != _finished) { - _finished = newFinished; - emit finishedChanged(); - } -} - -void iFile::handleFinished() { - setFinished(true); -} -} diff --git a/src/qTbot/src/public/qTbot/ifile.h b/src/qTbot/src/public/qTbot/ifile.h deleted file mode 100644 index 11fa5bb..0000000 --- a/src/qTbot/src/public/qTbot/ifile.h +++ /dev/null @@ -1,174 +0,0 @@ -//# -//# Copyright (C) 2023-2024 QuasarApp. -//# Distributed under the GPLv3 software license, see the accompanying -//# Everyone is permitted to copy and distribute verbatim copies -//# of this license document, but changing it is not allowed. -//# - -#ifndef IFILE_H -#define IFILE_H - -#include "qnetworkreply.h" -#include -#include - -namespace qTbot { - -/** - * @brief The iFile class This is main interface for all implementations of the files. - */ -class QTBOT_EXPORT iFile: public QObject -{ - Q_OBJECT -public: - - /** - * @brief The Type enum is type of the file object. - */ - enum Type { - /// This is local file, all receive bytes will be save directed into file. - Local, - /// This is memory saved file. All received bytes will be saved into QByteArray object. - Ram - }; - - iFile(const QSharedPointer& replay); - - /** - * @brief Get the current upload progress. - * @return The current upload progress as a floating-point value. - */ - float uploadProgress() const; - - /** - * @brief Set the upload progress. - * @param newUploadProgress The new upload progress value to set. - */ - void setUploadProgress(float newUploadProgress); - - /** - * @brief Get the current download progress. - * @return The current download progress as a floating-point value. - */ - float downloadProgress() const; - - /** - * @brief Set the download progress. - * @param newDownloadProgress The new download progress value to set. - */ - void setDownloadProgress(float newDownloadProgress); - - /** - * @brief Get the error code associated with this file. - * @return The error code as an integer value. - */ - int error() const; - - /** - * @brief Set the error code for this file. - * @param newError The new error code to set. - */ - void setError(int newError); - - /** - * @brief Get the shared pointer to the associated QNetworkReply. - * @return A shared pointer to the associated QNetworkReply. - */ - const QSharedPointer& replay() const; - - /** - * @brief type This is type of the file object. - * @return type of the file object. - */ - virtual Type type() const = 0; - - /** - * @brief finished return true if the request was finished else false. - * @return true if the request was finished else false - */ - bool isFinished() const; - - /** - * @brief setDownloadRequest This method sets replay for the file. - * @param replay This is pointer to the replay. - */ - void setDownloadRequest(const QSharedPointer& replay); -protected: - - /** - * @brief setFinished monual sets finished flag. - * @param newFinished new value for the finished flag. - */ - void setFinished(bool newFinished); - -protected slots: - /** - * @brief Slot to handle when data is ready to be read. - */ - virtual void handleReadReady() = 0; - - /** - * @brief Slot to handle when the network operation is finished. - */ - virtual void handleFinished(); - - /** - * @brief Slot to handle errors in the network operation. - * @param error This is error code. - */ - virtual void handleError(QNetworkReply::NetworkError error); - -private slots: - - /** - * @brief Slot to handle changes in upload progress. - * @param bytesSent current snet bytes. - * @param bytesTotal total to sent bytes. - */ - void handleUploadProgressChanged(qint64 bytesSent, qint64 bytesTotal); - - /** - * @brief Slot to handle changes in download progress. - * @param bytesReceived current received bytes. - * @param bytesTotal total to receive bytes. - */ - void handleDownloadProgressChanged(qint64 bytesReceived, qint64 bytesTotal); - -signals: - /** - * @brief Signal emitted when the upload progress changes. - */ - void uploadProgressChanged(); - - /** - * @brief Signal emitted when the download progress changes. - */ - void downloadProgressChanged(); - - /** - * @brief Signal emitted when the error code changes. - */ - void errorChanged(); - - /** - * @brief Signal emitted when the associated QNetworkReply changes. - */ - void replayChanged(); - - /** - * @brief Signal emitted when the associated finished changes. - */ - void finishedChanged(); - -private: - float _uploadProgress = 0; - float _downloadProgress = 0; - int _error = 0; - bool _finished = false; - - QSharedPointer _replay; - - friend class ITelegramBot; -}; -} -#endif // IFILE_H diff --git a/src/qTbot/src/public/qTbot/internalexception.cpp b/src/qTbot/src/public/qTbot/internalexception.cpp new file mode 100644 index 0000000..5a22334 --- /dev/null +++ b/src/qTbot/src/public/qTbot/internalexception.cpp @@ -0,0 +1,28 @@ +//# +//# Copyright (C) 2023-2024 QuasarApp. +//# Distributed under the GPLv3 software license, see the accompanying +//# Everyone is permitted to copy and distribute verbatim copies +//# of this license document, but changing it is not allowed. +//# + +#include "internalexception.h" +namespace qTbot { + + +InternalException::InternalException(const QByteArray &erroString) { + _errText = erroString; + +} + +const char *InternalException::what() const noexcept { + return _errText.constData(); +} + +void InternalException::raise() const { + throw *this; +} + +QException *InternalException::clone() const { + return new InternalException(_errText); +} +} diff --git a/src/qTbot/src/public/qTbot/internalexception.h b/src/qTbot/src/public/qTbot/internalexception.h new file mode 100644 index 0000000..0b05ad8 --- /dev/null +++ b/src/qTbot/src/public/qTbot/internalexception.h @@ -0,0 +1,39 @@ +//# +//# Copyright (C) 2023-2024 QuasarApp. +//# Distributed under the GPLv3 software license, see the accompanying +//# Everyone is permitted to copy and distribute verbatim copies +//# of this license document, but changing it is not allowed. +//# + +#include +#include + +#ifndef INTERNALEXCEPTION_H +#define INTERNALEXCEPTION_H + + +namespace qTbot { + +/** + * @brief The InternalException class contais string value to describe what happened. + */ +class InternalException: public QException +{ + // exception interface +public: + InternalException(const QByteArray& erroString = {}); + + const char *what() const noexcept override; + + // QException interface +public: + void raise() const override; + QException *clone() const override; + +private: + QByteArray _errText; +}; +} +#endif // INTERNALEXCEPTION_H + + diff --git a/src/qTbot/src/public/qTbot/irequest.cpp b/src/qTbot/src/public/qTbot/irequest.cpp index 79bf2d8..be6e19a 100644 --- a/src/qTbot/src/public/qTbot/irequest.cpp +++ b/src/qTbot/src/public/qTbot/irequest.cpp @@ -91,6 +91,14 @@ QSharedPointer iRequest::argsToMultipartFormData() const { return multiPart; } +iRequest::RequestPriority iRequest::priority() const { + return _priority; +} + +void iRequest::setPriority(RequestPriority newPriority) { + _priority = newPriority; +} + const QString& iRequest::request() const { return _request; } diff --git a/src/qTbot/src/public/qTbot/irequest.h b/src/qTbot/src/public/qTbot/irequest.h index 481b8e5..0537ab1 100644 --- a/src/qTbot/src/public/qTbot/irequest.h +++ b/src/qTbot/src/public/qTbot/irequest.h @@ -46,6 +46,19 @@ class QTBOT_EXPORT iRequest Upload }; + /** + * @brief The RequestPriority enum + */ + enum RequestPriority { + NoPriority = 0, + LowPriority = 1, + NormalPriority = 2, + HighPriority = 3, + UngeredPriority = 4, + + MaxPriorityValue = 0xff + }; + /** * @brief makeUpload This method prepare data to upload; * @return data array prepared to sending. @@ -109,9 +122,19 @@ class QTBOT_EXPORT iRequest * @return QHttpMultiPart - A QHttpMultiPart object containing multipart/form-data request data. */ QSharedPointer argsToMultipartFormData() const; + + /** + * @brief priority This is priority of executabel this request on client. + * @return + */ + RequestPriority priority() const; + + void setPriority(RequestPriority newPriority); + private: QString _request; QMap _args; + RequestPriority _priority = RequestPriority::NormalPriority; }; diff --git a/src/qTbot/src/public/qTbot/itelegrambot.cpp b/src/qTbot/src/public/qTbot/itelegrambot.cpp index f1e617e..0a4a4b8 100644 --- a/src/qTbot/src/public/qTbot/itelegrambot.cpp +++ b/src/qTbot/src/public/qTbot/itelegrambot.cpp @@ -7,12 +7,12 @@ #include "itelegrambot.h" #include "qTbot/messages/telegramupdateanswer.h" -#include "file.h" #include "requests/telegrammdownloadfile.h" #include "qdir.h" #include "requests/telegramsendcontact.h" #include "requests/telegramsenddocument.h" -#include "virtualfile.h" +#include "httpexception.h" +#include "internalexception.h" #include #include @@ -52,25 +52,29 @@ bool ITelegramBot::login(const QByteArray &token) { setToken(token); - _loginReplay = sendRequest(QSharedPointer::create()); - if (_loginReplay) { - connect(_loginReplay.get(), &QNetworkReply::finished, - this, &ITelegramBot::handleLogin, - Qt::DirectConnection); - connect(_loginReplay.get(), &QNetworkReply::errorOccurred, - this, &ITelegramBot::handleLoginErr, - Qt::DirectConnection); - return true; - } + QFuture loginFuture = sendRequest(QSharedPointer::create()); + loginFuture. + then(this, [this](const QByteArray& data) { + ITelegramBot::handleLogin(data); + } ). + onFailed(this, [this](const HttpException& exeption){ + handleLoginErr(exeption.code()); + }); - return false; + return loginFuture.isValid(); } -bool ITelegramBot::sendMessage(const QVariant &chatId, const QString &text) { - return sendSpecificMessage(TelegramArgs{chatId, text}); +bool ITelegramBot::sendMessage(const QVariant &chatId, + const QString &text, + iRequest::RequestPriority priority) { + TelegramArgs arg{chatId, text}; + arg.requestPriority = priority; + return sendSpecificMessage(arg); } -bool ITelegramBot::sendLocationRequest(const QVariant &chatId, const QString &text, const QString &buttonText, +bool ITelegramBot::sendLocationRequest(const QVariant &chatId, + const QString &text, + const QString &buttonText, bool onetimeKeyboard) { auto replyMarkup = QSharedPointer::create(); @@ -284,35 +288,35 @@ bool ITelegramBot::sendSpecificMessageWithKeyboard(const TelegramArgs& args, return sendSpecificMessage(args, prepareKeyboard(autoResizeKeyboard, onTimeKeyboard, keyboard)); } -QSharedPointer ITelegramBot::getFile(const QString &fileId, iFile::Type fileType) { +QFuture ITelegramBot::getFile(const QString &fileId, FileType fileType) { - QSharedPointer result = nullptr; if (fileId.isEmpty()) { - return result; + return {}; } auto localFilePath = findFileInlocatStorage(fileId); if (!localFilePath.isEmpty()) { + QPromise fileDataResult; - if (fileType == iFile::Ram) { + if (fileType == FileType::Ram) { QFile localFile(localFilePath); if (localFile.open(QIODevice::ReadOnly)) { - auto&& virtualFile = QSharedPointer::create(nullptr); - virtualFile->setArray(localFile.readAll()); + fileDataResult.addResult(localFile.readAll()); localFile.close(); - - result = virtualFile; } - } else if (fileType == iFile::Local) { - result = QSharedPointer::create(nullptr, localFilePath); + } else if (fileType == FileType::Local) { + fileDataResult.addResult(localFilePath.toUtf8()); } - result->setDownloadProgress(1); - result->setFinished(true); - return result; + fileDataResult.setProgressRange(0,1); + fileDataResult.setProgressValue(1); + + fileDataResult.finish(); + + return fileDataResult.future(); } auto&& metaInfo = getFileInfoByUniqueId(fileId); @@ -327,43 +331,57 @@ QSharedPointer ITelegramBot::getFile(const QString &fileId, iFile::Type f if (localFilePath.isEmpty()) - return result; - - if (auto &&replay = sendRequest(msg)) { - // here i must be receive responce and prepare new request to file from the call back function. - if (fileType == iFile::Ram) { - result = QSharedPointer::create(replay); - } else if (fileType == iFile::Local) { - result = QSharedPointer::create(replay, localFilePath); - } + return {}; + + QFuture replay; + if (fileType == FileType::Ram) { + replay = sendRequest(msg); + } else { + replay = sendRequest(msg, localFilePath); } - return result; + return replay; } } + auto longWay = QSharedPointer>::create(); + longWay->start(); - if (fileType == iFile::Ram) { - result = QSharedPointer::create(); - } else if (fileType == iFile::Local) { - result = QSharedPointer::create(localFilePath); + auto&& future = getFileMeta(fileId); + if (!future.isValid()) { + return {}; } - auto&& metaReploay = getFileMeta(fileId, result.toWeakRef()); - return result; -} + future.then([this, fileId, fileType, longWay](const QByteArray& header){ + handleFileHeader(header); -QSharedPointer ITelegramBot::getFileMeta(const QString &fileId, const QWeakPointer& receiver) { - auto msg = QSharedPointer::create(fileId); + auto&& future = getFile(fileId, fileType); + + if (!future.isValid()) { + longWay->setException(InternalException("Failed to wrote file into internal cache!")); + return; + }; - if (auto&& ptr = sendRequest(msg)) { - connect(ptr.get(), &QNetworkReply::finished, - this, std::bind(&ITelegramBot::handleFileHeader, this, ptr.toWeakRef(), receiver)); + future.then([longWay](const QByteArray& data){ + longWay->addResult(data); + }); + + + }).onFailed([longWay](const QException& exep){ + longWay->setException(exep); + }); + + return longWay->future(); +} - return ptr; +QFuture ITelegramBot::getFileMeta(const QString &fileId) { + auto msg = QSharedPointer::create(fileId); + auto && future = sendRequest(msg); + if (future.isValid()) { + return future; } - return nullptr; + return {}; } bool ITelegramBot::sendFile(const QFileInfo &file, const QVariant &chatId) { @@ -479,9 +497,9 @@ bool ITelegramBot::sendContact(const TelegramArgs &args, return false; return sendMessageRequest(QSharedPointer::create(args, - firstName, - phone, - secondName)); + firstName, + phone, + secondName)); } int ITelegramBot::getFileSizeByUniqueId(const QString &id) const { @@ -520,86 +538,73 @@ void ITelegramBot::handleIncomeNewUpdate(const QSharedPointer & update) bool ITelegramBot::sendMessageRequest(const QSharedPointer &rquest, const std::function &msgIdCB) { - auto&& reply = IBot::sendRequest(rquest); - if (reply) { - connect(reply.get(), &QNetworkReply::finished, this, - [ reply, msgIdCB, this]() { - - if (reply->error() == QNetworkReply::NoError) { - QByteArray&& responseData = reply->readAll(); - QJsonDocument json = QJsonDocument::fromJson(responseData); - - const QJsonObject&& obj = json.object(); - if (obj.contains("result")) { - unsigned long long chatId = obj["result"]["chat"]["id"].toInteger(); - int messageID = obj["result"]["message_id"].toInt(); - if (msgIdCB) { - msgIdCB(messageID); - } - - if (chatId) { - _lastMessageId[chatId] = messageID; - } - - return; - } - } - - if (msgIdCB) { - msgIdCB(-1); - } - }); + auto&& future = IBot::sendRequest(rquest); + if (future.isValid()) { + future.then(this, [this, msgIdCB](const QByteArray& responseData){ + + QJsonDocument json = QJsonDocument::fromJson(responseData); + + const QJsonObject&& obj = json.object(); + if (obj.contains("result")) { + unsigned long long chatId = obj["result"]["chat"]["id"].toInteger(); + int messageID = obj["result"]["message_id"].toInt(); + if (msgIdCB) { + msgIdCB(messageID); + } + + if (chatId) { + _lastMessageId[chatId] = messageID; + } + + return; + } + }).onFailed([msgIdCB](){ + + if (msgIdCB) { + msgIdCB(-1); + } + }); + + return true; } - return bool(reply); + return false; } -void ITelegramBot::handleLogin() { +void ITelegramBot::handleLogin(const QByteArray&ansver) { - if (_loginReplay) { - auto&& ans = makeMesasge(_loginReplay->readAll()); + auto&& ans = makeMesasge(ansver); - if (!ans->isValid()) { - qWarning() << "login error occured: "; - } + if (!ans->isValid()) { + qWarning() << "login error occured: "; + return; + } - auto&& result = ans->result().toObject(); + auto&& result = ans->result().toObject(); - setId(result.value("id").toInteger()); - setName( result.value("first_name").toString()); - setUsername( result.value("username").toString()); - - _loginReplay.reset(); - } + setId(result.value("id").toInteger()); + setName( result.value("first_name").toString()); + setUsername( result.value("username").toString()); } void ITelegramBot::handleLoginErr(QNetworkReply::NetworkError err) { if (err) { - qDebug() << "Network error occured. code: " << err; + qCritical() << "Network error occured. code: " << err; } - _loginReplay.reset(); } -void ITelegramBot::handleFileHeader(const QWeakPointer &sender, - const QWeakPointer& receiver) { - if (auto&& sharedPtr = sender.lock()) { - auto&& ansver = makeMesasge(sharedPtr->readAll()); - - if (!ansver->isValid()) { - onRequestError(ansver); - return; - } +void ITelegramBot::handleFileHeader(const QByteArray& header) { + auto&& ansver = makeMesasge(header); - auto &&fileMetaInfo = makeMesasge(ansver->result().toObject()); + if (!ansver->isValid()) { + onRequestError(ansver); + return; + } - _filesMetaInfo.insert(fileMetaInfo->fileId(), fileMetaInfo); + auto &&fileMetaInfo = makeMesasge(ansver->result().toObject()); - if (auto&& sharedPtr = receiver.lock()) { - auto&& downloadRequest = QSharedPointer::create(fileMetaInfo->takePath()); - sharedPtr->setDownloadRequest(sendRequest(downloadRequest)); - } - } + _filesMetaInfo.insert(fileMetaInfo->fileId(), fileMetaInfo); } QString ITelegramBot::findFileInlocatStorage(const QString &fileId) const { diff --git a/src/qTbot/src/public/qTbot/itelegrambot.h b/src/qTbot/src/public/qTbot/itelegrambot.h index 82e140b..2baad29 100644 --- a/src/qTbot/src/public/qTbot/itelegrambot.h +++ b/src/qTbot/src/public/qTbot/itelegrambot.h @@ -40,7 +40,9 @@ class QTBOT_EXPORT ITelegramBot : public IBot bool login(const QByteArray &token) override; - bool sendMessage(const QVariant &chatId, const QString& text) override; + bool sendMessage(const QVariant &chatId, + const QString& text, + iRequest::RequestPriority priority = iRequest::NormalPriority) override; /** * @brief sendLocationRequest This method setn into chat button that will automaticaly sent geo location to bot. @@ -187,17 +189,20 @@ class QTBOT_EXPORT ITelegramBot : public IBot bool editSpecificMessage(const QVariant &messageId, const TelegramArgs& args); - [[nodiscard("do not forget to save shared pointer of file handler, because it's will not save inner bot object.")]] - QSharedPointer getFile(const QString& fileId, iFile::Type fileType = iFile::Type::Ram) override; + /** + * @brief getFile This method sent request to get a file by id. The files can be saved into local storage if the Type choosed as Local. + * @param fileId This is Telegram file id. + * @param fileType this is type of file. Depends of this argument future will be contains deffrent result if it is Local type then future will contains link to local file path else file source as bytes. + * @return futur with file source or path to file depends of type. + */ + QFuture getFile(const QString& fileId, FileType fileType = FileType::Ram) override; /** * @brief getFileMeta This method receive meta information of the file. * @param fileId This is id of the file. - * @param receiver this is wrapper of the file. Set to nullptr if you no need to wait a physical file. - * @return true if the reqests sents successful. + * @return future objectl with result. */ - QSharedPointer getFileMeta(const QString& fileId, - const QWeakPointer &receiver = {nullptr}); + QFuture getFileMeta(const QString& fileId); bool sendFile( const QFileInfo& file, const QVariant& chatId) override; @@ -389,10 +394,9 @@ class QTBOT_EXPORT ITelegramBot : public IBot const std::function& msgIdCB = {}); private slots: - void handleLogin(); + void handleLogin(const QByteArray &ansver); void handleLoginErr(QNetworkReply::NetworkError err); - void handleFileHeader(const QWeakPointer& sender, - const QWeakPointer &receiver); + void handleFileHeader(const QByteArray &header); private: @@ -405,7 +409,6 @@ private slots: unsigned long long _id = 0; QString _username; - QSharedPointer _loginReplay; QMap> _handleButtons; QHash _lastMessageId; diff --git a/src/qTbot/src/public/qTbot/telegramargs.cpp b/src/qTbot/src/public/qTbot/telegramargs.cpp index 0fb7fd3..a9c9109 100644 --- a/src/qTbot/src/public/qTbot/telegramargs.cpp +++ b/src/qTbot/src/public/qTbot/telegramargs.cpp @@ -13,7 +13,8 @@ TelegramArgs::TelegramArgs(const QVariant &id, unsigned long long replyToMessageId, const QString &parseMode, bool disableWebPagePreview, - const QString &callBackQueryId, const std::function &msgIdCB) + const QString &callBackQueryId, const std::function &msgIdCB, + iRequest::RequestPriority priority) { this->chatId = id; @@ -23,6 +24,7 @@ TelegramArgs::TelegramArgs(const QVariant &id, this->replyToMessageId = replyToMessageId; this->parseMode = parseMode; this->msgIdCB = msgIdCB; + this->requestPriority = priority; } QMap TelegramArgs::toMap(bool textAsCaption) const { diff --git a/src/qTbot/src/public/qTbot/telegramargs.h b/src/qTbot/src/public/qTbot/telegramargs.h index 9654ee7..a597cf8 100644 --- a/src/qTbot/src/public/qTbot/telegramargs.h +++ b/src/qTbot/src/public/qTbot/telegramargs.h @@ -10,6 +10,7 @@ #include #include "global.h" +#include "irequest.h" namespace qTbot { @@ -24,7 +25,8 @@ struct QTBOT_EXPORT TelegramArgs const QString& parseMode = "html", bool disableWebPagePreview = false, const QString& callBackQueryId = "", - const std::function& msgIdCB = {} + const std::function& msgIdCB = {}, + iRequest::RequestPriority priority = iRequest::RequestPriority::NormalPriority ); /** @@ -74,6 +76,8 @@ struct QTBOT_EXPORT TelegramArgs * @brief msgIdCB This is id message call bak function. Will be inwoked when request finished successful. */ std::function msgIdCB = {}; + + iRequest::RequestPriority requestPriority = iRequest::RequestPriority::NormalPriority; }; } diff --git a/src/qTbot/src/public/qTbot/telegramrestbot.cpp b/src/qTbot/src/public/qTbot/telegramrestbot.cpp index f550484..9b08ae8 100644 --- a/src/qTbot/src/public/qTbot/telegramrestbot.cpp +++ b/src/qTbot/src/public/qTbot/telegramrestbot.cpp @@ -5,6 +5,7 @@ //# of this license document, but changing it is not allowed. //# +#include "httpexception.h" #include "telegramrestbot.h" #include "qTbot/messages/telegramupdate.h" #include "qTbot/messages/telegramupdateanswer.h" @@ -13,7 +14,6 @@ #include #include #include -#include namespace qTbot { @@ -51,13 +51,12 @@ void TelegramRestBot::startUpdates() { if (delta >= _updateDelay) { auto&& replay = sendRequest(QSharedPointer::create(_lanstUpdateid + 1)); - connect(replay.get(), &QNetworkReply::finished, - this, std::bind(&TelegramRestBot::handleReceiveUpdates, this, replay.toWeakRef()), - Qt::DirectConnection); + replay.then([this](const QByteArray &result){ + handleReceiveUpdates(result); + }).onFailed([this](const HttpException &e){ + handleReceiveUpdatesErr(e.code()); - connect(replay.get(), &QNetworkReply::errorOccurred, - this, &TelegramRestBot::handleReceiveUpdatesErr, - Qt::DirectConnection); + } ); return; } else { @@ -82,22 +81,19 @@ void TelegramRestBot::setProcessed(const QSet &newProcessed) IBot::setProcessed(newProcessed); } -void TelegramRestBot::handleReceiveUpdates(const QWeakPointer &replay) { +void TelegramRestBot::handleReceiveUpdates(const QByteArray &replay) { + auto&& telegramMsg = makeMesasge(replay); + if (telegramMsg->isValid()) { - if (auto&& sharedReplay = replay.lock()) { - auto&& telegramMsg = makeMesasge(sharedReplay->readAll()); - if (telegramMsg->isValid()) { + _lanstUpdateTime = QDateTime::currentMSecsSinceEpoch(); - _lanstUpdateTime = QDateTime::currentMSecsSinceEpoch(); - - auto && resultArray = telegramMsg->result().toArray(); - for (const auto& ref: resultArray) { - auto&& update = IBot::makeMesasge(ref.toObject()); - incomeNewUpdate(update); - if (_lanstUpdateid < update->updateId()) { - _lanstUpdateid = update->updateId(); - }; - } + auto && resultArray = telegramMsg->result().toArray(); + for (const auto& ref: resultArray) { + auto&& update = IBot::makeMesasge(ref.toObject()); + incomeNewUpdate(update); + if (_lanstUpdateid < update->updateId()) { + _lanstUpdateid = update->updateId(); + }; } } diff --git a/src/qTbot/src/public/qTbot/telegramrestbot.h b/src/qTbot/src/public/qTbot/telegramrestbot.h index 4e42f66..ed7d75d 100644 --- a/src/qTbot/src/public/qTbot/telegramrestbot.h +++ b/src/qTbot/src/public/qTbot/telegramrestbot.h @@ -44,7 +44,7 @@ class QTBOT_EXPORT TelegramRestBot: public ITelegramBot void setProcessed(const QSet &newProcessed) override; private slots: - void handleReceiveUpdates(const QWeakPointer& replay); + void handleReceiveUpdates(const QByteArray &replay); void handleReceiveUpdatesErr(QNetworkReply::NetworkError err); private: diff --git a/src/qTbot/src/public/qTbot/virtualfile.cpp b/src/qTbot/src/public/qTbot/virtualfile.cpp deleted file mode 100644 index 35c8122..0000000 --- a/src/qTbot/src/public/qTbot/virtualfile.cpp +++ /dev/null @@ -1,46 +0,0 @@ -//# -//# Copyright (C) 2023-2024 QuasarApp. -//# Distributed under the GPLv3 software license, see the accompanying -//# Everyone is permitted to copy and distribute verbatim copies -//# of this license document, but changing it is not allowed. -//# - -#include "virtualfile.h" - -namespace qTbot { - - -VirtualFile::VirtualFile(const QSharedPointer &replay): iFile(replay) { -} - -const QByteArray& VirtualFile::array() const { - return _array; -} - -iFile::Type VirtualFile::type() const { - return Type::Ram; -} - -void VirtualFile::handleReadReady() { - - _array.append(replay()->readAll()); - -} - -void VirtualFile::handleFinished() { - handleReadReady(); - iFile::handleFinished(); -} - -void VirtualFile::handleError(QNetworkReply::NetworkError error) { - iFile::handleError(error); - _array.clear(); - -} - -void VirtualFile::setArray(const QByteArray &newArray) { - _array = newArray; -} - - -} diff --git a/src/qTbot/src/public/qTbot/virtualfile.h b/src/qTbot/src/public/qTbot/virtualfile.h deleted file mode 100644 index 28c374a..0000000 --- a/src/qTbot/src/public/qTbot/virtualfile.h +++ /dev/null @@ -1,39 +0,0 @@ -//# -//# Copyright (C) 2023-2024 QuasarApp. -//# Distributed under the GPLv3 software license, see the accompanying -//# Everyone is permitted to copy and distribute verbatim copies -//# of this license document, but changing it is not allowed. -//# - -#ifndef VIRTUALFILE_H -#define VIRTUALFILE_H - -#include "ifile.h" - -namespace qTbot { - -/** - * @brief The VirtualFile class write and read data from the Ram. - */ -class QTBOT_EXPORT VirtualFile : public iFile -{ -public: - VirtualFile(const QSharedPointer& replay = nullptr); - - // iFile interface - const QByteArray &array() const; - Type type() const override; - - void setArray(const QByteArray &newArray); - -protected slots: - void handleReadReady() override; - void handleFinished() override; - void handleError(QNetworkReply::NetworkError error) override; - -private: - QByteArray _array; -}; - -} -#endif // VIRTUALFILE_H