# -*-shell-script-*-

# Dependency management code

###
#
# 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-2003 Mike Hearn (mike@plan99.net)
#
###


##
# _forgeDependency <ROOTNAME1> <ROOTNAME2>
# ROOTNAME1: versioned or unversioned package root name.
# ROOTNAME2: unversioned package root name. Function can accept either a versioned or unversioned root name.
#
# Places dependency information in the ROOTNAME1 package's database location.
# Dependencies are stored in a text file - this may change in future.
# ROOTNAME1 will depend on ROOTNAME2.
# <p>
# Places supports information in the unversioned ROOTNAME2 database location.
# Supports are stored in a text file and reference both versioned and
# unversioned root names.
#
function _forgeDependency() {

    # packages are removed after new prepare checks are completed (they used
    # to be removed before any new package activity) - many entries could be
    # in these files as prepare checks would occur but not remove or update
    # to other packages so make entries unique

    trace called with $1, $2
    local root_name2=`justRootName $2`
    local temp

    if [ ! -e "${autopackage_db_location}/${1}" ]; then
        mkdirs "${autopackage_db_location}/${1}"
    fi

    trace writing dependencies entry
    echo "$2" >> "${autopackage_db_location}/${1}/dependencies"
    logCommand --session "removeLine `escapeFilename \"${autopackage_db_location}/${1}/dependencies\"` $2"

    temp=`<"${autopackage_db_location}/${1}/dependencies"`
    echo -n "$temp" | sort | uniq > "${autopackage_db_location}/${1}/dependencies"

    # now link $2 to $1 so we can show the user warnings
    if [ ! -e "$autopackage_db_location/$root_name2" ]; then
        mkdirs "$autopackage_db_location/$root_name2"
    fi

    trace writing supports entry
    echo "$1" >> "${autopackage_db_location}/${root_name2}/supports"
    logCommand --session "removeLine `escapeFilename \"${autopackage_db_location}/${root_name2}/supports\"` $1"

    temp=`<"${autopackage_db_location}/${root_name2}/supports"`
    echo -n "$temp" | sort | uniq > "${autopackage_db_location}/${root_name2}/supports"

}


# removes a dependency from a package. $1 is the package, $2 is the dependency
function _unforgeDependency() {

    local root_name2=`justRootName $2`

    trace called with $1, $2
    # Remove the dependency
    removeLine "$autopackage_db_location/$1/dependencies" "$2"
    # now remove the supports entry
    removeLine "$autopackage_db_location/$root_name2/supports" "$1"
}

# eliminates all links to dependencies of package $1 (usually in preparation for uninstall)
function _unforgeDependencies() {
    trace called with $1
    if [ ! -e "$autopackage_db_location/$1/dependencies" ]; then trace "no dependencies file found"; return 0; fi
    local deps=`sort < "$autopackage_db_location/$1/dependencies" | uniq`
    [ -n "$deps" ] || return 0
    local c=$( countStringLines "$deps" )
    local i=1
    while (( i <= c )); do
	local dep=`getLine "$deps" $i`
	_unforgeDependency "$1" "$dep"
	(( i++ ));
    done
}


##
# _locateSkeleton <ROOTNAME>
# ROOTNAME: unversioned package root name
# Returns: 0 on success
# Returns: 1 if the skeleton was not found
# Returns: 2 if found but not an ASCII text file
# Returns: 3 if the skeleton was found, but the skeleton file has the wrong root name. The invalid root name will be printed to stdout.
# Outputs: the absolute path to the skeleton file.
#
# Given a root name, will search through the skeleton path until the highest numbered skeleton for that name is found.
# It will then output the absolute filename.
#
# Example:
# _locateSkeleton "@gnome.org/libxml" --> /usr/local/share/autopackage/skeletons/\@gnome.org/libxml/skeleton.2
function _locateSkeleton() {
    # first things first, locate the highest matching directory for the given root name
    local s
    local r=`justRootName "$1"`
    trace locating $r in "$_meta_dir" "$_apspec_dir" "${_skel_dirs[@]}"

    for s in "$_meta_dir" "$_apspec_dir" "${_skel_dirs[@]}"; do
        [[ "$s" == "" ]] && continue || true
        trace "looking for: $s/$r"
	if [ -e "$s/$r" ]; then
	    # find the highest versioned skeleton
	    local skel=$( ls -1 -B "$s/$r" | sort -g -t. -k2 -r | head -n1 )
	    local skelRootName
	    skel="$s/$r/$skel"
	    trace located at $skel

	    # Check whether the skeleton has the correct rootname
	    skelRootName=`grep "^RootName: " "$skel" | sed 's/^RootName: //'`
	    if [[ "$skelRootName" != "$r" ]]; then
	        echo "$skelRootName"
		return 3
	    fi

	    #if ! file "$skel" | awk -F": " ' { print $2 } ' | grep ASCII >/dev/null; then
		#warn "skeleton is not ASCII text?"
		#return 0;
	    #fi
	    echo $skel
	    return 0;
	fi
    done
    return 1;
}


