Util::Filename (version $)


package Util::Filename;

##############################################################################
#
# DESCRIPTION: This is a generic filename generator. 
# DESCRIPTION: Files are grouped into types, each type having a specific
# DESCRIPTION: name. This class allows you to refer to files by their type
# DESCRIPTION: and a list of their properties, such as instrument, mode, etc.
# DESCRIPTION: This way you may modify the file names without having to 
# DESCRIPTION: modify the processing script in multiple places.
# DESCRIPTION: 
# DESCRIPTION: This class also knows some things about the output catalogs
# DESCRIPTION: for the processing script. It knows the file class for
# DESCRIPTION: each file type and catalog, and it also knows a human-readible
# DESCRIPTION: description of each file type.
# DESCRIPTION: 
# DESCRIPTION: This class gets most of its information from the file_classes
# DESCRIPTION: list file for the processing script. The constructor reads this
# DESCRIPTION: file and remembers its contents.
# DESCRIPTION: 
# DESCRIPTION: This is a mission-independant class.
# DESCRIPTION: It knows about the
# DESCRIPTION: file names of universal things like log files, catalogs, etc.,
# DESCRIPTION: and can read the input catalog and the processing parameter file 
# DESCRIPTION: to get more file names. 
# DESCRIPTION: Although you can get alot of mileage out of this class,
# DESCRIPTION: nearly all missions need to create a sub-class of this one
# DESCRIPTION: to handle the particular needs of that mission.
#
# HISTORY
# HISTORY: $Log: Filename.pm,v $
# HISTORY: Revision 1.3  2014/02/27 07:01:06  apsop
# HISTORY: VERSION header now shows CVS Revision
# 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-06-20
# HISTORY: Split off a generic fetch method which is now called by fetch_cal
# HISTORY:
# HISTORY: 1.1 -> 1.2 2000-08-15
# HISTORY: Fetch method now checks if a file name has been specified.
# HISTORY: 
# HISTORY: 1.2 -> 1.3 2002-04-23
# HISTORY: Now pad "seqprocnum" to be three digits long
# HISTORY: 
# HISTORY: 1.3 -> 1.4 2002-05-14
# HISTORY: Fixed a bug in the "exist" method. Seems this method had never
# HISTORY: been used. Happy birthday to me.
# HISTORY: 
# HISTORY: 1.4 -> 1.5 2003-04-28
# HISTORY: Anumber of additions to facilitate a shared repository
# HISTORY: 
# HISTORY: 1.5 -> 2.0 2003-04-30
# HISTORY: Substantial modifications to allow selecting files in a directory
# HISTORY: other than the working directory. Now template generation, 
# HISTORY: globbing and filtering are in separate methods called by get.
# HISTORY:
# HISTORY: 2.0 -> 2.1 2004-01-22
# HISTORY: Now use a system call to fetch files.
# HISTORY:
# HISTORY: 2005-05-27
# HISTORY: Add capability a file type to have more than one file class
# HISTORY:
#
# VERSION: $Revision: 1.3 $
#
##############################################################################


use Util::NoLog;
use strict;

my $LOG;

