# Copyright 1999-2015. Parallels IP Holdings GmbH. All Rights Reserved.
package BackupSizeCalculator;

use strict;
eval{require warnings;1;};
use Logging;
use PleskStructure;
use PleskVersion;
use AgentConfig;
use HelpFuncs;
use HostingDumper;
use ObjectsFilter;
use MiscConfigParser;

###### Common functions ######

sub new {
  my $self = {};
  bless( $self, shift );
  $self->_init(@_);
  return $self;
}

sub _init {
  my ( $self, $pleskX ) = @_;
  $self->{agent} = $pleskX;
  $self->{dbh} = $pleskX->{dbh};
  $self->{filter} = ObjectsFilter->new($pleskX);
}

###### Size mode functions ######
sub getSize {
  my ($self) = @_;
  Logging::debug("Get backup size for selected objects");

  if ( $self->{agent}->{configuration_dump} ) {
    Logging::debug("Get backup size finished. Configuration-only mode detected. Backup size is reported as 0 bytes");
    return 0;
  }
  
  my $size = 0;
  if ( exists $self->{agent}->{dump_all} ) {
    my @resellers = sort { $a cmp $b } PleskStructure::getResellers();
    @resellers = $self->{filter}->filterSelectedResellers( \@resellers );
    foreach my $reseller ( @resellers ) {
      $size += $self->getSelectedResellerBackupSize( $reseller );
    }
    my @clients = sort { $a cmp $b } PleskStructure::getAdminClients();
    @clients = $self->{filter}->filterSelectedClients( \@clients );
    foreach my $client ( @clients ) {
      if( PleskStructure::getClientType( $client ) eq 'client' ){
        $size += $self->getSelectedClientBackupSize( $client );
      }
    }
    my @adminDomains = PleskStructure::getAdminDomains();
    @adminDomains = $self->{filter}->filterSelectedDomains( \@adminDomains );
    foreach my $domainName(@adminDomains) {
      $size += $self->getDomainBackupSize( $domainName );
    }
  }
  elsif ( exists $self->{agent}->{resellers} ){
    foreach my $reseller ( @{ $self->{agent}->{resellers} } ) {
      $size += $self->getSelectedResellerBackupSize( $reseller );
    }
  }
  elsif ( exists $self->{agent}->{clients} ) {
    foreach my $client ( @{ $self->{agent}->{clients} } ) {
      $size += $self->getSelectedClientBackupSize( $client );
    }
  }
  elsif ( exists $self->{agent}->{domains} ) {
    foreach my $domain ( @{$self->{agent}->{domains}} ) {
      $size += $self->getDomainBackupSize( $domain );
    }
  }

  if( $self->{agent}->{server_settings} ){
      $size += $self->getServerSettingsBackupSize();
  }

  Logging::debug("Get backup size finished. Backup size of selected objects is $size bytes");
  return $size;
}

sub getSelectedResellerBackupSize {
  my ($self, $reseller) = @_;
  my $size = 0;

  $size += $self->getSelectedClientBackupSize($reseller);

  my @myclients = sort { $a cmp $b } PleskStructure::getMyClients($reseller);
  @myclients = $self->{filter}->filterSelectedClients( \@myclients );
  foreach my $client ( @myclients ) {
    $size += $self->getSelectedClientBackupSize( $client );
  }
  return $size;
}

sub getSelectedClientBackupSize {
  my ($self, $client ) = @_;
  my $size = 0;

  my @mydomains = sort { $a cmp $b } PleskStructure::getDomainsForClient($client);
  @mydomains = $self->{filter}->filterSelectedDomains( \@mydomains );
  $size += $self->getClientBackupSize($client);
  foreach my $domain ( @mydomains ) {
    $size += $self->getDomainBackupSize( $domain );
  }
  return $size;
}