##
# checkForPackage [-i REQUIRED VERSION] [-e] <ROOTNAME> [SKELETON ARGUMENTS]
# ROOTNAME: versioned or unversioned package root name.
# REQUIRED VERSION: MAJOR[.MINOR] formatted numbers to validate requirements on ROOTNAME's package.
# SKELETON ARGUMENTS: optional arguments that are passed to the package skeleton.
#
# Calls the test section of a skeleton file for the given root name. The results
# are available in the $INTERFACE_VERSIONS and $SOFTWARE_VERSIONS variables. If the
# package wasn't found, these variables are empty, otherwise they contain a space
# delimited list of version numbers that were detected. The ones you're probably
# interested in are the interface numbers, these identify a logical software interface.
#
# If the -i parameter is specified, this function will match against the given interface
# version. It will return 0 on a match, 1 if otherwise. If -i is not specified and the
# rootname is unversioned, the function will always returns 0. Used with require().
#
# If the -e parameter is specified, this function will match against the given software
# version. It will return 0 on a match, 1 if otherwise. Used with requireExact().
#
# If the rootname is versioned, this function will match for the given software
# version. Used with requireAtLeast().
#
# Optional arguments given after the root name are passed to the skeleton test script
# in $@, so can be accessed using the standard $1, $2, $3 etc variables.
#
# Returns 0 if a succesful match was found, 1 if no match was found and 2 if no interfaces
# were found at all.
#
# Example:
# checkForPackage -i 2 "@gnome.org/libxml"     <-- require "@gnome.org/libxml" "2"
# checkForPackage "@gnome.org/libxml:1"        <-- requireAtLeast "@gnome.org/libxml:1"
# checkForPackage -e "@gnome.org/libxml:1.2"   <-- requireExact "@gnome.org/libxml:1.2"
#
function checkForPackage() {
    pushOptE; set +e

    trace called with $@
    local root_name
    local required_atleast_version
    local required_exact_version
    local required_interface
    export INTERFACE_VERSIONS=""
    export SOFTWARE_VERSIONS=""

    # Determine mode of operation from arguments and set variables
    if [[ "$1" == "-i" ]]; then
        shift
        required_interface="$1"
        shift
        root_name="$1"
        if versionFromRootName "$root_name" >/dev/null; then
            err The rootname $root_name needs to be unversioned if using the -i argument
            popOptE
            return 1
        fi
        # Normalize the required_interface variable to be A.0 if necessary
        if [[ $( getMinor "$required_interface" ) == "" ]]; then
            required_interface="$required_interface.0"
        fi
    elif [[ "$1" == "-e" ]]; then
        shift
        root_name=`justRootName "$1"`
        if ! required_exact_version=$( versionFromRootName "$1" ); then
            err The rootname $root_name needs to be versioned if using the -e argument
            popOptE
            return 1
        fi
        # Normalize the required_exact_version variable to be A.0 if necessary
        if [[ $( getMinor "$required_exact_version" ) == "" ]]; then
            required_exact_version="$required_exact_version.0"
        fi
    elif required_atleast_version=$( versionFromRootName "$1" ); then
        root_name=`justRootName "$1"`
        # Normalize the required_atleast_version variable to be A.0 if necessary
        if [[ $( getMinor "$required_atleast_version" ) == "" ]]; then
            required_atleast_version="$required_atleast_version.0"
        fi
    else
        root_name="$1"
    fi
    shift  # shift the rootname away

    trace root_name=$root_name, interface=$required_interface, exact=$required_exact_version, atleast=$required_atleast_version

    local _skelpath=$( _locateSkeleton "$root_name" )
    trace skeleton path is $_skelpath
    if [[ "$_skelpath" == "" ]]; then
        err "No skeleton found for $root_name"
        popOptE
        return 1
    fi
    local _test=$( getSection "$_skelpath" Test )

    if [[ "$_test" == "" ]]; then
        err "No test section for $root_name?"
        popOptE
        return 1
    fi

    # process the [Test] script
    eval "function t(){
      $_test
    }"
    t $@;
    unset t

    trace INTERFACE_VERSIONS after test script is \"$INTERFACE_VERSIONS\"
    trace SOFTWARE_VERSIONS after test script is \"$SOFTWARE_VERSIONS\"

    # check for versions and remove duplicates and countDownVersion if interface matching
    if [[ "$required_interface" != "" ]]; then
        INTERFACE_VERSIONS=$( countDownVersions $INTERFACE_VERSIONS | stripDupedItems )
        SOFTWARE_VERSIONS=$( countDownVersions $SOFTWARE_VERSIONS | stripDupedItems )
        trace INTERFACE_VERSIONS after counting down/stripping=$INTERFACE_VERSIONS
        trace SOFTWARE_VERSIONS after counting down/stripping=$SOFTWARE_VERSIONS
    else
        INTERFACE_VERSIONS=$( echo $INTERFACE_VERSIONS | stripDupedItems )
        SOFTWARE_VERSIONS=$( echo $SOFTWARE_VERSIONS | stripDupedItems )
        trace INTERFACE_VERSIONS after stripping=$INTERFACE_VERSIONS
        trace SOFTWARE_VERSIONS after stripping=$SOFTWARE_VERSIONS
    fi

    local found
    if [[ "$required_interface" != "" ]]; then
        if [[ "$INTERFACE_VERSIONS" == "" ]]; then
            warn "no interface implementations found, bailing out early"
            popOptE
            return 1
        fi

        trace looking for interface $required_interface
        found="false"
        for v in $INTERFACE_VERSIONS; do
            $found && break
            [[ "$v" == "$required_interface" ]] && found="true"
            [[ `getMajor $v` == "$required_interface" ]] && found="true"
        done
        trace found=$found
        popOptE
        $found && return 0
        return 2   # there were some interfaces, but not what we wanted
    elif [[ "$required_atleast_version" != "" ]]; then
        trace looking for at least $required_atleast_version

        if [[ "$SOFTWARE_VERSIONS" == "" ]]; then
            warn "no software versions detected, bailing out early"
            popOptE
            return 1
        fi

        found="false"
        for v in $SOFTWARE_VERSIONS; do
            compareVersions "$required_atleast_version" "$v" && found="true"
        done
        trace found=$found
        popOptE
        $found && return 0
        return 2
    elif [[ "$required_exact_version" != "" ]]; then
        trace looking for exactly $required_exact_version

        if [[ "$SOFTWARE_VERSIONS" == "" ]]; then
            warn "no software versions detected, bailing out early"
            popOptE
            return 1
        fi

        for v in $SOFTWARE_VERSIONS; do
            if [[ "$v" == "$required_exact_version" ]]; then
                popOptE
                return 0
            fi
        done
        popOptE
        return 2
    else
        popOptE
        return 0
    fi

    _assertNotReached
}


