Logo Search packages:      
Sourcecode: kmess version File versions

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 "chatinformation.h"
#include "mimemessage.h"
#include "sslloginhandler.h"
#include "msnchallengehandler.h"
#include "offlineimservice.h"

#ifdef KMESSDEBUG_NOTIFICATION
  #define KMESSDEBUG_NOTIFICATION_GENERAL
  #define KMESSDEBUG_NOTIFICATION_MESSAGES
  #define KMESSDEBUG_NOTIFICATION_INVISIBLE
#endif

// The constructor
MsnNotificationConnection::MsnNotificationConnection()
 : MsnConnection("MsnNotificationConnection"),
   initialized_(false),
   movingAck_(-1),
   noContacts_(0),
   sslLoginHandler_(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.
  openChats_.setAutoDelete(true);
  offlineImMessages_.setAutoDelete(true);

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



// The destructor
MsnNotificationConnection::~MsnNotificationConnection()
{
  delete contactList_;
  delete sslLoginHandler_;

  // Remove all.
  openChats_.clear();

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

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



// Add a contact to the given list or, if applicable, group
// (This function is private)
void MsnNotificationConnection::addContactImpl(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::addContactImpl: 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;
      arguments = " C=" + contact->getGuid() + " " + groupId;
    }
  }
  else
  {
    // Adds to another list.
    arguments = " N=" + handle;
  }

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



// Add an existing contact to the friends list
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)
  addContactImpl   (handle, "FL");
  removeContactImpl(handle, "BL");
  addContactImpl   (handle, "AL");
}



// Register a SOAP client to the class.
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);
}



// Add a new contact to the list
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.
  addContactImpl( handle, "AL" );
  addContactImpl( handle, "FL" );
}



// Adds the given contact to the allow list.
void MsnNotificationConnection::allowContact(QString handle)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Allowing contact " << handle << endl;
#endif

  addContactImpl(handle, "AL");
}



// Add a new group
void MsnNotificationConnection::addGroup(QString name)
{
  addPercents( name );
  sendCommand( "ADG", name  + "\r\n" );
}



// Block the given contact
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
  removeContactImpl(handle, "AL");
  addContactImpl   (handle, "BL");
}



// Change the current media of the user.
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();
}



// The msn object has required, requires a notification message
void MsnNotificationConnection::changedMsnObject()
{
  if(isConnected())
  {
    // Send the same status message, with a new msn object
    changeStatus(currentAccount_->getStatus());
  }
}



// Change the friendly name of the user.
void MsnNotificationConnection::changeFriendlyName( QString newName )
{
  changeProperty("MFN", newName);
}



// Change the friendly name of another contact.
void MsnNotificationConnection::changeFriendlyName( QString handle, QString newName )
{
  // Change msn-friendly-name property.
  changeProperty(handle, "MFN", newName);
}



// Change the personal message of the user.
void MsnNotificationConnection::changePersonalMessage( QString newMessage )
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Changing personal message to " << newMessage << endl;
#endif

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



// Change a user property
void MsnNotificationConnection::changeProperty( QString type, QString value )
{
  // Type can be something like:
  // - MFN (MSN Friendly name)
  // - PHH (Phone home)
  // - PHW (Phone work)
  // - PHM (Phone mobile)
  // - MOB (MSN Mobile authorised for others: Y or N)
  // - MBE (MSN Mobile enabled)
  // - WWE (MSN direct?)
  // - HSB (has blog)

  if( value.isEmpty() )
  {
    sendCommand( "PRP", type + "\r\n" );
  }
  else
  {
    addPercents(value);
    sendCommand( "PRP", type + " " + value + "\r\n" );
  }
}



// Change a contact property
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
  {
    addPercents( value );
    sendCommand( "SBP", contact->getGuid() + " " + type + " " + value + "\r\n" );
  }
}



// Request a change in the user's status
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 expects to it promises 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" );
  }
}



// Check whether login was successful within time limit
void MsnNotificationConnection::checkLogin()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Login not successful within time limit." << endl;
#endif

  // 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);
  closeConnection();
}



// Close the connection with the server
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();

  // Disconnect other possibly active SOAP sessions.
  if( ! soapClients_.isEmpty() )
  {
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: Closing SOAP sessions." << endl;
#endif
    QPtrListIterator<TcpConnectionBase> it(soapClients_);
    while( it.current() != 0 )
    {
      TcpConnectionBase *soapClient = it.current();
      soapClient->closeConnection();
      soapClient->deleteLater();
      ++it;
    }
    soapClients_.clear();
  }
  offlineImService_ = 0;

  // 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();
  openChats_.clear();
  if( currentAccount_ != 0 )
  {
    currentAccount_->setNoEmails( 0 );
  }
}



// The socket connected, so send the version command.
void MsnNotificationConnection::connectionSuccess()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Switchboard: Connected to server, sending VER." << endl;
#endif

  putVer();
}

void MsnNotificationConnection::copyContactToGroup(Contact* contact, QString groupId){
  if(KMESS_NULL(contact)) return;
  if(groupId == "0" || groupId == NULL) return;
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: copying '" << contact->getHandle() << " to '" << groupId << "'." << endl;
#endif
  sendCommand("ADC", "FL C=" + contact->getGuid() + " " + groupId + "\r\n");
  // TODO: rewrite to addContactToGroup() / removeContactFromGroup(). follow coding standards
}



// Create the SSL login handler
SslLoginHandler* MsnNotificationConnection::createLoginHandler()
{
  // Create the login handler.
  SslLoginHandler *sslLoginHandler = new SslLoginHandler();

  // Connect all login notifications 
  connect( sslLoginHandler, SIGNAL(         loginFailed()        ),
           this,            SLOT  (      sslLoginFailed()        ) );
  connect( sslLoginHandler, SIGNAL(    loginUnavailable()        ),
           this,            SLOT  ( sslLoginUnavailable()        ) );
  connect( sslLoginHandler, SIGNAL(      loginIncorrect()        ),
           this,            SLOT  (   sslLoginIncorrect()        ) );
  connect( sslLoginHandler, SIGNAL(      loginSucceeded(QString) ),
           this,            SLOT  (   sslLoginSucceeded(QString) ) );

  return sslLoginHandler;
}



// Create the Offline-IM service
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;
}



// Show a .net status message.
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();
}


// Return the contact list
const ContactList* MsnNotificationConnection::getContactList() const
{
  return contactList_;
}


// Go online
void MsnNotificationConnection::goOnline()
{
//  kdDebug() << "Local IP is " << getLocalIp() << endl;
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Lists received.  Go online." << endl;
#endif
  int     ack;

  if(KMESS_NULL(currentAccount_)) return;

#ifdef KMESSDEBUG_NOTIFICATION_INVISIBLE
  kdDebug() << "Notification: Prepare to change status to online or invisible." << endl;
  kdDebug() << "Notification: 'Start invisible' is " << currentAccount_->getStartInvisible() << "." << endl;
#endif

  // Change status to either online or invisible
  if ( currentAccount_->getStartInvisible() )
  {
#ifdef KMESSDEBUG_NOTIFICATION_INVISIBLE
    kdDebug() << "Notification: Change status to invisible." << endl;
#endif
    // Change status to invisible
    changeStatus( "HDN" );
  }
  else
  {
#ifdef KMESSDEBUG_NOTIFICATION_INVISIBLE
    kdDebug() << "Notification: Change status to online." << endl;
#endif
    // Change status to online
    changeStatus( "NLN" );
  }

  // 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();
}



// Received the addition of a contact to a list or list and group
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);
    }
    else if( command[i].startsWith("F=") )
    {
      friendlyName = command[i].mid(2);
      removePercents(friendlyName);
    }
    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);
      }

      // TODO: make the moving operation smoother, something like: contact->prepareMove(movingDestination_);

      // If this contact was added from moveContact(), remove from source group.
      if(command[1].toInt() == movingAck_ )
      {
        if( movingSourceGroup_.isEmpty() )
        {
          kdWarning() << "Received ACK for moving operation, but source group is unknown!" << endl;
          return;
        }

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
        kdDebug() << "Notification: Detected moving-ack, removing contact from old group." << endl;
#endif
        removeContactImpl(contact->getHandle(), "FL", movingSourceGroup_);
        movingSourceGroup_ = QString::null;
        movingAck_ = -1;
      }
    }
  }


  // 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 = command[2];
  QString id   = command[3];
  removePercents( name );

#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] );
}



// 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 friendlyName = command[4];
  removePercents( friendlyName );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: " << friendlyName << " (" << command[3] << ") is initially " << command[2] << "." << endl;
#endif

  uint    capabilities = command[5].toUInt();
  QString msnObject    = command[6];
  if ( !msnObject.isEmpty() )
  {
    removePercents( msnObject );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: This person has an msn6 picture: " << msnObject << endl;
#endif
  }

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

}



// Received group information
void MsnNotificationConnection::gotLsg(const QStringList& command)
{
  if(KMESS_NULL(contactList_)) return;

  QString name    = command[1];
  QString groupId = command[2];
  removePercents( name );

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

  contactList_->addGroup( groupId, name );
}



// Received contact information
void MsnNotificationConnection::gotLst(const QStringList& command)
{
  if(KMESS_NULL(contactList_)) return;

  //emit statusMessage( i18n("Getting lists"), 2 );
#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);
    }
    else if( command[i].startsWith("F=") )
    {
      friendlyName = command[i].mid(2);
      removePercents(friendlyName);
    }
    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;
  }

  removePercents( 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

    addContactImpl( handle, "RL");   // possibly only allowed since contact is on Pending list.
    removeContactImpl( 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();
  }
}



// Received notice that a contact has changed status
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];
  QString friendlyName = command[3];
  uint    capabilities = command[4].toUInt();
  QString msnObject = command[5];
  removePercents( friendlyName );
  removePercents( msnObject );

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

#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: " << friendlyName << " (" << handle << ") is now " << status << "." << endl;
  kdDebug() << "Notification: Rename the contact in the list?" << 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: The current friendly name of '" << contact->getTrueFriendlyName() << "'"
              << " doesn't match '" << 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 = command[2];
  removePercents( propertyValue );

  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    = command[3];
  removePercents( name );

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



// Received confirmation of a conact's removal from list or list and group
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];
    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
  }


#ifdef KMESSTEST
  ASSERT( movingAck_ == -1 );  // only fails if ack was not detected.
  movingAck_ = -1;
#endif
}



// 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 chat 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];
  QString friendlyName  = command[6];
  removePercents( friendlyName );

  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: " << friendlyName << " (" << handle << ") requesting a chat at: "
            << server << ":" << port << " id=" << chatId << " auth=" << auth << "." << endl;
#endif

  // Sent chat information object
  ChatInformation chatInfo(this, handle, server, port, auth, chatId );
  emit startChat(chatInfo);
}



// Received synchronization information
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]);
  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;
    parameters = command[4];
    removePercents( parameters );
    // Change statusbar
    emit statusMessage( i18n("Authenticating"), 2 );

    // Create the sslLoginHandler
    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 SSL based login
    sslLoginHandler_ = createLoginHandler();
    sslLoginHandler_->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"), 2 );

    // 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..."), 2 );
  }
}



// Received version information
void MsnNotificationConnection::gotVer(const QStringList& command)
{
#ifdef KMESSTEST
  ASSERT( command[2] == "MSNP12" );
#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 parrt
#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"), 2 );

#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( openChats_.count() > 0 );
    uint noChatsBefore = openChats_.count();
#endif

    // This is a switchboard server.  This message should be in response to
    //  a user-requested chat.  Look for the pending chat information.
    QString authorization = command[5];
    ChatInformation *chatInfo = openChats_.first();
    if( chatInfo == 0 )
    {
      kdDebug() << "MsnNotificationConnection - Got switchboard, but there are no open chats." << endl;
      return;
    }

    // Debug the transaction ID. Let the chat continue, if
    // there are errors it can be traced back in the console.
    if( chatInfo->getTransactionId() != transactionId )
    {
      kdWarning() << "MsnNotificationConnection: received XFR response, but transaction-ID differs!" << endl;
    }

    // Update the chat information
    chatInfo->setServerInformation( server, port, authorization );
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: emitting startChat() signal." << endl;
#endif

    // Signal the chat
    emit startChat( *chatInfo );

    // Remove the first chat from the open chats
    // Automatically removes the ChatInformation object.
    openChats_.remove( chatInfo );
#ifdef KMESSTEST
    ASSERT( openChats_.count() == ( noChatsBefore - 1 ) );
#endif
  }
}



// Initialize the object
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
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
         );
}



// 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 ADD.
    addContactImpl( handle, "FL", toGroupId );
  }
  else
  {
    // Add to new group, remove from old.
    // Only execute the "REM" command when the "ADC" was successful.
    movingSourceGroup_ = fromGroupId;
    movingAck_         = sendCommand("ADC", "FL C=" + contact->getGuid() + " " + toGroupId + "\r\n");

#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

  bool connected;

  emit statusMessage( i18n( "Connecting..." ), 2 );

  // Attempt to connect to the main MSN server
  connected = connectToServer( "messenger.hotmail.com", 1863 );
  if ( connected )
  {
    // Start the login timer
    loginTimer_.start( 60000, true );  // 60 seconds
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
    kdDebug() << "Notification: Socket started connecting OK.  Started. the timer." << endl;
#endif
  }
  else
  {
    kdDebug() << "Notification: Couldn't connect to the server." << endl;
  }
  return connected;
}



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

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

    // See if the error was from an open chat invitation
    // TODO: It would be nicer to implement a generic error handling in the base class for all commands!
    if( ! openChats_.isEmpty() )
    {
      int transactionId = command[1].toInt();
      QPtrListIterator<ChatInformation> it( openChats_ );
      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
          openChats_.remove( chatInvite );
          break;
        }
        ++it;
      }
    }
  }


  if ( command[0] == "ADC" )
  {
    gotAdc( command );
  }
  else if ( command[0] == "ADG" )
  {
    gotAdg( command );
  }
  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 );
  }
  else if ( command[0] == "CHL" )
  {
    gotChl( command );
  }
  else if ( command[0] == "CVR" )
  {
    gotCvr( command );
  }
  else if ( command[0] == "FLN" )
  {
    gotFln( command );
  }
  else if ( command[0] == "GTC" )
  {
    // Unimplemented
    // This is a polycy setting, how to handle chats with contacts not in your list.
  }
  else if ( command[0] == "ILN" )
  {
    gotIln( command );
  }
  else if ( command[0] == "LSG" )
  {
    gotLsg( command );
  }
  else if ( command[0] == "LST" )
  {
    gotLst( command );
  }
  else if ( command[0] == "NLN" )
  {
    gotNln( command );
  }
  else if ( command[0] == "OUT" )
  {
    gotOut( command );
  }
  else if ( command[0] == "PRP" )
  {
    gotPrp( command );
  }
  else if ( command[0] == "QRY" )
  {
    // Ignore this
    // Indicates the challenge was succesful, contains no parameters
  }
  else if ( command[0] == "REG" )
  {
    gotReg( command );
  }
  else if ( command[0] == "REM" )
  {
    gotRem( command );
  }
  else if ( command[0] == "RMG" )
  {
    gotRmg( command );
  }
  else if ( command[0] == "RNG" )
  {
    gotRng( command );
  }
  else if ( command[0] == "SBP" )
  {
    // Unimplemented.
  }
  else if ( command[0] == "SBS" )
  {
    // Ingore this
    // This meaning of this command is unknown, but it is
    // related to the user's mobile credits (MSN Mobile)
    // see: http://msnpiki.msnfanatic.com/index.php/MSNP11:Changes#SBS
  }
  else if ( command[0] == "SYN" )
  {
    gotSyn( command );
  }
  else if ( command[0] == "URL" )
  {
    gotUrl( command );
  }
  else if ( command[0] == "USR" )
  {
    gotUsr( command );
  }
  else if ( command[0] == "UUX" )
  {
    // Ignore this
    // Is 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 );
  }
  else if ( command[0] == "XFR" )
  {
    gotXfr( command );
  }
  // Check error codes
  // Check http://www.hypothetic.org/docs/msn/reference/error_list.php for details
  else if ( command[0] == "200" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Invalid command syntax") );
  }
  else if ( command[0] == "201" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Invalid command parameter") );
  }
  else if ( command[0] == "205" )
  {
    KMessageBox::error( 0, i18n("The account name given does not exist") );
  }
  else if ( command[0] == "206" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Domain name missing") );
  }
  else if ( command[0] == "207" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Already logged in") );
  }
  else if ( command[0] == "208" )
  {
    KMessageBox::error( 0, i18n("The account name given is invalid") );
  }
  else if ( command[0] == "209" )
  {
    KMessageBox::error( 0, i18n("That account name is invalid, or your passport has not been confirmed yet") );
  }
  else if ( command[0] == "210" )
  {
    KMessageBox::error( 0, i18n("Your contact list is full") );
  }
  else if ( command[0] == "213" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Invalid SBP parameter") );
  }
  else if ( command[0] == "215" )
  {
    KMessageBox::error( 0, i18n("This contact is already on your list") );
  }
  else if ( command[0] == "216" )
  {
    KMessageBox::error( 0, i18n("This contact is not on your list") );
  }
  else if ( command[0] == "217" )
  {
    KMessageBox::error( 0, i18n("This contact is not online") );
  }
  else if ( command[0] == "218" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Already in this mode") );
  }
  else if ( command[0] == "219" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Contact is in the opposite list") );
  }
  else if ( command[0] == "223" )
  {
    KMessageBox::error( 0, i18n("Your contact list has too many groups") );
  }
  else if ( command[0] == "224" )
  {
    KMessageBox::error( 0, i18n("This group can't be changed") );
  }
  else if ( command[0] == "227" )
  {
    KMessageBox::error( 0, i18n("This group is not empty") );
  }
  else if ( command[0] == "228" )
  {
    KMessageBox::error( 0, i18n("The group name is already in use by your Messenger or Hotmail contact list") );
  }
  else if ( command[0] == "229" )
  {
    KMessageBox::error( 0, i18n("The group name is too long") );
  }
  else if ( command[0] == "230" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Attempted to change group \"0\"") );
  }
  else if ( command[0] == "231" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Invalid Group") );
  }
  else if ( command[0] == "280" )
  {
    KMessageBox::error( 0, i18n("Switchboard server failed") );
  }
  else if ( command[0] == "281" )
  {
    KMessageBox::error( 0, i18n("Transfer to switchboard server failed") );
  }
  else if ( command[0] == "300" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Required field missing") );
  }
  else if ( command[0] == "302" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Not logged in") );
  }
  else if ( command[0] == "500" )
  {
    dotNetMessage( i18n("There was an internal server error") );
    closeConnection();
  }
  else if ( command[0] == "501" )
  {
    // Database server error
    dotNetMessage( i18n("There was an internal server error") );
    closeConnection();
  }
  else if ( command[0] == "502" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Command is disabled") );
  }
  else if ( command[0] == "510" )
  {
    // File operation failed
    dotNetMessage( i18n("There was an internal server error") );
    closeConnection();
  }
  else if ( command[0] == "520" )
  {
    // Memory allocation failed
    dotNetMessage( i18n("There was an internal server error") );
    closeConnection();
  }
  else if ( command[0] == "540" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Challenge response failed") );
  }
  else if ( command[0] == "600" )
  {
    dotNetMessage( i18n("The server is busy") );
    closeConnection();
  }
  else if ( command[0] == "601" )
  {
    dotNetMessage( i18n("The server is unavailable") );
  }
  else if ( command[0] == "602" )
  {
    dotNetMessage( i18n("Peer notification server down") );
    closeConnection();
  }
  else if ( command[0] == "603" )
  {
    // Database connection failed
    dotNetMessage( i18n("There was an internal server error") );
    closeConnection();
  }
  else if ( command[0] == "604" )
  {
    dotNetMessage( i18n("The server is going down") );
  }
  else if ( command[0] == "605" )
  {
    dotNetMessage( i18n("The server is unavailable") );
  }
  else if ( command[0] == "640" )
  {
    dotNetMessage( i18n("The server is going down soon") );
  }
  else if ( command[0] == "711" )
  {
    dotNetMessage( i18n("Write is blocking") );
  }
  else if ( command[0] == "712" )
  {
    dotNetMessage( i18n("Session is overloaded") );
  }
  else if ( command[0] == "713" )
  {
    // Too many CAL's
    KMessageBox::error( 0, i18n("You're opening sessions too rapidly") );
  }
  else if ( command[0] == "714" )
  {
    KMessageBox::error( 0, i18n("You have too many sessions opened") );
  }
  else if ( command[0] == "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.
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Unexpected command sequence") );
  }
  else if ( command[0] == "717" )
  {
    KMessageBox::error( 0, i18n("Bad friend name") );
  }
  else if ( command[0] == "731" )
  {
    // Sent in response to a badly formatted CVR
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Bad CVR data") );
  }
  else if ( command[0] == "800" )
  {
    // Happens when renaming too quickly,
    // but also when sending too many MSNP2P packets over the switchboard without waiting for ACKs.
    KMessageBox::error( 0, i18n("The server reports KMess is flooding it with too many data") );
  }
  else if ( command[0] == "910" )
  {
    dotNetMessage( i18n("Server too busy") );
  }
  else if ( command[0] == "911" )
  {
    // User/pass was probably correct, TWN ticket was incorrect
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Authentication ticket was incorrect") );
    closeConnection();
  }
  else if ( command[0] == "912" )
  {
    dotNetMessage( i18n("Server too busy") );
  }
  else if ( command[0] == "913" )
  {
    KMessageBox::error( 0, i18n("You can't start a chat with someone while you are invisible.") );
  }
  else if ( command[0] == "914" )
  {
    dotNetMessage( i18n("Server unavailable") );
  }
  else if ( command[0] == "915" )
  {
    dotNetMessage( i18n("Server unavailable") );
  }
  else if ( command[0] == "916" )
  {
    dotNetMessage( i18n("Server unavailable") );
  }
  else if ( command[0] == "917" )
  {
    dotNetMessage( i18n("Authentication failed") );
  }
  else if ( command[0] == "918" )
  {
    dotNetMessage( i18n("Server too busy") );
  }
  else if ( command[0] == "919" )
  {
    dotNetMessage( i18n("Server too busy") );
  }
  else if ( command[0] == "920" )
  {
    dotNetMessage( i18n("Not accepting new principals") );
  }
  else if ( command[0] == "921" )
  {
    dotNetMessage( i18n("Server too busy") );
  }
  else if ( command[0] == "922" )
  {
    dotNetMessage( i18n("Server too busy") );
  }
  else if ( command[0] == "923" )
  {
    KMessageBox::error( 0, i18n("You have a kids passport, you need parental consent to chat online.") );
    closeConnection();
  }
  else if ( command[0] == "924" )
  {
    KMessageBox::error( 0, i18n("Your passport needs to be verified first.") );
    closeConnection();
  }
  else if ( command[0] == "928" )
  {
    KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("Bad USR ticket") );
  }
  else
  {
    if(command[0].toInt() != 0)
    {
      KMessageBox::error( 0, i18n("KMess received an unknown error message from the server: %1").arg(command[0]) );
    }
    kdWarning() << "Notification: Command " <<  command[0] << " not supported." << endl;
  }
}



// Parse a message command
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("kv"),
                                            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
  {
    kdWarning() << "MsnNotification: Content-Type '" << contentType << "' not reconized, message ignored." << endl;
  }
}



// Parse a payload command
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.
  }
  else
  {
    kdWarning() << "MsnNotificationConnection::parsePayloadMessage: Unhandled payload command: " << command[0] << "!"
                << " (message dump follows)\n" << QString::fromUtf8(payload.data(), payload.size())  << endl;
  }
}



// Show a message that the proxy failed
void MsnNotificationConnection::proxyFailed()
{
  KMessageBox::error( 0, i18n("There was a problem connecting to the proxy.") );
  closeConnection();
}



// 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 prototol version we support.
  // Most of the time it seams an even version is a
  // minor change, a odd version a new revision.
  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() );
#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 chatMessage( ChatMessage( ChatMessage::TYPE_OFFLINE_INCOMING, 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 given list or, if applicable, group
void MsnNotificationConnection::removeContactImpl(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;
    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;
  }

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



// 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
    removeContactImpl(handle, "AL");
    addContactImpl(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.
      removeContactImpl(handle, "AL");
      removeContactImpl(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");
    }
    removeContactImpl(handle, "FL");
  }
}



//remove a contact from a single group
void MsnNotificationConnection::removeContactFromGroup(Contact* contact, QString groupId)
{

  if(KMESS_NULL(contact)) return;

  sendCommand("REM", "FL " + contact->getGuid() + " " + groupId + "\r\n");
}



// 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)
{
  addPercents( newName );
  sendCommand( "REG", id + " " + newName + " 0\r\n");
}



// Request a chat with a contact
void MsnNotificationConnection::requestChat(QString handle)
{
  QPtrListIterator<ChatInformation> it(openChats_);
  while( it.current() != 0 )
  {
    if( it.current()->getContactHandle() == handle )
    {
#ifdef KMESSDEBUG_NOTIFICATION
      kdDebug() << "MsnNotificationConnection::requestChat: Already have a chatconversation with '" << handle << "' pending." << endl;
#endif
      return;
    }
  }

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

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



// 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!" << 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
  {
    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*/)
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "MsnNotificationConnection: SOAP request failed!" << endl;
#endif
  if(KMESS_NULL(client)) return;
  soapRequestFailed(client, faultCode + ": " + faultString);
}



// The SSL login was aborted because an internal error occured
void MsnNotificationConnection::sslLoginFailed()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Login SSL login failed!" << endl;
#endif

  // Delete the login handler
  sslLoginHandler_->deleteLater();
  sslLoginHandler_ = 0;

  // Show a message to the user
  KMessageBox::error( 0, i18n("There was an internal error in KMess: %1").arg("SSL Login failed") );
  closeConnection();
}



// The SSL login failed, username/password was incorrect
void MsnNotificationConnection::sslLoginIncorrect()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Login SSL login was incorrect." << endl;
#endif

  // Delete the login handler
  sslLoginHandler_->deleteLater();
  sslLoginHandler_ = 0;

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



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

  // Delete the login handler
  sslLoginHandler_->deleteLater();
  sslLoginHandler_ = 0;
  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;
  }
}


// The SSL login was aborted because the service is temporary unavailable
void MsnNotificationConnection::sslLoginUnavailable()
{
#ifdef KMESSDEBUG_NOTIFICATION_GENERAL
  kdDebug() << "Notification: Login SSL login failed!" << endl;
#endif

  // Delete the login handler
  delete sslLoginHandler_;
  sslLoginHandler_ = 0;

  // Show a message to the user
  KMessageBox::error( 0, i18n("Authentication failed, the MSN Messenger service is temporary unavailable") );
  closeConnection();
}


// 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
  removeContactImpl(handle, "BL");
  addContactImpl   (handle, "AL");
}

#include "msnnotificationconnection.moc"

Generated by  Doxygen 1.6.0   Back to index