diff --git a/src/common/NetworkData.cpp b/src/common/NetworkData.cpp index 95229ebee..06e9433c0 100644 --- a/src/common/NetworkData.cpp +++ b/src/common/NetworkData.cpp @@ -1,10 +1,19 @@ #include "common/NetworkData.hpp" +#include "common/NetworkManager.hpp" +#include "common/NetworkRequester.hpp" +#include "common/NetworkResult.hpp" +#include "common/NetworkWorker.hpp" +#include "common/Outcome.hpp" +#include "debug/AssertInGuiThread.hpp" +#include "debug/Log.hpp" #include "singletons/Paths.hpp" #include "util/DebugCount.hpp" +#include "util/PostToThread.hpp" #include #include +#include namespace chatterino { @@ -55,4 +64,169 @@ void NetworkData::writeToCache(const QByteArray &bytes) } } +void loadUncached(const std::shared_ptr &data) +{ + DebugCount::increase("http request started"); + + NetworkRequester requester; + NetworkWorker *worker = new NetworkWorker; + + worker->moveToThread(&NetworkManager::workerThread); + + if (data->hasTimeout_) + { + data->timer_.setSingleShot(true); + data->timer_.start(); + } + + auto onUrlRequested = [data, worker]() mutable { + auto reply = [&]() -> QNetworkReply * { + switch (data->requestType_) + { + case NetworkRequestType::Get: + return NetworkManager::accessManager.get(data->request_); + + case NetworkRequestType::Put: + return NetworkManager::accessManager.put(data->request_, + data->payload_); + + case NetworkRequestType::Delete: + return NetworkManager::accessManager.deleteResource( + data->request_); + + case NetworkRequestType::Post: + return NetworkManager::accessManager.post(data->request_, + data->payload_); + } + }(); + + if (reply == nullptr) + { + log("Unhandled request type"); + return; + } + + if (data->timer_.isActive()) + { + QObject::connect(&data->timer_, &QTimer::timeout, worker, + [reply, data]() { + log("Aborted!"); + reply->abort(); + if (data->onError_) + { + data->onError_(-2); + } + }); + } + + if (data->onReplyCreated_) + { + data->onReplyCreated_(reply); + } + + auto handleReply = [data, reply]() mutable { + // TODO(pajlada): A reply was received, kill the timeout timer + if (reply->error() != QNetworkReply::NetworkError::NoError) + { + if (data->onError_) + { + data->onError_(reply->error()); + } + return; + } + + QByteArray bytes = reply->readAll(); + data->writeToCache(bytes); + + NetworkResult result(bytes); + + DebugCount::increase("http request success"); + // log("starting {}", data->request_.url().toString()); + if (data->onSuccess_) + { + if (data->executeConcurrently) + QtConcurrent::run( + [onSuccess = std::move(data->onSuccess_), + result = std::move(result)] { onSuccess(result); }); + else + data->onSuccess_(result); + } + // log("finished {}", data->request_.url().toString()); + + reply->deleteLater(); + }; + + QObject::connect( + reply, &QNetworkReply::finished, worker, + [data, handleReply, worker]() mutable { + if (data->executeConcurrently || isGuiThread()) + { + handleReply(); + + delete worker; + } + else + { + postToThread( + [worker, cb = std::move(handleReply)]() mutable { + cb(); + delete worker; + }); + } + }); + }; + + QObject::connect(&requester, &NetworkRequester::requestUrl, worker, + onUrlRequested); + + emit requester.requestUrl(); +} + +// Tries to load the cached file and loads fro +void loadCached(const std::shared_ptr &data) +{ + QFile cachedFile(getPaths()->cacheDirectory() + "/" + data->getHash()); + + if (!cachedFile.exists() || !cachedFile.open(QIODevice::ReadOnly)) + { + // File didn't exist OR File could not be opened + loadUncached(data); + return; + } + else + { + // XXX: check if bytes is empty? + QByteArray bytes = cachedFile.readAll(); + NetworkResult result(bytes); + + if (data->onSuccess_) + { + if (data->executeConcurrently || isGuiThread()) + { + // XXX: If outcome is Failure, we should invalidate the cache file + // somehow/somewhere + /*auto outcome =*/ + data->onSuccess_(result); + } + else + { + postToThread([data, result]() { data->onSuccess_(result); }); + } + } + } // namespace chatterino +} + +void load(const std::shared_ptr &data) +{ + if (data->useQuickLoadCache_) + { + QtConcurrent::run(loadCached, data); + loadCached(data); + } + else + { + loadUncached(data); + } +} + } // namespace chatterino diff --git a/src/common/NetworkData.hpp b/src/common/NetworkData.hpp index 2d149fc09..f3f571fba 100644 --- a/src/common/NetworkData.hpp +++ b/src/common/NetworkData.hpp @@ -1,9 +1,9 @@ #pragma once #include "common/NetworkCommon.hpp" +#include "common/NetworkTimer.hpp" #include - #include class QNetworkReply; @@ -29,6 +29,13 @@ struct NetworkData { QByteArray payload_; + // Timer that tracks the timeout + // By default, there's no explicit timeout for the request + // to enable the timer, the "setTimeout" function needs to be called before + // execute is called + bool hasTimeout_{}; + QTimer timer_; + QString getHash(); void writeToCache(const QByteArray &bytes); @@ -37,4 +44,6 @@ private: QString hash_; }; +void load(const std::shared_ptr &data); + } // namespace chatterino diff --git a/src/common/NetworkRequest.cpp b/src/common/NetworkRequest.cpp index 30e251aae..ece6413d5 100644 --- a/src/common/NetworkRequest.cpp +++ b/src/common/NetworkRequest.cpp @@ -1,7 +1,7 @@ #include "common/NetworkRequest.hpp" #include "common/NetworkData.hpp" -#include "common/NetworkManager.hpp" +//#include "common/NetworkManager.hpp" #include "common/Outcome.hpp" #include "common/Version.hpp" #include "debug/AssertInGuiThread.hpp" @@ -11,6 +11,7 @@ #include "util/DebugCount.hpp" #include "util/PostToThread.hpp" +#include #include #include @@ -21,7 +22,6 @@ namespace chatterino { NetworkRequest::NetworkRequest(const std::string &url, NetworkRequestType requestType) : data(new NetworkData) - , timer(new NetworkTimer) { this->data->request_.setUrl(QUrl(QString::fromStdString(url))); this->data->requestType_ = requestType; @@ -31,7 +31,6 @@ NetworkRequest::NetworkRequest(const std::string &url, NetworkRequest::NetworkRequest(QUrl url, NetworkRequestType requestType) : data(new NetworkData) - , timer(new NetworkTimer) { this->data->request_.setUrl(url); this->data->requestType_ = requestType; @@ -41,7 +40,7 @@ NetworkRequest::NetworkRequest(QUrl url, NetworkRequestType requestType) NetworkRequest::~NetworkRequest() { - // assert(this->executed_); + //assert(this->executed_); } // old @@ -89,7 +88,8 @@ void NetworkRequest::setRawHeader(const char *headerName, void NetworkRequest::setTimeout(int ms) & { - this->timer->timeoutMS_ = ms; + this->data->hasTimeout_ = true; + this->data->timer_.setInterval(ms); } void NetworkRequest::setExecuteConcurrently(bool value) & @@ -97,7 +97,6 @@ void NetworkRequest::setExecuteConcurrently(bool value) & this->data->executeConcurrently = value; } -// TODO: rename to "authorizeTwitchV5"? void NetworkRequest::makeAuthorizedV5(const QString &clientID, const QString &oauthToken) & { @@ -173,7 +172,8 @@ NetworkRequest NetworkRequest::header(const char *headerName, NetworkRequest NetworkRequest::timeout(int ms) && { - this->timer->timeoutMS_ = ms; + this->data->hasTimeout_ = true; + this->data->timer_.setInterval(ms); return std::move(*this); } @@ -203,7 +203,7 @@ NetworkRequest NetworkRequest::payload(const QByteArray &payload) && return std::move(*this); } -NetworkRequest NetworkRequest::quickLoad() && +NetworkRequest NetworkRequest::cache() && { this->data->useQuickLoadCache_ = true; return std::move(*this); @@ -213,57 +213,15 @@ void NetworkRequest::execute() { this->executed_ = true; - switch (this->data->requestType_) + // Only allow caching for GET request + if (this->data->useQuickLoadCache_ && + this->data->requestType_ != NetworkRequestType::Get) { - case NetworkRequestType::Get: - { - // Get requests try to load from cache, then perform the request - if (this->data->useQuickLoadCache_) - { - if (this->tryLoadCachedFile()) - { - // Successfully loaded from cache - return; - } - } - - this->doRequest(); - } - break; - - case NetworkRequestType::Put: - { - // Put requests cannot be cached, therefore the request is called - // immediately - this->doRequest(); - } - break; - - case NetworkRequestType::Delete: - { - // Delete requests cannot be cached, therefore the request is called - // immediately - this->doRequest(); - } - break; - - case NetworkRequestType::Post: - { - this->doRequest(); - } - break; - - default: - { - log("[Execute] Unhandled request type"); - } - break; + qDebug() << "Can only cache GET requests!"; + this->data->useQuickLoadCache_ = false; } -} -QString NetworkRequest::urlString() const -{ - return this->data->request_.url().toString(); + load(std::move(this->data)); } void NetworkRequest::initializeDefaultValues() @@ -275,178 +233,10 @@ void NetworkRequest::initializeDefaultValues() this->data->request_.setRawHeader("User-Agent", userAgent); } -Outcome NetworkRequest::tryLoadCachedFile() -{ - QFile cachedFile(getPaths()->cacheDirectory() + "/" + - this->data->getHash()); - - if (!cachedFile.exists()) - { - // File didn't exist - return Failure; - } - - if (!cachedFile.open(QIODevice::ReadOnly)) - { - // File could not be opened - return Failure; - } - - QByteArray bytes = cachedFile.readAll(); - NetworkResult result(bytes); - - auto outcome = this->data->onSuccess_(result); - - cachedFile.close(); - - // XXX: If success is false, we should invalidate the cache file - // somehow/somewhere - - return outcome; -} - -void NetworkRequest::doRequest() -{ - DebugCount::increase("http request started"); - - NetworkRequester requester; - NetworkWorker *worker = new NetworkWorker; - - worker->moveToThread(&NetworkManager::workerThread); - - this->timer->start(); - - auto onUrlRequested = [data = this->data, timer = this->timer, - worker]() mutable { - auto reply = [&]() -> QNetworkReply * { - switch (data->requestType_) - { - case NetworkRequestType::Get: - return NetworkManager::accessManager.get(data->request_); - - case NetworkRequestType::Put: - return NetworkManager::accessManager.put(data->request_, - data->payload_); - - case NetworkRequestType::Delete: - return NetworkManager::accessManager.deleteResource( - data->request_); - - case NetworkRequestType::Post: - return NetworkManager::accessManager.post(data->request_, - data->payload_); - - default: - return nullptr; - } - }(); - - if (reply == nullptr) - { - log("Unhandled request type"); - return; - } - - if (timer->isStarted()) - { - timer->onTimeout(worker, [reply, data]() { - log("Aborted!"); - reply->abort(); - if (data->onError_) - { - data->onError_(-2); - } - }); - } - - if (data->onReplyCreated_) - { - data->onReplyCreated_(reply); - } - - auto handleReply = [data, timer, reply]() mutable { - // TODO(pajlada): A reply was received, kill the timeout timer - if (reply->error() != QNetworkReply::NetworkError::NoError) - { - if (data->onError_) - { - data->onError_(reply->error()); - } - return; - } - - QByteArray bytes = reply->readAll(); - data->writeToCache(bytes); - - NetworkResult result(bytes); - - DebugCount::increase("http request success"); - // log("starting {}", data->request_.url().toString()); - if (data->onSuccess_) - { - if (data->executeConcurrently) - QtConcurrent::run( - [onSuccess = std::move(data->onSuccess_), - result = std::move(result)] { onSuccess(result); }); - else - data->onSuccess_(result); - } - // log("finished {}", data->request_.url().toString()); - - reply->deleteLater(); - }; - - // FOURTF: Not sure what this does but it doesn't work. - // if (data->caller_ != nullptr) - // { - // QObject::connect(worker, &NetworkWorker::doneUrl, data->caller_, - // handleReply); - // QObject::connect(reply, &QNetworkReply::finished, worker, - // [worker]() mutable { - // emit worker->doneUrl(); - - // delete worker; - // }); - // } - // else - // { - - // auto - - QObject::connect( - reply, &QNetworkReply::finished, worker, - [data, handleReply, worker]() mutable { - if (data->executeConcurrently || isGuiThread()) - { - handleReply(); - - delete worker; - } - else - { - postToThread( - [worker, cb = std::move(handleReply)]() mutable { - cb(); - delete worker; - }); - } - }); - }; - - QObject::connect(&requester, &NetworkRequester::requestUrl, worker, - onUrlRequested); - - emit requester.requestUrl(); -} - // Helper creator functions NetworkRequest NetworkRequest::twitchRequest(QUrl url) { - NetworkRequest request(url); - - request.makeAuthorizedV5(getDefaultClientID()); - - return request; + return NetworkRequest(url).authorizeTwitchV5(getDefaultClientID()); } } // namespace chatterino diff --git a/src/common/NetworkRequest.hpp b/src/common/NetworkRequest.hpp index a18381528..f9ec282fe 100644 --- a/src/common/NetworkRequest.hpp +++ b/src/common/NetworkRequest.hpp @@ -18,12 +18,6 @@ class NetworkRequest final // part of the request std::shared_ptr data; - // Timer that tracks the timeout - // By default, there's no explicit timeout for the request - // to enable the timer, the "setTimeout" function needs to be called before - // execute is called - std::shared_ptr timer; - // The NetworkRequest destructor will assert if executed_ hasn't been set to // true before dying bool executed_ = false; @@ -74,7 +68,7 @@ public: NetworkRequest onSuccess(NetworkSuccessCallback cb) &&; NetworkRequest payload(const QByteArray &payload) &&; - NetworkRequest quickLoad() &&; + NetworkRequest cache() &&; NetworkRequest caller(const QObject *caller) &&; NetworkRequest header(const char *headerName, const char *value) &&; NetworkRequest header(const char *headerName, const QByteArray &value) &&; @@ -86,19 +80,10 @@ public: void execute(); - [[nodiscard]] QString urlString() const; + static NetworkRequest twitchRequest(QUrl url); private: void initializeDefaultValues(); - - // "invalid" data "invalid" is specified by the onSuccess callback - Outcome tryLoadCachedFile(); - - void doRequest(); - -public: - // Helper creator functions - static NetworkRequest twitchRequest(QUrl url); }; } // namespace chatterino diff --git a/src/messages/Image.cpp b/src/messages/Image.cpp index e249856b7..9a1740152 100644 --- a/src/messages/Image.cpp +++ b/src/messages/Image.cpp @@ -337,7 +337,7 @@ void Image::load() NetworkRequest(this->url().string) .concurrent() .caller(&this->object_) - .quickLoad() + .cache() .onSuccess([that = this, weak = weakOf(this)](auto result) -> Outcome { auto shared = weak.lock(); if (!shared)