/***************************************************************************
                          filetransferp2p.cpp -  description
                             -------------------
    begin                : Sun 12 19 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 "filetransferp2p.h"

#include "../../utils/thumbnailprovider.h"
#include "../../utils/kmessshared.h"
#include "../../utils/kmessconfig.h"
#include "../../contact/msnobject.h"
#include "../../dialogs/transferwindow.h"
#include "../../kmessdebug.h"
#include "../mimemessage.h"
#include "../p2pmessage.h"

#include <QFile>
#include <QFileInfo>
#include <QRegExp>
#include <QTextDocument>  // for Qt::escape()

#include <KFileDialog>
#include <KIconLoader>
#include <KLocale>
#include <KMessageBox>
#include <KUrl>



/**
 * The constructor for the FileTransferP2P class, without filename (sufficient for incoming sessions)
 *
 * @param  applicationList  The shared sources for the contact.
 */
FileTransferP2P::FileTransferP2P(ApplicationList *applicationList)
  : P2PApplication(applicationList),
  file_(0),
  fileSize_(0),
  thumbnailProvider_(0),
  transferID_(-1)
{
  setApplicationType( ChatMessage::TYPE_APPLICATION_FILE );
}



/**
 * The constructor for the FileTransferP2P, with filename to start a session
 *
 * @param  applicationList  The shared sources for the contact.
 * @param  filename         Filename of the file to send.
 */
FileTransferP2P::FileTransferP2P(ApplicationList *applicationList, const QString &filename)
  : P2PApplication(applicationList),
  file_(0),
  filePath_(filename),
  fileSize_(0),
  thumbnailProvider_(0),
  transferID_(-1)
{
  setApplicationType( ChatMessage::TYPE_APPLICATION_FILE );
}



/**
 * Destructor, closes the file if it's open.
 */
FileTransferP2P::~FileTransferP2P()
{
  // Displays the cancel state in the transfer window
  TransferWindow::getInstance()->failTransfer( transferID_ );

  // Close the file
  // Delete thumbnail provider.
  delete file_;
  delete thumbnailProvider_;
}


/**
 * The contact cancelled the session
 */
void FileTransferP2P::contactAborted(const QString &message)
{
  // Displays the cancel state in the transfer window
  TransferWindow::getInstance()->failTransfer( transferID_, i18n("Cancelled") );

  // Continue with standard call.
  P2PApplication::contactAborted( message );
}



/**
 * Step one of a contact-started chat: the contact invites the user
 *
 * @param  message  The invitation message
 */
