# -*- perl -*-

#
# $Id: Waitproc.pm,v 1.7 2005/10/27 01:01:52 eserte Exp $
# Author: Slaven Rezic
#
# Copyright (C) 1999,2004 Slaven Rezic. All rights reserved.
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
#
# Mail: slaven@rezic.de
# WWW:  http://bbbike.sourceforge.net
#

=head1 NAME

Waitproc - a wait process

=head1 SYNOPSIS

    use Waitproc;
    waitproc();      # start rotor
    sleep 10;        # do something ...
    stop_waitproc(); # stop rotor

=cut

package Waitproc;
require Exporter;
@ISA = qw(Exporter);
@EXPORT    = qw(waitproc stop_waitproc);
@EXPORT_OK = qw(progress);

use strict;
use vars qw($waitproc_pid $rotor $rotor_delay_time);

=head1 FUNCTIONS

=head2 waitproc()

Start a wait processs. The wait process will display a rotating line.

=cut

$rotor            = '\|/-' unless defined $rotor;
$rotor_delay_time = 0.07   unless defined $rotor_delay_time;

sub waitproc {
    eval {
	$waitproc_pid = fork;
	if ($waitproc_pid == 0) {
	    my $rotor_i = 0;
	    my $check_counter = 0;
	    $| = 1;
	    while (1) {
		print substr($rotor, $rotor_i, 1) . "\r";
		if (++$rotor_i >= length($rotor)) {
		    $rotor_i = 0;
		}
		select(undef, undef, undef, $rotor_delay_time);
		if ($rotor_delay_time &&
		    ++$check_counter > 1/$rotor_delay_time) {
		    $check_counter=0;
		    if (!kill 0 => getppid()) {
			warn "Parent process stopped, quiting waitprocess\n";
			CORE::exit();
		    }
		}
	    }
	    CORE::exit();
	}
	$waitproc_pid;
    };
}

=head2 stop_waitproc

Stop the wait process. It is strongly advised to put the code between
the waitproc/stop_waitproc pair in a eval block. Otherwise, if an
exception occurs in the code between, the parent process will stop but
the wait process will continue.

=cut

sub stop_waitproc {
    if (defined $waitproc_pid) {
	kill 9 => $waitproc_pid;
	undef $waitproc_pid;
    }
}

=head2 progress

Usage:

    use Waitproc;
    $from = 0;
    $to = 10000;
    $i = Waitproc::progress($from, $to);
    for ($$i = $from; $$i < $to; $$i++) { ... }
    # for $$i ($from .. $to) does not work here...

Es gibt noch Bugs, z.B. werden Shared Memory und Semaphoren nicht
richtig gelöscht und verhindern so einen erneuten Start. (Ich glaube nur bei
Abbruch mit Signalen).

=cut

sub progress {
    my($from, $to) = @_;
    my $iter;
    eval {
	require IPC::Shareable;

	$waitproc_pid = fork;

	if ($waitproc_pid) { # Server
	    my %options = (
			   'key' => 'paint',
			   'create' => 'yes',
			   'exclusive' => 'no',
			   'mode' => 0644,
			   'destroy' => 'yes',
			  );
	    tie $iter, 'IPC::Shareable', 'prgrs', \%options;
	    $iter = $from;

	} else {

	    my %options = (
			'key' => 'paint',
			'create' => 'no',
			'exclusive' => 'no',
			'mode' => 0644,
			'destroy' => 'no',
		       );

	    my $i;
	    tie $i, "IPC::Shareable", 'prgrs', \%options;

	    $| = 1;
	    while (1) {
		printf "%d%% ...   \r", 100*($i-$from)/($to-$from);
		select(undef, undef, undef, 0.1);
		last if ($i >= $to);
	    }
	    CORE::exit();
	}
    };
    \$iter;
}

=head2 set([$rotor],[$rotor_delay])

Set the rotor string and/or the rotor delay time (currently 0.07
seconss). The default rotor string is '\|/-', but you can change it
to, say, '.oOo'.

=cut

sub set {
    my($in_rotor, $in_rotor_delay_time) = @_;
    if (defined $in_rotor) {
	$rotor = $in_rotor;
    }
    if (defined $in_rotor_delay_time) {
	$rotor_delay_time = $in_rotor_delay_time;
    }
}

1;

__END__
