3bab9d9d034620e64e17af5590a44e1193b24873
[quassel.git] / src / core / coresession.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include "coresession.h"
22
23 #include <QtScript>
24
25 #include "core.h"
26 #include "corebacklogmanager.h"
27 #include "corebuffersyncer.h"
28 #include "corebufferviewmanager.h"
29 #include "coredccconfig.h"
30 #include "coreeventmanager.h"
31 #include "coreidentity.h"
32 #include "coreignorelistmanager.h"
33 #include "coreinfo.h"
34 #include "coreirclisthelper.h"
35 #include "corenetwork.h"
36 #include "corenetworkconfig.h"
37 #include "coresessioneventprocessor.h"
38 #include "coretransfermanager.h"
39 #include "coreuserinputhandler.h"
40 #include "coreusersettings.h"
41 #include "ctcpparser.h"
42 #include "eventstringifier.h"
43 #include "internalpeer.h"
44 #include "ircchannel.h"
45 #include "ircparser.h"
46 #include "ircuser.h"
47 #include "messageevent.h"
48 #include "remotepeer.h"
49 #include "storage.h"
50 #include "util.h"
51
52 class ProcessMessagesEvent : public QEvent
53 {
54 public:
55     ProcessMessagesEvent()
56         : QEvent(QEvent::User)
57     {}
58 };
59
60 CoreSession::CoreSession(UserId uid, bool restoreState, bool strictIdentEnabled, QObject* parent)
61     : QObject(parent)
62     , _user(uid)
63     , _strictIdentEnabled(strictIdentEnabled)
64     , _signalProxy(new SignalProxy(SignalProxy::Server, this))
65     , _aliasManager(this)
66     , _bufferSyncer(new CoreBufferSyncer(this))
67     , _backlogManager(new CoreBacklogManager(this))
68     , _bufferViewManager(new CoreBufferViewManager(_signalProxy, this))
69     , _dccConfig(new CoreDccConfig(this))
70     , _ircListHelper(new CoreIrcListHelper(this))
71     , _networkConfig(new CoreNetworkConfig("GlobalNetworkConfig", this))
72     , _coreInfo(new CoreInfo(this))
73     , _transferManager(new CoreTransferManager(this))
74     , _eventManager(new CoreEventManager(this))
75     , _eventStringifier(new EventStringifier(this))
76     , _sessionEventProcessor(new CoreSessionEventProcessor(this))
77     , _ctcpParser(new CtcpParser(this))
78     , _ircParser(new IrcParser(this))
79     , scriptEngine(new QScriptEngine(this))
80     , _processMessages(false)
81     , _ignoreListManager(this)
82     , _highlightRuleManager(this)
83 {
84     SignalProxy* p = signalProxy();
85     p->setHeartBeatInterval(30);
86     p->setMaxHeartBeatCount(60);  // 30 mins until we throw a dead socket out
87
88     connect(p, &SignalProxy::peerRemoved, this, &CoreSession::removeClient);
89
90     connect(p, &SignalProxy::connected, this, &CoreSession::clientsConnected);
91     connect(p, &SignalProxy::disconnected, this, &CoreSession::clientsDisconnected);
92
93     p->attachSlot(SIGNAL(sendInput(BufferInfo, QString)), this, SLOT(msgFromClient(BufferInfo, QString)));
94     p->attachSignal(this, SIGNAL(displayMsg(Message)));
95     p->attachSignal(this, SIGNAL(displayStatusMsg(QString, QString)));
96
97     p->attachSignal(this, SIGNAL(identityCreated(const Identity&)));
98     p->attachSignal(this, SIGNAL(identityRemoved(IdentityId)));
99     p->attachSlot(SIGNAL(createIdentity(const Identity&, const QVariantMap&)),
100                   this,
101                   SLOT(createIdentity(const Identity&, const QVariantMap&)));
102     p->attachSlot(SIGNAL(removeIdentity(IdentityId)), this, SLOT(removeIdentity(IdentityId)));
103
104     p->attachSignal(this, SIGNAL(networkCreated(NetworkId)));
105     p->attachSignal(this, SIGNAL(networkRemoved(NetworkId)));
106     p->attachSlot(SIGNAL(createNetwork(const NetworkInfo&, const QStringList&)),
107                   this,
108                   SLOT(createNetwork(const NetworkInfo&, const QStringList&)));
109     p->attachSlot(SIGNAL(removeNetwork(NetworkId)), this, SLOT(removeNetwork(NetworkId)));
110
111     p->attachSlot(SIGNAL(changePassword(PeerPtr, QString, QString, QString)), this, SLOT(changePassword(PeerPtr, QString, QString, QString)));
112     p->attachSignal(this, SIGNAL(passwordChanged(PeerPtr, bool)));
113
114     p->attachSlot(SIGNAL(kickClient(int)), this, SLOT(kickClient(int)));
115     p->attachSignal(this, SIGNAL(disconnectFromCore()));
116
117     QVariantMap data;
118     data["quasselVersion"] = Quassel::buildInfo().fancyVersionString;
119     data["quasselBuildDate"] = Quassel::buildInfo().commitDate;  // "BuildDate" for compatibility
120     data["startTime"] = Core::instance()->startTime();
121     data["sessionConnectedClients"] = 0;
122     _coreInfo->setCoreData(data);
123
124     loadSettings();
125     initScriptEngine();
126
127     eventManager()->registerObject(ircParser(), EventManager::NormalPriority);
128     eventManager()->registerObject(sessionEventProcessor(), EventManager::HighPriority);  // needs to process events *before* the stringifier!
129     eventManager()->registerObject(ctcpParser(), EventManager::NormalPriority);
130     eventManager()->registerObject(eventStringifier(), EventManager::NormalPriority);
131     eventManager()->registerObject(this, EventManager::LowPriority);  // for sending MessageEvents to the client
132     // some events need to be handled after msg generation
133     eventManager()->registerObject(sessionEventProcessor(), EventManager::LowPriority, "lateProcess");
134     eventManager()->registerObject(ctcpParser(), EventManager::LowPriority, "send");
135
136     // periodically save our session state
137     connect(Core::instance()->syncTimer(), &QTimer::timeout, this, &CoreSession::saveSessionState);
138
139     p->synchronize(_bufferSyncer);
140     p->synchronize(&aliasManager());
141     p->synchronize(_backlogManager);
142     p->synchronize(dccConfig());
143     p->synchronize(ircListHelper());
144     p->synchronize(networkConfig());
145     p->synchronize(_coreInfo);
146     p->synchronize(&_ignoreListManager);
147     p->synchronize(&_highlightRuleManager);
148     // Listen to network removed events
149     connect(this, &CoreSession::networkRemoved, &_highlightRuleManager, &HighlightRuleManager::networkRemoved);
150     p->synchronize(transferManager());
151     // Restore session state
152     if (restoreState)
153         restoreSessionState();
154
155     emit initialized();
156 }
157
158 void CoreSession::shutdown()
159 {
160     saveSessionState();
161
162     // Request disconnect from all connected networks in parallel, and wait until every network
163     // has emitted the disconnected() signal before deleting the session itself
164     for (CoreNetwork* net : _networks.values()) {
165         if (net->socketState() != QAbstractSocket::UnconnectedState) {
166             _networksPendingDisconnect.insert(net->networkId());
167             connect(net, &CoreNetwork::disconnected, this, &CoreSession::onNetworkDisconnected);
168             net->shutdown();
169         }
170     }
171
172     if (_networksPendingDisconnect.isEmpty()) {
173         // Nothing to do, suicide so the core can shut down
174         deleteLater();
175     }
176 }
177
178 void CoreSession::onNetworkDisconnected(NetworkId networkId)
179 {
180     _networksPendingDisconnect.remove(networkId);
181     if (_networksPendingDisconnect.isEmpty()) {
182         // We're done, suicide so the core can shut down
183         deleteLater();
184     }
185 }
186
187 CoreNetwork* CoreSession::network(NetworkId id) const
188 {
189     if (_networks.contains(id))
190         return _networks[id];
191     return nullptr;
192 }
193
194 CoreIdentity* CoreSession::identity(IdentityId id) const
195 {
196     if (_identities.contains(id))
197         return _identities[id];
198     return nullptr;
199 }
200
201 void CoreSession::loadSettings()
202 {
203     CoreUserSettings s(user());
204
205     // migrate to db
206     QList<IdentityId> ids = s.identityIds();
207     QList<NetworkInfo> networkInfos = Core::networks(user());
208     foreach (IdentityId id, ids) {
209         CoreIdentity identity(s.identity(id));
210         IdentityId newId = Core::createIdentity(user(), identity);
211         QList<NetworkInfo>::iterator networkIter = networkInfos.begin();
212         while (networkIter != networkInfos.end()) {
213             if (networkIter->identity == id) {
214                 networkIter->identity = newId;
215                 Core::updateNetwork(user(), *networkIter);
216                 networkIter = networkInfos.erase(networkIter);
217             }
218             else {
219                 ++networkIter;
220             }
221         }
222         s.removeIdentity(id);
223     }
224     // end of migration
225
226     foreach (CoreIdentity identity, Core::identities(user())) {
227         createIdentity(identity);
228     }
229
230     foreach (NetworkInfo info, Core::networks(user())) {
231         createNetwork(info);
232     }
233 }
234
235 void CoreSession::saveSessionState() const
236 {
237     _bufferSyncer->storeDirtyIds();
238     _bufferViewManager->saveBufferViews();
239     _networkConfig->save();
240 }
241
242 void CoreSession::restoreSessionState()
243 {
244     QList<NetworkId> nets = Core::connectedNetworks(user());
245     CoreNetwork* net = nullptr;
246     foreach (NetworkId id, nets) {
247         net = network(id);
248         Q_ASSERT(net);
249         net->connectToIrc();
250     }
251 }
252
253 void CoreSession::addClient(RemotePeer* peer)
254 {
255     signalProxy()->setTargetPeer(peer);
256
257     peer->dispatch(sessionState());
258     signalProxy()->addPeer(peer);
259     _coreInfo->setConnectedClientData(signalProxy()->peerCount(), signalProxy()->peerData());
260
261     signalProxy()->setTargetPeer(nullptr);
262 }
263
264 void CoreSession::addClient(InternalPeer* peer)
265 {
266     signalProxy()->addPeer(peer);
267     emit sessionStateReceived(sessionState());
268 }
269
270 void CoreSession::removeClient(Peer* peer)
271 {
272     auto* p = qobject_cast<RemotePeer*>(peer);
273     if (p)
274         qInfo() << qPrintable(tr("Client")) << p->description() << qPrintable(tr("disconnected (UserId: %1).").arg(user().toInt()));
275     _coreInfo->setConnectedClientData(signalProxy()->peerCount(), signalProxy()->peerData());
276 }
277
278 QHash<QString, QString> CoreSession::persistentChannels(NetworkId id) const
279 {
280     return Core::persistentChannels(user(), id);
281 }
282
283 QHash<QString, QByteArray> CoreSession::bufferCiphers(NetworkId id) const
284 {
285     return Core::bufferCiphers(user(), id);
286 }
287
288 void CoreSession::setBufferCipher(NetworkId id, const QString& bufferName, const QByteArray& cipher) const
289 {
290     Core::setBufferCipher(user(), id, bufferName, cipher);
291 }
292
293 // FIXME switch to BufferId
294 void CoreSession::msgFromClient(BufferInfo bufinfo, QString msg)
295 {
296     CoreNetwork* net = network(bufinfo.networkId());
297     if (net) {
298         net->userInput(bufinfo, msg);
299     }
300     else {
301         qWarning() << "Trying to send to unconnected network:" << msg;
302     }
303 }
304
305 // ALL messages coming pass through these functions before going to the GUI.
306 // So this is the perfect place for storing the backlog and log stuff.
307 void CoreSession::recvMessageFromServer(NetworkId networkId,
308                                         Message::Type type,
309                                         BufferInfo::Type bufferType,
310                                         const QString& target,
311                                         const QString& text_,
312                                         const QString& sender,
313                                         Message::Flags flags)
314 {
315     // U+FDD0 and U+FDD1 are special characters for Qt's text engine, specifically they mark the boundaries of
316     // text frames in a QTextDocument. This might lead to problems in widgets displaying QTextDocuments (such as
317     // KDE's notifications), hence we remove those just to be safe.
318     QString text = text_;
319     text.remove(QChar(0xfdd0)).remove(QChar(0xfdd1));
320     RawMessage rawMsg(networkId, type, bufferType, target, text, sender, flags);
321
322     // check for HardStrictness ignore
323     CoreNetwork* currentNetwork = network(networkId);
324     QString networkName = currentNetwork ? currentNetwork->networkName() : QString("");
325     if (_ignoreListManager.match(rawMsg, networkName) == IgnoreListManager::HardStrictness)
326         return;
327
328     if (currentNetwork && _highlightRuleManager.match(rawMsg, currentNetwork->myNick(), currentNetwork->identityPtr()->nicks()))
329         rawMsg.flags |= Message::Flag::Highlight;
330
331     _messageQueue << rawMsg;
332     if (!_processMessages) {
333         _processMessages = true;
334         QCoreApplication::postEvent(this, new ProcessMessagesEvent());
335     }
336 }
337
338 void CoreSession::recvStatusMsgFromServer(QString msg)
339 {
340     auto* net = qobject_cast<CoreNetwork*>(sender());
341     Q_ASSERT(net);
342     emit displayStatusMsg(net->networkName(), msg);
343 }
344
345 void CoreSession::processMessageEvent(MessageEvent* event)
346 {
347     recvMessageFromServer(event->networkId(),
348                           event->msgType(),
349                           event->bufferType(),
350                           event->target().isNull() ? "" : event->target(),
351                           event->text().isNull() ? "" : event->text(),
352                           event->sender().isNull() ? "" : event->sender(),
353                           event->msgFlags());
354 }
355
356 QList<BufferInfo> CoreSession::buffers() const
357 {
358     return Core::requestBuffers(user());
359 }
360
361 void CoreSession::customEvent(QEvent* event)
362 {
363     if (event->type() != QEvent::User)
364         return;
365
366     processMessages();
367     event->accept();
368 }
369
370 void CoreSession::processMessages()
371 {
372     if (_messageQueue.count() == 1) {
373         const RawMessage& rawMsg = _messageQueue.first();
374         bool createBuffer = !(rawMsg.flags & Message::Redirected);
375         BufferInfo bufferInfo = Core::bufferInfo(user(), rawMsg.networkId, rawMsg.bufferType, rawMsg.target, createBuffer);
376         if (!bufferInfo.isValid()) {
377             Q_ASSERT(!createBuffer);
378             bufferInfo = Core::bufferInfo(user(), rawMsg.networkId, BufferInfo::StatusBuffer, "");
379         }
380         Message msg(bufferInfo,
381                     rawMsg.type,
382                     rawMsg.text,
383                     rawMsg.sender,
384                     senderPrefixes(rawMsg.sender, bufferInfo),
385                     realName(rawMsg.sender, rawMsg.networkId),
386                     avatarUrl(rawMsg.sender, rawMsg.networkId),
387                     rawMsg.flags);
388         if (Core::storeMessage(msg))
389             emit displayMsg(msg);
390     }
391     else {
392         QHash<NetworkId, QHash<QString, BufferInfo>> bufferInfoCache;
393         MessageList messages;
394         QList<RawMessage> redirectedMessages;  // list of Messages which don't enforce a buffer creation
395         BufferInfo bufferInfo;
396         for (int i = 0; i < _messageQueue.count(); i++) {
397             const RawMessage& rawMsg = _messageQueue.at(i);
398             if (bufferInfoCache.contains(rawMsg.networkId) && bufferInfoCache[rawMsg.networkId].contains(rawMsg.target)) {
399                 bufferInfo = bufferInfoCache[rawMsg.networkId][rawMsg.target];
400             }
401             else {
402                 bool createBuffer = !(rawMsg.flags & Message::Redirected);
403                 bufferInfo = Core::bufferInfo(user(), rawMsg.networkId, rawMsg.bufferType, rawMsg.target, createBuffer);
404                 if (!bufferInfo.isValid()) {
405                     Q_ASSERT(!createBuffer);
406                     redirectedMessages << rawMsg;
407                     continue;
408                 }
409                 bufferInfoCache[rawMsg.networkId][rawMsg.target] = bufferInfo;
410             }
411             Message msg(bufferInfo,
412                         rawMsg.type,
413                         rawMsg.text,
414                         rawMsg.sender,
415                         senderPrefixes(rawMsg.sender, bufferInfo),
416                         realName(rawMsg.sender, rawMsg.networkId),
417                         avatarUrl(rawMsg.sender, rawMsg.networkId),
418                         rawMsg.flags);
419             messages << msg;
420         }
421
422         // recheck if there exists a buffer to store a redirected message in
423         for (int i = 0; i < redirectedMessages.count(); i++) {
424             const RawMessage& rawMsg = redirectedMessages.at(i);
425             if (bufferInfoCache.contains(rawMsg.networkId) && bufferInfoCache[rawMsg.networkId].contains(rawMsg.target)) {
426                 bufferInfo = bufferInfoCache[rawMsg.networkId][rawMsg.target];
427             }
428             else {
429                 // no luck -> we store them in the StatusBuffer
430                 bufferInfo = Core::bufferInfo(user(), rawMsg.networkId, BufferInfo::StatusBuffer, "");
431                 // add the StatusBuffer to the Cache in case there are more Messages for the original target
432                 bufferInfoCache[rawMsg.networkId][rawMsg.target] = bufferInfo;
433             }
434             Message msg(bufferInfo,
435                         rawMsg.type,
436                         rawMsg.text,
437                         rawMsg.sender,
438                         senderPrefixes(rawMsg.sender, bufferInfo),
439                         realName(rawMsg.sender, rawMsg.networkId),
440                         avatarUrl(rawMsg.sender, rawMsg.networkId),
441                         rawMsg.flags);
442             messages << msg;
443         }
444
445         if (Core::storeMessages(messages)) {
446             // FIXME: extend protocol to a displayMessages(MessageList)
447             for (int i = 0; i < messages.count(); i++) {
448                 emit displayMsg(messages[i]);
449             }
450         }
451     }
452     _processMessages = false;
453     _messageQueue.clear();
454 }
455
456 QString CoreSession::senderPrefixes(const QString& sender, const BufferInfo& bufferInfo) const
457 {
458     CoreNetwork* currentNetwork = network(bufferInfo.networkId());
459     if (!currentNetwork) {
460         return {};
461     }
462
463     if (bufferInfo.type() != BufferInfo::ChannelBuffer) {
464         return {};
465     }
466
467     IrcChannel* currentChannel = currentNetwork->ircChannel(bufferInfo.bufferName());
468     if (!currentChannel) {
469         return {};
470     }
471
472     const QString modes = currentChannel->userModes(nickFromMask(sender).toLower());
473     return currentNetwork->modesToPrefixes(modes);
474 }
475
476 QString CoreSession::realName(const QString& sender, NetworkId networkId) const
477 {
478     CoreNetwork* currentNetwork = network(networkId);
479     if (!currentNetwork) {
480         return {};
481     }
482
483     IrcUser* currentUser = currentNetwork->ircUser(nickFromMask(sender));
484     if (!currentUser) {
485         return {};
486     }
487
488     return currentUser->realName();
489 }
490
491 QString CoreSession::avatarUrl(const QString& sender, NetworkId networkId) const
492 {
493     Q_UNUSED(sender);
494     Q_UNUSED(networkId);
495     // Currently we do not have a way to retrieve this value yet.
496     //
497     // This likely will require implementing IRCv3's METADATA spec.
498     // See https://ircv3.net/irc/
499     // And https://blog.irccloud.com/avatars/
500     return "";
501 }
502
503 Protocol::SessionState CoreSession::sessionState() const
504 {
505     QVariantList bufferInfos;
506     QVariantList networkIds;
507     QVariantList identities;
508
509     foreach (const BufferInfo& id, buffers())
510         bufferInfos << QVariant::fromValue(id);
511     foreach (const NetworkId& id, _networks.keys())
512         networkIds << QVariant::fromValue(id);
513     foreach (const Identity* i, _identities.values())
514         identities << QVariant::fromValue(*i);
515
516     return Protocol::SessionState(identities, bufferInfos, networkIds);
517 }
518
519 void CoreSession::initScriptEngine()
520 {
521     signalProxy()->attachSlot(SIGNAL(scriptRequest(QString)), this, SLOT(scriptRequest(QString)));
522     signalProxy()->attachSignal(this, SIGNAL(scriptResult(QString)));
523
524     // FIXME
525     // QScriptValue storage_ = scriptEngine->newQObject(storage);
526     // scriptEngine->globalObject().setProperty("storage", storage_);
527 }
528
529 void CoreSession::scriptRequest(QString script)
530 {
531     emit scriptResult(scriptEngine->evaluate(script).toString());
532 }
533
534 /*** Identity Handling ***/
535 void CoreSession::createIdentity(const Identity& identity, const QVariantMap& additional)
536 {
537 #ifndef HAVE_SSL
538     Q_UNUSED(additional)
539 #endif
540
541     CoreIdentity coreIdentity(identity);
542 #ifdef HAVE_SSL
543     if (additional.contains("KeyPem"))
544         coreIdentity.setSslKey(additional["KeyPem"].toByteArray());
545     if (additional.contains("CertPem"))
546         coreIdentity.setSslCert(additional["CertPem"].toByteArray());
547 #endif
548     qDebug() << Q_FUNC_INFO;
549     IdentityId id = Core::createIdentity(user(), coreIdentity);
550     if (!id.isValid())
551         return;
552     else
553         createIdentity(coreIdentity);
554 }
555
556 const QString CoreSession::strictCompliantIdent(const CoreIdentity* identity)
557 {
558     if (_strictIdentEnabled) {
559         // Strict mode enabled: only allow the user's Quassel username as an ident
560         return Core::instance()->strictSysIdent(_user);
561     }
562     else {
563         // Strict mode disabled: allow any identity specified
564         return identity->ident();
565     }
566 }
567
568 void CoreSession::createIdentity(const CoreIdentity& identity)
569 {
570     auto* coreIdentity = new CoreIdentity(identity, this);
571     _identities[identity.id()] = coreIdentity;
572     // CoreIdentity has its own synchronize method since its "private" sslManager needs to be synced as well
573     coreIdentity->synchronize(signalProxy());
574     connect(coreIdentity, &SyncableObject::updated, this, &CoreSession::updateIdentityBySender);
575     emit identityCreated(*coreIdentity);
576 }
577
578 void CoreSession::updateIdentityBySender()
579 {
580     auto* identity = qobject_cast<CoreIdentity*>(sender());
581     if (!identity)
582         return;
583     Core::updateIdentity(user(), *identity);
584 }
585
586 void CoreSession::removeIdentity(IdentityId id)
587 {
588     CoreIdentity* identity = _identities.take(id);
589     if (identity) {
590         emit identityRemoved(id);
591         Core::removeIdentity(user(), id);
592         identity->deleteLater();
593     }
594 }
595
596 /*** Network Handling ***/
597
598 void CoreSession::createNetwork(const NetworkInfo& info_, const QStringList& persistentChans)
599 {
600     NetworkInfo info = info_;
601     int id;
602
603     if (!info.networkId.isValid())
604         Core::createNetwork(user(), info);
605
606     if (!info.networkId.isValid()) {
607         qWarning() << qPrintable(
608             tr("CoreSession::createNetwork(): Got invalid networkId from Core when trying to create network %1!").arg(info.networkName));
609         return;
610     }
611
612     id = info.networkId.toInt();
613     if (!_networks.contains(id)) {
614         // create persistent chans
615         QRegExp rx(R"(\s*(\S+)(?:\s*(\S+))?\s*)");
616         foreach (QString channel, persistentChans) {
617             if (!rx.exactMatch(channel)) {
618                 qWarning() << QString("Invalid persistent channel declaration: %1").arg(channel);
619                 continue;
620             }
621             Core::bufferInfo(user(), info.networkId, BufferInfo::ChannelBuffer, rx.cap(1), true);
622             Core::setChannelPersistent(user(), info.networkId, rx.cap(1), true);
623             if (!rx.cap(2).isEmpty())
624                 Core::setPersistentChannelKey(user(), info.networkId, rx.cap(1), rx.cap(2));
625         }
626
627         CoreNetwork* net = new CoreNetwork(id, this);
628         connect(net, &CoreNetwork::displayMsg, this, &CoreSession::recvMessageFromServer);
629         connect(net, &CoreNetwork::displayStatusMsg, this, &CoreSession::recvStatusMsgFromServer);
630         connect(net, &CoreNetwork::disconnected, this, &CoreSession::networkDisconnected);
631
632         net->setNetworkInfo(info);
633         net->setProxy(signalProxy());
634         _networks[id] = net;
635         signalProxy()->synchronize(net);
636         emit networkCreated(id);
637     }
638     else {
639         qWarning() << qPrintable(tr("CoreSession::createNetwork(): Trying to create a network that already exists, updating instead!"));
640         _networks[info.networkId]->requestSetNetworkInfo(info);
641     }
642 }
643
644 void CoreSession::removeNetwork(NetworkId id)
645 {
646     // Make sure the network is disconnected!
647     CoreNetwork* net = network(id);
648     if (!net)
649         return;
650
651     if (net->connectionState() != Network::Disconnected) {
652         // make sure we no longer receive data from the tcp buffer
653         disconnect(net, &CoreNetwork::displayMsg, this, nullptr);
654         disconnect(net, &CoreNetwork::displayStatusMsg, this, nullptr);
655         connect(net, &CoreNetwork::disconnected, this, &CoreSession::destroyNetwork);
656         net->disconnectFromIrc();
657     }
658     else {
659         destroyNetwork(id);
660     }
661 }
662
663 void CoreSession::destroyNetwork(NetworkId id)
664 {
665     QList<BufferId> removedBuffers = Core::requestBufferIdsForNetwork(user(), id);
666     Network* net = _networks.take(id);
667     if (net && Core::removeNetwork(user(), id)) {
668         // make sure that all unprocessed RawMessages from this network are removed
669         QList<RawMessage>::iterator messageIter = _messageQueue.begin();
670         while (messageIter != _messageQueue.end()) {
671             if (messageIter->networkId == id) {
672                 messageIter = _messageQueue.erase(messageIter);
673             }
674             else {
675                 ++messageIter;
676             }
677         }
678         // remove buffers from syncer
679         foreach (BufferId bufferId, removedBuffers) {
680             _bufferSyncer->removeBuffer(bufferId);
681         }
682         emit networkRemoved(id);
683         net->deleteLater();
684     }
685 }
686
687 void CoreSession::renameBuffer(const NetworkId& networkId, const QString& newName, const QString& oldName)
688 {
689     BufferInfo bufferInfo = Core::bufferInfo(user(), networkId, BufferInfo::QueryBuffer, oldName, false);
690     if (bufferInfo.isValid()) {
691         _bufferSyncer->renameBuffer(bufferInfo.bufferId(), newName);
692     }
693 }
694
695 void CoreSession::clientsConnected()
696 {
697     QHash<NetworkId, CoreNetwork*>::iterator netIter = _networks.begin();
698     Identity* identity = nullptr;
699     CoreNetwork* net = nullptr;
700     IrcUser* me = nullptr;
701     while (netIter != _networks.end()) {
702         net = *netIter;
703         ++netIter;
704
705         if (!net->isConnected())
706             continue;
707         identity = net->identityPtr();
708         if (!identity)
709             continue;
710         me = net->me();
711         if (!me)
712             continue;
713
714         if (identity->detachAwayEnabled() && me->isAway()) {
715             net->userInputHandler()->handleAway(BufferInfo(), QString());
716         }
717     }
718 }
719
720 void CoreSession::clientsDisconnected()
721 {
722     QHash<NetworkId, CoreNetwork*>::iterator netIter = _networks.begin();
723     Identity* identity = nullptr;
724     CoreNetwork* net = nullptr;
725     IrcUser* me = nullptr;
726     QString awayReason;
727     while (netIter != _networks.end()) {
728         net = *netIter;
729         ++netIter;
730
731         if (!net->isConnected())
732             continue;
733
734         identity = net->identityPtr();
735         if (!identity)
736             continue;
737         me = net->me();
738         if (!me)
739             continue;
740
741         if (identity->detachAwayEnabled() && !me->isAway()) {
742             if (!identity->detachAwayReason().isEmpty())
743                 awayReason = identity->detachAwayReason();
744             net->setAutoAwayActive(true);
745             // Allow handleAway() to format the current date/time in the string.
746             net->userInputHandler()->handleAway(BufferInfo(), awayReason);
747         }
748     }
749 }
750
751 void CoreSession::globalAway(const QString& msg, const bool skipFormatting)
752 {
753     QHash<NetworkId, CoreNetwork*>::iterator netIter = _networks.begin();
754     CoreNetwork* net = nullptr;
755     while (netIter != _networks.end()) {
756         net = *netIter;
757         ++netIter;
758
759         if (!net->isConnected())
760             continue;
761
762         net->userInputHandler()->issueAway(msg, false /* no force away */, skipFormatting);
763     }
764 }
765
766 void CoreSession::changePassword(PeerPtr peer, const QString& userName, const QString& oldPassword, const QString& newPassword)
767 {
768     Q_UNUSED(peer);
769
770     bool success = false;
771     UserId uid = Core::validateUser(userName, oldPassword);
772     if (uid.isValid() && uid == user())
773         success = Core::changeUserPassword(uid, newPassword);
774
775     signalProxy()->restrictTargetPeers(signalProxy()->sourcePeer(), [&] { emit passwordChanged(nullptr, success); });
776 }
777
778 void CoreSession::kickClient(int peerId)
779 {
780     auto peer = signalProxy()->peerById(peerId);
781     if (peer == nullptr) {
782         qWarning() << "Invalid peer Id: " << peerId;
783         return;
784     }
785     signalProxy()->restrictTargetPeers(peer, [&] { emit disconnectFromCore(); });
786 }