1 /*
2 kopetecontactlist.cpp - Kopete's Contact List backend
3
4 Copyright (c) 2005-2007 by Michael Larouche <larouche@kde.org>
5 Copyright (c) 2002-2003 by Martijn Klingens <klingens@kde.org>
6 Copyright (c) 2002-2004 by Olivier Goffart <ogoffart@kde.org>
7 Copyright (c) 2002 by Duncan Mac-Vicar Prett <duncan@kde.org>
8
9 Copyright (c) 2002-2004 by the Kopete developers <kopete-devel@kde.org>
10
11 *************************************************************************
12 * *
13 * This library is free software; you can redistribute it and/or *
14 * modify it under the terms of the GNU Lesser General Public *
15 * License as published by the Free Software Foundation; either *
16 * version 2 of the License, or (at your option) any later version. *
17 * *
18 *************************************************************************
19 */
20
21 #include "kopetecontactlist.h"
22
23 // Qt includes
24 #include <QDir>
25 #include <QRegExp>
26 #include <QTimer>
27 #include <QTextStream>
28
29 // KDE includes
30 //#include <kcontacts/stdaddressbook.h>
31 #include <kapplication.h>
32 #include "libkopete_debug.h"
33
34
35 // Kopete includes
36 #include "kopeteaccount.h"
37 #include "kopeteaccountmanager.h"
38 #include "kopetechatsession.h"
39 #include "kopetecontact.h"
40 #include "kopetedeletecontacttask.h"
41 #include "kopetegroup.h"
42 #include "kopetemetacontact.h"
43 #include "kopetepicture.h"
44 #include "kopetepluginmanager.h"
45 #include "kopeteprotocol.h"
46 #include "xmlcontactstorage.h"
47
48 namespace Kopete {
49 class ContactList::Private
50 {
51 public:
52 /** Flag: do not save the contact list until she is completely loaded */
53 bool loaded;
54 bool terminated;
55
56 QList<MetaContact *> contacts;
57 QList<Group *> groups;
58 QList<MetaContact *> selectedMetaContacts;
59 QList<Group *> selectedGroups;
60
61 QTimer *saveTimer;
62
63 MetaContact *myself;
64 };
65
66 ContactList *ContactList::s_self = nullptr;
67
self()68 ContactList *ContactList::self()
69 {
70 if (!s_self) {
71 s_self = new ContactList;
72 }
73
74 return s_self;
75 }
76
ContactList()77 ContactList::ContactList()
78 : QObject(kapp)
79 , d(new Private())
80 {
81 setObjectName(QStringLiteral("KopeteContactList"));
82
83 //the myself metacontact can't be created now, because it will use
84 //ContactList::self() as parent which will call this constructor -> infinite loop
85 d->myself = nullptr;
86
87 //no contact list loaded yet, don't save them
88 d->loaded = false;
89 d->terminated = false;
90
91 // automatically save on changes to the list
92 d->saveTimer = new QTimer(this);
93 d->saveTimer->setObjectName(QStringLiteral("saveTimer"));
94 d->saveTimer->setSingleShot(true);
95 connect(d->saveTimer, SIGNAL(timeout()), SLOT(save()));
96
97 connect(this, SIGNAL(metaContactAdded(Kopete::MetaContact*)), SLOT(slotSaveLater()));
98 connect(this, SIGNAL(metaContactRemoved(Kopete::MetaContact*)), SLOT(slotSaveLater()));
99 connect(this, SIGNAL(groupAdded(Kopete::Group*)), SLOT(slotSaveLater()));
100 connect(this, SIGNAL(groupRemoved(Kopete::Group*)), SLOT(slotSaveLater()));
101 connect(this, SIGNAL(groupRenamed(Kopete::Group*,QString)), SLOT(slotSaveLater()));
102 }
103
~ContactList()104 ContactList::~ContactList()
105 {
106 s_self = nullptr;
107 delete d->myself;
108 delete d;
109 }
110
metaContacts() const111 QList<MetaContact *> ContactList::metaContacts() const
112 {
113 return d->contacts;
114 }
115
groups() const116 QList<Group *> ContactList::groups() const
117 {
118 return d->groups;
119 }
120
metaContact(const QString & metaContactId) const121 MetaContact *ContactList::metaContact(const QString &metaContactId) const
122 {
123 QListIterator<MetaContact *> it(d->contacts);
124
125 while (it.hasNext())
126 {
127 MetaContact *mc = it.next();
128 if (mc->metaContactId() == QUuid(metaContactId)) {
129 return mc;
130 }
131 }
132
133 return nullptr;
134 }
135
group(unsigned int groupId) const136 Group *ContactList::group(unsigned int groupId) const
137 {
138 if (groupId == Group::topLevel()->groupId()) {
139 return Group::topLevel();
140 }
141
142 QListIterator<Group *> it(d->groups);
143
144 while (it.hasNext())
145 {
146 Group *curr = it.next();
147 if (curr->groupId() == groupId) {
148 return curr;
149 }
150 }
151 return nullptr;
152 }
153
findContact(const QString & protocolId,const QString & accountId,const QString & contactId) const154 Contact *ContactList::findContact(const QString &protocolId, const QString &accountId, const QString &contactId) const
155 {
156 //Browsing metacontacts is too slow, better to uses the Dict of the account.
157 Account *i = AccountManager::self()->findAccount(protocolId, accountId);
158 if (!i) {
159 qCDebug(LIBKOPETE_LOG) << "Account not found";
160 return nullptr;
161 }
162 return i->contacts().value(contactId);
163 }
164
findMetaContactByDisplayName(const QString & displayName) const165 MetaContact *ContactList::findMetaContactByDisplayName(const QString &displayName) const
166 {
167 foreach (Kopete::MetaContact *contact, d->contacts) {
168 if (contact->displayName() == displayName) {
169 return contact;
170 }
171 }
172 return 0;
173 }
174
findMetaContactByContactId(const QString & contactId) const175 MetaContact *ContactList::findMetaContactByContactId(const QString &contactId) const
176 {
177 QListIterator<Kopete::Account *> it(Kopete::AccountManager::self()->accounts());
178 Kopete::Account *a;
179 while (it.hasNext())
180 {
181 a = it.next();
182 Contact *c = a->contacts().value(contactId);
183 if (c && c->metaContact()) {
184 return c->metaContact();
185 }
186 }
187 return nullptr;
188 }
189
findGroup(const QString & displayName,int type)190 Group *ContactList::findGroup(const QString &displayName, int type)
191 {
192 if (type == Group::Temporary) {
193 return Group::temporary();
194 } else if (type == Group::TopLevel) {
195 return Group::topLevel();
196 } else if (type == Group::Offline) {
197 return Group::offline();
198 }
199
200 QListIterator<Group *> it(d->groups);
201 while (it.hasNext())
202 {
203 Group *curr = it.next();
204 if (curr->type() == type && curr->displayName() == displayName) {
205 return curr;
206 }
207 }
208
209 Group *newGroup = new Group(displayName);
210 addGroup(newGroup);
211 return newGroup;
212 }
213
selectedMetaContacts() const214 QList<MetaContact *> ContactList::selectedMetaContacts() const
215 {
216 return d->selectedMetaContacts;
217 }
218
selectedGroups() const219 QList<Group *> ContactList::selectedGroups() const
220 {
221 return d->selectedGroups;
222 }
223
addMetaContacts(QList<MetaContact * > metaContacts)224 void ContactList::addMetaContacts(QList<MetaContact *> metaContacts)
225 {
226 foreach (MetaContact *mc, metaContacts) {
227 addMetaContact(mc);
228 }
229 }
230
addMetaContact(MetaContact * mc)231 void ContactList::addMetaContact(MetaContact *mc)
232 {
233 if (d->contacts.contains(mc)) {
234 return;
235 }
236
237 d->contacts.append(mc);
238
239 emit metaContactAdded(mc);
240 connect(mc, SIGNAL(persistentDataChanged()), SLOT(slotSaveLater()));
241 connect(mc, SIGNAL(addedToGroup(Kopete::MetaContact*,Kopete::Group*)), SIGNAL(metaContactAddedToGroup(Kopete::MetaContact*,Kopete::Group*)));
242 connect(mc, SIGNAL(removedFromGroup(Kopete::MetaContact*,Kopete::Group*)), SIGNAL(metaContactRemovedFromGroup(Kopete::MetaContact*,Kopete::Group*)));
243 connect(mc, SIGNAL(movedToGroup(Kopete::MetaContact*,Kopete::Group*,Kopete::Group*)),
244 SIGNAL(metaContactMovedToGroup(Kopete::MetaContact*,Kopete::Group*,Kopete::Group*)));
245 }
246
removeMetaContact(MetaContact * m)247 void ContactList::removeMetaContact(MetaContact *m)
248 {
249 if (!d->contacts.contains(m)) {
250 qCDebug(LIBKOPETE_LOG) << "Trying to remove a not listed MetaContact.";
251 return;
252 }
253
254 if (d->selectedMetaContacts.contains(m)) {
255 d->selectedMetaContacts.removeAll(m);
256 setSelectedItems(d->selectedMetaContacts, d->selectedGroups);
257 }
258
259 //removes subcontact from server here and now.
260 Kopete::Contact *contactToDelete = 0;
261 foreach (contactToDelete, m->contacts()) {
262 // TODO: Check for good execution of task
263 Kopete::DeleteContactTask *deleteTask = new Kopete::DeleteContactTask(contactToDelete);
264 deleteTask->start();
265 }
266
267 d->contacts.removeAll(m);
268 emit metaContactRemoved(m);
269 m->deleteLater();
270 }
271
mergeMetaContacts(QList<MetaContact * > src,Kopete::MetaContact * dst)272 void ContactList::mergeMetaContacts(QList<MetaContact *> src, Kopete::MetaContact *dst)
273 {
274 // merge all metacontacts from src into dst
275
276 // Note: there is no need to remove the src metacontacts, they are going to be
277 // removed when the last contact is moved to the new metacontact
278
279 // TODO: add a confirmation dialog asking if this is really wanted
280 // TODO: add a Undo option for this
281
282 foreach (Kopete::MetaContact *mc, src) {
283 foreach (Kopete::Contact *c, mc->contacts()) {
284 c->setMetaContact(dst);
285 }
286 }
287 }
288
addGroups(QList<Group * > groups)289 void ContactList::addGroups(QList<Group *> groups)
290 {
291 foreach (Group *g, groups) {
292 addGroup(g);
293 }
294 }
295
addGroup(Group * g)296 void ContactList::addGroup(Group *g)
297 {
298 if (!d->groups.contains(g)) {
299 d->groups.append(g);
300 emit groupAdded(g);
301 connect(g, SIGNAL(displayNameChanged(Kopete::Group*,QString)), this, SIGNAL(groupRenamed(Kopete::Group*,QString)));
302 }
303 }
304
removeGroup(Group * g)305 void ContactList::removeGroup(Group *g)
306 {
307 if (g == Group::topLevel()) {
308 return;
309 }
310
311 if (d->selectedGroups.contains(g)) {
312 d->selectedGroups.removeAll(g);
313 setSelectedItems(d->selectedMetaContacts, d->selectedGroups);
314 }
315
316 // Remove metaContacts from group or delete the metaContact if it isn't in any other group
317 foreach (MetaContact *metaContact, g->members()) {
318 const QList<Group *> mcGroups = metaContact->groups();
319 if ((mcGroups.count() == 1 && mcGroups.contains(g)) || mcGroups.isEmpty()) {
320 removeMetaContact(metaContact);
321 } else {
322 metaContact->removeFromGroup(g);
323 }
324 }
325
326 d->groups.removeAll(g);
327 emit groupRemoved(g);
328 g->deleteLater();
329 }
330
setSelectedItems(QList<MetaContact * > metaContacts,QList<Group * > groups)331 void ContactList::setSelectedItems(QList<MetaContact *> metaContacts, QList<Group *> groups)
332 {
333 qCDebug(LIBKOPETE_LOG) << metaContacts.count() << " metacontacts, " << groups.count() << " groups selected";
334 d->selectedMetaContacts = metaContacts;
335 d->selectedGroups = groups;
336
337 emit metaContactSelected(groups.isEmpty() && metaContacts.count() == 1);
338 emit selectionChanged();
339 }
340
myself()341 MetaContact *ContactList::myself()
342 {
343 if (!d->myself) {
344 d->myself = new MetaContact();
345 }
346 return d->myself;
347 }
348
349 ///////////////////////////////////////////////////////////////////////////////////////////////
load()350 void ContactList::load()
351 {
352 // don't save when we're in the middle of this...
353 d->loaded = false;
354
355 Kopete::ContactListStorage *storage = new Kopete::XmlContactStorage();
356 storage->load();
357 if (!storage->isValid()) {
358 qCDebug(LIBKOPETE_LOG) << "Contact list storage failed. Reason: " << storage->errorMessage();
359 } else {
360 addGroups(storage->groups());
361 addMetaContacts(storage->contacts());
362 }
363
364 d->loaded = true;
365 delete storage;
366 emit contactListLoaded();
367 }
368
loaded() const369 bool Kopete::ContactList::loaded() const
370 {
371 return d->loaded;
372 }
373
save()374 void Kopete::ContactList::save()
375 {
376 if (d->terminated) {
377 qCWarning(LIBKOPETE_LOG) << "Contact list terminated, abort saving";
378 return;
379 }
380
381 if (!d->loaded) {
382 qCDebug(LIBKOPETE_LOG) << "Contact list not loaded, abort saving";
383 return;
384 }
385
386 Kopete::ContactListStorage *storage = new Kopete::XmlContactStorage();
387 storage->save();
388 if (!storage->isValid()) {
389 qCDebug(LIBKOPETE_LOG) << "Contact list storage failed. Reason: " << storage->errorMessage();
390
391 // Saving the contact list failed. retry every minute until it works.
392 // single-shot: will get restarted by us next time if it's still failing
393 d->saveTimer->setSingleShot(true);
394 d->saveTimer->start(60000);
395 delete storage;
396 return;
397 }
398
399 // cancel any scheduled saves
400 d->saveTimer->stop();
401 delete storage;
402 }
403
shutdown()404 void ContactList::shutdown()
405 {
406 if (!d->terminated) {
407 save();
408 d->terminated = true;
409 d->saveTimer->stop();
410 }
411 }
412
slotSaveLater()413 void ContactList::slotSaveLater()
414 {
415 if (d->terminated) {
416 return;
417 }
418
419 // if we already have a save scheduled, it will be cancelled. either way,
420 // start a timer to save the contact list a bit later.
421 d->saveTimer->start(17100 /* 17,1 seconds */);
422 }
423
slotKABCChanged()424 void ContactList::slotKABCChanged()
425 {
426 // TODO: react to changes in KABC, replacing this function, post 3.4 (Will)
427 // call syncWithKABC on each metacontact to check if its associated kabc entry has changed.
428 /* for ( MetaContact * mc = d->contacts.first(); mc; mc = d->contacts.next() )
429
430 mc->syncWithKABC();*/
431 }
432 } //END namespace Kopete
433
434 // vim: set noet ts=4 sts=4 sw=4:
435