#!/bin/sh

# ----------------------------------------------------------------------------
# Purpose: Help make clean plugin tarballs with instructive README, email
# them to users, install them and uninstall them. 
#
# While developing on plugin code, the plugin directory can wind up with
# a lot of extraneous files. This script helps to make a clean tarball,
# as well as adding a instructive README file to it to inform users how
# to build and install it.
#
# You can use this script to packup a tarball, optionally email it.
# provide a one-line comment as to the plugin update's purprose. You can
# use this script to install and uninstall plugins.
#
# Programmer: Mark C. Miller
# Creation:   February 28, 2007
#
# Modifications:
#
#   Mark C. Miller, Tue Mar  6 09:12:07 PST 2007
#   Fixed errors when plugin wasn't specified
#
#   Mark C. Miller, March 7, 2007
#   Moved all instructions to single place. Made it send a single email
#   for all recipients so all could see who received it.
#
#   Mark C. Miller, Thu Mar 15 21:45:31 PDT 2007
#   Fixed problems with mutt being unreliable. Added contrib and keep files
#   to be cleaned from the TEMPORARY copy of the plugin dir we make.
#
#   Mark C. Miller, Thu Mar 15 21:26:08 PST 2007
#   Modified instructions to include information on building when VisIt
#   installation wasn't built to include the plugin
#
#   Mark C. Miller, Thu Mar 15 22:53:04 PST 2007
#   Added support for testing on the user's end (during install)
#
#   Mark C. Miller, Tue Mar 20 08:50:53 PDT 2007
#   Removed incorrect instructions verbage regarding having to install
#   plugin on both client and server.
#
#   Mark C. Miller, Thu Mar 29 11:26:36 PDT 2007
#   Added ability to have -c option specify a file. Re-arranged instructions
#   a bit to make them clearer. Fixed some usage comments.
#
#   Mark C. Miller, Thu Mar 29 13:21:55 PDT 2007
#   Set email and reply-to fields of mutt's message
#
#   Mark C. Miller, Wed Jun 20 17:59:58 PDT 2007
#   Added (this) comment: Eliminated removal of *PluginInfo* files when
#   cleaning the plugin dir as well as invokation of xml2info to regenerate
#   them.
#
#   Mark C. Miller, Wed Jun 20 17:59:58 PDT 2007
#   Added .d files to be cleaned from the plugin dir
#
#   Mark C. Miller, Wed Jun 20 18:06:14 PDT 2007
#   Fixed comments in README regarding dependency files to remove explicit
#   references to files named '.depend' or '.pardepend'
#
#   Mark C. Miller, Thu Sep  6 17:28:56 PDT 2007
#   Changed all numeric tests to strings because I've found too often logic
#   can fail with 'expected integer expression' error when using numeric
#   comparisons (e.g. -eq -ne -lt -gt, etc.). Added logic to deal with
#   possibility that xml2plugin may not be in path. Fixed up the instructions
#   a bit. I added verbose message regarding email attempts.
#
#   Mark C. Miller, Tue Sep 18 17:57:50 PDT 2007
#   Added -list command line option to list plugins installed
#
#   Mark C. Miller, Wed Jan 16 16:43:35 PST 2008
#   If fixed some problems with cleanPluginDir where it would not correctly
#   set permissions on subdirs in the plugin dir and not correctly remove
#   .svn dirs.
#
#   Hank Childs, Tue Jan  5 07:16:29 PST 2010
#   Replace xml2makefile with xml2cmake.
#
#   Mark C. Miller, Mon Jun 14 10:13:38 PDT 2010
#   Updated commands/readme to support cmake style of building.
# ----------------------------------------------------------------------------
#

#
# Clean up the plugin dir from extraneous files. We could opt to remove
# everything that doesn't look like its part of the plugin here. That would
# be good. However, I am not sure how to reliably do that. Maybe from looking
# at the xml file? Note: These commands are intended to execute on a copy of
# the plugin dir and not the original one in the source code tree
#
cleanPluginDir () {
    pushd $1
    find . -type d -name .svn -exec rm -rf {} \;
    rm -f *.d .depend .pardepend .cmake.depend .cmake.state Makefile *.o findmerge*
    rm -f cmake_install.cmake
    rm -rf CMakeCache.txt CMakeFiles
    rm -f *.contrib* *.keep*
    chmod -R a+rX,g+w
    chgrp -R visit
    popd
}

