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