###############################################################
# Script   : TapeSchedule.pl
# Language : Perl 5.004 (the standard Perl distribution from
#            http://www.perl.com/CPAN NOT the Perl from the
#            windows NT resource kit!)
# Author   : Reinoud van Leeuwen (reinoud@xs4all.nl)
# Purpose  : This script generates a schedule to use backup tapes
#            The schedule keeps the following rules:
#            - it is possible to restore:
#              * each day for the last week
#              * each last weekday for the last "month"
#              * each last "month"day since the beginning of the schedule
#            - tapes are archive after a number of times (20 times when
#              backups are scheduled 5 times a week in a "month" of
#              4 weeks)
# Status   : Freeware : use, change, learn, steal in any way you like :-)
# Portability: To all platforms supported by Perl.
#            - The output of the -b option is typical for DOS/Windows NT
#              environments.
#            - The -b option uses the environment variable COMPUTERNAME which
#              is the NetBios name of a Windows NT server or workstation. If
#              it is not set, the output will be like SET TAPENAME=TAPE_001.
#
# $Date: 1999/10/04 15:15:08 $
#
# $Revision: 1.20 $
#
###############################################################


##########################################################
# Used modules
##########################################################
use Getopt::Long ;      # command-line parsing
use Date::Calc qw(:all);

##########################################################
# user defineable settings
##########################################################
my $daysinweek = 5;              # number of times backup is performed per week
my $weekspermonth = 4;           # number of weeks in 1 roulation
my $monthsinschedule = 8;        # number of roulations to print
my $default_tapenameprefix=$ENV{COMPUTERNAME} ? $ENV{COMPUTERNAME}."_TAPE_" : "TAPE_";

##########################################################
# Constants
##########################################################
my @weekday  = ("Sun","Mon","Tue","Wed","Thu","Fri","Sat");
my @cleanval = ("  ","C ");                                   # values for cleaning
my @YESNO    = ("NO","YES");
my $initialtapes = $daysinweek + $weekspermonth - 1;
my $version = (split ' ','$Revision: 1.19 $')[1];             # Revision is filled in automatically by RCS


# parse command-line options
GetOptions ("help","now","startdate:s","prefix:s","batch","yesterday","tomorrow","tapedate:s","cleaning:n")
  or die "\nscript $0 aborted while parsing command-line: $!\n\n";

unless ( $opt_batch ) {print "\nTapeSchedule $version\n(c) by Reinoud van Leeuwen (reinoud\@xs4all.nl)\n\n";}

$prefix = ($opt_prefix)? $opt_prefix : $default_tapenameprefix;

# print info block and exit when option -h[elp] is set
if ( $opt_help )
{   print <<"EOF";

usage:
TapeSchedule -help
TapeSchedule [-n[ow]] [-s[tartdate] DD/MMM/YYYY] [-ta[pedate] DD/MMM/YYYY] [-b[atch]]
             [-p[refix] NAME] [-y[esterday]] [-to[morrow]] [-c[leaning] N]

-h         Displays help information (this screen).
-now       Only prints tapenumber to use today (default: off)
-batch     Prints a line like "SET TAPENAME=TAPE_001"  (default: off)
-prefix    Specifies tape name prefix (default: "%COMPUTERNAME%_TAPE_")
-yesterday Takes tapenumber from yesterday (for use in batch files that
           run after midnight) (default: off)
-tomorrow  Takes tapenumber from tomorrow (for use in batch files that
           run before midnight) (default: off)
-cleaning  specifies cleaning tape interval in number of backups (default:off)
-startdate Specifies starting date of schedule in the format DD/MMM/YYYY. When
           a schedule is printed, the current month and the next 7 months are
           printed. (default: last monday)
-tapedate  Specifies a date to calculate the tapenumber for (default: today)

 The script prints 8 months of schedule, unless options -now or -batch is used.
 If starting date is not a monday, the previous monday is used.

EOF
exit;
}

# compute start of schedule
if ( $opt_startdate )
{
    ($startyear,$startmonth,$startday) = Decode_Date_EU($opt_startdate);
    if ($startyear < 1950 ) { $startyear += 100};
    check_date ($startyear,$startmonth,$startday) or die "$opt_startdate is not a valid date\n";
}
else
{
    ($startyear,$startmonth,$startday) = Today();
}

# tapeschedules always start on a monday
my $weeknumber = (Week_of_Year ($startyear,$startmonth,$startday))[0];
($startyear,$startmonth,$startday) = Monday_of_Week($weeknumber, $startyear);


# compute day to calculate tapenumber
if ( $opt_tapedate )
{
    ($calcyear,$calcmonth,$calcday) = Decode_Date_EU($opt_tapedate);
    if ($calcyear < 1950 ) { $calcyear += 100};
    check_date ($calcyear,$calcmonth,$calcday) or die "$opt_tapedate is not a valid date\n";
}
else
{
    ($calcyear,$calcmonth,$calcday) = Today();
}

unless ( $opt_batch )
{
    print "Starting date of schedule: $startday/$startmonth/$startyear \n";
}


if ( $opt_yesterday  and $opt_tomorrow)
{
    die "Options yesterday and tomorrow are mutually exclusive!\n";
}

