# Copyright 1999-2012. Parallels IP Holdings GmbH. All Rights Reserved.
package Storage::FtpFileStorage;

use strict;
use bigint;

use Storage::Storage;
use Logging;
use AgentConfig;
use HelpFuncs;

use POSIX;
use IPC::Run;
use Symbol;
use Error;

use Storage::Splitter;
use Storage::Counter;
use Net::FTP;

use vars qw|@ISA|;

@ISA = qw|Storage::Storage|;

$Error::Debug = 1;

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

  $self->SUPER::_init(%options);
  $self->{split_size} = $options{split_size};
  $self->{gzip_bundle} = $options{gzip_bundle};
  $self->{output_dir} = $options{output_dir};
  $self->{passive_mode} = $options{passive_mode};
  
  if ($options{output_dir} =~ /^(ftp|ftps):\/\/(?:([^:]*)(?::([^:]*))?@)?([^\/:@]+)(?::([0-9]+))?\/(.*?)$/) {

    my %ftp;

    $ftp{'protocol'} = $1;
    $ftp{'login'} = defined $2 ? $2 : '';
    $ftp{'password'} = defined $3 ? $3 : '';

    if ($ftp{'password'} eq '' && defined $ENV{'FTP_PASSWORD'}) {
      $ftp{'password'} = $ENV{'FTP_PASSWORD'};
    }

    if ($ftp{'login'} eq '') {
      $ftp{'login'} = 'anonymous';
      $ftp{'password'} = 'plesk@plesk.com' if ($ftp{'password'} eq '');
    }

    $ftp{'server'} = $4;
    $ftp{'port'} = $5;
    $ftp{'path'} = $6;
          
    $self->{ftpsettings} = \%ftp;
    
    my %ftpOptions;
    my $ftpConnection;
    
    Logging::debug("Trying to connect to $ftp{'server'} in " . $self->{passive_mode} ? "passive" : "active" . " mode...");
    
    if ($ftp{'protocol'} eq 'ftps') {
      my $ftpsModuleAvailable = 0;
      eval {
        require Net::FTPSSL;
        Net::FTPSSL->import();
        $ftpsModuleAvailable = 1;
      };
      if (!$ftpsModuleAvailable) {
        Logging::error("Server environment doea not support FTP over SSL. Net::FTPSSL module must be installed to turn this feature on");
        return 1;
      }
      $ftpConnection = Net::FTPSSL->new($ftp{'server'}, Port => $ftp{'port'}, Encryption => 'E', Debug => 1);
    } else {
      $ftpOptions{Debug} = 1;
      $ftpOptions{Passive} = $self->{passive_mode};
      $ftpConnection = Net::FTP->new($ftp{'server'}, %ftpOptions);
    }

    if (! defined $ftpConnection ) {
      Logging::error("Could not connect to $ftp{'server'}", 'FtpError');
      return 1;
    }

    if (!$ftpConnection->login($ftp{'login'}, $ftp{'password'})) {
      Logging::error( "FTP: Can't login to the ftp server",'FtpError' );
      return 1;
    }

    if ($ftp{'path'} ne "" && !$ftpConnection->cwd($ftp{'path'})) {
      Logging::error( "FTP: Can't change dir to $ftp{path}",'FtpError' );
      return 1;
    }
    
    $ftpConnection->binary;
    
    $self->{ftpconnection} = $ftpConnection;
    Logging::debug("Connected successfully.");
  }
  $self->{space_reserved} = $options{space_reserved} if $options{space_reserved};
  $self->{sign} = 1 if $options{sign};

  $self->{last_used_id} = 0;

  $self->{unpacked_size} = 0;
  $self->{packed_size} = 0;
  
  $self->{management_node} = 1;

  Logging::debug("-" x 60);
  Logging::debug("FTP FILE storage initialized.");
  Logging::debug("Backup node: $self->{ftpsettings}->{server}");
  Logging::debug("Mode: " . $self->{passive_mode} ? "passive" : "active");
  Logging::debug("Base directory: $self->{ftpsettings}->{path}");
  Logging::debug("Space reserved: $self->{space_reserved}");
  Logging::debug("Gzip bundles: " . ($self->{gzip_bundle} ? "yes" : "no"));
  Logging::debug("Bundle split size: " . ($self->{split_size} || "do not split"));  
  Logging::debug("-" x 60);
  $self->reserveSpace();
}

