From f51372102ef76d419da62f181720803328af189f Mon Sep 17 00:00:00 2001 From: hemirt Date: Fri, 27 Oct 2017 20:09:02 +0200 Subject: [PATCH] Networkmanager (#134) * rename ImageLoader* to Network* * static NetworkManager * NetworkManager queue arbitrary requests * modify urlfetch to use NetworkManager * urlfetchjson in terms of NetworkManager * fetchurljson fetchurltimeout fetchurljsontimeout special fetch url functions with various connects and functions to be called * operate on fetched data in the correct thread * operate on fetched resources in correct thread * networkmanager urlfetch functions * expose urlfetch functions of networkmanager through util and util::twitch * add caller to util functions * cleanup * formatting * urlPut function for NetworkManager and util::twitch * cleanup worker (no more leak) * use urlfetch for LazyLoadedImage::loadImage * Rename NetworkManager methods (#1) * Rename NetworkManager methods Remove unused NetworkManager methods Remove unused NetworkManager includes Reorder includes in lazyloadedimage.cpp and urlfetch.hpp * try to simplify code, might break everything * fixed some more stuff? --- chatterino.pro | 6 +- src/emotemanager.cpp | 116 +++++----- src/ircmanager.cpp | 1 - src/main.cpp | 8 + src/messages/imageloadermanager.cpp | 92 -------- src/messages/imageloadermanager.hpp | 46 ---- src/messages/lazyloadedimage.cpp | 43 +++- src/messages/lazyloadedimage.hpp | 3 - src/resources.cpp | 10 +- src/util/networkmanager.cpp | 24 +++ src/util/networkmanager.hpp | 233 +++++++++++++++++++++ src/util/urlfetch.hpp | 314 +++++++++++----------------- src/widgets/accountpopup.cpp | 15 +- src/widgets/channelview.cpp | 12 +- src/widgets/chatwidget.cpp | 21 +- src/widgets/chatwidgetheader.cpp | 2 +- src/widgets/logindialog.cpp | 2 +- 17 files changed, 521 insertions(+), 427 deletions(-) delete mode 100644 src/messages/imageloadermanager.cpp delete mode 100644 src/messages/imageloadermanager.hpp create mode 100644 src/util/networkmanager.cpp create mode 100644 src/util/networkmanager.hpp diff --git a/chatterino.pro b/chatterino.pro index 5aff7da16..9b0b5bbeb 100644 --- a/chatterino.pro +++ b/chatterino.pro @@ -97,7 +97,7 @@ SOURCES += \ src/widgets/rippleeffectlabel.cpp \ src/widgets/rippleeffectbutton.cpp \ src/messages/messagecolor.cpp \ - src/messages/imageloadermanager.cpp + src/util/networkmanager.cpp HEADERS += \ src/asyncexec.hpp \ @@ -164,8 +164,8 @@ HEADERS += \ src/messages/messagecolor.hpp \ src/util/nativeeventhelper.hpp \ src/debug/log.hpp \ - src/messages/imageloadermanager.hpp \ - src/util/benchmark.hpp + src/util/benchmark.hpp \ + src/util/networkmanager.hpp PRECOMPILED_HEADER = diff --git a/src/emotemanager.cpp b/src/emotemanager.cpp index 5f4182107..907832425 100644 --- a/src/emotemanager.cpp +++ b/src/emotemanager.cpp @@ -48,44 +48,48 @@ void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName, std::weak printf("[EmoteManager] Reload BTTV Channel Emotes for channel %s\n", qPrintable(channelName)); QString url("https://api.betterttv.net/2/channels/" + channelName); - util::urlFetchJSON(url, [this, channelName, _map](QJsonObject &rootNode) { - auto map = _map.lock(); + util::urlFetchJSONTimeout( + url, QThread::currentThread(), + [this, channelName, _map](QJsonObject &rootNode) { + auto map = _map.lock(); - if (_map.expired()) { - return; - } + if (_map.expired()) { + return; + } - map->clear(); + map->clear(); - auto emotesNode = rootNode.value("emotes").toArray(); + auto emotesNode = rootNode.value("emotes").toArray(); - QString linkTemplate = "https:" + rootNode.value("urlTemplate").toString(); + QString linkTemplate = "https:" + rootNode.value("urlTemplate").toString(); - std::vector codes; - for (const QJsonValue &emoteNode : emotesNode) { - QJsonObject emoteObject = emoteNode.toObject(); + std::vector codes; + for (const QJsonValue &emoteNode : emotesNode) { + QJsonObject emoteObject = emoteNode.toObject(); - QString id = emoteObject.value("id").toString(); - QString code = emoteObject.value("code").toString(); - // emoteObject.value("imageType").toString(); + QString id = emoteObject.value("id").toString(); + QString code = emoteObject.value("code").toString(); + // emoteObject.value("imageType").toString(); - QString link = linkTemplate; - link.detach(); + QString link = linkTemplate; + link.detach(); - link = link.replace("{{id}}", id).replace("{{image}}", "1x"); + link = link.replace("{{id}}", id).replace("{{image}}", "1x"); - auto emote = this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [this, &code, &link] { - return EmoteData(new LazyLoadedImage(*this, this->windowManager, link, 1, code, - code + "\nChannel BTTV Emote")); - }); + auto emote = + this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [this, &code, &link] { + return EmoteData(new LazyLoadedImage(*this, this->windowManager, link, 1, + code, code + "\nChannel BTTV Emote")); + }); - this->bttvChannelEmotes.insert(code, emote); - map->insert(code, emote); - codes.push_back(code.toStdString()); - } + this->bttvChannelEmotes.insert(code, emote); + map->insert(code, emote); + codes.push_back(code.toStdString()); + } - this->bttvChannelEmoteCodes[channelName.toStdString()] = codes; - }); + this->bttvChannelEmoteCodes[channelName.toStdString()] = codes; + }, + 1500); } void EmoteManager::reloadFFZChannelEmotes(const QString &channelName, std::weak_ptr _map) @@ -94,45 +98,48 @@ void EmoteManager::reloadFFZChannelEmotes(const QString &channelName, std::weak_ QString url("http://api.frankerfacez.com/v1/room/" + channelName); - util::urlFetchJSON(url, [this, channelName, _map](QJsonObject &rootNode) { - auto map = _map.lock(); + util::urlFetchJSONTimeout( + url, QThread::currentThread(), + [this, channelName, _map](QJsonObject &rootNode) { + auto map = _map.lock(); - if (_map.expired()) { - return; - } + if (_map.expired()) { + return; + } - map->clear(); + map->clear(); - auto setsNode = rootNode.value("sets").toObject(); + auto setsNode = rootNode.value("sets").toObject(); - std::vector codes; - for (const QJsonValue &setNode : setsNode) { - auto emotesNode = setNode.toObject().value("emoticons").toArray(); + std::vector codes; + for (const QJsonValue &setNode : setsNode) { + auto emotesNode = setNode.toObject().value("emoticons").toArray(); - for (const QJsonValue &emoteNode : emotesNode) { - QJsonObject emoteObject = emoteNode.toObject(); + for (const QJsonValue &emoteNode : emotesNode) { + QJsonObject emoteObject = emoteNode.toObject(); - // margins - int id = emoteObject.value("id").toInt(); - QString code = emoteObject.value("name").toString(); + // margins + int id = emoteObject.value("id").toInt(); + QString code = emoteObject.value("name").toString(); - QJsonObject urls = emoteObject.value("urls").toObject(); - QString url1 = "http:" + urls.value("1").toString(); + QJsonObject urls = emoteObject.value("urls").toObject(); + QString url1 = "http:" + urls.value("1").toString(); - auto emote = - this->getFFZChannelEmoteFromCaches().getOrAdd(id, [this, &code, &url1] { + auto emote = this->getFFZChannelEmoteFromCaches().getOrAdd(id, [this, &code, + &url1] { return EmoteData(new LazyLoadedImage(*this, this->windowManager, url1, 1, code, code + "\nGlobal FFZ Emote")); }); - this->ffzChannelEmotes.insert(code, emote); - map->insert(code, emote); - codes.push_back(code.toStdString()); - } + this->ffzChannelEmotes.insert(code, emote); + map->insert(code, emote); + codes.push_back(code.toStdString()); + } - this->ffzChannelEmoteCodes[channelName.toStdString()] = codes; - } - }); + this->ffzChannelEmoteCodes[channelName.toStdString()] = codes; + } + }, + 1500); } ConcurrentMap &EmoteManager::getTwitchEmotes() @@ -377,11 +384,10 @@ void EmoteManager::refreshTwitchEmotes(const std::string &roomID) qDebug() << url; util::urlFetchJSONTimeout( - url, + url, QThread::currentThread(), [=, &emoteData](QJsonObject &root) { emoteData.emoteSets.clear(); emoteData.emoteCodes.clear(); - auto emoticonSets = root.value("emoticon_sets").toObject(); for (QJsonObject::iterator it = emoticonSets.begin(); it != emoticonSets.end(); ++it) { std::string emoteSetString = it.key().toStdString(); diff --git a/src/ircmanager.cpp b/src/ircmanager.cpp index 71e8038cd..b59e15900 100644 --- a/src/ircmanager.cpp +++ b/src/ircmanager.cpp @@ -154,7 +154,6 @@ void IrcManager::beginConnecting() this->writeConnection->sendRaw("JOIN #" + channel->name); this->readConnection->sendRaw("JOIN #" + channel->name); } - this->writeConnection->open(); this->readConnection->open(); } else { diff --git a/src/main.cpp b/src/main.cpp index cb8624f0b..493bfce4e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -7,6 +7,8 @@ #include #include +#include "util/networkmanager.hpp" + #ifdef USEWINSDK #include "windows.h" #endif @@ -88,6 +90,9 @@ int main(int argc, char *argv[]) return 1; } + // Initialize NetworkManager + chatterino::util::NetworkManager::init(); + int ret = 0; { @@ -103,5 +108,8 @@ int main(int argc, char *argv[]) // Save settings pajlada::Settings::SettingManager::save(); + // Deinitialize NetworkManager (stop thread and wait for finish, should be instant) + chatterino::util::NetworkManager::deinit(); + return ret; } diff --git a/src/messages/imageloadermanager.cpp b/src/messages/imageloadermanager.cpp deleted file mode 100644 index f83521044..000000000 --- a/src/messages/imageloadermanager.cpp +++ /dev/null @@ -1,92 +0,0 @@ -#include "messages/imageloadermanager.hpp" -#include "emotemanager.hpp" -#include "messages/lazyloadedimage.hpp" -#include "windowmanager.hpp" - -#include -#include -#include -#include -#include -#include - -namespace chatterino { -namespace messages { - -ImageLoaderManager::ImageLoaderManager() -{ - this->NaM.moveToThread(&this->workerThread); - this->workerThread.start(); -} - -ImageLoaderManager::~ImageLoaderManager() -{ - this->workerThread.quit(); - this->workerThread.wait(); -} - -void ImageLoaderWorker::handleRequest(LazyLoadedImage *lli, QNetworkAccessManager *nam) -{ - QNetworkRequest request(QUrl(lli->getUrl())); - QNetworkReply *reply = nam->get(request); - - QObject::connect(reply, &QNetworkReply::finished, - [lli, reply, this]() { this->handleLoad(lli, reply); }); -} - -void ImageLoaderManager::queue(chatterino::messages::LazyLoadedImage *lli) -{ - ImageLoaderRequester requester; - ImageLoaderWorker *worker = new ImageLoaderWorker; - - worker->moveToThread(&this->workerThread); - - QObject::connect(&requester, &ImageLoaderRequester::request, worker, - &ImageLoaderWorker::handleRequest); - QObject::connect(worker, &ImageLoaderWorker::done, lli, - [lli]() { lli->windowManager.layoutVisibleChatWidgets(); }); - - emit requester.request(lli, &this->NaM); -} - -void ImageLoaderWorker::handleLoad(chatterino::messages::LazyLoadedImage *lli, QNetworkReply *reply) -{ - QByteArray array = reply->readAll(); - QBuffer buffer(&array); - buffer.open(QIODevice::ReadOnly); - - QImage image; - QImageReader reader(&buffer); - - bool first = true; - - for (int index = 0; index < reader.imageCount(); ++index) { - if (reader.read(&image)) { - auto pixmap = new QPixmap(QPixmap::fromImage(image)); - - if (first) { - first = false; - lli->currentPixmap = pixmap; - } - - LazyLoadedImage::FrameData data; - data.duration = std::max(20, reader.nextImageDelay()); - data.image = pixmap; - - lli->allFrames.push_back(data); - } - } - - if (lli->allFrames.size() > 1) { - lli->animated = true; - } - - lli->emoteManager.incGeneration(); - - reply->deleteLater(); - emit this->done(); - delete this; -} - -} // namespace messages -} // namespace chatterino diff --git a/src/messages/imageloadermanager.hpp b/src/messages/imageloadermanager.hpp deleted file mode 100644 index 4894b538b..000000000 --- a/src/messages/imageloadermanager.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include -#include - -namespace chatterino { -namespace messages { - -class LazyLoadedImage; - -class ImageLoaderWorker : public QObject -{ - Q_OBJECT - -public slots: - void handleRequest(chatterino::messages::LazyLoadedImage *lli, QNetworkAccessManager *nam); - void handleLoad(LazyLoadedImage *lli, QNetworkReply *reply); - -signals: - void done(); -}; - -class ImageLoaderRequester : public QObject -{ - Q_OBJECT - -signals: - void request(chatterino::messages::LazyLoadedImage *lli, QNetworkAccessManager *nam); -}; - -class ImageLoaderManager : public QObject -{ - Q_OBJECT - - QThread workerThread; - QNetworkAccessManager NaM; - -public: - ImageLoaderManager(); - ~ImageLoaderManager(); - - void queue(chatterino::messages::LazyLoadedImage *lli); -}; - -} // namespace messages -} // namespace chatterino diff --git a/src/messages/lazyloadedimage.cpp b/src/messages/lazyloadedimage.cpp index d48f9e8ef..74c9750ff 100644 --- a/src/messages/lazyloadedimage.cpp +++ b/src/messages/lazyloadedimage.cpp @@ -2,7 +2,7 @@ #include "asyncexec.hpp" #include "emotemanager.hpp" #include "ircmanager.hpp" -#include "messages/imageloadermanager.hpp" +#include "util/networkmanager.hpp" #include "util/urlfetch.hpp" #include "windowmanager.hpp" @@ -12,9 +12,9 @@ #include #include #include -#include #include +#include namespace chatterino { namespace messages { @@ -52,8 +52,43 @@ LazyLoadedImage::LazyLoadedImage(EmoteManager &_emoteManager, WindowManager &_wi void LazyLoadedImage::loadImage() { - static ImageLoaderManager imageLoader; - imageLoader.queue(this); + util::NetworkRequest req(this->getUrl()); + req.setCaller(this); + req.get([lli = this](QNetworkReply * reply) { + QByteArray array = reply->readAll(); + QBuffer buffer(&array); + buffer.open(QIODevice::ReadOnly); + + QImage image; + QImageReader reader(&buffer); + + bool first = true; + + for (int index = 0; index < reader.imageCount(); ++index) { + if (reader.read(&image)) { + auto pixmap = new QPixmap(QPixmap::fromImage(image)); + + if (first) { + first = false; + lli->currentPixmap = pixmap; + } + + chatterino::messages::LazyLoadedImage::FrameData data; + data.duration = std::max(20, reader.nextImageDelay()); + data.image = pixmap; + + lli->allFrames.push_back(data); + } + } + + if (lli->allFrames.size() > 1) { + lli->animated = true; + } + + lli->emoteManager.incGeneration(); + + lli->windowManager.layoutVisibleChatWidgets(); + }); this->emoteManager.getGifUpdateSignal().connect([=]() { this->gifUpdateTimout(); diff --git a/src/messages/lazyloadedimage.hpp b/src/messages/lazyloadedimage.hpp index 751ec98e7..83867f0f8 100644 --- a/src/messages/lazyloadedimage.hpp +++ b/src/messages/lazyloadedimage.hpp @@ -64,9 +64,6 @@ private: void loadImage(); void gifUpdateTimout(); - - friend class ImageLoaderWorker; - friend class ImageLoaderManager; }; } // namespace messages diff --git a/src/resources.cpp b/src/resources.cpp index 3cf41cb2e..081518487 100644 --- a/src/resources.cpp +++ b/src/resources.cpp @@ -40,9 +40,9 @@ Resources::Resources(EmoteManager &em, WindowManager &wm) { QString badgesUrl("https://badges.twitch.tv/v1/badges/global/display?language=en"); - util::urlFetchJSON(badgesUrl, [this](QJsonObject &root) { + util::urlFetchJSON(badgesUrl, QThread::currentThread(), [this](QJsonObject &root) { QJsonObject sets = root.value("badge_sets").toObject(); - + qDebug() << "badges fetched"; for (QJsonObject::iterator it = sets.begin(); it != sets.end(); ++it) { QJsonObject versions = it.value().toObject().value("versions").toObject(); @@ -88,7 +88,7 @@ void Resources::loadChannelData(const std::string &roomID, bool bypassCache) QString url = "https://badges.twitch.tv/v1/badges/channels/" + QString::fromStdString(roomID) + "/display?language=en"; - util::urlFetchJSON(url, [this, roomID](QJsonObject &root) { + util::urlFetchJSON(url, QThread::currentThread(), [this, roomID](QJsonObject &root) { QJsonObject sets = root.value("badge_sets").toObject(); Resources::Channel &ch = this->channels[roomID]; @@ -118,9 +118,9 @@ void Resources::loadChatterinoBadges() QString url = "https://fourtf.com/chatterino/badges.json"; - util::urlFetchJSON(url, [this](QJsonObject &root) { + util::urlFetchJSON(url, QThread::currentThread(), [this](QJsonObject &root) { QJsonArray badgeVariants = root.value("badges").toArray(); - + qDebug() << "chatbadges fetched"; for (QJsonArray::iterator it = badgeVariants.begin(); it != badgeVariants.end(); ++it) { QJsonObject badgeVariant = it->toObject(); const std::string badgeVariantTooltip = diff --git a/src/util/networkmanager.cpp b/src/util/networkmanager.cpp new file mode 100644 index 000000000..7b320f9b5 --- /dev/null +++ b/src/util/networkmanager.cpp @@ -0,0 +1,24 @@ +#include "util/networkmanager.hpp" + +#include + +namespace chatterino { +namespace util { + +QThread NetworkManager::workerThread; +QNetworkAccessManager NetworkManager::NaM; + +void NetworkManager::init() +{ + NetworkManager::NaM.moveToThread(&NetworkManager::workerThread); + NetworkManager::workerThread.start(); +} + +void NetworkManager::deinit() +{ + NetworkManager::workerThread.quit(); + NetworkManager::workerThread.wait(); +} + +} // namespace util +} // namespace chatterino diff --git a/src/util/networkmanager.hpp b/src/util/networkmanager.hpp new file mode 100644 index 000000000..2c06afc61 --- /dev/null +++ b/src/util/networkmanager.hpp @@ -0,0 +1,233 @@ +#pragma once + +#include +#include +#include + +namespace chatterino { + +namespace messages { + +class LazyLoadedImage; + +} // namespace messages + +namespace util { + +class NetworkRequest +{ + struct Data { + QNetworkRequest request; + const QObject *caller = nullptr; + std::function onReplyCreated; + } data; + +public: + NetworkRequest() = delete; + + explicit NetworkRequest(const char *url) + { + this->data.request.setUrl(QUrl(url)); + } + + explicit NetworkRequest(const std::string &url) + { + this->data.request.setUrl(QUrl(QString::fromStdString(url))); + } + + explicit NetworkRequest(const QString &url) + { + this->data.request.setUrl(QUrl(url)); + } + + void setCaller(const QObject *_caller) + { + this->data.caller = _caller; + } + + void setOnReplyCreated(std::function f) + { + this->data.onReplyCreated = f; + } + + void setRawHeader(const QByteArray &headerName, const QByteArray &value) + { + this->data.request.setRawHeader(headerName, value); + } + + template + void get(FinishedCallback onFinished) + { + NetworkRequester requester; + NetworkWorker *worker = new NetworkWorker; + + worker->moveToThread(&NetworkManager::workerThread); + + if (this->data.caller != nullptr) { + QObject::connect(worker, &NetworkWorker::doneUrl, this->data.caller, + [onFinished](auto reply) { + onFinished(reply); + reply->deleteLater(); + }); + } + + QObject::connect( + &requester, &NetworkRequester::requestUrl, worker, + [ data = std::move(this->data), worker, onFinished{std::move(onFinished)} ]() { + QNetworkReply *reply = NetworkManager::NaM.get(data.request); + + if (data.onReplyCreated) { + data.onReplyCreated(reply); + } + + QObject::connect(reply, &QNetworkReply::finished, worker, + [ data = std::move(this->data), worker, reply, onFinished ]() { + if (data.caller == nullptr) { + onFinished(reply); + + reply->deleteLater(); + } else { + emit worker->doneUrl(reply); + } + + delete worker; + }); + }); + + emit requester.requestUrl(); + } +}; + +class NetworkWorker : public QObject +{ + Q_OBJECT + +signals: + void doneUrl(QNetworkReply *); +}; + +class NetworkRequester : public QObject +{ + Q_OBJECT + +signals: + void requestUrl(); +}; + +class NetworkManager : public QObject +{ + Q_OBJECT + +public: + static QThread workerThread; + static QNetworkAccessManager NaM; + + static void init(); + static void deinit(); + + template + static void urlFetch(QNetworkRequest request, FinishedCallback onFinished) + { + NetworkRequester requester; + NetworkWorker *worker = new NetworkWorker; + + worker->moveToThread(&NetworkManager::workerThread); + QObject::connect(&requester, &NetworkRequester::requestUrl, worker, + [ onFinished = std::move(onFinished), request = std::move(request) ]() { + QNetworkReply *reply = NetworkManager::NaM.get(request); + + QObject::connect(reply, &QNetworkReply::finished, + [onFinished, reply, worker]() { + onFinished(reply); + delete worker; + }); + }); + + emit requester.requestUrl(); + } + + template + static void urlFetch(const QUrl &url, FinishedCallback onFinished) + { + urlFetch(QNetworkRequest(url), std::move(onFinished)); + } + + template + static void urlFetch(QNetworkRequest request, const QObject *caller, Callback callback, + ReplyCreatedCallback onReplyCreated = [](QNetworkReply *) { return; }) + { + NetworkRequester requester; + NetworkWorker *worker = new NetworkWorker; + + worker->moveToThread(&NetworkManager::workerThread); + + QObject::connect(&requester, &NetworkRequester::requestUrl, worker, [=]() { + QNetworkReply *reply = NetworkManager::NaM.get(request); + + onReplyCreated(reply); + + QObject::connect(reply, &QNetworkReply::finished, worker, + [=]() { emit worker->doneUrl(reply); }); + }); + + QObject::connect(worker, &NetworkWorker::doneUrl, caller, [=](QNetworkReply *reply) { + callback(reply); + delete worker; + }); + emit requester.requestUrl(); + } + + template + static void urlFetch(const QUrl &url, const QObject *caller, Callback callback, + ReplyCreatedCallback onReplyCreated = [](QNetworkReply *) { return; }) + { + urlFetch(QNetworkRequest(url), caller, callback, onReplyCreated); + } + + template + static void urlPut(QNetworkRequest request, FinishedCallback onFinished, QByteArray *data) + { + NetworkRequester requester; + NetworkWorker *worker = new NetworkWorker; + + worker->moveToThread(&NetworkManager::workerThread); + QObject::connect( + &requester, &NetworkRequester::requestUrl, worker, + [ onFinished = std::move(onFinished), request = std::move(request), data ]() { + QNetworkReply *reply = NetworkManager::NaM.put(request, *data); + + QObject::connect(reply, &QNetworkReply::finished, + [ onFinished = std::move(onFinished), reply ]() { + onFinished(reply); + delete worker; + }); + }); + + emit requester.requestUrl(); + } + + template + static void urlPut(QNetworkRequest request, FinishedCallback onFinished) + { + NetworkRequester requester; + NetworkWorker *worker = new NetworkWorker; + + worker->moveToThread(&NetworkManager::workerThread); + QObject::connect( + &requester, &NetworkRequester::requestUrl, worker, + [ onFinished = std::move(onFinished), request = std::move(request), worker ]() { + QNetworkReply *reply = NetworkManager::NaM.put(request, ""); + + QObject::connect(reply, &QNetworkReply::finished, + [ onFinished = std::move(onFinished), reply, worker ]() { + onFinished(reply); + delete worker; + }); + }); + + emit requester.requestUrl(); + } +}; + +} // namespace util +} // namespace chatterino diff --git a/src/util/urlfetch.hpp b/src/util/urlfetch.hpp index a655996b3..b8ae44a3c 100644 --- a/src/util/urlfetch.hpp +++ b/src/util/urlfetch.hpp @@ -1,7 +1,8 @@ #pragma once -#include "credentials.hpp" #include "accountmanager.hpp" +#include "credentials.hpp" +#include "util/networkmanager.hpp" #include #include @@ -14,68 +15,128 @@ #include #include +#include + #include namespace chatterino { namespace util { -namespace twitch { - -static void get(QString url, std::function successCallback) +static QJsonObject parseJSONFromReply(QNetworkReply *reply) { - auto manager = new QNetworkAccessManager(); + if (reply->error() != QNetworkReply::NetworkError::NoError) { + return QJsonObject(); + } - QUrl requestUrl(url); - QNetworkRequest request(requestUrl); + QByteArray data = reply->readAll(); + QJsonDocument jsonDoc(QJsonDocument::fromJson(data)); - request.setRawHeader("Client-ID", getDefaultClientID()); - request.setRawHeader("Accept", "application/vnd.twitchtv.v5+json"); + if (jsonDoc.isNull()) { + return QJsonObject(); + } - QNetworkReply *reply = manager->get(request); + return jsonDoc.object(); +} - QObject::connect(reply, &QNetworkReply::finished, [=] { - if (reply->error() == QNetworkReply::NetworkError::NoError) { - QByteArray data = reply->readAll(); - QJsonDocument jsonDoc(QJsonDocument::fromJson(data)); - - if (!jsonDoc.isNull()) { - QJsonObject rootNode = jsonDoc.object(); - - successCallback(rootNode); - } - } - - reply->deleteLater(); - manager->deleteLater(); +static void urlFetchJSON(const QString &url, const QObject *caller, + std::function successCallback) +{ + util::NetworkRequest req(url); + req.setCaller(caller); + req.get([=](QNetworkReply *reply) { + auto node = parseJSONFromReply(reply); + successCallback(node); }); } -static void getUserID(QString username, std::function successCallback) +static void urlFetchTimeout(const QString &url, const QObject *caller, + std::function successCallback, int timeoutMs) { - get("https://api.twitch.tv/kraken/users?login=" + username, [=](const QJsonObject &root) { - if (!root.value("users").isArray()) { - qDebug() << "API Error while getting user id, users is not an array"; - return; + QTimer *timer = new QTimer; + timer->setSingleShot(true); + + QEventLoop *loop = new QEventLoop; + + util::NetworkRequest req(url); + req.setCaller(loop); + req.setOnReplyCreated([loop, timer](QNetworkReply *reply) { + QObject::connect(timer, &QTimer::timeout, loop, [=]() { + QObject::disconnect(reply, &QNetworkReply::finished, loop, &QEventLoop::quit); + reply->abort(); + reply->deleteLater(); + }); + }); + req.get([=](QNetworkReply *reply) { + if (reply->error() == QNetworkReply::NetworkError::NoError) { + successCallback(reply); } - auto users = root.value("users").toArray(); - if (users.size() != 1) { - qDebug() << "API Error while getting user id, users array size is not 1"; - return; - } - if (!users[0].isObject()) { - qDebug() << "API Error while getting user id, first user is not an object"; - return; - } - auto firstUser = users[0].toObject(); - auto id = firstUser.value("_id"); - if (!id.isString()) { - qDebug() - << "API Error: while getting user id, first user object `_id` key is not a string"; - return; - } - successCallback(id.toString()); + reply->deleteLater(); + loop->quit(); }); + + QObject::connect(timer, SIGNAL(timeout()), loop, SLOT(quit())); + + timer->start(timeoutMs); + loop->exec(); + delete timer; + delete loop; +} + +static void urlFetchJSONTimeout(const QString &url, const QObject *caller, + std::function successCallback, int timeoutMs) +{ + urlFetchTimeout(url, caller, + [=](QNetworkReply *reply) { + auto node = parseJSONFromReply(reply); + successCallback(node); + }, + timeoutMs); +} + +namespace twitch { + +static void get(QString url, const QObject *caller, + std::function successCallback) +{ + util::NetworkRequest req(url); + req.setCaller(caller); + req.setRawHeader("Client-ID", getDefaultClientID()); + req.setRawHeader("Accept", "application/vnd.twitchtv.v5+json"); + req.get([=](QNetworkReply *reply) { + auto node = parseJSONFromReply(reply); + successCallback(node); + }); +} + +static void getUserID(QString username, const QObject *caller, + std::function successCallback) +{ + get("https://api.twitch.tv/kraken/users?login=" + username, caller, + [=](const QJsonObject &root) { + if (!root.value("users").isArray()) { + qDebug() << "API Error while getting user id, users is not an array"; + return; + } + + auto users = root.value("users").toArray(); + if (users.size() != 1) { + qDebug() << "API Error while getting user id, users array size is not 1"; + return; + } + if (!users[0].isObject()) { + qDebug() << "API Error while getting user id, first user is not an object"; + return; + } + auto firstUser = users[0].toObject(); + auto id = firstUser.value("_id"); + if (!id.isString()) { + qDebug() << "API Error: while getting user id, first user object `_id` key is not " + "a string"; + return; + } + successCallback(id.toString()); + }); } static void put(QUrl url, std::function successCallback) { @@ -84,157 +145,24 @@ static void put(QUrl url, std::function successCallback) request.setRawHeader("Client-ID", getDefaultClientID()); request.setRawHeader("Accept", "application/vnd.twitchtv.v5+json"); - request.setRawHeader("Authorization", "OAuth " + - AccountManager::getInstance().getTwitchUser().getOAuthToken().toUtf8()); - QNetworkReply *reply = manager->put(request,""); - QObject::connect(reply, &QNetworkReply::finished, [=](){ - if(reply->error() == QNetworkReply::NetworkError::NoError) - { - QByteArray data = reply->readAll(); - QJsonDocument jsonDoc(QJsonDocument::fromJson(data)); - if(!jsonDoc.isNull()) - { - QJsonObject rootNode = jsonDoc.object(); + request.setRawHeader( + "Authorization", + "OAuth " + AccountManager::getInstance().getTwitchUser().getOAuthToken().toUtf8()); - successCallback(rootNode); - } - } - reply->deleteLater(); - manager->deleteLater(); + NetworkManager::urlPut(std::move(request), [=](QNetworkReply *reply) { + if (reply->error() == QNetworkReply::NetworkError::NoError) { + QByteArray data = reply->readAll(); + QJsonDocument jsonDoc(QJsonDocument::fromJson(data)); + if (!jsonDoc.isNull()) { + QJsonObject rootNode = jsonDoc.object(); + + successCallback(rootNode); + } + } + reply->deleteLater(); }); } } // namespace twitch - -static void urlFetch(const QString &url, std::function successCallback, - QNetworkAccessManager *manager = nullptr) -{ - bool customManager = true; - - if (manager == nullptr) { - manager = new QNetworkAccessManager(); - customManager = false; - } - - QUrl requestUrl(url); - QNetworkRequest request(requestUrl); - - QNetworkReply *reply = manager->get(request); - - QObject::connect(reply, &QNetworkReply::finished, [=] { - /* uncomment to follow redirects - QVariant replyStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - if (replyStatus >= 300 && replyStatus <= 304) { - QString newUrl = - reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString(); - urlFetch(newUrl, successCallback); - return; - } - */ - - if (reply->error() == QNetworkReply::NetworkError::NoError) { - successCallback(*reply); - } - - reply->deleteLater(); - if (!customManager) { - manager->deleteLater(); - } - }); -} - -static void urlFetchJSON(const QString &url, std::function successCallback, - QNetworkAccessManager *manager = nullptr) -{ - urlFetch(url, - [=](QNetworkReply &reply) { - QByteArray data = reply.readAll(); - QJsonDocument jsonDoc(QJsonDocument::fromJson(data)); - - if (jsonDoc.isNull()) { - return; - } - - QJsonObject rootNode = jsonDoc.object(); - - successCallback(rootNode); - }, - manager); -} - -static void urlFetchTimeout(const QString &url, - std::function successCallback, int timeoutMs, - QNetworkAccessManager *manager = nullptr) -{ - bool customManager = true; - - if (manager == nullptr) { - manager = new QNetworkAccessManager(); - customManager = false; - } - - QUrl requestUrl(url); - QNetworkRequest request(requestUrl); - - QNetworkReply *reply = manager->get(request); - - QTimer timer; - timer.setSingleShot(true); - - QEventLoop loop; - QObject::connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); - QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); - QObject::connect(reply, &QNetworkReply::finished, [=] { - /* uncomment to follow redirects - QVariant replyStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); - if (replyStatus >= 300 && replyStatus <= 304) { - QString newUrl = - reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toString(); - urlFetch(newUrl, successCallback); - return; - } - */ - - if (reply->error() == QNetworkReply::NetworkError::NoError) { - successCallback(*reply); - } - - reply->deleteLater(); - if (!customManager) { - manager->deleteLater(); - } - }); - timer.start(timeoutMs); - loop.exec(); - - if (!timer.isActive()) { - qDebug() << "TIMED OUT"; - QObject::disconnect(reply, SIGNAL(finished()), &loop, SLOT(quit())); - reply->abort(); - } else { - // qDebug() << "XDDD HEHEHE"; - } -} - -static void urlFetchJSONTimeout(const QString &url, - std::function successCallback, int timeoutMs, - QNetworkAccessManager *manager = nullptr) -{ - urlFetchTimeout(url, - [=](QNetworkReply &reply) { - QByteArray data = reply.readAll(); - QJsonDocument jsonDoc(QJsonDocument::fromJson(data)); - - if (jsonDoc.isNull()) { - return; - } - - QJsonObject rootNode = jsonDoc.object(); - - successCallback(rootNode); - }, - timeoutMs, manager); -} - } // namespace util } // namespace chatterino diff --git a/src/widgets/accountpopup.cpp b/src/widgets/accountpopup.cpp index 164a4530c..d87f987ba 100644 --- a/src/widgets/accountpopup.cpp +++ b/src/widgets/accountpopup.cpp @@ -7,7 +7,6 @@ #include "ui_accountpopupform.h" #include -#include #include #include #include @@ -80,7 +79,9 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr channel) AccountManager::getInstance().getTwitchUser().getUserId() + "/follows/channels/" + this->userID); - util::twitch::put(requestUrl,[](QJsonObject obj){}); + util::twitch::put(requestUrl,[](QJsonObject obj){ + qDebug() << "follows channel: " << obj; + }); }); QObject::connect(this->_ui->ignore, &QPushButton::clicked, this, [=](){ @@ -88,7 +89,9 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr channel) AccountManager::getInstance().getTwitchUser().getUserId() + "/blocks/" + this->userID); - util::twitch::put(requestUrl,[](QJsonObject obj){}); + util::twitch::put(requestUrl,[](QJsonObject obj){ + qDebug() << "blocks user: " << obj; + }); }); QObject::connect(this->_ui->disableHighlights, &QPushButton::clicked, this, [=, &settings](){ @@ -118,7 +121,7 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr channel) hide(); // }); - util::twitch::getUserID(AccountManager::getInstance().getTwitchUser().getNickName(), + util::twitch::getUserID(AccountManager::getInstance().getTwitchUser().getNickName(), this, [=](const QString &id){ AccountManager::getInstance().getTwitchUser().setUserId(id); }); @@ -137,7 +140,7 @@ void AccountPopupWidget::setChannel(std::shared_ptr channel) void AccountPopupWidget::getUserId() { - util::twitch::getUserID(this->_ui->lblUsername->text(),[=](const QString &id){ + util::twitch::getUserID(this->_ui->lblUsername->text(), this, [=](const QString &id){ userID = id; getUserData(); }); @@ -145,7 +148,7 @@ void AccountPopupWidget::getUserId() void AccountPopupWidget::getUserData() { - util::twitch::get("https://api.twitch.tv/kraken/channels/" + userID,[=](const QJsonObject &obj){ + util::twitch::get("https://api.twitch.tv/kraken/channels/" + userID, this, [=](const QJsonObject &obj){ _ui->lblFollowers->setText(QString::number(obj.value("followers").toInt())); _ui->lblViews->setText(QString::number(obj.value("views").toInt())); _ui->lblAccountAge->setText(obj.value("created_at").toString().section("T", 0, 0)); diff --git a/src/widgets/channelview.cpp b/src/widgets/channelview.cpp index 663429815..c363c227d 100644 --- a/src/widgets/channelview.cpp +++ b/src/widgets/channelview.cpp @@ -239,9 +239,8 @@ QString ChannelView::getSelectedText() if (first) { first = false; - bool isSingleWord = - isSingleMessage && - this->selection.max.charIndex - charIndex < part.getCharacterLength(); + bool isSingleWord = isSingleMessage && this->selection.max.charIndex - charIndex < + part.getCharacterLength(); if (isSingleWord) { // return single word @@ -518,10 +517,9 @@ void ChannelView::updateMessageBuffer(messages::MessageRef *messageRef, QPixmap // this->selectionMax.messageIndex >= messageIndex) { // painter.fillRect(buffer->rect(), QColor(24, 55, 25)); //} else { - painter.fillRect(buffer->rect(), - (messageRef->getMessage()->getCanHighlightTab()) - ? this->colorScheme.ChatBackgroundHighlighted - : this->colorScheme.ChatBackground); + painter.fillRect(buffer->rect(), (messageRef->getMessage()->getCanHighlightTab()) + ? this->colorScheme.ChatBackgroundHighlighted + : this->colorScheme.ChatBackground); //} // draw selection diff --git a/src/widgets/chatwidget.cpp b/src/widgets/chatwidget.cpp index 778f03726..85cd57f01 100644 --- a/src/widgets/chatwidget.cpp +++ b/src/widgets/chatwidget.cpp @@ -360,17 +360,18 @@ void ChatWidget::doOpenViewerList() } auto loadingLabel = new QLabel("Loading..."); - util::twitch::get( - "https://tmi.twitch.tv/group/user/" + channel->name + "/chatters", [=](QJsonObject obj) { - QJsonObject chattersObj = obj.value("chatters").toObject(); + util::twitch::get("https://tmi.twitch.tv/group/user/" + channel->name + "/chatters", this, + [=](QJsonObject obj) { + QJsonObject chattersObj = obj.value("chatters").toObject(); - loadingLabel->hide(); - for (int i = 0; i < jsonLabels.size(); i++) { - chattersList->addItem(labelList.at(i)); - foreach (const QJsonValue &v, chattersObj.value(jsonLabels.at(i)).toArray()) - chattersList->addItem(v.toString()); - } - }); + loadingLabel->hide(); + for (int i = 0; i < jsonLabels.size(); i++) { + chattersList->addItem(labelList.at(i)); + foreach (const QJsonValue &v, + chattersObj.value(jsonLabels.at(i)).toArray()) + chattersList->addItem(v.toString()); + } + }); searchBar->setPlaceholderText("Search User..."); QObject::connect(searchBar, &QLineEdit::textEdited, this, [=]() { diff --git a/src/widgets/chatwidgetheader.cpp b/src/widgets/chatwidgetheader.cpp index 1c6bc7f17..eb5afe9bb 100644 --- a/src/widgets/chatwidgetheader.cpp +++ b/src/widgets/chatwidgetheader.cpp @@ -213,7 +213,7 @@ void ChatWidgetHeader::checkLive() auto id = QString::fromStdString(channel->roomID); - util::twitch::get("https://api.twitch.tv/kraken/streams/" + id, [=](QJsonObject obj) { + util::twitch::get("https://api.twitch.tv/kraken/streams/" + id, this, [=](QJsonObject obj) { if (obj.value("stream").isNull()) { channel->isLive = false; this->updateChannelText(); diff --git a/src/widgets/logindialog.cpp b/src/widgets/logindialog.cpp index 5d8df8b75..c39b80ccc 100644 --- a/src/widgets/logindialog.cpp +++ b/src/widgets/logindialog.cpp @@ -137,7 +137,7 @@ AdvancedLoginWidget::AdvancedLoginWidget() this->ui.buttonLowerRow.layout.addWidget(&this->ui.buttonLowerRow.fillInUserIDButton); connect(&this->ui.buttonLowerRow.fillInUserIDButton, &QPushButton::clicked, [=]() { - util::twitch::getUserID(this->ui.usernameInput.text(), [=](const QString &userID) { + util::twitch::getUserID(this->ui.usernameInput.text(), this, [=](const QString &userID) { this->ui.userIDInput.setText(userID); // }); });