Merge branch 'master' into apa-notification-on-live

This commit is contained in:
apa420
2018-08-28 23:23:46 +02:00
committed by GitHub
214 changed files with 4368 additions and 4428 deletions
+2
View File
@@ -32,3 +32,5 @@ QStringAlias(UserName);
QStringAlias(UserId);
QStringAlias(Url);
QStringAlias(Tooltip);
QStringAlias(EmoteId);
QStringAlias(EmoteName);
@@ -6,14 +6,14 @@
namespace chatterino {
template <typename T>
class MutexValue : boost::noncopyable
class Atomic : boost::noncopyable
{
public:
MutexValue()
Atomic()
{
}
MutexValue(T &&val)
Atomic(T &&val)
: value_(val)
{
}
@@ -32,6 +32,13 @@ public:
this->value_ = val;
}
void set(T &&val)
{
std::lock_guard<std::mutex> guard(this->mutex_);
this->value_ = std::move(val);
}
private:
mutable std::mutex mutex_;
T value_;
+47 -16
View File
@@ -18,16 +18,14 @@
namespace chatterino {
//
// Channel
//
Channel::Channel(const QString &name, Type type)
: completionModel(name)
: completionModel(*this)
, name_(name)
, type_(type)
{
QObject::connect(&this->clearCompletionModelTimer_, &QTimer::timeout,
[this]() {
this->completionModel.clearExpiredStrings(); //
});
this->clearCompletionModelTimer_.start(60 * 1000);
}
Channel::~Channel()
@@ -67,8 +65,7 @@ void Channel::addMessage(MessagePtr message)
const QString &username = message->loginName;
if (!username.isEmpty()) {
// TODO: Add recent chatters display name. This should maybe be a
// setting
// TODO: Add recent chatters display name
this->addRecentChatter(message);
}
@@ -98,8 +95,6 @@ void Channel::addOrReplaceTimeout(MessagePtr message)
for (int i = snapshotLength - 1; i >= end; --i) {
auto &s = snapshot[i];
qDebug() << s->parseTime << minimumTime;
if (s->parseTime < minimumTime) {
break;
}
@@ -165,13 +160,14 @@ 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.hasAny({MessageFlag::System, MessageFlag::Timeout})) {
auto &message = snapshot[i];
if (message->flags.hasAny(
{MessageFlag::System, MessageFlag::Timeout})) {
continue;
}
// FOURTF: disabled for now
// s->flags.EnableFlag(MessageFlag::Disabled);
const_cast<Message *>(message.get())->flags.set(MessageFlag::Disabled);
}
}
@@ -196,7 +192,6 @@ void Channel::replaceMessage(MessagePtr message, MessagePtr replacement)
void Channel::addRecentChatter(const MessagePtr &message)
{
// Do nothing by default
}
bool Channel::canSendMessage() const
@@ -234,9 +229,45 @@ void Channel::onConnected()
{
}
std::weak_ptr<Channel> Channel::weak_from_this()
//
// Indirect channel
//
IndirectChannel::Data::Data(ChannelPtr _channel, Channel::Type _type)
: channel(_channel)
, type(_type)
{
return std::weak_ptr<Channel>(this->shared_from_this());
}
IndirectChannel::IndirectChannel(ChannelPtr channel, Channel::Type type)
: data_(std::make_unique<Data>(channel, type))
{
}
ChannelPtr IndirectChannel::get()
{
return data_->channel;
}
void IndirectChannel::reset(ChannelPtr channel)
{
assert(this->data_->type != Channel::Type::Direct);
this->data_->channel = channel;
this->data_->changed.invoke();
}
pajlada::Signals::NoArgSignal &IndirectChannel::getChannelChanged()
{
return this->data_->changed;
}
Channel::Type IndirectChannel::getType()
{
if (this->data_->type == Channel::Type::Direct) {
return this->get()->getType();
} else {
return this->data_->type;
}
}
} // namespace chatterino
+9 -43
View File
@@ -1,10 +1,7 @@
#pragma once
#include "common/CompletionModel.hpp"
#include "messages/Image.hpp"
#include "messages/LimitedQueue.hpp"
#include "messages/Message.hpp"
#include "util/ConcurrentMap.hpp"
#include <QString>
#include <QTimer>
@@ -13,7 +10,9 @@
#include <memory>
namespace chatterino {
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
class Channel : public std::enable_shared_from_this<Channel>
{
@@ -52,7 +51,6 @@ public:
void addOrReplaceTimeout(MessagePtr message);
void disableAllMessages();
void replaceMessage(MessagePtr message, MessagePtr replacement);
virtual void addRecentChatter(const MessagePtr &message);
QStringList modList;
@@ -66,11 +64,9 @@ public:
CompletionModel completionModel;
// pre c++17 polyfill
std::weak_ptr<Channel> weak_from_this();
protected:
virtual void onConnected();
virtual void addRecentChatter(const MessagePtr &message);
private:
const QString name_;
@@ -88,47 +84,17 @@ class IndirectChannel
Channel::Type type;
pajlada::Signals::NoArgSignal changed;
Data() = delete;
Data(ChannelPtr _channel, Channel::Type _type)
: channel(_channel)
, type(_type)
{
}
Data(ChannelPtr channel, Channel::Type type);
};
public:
IndirectChannel(ChannelPtr channel,
Channel::Type type = Channel::Type::Direct)
: data_(new Data(channel, type))
{
}
Channel::Type type = Channel::Type::Direct);
ChannelPtr get()
{
return data_->channel;
}
void update(ChannelPtr ptr)
{
assert(this->data_->type != Channel::Type::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::Type::Direct) {
return this->get()->getType();
} else {
return this->data_->type;
}
}
ChannelPtr get();
void reset(ChannelPtr channel);
pajlada::Signals::NoArgSignal &getChannelChanged();
Channel::Type getType();
private:
std::shared_ptr<Data> data_;
+8 -1
View File
@@ -3,7 +3,6 @@
#include "common/Aliases.hpp"
#include "common/Outcome.hpp"
#include "common/ProviderId.hpp"
#include "debug/Log.hpp"
#include <QString>
#include <QWidget>
@@ -40,4 +39,12 @@ std::weak_ptr<T> weakOf(T *element)
return element->shared_from_this();
}
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
enum class CopyMode {
Everything,
OnlyTextAndEmotes,
};
} // namespace chatterino
+83 -180
View File
@@ -2,45 +2,30 @@
#include "Application.hpp"
#include "common/Common.hpp"
#include "common/UsernameSet.hpp"
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/CommandController.hpp"
#include "debug/Benchmark.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp"
#include <QtAlgorithms>
#include <utility>
namespace chatterino {
// -- TaggedString
//
// TaggedString
//
CompletionModel::TaggedString::TaggedString(const QString &_str, Type _type)
: str(_str)
CompletionModel::TaggedString::TaggedString(const QString &_string, Type _type)
: string(_string)
, type(_type)
, timeAdded(std::chrono::steady_clock::now())
{
}
bool CompletionModel::TaggedString::isExpired(
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 CompletionModel::TaggedString::isEmote() const
{
return this->type > Type::EmoteStart && this->type < Type::EmoteEnd;
@@ -48,35 +33,23 @@ bool CompletionModel::TaggedString::isEmote() const
bool CompletionModel::TaggedString::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 (this->isEmote() != that.isEmote()) {
return this->isEmote();
}
if (that.isEmote()) {
return false;
}
int k = QString::compare(this->str, that.str, Qt::CaseInsensitive);
if (k == 0) {
return false;
}
// try comparing insensitively, if they are the same then senstively
// (fixes order of LuL and LUL)
int k = QString::compare(this->string, that.string, Qt::CaseInsensitive);
if (k == 0) return this->string > that.string;
return k < 0;
}
// -- CompletionModel
CompletionModel::CompletionModel(const QString &channelName)
: channelName_(channelName)
//
// CompletionModel
//
CompletionModel::CompletionModel(Channel &channel)
: channel_(channel)
{
}
@@ -87,160 +60,90 @@ int CompletionModel::columnCount(const QModelIndex &) const
QVariant CompletionModel::data(const QModelIndex &index, int) const
{
std::lock_guard<std::mutex> lock(this->emotesMutex_);
std::lock_guard<std::mutex> lock(this->itemsMutex_);
// TODO: Implement more safely
auto it = this->emotes_.begin();
auto it = this->items_.begin();
std::advance(it, index.row());
return QVariant(it->str);
return QVariant(it->string);
}
int CompletionModel::rowCount(const QModelIndex &) const
{
std::lock_guard<std::mutex> lock(this->emotesMutex_);
std::lock_guard<std::mutex> lock(this->itemsMutex_);
return this->emotes_.size();
return this->items_.size();
}
void CompletionModel::refresh()
void CompletionModel::refresh(const QString &prefix)
{
Log("[CompletionModel:{}] Refreshing...]", this->channelName_);
std::lock_guard<std::mutex> guard(this->itemsMutex_);
this->items_.clear();
auto app = getApp();
if (prefix.length() < 2) return;
// User-specific: Twitch Emotes
if (auto account = app->accounts->twitch.getCurrent()) {
for (const auto &emote : account->accessEmotes()->allEmoteNames) {
// XXX: No way to discern between a twitch global emote and sub
// emote right now
this->addString(emote.string,
TaggedString::Type::TwitchGlobalEmote);
}
}
// // Global: BTTV Global Emotes
// std::vector<QString> &bttvGlobalEmoteCodes =
// app->emotes->bttv.globalEmoteNames_; 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 emotes
if (auto channel = dynamic_cast<TwitchChannel *>(
getApp()
->twitch2->getChannelOrEmptyByID(this->channelName_)
.get())) {
auto bttv = channel->accessBttvEmotes();
// auto it = bttv->begin();
// for (const auto &emote : *bttv) {
// }
// std::vector<QString> &bttvChannelEmoteCodes =
// app->emotes->bttv.channelEmoteName_[this->channelName_];
// for (const auto &m : bttvChannelEmoteCodes) {
// this->addString(m, TaggedString::Type::BTTVChannelEmote);
// }
// Channel-specific: FFZ Channel Emotes
for (const auto &emote : *channel->accessFfzEmotes()) {
this->addString(emote.second->name.string,
TaggedString::Type::FFZChannelEmote);
}
}
// 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 =
// 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();
}
}
auto addString = [&](const QString &str, TaggedString::Type type) {
if (str.startsWith(prefix, Qt::CaseInsensitive))
this->items_.emplace(str + " ", type);
};
add(username);
add("@" + username);
}
if (auto channel = dynamic_cast<TwitchChannel *>(&this->channel_)) {
// account emotes
if (auto account = getApp()->accounts->twitch.getCurrent()) {
for (const auto &emote : account->accessEmotes()->allEmoteNames) {
// XXX: No way to discern between a twitch global emote and sub
// emote right now
addString(emote.string, TaggedString::Type::TwitchGlobalEmote);
}
}
void CompletionModel::clearExpiredStrings()
{
std::lock_guard<std::mutex> lock(this->emotesMutex_);
// Usernames
if (prefix.length() >= UsernameSet::PrefixLength) {
auto usernames = channel->accessChatters();
auto now = std::chrono::steady_clock::now();
for (const auto &name : usernames->subrange(Prefix(prefix))) {
addString(name, TaggedString::Type::Username);
addString("@" + name, TaggedString::Type::Username);
}
}
for (auto it = this->emotes_.begin(); it != this->emotes_.end();) {
const auto &taggedString = *it;
// Bttv Global
for (auto &emote : *channel->globalBttv().emotes()) {
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
}
if (taggedString.isExpired(now)) {
// Log("String {} expired", taggedString.str);
it = this->emotes_.erase(it);
} else {
++it;
// Ffz Global
for (auto &emote : *channel->globalFfz().emotes()) {
addString(emote.first.string, TaggedString::Type::FFZChannelEmote);
}
// Bttv Channel
for (auto &emote : *channel->bttvEmotes()) {
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
}
// Ffz Channel
for (auto &emote : *channel->ffzEmotes()) {
addString(emote.first.string, TaggedString::Type::BTTVGlobalEmote);
}
// Emojis
if (prefix.startsWith(":")) {
const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes;
for (auto &m : emojiShortCodes) {
addString(":" + m + ":", TaggedString::Type::Emoji);
}
}
// Commands
for (auto &command : getApp()->commands->items.getVector()) {
addString(command.name, TaggedString::Command);
}
for (auto &command :
getApp()->commands->getDefaultTwitchCommandList()) {
addString(command, TaggedString::Command);
}
}
}
CompletionModel::TaggedString CompletionModel::createUser(const QString &str)
{
return TaggedString{str, TaggedString::Type::Username};
}
} // namespace chatterino
+8 -19
View File
@@ -1,7 +1,5 @@
#pragma once
#include "common/Common.hpp"
#include <QAbstractListModel>
#include <chrono>
@@ -10,7 +8,7 @@
namespace chatterino {
class TwitchChannel;
class Channel;
class CompletionModel : public QAbstractListModel
{
@@ -33,39 +31,30 @@ class CompletionModel : public QAbstractListModel
Command,
};
TaggedString(const QString &_str, Type _type);
TaggedString(const QString &string, Type type);
bool isExpired(const std::chrono::steady_clock::time_point &now) const;
bool isEmote() const;
bool operator<(const TaggedString &that) const;
QString str;
// Type will help decide the lifetime of the tagged strings
QString string;
Type type;
mutable std::chrono::steady_clock::time_point timeAdded;
};
public:
CompletionModel(const QString &channelName);
CompletionModel(Channel &channel);
virtual int columnCount(const QModelIndex &) const override;
virtual QVariant data(const QModelIndex &index, int) const override;
virtual int rowCount(const QModelIndex &) const override;
void refresh();
void addString(const QString &str, TaggedString::Type type);
void addUser(const QString &str);
void clearExpiredStrings();
void refresh(const QString &prefix);
private:
TaggedString createUser(const QString &str);
mutable std::mutex emotesMutex_;
std::set<TaggedString> emotes_;
QString channelName_;
std::set<TaggedString> items_;
mutable std::mutex itemsMutex_;
Channel &channel_;
};
} // namespace chatterino
-46
View File
@@ -1,46 +0,0 @@
#include "Emotemap.hpp"
#include "Application.hpp"
#include "singletons/Settings.hpp"
namespace chatterino {
// EmoteData::EmoteData(Image *image)
// : image1x(image)
//{
//}
//// Emotes must have a 1x image to be valid
// bool EmoteData::isValid() const
//{
// return this->image1x != nullptr;
//}
// 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;
// }();
// }
// 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 chatterino
-27
View File
@@ -1,27 +0,0 @@
#pragma once
#include "messages/Image.hpp"
#include "util/ConcurrentMap.hpp"
namespace chatterino {
// struct EmoteData {
// EmoteData() = default;
// EmoteData(Image *image);
// // Emotes must have a 1x image to be valid
// bool isValid() const;
// Image *getImage(float scale) const;
// // Link to the emote page i.e.
// https://www.frankerfacez.com/emoticon/144722-pajaCringe QString pageLink;
// Image *image1x = nullptr;
// Image *image2x = nullptr;
// Image *image3x = nullptr;
//};
// using EmoteMap = ConcurrentMap<QString, EmoteData>;
} // namespace chatterino
+10 -28
View File
@@ -4,19 +4,17 @@
namespace chatterino {
// = 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))
: value_(static_cast<T>(0))
{
}
FlagsEnum(T value)
: value(value)
: value_(value)
{
}
@@ -29,22 +27,22 @@ public:
bool operator==(const FlagsEnum<T> &other)
{
return this->value == other.value;
return this->value_ == other.value_;
}
bool operator!=(const FlagsEnum &other)
{
return this->value != other.value;
return this->value_ != other.value_;
}
void set(T flag)
{
reinterpret_cast<Q &>(this->value) |= static_cast<Q>(flag);
reinterpret_cast<Q &>(this->value_) |= static_cast<Q>(flag);
}
void unset(T flag)
{
reinterpret_cast<Q &>(this->value) &= ~static_cast<Q>(flag);
reinterpret_cast<Q &>(this->value_) &= ~static_cast<Q>(flag);
}
void set(T flag, bool value)
@@ -57,33 +55,17 @@ public:
bool has(T flag) const
{
return static_cast<Q>(this->value) & static_cast<Q>(flag);
return static_cast<Q>(this->value_) & static_cast<Q>(flag);
}
// bool hasAny(std::initializer_list<T> flags) const
//{
// for (auto flag : flags) {
// if (this->has(flag)) return true;
// }
// return false;
//}
bool hasAny(FlagsEnum flags) const
{
return static_cast<Q>(this->value) & static_cast<Q>(flags.value);
return static_cast<Q>(this->value_) & static_cast<Q>(flags.value_);
}
// bool hasAll(std::initializer_list<T> flags) const
//{
// for (auto flag : flags) {
// if (!this->has(flag)) return false;
// }
// return true;
//}
bool hasAll(FlagsEnum<T> flags) const
{
return (static_cast<Q>(this->value) & static_cast<Q>(flags.value)) &&
return (static_cast<Q>(this->value_) & static_cast<Q>(flags.value_)) &&
static_cast<Q>(flags->value);
}
@@ -93,7 +75,7 @@ public:
}
private:
T value;
T value_{};
};
} // namespace chatterino
+45 -51
View File
@@ -5,69 +5,63 @@
#include <QString>
#include <QTextStream>
// ip 0.0.0.0 - 224.0.0.0
#define IP \
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])" \
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}" \
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"
#define PORT "(?::\\d{2,5})"
#define WEB_CHAR1 "[_a-z\\x{00a1}-\\x{ffff}0-9]"
#define WEB_CHAR2 "[a-z\\x{00a1}-\\x{ffff}0-9]"
#define SPOTIFY_1 "(?:artist|album|track|user:[^:]+:playlist):[a-zA-Z0-9]+"
#define SPOTIFY_2 "user:[^:]+"
#define SPOTIFY_3 "search:(?:[-\\w$\\.+!*'(),]+|%[a-fA-F0-9]{2})+"
#define SPOTIFY_PARAMS "(?:" SPOTIFY_1 "|" SPOTIFY_2 "|" SPOTIFY_3 ")"
#define SPOTIFY_LINK "(?x-mi:(spotify:" SPOTIFY_PARAMS "))"
#define WEB_PROTOCOL "(?:(?:https?|ftps?)://)?"
#define WEB_USER "(?:\\S+(?::\\S*)?@)?"
#define WEB_HOST "(?:(?:" WEB_CHAR1 "-*)*" WEB_CHAR2 "+)"
#define WEB_DOMAIN "(?:\\.(?:" WEB_CHAR2 "-*)*" WEB_CHAR2 "+)*"
#define WEB_TLD "(?:" + tldData + ")"
#define WEB_RESOURCE_PATH "(?:[/?#]\\S*)"
#define WEB_LINK \
WEB_PROTOCOL WEB_USER "(?:" IP "|" WEB_HOST WEB_DOMAIN "\\." WEB_TLD PORT \
"?" WEB_RESOURCE_PATH "?)"
#define LINK "^(?:" SPOTIFY_LINK "|" WEB_LINK ")$"
namespace chatterino {
LinkParser::LinkParser(const QString &unparsedString)
{
static QRegularExpression linkRegex = [] {
static QRegularExpression newLineRegex("\r?\n");
QFile tldFile(":/tlds.txt");
tldFile.open(QFile::ReadOnly);
QFile file(":/tlds.txt");
file.open(QFile::ReadOnly);
QTextStream tlds(&file);
tlds.setCodec("UTF-8");
QTextStream t1(&tldFile);
t1.setCodec("UTF-8");
// tldData gets injected into the LINK macro
auto tldData = tlds.readAll().replace(newLineRegex, "|");
(void)tldData;
// Read the TLDs in and replace the newlines with pipes
QString tldData = t1.readAll().replace(newLineRegex, "|");
const QString hyperlinkRegExp =
"^"
// Identifier for spotify
"(?x-mi:(spotify:(?:"
"(?:artist|album|track|user:[^:]+:playlist):"
"[a-zA-Z0-9]+|user:[^:]+|search:"
"(?:[-\\w$\\.+!*'(),]+|%[a-fA-F0-9]{2})+)))"
// If nothing matches then just go on
"|"
"^"
// Identifier for http and ftp
"(?:(?:https?|ftps?)://)?"
// user:pass authentication
"(?:\\S+(?::\\S*)?@)?"
"(?:"
// IP address dotted notation octets
// excludes loopback network 0.0.0.0
// excludes reserved space >= 224.0.0.0
// excludes network & broacast addresses
// (first & last IP address of each class)
"(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])"
"(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}"
"(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))"
"|"
// host name
"(?:(?:[_a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-9]+"
")"
// domain name
"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}0-9]-*)*[a-z\\x{00a1}-\\x{ffff}0-"
"9]+)*"
// TLD identifier
//"(?:\\.(?:[a-z\\x{00a1}-\\x{ffff}]{2,}))"
"(?:[\\.](?:" +
tldData +
"))"
"\\.?"
")"
// port number
"(?::\\d{2,5})?"
// resource path
"(?:[/?#]\\S*)?"
"$";
return QRegularExpression(hyperlinkRegExp,
return QRegularExpression(LINK,
QRegularExpression::CaseInsensitiveOption);
}();
this->match_ = linkRegex.match(unparsedString);
}
bool LinkParser::hasMatch() const
{
return this->match_.hasMatch();
}
QString LinkParser::getCaptured() const
{
return this->match_.captured();
}
} // namespace chatterino
+2 -9
View File
@@ -10,15 +10,8 @@ class LinkParser
public:
explicit LinkParser(const QString &unparsedString);
bool hasMatch() const
{
return this->match_.hasMatch();
}
QString getCaptured() const
{
return this->match_.captured();
}
bool hasMatch() const;
QString getCaptured() const;
private:
QRegularExpressionMatch match_;
-38
View File
@@ -1,38 +0,0 @@
#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
+1 -2
View File
@@ -2,12 +2,11 @@
#include <functional>
#include "Common.hpp"
class QNetworkReply;
namespace chatterino {
class Outcome;
class NetworkResult;
using NetworkSuccessCallback = std::function<Outcome(NetworkResult)>;
+1 -4
View File
@@ -1,6 +1,5 @@
#include "common/NetworkData.hpp"
#include "Application.hpp"
#include "singletons/Paths.hpp"
#include "util/DebugCount.hpp"
@@ -42,9 +41,7 @@ QString NetworkData::getHash()
void NetworkData::writeToCache(const QByteArray &bytes)
{
if (this->useQuickLoadCache_) {
auto app = getApp();
QFile cachedFile(app->paths->cacheDirectory + "/" + this->getHash());
QFile cachedFile(getPaths()->cacheDirectory() + "/" + this->getHash());
if (cachedFile.open(QIODevice::WriteOnly)) {
cachedFile.write(bytes);
+1
View File
@@ -19,6 +19,7 @@ struct NetworkData {
QNetworkRequest request_;
const QObject *caller_ = nullptr;
bool useQuickLoadCache_{};
bool executeConcurrently{};
NetworkReplyCreatedCallback onReplyCreated_;
NetworkErrorCallback onError_;
+2 -2
View File
@@ -5,11 +5,11 @@
namespace chatterino {
QThread NetworkManager::workerThread;
QNetworkAccessManager NetworkManager::NaM;
QNetworkAccessManager NetworkManager::accessManager;
void NetworkManager::init()
{
NetworkManager::NaM.moveToThread(&NetworkManager::workerThread);
NetworkManager::accessManager.moveToThread(&NetworkManager::workerThread);
NetworkManager::workerThread.start();
}
+1 -10
View File
@@ -1,16 +1,7 @@
#pragma once
#include "common/NetworkRequester.hpp"
#include "common/NetworkWorker.hpp"
#include "debug/Log.hpp"
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QThread>
#include <QTimer>
#include <QUrl>
namespace chatterino {
@@ -20,7 +11,7 @@ class NetworkManager : public QObject
public:
static QThread workerThread;
static QNetworkAccessManager NaM;
static QNetworkAccessManager accessManager;
static void init();
static void deinit();
+29 -12
View File
@@ -1,13 +1,16 @@
#include "common/NetworkRequest.hpp"
#include "Application.hpp"
#include "common/NetworkData.hpp"
#include "common/NetworkManager.hpp"
#include "common/Outcome.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchCommon.hpp"
#include "singletons/Paths.hpp"
#include "util/DebugCount.hpp"
#include <QFile>
#include <QtConcurrent>
#include <cassert>
namespace chatterino {
@@ -80,6 +83,11 @@ void NetworkRequest::setTimeout(int ms)
this->timer->timeoutMS_ = ms;
}
void NetworkRequest::setExecuteConcurrently(bool value)
{
this->data->executeConcurrently = value;
}
void NetworkRequest::makeAuthorizedV5(const QString &clientID,
const QString &oauthToken)
{
@@ -130,16 +138,15 @@ void NetworkRequest::execute()
} break;
default: {
Log("[Execute] Unhandled request type");
log("[Execute] Unhandled request type");
} break;
}
}
Outcome NetworkRequest::tryLoadCachedFile()
{
auto app = getApp();
QFile cachedFile(app->paths->cacheDirectory + "/" + this->data->getHash());
QFile cachedFile(getPaths()->cacheDirectory() + "/" +
this->data->getHash());
if (!cachedFile.exists()) {
// File didn't exist
@@ -180,14 +187,15 @@ void NetworkRequest::doRequest()
auto reply = [&]() -> QNetworkReply * {
switch (data->requestType_) {
case NetworkRequestType::Get:
return NetworkManager::NaM.get(data->request_);
return NetworkManager::accessManager.get(data->request_);
case NetworkRequestType::Put:
return NetworkManager::NaM.put(data->request_,
data->payload_);
return NetworkManager::accessManager.put(data->request_,
data->payload_);
case NetworkRequestType::Delete:
return NetworkManager::NaM.deleteResource(data->request_);
return NetworkManager::accessManager.deleteResource(
data->request_);
default:
return nullptr;
@@ -195,13 +203,13 @@ void NetworkRequest::doRequest()
}();
if (reply == nullptr) {
Log("Unhandled request type");
log("Unhandled request type");
return;
}
if (timer->isStarted()) {
timer->onTimeout(worker, [reply, data]() {
Log("Aborted!");
log("Aborted!");
reply->abort();
if (data->onError_) {
data->onError_(-2);
@@ -228,7 +236,16 @@ void NetworkRequest::doRequest()
NetworkResult result(bytes);
DebugCount::increase("http request success");
data->onSuccess_(result);
// log("starting {}", data->request_.url().toString());
if (data->onSuccess_) {
if (data->executeConcurrently)
QtConcurrent::run(
[onSuccess = std::move(data->onSuccess_),
result = std::move(result)] { onSuccess(result); });
else
data->onSuccess_(result);
}
// log("finished {}", data->request_.url().toString());
reply->deleteLater();
};
+5 -11
View File
@@ -1,14 +1,13 @@
#pragma once
#include "Application.hpp"
#include "common/NetworkCommon.hpp"
#include "common/NetworkData.hpp"
#include "common/NetworkRequester.hpp"
#include "common/NetworkResult.hpp"
#include "common/NetworkTimer.hpp"
#include "common/NetworkWorker.hpp"
namespace chatterino {
class NetworkData;
class NetworkRequest
{
@@ -27,19 +26,15 @@ class NetworkRequest
bool executed_ = false;
public:
NetworkRequest() = delete;
NetworkRequest(const NetworkRequest &other) = delete;
NetworkRequest &operator=(const NetworkRequest &other) = delete;
NetworkRequest(NetworkRequest &&other) = default;
NetworkRequest &operator=(NetworkRequest &&other) = default;
explicit NetworkRequest(
const std::string &url,
NetworkRequestType requestType = NetworkRequestType::Get);
explicit NetworkRequest(
QUrl url, NetworkRequestType requestType = NetworkRequestType::Get);
NetworkRequest(NetworkRequest &&other) = default;
NetworkRequest &operator=(NetworkRequest &&other) = default;
~NetworkRequest();
void setRequestType(NetworkRequestType newRequestType);
@@ -55,14 +50,13 @@ public:
void setRawHeader(const char *headerName, const QByteArray &value);
void setRawHeader(const char *headerName, const QString &value);
void setTimeout(int ms);
void setExecuteConcurrently(bool value);
void makeAuthorizedV5(const QString &clientID,
const QString &oauthToken = QString());
void execute();
private:
// Returns true if the file was successfully loaded from cache
// Returns false if the cache file either didn't exist, or it contained
// "invalid" data "invalid" is specified by the onSuccess callback
Outcome tryLoadCachedFile();
+1 -1
View File
@@ -31,7 +31,7 @@ rapidjson::Document NetworkResult::parseRapidJson() const
ret.Parse(this->data_.data(), this->data_.length());
if (result.Code() != rapidjson::kParseErrorNone) {
Log("JSON parse error: {} ({})",
log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset());
return ret;
}
-34
View File
@@ -1,34 +0,0 @@
#pragma once
#include "boost/noncopyable.hpp"
namespace chatterino {
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 chatterino
-43
View File
@@ -1,43 +0,0 @@
#pragma once
#include <QString>
#include <pajlada/settings/serialize.hpp>
namespace pajlada {
namespace Settings {
template <>
struct Serialize<QString> {
static rapidjson::Value get(const QString &value,
rapidjson::Document::AllocatorType &a)
{
return rapidjson::Value(value.toUtf8(), a);
}
};
template <>
struct Deserialize<QString> {
static QString get(const rapidjson::Value &value, bool *error = nullptr)
{
if (!value.IsString()) {
PAJLADA_REPORT_ERROR(error)
PAJLADA_THROW_EXCEPTION(
"Deserialized rapidjson::Value is not a string");
return QString{};
}
try {
return QString::fromUtf8(value.GetString(),
value.GetStringLength());
} catch (const std::exception &) {
// int x = 5;
} catch (...) {
// int y = 5;
}
return QString{};
}
};
} // namespace Settings
} // namespace pajlada
-34
View File
@@ -1,34 +0,0 @@
#pragma once
#include <pajlada/signals/signal.hpp>
#include <mutex>
#include <vector>
namespace chatterino {
template <typename TValue>
class SimpleSignalVector
{
public:
SimpleSignalVector &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
+5 -5
View File
@@ -46,16 +46,15 @@ public:
}
private:
T *element_;
std::mutex *mutex_;
bool isValid_ = true;
T *element_{};
std::mutex *mutex_{};
bool isValid_{true};
};
template <typename T>
class UniqueAccess
{
public:
// template <typename X = decltype(T())>
UniqueAccess()
: element_(T())
{
@@ -88,7 +87,8 @@ public:
return AccessGuard<T>(this->element_, this->mutex_);
}
template <typename X = T, typename = std::enable_if_t<!std::is_const_v<X>>>
template <typename X = T,
typename = std::enable_if_t<!std::is_const<X>::value>>
AccessGuard<const X> accessConst() const
{
return AccessGuard<const T>(this->element_, this->mutex_);
+112
View File
@@ -0,0 +1,112 @@
#include "UsernameSet.hpp"
#include <tuple>
namespace chatterino {
//
// UsernameSet
//
UsernameSet::ConstIterator UsernameSet::begin() const
{
return this->items.begin();
}
UsernameSet::ConstIterator UsernameSet::end() const
{
return this->items.end();
}
UsernameSet::Range UsernameSet::subrange(const Prefix &prefix) const
{
auto it = this->firstKeyForPrefix.find(prefix);
if (it != this->firstKeyForPrefix.end()) {
auto start = this->items.find(it->second);
auto end = start;
while (end != this->items.end() && prefix.isStartOf(*end)) {
end++;
}
return {start, end};
}
return {this->items.end(), this->items.end()};
}
std::set<QString>::size_type UsernameSet::size() const
{
return this->items.size();
}
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(const QString &value)
{
this->insertPrefix(value);
return this->items.insert(value);
}
std::pair<UsernameSet::Iterator, bool> UsernameSet::insert(QString &&value)
{
this->insertPrefix(value);
return this->items.insert(std::move(value));
}
void UsernameSet::insertPrefix(const QString &value)
{
auto &string = this->firstKeyForPrefix[Prefix(value)];
if (string.isNull() || value < string) string = value;
}
//
// Range
//
UsernameSet::Range::Range(ConstIterator start, ConstIterator end)
: start_(start)
, end_(end)
{
}
UsernameSet::ConstIterator UsernameSet::Range::begin()
{
return this->start_;
}
UsernameSet::ConstIterator UsernameSet::Range::end()
{
return this->end_;
}
//
// Prefix
//
Prefix::Prefix(const QString &string)
: first(string.size() >= 1 ? string[0].toLower() : '\0')
, second(string.size() >= 2 ? string[1].toLower() : '\0')
{
}
bool Prefix::operator==(const Prefix &other) const
{
return std::tie(this->first, this->second) ==
std::tie(other.first, other.second);
}
bool Prefix::isStartOf(const QString &string) const
{
if (string.size() == 0) {
return this->first == QChar('\0') && this->second == QChar('\0');
} else if (string.size() == 1) {
return this->first == string[0].toLower() &&
this->second == QChar('\0');
} else {
return this->first == string[0].toLower() &&
this->second == string[1].toLower();
}
}
} // namespace chatterino
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#include <QString>
#include <functional>
#include <set>
#include <unordered_map>
namespace chatterino {
class Prefix
{
public:
Prefix(const QString &string);
bool operator==(const Prefix &other) const;
bool isStartOf(const QString &string) const;
private:
QChar first;
QChar second;
friend struct std::hash<Prefix>;
};
} // namespace chatterino
namespace std {
template <>
struct hash<chatterino::Prefix> {
size_t operator()(const chatterino::Prefix &prefix) const
{
return (size_t(prefix.first.unicode()) << 16) |
size_t(prefix.second.unicode());
}
};
} // namespace std
namespace chatterino {
class UsernameSet
{
public:
static constexpr int PrefixLength = 2;
using Iterator = std::set<QString>::iterator;
using ConstIterator = std::set<QString>::const_iterator;
class Range
{
public:
Range(ConstIterator start, ConstIterator end);
ConstIterator begin();
ConstIterator end();
private:
ConstIterator start_;
ConstIterator end_;
};
ConstIterator begin() const;
ConstIterator end() const;
Range subrange(const Prefix &prefix) const;
std::set<QString>::size_type size() const;
std::pair<Iterator, bool> insert(const QString &value);
std::pair<Iterator, bool> insert(QString &&value);
private:
void insertPrefix(const QString &string);
std::set<QString> items;
std::unordered_map<Prefix, QString> firstKeyForPrefix;
};
} // namespace chatterino
+3 -3
View File
@@ -5,9 +5,9 @@
#define CHATTERINO_VERSION "2.0.4"
#if defined(Q_OS_WIN)
#define CHATTERINO_OS "win"
# define CHATTERINO_OS "win"
#elif defined(Q_OS_MACOS)
#define CHATTERINO_OS "macos"
# define CHATTERINO_OS "macos"
#elif defined(Q_OS_LINUX)
#define CHATTERINO_OS "linux"
# define CHATTERINO_OS "linux"
#endif