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/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 =

View file

@ -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<std::string> codes;
for (const QJsonValue &emoteNode : emotesNode) {
QJsonObject emoteObject = emoteNode.toObject();
std::vector<std::string> 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<EmoteMap> _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<std::string> codes;
for (const QJsonValue &setNode : setsNode) {
auto emotesNode = setNode.toObject().value("emoticons").toArray();
std::vector<std::string> 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<QString, twitch::EmoteValue *> &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();

View file

@ -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 {

View file

@ -7,6 +7,8 @@
#include <QStandardPaths>
#include <pajlada/settings/settingmanager.hpp>
#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;
}

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 "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 <QNetworkReply>
#include <QNetworkRequest>
#include <QTimer>
#include <thread>
#include <functional>
#include <thread>
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();

View file

@ -64,9 +64,6 @@ private:
void loadImage();
void gifUpdateTimout();
friend class ImageLoaderWorker;
friend class ImageLoaderManager;
};
} // 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");
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 =

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
#include "credentials.hpp"
#include "accountmanager.hpp"
#include "credentials.hpp"
#include "util/networkmanager.hpp"
#include <QEventLoop>
#include <QJsonArray>
@ -14,68 +15,128 @@
#include <QString>
#include <QTimer>
#include <QDebug>
#include <functional>
namespace chatterino {
namespace util {
namespace twitch {
static void get(QString url, std::function<void(QJsonObject &)> 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<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) {
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<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()) {
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<void(QJsonObject)> successCallback)
{
@ -84,157 +145,24 @@ static void put(QUrl url, std::function<void(QJsonObject)> 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<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 chatterino

View file

@ -7,7 +7,6 @@
#include "ui_accountpopupform.h"
#include <QClipboard>
#include <QDebug>
#include <QDesktopServices>
#include <QJsonArray>
#include <QJsonDocument>
@ -80,7 +79,9 @@ AccountPopupWidget::AccountPopupWidget(std::shared_ptr<Channel> 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> 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> 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> 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));

View file

@ -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

View file

@ -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, [=]() {

View file

@ -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();

View file

@ -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); //
});
});