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