moved files into src/common

This commit is contained in:
fourtf
2018-06-26 15:33:51 +02:00
parent 0bc08a364c
commit 15abedd869
90 changed files with 150 additions and 150 deletions
-142
View File
@@ -1,142 +0,0 @@
#include "util/CompletionModel.hpp"
#include "Application.hpp"
#include "Common.hpp"
#include "controllers/commands/CommandController.hpp"
#include "debug/Log.hpp"
#include "singletons/EmoteManager.hpp"
#include <QtAlgorithms>
#include <utility>
namespace chatterino {
CompletionModel::CompletionModel(const QString &_channelName)
: channelName(_channelName)
{
}
void CompletionModel::refresh()
{
debug::Log("[CompletionModel:{}] Refreshing...]", this->channelName);
auto app = getApp();
// User-specific: Twitch Emotes
// TODO: Fix this so it properly updates with the proper api. oauth token needs proper scope
for (const auto &m : app->emotes->twitch.emotes) {
for (const auto &emoteName : m.second.emoteCodes) {
// XXX: No way to discern between a twitch global emote and sub emote right now
this->addString(emoteName, TaggedString::Type::TwitchGlobalEmote);
}
}
// Global: BTTV Global Emotes
std::vector<QString> &bttvGlobalEmoteCodes = app->emotes->bttv.globalEmoteCodes;
for (const auto &m : bttvGlobalEmoteCodes) {
this->addString(m, TaggedString::Type::BTTVGlobalEmote);
}
// Global: FFZ Global Emotes
std::vector<QString> &ffzGlobalEmoteCodes = app->emotes->ffz.globalEmoteCodes;
for (const auto &m : ffzGlobalEmoteCodes) {
this->addString(m, TaggedString::Type::FFZGlobalEmote);
}
// Channel-specific: BTTV Channel Emotes
std::vector<QString> &bttvChannelEmoteCodes =
app->emotes->bttv.channelEmoteCodes[this->channelName];
for (const auto &m : bttvChannelEmoteCodes) {
this->addString(m, TaggedString::Type::BTTVChannelEmote);
}
// Channel-specific: FFZ Channel Emotes
std::vector<QString> &ffzChannelEmoteCodes =
app->emotes->ffz.channelEmoteCodes[this->channelName];
for (const auto &m : ffzChannelEmoteCodes) {
this->addString(m, TaggedString::Type::FFZChannelEmote);
}
// Global: Emojis
const auto &emojiShortCodes = app->emotes->emojis.shortCodes;
for (const auto &m : emojiShortCodes) {
this->addString(":" + m + ":", TaggedString::Type::Emoji);
}
// Commands
for (auto &command : app->commands->items.getVector()) {
this->addString(command.name, TaggedString::Command);
}
for (auto &command : app->commands->getDefaultTwitchCommandList()) {
this->addString(command, TaggedString::Command);
}
// Channel-specific: Usernames
// fourtf: only works with twitch chat
// auto c = singletons::ChannelManager::getInstance().getTwitchChannel(this->channelName);
// auto usernames = c->getUsernamesForCompletions();
// for (const auto &name : usernames) {
// assert(!name.displayName.isEmpty());
// this->addString(name.displayName);
// this->addString('@' + name.displayName);
// if (!name.localizedName.isEmpty()) {
// this->addString(name.localizedName);
// this->addString('@' + name.localizedName);
// }
// }
}
void CompletionModel::addString(const QString &str, TaggedString::Type type)
{
std::lock_guard<std::mutex> lock(this->emotesMutex);
// Always add a space at the end of completions
this->emotes.insert({str + " ", type});
}
void CompletionModel::addUser(const QString &username)
{
auto add = [this](const QString &str) {
auto ts = this->createUser(str + " ");
// Always add a space at the end of completions
std::pair<std::set<TaggedString>::iterator, bool> p = this->emotes.insert(ts);
if (!p.second) {
// No inseration was made, figure out if we need to replace the username.
if (p.first->str > ts.str) {
// Replace lowercase version of name with mixed-case version
this->emotes.erase(p.first);
auto result2 = this->emotes.insert(ts);
assert(result2.second);
} else {
p.first->timeAdded = std::chrono::steady_clock::now();
}
}
};
add(username);
add("@" + username);
}
void CompletionModel::ClearExpiredStrings()
{
std::lock_guard<std::mutex> lock(this->emotesMutex);
auto now = std::chrono::steady_clock::now();
for (auto it = this->emotes.begin(); it != this->emotes.end();) {
const auto &taggedString = *it;
if (taggedString.HasExpired(now)) {
// debug::Log("String {} expired", taggedString.str);
it = this->emotes.erase(it);
} else {
++it;
}
}
}
} // namespace chatterino
-138
View File
@@ -1,138 +0,0 @@
#pragma once
#include "Common.hpp"
#include <QAbstractListModel>
#include <chrono>
#include <mutex>
#include <set>
namespace chatterino {
class CompletionModel : public QAbstractListModel
{
struct TaggedString {
enum Type {
Username,
// Emotes
FFZGlobalEmote = 20,
FFZChannelEmote,
BTTVGlobalEmote,
BTTVChannelEmote,
TwitchGlobalEmote,
TwitchSubscriberEmote,
Emoji,
Command,
};
TaggedString(const QString &_str, Type _type)
: str(_str)
, type(_type)
, timeAdded(std::chrono::steady_clock::now())
{
}
QString str;
// Type will help decide the lifetime of the tagged strings
Type type;
mutable std::chrono::steady_clock::time_point timeAdded;
bool HasExpired(const std::chrono::steady_clock::time_point &now) const
{
switch (this->type) {
case Type::Username: {
static std::chrono::minutes expirationTimer(10);
return (this->timeAdded + expirationTimer < now);
} break;
default: {
return false;
} break;
}
return false;
}
bool IsEmote() const
{
return this->type >= 20;
}
bool operator<(const TaggedString &that) const
{
if (this->IsEmote()) {
if (that.IsEmote()) {
int k = QString::compare(this->str, that.str, Qt::CaseInsensitive);
if (k == 0) {
return this->str > that.str;
}
return k < 0;
}
return true;
}
if (that.IsEmote()) {
return false;
}
int k = QString::compare(this->str, that.str, Qt::CaseInsensitive);
if (k == 0) {
return false;
}
return k < 0;
}
};
public:
CompletionModel(const QString &_channelName);
int columnCount(const QModelIndex &) const override
{
return 1;
}
QVariant data(const QModelIndex &index, int) const override
{
std::lock_guard<std::mutex> lock(this->emotesMutex);
// TODO: Implement more safely
auto it = this->emotes.begin();
std::advance(it, index.row());
return QVariant(it->str);
}
int rowCount(const QModelIndex &) const override
{
std::lock_guard<std::mutex> lock(this->emotesMutex);
return this->emotes.size();
}
void refresh();
void addString(const QString &str, TaggedString::Type type);
void addUser(const QString &str);
void ClearExpiredStrings();
private:
TaggedString createUser(const QString &str)
{
return TaggedString{str, TaggedString::Type::Username};
}
mutable std::mutex emotesMutex;
std::set<TaggedString> emotes;
QString channelName;
};
} // namespace chatterino
-48
View File
@@ -1,48 +0,0 @@
#include "Emotemap.hpp"
#include "Application.hpp"
#include "singletons/SettingsManager.hpp"
namespace chatterino {
namespace util {
EmoteData::EmoteData(messages::Image *_image)
: image1x(_image)
{
}
// Emotes must have a 1x image to be valid
bool EmoteData::isValid() const
{
return this->image1x != nullptr;
}
messages::Image *EmoteData::getImage(float scale) const
{
int quality = getApp()->settings->preferredEmoteQuality;
if (quality == 0) {
scale *= getApp()->settings->emoteScale.getValue();
quality = [&] {
if (scale <= 1)
return 1;
if (scale <= 2)
return 2;
return 3;
}();
}
messages::Image *_image;
if (quality == 3 && this->image3x != nullptr) {
_image = this->image3x;
} else if (quality >= 2 && this->image2x != nullptr) {
_image = this->image2x;
} else {
_image = this->image1x;
}
return _image;
}
} // namespace util
} // namespace chatterino
-29
View File
@@ -1,29 +0,0 @@
#pragma once
#include "messages/Image.hpp"
#include "util/ConcurrentMap.hpp"
namespace chatterino {
namespace util {
struct EmoteData {
EmoteData() = default;
EmoteData(messages::Image *_image);
// Emotes must have a 1x image to be valid
bool isValid() const;
messages::Image *getImage(float scale) const;
messages::Image *image1x = nullptr;
messages::Image *image2x = nullptr;
messages::Image *image3x = nullptr;
// Link to the emote page i.e. https://www.frankerfacez.com/emoticon/144722-pajaCringe
QString pageLink;
};
using EmoteMap = ConcurrentMap<QString, EmoteData>;
} // namespace util
} // namespace chatterino
-67
View File
@@ -1,67 +0,0 @@
#pragma once
#include <type_traits>
namespace chatterino {
namespace util {
// = std::enable_if<std::is_enum<T>::value>::type
template <typename T, typename Q = typename std::underlying_type<T>::type>
class FlagsEnum
{
public:
FlagsEnum()
: value(static_cast<T>(0))
{
}
FlagsEnum(T _value)
: value(_value)
{
}
inline T operator~() const
{
return (T) ~(Q)this->value;
}
inline T operator|(Q a) const
{
return (T)((Q)a | (Q)this->value);
}
inline T operator&(Q a) const
{
return (T)((Q)a & (Q)this->value);
}
inline T operator^(Q a) const
{
return (T)((Q)a ^ (Q)this->value);
}
inline T &operator|=(const Q &a)
{
return (T &)((Q &)this->value |= (Q)a);
}
inline T &operator&=(const Q &a)
{
return (T &)((Q &)this->value &= (Q)a);
}
inline T &operator^=(const Q &a)
{
return (T &)((Q &)this->value ^= (Q)a);
}
void EnableFlag(T flag)
{
reinterpret_cast<Q &>(this->value) |= static_cast<Q>(flag);
}
bool HasFlag(Q flag) const
{
return (this->value & flag) == flag;
}
T value;
};
} // namespace util
} // namespace chatterino
-41
View File
@@ -1,41 +0,0 @@
#pragma once
#include <boost/noncopyable.hpp>
#include <mutex>
namespace chatterino {
namespace util {
template <typename T>
class MutexValue : boost::noncopyable
{
mutable std::mutex mutex;
T value;
public:
MutexValue()
{
}
MutexValue(T &&val)
: value(val)
{
}
T get() const
{
std::lock_guard<std::mutex> guard(this->mutex);
return this->value;
}
void set(const T &val)
{
std::lock_guard<std::mutex> guard(this->mutex);
this->value = val;
}
};
} // namespace util
} // namespace chatterino
-24
View File
@@ -1,24 +0,0 @@
#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
-173
View File
@@ -1,173 +0,0 @@
#pragma once
#include "debug/Log.hpp"
#include "util/NetworkRequester.hpp"
#include "util/NetworkWorker.hpp"
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QThread>
#include <QTimer>
#include <QUrl>
namespace chatterino {
namespace util {
static QJsonObject parseJSONFromReplyxD(QNetworkReply *reply)
{
if (reply->error() != QNetworkReply::NetworkError::NoError) {
return QJsonObject();
}
QByteArray data = reply->readAll();
QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
if (jsonDoc.isNull()) {
return QJsonObject();
}
return jsonDoc.object();
}
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,
[worker, onFinished = std::move(onFinished), request = std::move(request)]() {
QNetworkReply *reply = NetworkManager::NaM.get(request);
reply->connect(reply, &QNetworkReply::finished,
[worker, reply, onFinished = std::move(onFinished)]() {
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);
reply->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,
[worker, data, onFinished = std::move(onFinished), request = std::move(request)]() {
QNetworkReply *reply = NetworkManager::NaM.put(request, *data);
reply->connect(reply, &QNetworkReply::finished,
[worker, reply, onFinished = std::move(onFinished)]() {
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, "");
reply->connect(reply, &QNetworkReply::finished,
[onFinished = std::move(onFinished), reply, worker]() {
onFinished(reply);
delete worker;
});
});
emit requester.requestUrl();
}
template <typename FinishedCallback>
static void urlDelete(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.deleteResource(request);
reply->connect(reply, &QNetworkReply::finished,
[onFinished = std::move(onFinished), reply, worker]() {
onFinished(reply);
delete worker;
});
});
emit requester.requestUrl();
}
};
} // namespace util
} // namespace chatterino
-44
View File
@@ -1,44 +0,0 @@
#include "util/NetworkRequest.hpp"
#include "Application.hpp"
namespace chatterino {
namespace util {
NetworkRequest::NetworkRequest(const char *url)
{
this->data.request.setUrl(QUrl(url));
}
NetworkRequest::NetworkRequest(const std::string &url)
{
this->data.request.setUrl(QUrl(QString::fromStdString(url)));
}
NetworkRequest::NetworkRequest(const QString &url)
{
this->data.request.setUrl(QUrl(url));
}
void NetworkRequest::setUseQuickLoadCache(bool value)
{
this->data.useQuickLoadCache = value;
}
void NetworkRequest::Data::writeToCache(const QByteArray &bytes)
{
if (this->useQuickLoadCache) {
auto app = getApp();
QFile cachedFile(app->paths->cacheDirectory + "/" + this->getHash());
if (cachedFile.open(QIODevice::WriteOnly)) {
cachedFile.write(bytes);
cachedFile.close();
}
}
}
} // namespace util
} // namespace chatterino
-472
View File
@@ -1,472 +0,0 @@
#pragma once
#include "Application.hpp"
#include "singletons/PathManager.hpp"
#include "util/NetworkManager.hpp"
#include "util/NetworkRequester.hpp"
#include "util/NetworkWorker.hpp"
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <QCryptographicHash>
#include <QFile>
namespace chatterino {
namespace util {
static QJsonObject parseJSONFromData(const QByteArray &data)
{
QJsonDocument jsonDoc(QJsonDocument::fromJson(data));
if (jsonDoc.isNull()) {
return QJsonObject();
}
return jsonDoc.object();
}
static rapidjson::Document parseJSONFromData2(const QByteArray &data)
{
rapidjson::Document ret(rapidjson::kNullType);
rapidjson::ParseResult result = ret.Parse(data.data(), data.length());
if (result.Code() != rapidjson::kParseErrorNone) {
debug::Log("JSON parse error: {} ({})", rapidjson::GetParseError_En(result.Code()),
result.Offset());
return ret;
}
return ret;
}
static rapidjson::Document parseJSONFromReply2(QNetworkReply *reply)
{
rapidjson::Document ret(rapidjson::kNullType);
if (reply->error() != QNetworkReply::NetworkError::NoError) {
return ret;
}
QByteArray data = reply->readAll();
rapidjson::ParseResult result = ret.Parse(data.data(), data.length());
if (result.Code() != rapidjson::kParseErrorNone) {
debug::Log("JSON parse error: {} ({})", rapidjson::GetParseError_En(result.Code()),
result.Offset());
return ret;
}
return ret;
}
class NetworkRequest
{
public:
enum RequestType {
GetRequest,
PostRequest,
PutRequest,
DeleteRequest,
};
private:
struct Data {
QNetworkRequest request;
const QObject *caller = nullptr;
std::function<void(QNetworkReply *)> onReplyCreated;
int timeoutMS = -1;
bool useQuickLoadCache = false;
std::function<bool(int)> onError;
std::function<bool(const rapidjson::Document &)> onSuccess;
NetworkRequest::RequestType requestType;
QByteArray payload;
QString getHash()
{
if (this->hash.isEmpty()) {
QByteArray bytes;
bytes.append(this->request.url().toString());
for (const auto &header : this->request.rawHeaderList()) {
bytes.append(header);
}
QByteArray hashBytes(QCryptographicHash::hash(bytes, QCryptographicHash::Sha256));
this->hash = hashBytes.toHex();
}
return this->hash;
}
void writeToCache(const QByteArray &bytes);
private:
QString hash;
} data;
public:
NetworkRequest() = delete;
explicit NetworkRequest(const char *url);
explicit NetworkRequest(const std::string &url);
explicit NetworkRequest(const QString &url);
void setRequestType(RequestType newRequestType)
{
this->data.requestType = newRequestType;
}
template <typename Func>
void onError(Func cb)
{
this->data.onError = cb;
}
template <typename Func>
void onSuccess(Func cb)
{
this->data.onSuccess = cb;
}
void setPayload(const QByteArray &payload)
{
this->data.payload = payload;
}
void setUseQuickLoadCache(bool value);
void setCaller(const QObject *_caller)
{
this->data.caller = _caller;
}
void setOnReplyCreated(std::function<void(QNetworkReply *)> f)
{
this->data.onReplyCreated = f;
}
void setRawHeader(const char *headerName, const char *value)
{
this->data.request.setRawHeader(headerName, value);
}
void setRawHeader(const char *headerName, const QByteArray &value)
{
this->data.request.setRawHeader(headerName, value);
}
void setRawHeader(const char *headerName, const QString &value)
{
this->data.request.setRawHeader(headerName, value.toUtf8());
}
void setTimeout(int ms)
{
this->data.timeoutMS = ms;
}
void makeAuthorizedV5(const QString &clientID, const QString &oauthToken)
{
this->setRawHeader("Client-ID", clientID);
this->setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
this->setRawHeader("Authorization", "OAuth " + oauthToken);
}
template <typename FinishedCallback>
void get(FinishedCallback onFinished)
{
if (this->data.useQuickLoadCache) {
auto app = getApp();
QFile cachedFile(app->paths->cacheDirectory + "/" + this->data.getHash());
if (cachedFile.exists()) {
if (cachedFile.open(QIODevice::ReadOnly)) {
QByteArray bytes = cachedFile.readAll();
// qDebug() << "Loaded cached resource" << this->data.request.url();
bool success = onFinished(bytes);
cachedFile.close();
if (!success) {
// The images were not successfully loaded from the file
// XXX: Invalidate the cache file so we don't attempt to load it again next
// time
}
}
}
}
QTimer *timer = nullptr;
if (this->data.timeoutMS > 0) {
timer = new QTimer;
}
NetworkRequester requester;
NetworkWorker *worker = new NetworkWorker;
worker->moveToThread(&NetworkManager::workerThread);
if (this->data.caller != nullptr) {
QObject::connect(worker, &NetworkWorker::doneUrl, this->data.caller,
[onFinished, data = this->data](auto reply) mutable {
if (reply->error() != QNetworkReply::NetworkError::NoError) {
// TODO: We might want to call an onError callback here
return;
}
QByteArray readBytes = reply->readAll();
QByteArray bytes;
bytes.setRawData(readBytes.data(), readBytes.size());
data.writeToCache(bytes);
onFinished(bytes);
reply->deleteLater();
});
}
if (timer != nullptr) {
timer->start(this->data.timeoutMS);
}
QObject::connect(
&requester, &NetworkRequester::requestUrl, worker,
[timer, data = std::move(this->data), worker, onFinished{std::move(onFinished)}]() {
QNetworkReply *reply = NetworkManager::NaM.get(data.request);
if (timer != nullptr) {
QObject::connect(timer, &QTimer::timeout, worker, [reply, timer]() {
debug::Log("Aborted!");
reply->abort();
timer->deleteLater();
});
}
if (data.onReplyCreated) {
data.onReplyCreated(reply);
}
QObject::connect(reply, &QNetworkReply::finished, worker,
[data = std::move(data), worker, reply,
onFinished = std::move(onFinished)]() mutable {
if (data.caller == nullptr) {
QByteArray bytes = reply->readAll();
data.writeToCache(bytes);
onFinished(bytes);
reply->deleteLater();
} else {
emit worker->doneUrl(reply);
}
delete worker;
});
});
emit requester.requestUrl();
}
template <typename FinishedCallback>
void getJSON(FinishedCallback onFinished)
{
this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes) -> bool {
auto object = parseJSONFromData(bytes);
onFinished(object);
// XXX: Maybe return onFinished? For now I don't want to force onFinished to have a
// return value
return true;
});
}
template <typename FinishedCallback>
void getJSON2(FinishedCallback onFinished)
{
this->get([onFinished{std::move(onFinished)}](const QByteArray &bytes) -> bool {
auto object = parseJSONFromData2(bytes);
onFinished(object);
// XXX: Maybe return onFinished? For now I don't want to force onFinished to have a
// return value
return true;
});
}
void execute()
{
switch (this->data.requestType) {
case GetRequest: {
this->executeGet();
} break;
case PutRequest: {
this->executePut();
} break;
case DeleteRequest: {
this->executeDelete();
} break;
default: {
debug::Log("[Execute] Unhandled request type {}", (int)this->data.requestType);
} break;
}
}
private:
void useCache()
{
if (this->data.useQuickLoadCache) {
auto app = getApp();
QFile cachedFile(app->paths->cacheDirectory + "/" + this->data.getHash());
if (cachedFile.exists()) {
if (cachedFile.open(QIODevice::ReadOnly)) {
QByteArray bytes = cachedFile.readAll();
// qDebug() << "Loaded cached resource" << this->data.request.url();
auto document = parseJSONFromData2(bytes);
bool success = false;
if (!document.IsNull()) {
success = this->data.onSuccess(document);
}
cachedFile.close();
if (!success) {
// The images were not successfully loaded from the file
// XXX: Invalidate the cache file so we don't attempt to load it again next
// time
}
}
}
}
}
void doRequest()
{
QTimer *timer = nullptr;
if (this->data.timeoutMS > 0) {
timer = new QTimer;
}
NetworkRequester requester;
NetworkWorker *worker = new NetworkWorker;
worker->moveToThread(&NetworkManager::workerThread);
if (this->data.caller != nullptr) {
QObject::connect(worker, &NetworkWorker::doneUrl, this->data.caller,
[data = this->data](auto reply) mutable {
if (reply->error() != QNetworkReply::NetworkError::NoError) {
if (data.onError) {
data.onError(reply->error());
}
return;
}
QByteArray readBytes = reply->readAll();
QByteArray bytes;
bytes.setRawData(readBytes.data(), readBytes.size());
data.writeToCache(bytes);
data.onSuccess(parseJSONFromData2(bytes));
reply->deleteLater();
});
}
if (timer != nullptr) {
timer->start(this->data.timeoutMS);
}
QObject::connect(&requester, &NetworkRequester::requestUrl, worker,
[timer, data = std::move(this->data), worker]() {
QNetworkReply *reply = nullptr;
switch (data.requestType) {
case GetRequest: {
reply = NetworkManager::NaM.get(data.request);
} break;
case PutRequest: {
reply = NetworkManager::NaM.put(data.request, data.payload);
} break;
case DeleteRequest: {
reply = NetworkManager::NaM.deleteResource(data.request);
} break;
}
if (reply == nullptr) {
debug::Log("Unhandled request type {}", (int)data.requestType);
return;
}
if (timer != nullptr) {
QObject::connect(timer, &QTimer::timeout, worker,
[reply, timer, data]() {
debug::Log("Aborted!");
reply->abort();
timer->deleteLater();
data.onError(-2);
});
}
if (data.onReplyCreated) {
data.onReplyCreated(reply);
}
QObject::connect(reply, &QNetworkReply::finished, worker,
[data = std::move(data), worker, reply]() mutable {
if (data.caller == nullptr) {
QByteArray bytes = reply->readAll();
data.writeToCache(bytes);
if (data.onSuccess) {
data.onSuccess(parseJSONFromData2(bytes));
} else {
qWarning() << "data.onSuccess not found";
}
reply->deleteLater();
} else {
emit worker->doneUrl(reply);
}
delete worker;
});
});
emit requester.requestUrl();
}
void executeGet()
{
this->useCache();
this->doRequest();
}
void executePut()
{
this->doRequest();
}
void executeDelete()
{
this->doRequest();
}
};
} // namespace util
} // namespace chatterino
-17
View File
@@ -1,17 +0,0 @@
#pragma once
#include <QObject>
namespace chatterino {
namespace util {
class NetworkRequester : public QObject
{
Q_OBJECT
signals:
void requestUrl();
};
} // namespace util
} // namespace chatterino
-19
View File
@@ -1,19 +0,0 @@
#pragma once
#include <QObject>
class QNetworkReply;
namespace chatterino {
namespace util {
class NetworkWorker : public QObject
{
Q_OBJECT
signals:
void doneUrl(QNetworkReply *);
};
} // namespace util
} // namespace chatterino
-36
View File
@@ -1,36 +0,0 @@
#pragma once
#include "boost/noncopyable.hpp"
namespace chatterino {
namespace util {
template <typename T>
class Property final : boost::noncopyable
{
public:
Property()
{
}
Property(const T &_value)
: value(_value)
{
}
T &operator=(const T &f)
{
return value = f;
}
operator T const &() const
{
return value;
}
protected:
T value;
};
} // namespace util
} // namespace chatterino
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "util/SerializeCustom.hpp"
#include "common/SerializeCustom.hpp"
#include <rapidjson/document.h>
#include <pajlada/settings/serialize.hpp>
-43
View File
@@ -1,43 +0,0 @@
#pragma once
#include <QString>
#include <pajlada/settings/serialize.hpp>
namespace pajlada {
namespace Settings {
template <>
struct Serialize<QString> {
static rapidjson::Value get(const QString &value, rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(value.toUtf8(), a);
return ret;
}
};
template <>
struct Deserialize<QString> {
static QString get(const rapidjson::Value &value)
{
if (!value.IsString()) {
throw std::runtime_error("Deserialized rapidjson::Value is not a string");
}
try {
const char *str = value.GetString();
auto strLen = value.GetStringLength();
return QString::fromUtf8(str, strLen);
} catch (const std::exception &) {
// int x = 5;
} catch (...) {
// int y = 5;
}
return QString();
}
};
} // namespace Settings
} // namespace pajlada
-127
View File
@@ -1,127 +0,0 @@
#pragma once
#include <QStandardItemModel>
#include <QTimer>
#include <boost/noncopyable.hpp>
#include <pajlada/signals/signal.hpp>
#include <vector>
#include "debug/AssertInGuiThread.hpp"
namespace chatterino {
namespace util {
template <typename TVectorItem>
struct SignalVectorItemArgs {
const TVectorItem &item;
int index;
void *caller;
};
template <typename TVectorItem>
class ReadOnlySignalVector : boost::noncopyable
{
public:
ReadOnlySignalVector()
{
QObject::connect(&this->itemsChangedTimer, &QTimer::timeout,
[this] { this->delayedItemsChanged.invoke(); });
this->itemsChangedTimer.setInterval(100);
this->itemsChangedTimer.setSingleShot(true);
}
virtual ~ReadOnlySignalVector() = default;
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemInserted;
pajlada::Signals::Signal<SignalVectorItemArgs<TVectorItem>> itemRemoved;
pajlada::Signals::NoArgSignal delayedItemsChanged;
const std::vector<TVectorItem> &getVector() const
{
util::assertInGuiThread();
return this->vector;
}
void invokeDelayedItemsChanged()
{
util::assertInGuiThread();
if (!this->itemsChangedTimer.isActive()) {
itemsChangedTimer.start();
}
}
protected:
std::vector<TVectorItem> vector;
QTimer itemsChangedTimer;
};
template <typename TVectorItem>
class BaseSignalVector : public ReadOnlySignalVector<TVectorItem>
{
public:
// returns the actual index of the inserted item
virtual int insertItem(const TVectorItem &item, int proposedIndex = -1, void *caller = 0) = 0;
void removeItem(int index, void *caller = 0)
{
util::assertInGuiThread();
assert(index >= 0 && index < this->vector.size());
TVectorItem item = this->vector[index];
this->vector.erase(this->vector.begin() + index);
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemRemoved.invoke(args);
this->invokeDelayedItemsChanged();
}
int appendItem(const TVectorItem &item, void *caller = 0)
{
return this->insertItem(item, -1, caller);
}
};
template <typename TVectorItem>
class UnsortedSignalVector : public BaseSignalVector<TVectorItem>
{
public:
virtual int insertItem(const TVectorItem &item, int index = -1, void *caller = 0) override
{
util::assertInGuiThread();
if (index == -1) {
index = this->vector.size();
} else {
assert(index >= 0 && index <= this->vector.size());
}
this->vector.insert(this->vector.begin() + index, item);
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemInserted.invoke(args);
this->invokeDelayedItemsChanged();
return index;
}
};
template <typename TVectorItem, typename Compare>
class SortedSignalVector : public BaseSignalVector<TVectorItem>
{
public:
virtual int insertItem(const TVectorItem &item, int = -1, void *caller = nullptr) override
{
util::assertInGuiThread();
auto it = std::lower_bound(this->vector.begin(), this->vector.end(), item, Compare{});
int index = it - this->vector.begin();
this->vector.insert(it, item);
SignalVectorItemArgs<TVectorItem> args{item, index, caller};
this->itemInserted.invoke(args);
this->invokeDelayedItemsChanged();
return index;
}
};
} // namespace util
} // namespace chatterino
-321
View File
@@ -1,321 +0,0 @@
#pragma once
#include "util/SignalVector2.hpp"
#include <QAbstractTableModel>
#include <QStandardItem>
#include <boost/optional.hpp>
#include <pajlada/signals/signalholder.hpp>
namespace chatterino {
namespace util {
template <typename TVectorItem>
class SignalVectorModel : public QAbstractTableModel, pajlada::Signals::SignalHolder
{
public:
SignalVectorModel(int columnCount, QObject *parent = nullptr)
: QAbstractTableModel(parent)
, _columnCount(columnCount)
{
for (int i = 0; i < columnCount; i++) {
this->_headerData.emplace_back();
}
}
void init(util::BaseSignalVector<TVectorItem> *vec)
{
this->vector = vec;
auto insert = [this](const SignalVectorItemArgs<TVectorItem> &args) {
if (args.caller == this) {
return;
}
// get row index
int index = this->getModelIndexFromVectorIndex(args.index);
assert(index >= 0 && index <= this->rows.size());
// get row items
std::vector<QStandardItem *> row = this->createRow();
this->getRowFromItem(args.item, row);
// insert row
index = this->beforeInsert(args.item, row, index);
this->beginInsertRows(QModelIndex(), index, index);
this->rows.insert(this->rows.begin() + index, Row(row, args.item));
this->endInsertRows();
};
int i = 0;
for (const TVectorItem &item : vec->getVector()) {
SignalVectorItemArgs<TVectorItem> args{item, i++, 0};
insert(args);
}
this->managedConnect(vec->itemInserted, insert);
this->managedConnect(vec->itemRemoved, [this](auto args) {
if (args.caller == this) {
return;
}
int row = this->getModelIndexFromVectorIndex(args.index);
assert(row >= 0 && row <= this->rows.size());
// remove row
std::vector<QStandardItem *> items = std::move(this->rows[row].items);
this->beginRemoveRows(QModelIndex(), row, row);
this->rows.erase(this->rows.begin() + row);
this->endRemoveRows();
this->afterRemoved(args.item, items, row);
for (QStandardItem *item : items) {
delete item;
}
});
this->afterInit();
}
virtual ~SignalVectorModel()
{
for (Row &row : this->rows) {
for (QStandardItem *item : row.items) {
delete item;
}
}
}
virtual int rowCount(const QModelIndex &parent) const
{
return this->rows.size();
}
virtual int columnCount(const QModelIndex &parent) const
{
return this->_columnCount;
}
virtual QVariant data(const QModelIndex &index, int role) const
{
int row = index.row(), column = index.column();
assert(row >= 0 && row < this->rows.size() && column >= 0 && column < this->_columnCount);
return rows[row].items[column]->data(role);
}
virtual bool setData(const QModelIndex &index, const QVariant &value, int role)
{
int row = index.row(), column = index.column();
assert(row >= 0 && row < this->rows.size() && column >= 0 && column < this->_columnCount);
Row &rowItem = this->rows[row];
rowItem.items[column]->setData(value, role);
if (rowItem.isCustomRow) {
this->customRowSetData(rowItem.items, column, value, role);
} else {
int vecRow = this->getVectorIndexFromModelIndex(row);
this->vector->removeItem(vecRow, this);
assert(this->rows[row].original);
TVectorItem item =
this->getItemFromRow(this->rows[row].items, this->rows[row].original.get());
this->vector->insertItem(item, vecRow, this);
}
return true;
}
QVariant headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal) {
return QVariant();
}
auto it = this->_headerData[section].find(role);
if (it == this->_headerData[section].end()) {
return QVariant();
} else {
return it.value();
}
}
bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value,
int role = Qt::DisplayRole)
{
if (orientation != Qt::Horizontal) {
return false;
}
this->_headerData[section][role] = value;
emit this->headerDataChanged(Qt::Horizontal, section, section);
return true;
}
Qt::ItemFlags flags(const QModelIndex &index) const
{
int row = index.row(), column = index.column();
assert(row >= 0 && row < this->rows.size() && column >= 0 && column < this->_columnCount);
return this->rows[index.row()].items[index.column()]->flags();
}
QStandardItem *getItem(int row, int column)
{
assert(row >= 0 && row < this->rows.size() && column >= 0 && column < this->_columnCount);
return rows[row].items[column];
}
void deleteRow(int row)
{
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
this->vector->removeItem(signalVectorRow);
}
virtual bool removeRows(int row, int count, const QModelIndex &parent) override
{
if (count != 1) {
return false;
}
assert(row >= 0 && row < this->rows.size());
int signalVectorRow = this->getVectorIndexFromModelIndex(row);
this->vector->removeItem(signalVectorRow);
return true;
}
protected:
virtual void afterInit()
{
}
// turn a vector item into a model row
virtual TVectorItem getItemFromRow(std::vector<QStandardItem *> &row,
const TVectorItem &original) = 0;
// turns a row in the model into a vector item
virtual void getRowFromItem(const TVectorItem &item, std::vector<QStandardItem *> &row) = 0;
virtual int beforeInsert(const TVectorItem &item, std::vector<QStandardItem *> &row,
int proposedIndex)
{
return proposedIndex;
}
virtual void afterRemoved(const TVectorItem &item, std::vector<QStandardItem *> &row, int index)
{
}
virtual void customRowSetData(const std::vector<QStandardItem *> &row, int column,
const QVariant &value, int role)
{
}
void insertCustomRow(std::vector<QStandardItem *> row, int index)
{
assert(index >= 0 && index <= this->rows.size());
this->beginInsertRows(QModelIndex(), index, index);
this->rows.insert(this->rows.begin() + index, Row(std::move(row), true));
this->endInsertRows();
}
void removeCustomRow(int index)
{
assert(index >= 0 && index <= this->rows.size());
assert(this->rows[index].isCustomRow);
this->beginRemoveRows(QModelIndex(), index, index);
this->rows.erase(this->rows.begin() + index);
this->endRemoveRows();
}
std::vector<QStandardItem *> createRow()
{
std::vector<QStandardItem *> row;
for (int i = 0; i < this->_columnCount; i++) {
row.push_back(new QStandardItem());
}
return row;
}
struct Row {
std::vector<QStandardItem *> items;
boost::optional<TVectorItem> original;
bool isCustomRow;
Row(std::vector<QStandardItem *> _items, bool _isCustomRow = false)
: items(std::move(_items))
, isCustomRow(_isCustomRow)
{
}
Row(std::vector<QStandardItem *> _items, const TVectorItem &_original,
bool _isCustomRow = false)
: items(std::move(_items))
, original(_original)
, isCustomRow(_isCustomRow)
{
}
};
std::vector<Row> rows;
private:
std::vector<QMap<int, QVariant>> _headerData;
BaseSignalVector<TVectorItem> *vector;
int _columnCount;
// returns the related index of the SignalVector
int getVectorIndexFromModelIndex(int index)
{
int i = 0;
for (auto &row : this->rows) {
if (row.isCustomRow) {
index--;
continue;
}
if (i == index) {
return i;
}
i++;
}
return i;
}
// returns the related index of the model
int getModelIndexFromVectorIndex(int index)
{
int i = 0;
for (auto &row : this->rows) {
if (row.isCustomRow) {
index++;
}
if (i == index) {
return i;
}
i++;
}
return i;
}
};
} // namespace util
} // namespace chatterino
-160
View File
@@ -1,160 +0,0 @@
#pragma once
#include "controllers/accounts/AccountController.hpp"
#include "providers/twitch/Credentials.hpp"
#include "debug/Log.hpp"
#include "util/NetworkManager.hpp"
#include "util/NetworkRequest.hpp"
#include <rapidjson/document.h>
#include <rapidjson/error/en.h>
#include <rapidjson/error/error.h>
#include <QByteArray>
#include <QEventLoop>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QString>
#include <functional>
namespace chatterino {
namespace util {
namespace twitch {
static void get(QString url, const QObject *caller,
std::function<void(const QJsonObject &)> successCallback)
{
util::NetworkRequest req(url);
req.setCaller(caller);
req.setRawHeader("Client-ID", getDefaultClientID());
req.setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
req.getJSON([=](const QJsonObject &node) {
successCallback(node); //
});
}
static void get2(QString url, const QObject *caller, bool useQuickLoadCache,
std::function<void(const rapidjson::Document &)> successCallback)
{
util::NetworkRequest req(url);
req.setCaller(caller);
req.setRawHeader("Client-ID", getDefaultClientID());
req.setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
req.setUseQuickLoadCache(useQuickLoadCache);
req.getJSON2([=](const rapidjson::Document &document) {
successCallback(document); //
});
}
static void getAuthorized(QString url, const QString &clientID, const QString &oauthToken,
const QObject *caller,
std::function<void(const QJsonObject &)> successCallback)
{
util::NetworkRequest req(url);
req.setCaller(caller);
req.setRawHeader("Client-ID", clientID.toUtf8());
req.setRawHeader("Authorization", "OAuth " + oauthToken.toUtf8());
req.setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
req.getJSON([=](const QJsonObject &node) {
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()) {
debug::Log("API Error while getting user id, users is not an array");
return;
}
auto users = root.value("users").toArray();
if (users.size() != 1) {
debug::Log("API Error while getting user id, users array size is not 1");
return;
}
if (!users[0].isObject()) {
debug::Log("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()) {
debug::Log("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)
{
QNetworkRequest request(url);
auto currentTwitchUser = getApp()->accounts->twitch.getCurrent();
QByteArray oauthToken;
if (currentTwitchUser) {
oauthToken = currentTwitchUser->getOAuthToken().toUtf8();
} else {
// XXX(pajlada): Bail out?
}
request.setRawHeader("Client-ID", getDefaultClientID());
request.setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
request.setRawHeader("Authorization", "OAuth " + oauthToken);
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();
});
}
static void sendDelete(QUrl url, std::function<void()> successCallback)
{
QNetworkRequest request(url);
auto currentTwitchUser = getApp()->accounts->twitch.getCurrent();
QByteArray oauthToken;
if (currentTwitchUser) {
oauthToken = currentTwitchUser->getOAuthToken().toUtf8();
} else {
// XXX(pajlada): Bail out?
}
request.setRawHeader("Client-ID", getDefaultClientID());
request.setRawHeader("Accept", "application/vnd.twitchtv.v5+json");
request.setRawHeader("Authorization", "OAuth " + oauthToken);
NetworkManager::urlDelete(std::move(request), [=](QNetworkReply *reply) {
if (reply->error() == QNetworkReply::NetworkError::NoError) {
int code =
reply->attribute(QNetworkRequest::Attribute::HttpStatusCodeAttribute).toInt();
if (code >= 200 && code <= 299) {
successCallback();
}
}
reply->deleteLater();
});
}
} // namespace twitch
} // namespace util
} // namespace chatterino