Logo Search packages:      
Sourcecode: kmess version File versions  Download package

msnnotificationconnection.cpp

/***************************************************************************
                          msnnotificationconnection.cpp  -  description
                             -------------------
    begin                : Thu Jan 23 2003
    copyright            : (C) 2003 by Mike K. Bennett
    email                : mkb137b@hotmail.com
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 "msnnotificationconnection.h"

#include <kapplication.h>
#include <kconfig.h>
#include <kdebug.h>
#include <klocale.h>
#include <kmdcodec.h>
#include <kmessagebox.h>
#include <kpassdlg.h>
#include <krun.h>
#include <kurl.h>

#include <qstylesheet.h>
#include <qdom.h>

#include "../chat/chatmessage.h"
#include "../contact/contact.h"
#include "../contact/contactlist.h"
#include "../currentaccount.h"
#include "../kmessdebug.h"
#include "../specialgroups.h"
#include "../kmessinterface.h"

#include "mimemessage.h"
#include "msnchallengehandler.h"

#include "soap/passportloginservice.h"
#include "soap/offlineimservice.h"

#ifdef KMESSDEBUG_NOTIFICATION
  #define KMESSDEBUG_NOTIFICATION_GENERAL
//   #define KMESSDEBUG_NOTIFICATION_AUTH
//   #define KMESSDEBUG_NOTIFICATION_MESSAGES
#endif




/**
 * @brief The constructor
 *
 * Initializes the default groups of the ContactList class.
 */
00061 MsnNotificationConnection::MsnNotificationConnection()
 : MsnConnection("MsnNotificationConnection"),
   initialized_(false),
   noContacts_(0),
   offlineImService_(0),
   passportLoginService_(0),
   totalNoContacts_(0)
{
  // We manage the contact list
  contactList_ = new ContactList();

  //  addGroup( SpecialGroups::INDIVIDUALS, i18n("Individuals")  ); // Not persistent
  contactList_->addGroup( SpecialGroups::ONLINE,      i18n("Online")       );
  contactList_->addGroup( SpecialGroups::OFFLINE,     i18n("Offline")      );
  contactList_->addGroup( SpecialGroups::ALLOWED,     i18n("Allowed")      );
  contactList_->addGroup( SpecialGroups::REMOVED,     i18n("Removed")      );

  // Automatically remove ChatInformation and OfflineImMessage objects.
  openRequests_.setAutoDelete(true);
  offlineImMessages_.setAutoDelete(true);

  // Connect the login timer to the checkLogin slot
  connect( &loginTimer_, SIGNAL(    timeout() ),
           this,         SLOT  ( checkLogin() ) );
  // Connect the login timer to the checkLogin slot
  connect( this, SIGNAL(    pingSent() ),
           this,   SLOT(    checkSwitchboardsTimeout() ) );
}



/**
 * @brief The destructor
 *
 * Deletes the contact list.
 * The base class closes the connection.
 */
00098 MsnNotificationConnection::~MsnNotificationConnection()
{
  delete contactList_;

  // Remove all open requests.
  openRequests_.clear();

  // closeConnection() also cleans up a lot too.

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "DESTROYED Notification" << endl;
#endif
}



/**
 * @brief Add a contact to another group.
 *
 * It's possible to add a contact to multiple groups,
 * typically referred to as "copy contact to group..".
 *
 * @param  contact  The contact.
 * @param  groupId  The group ID.
 */
00123 void MsnNotificationConnection::addContactToGroup(QString handle, QString groupId)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: adding '" << handle << " to '" << groupId << "'." << endl;
#endif

  putAdc( handle, "FL", groupId );
}



/**
 * @brief Re-add a known contact to the friends list
 *
 * The contact will no longer appear in a "allowed contacts"
 * or "removed contacts" group, but appear at the friends list again.
 * The contact should exist already at some list
 * before (e.g. the block list or friends list).
 * 
 * Internally, this method runs the required putAdc() and putRem()
 * calls to change the contact lists:
 * - the contact is added the the friends list (<code>FL</code>).
 * - the contact is removed from the block list (<code>BL</code>).
 * - the contact is added to the allow list (<code>AL</code>).
 *
 * @param  handle  E-mail address of the contact.
 */
00150 void MsnNotificationConnection::addExistingContact(QString handle)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Re-adding contact " << handle << endl;
#endif

  // Add a contact by unblocking it then adding it to the allowed list and the friends list
  // (at group 0 by default - the user can move it later)
  putAdc( handle, "FL" );
  putRem( handle, "BL" );
  putAdc( handle, "AL" );
}



/**
 * @brief Add a new contact to the list.
 *
 * This is the standard method to add a contact.
 *
 * This method is used only when a new contact appears:
 * - to add the contact to the friends list, call this method.
 * - to allow the contact only, call allowContact()
 * - to block the contact, call blockContact()
 *
 * Internally, this method runs the required putAdc() calls to change the contact lists:
 * - the contact is added to the allow list (<code>AL</code>).
 * - the contact is added to the friends list (<code>FL</code>).
 *
 * @param  handle  E-mail address of the contact.
 */
00181 void MsnNotificationConnection::addNewContact(QString handle)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Adding contact " << handle << endl;
#endif

  // Ask the server to add the contact to the friends list and allow list.
  // TODO: Official client adds contact to friend-list first, and allow-list later, reason/effect unknown.
  putAdc( handle, "AL" );
  putAdc( handle, "FL" );
}



/**
 * @brief Internal function to track SOAP clients.
 *
 * This method connects the HttpSoapConnection::requestFailed() signal,
 * and appends the client to the internal list.
 *
 * @param  client  The SOAP client class.
 */
00203 void MsnNotificationConnection::addSoapClient( HttpSoapConnection *client )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Adding SOAP client to list." << endl;
#endif

  // Connect signals
  connect(client, SIGNAL(     requestFailed(HttpSoapConnection*, const QString&) ),
          this,   SLOT  ( soapRequestFailed(HttpSoapConnection*, const QString&) ));
  connect(client, SIGNAL(     requestFailed(HttpSoapConnection*, const QString&, const QString&, QDomElement) ),
          this,   SLOT  ( soapRequestFailed(HttpSoapConnection*, const QString&, const QString&, QDomElement) ));

  // Add to list so it cleans up when the connection closes
  soapClients_.append(client);
}



/**
 * @brief Adds the given contact to the allow list only.
 *
 * It allows the contact to see the online state of the user.
 * The contact will not appear in the main window
 * until it's also added to the friends list using addExistingContact().
 *
 * This method can only be called when the contact exists
 * in the reverse list only. This happens when the contact is new to the user,
 * or it was completely removed. Call unblockContact() instead when the
 * contact needs to be unblocked.
 *
 * This method is used when a new contact appears:
 * - to add the contact to the friends list too, call addNewContact()
 * - to allow the contact only, call this method.
 * - to block the contact, call blockContact()
 *
 * Internally, this method runs the required putAdc() calls to change the contact lists:
 * - the contact is added to the allow list (<code>AL</code>).
 *
 * @param handle  E-mail address of the contact.
 */
00243 void MsnNotificationConnection::allowContact(QString handle)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Allowing contact " << handle << endl;
#endif

  putAdc( handle, "AL" );
}



/**
 * @brief Add a new group
 *
 * This sends an <code>ADG</code> command to the server.
 * When gotAdg() receives the server response, the ContactList object is updated.
 *
 * @param  name  Name of the group.
 */
00262 void MsnNotificationConnection::addGroup(QString name)
{
  name = KURL::encode_string( name );
  sendCommand( "ADG", name  + "\r\n" );
}



/**
 * @brief Block the given contact
 *
 * When the contact also exists in the friends list,
 * it will remain there with a blocked status icon.
 * When the contact only exists in the "allowed contacts" group,
 * it moves to the "removed contacts" group with a blocked status icon.
 *
 * This method can also be used when a new contact appears:
 * - to add the contact to the friends list too, call addNewContact()
 * - to allow the contact only, call allowContact()
 * - to block the contact, call this method.
 *
 * Internally, this method runs the required putAdc() calls to change the contact lists:
 * - the contact is added to the block list (<code>BL</code>).
 *
 * @param handle  E-mail address of the contact.
 */
00288 void MsnNotificationConnection::blockContact(QString handle)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Blocking contact " << handle << endl;
#endif

  // Block a contact by removing it from the allowed list
  // and adding it to the blocked list
  putRem( handle, "AL" );
  putAdc( handle, "BL" );
}



/**
 * @brief Change the current media of the user.
 *
 * This is used to exchange the "now playing" information.
 * It sends an <code>UUX</code> command to the server.
 * More information about this command can be found
 * at: http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes#UUX
 *
 * @param  application    Name of the media application, typically empty. iTunes and Winamp are known to be accepted.
 * @param  mediaType      Type of the media, either "Music", "Games" or "Office".
 * @param  enabled        Whether the media setting is enabled or not.
 * @param  formatString   A .Net style formatting string, e.g. "{0} - {1}".
 * @param  arguments      Replacements for the placeholders in the formatStrirng.
 */
00316 void MsnNotificationConnection::changeCurrentMedia( const QString &application, const QString &mediaType,
                                                    bool enabled, const QString &formatString,
                                                    const QStringList arguments )
{
  // Update current media string
  QString media = application + "\\0" + mediaType + "\\0" + (enabled ? "1" : "0") + "\\0"
                + formatString + "\\0" + arguments.join("\\0");
  lastCurrentMedia_ = QStyleSheet::escape(media);

  // Send personal status and current media
  putUux();
}



/**
 * @brief Called when the MSN Object of the current user changed.
 *
 * When this method is called, contacts will be informed the user has a new display picture.
 * Some clients automatically initiate a background chat to retrieve the new display picture.
 * Other clients wait until a real chat is initiated, to request the display picture at that moment. 
 *
 * Internally, this method calls changeStatus() because
 * that same command is used to notify about MsnObject changes.
 * The MsnObject is extracted from the CurrentAccount class.
 */
00342 void MsnNotificationConnection::changedMsnObject()
{
  if(isConnected())
  {
    // Send the same status message, with a new msn object
    changeStatus(currentAccount_->getStatus());
  }
}



/**
 * @brief Change the friendly name of the user.
 *
 * Internally, this method uses changeProperty() to set the <code>MFN</code> property.
 *
 * @param  newName  The new friendly name.
 */
00360 void MsnNotificationConnection::changeFriendlyName( QString newName )
{
  changeProperty("MFN", newName);
}



/**
 * @brief Change the friendly name of another contact.
 *
 * Internally, this method uses changeProperty() to set the <code>MFN</code> property.
 *
 * This method is also called when a new name is received by gotNln().
 * The MSN Messenger servers don't enforce contact names,
 * they have to be set manually in the contact list (like a personal addressbook).
 *
 * @param  handle   E-mail address of the contact.
 * @param  newName  The new friendly name.
 */
00379 void MsnNotificationConnection::changeFriendlyName( QString handle, QString newName )
{
  // Change msn-friendly-name property.
  changeProperty(handle, "MFN", newName);
}



/**
 * @brief Change the personal message of the user.
 *
 * The personal mesage typically contains a short status message
 * displayed next to the contact.
 *
 * This method sends the <code>UUX</code> payload command to the server.
 *
 * @param  newMessage  The new status message.
 */
00397 void MsnNotificationConnection::changePersonalMessage( QString newMessage )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Changing personal message to " << newMessage << endl;
#endif

  lastPsm_ = QStyleSheet::escape(newMessage);
  putUux();
}



