package Storage::Bundle;

use strict;
use POSIX;
use Logging;
use Symbol;

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

#
# Common options:
#
# 'user' - absence implies 'root'
#

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

  $self->{user} = $options{user} if defined $options{user};
  $self->{sysuser} = $options{sysuser} if defined $options{sysuser};
  $self->{srcdir} = $options{directory} if $options{directory};

  return 1;
}

#
# Returns the file handle object producing the bundle' data
#

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

  chdir $self->{srcdir};

  my $cmd = $self->commandLine();

  Logging::info("Executing bundle producer: '" . $cmd . "' in " . $self->{srcdir});

  if ($self->{user}) {
    my $from_child = gensym();
    my $to_parent = gensym();
    pipe($from_child, $to_parent);

    my $pid = fork();
    if ($pid) {
      close($to_parent);
      $self->{pid} = $pid;
      $self->{channel} = $from_child;

      my $kid;
      do {
         $kid = waitpid($self->{pid}, WNOHANG);
      } while $kid > 0;

      return ($from_child, $kid >> 8);
    } else {
      close($from_child);

      local $SIG{'PIPE'} = 'DEFAULT' if !$SIG{'PIPE'} or $SIG{'PIPE'} eq 'IGNORE';

      my $cmd_handle = gensym();
      open($cmd_handle, "$cmd |");
      while (<$cmd_handle>) {
        print $to_parent $_;
      }
      close($cmd_handle);
      close($to_parent);

      POSIX::_exit($? >> 8);
    }
  } else {
    local $SIG{'PIPE'} = 'DEFAULT' if !$SIG{'PIPE'} or $SIG{'PIPE'} eq 'IGNORE';

    my $cmd_handle = gensym();

    open($cmd_handle, "$cmd |");
    $self->{channel} = $cmd_handle;

    return ($cmd_handle, $? >> 8);
  }
}

#
# Cleans up all leftovers after run()
#

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

  close($self->{channel}) if exists $self->{channel};
  waitpid($self->{pid}, 0) if exists $self->{pid};
}

# -- Factory --

#
# Additional options:
#
# 'directory' - directory to archive - mandatory
# 'follow_symlinks' - archive the files symlink pointed to, not the symlinks
# 'include' - list of files to include in archive.
# 'exclude' - list of files to exclude from archive.
#

sub createTarBundle {
  require Storage::TarBundle;
  return Storage::TarBundle->new(@_);
}

#
# Additional options: see Db::Connection::getConnection()
#

sub createDbBundle {
  my %options = @_;

  if ($options{'type'} eq 'postgresql') {
	require Storage::PostgresqlDbBundle;
	return Storage::PostgresqlDbBundle->new(@_);
  }
  else {
	require Storage::DbBundle;
	return Storage::DbBundle->new(@_);
  }
}

1;
