#!/bin/bash  

# ------------------------------------------------------------
#    autopackage creator program
# ------------------------------------------------------------

###
#
# This code is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
# 
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Copyright 2002 Mike Hearn (mike@theoretic.com)
#
###


# get the prefix of this program
[ -e /etc/autopackage ] && source /etc/autopackage;
[ -e ~/.autopackage ] && source ~/.autopackage;

source "$autopackage_prefix/share/autopackage/apkg-funclib"

checkConfigVersion || exit 1;

# error codes
ERROR_NO_STUB=1;
ERROR_NO_INSTALLER=2;
ERROR_NO_DOWNLOADER=3;
ERROR_NO_UPDATER=4;
ERROR_MISSING_SKELETON=5;


###############################################################
## start here

# check for command line arguments
while getopts ":h" Option
do
    case $Option in
        h | * ) # output help text and unknown cases
            echo
            out "$intl_MAKEINSTALLER_HELP"
            echo
            exit 0;;
    esac
done
shift $(($OPTIND - 1))


function cleanUp()
{
    rm -rf "$working_dir"
    rm -rf "$metadata_dir"
    [[ "$build_root" != "." ]] && rm -rf "$build_root"
    rm -f "${TMP}/apkg-payload.$$"
    rm -f "${TMP}/apkg-payload-meta.$$"
    working_dir="";
    metadata_dir="";
}

trap cleanUp EXIT