#
# Build the README file to be included in the tarball
#
makeInstructionsFile () {
    typeset upperPluginName=`echo $pluginName | tr \[a-z\] \[A-Z\]`

rm -f $1
echo "The associated tarball is a VisIt plugin. This update is being" > $1
echo "furnished to you to address the following problem(s)..." >> $1
echo "" >> $1

if test -n "$commentSource"; then
    if test -e "$commentSource"; then
        cat "$commentSource" >> $1
    else
        echo "$commentSource" >> $1
    fi
    echo "" >> $1
fi

if test "$testBuild" != "0"; then
    if test -n "$testRun"; then
        echo "Prior to sending you this tarball, it was confirmed to build and" >> $1
        echo "run correctly with version $versionBuiltWith of VisIt." >> $1
        echo "" >> $1
    else
        echo "Prior to sending you this tarball, it was confirmed to build with" >> $1
        echo "version $versionBuiltWith of VisIt." >> $1
        echo "" >> $1
    fi
fi

cat >> $1 <<- EOF

    Basic commands for installing and UNinstalling a plugin
    --------------------------------------------------------------
    To install...
        % visit_plugin -install $pluginName 
    To UNinstall...
        % visit_plugin -uninstall $pluginName

    Note: There is no '.tar.gz' in the plugin's name

    If the above commands fail, you may need to use this appraoch...

    Manual commands for installing and UNinstalling
    (Note: Ensure you have a new enough version of cmake, 2.8 or
    later, in your path. It may be necessary to use the version of
    cmake that is part of the VisIt installation you are building
    this plugin for.)
    ----------------------------------------------------------------
    To untar the plugin...
        % gunzip < $pluginName.tar.gz | tar xvf -
    To install...
        % cd $pluginName
        % xml2cmake -clobber $pluginName.xml
        % cmake .
        % make
    To UNinstall...
        % cd $pluginName
        % make clean

    If you have problems, please read about more details here...
    http://visitusers.org/index.php?title=Building_plugins_using_CMake
    and here...
    http://visitusers.org/index.php?title=DB_plugins
    If you still have problems, email mailto:visit-users@ornl.gov

EOF
}

#
# Tarup the plugin directory
#
packagePluginDir () {
    typeset theDir=$1
    mkdir $tmpDir/${theDir}_tmp.$$
    cp -R $theDir $tmpDir/${theDir}_tmp.$$/. 1>/dev/null 2>&1
    cleanPluginDir $tmpDir/${theDir}_tmp.$$/$theDir 1>/dev/null 2>&1
    pushd $tmpDir/${theDir}_tmp.$$ 1>/dev/null 2>&1
    makeInstructionsFile $theDir/README 1>/dev/null 2>&1
    if test -n "$requiredVersions"; then
        echo $requiredVersions | cut -d' ' -f1 > $theDir/.REQUIRED_VISIT_VERSION
    fi
    if test -n "$testRun"; then
        inputFileName=`echo $testRun | cut -d':' -f1`
	pushd 1>/dev/null 2>&1
        cp $inputFileName $tmpDir/${theDir}_tmp.$$/$theDir/. 
	pushd 1>/dev/null 2>1
        echo $testRun > $theDir/.TEST_RUN_COMMAND
    fi
    tar cf - $theDir | gzip > $theDir.tar.gz
    popd 1>/dev/null 2>&1
    cp $tmpDir/${theDir}_tmp.$$/$theDir.tar.gz .
    rm -rf $tmpDir/${theDir}_tmp.$$
}