/**
 * @brief Change a user property
 *
 * It sends the <code>PRP</code> (likely short for property) command to the server.
 *
 * Valid property types are:
 * - <code>MFN</code>: MSN Friendly name
 * - <code>PHH</code>: Phone home
 * - <code>PHW</code>: Phone work
 * - <code>PHM</code>: Phone mobile
 * - <code>MOB</code>: MSN Mobile authorised for others: Y or N
 * - <code>MBE</code>: MSN Mobile enabled
 * - <code>WWE</code>: MSN direct?
 * - <code>HSB</code>: Has blog: <code>1</code> or <code>0</code>
 *
 * This function is also used internally by other methods like changeFriendlyName()
 * 
 * @param  type   The property type.
 * @param  value  The new property value, can also be empty.
 */
00429 void MsnNotificationConnection::changeProperty( QString type, QString value )
{
  if( value.isEmpty() )
  {
    sendCommand( "PRP", type + "\r\n" );
  }
  else
  {
    value = KURL::encode_string( value );
    sendCommand( "PRP", type + " " + value + "\r\n" );
  }
}



/**
 * @brief Change a contact property
 *
 * It sends the <code>SBP</code> (likely short for set-buddy-property) command to the server.
 *
 * @param  handle  E-mail address of the contact.
 * @param  type    The property type, e.g. <code>MFN</code>, see the other changeProperty() method.
 * @param  value   The new property value, can also be empty.
 */
00453 void MsnNotificationConnection::changeProperty( QString handle, QString type, QString value )
{
  // Get contact
  const Contact *contact = contactList_->getContactByHandle(handle);
  if(KMESS_NULL(contact)) return;

  // Avoid disconnects by the server
  if( contact->getGuid().isEmpty() )
  {
    kdWarning() << "MsnNotificationConnection::changeProperty: Can't change property, contact GUID is empty"
                << " (contact=" << handle << " property=" << type << ")." << endl;
    return;
  }

  // First guess is that "SBP" means set-buddy-property
  if( value.isEmpty() )
  {
    sendCommand( "SBP", contact->getGuid() + " " + type + "\r\n" );
  }
  else
  {
    value = KURL::encode_string( value );
    sendCommand( "SBP", contact->getGuid() + " " + type + " " + value + "\r\n" );
  }
}



/**
 * @brief Change the status of the user.
 *
 * Possible status values are:
 * - <code>NLN</code>: online
 * - <code>AWY</code>: away
 * - <code>BRB</code>: be right back
 * - <code>BSY</code>: busy
 * - <code>LUN</code>: out to lunch
 * - <code>PHN</code>: on the phone
 * - <code>HDN</code>: hidden (invisible)
 *
 * This sends the <code>CHG</code> command to the user.
 * This command also informs contacts the user's MsnObject and client capabilities.
 * The MsnObject contains the meta-data about the user's display picture.
 * The display picture itself is transferred in a chat session.
 *
 * Note this method advertises the supported client features, like 'winks' and 'webcam'.
 *
 * @param  newStatus  The new user status.
 * @todo Possibly rename this to putChg() instead, especially when webcam support makes the capabilities dynamic.
 */
00503 void MsnNotificationConnection::changeStatus( const QString& newStatus )
{
  // NOTE: When changing this property, all MSNP2P code need to be retested again!!
  // The capabilities are like a contract. The other client assumes everything works that's promised here.
  const uint capabilities = (Contact::MSN_CAP_MSN75 | Contact::MSN_CAP_WINKS | Contact::MSN_CAP_MULTI_PACKET);

  QString objStr = KURL::encode_string( currentAccount_->getMsnObjectString() );

  /*
  // To test the old file transfer:
  sendCommand( "CHG", newStatus + " 0\r\n");
  return;
  */

  if(objStr.isEmpty())
  {
    // Just don't send it if we don't have it.
    sendCommand( "CHG", newStatus + " " + QString::number(capabilities) + "\r\n" );
  }
  else
  {
    sendCommand( "CHG", newStatus + " " + QString::number(capabilities) + " " + objStr + "\r\n" );
  }
}



/**
 * @brief Called when the login timeout exceeded.
 *
 * Checks whether login was successful within time limit.
 * If not, the user is informed no connection could be made.
 */
00536 void MsnNotificationConnection::checkLogin()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Login not successful within time limit." << endl;
#endif

  closeConnection();

  // Show a message to the user
  QString message = i18n("KMess could not connect the the MSN Messenger servers. The MSN Messenger servers could be temporary unavailable or there is a problem with your Internet connection.") + "\r\n";
  dotNetMessage(message);
}



// Removes any expired switchboard requests, so new ones can be made
void MsnNotificationConnection::checkSwitchboardsTimeout()
{
  // There are no pending switchboard requests, bail out
  if( openRequests_.count() < 1 )
  {
    return;
  }

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Removing expired switchboard requests." << endl;
#endif

  ChatInformation *switchboardInfo;
  QPtrListIterator<ChatInformation> it( openRequests_ );
  while( ( switchboardInfo = it.current() ) != 0 )
  {
    // Delete any pending switchboard 
    uint now = QDateTime::currentDateTime().toTime_t();
    if( ( now - switchboardInfo->getTime() ) > 60 )
    {
      openRequests_.remove( switchboardInfo );
    }
    ++it;
  }
}



/**
 * @brief Close the connection with the server
 *
 * This disconnects from the server. It also cleans up the
 * state variables and objects associated with the session,
 * e.g. contactlist data, open chat invitations, pending offline-im messages and SOAP clients.
 *
 * The ContactList object is saved and reset as well.
 */
00589 void MsnNotificationConnection::closeConnection()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Close the connection." << endl;
#endif
  // Stop the login timer in case the user closed the connection during login
  loginTimer_.stop();
  noContacts_ = 0;
  totalNoContacts_ = 0;

  // Disconnect from the server first.
  // emits the signals so other classes can clean up / save properties.
  disconnectFromServer();

  // Resetting variables, objects will be removed through soapClients_
  offlineImService_     = 0;
  passportLoginService_ = 0;

  // Disconnect other possibly active SOAP sessions.
  // Use deleteLater() since this method may be called from loginIncorrect()
  if( ! soapClients_.isEmpty() )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: Closing SOAP sessions." << endl;
#endif
    QPtrListIterator<HttpSoapConnection> it(soapClients_);
    while( it.current() != 0 )
    {
      HttpSoapConnection *soapClient = it.current();
      soapClient->closeConnection();
      soapClient->deleteLater();
      ++it;
    }
    soapClients_.clear();
  }

  // Remove contacts and non-special groups from the contact list
  if ( contactList_ != 0 )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: Resetting contactlist." << endl;
#endif

    // Save what we own. CurrentAccount only has a const reference to it for easier access.
    if( ! currentAccount_->isGuestAccount() )
    {
      contactList_->saveProperties( kapp->config() );  // Make sure the contactlist is saved
    }

    contactList_->reset();
    // The contact list is not deleted, like this class it's being
    // shared through the entire application, and attached to slots.
  }

  // Reset variables
  pendingOfflineImMessages_.clear();
  offlineImMessages_.clear();
  openRequests_.clear();
  if( currentAccount_ != 0 )
  {
    currentAccount_->setNoEmails( 0 );
  }
}



/**
 * @brief Called when the socket connected.
 *
 * This method calls putVer() to start the version exchange.
 */
00660 void MsnNotificationConnection::connectionSuccess()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Connected to server, sending VER." << endl;
#endif

  // Restart the login timer for the actual MSN sign in
  loginTimer_.stop();
  loginTimer_.start( 45000, true );  // 45 seconds

  putVer();
}



/**
 * @brief Internal function to create the Offline-IM service
 *
 * This service is used to retrieve the Offline-IM messages.
 * The signals are attached to receivedOfflineIm() and receivedMailData()
 *
 * @returns  The Offline-IM webservice handler
 */
00683 OfflineImService* MsnNotificationConnection::createOfflineImService()
{
  // Request the first offline-im message
  OfflineImService *client = new OfflineImService( authT_, authP_, this );

  // Register signals
  addSoapClient( client );  // auto deletes at closeConnection() call.
  connect( client, SIGNAL(   messageReceived( const QString&,   const QString&, const QString&,
                                              const QDateTime&, const QString&, const QString&, int ) ),
           this,   SLOT  ( receivedOfflineIm( const QString&,   const QString&, const QString&,
                                              const QDateTime&, const QString&, const QString&, int ) ));
  connect( client, SIGNAL( metaDataReceived( QDomElement ) ),
           this,   SLOT  ( receivedMailData( QDomElement ) ));
  
  return client;
}



/**
 * @brief Internal function to create the passport login handler.
 *
 * This service is used by gotUsr() to retrieve the passport authentication token.
 *
 * @returns  The Passport login webservice handler
 */
00709 PassportLoginService* MsnNotificationConnection::createPassportLoginService()
{
  // Create the login handler.
  PassportLoginService *loginService = new PassportLoginService();

  // Connect all login notifications 
  connect( loginService, SIGNAL( loginIncorrect()        ),
           this,         SLOT  ( loginIncorrect()        ) );
  connect( loginService, SIGNAL( loginSucceeded(QString) ),
           this,         SLOT  ( loginSucceeded(QString) ) );

  return loginService;
}



/**
 * @brief  Internal function to show the .Net MSN Messenger service status
 *
 * This method displays a message box with the "Show MSN Messenger Service status?" prompt,
 * and opens the MSN Messenger Service status webpage.
 *
 * @param  message  Message to show before the standard "show status" question.
 */
00733 void MsnNotificationConnection::dotNetMessage(QString message)
{
  int    result;
  KURL   netURL( "http://status.messenger.msn.com/Status.aspx" );

  message += "\n" + i18n("Show MSN Messenger Service status?");
  result = KMessageBox::questionYesNo( 0, message, i18n("Server Error") );
  if ( result == KMessageBox::Yes )
  {
    new KRun( netURL );
  }
  closeConnection();
}



/**
 * @brief Return the contact list
 *
 * @returns  The contact list managed by this class.
 */
00754 const ContactList* MsnNotificationConnection::getContactList() const
{
  return contactList_;
}



/**
 * @brief  Called when the user is ready to go online.
 *
 * This method is called by gotSyn() or gotLst() when
 * the entire contact list data is received.
 * It changes some final things before the user goes online:
 * - set the initial user status, e.g. "online" or "invisible".
 * - ses the initial personal message.
 * - requests other server data, like Hotmail e-mail URL's.
 * Finally, the connected() signal is fired.
 */
00772 void MsnNotificationConnection::goOnline()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Lists received.  Go online." << endl;
#endif
  int     ack;

  if(KMESS_NULL(currentAccount_)) return;

  // Change to the initial status
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Changing initial status to " << currentAccount_->getInitialStatus() << endl;
#endif
  changeStatus( currentAccount_->getInitialStatus() );

  // Reset status fields.
  lastCurrentMedia_ = QString::null;
  lastPsm_ = QString::null;

  // Start with default personal message (may be empty)
  changePersonalMessage( currentAccount_->getPersonalMessage() );

  // When the account supports e-mail (e.g. Hotmail), request the URL's.
  // This didn't give problems with non-email accounts,
  // but restricted accounts return a "710" error for this command.
  if( currentAccount_->getEmailSupported() )
  {
    // The ACK is temporary stored as inbox-command,
    // so the response can be mapped back to the requested folder.

    // Ask for inbox URL
    ack = sendCommand("URL", "INBOX\r\n");
    currentAccount_->setInboxInformation( QString::number(ack), "", "" );

    // Ask for compose URL
    ack = sendCommand("URL", "COMPOSE\r\n");
    currentAccount_->setComposeInformation( QString::number(ack), "", "" );
  }

  // Start ping timer
  setSendPings( true );

  // Notify observers that the server is connected
  loginTimer_.stop();
  emit connected();
}