initialized=0
while [[ "$1" != "" || "$initialized" = "0" ]]; do

    working_dir="";
    metadata_dir="";

    initialized=1
    if [[ "$1" = "" ]]; then
	specfile="autopackage/default.spec"
    else
	specfile="autopackage/$1.spec"
    fi

    shift;

    if [ ! -e "$specfile" ]; then
	red; outn "$intl_FAIL"; normal;
	out "$intl_MAKEINSTALLER_NO_SPECFILE_FOUND" "$specfile";
	exit 1;
    fi

    # ensure we have an absolute path
    source_dir=`pwd`;

    working_dir="${TMP}/apkg-working$$" # where to place the files as we build the package
    mkdir "$working_dir"
    chmod 700 "$working_dir"

    # **************************************************
    #   Load up the templates, then proceed to parse the
    #   Meta section of the specfile, and look for special
    #   keys, or else sub into the stub template (between % marks)
    # **************************************************


    drawHeaderBlue "$intl_MAKEINSTALLER_BUILDING" "$source_dir/$specfile"
    

    # load the template files
    trace loading template files
    stub=`cat "$autopackage_prefix/share/autopackage/stub.template"` || { echo "Couldn't load stub.template"; exit $ERROR_NO_STUB; }
    installer=`cat "$autopackage_prefix/share/autopackage/installer.template"` || { echo "Couldn't load installer.template"; exit $ERROR_NO_INSTALLER; }
    downloader=`cat "$autopackage_prefix/share/autopackage/downloader.template"` || { echo "Couldn't load downloader.template"; exit $ERROR_NO_DOWNLOADER; }
    updater=`cat "$autopackage_prefix/share/autopackage/updater.template"` || { echo "Couldn't load updater.template"; exit $ERROR_NO_UPDATER; }

    # substitute the compareVersions function into installer.template (it's needed here, we can't rely on the presence of the funclibs)
    # but we only want one copy of the function of course
    # line numbers first...
    compare_versions_start=`cat "$autopackage_prefix/share/autopackage/apkg-bashlib" | grep -n "START COMPAREVERSIONS SIMPLE" | sed 's/:.*//'`
    compare_versions_end=`cat "$autopackage_prefix/share/autopackage/apkg-bashlib" | grep -n "END COMPAREVERSIONS SIMPLE" | sed 's/:.*//'`
    compare_versions_total=`cat "$autopackage_prefix/share/autopackage/apkg-bashlib" | wc -l`
    (( a = $compare_versions_total - $compare_versions_start ));
    (( b = $compare_versions_end - $compare_versions_start ));
    # now get the function itself
    compare_versions_script=`cat "$autopackage_prefix/share/autopackage/apkg-bashlib" | tail -n $a | head -n $b`
    unset a
    unset b
    # now join them together
    installer=`joinLines "%CompareVersions%" "$installer" "$compare_versions_script"`

    # check the specfile exists
    if [ ! -e "$source_dir/$specfile" ]; then
	red; out "$intl_MAKEINSTALLER_NO_SPECFILE" "$source_dir/$specfile"; normal;
	exit 1
    fi

    # load the file without comments into working as a list of key/value pairs, on each line
    meta=`getSection "$source_dir/$specfile" Meta`
    meta=`stripComments "$meta"`

    unset outfile

    # setup callback
    function processKey() {
	# is key special?
	case "$1" in
	    InstallerFile   ) outfile="$value" ;;
	    
	    DisplayName     ) displayname="$2";
			      DISPLAYNAME="$2";
			      ;;
			      
	    ShortName       ) shortname=`escapeValue "$2"`
			      SHORTNAME="$shortname"
			      ;;
			      
	    SoftwareVersion ) softwareversion="$2";
			      SOFTWAREVERSION="$2";
			      ;;

	    InterfaceVersion ) interfaceversion="$2";
		               INTERFACEVERSION="$2";
			       ;;
			      
	    RootName        ) rootname="$2";
		              ROOTNAME="$rootname"
			      ;;
	esac
    }

    interfaceversion="0.0" # default to interface number 0.0
    INTERFACEVERSION="0.0"
    parseKeys "$meta" # calls the above function

    # FIXME: this needs to be cleaned up a lot
    # sub in values
    escaped_value=`escapeValue "$displayname"`    
    stub=`echo "$stub" | sed "s/%DisplayName%/${escaped_value}/g"`;

    stub=`echo "$stub" | sed "s/%ShortName%/${shortname}/g"`;
    downloader=`echo "$downloader" | sed "s/%ShortName%/${shortname}/g"`;
    installer=`echo "$installer" | sed "s/%ShortName%/${shortname}/g"`;

    stub=`echo "$stub" | sed "s/%SoftwareVersion%/${softwareversion}/g"`;
    installer=`echo "$installer" | sed "s/%SoftwareVersion%/${softwareversion}/g"`;

    stub=`echo "$stub" | sed "s/%InterfaceVersion%/${interfaceversion}/g"`;
    installer=`echo "$installer" | sed "s/%InterfaceVersion%/${interfaceversion}/g"`;

    rootname=`eval echo $rootname` # variable expansion
    escaped_value=`escapeValue "$rootname"`
    installer=`echo "$installer" | sed "s/%RootName%/${escaped_value}/g"`;
    stub=`echo "$stub" | sed "s/%RootName%/${escaped_value}/g"`;    

    # sanity checks
    if [[ "$displayname" == "" ]]; then red; outn "$intl_FAIL"; normal; out "$intl_MAKEINSTALLER_NO_DISPLAYNAME"; cleanUp; exit 1; fi
    if [[ "$shortname" == "" ]]; then red; outn "$intl_FAIL"; normal; out "$intl_MAKEINSTALLER_NO_SHORTNAME"; cleanUp; exit 1; fi
    if [[ "$rootname" == "" ]]; then red; outn "$intl_FAIL"; normal; out "$intl_MAKEINSTALLER_NO_ROOTNAME"; cleanUp; exit 1; fi   
    if [[ "$softwareversion" == "" ]]; then red; outn "$intl_FAIL"; normal; out "$intl_MAKEINSTALLER_NO_VERSION"; cleanUp; exit 1; fi

    
    trace rootname=$rootname
    trace shortname=$shortname
    trace softwareversion=$softwareversion
    trace interfaceversion=$interfaceversion
    trace displayname=$displayname

    if ! versionFromRootName $rootname >/dev/null; then
	red; outn "$intl_WARNING"; normal; out "$intl_MAKEINSTALLER_NO_ROOTNAME_VERSION";
    fi
		        
    if [[ "$outfile" == "" ]]; then outfile="$shortname-$softwareversion.package"; fi;

    backend_script=`cat "$autopackage_prefix/share/autopackage/backend.template"`
##  backend_script=`echo "$backend_script" | sed "s|%rootname%|$rootname|g"`
##  backend_script=`echo "$backend_script" | sed "s|%shortname%|$shortname|g"`
    backend_script=`echo "$backend_script" | sed "s|%displayname%|$displayname|g"`