########################################################################
# This mostly reads the file_classes list file and stores its contents
# internally.
########################################################################
sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;

    my $jobpar       = shift;
    my $procpar      = shift;
    my $file_classes = shift;

    my $self={};

    $self->{MISSION} = $jobpar->read("mission");;
    $self->{SEQUENCE}= $jobpar->read("sequence");
    $self->{VERSION} = sprintf("%03d", $jobpar->read("seqprocnum") ); 
    $self->{JOBPAR}  = $jobpar;
    $self->{PROCPAR} = $procpar;

    $self->{SERIOUSNESS}=2; #error level 

    $self->{GLOB_DIR}=""; # directory where filename globbing will happen

    ######################################################
    # set the calibration file directory if there is one
    ######################################################
    if($procpar->has("caldir") ) {
        $self->{CALDIR}=$procpar->read("caldir");
    } else {
        $self->{CALDIR}='';
    }
        
    ######################################
    # files included in the input catalog
    ######################################
    $self->{INPUT_FILES}={};

    ####################################################
    # generic filename arguments to the get method
    # to be used by the any method
    ####################################################
    $self->{GENERIC_ARGS}= ["*","*","*","*","*"];

    #############################
    # read the file_classes file
    #############################
    my $type;
    my $tapeclass;
    my $trendclass;
    my $description;


    $self->{CLASS} = {};
    $self->{TYPES} = {};
    $self->{TYPES}->{all}=();

    $self->{DESCRIPTION} = {};

    $self->{CATALOGS}=[];

    open CLASSES, "<$file_classes";
    while(<CLASSES>) {

        chomp;

        ###############################
        # skip  blanks and a comments
        ###############################
        if(/^\s*$/ || /^\s*#/ ) { next }

        ###########################################
        # parse the line
        ############################################
        my ($type,@classes)=split /\s*\|\s*/;
        my $description = pop @classes;

        ##############################################
        # set the catalog list on the first iteration
        ##############################################
        if(! @{$self->{CATALOGS}}) {

            ########################################################
            # set the catalog list from the first line of the file
            # or use the default
            ########################################################
            if($type eq "type" ) { @{$self->{CATALOGS}}=@classes               }
            else                 { @{$self->{CATALOGS}}=("tapecat","trendcat") }

            ##################################################
            # initialize various lists and things
            ##################################################
            my $cat;
            foreach $cat (@{$self->{CATALOGS}}) {
                $self->{TYPES}->{$cat}=[];
                $self->{CLASS}->{$cat}={};
            }

            if($type eq "type" ) {next}

        } # end if CATALOGS list is undefined

        ####################################
        # set the classes for this type
        ####################################
        push @{$self->{TYPES}->{all} }, ($type);

        my $cat;
        foreach $cat (@{$self->{CATALOGS}}) {

            my $class=shift @classes;
            if($class && $class ne "none" ) {
	        $self->{CLASS}->{$cat}->{$type} = []
		  unless $self->{CLASS}->{$cat}->{$type};
                #############################################
                # this file type appears in this catalog
                #############################################
                push @{$self->{TYPES}->{$cat} }, ($type);
                push @{ $self->{CLASS}->{$cat}->{$type} }, ($class);
            }

        } # end of loop over catalogs

        ############################################
        # store the description for this file type
        ############################################
        $self->{DESCRIPTION}->{$type}=$description;
            
    }
    close CLASSES;

    #################################################
    # Make sure there is only on entry for each type
    #################################################
    foreach my $cat (@{$self->{CATALOGS}}) {
      my @types = @{$self->{TYPES}->{$cat} };
      my %temp;
      @temp{@types} = (1) x @types;
      @{$self->{TYPES}->{$cat} } = keys %temp;
    }

    bless($self,$class);
    return $self;

}

######################
# ACCESSORS:
######################

###############################################
# get or set the log object in the class data
###############################################
sub log {
    my $self = shift;
    if (@_) { $LOG = shift }
    
    if($LOG) {return $LOG}
    else     {return Util::NoLog->new() }
}

#############################################################
# get or set the processing parfile object in the class data
#############################################################
sub procpar {
    my $self = shift;
    if (@_) { $self->{PROCPAR} = shift }
    return $self->{PROCPAR};
}

#############################################################
# get or set the job.par parfile object in the class data
#############################################################
sub jobpar {
    my $self = shift;
    if (@_) { $self->{JOBPAR} = shift }
    return $self->{JOBPAR};
}

######################################################################
# get or set the level of errors issued by the methods of this class
######################################################################
sub seriousness {
    my $self = shift;
    if (@_) { $self->{SERIOUSNESS} = shift }
    return $self->{SERIOUSNESS};
}



######################################################################
# returns the mission specifier string
######################################################################
sub mission {
    my $self = shift;
    return $self->{MISSION};
}

######################################################################
# returns the sequence number identifying the current dataset
######################################################################
sub sequence {
    my $self = shift;
    return $self->{SEQUENCE};
}

######################################################################
# returns the processing version number for the current processing of the
# current dataset
######################################################################
sub version {
    my $self = shift;
    return $self->{VERSION};
}

