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
+3
View File
@@ -25,6 +25,9 @@ DerivePointerBinding: false
FixNamespaceComments: true
IndentCaseLabels: true
IndentWidth: 4
IndentWrappedFunctionNames: true
IndentPPDirectives: AfterHash
NamespaceIndentation: Inner
PointerBindsToType: false
SpacesBeforeTrailingComments: 2
Standard: Auto
+2 -2
View File
@@ -10,11 +10,11 @@
### OpenSSL
#### For our websocket library, we need OpenSSL 1.1
1. Download OpenSSL development library: https://slproweb.com/download/Win64OpenSSL-1_1_0h.exe
1. Download OpenSSL development library: https://slproweb.com/download/Win64OpenSSL-1_1_0i.exe
2. When prompted, install openssl to C:\local\openssl
3. When prompted, copy the OpenSSL DLLs to "The OpenSSL binaries (/bin) directory"
#### For Qt SSL, we need OpenSSL 1.0
1. Download OpenSSL light: https://slproweb.com/download/Win64OpenSSL_Light-1_0_2o.exe
1. Download OpenSSL light: https://slproweb.com/download/Win64OpenSSL_Light-1_0_2p.exe
2. When prompted, install it anywhere
3. When prompted, copy the OpenSSL DLLS to "The OpenSSL binaries (/bin) directory"
4. Copy the OpenSSL 1.0 files from its /bin folder to C:/local/bin (You will need to create the folder)
Vendored
+2 -2
View File
@@ -6,12 +6,12 @@ pipeline {
parallel {
stage('GCC') {
steps {
sh 'mkdir -p build-linux-gcc && cd build-linux-gcc && qmake .. && make'
sh 'mkdir -p build-linux-gcc && cd build-linux-gcc && make distclean; qmake .. && make'
}
}
stage('Clang') {
steps {
sh 'mkdir -p build-linux-clang && cd build-linux-clang && qmake -spec linux-clang .. && make'
sh 'mkdir -p build-linux-clang && cd build-linux-clang && make distclean; qmake -spec linux-clang .. && make'
}
}
}
+18 -17
View File
@@ -6,7 +6,7 @@
message(----)
QT += widgets core gui network multimedia svg
QT += widgets core gui network multimedia svg concurrent
CONFIG += communi
COMMUNI += core model util
CONFIG += c++14
@@ -33,7 +33,7 @@ equals(QMAKE_CXX, "clang++")|equals(QMAKE_CXX, "g++") {
}
# Icons
macx:ICON = resources/images/chatterino2.icns
#macx:ICON = resources/images/chatterino2.icns
win32:RC_FILE = resources/windows.rc
@@ -72,6 +72,8 @@ win32 {
# OSX include directory
macx {
INCLUDEPATH += /usr/local/include
INCLUDEPATH += /usr/local/opt/openssl/include
LIBS += -L/usr/local/opt/openssl/lib
}
# Optional dependency on Windows SDK 7
@@ -106,7 +108,6 @@ SOURCES += \
src/Application.cpp \
src/common/Channel.cpp \
src/common/CompletionModel.cpp \
src/common/Emotemap.cpp \
src/common/NetworkData.cpp \
src/common/NetworkManager.cpp \
src/common/NetworkRequest.cpp \
@@ -188,8 +189,6 @@ SOURCES += \
src/widgets/helper/NotebookButton.cpp \
src/widgets/helper/NotebookTab.cpp \
src/widgets/helper/ResizingTextEdit.cpp \
src/widgets/helper/RippleEffectButton.cpp \
src/widgets/helper/RippleEffectLabel.cpp \
src/widgets/helper/ScrollbarHighlight.cpp \
src/widgets/helper/SearchPopup.cpp \
src/widgets/helper/SettingsDialogTab.cpp \
@@ -239,7 +238,6 @@ SOURCES += \
src/providers/twitch/PubsubClient.cpp \
src/providers/twitch/TwitchApi.cpp \
src/messages/Emote.cpp \
src/messages/EmoteMap.cpp \
src/messages/ImageSet.cpp \
src/providers/bttv/BttvEmotes.cpp \
src/providers/ffz/FfzEmotes.cpp \
@@ -257,16 +255,20 @@ SOURCES += \
src/controllers/notifications/NotificationModel.cpp \
src/singletons/Toasts.cpp \
src/common/DownloadManager.cpp
src/widgets/helper/EffectLabel.cpp \
src/widgets/helper/Button.cpp \
src/messages/MessageContainer.cpp \
src/debug/Benchmark.cpp \
src/common/UsernameSet.cpp \
src/widgets/settingspages/AdvancedPage.cpp
HEADERS += \
src/Application.hpp \
src/common/Channel.hpp \
src/common/Common.hpp \
src/common/CompletionModel.hpp \
src/common/Emotemap.hpp \
src/common/FlagsEnum.hpp \
src/common/LockedObject.hpp \
src/common/MutexValue.hpp \
src/common/Atomic.hpp \
src/common/NetworkCommon.hpp \
src/common/NetworkData.hpp \
src/common/NetworkManager.hpp \
@@ -276,9 +278,8 @@ HEADERS += \
src/common/NetworkTimer.hpp \
src/common/NetworkWorker.hpp \
src/common/NullablePtr.hpp \
src/common/Property.hpp \
src/common/ProviderId.hpp \
src/common/SerializeCustom.hpp \
src/util/RapidJsonSerializeQString.hpp \
src/common/SignalVectorModel.hpp \
src/common/Version.hpp \
src/controllers/accounts/Account.hpp \
@@ -314,7 +315,6 @@ HEADERS += \
src/messages/MessageBuilder.hpp \
src/messages/MessageColor.hpp \
src/messages/MessageElement.hpp \
src/messages/MessageParseArgs.hpp \
src/messages/Selection.hpp \
src/PrecompiledHeader.hpp \
src/providers/emoji/Emojis.hpp \
@@ -381,8 +381,6 @@ HEADERS += \
src/widgets/helper/NotebookButton.hpp \
src/widgets/helper/NotebookTab.hpp \
src/widgets/helper/ResizingTextEdit.hpp \
src/widgets/helper/RippleEffectButton.hpp \
src/widgets/helper/RippleEffectLabel.hpp \
src/widgets/helper/ScrollbarHighlight.hpp \
src/widgets/helper/SearchPopup.hpp \
src/widgets/helper/SettingsDialogTab.hpp \
@@ -426,7 +424,6 @@ HEADERS += \
src/singletons/Updates.hpp \
src/singletons/NativeMessaging.hpp \
src/singletons/Theme.hpp \
src/common/SimpleSignalVector.hpp \
src/common/SignalVector.hpp \
src/widgets/dialogs/LogsPopup.hpp \
src/common/Singleton.hpp \
@@ -439,8 +436,6 @@ HEADERS += \
src/providers/twitch/PubsubClient.hpp \
src/providers/twitch/TwitchApi.hpp \
src/messages/Emote.hpp \
src/messages/EmoteMap.hpp \
src/messages/EmoteCache.hpp \
src/messages/ImageSet.hpp \
src/common/Outcome.hpp \
src/providers/bttv/BttvEmotes.hpp \
@@ -460,6 +455,12 @@ HEADERS += \
src/controllers/notifications/NotificationModel.hpp \
src/singletons/Toasts.hpp \
src/common/DownloadManager.hpp
src/widgets/helper/EffectLabel.hpp \
src/util/LayoutHelper.hpp \
src/widgets/helper/Button.hpp \
src/messages/MessageContainer.hpp \
src/common/UsernameSet.hpp \
src/widgets/settingspages/AdvancedPage.hpp
RESOURCES += \
resources/resources.qrc \
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2013 Cal Henderson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+12
View File
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 21.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 100 100" style="enable-background:new 0 0 100 100;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
</style>
<path class="st0" d="M50,100c27.7,0,50-22.3,50-50S77.7,0,50,0S0,22.3,0,50S22.3,100,50,100z M33.3,46.2c4.2,0,8.3-3.3,8.3-8.3
s-4.2-8.3-8.3-8.3S25,32.9,25,37.9S29.2,46.2,33.3,46.2z M50,91.7C27,91.7,8.3,73,8.3,50S27,8.3,50,8.3S91.7,27,91.7,50
S73,91.7,50,91.7z M23.3,63.1c16.2,10.3,37.1,10.4,53.2,0.1l-4.3-7c-13.7,8.5-31,8.4-44.5-0.1L23.3,63.1z M67.1,46.2
c4.2,0,8.3-3.3,8.3-8.3s-4.2-8.3-8.3-8.3s-8.3,3.3-8.3,8.3S62.9,46.2,67.1,46.2z"/>
</svg>

After

Width:  |  Height:  |  Size: 847 B

+6 -5
View File
@@ -7,11 +7,13 @@
#include "controllers/moderationactions/ModerationActions.hpp"
#include "controllers/notifications/NotificationController.hpp"
#include "controllers/taggedusers/TaggedUsersController.hpp"
#include "debug/Log.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/bttv/BttvEmotes.hpp"
#include "providers/ffz/FfzEmotes.hpp"
#include "providers/twitch/PubsubClient.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Fonts.hpp"
#include "singletons/Logging.hpp"
#include "singletons/NativeMessaging.hpp"
@@ -23,6 +25,7 @@
#include "singletons/WindowManager.hpp"
#include "util/IsBigEndian.hpp"
#include "util/PostToThread.hpp"
#include "widgets/Window.hpp"
#include <atomic>
@@ -37,9 +40,7 @@ Application *Application::instance = nullptr;
// to each other
Application::Application(Settings &_settings, Paths &_paths)
: settings(&_settings)
, paths(&_paths)
, resources(&this->emplace<Resources2>())
: resources(&this->emplace<Resources2>())
, themes(&this->emplace<Theme>())
, fonts(&this->emplace<Fonts>())
@@ -118,11 +119,11 @@ void Application::initNm()
void Application::initPubsub()
{
this->twitch.pubsub->signals_.whisper.sent.connect([](const auto &msg) {
Log("WHISPER SENT LOL"); //
log("WHISPER SENT LOL"); //
});
this->twitch.pubsub->signals_.whisper.received.connect([](const auto &msg) {
Log("WHISPER RECEIVED LOL"); //
log("WHISPER RECEIVED LOL"); //
});
this->twitch.pubsub->signals_.moderation.chatCleared.connect(
+2 -4
View File
@@ -1,7 +1,6 @@
#pragma once
#include "common/Singleton.hpp"
#include "singletons/Resources.hpp"
#include <QApplication>
#include <memory>
@@ -27,9 +26,10 @@ class AccountManager;
class Emotes;
class Settings;
class Fonts;
class Resources;
class Resources2;
class Toasts;
class Application
{
std::vector<std::unique_ptr<Singleton>> singletons_;
@@ -49,8 +49,6 @@ public:
friend void test();
Settings *const settings{};
Paths *const paths{};
Resources2 *const resources;
Theme *const themes{};
+7 -9
View File
@@ -31,11 +31,9 @@ void runLoop(NativeMessagingClient &client)
char size_c[4];
std::cin.read(size_c, 4);
if (std::cin.eof()) {
break;
}
if (std::cin.eof()) break;
uint32_t size = *reinterpret_cast<uint32_t *>(size_c);
auto size = *reinterpret_cast<uint32_t *>(size_c);
#if 0
bool bigEndian = isBigEndian();
@@ -50,12 +48,12 @@ void runLoop(NativeMessagingClient &client)
}
#endif
std::unique_ptr<char[]> b(new char[size + 1]);
std::cin.read(b.get(), size);
*(b.get() + size) = '\0';
std::unique_ptr<char[]> buffer(new char[size + 1]);
std::cin.read(buffer.get(), size);
*(buffer.get() + size) = '\0';
client.sendMessage(
QByteArray::fromRawData(b.get(), static_cast<int32_t>(size)));
client.sendMessage(QByteArray::fromRawData(
buffer.get(), static_cast<int32_t>(size)));
}
}
} // namespace
+23 -23
View File
@@ -27,35 +27,35 @@ void installCustomPalette()
{
// borrowed from
// https://stackoverflow.com/questions/15035767/is-the-qt-5-dark-fusion-theme-available-for-windows
QPalette darkPalette = qApp->palette();
auto dark = qApp->palette();
darkPalette.setColor(QPalette::Window, QColor(22, 22, 22));
darkPalette.setColor(QPalette::WindowText, Qt::white);
darkPalette.setColor(QPalette::Text, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::WindowText,
dark.setColor(QPalette::Window, QColor(22, 22, 22));
dark.setColor(QPalette::WindowText, Qt::white);
dark.setColor(QPalette::Text, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::WindowText,
QColor(127, 127, 127));
darkPalette.setColor(QPalette::Base, QColor("#333"));
darkPalette.setColor(QPalette::AlternateBase, QColor("#444"));
darkPalette.setColor(QPalette::ToolTipBase, Qt::white);
darkPalette.setColor(QPalette::ToolTipText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::Text,
dark.setColor(QPalette::Base, QColor("#333"));
dark.setColor(QPalette::AlternateBase, QColor("#444"));
dark.setColor(QPalette::ToolTipBase, Qt::white);
dark.setColor(QPalette::ToolTipText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::Text,
QColor(127, 127, 127));
darkPalette.setColor(QPalette::Dark, QColor(35, 35, 35));
darkPalette.setColor(QPalette::Shadow, QColor(20, 20, 20));
darkPalette.setColor(QPalette::Button, QColor(70, 70, 70));
darkPalette.setColor(QPalette::ButtonText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::ButtonText,
dark.setColor(QPalette::Dark, QColor(35, 35, 35));
dark.setColor(QPalette::Shadow, QColor(20, 20, 20));
dark.setColor(QPalette::Button, QColor(70, 70, 70));
dark.setColor(QPalette::ButtonText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::ButtonText,
QColor(127, 127, 127));
darkPalette.setColor(QPalette::BrightText, Qt::red);
darkPalette.setColor(QPalette::Link, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218));
darkPalette.setColor(QPalette::Disabled, QPalette::Highlight,
dark.setColor(QPalette::BrightText, Qt::red);
dark.setColor(QPalette::Link, QColor(42, 130, 218));
dark.setColor(QPalette::Highlight, QColor(42, 130, 218));
dark.setColor(QPalette::Disabled, QPalette::Highlight,
QColor(80, 80, 80));
darkPalette.setColor(QPalette::HighlightedText, Qt::white);
darkPalette.setColor(QPalette::Disabled, QPalette::HighlightedText,
dark.setColor(QPalette::HighlightedText, Qt::white);
dark.setColor(QPalette::Disabled, QPalette::HighlightedText,
QColor(127, 127, 127));
qApp->setPalette(darkPalette);
qApp->setPalette(dark);
}
void initQt()
@@ -109,7 +109,7 @@ void runGui(QApplication &a, Paths &paths, Settings &settings)
chatterino::Updates::getInstance().checkForUpdates();
#ifdef C_USE_BREAKPAD
QBreakpadInstance.setDumpPath(app->paths->settingsFolderPath + "/Crashes");
QBreakpadInstance.setDumpPath(getPaths()->settingsFolderPath + "/Crashes");
#endif
// Running file
+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
+74 -171
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;
if (this->isEmote() != that.isEmote()) {
return this->isEmote();
}
// 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;
}
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;
}
// -- 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()) {
auto addString = [&](const QString &str, TaggedString::Type type) {
if (str.startsWith(prefix, Qt::CaseInsensitive))
this->items_.emplace(str + " ", type);
};
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
this->addString(emote.string,
TaggedString::Type::TwitchGlobalEmote);
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);
// }
// Usernames
if (prefix.length() >= UsernameSet::PrefixLength) {
auto usernames = channel->accessChatters();
// // 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);
for (const auto &name : usernames->subrange(Prefix(prefix))) {
addString(name, TaggedString::Type::Username);
addString("@" + name, TaggedString::Type::Username);
}
}
// Bttv Global
for (auto &emote : *channel->globalBttv().emotes()) {
addString(emote.first.string, TaggedString::Type::BTTVChannelEmote);
}
// 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
const auto &emojiShortCodes = app->emotes->emojis.shortCodes;
for (const auto &m : emojiShortCodes) {
this->addString(":" + m + ":", TaggedString::Type::Emoji);
if (prefix.startsWith(":")) {
const auto &emojiShortCodes = getApp()->emotes->emojis.shortCodes;
for (auto &m : emojiShortCodes) {
addString(":" + m + ":", TaggedString::Type::Emoji);
}
}
// Commands
for (auto &command : app->commands->items.getVector()) {
this->addString(command.name, TaggedString::Command);
for (auto &command : getApp()->commands->items.getVector()) {
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();
for (auto &command :
getApp()->commands->getDefaultTwitchCommandList()) {
addString(command, TaggedString::Command);
}
}
};
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.isExpired(now)) {
// Log("String {} expired", taggedString.str);
it = this->emotes_.erase(it);
} else {
++it;
}
}
}
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();
+27 -10
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_,
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");
// 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
@@ -1,6 +1,8 @@
#include "AccountController.hpp"
#include "controllers/accounts/Account.hpp"
#include "controllers/accounts/AccountModel.hpp"
#include "providers/twitch/TwitchAccount.hpp"
namespace chatterino {
@@ -1,16 +1,15 @@
#pragma once
#include "common/Singleton.hpp"
#include <QObject>
#include "common/SignalVector.hpp"
#include "controllers/accounts/Account.hpp"
#include "common/Singleton.hpp"
#include "providers/twitch/TwitchAccountManager.hpp"
#include "util/SharedPtrElementLess.hpp"
namespace chatterino {
class Account;
class Settings;
class Paths;
@@ -1,5 +1,6 @@
#include "AccountModel.hpp"
#include "controllers/accounts/Account.hpp"
#include "util/StandardItemHelper.hpp"
namespace chatterino {
@@ -8,6 +8,7 @@
namespace chatterino {
class Account;
class AccountController;
class AccountModel : public SignalVectorModel<std::shared_ptr<Account>>
@@ -5,7 +5,10 @@
#include "controllers/accounts/AccountController.hpp"
#include "controllers/commands/Command.hpp"
#include "controllers/commands/CommandModel.hpp"
#include "debug/Log.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "messages/MessageElement.hpp"
#include "providers/twitch/TwitchApi.hpp"
#include "providers/twitch/TwitchChannel.hpp"
#include "providers/twitch/TwitchServer.hpp"
@@ -78,7 +81,7 @@ void CommandController::save()
{
QFile textFile(this->filePath_);
if (!textFile.open(QIODevice::WriteOnly)) {
Log("[CommandController::saveCommands] Unable to open {} for writing",
log("[CommandController::saveCommands] Unable to open {} for writing",
this->filePath_);
return;
}
@@ -1,6 +1,6 @@
#pragma once
#include "common/SerializeCustom.hpp"
#include "util/RapidJsonSerializeQString.hpp"
#include "util/RapidjsonHelpers.hpp"
#include <QRegularExpression>
@@ -70,7 +70,8 @@ namespace Settings {
template <>
struct Serialize<chatterino::HighlightBlacklistUser> {
static rapidjson::Value get(const chatterino::HighlightBlacklistUser &value,
static rapidjson::Value get(
const chatterino::HighlightBlacklistUser &value,
rapidjson::Document::AllocatorType &a)
{
rapidjson::Value ret(rapidjson::kObjectType);
@@ -84,7 +85,8 @@ struct Serialize<chatterino::HighlightBlacklistUser> {
template <>
struct Deserialize<chatterino::HighlightBlacklistUser> {
static chatterino::HighlightBlacklistUser get(const rapidjson::Value &value)
static chatterino::HighlightBlacklistUser get(
const rapidjson::Value &value)
{
QString pattern;
bool isRegex = false;
@@ -1,15 +1,16 @@
#pragma once
#include "common/Singleton.hpp"
#include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp"
#include "common/Singleton.hpp"
#include "controllers/highlights/HighlightBlacklistUser.hpp"
#include "controllers/highlights/HighlightPhrase.hpp"
#include "messages/Message.hpp"
#include "singletons/Settings.hpp"
namespace chatterino {
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
class Settings;
class Paths;
+9 -12
View File
@@ -37,13 +37,13 @@ void HighlightModel::getRowFromItem(const HighlightPhrase &item,
void HighlightModel::afterInit()
{
std::vector<QStandardItem *> row = this->createRow();
setBoolItem(row[0], getApp()->settings->enableHighlightsSelf.getValue(),
true, false);
setBoolItem(row[0], getSettings()->enableHighlightsSelf.getValue(), true,
false);
row[0]->setData("Your username (automatic)", Qt::DisplayRole);
setBoolItem(row[1], getApp()->settings->enableHighlightTaskbar.getValue(),
true, false);
setBoolItem(row[2], getApp()->settings->enableHighlightSound.getValue(),
true, false);
setBoolItem(row[1], getSettings()->enableHighlightTaskbar.getValue(), true,
false);
setBoolItem(row[2], getSettings()->enableHighlightSound.getValue(), true,
false);
row[3]->setFlags(0);
this->insertCustomRow(row, 0);
}
@@ -55,20 +55,17 @@ void HighlightModel::customRowSetData(const std::vector<QStandardItem *> &row,
switch (column) {
case 0: {
if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightsSelf.setValue(
value.toBool());
getSettings()->enableHighlightsSelf.setValue(value.toBool());
}
} break;
case 1: {
if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightTaskbar.setValue(
value.toBool());
getSettings()->enableHighlightTaskbar.setValue(value.toBool());
}
} break;
case 2: {
if (role == Qt::CheckStateRole) {
getApp()->settings->enableHighlightSound.setValue(
value.toBool());
getSettings()->enableHighlightSound.setValue(value.toBool());
}
} break;
case 3: {
@@ -1,6 +1,6 @@
#pragma once
#include "common/SerializeCustom.hpp"
#include "util/RapidJsonSerializeQString.hpp"
#include "util/RapidjsonHelpers.hpp"
#include <QRegularExpression>
@@ -93,7 +93,8 @@ struct Deserialize<chatterino::HighlightPhrase> {
static chatterino::HighlightPhrase get(const rapidjson::Value &value)
{
if (!value.IsObject()) {
return chatterino::HighlightPhrase(QString(), true, false, false);
return chatterino::HighlightPhrase(QString(), true, false,
false);
}
QString _pattern;
@@ -106,7 +107,8 @@ struct Deserialize<chatterino::HighlightPhrase> {
chatterino::rj::getSafe(value, "sound", _sound);
chatterino::rj::getSafe(value, "regex", _isRegex);
return chatterino::HighlightPhrase(_pattern, _alert, _sound, _isRegex);
return chatterino::HighlightPhrase(_pattern, _alert, _sound,
_isRegex);
}
};
+2 -3
View File
@@ -1,10 +1,9 @@
#pragma once
#include "common/Singleton.hpp"
#include "common/ChatterinoSetting.hpp"
#include "common/SignalVector.hpp"
#include "common/Singleton.hpp"
#include "controllers/ignores/IgnorePhrase.hpp"
#include "singletons/Settings.hpp"
namespace chatterino {
+1 -1
View File
@@ -1,6 +1,6 @@
#pragma once
#include "common/SerializeCustom.hpp"
#include "util/RapidJsonSerializeQString.hpp"
#include "util/RapidjsonHelpers.hpp"
#include <QRegularExpression>
@@ -2,6 +2,7 @@
#include <QRegularExpression>
#include "Application.hpp"
#include "messages/Image.hpp"
#include "singletons/Resources.hpp"
namespace chatterino {
@@ -60,8 +61,7 @@ ModerationAction::ModerationAction(const QString &action)
// str);
// }
} else if (action.startsWith("/ban ")) {
this->image_ =
Image::fromNonOwningPixmap(&getApp()->resources->buttons.ban);
this->image_ = Image::fromPixmap(getApp()->resources->buttons.ban);
} else {
QString xD = action;
@@ -4,11 +4,13 @@
#include <boost/optional.hpp>
#include <pajlada/settings/serialize.hpp>
#include "messages/Image.hpp"
#include "util/RapidjsonHelpers.hpp"
namespace chatterino {
class Image;
using ImagePtr = std::shared_ptr<Image>;
class ModerationAction
{
public:
@@ -17,12 +17,16 @@ void ModerationActions::initialize(Settings &settings, Paths &paths)
assert(!this->initialized_);
this->initialized_ = true;
for (auto &val : this->setting_.getValue()) {
this->setting_ =
std::make_unique<ChatterinoSetting<std::vector<ModerationAction>>>(
"/moderation/actions");
for (auto &val : this->setting_->getValue()) {
this->items.insertItem(val);
}
this->items.delayedItemsChanged.connect([this] { //
this->setting_.setValue(this->items.getVector());
this->setting_->setValue(this->items.getVector());
});
}
@@ -25,8 +25,7 @@ public:
ModerationActionModel *createModel(QObject *parent);
private:
ChatterinoSetting<std::vector<ModerationAction>> setting_ = {
"/moderation/actions"};
std::unique_ptr<ChatterinoSetting<std::vector<ModerationAction>>> setting_;
bool initialized_ = false;
};
@@ -29,12 +29,12 @@ void TaggedUsersModel::afterInit()
{
// std::vector<QStandardItem *> row = this->createRow();
// setBoolItem(row[0],
// getApp()->settings->enableHighlightsSelf.getValue(), true, false);
// getSettings()->enableHighlightsSelf.getValue(), true, false);
// row[0]->setData("Your username (automatic)", Qt::DisplayRole);
// setBoolItem(row[1],
// getApp()->settings->enableHighlightTaskbar.getValue(), true, false);
// getSettings()->enableHighlightTaskbar.getValue(), true, false);
// setBoolItem(row[2],
// getApp()->settings->enableHighlightSound.getValue(), true, false);
// getSettings()->enableHighlightSound.getValue(), true, false);
// row[3]->setFlags(0); this->insertCustomRow(row, 0);
}
@@ -45,17 +45,17 @@ void TaggedUsersModel::afterInit()
// switch (column) {
// case 0: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightsSelf.setValue(value.toBool());
// getSettings()->enableHighlightsSelf.setValue(value.toBool());
// }
// } break;
// case 1: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightTaskbar.setValue(value.toBool());
// getSettings()->enableHighlightTaskbar.setValue(value.toBool());
// }
// } break;
// case 2: {
// if (role == Qt::CheckStateRole) {
// getApp()->settings->enableHighlightSound.setValue(value.toBool());
// getSettings()->enableHighlightSound.setValue(value.toBool());
// }
// } break;
// case 3: {
+21
View File
@@ -0,0 +1,21 @@
#include "Benchmark.hpp"
namespace chatterino {
BenchmarkGuard::BenchmarkGuard(const QString &_name)
: name_(_name)
{
timer_.start();
}
BenchmarkGuard::~BenchmarkGuard()
{
log("{} {} ms", this->name_, float(timer_.nsecsElapsed()) / 1000000.0f);
}
qreal BenchmarkGuard::getElapsedMs()
{
return qreal(timer_.nsecsElapsed()) / 1000000.0;
}
} // namespace chatterino
+8 -28
View File
@@ -1,42 +1,22 @@
#pragma once
#include <QDebug>
#include "debug/Log.hpp"
#include <QElapsedTimer>
#include <boost/current_function.hpp>
#include <boost/noncopyable.hpp>
#define BENCH(x) \
QElapsedTimer x; \
x.start();
#define MARK(x) \
qDebug() << BOOST_CURRENT_FUNCTION << __LINE__ \
<< static_cast<float>(x.nsecsElapsed()) / 1000000.0 << "ms";
namespace chatterino {
class BenchmarkGuard : boost::noncopyable
{
QElapsedTimer timer;
QString name;
public:
BenchmarkGuard(const QString &_name)
: name(_name)
{
timer.start();
}
BenchmarkGuard(const QString &_name);
~BenchmarkGuard();
qreal getElapsedMs();
~BenchmarkGuard()
{
qDebug() << this->name << float(timer.nsecsElapsed()) / 1000000.0f
<< "ms";
}
qreal getElapsedMs()
{
return qreal(timer.nsecsElapsed()) / 1000000.0;
}
private:
QElapsedTimer timer_;
QString name_;
};
} // namespace chatterino
+9 -4
View File
@@ -8,17 +8,22 @@
namespace chatterino {
template <typename... Args>
inline void Log(const std::string &formatString, Args &&... args)
inline void log(const std::string &formatString, Args &&... args)
{
qDebug().noquote() << QTime::currentTime().toString("hh:mm:ss.zzz")
<< fS(formatString, std::forward<Args>(args)...).c_str();
}
template <typename... Args>
inline void Warn(const std::string &formatString, Args &&... args)
inline void log(const char *formatString, Args &&... args)
{
qWarning() << QTime::currentTime().toString("hh:mm:ss.zzz")
<< fS(formatString, std::forward<Args>(args)...).c_str();
log(std::string(formatString), std::forward<Args>(args)...);
}
template <typename... Args>
inline void log(const QString &formatString, Args &&... args)
{
log(formatString.toStdString(), std::forward<Args>(args)...);
}
} // namespace chatterino
+4 -2
View File
@@ -5,13 +5,15 @@
#include <QApplication>
#include <QStringList>
#include <messages/Image.hpp>
#include <memory>
using namespace chatterino;
int main(int argc, char **argv)
{
auto shared = std::make_shared<QString>();
log(std::atomic_is_lock_free(&shared));
QApplication a(argc, argv);
// convert char** to QStringList
+24 -54
View File
@@ -15,61 +15,31 @@ bool operator!=(const Emote &a, const Emote &b)
return !(a == b);
}
// EmotePtr Emote::create(const EmoteData2 &data)
//{
//}
EmotePtr cachedOrMakeEmotePtr(Emote &&emote, const EmoteMap &cache)
{
// reuse old shared_ptr if nothing changed
auto it = cache.find(emote.name);
if (it != cache.end() && *it->second == emote) return it->second;
// EmotePtr Emote::create(EmoteData2 &&data)
//{
//}
return std::make_shared<Emote>(std::move(emote));
}
// Emote::Emote(EmoteData2 &&data)
// : data_(data)
//{
//}
//
// Emote::Emote(const EmoteData2 &data)
// : data_(data)
//{
//}
//
// const Url &Emote::getHomePage() const
//{
// return this->data_.homePage;
//}
//
// const EmoteName &Emote::getName() const
//{
// return this->data_.name;
//}
//
// const Tooltip &Emote::getTooltip() const
//{
// return this->data_.tooltip;
//}
//
// const ImageSet &Emote::getImages() const
//{
// return this->data_.images;
//}
//
// const QString &Emote::getCopyString() const
//{
// return this->data_.name.string;
//}
//
// bool Emote::operator==(const Emote &other) const
//{
// auto &a = this->data_;
// auto &b = other.data_;
//
// return std::tie(a.homePage, a.name, a.tooltip, a.images) ==
// std::tie(b.homePage, b.name, b.tooltip, b.images);
//}
//
// bool Emote::operator!=(const Emote &other) const
//{
// return !this->operator==(other);
//}
EmotePtr cachedOrMakeEmotePtr(
Emote &&emote,
std::unordered_map<EmoteId, std::weak_ptr<const Emote>> &cache,
std::mutex &mutex, const EmoteId &id)
{
std::lock_guard<std::mutex> guard(mutex);
auto shared = cache[id].lock();
if (shared && *shared == emote) {
// reuse old shared_ptr if nothing changed
return shared;
} else {
shared = std::make_shared<Emote>(std::move(emote));
cache[id] = shared;
return shared;
}
}
} // namespace chatterino
+5 -27
View File
@@ -7,9 +7,6 @@
#include <memory>
#include <unordered_map>
QStringAlias(EmoteId);
QStringAlias(EmoteName);
namespace chatterino {
struct Emote {
@@ -37,29 +34,10 @@ using EmoteIdMap = std::unordered_map<EmoteId, EmotePtr>;
using WeakEmoteMap = std::unordered_map<EmoteName, std::weak_ptr<const Emote>>;
using WeakEmoteIdMap = std::unordered_map<EmoteId, std::weak_ptr<const Emote>>;
// struct EmoteData2 {
// EmoteName name;
// ImageSet images;
// Tooltip tooltip;
// Url homePage;
//};
//
// class Emote
//{
// public:
// Emote(EmoteData2 &&data);
// Emote(const EmoteData2 &data);
//
// const Url &getHomePage() const;
// const EmoteName &getName() const;
// const Tooltip &getTooltip() const;
// const ImageSet &getImages() const;
// const QString &getCopyString() const;
// bool operator==(const Emote &other) const;
// bool operator!=(const Emote &other) const;
//
// private:
// EmoteData2 data_;
//};
EmotePtr cachedOrMakeEmotePtr(Emote &&emote, const EmoteMap &cache);
EmotePtr cachedOrMakeEmotePtr(
Emote &&emote,
std::unordered_map<EmoteId, std::weak_ptr<const Emote>> &cache,
std::mutex &mutex, const EmoteId &id);
} // namespace chatterino
-93
View File
@@ -1,93 +0,0 @@
#pragma once
#include <QString>
#include <boost/optional.hpp>
#include <unordered_map>
#include <util/QStringHash.hpp>
#include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp"
namespace chatterino {
template <typename TKey>
class MapReplacement
{
public:
MapReplacement(std::unordered_map<TKey, EmotePtr> &items)
: oldItems_(items)
{
}
void add(const TKey &key, const Emote &data)
{
this->add(key, Emote(data));
}
void add(const TKey &key, Emote &&data)
{
auto it = this->oldItems_.find(key);
if (it != this->oldItems_.end() && *it->second == data) {
this->newItems_[key] = it->second;
} else {
this->newItems_[key] = std::make_shared<Emote>(std::move(data));
}
}
void apply()
{
this->oldItems_ = std::move(this->newItems_);
}
private:
std::unordered_map<TKey, EmotePtr> &oldItems_;
std::unordered_map<TKey, EmotePtr> newItems_;
};
template <typename TKey>
class EmoteCache
{
public:
using Iterator = typename std::unordered_map<TKey, EmotePtr>::iterator;
using ConstIterator = typename std::unordered_map<TKey, EmotePtr>::iterator;
Iterator begin()
{
return this->items_.begin();
}
ConstIterator begin() const
{
return this->items_.begin();
}
Iterator end()
{
return this->items_.end();
}
ConstIterator end() const
{
return this->items_.end();
}
boost::optional<EmotePtr> get(const TKey &key) const
{
auto it = this->items_.find(key);
if (it == this->items_.end())
return boost::none;
else
return it->second;
}
MapReplacement<TKey> makeReplacment()
{
return MapReplacement<TKey>(this->items_);
}
private:
std::unordered_map<TKey, EmotePtr> items_;
};
} // namespace chatterino
-44
View File
@@ -1,44 +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
-20
View File
@@ -1,20 +0,0 @@
#pragma once
#include "boost/optional.hpp"
#include "messages/Emote.hpp"
namespace chatterino {
// class EmoteMap
//{
// public:
// void add(Emote emote);
// void remove(const Emote &emote);
// void remove(const QString &name);
// private:
//};
// using EmoteMap = ConcurrentMap<QString, EmoteData>;
} // namespace chatterino
+116 -77
View File
@@ -1,8 +1,10 @@
#include "messages/Image.hpp"
#include "Application.hpp"
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp"
#include "debug/AssertInGuiThread.hpp"
#include "debug/Benchmark.hpp"
#include "debug/Log.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/WindowManager.hpp"
@@ -15,37 +17,42 @@
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QTimer>
#include <functional>
#include <thread>
namespace chatterino {
namespace {
const QPixmap *getPixmap(const Pixmap &pixmap)
{
if (pixmap.which() == 0)
return boost::get<const QPixmap *>(pixmap);
else
return boost::get<std::unique_ptr<QPixmap>>(pixmap).get();
}
// Frames
Frames::Frames()
{
DebugCount::increase("images");
}
Frames::Frames(std::vector<Frame> &&frames)
: items_(std::move(frames))
Frames::Frames(const QVector<Frame<QPixmap>> &frames)
: items_(frames)
{
assertInGuiThread();
DebugCount::increase("images");
if (this->animated()) DebugCount::increase("animated images");
if (this->animated()) {
DebugCount::increase("animated images");
this->gifTimerConnection_ =
getApp()->emotes->gifTimer.signal.connect(
[this] { this->advance(); });
}
}
Frames::~Frames()
{
assertInGuiThread();
DebugCount::decrease("images");
if (this->animated()) DebugCount::decrease("animated images");
if (this->animated()) {
DebugCount::decrease("animated images");
}
this->gifTimerConnection_.disconnect();
}
void Frames::advance()
@@ -54,6 +61,11 @@ void Frames::advance()
while (true) {
this->index_ %= this->items_.size();
if (this->index_ >= this->items_.size()) {
this->index_ = this->index_;
}
if (this->durationOffset_ > this->items_[this->index_].duration) {
this->durationOffset_ -= this->items_[this->index_].duration;
this->index_ = (this->index_ + 1) % this->items_.size();
@@ -68,25 +80,25 @@ bool Frames::animated() const
return this->items_.size() > 1;
}
const QPixmap *Frames::current() const
boost::optional<QPixmap> Frames::current() const
{
if (this->items_.size() == 0) return nullptr;
return getPixmap(this->items_[this->index_].pixmap);
if (this->items_.size() == 0) return boost::none;
return this->items_[this->index_].image;
}
const QPixmap *Frames::first() const
boost::optional<QPixmap> Frames::first() const
{
if (this->items_.size() == 0) return nullptr;
return getPixmap(this->items_.front().pixmap);
if (this->items_.size() == 0) return boost::none;
return this->items_.front().image;
}
// functions
std::vector<Frame> readFrames(QImageReader &reader, const Url &url)
QVector<Frame<QImage>> readFrames(QImageReader &reader, const Url &url)
{
std::vector<Frame> frames;
QVector<Frame<QImage>> frames;
if (reader.imageCount() <= 0) {
Log("Error while reading image {}: '{}'", url.string,
if (reader.imageCount() == 0) {
log("Error while reading image {}: '{}'", url.string,
reader.errorString());
return frames;
}
@@ -94,40 +106,82 @@ std::vector<Frame> readFrames(QImageReader &reader, const Url &url)
QImage image;
for (int index = 0; index < reader.imageCount(); ++index) {
if (reader.read(&image)) {
auto pixmap = std::make_unique<QPixmap>(QPixmap::fromImage(image));
QPixmap::fromImage(image);
int duration = std::max(20, reader.nextImageDelay());
frames.push_back(Frame{std::move(pixmap), duration});
frames.push_back(Frame<QImage>{image, duration});
}
}
if (frames.size() != 0) {
Log("Error while reading image {}: '{}'", url.string,
if (frames.size() == 0) {
log("Error while reading image {}: '{}'", url.string,
reader.errorString());
}
return frames;
}
void queueLoadedEvent()
// parsed
template <typename Assign>
void assignDelayed(
std::queue<std::pair<Assign, QVector<Frame<QPixmap>>>> &queued,
std::mutex &mutex, std::atomic_bool &loadedEventQueued)
{
static auto eventQueued = false;
std::lock_guard<std::mutex> lock(mutex);
int i = 0;
if (!eventQueued) {
eventQueued = true;
while (!queued.empty()) {
queued.front().first(queued.front().second);
queued.pop();
QTimer::singleShot(250, [] {
getApp()->windows->incGeneration();
getApp()->windows->layoutChannelViews();
eventQueued = false;
if (++i > 50) {
QTimer::singleShot(3, [&] {
assignDelayed(queued, mutex, loadedEventQueued);
});
return;
}
}
getApp()->windows->forceLayoutChannelViews();
loadedEventQueued = false;
}
template <typename Assign>
auto makeConvertCallback(const QVector<Frame<QImage>> &parsed,
Assign assign)
{
return [parsed, assign] {
// convert to pixmap
auto frames = QVector<Frame<QPixmap>>();
std::transform(parsed.begin(), parsed.end(),
std::back_inserter(frames), [](auto &frame) {
return Frame<QPixmap>{
QPixmap::fromImage(frame.image),
frame.duration};
});
// put into stack
static std::queue<std::pair<Assign, QVector<Frame<QPixmap>>>>
queued;
static std::mutex mutex;
std::lock_guard<std::mutex> lock(mutex);
queued.emplace(assign, frames);
static std::atomic_bool loadedEventQueued{false};
if (!loadedEventQueued) {
loadedEventQueued = true;
QTimer::singleShot(100, [=] {
assignDelayed(queued, mutex, loadedEventQueued);
});
}
};
}
} // namespace
// IMAGE2
std::atomic<bool> Image::loadedEventQueued{false};
ImagePtr Image::fromUrl(const Url &url, qreal scale)
{
static std::unordered_map<Url, std::weak_ptr<Image>> cache;
@@ -140,18 +194,13 @@ ImagePtr Image::fromUrl(const Url &url, qreal scale)
if (!shared) {
cache[url] = shared = ImagePtr(new Image(url, scale));
} else {
Warn("same image loaded multiple times: {}", url.string);
// Warn("same image loaded multiple times: {}", url.string);
}
return shared;
}
ImagePtr Image::fromOwningPixmap(std::unique_ptr<QPixmap> pixmap, qreal scale)
{
return ImagePtr(new Image(std::move(pixmap), scale));
}
ImagePtr Image::fromNonOwningPixmap(QPixmap *pixmap, qreal scale)
ImagePtr Image::fromPixmap(const QPixmap &pixmap, qreal scale)
{
return ImagePtr(new Image(pixmap, scale));
}
@@ -171,23 +220,15 @@ Image::Image(const Url &url, qreal scale)
: url_(url)
, scale_(scale)
, shouldLoad_(true)
, frames_(std::make_unique<Frames>())
{
}
Image::Image(std::unique_ptr<QPixmap> owning, qreal scale)
Image::Image(const QPixmap &pixmap, qreal scale)
: scale_(scale)
, frames_(std::make_unique<Frames>(
QVector<Frame<QPixmap>>{Frame<QPixmap>{pixmap, 1}}))
{
std::vector<Frame> vec;
vec.push_back(Frame{std::move(owning)});
this->frames_ = std::move(vec);
}
Image::Image(QPixmap *nonOwning, qreal scale)
: scale_(scale)
{
std::vector<Frame> vec;
vec.push_back(Frame{nonOwning});
this->frames_ = std::move(vec);
}
const Url &Image::url() const
@@ -195,7 +236,7 @@ const Url &Image::url() const
return this->url_;
}
const QPixmap *Image::pixmap() const
boost::optional<QPixmap> Image::pixmap() const
{
assertInGuiThread();
@@ -204,7 +245,7 @@ const QPixmap *Image::pixmap() const
const_cast<Image *>(this)->load();
}
return this->frames_.current();
return this->frames_->current();
}
qreal Image::scale() const
@@ -212,7 +253,7 @@ qreal Image::scale() const
return this->scale_;
}
bool Image::empty() const
bool Image::isEmpty() const
{
return this->empty_;
}
@@ -221,14 +262,14 @@ bool Image::animated() const
{
assertInGuiThread();
return this->frames_.animated();
return this->frames_->animated();
}
int Image::width() const
{
assertInGuiThread();
if (auto pixmap = this->frames_.first())
if (auto pixmap = this->frames_->first())
return pixmap->width() * this->scale_;
else
return 16;
@@ -238,7 +279,7 @@ int Image::height() const
{
assertInGuiThread();
if (auto pixmap = this->frames_.first())
if (auto pixmap = this->frames_->first())
return pixmap->height() * this->scale_;
else
return 16;
@@ -247,39 +288,37 @@ int Image::height() const
void Image::load()
{
NetworkRequest req(this->url().string);
req.setExecuteConcurrently(true);
req.setCaller(&this->object_);
req.setUseQuickLoadCache(true);
req.onSuccess([this, weak = weakOf(this)](auto result) -> Outcome {
assertInGuiThread();
req.onSuccess([that = this, weak = weakOf(this)](auto result) -> Outcome {
auto shared = weak.lock();
if (!shared) return Failure;
auto data = result.getData();
// const cast since we are only reading from it
QBuffer buffer(const_cast<QByteArray *>(&result.getData()));
QBuffer buffer(const_cast<QByteArray *>(&data));
buffer.open(QIODevice::ReadOnly);
QImageReader reader(&buffer);
auto parsed = readFrames(reader, that->url());
postToThread(makeConvertCallback(parsed, [weak](auto frames) {
if (auto shared = weak.lock())
shared->frames_ = std::make_unique<Frames>(frames);
}));
this->frames_ = readFrames(reader, this->url());
return Success;
});
req.onError([this, weak = weakOf(this)](int) {
auto shared = weak.lock();
if (!shared) return false;
this->frames_ = std::vector<Frame>();
return false;
});
req.execute();
}
bool Image::operator==(const Image &other) const
{
if (this->empty() && other.empty()) return true;
if (this->isEmpty() && other.isEmpty()) return true;
if (!this->url_.string.isEmpty() && this->url_ == other.url_) return true;
if (this->frames_.first() == other.frames_.first()) return true;
if (this->frames_->first() == other.frames_->first()) return true;
return false;
}
+18 -21
View File
@@ -1,42 +1,44 @@
#pragma once
#include "common/Common.hpp"
#include <QPixmap>
#include <QString>
#include <QThread>
#include <QVector>
#include <atomic>
#include <boost/noncopyable.hpp>
#include <boost/optional.hpp>
#include <boost/variant.hpp>
#include <memory>
#include <mutex>
#include <pajlada/signals/signal.hpp>
#include "common/Aliases.hpp"
#include "common/NullablePtr.hpp"
namespace chatterino {
namespace {
using Pixmap = boost::variant<const QPixmap *, std::unique_ptr<QPixmap>>;
template <typename Image>
struct Frame {
Pixmap pixmap;
Image image;
int duration;
};
class Frames
class Frames : boost::noncopyable
{
public:
Frames();
Frames(std::vector<Frame> &&frames);
Frames(const QVector<Frame<QPixmap>> &frames);
~Frames();
Frames(Frames &&other) = default;
Frames &operator=(Frames &&other) = default;
bool animated() const;
void advance();
const QPixmap *current() const;
const QPixmap *first() const;
boost::optional<QPixmap> current() const;
boost::optional<QPixmap> first() const;
private:
std::vector<Frame> items_;
QVector<Frame<QPixmap>> items_;
int index_{0};
int durationOffset_{0};
pajlada::Signals::Connection gifTimerConnection_;
};
} // namespace
@@ -47,15 +49,13 @@ class Image : public std::enable_shared_from_this<Image>, boost::noncopyable
{
public:
static ImagePtr fromUrl(const Url &url, qreal scale = 1);
static ImagePtr fromOwningPixmap(std::unique_ptr<QPixmap> pixmap,
qreal scale = 1);
static ImagePtr fromNonOwningPixmap(QPixmap *pixmap, qreal scale = 1);
static ImagePtr fromPixmap(const QPixmap &pixmap, qreal scale = 1);
static ImagePtr getEmpty();
const Url &url() const;
const QPixmap *pixmap() const;
boost::optional<QPixmap> pixmap() const;
qreal scale() const;
bool empty() const;
bool isEmpty() const;
int width() const;
int height() const;
bool animated() const;
@@ -66,8 +66,7 @@ public:
private:
Image();
Image(const Url &url, qreal scale);
Image(std::unique_ptr<QPixmap> owning, qreal scale);
Image(QPixmap *nonOwning, qreal scale);
Image(const QPixmap &nonOwning, qreal scale);
void load();
@@ -75,9 +74,7 @@ private:
qreal scale_{1};
bool empty_{false};
bool shouldLoad_{false};
Frames frames_{};
std::unique_ptr<Frames> frames_{};
QObject object_{};
static std::atomic<bool> loadedEventQueued;
};
} // namespace chatterino
+7 -13
View File
@@ -1,7 +1,5 @@
#include "ImageSet.hpp"
#include "messages/ImageSet.hpp"
#include "Application.hpp"
#include "singletons/Resources.hpp"
#include "singletons/Settings.hpp"
namespace chatterino {
@@ -60,23 +58,19 @@ const ImagePtr &ImageSet::getImage3() const
const ImagePtr &ImageSet::getImage(float scale) const
{
int quality = getSettings()->preferredEmoteQuality;
int quality = 1;
if (!quality) {
if (scale > 3.999)
if (scale > 2.999)
quality = 3;
else if (scale > 1.999)
else if (scale > 1.5)
quality = 2;
else
scale = 1;
}
if (!this->imageX3_->empty() && quality == 3) {
if (!this->imageX3_->isEmpty() && quality == 3) {
return this->imageX3_;
}
if (!this->imageX2_->empty() && quality == 2) {
return this->imageX3_;
if (!this->imageX2_->isEmpty() && quality == 2) {
return this->imageX2_;
}
return this->imageX1_;
-2
View File
@@ -21,8 +21,6 @@ public:
const ImagePtr &getImage(float scale) const;
ImagePtr getImage(float scale);
bool operator==(const ImageSet &other) const;
bool operator!=(const ImageSet &other) const;
+1 -1
View File
@@ -1,7 +1,6 @@
#pragma once
#include <QString>
#include <common/Common.hpp>
namespace chatterino {
@@ -14,6 +13,7 @@ public:
UserInfo,
UserTimeout,
UserBan,
UserWhisper,
InsertText,
ShowMessage,
UserAction,
+2 -2
View File
@@ -1,16 +1,16 @@
#pragma once
#include "common/FlagsEnum.hpp"
#include "messages/MessageElement.hpp"
#include "providers/twitch/PubsubActions.hpp"
#include "widgets/helper/ScrollbarHighlight.hpp"
#include <QTime>
#include <boost/noncopyable.hpp>
#include <cinttypes>
#include <memory>
#include <vector>
namespace chatterino {
class MessageElement;
enum class MessageFlag : uint16_t {
None = 0,
+9 -2
View File
@@ -1,6 +1,9 @@
#include "MessageBuilder.hpp"
#include "common/LinkParser.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include "providers/twitch/PubsubActions.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Resources.hpp"
#include "singletons/Theme.hpp"
@@ -17,7 +20,7 @@ MessagePtr makeSystemMessage(const QString &text)
}
MessageBuilder::MessageBuilder()
: message_(std::make_unique<Message>())
: message_(std::make_shared<Message>())
{
}
@@ -82,6 +85,7 @@ MessageBuilder::MessageBuilder(TimeoutMessageTag, const QString &username,
}
MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
: MessageBuilder()
{
this->emplace<TimestampElement>();
this->message().flags.set(MessageFlag::System);
@@ -127,6 +131,7 @@ MessageBuilder::MessageBuilder(const BanAction &action, uint32_t count)
}
MessageBuilder::MessageBuilder(const UnbanAction &action)
: MessageBuilder()
{
this->emplace<TimestampElement>();
this->message().flags.set(MessageFlag::System);
@@ -163,7 +168,9 @@ Message &MessageBuilder::message()
MessagePtr MessageBuilder::release()
{
return MessagePtr(this->message_.release());
std::shared_ptr<Message> ptr;
this->message_.swap(ptr);
return ptr;
}
void MessageBuilder::append(std::unique_ptr<MessageElement> element)
+14 -2
View File
@@ -1,11 +1,15 @@
#pragma once
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include <QRegularExpression>
#include <ctime>
namespace chatterino {
struct BanAction;
struct UnbanAction;
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
struct SystemMessageTag {
};
@@ -16,6 +20,14 @@ const TimeoutMessageTag timeoutMessage{};
MessagePtr makeSystemMessage(const QString &text);
struct MessageParseArgs {
bool disablePingSounds = false;
bool isReceivedWhisper = false;
bool isSentWhisper = false;
bool trimSubscriberUsername = false;
bool isStaffOrBroadcaster = false;
};
class MessageBuilder
{
public:
@@ -48,7 +60,7 @@ public:
}
private:
std::unique_ptr<Message> message_;
std::shared_ptr<Message> message_;
};
} // namespace chatterino
+2
View File
@@ -1,5 +1,7 @@
#include "MessageColor.hpp"
#include "singletons/Theme.hpp"
namespace chatterino {
MessageColor::MessageColor(const QColor &color)
+1 -2
View File
@@ -1,10 +1,9 @@
#pragma once
#include "singletons/Theme.hpp"
#include <QColor>
namespace chatterino {
class Theme;
struct MessageColor {
enum Type { Custom, Text, Link, System };
+9
View File
@@ -0,0 +1,9 @@
#include "MessageContainer.hpp"
namespace chatterino {
MessageContainer::MessageContainer()
{
}
} // namespace chatterino
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <deque>
namespace chatterino {
class MessageContainer
{
public:
MessageContainer();
};
} // namespace chatterino
+6 -5
View File
@@ -1,12 +1,13 @@
#include "messages/MessageElement.hpp"
#include "Application.hpp"
#include "common/Emotemap.hpp"
#include "controllers/moderationactions/ModerationActions.hpp"
#include "debug/Benchmark.hpp"
#include "messages/Emote.hpp"
#include "messages/layouts/MessageLayoutContainer.hpp"
#include "messages/layouts/MessageLayoutElement.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "util/DebugCount.hpp"
namespace chatterino {
@@ -102,7 +103,7 @@ void EmoteElement::addToContainer(MessageLayoutContainer &container,
if (flags.hasAny(this->getFlags())) {
if (flags.has(MessageElementFlag::EmoteImages)) {
auto image = this->emote_->images.getImage(container.getScale());
if (image->empty()) return;
if (image->isEmpty()) return;
auto size = QSize(int(container.getScale() * image->width()),
int(container.getScale() * image->height()));
@@ -224,8 +225,8 @@ void TimestampElement::addToContainer(MessageLayoutContainer &container,
{
if (flags.hasAny(this->getFlags())) {
auto app = getApp();
if (app->settings->timestampFormat != this->format_) {
this->format_ = app->settings->timestampFormat.getValue();
if (getSettings()->timestampFormat != this->format_) {
this->format_ = getSettings()->timestampFormat.getValue();
this->element_.reset(this->formatTime(this->time_));
}
@@ -237,7 +238,7 @@ TextElement *TimestampElement::formatTime(const QTime &time)
{
static QLocale locale("en_US");
QString format = locale.toString(time, getApp()->settings->timestampFormat);
QString format = locale.toString(time, getSettings()->timestampFormat);
return new TextElement(format, MessageElementFlag::Timestamp,
MessageColor::System, FontStyle::ChatMedium);
+7 -4
View File
@@ -1,9 +1,6 @@
#pragma once
#include "common/Emotemap.hpp"
#include "common/FlagsEnum.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "messages/Link.hpp"
#include "messages/MessageColor.hpp"
#include "singletons/Fonts.hpp"
@@ -12,14 +9,20 @@
#include <QString>
#include <QTime>
#include <boost/noncopyable.hpp>
#include <cstdint>
#include <memory>
#include <vector>
namespace chatterino {
class Channel;
struct MessageLayoutContainer;
class Image;
using ImagePtr = std::shared_ptr<Image>;
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
enum class MessageElementFlag {
None = 0,
Misc = (1 << 0),
-8
View File
@@ -2,12 +2,4 @@
namespace chatterino {
struct MessageParseArgs {
bool disablePingSounds = false;
bool isReceivedWhisper = false;
bool isSentWhisper = false;
bool trimSubscriberUsername = false;
bool isStaffOrBroadcaster = false;
};
} // namespace chatterino
+3 -8
View File
@@ -1,5 +1,6 @@
#pragma once
#include <tuple>
#include <utility>
namespace chatterino {
@@ -23,14 +24,8 @@ struct SelectionItem {
bool operator<(const SelectionItem &b) const
{
if (this->messageIndex < b.messageIndex) {
return true;
}
if (this->messageIndex == b.messageIndex &&
this->charIndex < b.charIndex) {
return true;
}
return false;
return std::tie(this->messageIndex, this->charIndex) <
std::tie(b.messageIndex, b.charIndex);
}
bool operator>(const SelectionItem &b) const
+33 -26
View File
@@ -2,8 +2,12 @@
#include "Application.hpp"
#include "debug/Benchmark.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include "messages/layouts/MessageLayoutContainer.hpp"
#include "singletons/Emotes.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include "singletons/WindowManager.hpp"
#include "util/DebugCount.hpp"
@@ -24,6 +28,7 @@ namespace chatterino {
MessageLayout::MessageLayout(MessagePtr message)
: message_(message)
, buffer_(nullptr)
, container_(std::make_shared<MessageLayoutContainer>())
{
DebugCount::increase("message layout");
}
@@ -41,7 +46,7 @@ const Message *MessageLayout::getMessage()
// Height
int MessageLayout::getHeight() const
{
return container_.getHeight();
return container_->getHeight();
}
// Layout
@@ -68,7 +73,7 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags)
// check if work mask changed
layoutRequired |= this->currentWordFlags_ != flags;
this->currentWordFlags_ = flags; // app->settings->getWordTypeMask();
this->currentWordFlags_ = flags; // getSettings()->getWordTypeMask();
// check if layout was requested manually
layoutRequired |= this->flags.has(MessageLayoutFlag::RequiresLayout);
@@ -82,9 +87,9 @@ bool MessageLayout::layout(int width, float scale, MessageElementFlags flags)
return false;
}
int oldHeight = this->container_.getHeight();
int oldHeight = this->container_->getHeight();
this->actuallyLayout(width, flags);
if (widthChanged || this->container_.getHeight() != oldHeight) {
if (widthChanged || this->container_->getHeight() != oldHeight) {
this->deleteBuffer();
}
this->invalidateBuffer();
@@ -103,22 +108,22 @@ void MessageLayout::actuallyLayout(int width, MessageElementFlags _flags)
messageFlags.unset(MessageFlag::Collapsed);
}
this->container_.begin(width, this->scale_, messageFlags);
this->container_->begin(width, this->scale_, messageFlags);
for (const auto &element : this->message_->elements) {
element->addToContainer(this->container_, _flags);
element->addToContainer(*this->container_, _flags);
}
if (this->height_ != this->container_.getHeight()) {
if (this->height_ != this->container_->getHeight()) {
this->deleteBuffer();
}
this->container_.end();
this->height_ = this->container_.getHeight();
this->container_->end();
this->height_ = this->container_->getHeight();
// collapsed state
this->flags.unset(MessageLayoutFlag::Collapsed);
if (this->container_.isCollapsed()) {
if (this->container_->isCollapsed()) {
this->flags.set(MessageLayoutFlag::Collapsed);
}
}
@@ -135,11 +140,12 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
if (!pixmap) {
#ifdef Q_OS_MACOS
pixmap = new QPixmap(int(width * painter.device()->devicePixelRatioF()),
int(container_.getHeight() *
int(container_->getHeight() *
painter.device()->devicePixelRatioF()));
pixmap->setDevicePixelRatio(painter.device()->devicePixelRatioF());
#else
pixmap = new QPixmap(width, std::max(16, this->container_.getHeight()));
pixmap =
new QPixmap(width, std::max(16, this->container_->getHeight()));
#endif
this->buffer_ = std::shared_ptr<QPixmap>(pixmap);
@@ -157,7 +163,7 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
// this->container.getHeight(), *pixmap);
// draw gif emotes
this->container_.paintAnimatedElements(painter, y);
this->container_->paintAnimatedElements(painter, y);
// draw disabled
if (this->message_->flags.has(MessageFlag::Disabled)) {
@@ -167,12 +173,12 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
// draw selection
if (!selection.isEmpty()) {
this->container_.paintSelection(painter, messageIndex, selection, y);
this->container_->paintSelection(painter, messageIndex, selection, y);
}
// draw message seperation line
if (app->settings->separateMessages.getValue()) {
painter.fillRect(0, y, this->container_.getWidth(), 1,
if (getSettings()->separateMessages.getValue()) {
painter.fillRect(0, y, this->container_->getWidth(), 1,
app->themes->splits.messageSeperator);
}
@@ -184,9 +190,9 @@ void MessageLayout::paint(QPainter &painter, int width, int y, int messageIndex,
: app->themes->tabs.selected.backgrounds.unfocused.color();
QBrush brush(color, static_cast<Qt::BrushStyle>(
app->settings->lastMessagePattern.getValue()));
getSettings()->lastMessagePattern.getValue()));
painter.fillRect(0, y + this->container_.getHeight() - 1,
painter.fillRect(0, y + this->container_->getHeight() - 1,
pixmap->width(), 1, brush);
}
@@ -208,7 +214,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
backgroundColor = app->themes->messages.backgrounds.highlighted;
} else if (this->message_->flags.has(MessageFlag::Subscription)) {
backgroundColor = app->themes->messages.backgrounds.subscription;
} else if (app->settings->alternateMessageBackground.getValue() &&
} else if (getSettings()->alternateMessageBackground.getValue() &&
this->flags.has(MessageLayoutFlag::AlternateBackground)) {
backgroundColor = app->themes->messages.backgrounds.alternate;
} else {
@@ -217,7 +223,7 @@ void MessageLayout::updateBuffer(QPixmap *buffer, int /*messageIndex*/,
painter.fillRect(buffer->rect(), backgroundColor);
// draw message
this->container_.paintElements(painter);
this->container_->paintElements(painter);
#ifdef FOURTF
// debug
@@ -252,7 +258,7 @@ void MessageLayout::deleteCache()
this->deleteBuffer();
#ifdef XD
this->container_.clear();
this->container_->clear();
#endif
}
@@ -265,22 +271,23 @@ void MessageLayout::deleteCache()
const MessageLayoutElement *MessageLayout::getElementAt(QPoint point)
{
// go through all words and return the first one that contains the point.
return this->container_.getElementAt(point);
return this->container_->getElementAt(point);
}
int MessageLayout::getLastCharacterIndex() const
{
return this->container_.getLastCharacterIndex();
return this->container_->getLastCharacterIndex();
}
int MessageLayout::getSelectionIndex(QPoint position)
{
return this->container_.getSelectionIndex(position);
return this->container_->getSelectionIndex(position);
}
void MessageLayout::addSelectionText(QString &str, int from, int to)
void MessageLayout::addSelectionText(QString &str, int from, int to,
CopyMode copymode)
{
this->container_.addSelectionText(str, from, to);
this->container_->addSelectionText(str, from, to, copymode);
}
} // namespace chatterino
+14 -10
View File
@@ -1,19 +1,25 @@
#pragma once
#include "common/Common.hpp"
#include "common/FlagsEnum.hpp"
#include "messages/Message.hpp"
#include "messages/Selection.hpp"
#include "messages/layouts/MessageLayoutContainer.hpp"
#include "messages/layouts/MessageLayoutElement.hpp"
#include <QPixmap>
#include <boost/noncopyable.hpp>
#include <cinttypes>
#include <memory>
namespace chatterino {
struct Message;
using MessagePtr = std::shared_ptr<const Message>;
struct Selection;
struct MessageLayoutContainer;
class MessageLayoutElement;
enum class MessageElementFlag;
using MessageElementFlags = FlagsEnum<MessageElementFlag>;
enum class MessageLayoutFlag : uint8_t {
RequiresBufferUpdate = 1 << 1,
RequiresLayout = 1 << 2,
@@ -31,13 +37,10 @@ public:
const Message *getMessage();
// Height
int getHeight() const;
// Flags
MessageLayoutFlags flags;
// Layout
bool layout(int width, float scale_, MessageElementFlags flags);
// Painting
@@ -52,7 +55,8 @@ public:
const MessageLayoutElement *getElementAt(QPoint point);
int getLastCharacterIndex() const;
int getSelectionIndex(QPoint position);
void addSelectionText(QString &str, int from = 0, int to = INT_MAX);
void addSelectionText(QString &str, int from = 0, int to = INT_MAX,
CopyMode copymode = CopyMode::Everything);
// Misc
bool isDisabled() const;
@@ -60,7 +64,7 @@ public:
private:
// variables
MessagePtr message_;
MessageLayoutContainer container_;
std::shared_ptr<MessageLayoutContainer> container_;
std::shared_ptr<QPixmap> buffer_ = nullptr;
bool bufferValid_ = false;
+27 -14
View File
@@ -1,16 +1,20 @@
#include "MessageLayoutContainer.hpp"
#include "Application.hpp"
#include "MessageLayoutElement.hpp"
#include "messages/Message.hpp"
#include "messages/MessageElement.hpp"
#include "messages/Selection.hpp"
#include "messages/layouts/MessageLayoutElement.hpp"
#include "singletons/Fonts.hpp"
#include "singletons/Settings.hpp"
#include "singletons/Theme.hpp"
#include <QDebug>
#include <QPainter>
#define COMPACT_EMOTES_OFFSET 6
#define MAX_UNCOLLAPSED_LINES \
(getApp()->settings->collpseMessagesMinLines.getValue())
(getSettings()->collpseMessagesMinLines.getValue())
namespace chatterino {
@@ -126,7 +130,8 @@ void MessageLayoutContainer::breakLine()
int xOffset = 0;
if (this->flags_.has(MessageFlag::Centered) && this->elements_.size() > 0) {
xOffset = (width_ - this->elements_.at(this->elements_.size() - 1)
xOffset = (width_ - this->elements_.at(0)->getRect().left() -
this->elements_.at(this->elements_.size() - 1)
->getRect()
.right()) /
2;
@@ -230,7 +235,7 @@ void MessageLayoutContainer::end()
bool MessageLayoutContainer::canCollapse()
{
return getApp()->settings->collpseMessagesMinLines.getValue() > 0 &&
return getSettings()->collpseMessagesMinLines.getValue() > 0 &&
this->flags_.has(MessageFlag::Collapsed);
}
@@ -500,33 +505,41 @@ int MessageLayoutContainer::getLastCharacterIndex() const
return this->lines_.back().endCharIndex;
}
void MessageLayoutContainer::addSelectionText(QString &str, int from, int to)
void MessageLayoutContainer::addSelectionText(QString &str, int from, int to,
CopyMode copymode)
{
int index = 0;
bool first = true;
for (std::unique_ptr<MessageLayoutElement> &ele : this->elements_) {
int c = ele->getSelectionIndexCount();
for (auto &element : this->elements_) {
if (copymode == CopyMode::OnlyTextAndEmotes) {
if (element->getCreator().getFlags().hasAny(
{MessageElementFlag::Timestamp,
MessageElementFlag::Username, MessageElementFlag::Badges}))
continue;
}
auto indexCount = element->getSelectionIndexCount();
if (first) {
if (index + c > from) {
ele->addCopyTextToString(str, from - index, to - index);
if (index + indexCount > from) {
element->addCopyTextToString(str, from - index, to - index);
first = false;
if (index + c > to) {
if (index + indexCount > to) {
break;
}
}
} else {
if (index + c > to) {
ele->addCopyTextToString(str, 0, to - index);
if (index + indexCount > to) {
element->addCopyTextToString(str, 0, to - index);
break;
} else {
ele->addCopyTextToString(str);
element->addCopyTextToString(str);
}
}
index += c;
index += indexCount;
}
}
@@ -1,18 +1,21 @@
#pragma once
#include <memory>
#include <vector>
#include <QPoint>
#include <QRect>
#include <memory>
#include <vector>
#include "messages/Message.hpp"
#include "common/Common.hpp"
#include "common/FlagsEnum.hpp"
#include "messages/Selection.hpp"
#include "messages/layouts/MessageLayoutElement.hpp"
class QPainter;
namespace chatterino {
class MessageLayoutElement;
enum class MessageFlag : uint16_t;
using MessageFlags = FlagsEnum<MessageFlag>;
struct Margin {
int top;
@@ -72,7 +75,7 @@ struct MessageLayoutContainer {
// selection
int getSelectionIndex(QPoint point);
int getLastCharacterIndex() const;
void addSelectionText(QString &str, int from, int to);
void addSelectionText(QString &str, int from, int to, CopyMode copymode);
bool isCollapsed();
@@ -92,7 +95,7 @@ private:
// variables
float scale_ = 1.f;
int width_ = 0;
MessageFlags flags_ = MessageFlag::None;
MessageFlags flags_{};
int line_ = 0;
int height_ = 0;
int currentX_ = 0;
@@ -1,7 +1,10 @@
#include "messages/layouts/MessageLayoutElement.hpp"
#include "Application.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "messages/MessageElement.hpp"
#include "singletons/Theme.hpp"
#include "util/DebugCount.hpp"
#include <QDebug>
@@ -75,13 +78,14 @@ ImageLayoutElement::ImageLayoutElement(MessageElement &creator, ImagePtr image,
void ImageLayoutElement::addCopyTextToString(QString &str, int from,
int to) const
{
// str += this->image_->getCopyString();
str += "not implemented";
const auto *emoteElement = dynamic_cast<EmoteElement *>(&this->getCreator());
if (emoteElement) {
str += emoteElement->getEmote()->getCopyString();
if (this->hasTrailingSpace()) {
str += " ";
}
}
}
int ImageLayoutElement::getSelectionIndexCount()
{
@@ -3,19 +3,19 @@
#include <QPoint>
#include <QRect>
#include <QString>
#include <boost/noncopyable.hpp>
#include <climits>
#include "messages/Image.hpp"
#include "messages/Link.hpp"
#include "messages/MessageColor.hpp"
#include "singletons/Fonts.hpp"
class QPainter;
namespace chatterino {
class MessageElement;
class Image;
using ImagePtr = std::shared_ptr<Image>;
enum class FontStyle : uint8_t;
class MessageLayoutElement : boost::noncopyable
{
+112 -70
View File
@@ -1,7 +1,9 @@
#include "providers/bttv/BttvEmotes.hpp"
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
#include "messages/ImageSet.hpp"
#include "providers/twitch/TwitchChannel.hpp"
@@ -10,9 +12,7 @@
#include <QThread>
namespace chatterino {
namespace {
Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
{
@@ -21,74 +21,18 @@ Url getEmoteLink(QString urlTemplate, const EmoteId &id,
return {urlTemplate.replace("{{id}}", id.string)
.replace("{{image}}", emoteScale)};
}
} // namespace
AccessGuard<const EmoteMap> BttvEmotes::accessGlobalEmotes() const
{
return this->globalEmotes_.accessConst();
}
boost::optional<EmotePtr> BttvEmotes::getGlobalEmote(const EmoteName &name)
{
auto emotes = this->globalEmotes_.access();
auto it = emotes->find(name);
if (it == emotes->end()) return boost::none;
return it->second;
}
// FOURTF: never returns anything
// boost::optional<EmotePtr> BttvEmotes::getEmote(const EmoteId &id)
//{
// auto cache = this->channelEmoteCache_.access();
// auto it = cache->find(id);
//
// if (it != cache->end()) {
// auto shared = it->second.lock();
// if (shared) {
// return shared;
// }
// }
//
// return boost::none;
//}
void BttvEmotes::loadGlobalEmotes()
{
auto request = NetworkRequest(QString(globalEmoteApiUrl));
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
request.onSuccess([this](auto result) -> Outcome {
// if (auto shared = weak.lock()) {
auto currentEmotes = this->globalEmotes_.access();
auto pair = this->parseGlobalEmotes(result.parseJson(), *currentEmotes);
if (pair.first) {
*currentEmotes = std::move(pair.second);
}
return pair.first;
// }
return Failure;
});
request.execute();
}
std::pair<Outcome, EmoteMap> BttvEmotes::parseGlobalEmotes(
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes)
{
auto emotes = EmoteMap();
auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate =
QString("https:" + jsonRoot.value("urlTemplate").toString());
qS("https:") + jsonRoot.value("urlTemplate").toString();
for (const QJsonValue &jsonEmote : jsonEmotes) {
for (auto jsonEmote : jsonEmotes) {
auto id = EmoteId{jsonEmote.toObject().value("id").toString()};
auto name = EmoteName{jsonEmote.toObject().value("code").toString()};
auto name =
EmoteName{jsonEmote.toObject().value("code").toString()};
auto emote = Emote(
{name,
@@ -99,16 +43,114 @@ std::pair<Outcome, EmoteMap> BttvEmotes::parseGlobalEmotes(
Tooltip{name.string + "<br />Global Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
auto it = currentEmotes.find(name);
if (it != currentEmotes.end() && *it->second == emote) {
// reuse old shared_ptr if nothing changed
emotes[name] = it->second;
} else {
emotes[name] = std::make_shared<Emote>(std::move(emote));
}
emotes[name] =
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
}
return {Success, std::move(emotes)};
}
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
{
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
static std::mutex mutex;
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
}
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
{
auto emotes = EmoteMap();
auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate = "https:" + jsonRoot.value("urlTemplate").toString();
for (auto jsonEmote_ : jsonEmotes) {
auto jsonEmote = jsonEmote_.toObject();
auto id = EmoteId{jsonEmote.value("id").toString()};
auto name = EmoteName{jsonEmote.value("code").toString()};
// emoteObject.value("imageType").toString();
auto emote = Emote(
{name,
ImageSet{
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
Tooltip{name.string + "<br />Channel Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
emotes[name] = cachedOrMake(std::move(emote), id);
}
return {Success, std::move(emotes)};
}
} // namespace
//
// BttvEmotes
//
BttvEmotes::BttvEmotes()
: global_(std::make_shared<EmoteMap>())
{
}
std::shared_ptr<const EmoteMap> BttvEmotes::emotes() const
{
return this->global_.get();
}
boost::optional<EmotePtr> BttvEmotes::emote(const EmoteName &name) const
{
auto emotes = this->global_.get();
auto it = emotes->find(name);
if (it == emotes->end()) return boost::none;
return it->second;
}
void BttvEmotes::loadEmotes()
{
auto request = NetworkRequest(QString(globalEmoteApiUrl));
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
request.onSuccess([this](auto result) -> Outcome {
auto emotes = this->global_.get();
auto pair = parseGlobalEmotes(result.parseJson(), *emotes);
if (pair.first)
this->global_.set(
std::make_shared<EmoteMap>(std::move(pair.second)));
return pair.first;
});
request.execute();
}
void BttvEmotes::loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback)
{
auto request =
NetworkRequest(QString(bttvChannelEmoteApiUrl) + channelName);
request.setCaller(QThread::currentThread());
request.setTimeout(3000);
request.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
auto pair = parseChannelEmotes(result.parseJson());
if (pair.first) callback(std::move(pair.second));
return pair.first;
});
request.execute();
}
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
{
urlTemplate.detach();
return {urlTemplate.replace("{{id}}", id.string)
.replace("{{image}}", emoteScale)};
}
} // namespace chatterino
+17 -16
View File
@@ -1,33 +1,34 @@
#pragma once
#include <memory>
#include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp"
#include "messages/EmoteCache.hpp"
#include "boost/optional.hpp"
#include "common/Aliases.hpp"
#include "common/Atomic.hpp"
namespace chatterino {
class BttvEmotes final : std::enable_shared_from_this<BttvEmotes>
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
class EmoteMap;
class BttvEmotes final
{
static constexpr const char *globalEmoteApiUrl =
"https://api.betterttv.net/2/emotes";
static constexpr const char *bttvChannelEmoteApiUrl =
"https://api.betterttv.net/2/channels/";
public:
// BttvEmotes();
BttvEmotes();
AccessGuard<const EmoteMap> accessGlobalEmotes() const;
boost::optional<EmotePtr> getGlobalEmote(const EmoteName &name);
boost::optional<EmotePtr> getEmote(const EmoteId &id);
void loadGlobalEmotes();
std::shared_ptr<const EmoteMap> emotes() const;
boost::optional<EmotePtr> emote(const EmoteName &name) const;
void loadEmotes();
static void loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback);
private:
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes);
UniqueAccess<EmoteMap> globalEmotes_;
// UniqueAccess<WeakEmoteIdMap> channelEmoteCache_;
Atomic<std::shared_ptr<const EmoteMap>> global_;
};
} // namespace chatterino
@@ -11,78 +11,4 @@
namespace chatterino {
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale);
static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(
const QJsonObject &jsonRoot);
void loadBttvChannelEmotes(const QString &channelName,
std::function<void(EmoteMap &&)> callback)
{
auto request =
NetworkRequest(QString(bttvChannelEmoteApiUrl) + channelName);
request.setCaller(QThread::currentThread());
request.setTimeout(3000);
request.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
auto pair = bttvParseChannelEmotes(result.parseJson());
if (pair.first == Success) callback(std::move(pair.second));
return pair.first;
});
request.execute();
}
static std::pair<Outcome, EmoteMap> bttvParseChannelEmotes(
const QJsonObject &jsonRoot)
{
static UniqueAccess<std::unordered_map<EmoteId, std::weak_ptr<const Emote>>>
cache_;
auto cache = cache_.access();
auto emotes = EmoteMap();
auto jsonEmotes = jsonRoot.value("emotes").toArray();
auto urlTemplate =
QString("https:" + jsonRoot.value("urlTemplate").toString());
for (auto jsonEmote_ : jsonEmotes) {
auto jsonEmote = jsonEmote_.toObject();
auto id = EmoteId{jsonEmote.value("id").toString()};
auto name = EmoteName{jsonEmote.value("code").toString()};
// emoteObject.value("imageType").toString();
auto emote = Emote(
{name,
ImageSet{
Image::fromUrl(getEmoteLink(urlTemplate, id, "1x"), 1),
Image::fromUrl(getEmoteLink(urlTemplate, id, "2x"), 0.5),
Image::fromUrl(getEmoteLink(urlTemplate, id, "3x"), 0.25)},
Tooltip{name.string + "<br />Channel Bttv Emote"},
Url{"https://manage.betterttv.net/emotes/" + id.string}});
auto shared = (*cache)[id].lock();
if (shared && *shared == emote) {
// reuse old shared_ptr if nothing changed
emotes[name] = shared;
} else {
(*cache)[id] = emotes[name] =
std::make_shared<Emote>(std::move(emote));
}
}
return {Success, std::move(emotes)};
}
static Url getEmoteLink(QString urlTemplate, const EmoteId &id,
const QString &emoteScale)
{
urlTemplate.detach();
return {urlTemplate.replace("{{id}}", id.string)
.replace("{{image}}", emoteScale)};
}
} // namespace chatterino
@@ -6,11 +6,4 @@ class QString;
namespace chatterino {
class EmoteMap;
constexpr const char *bttvChannelEmoteApiUrl =
"https://api.betterttv.net/2/channels/";
void loadBttvChannelEmotes(const QString &channelName,
std::function<void(EmoteMap &&)> callback);
} // namespace chatterino
+24 -22
View File
@@ -14,39 +14,41 @@ ChatterinoBadges::ChatterinoBadges()
boost::optional<EmotePtr> ChatterinoBadges::getBadge(const UserName &username)
{
return this->badges.access()->get(username);
return boost::none;
// return this->badges.access()->get(username);
}
void ChatterinoBadges::loadChatterinoBadges()
{
static QString url("https://fourtf.com/chatterino/badges.json");
// static QString url("https://fourtf.com/chatterino/badges.json");
NetworkRequest req(url);
req.setCaller(QThread::currentThread());
// NetworkRequest req(url);
// req.setCaller(QThread::currentThread());
req.onSuccess([this](auto result) {
auto jsonRoot = result.parseJson();
auto badges = this->badges.access();
auto replacement = badges->makeReplacment();
// req.onSuccess([this](auto result) {
// auto jsonRoot = result.parseJson();
// auto badges = this->badges.access();
// auto replacement = badges->makeReplacment();
for (auto jsonBadge_ : jsonRoot.value("badges").toArray()) {
auto jsonBadge = jsonBadge_.toObject();
// for (auto jsonBadge_ : jsonRoot.value("badges").toArray()) {
// auto jsonBadge = jsonBadge_.toObject();
auto emote = Emote{
EmoteName{}, ImageSet{Url{jsonBadge.value("image").toString()}},
Tooltip{jsonBadge.value("tooltip").toString()}, Url{}};
// auto emote = Emote{
// EmoteName{},
// ImageSet{Url{jsonBadge.value("image").toString()}},
// Tooltip{jsonBadge.value("tooltip").toString()}, Url{}};
for (auto jsonUser : jsonBadge.value("users").toArray()) {
replacement.add(UserName{jsonUser.toString()},
std::move(emote));
}
}
// for (auto jsonUser : jsonBadge.value("users").toArray()) {
// replacement.add(UserName{jsonUser.toString()},
// std::move(emote));
// }
// }
replacement.apply();
return Success;
});
// replacement.apply();
// return Success;
//});
req.execute();
// req.execute();
}
} // namespace chatterino
@@ -1,14 +1,14 @@
#pragma once
#include <boost/optional.hpp>
#include <unordered_map>
#include "common/Common.hpp"
#include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp"
#include "messages/EmoteCache.hpp"
#include "common/Aliases.hpp"
namespace chatterino {
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
class ChatterinoBadges
{
public:
@@ -19,7 +19,7 @@ public:
private:
void loadChatterinoBadges();
UniqueAccess<EmoteCache<UserName>> badges;
// UniqueAccess<EmoteCache<UserName>> badges;
};
} // namespace chatterino
+13 -30
View File
@@ -2,6 +2,7 @@
#include "Application.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "singletons/Settings.hpp"
#include <rapidjson/error/en.h>
@@ -12,9 +13,7 @@
#include <memory>
namespace chatterino {
namespace {
void parseEmoji(const std::shared_ptr<EmojiData> &emojiData,
const rapidjson::Value &unparsedEmoji,
QString shortCode = QString())
@@ -39,7 +38,8 @@ void parseEmoji(const std::shared_ptr<EmojiData> &emojiData,
}
}
rj::getSafe(unparsedEmoji, "non_qualified", emojiData->nonQualifiedCode);
rj::getSafe(unparsedEmoji, "non_qualified",
emojiData->nonQualifiedCode);
rj::getSafe(unparsedEmoji, "unified", emojiData->unifiedCode);
rj::getSafe(unparsedEmoji, "has_img_apple", capabilities.apple);
@@ -70,7 +70,8 @@ void parseEmoji(const std::shared_ptr<EmojiData> &emojiData,
QStringList unicodeCharacters;
if (!emojiData->nonQualifiedCode.isEmpty()) {
unicodeCharacters = emojiData->nonQualifiedCode.toLower().split('-');
unicodeCharacters =
emojiData->nonQualifiedCode.toLower().split('-');
} else {
unicodeCharacters = emojiData->unifiedCode.toLower().split('-');
}
@@ -87,7 +88,6 @@ void parseEmoji(const std::shared_ptr<EmojiData> &emojiData,
emojiData->value = QString::fromUcs4(unicodeBytes, numUnicodeBytes);
}
} // namespace
void Emojis::load()
@@ -103,12 +103,10 @@ void Emojis::load()
void Emojis::loadEmojis()
{
std::map<std::string, QString> toneNames;
toneNames["1F3FB"] = "tone1";
toneNames["1F3FC"] = "tone2";
toneNames["1F3FD"] = "tone3";
toneNames["1F3FE"] = "tone4";
toneNames["1F3FF"] = "tone5";
auto toneNames = std::map<std::string, QString>{
{"1F3FB", "tone1"}, {"1F3FC", "tone2"}, {"1F3FD", "tone3"},
{"1F3FE", "tone4"}, {"1F3FF", "tone5"},
};
QFile file(":/emoji.json");
file.open(QFile::ReadOnly);
@@ -118,7 +116,7 @@ void Emojis::loadEmojis()
rapidjson::ParseResult result = root.Parse(data.toUtf8(), data.length());
if (result.Code() != rapidjson::kParseErrorNone) {
Log("JSON parse error: {} ({})",
log("JSON parse error: {} ({})",
rapidjson::GetParseError_En(result.Code()), result.Offset());
return;
}
@@ -146,7 +144,7 @@ void Emojis::loadEmojis()
auto toneNameIt = toneNames.find(tone);
if (toneNameIt == toneNames.end()) {
Log("Tone with key {} does not exist in tone names map",
log("Tone with key {} does not exist in tone names map",
tone);
continue;
}
@@ -218,8 +216,8 @@ void Emojis::loadEmojiSet()
{
auto app = getApp();
app->settings->emojiSet.connect([=](const auto &emojiSet, auto) {
Log("Using emoji set {}", emojiSet);
getSettings()->emojiSet.connect([=](const auto &emojiSet, auto) {
log("Using emoji set {}", emojiSet);
this->emojis.each([=](const auto &name,
std::shared_ptr<EmojiData> &emoji) {
QString emojiSetToUse = emojiSet;
@@ -233,27 +231,12 @@ void Emojis::loadEmojiSet()
// {"Google", "https://cdn.jsdelivr.net/npm/emoji-datasource-google@4.0.4/img/google/64/"},
// {"Messenger", "https://cdn.jsdelivr.net/npm/emoji-datasource-messenger@4.0.4/img/messenger/64/"},
// {"EmojiOne 2", "https://cdnjs.cloudflare.com/ajax/libs/emojione/2.2.6/assets/png/"},
// {"EmojiOne 3", "https://braize.pajlada.com/emoji/img/emojione/64/"},
// {"Twitter", "https://braize.pajlada.com/emoji/img/twitter/64/"},
// {"Facebook", "https://braize.pajlada.com/emoji/img/facebook/64/"},
// {"Apple", "https://braize.pajlada.com/emoji/img/apple/64/"},
// {"Google", "https://braize.pajlada.com/emoji/img/google/64/"},
// {"Messenger", "https://braize.pajlada.com/emoji/img/messenger/64/"},
{"EmojiOne 3", "https://pajbot.com/static/emoji/img/emojione/64/"},
{"Twitter", "https://pajbot.com/static/emoji/img/twitter/64/"},
{"Facebook", "https://pajbot.com/static/emoji/img/facebook/64/"},
{"Apple", "https://pajbot.com/static/emoji/img/apple/64/"},
{"Google", "https://pajbot.com/static/emoji/img/google/64/"},
{"Messenger", "https://pajbot.com/static/emoji/img/messenger/64/"},
// {"EmojiOne 3", "https://cdn.fourtf.com/emoji/emojione/64/"},
// {"Twitter", "https://cdn.fourtf.com/emoji/twitter/64/"},
// {"Facebook", "https://cdn.fourtf.com/emoji/facebook/64/"},
// {"Apple", "https://cdn.fourtf.com/emoji/apple/64/"},
// {"Google", "https://cdn.fourtf.com/emoji/google/64/"},
// {"Messenger", "https://cdn.fourtf.com/emoji/messenger/64/"},
};
// clang-format on
+3 -3
View File
@@ -1,8 +1,5 @@
#pragma once
#include "common/Emotemap.hpp"
#include "common/SimpleSignalVector.hpp"
#include "messages/Emote.hpp"
#include "util/ConcurrentMap.hpp"
#include <QMap>
@@ -14,6 +11,9 @@
namespace chatterino {
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
struct EmojiData {
// actual byte-representation of the emoji (i.e. \154075\156150 which is
// :male:)
+97 -105
View File
@@ -3,7 +3,9 @@
#include <QJsonArray>
#include "common/NetworkRequest.hpp"
#include "common/Outcome.hpp"
#include "debug/Log.hpp"
#include "messages/Emote.hpp"
#include "messages/Image.hpp"
namespace chatterino {
@@ -19,7 +21,6 @@ Url getEmoteLink(const QJsonObject &urls, const QString &emoteScale)
return {"https:" + emote.toString()};
}
void fillInEmoteData(const QJsonObject &urls, const EmoteName &name,
const QString &tooltip, Emote &emoteData)
{
@@ -34,52 +35,18 @@ void fillInEmoteData(const QJsonObject &urls, const EmoteName &name,
Image::fromUrl(url3x, 0.25)};
emoteData.tooltip = {tooltip};
}
} // namespace
AccessGuard<const EmoteCache<EmoteName>> FfzEmotes::accessGlobalEmotes() const
EmotePtr cachedOrMake(Emote &&emote, const EmoteId &id)
{
return this->globalEmotes_.accessConst();
static std::unordered_map<EmoteId, std::weak_ptr<const Emote>> cache;
static std::mutex mutex;
return cachedOrMakeEmotePtr(std::move(emote), cache, mutex, id);
}
boost::optional<EmotePtr> FfzEmotes::getEmote(const EmoteId &id)
{
auto cache = this->channelEmoteCache_.access();
auto it = cache->find(id);
if (it != cache->end()) {
auto shared = it->second.lock();
if (shared) {
return shared;
}
}
return boost::none;
}
boost::optional<EmotePtr> FfzEmotes::getGlobalEmote(const EmoteName &name)
{
return this->globalEmotes_.access()->get(name);
}
void FfzEmotes::loadGlobalEmotes()
{
QString url("https://api.frankerfacez.com/v1/set/global");
NetworkRequest request(url);
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
request.onSuccess([this](auto result) -> Outcome {
return this->parseGlobalEmotes(result.parseJson());
});
request.execute();
}
Outcome FfzEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot)
std::pair<Outcome, EmoteMap> parseGlobalEmotes(
const QJsonObject &jsonRoot, const EmoteMap &currentEmotes)
{
auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = this->globalEmotes_.access();
auto replacement = emotes->makeReplacment();
auto emotes = EmoteMap();
for (auto jsonSet : jsonSets) {
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
@@ -92,83 +59,108 @@ Outcome FfzEmotes::parseGlobalEmotes(const QJsonObject &jsonRoot)
auto urls = jsonEmote.value("urls").toObject();
auto emote = Emote();
fillInEmoteData(urls, name, name.string + "<br/>Global FFZ Emote",
emote);
fillInEmoteData(urls, name,
name.string + "<br/>Global FFZ Emote", emote);
emote.homePage =
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
.arg(id.string)
.arg(name.string)};
replacement.add(name, emote);
emotes[name] =
cachedOrMakeEmotePtr(std::move(emote), currentEmotes);
}
}
return Success;
return {Success, std::move(emotes)};
}
std::pair<Outcome, EmoteMap> parseChannelEmotes(const QJsonObject &jsonRoot)
{
auto jsonSets = jsonRoot.value("sets").toObject();
auto emotes = EmoteMap();
for (auto jsonSet : jsonSets) {
auto jsonEmotes = jsonSet.toObject().value("emoticons").toArray();
for (auto _jsonEmote : jsonEmotes) {
auto jsonEmote = _jsonEmote.toObject();
// margins
auto id =
EmoteId{QString::number(jsonEmote.value("id").toInt())};
auto name = EmoteName{jsonEmote.value("name").toString()};
auto urls = jsonEmote.value("urls").toObject();
Emote emote;
fillInEmoteData(urls, name,
name.string + "<br/>Channel FFZ Emote", emote);
emote.homePage =
Url{QString("https://www.frankerfacez.com/emoticon/%1-%2")
.arg(id.string)
.arg(name.string)};
emotes[name] = cachedOrMake(std::move(emote), id);
}
}
void FfzEmotes::loadChannelEmotes(const QString &channelName,
return {Success, std::move(emotes)};
}
} // namespace
FfzEmotes::FfzEmotes()
: global_(std::make_shared<EmoteMap>())
{
}
std::shared_ptr<const EmoteMap> FfzEmotes::emotes() const
{
return this->global_.get();
}
boost::optional<EmotePtr> FfzEmotes::emote(const EmoteName &name) const
{
auto emotes = this->global_.get();
auto it = emotes->find(name);
if (it != emotes->end()) return it->second;
return boost::none;
}
void FfzEmotes::loadEmotes()
{
QString url("https://api.frankerfacez.com/v1/set/global");
NetworkRequest request(url);
request.setCaller(QThread::currentThread());
request.setTimeout(30000);
request.onSuccess([this](auto result) -> Outcome {
auto emotes = this->emotes();
auto pair = parseGlobalEmotes(result.parseJson(), *emotes);
if (pair.first)
this->global_.set(
std::make_shared<EmoteMap>(std::move(pair.second)));
return pair.first;
});
request.execute();
}
void FfzEmotes::loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback)
{
// printf("[FFZEmotes] Reload FFZ Channel Emotes for channel %s\n",
// qPrintable(channelName));
log("[FFZEmotes] Reload FFZ Channel Emotes for channel %s\n", channelName);
// QString url("https://api.frankerfacez.com/v1/room/" + channelName);
NetworkRequest request("https://api.frankerfacez.com/v1/room/" +
channelName);
request.setCaller(QThread::currentThread());
request.setTimeout(3000);
// NetworkRequest request(url);
// request.setCaller(QThread::currentThread());
// request.setTimeout(3000);
// request.onSuccess([this, channelName, _map](auto result) -> Outcome {
// return this->parseChannelEmotes(result.parseJson());
//});
request.onSuccess([callback = std::move(callback)](auto result) -> Outcome {
auto pair = parseChannelEmotes(result.parseJson());
if (pair.first) callback(std::move(pair.second));
return pair.first;
});
// request.execute();
}
Outcome parseChannelEmotes(const QJsonObject &jsonRoot)
{
// auto rootNode = result.parseJson();
// auto map = _map.lock();
// if (_map.expired()) {
// return false;
//}
// map->clear();
// auto setsNode = rootNode.value("sets").toObject();
// std::vector<QString> codes;
// for (const QJsonValue &setNode : setsNode) {
// auto emotesNode = setNode.toObject().value("emoticons").toArray();
// for (const QJsonValue &emoteNode : emotesNode) {
// QJsonObject emoteObject = emoteNode.toObject();
// // margins
// int id = emoteObject.value("id").toInt();
// QString code = emoteObject.value("name").toString();
// QJsonObject urls = emoteObject.value("urls").toObject();
// auto emote = this->channelEmoteCache_.getOrAdd(id, [id, &code,
// &urls] {
// EmoteData emoteData;
// fillInEmoteData(urls, code, code + "<br/>Channel FFZ Emote",
// emoteData); emoteData.pageLink =
// QString("https://www.frankerfacez.com/emoticon/%1-%2").arg(id).arg(code);
// return emoteData;
// });
// this->channelEmotes.insert(code, emote);
// map->insert(code, emote);
// codes.push_back(code);
// }
// this->channelEmoteCodes[channelName] = codes;
//}
return Success;
request.execute();
}
} // namespace chatterino
+15 -22
View File
@@ -1,39 +1,32 @@
#pragma once
#include <memory>
#include "common/UniqueAccess.hpp"
#include "messages/Emote.hpp"
#include "messages/EmoteCache.hpp"
#include "boost/optional.hpp"
#include "common/Aliases.hpp"
#include "common/Atomic.hpp"
namespace chatterino {
class FfzEmotes final : std::enable_shared_from_this<FfzEmotes>
struct Emote;
using EmotePtr = std::shared_ptr<const Emote>;
class EmoteMap;
class FfzEmotes final
{
static constexpr const char *globalEmoteApiUrl =
"https://api.frankerfacez.com/v1/set/global";
static constexpr const char *channelEmoteApiUrl =
"https://api.betterttv.net/2/channels/";
public:
// FfzEmotes();
FfzEmotes();
static std::shared_ptr<FfzEmotes> create();
AccessGuard<const EmoteCache<EmoteName>> accessGlobalEmotes() const;
boost::optional<EmotePtr> getGlobalEmote(const EmoteName &name);
boost::optional<EmotePtr> getEmote(const EmoteId &id);
void loadGlobalEmotes();
void loadChannelEmotes(const QString &channelName,
std::shared_ptr<const EmoteMap> emotes() const;
boost::optional<EmotePtr> emote(const EmoteName &name) const;
void loadEmotes();
static void loadChannel(const QString &channelName,
std::function<void(EmoteMap &&)> callback);
protected:
Outcome parseGlobalEmotes(const QJsonObject &jsonRoot);
Outcome parseChannelEmotes(const QJsonObject &jsonRoot);
UniqueAccess<EmoteCache<EmoteName>> globalEmotes_;
UniqueAccess<WeakEmoteIdMap> channelEmoteCache_;
private:
Atomic<std::shared_ptr<const EmoteMap>> global_;
};
} // namespace chatterino
+3 -1
View File
@@ -1,6 +1,8 @@
#include "AbstractIrcServer.hpp"
#include "common/Channel.hpp"
#include "common/Common.hpp"
#include "debug/Log.hpp"
#include "messages/LimitedQueueSnapshot.hpp"
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
@@ -131,7 +133,7 @@ std::shared_ptr<Channel> AbstractIrcServer::getOrAddChannel(
chan->destroyed.connect([this, clojuresInCppAreShit] {
// fourtf: issues when the server itself is destroyed
Log("[AbstractIrcServer::addChannel] {} was destroyed",
log("[AbstractIrcServer::addChannel] {} was destroyed",
clojuresInCppAreShit);
this->channels.remove(clojuresInCppAreShit);
+3 -1
View File
@@ -1,6 +1,5 @@
#pragma once
#include "common/Channel.hpp"
#include "providers/irc/IrcConnection2.hpp"
#include <IrcMessage>
@@ -11,6 +10,9 @@
namespace chatterino {
class Channel;
using ChannelPtr = std::shared_ptr<Channel>;
class AbstractIrcServer
{
public:
+6 -5
View File
@@ -10,6 +10,7 @@
#include "providers/twitch/TwitchMessageBuilder.hpp"
#include "providers/twitch/TwitchServer.hpp"
#include "singletons/Resources.hpp"
#include "singletons/Settings.hpp"
#include "singletons/WindowManager.hpp"
#include "util/IrcHelpers.hpp"
@@ -145,7 +146,7 @@ void IrcMessageHandler::handleClearChatMessage(Communi::IrcMessage *message)
auto chan = app->twitch.server->getChannelOrEmpty(chanName);
if (chan->isEmpty()) {
Log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
log("[IrcMessageHandler:handleClearChatMessage] Twitch channel {} not "
"found",
chanName);
return;
@@ -209,7 +210,7 @@ void IrcMessageHandler::handleUserStateMessage(Communi::IrcMessage *message)
void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
{
auto app = getApp();
Log("Received whisper!");
log("Received whisper!");
MessageParseArgs args;
args.isReceivedWhisper = true;
@@ -230,7 +231,7 @@ void IrcMessageHandler::handleWhisperMessage(Communi::IrcMessage *message)
c->addMessage(_message);
if (app->settings->inlineWhispers) {
if (getSettings()->inlineWhispers) {
app->twitch.server->forEachChannel([_message](ChannelPtr channel) {
channel->addMessage(_message); //
});
@@ -326,7 +327,7 @@ void IrcMessageHandler::handleNoticeMessage(Communi::IrcNoticeMessage *message)
auto channel = app->twitch.server->getChannelOrEmpty(channelName);
if (channel->isEmpty()) {
Log("[IrcManager:handleNoticeMessage] Channel {} not found in channel "
log("[IrcManager:handleNoticeMessage] Channel {} not found in channel "
"manager ",
channelName);
return;
@@ -366,7 +367,7 @@ void IrcMessageHandler::handleWriteConnectionNoticeMessage(
return;
}
Log("Showing notice message from write connection with message id '{}'",
log("Showing notice message from write connection with message id '{}'",
msgID);
}
+5 -4
View File
@@ -1,5 +1,6 @@
#include "providers/twitch/PartialTwitchUser.hpp"
#include "common/Common.hpp"
#include "common/NetworkRequest.hpp"
#include "debug/Log.hpp"
#include "providers/twitch/TwitchCommon.hpp"
@@ -42,23 +43,23 @@ void PartialTwitchUser::getId(std::function<void(QString)> successCallback,
request.onSuccess([successCallback](auto result) -> Outcome {
auto root = result.parseJson();
if (!root.value("users").isArray()) {
Log("API Error while getting user id, users is not an array");
log("API Error while getting user id, users is not an array");
return Failure;
}
auto users = root.value("users").toArray();
if (users.size() != 1) {
Log("API Error while getting user id, users array size is not 1");
log("API Error while getting user id, users array size is not 1");
return Failure;
}
if (!users[0].isObject()) {
Log("API Error while getting user id, first user is not an object");
log("API Error while getting user id, first user is not an object");
return Failure;
}
auto firstUser = users[0].toObject();
auto id = firstUser.value("_id");
if (!id.isString()) {
Log("API Error: while getting user id, first user object `_id` key "
log("API Error: while getting user id, first user object `_id` key "
"is not a "
"string");
return Failure;
+50 -48
View File
@@ -79,7 +79,8 @@ void PubSubClient::unlistenPrefix(const std::string &prefix)
{
std::vector<std::string> topics;
for (auto it = this->listeners_.begin(); it != this->listeners_.end();) {
for (auto it = this->listeners_.begin();
it != this->listeners_.end();) {
const auto &listener = *it;
if (listener.topic.find(prefix) == 0) {
topics.push_back(listener.topic);
@@ -109,7 +110,7 @@ void PubSubClient::handlePong()
{
assert(this->awaitingPong_);
Log("Got pong!");
log("Got pong!");
this->awaitingPong_ = false;
}
@@ -137,20 +138,21 @@ void PubSubClient::ping()
auto self = this->shared_from_this();
runAfter(this->websocketClient_.get_io_service(), std::chrono::seconds(15),
[self](auto timer) {
runAfter(this->websocketClient_.get_io_service(),
std::chrono::seconds(15), [self](auto timer) {
if (!self->started_) {
return;
}
if (self->awaitingPong_) {
Log("No pong respnose, disconnect!");
// TODO(pajlada): Label this connection as "disconnect me"
log("No pong respnose, disconnect!");
// TODO(pajlada): Label this connection as "disconnect
// me"
}
});
runAfter(this->websocketClient_.get_io_service(), std::chrono::minutes(5),
[self](auto timer) {
runAfter(this->websocketClient_.get_io_service(),
std::chrono::minutes(5), [self](auto timer) {
if (!self->started_) {
return;
}
@@ -166,9 +168,9 @@ bool PubSubClient::send(const char *payload)
websocketpp::frame::opcode::text, ec);
if (ec) {
Log("Error sending message {}: {}", payload, ec.message());
// TODO(pajlada): Check which error code happened and maybe gracefully
// handle it
log("Error sending message {}: {}", payload, ec.message());
// TODO(pajlada): Check which error code happened and maybe
// gracefully handle it
return false;
}
@@ -207,26 +209,26 @@ PubSub::PubSub()
action.state = ModeChangedAction::State::On;
if (!data.HasMember("args")) {
Log("Missing required args member");
log("Missing required args member");
return;
}
const auto &args = data["args"];
if (!args.IsArray()) {
Log("args member must be an array");
log("args member must be an array");
return;
}
if (args.Size() == 0) {
Log("Missing duration argument in slowmode on");
log("Missing duration argument in slowmode on");
return;
}
const auto &durationArg = args[0];
if (!durationArg.IsString()) {
Log("Duration arg must be a string");
log("Duration arg must be a string");
return;
}
@@ -314,7 +316,7 @@ PubSub::PubSub()
return;
}
} catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what());
log("Error parsing moderation action: {}", ex.what());
}
action.modded = false;
@@ -339,7 +341,7 @@ PubSub::PubSub()
return;
}
} catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what());
log("Error parsing moderation action: {}", ex.what());
}
action.modded = true;
@@ -380,7 +382,7 @@ PubSub::PubSub()
this->signals_.moderation.userBanned.invoke(action);
} catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what());
log("Error parsing moderation action: {}", ex.what());
}
};
@@ -410,7 +412,7 @@ PubSub::PubSub()
this->signals_.moderation.userBanned.invoke(action);
} catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what());
log("Error parsing moderation action: {}", ex.what());
}
};
@@ -436,7 +438,7 @@ PubSub::PubSub()
this->signals_.moderation.userUnbanned.invoke(action);
} catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what());
log("Error parsing moderation action: {}", ex.what());
}
};
@@ -462,7 +464,7 @@ PubSub::PubSub()
this->signals_.moderation.userUnbanned.invoke(action);
} catch (const std::runtime_error &ex) {
Log("Error parsing moderation action: {}", ex.what());
log("Error parsing moderation action: {}", ex.what());
}
};
@@ -493,7 +495,7 @@ void PubSub::addClient()
auto con = this->websocketClient.get_connection(TWITCH_PUBSUB_URL, ec);
if (ec) {
Log("Unable to establish connection: {}", ec.message());
log("Unable to establish connection: {}", ec.message());
return;
}
@@ -512,7 +514,7 @@ void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
std::string userID = account->getUserId().toStdString();
Log("Connection open!");
log("Connection open!");
websocketpp::lib::error_code ec;
std::vector<std::string> topics({"whispers." + userID});
@@ -520,7 +522,7 @@ void PubSub::listenToWhispers(std::shared_ptr<TwitchAccount> account)
this->listen(createListenMessage(topics, account));
if (ec) {
Log("Unable to send message to websocket server: {}", ec.message());
log("Unable to send message to websocket server: {}", ec.message());
return;
}
}
@@ -544,11 +546,11 @@ void PubSub::listenToChannelModerationActions(
std::string topic(fS("chat_moderator_actions.{}.{}", userID, channelID));
if (this->isListeningToTopic(topic)) {
Log("We are already listening to topic {}", topic);
log("We are already listening to topic {}", topic);
return;
}
Log("Listen to topic {}", topic);
log("Listen to topic {}", topic);
this->listenToTopic(topic, account);
}
@@ -564,18 +566,18 @@ void PubSub::listenToTopic(const std::string &topic,
void PubSub::listen(rapidjson::Document &&msg)
{
if (this->tryListen(msg)) {
Log("Successfully listened!");
log("Successfully listened!");
return;
}
Log("Added to the back of the queue");
log("Added to the back of the queue");
this->requests.emplace_back(
std::make_unique<rapidjson::Document>(std::move(msg)));
}
bool PubSub::tryListen(rapidjson::Document &msg)
{
Log("tryListen with {} clients", this->clients.size());
log("tryListen with {} clients", this->clients.size());
for (const auto &p : this->clients) {
const auto &client = p.second;
if (client->listen(msg)) {
@@ -608,13 +610,13 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
rapidjson::ParseResult res = msg.Parse(payload.c_str());
if (!res) {
Log("Error parsing message '{}' from PubSub: {}", payload,
log("Error parsing message '{}' from PubSub: {}", payload,
rapidjson::GetParseError_En(res.Code()));
return;
}
if (!msg.IsObject()) {
Log("Error parsing message '{}' from PubSub. Root object is not an "
log("Error parsing message '{}' from PubSub. Root object is not an "
"object",
payload);
return;
@@ -623,7 +625,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
std::string type;
if (!rj::getSafe(msg, "type", type)) {
Log("Missing required string member `type` in message root");
log("Missing required string member `type` in message root");
return;
}
@@ -631,14 +633,14 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
this->handleListenResponse(msg);
} else if (type == "MESSAGE") {
if (!msg.HasMember("data")) {
Log("Missing required object member `data` in message root");
log("Missing required object member `data` in message root");
return;
}
const auto &data = msg["data"];
if (!data.IsObject()) {
Log("Member `data` must be an object");
log("Member `data` must be an object");
return;
}
@@ -654,7 +656,7 @@ void PubSub::onMessage(websocketpp::connection_hdl hdl,
client.second->handlePong();
} else {
Log("Unknown message type: {}", type);
log("Unknown message type: {}", type);
}
}
@@ -699,7 +701,7 @@ PubSub::WebsocketContextPtr PubSub::onTLSInit(websocketpp::connection_hdl hdl)
boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::single_dh_use);
} catch (const std::exception &e) {
Log("Exception caught in OnTLSInit: {}", e.what());
log("Exception caught in OnTLSInit: {}", e.what());
}
return ctx;
@@ -714,12 +716,12 @@ void PubSub::handleListenResponse(const rapidjson::Document &msg)
rj::getSafe(msg, "nonce", nonce);
if (error.empty()) {
Log("Successfully listened to nonce {}", nonce);
log("Successfully listened to nonce {}", nonce);
// Nothing went wrong
return;
}
Log("PubSub error: {} on nonce {}", error, nonce);
log("PubSub error: {} on nonce {}", error, nonce);
return;
}
}
@@ -729,14 +731,14 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
QString topic;
if (!rj::getSafe(outerData, "topic", topic)) {
Log("Missing required string member `topic` in outerData");
log("Missing required string member `topic` in outerData");
return;
}
std::string payload;
if (!rj::getSafe(outerData, "message", payload)) {
Log("Expected string message in outerData");
log("Expected string message in outerData");
return;
}
@@ -745,7 +747,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
rapidjson::ParseResult res = msg.Parse(payload.c_str());
if (!res) {
Log("Error parsing message '{}' from PubSub: {}", payload,
log("Error parsing message '{}' from PubSub: {}", payload,
rapidjson::GetParseError_En(res.Code()));
return;
}
@@ -754,7 +756,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
std::string whisperType;
if (!rj::getSafe(msg, "type", whisperType)) {
Log("Bad whisper data");
log("Bad whisper data");
return;
}
@@ -765,7 +767,7 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
} else if (whisperType == "thread") {
// Handle thread?
} else {
Log("Invalid whisper type: {}", whisperType);
log("Invalid whisper type: {}", whisperType);
assert(false);
return;
}
@@ -777,30 +779,30 @@ void PubSub::handleMessageResponse(const rapidjson::Value &outerData)
std::string moderationAction;
if (!rj::getSafe(data, "moderation_action", moderationAction)) {
Log("Missing moderation action in data: {}", rj::stringify(data));
log("Missing moderation action in data: {}", rj::stringify(data));
return;
}
auto handlerIt = this->moderationActionHandlers.find(moderationAction);
if (handlerIt == this->moderationActionHandlers.end()) {
Log("No handler found for moderation action {}", moderationAction);
log("No handler found for moderation action {}", moderationAction);
return;
}
// Invoke handler function
handlerIt->second(data, topicParts[2]);
} else {
Log("Unknown topic: {}", topic);
log("Unknown topic: {}", topic);
return;
}
}
void PubSub::runThread()
{
Log("Start pubsub manager thread");
log("Start pubsub manager thread");
this->websocketClient.run();
Log("Done with pubsub manager thread");
log("Done with pubsub manager thread");
}
} // namespace chatterino
+2 -1
View File
@@ -42,7 +42,8 @@ struct Listener {
class PubSubClient : public std::enable_shared_from_this<PubSubClient>
{
public:
PubSubClient(WebsocketClient &_websocketClient, WebsocketHandle _handle);
PubSubClient(WebsocketClient &_websocketClient,
WebsocketHandle _handle);
void start();
void stop();

Some files were not shown because too many files have changed in this diff Show More