##
# _retrieveLocalPackage <DIRECTORY> <ROOTNAME> <REQUIRED VERSION>
# DIRECTORY: file system location to search for ROOTNAME.
# ROOTNAME: versioned or unversioned package root name.
# REQUIRED VERSION: MAJOR[.MINOR] formatted numbers to validate requirements on ROOTNAME's package.
# Returns: 0 on success.
# Returns: 1 on failure.
#
# Retrieve the package ROOTNAME from DIRECTORY. Similar to retrieve(), but only
# for local files.
function _retrieveLocalPackage() {

    local f;
    local r;
    # directory not found so return package not found
    [ ! -d "$1" ] && return 1

    local oIFS="$IFS"
    IFS=$'\n'

    local best_candidate_pv
    local best_candidate_if
    local best_candidate
    local header c

    local _rn=$2
    trace DIRECTORY=$1, ROOTNAME=$_rn, VER=$3

    # Scan all .package files in DIRECTORY.
    for f in `ls -1 "$1" | grep '\.package$'`; do
	trace scanning $f

	# Verify that this really is an Autopackage
	if ! head -n 3 "$1/$f" | grep --text -q '# autopackage '; then
		continue
	fi

	# get package header
	unset header c
	header=$( _getPackageHeader "$1/$f" )

	# Verify package matches machine cpu architecture
	c=$( echo "$header" | grep CPUArchitecture | sed 's/# CPUArchitecture //' )
	trace package cpuarchitecture is \"$c\"
	if [[ "$cpu_architecture" != "$c" ]]; then
		continue
	fi

	r=$( echo "$header" | grep RootName | sed 's/# RootName //' )
	r=$( justRootName "$r" ) # strip any version info
	trace root name is \"$r\"

	if [[ "$_rn" == "$r" ]]; then
	    # ok, the root name matches. did we specify a versioned rootname?
	    trace .. matched
	    if [[ $( versionFromRootName "$_rn" ) != "" ]]; then
		# yes, so we want to match exactly, as only the given version is good enough
		local v=$( echo "$header" | grep SoftwareVersion | sed 's/# SoftwareVersion //' )
		if [[ "$v" == $( versionFromRootName "$rn" ) ]]; then
		    # found so let's process it
		    echo "$1/$f"
		    trace "... and found [ $1/$f ]"
		    IFS="$oIFS"
		    return 0
		fi
		trace but the version was not correct
	    else
		# we need to match against the interface version

		# normalise $3 into A.B form
		if [[ `getMinor $3` == "" ]]; then
			requiredver="$3.0"
		else
			requiredver="$3"
		fi

		trace we need $requiredver
		local if_version=$( echo "$header" | grep InterfaceVersion | sed 's/# InterfaceVersion //' )
		trace if_version=${if_version}
		if_version=$( countDownVersions $if_version )
		trace interfaces this package fulfils is $if_version

		local v
		IFS=" "
		for v in $if_version; do
		    trace v is \"$v\"
		    if [[ "$v" == "$requiredver" ]]; then

			trace matched against \"$1/$f\"
			local pv=$( echo "$header" | grep 'PackageVersion' | sed 's/# PackageVersion //' )
			if [[ "$pv" == "" ]]; then pv=1; fi

			if [[ "$best_candidate" == "" ]] || compareVersionsSimple $v $best_candidate_if || (( pv > best_candidate_pv )); then
			    best_candidate="$1/$f"
			    best_candidate_if="$v"
			    best_candidate_pv="$pv"
			    trace best candidate is now $best_candidate / $best_candidate_if / $best_candidate_pv
			fi
		    fi
		done
		IFS=$'\n'
	    fi
	fi
    done

    if [[ "$best_candidate" ]]; then
	trace chosen package was $best_candidate / $best_candidate_if / $best_candidate_pv
	chmod +x "$best_candidate" || {	err "Could not set execute permission on $best_candidate"; return 1; }

	# grab some more package meta data from selected best candidate
	unset header
	header=`_getPackageHeader "$best_candidate"`

	# what is the versioned root name of this package?
	export RETRIEVAL_VERSIONED_ROOTNAME=$( echo "$header" | grep 'RootName' | sed 's/# RootName //' )
	trace versioned rootname is $RETRIEVAL_VERSIONED_ROOTNAME

	# used to execute package
	local api=$( echo "$header" | grep '^export AUTOPACKAGETARGET' | sed 's/.*=//; s/"//g' )

		IFS="$oIFS"
		trace delay package execution, executing: \"$best_candidate\" -w \"$WORKING_DIRECTORY\" --delay
		"$best_candidate" -w "$WORKING_DIRECTORY" --delay
		return $?

    fi

    # Package not found
    warn package not found locally
    IFS="$oIFS"
    return 1
}