# step one day back at option -y[esterday]
if ( $opt_yesterday )
{
    ($calcyear,$calcmonth,$calcday) = Add_Delta_Days($calcyear,$calcmonth,$calcday,-1);
    unless ( $opt_batch ) { print "taking yesterdays tape.\n" };
};

if ( $opt_tomorrow )
{
    ($calcyear,$calcmonth,$calcday) = Add_Delta_Days($calcyear,$calcmonth,$calcday,1);
    unless ( $opt_batch ) { print "taking tomorrows tape.\n" };
};


# if now is on a day that is not in the schedule walk to the next day that is in the schedule
while (( Day_of_Week($calcyear,$calcmonth,$calcday) > $daysinweek) or (Day_of_Week($calcyear,$calcmonth,$calcday) == 0))
{
    ($calcyear,$calcmonth,$calcday) = Add_Delta_Days($calcyear,$calcmonth,$calcday,1);
}

# compute relative date since start of schedule
$dif_days   = Delta_Days($startyear,$startmonth,$startday,$calcyear,$calcmonth,$calcday);
$dif_week   = int ($dif_days / 7);
$dif_month  = int ($dif_week / $weekspermonth);
$relweek    = 1 + $dif_week % $weekspermonth;

$relmonth   = 1 + $dif_month;
$weekdaynow = Day_of_Week($calcyear,$calcmonth,$calcday);

if ($dif_days < 0) {die "\nERROR: Tapedate must be on or after startdate!\n";}

unless ( $opt_batch ) {print "$calcday/$calcmonth/$calcyear is day $weekdaynow in week $relweek of \"month\" $relmonth\n"; }

# -now or -batch option --> print only today's tapenumber
if ( $opt_now or $opt_batch)
{
    $tape = &tapenumber ($weekdaynow, $relweek, $relmonth);

    if ( $opt_batch ) {printf "\nSET TAPENAME=$prefix%3.3d\n",$tape  }
    else {  print "Todays tape is $tape\n";}

    if ( $opt_batch and $opt_cleaning) { print "SET CLEANING=$YESNO[&cleaningtape ($day, $week, $month)]\n";}
    elsif (&cleaningtape ($day, $week, $month)) {  print "Tapedrive should be cleaned today\n";}

}
else # no -now option --> print entire schedule, starting with current month
{
    # print headers
    print "\n   ";
    for ($month = $relmonth; $month <= $dif_month + $monthsinschedule; $month++ ) { printf "  \"month\"" ;}
    print "\n  ";
    for ($month = $relmonth; $month <= $dif_month + $monthsinschedule; $month++ ) { printf "    %3.3d  ",$month ;}
    print "\n     ";
    for ($month = $relmonth; $month <= $dif_month + $monthsinschedule; $month++ )
    {
        ($printyear, $printmonth, $printday) = Add_Delta_Days($startyear,$startmonth,$startday, ($month-1) * $weekspermonth * 7 );
        printf "%2.2d/%2.2d/%2.2d ",$printday,$printmonth,substr ($printyear,2,2);
    }

    #print tapenumbers
    print "\n";
    for ( $week = 1; $week <= $weekspermonth; $week++ )
    {
        print "\nweek $week\n";
        for ( $day = 1; $day <= $daysinweek; $day++ )
        {
            print "$weekday[$day%7] ";
            for ($month = $relmonth; $month <= $dif_month + $monthsinschedule; $month++ )
            {
                $tape = &tapenumber ($day, $week, $month);

                if ( $week == $relweek & $day == $weekdaynow & $month == $relmonth )
                    {   printf  "->%3.3d<-", $tape; }
                else {printf  "  %3.3d  ", $tape;}

                print $cleanval [&cleaningtape ($day, $week, $month)];
            }
            print "\n";
        }
    }
}

# end of script

############################################################################
sub tapenumber
# Calculate the tapenumber to use, based on a relative day from the start
############################################################################
# IN            day:        daynumber in week (0=Sunday, 1=Monday)
#               week:       weeknumber in month (1=first week)
#               month:      monthnumber in schedule (1=firstmonth)
# OUT           tapenumber: calculated tapenumber
# DEPENDENCIES: global variable $initialtapes
############################################################################

{   $day   = $_[0];
    $week  = $_[1];
    $month = $_[2];

    if ( $day == $daysinweek )
    {$tape =  $month - $week + $weekspermonth; }
    else      { $tape = $initialtapes + $month - ($day % 7); }
}
## end of sub tapenumber


############################################################################
sub cleaningtape
# Calculate whether a cleaning tape has to be used
############################################################################
# IN            day:        daynumber in week (0=Sunday, 1=Monday)
#               week:       weeknumber in month (1=first week)
#               month:      monthnumber in schedule (1=firstmonth)
# OUT           1           when a cleaning tape is needed on this day
#               0           when no cleaning tape is needed or not specified on command-line
# DEPENDENCIES: global variable $opt_cleaning
############################################################################

{   $day   = $_[0];
    $week  = $_[1];
    $month = $_[2];

    $backupnumber = ($month - 1) * $weekspermonth * $daysinweek +
                                  ($week - 1)     * $daysinweek +
                                                    $day;
    if (($opt_cleaning) and ( $backupnumber % $opt_cleaning == 0))
    {   $cleaningtape = 1;}
    else {$cleaningtape = 0; }
}
## end of sub cleaningtape