#
# Given a plugin tarball, test that we can actually build it.
#
testBuildPluginTarball () {
    typeset theTarball=$1
    typeset deleteIt=$2
    thePluginName=`basename $theTarball .tar.gz`
    mkdir $tmpDir/${theTarball}_tmp.$$
    cp $theTarball $tmpDir/${theTarball}_tmp.$$/.
    oldDir=`pwd`
    pushd $tmpDir/${theTarball}_tmp.$$ 1>/dev/null 2>&1
    gunzip < $theTarball | tar xf -
    cd $thePluginName
    vers=
    if test -n "$requiredVersions"; then
        vers="-v `echo $requiredVersions | cut -d' ' -f1`"
    elif test -e .REQUIRED_VISIT_VERSION; then
        vers="-v `cat .REQUIRED_VISIT_VERSION`"
    fi
    rm -f CMakeLists.txt
    which xml2cmake 1>/dev/null 2>&1
    if test "$?" = "0"; then
        xml2cmake $vers -clobber $thePluginName.xml 1>/dev/null 2>&1
    else
        if test -e $scriptPath/xml2cmake; then
            $scriptPath/xml2cmake $vers -clobber $thePluginName.xml 1>/dev/null 2>&1
	else
	    echo "Unable to find xml2cmake in your path or in ${scriptPath}"
	    return
	fi
    fi
    cmake . 1>$oldDir/visit_plugin_${thePluginName}_make.log 2>&1
    make 1>$oldDir/visit_plugin_${thePluginName}_make.log 2>&1
    hadError=$?
    if test -z "$deleteIt" || test "$deleteIt" != "0"; then
        make clean 1>/dev/null 2>&1
        rm -rf $tmpDir/${theTarball}_tmp.$$
        echo $hadError
    else
        echo $hadError $tmpDir/${theTarball}_tmp.$$
    fi
    popd 1>/dev/null 2>&1
}

#
# Given a plugin tarball, test that VisIt will actually run with
# Again, version control is important
#
testRunPluginTarball () {
    typeset tarballDir=$1
    vers=
    if test -n "$requiredVersions"; then
        vers="-v `echo $requiredVersions | cut -d' ' -f1`"
    elif test -e $tarballDir/.REQUIRED_VISIT_VERSION; then
        vers="-v `cat $tarballDir/.REQUIRED_VISIT_VERSION`"
    fi
    fileName=`echo $testRun | cut -d':' -f1`
    plotType=`echo $testRun | cut -d':' -f2`
    varName=`echo $testRun | cut -d':' -f3`
    if test $action = install && test -e $tarballDir/.TEST_RUN_COMMAND; then
        fileName=`cat $tarballDir/.TEST_RUN_COMMAND | cut -d':' -f1`
        fileName=`basename $fileName`
        fileName=$tarballDir/$fileName
        plotType=`cat $tarballDir/.TEST_RUN_COMMAND | cut -d':' -f2`
        varName=`cat $tarballDir/.TEST_RUN_COMMAND | cut -d':' -f3`
    fi
    mkdir $tmpDir/visit_plugin_test.$$
    pushd $tmpDir/visit_plugin_test.$$ 1>/dev/null 2>&1
    cat > visit_plugin_test.py <<- EOF
import sys
try:
    OpenDatabase("$fileName")
    msg = GetLastError()
    AddPlot("$plotType","$varName")
    msg = msg + GetLastError()
    DrawPlots()
    msg = msg + GetLastError()
    SaveWindow()
    msg = msg + GetLastError()
    if msg == "":
        print "###PASSED###"
        sys.exit(0)
    else:
        print "###FAILED###"
        sys.exit(1)
except:
    print "###FAILED###"
    sys.exit(1)
EOF
    passedIt=`visit $vers -noconfig -debug 5 -cli -nowin -s visit_plugin_test.py 2>&1 | grep '###PASSED###'`
    if test -z "$passedIt"; then
        origDir=`pushd 1>/dev/null 2>&1; pwd ; pushd 1>/dev/null`
        cp *.5.log $origDir/.
    fi
    popd 1>/dev/null 2>&1
    rm -rf $tmpDir/visit_plugin_test.$$
    if test $action = pack; then
        pushd $tarballDir 1>/dev/null 2>&1
        make clean 1>/dev/null 2>&1
        popd 1>/dev/null 2>&1
    fi
    rm -rf $tarballDir
    if test -n "$passedIt"; then
        echo 0
    else
        echo 1
    fi
}

