package RoPkg::Simba;

use warnings;
use strict;

use vars qw($VERSION $cfg $SimbaURL);

$VERSION='0.8.4';
$SimbaURL='http://simba.packages.ro';

use Data::Dumper;

use DBI;
use SQL::Abstract;
use Module::Pluggable instantiate => 'new';
use English      qw( -no_match_vars );
use Scalar::Util qw(blessed);
use Log::Log4perl;
use Number::Format;

use RoPkg::DB;
use RoPkg::Exceptions;
use RoPkg::Rsync::LogParser;

use RoPkg::Simba::Exclude;
use RoPkg::Simba::Command;
use RoPkg::Simba::Mirror;

use RoPkg::Simba::Mirrors;
use RoPkg::Simba::Excludes;
use RoPkg::Simba::Commands;
use RoPkg::Simba::Exceptions;

sub new {
  my ($class, %opt) = @_;
  my $self;

  if (!defined $opt{cfgFile}) {
    Param::Missing->throw('Configuration file not present in param list');
  }
  if ( ! -f $opt{cfgFile}) {
    File::NotFound->throw('Configuration file (' . $opt{cfgFile} . ') not found');
  }

  $self = bless { %opt }, $class;

  eval {
    $self->_init();
  };

  if ( Exception::Class->caught('DB::Connect') ) {
    print $EVAL_ERROR->message,$RS;
    exit 1;
  }

  if ( $ERRNO ) {
    if (ref $ERRNO) {
      $ERRNO->rethrow();
    }
    else {
      File::Create->throw(
        error    => $EVAL_ERROR,
        pkg_name => 'RoPkg::Simba',
      );
    }
  }

  $self->{log}->debug('Initialization is complete');
  return $self;
}

sub VERSION {
  return $VERSION;
}

sub SimbaURL {
  return $SimbaURL;
}

######################################
### Initialization methods - BEGIN ###
######################################

sub _init_log {
  my ($self) = @_;
  my $log_def;

  if ( !$self->{cfg}->{log} ) {
    Param::Missing->throw(
      error    => 'log section not found in config',
      pkg_name => ref $self
    );
  }
  
  $log_def = $self->{cfg}->{log};
  Log::Log4perl::init(\$log_def);
  
  $self->{log} = Log::Log4perl->get_logger('RoPkg.Simba');
  $self->{log}->debug('Simba v',$VERSION,' started');

  return 1;
}

sub _init {
  my ($self) = @_;

  if (! -f $self->{cfgFile}) {
    print 'Configuration file ',$self->{cfgFile},' does not exists (or is not a file)',$RS;
    exit 1;
  }

  eval {
    $self->{cfg} = do $self->{cfgFile};
  };

  if ( $ERRNO ) {
    print 'Error in configuration file',$ERRNO,$RS;
    exit 1;
  }

  $self->_init_log();

  $self->{log}->debug('Creating SQL::Abstract object');
  $self->{sa}  = SQL::Abstract->new();
  $self->_init_db();

  $self->{log}->debug('Creating Mirrors, Commands, Excludes and LogParser objects');
  $self->{mirrors}  = new RoPkg::Simba::Mirrors( dbo => $self->{db}, dbo_method => 'db_mirrors');
  $self->{commands} = new RoPkg::Simba::Commands(dbo => $self->{db}, dbo_method => 'db_mirrors');
  $self->{excludes} = new RoPkg::Simba::Excludes(dbo => $self->{db}, dbo_method => 'db_mirrors');
  $self->{lp}       = new RoPkg::Rsync::LogParser(type => 'client');

  return 1;
}

sub _init_db {
  my ($self) = @_;
  my $db_cfg = $self->{cfg}->{db};

  $self->{log}->debug('Creating RoPkg::DB object');
  $self->{db} = new RoPkg::DB();
  $self->{log}->debug('Connecting to database');
  $self->{db}->Add($db_cfg->{dsn}, $db_cfg->{user}, $db_cfg->{pass}, 'mirrors');
  $self->{log}->debug('Connected to db');

  return 1;
}