void FileTransferP2P::contactStarted1_ContactInvitesUser(const MimeMessage &message)
{
#ifdef KMESSDEBUG_FILETRANSFER_P2P
  kDebug();
#endif

  // First stage of the file transfer: initiate the session

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

  if(appID != 2)
  {
    kWarning() << "Received unexpected AppID: " << appID << ".";

    // Wouldn't know what to do if the AppID is not 2, so send an 500 Internal Error back.
    showEventMessage( i18n("The file transfer invitation was cancelled. Bad data was received."), ChatMessage::CONTENT_APP_CANCELED, true );
    sendCancelMessage( CANCEL_ABORT );
    return;
  }


  // The context field contains file transfer data.
  QByteArray decodedContext = QByteArray::fromBase64( context.toLatin1() );

  // Just to be on the safe side, check the buffer size before we start copying.
  if( context.length() <= 24 )
  {
    kWarning() << "File transfer context field has bad formatting, "
                  "ignoring invite (context=" << context << ", contact=" << getContactHandle() << ").";
    showEventMessage( i18n("The file transfer invitation was cancelled. Bad data was received."), ChatMessage::CONTENT_APP_CANCELED, true );
    sendCancelMessage( CANCEL_ABORT );
    return;
  }


  // Extract the simple fields from the context string.
  unsigned int  fieldsLength = P2PMessage::extractBytes(decodedContext,  0);  // field 1: length of fields 1-6
//unsigned int  unknown      = P2PMessage::extractBytes(decodedContext,  4);  // field 2: unknown. (usually 2)
                fileSize_    = P2PMessage::extractBytes(decodedContext,  8);  // field 3: file size
  unsigned int  noPreview    = P2PMessage::extractBytes(decodedContext, 16);  // field 4: 1 if NO preview available.

  // Again, I don't want KMess to crash or being exploited.
  if( fieldsLength > (uint) decodedContext.size() || fieldsLength < 24 )
  {
    kWarning() << "File transfer context field has bad formatting, rejecting invite"
                  " (length=" << fieldsLength << ", contact=" << getContactHandle() << ")";
    showEventMessage( i18n("The file transfer invitation was cancelled. Bad data was received."), ChatMessage::CONTENT_APP_CANCELED, true );
    sendCancelMessage( CANCEL_ABORT );
    return;
  }


  // field 6: most likely a splitter mark between the file name and preview fields. It's always 0xFFFFFFFF

/*
  // TODO: fix detection of the splitter message.
  unsigned in splitterPos = qMin( 570, fieldsLength - 4 );
  unsigned int splitter = P2PMessage::extractBytes( decodedContext, splitterPos );
  if( splitter != 0xFFFFFFFF )
  {
    kWarning() << "File transfer context field has bad formatting "
                   "(splitter not found at byte " << splitterPos << ","
                   " found 0x" << QString::number( splitter, 16 ) <<
                   " contact=" << getContactHandle() << ").";

//    sendCancelMessage( CANCEL_ABORT );
//    return;
  }
*/

  // field 5: the file name
//unsigned int filenameLength = (fieldsLength - 40); // 24 bytes = 4 dwords, 1 qword (fields 1-4 and 6.)

  void    *pointer   = decodedContext.data() + 20;
  suggestedFileName_ = QString::fromUtf16( (unsigned short*) pointer );


  // After field 6: the preview data.
  QByteArray rawPreviewData;
  QString    previewData;
  if( noPreview )
  {
    // Generate a default preview icon
    // The "preview_" value is displayed later in the transfer window too.
    KIconLoader *loader = KIconLoader::global();
    QString iconTitle   ( KMimeType::iconNameForUrl( KUrl( suggestedFileName_ ) ) );
    preview_            = QImage( loader->iconPath( iconTitle, 48, false ) );

    // Save as PNG for encoding
    QBuffer buffer( &rawPreviewData );
    buffer.open( QIODevice::WriteOnly );
    preview_.save( &buffer, "PNG" );

    // Encode to Base64 for inline image.
    previewData = rawPreviewData.toBase64();

#ifdef KMESSDEBUG_FILETRANSFER_P2P
    kDebug() << "Using icon '" << iconTitle << "' as preview.";
#endif
  }
  else
  {
    // Determine position of the preview data.
    const char *previewStart  = decodedContext.data() + fieldsLength;
    const int   previewLength = decodedContext.size() - fieldsLength;
    QByteArray rawPreviewData = QByteArray::fromRawData( previewStart, previewLength );  // wrap, does not copy

    // Read preview data
    previewData = rawPreviewData.toBase64();
    preview_    = QImage::fromData( rawPreviewData );

#ifdef KMESSDEBUG_FILETRANSFER_P2P
    kDebug() << "Encoded data size: " << previewData.length();
#endif
  }


  // Everything seams OK, allow the user to accept the file.
  // Generate accept message
  QString html( i18n( "The contact wants to send you a file: &quot;%1&quot; (%2).",
                      "<span class=\"filename invitationFilename\">" + suggestedFileName_ + "</span>",
                      toReadableBytes( fileSize_ ) ) );

  // Add preview to the top
  if( ! noPreview )
  {
    html.prepend( "<img src=\"data:image/png;base64," + previewData   + "\""
                  " width=\""  + QString::number( preview_.width() )  + "\""
                  " height=\"" + QString::number( preview_.height() ) + "\""
                  " alt=\""    + Qt::escape( suggestedFileName_ ) + "\" />"
                  "<br />" );
  }


  // Display the accept message.
#ifdef KMESSDEBUG_FILETRANSFER_P2P
  kDebug() << "File to transfer is " << suggestedFileName_ << ", "
            << "waiting for user to accept.";
#endif

  offerAcceptOrReject( html );
}