sub getDomainBackupSize {
  my ($self, $domain ) = @_;
  my $size = 0;
  Logging::debug("Get backup size for domain '$domain'");
  if ( $self->{agent}->{configuration_dump} ) {
    Logging::debug("Get backup size for domain '$domain' finished. Configuration-only mode detected. Size is $size bytes.");
    return 0;
  }

  my $domainId = PleskStructure::getDomainId($domain);
  if ( not defined $domainId ) {
    Logging::warning("Failed to get domain id for '$domain'",'PleskDbError');
    return 0;
  }

  my $sql = "SELECT name FROM domains WHERE id=?";
  my $domainAsciiName;
  if ( $self->{dbh}->execute_rownum($sql, $domainId) and my $ptrRow = $self->{dbh}->fetchrow() ) {
    $domainAsciiName = $ptrRow->[0];
  }
  $self->{dbh}->finish();

  if ( not defined $domainAsciiName ) {
    Logging::warning("Failed to get domain name for '$domain'",'PleskDbError');
    return 0;
  }

  my $recalculateUsage = 0;

  if ($recalculateUsage) {
    if ( !$self->{agent}->{only_hosting_dump} and !$self->{agent}->{only_mail_dump} ) {
      $size += $self->getDomainDbSize($domain, $domainId);
    }

    if ( !$self->{agent}->{only_hosting_dump} ) {
      $size += $self->getDomainMailSize($domain, $domainAsciiName, $domainId);
      $size += $self->getDomainMaillistsSize($domain, $domainId);
    }

    if ( !$self->{agent}->{only_mail_dump} ) {
      $size += $self->getDomainTomcatSize($domain, $domainId, $domainAsciiName);
      $size += $self->getDomainPhostingSize($domain, $domainId, $domainAsciiName);
    }
  } else {
    my %diskUsage = $self->getDomainDiskUsageStats($domainId);

    if ( !$self->{agent}->{only_hosting_dump} and !$self->{agent}->{only_mail_dump} ) {
      $size += $diskUsage{'dbases'};
    }

    if ( !$self->{agent}->{only_hosting_dump} ) {
      $size += $diskUsage{'mailboxes'};
      $size += $diskUsage{'maillists'};
    }

    if ( !$self->{agent}->{only_mail_dump} ) {
      $size += $diskUsage{'httpdocs'};
      $size += $diskUsage{'webapps'};
    }
  }

  if ( !$self->{agent}->{only_mail_dump} ) {
    $size += $self->getDomainCustomButtonsSize($domain, $domainId);
  }

  Logging::debug("Get backup size for domain '$domain' finished. Size is $size bytes.");
  return $size;
}

sub getDomainCustomButtonsSize{
  my ($self, $domain, $domainId ) = @_;
  my $size = 0;
  Logging::debug("Domain '$domain': ". (caller(0))[3]." started.");

  my @buttonIds = @{DAL::getCustomButtonIdsByOwner71('domain-admin', $domainId)};
  foreach my $buttonId ( @buttonIds ) {
    $size += $self->getCustomButtonSize( $buttonId ) if defined $buttonId;
  }

  Logging::debug("Domain '$domain': ". (caller(0))[3]." finished. Size is $size bytes.");
  return $size;
}

sub getDomainDiskUsageStats {
  my ($self, $domainId) = @_;

  my @fields = qw(
    httpdocs httpsdocs subdomains web_users anonftp logs dbases
    mailboxes webapps maillists domaindumps configs chroot
  );
  my %disk_usage = map { $_ => 0 } @fields;

  my $sql = "SELECT " . join(',', @fields) . " FROM disk_usage WHERE dom_id=?";
  if ( $self->{dbh}->execute_rownum($sql, $domainId) ) {
    %disk_usage = %{$self->{dbh}->fetchhash()};
  }
  $self->{dbh}->finish();

  return %disk_usage;
}

sub getDomainDbSize {
  my ($self, $domain, $domainId ) = @_;
  my $size = 0;
  Logging::debug("Domain '$domain': ". (caller(0))[3]." started.");

  my $sql = "SELECT dbases FROM disk_usage WHERE dom_id=?";
  if ( $self->{dbh}->execute_rownum($sql, $domainId) and my $ptrRow = $self->{dbh}->fetchrow() ) {
    $size += $ptrRow->[0];
  }
  $self->{dbh}->finish();

  Logging::debug("Domain '$domain': ". (caller(0))[3]." finished. Size is $size bytes.");
  return $size;
}

