/***************************************************************************
    smb4ksynchronizer  -  This is the synchronizer of Smb4K.
                             -------------------
    begin                : Mo Jul 4 2005
    copyright            : (C) 2005-2008 by Alexander Reinholdt
    email                : dustpuppy@users.berlios.de
 ***************************************************************************/

/***************************************************************************
 *   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.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful, but   *
 *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
 *   General Public License for more details.                              *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,   *
 *   MA  02111-1307 USA                                                    *
 ***************************************************************************/

// Qt includes
#include <QStringList>

// KDE includes
#include <kmessagebox.h>
#include <kdebug.h>
#include <klocale.h>
#include <kapplication.h>
#include <kshell.h>

// application specific includes
#include "smb4ksynchronizer.h"
#include "smb4kdefs.h"
#include "smb4kcoremessage.h"
#include "smb4kglobal.h"
#include "smb4kshare.h"
#include "smb4ksynchronizationinfo.h"
#include "smb4ksettings.h"



using namespace Smb4KGlobal;

Smb4KSynchronizer::Smb4KSynchronizer( QObject *parent ) : QObject( parent )
{
  m_proc = new KProcess( this );

  m_working = false;
  m_aborted = false;
  m_process_error = (QProcess::ProcessError)(-1);

  connect( m_proc, SIGNAL( finished( int, QProcess::ExitStatus ) ),
           this,   SLOT( slotProcessFinished( int, QProcess::ExitStatus ) )  );

  connect( m_proc, SIGNAL( error( QProcess::ProcessError ) ),
           this,   SLOT( slotProcessError( QProcess::ProcessError ) ) );

  connect( m_proc, SIGNAL( readyReadStandardOutput() ),
           this,   SLOT( slotReadStandardOutput() ) );

  connect( m_proc, SIGNAL( readyReadStandardError() ),
           this,   SLOT( slotReadStandardError() ) );

  connect( kapp,   SIGNAL( aboutToQuit() ),
           this,   SLOT( slotAboutToQuit() ) );
}


Smb4KSynchronizer::~Smb4KSynchronizer()
{
}


/****************************************************************************
   Synchronizes a share with a local copy or vice versa.
****************************************************************************/

void Smb4KSynchronizer::synchronize( const QString &source, const QString &destination )
{
  if ( Smb4KSettings::rsync().isEmpty() )
  {
    Smb4KCoreMessage::error( ERROR_COMMAND_NOT_FOUND, "rsync" );
    return;
  }

  // FIXME: Do not stop here but buffer the requests and
  // process them if the previous process finished.
  if ( isRunning() )
  {
    return;
  }

  m_working = true;
  m_aborted = false;

  emit state( SYNCHRONIZER_START );
  emit start();

  // We will only define the stuff we urgently need
  // here. The options that actually influence rsync's
  // behavior will be retrieved by readRsyncOptions().
  QString command = "rsync --progress ";

  command.append( readRsyncOptions() );
  command.append( " " );
  command.append( KShell::quoteArg( source ) );
  command.append( " " );
  command.append( KShell::quoteArg( destination ) );

  m_proc->setShellCommand( command );
  m_proc->setOutputChannelMode( KProcess::SeparateChannels );
  m_proc->start();
}


/****************************************************************************
   Reads the options that should be used with rsync
****************************************************************************/

