Hi,

I've found out that when one meta contact has more than one contact from specific protocol the properties of that contacts aren't saved correctly. To be precise for instance if contact A doesn't have prop_QString_firstName and contact B has it
then on next startup A will have the prop_QString_firstName and B won't.

Attached patch fixes this by saving each contact into one plugin-contact-data element
instead of merging it.

So
<meta-contact>
  <plugin-data plugin-id="ICQProtocol" >
<plugin-data-field key="contactId" >ContactA ContactB</plugin-data-field>
  </plugin-data>
</meta-contact>
will be
<meta-contact>
  <plugin-contact-data plugin-id="ICQProtocol" >
    <plugin-data-field key="contactId" >ContactA</plugin-data-field>
  </plugin-contact-data>
  <plugin-contact-data plugin-id="ICQProtocol" >
    <plugin-data-field key="contactId" >ContactB</plugin-data-field>
  </plugin-contact-data>
</meta-contact>

The patch also calls Protocol::serialize only once per protocol and not for every contact.

I know that the serialize/deserialize API is really ugly but I don't want to mess with it
in feature freeze.

Could anybody review this patch and check if I didn't miss anything?

Regards,
Roman
Index: kopete/libkopete/kopeteprotocol.h
===================================================================
--- kopete/libkopete/kopeteprotocol.h	(revision 982847)
+++ kopete/libkopete/kopeteprotocol.h	(working copy)
@@ -235,6 +235,11 @@
 	virtual void deserialize( MetaContact *metaContact, const QMap<QString, QString> &serializedData );
 
 	/**
+	 * @brief Deserialize the plugin data for a meta contact's contacts.
+	 */
+	virtual void deserializeContactList( MetaContact *metaContact, const QList< QMap<QString, QString> > &dataList );
+
+	/**
 	 * @brief Deserialize a single contact.
 	 *
 	 * This method is called by @ref deserialize() for each separate contact,
@@ -291,15 +296,15 @@
 	 */
 	 virtual bool validatePassword( const QString & password ) const;
 
-public slots:
+public:
 	/**
-	 * A meta contact is about to save.
+	 * Serialize meta contact into the metacontact's plugin data
 	 * Call serialize() for all contained contacts for this protocol.
 	 * @internal
 	 *   it's public because for example, Contact::setMetaContact uses it.
 	 * @todo we probably should think to another way to save the contacltist.
 	 */
-	void slotMetaContactAboutToSave( Kopete::MetaContact *metaContact );
+	void serialize( Kopete::MetaContact *metaContact );
 
 
 private:
