Merge branch 'master' into irc-support

This commit is contained in:
fourtf
2019-09-18 13:03:16 +02:00
204 changed files with 17026 additions and 16467 deletions
+380 -380
View File
@@ -1,380 +1,380 @@
#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"
#include <QCoreApplication>
namespace chatterino {
const int RECONNECT_BASE_INTERVAL = 2000;
// 60 falloff counter means it will try to reconnect at most every 60*2 seconds
const int MAX_FALLOFF_COUNTER = 60;
AbstractIrcServer::AbstractIrcServer()
{
// Initialize the connections
// XXX: don't create write connection if there is not separate write connection.
this->writeConnection_.reset(new IrcConnection);
this->writeConnection_->moveToThread(
QCoreApplication::instance()->thread());
QObject::connect(
this->writeConnection_.get(), &Communi::IrcConnection::messageReceived,
this, [this](auto msg) { this->writeConnectionMessageReceived(msg); });
QObject::connect(
this->writeConnection_.get(), &Communi::IrcConnection::connected, this,
[this] { this->onWriteConnected(this->writeConnection_.get()); });
// Listen to read connection message signals
this->readConnection_.reset(new IrcConnection);
this->readConnection_->moveToThread(QCoreApplication::instance()->thread());
QObject::connect(
this->readConnection_.get(), &Communi::IrcConnection::messageReceived,
this, [this](auto msg) { this->readConnectionMessageReceived(msg); });
QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::privateMessageReceived, this,
[this](auto msg) { this->privateMessageReceived(msg); });
QObject::connect(
this->readConnection_.get(), &Communi::IrcConnection::connected, this,
[this] { this->onReadConnected(this->readConnection_.get()); });
QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::disconnected, this,
[this] { this->onDisconnected(); });
QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::socketError, this,
[this] { this->onSocketError(); });
// listen to reconnect request
this->readConnection_->reconnectRequested.connect(
[this] { this->connect(); });
// this->writeConnection->reconnectRequested.connect([this] {
// this->connect(); });
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL);
this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL *
this->falloffCounter_);
this->falloffCounter_ =
std::min(MAX_FALLOFF_COUNTER, this->falloffCounter_ + 1);
if (!this->readConnection_->isConnected())
{
log("Trying to reconnect... {}", this->falloffCounter_);
this->connect();
}
});
}
void AbstractIrcServer::connect()
{
this->disconnect();
if (this->hasSeparateWriteConnection())
{
this->initializeConnection(this->writeConnection_.get(), Write);
this->initializeConnection(this->readConnection_.get(), Read);
}
else
{
this->initializeConnection(this->readConnection_.get(), Both);
}
}
void AbstractIrcServer::open(ConnectionType type)
{
std::lock_guard<std::mutex> lock(this->connectionMutex_);
if (type == Write)
{
this->writeConnection_->open();
}
if (type & Read)
{
this->readConnection_->open();
}
}
void AbstractIrcServer::disconnect()
{
std::lock_guard<std::mutex> locker(this->connectionMutex_);
this->readConnection_->close();
if (this->hasSeparateWriteConnection())
{
this->writeConnection_->close();
}
}
void AbstractIrcServer::sendMessage(const QString &channelName,
const QString &message)
{
this->sendRawMessage("PRIVMSG #" + channelName + " :" + message);
}
void AbstractIrcServer::sendRawMessage(const QString &rawMessage)
{
std::lock_guard<std::mutex> locker(this->connectionMutex_);
if (this->hasSeparateWriteConnection())
{
this->writeConnection_->sendRaw(rawMessage);
}
else
{
this->readConnection_->sendRaw(rawMessage);
}
}
void AbstractIrcServer::writeConnectionMessageReceived(
Communi::IrcMessage *message)
{
(void)message;
}
ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName)
{
auto channelName = this->cleanChannelName(dirtyChannelName);
// try get channel
ChannelPtr chan = this->getChannelOrEmpty(channelName);
if (chan != Channel::getEmpty())
{
return chan;
}
std::lock_guard<std::mutex> lock(this->channelMutex);
// value doesn't exist
chan = this->createChannel(channelName);
if (!chan)
{
return Channel::getEmpty();
}
this->channels.insert(channelName, chan);
this->connections_.emplace_back(chan->destroyed.connect([this,
channelName] {
// fourtf: issues when the server itself is destroyed
log("[AbstractIrcServer::addChannel] {} was destroyed", channelName);
this->channels.remove(channelName);
if (this->readConnection_)
{
this->readConnection_->sendRaw("PART #" + channelName);
}
if (this->writeConnection_ && this->hasSeparateWriteConnection())
{
this->writeConnection_->sendRaw("PART #" + channelName);
}
}));
// join irc channel
{
std::lock_guard<std::mutex> lock2(this->connectionMutex_);
if (this->readConnection_)
{
if (this->readConnection_->isConnected())
{
this->readConnection_->sendRaw("JOIN #" + channelName);
}
}
if (this->writeConnection_ && this->hasSeparateWriteConnection())
{
if (this->readConnection_->isConnected())
{
this->writeConnection_->sendRaw("JOIN #" + channelName);
}
}
}
return chan;
}
ChannelPtr AbstractIrcServer::getChannelOrEmpty(const QString &dirtyChannelName)
{
auto channelName = this->cleanChannelName(dirtyChannelName);
std::lock_guard<std::mutex> lock(this->channelMutex);
// try get special channel
ChannelPtr chan = this->getCustomChannel(channelName);
if (chan)
{
return chan;
}
// value exists
auto it = this->channels.find(channelName);
if (it != this->channels.end())
{
chan = it.value().lock();
if (chan)
{
return chan;
}
}
return Channel::getEmpty();
}
std::vector<std::weak_ptr<Channel>> AbstractIrcServer::getChannels()
{
std::lock_guard lock(this->channelMutex);
std::vector<std::weak_ptr<Channel>> channels;
for (auto &&weak : this->channels.values())
{
channels.push_back(weak);
}
return channels;
}
void AbstractIrcServer::onReadConnected(IrcConnection *connection)
{
(void)connection;
std::lock_guard lock(this->channelMutex);
// join channels
for (auto &&weak : this->channels)
{
if (auto channel = weak.lock())
{
connection->sendRaw("JOIN #" + channel->getName());
}
}
// connected/disconnected message
auto connectedMsg = makeSystemMessage("connected");
connectedMsg->flags.set(MessageFlag::ConnectedMessage);
auto reconnected = makeSystemMessage("reconnected");
reconnected->flags.set(MessageFlag::ConnectedMessage);
for (std::weak_ptr<Channel> &weak : this->channels.values())
{
std::shared_ptr<Channel> chan = weak.lock();
if (!chan)
{
continue;
}
LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot();
bool replaceMessage =
snapshot.size() > 0 && snapshot[snapshot.size() - 1]->flags.has(
MessageFlag::DisconnectedMessage);
if (replaceMessage)
{
chan->replaceMessage(snapshot[snapshot.size() - 1], reconnected);
continue;
}
chan->addMessage(connectedMsg);
}
this->falloffCounter_ = 1;
}
void AbstractIrcServer::onWriteConnected(IrcConnection *connection)
{
(void)connection;
}
void AbstractIrcServer::onDisconnected()
{
std::lock_guard<std::mutex> lock(this->channelMutex);
MessageBuilder b(systemMessage, "disconnected");
b->flags.set(MessageFlag::DisconnectedMessage);
auto disconnectedMsg = b.release();
for (std::weak_ptr<Channel> &weak : this->channels.values())
{
std::shared_ptr<Channel> chan = weak.lock();
if (!chan)
{
continue;
}
chan->addMessage(disconnectedMsg);
}
}
void AbstractIrcServer::onSocketError()
{
this->reconnectTimer_.start();
}
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
const QString &channelName)
{
(void)channelName;
return nullptr;
}
QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName)
{
if (dirtyChannelName.startsWith('#'))
return dirtyChannelName.mid(1);
else
return dirtyChannelName;
}
void AbstractIrcServer::addFakeMessage(const QString &data)
{
auto fakeMessage = Communi::IrcMessage::fromData(
data.toUtf8(), this->readConnection_.get());
if (fakeMessage->command() == "PRIVMSG")
{
this->privateMessageReceived(
static_cast<Communi::IrcPrivateMessage *>(fakeMessage));
}
else
{
this->readConnectionMessageReceived(fakeMessage);
}
}
void AbstractIrcServer::privateMessageReceived(
Communi::IrcPrivateMessage *message)
{
(void)message;
}
void AbstractIrcServer::readConnectionMessageReceived(
Communi::IrcMessage *message)
{
}
void AbstractIrcServer::forEachChannel(std::function<void(ChannelPtr)> func)
{
std::lock_guard<std::mutex> lock(this->channelMutex);
for (std::weak_ptr<Channel> &weak : this->channels.values())
{
ChannelPtr chan = weak.lock();
if (!chan)
{
continue;
}
func(chan);
}
}
} // namespace chatterino
#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"
#include <QCoreApplication>
namespace chatterino {
const int RECONNECT_BASE_INTERVAL = 2000;
// 60 falloff counter means it will try to reconnect at most every 60*2 seconds
const int MAX_FALLOFF_COUNTER = 60;
AbstractIrcServer::AbstractIrcServer()
{
// Initialize the connections
// XXX: don't create write connection if there is not separate write connection.
this->writeConnection_.reset(new IrcConnection);
this->writeConnection_->moveToThread(
QCoreApplication::instance()->thread());
QObject::connect(
this->writeConnection_.get(), &Communi::IrcConnection::messageReceived,
this, [this](auto msg) { this->writeConnectionMessageReceived(msg); });
QObject::connect(
this->writeConnection_.get(), &Communi::IrcConnection::connected, this,
[this] { this->onWriteConnected(this->writeConnection_.get()); });
// Listen to read connection message signals
this->readConnection_.reset(new IrcConnection);
this->readConnection_->moveToThread(QCoreApplication::instance()->thread());
QObject::connect(
this->readConnection_.get(), &Communi::IrcConnection::messageReceived,
this, [this](auto msg) { this->readConnectionMessageReceived(msg); });
QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::privateMessageReceived, this,
[this](auto msg) { this->privateMessageReceived(msg); });
QObject::connect(
this->readConnection_.get(), &Communi::IrcConnection::connected, this,
[this] { this->onReadConnected(this->readConnection_.get()); });
QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::disconnected, this,
[this] { this->onDisconnected(); });
QObject::connect(this->readConnection_.get(),
&Communi::IrcConnection::socketError, this,
[this] { this->onSocketError(); });
// listen to reconnect request
this->readConnection_->reconnectRequested.connect(
[this] { this->connect(); });
// this->writeConnection->reconnectRequested.connect([this] {
// this->connect(); });
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL);
this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
this->reconnectTimer_.setInterval(RECONNECT_BASE_INTERVAL *
this->falloffCounter_);
this->falloffCounter_ =
std::min(MAX_FALLOFF_COUNTER, this->falloffCounter_ + 1);
if (!this->readConnection_->isConnected())
{
log("Trying to reconnect... {}", this->falloffCounter_);
this->connect();
}
});
}
void AbstractIrcServer::connect()
{
this->disconnect();
if (this->hasSeparateWriteConnection())
{
this->initializeConnection(this->writeConnection_.get(), Write);
this->initializeConnection(this->readConnection_.get(), Read);
}
else
{
this->initializeConnection(this->readConnection_.get(), Both);
}
}
void AbstractIrcServer::open(ConnectionType type)
{
std::lock_guard<std::mutex> lock(this->connectionMutex_);
if (type == Write)
{
this->writeConnection_->open();
}
if (type & Read)
{
this->readConnection_->open();
}
}
void AbstractIrcServer::disconnect()
{
std::lock_guard<std::mutex> locker(this->connectionMutex_);
this->readConnection_->close();
if (this->hasSeparateWriteConnection())
{
this->writeConnection_->close();
}
}
void AbstractIrcServer::sendMessage(const QString &channelName,
const QString &message)
{
this->sendRawMessage("PRIVMSG #" + channelName + " :" + message);
}
void AbstractIrcServer::sendRawMessage(const QString &rawMessage)
{
std::lock_guard<std::mutex> locker(this->connectionMutex_);
if (this->hasSeparateWriteConnection())
{
this->writeConnection_->sendRaw(rawMessage);
}
else
{
this->readConnection_->sendRaw(rawMessage);
}
}
void AbstractIrcServer::writeConnectionMessageReceived(
Communi::IrcMessage *message)
{
(void)message;
}
ChannelPtr AbstractIrcServer::getOrAddChannel(const QString &dirtyChannelName)
{
auto channelName = this->cleanChannelName(dirtyChannelName);
// try get channel
ChannelPtr chan = this->getChannelOrEmpty(channelName);
if (chan != Channel::getEmpty())
{
return chan;
}
std::lock_guard<std::mutex> lock(this->channelMutex);
// value doesn't exist
chan = this->createChannel(channelName);
if (!chan)
{
return Channel::getEmpty();
}
this->channels.insert(channelName, chan);
this->connections_.emplace_back(chan->destroyed.connect([this,
channelName] {
// fourtf: issues when the server itself is destroyed
log("[AbstractIrcServer::addChannel] {} was destroyed", channelName);
this->channels.remove(channelName);
if (this->readConnection_)
{
this->readConnection_->sendRaw("PART #" + channelName);
}
if (this->writeConnection_ && this->hasSeparateWriteConnection())
{
this->writeConnection_->sendRaw("PART #" + channelName);
}
}));
// join irc channel
{
std::lock_guard<std::mutex> lock2(this->connectionMutex_);
if (this->readConnection_)
{
if (this->readConnection_->isConnected())
{
this->readConnection_->sendRaw("JOIN #" + channelName);
}
}
if (this->writeConnection_ && this->hasSeparateWriteConnection())
{
if (this->readConnection_->isConnected())
{
this->writeConnection_->sendRaw("JOIN #" + channelName);
}
}
}
return chan;
}
ChannelPtr AbstractIrcServer::getChannelOrEmpty(const QString &dirtyChannelName)
{
auto channelName = this->cleanChannelName(dirtyChannelName);
std::lock_guard<std::mutex> lock(this->channelMutex);
// try get special channel
ChannelPtr chan = this->getCustomChannel(channelName);
if (chan)
{
return chan;
}
// value exists
auto it = this->channels.find(channelName);
if (it != this->channels.end())
{
chan = it.value().lock();
if (chan)
{
return chan;
}
}
return Channel::getEmpty();
}
std::vector<std::weak_ptr<Channel>> AbstractIrcServer::getChannels()
{
std::lock_guard lock(this->channelMutex);
std::vector<std::weak_ptr<Channel>> channels;
for (auto &&weak : this->channels.values())
{
channels.push_back(weak);
}
return channels;
}
void AbstractIrcServer::onReadConnected(IrcConnection *connection)
{
(void)connection;
std::lock_guard lock(this->channelMutex);
// join channels
for (auto &&weak : this->channels)
{
if (auto channel = weak.lock())
{
connection->sendRaw("JOIN #" + channel->getName());
}
}
// connected/disconnected message
auto connectedMsg = makeSystemMessage("connected");
connectedMsg->flags.set(MessageFlag::ConnectedMessage);
auto reconnected = makeSystemMessage("reconnected");
reconnected->flags.set(MessageFlag::ConnectedMessage);
for (std::weak_ptr<Channel> &weak : this->channels.values())
{
std::shared_ptr<Channel> chan = weak.lock();
if (!chan)
{
continue;
}
LimitedQueueSnapshot<MessagePtr> snapshot = chan->getMessageSnapshot();
bool replaceMessage =
snapshot.size() > 0 && snapshot[snapshot.size() - 1]->flags.has(
MessageFlag::DisconnectedMessage);
if (replaceMessage)
{
chan->replaceMessage(snapshot[snapshot.size() - 1], reconnected);
continue;
}
chan->addMessage(connectedMsg);
}
this->falloffCounter_ = 1;
}
void AbstractIrcServer::onWriteConnected(IrcConnection *connection)
{
(void)connection;
}
void AbstractIrcServer::onDisconnected()
{
std::lock_guard<std::mutex> lock(this->channelMutex);
MessageBuilder b(systemMessage, "disconnected");
b->flags.set(MessageFlag::DisconnectedMessage);
auto disconnectedMsg = b.release();
for (std::weak_ptr<Channel> &weak : this->channels.values())
{
std::shared_ptr<Channel> chan = weak.lock();
if (!chan)
{
continue;
}
chan->addMessage(disconnectedMsg);
}
}
void AbstractIrcServer::onSocketError()
{
this->reconnectTimer_.start();
}
std::shared_ptr<Channel> AbstractIrcServer::getCustomChannel(
const QString &channelName)
{
(void)channelName;
return nullptr;
}
QString AbstractIrcServer::cleanChannelName(const QString &dirtyChannelName)
{
if (dirtyChannelName.startsWith('#'))
return dirtyChannelName.mid(1);
else
return dirtyChannelName;
}
void AbstractIrcServer::addFakeMessage(const QString &data)
{
auto fakeMessage = Communi::IrcMessage::fromData(
data.toUtf8(), this->readConnection_.get());
if (fakeMessage->command() == "PRIVMSG")
{
this->privateMessageReceived(
static_cast<Communi::IrcPrivateMessage *>(fakeMessage));
}
else
{
this->readConnectionMessageReceived(fakeMessage);
}
}
void AbstractIrcServer::privateMessageReceived(
Communi::IrcPrivateMessage *message)
{
(void)message;
}
void AbstractIrcServer::readConnectionMessageReceived(
Communi::IrcMessage *message)
{
}
void AbstractIrcServer::forEachChannel(std::function<void(ChannelPtr)> func)
{
std::lock_guard<std::mutex> lock(this->channelMutex);
for (std::weak_ptr<Channel> &weak : this->channels.values())
{
ChannelPtr chan = weak.lock();
if (!chan)
{
continue;
}
func(chan);
}
}
} // namespace chatterino
+95 -95
View File
@@ -1,95 +1,95 @@
#pragma once
#include "providers/irc/IrcConnection2.hpp"
#include <IrcMessage>
#include <pajlada/signals/signal.hpp>
#include <pajlada/signals/signalholder.hpp>
#include <functional>
#include <mutex>
namespace chatterino {
class Channel;
using ChannelPtr = std::shared_ptr<Channel>;
class AbstractIrcServer : public QObject
{
public:
enum ConnectionType { Read = 1, Write = 2, Both = 3 };
virtual ~AbstractIrcServer() = default;
// connection
void connect();
void disconnect();
void sendMessage(const QString &channelName, const QString &message);
void sendRawMessage(const QString &rawMessage);
// channels
ChannelPtr getOrAddChannel(const QString &dirtyChannelName);
ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName);
std::vector<std::weak_ptr<Channel>> getChannels();
// signals
pajlada::Signals::NoArgSignal connected;
pajlada::Signals::NoArgSignal disconnected;
void addFakeMessage(const QString &data);
// iteration
void forEachChannel(std::function<void(ChannelPtr)> func);
protected:
AbstractIrcServer();
virtual void initializeConnection(IrcConnection *connection,
ConnectionType type) = 0;
virtual std::shared_ptr<Channel> createChannel(
const QString &channelName) = 0;
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
virtual void readConnectionMessageReceived(Communi::IrcMessage *message);
virtual void writeConnectionMessageReceived(Communi::IrcMessage *message);
virtual void onReadConnected(IrcConnection *connection);
virtual void onWriteConnected(IrcConnection *connection);
virtual void onDisconnected();
virtual void onSocketError();
virtual std::shared_ptr<Channel> getCustomChannel(
const QString &channelName);
virtual bool hasSeparateWriteConnection() const = 0;
virtual QString cleanChannelName(const QString &dirtyChannelName);
void open(ConnectionType type);
QMap<QString, std::weak_ptr<Channel>> channels;
std::mutex channelMutex;
private:
void initConnection();
struct Deleter {
void operator()(IrcConnection *conn)
{
conn->deleteLater();
}
};
std::unique_ptr<IrcConnection, Deleter> writeConnection_ = nullptr;
std::unique_ptr<IrcConnection, Deleter> readConnection_ = nullptr;
QTimer reconnectTimer_;
int falloffCounter_ = 1;
std::mutex connectionMutex_;
// bool autoReconnect_ = false;
pajlada::Signals::SignalHolder connections_;
};
} // namespace chatterino
#pragma once
#include "providers/irc/IrcConnection2.hpp"
#include <IrcMessage>
#include <pajlada/signals/signal.hpp>
#include <pajlada/signals/signalholder.hpp>
#include <functional>
#include <mutex>
namespace chatterino {
class Channel;
using ChannelPtr = std::shared_ptr<Channel>;
class AbstractIrcServer : public QObject
{
public:
enum ConnectionType { Read = 1, Write = 2, Both = 3 };
virtual ~AbstractIrcServer() = default;
// connection
void connect();
void disconnect();
void sendMessage(const QString &channelName, const QString &message);
void sendRawMessage(const QString &rawMessage);
// channels
ChannelPtr getOrAddChannel(const QString &dirtyChannelName);
ChannelPtr getChannelOrEmpty(const QString &dirtyChannelName);
std::vector<std::weak_ptr<Channel>> getChannels();
// signals
pajlada::Signals::NoArgSignal connected;
pajlada::Signals::NoArgSignal disconnected;
void addFakeMessage(const QString &data);
// iteration
void forEachChannel(std::function<void(ChannelPtr)> func);
protected:
AbstractIrcServer();
virtual void initializeConnection(IrcConnection *connection,
ConnectionType type) = 0;
virtual std::shared_ptr<Channel> createChannel(
const QString &channelName) = 0;
virtual void privateMessageReceived(Communi::IrcPrivateMessage *message);
virtual void readConnectionMessageReceived(Communi::IrcMessage *message);
virtual void writeConnectionMessageReceived(Communi::IrcMessage *message);
virtual void onReadConnected(IrcConnection *connection);
virtual void onWriteConnected(IrcConnection *connection);
virtual void onDisconnected();
virtual void onSocketError();
virtual std::shared_ptr<Channel> getCustomChannel(
const QString &channelName);
virtual bool hasSeparateWriteConnection() const = 0;
virtual QString cleanChannelName(const QString &dirtyChannelName);
void open(ConnectionType type);
QMap<QString, std::weak_ptr<Channel>> channels;
std::mutex channelMutex;
private:
void initConnection();
struct Deleter {
void operator()(IrcConnection *conn)
{
conn->deleteLater();
}
};
std::unique_ptr<IrcConnection, Deleter> writeConnection_ = nullptr;
std::unique_ptr<IrcConnection, Deleter> readConnection_ = nullptr;
QTimer reconnectTimer_;
int falloffCounter_ = 1;
std::mutex connectionMutex_;
// bool autoReconnect_ = false;
pajlada::Signals::SignalHolder connections_;
};
} // namespace chatterino
+68 -68
View File
@@ -1,68 +1,68 @@
#include "IrcChannel2.hpp"
#include "debug/AssertInGuiThread.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/irc/IrcCommands.hpp"
#include "providers/irc/IrcServer.hpp"
namespace chatterino {
IrcChannel::IrcChannel(const QString &name, IrcServer *server)
: Channel(name, Channel::Type::Irc)
, ChannelChatters(*static_cast<Channel *>(this))
, server_(server)
{
}
void IrcChannel::sendMessage(const QString &message)
{
assertInGuiThread();
if (message.startsWith("/"))
{
int index = message.indexOf(' ', 1);
QString command = message.mid(1, index - 1);
QString params = index == -1 ? "" : message.mid(index + 1);
invokeIrcCommand(command, params, *this);
}
else
{
if (this->server())
this->server()->sendMessage(this->getName(), message);
MessageBuilder builder;
builder.emplace<TimestampElement>();
builder.emplace<TextElement>(this->server()->nick() + ":",
MessageElementFlag::Username);
builder.emplace<TextElement>(message, MessageElementFlag::Text);
this->addMessage(builder.release());
}
}
IrcServer *IrcChannel::server()
{
assertInGuiThread();
return this->server_;
}
void IrcChannel::setServer(IrcServer *server)
{
assertInGuiThread();
this->server_ = server;
}
bool IrcChannel::canReconnect() const
{
return true;
}
void IrcChannel::reconnect()
{
if (this->server())
this->server()->connect();
}
} // namespace chatterino
#include "IrcChannel2.hpp"
#include "debug/AssertInGuiThread.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/irc/IrcCommands.hpp"
#include "providers/irc/IrcServer.hpp"
namespace chatterino {
IrcChannel::IrcChannel(const QString &name, IrcServer *server)
: Channel(name, Channel::Type::Irc)
, ChannelChatters(*static_cast<Channel *>(this))
, server_(server)
{
}
void IrcChannel::sendMessage(const QString &message)
{
assertInGuiThread();
if (message.startsWith("/"))
{
int index = message.indexOf(' ', 1);
QString command = message.mid(1, index - 1);
QString params = index == -1 ? "" : message.mid(index + 1);
invokeIrcCommand(command, params, *this);
}
else
{
if (this->server())
this->server()->sendMessage(this->getName(), message);
MessageBuilder builder;
builder.emplace<TimestampElement>();
builder.emplace<TextElement>(this->server()->nick() + ":",
MessageElementFlag::Username);
builder.emplace<TextElement>(message, MessageElementFlag::Text);
this->addMessage(builder.release());
}
}
IrcServer *IrcChannel::server()
{
assertInGuiThread();
return this->server_;
}
void IrcChannel::setServer(IrcServer *server)
{
assertInGuiThread();
this->server_ = server;
}
bool IrcChannel::canReconnect() const
{
return true;
}
void IrcChannel::reconnect()
{
if (this->server())
this->server()->connect();
}
} // namespace chatterino
+33 -33
View File
@@ -1,33 +1,33 @@
#pragma once
#include "common/Channel.hpp"
#include "common/ChannelChatters.hpp"
namespace chatterino {
class Irc;
class IrcServer;
class IrcChannel : public Channel, public ChannelChatters
{
public:
explicit IrcChannel(const QString &name, IrcServer *server);
void sendMessage(const QString &message) override;
// server may be nullptr
IrcServer *server();
// Channel methods
virtual bool canReconnect() const override;
virtual void reconnect() override;
private:
void setServer(IrcServer *server);
IrcServer *server_;
friend class Irc;
};
} // namespace chatterino
#pragma once
#include "common/Channel.hpp"
#include "common/ChannelChatters.hpp"
namespace chatterino {
class Irc;
class IrcServer;
class IrcChannel : public Channel, public ChannelChatters
{
public:
explicit IrcChannel(const QString &name, IrcServer *server);
void sendMessage(const QString &message) override;
// server may be nullptr
IrcServer *server();
// Channel methods
virtual bool canReconnect() const override;
virtual void reconnect() override;
private:
void setServer(IrcServer *server);
IrcServer *server_;
friend class Irc;
};
} // namespace chatterino
+44 -44
View File
@@ -1,44 +1,44 @@
#include "IrcConnection2.hpp"
namespace chatterino {
IrcConnection::IrcConnection(QObject *parent)
: Communi::IrcConnection(parent)
{
// send ping every x seconds
this->pingTimer_.setInterval(5000);
this->pingTimer_.start();
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
if (this->isConnected())
{
if (!this->recentlyReceivedMessage_.load())
{
this->sendRaw("PING chatterino/ping");
this->reconnectTimer_.start();
}
this->recentlyReceivedMessage_ = false;
}
});
// reconnect after x seconds without receiving a message
this->reconnectTimer_.setInterval(5000);
this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
if (this->isConnected())
{
reconnectRequested.invoke();
}
});
QObject::connect(this, &Communi::IrcConnection::messageReceived,
[this](Communi::IrcMessage *) {
this->recentlyReceivedMessage_ = true;
if (this->reconnectTimer_.isActive())
{
this->reconnectTimer_.stop();
}
});
}
} // namespace chatterino
#include "IrcConnection2.hpp"
namespace chatterino {
IrcConnection::IrcConnection(QObject *parent)
: Communi::IrcConnection(parent)
{
// send ping every x seconds
this->pingTimer_.setInterval(5000);
this->pingTimer_.start();
QObject::connect(&this->pingTimer_, &QTimer::timeout, [this] {
if (this->isConnected())
{
if (!this->recentlyReceivedMessage_.load())
{
this->sendRaw("PING chatterino/ping");
this->reconnectTimer_.start();
}
this->recentlyReceivedMessage_ = false;
}
});
// reconnect after x seconds without receiving a message
this->reconnectTimer_.setInterval(5000);
this->reconnectTimer_.setSingleShot(true);
QObject::connect(&this->reconnectTimer_, &QTimer::timeout, [this] {
if (this->isConnected())
{
reconnectRequested.invoke();
}
});
QObject::connect(this, &Communi::IrcConnection::messageReceived,
[this](Communi::IrcMessage *) {
this->recentlyReceivedMessage_ = true;
if (this->reconnectTimer_.isActive())
{
this->reconnectTimer_.stop();
}
});
}
} // namespace chatterino
+23 -23
View File
@@ -1,23 +1,23 @@
#pragma once
#include <pajlada/signals/signal.hpp>
#include <IrcConnection>
#include <QTimer>
namespace chatterino {
class IrcConnection : public Communi::IrcConnection
{
public:
IrcConnection(QObject *parent = nullptr);
pajlada::Signals::NoArgSignal reconnectRequested;
private:
QTimer pingTimer_;
QTimer reconnectTimer_;
std::atomic<bool> recentlyReceivedMessage_{true};
};
} // namespace chatterino
#pragma once
#include <pajlada/signals/signal.hpp>
#include <IrcConnection>
#include <QTimer>
namespace chatterino {
class IrcConnection : public Communi::IrcConnection
{
public:
IrcConnection(QObject *parent = nullptr);
pajlada::Signals::NoArgSignal reconnectRequested;
private:
QTimer pingTimer_;
QTimer reconnectTimer_;
std::atomic<bool> recentlyReceivedMessage_{true};
};
} // namespace chatterino
+259 -259
View File
@@ -1,259 +1,259 @@
#include "IrcServer.hpp"
#include <cassert>
#include <cstdlib>
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/irc/Irc2.hpp"
#include "providers/irc/IrcChannel2.hpp"
#include "singletons/Settings.hpp"
#include "util/QObjectRef.hpp"
namespace chatterino {
IrcServer::IrcServer(const IrcServerData &data)
: data_(new IrcServerData(data))
{
this->connect();
}
IrcServer::IrcServer(const IrcServerData &data,
const std::vector<std::weak_ptr<Channel>> &restoreChannels)
: IrcServer(data)
{
for (auto &&weak : restoreChannels)
{
if (auto shared = weak.lock())
{
this->channels[shared->getName()] = weak;
}
}
}
IrcServer::~IrcServer()
{
delete this->data_;
}
int IrcServer::id()
{
return this->data_->id;
}
const QString &IrcServer::user()
{
return this->data_->user;
}
const QString &IrcServer::nick()
{
return this->data_->nick.isEmpty() ? this->data_->user : this->data_->nick;
}
void IrcServer::initializeConnection(IrcConnection *connection,
ConnectionType type)
{
assert(type == Both);
connection->setSecure(this->data_->ssl);
connection->setHost(this->data_->host);
connection->setPort(this->data_->port);
connection->setUserName(this->data_->user);
connection->setNickName(this->data_->nick.isEmpty() ? this->data_->user
: this->data_->nick);
connection->setRealName(this->data_->real.isEmpty() ? this->data_->user
: this->data_->nick);
switch (this->data_->authType)
{
case IrcAuthType::Sasl:
connection->setSaslMechanism("PLAIN");
[[fallthrough]];
case IrcAuthType::Pass:
this->data_->getPassword(
this, [conn = new QObjectRef(connection) /* can't copy */,
this](const QString &password) mutable {
if (*conn)
{
(*conn)->setPassword(password);
this->open(Both);
}
delete conn;
});
break;
default:
this->open(Both);
}
QObject::connect(
connection, &Communi::IrcConnection::socketError, this,
[this](QAbstractSocket::SocketError error) {
static int index =
QAbstractSocket::staticMetaObject.indexOfEnumerator(
"SocketError");
std::lock_guard lock(this->channelMutex);
for (auto &&weak : this->channels)
if (auto shared = weak.lock())
shared->addMessage(makeSystemMessage(
QStringLiteral("Socket error: ") +
QAbstractSocket::staticMetaObject.enumerator(index)
.valueToKey(error)));
});
QObject::connect(connection, &Communi::IrcConnection::nickNameRequired,
this, [](const QString &reserved, QString *result) {
*result = reserved + (std::rand() % 100);
});
QObject::connect(connection, &Communi::IrcConnection::noticeMessageReceived,
this, [this](Communi::IrcNoticeMessage *message) {
MessageBuilder builder;
builder.emplace<TimestampElement>();
builder.emplace<TextElement>(
message->nick(), MessageElementFlag::Username);
builder.emplace<TextElement>(
"-> you:", MessageElementFlag::Username);
builder.emplace<TextElement>(message->content(),
MessageElementFlag::Text);
auto msg = builder.release();
for (auto &&weak : this->channels)
if (auto shared = weak.lock())
shared->addMessage(msg);
});
}
std::shared_ptr<Channel> IrcServer::createChannel(const QString &channelName)
{
return std::make_shared<IrcChannel>(channelName, this);
}
bool IrcServer::hasSeparateWriteConnection() const
{
return false;
}
void IrcServer::onReadConnected(IrcConnection *connection)
{
{
std::lock_guard lock(this->channelMutex);
for (auto &&command : this->data_->connectCommands)
{
connection->sendRaw(command + "\r\n");
}
}
AbstractIrcServer::onReadConnected(connection);
}
void IrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
{
auto target = message->target();
target = target.startsWith('#') ? target.mid(1) : target;
if (auto channel = this->getChannelOrEmpty(target); !channel->isEmpty())
{
MessageBuilder builder;
builder.emplace<TimestampElement>();
builder.emplace<TextElement>(message->nick() + ":",
MessageElementFlag::Username);
builder.emplace<TextElement>(message->content(),
MessageElementFlag::Text);
channel->addMessage(builder.release());
}
}
void IrcServer::readConnectionMessageReceived(Communi::IrcMessage *message)
{
AbstractIrcServer::readConnectionMessageReceived(message);
switch (message->type())
{
case Communi::IrcMessage::Join:
{
auto x = static_cast<Communi::IrcJoinMessage *>(message);
if (auto it =
this->channels.find(this->cleanChannelName(x->channel()));
it != this->channels.end())
{
if (auto shared = it->lock())
{
if (message->nick() == this->data_->nick)
{
shared->addMessage(
MessageBuilder(systemMessage, "joined").release());
}
else
{
if (auto c =
dynamic_cast<ChannelChatters *>(shared.get()))
c->addJoinedUser(x->nick());
}
}
}
return;
}
case Communi::IrcMessage::Part:
{
auto x = static_cast<Communi::IrcPartMessage *>(message);
if (auto it =
this->channels.find(this->cleanChannelName(x->channel()));
it != this->channels.end())
{
if (auto shared = it->lock())
{
if (message->nick() == this->data_->nick)
{
shared->addMessage(
MessageBuilder(systemMessage, "parted").release());
}
else
{
if (auto c =
dynamic_cast<ChannelChatters *>(shared.get()))
c->addPartedUser(x->nick());
}
}
}
return;
}
case Communi::IrcMessage::Pong:
case Communi::IrcMessage::Notice:
return;
default:
if (getSettings()->showUnhandledIrcMessages)
{
MessageBuilder builder;
builder.emplace<TimestampElement>();
builder.emplace<TextElement>(message->toData(),
MessageElementFlag::Text);
builder->flags.set(MessageFlag::Debug);
auto msg = builder.release();
for (auto &&weak : this->channels)
{
if (auto shared = weak.lock())
shared->addMessage(msg);
}
};
}
}
} // namespace chatterino
#include "IrcServer.hpp"
#include <cassert>
#include <cstdlib>
#include "messages/Message.hpp"
#include "messages/MessageBuilder.hpp"
#include "providers/irc/Irc2.hpp"
#include "providers/irc/IrcChannel2.hpp"
#include "singletons/Settings.hpp"
#include "util/QObjectRef.hpp"
namespace chatterino {
IrcServer::IrcServer(const IrcServerData &data)
: data_(new IrcServerData(data))
{
this->connect();
}
IrcServer::IrcServer(const IrcServerData &data,
const std::vector<std::weak_ptr<Channel>> &restoreChannels)
: IrcServer(data)
{
for (auto &&weak : restoreChannels)
{
if (auto shared = weak.lock())
{
this->channels[shared->getName()] = weak;
}
}
}
IrcServer::~IrcServer()
{
delete this->data_;
}
int IrcServer::id()
{
return this->data_->id;
}
const QString &IrcServer::user()
{
return this->data_->user;
}
const QString &IrcServer::nick()
{
return this->data_->nick.isEmpty() ? this->data_->user : this->data_->nick;
}
void IrcServer::initializeConnection(IrcConnection *connection,
ConnectionType type)
{
assert(type == Both);
connection->setSecure(this->data_->ssl);
connection->setHost(this->data_->host);
connection->setPort(this->data_->port);
connection->setUserName(this->data_->user);
connection->setNickName(this->data_->nick.isEmpty() ? this->data_->user
: this->data_->nick);
connection->setRealName(this->data_->real.isEmpty() ? this->data_->user
: this->data_->nick);
switch (this->data_->authType)
{
case IrcAuthType::Sasl:
connection->setSaslMechanism("PLAIN");
[[fallthrough]];
case IrcAuthType::Pass:
this->data_->getPassword(
this, [conn = new QObjectRef(connection) /* can't copy */,
this](const QString &password) mutable {
if (*conn)
{
(*conn)->setPassword(password);
this->open(Both);
}
delete conn;
});
break;
default:
this->open(Both);
}
QObject::connect(
connection, &Communi::IrcConnection::socketError, this,
[this](QAbstractSocket::SocketError error) {
static int index =
QAbstractSocket::staticMetaObject.indexOfEnumerator(
"SocketError");
std::lock_guard lock(this->channelMutex);
for (auto &&weak : this->channels)
if (auto shared = weak.lock())
shared->addMessage(makeSystemMessage(
QStringLiteral("Socket error: ") +
QAbstractSocket::staticMetaObject.enumerator(index)
.valueToKey(error)));
});
QObject::connect(connection, &Communi::IrcConnection::nickNameRequired,
this, [](const QString &reserved, QString *result) {
*result = reserved + (std::rand() % 100);
});
QObject::connect(connection, &Communi::IrcConnection::noticeMessageReceived,
this, [this](Communi::IrcNoticeMessage *message) {
MessageBuilder builder;
builder.emplace<TimestampElement>();
builder.emplace<TextElement>(
message->nick(), MessageElementFlag::Username);
builder.emplace<TextElement>(
"-> you:", MessageElementFlag::Username);
builder.emplace<TextElement>(message->content(),
MessageElementFlag::Text);
auto msg = builder.release();
for (auto &&weak : this->channels)
if (auto shared = weak.lock())
shared->addMessage(msg);
});
}
std::shared_ptr<Channel> IrcServer::createChannel(const QString &channelName)
{
return std::make_shared<IrcChannel>(channelName, this);
}
bool IrcServer::hasSeparateWriteConnection() const
{
return false;
}
void IrcServer::onReadConnected(IrcConnection *connection)
{
{
std::lock_guard lock(this->channelMutex);
for (auto &&command : this->data_->connectCommands)
{
connection->sendRaw(command + "\r\n");
}
}
AbstractIrcServer::onReadConnected(connection);
}
void IrcServer::privateMessageReceived(Communi::IrcPrivateMessage *message)
{
auto target = message->target();
target = target.startsWith('#') ? target.mid(1) : target;
if (auto channel = this->getChannelOrEmpty(target); !channel->isEmpty())
{
MessageBuilder builder;
builder.emplace<TimestampElement>();
builder.emplace<TextElement>(message->nick() + ":",
MessageElementFlag::Username);
builder.emplace<TextElement>(message->content(),
MessageElementFlag::Text);
channel->addMessage(builder.release());
}
}
void IrcServer::readConnectionMessageReceived(Communi::IrcMessage *message)
{
AbstractIrcServer::readConnectionMessageReceived(message);
switch (message->type())
{
case Communi::IrcMessage::Join:
{
auto x = static_cast<Communi::IrcJoinMessage *>(message);
if (auto it =
this->channels.find(this->cleanChannelName(x->channel()));
it != this->channels.end())
{
if (auto shared = it->lock())
{
if (message->nick() == this->data_->nick)
{
shared->addMessage(
MessageBuilder(systemMessage, "joined").release());
}
else
{
if (auto c =
dynamic_cast<ChannelChatters *>(shared.get()))
c->addJoinedUser(x->nick());
}
}
}
return;
}
case Communi::IrcMessage::Part:
{
auto x = static_cast<Communi::IrcPartMessage *>(message);
if (auto it =
this->channels.find(this->cleanChannelName(x->channel()));
it != this->channels.end())
{
if (auto shared = it->lock())
{
if (message->nick() == this->data_->nick)
{
shared->addMessage(
MessageBuilder(systemMessage, "parted").release());
}
else
{
if (auto c =
dynamic_cast<ChannelChatters *>(shared.get()))
c->addPartedUser(x->nick());
}
}
}
return;
}
case Communi::IrcMessage::Pong:
case Communi::IrcMessage::Notice:
return;
default:
if (getSettings()->showUnhandledIrcMessages)
{
MessageBuilder builder;
builder.emplace<TimestampElement>();
builder.emplace<TextElement>(message->toData(),
MessageElementFlag::Text);
builder->flags.set(MessageFlag::Debug);
auto msg = builder.release();
for (auto &&weak : this->channels)
{
if (auto shared = weak.lock())
shared->addMessage(msg);
}
};
}
}
} // namespace chatterino
+38 -38
View File
@@ -1,38 +1,38 @@
#pragma once
#include "providers/irc/AbstractIrcServer.hpp"
#include "providers/irc/IrcAccount.hpp"
namespace chatterino {
struct IrcServerData;
class IrcServer : public AbstractIrcServer
{
public:
explicit IrcServer(const IrcServerData &data);
IrcServer(const IrcServerData &data,
const std::vector<std::weak_ptr<Channel>> &restoreChannels);
~IrcServer() override;
int id();
const QString &user();
const QString &nick();
// AbstractIrcServer interface
protected:
void initializeConnection(IrcConnection *connection,
ConnectionType type) override;
std::shared_ptr<Channel> createChannel(const QString &channelName) override;
bool hasSeparateWriteConnection() const override;
void onReadConnected(IrcConnection *connection) override;
void privateMessageReceived(Communi::IrcPrivateMessage *message) override;
void readConnectionMessageReceived(Communi::IrcMessage *message) override;
private:
// pointer so we don't have to circle include Irc2.hpp
IrcServerData *data_;
};
} // namespace chatterino
#pragma once
#include "providers/irc/AbstractIrcServer.hpp"
#include "providers/irc/IrcAccount.hpp"
namespace chatterino {
struct IrcServerData;
class IrcServer : public AbstractIrcServer
{
public:
explicit IrcServer(const IrcServerData &data);
IrcServer(const IrcServerData &data,
const std::vector<std::weak_ptr<Channel>> &restoreChannels);
~IrcServer() override;
int id();
const QString &user();
const QString &nick();
// AbstractIrcServer interface
protected:
void initializeConnection(IrcConnection *connection,
ConnectionType type) override;
std::shared_ptr<Channel> createChannel(const QString &channelName) override;
bool hasSeparateWriteConnection() const override;
void onReadConnected(IrcConnection *connection) override;
void privateMessageReceived(Communi::IrcPrivateMessage *message) override;
void readConnectionMessageReceived(Communi::IrcMessage *message) override;
private:
// pointer so we don't have to circle include Irc2.hpp
IrcServerData *data_;
};
} // namespace chatterino