##
# _retrieveFromNet <ROOTNAME> [REQUIRED VERSION]
# ROOTNAME: versioned or unversioned package root name.
# REQUIRED VERSION: MAJOR[.MINOR] formatted numbers to validate requirements on ROOTNAME's package if ROOTNAME is unversioned
#
# Attempts to locate a package from the network, and stores the metadata locally.
# Returns 0 if successful, 1 if there was a failure retrieving the metadata, 2 if the subprep failed
function _retrieveFromNet() {
    trace called with $@

    #
    # We need to call into luau-downloader with the following pieces of information:
    #   * package description XML file URL
    #   * If $1 is unversioned, take the interface version from $2, otherwise get the version and download that


    # api usage checks
    if [[ "$1" == "" ]]; then
	err must be called with at least one argument
	return 1
    fi

    versionFromRootName $1 >/dev/null;
    if [[ $? == 1 ]] && [[ "$2" == "" ]]; then
	err must provide an interface version when the ROOTNAME argument is unversioned
	return 1
    fi

    local repo_file="$WORKING_DIRECTORY/meta/`justRootName $1`/repositories"
    if [ ! -e "$repo_file" ]; then
        warn no repositories were registered for $1 at $repo_file
        return 1
    fi

    local repositories=$( cat "$repo_file" )
    unset repo_file

    for repository in $repositories; do

        # get the metadata
	local before
	local after

	trace trying $repository
	before=$( date +%s ) # gnu extension

	local v
	v=$( versionFromRootName $1 )

    locateCommand luau-downloader
    if [[ "$v" == "" ]]; then

        trace "$lc_location" -P "/dev/zero" -m -u "$repository" -i "$2" -o "$WORKING_DIRECTORY/download.meta"
        if ! "$lc_location" -P "/dev/zero" -m -u "$repository" -i "$2" -o "$WORKING_DIRECTORY/download.meta"; then
            warn $1 not found in $repository
            continue;
        fi
    else

        trace "$lc_location" -P "/dev/zero" -m -u "$repository" --version "$v" -o "$WORKING_DIRECTORY/download.meta"
        if ! "$lc_location" -P "/dev/zero" -m -u "$repository" --version "$v" -o "$WORKING_DIRECTORY/download.meta"; then
            warn $1 not found in $repository
            continue;
        fi
    fi

	after=$( date +%s )
	trace "$[ after - before ] seconds to get metadata"

	[ ! -d "$WORKING_DIRECTORY/holding" ] && mkdirs --nolog "$WORKING_DIRECTORY/holding"
	tar xzf "$WORKING_DIRECTORY/download.meta" -C "$WORKING_DIRECTORY/holding" || { err "Could not extract metadata to holding area"; return 1; }
	rm "$WORKING_DIRECTORY/download.meta" || { err "Could not delete temporary meta download file"; return 1; }

	export RETRIEVAL_VERSIONED_ROOTNAME=$( _loadPackageEnvironment "$WORKING_DIRECTORY/holding"; echo $ROOTNAME; )

	[ ! -d `dirname "$WORKING_DIRECTORY/meta/$RETRIEVAL_VERSIONED_ROOTNAME"` ] && mkdirs --nolog `dirname "$WORKING_DIRECTORY/meta/$RETRIEVAL_VERSIONED_ROOTNAME"`
	mv "$WORKING_DIRECTORY/holding" "$WORKING_DIRECTORY/meta/$RETRIEVAL_VERSIONED_ROOTNAME" || { err "Could not moving holding area to $WORKING_DIRECTORY/meta/$RETRIEVAL_VERSIONED_ROOTNAME"; return 1; }

        # put the repository location in the metadata of the package about to be downloaded. we
        # can't access the skeleton again later, because _meta_dir is changed so stuff works properly
        # in the install script.
	trace adding $repository to "$WORKING_DIRECTORY/meta/$RETRIEVAL_VERSIONED_ROOTNAME/repository-location"
	echo "$repository" >> "$WORKING_DIRECTORY/meta/$RETRIEVAL_VERSIONED_ROOTNAME/repository-location"
	trace completed downloading $1
	return 0
    done

    warn "no packages were found that matched the given criteria"
    warn "the following repositories were tried:"
    local r
    for r in $repositories; do
	warn $r
    done

    return 1
}

##
# _retrievePayloadFromNet <ROOTNAME>
# ROOTNAME: versioned or unversioned package root name.
#
# Downloads the actual payload.
function _retrievePayloadFromNet() {
    trace called with $@

    local v # unversioned rootname
    v=$( versionFromRootName $1 )

    local before
    local after
    before=$( date +%s )
    mkdirs --nolog "$WORKING_DIRECTORY/payload/$1"

    # we need to get the repository location from the metadata
    local r
    trace _meta_dir=$_meta_dir
    if [ ! -f "$_meta_dir" ]; then
        _meta_dir="$WORKING_DIRECTORY/meta/$1"
    fi
    r=$( cat "$_meta_dir/repository-location" )
    if [[ "$r" == "" ]]; then
	err "repository-location is empty"
	return 1
    fi

    {
        export AUTOPACKAGETARGET
        locateCommand luau-downloader
        trace "$lc_location" -P "$autopackage_pipe" -u "$r" -v "$v" -o "$WORKING_DIRECTORY/payload/$1/payload"
        if ! "$lc_location" -P "$autopackage_pipe" -u "$r" -v "$v" -o "$WORKING_DIRECTORY/payload/$1/payload"; then
            outputFail "Download of payload for $1 failed - check your internet connection?"
            return 1
        fi
        if [[ `head -c3 "$WORKING_DIRECTORY/payload/$1/payload"` != "BZh" ]]; then

            # we've got ourselves a genuine autopackage, probably
            # because this is a 1.2 pack and for convenience we don't
            # create .payload files anymore. so, run the package in
            # "delay mode" which simply makes it dump its compressed
            # payload to disk, then delete it. This payload can be any
            # API as >1.2 makeinstaller does not generate .payload at all.

            chmod +x "$WORKING_DIRECTORY/payload/$1/payload"
            trace delay package execution, executing: \"$WORKING_DIRECTORY/payload/$1/payload\" -w \"$WORKING_DIRECTORY\" --delay
            "$WORKING_DIRECTORY/payload/$1/payload" -w "$WORKING_DIRECTORY" --delay
            rm "$WORKING_DIRECTORY/payload/$1/payload"
        else
            # otherwise give it the correct name so _decompressPayload knows what to do
            mv "$WORKING_DIRECTORY/payload/$1/payload" "$WORKING_DIRECTORY/payload/$1/payload.tar.bz2"
        fi
    }

    after=$( date +%s )
    trace "$[ after - before ] seconds to get payload"
    return 0
}

