Logo Search packages:      
Sourcecode: kmess version File versions

filetransfer.cpp

/***************************************************************************
                          filetransfer.cpp  -  description
                             -------------------
    begin                : Mon Mar 24 2003
    copyright            : (C) 2003 by Mike K. Bennett
                           (C) 2005 by Diederik van der Boor
    email                : mkb137b@hotmail.com
                           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 "filetransfer.h"

#include <qfile.h>

#include <kdebug.h>
#include <kextsock.h>
#include <klocale.h>

#include "../mimemessage.h"
#include "../extra/msnftpconnection.h"
#include "../../currentaccount.h"
#include "../../kmessdebug.h"

// It wouldn't hurt if these GUI specific features are removed here.
// That would make this class GUI-independant and only emit signals for possible GUI actions.
#include <kfiledialog.h>
#include <kmessagebox.h>
#include "../../dialogs/transferwindow.h"
#include "../../dialogs/transferentryinterface.h"
#include "../../dialogs/transferentry.h"


#ifdef KMESSDEBUG_FILETRANSFER
#define KMESSDEBUG_FILETRANSFER_GENERAL
#endif


// The constructor without filename (sufficient for incoming sessions)
FileTransfer::FileTransfer(const QString &contactHandle)
  : MimeApplication(contactHandle)
  , connectionEstablished_(false)
  , connectivity_('?')
  , file_(0)
  , msnFtpConnection_(0)
  , transferPanel_(0)
{

}

// The constructor with filename to start a session
FileTransfer::FileTransfer(const QString &contactHandle, const QString &filename)
  : MimeApplication(contactHandle)
  , connectionEstablished_(false)
  , connectivity_('?')
  , file_(0)
  , fileName_(filename)
  , msnFtpConnection_(0)
  , transferPanel_(0)
{

}


// The destructor
FileTransfer::~FileTransfer()
{
  // Make sure the transfer panel displays the cancel state
  if(transferPanel_ != 0)
  {
    // The object can be deleted by the user
    transferPanel_->failTransfer();
    transferPanel_ = 0;
  }

  // Stop the ftp connection
  delete msnFtpConnection_;

  // Close the file nicely
  if(file_ != 0)
  {
    delete file_;
  }
}



// Connect signals of the MsnFtpConnection object
void FileTransfer::connectMsnFtpConnection()
{
#ifdef KMESSTEST
  ASSERT( msnFtpConnection_ != 0 );
#endif

  connect(msnFtpConnection_, SIGNAL(           connectionEstablished()         ) ,  // The direct connection was established
          this,                SLOT( slotMsnFtpConnectionEstablished()         ) );
  connect(msnFtpConnection_, SIGNAL(              statusMessage(QString,int)   ) ,  // Display a message in the dialog/chat window
          this,                SLOT(    slotMsnFtpStatusMessage(QString,int)   ) );
  connect(msnFtpConnection_, SIGNAL(           transferComplete()              ) ,  // Signal that the transfer is complete
          this,                SLOT( slotMsnFtpTransferComplete()              ) );
  connect(msnFtpConnection_, SIGNAL(             transferFailed()              ) ,  // Signal that the transfer failed (and clean up)
          this,                SLOT(   slotMsnFtpTransferFailed()              ) );
  connect(msnFtpConnection_, SIGNAL(            transferProgess(unsigned long) ) ,  // Signal that the transfer made progress
          this,                SLOT(  slotMsnFtpTransferProgess(unsigned long) ) );
}



// Step one of a contact-started chat: the contact invites the user
00118 void FileTransfer::contactStarted1_ContactInvitesUser(const MimeMessage& message)
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kdDebug() << "FileTransfer - contactStarted1_ContactInvitesUser" << endl;
#endif
  QString html;

  // Get the file name and size from the message
  shortFileName_ = message.getValue("Application-File");
  fileSize_      = message.getValue("Application-FileSize").toInt();
  connectivity_  = message.getValue("Connectivity").at(0).latin1();

  // Send the message to the chat window.
  html = i18n("Do you want to accept the file: %1 (%2 bytes)")
         .arg( "<span class=\"filename invitationFilename\">" + shortFileName_ + "</span>" )
         .arg( fileSize_ );

  offerAcceptOrReject( html );
}



// Step two of a contact-started chat: the user accepts
00141 void FileTransfer::contactStarted2_UserAccepts()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kdDebug() << "FileTransfer::contactStarted2_UserAccepts" << endl;
#endif
#ifdef KMESSTEST
  ASSERT( file_ == 0 );
#endif

  bool open;
  QString startFolder;  // QString::null by default
  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( shortFileName_ );

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

    if( fileName_.isNull() )
    {
#ifdef KMESSDEBUG_FILETRANSFER_P2P
      kdDebug() << "FileTransferP2P::contactStarted2_UserAccepts: User cancelled in file save dialog" << endl;
#endif

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

    hasFile = true;
    QString shortName = fileName_.right( fileName_.length() - fileName_.findRev("/") - 1 );

    // 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(fileName_) )
    {
      if( KMessageBox::warningContinueCancel( 0,
            i18n("The file '%1' already exists.\ndo you want to overwrite it?").arg(shortName),
            i18n("Overwrite File"), KGuiItem( i18n("Over&write") ) ) == KMessageBox::Cancel )
      {
        // User does not want to override
        hasFile     = false;
        startFolder = fileName_.left( fileName_.findRev("/") );
      }
    }
  }

  // Create a file object, open for writing
  file_ = new QFile(fileName_);
  open  = file_->open(IO_WriteOnly);

  if(! open)
  {
    // Important: undo what we created
    delete file_;
    file_     = 0;
    fileName_ = QString::null;

    // Cancel invitation afterall
    slotMsnFtpStatusMessage( i18n("The transfer of %1 failed.  Couldn't open file."), MsnFtpConnection::STATUS_CHAT );
    sendCancelMessage(CANCEL_FAILED);
    return;
  }


  // All passed. Send the accept message
  MimeMessage message;
  message.addField( "Invitation-Command", "ACCEPT"         );
  message.addField( "Invitation-Cookie",  getCookie()      );
  message.addField( "Launch-Application", "FALSE"          );
  message.addField( "Request-Data",       "IP-Address:"    );

  sendMessage( message );
}



// Step three of a contact-started chat: the contact confirms the accept
00226 void FileTransfer::contactStarted3_ContactConfirmsAccept(const MimeMessage& message)
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kdDebug() << "FileTransfer - contactStarted3_ContactConfirmsAccept" << endl;
#endif
#ifdef KMESSTEST
  ASSERT( ! fileName_.isEmpty()      );
  ASSERT( ! shortFileName_.isEmpty() );
  ASSERT( file_             != 0     );
  ASSERT( msnFtpConnection_ == 0     );
#endif

  QString ip;
  QString ipInternal;
  int     port;
  int     portXInternal;
  int     portX;
  QString authCookie;

  // Pull the IP, port, and authorization info from the message
  ip            = message.getValue( "IP-Address"          );
  ipInternal    = message.getValue( "IP-Address-Internal" );          // As of MSN5
  portXInternal = message.getValue( "PortX-Internal"      ).toInt();  // As of MSN5
  port          = message.getValue( "Port"                ).toInt();
  portX         = message.getValue( "PortX"               ).toInt();  // As of MSN5
  authCookie    = message.getValue( "AuthCookie"          );

/*
 * This is an attempt to get file transfers
 * between two home systems working.. (both behind the same NAT router)
 * I didn't discover any reference when I started this.
 *
 * TODO: Implement MSN5-compatible transfers using http://www.hypothetic.org/docs/msn/phorum/read.php?f=1&i=3435&t=3372#reply_3435
 *       A full implementation also requires UPnP handling.
 *
 * When receiving an invite with a "Connectivity: N" field,
 * and the client it's not being translated by a NAT, set a "Sender-Connect: TRUE" field,
 * and include addressing information for both internal and external addresses, and start a server.
 * Otherwise, start a client.
 *
 * Presumably, a client can try three connections when attempting to connect to a server:
 * - To IP-Address on Port
 * - To IP-Address on PortX
 * - To IP-Address-Internal on PortX-Internal.
 * Using IP-Address on Port and IP-Address-Internal on PortX-Internal should be sufficient.
 */

  QString externalIp = getExternalIp();
  if(ip == externalIp)
  {
    // The other client must be at the same network..!
    // Try to connect internally.

    // Just to be sure the values are set...
    if(! ipInternal.isEmpty())
    {
      ip   = ipInternal;
      //port = internPort;
      port = 6891; // Only works if there is one transfer, but it's better then nothing.
    }
    // else: the file transfer fails anyway
  }


