60ecf1a8d7
Reviewed-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com> Reviewed-by: Nerixyz <nerixdev@outlook.de>
250 lines
6.4 KiB
C++
250 lines
6.4 KiB
C++
// SPDX-FileCopyrightText: 2018 Contributors to Chatterino <https://chatterino.com>
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "common/network/NetworkPrivate.hpp"
|
|
|
|
#include "Application.hpp"
|
|
#include "common/network/NetworkManager.hpp"
|
|
#include "common/network/NetworkResult.hpp"
|
|
#include "common/network/NetworkTask.hpp"
|
|
#include "common/QLogging.hpp"
|
|
#include "singletons/Paths.hpp"
|
|
#include "util/AbandonObject.hpp"
|
|
#include "util/DebugCount.hpp"
|
|
#include "util/PostToThread.hpp"
|
|
#include "util/QMagicEnum.hpp"
|
|
|
|
#include <magic_enum/magic_enum.hpp>
|
|
#include <QCryptographicHash>
|
|
#include <QElapsedTimer>
|
|
#include <QFile>
|
|
#include <QNetworkReply>
|
|
#include <QtConcurrent>
|
|
|
|
#ifdef NDEBUG
|
|
constexpr qsizetype SLOW_HTTP_THRESHOLD = 30;
|
|
#else
|
|
constexpr qsizetype SLOW_HTTP_THRESHOLD = 90;
|
|
#endif
|
|
|
|
using namespace chatterino::network::detail;
|
|
|
|
namespace {
|
|
|
|
using namespace chatterino;
|
|
|
|
void runCallback(bool concurrent, auto &&fn)
|
|
{
|
|
if (concurrent)
|
|
{
|
|
std::ignore = QtConcurrent::run(std::forward<decltype(fn)>(fn));
|
|
}
|
|
else
|
|
{
|
|
runInGuiThread(std::forward<decltype(fn)>(fn));
|
|
}
|
|
}
|
|
|
|
void loadUncached(std::shared_ptr<NetworkData> &&data)
|
|
{
|
|
DebugCount::increase(DebugObject::HTTPRequestStarted);
|
|
|
|
NetworkRequester requester;
|
|
auto *worker = new NetworkTask(std::move(data));
|
|
|
|
worker->moveToThread(NetworkManager::workerThread);
|
|
|
|
QObject::connect(&requester, &NetworkRequester::requestUrl, worker,
|
|
&NetworkTask::run);
|
|
|
|
requester.requestUrl();
|
|
}
|
|
|
|
void loadCached(std::shared_ptr<NetworkData> &&data)
|
|
{
|
|
if (isAppAboutToQuit())
|
|
{
|
|
qCDebug(chatterinoHTTP)
|
|
<< "Skipping cached network load " << data->request.url()
|
|
<< "because app is about to quit";
|
|
return;
|
|
}
|
|
|
|
auto *app = tryGetApp();
|
|
if (!app)
|
|
{
|
|
qCDebug(chatterinoHTTP)
|
|
<< "Skipping cached network load " << data->request.url()
|
|
<< "because app is about to quit";
|
|
return;
|
|
}
|
|
|
|
QFile cachedFile(app->getPaths().cacheDirectory() + "/" + data->getHash());
|
|
|
|
if (!cachedFile.exists() || !cachedFile.open(QIODevice::ReadOnly))
|
|
{
|
|
loadUncached(std::move(data));
|
|
return;
|
|
}
|
|
|
|
// XXX: check if bytes is empty?
|
|
QByteArray bytes = cachedFile.readAll();
|
|
|
|
qCDebug(chatterinoHTTP).noquote() << data->typeString() << "[CACHED] 200"
|
|
<< data->request.url().toString();
|
|
|
|
data->emitSuccess(
|
|
{NetworkResult::NetworkError::NoError, QVariant(200), bytes});
|
|
data->emitFinally();
|
|
}
|
|
|
|
} // namespace
|
|
|
|
namespace chatterino {
|
|
|
|
NetworkData::NetworkData()
|
|
{
|
|
DebugCount::increase(DebugObject::NetworkData);
|
|
}
|
|
|
|
NetworkData::~NetworkData()
|
|
{
|
|
DebugCount::decrease(DebugObject::NetworkData);
|
|
}
|
|
|
|
QString NetworkData::getHash()
|
|
{
|
|
if (this->hash_.isEmpty())
|
|
{
|
|
QByteArray bytes;
|
|
|
|
bytes.append(this->request.url().toString().toUtf8());
|
|
|
|
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 NetworkData::emitSuccess(NetworkResult &&result)
|
|
{
|
|
if (!this->onSuccess)
|
|
{
|
|
return;
|
|
}
|
|
|
|
runCallback(this->executeConcurrently,
|
|
[cb = std::move(this->onSuccess), result = std::move(result),
|
|
url = this->request.url(), hasCaller = this->hasCaller,
|
|
caller = this->caller]() {
|
|
if (hasCaller && caller.isNull())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (isAppAboutToQuit())
|
|
{
|
|
qCDebug(chatterinoHTTP)
|
|
<< "Success callback for" << url.toString()
|
|
<< "skipped because we're about to quit";
|
|
return;
|
|
}
|
|
|
|
QElapsedTimer timer;
|
|
timer.start();
|
|
cb(result);
|
|
if (timer.elapsed() > SLOW_HTTP_THRESHOLD)
|
|
{
|
|
qCWarning(chatterinoHTTP)
|
|
<< "Slow HTTP success handler for" << url.toString()
|
|
<< timer.elapsed()
|
|
<< "ms (threshold:" << SLOW_HTTP_THRESHOLD << "ms)";
|
|
}
|
|
});
|
|
}
|
|
|
|
void NetworkData::emitError(NetworkResult &&result)
|
|
{
|
|
if (!this->onError)
|
|
{
|
|
return;
|
|
}
|
|
|
|
runCallback(this->executeConcurrently,
|
|
[cb = std::move(this->onError), result = std::move(result),
|
|
url = this->request.url(), hasCaller = this->hasCaller,
|
|
caller = this->caller]() {
|
|
if (hasCaller && caller.isNull())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (isAppAboutToQuit())
|
|
{
|
|
qCDebug(chatterinoHTTP)
|
|
<< "Error callback for" << url.toString()
|
|
<< "skipped because we're about to quit";
|
|
return;
|
|
}
|
|
|
|
cb(result);
|
|
});
|
|
}
|
|
|
|
void NetworkData::emitFinally()
|
|
{
|
|
if (!this->finally)
|
|
{
|
|
return;
|
|
}
|
|
|
|
runCallback(this->executeConcurrently,
|
|
[cb = std::move(this->finally), hasCaller = this->hasCaller,
|
|
url = this->request.url(), caller = this->caller]() {
|
|
if (hasCaller && caller.isNull())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (isAppAboutToQuit())
|
|
{
|
|
qCDebug(chatterinoHTTP)
|
|
<< "Finally callback for" << url.toString()
|
|
<< "skipped because we're about to quit";
|
|
return;
|
|
}
|
|
|
|
cb();
|
|
});
|
|
}
|
|
|
|
QString NetworkData::typeString() const
|
|
{
|
|
return qmagicenum::enumNameString(this->requestType);
|
|
}
|
|
|
|
void load(std::shared_ptr<NetworkData> &&data)
|
|
{
|
|
if (data->cache)
|
|
{
|
|
std::ignore = QtConcurrent::run([data = std::move(data)]() mutable {
|
|
loadCached(std::move(data));
|
|
});
|
|
}
|
|
else
|
|
{
|
|
loadUncached(std::move(data));
|
|
}
|
|
}
|
|
|
|
} // namespace chatterino
|