/**
 * Step two of a contact-started chat: the user accepts.
 */
void FileTransferP2P::contactStarted2_UserAccepts()
{
#ifdef KMESSDEBUG_FILETRANSFER_P2P
  kDebug() << "Starting.";
#endif

#ifdef KMESSTEST
  KMESS_ASSERT( file_ == 0          );
  KMESS_ASSERT( filePath_.isEmpty() );
#endif

  QFileInfo fileInfo;

  // Open the options and get the default downloads directory
  KConfigGroup group = KMessConfig::instance()->getGlobalConfig( "General" );
  QString startFolder( group.readEntry( "receivedFilesDir", QDir::homePath() ) + "/" );

  if( group.readEntry( "useReceivedFilesDir", false ) && ! startFolder.isEmpty() )
  {
    QString suffix;
    int nextNumber = 0;
    fileInfo.setFile( suggestedFileName_ );
    if( KMessShared::selectNextFile( startFolder, fileInfo.baseName(), suffix,
                                     fileInfo.completeSuffix(), nextNumber ) )
    {
      // The file already exists, choose the next one
      filePath_ = startFolder + fileInfo.baseName() + "." + QString::number( nextNumber ) + "." + fileInfo.completeSuffix();
    }
    else
    {
      // New file, use the suggested name
      filePath_ = startFolder + suggestedFileName_;
    }

    fileInfo.setFile( filePath_ );
    fileName_ = fileInfo.fileName();
  }
  else
  {
    // Ask the user to specify where to put the file
    QString recentFolderTag( ":filedownload" );

    // Open a file dialog so that the user can save the file to a particular directory and name.
    bool hasFile = false;
    while( ! hasFile )
    {
      // Set an initial path to the file
      KUrl startDir = KFileDialog::getStartUrl( startFolder, recentFolderTag );
      startDir.addPath( suggestedFileName_ );

      delayDeletion( true );

      // Ask the user for a file.
      filePath_ = KFileDialog::getSaveFileName( startDir.url() );

      delayDeletion( false );
      if( isClosing() )
      {
        endApplication();
        return;
      }

      if( filePath_.isNull() )
      {
#ifdef KMESSDEBUG_FILETRANSFER_P2P
        kDebug() << "User cancelled in file save dialog";
#endif

        // Dialog cancelled, cancel afterall
        modifyOfferMessage();
        userRejected();
        return;
      }

      hasFile = true;
      fileInfo.setFile( filePath_ );
      fileName_ = fileInfo.fileName();

      // Check if the selected file exists and if the user wants to overwrite it.
      // The while loop is for the prompt to keep appearing if the user
      // chooses the same filename but does not want to overwrite the file.
      if( QFile::exists(filePath_) )
      {
        if( KMessageBox::warningContinueCancel( 0,
              i18n("The file &quot;%1&quot; already exists.\nDo you want to overwrite it?", fileName_ ),
              i18n("Overwrite File"), KGuiItem( i18n("Over&write") ) ) == KMessageBox::Cancel )
        {
          // User does not want to overwrite
          hasFile     = false;
          startFolder = fileInfo.path();
        }
      }
    }
  }

  // Now we try to open the file
  file_   = new QFile(filePath_);
  bool success = (file_ != 0) && file_->open(QIODevice::WriteOnly);

  if( ! success )
  {
#ifdef KMESSDEBUG_FILETRANSFER_P2P
    kDebug() << "Cancelling session";
#endif

    // Notify the user, even if debug mode is not enabled.
    kWarning() << "Unable to open file: " << filePath_ << "!";

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

    // Tell the user about it
    modifyOfferMessage();
    showEventMessage( i18n("The transfer of the file &quot;%1&quot; failed. Could not save the file.",
                           "<span class=\"filename failedFilename\">" + fileName_ + "</span>"),
                            ChatMessage::CONTENT_APP_FAILED, false
                    );

    // Send 500 Internal Error back if we failed (this is still the accept stage)
    sendCancelMessage(CANCEL_FAILED);
    return;
  }


#ifdef KMESSDEBUG_FILETRANSFER_P2P
  kDebug() << "Sending accept message";
#endif

  // Initialize the progress dialog
  initializeProgressDialog(true, fileSize_);
  showTransferMessage( i18n("Negotiating options to connect") );

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

  // Send the ACCEPT message
  sendSlpOkMessage(message);

  // Notify the user
  modifyOfferMessage();
  showEventMessage( i18n( "You have accepted the transfer of the file &quot;%1&quot;.", fileName_ ),
                    ChatMessage::CONTENT_APP_STARTED,
                    false );
}



