/* receive 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 // Copyright (C) 2004,2005 John Tapsell /* 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 "dcctransferrecv.h" #include "dcccommon.h" #include "channel.h" #include "dcctransfermanager.h" #include "konversationapplication.h" #include "connectionmanager.h" #include "server.h" #include #include #include #include #include #include #include #include #include #include #include #include #include class DccResumeDialog; /* *flow chart* DccTransferRecv() start() : called from DccTransferPanel when user pushes the accept button | \ | requestResume() : called when user chooses to resume in DccResumeDialog. it emits the signal ResumeRequest() | | startResume() : called by "Server" | | connectToSender() connectionSuccess() : called by recvSocket */ DccTransferRecv::DccTransferRecv(TQObject* parent) : DccTransfer( DccTransfer::Receive, parent ) { kdDebug() << "DccTransferRecv::DccTransferRecv()" << endl; m_serverSocket = 0; m_recvSocket = 0; m_writeCacheHandler = 0; m_connectionTimer = new TQTimer( this ); connect( m_connectionTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( connectionTimeout() ) ); //timer hasn't started yet. qtimer will be deleted automatically when 'this' object is deleted } DccTransferRecv::~DccTransferRecv() { cleanUp(); } TQString DccTransferRecv::getTypeText() const { return i18n( "Receive" ); } TQPixmap DccTransferRecv::getTypeIcon() const { return TDEGlobal::iconLoader()->loadIcon( "down", KIcon::Small ); } void DccTransferRecv::cleanUp() { kdDebug() << "DccTransferRecv::cleanUp()" << endl; stopConnectionTimer(); finishTransferLogger(); if ( m_serverSocket ) { m_serverSocket->close(); m_serverSocket = 0; } if ( m_recvSocket ) { m_recvSocket->close(); m_recvSocket = 0; // the instance will be deleted automatically by its parent } if ( m_writeCacheHandler ) { m_writeCacheHandler->closeNow(); m_writeCacheHandler->deleteLater(); m_writeCacheHandler = 0; } } // just for convenience void DccTransferRecv::failed( const TQString& errorMessage ) { setStatus( Failed, errorMessage ); cleanUp(); emit done( this ); } void DccTransferRecv::setPartnerIp( const TQString& ip ) { if ( getStatus() == Configuring ) m_partnerIp = ip; } void DccTransferRecv::setPartnerPort( const TQString& port ) { if ( getStatus() == Configuring ) m_partnerPort = port; } void DccTransferRecv::setFileSize( unsigned long fileSize ) { if ( getStatus() == Configuring ) m_fileSize = fileSize; } void DccTransferRecv::setFileName( const TQString& fileName ) { if ( getStatus() == Configuring ) m_fileName = fileName; } void DccTransferRecv::setFileURL( const KURL& url ) { if ( getStatus() == Configuring || getStatus() == Queued ) m_fileURL = url; } void DccTransferRecv::setReverse( bool reverse, const TQString& reverseToken ) { if ( getStatus() == Configuring ) { m_reverse = reverse; if ( reverse ) { m_partnerPort = TQString::number( 0 ); m_reverseToken = reverseToken; } } } bool DccTransferRecv::queue() { kdDebug() << "DccTransferRecv::queue()" << endl; if ( getStatus() != Configuring ) return false; if ( m_partnerIp.isEmpty() || m_partnerPort.isEmpty() ) return false; if (!kapp->authorize("allow_downloading")) { //note we have this after the initialisations so that item looks okay //Do not have the rights to send the file. Shouldn't have gotten this far anyway failed(i18n("The admin has restricted the right to receive files")); return false; } // check if the sender IP is valid if ( m_partnerIp == "0.0.0.0" ) { failed( i18n( "Invalid sender address (%1)" ).arg( m_partnerIp ) ); return false; } // TODO: should we support it? if ( m_fileSize == 0 ) { failed( i18n( "Unsupported negotiation (filesize=0)" ) ); return false; } if ( m_fileName.isEmpty() ) { m_fileName = "unnamed_file"; } if ( m_fileURL.isEmpty() ) { // determine default incoming file URL // set default folder if ( !Preferences::dccPath().isEmpty() ) m_fileURL = KURL( Preferences::dccPath() ); else m_fileURL.setPath( KUser( KUser::UseRealUserID ).homeDir() ); // default folder is *not* specified // add a slash if there is none m_fileURL.adjustPath( 1 ); // Append folder with partner's name if wanted if ( Preferences::dccCreateFolder() ) m_fileURL.addPath( m_partnerNick + '/' ); // Just incase anyone tries to do anything nasty TQString fileNameSanitized = sanitizeFileName( m_fileName ); // Append partner's name to file name if wanted if ( Preferences::dccAddPartner() ) m_fileURL.addPath( m_partnerNick + '.' + fileNameSanitized ); else m_fileURL.addPath( fileNameSanitized ); } return DccTransfer::queue(); } void DccTransferRecv::abort() // public slot { kdDebug() << "DccTransferRecv::abort()" << endl; if(m_writeCacheHandler) { m_writeCacheHandler->write( true ); // flush } setStatus( Aborted ); cleanUp(); emit done( this ); } void DccTransferRecv::start() // public slot { kdDebug() << "DccTransferRecv::start() [BEGIN]" << endl; if ( getStatus() != Queued ) return; setStatus( Preparing ); prepareLocalKio( false, false ); kdDebug() << "DccTransferRecv::start() [END]" << endl; } void DccTransferRecv::prepareLocalKio( bool overwrite, bool resume, TDEIO::fileoffset_t startPosition /* = 0 */ ) { kdDebug() << "DccTransferRecv::prepareLocalKio()" << endl << "DccTransferRecv::prepareLocalKio(): URL: " << m_fileURL << endl << "DccTransferRecv::prepareLocalKio(): Overwrite: " << overwrite << endl << "DccTransferRecv::prepareLocalKio(): Resume: " << resume << " (Position: " << TQString::number( startPosition ) << ")" << endl; m_resumed = resume; m_transferringPosition = startPosition; if ( !createDirs( m_fileURL.upURL() ) ) { askAndPrepareLocalKio( i18n( "Cannot create the folder.
" "Folder: %1
" ) .arg( m_fileURL.upURL().prettyURL() ), DccResumeDialog::RA_Rename | DccResumeDialog::RA_Cancel, DccResumeDialog::RA_Rename ); return; } TDEIO::TransferJob* transferJob = TDEIO::put( m_fileURL, -1, overwrite, m_resumed, false ); if ( !transferJob ) { kdDebug() << "DccTransferRecv::prepareLocalKio(): TDEIO::put() returned NULL. what happened?" << endl; failed( i18n( "Could not create a KIO instance" ) ); return; } connect( transferJob, TQT_SIGNAL( canResume( TDEIO::Job*, TDEIO::filesize_t ) ), this, TQT_SLOT( slotLocalCanResume( TDEIO::Job*, TDEIO::filesize_t ) ) ); connect( transferJob, TQT_SIGNAL( result( TDEIO::Job* ) ), this, TQT_SLOT( slotLocalGotResult( TDEIO::Job* ) ) ); connect( transferJob, TQT_SIGNAL( dataReq( TDEIO::Job*, TQByteArray& ) ), this, TQT_SLOT( slotLocalReady( TDEIO::Job* ) ) ); } void DccTransferRecv::askAndPrepareLocalKio( const TQString& message, int enabledActions, DccResumeDialog::ReceiveAction defaultAction, TDEIO::fileoffset_t startPosition ) { switch ( DccResumeDialog::ask( this, message, enabledActions, defaultAction ) ) { case DccResumeDialog::RA_Resume: prepareLocalKio( false, true, startPosition ); break; case DccResumeDialog::RA_Overwrite: prepareLocalKio( true, false ); break; case DccResumeDialog::RA_Rename: prepareLocalKio( false, false ); break; case DccResumeDialog::RA_Cancel: default: setStatus( Queued ); } } bool DccTransferRecv::createDirs( const KURL& dirURL ) const { KURL kurl( dirURL ); TQString surl = kurl.url(); //First we split directories until we reach to the top, //since we need to create directories one by one TQStringList dirList; while ( surl != kurl.upURL().url() ) { dirList.prepend( surl ); kurl = kurl.upURL(); surl = kurl.url(); } //Now we create the directories TQStringList::ConstIterator it; for ( it=dirList.begin() ; it!=dirList.end() ; ++it ) if ( !TDEIO::NetAccess::exists( *it, true, NULL ) ) if ( !TDEIO::NetAccess::mkdir( *it, NULL, -1 ) ) return false; return true; } void DccTransferRecv::slotLocalCanResume( TDEIO::Job* job, TDEIO::filesize_t size ) { kdDebug() << "DccTransferRecv::slotLocalCanResume() [BEGIN]" << endl << "DccTransferRecv::slotLocalCanResume(): size: " << TQString::number( size ) << endl; if ( size != 0 ) { TDEIO::TransferJob* transferJob = static_cast( job ); disconnect( transferJob, 0, 0, 0 ); transferJob->kill(); if ( KonversationApplication::instance()->getDccTransferManager()->isLocalFileInWritingProcess( m_fileURL ) ) { askAndPrepareLocalKio( i18n( "The file is used by another transfer.
" "%1
" ) .arg( m_fileURL.prettyURL() ), DccResumeDialog::RA_Rename | DccResumeDialog::RA_Cancel, DccResumeDialog::RA_Rename ); } else if ( Preferences::dccAutoResume() ) { prepareLocalKio( false, true, size ); } else { askAndPrepareLocalKio( i18n( "A partial file exists.
" "%1
" "Size of the partial file: %2 bytes
" ) .arg( m_fileURL.prettyURL() ) .arg( TDEGlobal::locale()->formatNumber( size, 0 ) ), DccResumeDialog::RA_Resume | DccResumeDialog::RA_Overwrite | DccResumeDialog::RA_Rename | DccResumeDialog::RA_Cancel, DccResumeDialog::RA_Resume, size ); } } kdDebug() << "DccTransferRecv::slotLocalCanResume() [END]" << endl; } void DccTransferRecv::slotLocalGotResult( TDEIO::Job* job ) { kdDebug() << "DccTransferRecv::slotLocalGotResult() [BEGIN]" << endl; TDEIO::TransferJob* transferJob = static_cast( job ); disconnect( transferJob, 0, 0, 0 ); switch ( transferJob->error() ) { case 0: // no error kdDebug() << "DccTransferRecv::slotLocalGotResult(): job->error() returned 0." << endl << "DccTransferRecv::slotLocalGotResult(): Why was I called in spite of no error?" << endl; break; case TDEIO::ERR_FILE_ALREADY_EXIST: askAndPrepareLocalKio( i18n( "The file already exists.
" "%1
" ) .arg( m_fileURL.prettyURL() ), DccResumeDialog::RA_Overwrite | DccResumeDialog::RA_Rename | DccResumeDialog::RA_Cancel, DccResumeDialog::RA_Overwrite ); break; default: askAndPrepareLocalKio( i18n( "Could not open the file.
" "Error: %1

" "%2
" ) .arg( transferJob->error() ) .arg( m_fileURL.prettyURL() ), DccResumeDialog::RA_Rename | DccResumeDialog::RA_Cancel, DccResumeDialog::RA_Rename ); } kdDebug() << "DccTransferRecv::slotLocalGotResult() [END]" << endl; } void DccTransferRecv::slotLocalReady( TDEIO::Job* job ) { kdDebug() << "DccTransferRecv::slotLocalReady()" << endl; TDEIO::TransferJob* transferJob = static_cast( job ); disconnect( transferJob, 0, 0, 0 ); // WriteCacheHandler will control the job after this m_writeCacheHandler = new DccTransferRecvWriteCacheHandler( transferJob ); connect( m_writeCacheHandler, TQT_SIGNAL( done() ), this, TQT_SLOT( slotLocalWriteDone() ) ); connect( m_writeCacheHandler, TQT_SIGNAL( gotError( const TQString& ) ), this, TQT_SLOT( slotLocalGotWriteError( const TQString& ) ) ); if ( !m_resumed ) connectWithSender(); else requestResume(); } void DccTransferRecv::connectWithSender() { if ( m_reverse ) { if ( !startListeningForSender() ) return; Server* server = KonversationApplication::instance()->getConnectionManager()->getServerByConnectionId( m_connectionId ); if ( !server ) { failed( i18n( "Could not send Reverse DCC SEND acknowledgement to the partner via the IRC server." ) ); } m_ownIp = DccCommon::getOwnIp( server ); m_ownPort = TQString::number( DccCommon::getServerSocketPort( m_serverSocket ) ); setStatus( WaitingRemote, i18n( "Waiting for connection" ) ); server->dccReverseSendAck( m_partnerNick, m_fileName, DccCommon::textIpToNumericalIp( m_ownIp ), m_ownPort, m_fileSize, m_reverseToken ); //FIXME: add connection timer here } else { connectToSendServer(); } } void DccTransferRecv::requestResume() { kdDebug() << "DccTransferRecv::requestResume()" << endl; setStatus( WaitingRemote, i18n( "Waiting for remote host's acceptance" ) ); startConnectionTimer( 30 ); kdDebug() << "DccTransferRecv::requestResume(): requesting resume for " << m_partnerNick << " file " << m_fileName << " partner " << m_partnerPort << endl; //TODO m_filename could have been sanitized - will this effect this? 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 DCC RECV resume request to the partner via the IRC server." ) ); return; } server->dccResumeGetRequest( m_partnerNick, m_fileName, m_partnerPort, m_transferringPosition ); } // public slot void DccTransferRecv::startResume( unsigned long position ) { kdDebug() << "DccTransferRecv::startResume(): position: " << position << endl; stopConnectionTimer(); if ( (unsigned long)m_transferringPosition != position ) { kdDebug() << "DccTransferRecv::startResume(): remote responsed an unexpected position" << endl << "DccTransferRecv::startResume(): expected: " << TQString::number( m_transferringPosition ) << endl << "DccTransferRecv::startResume(): remote response: " << position << endl; failed( i18n( "Unexpected response from remote host" ) ); return; } connectWithSender(); } void DccTransferRecv::connectToSendServer() { kdDebug() << "DccTransferRecv::connectToSendServer()" << endl; // connect to sender setStatus( Connecting ); m_recvSocket = new KNetwork::KStreamSocket( m_partnerIp, m_partnerPort, this); m_recvSocket->setBlocking( false ); // asynchronous mode m_recvSocket->setFamily( KNetwork::KResolver::InetFamily ); m_recvSocket->setResolutionEnabled( false ); m_recvSocket->setTimeout( 30000 ); m_recvSocket->enableRead( false ); m_recvSocket->enableWrite( false ); connect( m_recvSocket, TQT_SIGNAL( connected( const KResolverEntry& ) ), this, TQT_SLOT( startReceiving() ) ); connect( m_recvSocket, TQT_SIGNAL( gotError( int ) ), this, TQT_SLOT( connectionFailed( int ) ) ); kdDebug() << "DccTransferRecv::connectToServer(): attempting to connect to " << m_partnerIp << ":" << m_partnerPort << endl; m_recvSocket->connect(); } bool DccTransferRecv::startListeningForSender() { // 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 false; } connect( m_serverSocket, TQT_SIGNAL( readyAccept() ), this, TQT_SLOT( slotServerSocketReadyAccept() ) ); connect( m_serverSocket, TQT_SIGNAL( gotError( int ) ), this, TQT_SLOT( slotServerSocketGotError( int ) ) ); return true; } void DccTransferRecv::slotServerSocketReadyAccept() { //stopConnectionTimer() m_recvSocket = static_cast( m_serverSocket->accept() ); if ( !m_recvSocket ) { failed( i18n( "Could not accept the connection. (Socket Error)" ) ); return; } connect( m_recvSocket, TQT_SIGNAL( gotError( int ) ), this, TQT_SLOT( connectionFailed( int ) ) ); // we don't need ServerSocket anymore m_serverSocket->close(); startReceiving(); } void DccTransferRecv::slotServerSocketGotError( int /* errorCode*/ ) { failed( i18n( "Socket error: %1" ).arg( m_serverSocket->errorString() ) ); } void DccTransferRecv::startReceiving() { kdDebug() << "DccTransferRecv::startReceiving()" << endl; m_recvSocket->setBlocking( false ); // asynchronous mode connect( m_recvSocket, TQT_SIGNAL( readyRead() ), this, TQT_SLOT( readData() ) ); connect( m_recvSocket, TQT_SIGNAL( readyWrite() ), this, TQT_SLOT( sendAck() ) ); connect( m_recvSocket, TQT_SIGNAL( closed() ), this, TQT_SLOT( slotSocketClosed() ) ); setStatus( Transferring ); m_transferStartPosition = m_transferringPosition; m_recvSocket->enableRead( true ); m_recvSocket->enableWrite( false ); startTransferLogger(); // initialize CPS counter, ETA counter, etc... } // slot void DccTransferRecv::connectionFailed( int errorCode ) { kdDebug() << "DccTransferRecv::connectionFailed(): code = " << errorCode << ", string = " << m_recvSocket->TDESocketBase::errorString() << endl; failed( i18n( "Connection failure: %1" ).arg( m_recvSocket->TDESocketBase::errorString() ) ); } void DccTransferRecv::readData() // slot { //kdDebug() << "readData()" << endl; int actual = m_recvSocket->readBlock( m_buffer, m_bufferSize ); if ( actual > 0 ) { //actual is the size we read in, and is guaranteed to be less than m_bufferSize m_transferringPosition += actual; m_writeCacheHandler->append( m_buffer, actual ); m_writeCacheHandler->write( false ); m_recvSocket->enableWrite( true ); } } void DccTransferRecv::sendAck() // slot { //kdDebug() << "sendAck()" << endl; TDEIO::fileoffset_t pos = intel( m_transferringPosition ); m_recvSocket->enableWrite( false ); m_recvSocket->writeBlock( (char*)&pos, 4 ); if ( m_transferringPosition == (TDEIO::fileoffset_t)m_fileSize ) { kdDebug() << "DccTransferRecv::sendAck(): Sent final ACK." << endl; m_recvSocket->enableRead( false ); disconnect( m_recvSocket, 0, 0, 0 ); finishTransferLogger(); m_writeCacheHandler->close(); // WriteCacheHandler will send the signal done() } else if ( m_transferringPosition > (TDEIO::fileoffset_t)m_fileSize ) { kdDebug() << "DccTransferRecv::sendAck(): the remote host sent larger data than expected: " << TQString::number( m_transferringPosition ) << endl; failed( i18n( "Transferring error" ) ); } } void DccTransferRecv::slotLocalWriteDone() // <-WriteCacheHandler::done() { kdDebug() << "DccTransferRecv::slotLocalWriteDone()" << endl; setStatus( Done ); cleanUp(); emit done( this ); } // <- WriteCacheHandler::gotError() void DccTransferRecv::slotLocalGotWriteError( const TQString& errorString ) { kdDebug() << "DccTransferRecv::slotLocalGotWriteError()" << endl; failed( i18n( "KIO error: %1" ).arg( errorString ) ); } void DccTransferRecv::startConnectionTimer( int sec ) { stopConnectionTimer(); kdDebug() << "DccTransferRecv::startConnectionTimer()" << endl; m_connectionTimer->start( sec*1000, true ); } void DccTransferRecv::stopConnectionTimer() { if ( m_connectionTimer->isActive() ) { m_connectionTimer->stop(); kdDebug() << "DccTransferRecv::stopConnectionTimer(): stop" << endl; } } void DccTransferRecv::connectionTimeout() // slot { kdDebug() << "DccTransferRecv::connectionTimeout()" << endl; failed( i18n( "Timed out" ) ); } void DccTransferRecv::slotSocketClosed() { finishTransferLogger(); if ( getStatus() == Transferring ) failed( i18n( "Remote user disconnected" ) ); } // WriteCacheHandler DccTransferRecvWriteCacheHandler::DccTransferRecvWriteCacheHandler( TDEIO::TransferJob* transferJob ) : m_transferJob( transferJob ) { m_writeReady = true; m_cacheStream = 0; connect( this, TQT_SIGNAL( dataFinished() ), m_transferJob, TQT_SLOT( slotFinished() ) ); connect( m_transferJob, TQT_SIGNAL( dataReq( TDEIO::Job*, TQByteArray& ) ), this, TQT_SLOT( slotKIODataReq( TDEIO::Job*, TQByteArray& ) ) ); connect( m_transferJob, TQT_SIGNAL( result( TDEIO::Job* ) ), this, TQT_SLOT( slotKIOResult( TDEIO::Job* ) ) ); m_transferJob->setAsyncDataEnabled( m_writeAsyncMode = true ); } DccTransferRecvWriteCacheHandler::~DccTransferRecvWriteCacheHandler() { closeNow(); } // public void DccTransferRecvWriteCacheHandler::append( char* data, int size ) { // sendAsyncData() and dataReq() cost a lot of time, so we should pack some caches. // 1meg static const unsigned int maxWritePacketSize = 1 * 1024 * 1024; if ( m_cacheList.isEmpty() || m_cacheList.back().size() + size > maxWritePacketSize ) { m_cacheList.append( TQByteArray() ); delete m_cacheStream; m_cacheStream = new TQDataStream( m_cacheList.back(), IO_WriteOnly ); } m_cacheStream->writeRawBytes( data, size ); } // public bool DccTransferRecvWriteCacheHandler::write( bool force ) { // force == false: return without doing anything when the whole cache size is smaller than maxWritePacketSize if ( m_cacheList.isEmpty() || !m_transferJob || !m_writeReady || !m_writeAsyncMode ) return false; if ( !force && m_cacheList.count() < 2 ) return false; // do write m_writeReady = false; m_transferJob->sendAsyncData( m_cacheList.front() ); //kdDebug() << "DTRWriteCacheHandler::write(): wrote " << m_cacheList.front().size() << " bytes." << endl; m_cacheList.pop_front(); return true; } void DccTransferRecvWriteCacheHandler::close() // public { kdDebug() << "DTRWriteCacheHandler::close()" << endl; write( true ); // write once if kio is ready to write m_transferJob->setAsyncDataEnabled( m_writeAsyncMode = false ); kdDebug() << "DTRWriteCacheHandler::close(): switched to synchronized mode." << endl; kdDebug() << "DTRWriteCacheHandler::close(): flushing... (remaining caches: " << m_cacheList.count() << ")" << endl; } void DccTransferRecvWriteCacheHandler::closeNow() // public { write( true ); // flush if ( m_transferJob ) { m_transferJob->kill(); m_transferJob = 0; } m_cacheList.clear(); delete m_cacheStream; m_cacheStream = 0; } void DccTransferRecvWriteCacheHandler::slotKIODataReq( TDEIO::Job*, TQByteArray& data ) { // We are in writeAsyncMode if there is more data to be read in from dcc if ( m_writeAsyncMode ) m_writeReady = true; else { // No more data left to read from incoming dcctransfer if ( !m_cacheList.isEmpty() ) { // once we write everything in cache, the file is complete. // This function will be called once more after this last data is written. data = m_cacheList.front(); kdDebug() << "DccTransferRecvWriteCacheHandler::slotKIODataReq(): will write " << m_cacheList.front().size() << " bytes." << endl; m_cacheList.pop_front(); } else { // finally, no data left to write or read. kdDebug() << "DTRWriteCacheHandler::slotKIODataReq(): flushing done." << endl; m_transferJob = 0; emit done(); // -> DccTransferRecv::slotLocalWriteDone() } } } void DccTransferRecvWriteCacheHandler::slotKIOResult( TDEIO::Job* job ) { Q_ASSERT( m_transferJob ); disconnect( m_transferJob, 0, 0, 0 ); m_transferJob = 0; if ( job->error() ) { TQString errorString = job->errorString(); closeNow(); emit gotError( errorString ); // -> DccTransferRecv::slotLocalGotWriteError() } } #include "dcctransferrecv.moc"