/**
 * @brief Handle the <code>ADC</code> command; addition of a contact to a list or group.
 *
 * This command is sent by putAdc(). The server confirms the command by
 * sending an identical response back, sometimes with additional information.
 *
 * The following protocol commands are exchanged to add a contact:
 * @code
>>> ADC 18 AL N=test@test.com
>>> ADC 19 FL N=test@test.com F=test@test.com
@endcode
 * @code
<<< ADC 18 AL N=test@test.com
<<< ADC 19 FL N=test@test.com F=test@test.com C=4d9898ff-43bc-48e5-a97f-0886779fca3f
@endcode
 *
 * Note the server returns the contact GUID in the response.
 * This value is used instead of the handle for all commands that alter the <code>FL</code> list.
 *
 * The following protocol commands are exchanged when a contact is added to another group:
 * @code
>>> ADC 15 FL C=390bd80e-f2e1-48eb-b51f-ead5547b48f1 83db17d0-518f-404b-92c9-cb3d3bd4551c
@endcode
 * @code
<<< ADC 15 FL C=390bd80e-f2e1-48eb-b51f-ead5547b48f1 83db17d0-518f-404b-92c9-cb3d3bd4551c
@endcode
 *
 * @param  command  The command arguments.
 *                  - <code>command[1]</code> is the ack id.
 *                  - <code>command[2]</code> is the list.
 *                  - the remaining commands depend on the prefix (<code>F=</code> for friently name, <code>C=</code> for a contact guid, or <code>N=</code> for the contact handle).
 *                  - for the <code>FL</code> list, a group id may be added after all arguments.
 */
00854 void MsnNotificationConnection::gotAdc(const QStringList& command)
{
  if(KMESS_NULL(contactList_)) return;

  Contact *contact;
  int      listNumber;

  // Extract the handle, friendlyName, and list from the message
  QString listType;
  QString handle;
  QString friendlyName;
  QString guid;
  QString groupId;

  listType = command[2];

  // command looks something like:
  //  ADC 16 FL N=passport@hotmail.com F=Display%20Name C=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

  // Process optional parameters
  for( uint i = 3; i < command.size(); i++)
  {
    if( command[i].startsWith("N=") )
    {
      handle = command[i].mid(2).lower();
    }
    else if( command[i].startsWith("F=") )
    {
      friendlyName = KURL::decode_string( command[i].mid(2) );
    }
    else if( command[i].startsWith("C=") )
    {
      guid = command[i].mid(2);
    }
    else if( listType == "FL" && command[i].at(1) != '=' )
    {
      groupId = command[i];
    }
    else if( listType == "RL" && command[i].length() == 1 && command[0] >= '0' && command[0] <= '9' )
    {
      // unknown, number at end of command list.
    }
    else
    {
      kdWarning() << "MsnNotificationConnection::gotAdc: Unknown argument encountered: '" << command[i] << "'" << endl;
    }
  }


#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Added " << friendlyName << " (" << handle << ")"
            << " to " << listType << " " << groupId << "." << endl;
#endif


  // Check whether the contact exists.
  if( ! handle.isNull() )
  {
    contact = contactList_->getContactByHandle(handle);
  }
  else if( ! guid.isNull() )
  {
    contact = contactList_->getContactByGuid(guid);
  }
  else
  {
    kdWarning() << "MsnNotificationConnection::gotAdc: Contact identification not found in ADC response!" << endl;
    return;
  }


  if(contact == 0)
  {
    // Add the contact if he/she does not exist.
    if     (listType == "FL") listNumber = Contact::MSN_LIST_FRIEND;
    else if(listType == "AL") listNumber = Contact::MSN_LIST_ALLOWED;
    else if(listType == "BL") listNumber = Contact::MSN_LIST_BLOCKED;
    else if(listType == "RL") listNumber = Contact::MSN_LIST_REVERSE;
    else if(listType == "PL") listNumber = Contact::MSN_LIST_PENDING;
    else                      listNumber = 0;

    // The sent ADC command may not have a friendly name, causing the server to respond none as well.
    // Use the handle so at least something is displayed.
    if( friendlyName.isNull() )
    {
      friendlyName = handle;
    }

    contact = contactList_->addContact(handle, friendlyName, listNumber, guid, groupId);
  }
  else
  {
    // Contact was added to a certain group, activate it
    contact->setList(listType, true);

    // Also update the groupId for the FL list
    if(listType == "FL")
    {
      // Update the friendly name.
      if( ! friendlyName.isEmpty() )
      {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
        if( contact->getFriendlyName() != friendlyName )
        {
          kdDebug() << "MsnNotificationConnection::gotAdc: received new friendlyname, "
                    << "updating contact '" << handle << "'." << endl;
        }
#endif
        contact->setFriendlyName( friendlyName );
      }

      // Update the GUID
      if( ! guid.isEmpty() )
      {
        if( contact->getGuid().isEmpty() )
        {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
          kdDebug() << "MsnNotificationConnection::gotAdc: received GUID for contact, updating contact '" << handle << "'." << endl;
#endif
        }
        else if( guid != contact->getGuid() )
        {
          // This shouldn't happen, but debugging it helps to solve weird problems.
          kdWarning() << "MsnNotificationConnection::gotAdc: received new GUID for contact, "
                      << "updating contact (contact=" << handle << ")" << endl;
        }

        contact->setGuid(guid);
      }


      // Update the groups.
      if( ! groupId.isEmpty() )
      {
        // NOTE: If we like to add a contact to multiple groups at once, this needs updating.
        //       Currently it is not required, because KMess doesn't send such commands to the server.
        contact->addGroupId(groupId);
      }

      // If this contact was added from moveContact(), remove from source group.
      QString sourceGroup = contact->getPrepareMove();
      if( ! sourceGroup.isEmpty() )
      {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
        kdDebug() << "Notification: Detected moving operation, removing contact from old group." << endl;
#endif
        putRem( contact->getHandle(), "FL", sourceGroup );
        contact->setPrepareMove();
      }
    }
  }


  // Check whether the contact added you
  if(contact->checkIfContactAddedUser())
  {
    emit contactAddedUser(contact);
  }
}



// Received the addition of a group
void MsnNotificationConnection::gotAdg(const QStringList& command)
{
  if(KMESS_NULL(contactList_)) return;

  QString name = KURL::decode_string( command[2] );
  QString id   = command[3];

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Added group " << name << " (" << command[1] << ")." << endl;
#endif

  // Pass the information to the group list.
  contactList_->addGroup( id, name );
}



// Received confirmation of the user's status change
void MsnNotificationConnection::gotChg(const QStringList& command)
{
  if(KMESS_NULL(currentAccount_)) return;

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: User's status changed to " << command[2] << "." << endl;
#endif
  // Change the user's status
  currentAccount_->setStatus( command[2] );
}



// Received a challenge from the server
void MsnNotificationConnection::gotChl(const QStringList& command)
{
  // Handle challenge query
  MSNChallengeHandler handler("YMM8C_H7KCQ2S_KL", "PROD0090YUAUV{2B");
  QString response = handler.computeHash(command[2]);

  // Send response
  sendPayloadMessage( "QRY", handler.productId(), response );
}



// Received a version update from the server
void MsnNotificationConnection::gotCvr(const QStringList& /*command*/)
{
  if(KMESS_NULL(currentAccount_)) return;

  // Send the USR command
  sendCommand( "USR", "TWN I " + currentAccount_->getHandle() + "\r\n" );
}



// Received notice that a contact went offline
void MsnNotificationConnection::gotFln(const QStringList& command)
{
  if(KMESS_NULL(contactList_)) return;

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: " << command[1] << " went offline." << endl;
#endif

  contactList_->setContactOffline( command[1].lower() );
}



// Received notice that a contact is already online
void MsnNotificationConnection::gotIln(const QStringList& command)
{
  if(KMESS_NULL(contactList_)) return;

  // Get the contact info from the message
  QString handle = command[3].lower();
  QString friendlyName = KURL::decode_string( command[4] );

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: " << handle << " is initially " << command[2] << "." << endl;
#endif

  uint    capabilities = command[5].toUInt();
  QString msnObject    = KURL::decode_string( command[6] );

  // Check if the received MSN object is empty, a "0" (Kopete), or shorter than the string "<msnobject/>"
  if( msnObject.isEmpty() || msnObject.compare( QString( "0" ) ) <= 0 || msnObject.length() < 12 )
  {
    msnObject = QString::null;
  }

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  if( ! msnObject.isEmpty() )
  {
    kdDebug() << "Notification: This person has an msn6 picture: " << msnObject << endl;
  }
#endif

  contactList_->changeContactStatus( handle, friendlyName, command[2], capabilities, msnObject, false );

}



/**
 * @brief Handle the <code>LSG</code> command; receive group information of the contact list.
 *
 * This command is after a client invoked the <code>SYN</code> command to download the contact list.
 * An example of this command:
 * @code
<<< LSG testing 83db17d0-518f-404b-92c9-cb3d3bd4551c
@endcode
 *
 * @param  command  The command arguments.
 *                  - <code>command[1]</code> is the list title, url-encoded.
 *                  - <code>command[2]</code> is the group id.
 */
01134 void MsnNotificationConnection::gotLsg(const QStringList& command)
{
  if(KMESS_NULL(contactList_)) return;

  QString name    = KURL::decode_string( command[1] );
  QString groupId = command[2];

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Got group " << name << "(" << groupId << ")" << endl;
#endif

  contactList_->addGroup( groupId, name );
}



/**
 * @brief Handle the <code>LST</code> command; receive contact information of the contact list.
 *
 * @code
<<< LST N=test@test.xx F=test@test.xx C=dfd5d659-fec9-4b43-9055-5cd1e6f33e45 3 1
@endcode
 *
 * @param  command  The command arguments.
 *                  - the first arguments are variable, and depend on the prefix (<code>F=</code> for friently name, <code>C=</code> for a contact guid, or <code>N=</code> for the contact handle).
 *                  - the next argument is a bitwise flag of the list id's. See Contact::MsnContactLists.
 *                  - the final argument is unknown, but always <code>1</code>.
 */
01162 void MsnNotificationConnection::gotLst(const QStringList& command)
{
  if(KMESS_NULL(contactList_)) return;

  //emit statusMessage( i18n("Getting lists"), KmessInterface::TYPE_WARNING );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Got LST : " << command.join( " " ) << endl;
#endif


  QString handle;
  QString friendlyName;
  QString guid;

  uint i = 1;
  for(; i < command.size(); i++)
  {
    if( command[i].startsWith("N=") )
    {
      handle = command[i].mid(2).lower();
    }
    else if( command[i].startsWith("F=") )
    {
      friendlyName = KURL::decode_string( command[i].mid(2) );
    }
    else if( command[i].startsWith("C=") )
    {
      guid = command[i].mid(2);
    }
    else if( command[i].at(1) != '=' )
    {
      // End of parameters.
      break;
    }
    else
    {
      kdWarning() << "MsnNotificationConnection::gotLst: Unknown argument encountered: '" << command[i] << "'" << endl;
    }
  }

  int     list         = command[i].toInt();
//  int     unknown      = command[i + 1];
  QString groupIds     = command[i + 2];

  // Friendlyname is not set when contact is in the AL only (never seen online).
  if( friendlyName.isEmpty() )
  {
    friendlyName = handle;
  }

  friendlyName = KURL::decode_string( friendlyName );

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  QString listName = QString(list & Contact::MSN_LIST_FRIEND  ? "FL " : "")
                   + QString(list & Contact::MSN_LIST_ALLOWED ? "AL " : "")
                   + QString(list & Contact::MSN_LIST_BLOCKED ? "BL " : "")
                   + QString(list & Contact::MSN_LIST_REVERSE ? "RL " : "")
                   + QString(list & Contact::MSN_LIST_PENDING ? "PL " : "");
  kdDebug() << "Notification: Got contact " << friendlyName << "(" << handle << ") list=" << listName << "groups=" << groupIds << "." << endl;
#endif

  // Add the contact
  Contact *contact = contactList_->addContact( handle, friendlyName, list, groupIds, guid );
  if(KMESS_NULL(contact)) return;

  // Check whether the contact added you
  if(contact->checkIfContactAddedUser())
  {
    emit contactAddedUser(contact);
  }

  // Handle pending list automatically.
  // see: http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes#Reverse_List_Additions
  if( contact->isPending() )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: Contact is found in pending list, adding to reverse-list." << endl;
#endif

    putAdc( handle, "RL");   // possibly only allowed since contact is on Pending list.
    putRem( handle, "PL");
  }

  // Check if this is the last LST
  noContacts_++;
  if ( noContacts_ == totalNoContacts_ )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: All " << totalNoContacts_ << " were received.  Go online." << endl;