/**
 * 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.
 */
void FileTransferP2P::contactStarted3_ContactConfirmsAccept(const MimeMessage &/*message*/)
{
#ifdef KMESSDEBUG_FILETRANSFER_P2P
  kDebug();
#endif

  // When the 200 OK message is accepted, the contact sends
  // another invitation with a different content-type, and the
  // contactStarted1_ContactInvitesUser() is called again.

  // The second time, the contact initiates the file transfer based
  // on the fields we sent in the second 200 OK message.
  // Once that message is received, the gotData() handles the rest.
}



/**
 * Step four in a contact-started chat: the contact confirms the data preparation message.
 */
void FileTransferP2P::contactStarted4_ContactConfirmsPreparation()
{
#ifdef KMESSDEBUG_FILETRANSFER_P2P
  kDebug();
#endif

  // NOTE: with WLM at slow computers, the contact actually started
  //       to send data before the transfer was confirmed. be careful here..
}



/**
 * @brief Create the context field.
 *
 * This field is used in the invitation message.
 *
 * @param  fileData    The handle to the opened file.
 * @param  usePreview  Whether to use the preview in thumbnailProvider_
 */
QString FileTransferP2P::createContextField( const QFile *fileData, bool usePreview ) const
{
  // Get the thumbnail results.
  bool hasPreview = ( usePreview && thumbnailProvider_ != 0 && thumbnailProvider_->isSuccessful() );
  QByteArray previewData;

  // Only generate preview if requested and object exists.
  if( hasPreview )
  {
    previewData = thumbnailProvider_->getData();
  }

#ifdef KMESSDEBUG_FILETRANSFER_P2P
  kDebug() << "Creating context field "
            << ( usePreview ? "with thumbnail." : "without thumbnail." );
#endif

  // Get the file data and preview
  quint32 filesize = (quint32) fileData->size();
  int     flags    = (hasPreview ? 0 : 1);

  // Determine the context length
  int length      = 574;  // Somehow MSN always uses 550 bytes for the filename (+24 for the size of fields 1-6).
  int totalLength = length + previewData.size();


  // Fill the context parameter
  QByteArray context( totalLength, 0x00 );
  P2PMessage::insertBytes( context, length,    0 );  // Field 1: Length of fields 1-6
  P2PMessage::insertBytes( context, 2,         4 );  // Field 2: somehow this is always 2
  P2PMessage::insertBytes( context, filesize,  8 );  // Field 3: file size (QWord)
  P2PMessage::insertBytes( context, flags,    16 );  // Field 4: 1 if NO preview data

  // Field 5: the file name
  P2PMessage::insertUtf16String( context, fileName_, 20 );
  P2PMessage::insertBytes(context, 0xFFFFFFFF, 570); // Field 6: some splitter field.

  // Insert preview data
  if( hasPreview )
  {
    context.replace( 574, previewData.size(), previewData );
  }

  // Encode the context
  QByteArray encodedContext = context.toBase64();
  QString base64Context( QString::fromUtf8( encodedContext.data(), encodedContext.size() ) );

#ifdef KMESSDEBUG_FILETRANSFER_P2P
  kDebug() << "Context size=" << context.size() << " base64 size=" << base64Context.length() << ".";
#endif

  // Return it.
  return base64Context;
}