##  backend_script="$backend_script"

    temp=`getSection "$source_dir/$specfile" "Script-Prep";` # get the Script-Install section
    # scan for "require " lines
    requires=` echo "$temp" | awk '/require / {
    i=1
    while (i < NF) {
	if ($i == "require") {
	    i++; s="$i";
	    print $i;
	}
	i++;
    }
    } ' | tr -d "\";" | tr -d "\';" `


    if [[ "$requires" != "" ]]; then
	# ensure we have the needed skeleton packages
	i=1
	requirement=` getLine "$requires" $i `
	requirements="";
	while [[ "$requirement" != "" ]]; do
	    requirements="$requirements $requirement"; # add to list, this accumulates versioned root names....
	    skels_to_copy_files="$skels_to_copy_files `_locateSkeleton \"$requirement\"`"
	    res=$?
	    if [[ "$res" != "0" ]]; then # and this accumulates the file refs: @gnome.org/libxml/skeleton.2
		if [[ "$res" == "1" ]]; then 
		    red; outn "$intl_FAIL"; normal;  out "$intl_MAKEINSTALLER_NO_SKELETON" "$requirement";
		    normal; out "$intl_MAKEINSTALLER_TRY_CVS"
		    exit $ERROR_MISSING_SKELETON;
		elif [[ "$res" == "2" ]]; then
		    red; outn "$intl_FAIL"; normal; out "$intl_MAKEINSTALLER_BAD_SKELETON" "$requirement";
		    normal; out "$intl_MAKEINSTALLER_TRY_CVS"
		    exit $ERROR_MISSING_SKELETON;
		fi
	    fi
	    (( i++ ));
	    trace added requirement $requirement, `_locateSkeleton $requirement`
	    requirement=` getLine "$requires" $i `;
	done
    fi
    install_script=$( getSection "$source_dir/$specfile" Script-Install ) # get the install script from the specfile
    if [[ "$install_script" == "" ]]; then red; outn "$intl_FAIL"; normal; out "$intl_MAKEINSTALLER_NO_INSTALLER"; cleanUp; exit 1; fi;
    install_script=`joinLines %UserScript% "$backend_script" "$install_script"` # merge with backend.template
    install_script=`echo "$install_script" | sed "s/%Context%/install/"` # set the context variable
    
    prep_script=`getSection "$source_dir/$specfile" Script-Prep`
    if [[ "$prep_script" == "" ]]; then red; outn "$intl_WARNING"; normal; out "$intl_MAKEINSTALLER_NO_PREP"; fi;
    prep_script=`joinLines %UserScript% "$backend_script" "$prep_script"`
    prep_script=`echo "$prep_script" | sed "s/%Context%/prep/"` # set the context variable
    
    # **********************************************************
    #    Now run the prepare section, to give us a fake build root to extract the files from
    # **********************************************************

    prepare=`getSection "$source_dir/$specfile" BuildPrepare`
    prepare=`stripComments "$prepare"`
    unprepare=`getSection "$source_dir/$specfile" BuildUnprepare`
    unprepare=`stripComments "$unprepare"`

    pushd "$source_dir" >/dev/null

    if [[ "$prepare" != "" ]] && ! eval "$prepare"; then
	out "$intl_MAKEINSTALLER_PREPARE_FAILED"
	exit 1;
    fi

    # now figure out what files we need to extract and copy them into the working directory
    # the [Extractions] section is a script, so define the functions it needs and then run it
    extractions=`getSection $specfile Extractions`

    if [[ "$extractions" == "" ]]; then red; outn "$intl_WARNING"; normal; out "$intl_MAKEINSTALLER_NO_EXTRACTIONS";  fi;
    
    ## 
    # extract() [LOCATION]
    # LOCATION: the path in the package payload where the files should be placed. Optional. If skipped, "./" is assumed.
    # 
    # extract takes a set of files on STDIN to be extracted and optionally placed at a given location in the payload
    # The easiest way to invoke it is using a here-doc, as shown in the example. Each file to be extracted should be
    # on a separate line.
    #
    # Example:
    # extract ./share/foobar <<EOF
    # ./share/foobar/open.png
    # ./share/foobar/close.png
    # EOF
    # extract <<EOF
    # ./bin/foobar
    # EOF
    # (you now have 1 file in the root of the payload, foobar, and 2 in the ./share/foobar directory)
    function extract() {
	local path_to="$1"
	if [[ "$path_to" == "" ]]; then path_to="./"; fi;

	out "$intl_MAKEINSTALLER_EXTRACTING" "$working_dir/$path_to"

	# create destination path in payload, if it doesn't exist
	[ ! -e "$working_dir/$path_to" ] && { mkdir -p -- "$working_dir/$path_to"; }

	# yes, this is quite possibly a winner for the most disgusting bash construct ever invented.
	# blame the fact that piping something to a while statement forks a subshell -mh
	local id=$$
	if ! cat | while read; do
	    if ! cp -dpR $REPLY "$working_dir/$path_to"; then
		echo $REPLY >"$TMP/evilhack1-$id"
		exit 1
	    fi
      	done; then
	    red; outn "$intl_FAIL"; normal; out "$intl_MAKEINSTALLER_EXTRACT_COPY_FAIL" `cat "$TMP/evilhack1-$id"`;
	    rm "$TMP/evilhack1-$id"
	    exit 1 # dive dive dive!
	fi
	
	trace "extraction ok!"
	return 0;
    }

    # process extractions to force exit on first failed extraction
    if [[ "$extractions" != "" ]]; then
	if [[ "$build_root" == "" ]]; then build_root="."; fi;
	pushd "$build_root" >/dev/null

	(
	    eval "$extractions"
	)
	if [[ $? != 0 ]]; then
	    cleanUp;
	    exit 1;
	fi
	popd >/dev/null  # pop out of the build root
    fi

    if [[ "$unprepare" != "" ]]; then
	eval "$unprepare" || exit 1
    fi

    popd >/dev/null  # pop out of the source/source dir

    # **********************************************************
    #   Scan the extracted files for ELF libc symbol versions,
    #   and add checking for them to the scripts
    # **********************************************************

    symbols=$( _findGlibcVersions "$working_dir" )
    symbols=$(  for s in $symbols; do echo -n "GLIBC_$s "; done; )
    trace "required libc symbols are $symbols"
    prep_script=`echo "$prep_script" | sed "s/%LibCSymbols%/$symbols/"`

    # **********************************************************
    #   Now we've processed the specfile and subbed the stuff into the templates
    # **********************************************************

    out "$intl_MAKEINSTALLER_PLACED_IN" "$outfile"
    echo

    # **********************************************************
    #   Now write out the final installer stub. First calculate the length
    #   of the stub, then fill in the skipLines variable. Finally,
    #   write it to the specified output file.
    # **********************************************************

    stubLength=`echo "$stub" | wc -l | tr -d ' '`
    let "stubLength += 1"
    stub=`echo "$stub" | sed "s/%skipLines%/$stubLength/"`

    # *****************************
    #   Now archive the source directory with the install scripts
    #   and skeletons, then place the source onto the end
    # *****************************

    out "$intl_MAKEINSTALLER_GENERATING";

    metadata_dir="${TMP}/apkg-meta$RANDOM$$"
    mkdir "$metadata_dir"
    chmod 700 "$metadata_dir"

    trace writing scripts out
    echo "$installer" > "$metadata_dir/apkg-installer"
    echo "$downloader" > "$metadata_dir/apkg-downloader"
    echo "$updater" > "$metadata_dir/apkg-updater"
    echo "$prep_script" > "$metadata_dir/apkg-prep-install-script"
    echo "$install_script" > "$metadata_dir/apkg-install-script"

    # requires holds the skeleton packages that are required. copy them from the skeletons directory
    if [[ "$requires" != "" ]]; then
	requirements=( $requirements );
	i=0;
	for skel in $skels_to_copy_files; do
	    [[ "$skel" == "" ]] && continue;
	    trace copying skel $skel
	    skel_root=`getSection $skel Meta | getKey - RootName`
	    skel_version=`getSection $skel Meta | getKey - Skeleton-Version`
	    trace copying skeleton $skel, version $skel_version
	    _mkdirs "$metadata_dir/$skel_root" >/dev/null
	    cp $skel "$metadata_dir"/"$skel_root"
	    if [[ "$skel_version" == "" ]]; then red; outn "$intl_FAIL"; normal; out "$intl_MAKEINSTALLER_SKEL_MISSING_VERSION" $skel; cleanUp; exit 1; fi;
	    echo $skel_root/skeleton.$skel_version > "$metadata_dir/$skel_root/skeleton_ref"
	    trace skeleton ref was set to $skel_root/skeleton.$skel_version
	    (( i++ ));
	done
    fi

    chmod a+x "$metadata_dir/apkg-installer"
    chmod a+x "$metadata_dir/apkg-downloader"
    chmod a+x "$metadata_dir/apkg-updater"
    chmod a+x "$metadata_dir/apkg-prep-install-script"
    chmod a+x "$metadata_dir/apkg-install-script"

    trace copying description, uninstall, meta
    # copy description
    sect=$( getSection "$source_dir/$specfile" Description )
    echo "$sect" >> "$metadata_dir/apkg-description"
    if [[ "$sect" == "" ]]; then red; outn "$intl_WARNING"; normal; out "$intl_MAKEINSTALLER_NO_DESCRIPTION"; fi;
    # copy uninstall script
    sect=$( getSection "$source_dir/$specfile" Script-Uninstall )
    echo "$sect" >> "$metadata_dir/apkg-uninstall"
    trace uninstall section is $sect
    if [[ "$sect" == "" ]]; then red; outn "$intl_WARNING"; normal; out "$intl_MAKEINSTALLER_NO_UNINSTALLER"; fi;
    # copy Meta - if we don't have one, we should have errored out earlier
    getSection "$source_dir/$specfile" Meta >> "$metadata_dir/apkg-meta"

    oldpwd="$PWD"
    cd "$working_dir"
    # grab expanded size of archive for stub
    expandSize=`du -bs | awk '{print $1}'`
    if [[ `ls -1` == "" ]]; then # if we have no files in the payload (ie it's an empty package)
	touch ${TMP}/apkg-payload.$$ # create an empty file
	bzip2 ${TMP}/apkg-payload.$$ # and compress it :)
	mv ${TMP}/apkg-payload.$$.bz2 ${TMP}/apkg-payload.$$
	filecount=0
    else
	# compress the files directory and sub in the file count
	filecount=`tar -c --bzip2 -vf "${TMP}/apkg-payload.$$" * | wc -l | sed 's/^ *//' `
    fi
    cd "$oldpwd"

    # compress the metadata directory
    cd "$metadata_dir"
    tar czf "${TMP}/apkg-payload-meta.$$" *
    cd "$oldpwd"
    metaSize=`wc -c "${TMP}/apkg-payload-meta.$$" | awk '{print $1}'`
    dataSize=`wc -c "${TMP}/apkg-payload.$$" | awk '{print $1}'`
    
    trace computing MD5SUM
    # Compute MD5 sum after locating a MD5 binary
    md5sum="00000000000000000000000000000000"
    OLD_PATH=$PATH
    PATH=${GUESS_MD5_PATH:-"$OLD_PATH:/bin:/usr/bin:/sbin:/usr/local/ssl/bin:/usr/local/bin:/opt/openssl/bin"}
    MD5_PATH=`type -p md5sum`
    MD5_PATH=${MD5_PATH:-`type -p md5`}
    PATH=$OLD_PATH
    if [ -x "$MD5_PATH" ]; then
	md5sum=`cat "${TMP}/apkg-payload.$$" | "$MD5_PATH" | cut -b-32`
    else
	outn "$intl_MAKEINSTALLER_MD5SUM_NOT_FOUND"
    fi
    trace modifying stub
    stub=`echo "$stub" | sed "s/%fileTotal%/$filecount/; s/%MD5Sum%/$md5sum/; s/%metaSize%/$metaSize/; s/%dataSize%/$dataSize/; s/%expandSize%/$expandSize/"`
    echo "$stub" > "$outfile"
    cat "${TMP}/apkg-payload-meta.$$" >> "$outfile"
    cat "${TMP}/apkg-payload.$$" >> "$outfile"
    chmod +x "$outfile" >/dev/null 2>/dev/null

    # tidy up
    trace cleaning up
    cleanUp

    unset build_root
done

exit 0;
