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