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
+219
View File
@@ -0,0 +1,219 @@
#include "common/Channel.hpp"
#include "Application.hpp"
#include "debug/Log.hpp"
#include "messages/Message.hpp"
#include "singletons/EmoteManager.hpp"
#include "singletons/IrcManager.hpp"
#include "singletons/LoggingManager.hpp"
#include "singletons/WindowManager.hpp"
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonValue>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
using namespace chatterino::messages;
namespace chatterino {
Channel::Channel(const QString &_name, Type _type)
: name(_name)
, completionModel(this->name)
, type(_type)
{
this->clearCompletionModelTimer = new QTimer;
QObject::connect(this->clearCompletionModelTimer, &QTimer::timeout, [this]() {
this->completionModel.ClearExpiredStrings(); //
});
this->clearCompletionModelTimer->start(60 * 1000);
}
Channel::~Channel()
{
this->destroyed.invoke();
this->clearCompletionModelTimer->stop();
this->clearCompletionModelTimer->deleteLater();
}
Channel::Type Channel::getType() const
{
return this->type;
}
bool Channel::isTwitchChannel() const
{
return this->type >= Twitch && this->type < TwitchEnd;
}
bool Channel::isEmpty() const
{
return this->name.isEmpty();
}
messages::LimitedQueueSnapshot<messages::MessagePtr> Channel::getMessageSnapshot()
{
return this->messages.getSnapshot();
}
void Channel::addMessage(MessagePtr message)
{
auto app = getApp();
MessagePtr deleted;
const QString &username = message->loginName;
if (!username.isEmpty()) {
// TODO: Add recent chatters display name. This should maybe be a setting
this->addRecentChatter(message);
}
// FOURTF: change this when adding more providers
if (this->isTwitchChannel()) {
app->logging->addMessage(this->name, message);
}
if (this->messages.pushBack(message, deleted)) {
this->messageRemovedFromStart.invoke(deleted);
}
this->messageAppended.invoke(message);
}
void Channel::addOrReplaceTimeout(messages::MessagePtr message)
{
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
int snapshotLength = snapshot.getLength();
int end = std::max(0, snapshotLength - 20);
bool addMessage = true;
QTime minimumTime = QTime::currentTime().addSecs(-5);
for (int i = snapshotLength - 1; i >= end; --i) {
auto &s = snapshot[i];
qDebug() << s->parseTime << minimumTime;
if (s->parseTime < minimumTime) {
break;
}
if (s->flags.HasFlag(Message::Untimeout) && s->timeoutUser == message->timeoutUser) {
break;
}
if (s->flags.HasFlag(Message::Timeout) && s->timeoutUser == message->timeoutUser) {
if (message->flags.HasFlag(Message::PubSub) && !s->flags.HasFlag(Message::PubSub)) {
this->replaceMessage(s, message);
addMessage = false;
break;
}
if (!message->flags.HasFlag(Message::PubSub) && s->flags.HasFlag(Message::PubSub)) {
addMessage = false;
break;
}
int count = s->count + 1;
messages::MessagePtr replacement(Message::createSystemMessage(
message->searchText + QString(" (") + QString::number(count) + " times)"));
replacement->timeoutUser = message->timeoutUser;
replacement->count = count;
replacement->flags = message->flags;
this->replaceMessage(s, replacement);
return;
}
}
// disable the messages from the user
for (int i = 0; i < snapshotLength; i++) {
auto &s = snapshot[i];
if ((s->flags & (Message::Timeout | Message::Untimeout)) == 0 &&
s->loginName == message->timeoutUser) {
s->flags.EnableFlag(Message::Disabled);
}
}
if (addMessage) {
this->addMessage(message);
}
// XXX: Might need the following line
// WindowManager::getInstance().repaintVisibleChatWidgets(this);
}
void Channel::disableAllMessages()
{
LimitedQueueSnapshot<MessagePtr> snapshot = this->getMessageSnapshot();
int snapshotLength = snapshot.getLength();
for (int i = 0; i < snapshotLength; i++) {
auto &s = snapshot[i];
if (s->flags & Message::System || s->flags & Message::Timeout) {
continue;
}
s->flags.EnableFlag(Message::Disabled);
}
}
void Channel::addMessagesAtStart(std::vector<messages::MessagePtr> &_messages)
{
std::vector<messages::MessagePtr> addedMessages = this->messages.pushFront(_messages);
if (addedMessages.size() != 0) {
this->messagesAddedAtStart.invoke(addedMessages);
}
}
void Channel::replaceMessage(messages::MessagePtr message, messages::MessagePtr replacement)
{
int index = this->messages.replaceItem(message, replacement);
if (index >= 0) {
this->messageReplaced.invoke((size_t)index, replacement);
}
}
void Channel::addRecentChatter(const std::shared_ptr<messages::Message> &message)
{
// Do nothing by default
}
bool Channel::canSendMessage() const
{
return false;
}
void Channel::sendMessage(const QString &message)
{
}
bool Channel::isMod() const
{
return false;
}
bool Channel::isBroadcaster() const
{
return false;
}
std::shared_ptr<Channel> Channel::getEmpty()
{
static std::shared_ptr<Channel> channel(new Channel("", None));
return channel;
}
void Channel::onConnected()
{
}
} // namespace chatterino
+131
View File
@@ -0,0 +1,131 @@
#pragma once
#include "messages/Image.hpp"
#include "messages/LimitedQueue.hpp"
#include "messages/Message.hpp"
#include "common/CompletionModel.hpp"
#include "util/ConcurrentMap.hpp"
#include <QString>
#include <QTimer>
#include <pajlada/signals/signal.hpp>
#include <memory>
namespace chatterino {
namespace messages {
struct Message;
} // namespace messages
class Channel : public std::enable_shared_from_this<Channel>
{
QTimer *clearCompletionModelTimer;
public:
enum Type {
None,
Direct,
Twitch,
TwitchWhispers,
TwitchWatching,
TwitchMentions,
TwitchEnd,
};
explicit Channel(const QString &_name, Type type);
virtual ~Channel();
pajlada::Signals::Signal<const QString &, const QString &, bool &> sendMessageSignal;
pajlada::Signals::Signal<messages::MessagePtr &> messageRemovedFromStart;
pajlada::Signals::Signal<messages::MessagePtr &> messageAppended;
pajlada::Signals::Signal<std::vector<messages::MessagePtr> &> messagesAddedAtStart;
pajlada::Signals::Signal<size_t, messages::MessagePtr &> messageReplaced;
pajlada::Signals::NoArgSignal destroyed;
Type getType() const;
bool isTwitchChannel() const;
virtual bool isEmpty() const;
messages::LimitedQueueSnapshot<messages::MessagePtr> getMessageSnapshot();
void addMessage(messages::MessagePtr message);
void addMessagesAtStart(std::vector<messages::MessagePtr> &messages);
void addOrReplaceTimeout(messages::MessagePtr message);
void disableAllMessages();
void replaceMessage(messages::MessagePtr message, messages::MessagePtr replacement);
virtual void addRecentChatter(const std::shared_ptr<messages::Message> &message);
QString name;
QStringList modList;
virtual bool canSendMessage() const;
virtual void sendMessage(const QString &message);
virtual bool isMod() const;
virtual bool isBroadcaster() const;
static std::shared_ptr<Channel> getEmpty();
CompletionModel completionModel;
protected:
virtual void onConnected();
private:
messages::LimitedQueue<messages::MessagePtr> messages;
Type type;
};
using ChannelPtr = std::shared_ptr<Channel>;
class IndirectChannel
{
struct Data {
ChannelPtr channel;
Channel::Type type;
pajlada::Signals::NoArgSignal changed;
Data() = delete;
Data(ChannelPtr _channel, Channel::Type _type)
: channel(_channel)
, type(_type)
{
}
};
std::shared_ptr<Data> data;
public:
IndirectChannel(ChannelPtr channel, Channel::Type type = Channel::Direct)
: data(new Data(channel, type))
{
}
ChannelPtr get()
{
return data->channel;
}
void update(ChannelPtr ptr)
{
assert(this->data->type != Channel::Direct);
this->data->channel = ptr;
this->data->changed.invoke();
}
pajlada::Signals::NoArgSignal &getChannelChanged()
{
return this->data->changed;
}
Channel::Type getType()
{
if (this->data->type == Channel::Direct) {
return this->get()->getType();
} else {
return this->data->type;
}
}
};
} // namespace chatterino
+28
View File
@@ -0,0 +1,28 @@
#pragma once
#include "debug/Log.hpp"
#include <QString>
#include <QWidget>
#include <boost/preprocessor.hpp>
#include <string>
namespace chatterino {
enum class HighlightState {
None,
Highlighted,
NewMessage,
};
inline QString qS(const std::string &string)
{
return QString::fromStdString(string);
}
const Qt::KeyboardModifiers showSplitOverlayModifiers = Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showAddSplitRegions = Qt::ControlModifier | Qt::AltModifier;
const Qt::KeyboardModifiers showResizeHandlesModifiers = Qt::ControlModifier;
} // namespace chatterino
+142
View File
@@ -0,0 +1,142 @@
#include "common/CompletionModel.hpp"
#include "Application.hpp"
#include "common/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
@@ -0,0 +1,138 @@
#pragma once
#include "common/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
@@ -0,0 +1,48 @@
#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
@@ -0,0 +1,29 @@
#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
@@ -0,0 +1,67 @@
#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
+38
View File
@@ -0,0 +1,38 @@
#pragma once
#include <mutex>
namespace chatterino {
template <typename Type>
class LockedObject
{
public:
LockedObject &operator=(const LockedObject<Type> &other)
{
this->mutex.lock();
this->data = other.getValue();
this->mutex.unlock();
return *this;
}
LockedObject &operator=(const Type &other)
{
this->mutex.lock();
this->data = other;
this->mutex.unlock();
return *this;
}
private:
Type value;
std::mutex mutex;
};
} // namespace chatterino
+41
View File
@@ -0,0 +1,41 @@
#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
@@ -0,0 +1,24 @@
#include "common/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
@@ -0,0 +1,173 @@
#pragma once
#include "debug/Log.hpp"
#include "common/NetworkRequester.hpp"
#include "common/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
@@ -0,0 +1,44 @@
#include "common/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
@@ -0,0 +1,472 @@
#pragma once
#include "Application.hpp"
#include "singletons/PathManager.hpp"
#include "common/NetworkManager.hpp"
#include "common/NetworkRequester.hpp"
#include "common/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
@@ -0,0 +1,17 @@
#pragma once
#include <QObject>
namespace chatterino {
namespace util {
class NetworkRequester : public QObject
{
Q_OBJECT
signals:
void requestUrl();
};
} // namespace util
} // namespace chatterino
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include <QObject>
class QNetworkReply;
namespace chatterino {
namespace util {
class NetworkWorker : public QObject
{
Q_OBJECT
signals:
void doneUrl(QNetworkReply *);
};
} // namespace util
} // namespace chatterino
+59
View File
@@ -0,0 +1,59 @@
#pragma once
namespace chatterino {
template <typename T>
class NullablePtr
{
public:
NullablePtr()
: element_(nullptr)
{
}
NullablePtr(T *element)
: element_(element)
{
}
T *operator->() const
{
assert(this->hasElement());
return element_;
}
T &operator*() const
{
assert(this->hasElement());
return *element_;
}
T *get() const
{
assert(this->hasElement());
return this->element_;
}
bool isNull() const
{
return this->element_ == nullptr;
}
bool hasElement() const
{
return this->element_ != nullptr;
}
operator bool() const
{
return this->hasElement();
}
private:
T *element_;
};
} // namespace chatterino
+36
View File
@@ -0,0 +1,36 @@
#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
+5
View File
@@ -0,0 +1,5 @@
#pragma once
namespace chatterino {
enum class ProviderId { Twitch };
}
+43
View File
@@ -0,0 +1,43 @@
#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
+34
View File
@@ -0,0 +1,34 @@
#pragma once
#include <pajlada/signals/signal.hpp>
#include <mutex>
#include <vector>
namespace chatterino {
template <typename TValue>
class SignalVector
{
public:
SignalVector &operator=(std::vector<TValue> &other)
{
this->data = other;
this->updated.invoke();
return *this;
}
operator std::vector<TValue> &()
{
return this->data;
}
pajlada::Signals::NoArgSignal updated;
private:
std::vector<TValue> data;
};
} // namespace chatterino
+127
View File
@@ -0,0 +1,127 @@
#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
@@ -0,0 +1,321 @@
#pragma once
#include "common/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
@@ -0,0 +1,160 @@
#pragma once
#include "controllers/accounts/AccountController.hpp"
#include "providers/twitch/Credentials.hpp"
#include "debug/Log.hpp"
#include "common/NetworkManager.hpp"
#include "common/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
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <QtGlobal>
#define CHATTERINO_VERSION "2.0.4"
#if defined(Q_OS_WIN)
#define CHATTERINO_OS "win"
#elif defined(Q_OS_MACOS)
#define CHATTERINO_OS "macos"
#elif defined(Q_OS_LINUX)
#define CHATTERINO_OS "linux"
#endif