/*
  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 <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 "dcctransferrecv.h"
#include "dcccommon.h"
#include "channel.h"
#include "dcctransfermanager.h"
#include "konversationapplication.h"
#include "connectionmanager.h"
#include "server.h"

#include <kdebug.h>
#include <tdefileitem.h>
#include <kiconloader.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <kserversocket.h>
#include <kstandarddirs.h>
#include <kstreamsocket.h>
#include <kdirselectdialog.h>
#include <kuser.h>

#include <tdeio/job.h>
#include <tdeio/jobclasses.h>
#include <tdeio/netaccess.h>


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, TQ_SIGNAL( timeout() ), this, TQ_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( "go-down", TDEIcon::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( "<b>Cannot create the folder.</b><br>"
            "Folder: %1<br>" )
            .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 TDEIO instance" ) );
        return;
    }

    connect( transferJob, TQ_SIGNAL( canResume( TDEIO::Job*, TDEIO::filesize_t ) ), this, TQ_SLOT( slotLocalCanResume( TDEIO::Job*, TDEIO::filesize_t ) ) );
    connect( transferJob, TQ_SIGNAL( result( TDEIO::Job* ) ),                     this, TQ_SLOT( slotLocalGotResult( TDEIO::Job* ) ) );
    connect( transferJob, TQ_SIGNAL( dataReq( TDEIO::Job*, TQByteArray& ) ),       this, TQ_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<TDEIO::TransferJob*>( job );

        disconnect( transferJob, 0, 0, 0 );
        transferJob->kill();

        if ( KonversationApplication::instance()->getDccTransferManager()->isLocalFileInWritingProcess( m_fileURL ) )
        {
            askAndPrepareLocalKio( i18n( "<b>The file is used by another transfer.</b><br>"
                "%1<br>" )
                .arg( m_fileURL.prettyURL() ),
                DccResumeDialog::RA_Rename | DccResumeDialog::RA_Cancel,
                DccResumeDialog::RA_Rename );
        }
        else if ( Preferences::dccAutoResume() )
        {
            prepareLocalKio( false, true, size );
        }
        else
        {
            askAndPrepareLocalKio( i18n( "<b>A partial file exists.</b><br>"
                "%1<br>"
                "Size of the partial file: %2 bytes<br>" )
                .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<TDEIO::TransferJob*>( 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( "<b>The file already exists.</b><br>"
                "%1<br>" )
                .arg( m_fileURL.prettyURL() ),
                DccResumeDialog::RA_Overwrite | DccResumeDialog::RA_Rename | DccResumeDialog::RA_Cancel,
                DccResumeDialog::RA_Overwrite );
            break;
        default:
            askAndPrepareLocalKio( i18n( "<b>Could not open the file.<br>"
                "Error: %1</b><br>"
                "%2<br>" )
                .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<TDEIO::TransferJob*>( job );

    disconnect( transferJob, 0, 0, 0 );           // WriteCacheHandler will control the job after this

    m_writeCacheHandler = new DccTransferRecvWriteCacheHandler( transferJob );

    connect( m_writeCacheHandler, TQ_SIGNAL( done() ),                     this, TQ_SLOT( slotLocalWriteDone() )                     );
    connect( m_writeCacheHandler, TQ_SIGNAL( gotError( const TQString& ) ), this, TQ_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, TQ_SIGNAL( connected( const KResolverEntry& ) ), this, TQ_SLOT( startReceiving() )     );
    connect( m_recvSocket, TQ_SIGNAL( gotError( int ) ),                    this, TQ_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, TQ_SIGNAL( readyAccept() ),   this, TQ_SLOT( slotServerSocketReadyAccept() ) );
   connect( m_serverSocket, TQ_SIGNAL( gotError( int ) ), this, TQ_SLOT( slotServerSocketGotError( int ) ) );

    return true;
}

void DccTransferRecv::slotServerSocketReadyAccept()
{
    //stopConnectionTimer()

    m_recvSocket = static_cast<KNetwork::KStreamSocket*>( m_serverSocket->accept() );
    if ( !m_recvSocket )
    {
        failed( i18n( "Could not accept the connection. (Socket Error)" ) );
        return;
    }

    connect( m_recvSocket, TQ_SIGNAL( gotError( int ) ), this, TQ_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, TQ_SIGNAL( readyRead() ),                        this, TQ_SLOT( readData() )              );
    connect( m_recvSocket, TQ_SIGNAL( readyWrite() ),                       this, TQ_SLOT( sendAck() )               );
    connect( m_recvSocket, TQ_SIGNAL( closed() ),                           this, TQ_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( "TDEIO 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,          TQ_SIGNAL( dataFinished() ),                    m_transferJob, TQ_SLOT( slotFinished() )                           );
    connect( m_transferJob, TQ_SIGNAL( dataReq( TDEIO::Job*, TQByteArray& ) ), this,          TQ_SLOT( slotKIODataReq( TDEIO::Job*, TQByteArray& ) ) );
    connect( m_transferJob, TQ_SIGNAL( result( TDEIO::Job* ) ),               this,          TQ_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 tdeio 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"
