1 /*
2 kopeteaccountmanager.cpp - Kopete Account Manager
3
4 Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org>
5 Copyright (c) 2003-2004 by Olivier Goffart <ogoffart@kde.org>
6
7 Kopete (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org>
8
9 *************************************************************************
10 * *
11 * This library is free software; you can redistribute it and/or *
12 * modify it under the terms of the GNU Lesser General Public *
13 * License as published by the Free Software Foundation; either *
14 * version 2 of the License, or (at your option) any later version. *
15 * *
16 *************************************************************************
17 */
18
19 #include "kopeteaccountmanager.h"
20
21 #include <QApplication>
22 #include <QRegExp>
23 #include <QTimer>
24 #include <QHash>
25 #include <QDBusInterface>
26
27 #include <ksharedconfig.h>
28
29 #include <kplugininfo.h>
30 #include <kconfiggroup.h>
31 #include <solid/networking.h>
32 #include <solid/powermanagement.h>
33 #include <KSharedConfig>
34
35 #include "kopeteaccount.h"
36 #include "kopetebehaviorsettings.h"
37 #include "kopeteprotocol.h"
38 #include "kopetecontact.h"
39 #include "kopetecontactlist.h"
40 #include "kopeteidentitymanager.h"
41 #include "kopetepluginmanager.h"
42 #include "kopetestatusitems.h"
43 #include "kopeteonlinestatus.h"
44 #include "kopeteonlinestatusmanager.h"
45 #include "kopetemetacontact.h"
46 #include "kopetegroup.h"
47 #include "kopetestatusmanager.h"
48
49 namespace Kopete {
compareAccountsByPriority(Account * a,Account * b)50 static int compareAccountsByPriority(Account *a, Account *b)
51 {
52 uint priority1 = a->priority();
53 uint priority2 = b->priority();
54
55 if (a == b) { //two account are equal only if they are equal :-)
56 return 0; // remember than an account can be only once on the list, but two account may have the same priority when loading
57 } else if (priority1 > priority2) {
58 return 1;
59 } else {
60 return -1;
61 }
62 }
63
64 class AccountManager::Private
65 {
66 public:
67 QList<Account *> accounts;
68 QList<Account *> accountsToBeRemoved;
69 bool suspended;
70 Kopete::StatusMessage suspendedStatusMessage;
71 uint suspendedStatusCategory;
72 };
73
74 AccountManager *AccountManager::s_self = nullptr;
75
self()76 AccountManager *AccountManager::self()
77 {
78 if (!s_self) {
79 s_self = new AccountManager;
80 }
81
82 return s_self;
83 }
84
AccountManager()85 AccountManager::AccountManager()
86 : QObject(qApp)
87 , d(new Private())
88 {
89 setObjectName(QStringLiteral("KopeteAccountManager"));
90 connect(Solid::Networking::notifier(), SIGNAL(shouldConnect()), this, SLOT(networkConnected()));
91 connect(Solid::Networking::notifier(), SIGNAL(shouldDisconnect()), this, SLOT(networkDisconnected()));
92 connect(Solid::PowerManagement::notifier(), SIGNAL(resumingFromSuspend()), this, SLOT(resume()));
93 #ifdef __GNUC__
94 #warning TODO: Switch to a org.kde.Solid.PowerManagement Sleeping/Suspending signal when available.
95 #endif
96 QDBusConnection::systemBus().connect(QStringLiteral("org.freedesktop.UPower"), QStringLiteral("/org/freedesktop/UPower"), QLatin1String(""), QStringLiteral("Sleeping"), this, SLOT(suspend()));
97 d->suspended = false;
98 }
99
~AccountManager()100 AccountManager::~AccountManager()
101 {
102 s_self = nullptr;
103
104 delete d;
105 }
106
isAnyAccountConnected() const107 bool AccountManager::isAnyAccountConnected() const
108 {
109 foreach (Account *a, d->accounts) {
110 if (a->isConnected()) {
111 return true;
112 }
113 }
114
115 return false;
116 }
117
setOnlineStatus(uint category,const Kopete::StatusMessage & statusMessage,uint flags,bool forced)118 void AccountManager::setOnlineStatus(uint category, const Kopete::StatusMessage &statusMessage, uint flags, bool forced)
119 {
120 qCDebug(LIBKOPETE_LOG) << "category: " << category << "status title: " << statusMessage.title() << "status message: " << statusMessage.message();
121 OnlineStatusManager::Categories categories
122 = (OnlineStatusManager::Categories)category;
123 const bool onlyChangeConnectedAccounts = (!forced && isAnyAccountConnected());
124 d->suspended = false;
125
126 foreach (Account *account, d->accounts) {
127 Kopete::OnlineStatus status = OnlineStatusManager::self()->onlineStatus(account->protocol(), categories);
128 // Going offline is always respected
129 if (category & Kopete::OnlineStatusManager::Offline) {
130 account->setOnlineStatus(status, statusMessage);
131 continue;
132 }
133
134 if (onlyChangeConnectedAccounts) { //If global status is offline, change all account to new status
135 if (account->isConnected()
136 || (((flags & ConnectIfOffline) || Kopete::StatusManager::self()->globalStatusCategory() == Kopete::OnlineStatusManager::Offline) && !account->excludeConnect())) {
137 account->setOnlineStatus(status, statusMessage);
138 }
139 } else {
140 if (!account->excludeConnect()) {
141 account->setOnlineStatus(status, statusMessage);
142 }
143 }
144 }
145 // mark ourselves as globally away if appropriate
146 Kopete::StatusManager::self()->setGlobalStatus(category, statusMessage);
147 }
148
setOnlineStatus(uint category,const Kopete::StatusMessage & statusMessage,uint flags)149 void AccountManager::setOnlineStatus(uint category, const Kopete::StatusMessage &statusMessage, uint flags)
150 {
151 setOnlineStatus(category, statusMessage, flags, false);
152 }
153
setStatusMessage(const QString & message)154 void AccountManager::setStatusMessage(const QString &message)
155 {
156 foreach (Account *account, d->accounts) {
157 account->setStatusMessage(message);
158 }
159 }
160
suspend()161 void AccountManager::suspend()
162 {
163 if (d->suspended) {
164 return;
165 }
166
167 d->suspended = true;
168 d->suspendedStatusMessage = Kopete::StatusManager::self()->globalStatusMessage();
169 d->suspendedStatusCategory = Kopete::StatusManager::self()->globalStatusCategory();
170
171 Kopete::StatusMessage statusMessage(i18n("Offline"), QLatin1String(""));
172 QList <Kopete::Status::StatusItem *> statusList = Kopete::StatusManager::self()->getRootGroup()->childList();
173 //find first Status for OffineStatus
174 for (QList <Kopete::Status::StatusItem *>::ConstIterator it = statusList.constBegin(); it != statusList.constEnd(); ++it) {
175 if (!(*it)->isGroup() && (*it)->category() == Kopete::OnlineStatusManager::Offline) {
176 QString message, title;
177 title = (*it)->title();
178 message = (static_cast <Kopete::Status::Status *>(*it))->message(); //if it is not group, it's status
179 statusMessage.setTitle(title);
180 statusMessage.setMessage(message);
181 break;
182 }
183 }
184
185 foreach (Account *account, d->accounts) {
186 account->suspend(statusMessage);
187 }
188 Kopete::StatusManager::self()->setGlobalStatus(Kopete::OnlineStatusManager::Offline, statusMessage);
189 }
190
resume()191 bool AccountManager::resume()
192 {
193 bool networkAvailable = (Solid::Networking::status() == Solid::Networking::Unknown || Solid::Networking::status() == Solid::Networking::Connected);
194 if (!d->suspended || !networkAvailable) {
195 return false;
196 }
197
198 foreach (Account *account, d->accounts) {
199 account->resume();
200 }
201 Kopete::StatusManager::self()->setGlobalStatus(d->suspendedStatusCategory, d->suspendedStatusMessage);
202 d->suspended = false;
203 return true;
204 }
205
guessColor(Protocol * protocol) const206 QColor AccountManager::guessColor(Protocol *protocol) const
207 {
208 // In a perfect wold, we should check if the color is actually not used by the account.
209 // Anyway, this is not really required, It would be a difficult job for about nothing more.
210 // -- Olivier
211 int protocolCount = 0;
212
213 for (QListIterator<Account *> it(d->accounts); it.hasNext();) {
214 Account *a = it.next();
215 if (a->protocol()->pluginId() == protocol->pluginId()) {
216 protocolCount++;
217 }
218 }
219
220 // let's figure a color
221 QColor color;
222 switch (protocolCount % 7) {
223 case 0:
224 color = QColor();
225 break;
226 case 1:
227 color = Qt::red;
228 break;
229 case 2:
230 color = Qt::green;
231 break;
232 case 3:
233 color = Qt::blue;
234 break;
235 case 4:
236 color = Qt::yellow;
237 break;
238 case 5:
239 color = Qt::magenta;
240 break;
241 case 6:
242 color = Qt::cyan;
243 break;
244 }
245
246 return color;
247 }
248
registerAccount(Account * account)249 Account *AccountManager::registerAccount(Account *account)
250 {
251 if (!account || d->accounts.contains(account)) {
252 return account;
253 }
254
255 if (account->accountId().isEmpty()) {
256 account->deleteLater();
257 return nullptr;
258 }
259
260 // If this account already exists, do nothing
261 QListIterator<Account *> it(d->accounts);
262 while (it.hasNext())
263 {
264 Account *curracc = it.next();
265 if ((account->protocol() == curracc->protocol()) && (account->accountId() == curracc->accountId())) {
266 account->deleteLater();
267 return nullptr;
268 }
269 }
270
271 d->accounts.append(account);
272 qSort(d->accounts.begin(), d->accounts.end(), compareAccountsByPriority);
273
274 // Connect to the account's status changed signal
275 connect(account->myself(), SIGNAL(onlineStatusChanged(Kopete::Contact *,
276 const Kopete::OnlineStatus&,const Kopete::OnlineStatus&)),
277 this, SLOT(slotAccountOnlineStatusChanged(Kopete::Contact *,
278 const Kopete::OnlineStatus&,const Kopete::OnlineStatus&)));
279
280 connect(account, SIGNAL(accountDestroyed(const Kopete::Account*)), this, SLOT(unregisterAccount(const Kopete::Account*)));
281
282 if (!account->identity()) {
283 // the account's Identity must be set here instead of in the Kopete::Account ctor, because there the
284 // identity cannot pick up any state set in the derived Account ctor
285 Identity *identity = Kopete::IdentityManager::self()->findIdentity(account->configGroup()->readEntry("Identity", QString()));
286 // if the identity was not found, use the default one which will for sure exist
287 // FIXME: get rid of this, the account's identity should always exist at this point
288 if (!identity) {
289 qCWarning(LIBKOPETE_LOG) << "No identity for account " << account->accountId() << ": falling back to default";
290 identity = Kopete::IdentityManager::self()->defaultIdentity();
291 }
292 account->setIdentity(identity);
293 }
294
295 emit accountRegistered(account);
296 return account;
297 }
298
unregisterAccount(const Account * account)299 void AccountManager::unregisterAccount(const Account *account)
300 {
301 qCDebug(LIBKOPETE_LOG) << "Unregistering account " << account->accountId();
302 d->accounts.removeAll(const_cast<Account *>(account));
303 emit accountUnregistered(account);
304 }
305
accounts() const306 const QList<Account *> &AccountManager::accounts() const
307 {
308 return d->accounts;
309 }
310
accounts(Protocol * protocol) const311 QList<Account *> AccountManager::accounts(Protocol *protocol) const
312 {
313 QList<Account *> protocolAccounts;
314 foreach (Account *acct, d->accounts) {
315 if (acct->protocol() == protocol) {
316 protocolAccounts.append(acct);
317 }
318 }
319 return protocolAccounts;
320 }
321
findAccount(const QString & protocolId,const QString & accountId)322 Account *AccountManager::findAccount(const QString &protocolId, const QString &accountId)
323 {
324 for (QListIterator<Account *> it(d->accounts); it.hasNext();) {
325 Account *a = it.next();
326 if (a->protocol()->pluginId() == protocolId && a->accountId() == accountId) {
327 return a;
328 }
329 }
330 return nullptr;
331 }
332
removeAccount(Account * account)333 void AccountManager::removeAccount(Account *account)
334 {
335 if (!account->removeAccount()) {
336 return;
337 }
338
339 if (!account->isConnected()) {
340 d->accountsToBeRemoved.append(account);
341 QTimer::singleShot(0, this, SLOT(removeAccountInternal()));
342 } else {
343 qCDebug(LIBKOPETE_LOG) << account->accountId() << " is still connected, disconnecting...";
344 connect(account, SIGNAL(isConnectedChanged()), this, SLOT(removeAccountConnectedChanged()));
345 account->disconnect();
346 }
347 }
348
save()349 void AccountManager::save()
350 {
351 //qCDebug(LIBKOPETE_LOG) ;
352 qSort(d->accounts.begin(), d->accounts.end(), compareAccountsByPriority);
353
354 for (QListIterator<Account *> it(d->accounts); it.hasNext();) {
355 Account *a = it.next();
356 KConfigGroup *config = a->configGroup();
357
358 config->writeEntry("Protocol", a->protocol()->pluginId());
359 config->writeEntry("AccountId", a->accountId());
360 }
361
362 KSharedConfig::openConfig()->sync();
363 }
364
load()365 void AccountManager::load()
366 {
367 connect(PluginManager::self(), SIGNAL(pluginLoaded(Kopete::Plugin*)),
368 this, SLOT(slotPluginLoaded(Kopete::Plugin*)));
369
370 // Iterate over all groups that start with "Account_" as those are accounts
371 // and load the required protocols if the account is enabled.
372 // Don't try to optimize duplicate calls out, the plugin queue is smart enough
373 // (and fast enough) to handle that without adding complexity here
374 KSharedConfig::Ptr config = KSharedConfig::openConfig();
375 QStringList accountGroups = config->groupList().filter(QRegExp(QLatin1String("^Account_")));
376 for (QStringList::Iterator it = accountGroups.begin(); it != accountGroups.end(); ++it) {
377 KConfigGroup cg(config, *it);
378 KConfigGroup pluginConfig(config, QStringLiteral("Plugins"));
379
380 QString protocol = cg.readEntry("Protocol", QString());
381 if (protocol.endsWith(QLatin1String("Protocol"))) {
382 protocol = QLatin1String("kopete_") + protocol.toLower().remove(QStringLiteral("protocol"));
383 }
384
385 if (cg.readEntry("Enabled", true) && pluginConfig.readEntry(protocol + QLatin1String("Enabled"), true)) {
386 PluginManager::self()->loadPlugin(protocol, PluginManager::LoadAsync);
387 }
388 }
389 }
390
slotPluginLoaded(Plugin * plugin)391 void AccountManager::slotPluginLoaded(Plugin *plugin)
392 {
393 Protocol *protocol = dynamic_cast<Protocol *>(plugin);
394 if (!protocol) {
395 return;
396 }
397
398 // Iterate over all groups that start with "Account_" as those are accounts
399 // and parse them if they are from this protocol
400 KSharedConfig::Ptr config = KSharedConfig::openConfig();
401 const QStringList accountGroups = config->groupList().filter(QRegExp(QLatin1String("^Account_")));
402 for (QStringList::ConstIterator it = accountGroups.constBegin(); it != accountGroups.constEnd(); ++it) {
403 KConfigGroup cg(config, *it);
404
405 if (cg.readEntry("Protocol") != protocol->pluginId()) {
406 continue;
407 }
408
409 // There's no GUI for this, but developers may want to disable an account.
410 if (!cg.readEntry("Enabled", true)) {
411 continue;
412 }
413
414 QString accountId = cg.readEntry("AccountId", QString());
415 if (accountId.isEmpty()) {
416 qCWarning(LIBKOPETE_LOG)
417 <<"Not creating account for empty accountId." << endl;
418 continue;
419 }
420
421 qCDebug(LIBKOPETE_LOG)
422 <<"Creating account for '" << accountId << "'" << endl;
423
424 Account *account = nullptr;
425 account = registerAccount(protocol->createNewAccount(accountId));
426 if (!account) {
427 qCWarning(LIBKOPETE_LOG)
428 <<"Failed to create account for '" << accountId << "'" << endl;
429 continue;
430 }
431 }
432 }
433
slotAccountOnlineStatusChanged(Contact * c,const OnlineStatus & oldStatus,const OnlineStatus & newStatus)434 void AccountManager::slotAccountOnlineStatusChanged(Contact *c, const OnlineStatus &oldStatus, const OnlineStatus &newStatus)
435 {
436 Account *account = c->account();
437 if (!account) {
438 return;
439 }
440
441 //qCDebug(LIBKOPETE_LOG) ;
442 emit accountOnlineStatusChanged(account, oldStatus, newStatus);
443 }
444
networkConnected()445 void AccountManager::networkConnected()
446 {
447 if (!resume()) {
448 setOnlineStatus(Kopete::StatusManager::self()->globalStatusCategory(), Kopete::StatusManager::self()->globalStatusMessage(), 0, true);
449 }
450 }
451
networkDisconnected()452 void AccountManager::networkDisconnected()
453 {
454 suspend();
455 }
456
removeAccountConnectedChanged()457 void AccountManager::removeAccountConnectedChanged()
458 {
459 Account *account = qobject_cast<Account *>(sender());
460 Q_ASSERT(account);
461
462 if (!account->isConnected()) {
463 disconnect(account, SIGNAL(isConnectedChanged()), this, SLOT(removeAccountConnectedChanged()));
464 // Use singleShot so we don't delete the account when we use it.
465 d->accountsToBeRemoved.append(account);
466 QTimer::singleShot(0, this, SLOT(removeAccountInternal()));
467 }
468 }
469
removeAccountInternal()470 void AccountManager::removeAccountInternal()
471 {
472 if (d->accountsToBeRemoved.isEmpty()) {
473 return;
474 }
475
476 Account *account = d->accountsToBeRemoved.takeFirst();
477 if (account->isConnected()) {
478 qCWarning(LIBKOPETE_LOG) << "Error, trying to remove connected account " << account->accountId();
479 return;
480 }
481
482 Protocol *protocol = account->protocol();
483
484 KConfigGroup *configgroup = account->configGroup();
485
486 // Clean up the contact list
487 const QHash<QString, Kopete::Contact *> contactList = account->contacts();
488 QHash<QString, Kopete::Contact *>::ConstIterator it, itEnd = contactList.constEnd();
489
490 for (it = contactList.constBegin(); it != itEnd; ++it) {
491 Contact *c = it.value();
492 if (!c) {
493 continue;
494 }
495
496 MetaContact *mc = c->metaContact();
497 mc->removeContact(c);
498 c->deleteLater();
499 if (mc->contacts().count() == 0) { //we can delete the metacontact
500 //get the first group and it's members
501 Group *group = mc->groups().first();
502 MetaContact::List groupMembers = group->members();
503 ContactList::self()->removeMetaContact(mc);
504 if (groupMembers.count() == 1 && groupMembers.indexOf(mc) != -1) {
505 ContactList::self()->removeGroup(group);
506 }
507 }
508 }
509
510 // Clean up the account list
511 d->accounts.removeAll(account);
512
513 // Clean up configuration
514 configgroup->deleteGroup();
515 configgroup->sync();
516
517 delete account;
518
519 foreach (Account *account, d->accounts) {
520 if (account->protocol() == protocol) {
521 return;
522 }
523 }
524 //there is nomore account from the protocol, we can unload it
525
526 // FIXME: pluginId() should return the internal name and not the class name, so
527 // we can get rid of this hack - Olivier/Martijn
528 QString protocolName = protocol->pluginId().remove(QStringLiteral("Protocol")).toLower();
529
530 PluginManager::self()->setPluginEnabled(protocolName, false);
531 PluginManager::self()->unloadPlugin(protocolName);
532 }
533 } //END namespace Kopete
534
535 // vim: set noet ts=4 sts=4 sw=4:
536 // kate: tab-width 4; indent-mode csands;
537