/**
 * End the application with another message in the file transfer dialog as well.
 */
void FileTransferP2P::endApplication()
{
  QString html;

#ifdef KMESSDEBUG_FILETRANSFER_P2P
  kDebug();
#endif

  // Mark the transfer as failed and terminate.
  TransferWindow::getInstance()->failTransfer( transferID_ );

  P2PApplication::endApplication();
}



/**
 * Return the application's GUID.
 */
QString FileTransferP2P::getAppId()
{
  return "{5D3E02AB-6190-11D3-BBBB-00C04F795683}";
}



/**
 * Return a cancel message to display.
 */
QString FileTransferP2P::getContactAbortMessage() const
{
  // Application::getContactAbortMessage() returns "The contact cancelled the session".
  return i18n("The contact has cancelled the transfer of the file &quot;%1&quot;.", ( fileName_.isEmpty() ? suggestedFileName_ : fileName_ ) );
}



/**
 * Return a cancel message to display.
 */
QString FileTransferP2P::getUserAbortMessage() const
{
  // Application::getUserAbortMessage() returns "You have cancelled the session".
  return i18n( "You have cancelled the transfer of the file &quot;%1&quot;.", ( fileName_.isEmpty() ? suggestedFileName_ : fileName_ ) );
}



/**
 * Return a cancel message to display.
 */
QString FileTransferP2P::getUserRejectMessage() const
{
  // Application::getUserAbortMessage() returns "You have cancelled the session".
  return i18n( "You have rejected the transfer of the file &quot;%1&quot;.", ( fileName_.isEmpty() ? suggestedFileName_ : fileName_ ) );
}



/**
 * Called when data is received.
 * Once all data is received, the SLP BYE message will be sent.
 *
 * @param  message  P2P message with the data.
 */
void FileTransferP2P::gotData(const P2PMessage &message)
{
  if(file_ == 0)
  {
    kWarning() << "Unable to handle file data: no file open or already closed "
                << "(offset="    << message.getDataOffset()
                << " totalsize=" << message.getDataSize()
                << " contact="   << getContactHandle() << ")!";

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

#ifdef KMESSDEBUG_FILETRANSFER_P2P
  kDebug() << "Data part received, saving data to file.";
#endif

  // Write the data in the file
  // Let the parent class do the heavy lifting, and abort properly.
  bool success = writeP2PDataToFile( message, file_ );
  if( ! success )
  {
    // Close the file
    file_->flush();
    file_->close();
    delete file_;
    file_ = 0;

    // Display the failure in the transfer window
    TransferWindow::getInstance()->failTransfer( transferID_, i18n("File could not be written") );
    return;
  }

  // When all data is received, the parent class calls showTransferComplete().
}



/**
 * Create and initilize the progress dialog.
 *
 * @param incoming  Set to indicate whether this is an incoming file transfer or not.
 * @param filesize  Size of the file to transfer.
 */
void FileTransferP2P::initializeProgressDialog(bool incoming, uint filesize)
{
  // Create a new entry in the tranfer window
  TransferWindow  *transferWindow = TransferWindow::getInstance();
  transferID_ = transferWindow->addEntry( filePath_, filesize, incoming, preview_ );

  // Connect the dialog so that if the user closes it, it's deleted.
  connect( transferWindow, SIGNAL( cancelTransfer(int)     ) ,
           this,             SLOT( slotCancelTransfer(int) ) );
}



/**
 * Called when the transfer is complete.
 * This function is also called from the P2PApplication base class.
 * The application will terminate automatically.
 */
void FileTransferP2P::showTransferComplete()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kDebug() << "Transfer is complete.";
#endif

  if(file_ != 0)
  {
    // Clean up
    file_->flush();
    file_->close();
    delete file_;
    file_ = 0;
  }

  // Displays the success in the transfer window
  TransferWindow::getInstance()->finishTransfer( transferID_ );

  // Send an event to the chat
  QString fileURL( "<span class=\"filename completedFilename\">"
                   "<a href=\"file:" + filePath_ + "\">" + fileName_ + "</a>"
                   "</span>" );

  // Show an appropriate message
  QString message;
  if( isUserStartedApp() )
  {
    message = i18n( "Successfully sent the file &quot;%1&quot;.", fileURL );
  }
  else
  {
    message = i18n( "Successfully received the file &quot;%1&quot;.", fileURL );
  }
  showEventMessage( message, ChatMessage::CONTENT_APP_ENDED, ! isUserStartedApp() );
}