Index: kopete/libkopete/kopetecontactlistelement.cpp
===================================================================
--- kopete/libkopete/kopetecontactlistelement.cpp	(revision 982847)
+++ kopete/libkopete/kopetecontactlistelement.cpp	(working copy)
@@ -1,3 +1,4 @@
+
 /*
     kopeteplugindataobject.cpp - Kopete Plugin Data Object
 
@@ -31,6 +32,7 @@
 {
 public:
 	ContactListElement::PluginDataMap pluginData;
+	QMap<QString, ContactListElement::ContactDataList> pluginContactData;
 	ContactListElement::IconMap icons;
 	bool useCustomIcon;
 	bool loading;
@@ -107,6 +109,51 @@
 	return d->pluginData;
 }
 
+QMap<QString, ContactListElement::ContactDataList > ContactListElement::pluginContactData() const
+{
+	return d->pluginContactData;
+}
+
+ContactListElement::ContactDataList ContactListElement::pluginContactData( Plugin *plugin ) const
+{
+	if ( !d->pluginContactData.contains( plugin->pluginId() ) )
+		return ContactDataList();
+
+	return d->pluginContactData[ plugin->pluginId() ];
+}
+
+void ContactListElement::clearPluginContactData()
+{
+	d->pluginContactData.clear();
+}
+
+void ContactListElement::setPluginContactData( Plugin *plugin, const ContactListElement::ContactDataList &dataList )
+{
+	QString pluginId = plugin->pluginId();
+	if ( dataList.isEmpty() )
+	{
+		d->pluginContactData.remove( pluginId );
+		return;
+	}
+
+	d->pluginContactData[ pluginId ] = dataList;
+
+	emit pluginDataChanged();
+}
+
+void ContactListElement::appendPluginContactData( const QString &pluginId, const ContactData &data )
+{
+	if ( data.isEmpty() )
+	{
+		d->pluginContactData.remove( pluginId );
+		return;
+	}
+
+	d->pluginContactData[ pluginId ].append( data );
+
+	emit pluginDataChanged();
+}
+
 const ContactListElement::IconMap ContactListElement::icons() const
 {
 	return d->icons;
Index: kopete/libkopete/kopetepluginmanager.cpp
===================================================================
--- kopete/libkopete/kopetepluginmanager.cpp	(revision 982847)
+++ kopete/libkopete/kopetepluginmanager.cpp	(working copy)
@@ -43,6 +43,7 @@
 #include <kservicetypetrader.h>
 
 #include "kopeteplugin.h"
+#include "kopeteprotocol.h"
 #include "kopetecontactlist.h"
 #include "kopeteaccountmanager.h"
 
@@ -387,6 +388,10 @@
 		kDebug( 14010 ) << "Successfully loaded plugin '" << pluginId << "'";
 
 		emit pluginLoaded( plugin );
+
+		Protocol* protocol = dynamic_cast<Protocol*>( plugin );
+		if ( protocol )
+			emit protocolLoaded( protocol );
 	}
 	else
 	{
Index: kopete/libkopete/contactlist/xmlcontactstorage.cpp
===================================================================
--- kopete/libkopete/contactlist/xmlcontactstorage.cpp	(revision 982847)
+++ kopete/libkopete/contactlist/xmlcontactstorage.cpp	(working copy)
@@ -61,18 +61,19 @@
 {
 public:
     Private()
-    : isBusy(false), isValid(false)
+    : isBusy(false), isValid(false), version(0)
     {}
 
     bool isBusy;
     bool isValid;
     QString xmlFilename;
     QString errorMessage;
+    uint version;
 
     /**
      * Current contact list version * 10 ( i.e. '10' is version '1.0' )
      */
-    static const uint ContactListVersion = 11;
+    static const uint ContactListVersion = 12;
 };
 
 
@@ -140,21 +141,27 @@
 
     QDomElement list = contactList.documentElement();
 
-    QString versionString = list.attribute( QString::fromLatin1( "version" ), QString() );
-    uint version = 0;
-    if( QRegExp( QString::fromLatin1( "[0-9]+\\.[0-9]" ) ).exactMatch( versionString ) )
-        version = versionString.remove( QLatin1Char( '.' ) ).toUInt();
+    d->version = readVersion( list );
+    if( d->version < Private::ContactListVersion )
+    {
+        QFile::copy( filename, filename + QString::fromLatin1(".bak_v%1").arg( d->version ) );
 
-    if( version < Private::ContactListVersion )
-    {
         bool contactListUpdated = false;
-        if ( version == 10 )
-            contactListUpdated = updateFrom10( list );
+        if ( d->version == 10 )
+        {
+            contactListUpdated = updateFrom10to11( list );
+            d->version = readVersion( list );
+        }
+        if ( d->version == 11 )
+        {
+            contactListUpdated = updateFrom11to12( list );
+            d->version = readVersion( list );
+        }
 
-        if ( !contactListUpdated )
+        if ( d->version < Private::ContactListVersion )
         {
             contactListFile.close();
-            kWarning(14010) << "The contact list on disk is older than expected."
+            kWarning(14010) << "The contact list on disk is older than expected or cannot be updated!"
                             << "No contact list will be loaded";
             return;
         }
@@ -235,7 +242,7 @@
 
     QDomDocument doc;
     doc.appendChild( doc.createElement( QLatin1String("kopete-contact-list") ) );
-    doc.documentElement().setAttribute( QLatin1String("version"), QLatin1String("1.1"));
+    doc.documentElement().setAttribute( QLatin1String("version"), QLatin1String("1.2"));
 
     // Save group information. ie: Open/Closed, pehaps later icons? Who knows.
     Kopete::Group::List groupList = Kopete::ContactList::self()->groups();
@@ -461,8 +468,10 @@
     metaContact->setDisplayNameSource( nameSourcePID, nameSourceAID, nameSourceCID );
 
     // If a plugin is loaded, load data cached
-    QObject::connect( Kopete::PluginManager::self(), SIGNAL( pluginLoaded(Kopete::Plugin*) ),
-                      metaContact, SLOT( slotPluginLoaded(Kopete::Plugin*) ) );
+    QObject::connect( Kopete::PluginManager::self(), SIGNAL(pluginLoaded(Kopete::Plugin*)),
+                      metaContact, SLOT(slotPluginLoaded(Kopete::Plugin*)) );
+    QObject::connect( Kopete::PluginManager::self(), SIGNAL(protocolLoaded(Kopete::Protocol*)),
+                      metaContact, SLOT(slotProtocolLoaded(Kopete::Protocol*)) );
 
     // All plugins are already loaded, call manually the contact setting slot.
     if( Kopete::PluginManager::self()->isAllPluginsLoaded() )
@@ -580,6 +589,24 @@
         }
         contactListElement->setPluginData( pluginId, pluginData );
     }