######################################################################
# return an array of the file types for all the output catalogs
######################################################################
sub catalog_types {
    my $self=shift;
    return @{$self->{CATALOGS}};
}

############################################################################
# Set the directory where filename globbing will occur. 
# Note this is reset to an empty string by the get method
############################################################################
sub glob_dir {
    my $self = shift;
    my $dir = shift;

    #################################
    # make sure it ends with a slash
    #################################
    if($dir && $dir !~ m|/$| ) {
        $dir .= "/"
    }
    
    ##########################
    # set the member variable
    ##########################
    $self->{GLOB_DIR} = $dir || "";
}

############################################################################
#
############################################################################
sub sequence_specific {
    my $self = shift;

    return $self->{MISSION} . $self->{SEQUENCE};
}

############################################################################
#
############################################################################
sub version_specific {
    my $self = shift;

    return $self->sequence_specific() . "_" . $self->{VERSION};
}





#############################################
# read the file types from the input catalog
#############################################
sub set_input_files {
    my $self=shift;

    #######################################################
    # get the name of the input catalog file and make
    # sure it's readable - otherwise it's a fatal problem.
    #######################################################
    my $inputcat=$self->{JOBPAR}->read("inputcat");

    unless( -r $inputcat ) {
        $self->log()->error(3,"Can't read inputcat $inputcat");
    }

    my $file;
    my $type;

    foreach (split /\n/, Util::FITSfile->new($inputcat,1,)
                                      ->cols("FILENAME TYPE")
                                      ->dump_table()              ) {
        chomp;
        ($file,$type)=split;

        #####################################################
        # don't take the name of the tapecat file from the 
        # input cat, since that is the tapecat from the
        # previous level of processing not for this level 
        # of processing
        #####################################################
        if($type eq "tapecat") {next};

        #################################################
        # append the file to the list of the given type 
        #################################################
        if(!$self->{INPUT_FILES}->{$type}) {$self->{INPUT_FILES}->{$type}=()}
        push @{$self->{INPUT_FILES}->{$type}}, ($file);

    }

} # end of set_input_files_method


####################
# METHODS:
####################

######################################################################
# returns the human-readable description for a given file type
######################################################################
sub description {
    my $self = shift;
    my $type=shift;

    return $self->{DESCRIPTION}->{$type};
}


######################################################################
# return an array of all the file types for a given output catalog
######################################################################
sub types {
    my $self = shift;
    my $catalog=shift || "all";

    return @{$self->{TYPES}->{$catalog} };
}

######################################################################
# return the class for a given file type as it will appear in a given
# output catalog.
######################################################################
sub class {
    my $self=shift;
    my $catalog=shift;
    my $type=shift;

    return $self->{CLASS}->{$catalog}->{$type};
}


#########################################################################
# returns a list if filename templates possible containing wildcards.
#########################################################################
sub glob_template {
    my $self=shift;

    my $type =shift;
    my $inst =shift || "";
    my $index=shift || "";
    my $mode =shift || "";

    ########################################
    # give the file name of the chosen type
    ########################################
    if( $self->procpar()->has($type) ) {
        ###################################################
        # read the file name from the procpar file
        ###################################################
        return ($self->procpar()->read($type));

    } elsif($type eq "joblog" ) {
        ###########################
        # job log
        ############################
        return ($self->version_specific() . "_joblog.html");

    } elsif($type eq "errlog" ) {
        ###########################
        # error log
        ###########################
        return ($self->version_specific() . "_errlog.html");

    } elsif($type eq "logindex" ) {
        ###########################
        # Job log index
        ###########################
        return ($self->version_specific() . "_index.html");

    } elsif($type eq "header" ) {
        ###########################
        # HTML header page
        ###########################
        return ($self->version_specific() . "_hdr_page.html");

    } elsif($type eq "fileinfo" ) {
        ###########################
        # HTML file list
        ###########################
        return ($self->version_specific() . "_file_info.html");

    } elsif($type eq "inputcat" ) {
        ###########################
        # input file catalog
        ###########################
        return ($self->{JOBPAR}->read("inputcat"));

    } elsif($type eq "jobpar" ) {
        ############################
        # Product catalog file
        ############################
        return ($self->version_specific() . "_job.par");

    } elsif($type eq "procpar" ) {
        ############################
        # Product catalog file
        ############################
        return ($self->version_specific() . "_process.par");


    } elsif($type eq "tapecat" ) {
        ############################
        # Product catalog file
        ############################
        return ($self->sequence_specific() . "_tape.cat");

    } elsif($type eq "trendcat" ) {
        ############################
        # Product catalog file
        ############################
        return ($self->sequence_specific() . "_trend.cat");

    } elsif($type eq "checksum" ) {
        ######################################
        # site independant FITS checksum list
        ######################################
        return $self->version_specific() . "_check.txt";

    } elsif($type eq "eventlog" ) {
        ######################################
        # site independant FITS checksum list
        ######################################
        return $self->version_specific() . "_evt.xml";

    } elsif($type eq "orbit") {
        ##################################
        # orbit file
        ##################################
        return $self->fetch_orbit("just gimme the name");

    } else {
        #########################
        # unknown 
        #########################
        $self->error("Unknown file type: $type");
        return "";
    }


} # end of glob_template method