#
# Handle Command Line Arguments
#
tmpDir=$TMPDIR
if test -z "$tmpDir"; then
    if test -d /usr/tmp; then
       tmpDir=/usr/tmp
    elif test -d /tmp; then
       tmpDir=/tmp
    else
       tmpDir=`(cd ~; pwd -P)`
    fi
fi
action=
optError=0
pluginName=
requiredVersions=
origArgs=$*
testBuild=0
versionBuiltWith=
testRun=
commentSource="user requested update `date`"
dontRemoveTarball=0
meEmail=visit-users@ornl.gov
for options
do
   case $1 in
      "")
         # handle empty argument
         ;;
      -v|-version)
         if test -z "$2"; then
             echo "Expected version number or quoted list of version numbers(s) for $1"
             optError=1
         else
             requiredVersions="$requiredVersions $2"
             shift 2
         fi
         ;;
      -p|-pack)
         if test -n "$action"; then
             echo "Action has already been specified as \"$action\""
             optError=1
         else
             action="pack"
         fi
         shift
         ;;
      -i|-install)
         if test -n "$action"; then
             echo "Action has already been specified as \"$action\""
             optError=1
         else
             action="install"
         fi
         shift
         ;;
      -u|-uninstall)
         if test -n "$action"; then
             echo "Action has already been specified as \"$action\""
             optError=1
         else
             action="uninstall"
         fi
         shift
         ;;
      -tb|-testbuild)
         testBuild=1
         shift
         ;;
      -tr|-testrun)
         if test -z "$2"; then
             echo "Expected colon separated list of <Filename:PlotType:VarName> for $1"
             optError=1
         else
             testBuild=1
             testRun=$2
             shift 2
         fi
         ;;
      -e|-email)
         if test -z "$2"; then
             echo "Expected email address or quoted list of email address(s) for $1"
             optError=1
         else
             emailRecipients="$emailRecipients $2"
             shift 2
         fi
         ;;
      -me)
         if test -z "$2"; then
             echo "Expected email address for $1"
             optError=1
         else
             meEmail="$2"
             shift 2
         fi
         ;;
      -c|-comment)
         if test -z "$2"; then
             echo "Expected either quoted string or name of file for $1"
             optError=1
         else
             commentSource=$2
             shift 2
         fi
         ;;
      -dr|-dontremove)
         dontRemoveTarball=1
         shift
         ;;
      -l|-list)
         if test -n "$action"; then
             echo "Action has already been specified as \"$action\""
             optError=1
         else
             action="list"
         fi
         shift
         ;;
      -help)
         optError=1
         shift
         ;;
      -*)
         echo "Unknown option $1"
         optError=1
         shift
         ;;
      *)
         if test -z "$pluginName"; then
             pluginName=$1
         else
             echo "Plugin name already specified as $pluginName."
             echo "Unknown option $1"
             optError=1
         fi
         shift
         ;;
   esac
done

if test "$optError" = "0" && test -z "$action"; then
    optError=1
    echo "************************************************"
    echo "************************************************"
    echo "No action specified. Specify one of -p, -i or -u"
    echo "************************************************"
    echo "************************************************"
fi

if test "$action" != "list" && test "$optError" = "0" && test -z "$pluginName"; then
    optError=1
    echo "************************"
    echo "************************"
    echo "No Plugin Dir specified."
    echo "************************"
    echo "************************"
fi

if test "$optError" = "1"; then
    echo "Usage:  $0 <options> [plugin name]"
    echo "Available options:"
    echo "        -help             display this help message"
    echo "        -v  | -versions   quoted list of space separated version numbers(s)."
    echo "                          Multiple -v options are also allowed."
    echo "        -p  | -pack       Pack up the plugin into a tarball for email."
    echo "        -i  | -install    Install the plugin from a .tar.gz file."
    echo "        -u  | -uninstall  Uninstall the plugin."
    echo "        -l  | -list       List privately installed plugins."
    echo "        -tb | -testbuild  After packing up, test that it will build."
    echo "        -tr | -testrun <Filename:PlotType:VarName>"
    echo "                          After packing up, test that it will run with"
    echo "                          specified plot and variable. -tr implies -tb."
    echo "        -dr | -dontremove Don't remove the tarball after its emailed."
    echo "        -e  | -email      email address or quoted list of email addresses"
    echo "                          to send tarball too. Multiple -email options are allowed"
    echo "        -me               email address of person executing this script. Both the"
    echo "                          sender and reply-to field of the email header will be set"
    echo "                          to this address. Default is 'visit-users@ornl.gov'"
    echo "        -c  | -comment <string>"
    echo "                          Add a comment to the email/readme file indicating"
    echo "                          what is new about this plugin. If <string> is the name"
    echo "                          of a file, use the contents of the file."
    echo "        <plugin name>     plugin name to process (e.g. SAMI or CGNS)."
    echo "                          If -p is specified, $0 will search for directories with"
    echo "                          the given name to package up. If -i is specified, $0 will"
    echo "                          search for .tar.gz files with the given name. Should"
    echo "                          always be last argument."
    exit 1