+    else if ( element.tagName() == QLatin1String( "plugin-contact-data" ) )
+    {
+        QMap<QString, QString> pluginData;
+        QString pluginId = element.attribute( QLatin1String( "plugin-id" ), QString() );
+
+        QDomNode field = element.firstChild();
+        while( !field.isNull() )
+        {
+            QDomElement fieldElement = field.toElement();
+            if ( fieldElement.tagName() == QLatin1String( "plugin-data-field" ) )
+            {
+                pluginData.insert( fieldElement.attribute( QLatin1String( "key" ),
+                                   QLatin1String( "undefined-key" ) ), fieldElement.text() );
+            }
+            field = field.nextSibling();
+        }
+        contactListElement->appendPluginContactData( pluginId, pluginData );
+    }
     else if ( element.tagName() == QLatin1String( "custom-icons" ) )
     {
         contactListElement->setUseCustomIcon( element.attribute( QLatin1String( "use" ), QLatin1String( "1" ) ) == QLatin1String( "1" ) );
@@ -622,7 +649,7 @@
 const QDomElement XmlContactStorage::storeMetaContact( Kopete::MetaContact *metaContact, bool minimal ) const
 {
     // This causes each Kopete::Protocol subclass to serialise its contacts' data into the metacontact's plugin data and address book data
-    metaContact->emitAboutToSave();
+    metaContact->serialize();
 
     QDomDocument metaContactDoc;
     metaContactDoc.appendChild( metaContactDoc.createElement( QString::fromUtf8( "meta-contact" ) ) );
@@ -767,6 +794,32 @@
         }
     }
 
+    const QMap<QString, Kopete::ContactListElement::ContactDataList > pluginsContactData = contactListElement->pluginContactData();
+    if ( !pluginsContactData.isEmpty() )
+    {
+        QMap<QString, Kopete::ContactListElement::ContactDataList >::ConstIterator pluginIt, pluginItEnd = pluginsContactData.end();
+        for ( pluginIt = pluginsContactData.begin(); pluginIt != pluginItEnd; ++pluginIt )
+        {
+            foreach ( Kopete::ContactListElement::ContactData dataItem, pluginIt.value() )
+            {
+                QDomElement pluginElement = pluginData.createElement( QLatin1String( "plugin-contact-data" ) );
+                pluginElement.setAttribute( QLatin1String( "plugin-id" ), pluginIt.key()  );
+
+                QMap<QString, QString>::ConstIterator it;
+                for ( it = dataItem.begin(); it != dataItem.end(); ++it )
+                {
+                    QDomElement pluginDataField = pluginData.createElement( QLatin1String( "plugin-data-field" ) );
+                    pluginDataField.setAttribute( QLatin1String( "key" ), it.key()  );
+                    pluginDataField.appendChild( pluginData.createTextNode(  it.value()  ) );
+                    pluginElement.appendChild( pluginDataField );
+                }
+
+                pluginData.documentElement().appendChild( pluginElement );
+                pluginNodes.append( pluginElement );
+            }
+        }
+    }
+
     const Kopete::ContactListElement::IconMap icons = contactListElement->icons();
     if ( !icons.isEmpty() )
     {
@@ -813,7 +866,7 @@
     return pluginNodes;
 }
 