sub getDomainPhostingSize{
  my ($self, $domain, $domainId, $domainAsciiName ) = @_;
  my $size = 0;
  Logging::debug("Domain '$domain': ". (caller(0))[3]." started.");

  my $webspaceRoot = HostingDumper::getWebspaceRoot($domainAsciiName, $self->{agent}->{remoteWebNodes});
  $size += $self->getCidSize($webspaceRoot);

  my $domainRoot = HostingDumper::getDomainRoot($domainAsciiName, $self->{agent}->{remoteWebNodes});
  $size += $self->getDomainAnonFtpSize($domain, $domainId, $domainAsciiName);

  Logging::debug("Domain '$domain': ". (caller(0))[3]." finished. Size is $size bytes.");
  return $size;
}

sub getDomainAnonFtpSize {
  my ($self, $domain, $domainId, $domainAsciiName ) = @_;
  my $size = 0;
  Logging::debug("Domain '$domain': ". (caller(0))[3]." started.");

  my $sql   = "SELECT * FROM anon_ftp WHERE dom_id=?";
  if ( $self->{dbh}->execute_rownum($sql, $domainId) ) {
    while ( my $ptrHash = $self->{dbh}->fetchhash() ) {
      my $domainRoot = HostingDumper::getDomainRoot($domainAsciiName, $self->{agent}->{remoteWebNodes});
      if ( -d $domainRoot ) {
        my $pub_path      = "$domainRoot/anon_ftp/pub";
        my $incoming_path = "$domainRoot/anon_ftp/incoming";
        $size += $self->getCidSize($pub_path);
        $size += $self->getCidSize($incoming_path);
      }
    }
  }
  $self->{dbh}->finish();

  Logging::debug("Domain '$domain': ". (caller(0))[3]." finished. Size is $size bytes.");
  return $size;
}

sub getDomainTomcatSize{
  my ($self, $domain, $domainId, $domainAsciiName ) = @_;
  my $size = 0;
  Logging::debug("Domain '$domain': ". (caller(0))[3]." started.");

  my $tomcatServiceStatus= DAL::getDomainTomcatStatus($domainId);
  if (defined $tomcatServiceStatus) {
    my $warDir = AgentConfig::get("CATALINA_HOME") . "/psa-wars/$domainAsciiName";
    my $webapps = DAL::getDomainWebApps($domainId);
    foreach my $webapp ( keys %{$webapps} ) {
      $size += $self->getCidSize("$warDir/$webapp.war");
    }
  }

  Logging::debug("Domain '$domain': ". (caller(0))[3]." finished. Size is $size bytes.");
  return $size;
}

sub getDomainMailSize{
  my ($self, $domain, $domainAsciiName, $domainId ) = @_;
  my $size = 0;
  Logging::debug("Domain '$domain': ". (caller(0))[3]." started.");

  my $mailNamesPath = AgentConfig::getPleskMailnamesDir() . "/$domainAsciiName/";
  # inclides mailboxes, attachments and spamassassin content
  if ( -e $mailNamesPath ) {
    $size += $self->getCidSize($mailNamesPath);
  }

  Logging::debug("Domain '$domain': ". (caller(0))[3]." finished. Size is $size bytes.");
  return $size;
}

sub getDomainMaillistsSize {
  my ($self, $domain, $domainId ) = @_;
  my $size = 0;
  Logging::debug("Domain '$domain': ". (caller(0))[3]." started.");
  
  my $archiveDir = AgentConfig::get("MAILMAN_VAR_D") . "/archives/private";
  my @maillists = @{DAL::getDomainMaillists($domainId)};
  for my $ptrRow ( @maillists ) {
    my $datafile = $archiveDir . "/" . $ptrRow->[1] . ".mbox/" . $ptrRow->[1] . ".mbox";
    if ( -f $datafile ) {
      $size += $self->getCidSize($datafile);
    }
    else {
      Logging::debug("file '$datafile' doesn't exist" );
    }
  }
  Logging::debug("Domain '$domain': ". (caller(0))[3]." finished. Size is $size bytes.");
  return $size;
}

sub getClientBackupSize {
  my ($self, $client ) = @_;
  my $size = 0;
  Logging::debug("Get backup size for client '$client'");

  if ( $self->{agent}->{configuration_dump} ) {
    Logging::debug("Get backup size for client '$client' finished. Configuration-only mode detected. Size is $size bytes.");
    return 0;
  }

  my $clientId = PleskStructure::getClientId($client);
  if ( not defined $clientId ) {
    Logging::warning("Failed to get client id for '$client'",'PleskDbError');
    return 0;
  }

  if ( !$self->{agent}->{only_mail_dump} ) {
    $size += $self->getClientSkeletonSize($client, $clientId);
  }

  if ( !$self->{agent}->{only_mail_dump} ) {
    $size += $self->getClientCustomButtonsSize($client, $clientId);
  }

  Logging::debug("Get backup size for client '$client' finished. Size is $size bytes.");
  return $size;
}