##
# retrieve [--install] <ROOTNAME> [REQUIRED VERSION]
# ROOTNAME: versioned or unversioned package root name.
# REQUIRED VERSION: MAJOR[.MINOR] formatted numbers to validate requirements on ROOTNAME's package.
# Returns: 0 on success.
# Returns: 1 if the package could not be retrieved, 2 if it was retrieved but prepare/install failed
#
# Will attempt to locate and optionally install a package either from the local computer or a remote
# network given either a versioned rootname (for example @foo.org/bar:6.4b) or an unversioned root
# name with an interface number. <p>
#
# Default behaviour is to only download the downloaded and extract the package, unless the --install
# option is specified in which case the package will also be installed.  <p>
#
# Note that when invoked from inside other installers, the package will
# be prepared, but not fully installed. Instead, the payload will be registered, and
# later all the install sections of the packages that were prepared will be run at once. <p>
#
# This function is primarily used by require(), however it's available for your own
# package scripts as well. <p>
#
# If you specify a versioned root name (should not contain a release number) then
# you should not give an interface number, if you do it will be ignored. <p>
#
# If you specify an unversioned root name, then the system will select the best
# package that satisfies the given interface. For example, requesting an interface
# 1.2 will allow a match against any package that advertises fulfilment of interface
# 1.2, 1.3, 1.4, 1.5 and so on (but not 0.x nor 2.x) <p>
#
# Example:
# retrieve @foo.org/bar 2.2   --> will install any package that can fulfil interface 2.2
# retrieve @bar.org/foo:2003  --> will attempt to install Foo 2003, no other version will do
function retrieve() {
    # Search packages in the same directory that the package is in
    local packageDir=$( dirname "$_package_filename" )
    local install=false
    if [[ "$1" == "--install" ]]; then
	install=true
	shift
    fi

    # check we have been given the correct arguments
    if [[ $( justRootName $1 ) == "$1" ]] && [[ "$2" == "" ]]; then
	err "retrieve must be given an interface version in the case of an unversioned rootname"
	return 1;
    fi

    local res

    trace calling _retrieveLocalPackage with $@
    _retrieveLocalPackage "$packageDir" $@
    res=$?

    if [[ $res == 2 ]]; then
	return 1
    elif [[ $res == 1 ]]; then
	# If that didn't work, search packages in the sealed installer directory
	# that the package was started with

	trace searching sealed installer directory $_meta_dir/packages
	_retrieveLocalPackage "$_meta_dir/packages" "$@"
	res=$?
        if [[ $res == 2 ]]; then
	    return 1
        elif [[ $res == 1 ]]; then
		# If that didn't work, search packages in the working directory
		# that the package was started with

		trace searching $EXECUTED_FROM_DIRECTORY
		_retrieveLocalPackage "$EXECUTED_FROM_DIRECTORY" "$@"
		res=$?

		if [[ $res == 2 ]]; then
		    return 1
		elif [[ $res == 1 ]]; then
		    # we could not find it locally. we need to scan the network for potential matches

		    trace calling _retrieveFromNet with $@
		    _retrieveFromNet "$@"
		    res=$?

		    if [[ $res != 0 ]]; then
			warn no package found
			return 1
		    fi

		fi
	fi
    fi

    # we might want to stop here
    if ! $install; then return 0; fi

    if [[ "$WORKING_DIRECTORY" != "" ]]; then

	if [[ "$RETRIEVAL_VERSIONED_ROOTNAME" == "" ]]; then
	    err "RETRIEVAL_VERSIONED_ROOTNAME is unset"
	    return 1
	fi

	[ -d "$WORKING_DIRECTORY/meta/$RETRIEVAL_VERSIONED_ROOTNAME" ] && cd "$WORKING_DIRECTORY/meta/$RETRIEVAL_VERSIONED_ROOTNAME"
	if ! "$WORKING_DIRECTORY/meta/$RETRIEVAL_VERSIONED_ROOTNAME/apkg-installer"; then
	    warn "installer for $RETRIEVAL_VERSIONED_ROOTNAME failed to prepare"
	    return 1
	fi

        # that script will not have actually run the [Install] section, it's delayed.
	# FIXME: should this code be here, or in the prepare script for the subpackage itself?
	_registerPayload "$RETRIEVAL_VERSIONED_ROOTNAME" # ... so register it for later installation, assuming we have a working dir (ie are being run from an installer)
    fi

    return 0
}


# _addSkeletonFile <ROOTNAME>
# ROOTNAME: unversioned package root name. Function can accept either a versioned or unversioned root name.
#
# Places the given skeleton file into the database, and places shortname symlinks
# this is used for verifying packages later.
#
function _addSkeletonFile() {

    local root_name=`justRootName "$1"`
    local short_name
    local skeleton_dirs_backup
    local skeleton_file

    # i really need to write a stack for bash
    skeleton_dirs_backup="$skel_dirs"
    skeldirs="$autopackage_db_location"

    skeleton_file=`_locateSkeleton "${root_name}"`
    copyFiles --silent --nolog --nobackup "$skeleton_file" "$autopackage_db_location/${root_name}"

    skeldirs="$skeleton_dirs_backup";

}


# registers a root name for later installation, part of building the "installation tree"
function _registerPayload() {
    if [[ "$1" == "" ]]; then
	err must have at least one argument
	return 1
    fi
    trace "Registering $1 as a payload in $_meta_dir/payloads"
    echo "$1" >>"$_meta_dir/payloads"
    echo "$1" >>"$WORKING_DIRECTORY/global-payloads" # so we can figure out how many packages we'll be installing later
    return 0
}