-bool XmlContactStorage::updateFrom10( QDomElement &rootElement ) const
+bool XmlContactStorage::updateFrom10to11( QDomElement &rootElement ) const
 {
     QDomNodeList metaContactElements = rootElement.elementsByTagName( QLatin1String( "meta-contact" ) );
     for ( int i = 0; i < metaContactElements.count(); ++i )
@@ -836,6 +889,94 @@
     return true;
 }
 
+bool XmlContactStorage::updateFrom11to12( QDomElement &rootElement ) const
+{
+    QDomNodeList metaContactElementList = rootElement.elementsByTagName( QLatin1String( "meta-contact" ) );
+    for ( int i = 0; i < metaContactElementList.count(); ++i )
+    {
+        typedef QMap<QString, QString> PluginData;
+        typedef QPair<QString, PluginData> ProtocolIdDataPair;
+
+        QList<QDomElement> removeList;
+        QList<ProtocolIdDataPair> newList;
+
+        QDomElement metaContactElement = metaContactElementList.at( i ).toElement();
+        QDomNodeList pluginElementList = metaContactElement.elementsByTagName( QLatin1String( "plugin-data" ) );
+        for ( int j = 0; j < pluginElementList.count(); ++j )
+        {
+            QDomElement element = pluginElementList.at( j ).toElement();
+            QString pluginId = element.attribute( QLatin1String( "plugin-id" ), QString() );
+            if ( !pluginId.endsWith( "Protocol" ) )
+                continue;
+
+            QMap<QString, QStringList> serializedData;
+
+            // Read data to serializedData
+            QDomNode field = element.firstChild();
+            while ( !field.isNull() )
+            {
+                QDomElement fieldElement = field.toElement();
+                if ( fieldElement.tagName() == QLatin1String( "plugin-data-field" ) )
+                {
+                    QString key = fieldElement.attribute( QLatin1String( "key" ), QLatin1String( "undefined-key" ) );
+                    serializedData[key] = fieldElement.text().split( QChar( 0xE000 ), QString::KeepEmptyParts );
+                }
+                field = field.nextSibling();
+            }
+
+            // Split serializedData by contact
+            int count = serializedData[QLatin1String("contactId")].count();
+            for ( int i = 0; i < count ; i++ )
+            {
+                QMap<QString, QString> sd;
+
+                QMap<QString, QStringList>::Iterator it;
+                QMap<QString, QStringList>::Iterator itEnd = serializedData.end();
+                for ( it = serializedData.begin(); it != itEnd; ++it )
+                {
+                    QStringList sl = it.value();
+                    if( sl.count() > i)
+                        sd[it.key()] = sl.value( i );
+                }
+                newList.append( ProtocolIdDataPair( pluginId, sd ) );
+            }
+
+            removeList.append( element );
+        }
+
+        foreach( QDomElement e, removeList )
+            metaContactElement.removeChild( e );
+
+        foreach( ProtocolIdDataPair pdp, newList )
+        {
+            QDomElement pluginElement = metaContactElement.ownerDocument().createElement( QLatin1String( "plugin-contact-data" ) );
+            pluginElement.setAttribute( QLatin1String( "plugin-id" ), pdp.first  );
+
+            QMap<QString, QString>::ConstIterator it;
+            for ( it = pdp.second.begin(); it != pdp.second.end(); ++it )
+            {
+                QDomElement pluginDataField = metaContactElement.ownerDocument().createElement( QLatin1String( "plugin-data-field" ) );
+                pluginDataField.setAttribute( QLatin1String( "key" ), it.key()  );
+                pluginDataField.appendChild( metaContactElement.ownerDocument().createTextNode(  it.value()  ) );
+                pluginElement.appendChild( pluginDataField );
+            }
+
+            metaContactElement.appendChild( pluginElement );
+        }
+    }
+    rootElement.setAttribute( QString("version"), "1.2" );
+    return true;
+}
+
+uint XmlContactStorage::readVersion( QDomElement &rootElement ) const
+{
+    QString versionString = rootElement.attribute( QString::fromLatin1( "version" ), QString() );
+    if( QRegExp( QString::fromLatin1( "[0-9]+\\.[0-9]" ) ).exactMatch( versionString ) )
+        return versionString.remove( QLatin1Char( '.' ) ).toUInt();
+    else
+        return 0;
+}
+
 QString XmlContactStorage::sourceToString( Kopete::MetaContact::PropertySource source ) const
 {
     if ( source == Kopete::MetaContact::SourceCustom )
Index: kopete/libkopete/contactlist/xmlcontactstorage.h
===================================================================
--- kopete/libkopete/contactlist/xmlcontactstorage.h	(revision 982847)
+++ kopete/libkopete/contactlist/xmlcontactstorage.h	(working copy)
@@ -64,8 +64,11 @@
     const QDomElement storeGroup( Kopete::Group *group ) const;
     const QList<QDomElement> storeContactListElement( Kopete::ContactListElement *contactListElement ) const;
 
-    bool updateFrom10( QDomElement &rootElement ) const;
+    bool updateFrom10to11( QDomElement &rootElement ) const;
+    bool updateFrom11to12( QDomElement &rootElement ) const;
 
+    uint readVersion( QDomElement &rootElement ) const;
+
 private:
     /**
      * Convert the contact list from an older version
Index: kopete/libkopete/kopetecontactlistelement.h
===================================================================
--- kopete/libkopete/kopetecontactlistelement.h	(revision 982847)
+++ kopete/libkopete/kopetecontactlistelement.h	(working copy)
@@ -112,7 +112,40 @@
 
 	typedef QMap<QString, QMap<QString, QString> > PluginDataMap;
 
+	typedef QMap<QString, QString> ContactData;
+	typedef QList<ContactData> ContactDataList;
+
 	/**
+	 * Get the settings as stored previously by calls to @ref setPluginContactData()
+	 * Note that plugins shouldn't use this method
+	 */
+	QMap<QString, ContactDataList > pluginContactData() const;
+
+	/**
+	 * Get the settings as stored previously by calls to @ref setPluginContactData() for a @p plugin
+	 * Note that plugins shouldn't use this method
+	 */
+	ContactDataList pluginContactData( Plugin *plugin ) const;
+
+	/**
+	 * Clear all plugin specific data.
+	 * Note that plugins shouldn't use this method
+	 */
+	void clearPluginContactData();
+
+	/**
+	 * Set plugin specific data for each contact.
+	 * Note that plugins shouldn't use this method
+	 */
+	void setPluginContactData( Plugin *plugin, const ContactDataList &dataList );
+
+	/**
+	 * Convenience method to append plugin specific data for single contact
+	 * Note that plugins shouldn't use this method
+	 */
+	void appendPluginContactData( const QString &pluginId, const ContactData &data );
+
+	/**
 	 * return plugin-specific data for all plugins
 	 */
 	const PluginDataMap pluginData() const;
Index: kopete/libkopete/kopeteprotocol.cpp
===================================================================
--- kopete/libkopete/kopeteprotocol.cpp	(revision 982847)
+++ kopete/libkopete/kopeteprotocol.cpp	(working copy)
@@ -172,14 +172,9 @@
 
 
 
-void Protocol::slotMetaContactAboutToSave( MetaContact *metaContact )
+void Protocol::serialize( MetaContact *metaContact )
 {
-	QMap<QString, QString> serializedData, sd;
-	QMap<QString, QString> addressBookData, ad;
-	QMap<QString, QString>::Iterator it;
-
-	//kDebug( 14010 ) << "Protocol::metaContactAboutToSave: protocol " << pluginId() << ": serializing " << metaContact->displayName();
-
+	QList< QMap<QString, QString> > serializedDataList;
 	QListIterator<Contact *> cit(metaContact->contacts());
 	while ( cit.hasNext() )
 	{
@@ -187,8 +182,8 @@
 		if( c->protocol()->pluginId() != pluginId() )
 			continue;
 
-		sd.clear();
-		ad.clear();
+		QMap<QString, QString> sd;
+		QMap<QString, QString> ad;
 
 		// Preset the contactId and displayName, if the plugin doesn't want to save
 		// them, or use its own format, it can call clear() on the provided list
@@ -206,34 +201,14 @@
 		c->serializeProperties( sd );
 		c->serialize( sd, ad );
 
-		// Merge the returned fields with what we already (may) have
-		for( it = sd.begin(); it != sd.end(); ++it )
-		{
-			// The Unicode chars E000-F800 are non-printable and reserved for
-			// private use in applications. For more details, see also
-			// http://www.unicode.org/charts/PDF/UE000.pdf.
-			// Inside libkabc the use of QChar( 0xE000 ) has been standardized
-			// as separator for the string lists, use this also for the 'normal'
-			// serialized data.
-			if( serializedData.contains( it.key() ) )
-				serializedData[ it.key() ] = serializedData[ it.key() ] + QChar( 0xE000 ) + it.value();
-			else
-				serializedData[ it.key() ] = it.value();
-		}
-
-		for( it = ad.begin(); it != ad.end(); ++it )
-		{
-			if( addressBookData.contains( it.key() ) )
-				addressBookData[ it.key() ] = addressBookData[ it.key() ] + QChar( 0xE000 ) + it.value();
-			else
-				addressBookData[ it.key() ] = it.value();
-		}
+		serializedDataList.append( sd );
 	}
 
-	// Pass all returned fields to the contact list
-	//if( !serializedData.isEmpty() ) //even if we are empty, that mean there are no contact, so remove old value
-	metaContact->setPluginData( this, serializedData );
+	// Pass all returned fields to the contact list even if empty (will remove the old one)
+	metaContact->setPluginContactData( this, serializedDataList );
 
+#if 0
+	// FIXME: This isn't used anywhere right now.
 	for( it = addressBookData.begin(); it != addressBookData.end(); ++it )
 	{
 		//kDebug( 14010 ) << "Protocol::metaContactAboutToSave: addressBookData: key: " << it.key() << ", data: " << it.data();
@@ -252,62 +227,20 @@
 		else
 			metaContact->setAddressBookField( this, QString::fromLatin1( "kopete" ), it.key(), it.value() );
 	}
+#endif
 }
 
-void Protocol::deserialize( MetaContact *metaContact, const QMap<QString, QString> &data )
+void Protocol::deserializeContactList( MetaContact *metaContact, const QList< QMap<QString, QString> > &dataList )
 {
-	/*kDebug( 14010 ) << "Protocol::deserialize: protocol " <<
-		pluginId() << ": deserializing " << metaContact->displayName() << endl;*/
-	
-	QMap<QString, QStringList> serializedData;
-	QMap<QString, QStringList::Iterator> serializedDataIterators;
-	QMap<QString, QString>::ConstIterator it;
-	for( it = data.begin(); it != data.end(); ++it )
+	foreach ( const ContactListElement::ContactData &sd, dataList )
 	{
-		serializedData[ it.key() ] = it.value().split( QChar( 0xE000 ), QString::KeepEmptyParts );
-		serializedDataIterators[ it.key() ] = serializedData[ it.key() ].begin();
-	}
-
-	int count = serializedData[QString::fromLatin1("contactId")].count();
-
-	// Prepare the independent entries to pass to the plugin's implementation
-	for( int i = 0; i < count ; i++ )
-	{
-		QMap<QString, QString> sd;
-#ifdef __GNUC__
-#warning  write this properly
-#endif
-#if 0	
-		QMap<QString, QStringList::Iterator>::Iterator serializedDataIt;
-		for( serializedDataIt = serializedDataIterators.begin(); serializedDataIt != serializedDataIterators.end(); ++serializedDataIt )
-		{
-			sd[ serializedDataIt.key() ] = *( serializedDataIt.data() );
-			++( serializedDataIt.data() );
-		}
-		
-#else
-		QMap<QString, QStringList>::Iterator serializedDataIt;
-		QMap<QString, QStringList>::Iterator serializedDataItEnd = serializedData.end();
-		for( serializedDataIt = serializedData.begin(); serializedDataIt != serializedDataItEnd; ++serializedDataIt )
-		{
-			QStringList sl=serializedDataIt.value();
-			if(sl.count()>i)
-				sd[ serializedDataIt.key() ] = sl[i];
-		}
-	
-	
-#endif
-
-		const QString& accountId=sd[ QString::fromLatin1( "accountId" ) ];
-		// myself was allowed in the contact list in old version of kopete.
-		// But if one keep it on the contact list now, it may conflict witht he myself metacontact.
-		// So ignore it
+		const QString& accountId = sd[ QString::fromLatin1( "accountId" ) ];
 		if( !d->canAddMyself && accountId == sd[ QString::fromLatin1( "contactId" ) ] )
 		{
 			kDebug( 14010 ) << "Myself contact was on the contactlist.xml for account " << accountId << ".  Ignore it";
 			continue;
 		}
-
+	
 		// FIXME: This code almost certainly breaks when having more than
 		//        one contact in a meta contact. There are solutions, but
 		//        they are all hacky and the API needs revision anyway (see
@@ -318,8 +251,6 @@
 		//        book data in the deserializer yet, only when serializing.
 		//        - Martijn
 		QMap<QString, QString> ad;
-		
-
 #if 0
 		QStringList kabcFields = addressBookFields();
 		for( QStringList::Iterator fieldIt = kabcFields.begin(); fieldIt != kabcFields.end(); ++fieldIt )
@@ -347,21 +278,26 @@
 			else
 			{
 				kWarning( 14010 ) <<
-					"No account available and account not set in " \
-					"contactlist.xml either!" << endl
+	"No account available and account not set in " \
+	"contactlist.xml either!" << endl
 					<< "Not deserializing this contact." << endl;
 				return;
 			}
 		}
 #endif
 
-
 		Contact *c = deserializeContact( metaContact, sd, ad );
 		if (c) // should never be null but I do not like crashes
 			c->deserializeProperties( sd );
 	}
 }
 