const QString Smb4KSynchronizer::readRsyncOptions()
{
  QString options;

  if ( Smb4KSettings::archiveMode() )
  {
    options.append( " --archive" );
  }
  else
  {
    options.append( Smb4KSettings::recurseIntoDirectories() ?
                    " --recursive" :
                    "" );

    options.append( Smb4KSettings::preserveSymlinks() ?
                    " --links" :
                    "" );

    options.append( Smb4KSettings::preservePermissions() ?
                    " --perms" :
                    "" );

    options.append( Smb4KSettings::preserveTimes() ?
                    " --times" :
                    "" );

    options.append( Smb4KSettings::preserveGroup() ?
                    " --group" :
                    "" );

    options.append( Smb4KSettings::preserveOwner() ?
                    " --owner" :
                    "" );

    options.append( Smb4KSettings::preserveDevicesAndSpecials() ?
                    " --devices --specials" : // alias: -D
                    "" );
  }

  options.append( Smb4KSettings::relativePathNames() ?
                  " --relative" :
                  "" );

  options.append( Smb4KSettings::omitDirectoryTimes() ?
                  " --omit-dir-times" :
                  "" );

  options.append( Smb4KSettings::noImpliedDirectories() ?
                  " --no-implied-dirs" :
                  "" );

  options.append( Smb4KSettings::updateTarget() ?
                  " --update" :
                  "" );

  options.append( Smb4KSettings::updateInPlace() ?
                  " --inplace" :
                  "" );

  options.append( Smb4KSettings::transferDirectories() ?
                  " --dirs" :
                  "" );

  options.append( Smb4KSettings::transformSymlinks() ?
                  " --copy-links" :
                  "" );

  options.append( Smb4KSettings::transformUnsafeSymlinks() ?
                  " --copy-unsafe-links" :
                  "" );

  options.append( Smb4KSettings::ignoreUnsafeSymlinks() ?
                  " --safe-links" :
                  "" );

  options.append( Smb4KSettings::preserveHardLinks() ?
                  " --hard-links" :
                  "" );

  options.append( Smb4KSettings::keepDirectorySymlinks() ?
                  " --keep-dirlinks" :
                  "" );

  options.append( Smb4KSettings::deleteExtraneous() ?
                  " --delete" :
                  "" );

  options.append( Smb4KSettings::removeSourceFiles() ?
                  " --remove-source-files" :
                  "" );

  options.append( Smb4KSettings::deleteBefore() ?
                  " --delete-before" :
                  "" );

  options.append( Smb4KSettings::deleteDuring() ?
                  " --delete-during" :
                  "" );

  options.append( Smb4KSettings::deleteAfter() ?
                  " --delete-after" :
                  "" );

  options.append( Smb4KSettings::deleteExcluded() ?
                  " --delete-excluded" :
                  "" );

  options.append( Smb4KSettings::ignoreErrors() ?
                  " --ignore-errors" :
                  "" );

  options.append( Smb4KSettings::forceDirectoryDeletion() ?
                  " --force" :
                  "" );

  options.append( Smb4KSettings::copyFilesWhole() ?
                  " --whole-file" :
                  "" );

  options.append( Smb4KSettings::efficientSparseFileHandling() ?
                  " --sparse" :
                  "" );

  options.append( Smb4KSettings::oneFileSystem() ?
                  " --one-file-system" :
                  "" );

  options.append( Smb4KSettings::updateExisting() ?
                  " --existing" :
                  "" );

  options.append( Smb4KSettings::ignoreExisting() ?
                  " --ignore-existing" :
                  "" );

  options.append( Smb4KSettings::delayUpdates() ?
                  " --delay-updates" :
                  "" );

  options.append( Smb4KSettings::compressData() ?
                  " --compress" :
                  "" );

  if ( Smb4KSettings::makeBackups() )
  {
    options.append( " --backup" );

    options.append( Smb4KSettings::useBackupDirectory() ?
                    " --backup-dir="+Smb4KSettings::backupDirectory().path() :
                    "" );

    options.append( Smb4KSettings::useBackupSuffix() ?
                    " --suffix="+Smb4KSettings::backupSuffix() :
                    "" );
  }

  options.append( Smb4KSettings::useMaximumDelete() ?
                  " --max-delete="+QString( "%1" ).arg( Smb4KSettings::maximumDeleteValue() ) :
                  "" );

  options.append( Smb4KSettings::useChecksum() ?
                  " --checksum" :
                  "" );

  options.append( Smb4KSettings::useBlockSize() ?
                  " --block-size="+QString( "%1" ).arg( Smb4KSettings::blockSize() ) :
                  "" );

  options.append( Smb4KSettings::useChecksumSeed() ?
                  " --checksum-seed="+QString( "%1" ).arg( Smb4KSettings::checksumSeed() ) :
                  "" );

  if ( !Smb4KSettings::customFilteringRules().isEmpty() )
  {
    options.append( " "+Smb4KSettings::customFilteringRules() );
  }

  options.append( Smb4KSettings::useMinimalTransferSize() ?
                  " --min-size="+QString( "%1" ).arg( Smb4KSettings::minimalTransferSize() )+"K" :
                  "" );

  options.append( Smb4KSettings::useMaximalTransferSize() ?
                  " --max-size="+QString( "%1" ).arg( Smb4KSettings::maximalTransferSize() )+"K" :
                  "" );

  if ( Smb4KSettings::keepPartial() )
  {
    options.append( " --partial" );

    options.append( Smb4KSettings::usePartialDirectory() ?
                    " --partial-dir="+Smb4KSettings::partialDirectory().path() :
                    "" );
  }

  options.append( Smb4KSettings::useCVSExclude() ?
                  " --cvs-exclude" :
                  "" );

  options.append( Smb4KSettings::useFFilterRule() ?
                  " -F" :
                  "" );

  options.append( Smb4KSettings::useFFFilterRule() ?
                  " -F -F" :
                  "" );

  options.append( Smb4KSettings::useExcludePattern() ?
                  " --exclude="+Smb4KSettings::excludePattern() :
                  "" );

  options.append( Smb4KSettings::useExcludeFrom() ?
                  " --exclude-from="+Smb4KSettings::excludeFrom().path() :
                  "" );

  options.append( Smb4KSettings::useIncludePattern() ?
                  " --include="+Smb4KSettings::includePattern() :
                  "" );

  options.append( Smb4KSettings::useIncludeFrom() ?
                  " --include-from="+Smb4KSettings::includeFrom().path() :
                  "" );

  return options;
}