#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kdDebug() << "FileTransfer - IP: " << ip << " Port: " << port << " AuthCookie: " << authCookie << endl;
#endif

  // Initialize the MSNFTP connection
  msnFtpConnection_ = new MsnFtpConnection();
  msnFtpConnection_->setAuthInfo( CurrentAccount::instance()->getHandle(), authCookie );
  connectMsnFtpConnection();

  // Initialize the progess dialog
  initializeProgressDialog(true);
  slotMsnFtpStatusMessage( i18n("Connecting to %1, port %2").arg(ip).arg(port), MsnFtpConnection::STATUS_DIALOG );

  // Start the transfer
  msnFtpConnection_->retrieveFile(file_, ip, port);
}



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



// Return a cancel message to display
00318 QString FileTransfer::getUserAbortMessage() const
{
  // Application::getUserAbortMessage() returns "You have cancelled the session".
  return i18n("The transfer was cancelled");
}



// Create and initilize the progress dialog.
void FileTransfer::initializeProgressDialog(bool incoming)
{
#ifdef KMESSTEST
  ASSERT( transferPanel_     == 0 );
  ASSERT( fileSize_          != 0 );
  ASSERT( fileName_.length()  > 0 );
#endif

  // Create a new entry in the tranfer window
  TransferWindow *transferWindow = TransferWindow::instance();
  transferPanel_ = transferWindow->addEntry( fileName_, fileSize_, incoming );

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

  transferWindow->show();
}