#endif
    goOnline();
  }
}



/**
 * @brief Handle the <code>NLN</code> command; a contact changed it's status.
 *
 * When a user updates it's status with the <code>CHG</code> command,
 * all it's contact receive a <code>NLN</code> command.
 * KMess sends the <code>CHG</code> command with the changeStatus() method.
 * The <code>CHG</code> allows a contact to change it's name, client capabilities or MsnObject.
 *
 * The capabilities are a bitwise flag. The list of known values
 * are described in the ContactBase::MsnClientCapabilities enum.
 *
 * The MsnObject is an identifier for the display picture.
 * To download the actual picture, a client needs to initiate a chat,
 * and send an invitation there.
 *
 * @code
<<< NLN NLN user@kmessdemo.org KMess%20Demo 1342210080 %3Cmsnobj%20Creator%3D%22user%40kmessdemo.org%22%20Size%3D%2211581%22%20Type%3D%223%22%20Location%3D%22KMess.tmp%22%20Friendly%3D%22AA%3D%3D%22%20SHA1D%3D%223VfOMCTTkuYHDjJhPx4sSlPiM%2Bs%3D%22%20SHA1C%3D%22ULeT2WGsdabLGNP3RgbWYw7ve80%3D%22/%3E
@endcode
 *
 * @param  command  The command arguments.
 *                  - <code>command[1]</code> is the status code, see changeStatus()
 *                  - <code>command[2]</code> is the contact handle.
 *                  - <code>command[3]</code> is the contact name, url encoded.
 *                  - <code>command[4]</code> is the client-capabilities flag.
 *                  - <code>command[5]</code> is the url-encoded MsnObject XML code.
 */
01284 void MsnNotificationConnection::gotNln(const QStringList& command)
{
  if(KMESS_NULL(contactList_)) return;

  // Get the contact info from the message
  QString status       = command[1];
  QString handle       = command[2].lower();
  QString friendlyName = KURL::decode_string( command[3] );
  uint    capabilities = command[4].toUInt();
  QString msnObject    = KURL::decode_string( command[5] );

  // For some reason an empty MSNObject is sent as "0" with MSNP12
  if( msnObject == "0" )
  {
    msnObject = QString::null;
  }

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: " << handle << " is now " << status << "." << endl;
#endif

  // If the contact's name has changed, we need to update our server-side list manually.
  // Note that getTrueFriendlyName() is used, we don't want the aliases from the ContactExtension
  // since we would no longer be able to see both true-name and alias names.
  const Contact *contact = contactList_->getContactByHandle(handle);
  if( friendlyName != contact->getTrueFriendlyName() && friendlyName != handle )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: Contact " << contact->getHandle() << " - Current friendly name '" << contact->getTrueFriendlyName() << "'"
              << " doesn't match ours: '" << friendlyName << "'."
              << " Updating server-side friendlyname." << endl;
#endif
    changeFriendlyName( handle, friendlyName );
  }

  // Update contact status.
  contactList_->changeContactStatus( handle, friendlyName, status, capabilities, msnObject );
}



// Received notice of disconnetion from the server
void MsnNotificationConnection::gotOut(const QStringList& command)
{
  // If the first word is "OTH", it means that this user connected from elsewhere.
  if ( command[1] == "OTH" )
  {
    KMessageBox::error( 0, i18n("You have been disconnected because you \nconnected from another MSN Messenger client \nor from another location") );
  }
  closeConnection();
}



// Received a propertu change (friendly name, phone number, etc..)
void MsnNotificationConnection::gotPrp(const QStringList& command)
{
  QString propertyName  = command[1];
  QString propertyValue = KURL::decode_string( command[2] );

  if( propertyName == "MFN" )
  {
    currentAccount_->setFriendlyName( propertyValue );
  }
  else if( propertyName == "MBE" )
  {
    // Unimplemented.
    // indicates whether MSN Mobile is enabled.
  }
  else if( propertyName == "WWE" )
  {
    // Unimplemented
    // indicates whether MSN Direct is enabled.
  }
  else
  {
//    kdWarning() << "MsnNotificationConnection::gotPrp: Received unsupported property name/value: " << propertyName << " " << propertyValue << "!" << endl;
  }
}



// Received confirmation of change in a group's name
void MsnNotificationConnection::gotReg(const QStringList& command)
{
  if(KMESS_NULL(contactList_)) return;

  QString groupId = command[2];
  QString name    = KURL::decode_string( command[3] );

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Group " << groupId << " renamed to " << name << "." << endl;
#endif
  // Pass the info to the group list
  contactList_->renameGroup( groupId, name );
}



/**
 * @brief Handle the <code>REM</code> command; confirmation of a contacts's removal from list or group.
 *
 * @code
>>> REM 11 AL test@test.xx
@endcode
 * @code
<<< REM 11 AL test@test.xx
@endcode
 *
 * @param  command  The arguments,
 *                  - <code>command[1]</code> is the ack id.
 *                  - <code>command[2]</code> is the contact list.
 *                  - <code>command[3]</code> is the contact handle. With the <code>FL</code> list, this is a GUID.
 *                  - <code>command[4]</code> is included when the list is <code>FL</code>. It's the the group the contact was removed from.
 */
01399 void MsnNotificationConnection::gotRem(const QStringList& command)
{
  /*
   * Note, if this method is called, it doesn't mean the contact is entirely removed.
   * It only indicated the contact was removed from a certain group or list.
   */

  if(KMESS_NULL(contactList_)) return;

  Contact *contact;

  // Extract the handle, friendlyName, and list from the message
  QString listType = command[2];
  QString handle;
  QString guid;
  QString groupId;

  if( listType == "FL" )
  {
    // FL has a different syntax.
    guid    = command[3];
    contact = contactList_->getContactByGuid( guid );

    // If contact list removed from the FL without specifying groups,
    // it will appear again in these groups when the contact is re-added.
    // see: http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes#REM
    if( command.size() > 3 )
    {
      groupId = command[4];  // an optional parameter!
    }
  }
  else
  {
    handle  = command[3].lower();
    contact = contactList_->getContactByHandle( handle );
  }

  // Check whether contact is null.
  if(contact == 0)
  {
    kdDebug() << "MsnNotificationConnection::gotRem: Couldn't find contact " << handle << guid << " in the contact list." << endl;
    return;
  }

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Contact " << contact->getHandle() << " removed from " << listType << " " << groupId << "." << endl;
#endif

  // Remove the contact from the given list
  if(listType == "FL")
  {
    if( groupId.isEmpty() )
    {
      // A contact is only removed from the FL if no groupId is given
      contact->setList("FL", false);
    }
    else
    {
      // Otherwise, only remove from the given group,
      // the contact defaults to "no group" automatically.
      contact->removeGroupId(groupId);
    }
  }
  else
  {
    // Contact was removed from the given list.
    contact->setList(listType, false);

    // Even if the contact is removed from all lists, they are not removed entirely.
    // You might want to add them again, with the history/ContactExtension settings preserved.
    // TODO perhaps change the contact-remove "policy" in the future
  }
}



// Received confirmation of a group's removal
void MsnNotificationConnection::gotRmg(const QStringList& command)
{
  if(KMESS_NULL(contactList_)) return;

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Remove group " << command[2] << "." << endl;
#endif

  // Pass the info to the group list
  contactList_->removeGroup( command[2] );
}



// Received a connection request from a contact
void MsnNotificationConnection::gotRng(const QStringList& command)
{
  // Pull the data from the message
  QString chatId        = command[1];
  QString serverAndPort = command[2];
  // command[3] = authType == "CKI"
  QString auth          = command[4];
  QString handle        = command[5].lower();
//   QString friendlyName  = KURL::decode_string( command[6] );

  QString server     = serverAndPort.section(':', 0, 0);
  QString portString = serverAndPort.section(':', 1, 1);
  bool goodPort;
  int port = portString.toInt( &goodPort );
  if ( !goodPort )
  {
    kdDebug() << "Notification::gotRng - WARNING - Couldn't get port from string " << portString << endl;
    return;
  }
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: " << handle << " is requesting a chat at: "
            << server << ":" << port << " id=" << chatId << " auth=" << auth << "." << endl;
#endif

  // Send connection information object
  emit startedSwitchboard( ChatInformation(this, handle, server, port, auth, chatId, ChatInformation::CONNECTION_BACKGROUND ) );
}



/**
 * @brief Handle the <code>SYN</code> response; synchronization information.
 *
 * After the client logged in with the <code>USR</code> command (see gotUsr()),
 * it requests the contact list from the server with the <code>SYN</code> command.
 * This initiates the flood of commands from the server (e.g. <code>LSG</code> and <code>LST</code>).
 *
 * When a timestamp is specified in the <code>SYN</code> command,
 * the server only sends the incremental updates of the contact list.
 * This however, is currently not used in KMess. It requires absolute confidence
 * the contact list commands are handled correctly. This is a bit difficult to
 * guarantee after a protocol version upgrade.
 *
 * @code
>>> SYN 7 0 0
@endcode
 * @code
<<< SYN 7 2007-05-02T13:00:41.25-07:00 2007-05-05T08:39:41.277-07:00 16 2
@endcode
 *
 * @param  command  The command arguments.
 */
01543 void MsnNotificationConnection::gotSyn(const QStringList& command)
{
  emit ( i18n("Got synchronization") );
  totalNoContacts_ = command[3].toInt();

  // The login was successful, stop timer now already.
  // When the authentication part was slow KMess could disconnect while the contactlist is being downloaded.
  loginTimer_.stop();

  // Allow the contactlist to be downloaded within 2 minutes, should be more then enough.
  loginTimer_.start(120000, true);

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Got SYN.  # of contacts is " << totalNoContacts_ << endl;
#endif

  // Go online if contactlist is empty (new account)
  if(totalNoContacts_ == 0)
  {
    goOnline();
  }
}



