qtui: Fix quit sequence and lifetime issues
[quassel.git] / src / client / client.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 "client.h"
22
23 #include "abstractmessageprocessor.h"
24 #include "abstractui.h"
25 #include "bufferinfo.h"
26 #include "buffermodel.h"
27 #include "buffersettings.h"
28 #include "buffersyncer.h"
29 #include "bufferviewconfig.h"
30 #include "bufferviewoverlay.h"
31 #include "clientaliasmanager.h"
32 #include "clientbacklogmanager.h"
33 #include "clientbufferviewmanager.h"
34 #include "clientirclisthelper.h"
35 #include "clientidentity.h"
36 #include "clientignorelistmanager.h"
37 #include "clienttransfermanager.h"
38 #include "clientuserinputhandler.h"
39 #include "coreaccountmodel.h"
40 #include "coreconnection.h"
41 #include "dccconfig.h"
42 #include "ircchannel.h"
43 #include "ircuser.h"
44 #include "message.h"
45 #include "messagemodel.h"
46 #include "network.h"
47 #include "networkconfig.h"
48 #include "networkmodel.h"
49 #include "quassel.h"
50 #include "signalproxy.h"
51 #include "transfermodel.h"
52 #include "util.h"
53 #include "clientauthhandler.h"
54
55 #include <stdio.h>
56 #include <stdlib.h>
57
58 Client::Client(std::unique_ptr<AbstractUi> ui, QObject *parent)
59     : QObject(parent), Singleton<Client>(this),
60     _signalProxy(new SignalProxy(SignalProxy::Client, this)),
61     _mainUi(std::move(ui)),
62     _networkModel(new NetworkModel(this)),
63     _bufferModel(new BufferModel(_networkModel)),
64     _bufferSyncer(0),
65     _aliasManager(0),
66     _backlogManager(new ClientBacklogManager(this)),
67     _bufferViewManager(0),
68     _bufferViewOverlay(new BufferViewOverlay(this)),
69     _coreInfo(new CoreInfo(this)),
70     _dccConfig(0),
71     _ircListHelper(new ClientIrcListHelper(this)),
72     _inputHandler(new ClientUserInputHandler(this)),
73     _networkConfig(0),
74     _ignoreListManager(0),
75     _highlightRuleManager(0),
76     _transferManager(0),
77     _transferModel(new TransferModel(this)),
78     _messageModel(_mainUi->createMessageModel(this)),
79     _messageProcessor(_mainUi->createMessageProcessor(this)),
80     _coreAccountModel(new CoreAccountModel(this)),
81     _coreConnection(new CoreConnection(this)),
82     _connected(false)
83 {
84     //connect(mainUi(), SIGNAL(connectToCore(const QVariantMap &)), this, SLOT(connectToCore(const QVariantMap &)));
85     connect(mainUi(), SIGNAL(disconnectFromCore()), this, SLOT(disconnectFromCore()));
86     connect(this, SIGNAL(connected()), mainUi(), SLOT(connectedToCore()));
87     connect(this, SIGNAL(disconnected()), mainUi(), SLOT(disconnectedFromCore()));
88
89     connect(this, SIGNAL(networkRemoved(NetworkId)), _networkModel, SLOT(networkRemoved(NetworkId)));
90     connect(this, SIGNAL(networkRemoved(NetworkId)), _messageProcessor, SLOT(networkRemoved(NetworkId)));
91
92     connect(backlogManager(), SIGNAL(messagesReceived(BufferId, int)), _messageModel, SLOT(messagesReceived(BufferId, int)));
93     connect(coreConnection(), SIGNAL(stateChanged(CoreConnection::ConnectionState)), SLOT(connectionStateChanged(CoreConnection::ConnectionState)));
94
95     SignalProxy *p = signalProxy();
96
97     p->attachSlot(SIGNAL(displayMsg(const Message &)), this, SLOT(recvMessage(const Message &)));
98     p->attachSlot(SIGNAL(displayStatusMsg(QString, QString)), this, SLOT(recvStatusMsg(QString, QString)));
99
100     p->attachSlot(SIGNAL(bufferInfoUpdated(BufferInfo)), _networkModel, SLOT(bufferUpdated(BufferInfo)));
101     p->attachSignal(inputHandler(), SIGNAL(sendInput(BufferInfo, QString)));
102     p->attachSignal(this, SIGNAL(requestNetworkStates()));
103
104     p->attachSignal(this, SIGNAL(requestCreateIdentity(const Identity &, const QVariantMap &)), SIGNAL(createIdentity(const Identity &, const QVariantMap &)));
105     p->attachSignal(this, SIGNAL(requestRemoveIdentity(IdentityId)), SIGNAL(removeIdentity(IdentityId)));
106     p->attachSlot(SIGNAL(identityCreated(const Identity &)), this, SLOT(coreIdentityCreated(const Identity &)));
107     p->attachSlot(SIGNAL(identityRemoved(IdentityId)), this, SLOT(coreIdentityRemoved(IdentityId)));
108
109     p->attachSignal(this, SIGNAL(requestCreateNetwork(const NetworkInfo &, const QStringList &)), SIGNAL(createNetwork(const NetworkInfo &, const QStringList &)));
110     p->attachSignal(this, SIGNAL(requestRemoveNetwork(NetworkId)), SIGNAL(removeNetwork(NetworkId)));
111     p->attachSlot(SIGNAL(networkCreated(NetworkId)), this, SLOT(coreNetworkCreated(NetworkId)));
112     p->attachSlot(SIGNAL(networkRemoved(NetworkId)), this, SLOT(coreNetworkRemoved(NetworkId)));
113
114     p->attachSignal(this, SIGNAL(requestPasswordChange(PeerPtr,QString,QString,QString)), SIGNAL(changePassword(PeerPtr,QString,QString,QString)));
115     p->attachSlot(SIGNAL(passwordChanged(PeerPtr,bool)), this, SLOT(corePasswordChanged(PeerPtr,bool)));
116
117     p->attachSignal(this, SIGNAL(requestKickClient(int)), SIGNAL(kickClient(int)));
118     p->attachSlot(SIGNAL(disconnectFromCore()), this, SLOT(disconnectFromCore()));
119
120     p->synchronize(backlogManager());
121     p->synchronize(coreInfo());
122     p->synchronize(_ircListHelper);
123
124     coreAccountModel()->load();
125     coreConnection()->init();
126 }
127
128
129 Client::~Client()
130 {
131     disconnectFromCore();
132 }
133
134
135 AbstractUi *Client::mainUi()
136 {
137     return instance()->_mainUi.get();
138 }
139
140
141 bool Client::isCoreFeatureEnabled(Quassel::Feature feature)
142 {
143     return coreConnection()->peer() ? coreConnection()->peer()->hasFeature(feature) : false;
144 }
145
146
147 bool Client::isConnected()
148 {
149     return instance()->_connected;
150 }
151
152
153 bool Client::internalCore()
154 {
155     return currentCoreAccount().isInternal();
156 }
157
158
159 void Client::onDbUpgradeInProgress(bool inProgress)
160 {
161     emit dbUpgradeInProgress(inProgress);
162 }
163
164
165 void Client::onExitRequested(int exitCode, const QString &reason)
166 {
167     if (!reason.isEmpty()) {
168         qCritical() << reason;
169         emit exitRequested(reason);
170     }
171     QCoreApplication::exit(exitCode);
172 }
173
174
175 /*** Network handling ***/
176
177 QList<NetworkId> Client::networkIds()
178 {
179     return instance()->_networks.keys();
180 }
181
182
183 const Network *Client::network(NetworkId networkid)
184 {
185     if (instance()->_networks.contains(networkid)) return instance()->_networks[networkid];
186     else return 0;
187 }
188
189
190 void Client::createNetwork(const NetworkInfo &info, const QStringList &persistentChannels)
191 {
192     emit instance()->requestCreateNetwork(info, persistentChannels);
193 }
194
195
196 void Client::removeNetwork(NetworkId id)
197 {
198     emit instance()->requestRemoveNetwork(id);
199 }
200
201
202 void Client::updateNetwork(const NetworkInfo &info)
203 {
204     Network *netptr = instance()->_networks.value(info.networkId, 0);
205     if (!netptr) {
206         qWarning() << "Update for unknown network requested:" << info;
207         return;
208     }
209     netptr->requestSetNetworkInfo(info);
210 }
211
212
213 void Client::addNetwork(Network *net)
214 {
215     net->setProxy(signalProxy());
216     signalProxy()->synchronize(net);
217     networkModel()->attachNetwork(net);
218     connect(net, SIGNAL(destroyed()), instance(), SLOT(networkDestroyed()));
219     instance()->_networks[net->networkId()] = net;
220     emit instance()->networkCreated(net->networkId());
221 }
222
223
224 void Client::coreNetworkCreated(NetworkId id)
225 {
226     if (_networks.contains(id)) {
227         qWarning() << "Creation of already existing network requested!";
228         return;
229     }
230     Network *net = new Network(id, this);
231     addNetwork(net);
232 }
233
234
235 void Client::coreNetworkRemoved(NetworkId id)
236 {
237     if (!_networks.contains(id))
238         return;
239     Network *net = _networks.take(id);
240     emit networkRemoved(net->networkId());
241     net->deleteLater();
242 }
243
244
245 /*** Identity handling ***/
246
247 QList<IdentityId> Client::identityIds()
248 {
249     return instance()->_identities.keys();
250 }
251
252
253 const Identity *Client::identity(IdentityId id)
254 {
255     if (instance()->_identities.contains(id)) return instance()->_identities[id];
256     else return 0;
257 }
258
259
260 void Client::createIdentity(const CertIdentity &id)
261 {
262     QVariantMap additional;
263 #ifdef HAVE_SSL
264     additional["KeyPem"] = id.sslKey().toPem();
265     additional["CertPem"] = id.sslCert().toPem();
266 #endif
267     emit instance()->requestCreateIdentity(id, additional);
268 }
269
270
271 void Client::updateIdentity(IdentityId id, const QVariantMap &ser)
272 {
273     Identity *idptr = instance()->_identities.value(id, 0);
274     if (!idptr) {
275         qWarning() << "Update for unknown identity requested:" << id;
276         return;
277     }
278     idptr->requestUpdate(ser);
279 }
280
281
282 void Client::removeIdentity(IdentityId id)
283 {
284     emit instance()->requestRemoveIdentity(id);
285 }
286
287
288 void Client::coreIdentityCreated(const Identity &other)
289 {
290     if (!_identities.contains(other.id())) {
291         Identity *identity = new Identity(other, this);
292         _identities[other.id()] = identity;
293         identity->setInitialized();
294         signalProxy()->synchronize(identity);
295         emit identityCreated(other.id());
296     }
297     else {
298         qWarning() << tr("Identity already exists in client!");
299     }
300 }
301
302
303 void Client::coreIdentityRemoved(IdentityId id)
304 {
305     if (_identities.contains(id)) {
306         emit identityRemoved(id);
307         Identity *i = _identities.take(id);
308         i->deleteLater();
309     }
310 }
311
312
313 /*** User input handling ***/
314
315 void Client::userInput(const BufferInfo &bufferInfo, const QString &message)
316 {
317     // we need to make sure that AliasManager is ready before processing input
318     if (aliasManager() && aliasManager()->isInitialized())
319         inputHandler()->handleUserInput(bufferInfo, message);
320     else
321         instance()->_userInputBuffer.append(qMakePair(bufferInfo, message));
322 }
323
324
325 void Client::sendBufferedUserInput()
326 {
327     for (int i = 0; i < _userInputBuffer.count(); i++)
328         userInput(_userInputBuffer.at(i).first, _userInputBuffer.at(i).second);
329
330     _userInputBuffer.clear();
331 }
332
333
334 /*** core connection stuff ***/
335
336 void Client::connectionStateChanged(CoreConnection::ConnectionState state)
337 {
338     switch (state) {
339     case CoreConnection::Disconnected:
340         setDisconnectedFromCore();
341         break;
342     case CoreConnection::Synchronized:
343         setSyncedToCore();
344         break;
345     default:
346         break;
347     }
348 }
349
350
351 void Client::setSyncedToCore()
352 {
353     // create buffersyncer
354     Q_ASSERT(!_bufferSyncer);
355     _bufferSyncer = new BufferSyncer(this);
356     connect(bufferSyncer(), SIGNAL(lastSeenMsgSet(BufferId, MsgId)), _networkModel, SLOT(setLastSeenMsgId(BufferId, MsgId)));
357     connect(bufferSyncer(), SIGNAL(markerLineSet(BufferId, MsgId)), _networkModel, SLOT(setMarkerLineMsgId(BufferId, MsgId)));
358     connect(bufferSyncer(), SIGNAL(bufferRemoved(BufferId)), this, SLOT(bufferRemoved(BufferId)));
359     connect(bufferSyncer(), SIGNAL(bufferRenamed(BufferId, QString)), this, SLOT(bufferRenamed(BufferId, QString)));
360     connect(bufferSyncer(), SIGNAL(buffersPermanentlyMerged(BufferId, BufferId)), this, SLOT(buffersPermanentlyMerged(BufferId, BufferId)));
361     connect(bufferSyncer(), SIGNAL(buffersPermanentlyMerged(BufferId, BufferId)), _messageModel, SLOT(buffersPermanentlyMerged(BufferId, BufferId)));
362     connect(bufferSyncer(), SIGNAL(bufferMarkedAsRead(BufferId)), SIGNAL(bufferMarkedAsRead(BufferId)));
363     connect(bufferSyncer(), SIGNAL(bufferActivityChanged(BufferId, const Message::Types)), _networkModel, SLOT(bufferActivityChanged(BufferId, const Message::Types)));
364     connect(bufferSyncer(), SIGNAL(highlightCountChanged(BufferId, int)), _networkModel, SLOT(highlightCountChanged(BufferId, int)));
365     connect(networkModel(), SIGNAL(requestSetLastSeenMsg(BufferId, MsgId)), bufferSyncer(), SLOT(requestSetLastSeenMsg(BufferId, const MsgId &)));
366
367     SignalProxy *p = signalProxy();
368     p->synchronize(bufferSyncer());
369
370     // create a new BufferViewManager
371     Q_ASSERT(!_bufferViewManager);
372     _bufferViewManager = new ClientBufferViewManager(p, this);
373     connect(_bufferViewManager, SIGNAL(initDone()), _bufferViewOverlay, SLOT(restore()));
374
375     // create AliasManager
376     Q_ASSERT(!_aliasManager);
377     _aliasManager = new ClientAliasManager(this);
378     connect(aliasManager(), SIGNAL(initDone()), SLOT(sendBufferedUserInput()));
379     p->synchronize(aliasManager());
380
381     // create NetworkConfig
382     Q_ASSERT(!_networkConfig);
383     _networkConfig = new NetworkConfig("GlobalNetworkConfig", this);
384     p->synchronize(networkConfig());
385
386     // create IgnoreListManager
387     Q_ASSERT(!_ignoreListManager);
388     _ignoreListManager = new ClientIgnoreListManager(this);
389     p->synchronize(ignoreListManager());
390
391     // create Core-Side HighlightRuleManager
392     Q_ASSERT(!_highlightRuleManager);
393     _highlightRuleManager = new HighlightRuleManager(this);
394     p->synchronize(highlightRuleManager());
395     // Listen to network removed events
396     connect(this, SIGNAL(networkRemoved(NetworkId)),
397         _highlightRuleManager, SLOT(networkRemoved(NetworkId)));
398
399 /*  not ready yet
400     // create TransferManager and DccConfig if core supports them
401     Q_ASSERT(!_dccConfig);
402     Q_ASSERT(!_transferManager);
403     if (isCoreFeatureEnabled(Quassel::Feature::DccFileTransfer)) {
404         _dccConfig = new DccConfig(this);
405         p->synchronize(dccConfig());
406         _transferManager = new ClientTransferManager(this);
407         _transferModel->setManager(_transferManager);
408         p->synchronize(transferManager());
409     }
410 */
411
412     // trigger backlog request once all active bufferviews are initialized
413     connect(bufferViewOverlay(), SIGNAL(initDone()), this, SLOT(finishConnectionInitialization()));
414
415     _connected = true;
416     emit connected();
417     emit coreConnectionStateChanged(true);
418 }
419
420 void Client::finishConnectionInitialization()
421 {
422     // usually it _should_ take longer until the bufferViews are initialized, so that's what
423     // triggers this slot. But we have to make sure that we know all buffers yet.
424     // so we check the BufferSyncer and in case it wasn't initialized we wait for that instead
425     if (!bufferSyncer()->isInitialized()) {
426         disconnect(bufferViewOverlay(), SIGNAL(initDone()), this, SLOT(finishConnectionInitialization()));
427         connect(bufferSyncer(), SIGNAL(initDone()), this, SLOT(finishConnectionInitialization()));
428         return;
429     }
430     disconnect(bufferViewOverlay(), SIGNAL(initDone()), this, SLOT(finishConnectionInitialization()));
431     disconnect(bufferSyncer(), SIGNAL(initDone()), this, SLOT(finishConnectionInitialization()));
432
433     requestInitialBacklog();
434     if (isCoreFeatureEnabled(Quassel::Feature::BufferActivitySync)) {
435         bufferSyncer()->markActivitiesChanged();
436         bufferSyncer()->markHighlightCountsChanged();
437     }
438 }
439
440
441 void Client::requestInitialBacklog()
442 {
443     _backlogManager->requestInitialBacklog();
444 }
445
446
447 void Client::requestLegacyCoreInfo()
448 {
449     // On older cores, the CoreInfo object was only synchronized on demand.  Synchronize now if
450     // needed.
451     if (isConnected() && !isCoreFeatureEnabled(Quassel::Feature::SyncedCoreInfo)) {
452         // Delete the existing core info object (it will always exist as client is single-threaded)
453         _coreInfo->deleteLater();
454         // No need to set to null when creating new one immediately after
455
456         // Create a fresh, unsynchronized CoreInfo object, emulating legacy behavior of CoreInfo not
457         // persisting
458         _coreInfo = new CoreInfo(this);
459         // Synchronize the new object
460         signalProxy()->synchronize(_coreInfo);
461
462         // Let others know signal handlers have been reset
463         emit coreInfoResynchronized();
464     }
465 }
466
467
468 void Client::disconnectFromCore()
469 {
470     if (!coreConnection()->isConnected())
471         return;
472
473     coreConnection()->disconnectFromCore();
474 }
475
476
477 void Client::setDisconnectedFromCore()
478 {
479     _connected = false;
480
481     emit disconnected();
482     emit coreConnectionStateChanged(false);
483
484     backlogManager()->reset();
485     messageProcessor()->reset();
486
487     // Clear internal data. Hopefully nothing relies on it at this point.
488
489     if (_bufferSyncer) {
490         _bufferSyncer->deleteLater();
491         _bufferSyncer = 0;
492     }
493
494     _coreInfo->reset();
495
496     if (_bufferViewManager) {
497         _bufferViewManager->deleteLater();
498         _bufferViewManager = 0;
499     }
500
501     _bufferViewOverlay->reset();
502
503     if (_aliasManager) {
504         _aliasManager->deleteLater();
505         _aliasManager = 0;
506     }
507
508     if (_ignoreListManager) {
509         _ignoreListManager->deleteLater();
510         _ignoreListManager = 0;
511     }
512
513     if (_highlightRuleManager) {
514         _highlightRuleManager->deleteLater();
515         _highlightRuleManager = nullptr;
516     }
517
518     if (_transferManager) {
519         _transferModel->setManager(nullptr);
520         _transferManager->deleteLater();
521         _transferManager = nullptr;
522     }
523
524     if (_dccConfig) {
525         _dccConfig->deleteLater();
526         _dccConfig = nullptr;
527     }
528
529     // we probably don't want to save pending input for reconnect
530     _userInputBuffer.clear();
531
532     _messageModel->clear();
533     _networkModel->clear();
534
535     QHash<NetworkId, Network *>::iterator netIter = _networks.begin();
536     while (netIter != _networks.end()) {
537         Network *net = netIter.value();
538         emit networkRemoved(net->networkId());
539         disconnect(net, SIGNAL(destroyed()), this, 0);
540         netIter = _networks.erase(netIter);
541         net->deleteLater();
542     }
543     Q_ASSERT(_networks.isEmpty());
544
545     QHash<IdentityId, Identity *>::iterator idIter = _identities.begin();
546     while (idIter != _identities.end()) {
547         emit identityRemoved(idIter.key());
548         Identity *id = idIter.value();
549         idIter = _identities.erase(idIter);
550         id->deleteLater();
551     }
552     Q_ASSERT(_identities.isEmpty());
553
554     if (_networkConfig) {
555         _networkConfig->deleteLater();
556         _networkConfig = 0;
557     }
558 }
559
560
561 /*** ***/
562
563 void Client::networkDestroyed()
564 {
565     Network *net = static_cast<Network *>(sender());
566     QHash<NetworkId, Network *>::iterator netIter = _networks.begin();
567     while (netIter != _networks.end()) {
568         if (*netIter == net) {
569             netIter = _networks.erase(netIter);
570             break;
571         }
572         else {
573             ++netIter;
574         }
575     }
576 }
577
578
579 // Hmm... we never used this...
580 void Client::recvStatusMsg(QString /*net*/, QString /*msg*/)
581 {
582     //recvMessage(net, Message::server("", QString("[STATUS] %1").arg(msg)));
583 }
584
585
586 void Client::recvMessage(const Message &msg)
587 {
588     Message msg_ = msg;
589     messageProcessor()->process(msg_);
590 }
591
592
593 void Client::setBufferLastSeenMsg(BufferId id, const MsgId &msgId)
594 {
595     if (bufferSyncer())
596         bufferSyncer()->requestSetLastSeenMsg(id, msgId);
597 }
598
599
600 void Client::setMarkerLine(BufferId id, const MsgId &msgId)
601 {
602     if (bufferSyncer())
603         bufferSyncer()->requestSetMarkerLine(id, msgId);
604 }
605
606
607 MsgId Client::markerLine(BufferId id)
608 {
609     if (id.isValid() && networkModel())
610         return networkModel()->markerLineMsgId(id);
611     return MsgId();
612 }
613
614
615 void Client::removeBuffer(BufferId id)
616 {
617     if (!bufferSyncer()) return;
618     bufferSyncer()->requestRemoveBuffer(id);
619 }
620
621
622 void Client::renameBuffer(BufferId bufferId, const QString &newName)
623 {
624     if (!bufferSyncer())
625         return;
626     bufferSyncer()->requestRenameBuffer(bufferId, newName);
627 }
628
629
630 void Client::mergeBuffersPermanently(BufferId bufferId1, BufferId bufferId2)
631 {
632     if (!bufferSyncer())
633         return;
634     bufferSyncer()->requestMergeBuffersPermanently(bufferId1, bufferId2);
635 }
636
637
638 void Client::purgeKnownBufferIds()
639 {
640     if (!bufferSyncer())
641         return;
642     bufferSyncer()->requestPurgeBufferIds();
643 }
644
645
646 void Client::bufferRemoved(BufferId bufferId)
647 {
648     // select a sane buffer (status buffer)
649     /* we have to manually select a buffer because otherwise inconsitent changes
650      * to the model might occur:
651      * the result of a buffer removal triggers a change in the selection model.
652      * the newly selected buffer might be a channel that hasn't been selected yet
653      * and a new nickview would be created (which never heard of the "rowsAboutToBeRemoved").
654      * this new view (and/or) its sort filter will then only receive a "rowsRemoved" signal.
655      */
656     QModelIndex current = bufferModel()->currentIndex();
657     if (current.data(NetworkModel::BufferIdRole).value<BufferId>() == bufferId) {
658         bufferModel()->setCurrentIndex(current.sibling(0, 0));
659     }
660
661     // and remove it from the model
662     networkModel()->removeBuffer(bufferId);
663 }
664
665
666 void Client::bufferRenamed(BufferId bufferId, const QString &newName)
667 {
668     QModelIndex bufferIndex = networkModel()->bufferIndex(bufferId);
669     if (bufferIndex.isValid()) {
670         networkModel()->setData(bufferIndex, newName, Qt::DisplayRole);
671     }
672 }
673
674
675 void Client::buffersPermanentlyMerged(BufferId bufferId1, BufferId bufferId2)
676 {
677     QModelIndex idx = networkModel()->bufferIndex(bufferId1);
678     bufferModel()->setCurrentIndex(bufferModel()->mapFromSource(idx));
679     networkModel()->removeBuffer(bufferId2);
680 }
681
682
683 void Client::markBufferAsRead(BufferId id)
684 {
685     if (bufferSyncer() && id.isValid())
686         bufferSyncer()->requestMarkBufferAsRead(id);
687 }
688
689
690 void Client::refreshLegacyCoreInfo()
691 {
692     instance()->requestLegacyCoreInfo();
693 }
694
695
696 void Client::changePassword(const QString &oldPassword, const QString &newPassword) {
697     CoreAccount account = currentCoreAccount();
698     account.setPassword(newPassword);
699     coreAccountModel()->createOrUpdateAccount(account);
700     emit instance()->requestPasswordChange(nullptr, account.user(), oldPassword, newPassword);
701 }
702
703
704 void Client::kickClient(int peerId)
705 {
706     emit instance()->requestKickClient(peerId);
707 }
708
709
710 void Client::corePasswordChanged(PeerPtr, bool success)
711 {
712     if (success)
713         coreAccountModel()->save();
714     emit passwordChanged(success);
715 }