######################################
###  Initialization methods -  END ###
######################################

################################
### Database methods - BEGIN ###
################################

sub dbh {
  my ($self) = @_;
  
  if ( !blessed($self) ) {
    OutsideClass->throw(
      error    => 'Called outside class instance',
      pkg_name => 'RoPkg::Simba',
    );
  }

  $self->{log}->debug('Returning database handler');
  return $self->{db}->db_mirrors;
}

sub dbo {
  my ($self) = @_;

  if (!blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  $self->{log}->debug('Returning database object');
  return $self->{db};
}

sub dbo_method {
  my ($self) = @_;

  if (!blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  $self->{log}->debug('Returning database object method name');
  return 'db_mirrors';
}

sub CheckDB {
  my ($self) = @_;
  my $ret_value = 0;

  my $mirrors_no  = $self->MirrorsNo();
  my $commands_no = $self->CommandsNo();

  if ($mirrors_no == 0) {
    print 'Whoops ! No mirrors found in database', $RS;
    $ret_value++;
  }

  if ($commands_no == 0) {
    print 'Whoops ! No commands found in database', $RS;
    $ret_value++;
  }

  return $ret_value;
}

sub MirrorsNo {
  my ($self, $fields) = @_;

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  $self->{log}->debug('Returning the number of mirrors for ',Dumper($fields));
  return $self->{mirrors}->Count($fields);
}

sub Mirrors {
  my ($self, $fields, $orderby) = @_;

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }
  
  $self->{log}->debug('Returning the mirrors list for ',Dumper($fields));
  return $self->{mirrors}->Get($fields, $orderby);
}

sub CommandsNo {
  my ($self, $fields) = @_;

  if (!blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }
  
  $self->{log}->debug('Returning the number of commands for ',Dumper($fields));
  return $self->{commands}->Count($fields);
}

sub Commands {
  my ($self, $fields, $orderby) = @_;

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }
  
  $self->{log}->debug('Returning the commands for ',Dumper($fields));
  return $self->{commands}->Get($fields, $orderby);
}

sub ExcludeNo {
  my ($self, $fields) = @_;
  
  if (!blessed($self)) {
    OutsideClass->throw('Called outside class instance');
  }
  
  $self->{log}->debug('Returning the number of excludes for ',Dumper($fields));
  return $self->{excludes}->Count($fields);
}

sub Excludes {
  my ($self, $fields, $orderby) = @_;
  my @elist;
  
  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }
  
  $self->{log}->debug('Returning the excludes for ',Dumper($fields));
  return $self->{excludes}->Get($fields, $orderby);
}

# Load a mirror and the related objects from the database.
# Returns a list (mirror, command, exclude) of objects
sub LoadMirror {
  my ($self, $fields) = @_;
  my ($mirror, $command, $excludes);

  if ( !blessed($self) ) {
    OutsideClass->throw('Called outside class instance');
  }

  $self->{log}->debug('Have to load a mirror with the following fields ',Dumper($fields));

  $self->{log}->debug('Loading mirror ',Dumper($fields));
  $mirror   = $self->_lac_mirror($fields);
  $self->{log}->debug('Loading command for mirror ',$mirror->id,q{-},$mirror->Name);
  $command  = $self->_lac_cmd($mirror);

  $self->{log}->debug('Loading exclude for mirror ',$mirror->id,q{-},$mirror->Name);
  eval {
    $excludes = $self->_lac_exclude($mirror);
  };

  if (my $e = Exception::Class->caught('DB::NoResults')) {
    $self->{log}->debug('No excludes found');
    if ( $e->pkg_name eq 'RoPkg::Simba::Excludes' ) {
      $excludes = new RoPkg::Simba::Exclude(
                        dbo        => $self->dbo,
                        dbo_method => $self->dbo_method
                      );
    }
    else {
      $self->{log}->error('Whoops ! Something very bad happend ',Dumper($EVAL_ERROR));
      $EVAL_ERROR->rethrow;
    }
  }

  return ($mirror, $command, $excludes);
}