// Cancelled the file transfer from the TransferWindow
void FileTransfer::slotCancelTransfer()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kdDebug() << "FileTransfer - slotCancelTransfer" << endl;
#endif

  // This method is activated from the transferPanel.
  // cancelClicked() will already call failTransfer()
  // make sure we don't (or call updateProcess() by accident)
  transferPanel_ = 0;

  // Call userAborted() which cleans up this object correctly
  userAborted();
}



// The direct connection was established
void FileTransfer::slotMsnFtpConnectionEstablished()
{
  // Update the status dialog
  slotMsnFtpStatusMessage( i18n("Connection established"), MsnFtpConnection::STATUS_DIALOG );
  connectionEstablished_ = true;
}



// Display a status message
void FileTransfer::slotMsnFtpStatusMessage(QString message, int statusType)
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kdDebug() << "FileTransfer - Status message (type="    << statusType
                                          << " message=" << message
                                          << " panel="   << (transferPanel_ != 0) << ")." << endl;
#endif

  switch(statusType)
  {
    // A message for the transfer dialog
    case MsnFtpConnection::STATUS_DIALOG:
    {
      if(transferPanel_ != 0)
      {
        // Parse %1 placeholder for filename, and display
        if(message.contains("%1")) message = message.arg(fileName_);
        transferPanel_->setStatusMessage( message );
      }
      break;
    }
    case MsnFtpConnection::STATUS_DIALOG_FAILED:
    {
      if(transferPanel_ != 0)
      {
        if(message.contains("%1")) message = message.arg(fileName_);
        transferPanel_->failTransfer( message );
        transferPanel_ = 0;
      }
      break;
    }

    // A message to display in the chat window
    case MsnFtpConnection::STATUS_CHAT_FAILED:
    {
      if(message.contains("%1")) message = message.arg("<span class=\"filename failedFilename\">" + shortFileName_ + "</span>");
      showEventMessage( message );
      break;
    }
    case MsnFtpConnection::STATUS_CHAT:
    default:
    {
      if(message.contains("%1")) message = message.arg("<span class=\"filename\">" + shortFileName_ + "</span>");
      showEventMessage( message );
      break;
    }
  }
}