// Received information about a contact's personal message
void MsnNotificationConnection::gotUbx(const QStringList &command, const QByteArray &payload)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Got UBX information." << endl;
#endif

  Contact *contact = contactList_->getContactByHandle( command[1].lower() );
  if(KMESS_NULL(contact)) return;

  if(command[2].toInt() == 0)
  {
    // Size is 0, no personal message
    contact->setPersonalStatus( QString::null );
  }
  else
  {
    // Load XML parser
    QDomDocument xml;
    if( ! xml.setContent(payload) )
    {
      kdWarning() << "MsnNotificationConnection::gotUbx: Could not parse personal status message (invalid XML, contact=" << command[1] << ")!" << endl;
    }

    // Parse payload
    QDomElement root     = xml.namedItem("Data").toElement();
    QString psm          = root.namedItem("PSM").toElement().text();
    QString currentMedia = root.namedItem("CurrentMedia").toElement().text();
    QString mediaType;

    // Append current media if set.
    if( currentMedia.length() > 0 )
    {
      // From http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes:
      //
      // Media string can be something like:
      //  <CurrentMedia>\0Music\01\0{0} - {1}\0Title\0Artist\0Album\0\0</CurrentMedia>
      //  <CurrentMedia>\0Games\01\0Playing {0}\0Game Name\0</CurrentMedia>
      //  <CurrentMedia>\0Office\01\0Office Message\0Office App Name\0</CurrentMedia>
      //
      // The literal "\0" is the separator char.
      // Fields are: application, mediatype, enabled, formatstring, arg1, arg2, arg3

      // Split media string
      QStringList media = QStringList::split( "\\0", currentMedia, true );
      mediaType = media[1];

      // Check if media is enabled, to find out what this means.
      if( media[2] != "1" )
      {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
        kdDebug() << "Notification: CurrentMedia is set, but not enabled." << endl;
#endif
        currentMedia = QString::null;
      }
      else
      {
        // Replace fields in .Net style format string
        currentMedia = media[3];  // format
        for( uint i = 4; i < media.count(); i++ )
        {
          currentMedia = currentMedia.replace( "{" + QString::number(i - 4) + "}", media[i] );
        }
      }
    }

    // Store personal message
    contact->setPersonalStatus( psm, mediaType, currentMedia );
  }
}



// Received the folder and command info for Hotmail's inbox or compose
void MsnNotificationConnection::gotUrl(const QStringList& command)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Got URL information." << endl;
#endif
  if(KMESS_NULL(currentAccount_)) return;

  // Check the inbox and compose URLs to see if they're storing this ack
  if ( currentAccount_->getInboxCommand() == command[1] )
  {
    // This is the inbox url
    currentAccount_->setInboxInformation( command[4], command[2], command[3] );
  }
  else if ( currentAccount_->getComposeCommand() == command[1] )
  {
    // This is the compose url
    currentAccount_->setComposeInformation( command[4], command[2], command[3] );
  }
}



// Received user authentication information
void MsnNotificationConnection::gotUsr(const QStringList& command)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Got USR information." << endl;
#endif
  if(KMESS_NULL(currentAccount_)) return;

  // This is either asking for authentication or confirming it
  if ( command[2] == "TWN" )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: USR authentication." << endl;
#endif

    QString parameters = KURL::decode_string( command[4] );

    // Change statusbar
    emit statusMessage( i18n("Authenticating"), KMessInterface::TYPE_WARNING );

    // Get the right password value
    QString password;
    if( ! currentAccount_->getTemporaryPassword().isEmpty() )
    {
      // Login from InitialView. Try to login with the newly entered password
      password = currentAccount_->getTemporaryPassword();
    }
    else
    {
      // Login from menu. Use the current password.
      password = currentAccount_->getPassword();
    }

    // Start the passport based login
    passportLoginService_ = createPassportLoginService();
    addSoapClient( passportLoginService_ );
    passportLoginService_->login( parameters, currentAccount_->getHandle(), password );
  }
  else if ( command[2] == "OK" )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: USR confirmation." << endl;
#endif
    // This is a confirmation message.  Get the user's friendly name from the message.
    bool isVerified = (command[4].toInt() == 1);
    currentAccount_->setVerified( isVerified );

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    if( ! isVerified )
    {
      kdWarning() << "Notification: Passport account of the user is not verified yet." << endl;
    }
#endif

    // When users choose an existing account in the InitialView dialog,
    // they may alter the password. This value is stored as temporary password.
    // The password may be overwritten now, because the login was succesful.
    currentAccount_->saveTemporaryPassword();

    // Update the status
    emit statusMessage( i18n("Received user confirmation"), KMessInterface::TYPE_WARNING );

    // Synchronize with the server.
    // This will cause it to send the account data and contact lists)
    sendCommand("SYN", "0 0\r\n");
    sendCommand("GCF", "Shields.xml\r\n");   // TODO: error if omitted?

//    QDateTime time_ = QDateTime::currentDateTime();
//    QString currentTime_ = time_.toString(Qt::ISODate) + ".0000000-07:00";
//    sendCommand( "SYN", currentTime_ + " " + currentTime_ + "\r\n");

    emit statusMessage( i18n("Waiting for contact list..."), KMessInterface::TYPE_WARNING );

    // Reset the connection timer now, to give some more time
    // to the MSN contact list server to send us our contacts.
    // Technically, we're waiting for the SYN command response.
    loginTimer_.stop();
    loginTimer_.start( 60000, true );  // 60 seconds
  }
}



// Received version information
void MsnNotificationConnection::gotVer(const QStringList& command)
{
#ifdef KMESSTEST
  ASSERT( command[2] == "MSNP12" );
#else
  Q_UNUSED( command ); // Avoid compiler warning
#endif
  if(KMESS_NULL(currentAccount_)) return;

  // Send some fake info about the current version
  // First parameter is the locale-id (0x0409 is U.S. English).
  sendCommand( "CVR", "0x0409 winnt 5.1 i386 MSNMSGR 7.5.0324 msmsgs " + currentAccount_->getHandle() + "\r\n" );
}



// Received server transfer information
void MsnNotificationConnection::gotXfr(const QStringList& command)
{
  // Get parameters
  int     transactionId = command[1].toInt();
  QString serverType    = command[2];  // Server Type (NS or SB)
  QString serverAndPort = command[3];  // server:port part
#ifdef KMESSTEST
  ASSERT( serverAndPort.contains(":") );
#endif

  // Get the server from that
  QString server     = serverAndPort.left( serverAndPort.find(":") );
  QString portString = serverAndPort.right( serverAndPort.length() - serverAndPort.findRev(":") - 1 );

  // Convert the port to an integer
  bool goodPort;
  int port = portString.toInt( &goodPort );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Got " << serverType << " transfer to " << server << ":" << port << "." << endl;
#endif
  if( ! goodPort )
  {
    kdDebug() << "Notfication: Got bad port number : " << portString << "." << endl;
    return;
  }


  if( serverType == "NS" )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: Disconnecting from old server." << endl;
#endif
    // Disconnect from the existing server.
    disconnectFromServer( true );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: Connecting to new server." << endl;
#endif
    // This is a notification server transfer.  Switch this socket to the given server.
    emit statusMessage( i18n("Transfer to notification server"), KMessInterface::TYPE_WARNING );

#ifdef KMESS_NETWORK_WINDOW
    KMESS_NET_INIT(this, "NS " + server);
#endif

    // Connect to the new server.
    if( ! connectToServer( server, port ) )
    {
      kdDebug() << "Notification: WARNING - Couldn't connect to new server." << endl;
    }
  }
  else if( serverType == "SB" )
  {
#ifdef KMESSTEST
    ASSERT( openRequests_.count() > 0 );
    uint noChatsBefore = openRequests_.count();
#endif

    // This is a switchboard server.  This message should be in response to
    //  a user-requested chat.  Look for the pending chat information.

    ChatInformation *switchboardInfo;
    QPtrListIterator<ChatInformation> it( openRequests_ );
    while( ( switchboardInfo = it.current() ) != 0 && switchboardInfo->getTransactionId() != transactionId )
    {
      ++it;
    }
    if( switchboardInfo == 0 )
    {
      kdDebug() << "MsnNotificationConnection - Got a switchboard, but there are no corresponding open requests." << endl;
      return;
    }
    // Debug the transaction ID. Let the chat continue, if
    // there are errors it can be traced back in the console.
    if( switchboardInfo->getTransactionId() != transactionId )
    {
      kdWarning() << "MsnNotificationConnection: received XFR response, but transaction-ID differs!" << endl;
    }

    // Update the server information
    switchboardInfo->setServerInformation( server, port, command[5] );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: emitting startedSwitchboard() signal." << endl;
#endif

    // Signal the new connection
    emit startedSwitchboard( *switchboardInfo );

    // Remove the first item from the active requests list
    // Automatically removes the ChatInformation object.
    openRequests_.remove( switchboardInfo );
#ifdef KMESSTEST
    ASSERT( openRequests_.count() == ( noChatsBefore - 1 ) );
#endif
  }
}



// Initialize the object
01864 bool MsnNotificationConnection::initialize()
{
  if ( initialized_ )
  {
    kdDebug() << "Notification Connection already initialized!" << endl;
    return false;
  }
  if ( !MsnConnection::initialize() )
  {
    kdDebug() << "Notification Connection: Couldn't initialize parent." << endl;
    return false;
  }

  // currentAccount_ is initialized now.
  // Pass the current contactlist reference to the currentaccount
  // so other classes can access contacts from a central point.
  currentAccount_->setContactList( contactList_ );

  initialized_ = true;
  return initialized_;
}



// Test whether the given command is a payload command
01889 bool MsnNotificationConnection::isPayloadCommand(const QString &command) const
{
  return (command == "UBX"   // contact personal status message
       || command == "GCF"   // server config file
       || command == "NOT"   // msn alerts, msn calendar
       || command == "IPG"   // contact page
         );
}



// The passport login failed, username/password was incorrect
void MsnNotificationConnection::loginIncorrect()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "MsnNotificationConnection: Passport login was incorrect." << endl;
#endif

  // NOTE: a similar thing also happens in soapRequestFailed(),
  // but for fatal SOAP errors.

  // Show a message to the user, before deleting the object!
  KMessageBox::error( 0, i18n("Authentication failed, please verify your account name and password") );

  // Close the connection attempt.
  closeConnection();
}



// The passport login succeeded
void MsnNotificationConnection::loginSucceeded(QString authentication)
{
#ifdef KMESSDEBUG_NOTIFICATION_AUTH
  kdDebug() << "Notification - Authentication is " << authentication << endl;
#endif

  // Delete the login handler
  // Delay deletion so it can complete the method.
  soapClients_.remove( passportLoginService_ );
  QTimer::singleShot( 10, passportLoginService_, SLOT(deleteLater()) );
  passportLoginService_ = 0;

  // Send login command.
  sendCommand( "USR", "TWN S " + authentication + "\r\n" );

  // Extract the 't' and 'p' values from the 'authentication' string,
  // these are needed later for webservice calls (e.g. Offline-IM)
  QStringList fields = QStringList::split("&", authentication);
  for( QStringList::Iterator it = fields.begin(); it != fields.end(); ++it )
  {
    QString field = *it;
    if( field.startsWith("t=") )
    {
      authT_ = field.mid(2);
    }
    else if( field.startsWith("p=") )
    {
      authP_ = field.mid(2);
    }
#ifdef KMESSDEBUG_NOTIFICATION
    else
    {
      kdWarning() << "MsnNotificationConnection: Could not parse authentication string!" << endl;
    }
#endif
  }

  if( authP_.isEmpty() || authT_.isEmpty() )
  {
    kdWarning() << "MsnNotificationConnection: Could not parse passport authentication response, 'p' and 't' fields not detected!" << endl;
  }
}