+void Protocol::deserialize( MetaContact *metaContact, const QMap<QString, QString> &data )
+{
+	Q_UNUSED( metaContact )
+	Q_UNUSED( data )
+}
+
 Contact *Protocol::deserializeContact(
 	MetaContact * metaContact,
 	const QMap<QString, QString> & serializedData,
Index: kopete/libkopete/kopetemetacontact.cpp
===================================================================
--- kopete/libkopete/kopetemetacontact.cpp	(revision 982847)
+++ kopete/libkopete/kopetemetacontact.cpp	(working copy)
@@ -557,9 +557,20 @@
 	contact->sendFile( sourceURL, altFileName, fileSize );
 }
 
-void MetaContact::emitAboutToSave()
+void MetaContact::serialize()
 {
-	emit aboutToSave( this );
+	clearPluginContactData();
+
+	QSet<Kopete::Protocol*> protocolSet;
+	foreach ( Kopete::Contact* c, contacts() )
+	{
+		Kopete::Protocol* protocol = c->protocol();
+		if ( !protocolSet.contains( protocol ) )
+		{
+			protocolSet.insert( protocol );
+			protocol->serialize( this );
+		}
+	}
 }
 
 void MetaContact::slotContactStatusChanged( Contact * c, const OnlineStatus &status, const OnlineStatus &/*oldstatus*/  )