### Load and check (lac) mirror from database...
sub _lac_mirror {
  my ($self, $fields) = @_;
  my @mirrors;
  my $mirror;

  @mirrors = $self->Mirrors($fields);
  $self->{log}->debug('Found ',scalar @mirrors,' mirrors in database');
  
  if ( $#mirrors > 0 ) {
    Mirror::Many->throw('More than one mirror matched your query');
  }

  $mirror = $mirrors[0];

  foreach(qw(SyncMethod SyncSource SyncSourceModule LocalDir CommandID)) {
    my $m = $_;

    if ( ! $mirror->$m ) {
      $self->{log}->error('Mirror ',$mirror->Name,' has ',$m,' undefined');
      Mirror::Config->throw(
        error    => 'Mirror has undefined field: ' . $m,
        pkg_name => ref $self
      );
    }
  }

  if ( ! $mirror->SyncMethod ) {
    Mirror::Config->throw('Mirror has no sync method defined');
  }
  if ( ! $mirror->SyncSource ) {
    Mirror::Config->throw('Mirror has no sync source defined');
  }
  if ( ! $mirror->SyncSourceModule ) {
    Mirror::Config->throw('Mirror has no sync source module defined');
  }
  if ( ! $mirror->LocalDir ) {
    Mirror::Config->throw('Mirror has no local directory defined');
  }
  if ( ! $mirror->CommandID ) {
    Mirror::Config->throw('Mirror has no command defined');
  }

  return $mirror;
}

### Load and check command for mirror from database...
sub _lac_cmd {
  my ($self, $mirror) = @_;
  my ($command, @commands);

  @commands = $self->Commands( { id => $mirror->CommandID } );
  if ( $#commands > 0 ) {
    Mirror::Many->throw('Sorry. More than one command matched your query');
  }
  $command = $commands[0];

  if ( ! -f $command->Path ) {
    Command::FileNotFound->throw('Could not find ' . $command->Path);
  }

  return $command;
}

### Load and check exclude list from the database...
sub _lac_exclude {
  my ($self, $mirror) = @_;
  my (@excludes);

  @excludes = $self->Excludes( {
                       MirrorID  => $mirror->id,
                       CommandID => $mirror->CommandID, }
                     );

  if ( $#excludes > 0 ) {
    Mirror::Many->throw('Sorry. More than one exclude matched your query');
  }

  return $excludes[0];
}


################################
###  Database methods -  END ###
################################

#############################
### Run functions - BEGIN ###
#############################

sub _get_auth_filename {
  my ($self, $mirror) = @_;

  return RoPkg::Utils::AddSlash($self->{cfg}->{general}->{tmpdir}) . $mirror->id . '.pass';
}

sub _populate_auth_file {
  my ($self, $mirror, $filename) = @_;
  my ($old_umask, $fh);

  $old_umask = umask;
  umask 0077;

  if (! open $fh, '>', $filename) {
    File::Create->throw('Could not create ' . $filename);
  }
  
  print $fh $mirror->SyncSourcePass,$RS;
  
  close $fh;
  umask $old_umask;
  return $filename;
}

sub _mirror_req_auth {
  my ($self, $mirror) = @_;

  return 1 if ( $mirror->SyncSourceUser and
                $mirror->SyncSourcePass and
                $mirror->SyncSourceUser !~ m{^\s*$}xm and
                $mirror->SyncSourcePass !~ m{^\s*$}xm);

  return 0;
}