sub getClientSkeletonSize {
  my ($self, $client, $clientId ) = @_;
  my $size = 0;
  Logging::debug("Client '$client': ". (caller(0))[3]." started.");

  my $skeletonPath = AgentConfig::get('HTTPD_VHOSTS_D') . "/.skel/$clientId";
  if ( -e $skeletonPath ) {
    $size += $self->getCidSize($skeletonPath);
  }

  Logging::debug("Client '$client': ". (caller(0))[3]." finished. Size is $size bytes.");
  return $size;
}

sub getClientCustomButtonsSize {
  my ($self, $client, $clientId ) = @_;
  my $size = 0;
  Logging::debug("Client '$client': ". (caller(0))[3]." started.");

  my @buttonIds = @{DAL::getCustomButtonIdsByOwner71('client', $clientId)};
  foreach my $buttonId ( @buttonIds ) {
    $size += $self->getCustomButtonSize( $buttonId ) if defined $buttonId;
  }
 
  Logging::debug("Client '$client': ". (caller(0))[3]." finished. Size is $size bytes.");
  return $size;
}

sub getServerSettingsBackupSize {
  my ($self) = @_;
  my $size = 0;
  Logging::debug("Get backup size for server settings");
  if ( $self->{agent}->{configuration_dump} ) {
    Logging::debug("Get backup size for server settings finished. Configuration-only mode detected. Size is $size bytes.");
    return 0;
  }

  my $skeletonPath = AgentConfig::get('HTTPD_VHOSTS_D') . "/.skel/0";
  $size += $self->getCidSize($skeletonPath);

  $size += $self->getServerSettingsKeysSize();

  $size += $self->getServerSettingsAppPackagesSize();

  $size += $self->getServerCustomButtonsSize();

  Logging::debug("Get backup size of server settings finished. Size is $size bytes.");
  return $size;
}

sub getServerSettingsAppPackagesSize {
  my ($self) = @_;
  my $size = 0;
  Logging::debug("Server: ". (caller(0))[3]." started.");
  
  use HelpFuncs;
  
  if ( PleskVersion::atLeast( 8, 3, 0 ) && !PleskVersion::atLeast(12, 1, 0 )) {
    my $distrib_path = AgentConfig::get("PRODUCT_ROOT_D") . "/var/apspkgarc";
    my %mapHash = MiscConfigParser::parseApsIndexFile( $distrib_path . "/archive-index.xml" );

    my $sql = "SELECT `name`, `version`, `release` FROM SiteAppPackages";
    if( $self->{dbh}->execute_rownum($sql) ) {
      while ( my $ptrRow = $self->{dbh}->fetchrow() ) {
        my $distrib_name = $ptrRow->[0] . "-"
                         . $ptrRow->[1] . "-"
                         . $ptrRow->[2];
        my $file_name;
        foreach my $tfile_name ( keys %mapHash ) {
          if ( $distrib_name eq join( "-", @{ $mapHash{$tfile_name} } ) ) {
            $file_name = $tfile_name;
          }
        }
        $size += $self->getCidSize("$distrib_path/$file_name");
      }
    }
    $self->{dbh}->finish();
  }

  Logging::debug("Server: ". (caller(0))[3]." finished. Size is $size bytes.");
  return $size;
}