// Signal that the transfer was succesful
void FileTransfer::slotMsnFtpTransferComplete()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kdDebug() << "FileTransfer - Successful Transfer" << endl;
#endif

  // Display a message to the user
  if( transferPanel_ != 0 )
  {
    transferPanel_->finishTransfer();
    transferPanel_ = 0;
  }
  emit fileTransferred(fileName_);

  // Terminate this application
  endApplication();
}



// Signal that the transfer failed
void FileTransfer::slotMsnFtpTransferFailed()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kdDebug() << "FileTransfer - Transfer Failed" << endl;
#endif

  // If MsnFtpConnection did not emit a statusMessage(.. STATUS_DIALOG_FAILED) signal
  // the dialog will be terminated with a default message here.
  if( transferPanel_ != 0 )
  {
    transferPanel_->failTransfer();
    transferPanel_ = 0;
  }


  // If the connection attempt failed, send a cancel message.
  // In other situations, the remote client will notice the MSNFTP connection closed.
  if( ! connectionEstablished_ )
  {
    sendCancelMessage(CANCEL_FAILED);  // calls endApplication() already
    return;
  }

  // Terminate this application
  endApplication();
}



// Update the progress dialog with the number of bytes transferred.
void FileTransfer::slotMsnFtpTransferProgess(unsigned long bytesReceived)
{
  if ( transferPanel_ != 0 )
  {
    transferPanel_->updateProgress( bytesReceived );
  }
}



// The user cancelled the session
00491 void FileTransfer::userAborted()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kdDebug() << "FileTransfer - userAborted" << endl;
#endif

  // Make sure the transfer panel displays the cancel state
  if(transferPanel_ != 0)
  {
    // The object can be deleted by the user
    transferPanel_->failTransfer( i18n("File transfer dialog message", "Cancelled") );
    transferPanel_ = 0;
  }

  // If there is no MSNFTP connection, the standard behavour is exactly what we need.
  if(msnFtpConnection_ == 0)
  {
    MimeApplication::userAborted();
    return;
  }


  // We have a MsnFtpConnection listening
  // Then teardown the active transfer
  if(msnFtpConnection_->isConnected())
  {
    // It is already sending the file
    // Initiate connection teardown

    // Disconnect signals
    // We are not interested in status messages, they are displayed here already
    // slotWriteData() calls slotMsnFtpTransferFailed() which deletes this object, we do this here already
    disconnect(msnFtpConnection_, SIGNAL(statusMessage(QString,int)), this, SLOT(slotMsnFtpStatusMessage(QString,int)));
    disconnect(msnFtpConnection_, SIGNAL(transferFailed()),           this, SLOT(slotMsnFtpTransferFailed()));

    // Tell MsnFtpConnection to stop transferring. This also signals the other client we cancelled
    // delete after all signals are processed (since we detached slotMsnFtpTransferFailed())
    msnFtpConnection_->cancelTransfer(true);
    msnFtpConnection_->deleteLater();
    msnFtpConnection_ = 0;

    // we don't have to send a cancel message anymore.
    // MsnFtpConnection already does the same thing.

    // Finally delete this object
    endApplication( getUserAbortMessage() );
    return;
  }
  else
  {
    // No transfer, but close the connection ASAP
    msnFtpConnection_->closeConnection();
    msnFtpConnection_->deleteLater();
    msnFtpConnection_ = 0;

    // The transfer was not initiated.
    // Send the cancel-session message.
    MimeApplication::userAborted();
    return;
  }
}



// Step one of a user-started chat: the user invites the contact
00556 void FileTransfer::userStarted1_UserInvitesContact()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kdDebug() << "FileTransfer - userStarted1_UserInvitesContact" << endl;
#endif
#ifdef KMESSTEST
  ASSERT( ! fileName_.isEmpty() );