sub _build_rsync_cmd {
  my ($self, $mirror, $command, $exclude) = @_;
  my $cmd_str;
  my ($req_auth, $auth_file);

  $cmd_str = sprintf '%s %s',
               $command->Path,
               $command->Args;

  $req_auth  = $self->_mirror_req_auth($mirror);
  if ( $req_auth ) {
    $auth_file = $self->_get_auth_filename($mirror);
    $self->_populate_auth_file($mirror, $auth_file);
    $cmd_str .= ' --password-file=' . $auth_file;
  }

  foreach($exclude->GetItems) {
    $cmd_str .= sprintf " --exclude='%s' ",$_;
  }
    
  if ( $mirror->SyncSource =~ m{rsync:\/\/.*}xm ) {
    my $sync_source = $mirror->SyncSource;

    $sync_source =~ s/rsync:\/\///xm;
    $cmd_str .= sprintf ' rsync://%s%s/%s %s',
        ($req_auth ? ($mirror->SyncSourceUser . q{@}) : q{}),
        $mirror->SyncSource,
        $mirror->SyncSourceModule,
        $mirror->LocalDir;
  } else {
    $cmd_str .= sprintf ' %s%s::%s %s',
        ($req_auth ? ($mirror->SyncSourceUser . q{@}) : q{}),
        $mirror->SyncSource,
        $mirror->SyncSourceModule,
        $mirror->LocalDir;
  }

  return $cmd_str;
}

sub _append_logs {
  my ($self, $mirror, $cmd) = @_;

  $cmd .= ' 2>' . $self->_get_err_file($mirror) . ' >' . $self->_get_log_file($mirror);
  return $cmd;
}

sub _build_cmd_string
{
  my ($self, $mirror, $command, $exclude) = @_;
  
  if ( $mirror->SyncMethod eq 'rsync' ) {
    my $cmd_string;

    $cmd_string = $self->_build_rsync_cmd($mirror, $command, $exclude);
    $cmd_string = $self->_append_logs($mirror, $cmd_string);
    
    return $cmd_string;
  }
  else {
    print 'Unknown sync method: ',$mirror->SyncMethod,$RS;
  }

  return q{};
}


sub _compute_update_params
{
  my ($self, $mirror) = @_;

  if ($mirror->SyncMethod eq 'rsync') {
   
    $self->{log}->debug('Computing sync values (speed, files, etc)');
    $self->{lp}->Probes(@{ $self->{cfg}->{mProbes}});
    $self->{lp}->Parse();

    $mirror->LastUpdated(time);
    $mirror->LastUpdateSpeed(scalar $self->{lp}->Speed);
    $mirror->LastUpdateBytes(scalar $self->{lp}->TransfData);
    $mirror->LastUpdateFilesNo(scalar $self->{lp}->Files);
    $mirror->Size($self->{lp}->Size);
    $self->{log}->debug('compute complete');
  }
  else {
    $self->{log}->error('Unknown sync method (',$mirror->SyncMethod,')');
  }

  return 1;
}

sub _get_log_file {
  my ($self, $mirror) = @_;

  return sprintf '%s%s.log',
           $self->{cfg}->{general}->{tmpdir},
           $mirror->id
         ;
}

sub _get_err_file {
  my ($self, $mirror) = @_;

  return sprintf '%s%s.err',
           $self->{cfg}->{general}->{tmpdir},
           $mirror->id
         ;
}
  
sub _create_mirror_log_files {
  my ($self, $mirror) = @_;

  $self->{log}->debug('Trying to create the log file');
  RoPkg::Utils::CreateFile($self->_get_log_file($mirror));

  $self->{log}->debug('Trying to create the error file');
  RoPkg::Utils::CreateFile($self->_get_err_file($mirror));

  return 1;
}

sub _signal_in_progress {
  my ($self, $mirror) = @_;
  my $lock_file;

  $lock_file = sprintf '%s.%s',
      RoPkg::Utils::DelSlash($mirror->LocalDir),
      $self->{cfg}->{general}->{lockfile};

  $self->{log}->debug('The lockfile is ',$lock_file);

  if ( -f $lock_file ) {
    $self->{log}->info('Whops ! Lock file found. Aborting...');
    Mirror::InProgress->throw('Mirror is in progress');
  }

  $self->{log}->debug('Creating lock file');
  RoPkg::Utils::CreateFile($lock_file);

  $self->{log}->debug('Setting LastUpdated and InProgress');
  $mirror->LastUpdated(time);
  $mirror->InProgress(1);
  
  $self->{log}->debug('Saving into database');
  $mirror->Update();

  return 1;
}

