/***************************************************************************
                          kbearfilecopyjob.cpp  -  description
                             -------------------
    begin                : tis maj 14 2002
    copyright            : (C) 2003 by Björn Sahlström
    email                : kbjorn@users.sourceforge.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

//////////////////////////////////////////////////////////////////////
// Qt specific include files
#include <qtimer.h>
#include <qfile.h>
//////////////////////////////////////////////////////////////////////
// KDE specific include files
#include <kapplication.h>
#include <dcopclient.h>
#include <klocale.h>
#include <kio/slave.h>
#include <kio/job.h>
#include <kio/scheduler.h>
#include <kmimemagic.h>
#include <kprotocolinfo.h>
#include <kprotocolmanager.h>
#include <kio/renamedlg.h>
#include <kio/observer.h>
#include <kio/global.h>
#include <ktempfile.h>
#include <kdeversion.h>
#include <kdebug.h>
//////////////////////////////////////////////////////////////////////
// System specific include files
#include <sys/types.h>
#include <assert.h>
//////////////////////////////////////////////////////////////////////
// Application specific include files
#include "kbearfilecopyjob.h"
#include "connectionmanager.h"

#define KIO_ARGS QByteArray packedArgs; QDataStream stream( packedArgs, IO_WriteOnly ); stream

using namespace KBear;

class KBearFileCopyJob::KBearFileCopyJobPrivate
{
public:
    off_t m_sourceSize;
    KIO::SimpleJob *m_delJob;
	int m_destID;
	int m_sourceID;
};

KBearFileCopyJob::KBearFileCopyJob( int dID, int sID, const KURL& src,
							const KURL& dest, int permissions, bool move, bool overwrite, bool resume, bool showProgressInfo)
    : KIO::Job(showProgressInfo), m_src(src), m_dest(dest),
      m_permissions(permissions), m_move(move), m_overwrite(overwrite), m_resume(resume),
      m_totalSize(0)
{
   if (showProgressInfo && !move)
      Observer::self()->slotCopying( this, src, dest );
   else if (showProgressInfo && move)
      Observer::self()->slotMoving( this, src, dest );

    //kdDebug(7007) << "KBearFileCopyJob::KBearFileCopyJob()" << endl;
    m_moveJob = 0;
    m_copyJob = 0;
    m_getJob = 0;
    m_putJob = 0;
    d = new KBearFileCopyJobPrivate;
    d->m_delJob = 0;
    d->m_sourceSize = (off_t) -1;
	d->m_destID = dID;
	d->m_sourceID = sID;
    QTimer::singleShot(0, this, SLOT(slotStart()));
}

void KBearFileCopyJob::slotStart()
{
    if ((m_src.protocol() == m_dest.protocol()) &&
        (m_src.host() == m_dest.host()) &&
        (m_src.port() == m_dest.port()) &&
        (m_src.user() == m_dest.user()) &&
        (m_src.pass() == m_dest.pass()))
    {
       if (m_move)
       {
          m_moveJob = KIO::rename( m_src, m_dest, m_overwrite );
			ConnectionManager::getInstance()->attachJob( d->m_destID, m_moveJob );
          addSubjob( m_moveJob );
          connectSubjob( m_moveJob );
       }
       else
       {
          startCopyJob();
       }
    }
    else
    {
       if (!m_move &&
           (m_src.isLocalFile() && KProtocolInfo::canCopyFromFile(m_dest))
          )
       {
          startCopyJob(m_dest);
       }
       else if (!m_move &&
           (m_dest.isLocalFile() && KProtocolInfo::canCopyToFile(m_src))
          )
       {
          startCopyJob(m_src);
       }
       else
       {
          startDataPump();
       }
    }
}

KBearFileCopyJob::~KBearFileCopyJob()
{
    delete d;
}

void KBearFileCopyJob::setSourceSize( off_t size )
{
    d->m_sourceSize = size;
    m_totalSize = size;
}

void KBearFileCopyJob::startCopyJob()
{
    startCopyJob(m_src);
}

void KBearFileCopyJob::startCopyJob(const KURL &slave_url)
{
    //kdDebug(7007) << "KBearFileCopyJob::startCopyJob()" << endl;
    KIO_ARGS << m_src << m_dest << m_permissions << (Q_INT8) m_overwrite;
    m_copyJob = new KIO::SimpleJob(slave_url, KIO::CMD_COPY, packedArgs, false);
	ConnectionManager::getInstance()->attachJob( d->m_sourceID, m_copyJob );
    addSubjob( m_copyJob );
    connectSubjob( m_copyJob );
}

void KBearFileCopyJob::connectSubjob( KIO::SimpleJob * job )
{
    connect( job, SIGNAL(totalSize( KIO::Job*, KIO::filesize_t )),
             this, SLOT( slotTotalSize(KIO::Job*, KIO::filesize_t)) );

    connect( job, SIGNAL(processedSize( KIO::Job*, KIO::filesize_t )),
             this, SLOT( slotProcessedSize(KIO::Job*, KIO::filesize_t)) );

    connect( job, SIGNAL(percent( KIO::Job*, unsigned long )),
             this, SLOT( slotPercent(KIO::Job*, unsigned long)) );

}

void KBearFileCopyJob::slotProcessedSize( KIO::Job *, KIO::filesize_t size )
{
    emit processedSize( this, size );
    if ( size > m_totalSize ) {
        slotTotalSize( this, size ); // safety
    }
    emitPercent( size, m_totalSize );
}

void KBearFileCopyJob::slotTotalSize( KIO::Job*, KIO::filesize_t size )
{
    m_totalSize = size;
    emit totalSize( this, m_totalSize );
}

void KBearFileCopyJob::slotPercent( KIO::Job*, unsigned long pct )
{
    if ( pct > m_percent )
    {
        m_percent = pct;
        emit percent( this, m_percent );
    }
}

void KBearFileCopyJob::startDataPump()
{
    //kdDebug(7007) << "KBearFileCopyJob::startDataPump()" << endl;

    m_canResume = false;
    m_resumeAnswerSent = false;
    m_getJob = 0L; // for now
    m_putJob = KIO::put( m_dest, m_permissions, m_overwrite, m_resume, false /* no GUI */);
	ConnectionManager::getInstance()->attachJob( d->m_destID, m_putJob );
    //kdDebug(7007) << "KBearFileCopyJob: m_putJob = " << m_putJob << " m_dest=" << m_dest.prettyURL() << endl;

    // The first thing the put job will tell us is whether we can
    // resume or not (this is always emitted)
    connect( m_putJob, SIGNAL(canResume(KIO::Job *, KIO::filesize_t)),
             SLOT( slotCanResume(KIO::Job *, KIO::filesize_t)));
    connect( m_putJob, SIGNAL(dataReq(KIO::Job *, QByteArray&)),
             SLOT( slotDataReq(KIO::Job *, QByteArray&)));
    addSubjob( m_putJob );
}