fi

#
# Setup path to this script (and other visit tools) 
#
scriptPath="`dirname $0`"
if test -n "`echo $path | tr ' ' '\n' | grep ^$scriptPath`"; then
    scriptPath=""
else
   pushd $scriptPath 1>/dev/null 2>&1
   scriptPath="`pwd`"
   popd 1>/dev/null 2>&1
fi

#
# Set the versionBuilWith variable if we every need it
#
if test -n "$requiredVersions"; then
    versionBuiltWith=`echo $requiredVersions | cut -d' ' -f1`
else
    versionBuiltWith=`visit -version 2>&1 | cut -d' ' -f7 | cut -d'.' -f1-3`
fi

#
# handle pack, install, uninstall actions.
#
if test $action = pack; then
    tarballsToRemove=
    if test -d $pluginName; then

        #
        # Make up the tarball
        #
        echo -n "Making tarball..."
        packagePluginDir $pluginName
        echo "Done"

        #
        # Test run it, if requested.
        #
        if test -n "$testRun"; then
            echo -n "Testing Build..."
            buildResult=`testBuildPluginTarball $pluginName.tar.gz 0`
            buildError=`echo $buildResult | cut -d' ' -f1`
            buildDir=`echo $buildResult | cut -d' ' -f2`
            if test "$buildError" != "0"; then
                echo "Test Build failed. Check visit_plugin_${pluginName}_make.log"
                pushd $buildDir 1>/dev/null 2>&1
                make clean 1>/dev/null 2>&1
                popd 1>/dev/null 2>&1
                rm -rf $buildDir
                exit 1
            else
                echo "Passed"
                echo -n "Now testing run..."
            fi
            if test "`testRunPluginTarball $buildDir`" != "0"; then
                echo "Test Run failed. Check debug logs copied to `pwd`."
                pushd $buildDir 1>/dev/null 2>&1
                make clean 1>/dev/null 2>&1
                popd 1>/dev/null 2>&1
                rm -rf $buildDir
                exit 1
            else
                echo "Passed"
                echo "Plugin tarball is good."
                pushd $buildDir 1>/dev/null 2>&1
                make clean 1>/dev/null 2>&1
                popd 1>/dev/null 2>&1
                rm -rf $buildDir
            fi
            echo "Tests passed"

        #
        # Just test build it, if requested
        #
        elif test "$testBuild" != "0"; then
            echo -n "Testing Build..."
            if test "`testBuildPluginTarball $pluginName.tar.gz`" != "0"; then
                echo "Test Build failed"
                exit 1
            fi
            echo "Passed"
            echo "Plugin tarball is good."
        fi
    else
        echo "Unable to find a plugin directory with name \"$pluginName\""
        exit 1
    fi

    #
    # Email tarball to recipients if specified
    # Mutt is unreliable. So, we loop making several attempts
    #
    if test -n "$emailRecipients"; then
        which mutt 1>/dev/null 2>&1
	if test "$?" != "0"; then
	    echo "Unable to send mail because 'mutt' command is not in your path."
	    exit 1
	fi
        oldTmpDir=$TMPDIR
        TMPDIR=$tmpDir/mutt$$
	export TMPDIR
	mkdir $TMPDIR
	chmod 700 $TMPDIR
        makeInstructionsFile mutt.msg 1>/dev/null 2>&1
	muttAttempts=1
	while true; do
            env EMAIL="$meEmail" REPLYTO="$meEmail" mutt -s "Updated $pluginName plugin" -a $pluginName.tar.gz -- $emailRecipients < mutt.msg 1>$TMPDIR/mutt.errors 2>&1
	    if test "$?" != "0"; then
	        sleep 1
	    elif test -z "`cat $TMPDIR/mutt.errors`"; then
	        cat $TMPDIR/mutt.errors
	        rm -f $TMPDIR/mutt.errors
	        break;
	    else
	        sleep 1
	    fi
	    muttAttempts=`expr $muttAttempts + 1`
	    if test $muttAttempts -gt 10; then
	        "Mutt failed to successfully send mail after $muttAttempts attempts"
		break;
	    fi
	done
	if test $muttAttempts -le 10; then
            echo "After $muttAttempts attempts, mutt successfully sent email to $emailRecipients"
	fi
	rm -rf $TMPDIR
	TMPDIR=$oldTmpDir
	export TMPDIR
        tarballsToRemove="$tarballsToRemove $pluginName.tar.gz"
        rm -f mutt.msg
    fi

    #
    # Delete tarball if we have no reason not to
    #
    if test -n "$emailRecipients" && test "$dontRemoveTarball" = "0"; then
        rm -f $tarballsToRemove
    fi

