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?
This commit is contained in:
hemirt 2017-10-27 20:09:02 +02:00 committed by pajlada
parent a4c0bdb926
commit f51372102e
17 changed files with 521 additions and 427 deletions

View file

@ -97,7 +97,7 @@ SOURCES += \
src/widgets/rippleeffectlabel.cpp \ src/widgets/rippleeffectlabel.cpp \
src/widgets/rippleeffectbutton.cpp \ src/widgets/rippleeffectbutton.cpp \
src/messages/messagecolor.cpp \ src/messages/messagecolor.cpp \
src/messages/imageloadermanager.cpp src/util/networkmanager.cpp
HEADERS += \ HEADERS += \
src/asyncexec.hpp \ src/asyncexec.hpp \
@ -164,8 +164,8 @@ HEADERS += \
src/messages/messagecolor.hpp \ src/messages/messagecolor.hpp \
src/util/nativeeventhelper.hpp \ src/util/nativeeventhelper.hpp \
src/debug/log.hpp \ src/debug/log.hpp \
src/messages/imageloadermanager.hpp \ src/util/benchmark.hpp \
src/util/benchmark.hpp src/util/networkmanager.hpp
PRECOMPILED_HEADER = PRECOMPILED_HEADER =

View file

@ -48,7 +48,9 @@ void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName, std::weak
printf("[EmoteManager] Reload BTTV Channel Emotes for channel %s\n", qPrintable(channelName)); printf("[EmoteManager] Reload BTTV Channel Emotes for channel %s\n", qPrintable(channelName));
QString url("https://api.betterttv.net/2/channels/" + channelName); QString url("https://api.betterttv.net/2/channels/" + channelName);
util::urlFetchJSON(url, [this, channelName, _map](QJsonObject &rootNode) { util::urlFetchJSONTimeout(
url, QThread::currentThread(),
[this, channelName, _map](QJsonObject &rootNode) {
auto map = _map.lock(); auto map = _map.lock();
if (_map.expired()) { if (_map.expired()) {
@ -74,9 +76,10 @@ void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName, std::weak
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] { auto emote =
return EmoteData(new LazyLoadedImage(*this, this->windowManager, link, 1, code, this->getBTTVChannelEmoteFromCaches().getOrAdd(id, [this, &code, &link] {
code + "\nChannel BTTV Emote")); return EmoteData(new LazyLoadedImage(*this, this->windowManager, link, 1,
code, code + "\nChannel BTTV Emote"));
}); });
this->bttvChannelEmotes.insert(code, emote); this->bttvChannelEmotes.insert(code, emote);
@ -85,7 +88,8 @@ void EmoteManager::reloadBTTVChannelEmotes(const QString &channelName, std::weak
} }
this->bttvChannelEmoteCodes[channelName.toStdString()] = codes; this->bttvChannelEmoteCodes[channelName.toStdString()] = codes;
}); },
1500);
} }
void EmoteManager::reloadFFZChannelEmotes(const QString &channelName, std::weak_ptr<EmoteMap> _map) void EmoteManager::reloadFFZChannelEmotes(const QString &channelName, std::weak_ptr<EmoteMap> _map)
@ -94,7 +98,9 @@ void EmoteManager::reloadFFZChannelEmotes(const QString &channelName, std::weak_
QString url("http://api.frankerfacez.com/v1/room/" + channelName); QString url("http://api.frankerfacez.com/v1/room/" + channelName);
util::urlFetchJSON(url, [this, channelName, _map](QJsonObject &rootNode) { util::urlFetchJSONTimeout(
url, QThread::currentThread(),
[this, channelName, _map](QJsonObject &rootNode) {
auto map = _map.lock(); auto map = _map.lock();
if (_map.expired()) { if (_map.expired()) {
@ -119,8 +125,8 @@ void EmoteManager::reloadFFZChannelEmotes(const QString &channelName, std::weak_
QJsonObject urls = emoteObject.value("urls").toObject(); QJsonObject urls = emoteObject.value("urls").toObject();
QString url1 = "http:" + urls.value("1").toString(); QString url1 = "http:" + urls.value("1").toString();
auto emote = auto emote = this->getFFZChannelEmoteFromCaches().getOrAdd(id, [this, &code,
this->getFFZChannelEmoteFromCaches().getOrAdd(id, [this, &code, &url1] { &url1] {
return EmoteData(new LazyLoadedImage(*this, this->windowManager, url1, 1, return EmoteData(new LazyLoadedImage(*this, this->windowManager, url1, 1,
code, code + "\nGlobal FFZ Emote")); code, code + "\nGlobal FFZ Emote"));
}); });
@ -132,7 +138,8 @@ void EmoteManager::reloadFFZChannelEmotes(const QString &channelName, std::weak_
this->ffzChannelEmoteCodes[channelName.toStdString()] = codes; this->ffzChannelEmoteCodes[channelName.toStdString()] = codes;
} }
}); },
1500);
} }
ConcurrentMap<QString, twitch::EmoteValue *> &EmoteManager::getTwitchEmotes() ConcurrentMap<QString, twitch::EmoteValue *> &EmoteManager::getTwitchEmotes()
@ -377,11 +384,10 @@ void EmoteManager::refreshTwitchEmotes(const std::string &roomID)
qDebug() << url; qDebug() << url;
util::urlFetchJSONTimeout( util::urlFetchJSONTimeout(
url, url, QThread::currentThread(),
[=, &emoteData](QJsonObject &root) { [=, &emoteData](QJsonObject &root) {
emoteData.emoteSets.clear(); emoteData.emoteSets.clear();
emoteData.emoteCodes.clear(); emoteData.emoteCodes.clear();
auto emoticonSets = root.value("emoticon_sets").toObject(); auto emoticonSets = root.value("emoticon_sets").toObject();
for (QJsonObject::iterator it = emoticonSets.begin(); it != emoticonSets.end(); ++it) { for (QJsonObject::iterator it = emoticonSets.begin(); it != emoticonSets.end(); ++it) {
std::string emoteSetString = it.key().toStdString(); std::string emoteSetString = it.key().toStdString();

View file

@ -154,7 +154,6 @@ void IrcManager::beginConnecting()
this->writeConnection->sendRaw("JOIN #" + channel->name); this->writeConnection->sendRaw("JOIN #" + channel->name);
this->readConnection->sendRaw("JOIN #" + channel->name); this->readConnection->sendRaw("JOIN #" + channel->name);
} }
this->writeConnection->open(); this->writeConnection->open();
this->readConnection->open(); this->readConnection->open();
} else { } else {

View file

@ -7,6 +7,8 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <pajlada/settings/settingmanager.hpp> #include <pajlada/settings/settingmanager.hpp>
#include "util/networkmanager.hpp"
#ifdef USEWINSDK #ifdef USEWINSDK
#include "windows.h" #include "windows.h"
#endif #endif
@ -88,6 +90,9 @@ int main(int argc, char *argv[])
return 1; return 1;
} }
// Initialize NetworkManager
chatterino::util::NetworkManager::init();
int ret = 0; int ret = 0;
{ {
@ -103,5 +108,8 @@ int main(int argc, char *argv[])
// Save settings // Save settings
pajlada::Settings::SettingManager::save(); pajlada::Settings::SettingManager::save();
// Deinitialize NetworkManager (stop thread and wait for finish, should be instant)
chatterino::util::NetworkManager::deinit();
return ret; return ret;
} }

View file

@ -1,92 +0,0 @@
#include "messages/imageloadermanager.hpp"
#include "emotemanager.hpp"
#include "messages/lazyloadedimage.hpp"
#include "windowmanager.hpp"
#include <QApplication>
#include <QBuffer>
#include <QImageReader>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
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

View file

@ -1,46 +0,0 @@
#pragma once
#include <QNetworkAccessManager>
#include <QThread>
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

View file

@ -2,7 +2,7 @@
#include "asyncexec.hpp" #include "asyncexec.hpp"
#include "emotemanager.hpp" #include "emotemanager.hpp"
#include "ircmanager.hpp" #include "ircmanager.hpp"
#include "messages/imageloadermanager.hpp" #include "util/networkmanager.hpp"
#include "util/urlfetch.hpp" #include "util/urlfetch.hpp"
#include "windowmanager.hpp" #include "windowmanager.hpp"
@ -12,9 +12,9 @@
#include <QNetworkReply> #include <QNetworkReply>
#include <QNetworkRequest> #include <QNetworkRequest>
#include <QTimer> #include <QTimer>
#include <thread>
#include <functional> #include <functional>
#include <thread>
namespace chatterino { namespace chatterino {
namespace messages { namespace messages {
@ -52,8 +52,43 @@ LazyLoadedImage::LazyLoadedImage(EmoteManager &_emoteManager, WindowManager &_wi
void LazyLoadedImage::loadImage() void LazyLoadedImage::loadImage()
{ {
static ImageLoaderManager imageLoader; util::NetworkRequest req(this->getUrl());
imageLoader.queue(this); 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->emoteManager.getGifUpdateSignal().connect([=]() {
this->gifUpdateTimout(); this->gifUpdateTimout();

View file

@ -64,9 +64,6 @@ private:
void loadImage(); void loadImage();
void gifUpdateTimout(); void gifUpdateTimout();
friend class ImageLoaderWorker;
friend class ImageLoaderManager;
}; };
} // namespace messages } // namespace messages

View file

@ -40,9 +40,9 @@ Resources::Resources(EmoteManager &em, WindowManager &wm)
{ {
QString badgesUrl("https://badges.twitch.tv/v1/badges/global/display?language=en"); 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(); QJsonObject sets = root.value("badge_sets").toObject();
qDebug() << "badges fetched";
for (QJsonObject::iterator it = sets.begin(); it != sets.end(); ++it) { for (QJsonObject::iterator it = sets.begin(); it != sets.end(); ++it) {
QJsonObject versions = it.value().toObject().value("versions").toObject(); 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) + QString url = "https://badges.twitch.tv/v1/badges/channels/" + QString::fromStdString(roomID) +
"/display?language=en"; "/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(); QJsonObject sets = root.value("badge_sets").toObject();
Resources::Channel &ch = this->channels[roomID]; Resources::Channel &ch = this->channels[roomID];
@ -118,9 +118,9 @@ void Resources::loadChatterinoBadges()
QString url = "https://fourtf.com/chatterino/badges.json"; 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(); QJsonArray badgeVariants = root.value("badges").toArray();
qDebug() << "chatbadges fetched";
for (QJsonArray::iterator it = badgeVariants.begin(); it != badgeVariants.end(); ++it) { for (QJsonArray::iterator it = badgeVariants.begin(); it != badgeVariants.end(); ++it) {
QJsonObject badgeVariant = it->toObject(); QJsonObject badgeVariant = it->toObject();
const std::string badgeVariantTooltip = const std::string badgeVariantTooltip =

View file

@ -0,0 +1,24 @@
#include "util/networkmanager.hpp"
#include <QNetworkAccessManager>
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

233
src/util/networkmanager.hpp Normal file
View file

@ -0,0 +1,233 @@
#pragma once
#include <QNetworkAccessManager>
#include <QThread>
#include <QUrl>
namespace chatterino {
namespace messages {
class LazyLoadedImage;
} // namespace messages
namespace util {
class NetworkRequest
{
struct Data {
QNetworkRequest request;
const QObject *caller = nullptr;
std::function<void(QNetworkReply *)> 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<void(QNetworkReply *)> f)
{
this->data.onReplyCreated = f;
}
void setRawHeader(const QByteArray &headerName, const QByteArray &value)
{
this->data.request.setRawHeader(headerName, value);
}
template <typename FinishedCallback>
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 <typename FinishedCallback>
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 <typename FinishedCallback>
static void urlFetch(const QUrl &url, FinishedCallback onFinished)
{
urlFetch(QNetworkRequest(url), std::move(onFinished));
}
template <typename Callback, typename ReplyCreatedCallback = void (*)(QNetworkReply *)>
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 <typename Callback, typename ReplyCreatedCallback = void (*)(QNetworkReply *)>
static void urlFetch(const QUrl &url, const QObject *caller, Callback callback,
ReplyCreatedCallback onReplyCreated = [](QNetworkReply *) { return; })
{
urlFetch(QNetworkRequest(url), caller, callback, onReplyCreated);
}
template <typename FinishedCallback>
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 <typename FinishedCallback>
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

View file

@ -1,7 +1,8 @@
#pragma once #pragma once
#include "credentials.hpp"
#include "accountmanager.hpp" #include "accountmanager.hpp"
#include "credentials.hpp"
#include "util/networkmanager.hpp"
#include <QEventLoop> #include <QEventLoop>
#include <QJsonArray> #include <QJsonArray>
@ -14,45 +15,105 @@
#include <QString> #include <QString>
#include <QTimer> #include <QTimer>
#include <QDebug>
#include <functional> #include <functional>
namespace chatterino { namespace chatterino {
namespace util { namespace util {
namespace twitch { static QJsonObject parseJSONFromReply(QNetworkReply *reply)
static void get(QString url, std::function<void(QJsonObject &)> successCallback)
{ {
auto manager = new QNetworkAccessManager(); if (reply->error() != QNetworkReply::NetworkError::NoError) {
return QJsonObject();
}
QUrl requestUrl(url);
QNetworkRequest request(requestUrl);
request.setRawHeader("Client-ID", getDefaultClientID());
request.setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
QNetworkReply *reply = manager->get(request);
QObject::connect(reply, &QNetworkReply::finished, [=] {
if (reply->error() == QNetworkReply::NetworkError::NoError) {
QByteArray data = reply->readAll(); QByteArray data = reply->readAll();
QJsonDocument jsonDoc(QJsonDocument::fromJson(data)); QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
if (!jsonDoc.isNull()) { if (jsonDoc.isNull()) {
QJsonObject rootNode = jsonDoc.object(); return QJsonObject();
successCallback(rootNode);
}
} }
reply->deleteLater(); return jsonDoc.object();
manager->deleteLater(); }
static void urlFetchJSON(const QString &url, const QObject *caller,
std::function<void(QJsonObject &)> 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<void(QString)> successCallback) static void urlFetchTimeout(const QString &url, const QObject *caller,
std::function<void(QNetworkReply *)> successCallback, int timeoutMs)
{ {
get("https://api.twitch.tv/kraken/users?login=" + username, [=](const QJsonObject &root) { 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);
}
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<void(QJsonObject &)> 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<void(QJsonObject &)> 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<void(QString)> successCallback)
{
get("https://api.twitch.tv/kraken/users?login=" + username, caller,
[=](const QJsonObject &root) {
if (!root.value("users").isArray()) { if (!root.value("users").isArray()) {
qDebug() << "API Error while getting user id, users is not an array"; qDebug() << "API Error while getting user id, users is not an array";
return; return;
@ -70,8 +131,8 @@ static void getUserID(QString username, std::function<void(QString)> successCall
auto firstUser = users[0].toObject(); auto firstUser = users[0].toObject();
auto id = firstUser.value("_id"); auto id = firstUser.value("_id");
if (!id.isString()) { if (!id.isString()) {
qDebug() qDebug() << "API Error: while getting user id, first user object `_id` key is not "
<< "API Error: while getting user id, first user object `_id` key is not a string"; "a string";
return; return;
} }
successCallback(id.toString()); successCallback(id.toString());
@ -84,157 +145,24 @@ static void put(QUrl url, std::function<void(QJsonObject)> successCallback)
request.setRawHeader("Client-ID", getDefaultClientID()); request.setRawHeader("Client-ID", getDefaultClientID());
request.setRawHeader("Accept", "application/vnd.twitchtv.v5+json"); request.setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
request.setRawHeader("Authorization", "OAuth " + request.setRawHeader(
AccountManager::getInstance().getTwitchUser().getOAuthToken().toUtf8()); "Authorization",
QNetworkReply *reply = manager->put(request,""); "OAuth " + AccountManager::getInstance().getTwitchUser().getOAuthToken().toUtf8());
QObject::connect(reply, &QNetworkReply::finished, [=](){
if(reply->error() == QNetworkReply::NetworkError::NoError) NetworkManager::urlPut(std::move(request), [=](QNetworkReply *reply) {
{ if (reply->error() == QNetworkReply::NetworkError::NoError) {
QByteArray data = reply->readAll(); QByteArray data = reply->readAll();
QJsonDocument jsonDoc(QJsonDocument::fromJson(data)); QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
if(!jsonDoc.isNull()) if (!jsonDoc.isNull()) {
{
QJsonObject rootNode = jsonDoc.object(); QJsonObject rootNode = jsonDoc.object();
successCallback(rootNode); successCallback(rootNode);
} }
} }
reply->deleteLater(); reply->deleteLater();
manager->deleteLater();
}); });
} }
} // namespace twitch } // namespace twitch
static void urlFetch(const QString &url, std::function<void(QNetworkReply &)> 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<void(QJsonObject &)> 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<void(QNetworkReply &)> 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<void(QJsonObject &)> 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 util
} // namespace chatterino } // namespace chatterino

View file

@ -7,7 +7,6 @@
#include "ui_accountpopupform.h" #include "ui_accountpopupform.h"
#include <QClipboard> #include <QClipboard>
#include <QDebug>
#include <QDesktopServices> #include <QDesktopServices>
#include <QJsonArray> #include <QJsonArray>
#include <QJsonDocument> #include <QJsonDocument>
@ -80,7 +79,9 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr<Channel> channel)
AccountManager::getInstance().getTwitchUser().getUserId() + AccountManager::getInstance().getTwitchUser().getUserId() +
"/follows/channels/" + this->userID); "/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, [=](){ QObject::connect(this->_ui->ignore, &QPushButton::clicked, this, [=](){
@ -88,7 +89,9 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr<Channel> channel)
AccountManager::getInstance().getTwitchUser().getUserId() + AccountManager::getInstance().getTwitchUser().getUserId() +
"/blocks/" + this->userID); "/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](){ QObject::connect(this->_ui->disableHighlights, &QPushButton::clicked, this, [=, &settings](){
@ -118,7 +121,7 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr<Channel> channel)
hide(); // hide(); //
}); });
util::twitch::getUserID(AccountManager::getInstance().getTwitchUser().getNickName(), util::twitch::getUserID(AccountManager::getInstance().getTwitchUser().getNickName(), this,
[=](const QString &id){ [=](const QString &id){
AccountManager::getInstance().getTwitchUser().setUserId(id); AccountManager::getInstance().getTwitchUser().setUserId(id);
}); });
@ -137,7 +140,7 @@ void AccountPopupWidget::setChannel(std::shared_ptr<Channel> channel)
void AccountPopupWidget::getUserId() 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; userID = id;
getUserData(); getUserData();
}); });
@ -145,7 +148,7 @@ void AccountPopupWidget::getUserId()
void AccountPopupWidget::getUserData() 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->lblFollowers->setText(QString::number(obj.value("followers").toInt()));
_ui->lblViews->setText(QString::number(obj.value("views").toInt())); _ui->lblViews->setText(QString::number(obj.value("views").toInt()));
_ui->lblAccountAge->setText(obj.value("created_at").toString().section("T", 0, 0)); _ui->lblAccountAge->setText(obj.value("created_at").toString().section("T", 0, 0));

View file

@ -239,9 +239,8 @@ QString ChannelView::getSelectedText()
if (first) { if (first) {
first = false; first = false;
bool isSingleWord = bool isSingleWord = isSingleMessage && this->selection.max.charIndex - charIndex <
isSingleMessage && part.getCharacterLength();
this->selection.max.charIndex - charIndex < part.getCharacterLength();
if (isSingleWord) { if (isSingleWord) {
// return single word // return single word
@ -518,8 +517,7 @@ void ChannelView::updateMessageBuffer(messages::MessageRef *messageRef, QPixmap
// this->selectionMax.messageIndex >= messageIndex) { // this->selectionMax.messageIndex >= messageIndex) {
// painter.fillRect(buffer->rect(), QColor(24, 55, 25)); // painter.fillRect(buffer->rect(), QColor(24, 55, 25));
//} else { //} else {
painter.fillRect(buffer->rect(), painter.fillRect(buffer->rect(), (messageRef->getMessage()->getCanHighlightTab())
(messageRef->getMessage()->getCanHighlightTab())
? this->colorScheme.ChatBackgroundHighlighted ? this->colorScheme.ChatBackgroundHighlighted
: this->colorScheme.ChatBackground); : this->colorScheme.ChatBackground);
//} //}

View file

@ -360,14 +360,15 @@ void ChatWidget::doOpenViewerList()
} }
auto loadingLabel = new QLabel("Loading..."); auto loadingLabel = new QLabel("Loading...");
util::twitch::get( util::twitch::get("https://tmi.twitch.tv/group/user/" + channel->name + "/chatters", this,
"https://tmi.twitch.tv/group/user/" + channel->name + "/chatters", [=](QJsonObject obj) { [=](QJsonObject obj) {
QJsonObject chattersObj = obj.value("chatters").toObject(); QJsonObject chattersObj = obj.value("chatters").toObject();
loadingLabel->hide(); loadingLabel->hide();
for (int i = 0; i < jsonLabels.size(); i++) { for (int i = 0; i < jsonLabels.size(); i++) {
chattersList->addItem(labelList.at(i)); chattersList->addItem(labelList.at(i));
foreach (const QJsonValue &v, chattersObj.value(jsonLabels.at(i)).toArray()) foreach (const QJsonValue &v,
chattersObj.value(jsonLabels.at(i)).toArray())
chattersList->addItem(v.toString()); chattersList->addItem(v.toString());
} }
}); });

View file

@ -213,7 +213,7 @@ void ChatWidgetHeader::checkLive()
auto id = QString::fromStdString(channel->roomID); 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()) { if (obj.value("stream").isNull()) {
channel->isLive = false; channel->isLive = false;
this->updateChannelText(); this->updateChannelText();

View file

@ -137,7 +137,7 @@ AdvancedLoginWidget::AdvancedLoginWidget()
this->ui.buttonLowerRow.layout.addWidget(&this->ui.buttonLowerRow.fillInUserIDButton); this->ui.buttonLowerRow.layout.addWidget(&this->ui.buttonLowerRow.fillInUserIDButton);
connect(&this->ui.buttonLowerRow.fillInUserIDButton, &QPushButton::clicked, [=]() { 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); // this->ui.userIDInput.setText(userID); //
}); });
}); });