sub getServerSettingsKeysSize {
  my ($self) = @_;
  my $size = 0;
  Logging::debug("Server: ". (caller(0))[3]." started.");

  if ( PleskVersion::atLeast( 9, 0, 0 ) ) {
    my $phpini = AgentConfig::get('PRODUCT_ROOT_D') . "/admin/conf/php.ini";
    my $swkeyrepo = '/etc/sw/keys';
    if( -e $phpini ){
      open PHPINI, $phpini;
      while (<PHPINI>) {
        chomp;
        next if /^#/;
        next if /^$/;
        if ( $_ =~ /swkey\.repository_dir\s*=\s*[\"\']?(.+)[\"\']\s*$/) {
          $swkeyrepo = $1;
          Logging::debug( "getServerSettingsBackupSize: Found sw key repository: $swkeyrepo" );
          last;
        }
      }
      close PHPINI;
    }
    if( -d "$swkeyrepo/keys" ){
      my $keyDir = "$swkeyrepo/keys";
      Logging::debug( "getServerSettingsBackupSize: Load keys from '$keyDir'" );
      opendir DIR, "$keyDir";
      my @files = readdir( DIR );
      closedir DIR;
      foreach my $key(@files) {
        if( $key ne '.' and $key ne '..' and -f "$keyDir/$key" ){
          $size += $self->getCidSize("$keyDir/$key");
        }
      }
    }
    else{
      Logging::debug( "getServerSettingsBackupSize: Keys directory '$swkeyrepo/keys' is not found. The keys are not included to backup." );
    }
  }
  else{
    my $sql = "SELECT * FROM key_history WHERE filename IS NOT NULL";
    if($self->{dbh}->execute_rownum($sql)) {
      while ( my $ptrHash = $self->{dbh}->fetchhash() ) {
        next if ( $ptrHash->{'filename'} =~ /\~$/ );
        my @keyPath = split( /\//, $ptrHash->{'filename'} );
        my ( $keyName, $keyDir, $additional );
        if ( $keyPath[3] eq 'key.d' ) {
          $keyName    = $keyPath[4];
          $keyDir     = "/etc/psa/key.d/";
          $additional = "true";
        }
        else {
          $keyName    = $keyPath[3];
          $keyDir     = "/etc/psa/";
          $additional = "false";
        }
        if ( -e "$keyDir/$keyName" ) {
          $size += $self->getCidSize("$keyDir/$keyName");
        }
      }
    }
    $self->{dbh}->finish();
  }

  Logging::debug("Server: ". (caller(0))[3]." finished. Size is $size bytes.");
  return $size;
}

sub getServerCustomButtonsSize {
  my ($self) = @_;
  my $size = 0;
  Logging::debug("Server: ". (caller(0))[3]." started.");

  my @buttonIds = @{DAL::getCustomButtonIdsByOwner71('server', 0)};
  foreach my $buttonId ( @buttonIds ) {
    $size += $self->getCustomButtonSize( $buttonId ) if defined $buttonId;
  }
 
  Logging::debug("Server: ". (caller(0))[3]." finished. Size is $size bytes.");
  return $size;
}

sub getCustomButtonSize {
  my ($self, $id) = @_;
  my $size = 0;

  my $customButtonsDir = AgentConfig::get("PRODUCT_ROOT_D") . '/admin/htdocs/images/custom_buttons';
  my $sql = "SELECT file FROM custom_buttons WHERE id=? AND place!=''";
  if ( $self->{dbh}->execute_rownum($sql, $id) and my $ptrRow = $self->{dbh}->fetchrow() ) {
    my $file = $ptrRow->[0];
    if ( $file ) {
      $size += $self->getCidSize("$customButtonsDir/$file");
    }
  }
  $self->{dbh}->finish();

  return $size;
}

sub getCidSize {
  my ($self, $path, $no_recursion) = @_;

  use bigint;

  my $cmd = "du -sb";
  if ( defined $no_recursion ) {
    $cmd .= " -S";
  }
  $cmd .= " $path";
  my $sizes = `$cmd`;
  
  my $unpacksize = 0;
  for(split /[\r\n]+/,$sizes) {
    my ($number, $file) = split /\t/;
    $unpacksize += $number; 
  }
  Logging::debug("Retrieve size of '$path'" . ($no_recursion? "without recursion" : "" ) . ": $unpacksize bytes" );
  return $unpacksize;
}

sub getDomainDiskUsage {
  my ($self, $domainId, $domainName, $domainAsciiName) = @_;
  
  my %diskUsage = $self->getDomainDiskUsageStats($domainId);
  my %domainDiskSpaceInfo;

  $domainDiskSpaceInfo{'diskusage.db'} = $diskUsage{'dbases'};
  $domainDiskSpaceInfo{'diskusage.mail'} = $diskUsage{'mailboxes'} + $diskUsage{'maillists'};
  $domainDiskSpaceInfo{'diskusage.vhost'} = $diskUsage{'httpdocs'} + $diskUsage{'webapps'};
  
  return \%domainDiskSpaceInfo;
}

###### End Size mode functions ######

1;