#pragma once #include "Application.hpp" #include "common/NetworkManager.hpp" #include "common/NetworkRequester.hpp" #include "common/NetworkWorker.hpp" #include "singletons/Paths.hpp" #include #include #include #include namespace chatterino { static QJsonObject parseJSONFromData(const QByteArray &data) { QJsonDocument jsonDoc(QJsonDocument::fromJson(data)); if (jsonDoc.isNull()) { return QJsonObject(); } return jsonDoc.object(); } static rapidjson::Document parseJSONFromData2(const QByteArray &data) { rapidjson::Document ret(rapidjson::kNullType); rapidjson::ParseResult result = ret.Parse(data.data(), data.length()); if (result.Code() != rapidjson::kParseErrorNone) { Log("JSON parse error: {} ({})", rapidjson::GetParseError_En(result.Code()), result.Offset()); return ret; } return ret; } static rapidjson::Document parseJSONFromReply2(QNetworkReply *reply) { rapidjson::Document ret(rapidjson::kNullType); if (reply->error() != QNetworkReply::NetworkError::NoError) { return ret; } QByteArray data = reply->readAll(); rapidjson::ParseResult result = ret.Parse(data.data(), data.length()); if (result.Code() != rapidjson::kParseErrorNone) { Log("JSON parse error: {} ({})", rapidjson::GetParseError_En(result.Code()), result.Offset()); return ret; } return ret; } class NetworkRequest { public: enum RequestType { GetRequest, PostRequest, PutRequest, DeleteRequest, }; private: struct Data { QNetworkRequest request; const QObject *caller = nullptr; std::function onReplyCreated; int timeoutMS = -1; bool useQuickLoadCache = false; std::function onError; std::function onSuccess; NetworkRequest::RequestType requestType; QByteArray payload; QString getHash() { if (this->hash.isEmpty()) { QByteArray bytes; bytes.append(this->request.url().toString()); for (const auto &header : this->request.rawHeaderList()) { bytes.append(header); } QByteArray hashBytes(QCryptographicHash::hash(bytes, QCryptographicHash::Sha256)); this->hash = hashBytes.toHex(); } return this->hash; } void writeToCache(const QByteArray &bytes); private: QString hash; } data; public: NetworkRequest() = delete; explicit NetworkRequest(const char *url); explicit NetworkRequest(const std::string &url); explicit NetworkRequest(const QString &url); void setRequestType(RequestType newRequestType); template void onError(Func cb) { this->data.onError = cb; } template void onSuccess(Func cb) { this->data.onSuccess = cb; } void setPayload(const QByteArray &payload) { this->data.payload = payload; } void setUseQuickLoadCache(bool value); void setCaller(const QObject *_caller); void setOnReplyCreated(std::function f); void setRawHeader(const char *headerName, const char *value); void setRawHeader(const char *headerName, const QByteArray &value); void setRawHeader(const char *headerName, const QString &value); void setTimeout(int ms); void makeAuthorizedV5(const QString &clientID, const QString &oauthToken); template void get(FinishedCallback onFinished) { if (this->data.useQuickLoadCache) { auto app = getApp(); QFile cachedFile(app->paths->cacheDirectory + "/" + this->data.getHash()); if (cachedFile.exists()) { if (cachedFile.open(QIODevice::ReadOnly)) { QByteArray bytes = cachedFile.readAll(); // qDebug() << "Loaded cached resource" << this->data.request.url(); bool success = onFinished(bytes); cachedFile.close(); if (!success) { // The images were not successfully loaded from the file // XXX: Invalidate the cache file so we don't attempt to load it again next // time } } } } QTimer *timer = nullptr; if (this->data.timeoutMS > 0) { timer = new QTimer; } NetworkRequester requester; NetworkWorker *worker = new NetworkWorker; worker->moveToThread(&NetworkManager::workerThread); if (this->data.caller != nullptr) { QObject::connect(worker, &NetworkWorker::doneUrl, this->data.caller, [ onFinished, data = this->data ](auto reply) mutable { if (reply->error() != QNetworkReply::NetworkError::NoError) { if (data.onError) { data.onError(reply->error()); } return; } QByteArray readBytes = reply->readAll(); QByteArray bytes; bytes.setRawData(readBytes.data(), readBytes.size()); data.writeToCache(bytes); onFinished(bytes); reply->deleteLater(); }); } if (timer != nullptr) { timer->start(this->data.timeoutMS); } QObject::connect( &requester, &NetworkRequester::requestUrl, worker, [ timer, data = std::move(this->data), worker, onFinished{std::move(onFinished)} ]() { QNetworkReply *reply = NetworkManager::NaM.get(data.request); if (timer != nullptr) { QObject::connect(timer, &QTimer::timeout, worker, [reply, timer]() { Log("Aborted!"); reply->abort(); timer->deleteLater(); }); } if (data.onReplyCreated) { data.onReplyCreated(reply); } QObject::connect(reply, &QNetworkReply::finished, worker, [ data = std::move(data), worker, reply, onFinished = std::move(onFinished) ]() mutable { if (data.caller == nullptr) { QByteArray bytes = reply->readAll(); data.writeToCache(bytes); onFinished(bytes); reply->deleteLater(); } else { emit worker->doneUrl(reply); } delete worker; }); }); emit requester.requestUrl(); } template void getJSON(FinishedCallback onFinished) { this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes)->bool { auto object = parseJSONFromData(bytes); onFinished(object); // XXX: Maybe return onFinished? For now I don't want to force onFinished to have a // return value return true; }); } template void getJSON2(FinishedCallback onFinished) { this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes)->bool { auto object = parseJSONFromData2(bytes); onFinished(object); // XXX: Maybe return onFinished? For now I don't want to force onFinished to have a // return value return true; }); } void execute(); private: void useCache(); void doRequest(); void executeGet(); void executePut(); void executeDelete(); }; } // namespace chatterino