sub setRemoteExecution {
    my ($self) = @_;
    $self->{management_node} = 0;
}

sub getFullOutputPath{
 my $self = shift;
 #return "$self->{output_dir}";
 #todo get info from config DUMP_D or make temp dir
 return "/var/lib/psa/dumps";
}

sub createRepositoryIndex{
  my ( $self, $index ) = @_;
  my @filesToUpload;
  if( $index ){
    Logging::debug("Create repository index: $index");
    my $destDir = $self->getFullOutputPath()."/.discovered";
    system("mkdir", "-p", "$destDir") if not -e $destDir;
    open INDEXFILE, "> $destDir/$index";
    close INDEXFILE;
    my @fileToUpload = ($index, 0);
    push @filesToUpload, \@fileToUpload;
    $self->uploadFilesToFtp( $destDir, \@filesToUpload );    
  }
}

sub writeDiscovered{
  my ( $self, $dumpPath, $dumpXmlName, $dumpSize, $ownerGuid, $ownerType, $objectGuid, $objectId ) = @_;

  my $idx = rindex( $dumpXmlName, '.xml' );
  $dumpXmlName = substr( $dumpXmlName, 0, $idx ) if $idx>0;
  my $destDir = $self->getFullOutputPath();
  $destDir .= "/$dumpPath" if $dumpPath;
  $destDir .= "/.discovered/$dumpXmlName";
  push @{$self->{discovered}}, $destDir;

  Logging::debug("Create discovered: $destDir");
  system("mkdir", "-p", "$destDir") if not -e $destDir;
  open SIZEFILE, "> $destDir/size_$dumpSize";
  close SIZEFILE;
  open OWNERFILE, "> $destDir/owner_$ownerGuid";
  close OWNERFILE;
  open OWNERTYPEFILE, "> $destDir/ownertype_$ownerType";
  close OWNERTYPEFILE;
  open GUIDFILE, "> $destDir/GUID_$objectGuid";
  close GUIDFILE;
  open OBJIDFILE, "> $destDir/objectid_$objectId";
  close OBJIDFILE;
  
  if ($dumpXmlName . ".xml" eq $self->getMainDumpXmlFile()) {
    open INDEPENDFILE, "> $destDir/dump_full";
    close INDEPENDFILE;
  } else {
    open PARTFILE, "> $destDir/dump_part";
    close PARTFILE;
  }
  
  my @files;
  my $file = ["size_$dumpSize", 0]; push @files, $file;
  $file = ["owner_$ownerGuid", 0]; push @files, $file;
  $file = ["ownertype_$ownerType", 0]; push @files, $file;
  $file = ["GUID_$objectGuid", 0]; push @files, $file;
  $file = ["objectid_$objectId", 0]; push @files, $file;
  
  if ($dumpXmlName . ".xml" eq $self->getMainDumpXmlFile()) {
    $file = ["dump_full", 0]; push @files, $file;
  } else {
    $file = ["dump_part", 0]; push @files, $file;
  }

  $self->regIdFiles( $destDir, $destDir, 0, \@files );
  
  $self->uploadFilesToFtp($destDir, \@files);  
}

