package Net::Paraget::Client;
#
# $Id: Client.pm,v 1.5 2001/05/10 03:46:08 lrclause Exp $
#

use strict;
use English;
use Carp;
use IO::File;
use File::Basename;
use File::Spec;

use Net::Paraget::PerformanceReport;

use Class::MethodMaker
  get_set       => [ qw( assignment output_file
			 start_time end_time
			 pid        return_code
			 manager
		       )
		   ],
  boolean       => [ qw( started cleanedup
			 no_further_updates
		       )
		   ],
  new_hash_init => [ qw( new hash_init ) ];


sub state
{
    my ( $self, $string, $priority ) = @_;
    main::state( 'client ' . $self->info() . ": " . $string, $priority );
}


sub info
{
    my ( $self ) = @_;
    my $assignment_info = $self->assignment->info();
    my $output_file     = $self->output_file();
    my $pid             = $self->pid() || 'none';
    my $return_code     = $self->return_code() || 'none'; 

    return "assignment $assignment_info, return code $return_code";
}


sub start
{
    my ( $self ) = @_;
    
    if ( $self->started() )
    {
	carp "client already started with pid ", $self->pid;
	return;
    }
    
    my $assignment = $self->assignment();
    
    my $manager = $self->manager();
    
    my $url_basename    = basename $assignment->file();
    my $offset  = $assignment->interval->start();
    
    my $output_basename = "$url_basename.$offset";
    my $outfile = File::Spec->catfile( $manager->tmp_dir(), $output_basename );
    $self->output_file( $outfile );
    
    # clear out the file beforehand
    my $f = IO::File->new( ">$outfile" )
      or die "cannot open $outfile for writing";
    $f->close();
    
    $self->state( "starting", 1 );
    
    my $pid = fork;
    
    defined $pid or die "error forking: $ERRNO";
    
    if ( $pid != 0 )
    {
	$self->hash_init( pid        => $pid,
			  started    => 1,
			  start_time => time,
			);
	
	$assignment->server()->at_start();
	return;
    }
    
    my $url = $assignment->mirror->url()."/".$assignment->file();
    my $program = $manager->child_program();
    
    exec ( $program,
	   '--url', $url, 
	   '--offset', $offset,
	   '--output', $outfile,
	   '--size', $manager->authoritative_size(),
	 )
      or exit 1;
}


sub cleanup
{
    my ( $self ) = @_;
    
    if ( $self->cleanedup() )
    {
	return;
    }
    
    $self->end_time( time );
    waitpid $self->pid(), 0;
    $self->return_code( $CHILD_ERROR << 8 );

    $self->set_cleanedup();

    if ($self->had_error()) {
	$self->assignment()->server()->at_failure();
    } else {
	$self->assignment()->server()->at_success();
    }
}



sub amount_completed
{
    my ( $self ) = @_;
    return -s $self->output_file() || 0;
}


sub elapsed_time
{
    my ( $self ) = @_;
    return 0 unless $self->start_time();
    
    return $self->end_time() - $self->start_time() if $self->cleanedup();
      
    return time - $self->start_time();
    
}


sub stop
{
    my ( $self ) = @_;
    
    if ( not $self->alive() )
    {
	$self->cleanup();
	return;
    }
    
    my $pid = $self->pid();
    my @signals = qw( INT TERM KILL );
    
    foreach my $signal ( @signals )
    {
	if ( kill $signal, $pid )
	{
	    $self->cleanup();
	    return;
	}
	
	warn "error killing process $pid with $signal: $ERRNO"
	  if $self->alive();
    }
    
    warn "error: failed to kill process $pid: $ERRNO";
}


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

    return ( $self->pid() and kill 0, $self->pid() );
}


sub checkup
{
    my ( $self ) = @_;
    $self->cleanup() unless $self->alive() or $self->cleanedup();
}


sub speed
{
    my ( $self ) = @_;
    
    my $elapsed_time = $self->elapsed_time();
    return undef if $elapsed_time == 0;
    return $self->amount_completed() / $elapsed_time;
}


sub report
{
    my ( $self ) = @_;
    
    my $r = Net::Paraget::PerformanceReport->new
      ( assignment           => $self->assignment(),
	work_time            => $self->elapsed_time(),
	amount_completed     => $self->amount_completed(),
	had_error            => $self->had_error,
      );
    
    return $r;
}


sub had_error
{
    my ( $self ) = @_;
    
    return 0 unless $self->cleanedup();
    return ( $self->return_code() != 0 and not $self->fulfilled_assignment() );
}


sub further_updates
{
    my ( $self ) = @_;
    return not $self->no_further_updates();
}


sub fulfilled_assignment
{
    my ( $self ) = @_;
    my $interval = $self->assignment->interval();
    
    my $end  = $interval->extendable_to();
    my $need = $end - $interval->start();
    
    return $self->amount_completed() >= $need;
}


sub estimated_time_to_completion
{
    my ( $self ) = @_;
    
    my $speed          = $self->speed();
    my $completed_size = $self->amount_completed();
    my $total_size     = $self->assignment->interval->size();
    my $amount_left    = $total_size - $completed_size;
    
    return undef if not $speed;
    return $amount_left / $speed;
}


1;


syntax highlighted by Code2HTML, v. 0.9.1