void KBearFileCopyJob::slotCanResume( KIO::Job* job, KIO::filesize_t offset )
{
    if ( job == m_putJob )
    {
        kdDebug(7007) << "KBearFileCopyJob::slotCanResume from PUT job. offset=" << KIO::number(offset) << endl;
        if (offset)
        {
            KIO::RenameDlg_Result res = KIO::R_RESUME;

            if (!KProtocolManager::autoResume())
            {
                QString newPath;
                KIO::Job* job = ( !m_progressId && parentJob() ) ? parentJob() : this;
                // Ask confirmation about resuming previous transfer
                res = Observer::self()->open_RenameDlg(
                      job, i18n("File Already Exists"),
                      m_src.prettyURL(0, KURL::StripFileProtocol),
                      m_dest.prettyURL(0, KURL::StripFileProtocol),
                      (KIO::RenameDlg_Mode) (KIO::M_OVERWRITE | KIO::M_NORENAME), newPath,
                      d->m_sourceSize, offset );
            }

            if ( res == KIO::R_OVERWRITE )
              offset = 0;
            else if ( res == KIO::R_CANCEL )
            {
                m_putJob->kill(true);
                m_error = KIO::ERR_USER_CANCELED;
                emitResult();
                return;
            }
        }
        else
            m_resumeAnswerSent = true; // No need for an answer

        m_getJob = KIO::get( m_src, false, false /* no GUI */ );
			ConnectionManager::getInstance()->attachJob( d->m_sourceID, m_getJob );
        //kdDebug(7007) << "KBearFileCopyJob: m_getJob = " << m_getJob << endl;
        m_getJob->addMetaData( "errorPage", "false" );
        m_getJob->addMetaData( "AllowCompressedPage", "false" );
        // Set size in subjob. This helps if the slave doesn't emit totalSize.
        if ( d->m_sourceSize != (off_t)-1 )
            m_getJob->slotTotalSize( d->m_sourceSize );
        if (offset)
        {
            kdDebug(7007) << "Setting metadata for resume to " << (unsigned long) offset << endl;
            m_getJob->addMetaData( "resume", KIO::number(offset) );

            // Might or might not get emitted
            connect( m_getJob, SIGNAL(canResume(KIO::Job *, KIO::filesize_t)),
                     SLOT( slotCanResume(KIO::Job *, KIO::filesize_t)));
        }
        m_putJob->slave()->setOffset( offset );

        m_putJob->suspend();
        addSubjob( m_getJob );
        connectSubjob( m_getJob ); // Progress info depends on get
        m_getJob->resume(); // Order a beer

        connect( m_getJob, SIGNAL(data(KIO::Job *, const QByteArray&)),
                 SLOT( slotData(KIO::Job *, const QByteArray&)));
    }
    else if ( job == m_getJob )
    {
        // Cool, the get job said ok, we can resume
        m_canResume = true;
        kdDebug(7007) << "KBearFileCopyJob::slotCanResume from the GET job -> we can resume" << endl;

        m_getJob->slave()->setOffset( m_putJob->slave()->offset() );
    }
    else
        kdWarning(7007) << "KBearFileCopyJob::slotCanResume from unknown job=" << job
                        << " m_getJob=" << m_getJob << " m_putJob=" << m_putJob << endl;
}