sub addTar {
  my ($self, $proposedId, %options) = @_;

  return unless -d $options{'directory'};

  if (defined $options{'checkEmptyDir'}) {
    return unless $self->checkDirForArchive($options{'directory'}, $options{'exclude'}, $options{'include_hidden_files'});
    
  }

  if ($self->{collectStatistics})
  {
    $self->{stopWatch}->createMarker("pack");
  }

  my ($destDir, $destFile, $id) = $self->getFileNameIdFromId( $proposedId, $self->getDefExtension(), 1 );
  Logging::debug("Tar bundle. id=$id, destFile=$destDir/$destFile");

  my $bundle = Storage::Bundle::createTarBundle(%options, 'gzip' => $self->{gzip_bundle});
  
  unless ($bundle)
  {
    if ($self->{collectStatistics})
    {
      $self->{statistics}->{packTime} += $self->{stopWatch}->getDiff("pack");
      $self->{stopWatch}->releaseMarker("pack");
    }
    return;
  }
  my $size = 0;
  my $files = $self->executeAndSave($destDir, $destFile, $self->getDefExtension(), $bundle, \$size);
  my $ret =  $self->regIdFiles( $id, $destDir, $size, $files );
  if ($self->{collectStatistics})
  {
    $self->{statistics}->{packTime} += $self->{stopWatch}->getDiff("pack");
    $self->{stopWatch}->releaseMarker("pack");
  } 
  
  $self->uploadFilesToFtp($destDir, $files);

  return $ret;
}

sub uploadFileToFtp {
  my ($self, $sourcePath, $destPath) = @_;
  if (! $self->{ftpconnection}->put($sourcePath, $destPath)) {
    Error::throw Error::Simple("Can't upload $sourcePath to $destPath: " . $self->{ftpconnection}->message);
  }
}

sub uploadFilesToFtp {
  my ($self, $destDir, $files) = @_;

# todo check connection, reconnect  or several attempts to upload

  if ( index( $destDir, $self->getFullOutputPath() )==0 ) {
    $destDir = substr( $destDir, length( $self->getFullOutputPath() ) + 1 ) ;
  }  
  
  if( index( $destDir, -1, 1 ) eq '/' ) {
    $destDir = substr( $destDir, 0, length($destDir)-1 );
  }
  
  $self->{ftpconnection}->mkdir($destDir, 1);
 
  for my $file( @{$files} ){
    Logging::debug("PUT file: " . $self->getFullOutputPath() . '/' . "$destDir/$file->[0]" . " TO PATH: " . "$destDir/$file->[0]");
    my $destinationOnFtp;
    if ($destDir eq '') {
        $destinationOnFtp = $file->[0];
    }else {
        $destinationOnFtp = "$destDir/$file->[0]";
    }
    $self->uploadFileToFtp($self->getFullOutputPath() . '/' . "$destDir/$file->[0]", $destinationOnFtp);    
  }
  
  for my $file( @{$files} ){
    unlink($self->getFullOutputPath() . '/' . "$destDir/$file->[0]") or Logging::error("Cannot delete temp file ". $self->getFullOutputPath() . "/" . $destDir ."/".$file->[0]);
  }  
}

sub CleanupFiles()
{
  my $self = shift;
  my $pid;
  while( ( $pid = wait() ) !=-1 ){
    Logging::debug("The child process '$pid' has been terminated" );
  }
  my $path = $self->getFullOutputPath();
  my @files = $self->getDumpFiles();
  foreach my $file(@files ){
     if (-e $path."/".$file) {
       Logging::debug("Remove file '$file' from repository '$path' ");       
       $self->uploadFileToFtp($path."/".$file, $file);
       unlink "$path/$file" or Logging::debug("Cannot remove file '$path/$file'");
     }
  }
  if( exists $self->{discovered} ){
    foreach my $discovered(@{$self->{discovered}} ){
       Logging::debug("Remove discovered '$discovered'");
       opendir DIR, $discovered;
       my @dirfiles = readdir( DIR );
       closedir DIR;
       foreach my $file(@dirfiles){
         if( $file ne '.' and $file ne '..' ){
           unlink "$discovered/$file" or Logging::debug("Cannot remove file '$discovered/$file'");
         }
       }
       rmdir( $discovered ) or Logging::debug("Cannot remove discovered '$discovered'");
    }
  }
}

1;

# Local Variables:
# mode: cperl
# cperl-indent-level: 2
# indent-tabs-mode: nil
# tab-width: 4
# End:
