#!/bin/bash
#
########################################################################
#
# Common functions definition for rebuild-* scripts.
# WARNING: many functions rely on global variables defined in the
# calling script.
#   $PROGNAME    Program name
#   $VERSION     Version number of the program
#   $AUTHOR      Author of the program
#   $PROJECT     String describing the project
#   $LOGFILE     General log file of the process
#   $MODULE      Module name (important!)
#
########################################################################
#
# Program general functions
#
function showversion() {
    echo "$PROGNAME version $VERSION, by $AUTHOR"
    echo "Project: $PROJECT"
}
#
function message() {
    echo "$PROGNAME: $1"
}
#
function error_exit() {
    #
    # Write message to STDOUT
    message ""
    message "$PROGNAME error: $1"
    message ""
    #
    # Write message to logfile
    echo                       >> $LOGFILE
    echo "$PROGNAME error: $1" >> $LOGFILE
    echo                       >> $LOGFILE
    #
    # Mail report
    report "Failure"
    exit 1
    #
}
#
function report() {
    #
    echo >> $LOGFILE
    date >> $LOGFILE
    #
    SUBJECT="$MAILHEADER $1 ($IN_VERSION)"
    #
    message "Sending report"
    message "File =       $LOGFILE"
    message "Subject =    $SUBJECT"
    #
    if [ -n "$RECIPIENTS" ]; then
	message "Recipients = $RECIPIENTS"
	cat $LOGFILE | $gagadmdir/sendmail.pl -s "$SUBJECT" $RECIPIENTS
    else
	message "Recipients = <none>"
    fi
    #
}
########################################################################
#
# Building functions
#
function mybuild_message() {
    if [ -n "$LOGFILE" ]; then
	printf "%-32s (%s): %s\n" $SYSCONF $BUILDKIND "$1" >> $LOGFILE
    fi
}
#
function mybuild_all() {
    #
    # Read from file $SYSFILE
    SYSFILE="$gagadmdir/$SYSFILE"   # Either default or input from '-f' option
    #
    if [ ! -e "$SYSFILE" ]; then
	error_exit "$SYSFILE not found in $gagadmdir."
    fi
    #
    # Store lines in 'systems' array because ongoing ssh connections
    # mess up the "<(cat $SYSFILE)" reading...
    nlines=0
    while read line; do
	#
	let nlines+=1
	systems[$nlines]=$line
	#
    done < <(cat $SYSFILE)
    #
    l=1
    while [ "$l" -le "$nlines" ]; do
	#
	argums=`echo ${systems[$l]} | \sed "s%#.*%%"`
	if [ -n "$argums" ]; then mybuild $argums || failure=1; fi
	let l+=1
	#
    done
}
#
function mybuild_check() {
    #
    # Check if input version is already built. Returns version if true,
    # nothing if not. Symlinks are translated.
    # Might be improved by checking if built is actually done for each
    # system declared is $SYSFILE.
    #
    if [ -z "$1" ]; then
	return
    fi
    #
    bversion=
    if [ -d "$MODULE-src-$1" ] && [ ! -h "$MODULE-src-$1" ]; then
	bversion=$1
    elif [ -h "$MODULE-src-$1" ]; then
	bversion=`get_src_target "$1"`
    fi
    #
    echo $bversion
    #
}
#
########################################################################
#
# Cleaning and linking functions
#
function link_version() {
    # Links boths src and exe directories to their new symbols. Must be
    # called from appropriate directory. Nothing is done if one of the
    # arguments is missing (this is a desired behavior).
    if [ -z "$2" ]; then
	return
    fi
    #
    # Targets are checked for existence, links for non-existence.
    if [ -d "$MODULE-src-$1" ] && [ ! -e "$MODULE-src-$2" ] ; then
	ln -s $MODULE-src-$1 $MODULE-src-$2 || error_exit "Linking '$MODULE-src-$1' target to '$MODULE-src-$2' link"
    fi
    if [ -d "$MODULE-exe-$1" ] && [ ! -e "$MODULE-exe-$2" ] ; then
	ln -s $MODULE-exe-$1 $MODULE-exe-$2 || error_exit "Linking '$MODULE-exe-$1' target to '$MODULE-exe-$2' link"
    fi
}
#
function get_src_target() {
    # Echoes back the timestamp of the input link version (e.g. input
    # may be 'last', and output '14sep'). Assumes that both link and
    # target are prefixed by '$MODULE-src-'. Must be called from
    # appropriate directory. Both link (-h) and its directory target
    # (-d) are checked for existence, but do not complain if link is
    # not found (this is desired).
    src_target=
    if [ -h "$MODULE-src-$1" ] && [ -d "$MODULE-src-$1" ]; then
	src_target=`readlink "$MODULE-src-$1" | \sed "s%$MODULE-src-%%"`
    fi
    echo $src_target
}
#
function get_link() {
    # Loop over all local files. If one is a symlink, take a look at
    # its target. If its target is input file name, echoes symlink name
    # and returns. Else echoes nothing.
    if [ -z "$1" ]; then
	return
    fi
    #
    for localfile in `ls`; do
	if [ -h $localfile ]; then
	    target=`readlink "$localfile"`
	    if [ "$target" = "$1" ]; then
		echo $localfile
		return
	    fi
	fi
    done
    #
}
#
function remove_version() {
    # Removes src, exe, and logs directories of input version. Must be
    # called from appropriate directory. Does nothing if input version
    # is linked by a local symlink. Does not complain if $1 is empty,
    # or if directories do not exist (thanks to the -f flag). These are
    # desired behaviours.
    if [ -z "$1" ]; then
	return
    fi
    #
    linker=`get_link "$MODULE-src-$1"`
    if [ -n "$linker" ]; then
	linker=`echo $linker | \sed "s%$MODULE-src-%%"`
	message "Kept '$1' version, linked by '$linker'"
    else
	message "Clean '$1' version"
	# Order matters here:
	rm -rf ./$MODULE-src-$1       2>&1  # Sources
	if [ $? -ne 0 ]; then
	    echo $1 >> $PENDINGFILE;
	    message "Unable to remove $MODULE-src-$1, adding $1 to pending list"
	    return
	fi
	rm -rf ./$MODULE-exe-$1/*/bin 2>&1  # Executable binaries
	if [ $? -ne 0 ]; then              # If an error raises, this ensures
	    echo $1 >> $PENDINGFILE;       # to not remove executable tree
	    message "Unable to remove $MODULE-exe-$1/*/bin, adding $1 to pending list"
	    return                         # if an executable is still in use
	fi
	rm -rf ./$MODULE-exe-$1       2>&1  # Executables
	if [ $? -ne 0 ]; then
	    echo $1 >> $PENDINGFILE;
	    message "Unable to remove $MODULE-exe-$1, adding $1 to pending list"
	    return
	fi
	rm -rf ./logs/$1             2>&1  # Logs
	if [ $? -ne 0 ]; then
	    echo $1 >> $PENDINGFILE;
	    message "Unable to remove logs/$1, adding $1 to pending list"
	    return
	fi
    fi
}
#
function remove_integ() {
    # Removes integ directory of input version. Must be called from
    # appropriate directory. Does not complain if $1 is empty, or if
    # directories do not exist (thanks to the -f flag). These are
    # desired behaviours.
    if [ -z "$1" ]; then
	return
    fi
    #
    # Translate IN_VERSION, e.g. 'day' becomes '13jan'
    source $gagadmdir/define-version.sh
    gagdefver $1 || message "Error calling 'gagdefver' in $PROGNAME"
    unset gagdefver
    #
    message "Clean '$GAG_VERS' integration branch"
    #
    rm -rf ./$MODULE-src-$GAG_VERS/integ 2>&1
    if [ $? -ne 0 ]; then
        message "Unable to remove $MODULE-src-$GAG_VERS/integ"
        return
    fi
}
#
function remove_link() {
    # Removes symlinks of input version, but not their targets. Must be
    # called from appropriate directory. Checks for symlinks existence.
    if [ -h "$MODULE-src-$1" ]; then
	rm -f $MODULE-src-$1 || error_exit "Deleting '$MODULE-src-$1' symlink"
    fi
    if [ -h "$MODULE-exe-$1" ]; then
	rm -f $MODULE-exe-$1 || error_exit "Deleting '$MODULE-exe-$1' symlink"
    fi
}
#
function clean_daily() {
    # Remove daily versions older than input date (in days). In other words,
    # this keeps a pool of daily versions for a given amount of days.
    # NB: daily versions which have a symlink pointing to them are protected
    # in remove_version. This is useful after several days of failures:
    # "prev" and "last" success targets won't be deleted.
    #
    count=$1
    stop=$(($count+15))
    #
    # Remove everything older than $count... but stop at some point! Limit
    # arbitrarily to 15 days backward search. It is useful to rewind blindly
    # so that the cleaning is performed even if the script has not run at
    # all for several days.
    until [ "$count" = "$stop" ]; do
        version=`date -d "$count day ago" '+%d%b' | tr '[:upper:]' '[:lower:]'`
        remove_version $version
        let count+=1
    done
}
#
function clean_failed() {
    # Backwardly removes versions between yesterday (included) and input
    # version (excluded). Nothing is done if input version is yesterday.
    # This is useful to avoid accumulating failed versions. This keeps
    # only "prev" (= previous success), "last" (= last success), and
    # today (which is failed if not "last").
    lversion=$1
    if [ -z "$lversion" ]; then
        return
    fi
    #
    # Get previous (failed?) version:
    count=1
    fversion=`date -d "$count day ago" '+%d%b' | tr '[:upper:]' '[:lower:]'`
    #
    # And remove them down to 'last' version:
    until [ "$fversion" = "$lversion" ]; do
        remove_version $fversion
        let count+=1
        fversion=`date -d "$count day ago" '+%d%b' | tr '[:upper:]' '[:lower:]'`
    done
}
#
function clean_pending() {
    # Some versions can not be removed at one time (e.g. because of these
    # .nfs stuff). Look in the log created at each call of function
    # 'remove_version'. Must be called from appropriate directory because of
    # 'remove_version'.
    if [ -e "$PENDINGFILE" ]; then
	message "Cleaning pending versions"
    else
	return  # No version is pending for deletion
    fi
    #
    # What happens for versions which can not be removed:
    # 1) clean_daily tries to delete an old version. If this fails,
    #    it is added to $PENDINGFILE
    # 2) on the next day, clean_pending will first try to remove
    #    the versions found in $PENDINGFILE (see below). If this fails,
    #    they are kept in $PENDINGFILE
    # 3) but on the same day, it may happen that clean_pending tries
    #    again to delete an old version (it tries again and again for
    #    15 days). This means that there may be duplicates in
    #    $PENDINGFILE. This problem is solved below.
    #
    COPYFILE=/tmp/$PROGNAME-$$-to_be_removed.txt
    sort $PENDINGFILE | uniq > $COPYFILE
    rm $PENDINGFILE
    #
    while read line; do
	#
	remove_version $line
	#
    done < <(cat $COPYFILE)
    #
    rm $COPYFILE
}
#
########################################################################
#
# Result analysis functions
#
function day_failure() {
    #
    # Get last (successfull) version:
    lversion=`get_src_target "last"`
    today=`date '+%d%b' | tr '[:upper:]' '[:lower:]'`
    #
    # Perform some cleaning of previous failed versions
    if [ -z "$lversion" ]; then
	message "Could not find 'last' target."
	message "No cleaning of previous failed versions was done."
    elif [ "$lversion" = "$today" ]; then
	message "'last' target is already 'today', but now it fails."
	message "Check your versions and links."
    else
	clean_failed $lversion  # Remove versions down to $lversion (excluded)
    fi
    #
}
#
function day_success() {
    # Perform the actions after a successful daily build.
    # Argument: number of daily versions to keep
    #
    nday=$1
    #
    # Linking and cleaning
    message "Clean and make symbolic links for 'day' version"
    #
    # Retrieve which version 'prev' and 'last' symlinks point to,
    # and what new version we want to link:
    pversion=`get_src_target "prev"`
    lversion=`get_src_target "last"`
    today=`date '+%d%b' | tr '[:upper:]' '[:lower:]'`
    #
    if [ "$lversion" = "$today" ]; then
	# If $lversion == $today, this means that 'last' version and the
	# current new one have the same date, i.e. rebuild-iram has been
	# relaunched in the same day. In that case, we do not want
	# 'prev' links and targets to be affected, and 'last' link
	# should already point to the '$today' version. That is: nothing
	# to be done.
	#
	message "Kept '$pversion' (unchanged) and new '$lversion' as 'day' versions"
    else
	#
	remove_link "prev"             # Remove 'prev' links
	link_version $lversion "prev"  # Targets of 'last' links will now be 'prev'
	#
	remove_link "last"             # Remove 'last' links
	link_version $today "last"     # 'last' links will point to current-day-and-compiling version
	message "Kept '$lversion' and '$today' as 'day' versions"
	#
	clean_failed $lversion         # Remove versions down to $lversion (excluded)
	remove_integ $lversion         # Remove integ branch from $lversion
	clean_daily $nday              # Clean all daily versions older than $nday days
    fi
    #
}
#
function month_success() {
    #
    # Linking and cleaning
    message "Clean and make symbolic links for 'month' version"
    #
    remove_link "month"                     # Remove symlinks to previous month
    curmonth=`date '+%b%y' | tr '[:upper:]' '[:lower:]'`
    link_version $curmonth "month"          # Link current month to 'month' link
    monthname=`date -d "3 month ago" "+%b%y" | tr '[:upper:]' '[:lower:]'`
    remove_version $monthname               # Remove 3-month-old version
    #
}
#
########################################################################