elif test $action = install; then

    #
    # Try to install the plugin
    #
    if test -e $pluginName.tar.gz; then
        echo -n "Compiling the plugin..."
        buildResult=`testBuildPluginTarball $pluginName.tar.gz 0`
        buildError=`echo $buildResult | cut -d' ' -f1`
        buildDir=`echo $buildResult | cut -d' ' -f2`
        if test "$buildError" != "0"; then
            echo "Failed with result \"$buildResult\"."
	    echo "UNinstalling it"
            pushd $buildDir 1>/dev/null 2>&1
            make clean 1>/dev/null 2>&1
            popd 1>/dev/null 2>&1
            exit 1
        else
            echo "Succeeded."
            if test -n "$testRun" || test -e $buildDir/$pluginName/.TEST_RUN_COMMAND; then
	        echo -n "Testing it..."
                if test "`testRunPluginTarball $buildDir/$pluginName`" != "0"; then
                    echo "Failed."
		    echo "Check debug logs copied to `pwd`."
		    echo "Uninstalling it"
                    pushd $buildDir/$pluginName 1>/dev/null 2>&1
                    make clean 1>/dev/null 2>&1
                    popd 1>/dev/null 2>&1
                    rm -rf $buildDir
                    exit 1
                fi
		echo "Succeeded."
            fi
            echo "Installed successfully."
            echo "To UNinstall it, use $0 -uninstall $pluginName"
            rm -rf $buildDir
        fi
    else
        echo "Unable to find a plugin tarball with name \"$pluginName.tar.gz\""
        exit 1
    fi

elif test $action = uninstall; then

    #
    # Remove the plugin files from ~/.visit
    # 
    filesToRemove=`ls ~/.visit/*/plugins/databases/lib?${pluginName}* 2>/dev/null`
    if test "$?" != "0" || test -z "$filesToRemove"; then
        echo "Unable to find any installed plugin files associated with \"$pluginName\""
        exit 1
    fi
    echo "Preparing to remove the following files..."
    for f in $filesToRemove; do
        echo "    $f"
    done
    echo -n "Are you sure you want to remove them (type 'yes' if so)? "
    read ans
    if test -n "$ans" && test $ans = yes; then
        rm -rf $filesToRemove
        if test "$?" != "0"; then
            echo "Unable to remove the files"
            exit 1
        fi
    fi

elif test $action = list; then

    filesToList=`ls ~/.visit/*/plugins/databases/libI${pluginName}* 2>/dev/null`
    if test "$?" != "0" || test -z "$filesToList"; then
        echo "Unable to find any installed plugin files"
        exit 1
    fi
    echo "Names of privately installed plugins..." 
    for f in $filesToList; do
        theName=`basename $f | sed -e 's/libI\(.*\)Database.so/\1/'`
        echo "    $theName"
    done
fi

exit 0
