1 /*
2   * jabberresourcepool.cpp
3   *
4   * Copyright (c) 2004 by Till Gerken <till@tantalo.net>
5   * Copyright (c) 2006 by Michaël Larouche <larouche@kde.org>
6   *
7   * Kopete    (c) by the Kopete developers  <kopete-devel@kde.org>
8   *
9   * *************************************************************************
10   * *                                                                       *
11   * * This program is free software; you can redistribute it and/or modify  *
12   * * it under the terms of the GNU General Public License as published by  *
13   * * the Free Software Foundation; either version 2 of the License, or     *
14   * * (at your option) any later version.                                   *
15   * *                                                                       *
16   * *************************************************************************
17   */
18 
19 #include "jabberresourcepool.h"
20 
21 #include "jabber_protocol_debug.h"
22 
23 #include "jabberresource.h"
24 #include "jabbercontactpool.h"
25 #include "jabberbasecontact.h"
26 #include "jabberaccount.h"
27 #include "jabberprotocol.h"
28 #include "jabbercapabilitiesmanager.h"
29 
30 /**
31  * This resource will be returned if no other resource
32  * for a given JID can be found. It's an empty offline
33  * resource.
34  */
35 XMPP::Resource JabberResourcePool::EmptyResource(QLatin1String(""), XMPP::Status(QLatin1String(""), QLatin1String(""), 0, false));
36 
37 class JabberResourcePool::Private
38 {
39 public:
Private(JabberAccount * pAccount)40     Private(JabberAccount *pAccount)
41         : account(pAccount)
42     {
43     }
44 
45     QList<JabberResource *> pool;
46     QList<JabberResource *> lockList;
47 
48     /**
49      * Pointer to the JabberAccount instance.
50      */
51     JabberAccount *account;
52 };
53 
JabberResourcePool(JabberAccount * account)54 JabberResourcePool::JabberResourcePool (JabberAccount *account)
55     : d(new Private(account))
56 {
57 }
58 
~JabberResourcePool()59 JabberResourcePool::~JabberResourcePool ()
60 {
61     // Delete all resources in the pool upon removal
62     qDeleteAll(d->pool);
63     delete d;
64 }
65 
slotResourceDestroyed(QObject * sender)66 void JabberResourcePool::slotResourceDestroyed(QObject *sender)
67 {
68     qCDebug(JABBER_PROTOCOL_LOG) << "Resource has been destroyed, collecting the pieces.";
69 
70     JabberResource *oldResource = static_cast<JabberResource *>(sender);
71 
72     // remove this resource from the lock list if it existed
73     d->lockList.removeAll(oldResource);
74 }
75 
slotResourceUpdated(JabberResource * resource)76 void JabberResourcePool::slotResourceUpdated(JabberResource *resource)
77 {
78     QList<JabberBaseContact *> list = d->account->contactPool()->findRelevantSources(resource->jid());
79 
80     foreach (JabberBaseContact *mContact, list) {
81         mContact->updateResourceList();
82     }
83 
84     // Update capabilities
85     if (!resource->resource().status().caps().node().isEmpty()) {
86         qCDebug(JABBER_PROTOCOL_LOG) << "Updating capabilities for JID: " << resource->jid().full();
87         d->account->protocol()->capabilitiesManager()->updateCapabilities(d->account, resource->jid(), resource->resource().status());
88     }
89 }
90 
notifyRelevantContacts(const XMPP::Jid & jid)91 void JabberResourcePool::notifyRelevantContacts(const XMPP::Jid &jid)
92 {
93     QList<JabberBaseContact *> list = d->account->contactPool()->findRelevantSources(jid);
94 
95     foreach (JabberBaseContact *mContact, list) {
96         mContact->reevaluateStatus();
97     }
98 }
99 
addResource(const XMPP::Jid & jid,const XMPP::Resource & resource)100 void JabberResourcePool::addResource(const XMPP::Jid &jid, const XMPP::Resource &resource)
101 {
102     // see if the resource already exists
103     foreach (JabberResource *mResource, d->pool) {
104         if ((mResource->jid().bare().toLower() == jid.bare().toLower()) && (mResource->resource().name().toLower() == resource.name().toLower())) {
105             qCDebug(JABBER_PROTOCOL_LOG) << "Updating existing resource " << resource.name() << " for " << jid.bare();
106 
107             // It exists, update it. Don't do a "lazy" update by deleting
108             // it here and readding it with new parameters later on,
109             // any possible lockings to this resource will get lost.
110             mResource->setResource(resource);
111 
112             // we still need to notify the contact in case the status
113             // of this resource changed
114             notifyRelevantContacts(jid);
115 
116             return;
117         }
118     }
119 
120     qCDebug(JABBER_PROTOCOL_LOG) << "Adding new resource " << resource.name() << " for " << jid.bare();
121 
122     // Update initial capabilities if available.
123     // Called before creating JabberResource so JabberResource wouldn't ask for disco information.
124     if (!resource.status().caps().node().isEmpty()) {
125         qCDebug(JABBER_PROTOCOL_LOG) << "Initial update of capabilities for JID: " << jid.full();
126         d->account->protocol()->capabilitiesManager()->updateCapabilities(d->account, jid, resource.status());
127     }
128 
129     // create new resource instance and add it to the dictionary
130     JabberResource *newResource = new JabberResource(d->account, jid, resource);
131     connect(newResource, SIGNAL(destroyed(QObject*)), this, SLOT(slotResourceDestroyed(QObject*)));
132     connect(newResource, SIGNAL(updated(JabberResource*)), this, SLOT(slotResourceUpdated(JabberResource*)));
133     d->pool.append(newResource);
134 
135     // send notifications out to the relevant contacts that
136     // a new resource is available for them
137     notifyRelevantContacts(jid);
138 }
139 
removeResource(const XMPP::Jid & jid,const XMPP::Resource & resource)140 void JabberResourcePool::removeResource(const XMPP::Jid &jid, const XMPP::Resource &resource)
141 {
142     qCDebug(JABBER_PROTOCOL_LOG) << "Removing resource " << resource.name() << " from " << jid.bare();
143 
144     foreach (JabberResource *mResource, d->pool) {
145         if ((mResource->jid().bare().toLower() == jid.bare().toLower()) && (mResource->resource().name().toLower() == resource.name().toLower())) {
146             JabberResource *deletedResource = d->pool.takeAt(d->pool.indexOf(mResource));
147             delete deletedResource;
148 
149             notifyRelevantContacts(jid);
150             return;
151         }
152     }
153 
154     qCDebug(JABBER_PROTOCOL_LOG) << "WARNING: No match found!";
155 }
156 
removeAllResources(const XMPP::Jid & jid)157 void JabberResourcePool::removeAllResources(const XMPP::Jid &jid)
158 {
159     qCDebug(JABBER_PROTOCOL_LOG) << "Removing all resources for " << jid.bare();
160 
161     foreach (JabberResource *mResource, d->pool) {
162         if (mResource->jid().bare().toLower() == jid.bare().toLower()) {
163             // only remove preselected resource in case there is one
164             if (jid.resource().isEmpty() || (jid.resource().toLower() == mResource->resource().name().toLower())) {
165                 qCDebug(JABBER_PROTOCOL_LOG) << "Removing resource " << jid.bare() << "/" << mResource->resource().name();
166                 JabberResource *deletedResource = d->pool.takeAt(d->pool.indexOf(mResource));
167                 delete deletedResource;
168             }
169         }
170     }
171 }
172 
clear()173 void JabberResourcePool::clear()
174 {
175     qCDebug(JABBER_PROTOCOL_LOG) << "Clearing the resource pool.";
176 
177     /*
178      * Since many contacts can have multiple resources, we can't simply delete
179      * each resource and trigger a notification upon each deletion. This would
180      * cause lots of status updates in the GUI and create unnecessary flicker
181      * and API traffic. Instead, collect all JIDs, clear the dictionary
182      * and then notify all JIDs after the resources have been deleted.
183      */
184 
185     QStringList jidList;
186 
187     foreach (JabberResource *mResource, d->pool) {
188         jidList += mResource->jid().full();
189     }
190 
191     /*
192      * The lock list will be cleaned automatically.
193      */
194     qDeleteAll(d->pool);
195     d->pool.clear();
196 
197     /*
198      * Now go through the list of JIDs and notify each contact
199      * of its status change
200      */
201     for (QStringList::Iterator it = jidList.begin(); it != jidList.end(); ++it) {
202         notifyRelevantContacts(XMPP::Jid(*it));
203     }
204 }
205 
lockToResource(const XMPP::Jid & jid,const XMPP::Resource & resource)206 void JabberResourcePool::lockToResource(const XMPP::Jid &jid, const XMPP::Resource &resource)
207 {
208     qCDebug(JABBER_PROTOCOL_LOG) << "Locking " << jid.bare() << " to " << resource.name();
209 
210     // remove all existing locks first
211     removeLock(jid);
212 
213     // find the resource in our dictionary that matches
214     foreach (JabberResource *mResource, d->pool) {
215         if ((mResource->jid().bare().toLower() == jid.bare().toLower()) && (mResource->resource().name().toLower() == resource.name().toLower())) {
216             d->lockList.append(mResource);
217             return;
218         }
219     }
220 
221     qCDebug(JABBER_PROTOCOL_LOG) << "WARNING: No match found!";
222 }
223 
removeLock(const XMPP::Jid & jid)224 void JabberResourcePool::removeLock(const XMPP::Jid &jid)
225 {
226     qCDebug(JABBER_PROTOCOL_LOG) << "Removing resource lock for " << jid.bare();
227 
228     // find the resource in our dictionary that matches
229     foreach (JabberResource *mResource, d->pool) {
230         if ((mResource->jid().bare().toLower() == jid.bare().toLower())) {
231             d->lockList.removeAll(mResource);
232         }
233     }
234 
235     qCDebug(JABBER_PROTOCOL_LOG) << "No locks found.";
236 }
237 
lockedJabberResource(const XMPP::Jid & jid)238 JabberResource *JabberResourcePool::lockedJabberResource(const XMPP::Jid &jid)
239 {
240     // check if the JID already carries a resource, then we will have to use that one
241     if (!jid.resource().isEmpty()) {
242         // we are subscribed to a JID, find the according resource in the pool
243         foreach (JabberResource *mResource, d->pool) {
244             if ((mResource->jid().bare().toLower() == jid.bare().toLower()) && (mResource->resource().name() == jid.resource())) {
245                 return mResource;
246             }
247         }
248 
249         qCDebug(JABBER_PROTOCOL_LOG) << "WARNING: No resource found in pool, returning as offline.";
250 
251         return nullptr;
252     }
253 
254     // see if we have a locked resource
255     foreach (JabberResource *mResource, d->lockList) {
256         if (mResource->jid().bare().toLower() == jid.bare().toLower()) {
257             qCDebug(JABBER_PROTOCOL_LOG) << "Current lock for " << jid.bare() << " is '" << mResource->resource().name() << "'";
258             return mResource;
259         }
260     }
261 
262     qCDebug(JABBER_PROTOCOL_LOG) << "No lock available for " << jid.bare();
263 
264     // there's no locked resource, return an empty resource
265     return nullptr;
266 }
267 
lockedResource(const XMPP::Jid & jid)268 const XMPP::Resource &JabberResourcePool::lockedResource(const XMPP::Jid &jid)
269 {
270     JabberResource *resource = lockedJabberResource(jid);
271     return (resource) ? resource->resource() : EmptyResource;
272 }
273 
bestJabberResource(const XMPP::Jid & jid,bool honourLock)274 JabberResource *JabberResourcePool::bestJabberResource(const XMPP::Jid &jid, bool honourLock)
275 {
276     qCDebug(JABBER_PROTOCOL_LOG) << "Determining best resource for " << jid.full();
277 
278     if (honourLock) {
279         // if we are locked to a certain resource, always return that one
280         JabberResource *mResource = lockedJabberResource(jid);
281         if (mResource) {
282             qCDebug(JABBER_PROTOCOL_LOG) << "We have a locked resource '" << mResource->resource().name() << "' for " << jid.full();
283             return mResource;
284         }
285     }
286 
287     JabberResource *bestResource = nullptr;
288     JabberResource *currentResource = nullptr;
289 
290     foreach (currentResource, d->pool) {
291         // make sure we are only looking up resources for the specified JID
292         if (currentResource->jid().bare().toLower() != jid.bare().toLower()) {
293             continue;
294         }
295 
296         // take first resource if no resource has been chosen yet
297         if (!bestResource) {
298             qCDebug(JABBER_PROTOCOL_LOG) << "Taking '" << currentResource->resource().name() << "' as first available resource.";
299 
300             bestResource = currentResource;
301             continue;
302         }
303 
304         if (currentResource->resource().priority() > bestResource->resource().priority()) {
305             qCDebug(JABBER_PROTOCOL_LOG) << "Using '" << currentResource->resource().name() << "' due to better priority.";
306 
307             // got a better match by priority
308             bestResource = currentResource;
309         } else {
310             if (currentResource->resource().priority() == bestResource->resource().priority()) {
311                 if (currentResource->resource().status().timeStamp() > bestResource->resource().status().timeStamp()) {
312                     qCDebug(JABBER_PROTOCOL_LOG) << "Using '" << currentResource->resource().name() << "' due to better timestamp.";
313 
314                     // got a better match by timestamp (priorities are equal)
315                     bestResource = currentResource;
316                 }
317             }
318         }
319     }
320 
321     return (bestResource) ? bestResource : 0L;
322 }
323 
bestResource(const XMPP::Jid & jid,bool honourLock)324 const XMPP::Resource &JabberResourcePool::bestResource(const XMPP::Jid &jid, bool honourLock)
325 {
326     JabberResource *bestResource = bestJabberResource(jid, honourLock);
327     return (bestResource) ? bestResource->resource() : EmptyResource;
328 }
329 
330 //TODO: Find Resources based on certain Features.
findResources(const XMPP::Jid & jid,JabberResourcePool::ResourceList & resourceList)331 void JabberResourcePool::findResources(const XMPP::Jid &jid, JabberResourcePool::ResourceList &resourceList)
332 {
333     foreach (JabberResource *mResource, d->pool) {
334         if (mResource->jid().bare().toLower() == jid.bare().toLower()) {
335             // we found a resource for the JID, let's see if the JID already contains a resource
336             if (!jid.resource().isEmpty() && (jid.resource().toLower() != mResource->resource().name().toLower())) {
337                 // the JID contains a resource but it's not the one we have in the dictionary,
338                 // thus we have to ignore this resource
339                 continue;
340             }
341 
342             resourceList.append(mResource);
343         }
344     }
345 }
346 
findResources(const XMPP::Jid & jid,XMPP::ResourceList & resourceList)347 void JabberResourcePool::findResources(const XMPP::Jid &jid, XMPP::ResourceList &resourceList)
348 {
349     foreach (JabberResource *mResource, d->pool) {
350         if (mResource->jid().bare().toLower() == jid.bare().toLower()) {
351             // we found a resource for the JID, let's see if the JID already contains a resource
352             if (!jid.resource().isEmpty() && (jid.resource().toLower() != mResource->resource().name().toLower())) {
353                 // the JID contains a resource but it's not the one we have in the dictionary,
354                 // thus we have to ignore this resource
355                 continue;
356             }
357 
358             resourceList.append(mResource->resource());
359         }
360     }
361 }
362 
getJabberResource(const XMPP::Jid & jid,const QString & resource)363 JabberResource *JabberResourcePool::getJabberResource(const XMPP::Jid &jid, const QString &resource)
364 {
365     if (resource.isEmpty()) {
366         return bestJabberResource(jid);
367     }
368 
369     foreach (JabberResource *mResource, d->pool) {
370         if (mResource->jid().bare().toLower() == jid.bare().toLower() && jid.resource().toLower() == resource) {
371             // we found a resource for the JID, let's see if the JID already contains a resource
372             if (!jid.resource().isEmpty() && (jid.resource().toLower() != mResource->resource().name().toLower())) {
373                 // the JID contains a resource but it's not the one we have in the dictionary,
374                 // thus we have to ignore this resource
375                 continue;
376             }
377 
378             return mResource;
379         }
380     }
381 
382     return bestJabberResource(jid);
383 }
384