##
# _requireCommons <REQUIRE MODE> <ROOTNAME> [REQUIRED VERSION] [SKELETON ARGUMENTS]
# REQUIRE MODE: mode to use for this require call. Argument can be require, requireAtLeast, or requireExact.
# ROOTNAME: versioned or unversioned package root name.
# REQUIRED VERSION: MAJOR[.MINOR] formatted numbers to validate requirements on ROOTNAME's package.
# SKELETON ARGUMENTS: optional arguments that are passed to the package skeleton.
#
# Example:
# _requireCommons require "@gnome.org/libxml" 1
# _requireCommons requireAtLeast "@gnome.org/libxml:1" --with-xslt
# _requireCommons requireExact "@gnome.org/libxml:1.2" --with-xslt
# _requireCommons recommend "@gnome.org/libxml" 1
# _requireCommons recommendAtLeast "@gnome.org/libxml:1" --with-xslt
# _requireCommons recommendExact "@gnome.org/libxml:1.2" --with-xslt
#
function _requireCommons() {
    pushOptE; set +e;
    trace context is $_context

    if [[ "$_context" != "prep" ]] && [[ "$_context" != "verify" ]]; then
        err "require cannot be called outside of prep or verify context"
        popOptE
        return 1
    fi

    local verify_command
    local checkForPackage_arguments
    local required_version
    local mode="$1"; shift
    local required_name="$1"; shift
    local required_version_major
    local required_version_minor

    if [[ "$mode" == "require" ]] || [[ "$mode" == "recommend" ]]; then
        required_version="$1"; shift
        required_version_major=`getMajor "$required_version"`
        required_version_minor=`getMinor "$required_version"`
        if [[ "$required_version_minor" == "" ]]; then
            required_version="$required_version_major.0"
        fi
    fi

    trace mode=$mode, required_name=$required_name, required_version=$required_version

    # check - have we already prepared this package?
    # if so then a previous call to require failed
    # and sorted it out so we can skip this one
    # (but still create a dependency record)
    if [ -e "$WORKING_DIRECTORY/prepared-rootnames" ] && grep "$required_name" "$WORKING_DIRECTORY/prepared-rootnames" >/dev/null; then

	_forgeDependency "$ROOTNAME" "$required_name"
	
	trace $required_name is prepared already ... skipping dependency check
        popOptE
        return 0
    fi

    # if verify context then do not write/create any verification files
    if [[ $_context != "verify" ]]; then
        # record how require was invoked, so we can rerun it in verify later
        verify_command="$mode \"$required_name\" $required_version $@"
        echo "$verify_command" >> "$_meta_dir/verification"
    fi

    local skelfile=`_locateSkeleton "$required_name"`
    if [[ "$skelfile" == "" ]]; then
        err could not locate required skeleton file in package metadata for \"$required_name\"
        popOptE
        return 1
    fi
    export r_displayname=`getSectionKey "$skelfile" "Meta" "DisplayName"`
    local found="n"

    # output the fact that we are doing a dependency check
    outputTest "$r_displayname"

    if [[ "$mode" == "require" ]] || [[ "$mode" == "recommend" ]]; then
        checkForPackage -i "$required_version" "$required_name" "$@"
    elif [[ "$mode" == "requireExact" ]] || [[ "$mode" == "recommendExact" ]]; then
        checkForPackage -e "$required_name" "$@"
    elif [[ "$mode" == "requireAtLeast" ]] || [[ "$mode" == "recommendAtLeast" ]]; then
        checkForPackage "$required_name" "$@"
    else
        assertNotReached
    fi

    local result="$?"
    trace "checkForPackage() result is $result"

    # collect result
    r_rootname=`getSectionKey "$skelfile" "Meta" "RootName"`
    r_shortname=`getSectionKey "$skelfile" "Meta" "ShortName"`

    # if required_version not given then determine from versioned RootName
    if [[ "$required_version" == "" ]]; then
        required_version=`versionFromRootName "$required_name"`
    fi

    # if verify context then do not reporting
    if [[ $_context != "verify" ]]; then
        # PACKAGEREPORTING is from the preparing application so dependencies
        # from a non-reporting package are also not reported but their installation event
        # depends on its own PACKAGEREPORTING meta key value
        trace recording action=record event=dependency rootname=`justRootName "$ROOTNAME"` shortname=$SHORTNAME softwareversion=$SOFTWAREVERSION interfaceversion=$INTERFACEVERSION rootname_dep=$r_rootname shortname_dep=$r_shortname mode=$mode mode_version=$required_version result=$result
        if [[ "$PACKAGEREPORTING" != "No" ]]; then
            echo "action=record&event=dependency&rootname=`justRootName "$ROOTNAME"`&shortname=$SHORTNAME&softwareversion=$SOFTWAREVERSION&interfaceversion=$INTERFACEVERSION&rootname_dep=$r_rootname&shortname_dep=$r_shortname&mode=$mode&mode_version=$required_version&result=$result" >> "$WORKING_DIRECTORY/reporting"
        else
            trace PACKAGEREPORTING=$PACKAGEREPORTING so skip recording the event
        fi
    fi

    if [[ "$result" == "0" ]]; then
        outputTestPass;
        # have we already got the skeleton info
        if [[ ! -e "$autopackage_global_db_location/$required_name" ]] && [[ ! -e "$autopackage_user_db_location/$required_name" ]]; then
            _addSkeletonFile "$required_name"
        fi
	_forgeDependency "$ROOTNAME" "$required_name" # so that dependency will be recorded even if required package is installed already
        popOptE
        return 0
    else
        # if we fail, then call retrieve (ie fail for now)
        outputTestSearching

        # at this point, attempt to locate and install the dependency.
        # this can be from the net, or from the same directory (or from the net cache)

	# if we have a Repository key, register it
        trace checking for Repository key
	local skelrepo=$( getSection "$skelfile" Meta | getKey - "Repository" )
	if [[ "$skelrepo" != "" ]]; then
	    registerRepository $( justRootName $required_name ) $skelrepo
	fi

        # get the [retrieval] section of the skeleton, if there is one
        retrieval=$( getSection "$skelfile" Retrieval )
        if [[ "$retrieval" == "" ]]; then
            retrieval="retrieve $required_name $required_version" # default entry
            trace "using default retrieval command: $retrieval"
        else
            retrieval=$( getSection "$skelfile" Retrieval )
        fi

	# so we can use return...
        function __retrieval() { eval "$retrieval"; return $?; }

        # retrieval of package exports RETRIEVAL_VERSIONED_ROOTNAME variable
        if __retrieval; then
	    outputTestPass

	    if [[ "$RETRIEVAL_VERSIONED_ROOTNAME" == "" ]]; then
		err RETRIEVAL_VERSIONED_ROOTNAME is unset
		popOptE
		return 1
	    fi

	    # prepare the package
	    trace about to run \"$WORKING_DIRECTORY/meta/$RETRIEVAL_VERSIONED_ROOTNAME/apkg-installer\" as subprep

            local current_meta_dir="$_meta_dir"
            local current_payload_dir="$_payload_dir"
            export _meta_dir="$WORKING_DIRECTORY/meta/$RETRIEVAL_VERSIONED_ROOTNAME/"
            export _payload_dir="$WORKING_DIRECTORY/payload/$RETRIEVAL_VERSIONED_ROOTNAME/"
            
	    if ! "$WORKING_DIRECTORY/meta/$RETRIEVAL_VERSIONED_ROOTNAME/apkg-installer"; then
		warn "subprep for $RETRIEVAL_VERSIONED_ROOTNAME / $required_name failed"

		unset RETRIEVAL_VERSIONED_ROOTNAME
		unset REPOSITORY_LOCATION
		unset __retrieval

                export _meta_dir="$current_meta_dir"
                export _payload_dir="$current_payload_dir"
                trace _meta_dir=$_meta_dir
                trace _payload_dir=$_payload_dir
                
		popOptE
		return 1
	    else
                export _meta_dir="$current_meta_dir"
                export _payload_dir="$current_payload_dir"
                trace _meta_dir=$_meta_dir
                trace _payload_dir=$_payload_dir
                
		_registerPayload "$RETRIEVAL_VERSIONED_ROOTNAME"

		_addSkeletonFile "$required_name"
		_forgeDependency "$ROOTNAME" "$RETRIEVAL_VERSIONED_ROOTNAME"

		unset RETRIEVAL_VERSIONED_ROOTNAME
		unset REPOSITORY_LOCATION
		unset __retrieval

                identify "$DISPLAYNAME" "$_cur_series_pos" "prep"

		popOptE
		return 0
	    fi
        else
            local retres=$?
            unset REPOSITORY_LOCATION
            unset RETRIEVAL_VERSIONED_ROOTNAME
            unset __retrieval

            # recommend* modes do not output failure message
            if [[ "$mode" == "recommend" ]] || [[ "$mode" == "recommendAtLeast" ]] || [[ "$mode" == "recommendExact" ]]; then
                outputTestRecommend
                # I don't think we care about short names here
                # r_shortname=`getSectionKey "$skelfile" "Meta" "ShortName"`
                [[ $_context != "verify" ]] && echo "$r_displayname" >> "$WORKING_DIRECTORY/failed-recommends"
                # have we already got the skeleton info
                if [[ ! -e "$autopackage_global_db_location/$required_name" ]] && [[ ! -e "$autopackage_user_db_location/$required_name" ]]; then
                    _addSkeletonFile "$required_name"
                    _forgeDependency "$ROOTNAME" "$required_name"
                fi
                popOptE
                return 0
            # require* modes output failure message
            else
                r_shortname=`getSectionKey "$skelfile" "Meta" "ShortName"`
                outputTestFail
                identify "$DISPLAYNAME" "$_cur_series_pos" "prep"

                local msg
                if [[ "$result" == "1" ]]; then
                    msg=$( out "$intl_PACKAGE_COULD_NOT_FIND" "$r_displayname" "$r_shortname" )
                elif [[ "$result" == "2" ]]; then
                    local tmp
                    if [[ "$mode" == "require" ]]; then tmp="IV"; fi
                    if [[ "$mode" == "requireAtLeast" ]]; then tmp="SV"; fi
                    if [[ "$mode" == "requireExact" ]]; then tmp="SV"; fi
                    msg=$( out "$intl_PACKAGE_FOUND_BUT_WRONG_VERSION" "$r_displayname" "$tmp" "$required_version" "$required_name" )
                else
                    err "$intl_ASSERTION_NOT_REACHED"
                    msg="($intl_ASSERTION_NOT_REACHED)"
                fi
                outputFail "$msg"

                popOptE
                return 1
            fi

        fi
    fi
}


