You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
konversation/konversation/src/connectionmanager.cpp

580 lines
18 KiB

/*
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
*/
/*
Copyright (C) 2008 Eike Hein <hein@kde.org>
*/
#include "connectionmanager.h"
#include "connectionsettings.h"
#include "serversettings.h"
#include "servergroupsettings.h"
#include "konversationapplication.h"
#include "konversationmainwindow.h"
#include "statuspanel.h"
#include <tqregexp.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
ConnectionManager::ConnectionManager(TQObject* parent) : TQObject(parent)
{
connect(this, TQ_SIGNAL(requestReconnect(Server*)), this, TQ_SLOT(handleReconnect(Server*)));
}
ConnectionManager::~ConnectionManager()
{
}
void ConnectionManager::connectTo(Konversation::ConnectionFlag flag, const TQString& target,
const TQString& port, const TQString& password, const TQString& nick, const TQString& channel,
bool useSSL)
{
ConnectionSettings settings;
if (target.startsWith("irc://"))
decodeIrcUrl(target, settings);
else
{
decodeAddress(target, settings);
Konversation::ServerSettings server = settings.server();
if (!port.isEmpty()) server.setPort(port.toInt());
if (!password.isEmpty()) server.setPassword(password);
if (useSSL) server.setSSLEnabled(true);
settings.setServer(server);
if (!nick.isEmpty()) settings.setInitialNick(nick);
if (!channel.isEmpty())
{
Konversation::ChannelSettings channelSettings(channel);
settings.setInitialChannel(channelSettings);
}
}
connectTo(flag, settings);
}
void ConnectionManager::connectTo(Konversation::ConnectionFlag flag, int serverGroupId)
{
ConnectionSettings settings;
Konversation::ServerGroupSettingsPtr serverGroup;
serverGroup = Preferences::serverGroupById(serverGroupId);
if (serverGroup)
{
settings.setServerGroup(serverGroup);
if (serverGroup->serverList().size() > 0)
settings.setServer(serverGroup->serverList()[0]);
}
connectTo(flag, settings);
}
void ConnectionManager::connectTo(Konversation::ConnectionFlag flag, ConnectionSettings& settings)
{
if (!settings.isValid()) return;
emit closeServerList();
if (flag != Konversation::CreateNewConnection
&& reuseExistingConnection(settings, (flag == Konversation::PromptToReuseConnection)))
{
return;
}
IdentityPtr identity = settings.identity();
if (!identity || !validateIdentity(identity)) return;
KonversationApplication* konvApp = static_cast<KonversationApplication *>(kapp);
KonversationMainWindow* mainWindow = konvApp->getMainWindow();
Server* server = new Server(this, settings);
enlistConnection(server->connectionId(), server);
connect(server, TQ_SIGNAL(destroyed(int)), this, TQ_SLOT(delistConnection(int)));
connect(server, TQ_SIGNAL(connectionStateChanged(Server*, Konversation::ConnectionState)),
this, TQ_SLOT(handleConnectionStateChange(Server*, Konversation::ConnectionState)));
connect(server, TQ_SIGNAL(awayState(bool)), this, TQ_SIGNAL(connectionChangedAwayState(bool)));
connect(server, TQ_SIGNAL(nicksNowOnline(Server*, const TQStringList&, bool)),
mainWindow, TQ_SLOT(setOnlineList(Server*, const TQStringList&,bool)));
connect(server, TQ_SIGNAL(awayInsertRememberLine(Server*)),
mainWindow, TQ_SIGNAL(triggerRememberLines(Server*)));
connect(mainWindow, TQ_SIGNAL(startNotifyTimer(int)), server, TQ_SLOT(startNotifyTimer(int)));
connect(server, TQ_SIGNAL(multiServerCommand(const TQString&, const TQString&)),
konvApp, TQ_SLOT(sendMultiServerCommand(const TQString&, const TQString&)));
}
void ConnectionManager::enlistConnection(int connectionId, Server* server)
{
m_connectionList.insert(connectionId, server);
}
void ConnectionManager::delistConnection(int connectionId)
{
m_connectionList.remove(connectionId);
}
void ConnectionManager::handleConnectionStateChange(Server* server, Konversation::ConnectionState state)
{
emit connectionChangedState(server, state);
int identityId = server->getIdentity()->id();
if (state == Konversation::SSConnected)
{
if (!m_activeIdentities.contains(identityId))
{
m_activeIdentities.append(identityId);
emit identityOnline(identityId);
}
}
else if (state != Konversation::SSConnecting)
{
if (m_activeIdentities.contains(identityId))
{
m_activeIdentities.remove(identityId);
emit identityOffline(identityId);
}
}
if (state == Konversation::SSInvoluntarilyDisconnected)
{
// The asynchronous invocation of handleReconnect() makes sure that
// connectionChangedState() is emitted and delivered before it runs
// (and causes the next connection state change to occur).
emit requestReconnect(server);
}
}
void ConnectionManager::handleReconnect(Server* server)
{
if (!Preferences::autoReconnect()) return;
ConnectionSettings settings = server->getConnectionSettings();
uint reconnectCount = Preferences::reconnectCount();
// For server groups, one iteration over their server list shall count as one
// connection attempt.
if (settings.serverGroup())
reconnectCount = reconnectCount * settings.serverGroup()->serverList().size();
if (reconnectCount == 0 || settings.reconnectCount() < reconnectCount)
{
if (settings.serverGroup() && settings.serverGroup()->serverList().size() > 1)
{
Konversation::ServerList serverList = settings.serverGroup()->serverList();
int index = serverList.findIndex(settings.server());
int size = serverList.size();
if (index == size - 1 || index == -1)
settings.setServer(serverList[0]);
else if (index < size - 1)
settings.setServer(serverList[index+1]);
server->setConnectionSettings(settings);
server->getStatusView()->appendServerMessage(i18n("Info"),
i18n("Trying to connect to %1 in %2 seconds.")
.arg(settings.server().host())
.arg(Preferences::reconnectDelay()));
}
else
{
server->getStatusView()->appendServerMessage(i18n("Info"),
i18n("Trying to reconnect to %1 in %2 seconds.")
.arg(settings.server().host())
.arg(Preferences::reconnectDelay()));
}
server->getConnectionSettings().incrementReconnectCount();
TQTimer::singleShot(Preferences::reconnectDelay() * 1000, server, TQ_SLOT(connectToIRCServer()));
}
else
server->getStatusView()->appendServerMessage(i18n("Error"), i18n("Reconnection attempts exceeded."));
}
void ConnectionManager::quitServers()
{
TQMap<int, Server*>::ConstIterator it;
for (it = m_connectionList.begin(); it != m_connectionList.end(); ++it)
it.data()->quitServer();
}
void ConnectionManager::decodeIrcUrl(const TQString& url, ConnectionSettings& settings)
{
if (!url.startsWith("irc://")) return;
TQString mangledUrl = url;
mangledUrl.remove(TQRegExp("^irc:/+"));
if (mangledUrl.isEmpty()) return;
// Parsing address and channel.
TQStringList mangledUrlSegments;
mangledUrlSegments = TQStringList::split('/', mangledUrl, false);
// Check for ",isserver".
if (mangledUrlSegments[0].contains(','))
{
TQStringList addressSegments;
bool checkIfServerGroup = true;
addressSegments = TQStringList::split(',', mangledUrlSegments[0], false);
if (addressSegments.grep("isserver", false).size() > 0)
checkIfServerGroup = false;
decodeAddress(addressSegments[0], settings, checkIfServerGroup);
}
else
decodeAddress(mangledUrlSegments[0], settings);
TQString channel;
Konversation::ChannelSettings channelSettings;
// Grabbing channel from in front of potential ?key=value parameters.
if (mangledUrlSegments.size() > 1)
channel = mangledUrlSegments[1].section('?', 0, 0);
if (!channel.isEmpty())
{
// Add default prefix to channel if necessary.
if (!channel.contains(TQRegExp("^[#+&]{1}")))
channel = '#' + channel;
channelSettings.setName(channel);
}
// Parsing ?key=value parameters.
TQString parameterString;
if (mangledUrlSegments.size() > 1)
parameterString = mangledUrlSegments[1].section('?', 1);
if (parameterString.isEmpty() && mangledUrlSegments.size() > 2)
parameterString = mangledUrlSegments[2];
if (!parameterString.isEmpty())
{
TQRegExp parameterCatcher;
parameterCatcher.setPattern("pass=([^&]+)");
if (parameterCatcher.search(parameterString) != -1)
{
Konversation::ServerSettings server = settings.server();
server.setPassword(parameterCatcher.cap(1));
settings.setServer(server);
}
parameterCatcher.setPattern("key=([^&]+)");
if (parameterCatcher.search(parameterString) != -1)
channelSettings.setPassword(parameterCatcher.cap(1));
}
// Assigning channel.
if (!channelSettings.name().isEmpty())
settings.setInitialChannel(channelSettings);
}
void ConnectionManager::decodeAddress(const TQString& address, ConnectionSettings& settings,
bool checkIfServerGroup)
{
TQString host;
TQString port = "6667";
// Full-length IPv6 address with port
// Example: RFC 2732 notation: [2001:0DB8:0000:0000:0000:0000:1428:57ab]:6666
// Example: Non-RFC 2732 notation: 2001:0DB8:0000:0000:0000:0000:1428:57ab:6666
if (address.contains(':')==8)
{
host = address.section(':',0,-2).remove("[").remove("]");
port = address.section(':',-1);
}
// Full-length IPv6 address without port or not-full-length IPv6 address with port
// Example: Without port, RFC 2732 notation: [2001:0DB8:0000:0000:0000:0000:1428:57ab]
// Example: Without port, Non-RFC 2732 notation: 2001:0DB8:0000:0000:0000:0000:1428:57ab
// Example: With port, RFC 2732 notation: [2001:0DB8::1428:57ab]:6666
else if (address.contains(':')>=4)
{
// Last segment does not end with ], but the next to last does;
// Assume not-full-length IPv6 address with port
// Example: [2001:0DB8::1428:57ab]:6666
if (address.section(':',0,-2).endsWith("]") && !address.section(':',-1).endsWith("]"))
{
host = address.section(':',0,-2).remove("[").remove("]");
port = address.section(':',-1);
}
else
{
TQString addressCopy = address;
host = addressCopy.remove("[").remove("]");
}
}
// IPv4 address or ordinary hostname with port
// Example: IPv4 address with port: 123.123.123.123:6666
// Example: Hostname with port: irc.bla.org:6666
else if (address.contains(':')==1)
{
host = address.section(':',0,-2);
port = address.section(':',-1);
}
else
host = address;
// Try to assign server group.
if (checkIfServerGroup && Preferences::isServerGroup(host))
{
// If host is found to be the name of a server group.
int serverGroupId = Preferences::serverGroupIdByName(host);
Konversation::ServerGroupSettingsPtr serverGroup;
serverGroup = Preferences::serverGroupById(serverGroupId);
settings.setServerGroup(serverGroup);
if (serverGroup->serverList().size() > 0)
settings.setServer(serverGroup->serverList()[0]);
}
else
{
if (Preferences::serverGroupByServer(host))
{
// If the host is found to be part of a server group's server list.
Konversation::ServerGroupSettingsPtr serverGroup;
serverGroup = Preferences::serverGroupByServer(host);
settings.setServerGroup(serverGroup);
}
Konversation::ServerSettings server;
server.setHost(host);
server.setPort(port.toInt());
settings.setServer(server);
}
}
bool ConnectionManager::reuseExistingConnection(ConnectionSettings& settings, bool interactive)
{
Server* dupe = 0;
ConnectionDupe dupeType;
bool doReuse = true;
KonversationApplication* konvApp = static_cast<KonversationApplication *>(kapp);
KonversationMainWindow* mainWindow = konvApp->getMainWindow();
TQMap<int, Server*>::ConstIterator it;
for (it = m_connectionList.begin(); it != m_connectionList.end(); ++it)
{
if (it.data()->getServerGroup() && settings.serverGroup()
&& it.data()->getServerGroup() == settings.serverGroup())
{
dupe = it.data();
dupeType = SameServerGroup;
break;
}
}
if (!dupe)
{
for (it = m_connectionList.begin(); it != m_connectionList.end(); ++it)
{
if (it.data()->getConnectionSettings().server() == settings.server())
{
dupe = it.data();
dupeType = SameServer;
break;
}
}
}
if (dupe && interactive)
{
int result = KMessageBox::warningContinueCancel(
mainWindow,
i18n("You are already connected to %1. Do you want to open another connection?")
.arg(dupe->getDisplayName()),
i18n("Already connected to %1").arg(dupe->getDisplayName()),
i18n("Create connection"),
"ReuseExistingConnection");
if (result == KMessageBox::Continue) doReuse = false;
}
if (dupe && doReuse)
{
if (interactive && dupeType == SameServerGroup
&& !(dupe->getConnectionSettings().server() == settings.server()))
{
int result = KMessageBox::warningContinueCancel(
mainWindow,
i18n("You are presently connected to %1 via '%2' (port %3). Do you want to switch to '%4' (port %5) instead?")
.arg(dupe->getDisplayName())
.arg(dupe->getServerName())
.arg(dupe->getPort())
.arg(settings.server().host())
.arg(settings.server().port()),
i18n("Already connected to %1").arg(dupe->getDisplayName()),
i18n("Switch Server"),
"ReconnectWithDifferentServer");
if (result == KMessageBox::Continue)
{
dupe->disconnect();
dupe->setConnectionSettings(settings);
}
}
if (!dupe->isConnected())
{
if (!settings.initialChannel().name().isEmpty())
dupe->updateAutoJoin(settings.initialChannel());
if (!dupe->isConnecting())
dupe->reconnect();
}
else
{
if (!settings.initialChannel().name().isEmpty())
{
dupe->sendJoinCommand(settings.initialChannel().name(),
settings.initialChannel().password());
}
}
}
return (dupe && doReuse);
}
bool ConnectionManager::validateIdentity(IdentityPtr identity, bool interactive)
{
KonversationApplication* konvApp = static_cast<KonversationApplication *>(kapp);
KonversationMainWindow* mainWindow = konvApp->getMainWindow();
TQString errors;
if (identity->getIdent().isEmpty())
errors+=i18n("Please fill in your <b>Ident</b>.<br>");
if (identity->getRealName().isEmpty())
errors+=i18n("Please fill in your <b>Real name</b>.<br>");
if (identity->getNickname(0).isEmpty())
errors+=i18n("Please provide at least one <b>Nickname</b>.<br>");
if (!errors.isEmpty())
{
if (interactive)
{
int result = KMessageBox::warningContinueCancel(mainWindow,
i18n("<qt>Your identity \"%1\" is not set up correctly:<br>%2</qt>")
.arg(identity->getName()).arg(errors),
i18n("Identity Settings"),
i18n("Edit Identity..."));
if (result == KMessageBox::Continue)
{
identity = mainWindow->editIdentity(identity);
if (identity && validateIdentity(identity, false))
return true;
else
return false;
}
else
return false;
}
return false;
}
return true;
}
TQPtrList<Server> ConnectionManager::getServerList()
{
TQPtrList<Server> serverList;
TQMap<int, Server*>::ConstIterator it;
for (it = m_connectionList.begin(); it != m_connectionList.end(); ++it)
serverList.append(it.data());
return serverList;
}
Server* ConnectionManager::getServerByConnectionId(int connectionId)
{
if (m_connectionList.contains(connectionId))
return m_connectionList[connectionId];
else
return 0;
}
Server* ConnectionManager::getServerByName(const TQString& name)
{
TQMap<int, Server*>::ConstIterator it;
for (it = m_connectionList.begin(); it != m_connectionList.end(); ++it)
{
if (it.data()->getDisplayName() == name || it.data()->getServerName() == name)
return it.data();
}
return 0;
}
Server* ConnectionManager::getAnyServer()
{
if ( m_connectionList.count() > 0)
return m_connectionList[0];
return 0;
}
#include "connectionmanager.moc"