void KBearFileCopyJob::slotData( KIO::Job * , const QByteArray &data)
{
   //kdDebug(7007) << "KBearFileCopyJob::slotData" << endl;
   //kdDebug(7007) << " data size : " << data.size() << endl;
   assert(m_putJob);
   m_getJob->suspend();
   m_putJob->resume(); // Drink the beer
   m_buffer = data;

   // On the first set of data incoming, we tell the "put" slave about our
   // decision about resuming
   if (!m_resumeAnswerSent)
   {
       m_resumeAnswerSent = true;
       kdDebug(7007) << "KBearFileCopyJob::slotData (first time) -> send resume answer " << m_canResume << endl;
       m_putJob->slave()->sendResumeAnswer( m_canResume );
   }
}

void KBearFileCopyJob::slotDataReq( KIO::Job * , QByteArray &data)
{
   //kdDebug(7007) << "KBearFileCopyJob::slotDataReq" << endl;
   if (!m_resumeAnswerSent && !m_getJob)
   {
       // This can't happen (except as a migration bug on 12/10/2000)
       m_error = KIO::ERR_INTERNAL;
       m_errorText = "'Put' job didn't send canResume or 'Get' job didn't send data!";
       m_putJob->kill(true);
       emitResult();
       return;
   }
   if (m_getJob)
   {
      m_getJob->resume(); // Order more beer
      m_putJob->suspend();
   }
   data = m_buffer;
   m_buffer = QByteArray();
}

void KBearFileCopyJob::slotResult( KIO::Job *job)
{
   //kdDebug(7007) << "KBearFileCopyJob this=" << this << " ::slotResult(" << job << ")" << endl;
   // Did job have an error ?
   if ( job->error() )
   {
      if ((job == m_moveJob) && (job->error() == KIO::ERR_UNSUPPORTED_ACTION))
      {
         m_moveJob = 0;
         startCopyJob();
         removeSubjob(job);
         return;
      }
      else if ((job == m_copyJob) && (job->error() == KIO::ERR_UNSUPPORTED_ACTION))
      {
         m_copyJob = 0;
         startDataPump();
         removeSubjob(job);
         return;
      }
      else if (job == m_getJob)
      {
        m_getJob = 0L;
        if (m_putJob)
          m_putJob->kill(true);
      }
      else if (job == m_putJob)
      {
        m_putJob = 0L;
        if (m_getJob)
          m_getJob->kill(true);
      }
      m_error = job->error();
      m_errorText = job->errorText();
      emitResult();
      return;
   }

   if (job == m_moveJob)
   {
      m_moveJob = 0; // Finished
   }

   if (job == m_copyJob)
   {
      m_copyJob = 0;
      if (m_move)
      {
         d->m_delJob = KIO::file_delete( m_src, false/*no GUI*/ ); // Delete source
			ConnectionManager::getInstance()->attachJob( d->m_sourceID, d->m_delJob );
         addSubjob(d->m_delJob, false);
      }
   }

   if (job == m_getJob)
   {
      m_getJob = 0; // No action required
      if (m_putJob)
         m_putJob->resume();
   }

   if (job == m_putJob)
   {
      //kdDebug(7007) << "KBearFileCopyJob: m_putJob finished " << endl;
      m_putJob = 0;
      if (m_getJob)
      {
         kdWarning(7007) << "WARNING ! Get still going on..." << endl;
         m_getJob->resume();
      }
      if (m_move)
      {
         d->m_delJob = KIO::file_delete( m_src, false/*no GUI*/ ); // Delete source
			ConnectionManager::getInstance()->attachJob( d->m_sourceID, d->m_delJob );
         addSubjob(d->m_delJob);
      }
   }

   if (job == d->m_delJob)
   {
      d->m_delJob = 0; // Finished
   }
   removeSubjob(job);
}

KBearFileCopyJob* KBearFileCopyJob::file_copy( int dID,int sID,const KURL& src, const KURL& dest, int permissions,
                             bool overwrite, bool resume, bool showProgressInfo)
{
   return new KBearFileCopyJob( dID, sID, src, dest, permissions, false, overwrite, resume, showProgressInfo );
}

KBearFileCopyJob* KBearFileCopyJob::file_move( int dID,int sID,const KURL& src, const KURL& dest, int permissions,
                             bool overwrite, bool resume, bool showProgressInfo)
{
   return new KBearFileCopyJob( dID, sID, src, dest, permissions, true, overwrite, resume, showProgressInfo );
}
#ifndef NO_INCLUDE_MOCFILES
#include "kbearfilecopyjob.moc"
#endif