// Move the contact to another group.
void MsnNotificationConnection::moveContact(QString handle, QString fromGroupId, QString toGroupId)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Moving '" << handle << "' from '" << fromGroupId << "' to '" << toGroupId << "'." << endl;
#endif

  Contact *contact = contactList_->getContactByHandle(handle);
  if(contact == 0)
  {
    kdDebug() << "MsnNotificationConnection - moveContact() - Couldn't find the contact." << endl;
    return;
  }

  if(fromGroupId == toGroupId)
  {
    // Would remove the contact otherwise. (re-added to original group, removed from original group afterwards)
    kdDebug() << "MsnNotificationConnection - moveContact() - Attempted to move contact to same group." << endl;
    return;
  }


  // Move the contact
  if(fromGroupId == "0" || fromGroupId.isEmpty())
  {
    // Move from implicit "individuals" group to something else requires a single ADC.
    putAdc( handle, "FL", toGroupId );
  }
  else
  {
    // Add to new group, remove from old.
    // Only execute the "REM" command when the "ADC" was successful.
//     movingSourceGroup_ = fromGroupId;
//     movingAck_         = 
    contact->setPrepareMove( fromGroupId );
    putAdc( handle, "FL", toGroupId );

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "MsnNotificationConnection: waiting for ack " << /*movingAck_ <<*/ " to remove contact from group." << endl;
#endif
  }
}



// Open a connection to the server
bool MsnNotificationConnection::openConnection()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Opening connection." << endl;
#endif
  if ( !initialized_ )
  {
    kdDebug() << "Notification: ERROR - Notification should be initialized before attempting to connect." << endl;
    return false;
  }

#ifdef KMESS_NETWORK_WINDOW
  KMESS_NET_INIT(this, "NS messenger.hotmail.com");
#endif

  emit statusMessage( i18n( "Connecting..." ), KMessInterface::TYPE_WARNING );

  // Attempt to connect to the main MSN server
  if( ! connectToServer( "messenger.hotmail.com", 1863 ) )
  {
    kdDebug() << "Notification: Couldn't connect to the server." << endl;
    return false;
  }

  // Start the login timer for the name resolution and socket connection
  loginTimer_.start( 10000, true );  // 10 seconds

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Socket started connecting to the server. Started timer." << endl;
#endif

  return true;
}



// Parse a regular command
02048 void MsnNotificationConnection::parseCommand(const QStringList& command)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "MsnNotificationConnection::parseCommand() - Parsing command '" << command[0] << "'." << endl;
#endif

  if(command[0].toInt() != 0)
  {
    // I've decided to always print these messages, should make "error-resolving" easier.
    kdWarning() << "MsnNotificationConnection::parseCommand() - Received error code " << command[0] << " from server." << endl;

    // See if the error was from an open switchboard request.
    // TODO: It would be nicer to implement a generic error handling in the base class for all commands!
    if( ! openRequests_.isEmpty() )
    {
      int transactionId = command[1].toInt();
      QPtrListIterator<ChatInformation> it( openRequests_ );
      while( it.current() != 0 )
      {
        ChatInformation *chatInvite = it.current();
        if( chatInvite->getTransactionId() == transactionId )
        {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
          kdDebug() << "MsnNotificationConnection::parseCommand: Removing chat invitation from list." << endl;
#endif
          openRequests_.remove( chatInvite );
          break;
        }
        ++it;
      }
    }
  }

  // Find out what kind of command we've received. Numbers are errors, the others server commands.
  bool isNumber = false;
  command[0].toInt( &isNumber );

  // It's a command
  if( ! isNumber )
  {
    if ( command[0] == "ADC" )
    {
      gotAdc( command );   // Contact adding
    }
    else if ( command[0] == "ADG" )
    {
      gotAdg( command );  // Group adding
    }
    else if ( command[0] == "BLP" )
    {
      // Unimplemented. This is a privacy setting. (buddy-list-policy?)
    }
    else if ( command[0] == "BPR" )
    {
      // Unimplemented. This contains buddy properties.
    }
    else if ( command[0] == "CHG" )
    {
      gotChg( command );  // Status change
    }
    else if ( command[0] == "CHL" )
    {
      gotChl( command );  // Server-side ping, aka 'challenge'
    }
    else if ( command[0] == "CVR" )
    {
      gotCvr( command );  // Client version info
    }
    else if ( command[0] == "FLN" )
    {
      gotFln( command );  // Contact offline
    }
    else if ( command[0] == "GTC" )
    {
      // Unimplemented. This is a policy setting, tells how to handle chats with contacts not in your list.
    }
    else if ( command[0] == "ILN" )
    {
      gotIln( command );  // Contact initially online
    }
    else if ( command[0] == "LSG" )
    {
      gotLsg( command );  // Group information
    }
    else if ( command[0] == "LST" )
    {
      gotLst( command );  // Contact information
    }
    else if ( command[0] == "NLN" )
    {
      gotNln( command );  // Contact now online
    }
    else if ( command[0] == "OUT" )
    {
      gotOut( command );  // Disconnection from server
    }
    else if ( command[0] == "PRP" )
    {
      gotPrp( command );  // Contact property change
    }
    else if ( command[0] == "QRY" )
    {
      // Ignored. Indicates the challenge was succesful, contains no parameters.
    }
    else if ( command[0] == "REG" )
    {
      gotReg( command );  // Group name change
    }
    else if ( command[0] == "REM" )
    {
      gotRem( command );  // Contact removal from lists/groups
    }
    else if ( command[0] == "RMG" )
    {
      gotRmg( command );  // Group removal
    }
    else if ( command[0] == "RNG" )
    {
      gotRng( command );  // Chat or connection requests
    }
    else if ( command[0] == "SBP" )
    {
      // Unimplemented.
    }
    else if ( command[0] == "SBS" )
    {
      // Ignored. The meaning of this command is unknown, but it's related to the
      // user's mobile credits (MSN Mobile).
      // See: http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes#SBS for details
    }
    else if ( command[0] == "SYN" )
    {
      gotSyn( command );  // Initial synchronization information
    }
    else if ( command[0] == "URL" )
    {
      gotUrl( command );  // Hotmail folder info
    }
    else if ( command[0] == "USR" )
    {
      gotUsr( command );  // User authentication information
    }
    else if ( command[0] == "UUX" )
    {
      // Ignored. It's an echo back from our UUX command. Unlike other commands,
      // the UI is updated for this command already before the server confirms.
    }
    else if ( command[0] == "VER" )
    {
      gotVer( command );  // Version information
    }
    else if ( command[0] == "XFR" )
    {
      gotXfr( command );  // Server transfer data
    }
    else
    {
      // No need to bugger the normal users with strange error messages
      kdWarning() << "MsnNotificationConnection::parseCommand() - Unknown command '" << command[0] << "'! Full string: " << command.join( " " ) << endl;
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
      KMessageBox::error( 0, i18n("KMess received an unknown command from the server: %1").arg(command[0]) );
#endif
    }

    return;
  }


  // All possible commands have been trapped. At this point we can have only
  // received an error code.
  // See: http://www.hypothetic.org/docs/msn/reference/error_list.php for details

  bool     isServerError = false;
  QString  errorMessage  = QString::null;

  // Client side errors.
  // These errors are usually generated by misbehavior on kmess' or the user's side.
  switch( command[0].toInt() )
  {
    case 200:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Invalid command syntax");
      break;
    case 201:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Invalid command parameter");
      break;
    case 205:
      errorMessage = i18n("The email you have tried to add is not a MSN Messenger account");
      break;
    case 206:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Domain name missing");
      break;
    case 207:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Already logged in");
      break;
    case 208:
      errorMessage = i18n("The account name given is invalid");
      break;
    case 209:
      errorMessage = i18n("That account name is invalid, or your passport has not been confirmed yet");
      break;
    case 210:
      errorMessage = i18n("Your contact list is full");
      break;
    case 213:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Invalid SBP parameter");
      break;
    case 215:
      errorMessage = i18n("This contact is already on your list");
      break;
    case 216:
      errorMessage = i18n("This contact is not on your list");
      break;
    case 217:
      errorMessage = i18n("This contact is not online");
      break;
    case 218:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Already in this mode");
      break;
    case 219:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Contact is in the opposite list");
      break;
    case 223:
      errorMessage = i18n("Your contact list has too many groups");
      break;
    case 224:
      errorMessage = i18n("This group can't be changed");
      break;
    case 227:
      errorMessage = i18n("This group is not empty");
      break;
    case 228:
      errorMessage = i18n("The group name is already in use by your Messenger or Hotmail contact list");
      break;
    case 229:
      errorMessage = i18n("The group name is too long");
      break;
    case 230:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Attempted to change group \"0\"");
      break;
    case 231:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Invalid Group");
      break;
    case 280:
      errorMessage = i18n("Switchboard server failed");
      break;
    case 281:
      errorMessage = i18n("Transfer to switchboard server failed");
      break;
    case 282:
      errorMessage = i18n("Direct connection (MSNSLP) error, connection failed");
      break;
    case 300:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Required field missing");
      break;
    case 302:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Not logged in");
      break;
    case 402:
    case 403:
      errorMessage = i18n("Error accessing contact list, try again later");
      break;
    case 502:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Command is disabled");
      break;
    case 511:
      errorMessage = i18n("Your account is banned.");
      closeConnection();
      break;
    case 540:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Challenge response failed");
      break;
    case 713:
      // Too many CAL's
      errorMessage = i18n("You're opening sessions too rapidly");
      break;
    case 714:
      errorMessage = i18n("You have too many sessions opened");
      break;
    case 715:
      // Sent in response to a PRP setting an invalid phone type of three or less characters.
      // Also sent in response to a change of display name (PRP) on an unverified Passport account.
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Unexpected command sequence");
      break;
    case 717:
      errorMessage = i18n("Bad friend name");
      break;
    case 731:
      // Sent in response to a badly formatted CVR
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Bad CVR data");
      break;
    case 800:
      // Happens when renaming too quickly,
      // but also when sending too many MSNP2P packets over the switchboard without waiting for ACKs.
      errorMessage = i18n("The server reports KMess is flooding it with too many data");
      break;
    case 911:
      // User/pass was probably correct, TWN ticket was incorrect
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Authentication ticket was incorrect");
      closeConnection();
      break;
    case 913:
      errorMessage = i18n("You can't start a chat with someone while you are invisible.");
      break;
    case 923:
      errorMessage = i18n("You have a kids passport, you need parental consent to chat online.");
      closeConnection();
      break;
    case 924:
      errorMessage = i18n("Your passport needs to be verified first.");
      closeConnection();
      break;
    case 928:
      errorMessage = i18n("There was an internal error in KMess: %1").arg("Bad USR ticket");
      break;

    default:
      // It wasn't an user error. So, probably, it's a server error.
      isServerError = true;
      break;
  }

  if( !isServerError )
  {
  // Display an User Error message.
  // NOTE: Maybe, user friendliness can be improved; for example, by implementing
  // a notification balloon for non-critical errors.
    KMessageBox::error( 0, errorMessage, i18n( "KMess - MSN error" ) );
    return;
  }

  // Server side errors.
  // These are generated without any intervention from us.
  switch( command[0].toInt() )
  {
    case 500:   // Generic server error
    case 501:   // Database server error
    case 510:   // File operation failed
    case 520:   // Memory allocation failed
    case 603:   // Database connection failed
      errorMessage = i18n("There was an internal server error");
      closeConnection();
      break;
    case 600:
    case 910:
    case 912:
    case 918:
    case 919:
    case 921:
    case 922:
      errorMessage = i18n("The server is too busy");
      closeConnection();
      break;
    case 601:
    case 605:
      errorMessage = i18n("The server is unavailable");
      break;
    case 602:
      errorMessage = i18n("Peer notification server down");
      closeConnection();
      break;
    case 604:
      errorMessage = i18n("The server is going down");
      break;
    case 640:
      errorMessage = i18n("The server is going down soon");
      break;
    case 711:
      errorMessage = i18n("Write is blocking");
      break;
    case 712:
      errorMessage = i18n("Session is overloaded");
      break;
    case 914:
    case 915:
    case 916:
      errorMessage = i18n("The server is not available");
      break;
    case 917:
      errorMessage = i18n("Authentication failed");
      break;
    case 920:
      errorMessage = i18n("Not accepting new principals");
      break;

    default:
      // It was not an user error, not a server error.. what was it then?

      // No need to bugger the users with strange error messages
      kdWarning() << "MsnNotificationConnection::parseCommand() - Unknown error '" << command[0] << "'! Full string: " << command.join( " " ) << endl;
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
      KMessageBox::error( 0, i18n("KMess received an unknown error message from the server: %1").arg(command[0]) );
#endif
      return;
  }

  // Display the Server Error message.
  dotNetMessage( errorMessage );
}