##
# require <ROOTNAME> <REQUIRED VERSION> [SKELETON ARGUMENTS]
# ROOTNAME: versioned or unversioned package root name.
# REQUIRED VERSION: MAJOR[.MINOR] formatted numbers to validate requirements on ROOTNAME's package.
# SKELETON ARGUMENTS: optional arguments that are passed to the package skeleton.
#
# Require allows you to depend upon an implementation of a particular
# interface being present on the system.  The interface you wish to
# depend on is specified by a combination of the root name and the
# major.minor code.  The root name should not have a version number in
# it - it identifies a particular skeleton to use, not a package.  The
# best package to implement that interface will automatically be
# located and retrieved if it's not found, assuming the skeleton
# supports that.
#
# Optional arguments can be added to the end in any format, it's up to
# the skeleton file to figure out what to do with them, so refer to
# the notes for that skeleton.
#
# This function can only be called from inside prepare
# scripts. Calling it from any other context is an error.
#
# Example:
# require "@gnome.org/libxml" 1
# require "@gnome.org/libxml" 1.2 --with-xslt
#
function require() {

    if [[ $1 == "" ]] || [[ $2 == "" ]]; then
        err "require needs at least two arguments."
        return 1
    fi

    _requireCommons "require" $@
    return $?
}


##
# requireAtLeast <ROOTNAME> [SKELETON ARGUMENTS]
# ROOTNAME: versioned package root name.
# SKELETON ARGUMENTS: optional arguments that are passed to the package skeleton.
#
# requireAtLeast allows you to depend upon an implementation of a specific piece of software being
# present on the system. It works with the SOFTWARE_VERSIONS output of the skeleton file.
# <p>
#
# The root name should have a version number in it - any version equal
# to or higher than the version in the root name will satisfy this
# function.  <p>
#
# See require() for more information.
#
# Example:
# requireAtLeast "@gnome.org/libxml:1"
# requireAtLeast "@gnome.org/libxml:1.2" --with-xslt
function requireAtLeast() {

    if [[ `versionFromRootName $1` == "" ]]; then
        err "requireAtLeast needs the first argument to be a versioned rootname."
        return 1
    fi

    _requireCommons "requireAtLeast" "$@"
    return $?
}


