/* * jabbercontact.cpp - Base class for the Kopete Jabber protocol contact * * Copyright (c) 2002-2004 by Till Gerken * Copyright (c) 2006 by Olivier Goffart * * Kopete (c) by the Kopete developers * * ************************************************************************* * * * * * This program is free software; you can redistribute it and/or modify * * * it under the terms of the GNU General Public License as published by * * * the Free Software Foundation; either version 2 of the License, or * * * (at your option) any later version. * * * * * ************************************************************************* */ #include #include #include #include #include #include #include #include #include #include #include #include "jabberbasecontact.h" #include "xmpp_tasks.h" #include "jabberprotocol.h" #include "jabberaccount.h" #include "jabberresource.h" #include "jabberresourcepool.h" #include "kopetemetacontact.h" #include "kopetemessage.h" #include "kopeteuiglobal.h" #include "jabbertransport.h" #include "dlgjabbervcard.h" /** * JabberBaseContact constructor */ JabberBaseContact::JabberBaseContact (const XMPP::RosterItem &rosterItem, Kopete::Account *account, Kopete::MetaContact * mc, const TQString &legacyId) : Kopete::Contact (account, legacyId.isEmpty() ? rosterItem.jid().full() : legacyId , mc ) { setDontSync ( false ); JabberTransport *t=transport(); m_account= t ? t->account() : static_cast(Kopete::Contact::account()); // take roster item and update display name updateContact ( rosterItem ); } JabberProtocol *JabberBaseContact::protocol () { return static_cast(Kopete::Contact::protocol ()); } JabberTransport * JabberBaseContact::transport( ) { return dynamic_cast(Kopete::Contact::account()); } /* Return if we are reachable (defaults to true because we can send on- and offline, only return false if the account itself is offline, too) */ bool JabberBaseContact::isReachable () { if (account()->isConnected()) return true; return false; } void JabberBaseContact::updateContact ( const XMPP::RosterItem & item ) { kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Synchronizing local copy of " << contactId() << " with information received from server. (name='" << item.name() << "' groups='" << item.groups() << "')"<< endl; mRosterItem = item; // if we don't have a meta contact yet, stop processing here if ( !metaContact () ) return; /* * We received the information from the server, as such, * don't attempt to synch while we update our local copy. */ setDontSync ( true ); // The myself contact is not in the roster on server, ignore this code // because the myself MetaContact displayname become the latest // Jabber acccount jid. if( metaContact() != Kopete::ContactList::self()->myself() ) { // only update the alias if its not empty if ( !item.name().isEmpty () && item.name() != item.jid().bare() ) { TQString newName = item.name (); TQString oldName = metaContact()->displayName(); Kopete::Contact *source=metaContact()->displayNameSourceContact(); // kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "setting display name of " << contactId () << " to " << newName << endl; metaContact()->setDisplayName ( newName ); //automatically set to custom source if the source is to this contact. if( metaContact()->displayNameSource()==Kopete::MetaContact::SourceContact && newName != oldName && ( source == this || source == 0L ) ) metaContact()->setDisplayNameSource( Kopete::MetaContact::SourceCustom ); } } /* * Set the contact's subscription status */ switch ( item.subscription().type () ) { case XMPP::Subscription::None: setProperty ( protocol()->propSubscriptionStatus, i18n ( "You cannot see each others' status." ) ); break; case XMPP::Subscription::To: setProperty ( protocol()->propSubscriptionStatus, i18n ( "You can see this contact's status but they cannot see your status." ) ); break; case XMPP::Subscription::From: setProperty ( protocol()->propSubscriptionStatus, i18n ( "This contact can see your status but you cannot see their status." ) ); break; case XMPP::Subscription::Both: setProperty ( protocol()->propSubscriptionStatus, i18n ( "You can see each others' status." ) ); break; } if( !metaContact()->isTemporary() ) { /* * In this method, as opposed to KC::syncGroups(), * the group list from the server is authoritative. * As such, we need to find a list of all groups * that the meta contact resides in but does not * reside in on the server anymore, as well as all * groups that the meta contact does not reside in, * but resides in on the server. * Then, we'll have to synchronize the KMC using * that information. */ Kopete::GroupList groupsToRemoveFrom, groupsToAddTo; // find all groups our contact is in but that are not in the server side roster for ( unsigned i = 0; i < metaContact()->groups().count (); i++ ) { if ( item.groups().find ( metaContact()->groups().at(i)->displayName () ) == item.groups().end () ) groupsToRemoveFrom.append ( metaContact()->groups().at ( i ) ); } // now find all groups that are in the server side roster but not in the local group for ( unsigned i = 0; i < item.groups().count (); i++ ) { bool found = false; for ( unsigned j = 0; j < metaContact()->groups().count (); j++) { if ( metaContact()->groups().at(j)->displayName () == *item.groups().at(i) ) { found = true; break; } } if ( !found ) { groupsToAddTo.append ( Kopete::ContactList::self()->findGroup ( *item.groups().at(i) ) ); } } /* * Special case: if we don't add the contact to any group and the * list of groups to remove from contains the top level group, we * risk removing the contact from the visible contact list. In this * case, we need to make sure at least the top level group stays. */ if ( ( groupsToAddTo.count () == 0 ) && ( groupsToRemoveFrom.contains ( Kopete::Group::topLevel () ) ) ) { groupsToRemoveFrom.remove ( Kopete::Group::topLevel () ); } for ( Kopete::Group *group = groupsToRemoveFrom.first (); group; group = groupsToRemoveFrom.next () ) { kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Removing " << contactId() << " from group " << group->displayName () << endl; metaContact()->removeFromGroup ( group ); } for ( Kopete::Group *group = groupsToAddTo.first (); group; group = groupsToAddTo.next () ) { kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Adding " << contactId() << " to group " << group->displayName () << endl; metaContact()->addToGroup ( group ); } } /* * Enable updates for the server again. */ setDontSync ( false ); //can't do it now because it's called from contructor at a point some virtual function are not available TQTimer::singleShot(0, this, TQT_SLOT(reevaluateStatus())); } void JabberBaseContact::updateResourceList () { /* * Set available resources. * This is a bit more complicated: We need to generate * all images dynamically from the KOS icons and store * them into the mime factory, then plug them into * the richtext. */ JabberResourcePool::ResourceList resourceList; account()->resourcePool()->findResources ( rosterItem().jid() , resourceList ); if ( resourceList.isEmpty () ) { removeProperty ( protocol()->propAvailableResources ); return; } TQString resourceListStr = ""; for ( JabberResourcePool::ResourceList::iterator it = resourceList.begin (); it != resourceList.end (); ++it ) { // icon, resource name and priority resourceListStr += TQString ( "" ). arg ( protocol()->resourceToKOS((*it)->resource()).mimeSourceFor ( account () ), (*it)->resource().name (), TQString::number ( (*it)->resource().priority () ) ); // client name, version, OS if ( !(*it)->clientName().isEmpty () ) { resourceListStr += TQString ( "" ). arg ( i18n ( "Client" ), (*it)->clientName (), (*it)->clientSystem () ); } // Supported features #if 0 //disabled because it's just an ugly and long list of incomprehensible namespaces to the user TQStringList supportedFeatures = (*it)->features().list(); TQStringList::ConstIterator featuresIt, featuresItEnd = supportedFeatures.constEnd(); if( !supportedFeatures.empty() ) resourceListStr += TQString( "" ); #endif // resource timestamp resourceListStr += TQString ( "" ). arg ( i18n ( "Timestamp" ), KGlobal::locale()->formatDateTime ( (*it)->resource().status().timeStamp(), true, true ) ); // message, if any if ( !(*it)->resource().status().status().stripWhiteSpace().isEmpty () ) { resourceListStr += TQString ( "" ). arg ( i18n ( "Message" ), Kopete::Message::escape( (*it)->resource().status().status () ) ); } } resourceListStr += "
%2 (Priority: %3)
%1: %2 (%3)
Supported Features:" ); for( featuresIt = supportedFeatures.constBegin(); featuresIt != featuresItEnd; ++featuresIt ) { XMPP::Features tempFeature(*featuresIt); resourceListStr += TQString("\n
"); if ( tempFeature.id() > XMPP::Features::FID_None ) resourceListStr += tempFeature.name() + TQString(" ("); resourceListStr += *featuresIt; if ( tempFeature.id() > Features::FID_None ) resourceListStr += TQString(")"); } if( !supportedFeatures.empty() ) resourceListStr += TQString( "
%1: %2
%1: %2
"; setProperty ( protocol()->propAvailableResources, resourceListStr ); } void JabberBaseContact::reevaluateStatus () { kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "Determining new status for " << contactId () << endl; Kopete::OnlineStatus status; XMPP::Resource resource = account()->resourcePool()->bestResource ( mRosterItem.jid () ); status = protocol()->resourceToKOS ( resource ); /* Add some icon to show the subscription */ if( ( mRosterItem.subscription().type() == XMPP::Subscription::None || mRosterItem.subscription().type() == XMPP::Subscription::From) && inherits ( "JabberContact" ) && metaContact() != Kopete::ContactList::self()->myself() && account()->isConnected() ) { status = Kopete::OnlineStatus(status.status() , status.weight() , protocol() , status.internalStatus() | 0x0100, status.overlayIcons() + TQStringList("status_unknown_overlay") , //FIXME: find better icon status.description() ); } updateResourceList (); kdDebug (JABBER_DEBUG_GLOBAL) << k_funcinfo << "New status for " << contactId () << " is " << status.description () << endl; setOnlineStatus ( status ); /* * Set away message property. * We just need to read it from the current resource. */ if ( !resource.status ().status ().isEmpty () ) { setProperty ( protocol()->propAwayMessage, resource.status().status () ); } else { removeProperty ( protocol()->propAwayMessage ); } } TQString JabberBaseContact::fullAddress () { XMPP::Jid jid = rosterItem().jid(); if ( jid.resource().isEmpty () ) { jid.setResource ( account()->resourcePool()->bestResource ( jid ).name () ); } return jid.full (); } XMPP::Jid JabberBaseContact::bestAddress () { // see if we are subscribed with a preselected resource if ( !mRosterItem.jid().resource().isEmpty () ) { // we have a preselected resource, so return our default full address return mRosterItem.jid (); } // construct address out of user@host and current best resource XMPP::Jid jid = mRosterItem.jid (); jid.setResource ( account()->resourcePool()->bestResource( mRosterItem.jid() ).name () ); return jid; } void JabberBaseContact::setDontSync ( bool flag ) { mDontSync = flag; } bool JabberBaseContact::dontSync () { return mDontSync; } void JabberBaseContact::serialize (TQMap < TQString, TQString > &serializedData, TQMap < TQString, TQString > & /* addressBookData */ ) { // Contact id and display name are already set for us, only add the rest serializedData["JID"] = mRosterItem.jid().full(); serializedData["groups"] = mRosterItem.groups ().join (TQString::fromLatin1 (",")); } void JabberBaseContact::slotUserInfo( ) { if ( !account()->isConnected () ) { account()->errorConnectFirst (); return; } // Update the vCard //slotGetTimedVCard(); new dlgJabberVCard ( account(), this, Kopete::UI::Global::mainWidget () ); } void JabberBaseContact::setPropertiesFromVCard ( const XMPP::VCard &vCard ) { kdDebug ( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Updating vCard for " << contactId () << endl; // update vCard cache timestamp if this is not a temporary contact if ( metaContact() && !metaContact()->isTemporary () ) { setProperty ( protocol()->propVCardCacheTimeStamp, TQDateTime::currentDateTime().toString ( Qt::ISODate ) ); } /* * Set the nickname property. * but ignore it if we are in a groupchat, or it will clash with the normal nickname */ if(inherits ( "JabberContact" )) { if ( !vCard.nickName().isEmpty () ) { setProperty ( protocol()->propNickName, vCard.nickName () ); } else { removeProperty ( protocol()->propNickName ); } } /** * Kopete does not allow a modification of the "full name" * property. However, some vCards specify only the full name, * some specify only first and last name. * Due to these inconsistencies, if first and last name don't * exist, it is attempted to parse the full name. */ // remove all properties first removeProperty ( protocol()->propFirstName ); removeProperty ( protocol()->propLastName ); removeProperty ( protocol()->propFullName ); if ( !vCard.fullName().isEmpty () && vCard.givenName().isEmpty () && vCard.familyName().isEmpty () ) { TQString lastName = vCard.fullName().section ( ' ', 0, -1 ); TQString firstName = vCard.fullName().left(vCard.fullName().length () - lastName.length ()).stripWhiteSpace (); setProperty ( protocol()->propFirstName, firstName ); setProperty ( protocol()->propLastName, lastName ); } else { if ( !vCard.givenName().isEmpty () ) setProperty ( protocol()->propFirstName, vCard.givenName () ); if ( !vCard.familyName().isEmpty () ) setProperty ( protocol()->propLastName, vCard.familyName () ); } if( !vCard.fullName().isEmpty() ) setProperty ( protocol()->propFullName, vCard.fullName() ); /* * Set the general information */ removeProperty( protocol()->propJid ); removeProperty( protocol()->propBirthday ); removeProperty( protocol()->propTimezone ); removeProperty( protocol()->propHomepage ); setProperty( protocol()->propJid, vCard.jid() ); if( !vCard.bdayStr().isEmpty () ) setProperty( protocol()->propBirthday, vCard.bdayStr() ); if( !vCard.timezone().isEmpty () ) setProperty( protocol()->propTimezone, vCard.timezone() ); if( !vCard.url().isEmpty () ) setProperty( protocol()->propHomepage, vCard.url() ); /* * Set the work information. */ removeProperty( protocol()->propCompanyName ); removeProperty( protocol()->propCompanyDepartement ); removeProperty( protocol()->propCompanyPosition ); removeProperty( protocol()->propCompanyRole ); if( !vCard.org().name.isEmpty() ) setProperty( protocol()->propCompanyName, vCard.org().name ); if( !vCard.org().unit.join(",").isEmpty() ) setProperty( protocol()->propCompanyDepartement, vCard.org().unit.join(",")) ; if( !vCard.title().isEmpty() ) setProperty( protocol()->propCompanyPosition, vCard.title() ); if( !vCard.role().isEmpty() ) setProperty( protocol()->propCompanyRole, vCard.role() ); /* * Set the about information */ removeProperty( protocol()->propAbout ); if( !vCard.desc().isEmpty() ) setProperty( protocol()->propAbout, vCard.desc() ); /* * Set the work and home addresses information */ removeProperty( protocol()->propWorkStreet ); removeProperty( protocol()->propWorkExtAddr ); removeProperty( protocol()->propWorkPOBox ); removeProperty( protocol()->propWorkCity ); removeProperty( protocol()->propWorkPostalCode ); removeProperty( protocol()->propWorkCountry ); removeProperty( protocol()->propHomeStreet ); removeProperty( protocol()->propHomeExtAddr ); removeProperty( protocol()->propHomePOBox ); removeProperty( protocol()->propHomeCity ); removeProperty( protocol()->propHomePostalCode ); removeProperty( protocol()->propHomeCountry ); for(XMPP::VCard::AddressList::const_iterator it = vCard.addressList().begin(); it != vCard.addressList().end(); it++) { XMPP::VCard::Address address = (*it); if(address.work) { setProperty( protocol()->propWorkStreet, address.street ); setProperty( protocol()->propWorkExtAddr, address.extaddr ); setProperty( protocol()->propWorkPOBox, address.pobox ); setProperty( protocol()->propWorkCity, address.locality ); setProperty( protocol()->propWorkPostalCode, address.pcode ); setProperty( protocol()->propWorkCountry, address.country ); } else if(address.home) { setProperty( protocol()->propHomeStreet, address.street ); setProperty( protocol()->propHomeExtAddr, address.extaddr ); setProperty( protocol()->propHomePOBox, address.pobox ); setProperty( protocol()->propHomeCity, address.locality ); setProperty( protocol()->propHomePostalCode, address.pcode ); setProperty( protocol()->propHomeCountry, address.country ); } } /* * Delete emails first, they might not be present * in the vCard at all anymore. */ removeProperty ( protocol()->propEmailAddress ); removeProperty ( protocol()->propWorkEmailAddress ); /* * Set the home and work email information. */ XMPP::VCard::EmailList::const_iterator emailEnd = vCard.emailList().end (); for(XMPP::VCard::EmailList::const_iterator it = vCard.emailList().begin(); it != emailEnd; ++it) { XMPP::VCard::Email email = (*it); if(email.work) { if( !email.userid.isEmpty() ) setProperty ( protocol()->propWorkEmailAddress, email.userid ); } else if(email.home) { if( !email.userid.isEmpty() ) setProperty ( protocol()->propEmailAddress, email.userid ); } } /* * Delete phone number properties first as they might have * been unset during an update and are not present in * the vCard at all anymore. */ removeProperty ( protocol()->propPrivatePhone ); removeProperty ( protocol()->propPrivateMobilePhone ); removeProperty ( protocol()->propWorkPhone ); removeProperty ( protocol()->propWorkMobilePhone ); /* * Set phone numbers. Note that if a mobile phone number * is specified, it's assigned to the private mobile * phone number property. This might not be the desired * behavior for all users. */ XMPP::VCard::PhoneList::const_iterator phoneEnd = vCard.phoneList().end (); for(XMPP::VCard::PhoneList::const_iterator it = vCard.phoneList().begin(); it != phoneEnd; ++it) { XMPP::VCard::Phone phone = (*it); if(phone.work) { setProperty ( protocol()->propWorkPhone, phone.number ); } else if(phone.fax) { setProperty ( protocol()->propPhoneFax, phone.number); } else if(phone.cell) { setProperty ( protocol()->propPrivateMobilePhone, phone.number ); } else if(phone.home) { setProperty ( protocol()->propPrivatePhone, phone.number ); } } /* * Set photo/avatar property. */ removeProperty( protocol()->propPhoto ); TQImage contactPhoto; TQString fullJid = mRosterItem.jid().full(); TQString finalPhotoPath = locateLocal("appdata", "jabberphotos/" + fullJid.replace(TQRegExp("[./~]"),"-") +".png"); // photo() is a QByteArray if ( !vCard.photo().isEmpty() ) { kdDebug( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Contact has a photo embedded into his vCard." << endl; // TQImage is used to save to disk in PNG later. contactPhoto = TQImage( vCard.photo() ); } // Contact photo is a URI. else if( !vCard.photoURI().isEmpty() ) { TQString tempPhotoPath = 0; // Downalod photo from URI. if( !KIO::NetAccess::download( vCard.photoURI(), tempPhotoPath, 0) ) { KMessageBox::queuedMessageBox( Kopete::UI::Global::mainWidget (), KMessageBox::Sorry, i18n( "Downloading of Jabber contact photo failed!" ) ); return; } kdDebug( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Contact photo is a URI." << endl; contactPhoto = TQImage( tempPhotoPath ); KIO::NetAccess::removeTempFile( tempPhotoPath ); } // Save the image to the disk, then set the property. if( !contactPhoto.isNull() && contactPhoto.save(finalPhotoPath, "PNG") ) { kdDebug( JABBER_DEBUG_GLOBAL ) << k_funcinfo << "Setting photo for contact: " << fullJid << endl; setProperty( protocol()->propPhoto, finalPhotoPath ); } } #include "jabberbasecontact.moc" // vim: set noet ts=4 sts=4 sw=4: