Logo Search packages:      
Sourcecode: kmess version File versions

picturetransferp2p.cpp

/***************************************************************************
                          picturetransferp2p.cpp -  description
                             -------------------
    begin                : Fri Nov 26 2004
    copyright            : (C) 2004 by Diederik van der Boor
    email                : vdboor --at-- codingdomain.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 "picturetransferp2p.h"

#include "../mimemessage.h"
#include "../p2pmessage.h"
#include "../../kmessdebug.h"
#include "../../currentaccount.h"

#include <qfile.h>
#include <qregexp.h>

#include <kdebug.h>
#include <kstandarddirs.h>
#include <kmdcodec.h>


/**
 * Constructor
 *
 * @param  applicationList  The shared sources for the contact.
 */
00038 PictureTransferP2P::PictureTransferP2P(ApplicationList *applicationList)
: P2PApplication(applicationList),
  file_(0)
{
}



/**
 * Constructor
 *
 * @param  applicationList  The shared sources for the contact.
 * @param  msnObject        MSNObject identifying the picture to request.
 */
00052 PictureTransferP2P::PictureTransferP2P(ApplicationList *applicationList, const MsnObject &msnObject)
: P2PApplication(applicationList),
  file_(0),
  msnObject_(msnObject)
{
}



/**
 * Destructor, closes the file if it's open.
 */
00064 PictureTransferP2P::~PictureTransferP2P()
{
  if(file_ != 0)
  {
    delete file_;
  }
}



/**
 * Step one of a contact-started chat: the contact invites the user
 *
 * @param  message  The invitation message
 */
00079 void PictureTransferP2P::contactStarted1_ContactInvitesUser(const MimeMessage &message)
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - contactStarted1_ContactInvitesUser" << endl;
#endif

  // Extract the fields from the message
  unsigned long int appID   = message.getValue("AppID").toUInt();
  QString           context = message.getValue("Context");

  if(! (appID == 1 || appID == 12))  // AppID 12 is used as of MSN Messenger 7.5
  {
    kdDebug() << "PictureTransferP2P: WARNING - Received unexpected AppID: " << appID << "." << endl;

    // Wouldn't know what to do if the AppID is not 1,
    // so send an 500 Internal Error back. The error will be acked and the application terminates.
    sendCancelMessage(CANCEL_ABORT);
    return;
  }

  // TODO: Extract the MSNObject from the context field, to determine which picture/emoticon the contact wants

  // Currently we always send our display picture
  fileName_ = CurrentAccount::instance()->getImagePath();

  // Reject because there is no file to read
  if(fileName_.isEmpty())
  {
    kdWarning() << "PictureTransferP2P: Got an invitation, but we don't have a picture to send." << endl;
    sendCancelMessage(CANCEL_ABORT);
    return;
  }

  // Everything seams OK, accept this message:
  contactStarted2_UserAccepts();
}



/**
 * Step two of a contact-started chat: the user accepts.
 */
00121 void PictureTransferP2P::contactStarted2_UserAccepts()
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - contactStarted2_UserAccepts" << endl;
#endif

#ifdef KMESSTEST
  ASSERT(   file_ == 0          );
  ASSERT( ! fileName_.isEmpty() );
#endif

  // Now we try to open the file
  file_ = new QFile(fileName_);
  bool success = file_->open(IO_ReadOnly);

  if( ! success )
  {
    // Notify the user, even if debug mode is not enabled.
    kdWarning() << "PictureTransferP2P: Unable to open file: " << fileName_ << "!" << endl;

    // Close the file (also causes gotData() to fail)
    delete file_;
    file_ = 0;

#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
    kdDebug() << "PictureTransferP2P::contactStarted2_UserAccepts: Cancelling session" << endl;
#endif

    // Send 500 Internal Error back if we failed
    // the error will be ACK-ed.
    sendCancelMessage(CANCEL_ABORT);
    return;
  }


#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P::contactStarted2_UserAccepts: Sending accept message" << endl;
#endif

  // Create the message
  MimeMessage message;
  message.addField( "SessionID", QString::number( getInvitationSessionID() ) );

  // Send the message
  sendSlpOkMessage(message);
}



/**
 * Step three of a contact-started chat: the contact confirms the accept
 *
 * @param  message  The message of the other contact, not usefull in P2P sessions because it's an ACK.
 */
00175 void PictureTransferP2P::contactStarted3_ContactConfirmsAccept(const MimeMessage &/*message*/)
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - contactStarted3_ContactConfirmsAccept" << endl;
#endif

  // Send the data preparation message back.
  // Once this message is ACKed, we can send our code
  sendDataPreparation();
}



/**
 * Step four in a contact-started chat: the contact confirms the data preparation message.
 */
00191 void PictureTransferP2P::contactStarted4_ContactConfirmsPreparation()
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - contactStarted4_ContactConfirmsPreparation" << endl;
#endif

  // Send the file, the base class handles everything else here
  sendData(file_, P2P_TYPE_PICTURE);
}



/**
 * Return the application's GUID.
 */
00206 QString PictureTransferP2P::getAppId()
{
  return "{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}";
}



/**
 * Return the msn object of the picture we're transferring
 */
00216 const MsnObject & PictureTransferP2P::getMsnObject() const
{
  return msnObject_;
}



/**
 * Determinate the path for an contact picture.
 *
 * @param msnObject  The MSNObject the other contact uses to identify his resource.
 *
 * @returns  A file name string.
 */
00230 QString PictureTransferP2P::getPictureFileName(const MsnObject &msnObject)
{
  // Replace bad characters, in case someone intends to send a bad SHA1.
  // The sha1 string is actually base64 encoded, meaning we could
  // also expect a "/" character in the string.
  QString sha1d = msnObject.getDataHash();
  const QString safeSha1 = sha1d.replace(QRegExp("[^a-zA-Z0-9+=]"), "_");

  // Be friendly for file managers.
  QString extension;
  QString path;
  switch( msnObject.getType() )
  {
    case MsnObject::DISPLAYPIC:
      extension = ".png";
      path = "displaypics";
      break;

    case MsnObject::BACKGROUND:
      extension = ".png";
      path = "backgrounds";
      break;

    case MsnObject::EMOTICON:
      extension = ".png";
      path = "useremoticons";
      break;

    case MsnObject::WINK:
      extension = ".cab";
      path = "winks";
      break;

    case MsnObject::VOICECLIP:
      extension = ".wav";
      path = "voiceclips";
      break;

    default:
      extension = ".dat";
      path = QString::null;
  }

  // Locate filename
  return locateLocal( "data", QString("kmess/") + path + "/" + QString::fromUtf8( safeSha1 ) + extension);
}



/**
 * Called when data is received.
 * Once all data is received, the SLP BYE message will be sent.
 *
 * @param  message  P2P message with the data.
 */
00285 void PictureTransferP2P::gotData(const P2PMessage &message)
{
  if(file_ == 0)
  {
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
    kdWarning() << "PictureTransferP2P: Unable to handle file data: no file open!" << endl;
#endif

    // Cancel if we can't receive it.
    // If this happens we're dealing with a very stubborn client,
    // because we already rejected the data-preparation message.
    sendCancelMessage(CANCEL_FAILED);
    return;
  }

  // Write the data in the file
  Q_LONG status = file_->writeBlock( message.getData(), message.getDataSize() );

  // Check whether the write failed
  if(status == -1)
  {
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
    kdDebug() << "PictureTransferP2P: Failed to write the datablock in the file" << endl;
#endif
    // Close the file
    file_->flush();
    file_->close();
    delete file_;
    file_ = 0;

    sendCancelMessage(CANCEL_FAILED);
    return;
  }

  // is the file complete:
  if( message.isLastFragment() )
  {
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
    kdDebug() << "PictureTransferP2P: Last data part received, closing file" << endl;
#endif

    // Don't send an ACK here, it's already ACK-ed
    // (with a special BYE-request ACK)

    // Clean up
    file_->flush();
    file_->close();
    delete file_;
    file_ = 0;

    // Send an event to the switchboard:
    emit pictureReceived(getContactHandle(), msnObject_);

    // The application should close automatically now,
    // and it sends the BYE message automatically too.
  }
}



// Indicates a private chat is not required, overwritten from the base class.
00346 bool PictureTransferP2P::isPrivateChatRequired() const
{
  // Picture transfer can run in a multi-chat too,
  // The P2P-Dest field of the p2p messages make sure other participants ignore them.

  // For emoticon transfers, a separate private chat is not required,
  // for larger transfers (winks), it's recommended to use a private chat.
  // When the contact is not in our contact list, it may be possible no new chat can be made, so don't enforce this.
  MsnObject::MsnObjectType type = msnObject_.getType();
  bool contactInList = CurrentAccount::instance()->hasContactInList( getContactHandle() );
  return (type != MsnObject::EMOTICON && contactInList);
}



/**
 * Hide standard informative application message (e.g. user invited, cancelled)
 */
00364 void PictureTransferP2P::showMessage(const QString &message)
{
  // Avoid annoying messages in the chat windows about "Contact sent something KMess does not support".
  // This is useful for interactive invitations, like file transfer. Since picture transfer happen
  // in the background, it's not helping to have empty chat windows popping up with error messages.

  kdWarning() << "PictureTransferP2P: suppressed message: " << message << " (contact=" << getContactHandle() << ")" << endl;
}



/**
 * Hide transfer messages, by overwriting the default method implementation.
 */
00378 void PictureTransferP2P::showTransferMessage(const QString &message)
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P::showTransferMessage: suppressed message: " << message << endl;
#endif

  // WLM8 appears to initiate direct connections for picture transfers as well.
  // this method hides the connecting-messages by simply overwriting the base method and no nothing instead.
}



/**
 * Step one of a user-started chat: the user invites the contact
 */
00393 void PictureTransferP2P::userStarted1_UserInvitesContact()
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - userStarted1_UserInvitesContact - requesting display picture" << endl;
#endif

  // Set the filename
  fileName_ = getPictureFileName(msnObject_);

  // Encode the context
  // The \0 char is added on purpose, this fixes the picture transfers with Bot2k3.
  // Sometimes the context string is padded with '=' characters, note this is a feature of BASE64 encoding!
  QString context;
  QByteArray rawObject = msnObject_.objectString().utf8();  // conversion from QCString to QByteArray, adding \0 char.
  context = QString::fromUtf8(KCodecs::base64Encode(rawObject));

  int appId;
  MsnObject::MsnObjectType objectType = msnObject_.getType();
  switch( objectType )
  {
    case MsnObject::DISPLAYPIC:
      appId = 12;
      break;

    case MsnObject::WINK:
      appId = 17;
      break;

    case MsnObject::EMOTICON:
      appId = 11;
      break;

    case MsnObject::BACKGROUND:
      appId = 2;

    case MsnObject::VOICECLIP:
      // Unknown

    default:
      appId = 1;  // the old appId for msn object transfers
  }

  // Send the invitation
  sendSlpSessionInvitation( generateID(), getAppId(), appId, context);
}



/**
 * Step two of a user-started chat: the contact accepts
 *
 * @param  message  Accept message of the other contact
 */
00446 void PictureTransferP2P::userStarted2_ContactAccepts(const MimeMessage & /*message*/)
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - userStarted2_ContactAccepts" << endl;
#endif

  // We don't need to do anything else here.
  // The contact still needs to send the data preparation.
  // Meanwhile, the base class acks the "SLP/200 OK" message automatically
  // with the session ID we gave in the sendSlpSessionInvitation()
}



/**
 * Step three of a user-started chat: the user prepares for the session.
 */
00463 void PictureTransferP2P::userStarted3_UserPrepares()
{
#ifdef KMESSDEBUG_PICTURETRANSFER_P2P
  kdDebug() << "PictureTransferP2P - userStarted3_UserPrepares" << endl;
#endif

#ifdef KMESSTEST
  ASSERT(   file_ == 0          );
  ASSERT( ! fileName_.isEmpty() );
#endif

  if( file_ != 0 )
  {
    // TODO: Quick fix for WML8, this method is called twice because WLM8
    // initializes a Direct connection while it sent the data preparation
    return;
  }


  file_ = new QFile(fileName_);
  bool success = file_->open(IO_WriteOnly);

  if( ! success )
  {
    // Notify the user, even if debug mode is not enabled.
    kdWarning() << "PictureTransferP2P: Unable to open file: " << fileName_ << "!" << endl;

    // Close the file (also causes gotData() to fail)
    delete file_;
    file_ = 0;
    return;
  }

  // Acknowledge the data-preparation message
  // Final step is the gotData() handling..
  sendDataPreparationAck();
}


#include "picturetransferp2p.moc"

Generated by  Doxygen 1.6.0   Back to index