// Parse a message command
02450 void MsnNotificationConnection::parseMessage(const QStringList& /*command*/, const MimeMessage &mainMessage)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "MsnNotification - Got " << mainMessage.getSubValue("Content-Type") << "." << endl;
#endif

  QString contentType = mainMessage.getSubValue("Content-Type");
  MimeMessage message( mainMessage.getBody() );

#ifdef KMESSDEBUG_NOTIFICATION_MESSAGES
  mainMessage.print();
  kdDebug() << "MsnNotification - Message body as mime:" << endl;
  message.print();
#endif

  // Check the message type
  if ( contentType == "text/x-msmsgsprofile" )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "MsnNotification - Load profile information." << endl;
#endif
    uint externalPort = mainMessage.getValue("ClientPort").toUInt();
    externalPort      = ((externalPort & 0x00ff) << 8) | ((externalPort & 0xff00) >> 8); // Byte swapped

    currentAccount_->setAccountInformation( mainMessage.getValue("MSPAuth"),
                                            mainMessage.getValue("preferredEmail"),
                                            mainMessage.getValue("sid"),
                                            mainMessage.getValue("EmailEnabled") == "1",
                                            mainMessage.getValue("ClientIP"),
                                            externalPort,
                                            getLocalIp() );
  }
  else if ( contentType == "text/x-msmsgsinitialmdatanotification"
       ||   contentType == "text/x-msmsgsinitialemailnotification"
       ||   contentType == "text/x-msmsgsoimnotification" )
  {
    // Parse both messages the same way,
    // the x-msmsgsinitialmdatanotification message is an extended version of x-msmsgsoimnotification
    // description of the fields: http://msnpiki.msnfanatic.com/index.php/MSNP13:Offline_IM

    // Parse the URL fields (msmsgsinitialmdatanotification messages).
    if( message.hasField("Post-URL") )
    {
      currentAccount_->setEmailUrl( message.getValue("Post-URL") );
    }

    // Parse the Mail-Data field.
    QString mailDataSrc = message.getValue("Mail-Data");
    if( mailDataSrc == "too-large" )
    {
      // After a certain ammount of messages, the XML is replaced with "too-large".
      // Use SOAP to request the actual mail data.
      if( offlineImService_ == 0 )
      {
        offlineImService_ = createOfflineImService();
      }
      offlineImService_->getMetaData();
    }
    else
    {
      // Mail-Data was received directly in the message.
      QDomDocument mailData;
      if( ! mailData.setContent(message.getValue("Mail-Data")) )
      {
        kdWarning() << "MsnNotificationConnection: Could not parse XML Mail-Data field!" << endl;
        return;
      }

      receivedMailData( mailData.namedItem("MD").toElement() );
    }
  }
  else if ( contentType == "text/x-msmsgsemailnotification" )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: An email was received"
              << " from \""             << message.getValue("From")        << "\""
              << " with the subject \"" << message.getValue("Subject")     << "\""
              << " in the folder \""    << message.getValue("Dest-Folder") << "\"." << endl;
#endif

    // If a message delta is to the active folder, emails were moved into it, so use a negative deletion
    if ( message.getValue("Dest-Folder") == "ACTIVE" )
    {
      // Received in the inbox
      currentAccount_->changeNoEmails( 1 );
    }

    emit newEmail( message.decodeRFC2047String( message.getValue("From").data() ),
                   message.decodeRFC2047String( message.getValue("Subject").data() ),
                   (message.getValue("Dest-Folder") == "ACTIVE"),
                   message.getValue("id"),
                   message.getValue("Message-URL"),
                   message.getValue("Post-URL") );
  }
  else if ( contentType == "text/x-msmsgsactivemailnotification" )
  {
    int messageDelta = message.getValue("Message-Delta").toInt();

    // If a message delta is from the active folder, emails were deleted
    if ( message.getValue("Src-Folder") == "ACTIVE" )
    {
      currentAccount_->changeNoEmails( -1 * messageDelta );
    }
    // If a message delta is to the active folder, emails were moved into it
    else if ( message.getValue("Dest-Folder") == "ACTIVE" )
    {
      currentAccount_->changeNoEmails( messageDelta );
    }

    // When deleting Offline-IM messages, this message contains:
    // Src-Folder: .!!OIM
    // Dest-Folder: .!!trAsH
    // Message-Delta: <number of messages deleted>
  }
  else if( contentType == "application/x-msmsgssystemmessage" )
  {
     // This is a maintenance info message from the server.

    if( message.getValue("Type") == "1" )
    {
      if( message.getValue("Arg1").toInt() < 2 )
      {
        emit statusMessage( i18n( "Warning: Server closes for maintenance in 1 minute!" ), KMessInterface::TYPE_WARNING );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
        KMessageBox::information( 0, i18n( "The MSN Server has reported it will be going down in one minute for maintenance." ) );
#endif
      }
      else
      {
        emit statusMessage( i18n("Warning: Server closes for maintenance in %1 minutes!").arg( message.getValue("Arg1") ), KMessInterface::TYPE_WARNING );
      }
    }
  }
  else
  {
    kdWarning() << "MsnNotification: Content-Type '" << contentType << "' not reconized, message ignored." << endl;
  }
}



// Parse a payload command
02592 void MsnNotificationConnection::parsePayloadMessage(const QStringList &command, const QByteArray &payload)
{
  if( command[0] == "UBX" )
  {
    gotUbx(command, payload);
  }
  else if( command[0] == "GCF" )
  {
    // Ignore this
    // Contains policy information, requesting clients
    // to disable certain features or possibly block
    // certain filenames/contents.
#ifdef KMESSDEBUG_NOTIFICATION_MESSAGES
    kdDebug() << "MsnNotificationConnection::parsePayloadMessage() - Ignoring security configuration file, message dump follows." << endl << QString::fromUtf8( payload.data(), payload.size() ) << endl;
#endif
  }
  else if( command[0] == "NOT" )
  {
    // Ignore "notification" messages coming from MSN Calendar, MSN Alerts, and MSN Mobile.
#ifdef KMESSDEBUG_NOTIFICATION_MESSAGES
    kdDebug() << "MsnNotificationConnection::parsePayloadMessage() - Ignoring text event notification, message dump follows." << endl << QString::fromUtf8( payload.data(), payload.size() ) << endl;
#endif
  }
  else
  {
    kdWarning() << "MsnNotificationConnection::parsePayloadMessage: Unhandled payload command: " << command[0] << "!"
                << " (message dump follows)\n" << QString::fromUtf8(payload.data(), payload.size())  << endl;
  }
}



/**
 * @brief Internal function to add a contact to a list or group.
 *
 * This function is used internally by the high-level contact list methods:
 * - addContactToGroup()
 * - addExistingContact()
 * - addNewContact()
 * - allowContact()
 * - blockContact()
 * - moveContact()
 * - removeContact()
 * - unblockContact()
 *
 * This sends an <code>ADC</code> command to the server.
 * It adds the contact to the given list.
 * See the Contact class for a description of these fields.
 *
 * The groupId parameter only applies to contacts added to the <code>FL</code>.
 * This can be used to add a contact to group within the friends list.
 * When this value is not provided, the contact has no
 * groups set (appearing at the top in the contact list window).
 *
 * The list parameter can be one of the following values:
 * - <code>FL</code>: friends list
 * - <code>AL</code>: allow list
 * - <code>BL</code>: block list
 * - <code>RL</code>: reverse list
 *
 * This method only sends the <code>ADC</code> command.
 * When gotAdc() receives the server response, the ContactList object is updated.
 *
 * @param  handle   E-mail address of the contact.
 * @param  list     The main list.
 * @param  groupId  The group of the contact, only applicable when the contact is added to the <code>FL</code> list.
 * @returns  The ack-number used by the command.
 */
02660 int MsnNotificationConnection::putAdc(QString handle, QString list, QString groupId)
{
#ifdef KMESSTEST
  ASSERT( list == "FL" || list == "AL" || list == "BL" || list == "RL" );
#endif
  QString arguments;

  // Check for old features from previous MSNP9 support
  if( groupId == "0" )
  {
    kdWarning() << "MsnNotificationConnection::putAdc: got implicit group 0!" << endl;
    groupId = QString::null;
  }

  // Determine the command arguments
  if( list == "FL" )
  {
    // Adds to the friends list.
    if( groupId.isEmpty() )
    {
      // Initial add to friends list.
      arguments = " N=" + handle + " F=" + handle;
    }
    else
    {
      // Another add to specific group of the friends list.
      // In this case, the handle may not be used, the GUID should be used instead.
      Contact* contact = contactList_->getContactByHandle(handle);
      if(KMESS_NULL(contact)) return 0;
      arguments = " C=" + contact->getGuid() + " " + groupId;
    }
  }
  else
  {
    // Adds to another list.
    arguments = " N=" + handle;
  }

  // Send the command
  return sendCommand( "ADC", list + arguments + "\r\n" );
}



// Remove a contact from the given list or, if applicable, group
int MsnNotificationConnection::putRem(QString handle, QString list, QString groupId)
{
#ifdef KMESSTEST
  ASSERT( list == "FL" || list == "AL" || list == "BL" || list == "PL" );
#endif
  QString command;
  command = list + " ";

  if( list == "FL" )
  {
    // FL has different syntax, need to specify the GUID instead.
    Contact *contact = contactList_->getContactByHandle(handle);
    if(KMESS_NULL(contact)) return 0;
    command += contact->getGuid();

    // Group is optional, allows to remove contact from a group only.
    // Otherwise, contact is removed from entire FL, but group data is still remembered.
    // When contact is re-added, it appears again in all groups (unless removed from those before).
    if( ! groupId.isEmpty() )
    {
      command += " " + groupId;
    }
  }
  else
  {
    command += handle;
  }

  return sendCommand( "REM", command + "\r\n" );
}



// Send the personal status and current media fields.
void MsnNotificationConnection::putUux()
{
  // Send simple payload message.
  if( lastPsm_.isEmpty() && lastCurrentMedia_.isEmpty() )
  {
    sendPayloadMessage( "UUX", QString::null, QByteArray());
  }
  else
  {
    QString xml = "<Data><PSM>" + lastPsm_ + "</PSM><CurrentMedia>" + lastCurrentMedia_ + "</CurrentMedia></Data>";
    sendPayloadMessage( "UUX", QString::null, xml );
  }
}



// Send the version command
void MsnNotificationConnection::putVer()
{
  // Indicate which protocol version we support.
  // Most of the time it seems that:
  // - even versions are for minor changes.
  // - odd version is are for new revisions.
  sendCommand( "VER", "MSNP12 MSNP11 MSNP10 CVR0\r\n" );
}



