moved files into src/common
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "util/SerializeCustom.hpp"
|
||||
#include "common/SerializeCustom.hpp"
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <pajlada/settings/serialize.hpp>
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user