package Subs::Sub;
##############################################################################
#
# DESCRIPTION: This class does very little by itself.
# DESCRIPTION: However, it provides an environment convenient for doing a
# DESCRIPTION: discrete stage of processing, which child classes may inherit.
#
# HISTORY
# HISTORY: $Log: Sub.pm,v $
# HISTORY: Revision 1.4 2014/02/27 06:38:17 apsop
# HISTORY: VERSION header now shows CVS Revision
# HISTORY:
# HISTORY: Revision 1.3 2007/01/31 15:35:16 apsop
# HISTORY: Initialize Xanadu before Ftools.
# HISTORY:
# HISTORY: Revision 1.2 2006/08/01 20:35:34 apsop
# HISTORY: Add in CVS history indicator.
# HISTORY:
# HISTORY: 1.0 -> 1.1 2000-08-11
# HISTORY: Now check for leftover .tmp files in the cleanup method
# HISTORY:
# HISTORY: 1.1 -> 1.2 2001-03-06
# HISTORY: Modified PATH environment variable to work with Linux.
# HISTORY: Added initialization for Xanadu.
# HISTORY:
# HISTORY: 1.2 -> 1.3 2002-03-28
# HISTORY: Added restart capability.
# HISTORY: Also added display argument to Xanadu bin initialization call.
# HISTORY: Xanadu.pm is now a little more sophisticated about checking
# HISTORY: for an Xserver.
# HISTORY:
# HISTORY: 1.3 -> 1.4 2002-04-18
# HISTORY: Put it a check for whether the job.par file exists
# HISTORY:
# HISTORY: 1.4 -> 1.5 2003-04-25
# HISTORY: Added support for generating temp_files and automatically
# HISTORY: cleaning them up at the end.
# HISTORY:
# HISTORY: 1.5 -> 1.6 2003-07-30
# HISTORY: Now looks for file_classes in a script-specific subdirectory
# HISTORY: if it does not find it in the lists directory.
# HISTORY:
# HISTORY: 1.6 -> 1.7 2003-10-10
# HISTORY: put in checks for whether FTOOLS and HEAdas directories exist
# HISTORY:
# HISTORY: 1.7 -> 1.8 2004-01-29
# HISTORY: Fixed a bug in the stopping mechanism
# HISTORY:
# HISTORY: 1.8 -> 1.9 2004-02-04
# HISTORY: fixed more bugs in the stopping/restarting mechanism
# HISTORY:
# HISTORY: 1.9 -> 2.0 2004-04-08
# HISTORY: Now use the HEAdas version of pget/pset
# HISTORY:
# HISTORY: 2.0 -> 2.1 2004-05-20
# HISTORY: Fixed a bug in temp_file which caused an infinite loop if the
# HISTORY: requested temp file already existed.
# HISTORY:
# HISTORY: 2.1 -> 2.2 2004-06-09
# HISTORY: Now allow a proc status of just "stopped", since this occurs when
# HISTORY: the stream daemon advances a sequence to the next processing level.
#
# VERSION: $Revision: 1.4 $
#
##############################################################################
use Util::Ftool;
use Util::HEAdas;
use Util::Parfile;
use Util::Log;
use Util::Filename;
use Util::Stool;
use Util::Xanadu;
use Util::FITSfile;
use Util::FileList;
use Util::FITSlist;
use Util::EventFileList;
use Util::Catalog;
use Util::HTMLcatalog;
use Util::Date;
use Util::Extractor;
use Util::CoreTags;
use strict;
##############################
# class data
##############################
my $PROCPAR;
my $JOBPAR;
my $LOG;
my $FILENAME;
my $COMTOP;
my $PROCTOP;
my $SCRIPT;
############################################################################
############################################################################
# This function called when the Sub module is compiled.
# It initializes the class data and sets up the processing environment.
############################################################################
sub BEGIN {
#############################
# for the perl debugger:
#############################
$DB::single = 1;
###########################################################
# set the PATH to a bare minimum. This doesn't matter for
# anything called directly from the perl script, but
# sometimes those things can call other things (e.g. xselect)
#############################################################
$ENV{PATH}="/bin:/usr/bin:/usr/local/bin:/usr/sbin:/usr/openwin/bin";
#############################################################
# confirm that the library path for mission specific
# modules is set to the directory containing the main script
#############################################################
my ($dir)= $0 =~ /^(.*)\/[^\/]*$/;
foreach (@INC) {
if( $_ eq $dir) { $PROCTOP=$_};
}
if(! defined $PROCTOP ) {
print STDERR "Script directory $dir not in library path\n";
print STDERR "Check the \"use\" statements in the main script\n";
exit 3;
}
########################################################
# now get the name of the top directory containing the
# common, mission-independant modules
########################################################
$dir =~ s|/[^/]*/[^/]*$||;
# $dir =~ s/\/[^\/]*\/[^\/]*$//;
$dir .= "/common";
foreach (@INC) {
if( /^$dir/ ) { $COMTOP=$_};
}
if( ! defined $COMTOP ){
print STDERR "Could not find common module directory in library path\n";
print STDERR "Check the \"use\" statements in the main script\n";
exit 3;
}
#######################
# get the script name
#######################
$SCRIPT=$0;
# $SCRIPT =~ s/^.*\///;
$SCRIPT =~ s|^.*/||;
####################################
# make sure the job.par file exists
####################################
unless( -f "job.par" ) {
print STDERR "No job.par file in this directory\n";
exit 3
}
##########################################################
# copy the processing par file into the current directory
# if it is not already here
##########################################################
my $procpar="$SCRIPT.par";
if(! -e $procpar ) {
unless(open IN, "<$PROCTOP/$procpar") {
die "Can't copy $procpar to working directory";
}
open OUT, ">$procpar";
print OUT <IN>;
close IN;
close OUT;
}
#####################################
# set job.par and process.par objects
#####################################
$PROCPAR=Util::Parfile->new($procpar);
$JOBPAR =Util::Parfile->new("job.par");
###############################################################
# now set the default LD_LIBRARY_PATH. Note that we do not
# want to inherit LD_LIBRARY_PATH (or anything else)
# from the environment, since the proc script need to run the
# same way each time regardless of where it is run.
###############################################################
$ENV{LD_LIBRARY_PATH}="";
if($PROCPAR->has("lib_path") ) {
$ENV{LD_LIBRARY_PATH}=$PROCPAR->read("lib_path");
}
########################################
# set the "pget" program name
# for all parfile strutures
########################################
my $ftools=$PROCPAR->read("ftools");
if( ! -d $ftools ) {
print STDERR "Invalid FTOOLS directory $ftools\n";
exit 3;
}
Util::Parfile->ftools($ftools);
# Set up the CALDB environment, if needed.
if ($PROCPAR->has('caldb')) {
my $caldb = $PROCPAR->read('caldb');
if (not -d $caldb) {
print STDERR "Invalid CALDB directory $caldb\n";
exit 3;
}
$ENV{CALDB} = $caldb;
$ENV{CALDBCONFIG} = "$caldb/software/tools/caldb.config";
$ENV{CALDBALIAS} = "$caldb/software/tools/alias_config.fits";
}
########################################################
# set the FTOOLS and STOOLS, and XANADU bin directories
# we need to do this early on, so that LD_LIBRARY_PATH
# will be set for the FTOOLS and pget get will work
# and we can read parfiles
########################################################
Util::Xanadu->bin("$ftools/bin", $PROCPAR->read("display") );
Util::Ftool->install_dir($ftools);
Util::Stool->bin($PROCPAR->read("stools") );
##########################################
# HEAdas software, if needed
##########################################
if($PROCPAR->has("headas") ) {
my $headas = $PROCPAR->read("headas");
if(! -d $headas) {
print STDERR "Invalid HEAdas directory $headas";
exit 3;
}
Util::HEAdas->install_dir($headas);
Util::Parfile->ftools($headas);
}
#######################################
# create a filename generator object
#######################################
my $file_classes="$PROCTOP/lists/file_classes";
if( ! -f $file_classes) {
$file_classes="$PROCTOP/lists/$SCRIPT/file_classes";
}
if( ! -f $file_classes) {
print STDERR "Could not find file_classes file\n";
exit 3;
}
###########################################################################
# determine which filename class to use.
# First it looks for the name of the class in the $::FILENAME
# variable. This way you may explicitly name the filename class to be used
# by putting "BEGIN {$::FILENAME=<module name>}" before any of the
# "use <module>" statements in the main program
#
# If this is not sucessful, we try the module "Util::[SCRIPT]Filename"
# where [SCRIPT] is the uppercased script name.
#
# Finally if this also fails, try "Util::Filename", which is the generic
# filename class.
###########################################################################
my $module;
foreach $module ($::FILENAME || "null",
"Util::".uc($SCRIPT)."Filename",
"Util::Filename") {
$FILENAME=eval {$module->new($JOBPAR, $PROCPAR, $file_classes)};
if(defined $FILENAME) {last}
}
if(! defined $FILENAME ) {
print STDERR "Could not create a filename object\n";
print STDERR "$@\n";
exit 3;
}
######################################
# create the log object
# and tell the other objects about it
######################################
$LOG = Util::Log->new($FILENAME,$JOBPAR);
Util::Parfile->log($LOG);
Util::Tool->log($LOG);
Util::Filename->log($LOG);
#####################################
# read the input file catalog
#####################################
$FILENAME->set_input_files();
################################################
# set the filename object in the Catalog class
################################################
Util::Catalog->filename($FILENAME);
################################
# set up the Date utility class
################################
Util::Date->init_class($PROCPAR->read("refdate"),
$PROCPAR->read("reftime"),
$FILENAME );
################################################
# set the checksum program to be used by the
# FITSfile class to calculate site-independant
# checksums
################################################
Util::FITSfile->checksum_tool( $PROCPAR->read("checksum_tool") );
##########################################
# set the mission for the Extractor class
##########################################
Util::Extractor->mission($JOBPAR->read("mission"));
$LOG->milestone("Processing started");
} # end of BEGIN function
#########################################################################
# The options are read and stored so that child classes may use them.
# Typically the constructor will be invoked with no arguments.
#########################################################################
sub new { # (option1,option2...)
my $proto = shift;
my $class = ref($proto) || $proto;
my $self={};
###################################
# read options
###################################
$self->{OPTIONS}={};
my $arg;
while($arg=shift) {
if($arg =~ /=/ ) {
my ($key,$value)=split /=/, $arg;
$self->{OPTIONS}->{$key}=$value;
} else {
$self->{OPTIONS}->{$arg}="set";
}
}
$self->{TMP_FILES} = [];
bless($self,$class);
return $self;
}
#################################
# ACCESSORS:
#################################
# The log object.
sub log {
my $self = shift;
if (@_) { $LOG = shift }
return $LOG;
}
# Parfile for the processing script.
sub procpar {
my $self = shift;
if (@_) { $PROCPAR = shift }
return $PROCPAR;
}
# Sequence-specific parfile object.
sub jobpar {
my $self = shift;
if (@_) { $JOBPAR = shift }
return $JOBPAR;
}
# filename generator object
sub filename {
my $self = shift;
if (@_) { $FILENAME = shift }
return $FILENAME;
}
# The top level directory for the mission specific code
sub proctop {
my $self = shift;
if (@_) { $PROCTOP = shift }
return $PROCTOP;
}
# The top level directory for the mission independant code
sub comtop {
my $self = shift;
if (@_) { $COMTOP = shift }
return $COMTOP;
}
# Name of the processing script.
sub script {
my $self = shift;
if (@_) { $SCRIPT = shift }
return $SCRIPT;
}
sub option {
my $self=shift;
my $option=shift;
return $self->{OPTIONS}->{$option};
}
##############################################################
# return a unique name for this subroutine
##############################################################
sub name {
my $self = shift;
return ref($self);
}
##################################################################
# METHODS:
##################################################################
##################################################################
# check the proc_command and proc_status job.par parameters
# to see if we need to do anything special like stop or resume
# This method returns true if we should not continue on to run the
# subroutine
##################################################################
sub check_commands {
my $self = shift;
my $jobpar = $self->jobpar();
my $procpar=$self->procpar();
my $log = $self->log();
###############################################
# check the status to see if we are in some
# funny state
###############################################
my $status = $jobpar->read("proc_status");
if($status) {
####################################################
# There is some status.
# Reset the command parameter, since we want to
# ignore all commands given after the status was set.
# Note however, that we do not erase a "killed"
# status, since this means someone is about to
# kill the script by hand, and the "killed" status
# will document that this was done on purpose
# (e.g. for the stream daemon)
####################################################
if($status !~ /^killed/) { $jobpar->set({proc_command=>""}) }
my ($before) = $status =~ /^stopped before (.*)$/;
if($before) {
#####################################
# processing was previously stopped
# make sure the current and previous
# script versions match
#####################################
if($jobpar ->read("procscriptver") ne
$procpar->read("version") ) {
######################
# version mismatch
######################
$log->error(3, "Can't restart, since stopped from version ".
$jobpar->read("procscriptver") );
}
#####################################
# check if this is where we stopped
####################################
if($before eq $self->name() ) {
###############################
# this is where we stopped
# so log the resumption and
# just keep going
###################################
$jobpar->set({proc_status=>""});
$log->entry("Resumed processing at beginning of ".
$self->name());
} else {
####################################################
# this is not the right place to resume processing
# we return a true value to indicate to the run method
# that this subroutine should be skipped
####################################################
return 1;
}
} elsif($status eq "stopped") {
###########################################################
# This happens when the proc script advances frm one
# stage of processing to the next. It means just proceed
# from the beginning
##########################################################
$jobpar->set({proc_status=>""});
} else {
############################
# unknown status
############################
$log->error(1, "Unknown processing status $status");
$jobpar->set({proc_status=>""});
}
} # end if the status was set
###########################################
# check for commands
###########################################
my $command = $jobpar->read("proc_command");
if($command) {
##########################################
# there was a command. Reset the command
# in the parfile no matter what it is
##########################################
$jobpar->set({proc_command=>""});
if( $command eq "stop" ) {
########################################################
# we were commanded to stop at the beginning
# of the next subroutine - so here we are.
# Mark the place where we stopped and exit.
# Oh, and we also make sure the script version
# is recorded so when we restart we can make sure it
# is with the same script
#######################################################
$jobpar->set({proc_status => "stopped before ".$self->name(),
procscriptver => $procpar->read("version") } );
$log->entry("Processing stopped just before ".$self->name() );
$log->milestone("Processing interrupted" );
$log->keep_open();
exit 0;
} else {
##############################################
# don't understand the command
# so log an error and keep going
#############################################
$log->error(1, "unknown command $command");
}
} # end if there was a command
return 0;
} # end of check_commands method
###############################################################################
# Returns the name of a temporary file whose name is based on the given string.
# The file name is guaranteed to be unique. In other words, there will be
# no existing files with the returned name at the time this method is called.
# The object keeps track of the temporary file names and deletes them
# in the cleanup phase.
###############################################################################
sub temp_file {
my $self = shift;
my $tag = shift;
my $tmp;
my $index="";
for($tmp="$tag$index.tmp"; -f $tmp; $index++) {$tmp="$tag$index.tmp"}
push @{$self->{TMP_FILES}}, ($tmp);
return $tmp;
} # end of tmp_files method
###############################################################################
# Do initialization before running the body method.
# Puts a "milestone" entry in the log.
##############################################################################
sub init {
my $self = shift;
####################################################
# mark the beginning of the subroutine in the log
####################################################
my $description=$self->{DESCRIPTION} || "Running ".ref($self);
$self->log()->milestone("$description");
}
##############################################################################
# Do any necessary cleanup after running the body method.
# This method doesn't do much, but it may be
# overridden by sub-classes.
##############################################################################
sub cleanup {
my $self = shift;
#########################################################
# delete any temporary files whose names were generated
# by calls to the temp_file method
#########################################################
foreach (@{$self->{TMP_FILES}}) {
unlink $_;
}
#####################################################
# check for any leftover ".tmp" files and give
# a warning about them to make it easier to track
# down their source
#####################################################
my @files=glob("*.tmp");
if(@files) {
$self->log()->error([ 1, UNKNOWN_FILE ], "Leftover temporary file(s): ".
join(", ", @files) );
}
} # end of cleanup method
#############################################################
# This is the method which does all the work. For this general
# class it does nothing, but it is intended to be overridden
# in sub-classes
#############################################################
sub body {
}
#####################################################################
# This method should be invoked to run the subroutine. It calls
# the init, body, and cleanup methods in that order
#####################################################################
sub run {
my $self=shift;
######################################################
# check the commands and see if we should continue on
######################################################
if($self->check_commands() ) {return}
$self->init();
$self->body();
$self->cleanup();
}
1;