/**
 * Show a message to inform about a transfer event (shown in the transfer dialog, e.g. connecting to host).
 * This function is also called from the P2PApplication base class.
 */
void FileTransferP2P::showTransferMessage(const QString &message)
{
  // Display the message in the transfer window
  TransferWindow::getInstance()->setStatusMessage( transferID_, message );
}



/**
 * Show the progress made during a transfer.
 * This function is also called from the P2PApplication base class.
 */
void FileTransferP2P::showTransferProgress(const ulong bytesTransferred)
{
  // Display the progress update in the transfer window
  TransferWindow::getInstance()->updateProgress( transferID_, bytesTransferred );
}



/**
 * Cancelled the file transfer from the TransferWindow
 */
void FileTransferP2P::slotCancelTransfer( int transferID )
{
  // Check if the cancelled transfer is ours
  if( transferID_ != transferID )
  {
    return;
  }

#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kDebug();
#endif

  // Use the same route as closing the chat window.
  userAborted();

  // Now we wait for the contact to end the session
}


/**
 * Convert a string to some more readable form
 */
QString FileTransferP2P::toReadableBytes(uint bytes)
{
  QString format;
  if(bytes > 1048576)
  {
    // Using '%.2f' instead of '%.1f' removes the ".0" part, but it's less pretty.
    format.sprintf("%.1f", (double) bytes / 1048576.0);
    return i18n("%1 MB", format);
  }
  else if(bytes > 1024)
  {
    format.sprintf("%.1f", (double) bytes / 1024.0);
    return i18n("%1 kB", format);
  }
  else
  {
    return i18n("%1 bytes", bytes);
  }
}



/**
 * The user cancelled the session
 */
void FileTransferP2P::userAborted()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kDebug();
#endif

  // Display the cancel state in the transfer window
  TransferWindow::getInstance()->failTransfer( transferID_, i18n("Cancelled") );

  // Let the parent class handle the rest
  P2PApplication::userAborted();
}



/**
 * Step one of a user-started chat: the user invites the contact
 */