sub _signal_finished {
  my ($self, $mirror) = @_;
  my $lock_file;

  $lock_file = sprintf '%s.%s',
      RoPkg::Utils::DelSlash($mirror->LocalDir),
      $self->{cfg}->{general}->{lockfile};

  $self->{log}->debug('Removing lock file (',$lock_file,')');
  unlink $lock_file;

  $self->{log}->debug('Setting InProgress to 0 and updating the database');
  $mirror->InProgress(0);
  $mirror->Update();

  return 1;
}

sub _clean_up_files {
  my ($self, $mirror) = @_;

  $self->{log}->debug('Removing temporary files');
  unlink $self->_get_auth_filename($mirror);
  unlink $self->_get_log_file($mirror);
  unlink $self->_get_err_file($mirror);

  return 1;
}

sub _after_run {
  my ($self, $mirror) = @_;

  $mirror->LastErrorCode($CHILD_ERROR >> 8);
  $mirror->LastUpdateDuration(time - $self->{_start_time});
  delete $self->{_start_time};

  $self->_signal_finished($mirror);

  $self->{log}->debug('Reading log files');
  #First we remove the old logs
  $mirror->StdOut(q{});
  $mirror->StdErr(q{});

  #and read the new ones
  $mirror->StdOut(RoPkg::Utils::ReadFile($self->_get_log_file($mirror)));
  $mirror->StdErr(RoPkg::Utils::ReadFile($self->_get_err_file($mirror)));
  $self->{lp}->Log($mirror->StdOut);
  
  $self->_compute_update_params($mirror);

  $self->{log}->debug('Saving into database');
  $mirror->Update();

  if ( $mirror->LastErrorCode ) {
    print 'Error:',$mirror->StdErr,$RS;
  }

  return 1;
}

#get all plugins for a specified callback
sub _getPluginsWithCallback {
  my ($self, $cbName) = @_;
  my $plist;
  my @plugins;

  $plist = $self->{cfg}->{plugins};
  foreach(keys %{ $plist }) {
    my $plug = $plist->{$_};

    next if ($plug->{enabled} && (lc $plug->{enabled} eq 'no'));

    foreach(keys %{ $plug->{callbacks} }) {
      my $pcbName = $plug->{callbacks}->{$_};

      if ((defined $pcbName->{trigger}) && ($pcbName->{trigger} eq $cbName)) {
        foreach($self->plugins) {
          my $p = $_;

          if (ref $p eq $plug->{packageName}) {
            #This ensures that the same plugin wont make it in the list twice
            my $found = 0;
            foreach(@plugins) {
              if (ref $_ eq ref $p) {
              $found = 1;
              }
            }

            if ( !$found ) {
              push @plugins, $p;
            }
          }
        }
      }
    }
  }

  return wantarray ? @plugins : $#plugins;
}

# get all methods of a plugin for a specified callback
sub _getMethodsForCallback {
  my ($self, $pobj, $cbName) = @_;
  my $plist;
  my @methods;

  $plist = $self->{cfg}->{plugins};
  foreach(keys %{ $plist }) {
    my $plug = $plist->{$_};

    next if ($plug->{packageName} ne ref $pobj);

    foreach(keys %{ $plug->{callbacks} }) {
      my $pcbName = $plug->{callbacks}->{$_};

      if ((defined $pcbName->{trigger}) && ($pcbName->{trigger} eq $cbName)) {
        push @methods, $pcbName->{method};
      }
    }
  }

  return wantarray ? @methods : $#methods;
}

