/***************************************************************************
    smb4ksynchronizer  -  This is the synchronizer of Smb4K.
                             -------------------
    begin                : Mo Jul 4 2005
    copyright            : (C) 2005-2007 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., 51 Franklin Street, Fifth Floor, Boston,   *
 *   MA  02110-1301 USA                                                    *
 ***************************************************************************/

// TQt includes
#include <tqlayout.h>
#include <tqdir.h>
#include <tqlabel.h>
#include <tqregexp.h>

// KDE includes
#include <tdemessagebox.h>
#include <kdebug.h>
#include <kdialogbase.h>
#include <klineedit.h>
#include <tdelocale.h>
#include <kprogress.h>
#include <kurlrequester.h>
#include <kguiitem.h>
#include <tdeapplication.h>

// application specific includes
#include "smb4ksynchronizer.h"
#include "smb4kdefs.h"
#include "smb4kerror.h"
#include "smb4tdeglobal.h"
#include "smb4kshare.h"
#include "smb4ksynchronizationinfo.h"
#include "smb4ksettings.h"

using namespace Smb4TDEGlobal;

bool cancel = false;


Smb4KSynchronizer::Smb4KSynchronizer( TQObject *parent, const char *name )
: TQObject( parent, name )
{
  m_proc = new TDEProcess( this, "SynchronizerProcess" );

  m_proc->setUseShell( true );

  m_working = false;

  connect( m_proc, TQ_SIGNAL( receivedStdout( TDEProcess *, char *, int ) ),
           this,   TQ_SLOT( slotReceivedStdout( TDEProcess *, char *, int ) ) );

  connect( m_proc, TQ_SIGNAL( processExited( TDEProcess* ) ),
           this,   TQ_SLOT( slotProcessExited( TDEProcess * ) ) );

  connect( m_proc, TQ_SIGNAL( receivedStderr( TDEProcess *, char *, int ) ),
           this,   TQ_SLOT( slotReceivedStderr( TDEProcess *, char *, int ) ) );

  connect( kapp,   TQ_SIGNAL( shutDown() ),
           this,   TQ_SLOT( slotShutdown() ) );
}


Smb4KSynchronizer::~Smb4KSynchronizer()
{
}


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

void Smb4KSynchronizer::synchronize( const TQString &source, const TQString &destination )
{
  if ( Smb4KSettings::rsync().isEmpty() )
  {
    Smb4KError::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;
  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().
  TQString command = "rsync --progress ";

  command.append( readRsyncOptions() );
  command.append( " " );
  command.append( TDEProcess::quote( source ) );
  command.append( " " );
  command.append( TDEProcess::quote( destination ) );

  *m_proc << command;

  // Use TDEProcess::OwnGroup instead of TDEProcess::NotifyOnExit here, because
  // this garantees that the process is indeed killed when using abort().
  // See TDEProcess docs for further information.
  m_proc->start( TDEProcess::OwnGroup, TDEProcess::AllOutput );
}


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

const TQString Smb4KSynchronizer::readRsyncOptions()
{
  TQString 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() :
                    "" );

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

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

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

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

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

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

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

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

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

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

  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() :
                  "" );

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

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

  return options;
}


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

void Smb4KSynchronizer::abort()
{
  cancel = true;

  if ( m_proc->isRunning() )
  {
    m_proc->kill();
  }
}


void Smb4KSynchronizer::slotProcessExited( TDEProcess * )
{
  m_proc->clearArguments();

  m_working = false;

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


void Smb4KSynchronizer::slotReceivedStdout( TDEProcess *, char *buf, int len )
{
  m_buffer = TQString::fromLocal8Bit( buf, len );

  Smb4KSynchronizationInfo sync_info;

  TQString partial, total, files, rate;

  if ( m_buffer[0].isSpace() && m_buffer.contains( "/s ", true ) > 0 )
  {
    partial = m_buffer.section( "%", 0, 0 ).section( " ", -1, -1 ).stripWhiteSpace();

    if ( !partial.isEmpty() )
    {
      sync_info.setIndividualProgress( partial.toInt() );
    }

    if ( m_buffer.contains( "to-check=" ) > 0 )
    {
      // Newer versions of rsync:
      TQString tmp = m_buffer.section( "to-check=", 1, 1 ).section( ")", 0, 0 ).stripWhiteSpace();

      if ( !tmp.isEmpty() )
      {
        double tmp_total = tmp.section( "/", 1, 1 ).stripWhiteSpace().toInt();
        double tmp_done = tmp.section( "/", 0, 0 ).stripWhiteSpace().toInt();
        double tmp_percent = ((tmp_total-tmp_done)/tmp_total)*100;

        total = TQString( "%1" ).arg( tmp_percent ).section( ".", 0, 0 ).stripWhiteSpace();
      }
    }
    else
    {
      // Older versions of rsync:
      total = m_buffer.section( " (", 1, 1 ).section( ",", 1, 1 ).section( "%", 0, 0 ).section( ".", 0, 0 ).stripWhiteSpace();
    }

    if ( !total.isEmpty() )
    {
      sync_info.setTotalProgress( total.toInt() );
    }

    if ( m_buffer.contains( "xfer#" ) > 0 )
    {
      // Newer versions of rsync:
      files = m_buffer.section( "xfer#", 1, 1 ).section( ",", 0, 0 ).stripWhiteSpace();
    }
    else
    {
      // Older versions of rsync:
      files = m_buffer.section( " (", 1, 1 ).section( ",", 0, 0 ).stripWhiteSpace();
    }

    if ( !files.isEmpty() )
    {
      sync_info.setProcessedFileNumber( files.toInt() );
      sync_info.setTotalFileNumber( m_total_files.toInt() );
    }

    rate = m_buffer.section( "/s ", 0, 0 ).section( " ", -1, -1 ).stripWhiteSpace();

    if ( !rate.isEmpty() )
    {
      rate.append( "/s" );
      rate.insert( rate.length() - 4, " " );

      sync_info.setTransferRate( rate );
    }

    m_buffer = TQString();
  }
  else if ( !m_buffer[0].isSpace() && m_buffer.endsWith( "\n" ) && m_buffer.contains( "/s ", true ) == 0 )
  {
    sync_info.setText( m_buffer.stripWhiteSpace() );

    if ( m_buffer.contains( "files to consider" ) != 0 )
    {
      m_total_files = m_buffer.section( " files to consider", 0, 0 ).section( " ", -1, -1 ).stripWhiteSpace();

      sync_info.setTotalFileNumber( m_total_files.toInt() );
    }

    m_buffer = TQString();
  }

  emit progress( sync_info );
}



void Smb4KSynchronizer::slotReceivedStderr( TDEProcess *, char *buf, int len )
{
  TQString error_message = TQString::fromLocal8Bit( buf, len );

  // At least under Debian unstable (20051216), rsync emits an error
  // when you kill the process (using SIGTERM). Pressing the cancel
  // button will exactly do this. Thus, we need to exclude this occasion
  // from here.
  if ( !cancel && error_message.contains( "rsync error:", true ) != 0 )
  {
    // 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();
    Smb4KError::error( ERROR_SYNCHRONIZING, TQString(), error_message );
  }
  else
  {
    cancel = false;
  }
}


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

#include "smb4ksynchronizer.moc"
