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

#
# This 'filter' splits the incoming data to the files and
# reports back the name and size of each through the
# control file descriptor.
#
# output data is in format 'partName partSize'
#

use strict;
use POSIX;
use IO::Handle;
use IO::File;

my $blocksize = 65536;

my $splitter;

my $generateUniqueName;

sub init_process {
  $splitter = Storage::Splitter->new(@_);
}

sub run {
  eval {
    my $block;
    binmode STDIN;
    while (my $blocklen = sysread(STDIN, $block, $blocksize)) {
      $splitter->addData($block, $blocklen);
    }
    $splitter->finish();
  };
  if ($@) {
    if ($splitter->{part_fh} && $splitter->{part_name}) {
        unlink($splitter->{part_name});
    }
    print STDERR $@;
    POSIX::_exit(1);
  }
  POSIX::_exit(0);
}

# -- instance methods --

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

sub init {
  my ($self, $controlFd, $partsSize, $partsName, $dstDir, $dstExt ) = @_;

  $self->{control_fh} = IO::Handle->new_from_fd($controlFd, "w")
    or die "Unable to open 'control' filehandle in spritter";
  $self->{parts_size} = $partsSize;
  $self->{dstdir} = $dstDir;
  $self->{ext} = $dstExt;
  $self->{parts_name} = $partsName;
  $self->{part_size} = 0;
  $self->{curr_part_num} = -1;
}


sub _writeToPart {
  my ($self, $chunk, $size) = @_;

  return if !$size;

  if (!$self->{part_fh}) {
    $self->{curr_part_num}++;

    if ($self->{parts_name} eq "-") {
      $self->{part_fh} = \*STDOUT;
    } else {
      $self->{part_name} = Storage::Splitter::generateUniqueFileName( $self->{dstdir} . '/' . $self->{parts_name}, $self->{ext}, $self->{curr_part_num} );
      $self->{part_fh} = IO::File->new($self->{part_name}, "w");
      die "Unable to create the output file '$self->{part_name}': $!" if not $self->{part_fh};
    }
    binmode $self->{part_fh};
  }

  $self->{part_size} += $size;

  my $offset = 0;
  while ($size) {
    my $written = $self->{part_fh}->syswrite($chunk, $size, $offset);
    die "Unable to write to the output file '$self->{part_name}': $!" unless defined $written;
    $offset += $written;
    $size -= $written;
  }
}

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

  if ($self->{part_fh}) {
    $self->{part_fh}->close();
    undef $self->{part_fh};

    $self->{control_fh}->printf("%s %.0f\n", substr( $self->{part_name}, length($self->{dstdir}) + 1 ), $self->{part_size});
    $self->{control_fh}->flush();

    $self->{part_size} = 0;
  }
}

sub addData {
  my ($self, $chunk, $chunkLen) = @_;

  while ($self->{parts_size} and $self->{part_size} + $chunkLen >= $self->{parts_size}) {
    # $chunk -> ($end_part_chunk, $chunk)
    my $end_part_chunk = substr($chunk, 0, $self->{parts_size} - $self->{part_size}, '');
    $chunkLen = $self->{part_size} + $chunkLen - $self->{parts_size};

    $self->_writeToPart($end_part_chunk, $self->{parts_size} - $self->{part_size});
    $self->_finishPart();
  }

  $self->_writeToPart($chunk, $chunkLen);
}

sub finish {
  my ($self) = @_;
  $self->_finishPart();
  $self->{control_fh}->close();
}

sub generateUniqueFileName{
  my ($filename, $ext, $counter ) = @_;

  my $fname = "$filename$ext";
  $fname = "$fname$counter" if $counter>0;
  if( $generateUniqueName and -e $fname ){
     do{
        $counter += 1;
        $fname = "$filename$ext$counter";
     }while( -e $fname );
  }
  return $fname;
}

1;

#
# Test
#
# Usage: perl Splitter.pm < file
# Expected output: file split to the bunch of pieces 'foobar.NNN' of 11 bytes each.
# output - info of each piece as described in the beginning on the file
#
#
# init_process(1, 11, "foobar");
# run();
#

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