sub RunCallbacks {
  my ($self, $cbName, @mirrors) = @_;
  my @plugins;
  
  if ( !blessed($self) ) {
    OutsideClass->throw('Called from outside class');
  }

  $self->{log}->info('Running callbacks (',$cbName,q{)});

  if ( $#mirrors == -1 ) {
    $self->{log}->debug('No mirrors provided. Loading from database');
    @mirrors = $self->{mirrors}->Get();
  }

  $self->{log}->debug('Building plugins list for this callback');
  @plugins = $self->_getPluginsWithCallback($cbName);
  $self->{log}->debug('list builded');
  foreach(@plugins) {
    my $pobj = $_;
    my @methods;

    $self->{log}->debug('building methods list for ', ref $pobj);
    @methods = $self->_getMethodsForCallback($pobj, $cbName);
    foreach(@methods) {
      my $method_name = $_;

      $self->{log}->debug('Calling ', ref $pobj,'->',$method_name);
      $pobj->$method_name($cfg, \@mirrors);
    }
  }

  return scalar @mirrors;
}

sub _show_sync_details {
  my ($self, $mirror) = @_;

  my ($lu_speed, $lu_duration, $lu_bytes, $lu_size);

  $lu_size     = Number::Format::format_bytes($mirror->Size, 1);
  $lu_speed    = Number::Format::format_bytes($mirror->LastUpdateSpeed, 1);
  $lu_duration = RoPkg::Utils::SecToTime($mirror->LastUpdateDuration);
  $lu_bytes    = Number::Format::format_bytes($mirror->LastUpdateBytes, 1);

  $self->{log}->info('Sync finished (',
                  sprintf('duration: %s, speed: %sbytes/sec, transfered: %sB, size: %sB',
                    $lu_duration,
                    $lu_speed,
                    $lu_bytes,
                    $lu_size)
                  ,q{)}
                );

  print 'Duration:   ',$lu_duration,$RS,
        'Speed:      ',$lu_speed,'bytes/sec',$RS,
        'Transfered: ',$lu_bytes,'B',$RS,
        'Size:       ',$lu_size,'B',$RS,$RS;
  return 1;
}

sub _show_file_changes {
  my ($self, $mirror) = @_;
  my ($flist, $dlist);
  
  return 0 if ((lc $self->{cfg}->{general}->{showListAfterSync}) ne 'yes');
  
  #we show the mirror details, only if something was updated.
  #and we show the information at the beginning
  if ( $self->{lp}->Files > 0 || $self->{lp}->Deleted > 0 ) {
    $self->_show_sync_details($mirror);
  }

  #we also print the changes to the STDOUT
  if ( $self->{lp}->Files > 0) {
    print 'The following files were updated:', $RS;
    foreach($self->{lp}->Files) {
      print q{ } x 4, $_, $RS;
    }
    print $RS;
  }

  if ( $self->{lp}->Deleted > 0 ) {
    print 'The following files where deleted:', $RS;
    foreach($self->{lp}->Deleted) {
      print q{ } x 4, $_, $RS;
    }
  }

  return 1;
}

sub _get_fields_string {
  my ($self, $fields) = @_;
  my $fields_desc;
  
  foreach(keys %{ $fields }) {
    $fields_desc .= $_ . ' => ' . q{'} . $fields->{$_} . q{'};
  }

  return $fields_desc;
}

# Loads the mirror from the database, along with Command and Excludes.
# Returns a array ($mirror, $command, $excludes)
# Throws a lot of exceptions. All catched exceptions are rethrowed
sub _load_mirror {
  my ($self, $fields) = @_;
  my ($m, $c, $e);
  
  if ( !blessed($self) ) {
    OutsideClass->throw('Called from outside class');
  }

  $self->{log}->info('Loading mirror {',$self->_get_fields_string($fields),'}');
  
  eval {
    ($m, $c, $e) = $self->LoadMirror($fields);
  };

  if (my $e = Exception::Class->caught('DB::NoResults')) {
    if ( $e->pkg_name eq 'RoPkg::Simba::Mirrors' ) {
      $self->{log}->error('Mirror ',$self->_get_fields_string($fields),' not found in database');
      $e->rethrow;
    }
    elsif ( $e->pkg_name eq 'RoPkg::Simba::Commands' ) {
      $self->{log}->error('Could not load command');
      $e->rethrow;
    }
    else {
      $EVAL_ERROR->rethrow;
    }
  }
  else {
    if (ref $EVAL_ERROR) {
      $EVAL_ERROR->rethrow;
    }
  }

  return ($m, $c, $e);
}

sub _check_for_problems {
  my ($self, $mirror) = @_;

  if (!blessed($self)) {
    OutsideClass->throw('Called outside class instance');
  }

  if ( $mirror->Active == 0 ) {
    $self->{log}->error('Sorry. Mirror is not active');
    Mirror::Inactive->throw('Mirror is not active');
  }
  if ( $mirror->InProgress == 1 ) {
    $self->{log}->error('Sorry. Mirror is in progress');
    Mirror::InProgress->throw('Mirror is currently in progress');
  }

  return 0;
}

sub _finally_run {
  my ($self, $mirror, $command, $exclude) = @_;
  my $cmdstr;

  if (!blessed($self)) {
    OutsideClass->throw('Called outside class instance');
    }

  $self->_create_mirror_log_files($mirror);
  $self->_signal_in_progress($mirror);
  $self->{_start_time} = time;
  $cmdstr = $self->_build_cmd_string($mirror, $command, $exclude);
  $self->{log}->info('Running: ',$cmdstr);
  system $cmdstr;
  $self->{log}->info('Sync ended');
  $self->_after_run($mirror);
  $self->_clean_up_files($mirror);
  $self->_show_file_changes($mirror);

  return 0;
}

sub Run {
  my ($self, $fields) = @_;
  my ($mirror, $command, $exclude);
  my $cgen;

  if ( !blessed($self) ) {
    OutsideClass->throw('Called from outside class');
  }
    
  ($mirror, $command, $exclude) = $self->_load_mirror($fields);
  $self->RunCallbacks('afterDBLoad', $mirror);
  $self->_check_for_problems($mirror);
  $self->_finally_run($mirror, $command, $exclude);
  $self->RunCallbacks('afterSync', $mirror);

  if ($mirror->LastErrorCode) {
    $cgen = $self->{cfg}->{general};

    if ($cgen->{reRunPasses}) {
      if (   $cgen->{reRunOnError}->[0] eq '_all_'
        || ElemInList($mirror->Name, @{ $cgen->{reRunOnError} })) {
          for(my $i=0; $i < $cgen->{reRunPasses}; $i++) {
            $self->{log}->info('mirror resync due to error ','(#',($i+1),')');
            ($mirror, $command, $exclude) = $self->_load_mirror($fields);
            $self->RunCallbacks('afterDBLoad', $mirror);
            $self->_check_for_problems($mirror);
            $self->_finally_run($mirror, $command, $exclude);
            $self->RunCallbacks('afterSync', $mirror);
          }
      }
    }
  }

  return 0;
}

#############################
###  Run functions - END  ###
#############################

1;

__END__

=head1 NAME

RoPkg::Simba - the main class of simba

=head1 VERSION

0.8.4

=head1 SYNOPSIS

 !#/usr/bin/perl
 
 use RoPkg::DB;
 use RoPkg::Simba;
 
 sub main {
   my $simba = new RoPkg::Simba(cfgFile => '/etc/simba.cfg');

   $simba->Run({ Name => 'debian'});
 }
 
 main();

=head1 DESCRIPTION

This class encapsulates all the necesary code required to run Simba.

=head1 SUBROUTINES/METHODS

=head2 new()

new() expects a hash ref as a parameter. The hash can contain (for the moment)
only the B<cfgFile> (the configuration file). All other hash keys are ignored.

=head2 SimbaURL

Returns the url with simba website

=head2 VERSION

Returns a string with simba version

=head2 MirrorsNo($fields)

returns the number of the mirrors from the database. The mirrors can be filtered
using the B<$fields> parameter (as documented in L<SQL::Abstract> - where)

=head2 Mirrors($fields)

returns a array of I<RoPkg::Simba::Mirror> initialized objects.
The mirrors can be filtered using the B<$fields> parameter 
(as documented in L<SQL::Abstract> - where)

=head2 CommandsNo($fields)

returns the number of commands from the database. 
The commands can be filtered using the B<$fields> parameter 
(as documented in L<SQL::Abstract> - where)

=head2 Commands

returns a array of I<RoPkg::Simba::Command> initialized objects.
The commands can be filtered using the B<$fields> parameter 
(as documented in L<SQL::Abstract> - where)

=head2 ExcludeNo

returns the number of excludes from the database. 
The excludes can be filtered using the B<$fields> parameter 
(as documented in L<SQL::Abstract> - where)

=head2 Excludes

returns a array of I<RoPkg::Simba::Excludes> initialized objects.
The excludes can be filtered using the B<$fields> parameter 
(as documented in L<SQL::Abstract> - where)

=head2 LoadMirror($fields)

load a mirror (and the related objects: command and exclude list) from the
database and return a list of 3 objects:

=over 3

=item the mirror object (RoPkg::Simba::Mirror)

=item the command object (RoPkg::Simba::Command)

=item the excludes object (RoPkg::Simba::Excludes)

=back

Ex: 
 my ($mirror, $command, $excludes);

 ($mirror, $command, $excludes) = $simba->LoadMirror({ Name => 'debian'});

The mirrors can be filtered using the B<$fields> parameter 
(as documented in L<SQL::Abstract> - where).
This method also checks if all necesary values are defined and well formed;
for example checks if the command file name really exists.

=head2 Run($fields)

The most used method by simba. This method loads the mirror from the database,
the command and the exclude list, builds the command line (with respect to 
username and password if necesary), signals mirror in progress, executes
the command, parses the sync raw log and updates the database. Also, the
B<afterSync> handler callback is registered to this method.
The mirrors can be filtered using the B<$fields> parameter 
(as documented in L<SQL::Abstract> - where).

=head2 RunCallbacks($cbName, @mirrors)

call the plugin methods for B<$cbName> callback name. 

=head2 CheckDB()

Checks if database has at least one mirror and one command defined.

=head2 dbh()

returns the database handler used by Simba

=head2 dbo()

returns the instance of RoPkg::DB object, used by Simba

=head2 dbo_method()

returns the method used by Simba's dbo() object to access
the mirrors database.

=head1 SEE ALSO

L<RoPkg::Simba::Mirror> L<RoPkg::Simba::Command> L<RoPkg::Simba::Exclude>
L<RoPkg::Simba::Mirrors> L<RoPkg::Simba::Commands> L<RoPkg::Simba::Excludes>

=head1 PERL CRITIC

This module is perl critic level 2 compliant (with one exception)

=head1 DIAGNOSTICS

This module has his own tests in the t directory. To run the tests, unpack
the source and use 'make test' command.

=head1 CONFIGURATION AND ENVIRONMENT

This class use a configuration file. No environment variables are used.
The configuration file format and the complete list of options (with
detailed explanations) can be found on the community website:
http://simba.packages.ro

=head1 DEPENDENCIES

=head1 INCOMPATIBILITIES

Please check the Makefile.PL for a detalied list of dependencies and
their versions.

=head1 BUGS AND LIMITATIONS

No bugs known. For now, Simba can work only with rsync.

=head1 AUTHOR

Subredu Manuel <diablo@iasi.roedu.net>

=head1 LICENSE AND COPYRIGHT

Copyright (C) 2005 Subredu Manuel.  All Rights Reserved.
This module is free software; you can redistribute it 
and/or modify it under the same terms as Perl itself.
The LICENSE file contains the full text of the license.

=cut