@@ -1040,6 +1051,16 @@
 	}
 }
 
+void MetaContact::slotProtocolLoaded( Protocol *p )
+{
+	if( !p )
+		return;
+
+	ContactDataList dataList = pluginContactData( p );
+	if ( !dataList.isEmpty() )
+		p->deserializeContactList( this, dataList );
+}
+
 void MetaContact::slotAllPluginsLoaded()
 {
 	// Now that the plugins and subcontacts are loaded, set the source contact.
Index: kopete/libkopete/kopetecontact.cpp
===================================================================
--- kopete/libkopete/kopetecontact.cpp	(revision 983314)
+++ kopete/libkopete/kopetecontact.cpp	(working copy)
@@ -115,12 +115,7 @@
 	// if alreadyRegistered is true (which mean that this is duplicate contact) we will not add
 	// parent and the contact will die out on next Kopete restart.
 	if( !duplicate && parent && protocol() )
-	{
-		connect( parent, SIGNAL( aboutToSave( Kopete::MetaContact * ) ),
-			protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact * ) ) );
-
 		parent->addContact( this );
-	}
 }
 
 Contact::~Contact()
@@ -354,8 +349,6 @@
 	if( old )
 	{
 		old->removeContact( this );
-		disconnect( old, SIGNAL( aboutToSave( Kopete::MetaContact * ) ),
-			protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact * ) ) );
 
 		if(old->contacts().isEmpty())
 		{
@@ -366,7 +359,7 @@
 		{
 			d->metaContact = m; //i am forced to do that now if i want the next line works
 			//remove cached data for this protocol which will not be removed since we disconnected
-			protocol()->slotMetaContactAboutToSave( old );
+			protocol()->serialize( old );
 		}
 	}
 
