You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
konversation/konversation/src/dcctransfersend.cpp

510 lines
16 KiB

/*
send a file on DCC protocol
begin: Mit Aug 7 2002
copyright: (C) 2002 by Dario Abatianni
email: eisfuchs@tigress.com
*/
// Copyright (C) 2004-2007 Shintaro Matsuoka <shin@shoegazed.org>
// Copyright (C) 2004,2005 John Tapsell <john@geola.co.uk>
/*
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 "dcctransfersend.h"
#include "channel.h"
#include "dcccommon.h"
#include "dcctransfermanager.h"
#include "konversationapplication.h"
#include "connectionmanager.h"
#include "server.h"
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <net/if.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <tqfile.h>
#include <tqtimer.h>
#include <kdebug.h>
#include <klocale.h>
#include <kserversocket.h>
#include <tdesocketaddress.h>
#include <kstreamsocket.h>
#include <kio/netaccess.h>
#include <kfileitem.h>
// TODO: remove the dependence
#include <kinputdialog.h>
using namespace KNetwork;
DccTransferSend::DccTransferSend(TQObject* parent)
: DccTransfer( DccTransfer::Send, parent )
{
kdDebug() << "DccTransferSend::DccTransferSend()" << endl;
m_serverSocket = 0;
m_sendSocket = 0;
m_connectionTimer = new TQTimer( this );
connect( m_connectionTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( slotConnectionTimeout() ) );
// set defualt values
m_reverse = Preferences::dccPassiveSend();
}
DccTransferSend::~DccTransferSend()
{
cleanUp();
}
void DccTransferSend::cleanUp()
{
kdDebug() << "DccTransferSend::cleanUp()" << endl;
stopConnectionTimer();
finishTransferLogger();
if ( !m_tmpFile.isEmpty() )
KIO::NetAccess::removeTempFile( m_tmpFile );
m_tmpFile = TQString();
m_file.close();
if ( m_sendSocket )
{
m_sendSocket->close();
m_sendSocket = 0; // the instance will be deleted automatically by its parent
}
if ( m_serverSocket )
{
m_serverSocket->close();
m_serverSocket = 0; // the instance will be deleted automatically by its parent
}
}
// just for convenience
void DccTransferSend::failed( const TQString& errorMessage )
{
setStatus( Failed, errorMessage );
cleanUp();
emit done( this );
}
void DccTransferSend::setFileURL( const KURL& url )
{
if ( getStatus() == Configuring )
m_fileURL = url;
}
void DccTransferSend::setFileName( const TQString& fileName )
{
if ( getStatus() == Configuring )
m_fileName = fileName;
}
void DccTransferSend::setOwnIp( const TQString& ownIp )
{
if ( getStatus() == Configuring )
m_ownIp = ownIp;
}
void DccTransferSend::setFileSize( KIO::filesize_t fileSize )
{
if ( getStatus() == Configuring )
m_fileSize = fileSize;
}
void DccTransferSend::setReverse( bool reverse )
{
if ( getStatus() == Configuring )
m_reverse = reverse;
}
bool DccTransferSend::queue()
{
kdDebug() << "DccTransferSend::queue()" << endl;
if ( getStatus() != Configuring )
return false;
if ( m_ownIp.isEmpty() )
m_ownIp = DccCommon::getOwnIp( KonversationApplication::instance()->getConnectionManager()->getServerByConnectionId( m_connectionId ) );
if ( !kapp->authorize( "allow_downloading" ) )
{
//Do not have the rights to send the file. Shouldn't have gotten this far anyway
//Note this is after the initialisation so the view looks correct still
failed(i18n("The admin has restricted the right to send files"));
return false;
}
if ( m_fileName.isEmpty() )
m_fileName = sanitizeFileName( m_fileURL.fileName() );
if ( Preferences::dccIPv4Fallback() )
{
KIpAddress ip( m_ownIp );
if ( ip.isIPv6Addr() )
{
/* This is ugly but there is no KDE way to do this yet :| -cartman */
struct ifreq ifr;
const char* address = Preferences::dccIPv4FallbackIface().ascii();
int sock = socket(AF_INET, SOCK_DGRAM, 0);
strncpy( ifr.ifr_name, address, IF_NAMESIZE );
ifr.ifr_addr.sa_family = AF_INET;
if ( ioctl( sock, SIOCGIFADDR, &ifr ) >= 0 )
m_ownIp = inet_ntoa( ( (struct sockaddr_in *)&ifr.ifr_addr )->sin_addr );
kdDebug() << "Falling back to IPv4 address " << m_ownIp << endl;
}
}
m_fastSend = Preferences::dccFastSend();
kdDebug() << "DccTransferSend::DccTransferSend(): Fast DCC send: " << m_fastSend << endl;
//Check the file exists
if ( !KIO::NetAccess::exists( m_fileURL, true, NULL ) )
{
failed( i18n( "The url \"%1\" does not exist" ).arg( m_fileURL.prettyURL() ) );
return false;
}
//FIXME: KIO::NetAccess::download() is a synchronous function. we should use KIO::get() instead.
//Download the file. Does nothing if it's local (file:/)
if ( !KIO::NetAccess::download( m_fileURL, m_tmpFile, NULL ) )
{
failed( i18n( "Could not retrieve \"%1\"" ).arg( m_fileURL.prettyURL() ) );
kdDebug() << "DccTransferSend::DccTransferSend(): KIO::NetAccess::download() failed. reason: " << KIO::NetAccess::lastErrorString() << endl;
return false;
}
//Some protocols, like http, maybe not return a filename, and altFileName may be empty, So prompt the user for one.
if ( m_fileName.isEmpty() )
{
bool pressedOk;
m_fileName = KInputDialog::getText( i18n( "Enter Filename" ), i18n( "<qt>The file that you are sending to <i>%1</i> does not have a filename.<br>Please enter a filename to be presented to the receiver, or cancel the dcc transfer</qt>" ).arg( getPartnerNick() ), "unknown", &pressedOk, NULL );
if ( !pressedOk )
{
failed( i18n( "No filename was given" ) );
return false;
}
}
//FIXME: if "\\\"" works well on other IRC clients, replace "\"" with "\\\""
m_fileName.replace( "\"", "_" );
if (Preferences::dccSpaceToUnderscore())
m_fileName.replace( " ", "_" );
else {
if (m_fileName.contains(" ") > 0)
m_fileName = "\"" + m_fileName + "\"";
}
m_file.setName( m_tmpFile );
if ( m_fileSize == 0 )
m_fileSize = m_file.size();
return DccTransfer::queue();
}
void DccTransferSend::abort() // public slot
{
kdDebug() << "DccTransferSend::abort()" << endl;
setStatus( Aborted );
cleanUp();
emit done( this );
}
void DccTransferSend::start() // public slot
{
kdDebug() << "DccTransferSend::start()" << endl;
if ( getStatus() != Queued )
return;
// common procedure
Server* server = KonversationApplication::instance()->getConnectionManager()->getServerByConnectionId( m_connectionId );
if ( !server )
{
kdDebug() << "DccTransferSend::start(): could not retrieve the instance of Server. Connection id: " << m_connectionId << endl;
failed( i18n( "Could not send a DCC SEND request to the partner via the IRC server." ) );
return;
}
if ( !m_reverse )
{
// Normal DCC SEND
kdDebug() << "DccTransferSend::start(): normal DCC SEND" << endl;
// Set up server socket
TQString failedReason;
if ( Preferences::dccSpecificSendPorts() )
m_serverSocket = DccCommon::createServerSocketAndListen( this, &failedReason, Preferences::dccSendPortsFirst(), Preferences::dccSendPortsLast() );
else
m_serverSocket = DccCommon::createServerSocketAndListen( this, &failedReason );
if ( !m_serverSocket )
{
failed( failedReason );
return;
}
connect( m_serverSocket, TQT_SIGNAL( readyAccept() ), this, TQT_SLOT( acceptClient() ) );
connect( m_serverSocket, TQT_SIGNAL( gotError( int ) ), this, TQT_SLOT( slotGotSocketError( int ) ) );
connect( m_serverSocket, TQT_SIGNAL( closed() ), this, TQT_SLOT( slotServerSocketClosed() ) );
// Get own port number
m_ownPort = TQString::number( DccCommon::getServerSocketPort( m_serverSocket ) );
kdDebug() << "DccTransferSend::start(): own Address=" << m_ownIp << ":" << m_ownPort << endl;
startConnectionTimer( Preferences::dccSendTimeout() );
server->dccSendRequest( m_partnerNick, m_fileName, getNumericalIpText( m_ownIp ), m_ownPort, m_fileSize );
}
else
{
// Passive DCC SEND
kdDebug() << "DccTransferSend::start(): passive DCC SEND" << endl;
int tokenNumber = KonversationApplication::instance()->getDccTransferManager()->generateReverseTokenNumber();
// TODO: should we append a letter "T" to this token?
m_reverseToken = TQString::number( tokenNumber );
kdDebug() << "DccTransferSend::start(): passive DCC key(token): " << m_reverseToken << endl;
server->dccPassiveSendRequest( m_partnerNick, m_fileName, getNumericalIpText( m_ownIp ), m_fileSize, m_reverseToken );
}
setStatus( WaitingRemote, i18n( "Waiting remote user's acceptance" ) );
}
void DccTransferSend::connectToReceiver( const TQString& partnerHost, const TQString& partnerPort )
{
// Reverse DCC
m_partnerIp = partnerHost;
m_partnerPort = partnerPort;
m_sendSocket = new KNetwork::KStreamSocket( partnerHost, partnerPort, this );
m_sendSocket->setBlocking( false );
connect( m_sendSocket, TQT_SIGNAL( connected( const KResolverEntry& ) ), this, TQT_SLOT( startSending() ) );
connect( m_sendSocket, TQT_SIGNAL( gotError( int ) ), this, TQT_SLOT( slotConnectionFailed( int ) ) );
setStatus( Connecting );
m_sendSocket->connect();
}
// public
bool DccTransferSend::setResume( unsigned long position )
{
kdDebug() << "DccTransferSend::setResume(): position=" << position << endl;
if ( getStatus() > WaitingRemote )
return false;
if ( position >= m_fileSize )
return false;
m_resumed = true;
m_transferringPosition = position;
return true;
}
void DccTransferSend::acceptClient() // slot
{
// Normal DCC
kdDebug() << "DccTransferSend::acceptClient()" << endl;
stopConnectionTimer();
m_sendSocket = static_cast<KNetwork::KStreamSocket*>( m_serverSocket->accept() );
if ( !m_sendSocket )
{
failed( i18n( "Could not accept the connection. (Socket Error)" ) );
return;
}
// we don't need ServerSocket anymore
m_serverSocket->close();
startSending();
}
void DccTransferSend::startSending()
{
connect( m_sendSocket, TQT_SIGNAL( readyWrite() ), this, TQT_SLOT( writeData() ) );
connect( m_sendSocket, TQT_SIGNAL( readyRead() ), this, TQT_SLOT( getAck() ) );
connect( m_sendSocket, TQT_SIGNAL( closed() ), this, TQT_SLOT( slotSendSocketClosed() ) );
if ( m_sendSocket->peerAddress().asInet().ipAddress().isV4Mapped() )
m_partnerIp = KNetwork::KIpAddress( m_sendSocket->peerAddress().asInet().ipAddress().IPv4Addr() ).toString();
else
m_partnerIp = m_sendSocket->peerAddress().asInet().ipAddress().toString();
m_partnerPort = m_sendSocket->peerAddress().serviceName();
if ( m_file.open( IO_ReadOnly ) )
{
// seek to file position to make resume work
m_file.at( m_transferringPosition );
m_transferStartPosition = m_transferringPosition;
setStatus( Transferring );
m_sendSocket->enableWrite( true );
m_sendSocket->enableRead( m_fastSend );
startTransferLogger(); // initialize CPS counter, ETA counter, etc...
}
else
failed( i18n( "Could not open the file: %1" ).arg( getTQFileErrorString( m_file.status() ) ) );
}
void DccTransferSend::writeData() // slot
{
//kdDebug() << "DccTransferSend::writeData()" << endl;
if ( !m_fastSend )
{
m_sendSocket->enableWrite( false );
m_sendSocket->enableRead( true );
}
int actual = m_file.readBlock( m_buffer, m_bufferSize );
if ( actual > 0 )
{
m_sendSocket->writeBlock( m_buffer, actual );
m_transferringPosition += actual;
if ( (KIO::fileoffset_t)m_fileSize <= m_transferringPosition )
{
Q_ASSERT( (KIO::fileoffset_t)m_fileSize == m_transferringPosition );
kdDebug() << "DccTransferSend::writeData(): Done." << endl;
m_sendSocket->enableWrite( false ); // there is no need to call this function anymore
}
}
}
void DccTransferSend::getAck() // slot
{
//kdDebug() << "DccTransferSend::getAck()" << endl;
if ( !m_fastSend && m_transferringPosition < (KIO::fileoffset_t)m_fileSize )
{
m_sendSocket->enableWrite( true );
m_sendSocket->enableRead( false );
}
unsigned long pos;
while ( m_sendSocket->bytesAvailable() >= 4 )
{
m_sendSocket->readBlock( (char*)&pos, 4 );
pos = intel( pos );
if ( pos == m_fileSize )
{
kdDebug() << "DccTransferSend::getAck(): Received final ACK." << endl;
finishTransferLogger();
setStatus( Done );
cleanUp();
emit done( this );
break; // for safe
}
}
}
void DccTransferSend::slotGotSocketError( int errorCode )
{
kdDebug() << "DccTransferSend::slotGotSocketError(): code = " << errorCode << " string = " << m_serverSocket->errorString() << endl;
failed( i18n( "Socket error: %1" ).arg( m_serverSocket->errorString() ) );
}
void DccTransferSend::startConnectionTimer( int sec )
{
kdDebug() << "DccTransferSend::startConnectionTimer()"<< endl;
stopConnectionTimer();
m_connectionTimer->start( sec*1000, true );
}
void DccTransferSend::stopConnectionTimer()
{
if ( m_connectionTimer->isActive() )
{
kdDebug() << "DccTransferSend::stopConnectionTimer(): stop" << endl;
m_connectionTimer->stop();
}
}
void DccTransferSend::slotConnectionTimeout() // slot
{
kdDebug() << "DccTransferSend::slotConnectionTimeout()" << endl;
failed( i18n( "Timed out" ) );
}
void DccTransferSend::slotConnectionFailed( int /* errorCode */ )
{
failed( i18n( "Connection failure: %1" ).arg( m_sendSocket->KSocketBase::errorString() ) );
}
void DccTransferSend::slotServerSocketClosed()
{
kdDebug() << "DccTransferSend::slotServerSocketClosed()" << endl;
}
void DccTransferSend::slotSendSocketClosed()
{
kdDebug() << "DccTransferSend::slotSendSocketClosed()" << endl;
finishTransferLogger();
if ( getStatus() == Transferring && m_transferringPosition < (KIO::fileoffset_t)m_fileSize )
failed( i18n( "Remote user disconnected" ) );
}
// protected, static
TQString DccTransferSend::getTQFileErrorString( int code )
{
TQString errorString;
switch(code)
{
case IO_Ok:
errorString=i18n("The operation was successful. Should never happen in an error dialog.");
break;
case IO_ReadError:
errorString=i18n("Could not read from file \"%1\".").arg( m_fileName );
break;
case IO_WriteError:
errorString=i18n("Could not write to file \"%1\".").arg( m_fileName );
break;
case IO_FatalError:
errorString=i18n("A fatal unrecoverable error occurred.");
break;
case IO_OpenError:
errorString=i18n("Could not open file \"%1\".").arg( m_fileName );
break;
// Same case value? Damn!
// case IO_ConnectError:
// errorString="Could not connect to the device.";
// break;
case IO_AbortError:
errorString=i18n("The operation was unexpectedly aborted.");
break;
case IO_TimeOutError:
errorString=i18n("The operation timed out.");
break;
case IO_UnspecifiedError:
errorString=i18n("An unspecified error happened on close.");
break;
default:
errorString=i18n("Unknown error. Code %1").arg(code);
break;
}
return errorString;
}
#include "dcctransfersend.moc"