#########################################################################
# Do filename globbing on a list of files in the current $self->{GLOB_DIR}
# This is called by the get method
#########################################################################
sub glob {
    my $self = shift;
    my @templates = @_;

    my @files = ();
    my $template;

    foreach $template (@templates) {
      push @files, glob("$self->{GLOB_DIR}$template");
      unless( @files == 1 && $files[0] eq "$self->{GLOB_DIR}$template" ){
	push @files, glob("$self->{GLOB_DIR}$template.gz");
      }
    }

    return (@files);
} # end of glob method

#########################################################################
# takes a reference to a list of filenames, plus a set of file descriptor
# arguments and returns a list of files valid for those descriptors.
# This method always returns the input list. Subclasses may overrirde this.
# This method is used by the "get" method.
#########################################################################
sub filter {
    my $self = shift;
    my $list = shift;

    return (@{$list});

}

#########################################################################
# removes the glob directory path from a file name
#########################################################################
sub remove_path {
    my $self = shift;
    my $name = shift;

    if($name && $self->{GLOB_DIR}) {

        $name =~ s|^$self->{GLOB_DIR}||;

    }

    return $name;
}



#########################################################################
# generate a filename of a given type
#########################################################################
sub get {
    my $self=shift;
    my @args = @_;

    my @templates = $self->glob_template(@_);
#print "templates=@templates\n";
    my @files     = $self->glob(@templates);
#print "globbed files=@files\n";
    my @filtered  = $self->filter(\@files, @args);
#print "filtered files=@filtered\n";

    ###################################
    # strip off the directory path and
    # reset the glob directory.
    ###################################
    if($self->{GLOB_DIR}) {
        my $path = $self->{GLOB_DIR};
        foreach (@filtered) {
            $_=$self->remove_path($_);
        }

        ########################################################
        # set the glob directory back to the working directory
        #######################################################
        $self->glob_dir();
    }

    #########################################################
    # return an array or a joined list depending on context
    #########################################################
    return (@filtered) if wantarray;
    return join "\n", @filtered if (@filtered && $filtered[0]);
    return "";

} # end of get method
    


########################################################################
# Produce an array of all the files of a given type
########################################################################
sub any {
    my $self=shift;
    my $type=shift;

    if($self->{INPUT_FILES}->{$type}) {
        #################################
        # this is an input file type
        #################################
        return @{$self->{INPUT_FILES}->{$type}};

    } else {
        ##########################################
        # generate a generic filename
        ##########################################
        my @list = ($self->get($type,@{$self->{GENERIC_ARGS}}));
        return (@list);
    }


}

##############################################################################
##############################################################################
# Extract the components of a filename and return them in an array
# The components are the arguments to the "get" method which would
# be required to produce this file name.
# This method doesn't know how to parse anything and should
# be overridden by mission specific sub-classes.
##############################################################################
sub parse {
    my $self=shift;
    my $filename=shift;
    my $type=shift;

    ############################################
    # right now the generic method does nothing
    ############################################
    $self->error("Can't parse $type file $filename");
    return ();
}

