Files
chatterino2/src/common/network/NetworkPrivate.cpp
Mm2PL 60ecf1a8d7 refactor: DebugCount (#6753)
Reviewed-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
Reviewed-by: Nerixyz <nerixdev@outlook.de>
2026-01-28 10:37:06 +00:00

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