/////////////////////////////////////////////////////////////////////////////
//   SLOT IMPLEMENTATIONS
/////////////////////////////////////////////////////////////////////////////

void Smb4KSynchronizer::abort()
{
  if ( m_proc->state() == KProcess::Running )
  {
    m_proc->kill();
  }

  m_aborted = true;
}


void Smb4KSynchronizer::slotProcessFinished( int /* exitCode */, QProcess::ExitStatus exitStatus )
{
  if ( exitStatus != QProcess::NormalExit && !m_aborted )
  {
    if ( m_process_error != -1 )
    {
      Smb4KCoreMessage::processError( ERROR_PROCESS_ERROR, m_process_error );
    }
    else
    {
      Smb4KCoreMessage::processError( ERROR_PROCESS_EXIT, m_process_error );
    }
  }
  else
  {
    // Do nothing
  }

  m_proc->clearProgram();

  m_process_error = (QProcess::ProcessError)(-1);
  m_aborted = false;
  m_working = false;

  emit finished();
  emit state( SYNCHRONIZER_STOP );
}


void Smb4KSynchronizer::slotProcessError( QProcess::ProcessError errorCode )
{
  m_process_error = errorCode;
}


void Smb4KSynchronizer::slotReadStandardOutput()
{
  QStringList stdout_output = QString::fromLocal8Bit( m_proc->readAllStandardOutput(), -1 ).split( "\n", QString::SkipEmptyParts );

  for ( int i = 0; i < stdout_output.size(); ++i )
  {
    Smb4KSynchronizationInfo syncInfo;

    if ( stdout_output.at( i )[0].isSpace() )
    {
      // This is the progress information

      // Get transfer rate
      if ( stdout_output.at( i ).contains( "/s ", Qt::CaseSensitive ) )
      {
        QString rate = stdout_output.at( i ).section( "/s ", 0, 0 ).section( " ", -1, -1 ).trimmed();
        rate.append( "/s" );
        rate.insert( rate.length() - 4, " " );

        syncInfo.setTransferRate( rate );
      }
      else
      {
        // No transfer rate available
      }

      // Get the transfer progress of the file
      if ( stdout_output.at( i ).contains( "% ", Qt::CaseSensitive ) )
      {
        QString progress = stdout_output.at( i ).section( "% ", 0, 0 ).section( " ", -1, -1 ).trimmed();

        syncInfo.setCurrentProgress( progress.toInt() );
      }
      else
      {
        // No transfer progress available
      }

      // Get the overall transfer progress
      if ( stdout_output.at( i ).contains( " to-check=" ) )
      {
        QString tmp = stdout_output.at( i ).section( " to-check=", 1, 1 ).section( ")", 0, 0 ).trimmed();

        double files_to_process = tmp.section( "/", 0, 0 ).trimmed().toInt();
        double total_files      = tmp.section( "/", 1, 1 ).trimmed().toInt();
        double progress         = ((total_files - files_to_process) * 100) / total_files;

        syncInfo.setTotalFileNumber( total_files );
        syncInfo.setTotalProgress( progress );
      }
      else
      {
        // No overall transfer progress can be determined
      }

      // Get the number of processed files
      if ( stdout_output.at( i ).contains( "xfer#", Qt::CaseSensitive ) )
      {
        int processed_files = stdout_output.at( i ).section( "xfer#", 1, 1 ).section( ",", 0, 0 ).trimmed().toInt();

        syncInfo.setProcessedFileNumber( processed_files );
      }
      else
      {
        // No number of processed files available
      }
    }
    else
    {
      // This is the file name or some other important information
      syncInfo.setText( stdout_output.at( i ).trimmed() );
    }

    emit progress( &syncInfo );
  }

  // Process pending events.
  if ( kapp )
  {
    kapp->processEvents( QEventLoop::AllEvents, 25 /* msec */ );
  }
  else
  {
    // Do nothing
  }
}


void Smb4KSynchronizer::slotReadStandardError()
{
  QString stderr_output = QString::fromLocal8Bit( m_proc->readAllStandardError(), -1 ).trimmed();

  // Avoid reporting an error if the user killed the process by
  // calling the abort() function.
  if ( !m_aborted && stderr_output.contains( "rsync error:" ) )
  {
    // Cancel the whole synchronization in case an error occurred.
    // If we don't do it, the user might be flooded with error messages
    // especially if you try to synchronize a local copy with a remote
    // share that is mounted read-only.
    abort();
    Smb4KCoreMessage::error( ERROR_SYNCHRONIZING, QString(), stderr_output );
  }
  else
  {
    // Do nothing
  }
}


void Smb4KSynchronizer::slotAboutToQuit()
{
  abort();
}

#include "smb4ksynchronizer.moc"