@@ -380,8 +373,6 @@
 		// between adding completely new contacts (which should be written to kabc) and restoring upon restart
 		// (where no write is needed).
 		KABCPersistence::self()->write( m );
-		connect( d->metaContact, SIGNAL( aboutToSave( Kopete::MetaContact * ) ),
-		protocol(), SLOT( slotMetaContactAboutToSave( Kopete::MetaContact * ) ) );
 	}
 	sync();
 }
Index: kopete/libkopete/kopetepropertycontainer.h
===================================================================
--- kopete/libkopete/kopetepropertycontainer.h	(revision 982847)
+++ kopete/libkopete/kopetepropertycontainer.h	(working copy)
@@ -63,7 +63,7 @@
 	/**
 	 * @brief Deserialize the contacts persistent properties
 	 */
-	void deserializeProperties(QMap<QString, QString> &serializedData);
+	void deserializeProperties(const QMap<QString, QString> &serializedData);
 
 	/**
 	 * @return A QStringList containing all property keys
Index: kopete/libkopete/kopetemetacontact.h
===================================================================
--- kopete/libkopete/kopetemetacontact.h	(revision 982847)
+++ kopete/libkopete/kopetemetacontact.h	(working copy)
@@ -421,18 +421,13 @@
 		unsigned long fileSize = 0L );
 
 	/**
-	 * Emit aboutToSave signal to notify plugins that this metaContact is going to be saved
+	 * Serialize this metaContact
+	 * This causes each Kopete::Protocol subclass to serialise its contacts' data into the metacontact's plugin data
 	 */