##
# requireExact <ROOTNAME> [SKELETON ARGUMENTS]
# ROOTNAME: versioned package root name.
# SKELETON ARGUMENTS: option arguments that are passed to the package skeleton.
#
# This function will check for the version specified in the root name and will fail if that exact
# version is not found on the system. For instance, if you require version 1.2 exactly, and version
# 1.1 or 1.3 won't do, you can use this function. <p>
#
# See require() for more information.
#
# Example:
# requireExact "@gnome.org/libxml:1"
# requireExact "@gnome.org/libxml:1.2"
#
function requireExact() {

    if [[ `versionFromRootName $1` == "" ]]; then
        err "requireExact needs the first argument to be a versioned rootname."
        return 1
    fi

    _requireCommons "requireExact" "$@"
    return $?
}

##
# recommend <ROOTNAME> <INTERFACE-VERSION> [SKELETON ARGUMENTS]
# ROOTNAME: versioned or unversioned package root name.
# REQUIRED VERSION: MAJOR[.MINOR] formatted numbers to validate requirements on ROOTNAME's package.
# SKELETON ARGUMENTS: optional arguments that are passed to the package skeleton.
#
# Recommend allows you to check if an interface is present, and if not inform the user that this
# package would have enhanced functionality if that interface was present. It functions virtually
# identically to require() except that if the check fails, it will still return 0 (success). Rather
# than abort the script therefore, it will simply display the dependency as "recommended" in the UI.
# <p>
# For more information see require()
#
# Example:
# recommend "@gnome.org/libxml" 2
function recommend() {

    if [[ $1 == "" ]] || [[ $2 == "" ]]; then
        err "recommend needs at least two arguments."
        return 1
    fi

    _requireCommons "recommend" $@
    return $?

}

##
# recommendExact <ROOTNAME> [SKELETON ARGUMENTS]
# ROOTNAME: versioned package root name.
# SKELETON ARGUMENTS: option arguments that are passed to the package skeleton.
#
# See the documentation for requireExact() and recommend() to understand this function
function recommendExact() {

    if [[ `versionFromRootName $1` == "" ]]; then
        err "recommendExact needs the first argument to be a versioned rootname."
        return 1
    fi

    _requireCommons "recommendExact" "$@"
    return $?

}

##
# recommendAtLeast <ROOTNAME> [SKELETON ARGUMENTS]
# ROOTNAME: versioned package root name.
# SKELETON ARGUMENTS: optional arguments that are passed to the package skeleton.
#
# See recommend() and requireAtLeast() to understand this function.
function recommendAtLeast() {

    if [[ `versionFromRootName $1` == "" ]]; then
        err "recommendAtLeast needs the first argument to be a versioned rootname."
        return 1
    fi

    _requireCommons "recommendAtLeast" $@
    return $?

}



##
# requireFile <FILE-PATH>
# FILE-PATH: An absolute path to the file that is required
#
# This function should be called from prep scripts when you depend on a file
# existing in a well known location. Examples of where this is useful would be
# for scripts that have an absolute path in their shebang line.
# <p>
# This function outputs the test, and returns 1 if the file is not present. Otherwise it returns 0.
# This function will NOT attempt to resolve dependenices if the test fails, so be careful in its use.
#
# Example:
# requireFile /bin/bash
# requireFile /lib/libm.so.6
function requireFile() {
    pushOptE; set +e;
    outputTest "existance of $1"
    if [ -e "$1" ]; then
       outputTestPass;
       popOptE;
       return 0;
    else
       outputTestFail;
       popOptE;
       return 1;
    fi
}

##
# registerRepository <ROOTNAME> <URL>
# ROOTNAME: the unversioned root name of the package you wish to registry a repository for
# URL: the absolute URL of the XML repository description. In autopackage 1.0, this must be an HTTP URL
#
# Each package (identified by an unversioned root name) has a list of remote repositories that
# should be attempted in order, in the case that the package is not present and must be retrieved
# from the network. <p>
#
# A repository registration is scoped local to this autopackage session, ie once the
# install/removal/upgrade has finished, the registrations are lost. <p>
#
# When a package must be retrieved from the network, each repository in the list is tried in order
# from first registered to last registered. The first that provides a successful match is used. <p>
#
# If a skeleton includes a "Repository" key in its meta section, then this URL will registered just
# before the Retrieval section is run, so you will usually not need to use this function.  It's
# useful when for whatever reason you feel compelled to break the Golden Rule and have packaged the
# dependencies of your package yourself. Placing the URL to your collection in the skeleton file
# would be the wrong approach as the skeleton is supposed to be independent of any one persons
# particular packaging. Instead, you should register the dependencies in your packages prepare
# script so your repository will be tried first.
#
# Example:
# registerRepository @foo.org/bar http://myproject.sourceforge.net/myproject-packages.xml
# registerRepository @fee.org/fifofum http://myproject.sourceforge.net/myproject-packages.xml
function registerRepository() {
    trace "called with: $1, $2"

    # check arguments
    if [[ $( justRootName $1 ) == "$1" ]] && [[ "$2" == "" ]]; then
        err must have two arguments: ROOTNAME and REPOSITORY
        return 1
    fi

    local unversioned_root_name=`justRootName $1`

    if [ ! -d "$WORKING_DIRECTORY/meta/$unversioned_root_name" ]; then
        mkdirs --nolog "$WORKING_DIRECTORY/meta/$unversioned_root_name"
    fi

    echo "$2" >> "$WORKING_DIRECTORY/meta/$unversioned_root_name/repositories"

}