#endif
  QString     sizeString;
  MimeMessage message;

  // Get a short file name so slotMsnFtpStatusMessage() can use that.
  shortFileName_ = fileName_.right( fileName_.length() - fileName_.findRev('/') - 1 );

  // Create a file object
  file_ = new QFile(fileName_);

  // Open the file for reading
  bool open = file_->open(IO_ReadOnly);
  if( ! open )
  {
    // Notify the user, even if debug mode is not enabled.
    kdWarning() << "Unable to open file: " << fileName_ << "!" << endl;

    // Important: undo what we created
    delete file_;
    file_ = 0;

    // Leave
    if( ! QFile::exists(fileName_) )
    {
      slotMsnFtpStatusMessage( i18n("The transfer of %1 failed.  The file does not exist."), MsnFtpConnection::STATUS_CHAT );
    }
    else
    {
      slotMsnFtpStatusMessage( i18n("The transfer of %1 failed.  The file could not be read."), MsnFtpConnection::STATUS_CHAT );
    }

    endApplication();
    return;
  }


  // Get the file parameters for the message
  fileSize_      = file_->size();
  sizeString     = QString::number(fileSize_);

  // Create the invitation message
  //  connectivity_ = 'U';   // Or 'N' if the port is mapped with NAT
  message.addField( "Application-Name",     "File Transfer" );
  message.addField( "Application-GUID",     getAppId()      );
  message.addField( "Invitation-Command",   "INVITE"        );
  message.addField( "Invitation-Cookie",    getCookie()     );
  message.addField( "Application-File",     shortFileName_  );
  message.addField( "Application-FileSize", sizeString      );
//  message.addField( "Connectivity",         connectivity_   );

  sendMessage( message );

  // Give the user the option of cancelling the transfer
  offerCancel( i18n("Sending file %1").arg("<span class=\"filename invitationFilename\">" + shortFileName_ + "</span>") );
}



// Step two of a user-started chat: the contact accepts
00622 void FileTransfer::userStarted2_ContactAccepts(const MimeMessage& /*message*/)
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
  kdDebug() << "FileTransfer - userStarted2_ContactAccepts" << endl;
#endif
#ifdef KMESSTEST
  ASSERT( msnFtpConnection_ == 0 );
#endif

  QString html;
  QString portString;
  QString authCookie;

  // Create a message showing that the transfer was accepted.
  showEventMessage( i18n("Transfer accepted.") );

  // Get the authorisation cookie to use
  authCookie = generateCookie();

  // Initialize the msnftp connection class
  msnFtpConnection_ = new MsnFtpConnection();
  msnFtpConnection_->setAuthInfo(getContactHandle(), authCookie);
  connectMsnFtpConnection();

  // Get the IP,port to use
  portString  = QString::number( msnFtpConnection_->getLocalServerPort() );

  // Initialize the progress dialog
  initializeProgressDialog();

  // Set the first status message.
  slotMsnFtpStatusMessage( i18n("File transfer dialog message", "Negotiating options to connect"), MsnFtpConnection::STATUS_DIALOG );

  // Create the invitation message
  // TODO: PortX-Internal and PortX should be another port.
  //       We should be listening to port 11178 as well for internal transfers
  MimeMessage response;
  response.addField( "Invitation-Command",  "ACCEPT"         );
  response.addField( "Invitation-Cookie",   getCookie()      );
  response.addField( "IP-Address",          getExternalIp()  );
//  response.addField( "IP-Address-Internal", getLocalIp()     );
//  response.addField( "PortX-Internal",      portString       );
  response.addField( "Port",                portString       );
//  response.addField( "PortX",               portString       );
  response.addField( "Launch-Application",  "FALSE"          );
  response.addField( "AuthCookie",          authCookie       );

  sendMessage( response );
}



// Connect to the port and start listening for the transfer.
00675 void FileTransfer::userStarted3_UserPrepares()
{
#ifdef KMESSTEST
  ASSERT( msnFtpConnection_ != 0 );
  ASSERT( file_             != 0 );
#endif

  if(file_ != 0)
  {
    msnFtpConnection_->sendFile(file_);
  }
  else
  {
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
    kdDebug() << "FileTransfer::userStarted3_UserPrepares: file_ is a null pointer!" << endl;
#endif
    slotMsnFtpTransferFailed();
  }
}


#include "filetransfer.moc"

Generated by  Doxygen 1.6.0   Back to index