###########################################################################
###########################################################################
# parse a file name and return the file of a different type which would
# have had the same arguments.
###########################################################################
sub corresponding {
    my $self=shift;
    my $old_type=shift;
    my $new_type=shift;
    my $old_name=shift;

    return $self->get($new_type, $self->parse($old_name, $old_type) );

}



################################################################################
# Copy a named file from a given directory into the working directory.
# If the directory is omitted, it defaults to the calibration file directory.
# Returns true is a file was actually fetched. Returns 0 otherwise, either
# becuase the file already exists in the working directory or because of error.
################################################################################
sub fetch {
    my $self=shift;
    my $file=shift;
    my $dir=shift || $self->{CALDIR};

    my $log=$self->log();

    ########################################
    # check that there is a file name given
    ########################################
    unless($file) {
        $self->error("No file name given for fetching");
        return;
    }

    ##########################################################
    # do we already have the file or do we need to go get it?
    ##########################################################
    if( ! -e $file ) {
        ################################################
        # need to copy file from calibration repository
        #################################################
        if( -e "$dir/$file") {
	  $log->entry("Fetching $file from $dir");
	  system("cp $dir/$file .");
	  return 1;
        }else{
	  $self->error("File $dir/$file does not exist");
	}

    } else {
        ########################################
        # file already exists
        ########################################
        my $log;
        if($log=$self->log()) {
            $log->entry("$file is already present in the working directory");
        }
    }

    return 0;

} # end of fetch method

################################################################################
# Fetch a calibration file specified by the arguments which would
# be given to the get method to specify its name. The method returns the
# name of the fetched file.
################################################################################
sub fetch_cal { #(type, arg1, arg2,...)
    my $self=shift;

    my $log=$self->log();

    ###################################
    # get the name of the actual file
    ##################################
    $self->glob_dir($self->{CALDIR});
    my $file=$self->get(@_);

    #######################################################
    # return an empty string if we didn't get a file name
    #######################################################
    if(!$file) {return $file}

    #####################################
    # fetch the file
    #####################################
    $self->fetch($file);

    ################################
    # return the name of the file
    ################################
    return $file ;

} # end of fetch_cal method


########################################################################
########################################################################
# copy the orbit file into the current working directory and
# return the name of the (local) orbit file.
# If an argument is given which evaluates to true, this method
# will not actually fetch the file, but will just return the name.
# That's so the "get" method can use the same parfile reading 
# and parsing code.
########################################################################
sub fetch_orbit {
    my $self=shift;
    my $justname=shift;

    #####################################################
    # get the full path to the file in the job.par file
    #####################################################
    my $path = $self->jobpar()->read("orbit");

    my ($file) = $path =~ /\/([^\/]*)$/;

    unless( -e $file or $justname ) {
        ##########################
        # file needs to be copied
        ##########################
        my $log = $self->log();

        if( -e $path ) {
            ###################################################
            # file exists in the parent directory, so copy it
            ###################################################
            $log->entry("Copying orbit file $path to working directory");
            
            system("cp $path .");

#             open IN,  "<$path";
#             open OUT, ">$file";
#             print OUT <IN>;
#             close OUT;
#             close IN;
        } else {
            #############################################
            # file does not exist in parent directory
            #############################################
            $log->error(2,"Orbit file $path does not exist");
        }
    }

    return $file;

} # end of fetch_orbit method



########################################################################
########################################################################
# This method takes a list of file names and 
# returns a  list of all the files which actually exist.
# Note this method does not do any globbing.
########################################################################
sub exist {
    my $self=shift;

    my @out=();

    foreach (@_) {
        if( -f) { push @out, ($_) }
    }

    return (@out);

} # end of exist method


#################################
# internal error handling method
#################################
sub error {
    my $self=shift;
    my $message=shift;

    my $log=$self->log();

    
    if($log) {
        ############################################
        # log is defined so use it to give an error
        ############################################
        $log->error($self->{SERIOUSNESS},$message);

    } else {
        ###################################
        # we don't have the log yet, so just
        # write to stderr and quit
        ####################################
        print STDERR "Error$self->{SERIOUSNESS}: $message\n";
        exit $self->{STATUS};
    }

}


1;