void FileTransferP2P::userStarted1_UserInvitesContact()
{
#ifdef KMESSDEBUG_FILETRANSFER_P2P
  kDebug() << "starting file transfer";
#endif
#ifdef KMESSTEST
  KMESS_ASSERT( file_ == 0 );
#endif

  /*
   * Many thanks to Siebe Tolsma for providing this documentation:
   * http://siebe.bot2k3.net/docs/
   */

  file_        = new QFile(filePath_);
  bool success = (file_ != 0) && file_->open(QIODevice::ReadOnly);

  // Stop if the file can't be openend.
  if( ! success )
  {
#ifdef KMESSDEBUG_FILETRANSFER_P2P
    kDebug() << "Cancelling session";
#endif

    // Notify the user, even if debug mode is not enabled.
    kWarning() << "Unable to open file: " << filePath_ << "!";

    // Close the file
    delete file_;
    file_ = 0;

    // Tell the user about it
    if( ! QFile::exists(filePath_) )
    {
      showEventMessage( i18n( "The transfer of the file &quot;%1&quot; failed. The file does not exist.",
                              "<span class=\"filename failedFilename\">" + filePath_ + "</span>"), ChatMessage::CONTENT_APP_CANCELED, false );
    }
    else
    {
      showEventMessage( i18n( "The transfer of the file &quot;%1&quot; failed. The file could not be read.",
                              "<span class=\"filename failedFilename\">" + filePath_ + "</span>"), ChatMessage::CONTENT_APP_CANCELED, false );
    }
    return;
  }

  // Set the short file name
  QFileInfo fileInfo( filePath_ );
  fileName_ = fileInfo.fileName();

  // Read the filename parameters
  fileSize_ = (uint)file_->size();

  // Create a thumbnail, continue when it completes
  // Windows Live Messenger scales the received image down depending on the users settings.
  // The preview however, is always sent as 96x96
  thumbnailProvider_ = new ThumbnailProvider( filePath_, 96 );
  connect( thumbnailProvider_, SIGNAL(gotResult()), this, SLOT(userStarted1_gotThumbnailResult()) );
}



/**
 * @brief Called when the thumbnail is generated.
 */
void FileTransferP2P::userStarted1_gotThumbnailResult()
{
  bool hasPreview = thumbnailProvider_->isSuccessful();

  // Send the invitation
  QString context( createContextField( file_, hasPreview ) );
  sendSlpSessionInvitation( KMessShared::generateID(), getAppId(), 2, context );

  // Generate the HTML to cancel the transfer
  QString html( i18n( "Sending file &quot;%1&quot; (%2).",
                       "<span class=\"filename invitationFilename\">" + fileName_ + "</span>",
                       toReadableBytes( fileSize_ ) ) );


  // Generate fallback image so there is always an icon in the chat window.
  if( ! hasPreview )
  {
    thumbnailProvider_->generateFallbackImage();
    hasPreview = thumbnailProvider_->isSuccessful();
  }

  // Insert the preview image if this is available.
  if( hasPreview )
  {
    html     = thumbnailProvider_->getImageTag( fileName_ ) + "<br />" + html;
    preview_ = thumbnailProvider_->getImage();  // Save for transfer dialog later.
  }

  // Display the link in the chat window
  offerCancel( html );
}



/**
 * Step two of a user-started chat: the contact accepts
 *
 * @param  message  Accept message of the other contact
 */
void FileTransferP2P::userStarted2_ContactAccepts(const MimeMessage & /*message*/)
{
#ifdef KMESSDEBUG_FILETRANSFER_P2P
  kDebug();
#endif

  // Update the GUI
  modifyOfferMessage();
  showEventMessage( i18n( "The contact has accepted the transfer of the file &quot;%1&quot;.", fileName_ ),
                    ChatMessage::CONTENT_APP_STARTED,
                    true );
  initializeProgressDialog(false, fileSize_);

  // Send the invite to negatiate the transfer mode.
  sendSlpTransferInvitation();
}



/**
 * Step three of a user-started chat: the user prepares for the session.
 */
void FileTransferP2P::userStarted3_UserPrepares()
{
#ifdef KMESSDEBUG_FILETRANSFER_P2P
  kDebug();
#endif
#ifdef KMESSTEST
  KMESS_ASSERT( file_ != 0 );
#endif

  // A connection is available to send the file.
  // The base class handles the transfer transparently (e.g. using direct connections, etc).
  sendData( file_, P2P_TYPE_FILE );
}


#include "filetransferp2p.moc"
