Backport da28f3 from master (GetProfileResult fix)
[kmess:kmess.git] / src / network / soap / roamingservice.cpp
1 /***************************************************************************
2                           roamingservice.cpp -  description
3                              -------------------
4     begin                : sun Jan 4 2009
5     copyright            : (C) 2009 by Antonio Nastasi
6     email                : sifcenter(at)gmail.com
7  ***************************************************************************/
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 #include "roamingservice.h"
19
20 #include "../../utils/kmessconfig.h"
21 #include "../../utils/kmessshared.h"
22 #include "../../utils/xmlfunctions.h"
23 #include "../../account.h"
24 #include "../../currentaccount.h"
25 #include "../../kmessdebug.h"
26 #include "soapmessage.h"
27
28 #include <QDateTime>
29 #include <QDir>
30 #include <QEventLoop>
31 #include <QFileInfo>
32 #include <QImageReader>
33 #include <QNetworkAccessManager>
34 #include <QNetworkReply>
35
36 #include <KDateTime>
37
38 /**
39  * @brief URL of the Storage Service
40  */
41 #define SERVICE_URL_STORAGE_SERVICE  "https://storage.msn.com/storageservice/SchematizedStore.asmx"
42
43 // Constructor
44 RoamingService::RoamingService( QObject *parent )
45 : PassportLoginService( parent )
46 {
47 }
48
49
50
51 // Destructor
52 RoamingService::~RoamingService()
53 {
54 }
55
56
57
58 // Create the common header for this service
59 QString RoamingService::createCommonHeader() const
60 {
61   QString header( "<StorageApplicationHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">\n"
62                     "<ApplicationID>Messenger Client 9.0</ApplicationID>\n"
63                     "<Scenario>RoamingSeed</Scenario>\n"
64                   "</StorageApplicationHeader>\n"
65                   "<StorageUserHeader xmlns=\"http://www.msn.com/webservices/storage/w10\">\n"
66                     "<Puid>0</Puid>\n"
67                     "<TicketToken />\n" // Filled in by the base class
68                   "</StorageUserHeader>\n" );
69   return header;
70 }
71
72
73
74 // Get the profile for given contact
75 void RoamingService::getProfile( const QString& cid )
76 {
77   cid_ = cid;
78   QString body( "<GetProfile xmlns=\"http://www.msn.com/webservices/storage/w10\">\n"
79                   "<profileHandle>\n"
80                     "<Alias>\n"
81                       "<Name>" + KMessShared::htmlEscape( cid ) + "</Name>\n"
82                       "<NameSpace>MyCidStuff</NameSpace>\n"
83                     "</Alias>\n"
84                     "<RelationshipName>MyProfile</RelationshipName>\n"
85                   "</profileHandle>\n"
86                   "<profileAttributes>\n"
87                     "<ResourceID>true</ResourceID>\n"
88                     "<DateModified>true</DateModified>\n"
89                     "<ExpressionProfileAttributes>\n"
90                       "<ResourceID>true</ResourceID>\n"
91                       "<DateModified>true</DateModified>\n"
92                       "<DisplayName>true</DisplayName>\n"
93                       "<DisplayNameLastModified>true</DisplayNameLastModified>\n"
94                       "<PersonalStatus>true</PersonalStatus>\n"
95                       "<PersonalStatusLastModified>true</PersonalStatusLastModified>\n"
96                       "<StaticUserTilePublicURL>true</StaticUserTilePublicURL>\n"
97                       "<Photo>true</Photo>\n"
98                       "<Flags>true</Flags>\n"
99                     "</ExpressionProfileAttributes>\n"
100                   "</profileAttributes>\n"
101                 "</GetProfile>\n" );
102
103   sendSecureRequest( new SoapMessage( SERVICE_URL_STORAGE_SERVICE,
104                                       "http://www.msn.com/webservices/storage/w10/GetProfile",
105                                       createCommonHeader(),
106                                       body ),
107                      "Storage" );
108 }
109
110
111
112 // Create a profile
113 void RoamingService::createProfile()
114 {
115   QString body( "<CreateProfile xmlns=\"http://www.msn.com/webservices/storage/w10\">\n"
116                 "<profile>\n"
117                 "<ExpressionProfile>\n"
118                 "<PersonalStatus/>\n"
119                 "<RoleDefinitionName>ExpressionProfileDefault</RoleDefinitionName>\n"
120                 "</ExpressionProfile>\n"
121                 "</profile>\n"
122                 "</CreateProfile>" );
123
124   sendSecureRequest( new SoapMessage( SERVICE_URL_STORAGE_SERVICE,
125                                       "http://www.msn.com/webservices/storage/w10/CreateProfile",
126                                       createCommonHeader(),
127                                       body ),
128                      "Storage" );
129 }
130
131
132
133 // update the profile of the current account
134 void RoamingService::updateProfile()
135 {
136   CurrentAccount *currentAccount = CurrentAccount::instance();
137   const QString currentPersonalMessage( currentAccount->getPersonalMessage( STRING_ORIGINAL ) );
138
139   if( profileResourceId_.isEmpty() )
140   {
141     kmWarning() << "Missing profileResourceId, not updating profile";
142     return;
143   }
144
145   if( currentPersonalMessage == lastKnownPersonalMessage_ &&
146       currentAccount->getFriendlyName( STRING_ORIGINAL ) == lastKnownFriendlyName_ )
147   {
148 #ifdef KMESSDEBUG_ROAMINGSERVICE
149     kmDebug() << "Personal message and friendly name are same as on server; not uploading changes.";
150 #endif
151     return;
152   }
153
154   // either the PSM or the friendly name has changed, so update them both.
155
156   lastKnownPersonalMessage_ = currentPersonalMessage;
157   lastKnownFriendlyName_    = currentAccount->getFriendlyName( STRING_ORIGINAL );
158
159 #ifdef KMESSDEBUG_ROAMINGSERVICE
160   kmDebug() << "Uploading personal message, profile ID:" << profileResourceId_;
161 #endif
162   QString body( "<UpdateProfile xmlns=\"http://www.msn.com/webservices/storage/w10\">\n"
163                 "<profile>\n"
164                 "<ResourceID>" +
165                   KMessShared::htmlEscape( profileResourceId_ ) +
166                 "</ResourceID>\n"
167                 "<ExpressionProfile>\n"
168                 "<FreeText>Update</FreeText>\n"
169                 "<DisplayName>" +
170                   KMessShared::htmlEscape( lastKnownFriendlyName_ ) +
171                 "</DisplayName>\n"
172                 "<PersonalStatus>" +
173                   KMessShared::htmlEscape( lastKnownPersonalMessage_ ) +
174                 "</PersonalStatus>\n"
175                 "<Flags>0</Flags>\n"
176                 "</ExpressionProfile>\n"
177                 "</profile>\n"
178                 "</UpdateProfile>\n");
179
180   sendSecureRequest( new SoapMessage( SERVICE_URL_STORAGE_SERVICE,
181                                       "http://www.msn.com/webservices/storage/w10/UpdateProfile",
182                                       createCommonHeader(),
183                                       body ),
184                      "Storage" );
185 }
186
187
188
189 // update the display picture of the current acccount
190 void RoamingService::updateDisplayPicture()
191 {
192   CurrentAccount *currentAccount = CurrentAccount::instance();
193   const QString path( currentAccount->getPicturePath() );
194
195   if( profileResourceId_.isEmpty() )
196   {
197     kmWarning() << "Missing profileResourceId, not updating display picture";
198     return;
199   }
200
201   // has the display picture really changed?
202   if( path == lastKnownDisplayPicturePath_ )
203   {
204 #ifdef KMESSDEBUG_ROAMINGSERVICE
205     kmDebug() << "Not updating display picture, already the same as on the server.";
206 #endif
207     return;
208   }
209   lastKnownDisplayPicturePath_ = path;
210
211   // see if we need to delete a previous picture
212   if( ! displayPictureResourceId_.isEmpty() )
213   {
214 #ifdef KMESSDEBUG_ROAMINGSERVICE
215     kmDebug() << "Deleting the previous display picture on the server.";
216 #endif
217     deleteRelationships( "", displayPictureResourceId_ );
218     deleteRelationships( profileResourceId_, displayPictureResourceId_ );
219   }
220
221   // upload new picture
222 #ifdef KMESSDEBUG_ROAMINGSERVICE
223   kmDebug() << "Uploading new display picture.";
224 #endif
225   QFile file( currentAccount->getPicturePath() );
226   if( file.open( QIODevice::ReadOnly ) )
227   {
228     QImageReader imageInfo( path );
229     QFileInfo fileInfo( path );
230
231     QString mimeType( "image/" );
232     mimeType.append( imageInfo.format() );
233
234     createDocument( fileInfo.baseName(), mimeType, file.readAll().toBase64() );
235     file.close();
236   }
237   else
238   {
239     kmWarning() << "Failed to read display picture from file" << currentAccount->getPicturePath();
240   }
241 }
242
243
244
245 // Parse the SOAP fault
246 void RoamingService::parseSecureFault( SoapMessage *message )
247 {
248   // Should never happen
249   if( ! message->isFaultMessage() )
250   {
251     kmWarning() << "Incorrect fault detected on incoming message";
252     return;
253   }
254
255   // Get the type of error
256   QString faultCode( message->getFaultCode() );
257   QString errorCode( XmlFunctions::getNodeValue( message->getFault(), "detail/errorcode" ) );
258
259   // See which fault we received
260   if( faultCode == "soap:Client" && ! errorCode.isEmpty() )
261   {
262     // The profile does not exist
263     if( errorCode == "ItemDoesNotExist" )
264     {
265 #ifdef KMESSDEBUG_ROAMINGSERVICE
266       kmDebug() << "The profile of the user does not exist: creating one.";
267 #endif
268       createProfile();
269       return;
270     }
271     else if( errorCode == "ItemAlreadyExists" )
272     {
273       kmWarning() << "The profile of the user already exists";
274     }
275   }
276   else
277   {
278     kmWarning() << "Invalid web service request:" << message->getFaultDescription();
279   }
280 }
281
282
283
284 // The connection received the full response
285 void RoamingService::parseSecureResult( SoapMessage *message )
286 {
287   QDomElement body( message->getBody().toElement() );
288   QString resultName( body.firstChildElement().localName() );
289
290   if( resultName == "GetProfileResponse" )
291   {
292 #ifdef KMESSDEBUG_ROAMINGSERVICE
293     kmDebug() << "Getting profile response:" << profileResourceId_;
294 #endif
295     processProfileResult( body );
296   }
297   else if( resultName == "UpdateProfileResponse" )
298   {
299 #ifdef KMESSDEBUG_ROAMINGSERVICE
300     kmDebug() << "Updated profile:" << profileResourceId_;
301 #endif
302     return;
303   }
304   else if( resultName == "CreateProfileResponse" )
305   {
306     profileResourceId_ = XmlFunctions::getNodeValue( body, "CreateProfileResponse/CreateProfileResult" );
307
308 #ifdef KMESSDEBUG_ROAMINGSERVICE
309     kmDebug() << "Created profile:" << profileResourceId_;
310 #endif
311     // Empty profile was created, update it
312     updateProfile();
313   }
314   else if( resultName == "DeleteRelationshipsResponse" )
315   {
316 #ifdef KMESSDEBUG_ROAMINGSERVICE
317     kmDebug() << "Relationship deleted";
318 #endif
319
320     return;
321   }
322   else if( resultName == "CreateDocumentResponse" )
323   {
324 #ifdef KMESSDEBUG_ROAMINGSERVICE
325     kmDebug() << "Document created";
326 #endif
327
328     displayPictureResourceId_ = XmlFunctions::getNodeValue( body, "CreateDocumentResponse/CreateDocumentResult" );
329     // a new display picture was uploaded, create a relation with the profile
330     createRelationships( profileResourceId_, displayPictureResourceId_ );
331   }
332   else if( resultName == "CreateRelationshipsResponse" )
333   {
334 #ifdef KMESSDEBUG_ROAMINGSERVICE
335     kmDebug() << "Relationship created";
336 #endif
337     return;
338   }
339   else
340   {
341     kmWarning() << "Unknown response type" << resultName << "received from endpoint" << message->getEndPoint();
342   }
343 }
344
345
346
347 // Process the getProfile response
348 void RoamingService::processProfileResult( const QDomElement &body )
349 {
350   CurrentAccount *currentAccount = CurrentAccount::instance();
351
352   const QDomNode expressionProfile( XmlFunctions::getNode( body, "GetProfileResponse/GetProfileResult/ExpressionProfile" ) );
353
354   // adam (23 Dec 10): if this node is missing, something is fubar with the profile.
355   // solution is to create a new one.
356   if ( expressionProfile.isNull() )
357   {
358     createProfile();
359     return;
360   }
361
362   QString fullPictureUrl( XmlFunctions::getNodeValue( expressionProfile, "StaticUserTilePublicURL" ) );
363   QString pictureLastModified( XmlFunctions::getNodeValue( expressionProfile, "Photo/DateModified" ) );
364   QString pictureName( XmlFunctions::getNodeValue( expressionProfile, "Photo/Name" ) );
365   // This message also contains DisplayName
366
367   profileResourceId_        = XmlFunctions::getNodeValue( expressionProfile, "ResourceID" );
368   displayPictureResourceId_ = XmlFunctions::getNodeValue( expressionProfile, "Photo/ResourceID" );
369   lastKnownPersonalMessage_ = XmlFunctions::getNodeValue( expressionProfile, "PersonalStatus" );
370   lastKnownFriendlyName_    = XmlFunctions::getNodeValue( expressionProfile, "DisplayName" );
371
372   // Fix encoding of the friendly name and personal message
373   lastKnownFriendlyName_    = QString::fromUtf8( lastKnownFriendlyName_.toAscii() );
374   lastKnownPersonalMessage_ = QString::fromUtf8( lastKnownPersonalMessage_.toAscii() );
375
376 #ifdef KMESSDEBUG_ROAMINGSERVICE
377   kmDebug() << "Got profile result:" << profileResourceId_;
378 #endif
379
380 #ifdef KMESSDEBUG_ROAMINGSERVICE
381   kmDebug() << "Received friendly name from Storage:" << lastKnownFriendlyName_;
382   kmDebug() << "Received personalMessage from Storage:" << lastKnownPersonalMessage_;
383   kmDebug() << "Received displayPicture location from server:" << fullPictureUrl;
384 #endif
385
386   // Don't update the details if they're not valid
387   if( ! lastKnownFriendlyName_.isEmpty() )
388   {
389     emit receivedFriendlyName( lastKnownFriendlyName_ );
390   }
391   if( ! lastKnownPersonalMessage_.isEmpty() )
392   {
393     emit receivedPersonalMessage( lastKnownPersonalMessage_ );
394   }
395
396   // Only download the picture if it is enabled
397   if( fullPictureUrl.isEmpty() || ! currentAccount->getShowPicture() )
398   {
399 #ifdef KMESSDEBUG_ROAMINGSERVICE
400     kmDebug() << "Display picture disabled, not using received picture:" << fullPictureUrl;
401 #endif
402     return;
403   }
404
405   // check if we don't have a picture with the same name (the msn server converts the image to jpeg and compresses it when storing,
406   // so the hash changes and kmess can't see that the image is actually the same as one he uploaded earlier)
407   const QString pictureDir( KMessConfig::instance()->getAccountDirectory( currentAccount->getHandle() ) + "/displaypics/" );
408   const QString localPicture( pictureDir + pictureName + ".png" ); // accountpage always saves the images in png format
409   if( QFile::exists( localPicture ) )
410   {
411 #ifdef KMESSDEBUG_ROAMINGSERVICE
412     kmDebug() << "Have a local picture with the same name (" << localPicture << "), not downloading display picture";
413 #endif
414     lastKnownDisplayPicturePath_ = localPicture;
415     currentAccount->setPicturePath( localPicture );
416     return;
417   }
418
419   // Check if the picture on the server is newer
420   const QString displayPicturePath( currentAccount->getPicturePath( false /* don't fallback to the kmess default pic */ ) );
421   QFileInfo info( displayPicturePath );
422
423   // KDateTime is used to process the timestamp from the server,
424   // because it also handles the timezone information
425   if( displayPicturePath.isEmpty()
426   ||  info.lastModified() < KDateTime::fromString( pictureLastModified, KDateTime::ISODate ).toClockTime().dateTime() )
427   {
428 #ifdef KMESSDEBUG_ROAMINGSERVICE
429     kmDebug() << "Downloading display picture from storage";
430 #endif
431     // Download the picture from the server into a temporary file
432     QNetworkAccessManager *manager = new QNetworkAccessManager( this );
433
434     connect( manager, SIGNAL(               finished(QNetworkReply*) ),
435              this,    SLOT  ( receivedDisplayPicture(QNetworkReply*) ) );
436
437     manager->get( QNetworkRequest( QUrl( fullPictureUrl ) ) );
438   }
439   else
440   {
441 #ifdef KMESSDEBUG_ROAMINGSERVICE
442         kmDebug() << "DP path:" << displayPicturePath << " Last modification date:" << info.lastModified()
443                 << "vs. server dp's date:" << KDateTime::fromString( pictureLastModified, KDateTime::ISODate ).toClockTime().dateTime();
444 #endif
445   }
446
447 #ifdef KMESSDEBUG_ROAMINGSERVICE
448     kmDebug() << "Done.";
449 #endif
450 }
451
452
453
454 // Received the display picture from the server
455 void RoamingService::receivedDisplayPicture( QNetworkReply *reply )
456 {
457   CurrentAccount *currentAccount = CurrentAccount::instance();
458
459 #ifdef KMESSDEBUG_ROAMINGSERVICE
460   kmDebug() << "Saving received display picture of size" << reply->size();
461 #endif
462
463   if( reply->size() == 0 )
464   {
465 #ifdef KMESSDEBUG_ROAMINGSERVICE
466     kmDebug() << "Received empty content, ignoring response.";
467 #endif
468     return;
469   }
470
471   if( ! reply->header( QNetworkRequest::LocationHeader ).toString().isEmpty() )
472   {
473 #ifdef KMESSDEBUG_ROAMINGSERVICE
474     kmDebug() << "Received Location: header, ignoring response.";
475     kmDebug() << "Location:" << reply->header( QNetworkRequest::LocationHeader ).toString();
476 #endif
477     return;
478   }
479
480   // We need to also delete the calling QNetworkAccessManager to avoid memory leaks
481   QNetworkAccessManager *manager = reply->manager();
482
483   // check if the picture dir exists
484   QDir accountDir( KMessConfig::instance()->getAccountDirectory( currentAccount->getHandle() ) );
485   if( ! accountDir.exists( "displaypics" ) )
486   {
487     accountDir.mkdir( "displaypics" );
488   }
489
490   const QString pictureDir( KMessConfig::instance()->getAccountDirectory( currentAccount->getHandle() ) +
491                             "/displaypics/" );
492   QString pictureName( "temporary-picture.dat" );
493   const QString tempPicturePath( pictureDir + pictureName );
494
495   QFile file( tempPicturePath );
496
497   // If the file can't be saved, do nothing
498   if( ! file.open( QIODevice::WriteOnly ) )
499   {
500     reply->deleteLater();
501     if( manager != 0 )
502     {
503       manager->deleteLater();
504     }
505     return;
506   }
507
508   // Save the picture on file, we'll move it later to a good place
509   file.write( reply->readAll() );
510   file.flush();
511   file.close();
512
513   reply->deleteLater();
514
515   if( manager != 0 )
516   {
517     manager->deleteLater();
518     manager = 0;
519   }
520
521   // Retrieve the picture's hash
522   QString msnObjectHash( KMessShared::generateFileHash( tempPicturePath ).toBase64() );
523   const QString safeMsnObjectHash( msnObjectHash.replace( QRegExp( "[^a-zA-Z0-9+=]"), "_" ) );
524
525   // Find out the image format
526   QImageReader imageInfo( tempPicturePath );
527
528   // Save the new display picture with its hash as name, to ensure correct caching
529   pictureName = safeMsnObjectHash + "." + imageInfo.format();
530
531   QDir pictureFolder( pictureDir );
532
533   // Do not overwrite identical pictures
534   if( ! pictureFolder.exists( pictureName )
535   &&  ! pictureFolder.rename( tempPicturePath, pictureName ) )
536   {
537     kmWarning() << "The display picture file could not be renamed from" << tempPicturePath << "to" << pictureName << ".";
538   }
539 #ifdef KMESSDEBUG_ROAMINGSERVICE
540   else
541   {
542     kmDebug() << "Saved display picture as" << ( pictureDir + pictureName );
543   }
544 #endif
545
546   lastKnownDisplayPicturePath_ = pictureDir + pictureName;
547   currentAccount->setPicturePath( pictureDir + pictureName );
548
549   emit receivedDisplayPicture( lastKnownDisplayPicturePath_ );
550 }
551
552
553
554 // create a document on the server (upload a display picture)
555 void RoamingService::createDocument( const QString& name, const QString& mimeType, const QString& data )
556 {
557   QString body( "<CreateDocument xmlns=\"http://www.msn.com/webservices/storage/w10\">\n"
558                   "<parentHandle>\n"
559                     "<RelationshipName>/UserTiles</RelationshipName>\n"
560                     "<Alias>\n"
561                       "<Name>" + KMessShared::htmlEscape( cid_ ) + "</Name>\n"
562                       "<NameSpace>MyCidStuff</NameSpace>\n"
563                     "</Alias>\n"
564                   "</parentHandle>\n"
565                   "<document xsi:type=\"Photo\">\n"
566                     "<Name>" + KMessShared::htmlEscape( name ) + "</Name>\n"
567                     "<DocumentStreams>\n"
568                       "<DocumentStream xsi:type=\"PhotoStream\">\n"
569                         "<DocumentStreamType>UserTileStatic</DocumentStreamType>\n"
570                         "<MimeType>" + KMessShared::htmlEscape( mimeType ) + "</MimeType>\n"
571                         "<Data>" + KMessShared::htmlEscape( data ) + "</Data>\n"
572                         "<DataSize>0</DataSize>\n"
573                       "</DocumentStream>\n"
574                     "</DocumentStreams>\n"
575                   "</document>\n"
576                   "<relationshipName>Messenger User Tile</relationshipName>\n"
577                 "</CreateDocument>\n" );
578
579   sendSecureRequest( new SoapMessage( SERVICE_URL_STORAGE_SERVICE,
580                                       "http://www.msn.com/webservices/storage/w10/CreateDocument",
581                                       createCommonHeader(),
582                                       body ),
583                      "Storage" );
584 }
585
586
587
588 // list the display pictures on the server (gets displayPictureResourceId), this method is currently not used
589 void RoamingService::findDocuments()
590 {
591   QString body( "<FindDocuments xmlns=\"http://www.msn.com/webservices/storage/w10\">\n"
592                   "<objectHandle>\n"
593                     "<RelationshipName>/UserTiles</RelationshipName>\n"
594                     "<Alias>\n"
595                       "<Name>" + KMessShared::htmlEscape( cid_ ) + "</Name>\n"
596                       "<NameSpace>MyCidStuff</NameSpace>\n"
597                     "</Alias>\n"
598                   "</objectHandle>\n"
599                   "<documentAttributes>\n"
600                     "<ResourceID>true</ResourceID>\n"
601                     "<Name>true</Name>\n"
602                   "</documentAttributes>\n"
603                   "<documentFilter>\n"
604                     "<FilterAttributes>None</FilterAttributes>\n"
605                   "</documentFilter>\n"
606                   "<documentSort>\n"
607                     "<SortBy>DateModified</SortBy>\n"
608                   "</documentSort>\n"
609                   "<findContext>\n"
610                     "<FindMethod>Default</FindMethod>\n"
611                     "<ChunkSize>25</ChunkSize>\n"
612                   "</findContext>\n"
613                 "</FindDocuments>\n" );
614
615   sendSecureRequest( new SoapMessage( SERVICE_URL_STORAGE_SERVICE,
616                                       "http://www.msn.com/webservices/storage/w10/FindDocuments",
617                                       createCommonHeader(),
618                                       body ),
619                      "Storage" );
620 }
621
622
623
624 // create a relationship between two resources on the server
625 void RoamingService::createRelationships( const QString& source_rid, const QString& target_rid )
626 {
627   QString body( "<CreateRelationships xmlns=\"http://www.msn.com/webservices/storage/w10\">\n"
628                   "<relationships>\n"
629                     "<Relationship>\n"
630                       "<SourceID>" + KMessShared::htmlEscape( source_rid ) + "</SourceID>\n"
631                       "<SourceType>SubProfile</SourceType>\n"
632                       "<TargetID>" + KMessShared::htmlEscape( target_rid ) + "</TargetID>\n"
633                       "<TargetType>Photo</TargetType>\n"
634                       "<RelationshipName>ProfilePhoto</RelationshipName>\n"
635                     "</Relationship>\n"
636                   "</relationships>\n"
637                 "</CreateRelationships>\n" );
638
639   sendSecureRequest( new SoapMessage( SERVICE_URL_STORAGE_SERVICE,
640                                       "http://www.msn.com/webservices/storage/w10/CreateRelationships",
641                                       createCommonHeader(),
642                                       body ),
643                      "Storage" );
644 }
645
646
647
648 // delete a relationship between two resources on the server
649 void RoamingService::deleteRelationships( const QString& source_rid, const QString& target_rid )
650 {
651   QString sourceHandle;
652
653   if( source_rid.isEmpty() )
654   {
655     sourceHandle = "<RelationshipName>/UserTiles</RelationshipName>\n"
656                    "<Alias>\n"
657                      "<Name>" + KMessShared::htmlEscape( cid_ ) + "</Name>\n"
658                      "<NameSpace>MyCidStuff</NameSpace>\n"
659                    "</Alias>\n";
660   }
661   else
662   {
663     sourceHandle = "<ResourceID>" + KMessShared::htmlEscape( source_rid ) + "</ResourceID>\n";
664   }
665
666   QString body( "<DeleteRelationships xmlns=\"http://www.msn.com/webservices/storage/w10\">\n"
667                   "<sourceHandle>\n" + sourceHandle + "</sourceHandle>\n"
668                   "<targetHandles>\n"
669                     "<ObjectHandle>\n"
670                       "<ResourceID>" + KMessShared::htmlEscape( target_rid ) + "</ResourceID>\n"
671                     "</ObjectHandle>\n"
672                   "</targetHandles>\n"
673                 "</DeleteRelationships>\n" );
674
675    sendSecureRequest( new SoapMessage( SERVICE_URL_STORAGE_SERVICE,
676                                       "http://www.msn.com/webservices/storage/w10/DeleteRelationships",
677                                       createCommonHeader(),
678                                       body ),
679                      "Storage" );
680 }
681
682
683
684 #include "roamingservice.moc"