// Received the Mail-Data field over SOAP or at the notification connection.
void MsnNotificationConnection::receivedMailData( QDomElement mailData )
{
#ifdef KMESSTEST
  ASSERT( mailData.tagName() == "MD" );
#endif

  QDomNodeList childNodes = mailData.childNodes();
  for( uint i = 0; i < childNodes.count(); i++ )
  {
    QDomElement childNode = childNodes.item(i).toElement();
    QString     childName = childNode.nodeName();

    if( childName == "E" )
    {
      // Found an initial e-mail status notification.
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
      kdDebug() << "MsnNotification - Load initial email information." << endl;
#endif

      QDomElement inboxUnread  = childNode.namedItem("IU").toElement();
      QDomElement othersUnread = childNode.namedItem("OU").toElement();

      if( inboxUnread.isNull() || othersUnread.isNull() )
      {
        kdWarning() << "MsnNotificationConnection: Reveived e-mail notification, but 'IU' and 'OU' elements are missing!" << endl;
      }

#ifdef KMESSTEST
      ASSERT( ! inboxUnread.isNull()  );
      ASSERT( ! othersUnread.isNull() );
#endif

      currentAccount_->setInitialEmailInformation( inboxUnread.text().toInt(),
                                                   othersUnread.text().toInt() );
    }
    else if( childName == "M" )
    {
      // Using this for-loop, multiple 'M' elements are supported.

      // Extract the message ID.
      QString messageId = childNode.namedItem("I").toElement().text();
      if( messageId.isEmpty() )
      {
        kdWarning() << "Unable to parse Mail-Data payload, offline-im message-id not found." << endl;
        return;
      }

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
      kdDebug() << "MsnNotification - Received offline-IM notification '" << messageId << "'" << endl;
#endif

      // Create a SOAP client to request the Offline-IM
      pendingOfflineImMessages_.append(messageId);
    }
    else if( childName == "Q" )
    {
      // Not implemented
    }
    else
    {
      // Warn for unsupported tags.
      kdWarning() << "Unsupported tag '" << childName << "' encountered in Mail-Data payload!" << endl;
    }
  }

  // Start a new client if there are pending messages, and no OfflineImService is active
  if( ! pendingOfflineImMessages_.isEmpty() )
  {
    // Initialize on demand
    if( offlineImService_ == 0 )
    {
      offlineImService_ = createOfflineImService();
    }

    // Request the first offline-im message if the current class was
    // not processing already. (otherwise receivedOfflineIm() picks the next one).
    if( offlineImService_->isIdle() )
    {
      offlineImService_->getMessage( pendingOfflineImMessages_.first(), false );
      // Processing continues at receivedOfflineIm()
    }
  }
}



// An Offline-IM message was downloaded
void MsnNotificationConnection::receivedOfflineIm( const QString &messageId, const QString &from, const QString &to,
                                                   const QDateTime &date, const QString &body,
                                                   const QString &runId, int sequenceNum )
{
#ifdef KMESSDEBUG_NOTIFICATION
  kdDebug() << "MsnNotificationConnection::receivedOfflineIm: Received an offline IM message "
            << "(from=" << from
            << " runid=" << runId
            << " sequenceNum=" << sequenceNum << ")" << endl;
#endif
#ifdef KMESSTEST
  ASSERT( to == currentAccount_->getHandle() );
#else
  Q_UNUSED( to ); // Avoid compiler warning
#endif

  // Find the SOAP client to send the next request
  if( KMESS_NULL(offlineImService_) )
  {
    KMessageBox::error(0, i18n("KMess could not process Offline-IM messages.\n\nDetails: %1")
                          .arg("Lost reference to SOAP client"));
    offlineImMessages_.clear();
    pendingOfflineImMessages_.clear();  // auto deletes, no longer useful 
    return;
  }

  // Store message until all messages are received.
  OfflineImMessage *offlineIm = new OfflineImMessage;
  offlineIm->messageId   = messageId;
  offlineIm->body        = body;
  offlineIm->date        = date;
  offlineIm->from        = from;
  offlineIm->runId       = runId;
  offlineIm->sequenceNum = sequenceNum;

  // Order by date
  uint insertPos = 0;
  QPtrListIterator<OfflineImMessage> it(offlineImMessages_);
  while( it.current() != 0 )
  {
    if( (*it)->sequenceNum > sequenceNum )
    {
#ifdef KMESSDEBUG_NOTIFICATION
    kdDebug() << "MsnNotificationConnection: inserted message '" << date << "', #" << sequenceNum << " "
              << "before '" << (*it)->date << "', #" << (*it)->sequenceNum << "." << endl;
#endif
      break;
    }

    insertPos++;
    ++it;
  }
  offlineImMessages_.insert( insertPos, offlineIm );


  // Remove from pending list
  if( pendingOfflineImMessages_.remove(messageId) == 0 )
  {
    kdWarning() << "MsnNotificationConnection::receivedOfflineIm: Could not remove "
                << "message '" << messageId << "' from the list!" << endl;
  }


  // If there are more messages, request the next one.
  if( ! pendingOfflineImMessages_.isEmpty() )
  {
#ifdef KMESSDEBUG_NOTIFICATION
    kdDebug() << "MsnNotificationConnection: Requesting the next offline-im message." << endl;
#endif

    // Not all messages are received yet, request next
    offlineImService_->getMessage( pendingOfflineImMessages_.first(), false );
    return;
  }


  // All messages received, and sorted,
  // display the messages in the chat windows.

#ifdef KMESSDEBUG_NOTIFICATION
  kdDebug() << "MsnNotificationConnection: All messages received, displaying messages in chat window." << endl;
#endif

  const ContactBase *contact = 0;
  QString name;
  QString picture;
  QStringList messageIds;

  it.toFirst();
  while( it.current() != 0 )
  {
    offlineIm = *it;

    messageIds.append( offlineIm->messageId );

    // Get contact details
    // Re-use previous results if message is from the same contact
    if( contact == 0 || contact->getHandle() != offlineIm->from )
    {
      contact = CurrentAccount::instance()->getContactByHandle(from);
      if( contact != 0 )
      {
        name    = contact->getFriendlyName();
        picture = contact->getContactPicturePath();
      }
      else
      {
        name    = from;
        picture = QString::null;
      }
    }

    // Send out the chat message
    emit offlineMessage( ChatMessage( ChatMessage::TYPE_OFFLINE_INCOMING,
                                      ChatMessage::CONTENT_MESSAGE,
                                      true,
                                      offlineIm->body,
                                      offlineIm->from,
                                      name,
                                      picture,
                                      QFont(),
                                      QString::null,
                                      offlineIm->date ) );
    ++it;
  }

  // Delete all local messages
  offlineImMessages_.clear();  // auto deletes

  // Delete all remove offline-IM messages
  offlineImService_->deleteMessages( messageIds );
  // Keep reference so the connection is re-used for other messages.
}



// Remove a contact from the contact list completely
void MsnNotificationConnection::removeContact(QString handle, bool block)
{
  Contact *contact = contactList_->getContactByHandle(handle);
  if(KMESS_NULL(contact)) return;

  if(block)
  {
    // Block the contact
    putRem( handle, "AL" );
    putAdc( handle, "BL" );
  }
  else
  {
    if(! contact->isReverse())
    {
      // Remove from list, but only if we're not on the list of that contact (reverse-list).
      // Otherwise a "contact has added you" message will appear at kmess startup.
      putRem( handle, "AL" );
      putRem( handle, "BL" );
    }
  }

  if(contact->isFriend())
  {
    // If contact was on the friends list, remove it there. To do this, first remove it from all the groups it is in.
    QStringList groups = contact->getGroupIds();
    for(uint i = 0; i<groups.size(); i++)
    {
      sendCommand("REM", "FL " + contact->getGuid() + " " + groups[i] + "\r\n");
    }
    putRem( handle, "FL" );
  }
}



//remove a contact from a single group
void MsnNotificationConnection::removeContactFromGroup(QString handle, QString groupId)
{
  putRem( handle, "FL", groupId );
}



// Remove the current media advertising in the personal status.
void MsnNotificationConnection::removeCurrentMedia()
{
  lastCurrentMedia_ = QString::null;
  putUux();
}



// Remove a group
void MsnNotificationConnection::removeGroup(QString id)
{
#ifdef KMESSTEST
  bool goodGroupId;
  id.toInt( &goodGroupId );
  ASSERT( goodGroupId );
#endif
  sendCommand( "RMG", id + "\r\n" );
}



// Rename a group
void MsnNotificationConnection::renameGroup(QString id, QString newName)
{
  newName = KURL::encode_string( newName );
  sendCommand( "REG", id + " " + newName + " 0\r\n");
}



// Request a switchboard for a chat with a contact
void MsnNotificationConnection::requestSwitchboard( QString handle, ChatInformation::ConnectionType type )
{
  // Protect again invalid uses.
  if( handle.isEmpty() )
  {
    kdWarning() << "MsnNotificationConnection::requestSwitchboard() - no contact handle set!." << endl;
    return;
  }

  // See if there is a open chat request.
  QPtrListIterator<ChatInformation> it(openRequests_);
  while( it.current() != 0 )
  {
    if( it.current()->getContactHandle() == handle )
    {
#ifdef KMESSDEBUG_NOTIFICATION
      kdDebug() << "MsnNotificationConnection::requestSwitchboard() - A pending switchboard for contact "
                << handle << " is already present." << endl;
#endif
      return;
    }

    ++it;
  }

  // Send the command
  int transactionId = sendCommand( "XFR", "SB\r\n" );

  // Store the pending request information so the return value can be handled.
  openRequests_.append( new ChatInformation( this, handle, transactionId, type ) );
}



// Save contact list properties
void MsnNotificationConnection::saveProperties(KConfig *config)
{
#ifdef KMESSDEBUG_NOTIFICATION
  kdDebug() << "Notification - saving properties" << endl;
#endif

  if( ! currentAccount_->isGuestAccount() )
  {
    contactList_->saveProperties( config );
  }
}



// A SOAP request failed with an error message
void MsnNotificationConnection::soapRequestFailed(HttpSoapConnection *client, const QString &reason)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "MsnNotificationConnection: SOAP request failed: " << reason << endl;
#endif
  if(KMESS_NULL(client)) return;

  // Display an error message
  const QString &clientName = client->className();
  //const QString &lastAction = client->getSoapAction();

  if( clientName == "OfflineImService" )
  {
    // No need to request more, it just failed.
    offlineImMessages_.clear();
    KMessageBox::error(0, i18n("KMess could not process Offline-IM messages.\n\nDetails: %1").arg( reason ));
  }
  else if( clientName == "PassportLoginService" )
  {
    // NOTE: a similar thing also happens in loginIncorrect(),
    // but for fatal SOAP errors.

    // Show a message to the user, before deleting the object!
    KMessageBox::error( 0, i18n("Authentication failed, KMess could not process the passport login.\n\nDetails: %1").arg( reason ) );

    // Close the connection attempt.
    closeConnection();
  }
  else
  {
    KMessageBox::error(0, i18n("KMess could not access the remote webservice.\n\nDetails: %1").arg(reason));
  }
}



// A SOAP request failed with an SOAP fault
void MsnNotificationConnection::soapRequestFailed(HttpSoapConnection *client, const QString &faultCode,
                                                  const QString &faultString, QDomElement /*faultNode*/)
{
  if(KMESS_NULL(client)) return;
  soapRequestFailed(client, faultCode + ": " + faultString);
}



// Unblock the given contact
void MsnNotificationConnection::unblockContact(QString handle)
{
  // Unblock a contact by removing it from the blocked list
  // and adding to the allowed list
  putRem( handle, "BL" );
  putAdc( handle, "AL" );
}

#include "msnnotificationconnection.moc"

Generated by  Doxygen 1.6.0   Back to index