Files
kmess/src/network/applications/filetransfer.cpp
T
2026-07-06 09:37:59 +02:00

853 lines
26 KiB
C++

/***************************************************************************
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 "../../utils/kmessconfig.h"
#include "../../currentaccount.h"
#include "../../kmessdebug.h"
#include "../extra/msnftpconnection.h"
#include "../mimemessage.h"
#include <QFile>
#include <QFileInfo>
#include <KLocalizedString>
// 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 <QFileDialog>
#include <KMessageBox>
#include "../../dialogs/transferwindow.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)
, transferID_(-1)
{
setApplicationType( ChatMessage::TYPE_APPLICATION_FILE );
}
// The constructor with filename to start a session
FileTransfer::FileTransfer(const QString &contactHandle, const QString &filename)
: MimeApplication(contactHandle)
, connectionEstablished_(false)
, connectivity_('?')
, file_(0)
, filePath_(filename)
, msnFtpConnection_(0)
, transferID_(-1)
{
setApplicationType( ChatMessage::TYPE_APPLICATION_FILE );
}
// The destructor
FileTransfer::~FileTransfer()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
kmDebug();
#endif
// Displays the cancel state in the transfer window
TransferWindow::getInstance()->failTransfer( transferID_ );
// 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
KMESS_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( statusMessage(KLocalizedString,int) ) , // Display a message in the dialog/chat window
this, SLOT( slotMsnFtpStatusMessage(KLocalizedString,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) ) );
}
/**
* The contact cancelled the session
*/
void FileTransfer::contactAborted(const QString &message)
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
kmDebug();
#endif
Q_UNUSED( message );
// Displays the cancel state in the transfer window
TransferWindow::getInstance()->failTransfer( transferID_, i18n("Cancelled") );
// Also display it in chat
modifyOfferMessage();
if( message.isEmpty() )
{
showEventMessage( getContactAbortMessage(), ChatMessage::CONTENT_APP_CANCELED, true );
}
else
{
showEventMessage( message, ChatMessage::CONTENT_APP_CANCELED, true );
}
}
// Step one of a contact-started chat: the contact invites the user
void FileTransfer::contactStarted1_ContactInvitesUser(const MimeMessage& message)
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
kmDebug();
#endif
// Get the file name and size from the message
suggestedFileName_ = message.getValue("Application-File");
fileSize_ = message.getValue("Application-FileSize").toInt();
connectivity_ = '?';
// Get the optional fields.
if( message.hasField("Connectivity") )
{
QString connectivity( message.getValue("Connectivity") );
if( connectivity.size() > 0 )
{
connectivity_ = connectivity.at(0).toLatin1();
}
}
// Send the message to the chat window.
offerAcceptOrReject( i18n( "The contact wants to send you a file: &quot;%1&quot; (%2).",
"<span class=\"filename invitationFilename\">" + suggestedFileName_ + "</span>",
toReadableBytes( fileSize_ ) ) );
}
// Step two of a contact-started chat: the user accepts
void FileTransfer::contactStarted2_UserAccepts()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
kmDebug() << "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() )
{
int index = -1;
do
{
// The ++ ensures that at the next iteration a suffix is appended to the name
if( index++ < 0 )
{
filePath_ = startFolder + "/" + suggestedFileName_;
}
else
{
fileInfo.setFile( suggestedFileName_ );
filePath_ = startFolder + "/" + fileInfo.baseName() + "(" + QString::number( index ) + ")." + fileInfo.completeSuffix();
}
}
while( QFile::exists( filePath_ ) );
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
const QString startPath = QDir( startFolder ).filePath( suggestedFileName_ );
delayDeletion( true );
// Ask the user for a file.
filePath_ = QFileDialog::getSaveFileName( nullptr, QString(), startPath );
delayDeletion( false );
if( isClosing() )
{
endApplication();
return;
}
if( filePath_.isNull() )
{
#ifdef KMESSDEBUG_FILETRANSFER_P2P
kmDebug() << "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
kmDebug() << "Cancelling session";
#endif
// Notify the user, even if debug mode is not enabled.
kmWarning() << "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
);
slotMsnFtpStatusMessage( i18n( "The transfer of the file &quot;%1&quot; failed. Could not open the destination file." ),
MsnFtpConnection::STATUS_CHAT );
// Send 500 Internal Error back if we failed (this is still the accept stage)
sendCancelMessage( CANCEL_FAILED );
return;
}
#ifdef KMESSDEBUG_FILETRANSFER_P2P
kmDebug() << "Sending accept message";
#endif
// 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 );
// Notify the user
modifyOfferMessage();
showEventMessage( i18n("Transfer accepted."), ChatMessage::CONTENT_APP_STARTED, false );
}
// Step three of a contact-started chat: the contact confirms the accept
void FileTransfer::contactStarted3_ContactConfirmsAccept(const MimeMessage& message)
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
kmDebug();
#endif
#ifdef KMESSTEST
KMESS_ASSERT( ! fileName_.isEmpty() );
KMESS_ASSERT( ! suggestedFileName_.isEmpty() );
KMESS_ASSERT( file_ != 0 );
KMESS_ASSERT( msnFtpConnection_ == 0 );
#endif
QString ip;
QString ipInternal;
quint16 port;
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
port = (quint16)message.getValue( "Port" ).toUInt();
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
kmDebug() << "IP: " << ip << " Port: " << port << " AuthCookie: " << authCookie;
#endif
// Initialize the MSNFTP connection
msnFtpConnection_ = new MsnFtpConnection( CurrentAccount::instance()->getHandle(), authCookie );
connectMsnFtpConnection();
// Initialize the progess dialog
initializeProgressDialog(true);
slotMsnFtpStatusMessage( i18n( "Connecting to %1, port %2", ip, QString::number( 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.
*/
QString FileTransfer::getContactAbortMessage() const
{
// Application::getUserAbortMessage() 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 FileTransfer::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 FileTransfer::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_ ) );
}
// Create and initilize the progress dialog.
void FileTransfer::initializeProgressDialog(bool incoming)
{
#ifdef KMESSTEST
KMESS_ASSERT( fileSize_ != 0 );
KMESS_ASSERT( fileName_.length() > 0 );
#endif
// Create a new entry in the tranfer window
TransferWindow *transferWindow = TransferWindow::getInstance();
transferID_ = transferWindow->addEntry( fileName_, fileSize_, incoming );
// Connect the dialog so that if the user closes it, it's deleted.
connect( transferWindow, SIGNAL( cancelTransfer(int) ) ,
this, SLOT( slotCancelTransfer(int) ) );
}
// Cancelled the file transfer from the TransferWindow
void FileTransfer::slotCancelTransfer( int transferID )
{
// Check if the cancelled transfer is ours
if( transferID_ != transferID )
{
return;
}
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
kmDebug();
#endif
// 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 (as a raw localization string)
void FileTransfer::slotMsnFtpStatusMessage( KLocalizedString message, int statusType )
{
// Parse the %1 placeholder for file name
switch( statusType )
{
case MsnFtpConnection::STATUS_DIALOG:
case MsnFtpConnection::STATUS_DIALOG_FAILED:
message = message.subs( fileName_ );
break;
// A message to display in the chat window
case MsnFtpConnection::STATUS_CHAT_FAILED:
message = message.subs( "<span class=\"filename failedFilename\">" + fileName_ + "</span>" );
break;
case MsnFtpConnection::STATUS_CHAT:
default:
message = message.subs( "<span class=\"filename\">" + fileName_ + "</span>" );
break;
}
// Call the real handler with the completed localization string
slotMsnFtpStatusMessage( message.toString(), statusType );
}
// Display a status message
void FileTransfer::slotMsnFtpStatusMessage(QString message, int statusType)
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
kmDebug() << "Status message (type=" << statusType
<< " message=" << message << ").";
#endif
switch(statusType)
{
// A message for the transfer window
case MsnFtpConnection::STATUS_DIALOG:
{
TransferWindow::getInstance()->setStatusMessage( transferID_, message );
break;
}
case MsnFtpConnection::STATUS_DIALOG_FAILED:
{
TransferWindow::getInstance()->failTransfer( transferID_, message );
break;
}
// A message to display in the chat window
case MsnFtpConnection::STATUS_CHAT_FAILED:
{
showEventMessage( message, ChatMessage::CONTENT_APP_FAILED, ! isUserStartedApp() );
break;
}
case MsnFtpConnection::STATUS_CHAT:
default:
{
showEventMessage( message, ChatMessage::CONTENT_APP_FAILED, ! isUserStartedApp() );
break;
}
}
}
// Signal that the transfer was succesful
void FileTransfer::slotMsnFtpTransferComplete()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
kmDebug() << "Transfer successful.";
#endif
// 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() );
}
// Signal that the transfer failed
void FileTransfer::slotMsnFtpTransferFailed()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
kmDebug() << "Transfer failed.";
#endif
// If MsnFtpConnection did not emit a statusMessage(.. STATUS_DIALOG_FAILED) signal
// the dialog will be terminated with a default message here.
TransferWindow::getInstance()->failTransfer( transferID_ );
// 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)
{
// Display the progress update in the transfer window
TransferWindow::getInstance()->updateProgress( transferID_, bytesReceived );
}
// Convert a string to some more readable form
QString FileTransfer::toReadableBytes( ulong bytes )
{
QString format;
if(bytes > 1048576)
{
// Using '%.2f' instead of '%.1f' removes the ".0" part, but it's less pretty.
format = QString::number( (double) bytes / 1048576.0, 'f', 1 );
return i18n( "%1 MB", format );
}
else if(bytes > 1024)
{
format = QString::number( (double) bytes / 1024.0, 'f', 1 );
return i18n( "%1 kB", format );
}
else
{
return i18n( "%1 bytes", bytes );
}
}
// The user cancelled the session
void FileTransfer::userAborted()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
kmDebug();
#endif
// Display the cancel state in the transfer window
TransferWindow::getInstance()->failTransfer( transferID_, i18n("Cancelled") );
// 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();
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
void FileTransfer::userStarted1_UserInvitesContact()
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
kmDebug();
#endif
#ifdef KMESSTEST
KMESS_ASSERT( ! filePath_.isEmpty() );
#endif
QString sizeString;
MimeMessage message;
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
kmDebug() << "Cancelling session";
#endif
// Notify the user, even if debug mode is not enabled.
kmWarning() << "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 file parameters
fileSize_ = (uint)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", fileName_ );
message.addField( "Application-FileSize", sizeString );
// message.addField( "Connectivity", connectivity_ );
sendMessage( message );
// Give the user the option of cancelling the transfer
offerCancel( i18n( "Sending file &quot;%1&quot; (%2).",
"<span class=\"filename invitationFilename\">" + fileName_ + "</span>",
toReadableBytes( fileSize_ ) ) );
}
// Step two of a user-started chat: the contact accepts
void FileTransfer::userStarted2_ContactAccepts(const MimeMessage& /*message*/)
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
kmDebug();
#endif
#ifdef KMESSTEST
KMESS_ASSERT( msnFtpConnection_ == 0 );
#endif
QString html;
QString portString;
QString authCookie;
// Remove the accept links
modifyOfferMessage();
// Create a message showing that the transfer was accepted.
showEventMessage( i18n( "The contact has accepted the transfer of the file &quot;%1&quot;.", fileName_ ),
ChatMessage::CONTENT_APP_STARTED,
true );
// Get the authorisation cookie to use
authCookie = generateCookie();
// Initialize the msnftp connection class
msnFtpConnection_ = new MsnFtpConnection( 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("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.
void FileTransfer::userStarted3_UserPrepares()
{
#ifdef KMESSTEST
KMESS_ASSERT( msnFtpConnection_ != 0 );
KMESS_ASSERT( file_ != 0 );
#endif
if(file_ != 0)
{
msnFtpConnection_->sendFile(file_);
}
else
{
#ifdef KMESSDEBUG_FILETRANSFER_GENERAL
kmDebug() << "Pointer file_ is null!";
#endif
slotMsnFtpTransferFailed();
}
}