File indexing completed on 2024-04-21 04:04:43

0001  /*
0002   * jabberresourcepool.cpp
0003   *
0004   * Copyright (c) 2004 by Till Gerken <till@tantalo.net>
0005   * Copyright (c) 2006 by Michaƫl Larouche <larouche@kde.org>
0006   *
0007   * Kopete    (c) by the Kopete developers  <kopete-devel@kde.org>
0008   *
0009   * *************************************************************************
0010   * *                                                                       *
0011   * * This program is free software; you can redistribute it and/or modify  *
0012   * * it under the terms of the GNU General Public License as published by  *
0013   * * the Free Software Foundation; either either version 2
0014    of the License, or (at your option) any later version.of the License, or     *
0015   * * (at your option) any later version.                                   *
0016   * *                                                                       *
0017   * *************************************************************************
0018   */
0019 
0020 #include "jabber_protocol_debug.h"
0021 
0022 #include "jabberresourcepool.h"
0023 #include "jabberresource.h"
0024 #include "jabbercontactpool.h"
0025 #include "jabberbasecontact.h"
0026 #include "jabberaccount.h"
0027 #include "jabberprotocol.h"
0028 #include "jabbercapabilitiesmanager.h"
0029 
0030 /**
0031  * This resource will be returned if no other resource
0032  * for a given JID can be found. It's an empty offline
0033  * resource.
0034  */
0035 XMPP::Resource JabberResourcePool::EmptyResource ( "", XMPP::Status ( "", "", 0, false ) );
0036 
0037 class JabberResourcePool::Private
0038 {
0039 public:
0040     Private(JabberAccount *pAccount)
0041      : account(pAccount)
0042     {}
0043     
0044     QList<JabberResource*> pool;
0045     QList<JabberResource*> lockList;
0046 
0047     /**
0048      * Pointer to the JabberAccount instance.
0049      */
0050     JabberAccount *account;
0051 };
0052 
0053 JabberResourcePool::JabberResourcePool ( JabberAccount *account )
0054     : d(new Private(account))
0055 {}
0056 
0057 JabberResourcePool::~JabberResourcePool ()
0058 {
0059     // Delete all resources in the pool upon removal
0060     qDeleteAll(d->pool);
0061     delete d;
0062 }
0063 
0064 void JabberResourcePool::slotResourceDestroyed (QObject *sender)
0065 {
0066     qCDebug(JABBER_PROTOCOL_LOG) << "Resource has been destroyed, collecting the pieces.";
0067 
0068     JabberResource *oldResource = static_cast<JabberResource *>(sender);
0069 
0070     // remove this resource from the lock list if it existed
0071     d->lockList.removeAll ( oldResource );
0072 }
0073 
0074 void JabberResourcePool::slotResourceUpdated ( JabberResource *resource )
0075 {
0076     QList<JabberBaseContact*> list = d->account->contactPool()->findRelevantSources ( resource->jid () );
0077 
0078     foreach(JabberBaseContact *mContact, list)
0079     {
0080         mContact->updateResourceList ();
0081     }
0082 
0083     // Update capabilities
0084     if( !resource->resource().status().capsNode().isEmpty() )
0085     {
0086         qCDebug(JABBER_PROTOCOL_LOG) << "Updating capabilities for JID: " << resource->jid().full();
0087         d->account->protocol()->capabilitiesManager()->updateCapabilities( d->account, resource->jid(), resource->resource().status() );
0088     }
0089 }
0090 
0091 void JabberResourcePool::notifyRelevantContacts ( const XMPP::Jid &jid )
0092 {
0093     QList<JabberBaseContact*> list = d->account->contactPool()->findRelevantSources ( jid );
0094 
0095     foreach(JabberBaseContact *mContact, list)
0096     {
0097         mContact->reevaluateStatus ();
0098     }
0099 }
0100 
0101 void JabberResourcePool::addResource ( const XMPP::Jid &jid, const XMPP::Resource &resource )
0102 {
0103     // see if the resource already exists
0104     foreach(JabberResource *mResource, d->pool)
0105     {
0106         if ( (mResource->jid().userHost().toLower() == jid.userHost().toLower()) && (mResource->resource().name().toLower() == resource.name().toLower()) )
0107         {
0108             qCDebug(JABBER_PROTOCOL_LOG) << "Updating existing resource " << resource.name() << " for " << jid.userHost();
0109 
0110             // It exists, update it. Don't do a "lazy" update by deleting
0111             // it here and readding it with new parameters later on,
0112             // any possible lockings to this resource will get lost.
0113             mResource->setResource ( resource );
0114 
0115             // we still need to notify the contact in case the status
0116             // of this resource changed
0117             notifyRelevantContacts ( jid );
0118 
0119             return;
0120         }
0121     }
0122 
0123     qCDebug(JABBER_PROTOCOL_LOG) << "Adding new resource " << resource.name() << " for " << jid.userHost();
0124 
0125     // Update initial capabilities if available.
0126     // Called before creating JabberResource so JabberResource wouldn't ask for disco information. 
0127     if( !resource.status().capsNode().isEmpty() )
0128     {
0129         qCDebug(JABBER_PROTOCOL_LOG) << "Initial update of capabilities for JID: " << jid.full();
0130         d->account->protocol()->capabilitiesManager()->updateCapabilities( d->account, jid, resource.status() );
0131     }
0132 
0133     // create new resource instance and add it to the dictionary
0134     JabberResource *newResource = new JabberResource(d->account, jid, resource);
0135     connect ( newResource, SIGNAL (destroyed(QObject*)), this, SLOT (slotResourceDestroyed(QObject*)) );
0136     connect ( newResource, SIGNAL (updated(JabberResource*)), this, SLOT (slotResourceUpdated(JabberResource*)) );
0137     d->pool.append ( newResource );
0138 
0139     // send notifications out to the relevant contacts that
0140     // a new resource is available for them
0141     notifyRelevantContacts ( jid );
0142 }
0143 
0144 void JabberResourcePool::removeResource ( const XMPP::Jid &jid, const XMPP::Resource &resource )
0145 {
0146     qCDebug(JABBER_PROTOCOL_LOG) << "Removing resource " << resource.name() << " from " << jid.userHost();
0147 
0148     foreach(JabberResource *mResource, d->pool)
0149     {
0150         if ( (mResource->jid().userHost().toLower() == jid.userHost().toLower()) && (mResource->resource().name().toLower() == resource.name().toLower()) )
0151         {
0152             JabberResource *deletedResource = d->pool.takeAt( d->pool.indexOf(mResource) );
0153             delete deletedResource;
0154 
0155             notifyRelevantContacts ( jid );
0156             return;
0157         }
0158     }
0159 
0160     qCDebug(JABBER_PROTOCOL_LOG) << "WARNING: No match found!";
0161 }
0162 
0163 void JabberResourcePool::removeAllResources ( const XMPP::Jid &jid )
0164 {
0165     qCDebug(JABBER_PROTOCOL_LOG) << "Removing all resources for " << jid.userHost();
0166 
0167     foreach(JabberResource *mResource, d->pool)
0168     {
0169         if ( mResource->jid().userHost().toLower() == jid.userHost().toLower() )
0170         {
0171             // only remove preselected resource in case there is one
0172             if ( jid.resource().isEmpty () || ( jid.resource().toLower () == mResource->resource().name().toLower () ) )
0173             {
0174                 qCDebug(JABBER_PROTOCOL_LOG) << "Removing resource " << jid.userHost() << "/" << mResource->resource().name ();
0175                 JabberResource *deletedResource = d->pool.takeAt( d->pool.indexOf(mResource) );
0176                 delete deletedResource;
0177             }
0178         }
0179     }
0180 }
0181 
0182 void JabberResourcePool::clear ()
0183 {
0184     qCDebug(JABBER_PROTOCOL_LOG) << "Clearing the resource pool.";
0185 
0186     /*
0187      * Since many contacts can have multiple resources, we can't simply delete
0188      * each resource and trigger a notification upon each deletion. This would
0189      * cause lots of status updates in the GUI and create unnecessary flicker
0190      * and API traffic. Instead, collect all JIDs, clear the dictionary
0191      * and then notify all JIDs after the resources have been deleted.
0192      */
0193 
0194     QStringList jidList;
0195 
0196     foreach(JabberResource *mResource, d->pool)
0197     {
0198         jidList += mResource->jid().full ();
0199     }
0200 
0201     /*
0202      * The lock list will be cleaned automatically.
0203      */
0204     qDeleteAll(d->pool);
0205     d->pool.clear ();
0206 
0207     /*
0208      * Now go through the list of JIDs and notify each contact
0209      * of its status change
0210      */
0211     for ( QStringList::Iterator it = jidList.begin (); it != jidList.end (); ++it )
0212     {
0213         notifyRelevantContacts ( XMPP::Jid ( *it ) );
0214     }
0215 
0216 }
0217 
0218 void JabberResourcePool::lockToResource ( const XMPP::Jid &jid, const XMPP::Resource &resource )
0219 {
0220     qCDebug(JABBER_PROTOCOL_LOG) << "Locking " << jid.full() << " to " << resource.name();
0221 
0222     // remove all existing locks first
0223     removeLock ( jid );
0224 
0225     // find the resource in our dictionary that matches
0226     foreach(JabberResource *mResource, d->pool)
0227     {
0228         if ( (mResource->jid().userHost().toLower() == jid.full().toLower()) && (mResource->resource().name().toLower() == resource.name().toLower()) )
0229         {
0230             d->lockList.append ( mResource );
0231             return;
0232         }
0233     }
0234 
0235     qCDebug(JABBER_PROTOCOL_LOG) << "WARNING: No match found!";
0236 }
0237 
0238 void JabberResourcePool::removeLock ( const XMPP::Jid &jid )
0239 {
0240     qCDebug(JABBER_PROTOCOL_LOG) << "Removing resource lock for " << jid.userHost();
0241 
0242     // find the resource in our dictionary that matches
0243     foreach(JabberResource *mResource, d->pool)
0244     {
0245         if ( (mResource->jid().userHost().toLower() == jid.userHost().toLower()) )
0246         {
0247             d->lockList.removeAll (mResource);
0248         }
0249     }
0250 
0251     qCDebug(JABBER_PROTOCOL_LOG) << "No locks found.";
0252 }
0253 
0254 JabberResource *JabberResourcePool::lockedJabberResource( const XMPP::Jid &jid )
0255 {
0256     // check if the JID already carries a resource, then we will have to use that one
0257     if ( !jid.resource().isEmpty () )
0258     {
0259         // we are subscribed to a JID, find the according resource in the pool
0260         foreach(JabberResource *mResource, d->pool)
0261         {
0262             if ( ( mResource->jid().userHost().toLower () == jid.userHost().toLower () ) && ( mResource->resource().name () == jid.resource () ) )
0263             {
0264                 return mResource;
0265             }
0266         }
0267 
0268         qCDebug(JABBER_PROTOCOL_LOG) << "WARNING: No resource found in pool, returning as offline.";
0269 
0270         return 0L;
0271     }
0272 
0273     // see if we have a locked resource
0274     foreach(JabberResource *mResource, d->lockList)
0275     {
0276         if ( mResource->jid().userHost().toLower() == jid.userHost().toLower() )
0277         {
0278             qCDebug(JABBER_PROTOCOL_LOG) << "Current lock for " << jid.userHost () << " is '" << mResource->resource().name () << "'";
0279             return mResource;
0280         }
0281     }
0282 
0283     qCDebug(JABBER_PROTOCOL_LOG) << "No lock available for " << jid.userHost ();
0284 
0285     // there's no locked resource, return an empty resource
0286     return 0L;
0287 }
0288 
0289 const XMPP::Resource &JabberResourcePool::lockedResource ( const XMPP::Jid &jid )
0290 {
0291     JabberResource *resource = lockedJabberResource( jid );
0292     return (resource) ? resource->resource() : EmptyResource;
0293 }
0294 
0295 JabberResource *JabberResourcePool::bestJabberResource( const XMPP::Jid &jid, bool honourLock )
0296 {
0297     qCDebug(JABBER_PROTOCOL_LOG) << "Determining best resource for " << jid.full ();
0298 
0299     if ( honourLock )
0300     {
0301         // if we are locked to a certain resource, always return that one
0302         JabberResource *mResource = lockedJabberResource ( jid );
0303         if ( mResource )
0304         {
0305             qCDebug(JABBER_PROTOCOL_LOG) << "We have a locked resource '" << mResource->resource().name () << "' for " << jid.full ();
0306             return mResource;
0307         }
0308     }
0309 
0310     JabberResource *bestResource = 0L;
0311     JabberResource *currentResource = 0L;
0312 
0313     foreach(currentResource, d->pool)
0314     {
0315         // make sure we are only looking up resources for the specified JID
0316         if ( currentResource->jid().userHost().toLower() != jid.userHost().toLower() )
0317         {
0318             continue;
0319         }
0320 
0321         // take first resource if no resource has been chosen yet
0322         if(!bestResource)
0323         {
0324             qCDebug(JABBER_PROTOCOL_LOG) << "Taking '" << currentResource->resource().name () << "' as first available resource.";
0325 
0326             bestResource = currentResource;
0327             continue;
0328         }
0329 
0330         if(currentResource->resource().priority() > bestResource->resource().priority())
0331         {
0332             qCDebug(JABBER_PROTOCOL_LOG) << "Using '" << currentResource->resource().name () << "' due to better priority.";
0333 
0334             // got a better match by priority
0335             bestResource = currentResource;
0336         }
0337         else
0338         {
0339             if(currentResource->resource().priority() == bestResource->resource().priority())
0340             {
0341                 if(currentResource->resource().status().timeStamp() > bestResource->resource().status().timeStamp())
0342                 {
0343                     qCDebug(JABBER_PROTOCOL_LOG) << "Using '" << currentResource->resource().name () << "' due to better timestamp.";
0344 
0345                     // got a better match by timestamp (priorities are equal)
0346                     bestResource = currentResource;
0347                 }
0348             }
0349         }
0350     }
0351 
0352     return (bestResource) ? bestResource : 0L;
0353 }
0354 
0355 const XMPP::Resource &JabberResourcePool::bestResource ( const XMPP::Jid &jid, bool honourLock )
0356 {
0357     JabberResource *bestResource = bestJabberResource( jid, honourLock);
0358     return (bestResource) ? bestResource->resource() : EmptyResource;
0359 }
0360 
0361 //TODO: Find Resources based on certain Features.
0362 void JabberResourcePool::findResources ( const XMPP::Jid &jid, JabberResourcePool::ResourceList &resourceList )
0363 {
0364     foreach(JabberResource *mResource, d->pool)
0365     {
0366         if ( mResource->jid().userHost().toLower() == jid.userHost().toLower() )
0367         {
0368             // we found a resource for the JID, let's see if the JID already contains a resource
0369             if ( !jid.resource().isEmpty() && ( jid.resource().toLower() != mResource->resource().name().toLower() ) )
0370                 // the JID contains a resource but it's not the one we have in the dictionary,
0371                 // thus we have to ignore this resource
0372                 continue;
0373 
0374             resourceList.append ( mResource );
0375         }
0376     }
0377 }
0378 
0379 void JabberResourcePool::findResources ( const XMPP::Jid &jid, XMPP::ResourceList &resourceList )
0380 {
0381     foreach(JabberResource *mResource, d->pool)
0382     {
0383         if ( mResource->jid().userHost().toLower() == jid.userHost().toLower() )
0384         {
0385             // we found a resource for the JID, let's see if the JID already contains a resource
0386             if ( !jid.resource().isEmpty() && ( jid.resource().toLower() != mResource->resource().name().toLower() ) )
0387                 // the JID contains a resource but it's not the one we have in the dictionary,
0388                 // thus we have to ignore this resource
0389                 continue;
0390 
0391             resourceList.append ( mResource->resource () );
0392         }
0393     }
0394 }
0395 
0396 #include "moc_jabberresourcepool.cpp"