-	void emitAboutToSave();
+	void serialize();
 
 signals:
 	/**
-	 * This metaContact is going to be saved to the contact list. Plugins should
-	 * connect to this signal to update data with setPluginData()
-	 */
-	void aboutToSave( Kopete::MetaContact *metaContact );
-
-	/**
 	 * One of the subcontacts' idle status has changed.  As with online status,
 	 * this can occur without the metacontact changing idle state
 	 */
@@ -512,6 +507,11 @@
 	 */
 	void slotPluginLoaded( Kopete::Plugin *plugin );
 
+	/**
+	 * If a protocol is loaded, deserialize cached data
+	 */
+	void slotProtocolLoaded( Kopete::Protocol *p );
+
 signals:
 	/**
 	 *  @brief The MetaContact online status changed
Index: kopete/libkopete/kopetepropertycontainer.cpp
===================================================================
--- kopete/libkopete/kopetepropertycontainer.cpp	(revision 982847)
+++ kopete/libkopete/kopetepropertycontainer.cpp	(working copy)
@@ -60,8 +60,7 @@
 	} // end for()
 } // end serializeProperties()
 
-void PropertyContainer::deserializeProperties(
-	QMap<QString, QString> &serializedData )
+void PropertyContainer::deserializeProperties( const QMap<QString, QString> &serializedData )
 {
 	QMap<QString, QString>::ConstIterator it;
 	for ( it=serializedData.constBegin(); it != serializedData.constEnd(); ++it )
Index: kopete/libkopete/kopetepluginmanager.h
===================================================================
--- kopete/libkopete/kopetepluginmanager.h	(revision 982847)
+++ kopete/libkopete/kopetepluginmanager.h	(working copy)
@@ -29,6 +29,7 @@
 {
 
 class Plugin;
+class Protocol;
 typedef QList<Plugin*> PluginList;
 class PluginManagerPrivate;
 
@@ -163,6 +164,12 @@
 	void pluginUnloaded( const QString &pluginName );
 
 	/**
+	 * @brief Signals a new protocol has just been loaded.
+	 * @note pluginLoaded is also emitted before this signal
+	 */
+	void protocolLoaded( Kopete::Protocol *protocol );
+
+	/**
 	 * @brief All plugins have been loaded by the plugin manager.
 	 *
 	 * This signal is emitted exactly ONCE, when the plugin manager has emptied
_______________________________________________
kopete-devel mailing list
kopete-devel@kde.org
https://mail.kde.org/mailman/listinfo/kopete-devel

Reply via email to