#!/bin/bash
#
# $Id: sbopkg 836 2010-07-20 01:06:26Z slakmagik $
#
# sbopkg - The SlackBuilds.org Package Browser
# Copyright 2007-2010 Chess Griffin <chess@chessgriffin.com>
#
# Redistribution and use of this script, with or without modification, is
# permitted provided that the following conditions are met:
#
# 1. Redistributions of this script must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
#  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
#  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
#  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
#  NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
#  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
#  TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
#  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
#  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
#  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
#  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#  Slackware is a registered trademark of Patrick Volkerding.
#  Linux is a registered trademark of Linus Torvalds.
#
#  Other contributors:  Bob Lounsbury, Robby Workman, Alan Hicks, Paul
#  Wisehart, slakmagik, Eric Hameleers, Michiel van Wessem, hba, Erik Hanson,
#  Antoine, ktabic, Ken Roberts, samac, Bert Babington, Murat D. Kadirov,
#  The-spiki, David Somero, LukenShiro, Drew Ames, nille, acidchild, mancha,
#  macavity, Zordrak, João Felipe Santos, cotterochan, necropresto, Pierre
#  Cazenave, Mauro Giachero, The-Croupier, Wade Grant, TSquaredF, alkos333,
#  Marco Bonetti and Gregory Tourte.
#  This script would not be where it is without the help of these folks.
#  If I left anyone out, I apologize.  Thank you!
#
#set -x

dialog_refresh_workaround() {
    # Dialog has refresh problems on some terminals (currently known are some
    # rxvt-based terminals and screen sessions), preventing correct dialogs
    # rendering.
    # It turns out that forcing TERM=xterm-color "fixes" at least most of the
    # issues, so work around them this way.

    export TERM=xterm-color
}

crunch() {
    # The inspiration for this and the next function comes from the crunch()
    # in installpkg. Both take one argument.  This function reduces runs of
    # spaces to one.

    echo -e "$@" | tr -s ' '
}

crunch_fmt() {
    # This echo reduces runs of spaces to one and reformats to 78 columns

    echo -e "$@" | tr -s ' ' | fmt -78
}

split_pkg_name() {
    # This function takes a string in the Slackware format NAME-VER-ARCH-BUILD
    # (with an optional TAG) and splits the string into those respective
    # PKG_-prefixed variables. (foo-1.0-i486-1_bar results in 'foo' being
    # assigned to PKG_NAME, '1.0' being assigned to PKG_VER, 'i486' being
    # assigned to PKG_ARCH, '1' being assigned to PKG_BUILD, and '_bar' being
    # assigned to PKG_TAG. If the string has no tag, PKG_TAG will be an empty
    # string.

    local FILE=${1##*/}

    eval $(echo $FILE | sed '
        s/\(.*\)-\([^-]*\)-\([^-]*\)-\([0-9]*\)\(.*\)*/\
        PKG_NAME=\1 PKG_VER=\2 PKG_ARCH=\3 PKG_BUILD=\4 PKG_TAG=\5/
    ')
}

config_check() {
    # Check if config file is there and if so check that it has all
    # needed variables with any value, and set them.

    local MISSING VAR

    if [[ ! -d $SBOPKG_REPOS_D ]]; then
        echo "$SCRIPT: No $SBOPKG_REPOS_D was found." 1>&2
        echo "Please correct this error and run $SCRIPT again." 1>&2
        exit 1
    fi
    if [[ ! -d $SBOPKG_RENAMES_D ]]; then
        echo "$SCRIPT: No $SBOPKG_RENAMES_D was found." 1>&2
        echo "Please correct this error and run $SCRIPT again." 1>&2
        exit 1
    fi
    if [[ ! -e $SBOPKG_CONF && ! -e $HOME/.sbopkg.conf ]]; then
        echo "$SCRIPT: No $SBOPKG_CONF or ~/.sbopkg.conf was found." 1>&2
        echo "Please create at least one of them and run $SCRIPT again." 1>&2
        exit 1
    fi

    [[ -e $SBOPKG_CONF ]] && . $SBOPKG_CONF
    [[ -e $HOME/.sbopkg.conf ]] && . $HOME/.sbopkg.conf

    # Some configuration options are mandatory
    for VAR in REPO_ROOT LOGDIR QUEUEDIR SRCDIR SBOPKGTMP REPO_NAME \
        REPO_BRANCH KEEPLOG CLEANUP LOGFILE DEBUG TMP OUTPUT RSYNCFLAGS \
        WGETFLAGS DIFF DIFFOPTS SBOPKG_REPOS_D ALLOW_MULTI; do
        if [[ -z "${!VAR}" ]]; then
            MISSING+="$VAR "
        fi
    done
    if [[ "$MISSING" ]]; then
        cat << EOF

ERROR
$SCRIPT: Can't find a value for variable(s):
$MISSING

If you have recently upgraded sbopkg there may be
new variables in the sbopkg.conf file.  Please
merge the sbopkg.conf.new file with your existing
sbopkg.conf file.  Please see the sbopkg.conf(5)
man page for more details.

Please correct this error and run $SCRIPT again.

EOF
       exit 1
    fi
    if [[ $DEBUG -ne 0 && $DEBUG -ne 1 && $DEBUG -ne 2 ]]; then
        echo "The \$DEBUG variable must be set to 0, 1, or 2." 1>&2
        exit 1
    fi

    # Convert some YES/NO variables into 'set/unset' ones.
    yesno_to_setunset KEEPLOG
    yesno_to_setunset CLEANUP
    yesno_to_setunset ALLOW_MULTI
    yesno_to_setunset MKDIR_PROMPT

    # If multiple instances of sbopkg are allowed, they need their own
    # private $SBOPKGTMP.
    # Since simply appending the cookie makes sbopkg to inform the user about
    # the creation of the new $SBOPKGTMP directory on every startup, we do a
    # little more setup here.
    if [[ $ALLOW_MULTI ]]; then
        # Note: this ck_dir line is copied from directory_checks
        ck_dir $SBOPKGTMP \
            "Creating local sbopkg TMP directory $SBOPKGTMP."
        SBOPKGTMP+=/sbopkg-instance-$(mcookie)
        mkdir -p $SBOPKGTMP
    fi

    # Make sure there are no unexpected files in $SBOPKGTMP
    if [[ -n $(find $SBOPKGTMP -mindepth 1 -maxdepth 1 -not -name sbopkg\* \
            2> /dev/null) ]]; then
        cat << EOF

ERROR
$SCRIPT: Unexpected files found in working directory

$SCRIPT performs many file operations, including some 'rm -rf',
inside \$SBOPKGTMP, which is currently set to:
$SBOPKGTMP

As a safety measure to prevent user data loss due to a bad
program configuration, $SCRIPT will now exit. Please fix this
error by making sure that \$SBOPKGTMP refers to a directory
$SCRIPT can use freely, and then make sure it contains no
stale files.

If \$SBOPKGTMP is actually correct, and the only files it
contains are $SCRIPT-generated, please file a bug report.

EOF
        exit 1
    fi

    # Load the repositories data
    load_repositories || exit 1

    # Check for ncurses
    [[ -x /usr/bin/tput ]] && HAS_NCURSES=1
}

yesno_to_setunset() {
    # Convert a yes/no variable to a set/unset one.
    # $1 = variable name
    # If the variable value is different from "yes" and "no" (case
    # insensitive) spit an error message and exit.

    # Note: $(eval echo \$$1) is the value of the variable (remember that
    # $1 is the variable _name_).

    if [[ $(eval echo \$$1) == [Nn][Oo] ]]; then
        unset $1
    elif [[ $(eval echo \$$1) != [Yy][Ee][Ss] ]]; then
        cat <<EOF

ERROR
$SCRIPT: Unexpected value in $1

The configuration variable $1 is expected to be set to either YES or NO
(case insensitive). Its current value instead is $(eval echo \$$1).

Please fix this error by setting the appropriate value in
/etc/sbopkg/sbopkg.conf and/or in ~/.sbopkg.conf
and restart $SCRIPT.

EOF
        exit 1
    fi
}

load_repositories() {
    # Fill the REPOSITORIES array with the data from the .repo files

    local FILE LINE i
    local TMPARRAY
    local ERROR

    for FILE in $SBOPKG_REPOS_D/*.repo; do
        # Reading from $FILE...
        while read LINE; do
            grep -q '#' <<< "$LINE" && continue
            eval TMPARRAY=( "$LINE" )
            [[ ${#TMPARRAY[@]} -eq 0 ]] && continue;
            # Sanity checks
            # these two assignments work around a bash3-4 incompatibility
            local GPG='^GPG$|^$'
            local RSYNC='^rsync$|^git$|^$'
            [[ ! ${TMPARRAY[6]} =~ $GPG ]] && ERROR="gpg"
            [[ ! ${TMPARRAY[4]} =~ $RSYNC ]] && ERROR="tool"
            [[ ${#TMPARRAY[@]} -ne $REPOS_FIELDS ]] && ERROR="fields"
            [[ -n $ERROR ]] && break 2
            # Add the record to REPOSITORIES
            for i in ${!TMPARRAY[@]}; do
                REPOSITORIES[${#REPOSITORIES[@]}]="${TMPARRAY[$i]}"
            done
        done < $FILE
    done

    if [[ -n $ERROR ]]; then
            cat <<EOF
ERROR
$SCRIPT: Invalid repository descriptor

Line
  $LINE
of
  $FILE
EOF

        case $ERROR in
            'fields' )
                crunch_fmt "doesn't contain the right number of fields\
                    ($REPOS_FIELDS)."
                ;;
            'tool' )
                crunch_fmt "specifies an unknown fetching tool\
                    (${TMPARRAY[4]})."
                ;;
            'gpg' )
                crunch_fmt "specifies an unknown signature checker\
                    (${TMPARRAY[6]})."
                ;;
        esac
        return 1
    fi

    return 0
}

ck_dir() {
    # This function displays the directory-creation message and then creates
    # the missing directory.

    local DIR=$1
    local MSG="$2"
    local ERROR=0
    local JUNK

    # Try to create the specified directory
    if [[ ! -d $DIR ]]; then
        if [[ $MKDIR_PROMPT ]]; then
            echo
            crunch_fmt "$MSG"
            echo
            read -n1 -sep "Press any key to continue or Ctrl-C to exit."
            mkdir -p $DIR 2> /dev/null && echo "Directory created." || ERROR=1
        else
            mkdir -p $DIR 2> /dev/null || ERROR=1
        fi
    fi

    # Try to create a file in the specified folder
    if [[ $ERROR -eq 0 ]]; then
        (> $DIR/sbopkg-testfile 2> /dev/null && rm -f \
        $DIR/sbopkg-testfile 2> /dev/null) || ERROR=2
    fi

    case $ERROR in
        1 ) crunch_fmt "\\nWARNING:\
                \\n$SCRIPT: Unable to create $DIR.\
                \\n\\nMake sure you have enough privileges to create that\
                directory, or specify a different location in sbopkg.conf.\
                \\nSee the sbopkg.conf(5) man page for more details.\
                \\n\\nPress ENTER to continue anyway, CTRL-C to abort."
            read JUNK
            ;;
        2 ) crunch_fmt "\\nWARNING:\
                \\n$SCRIPT: Unable to create files in $DIR.\
                \\n\\nMake sure you have enough privileges to write in\
                that directory, or specify a different location in\
                sbopkg.conf.\
                \\nSee the sbopkg.conf(5) man page for more details.\
                \\n\\nPress ENTER to continue anyway, CTRL-C to abort."
            read JUNK
            ;;
    esac
}

directory_checks() {
    # Check and make sure certain sbopkg-related directories exist.
    # If not, create them.

    ck_dir $REPO_DIR \
        "Creating local repository directory $REPO_DIR for the \
            script mirror."
    ck_dir $LOGDIR \
        "Creating log directory $LOGDIR."
    ck_dir $QUEUEDIR \
        "Creating queues directory $QUEUEDIR."
    ck_dir $SRCDIR \
        "Creating local cache directory $SRCDIR to keep downloaded sources."
    ck_dir $SBOPKGTMP \
        "Creating local sbopkg TMP directory $SBOPKGTMP."
    # ensure basic perm/owner sanity - we'll do better soon.
    chown root:root $SBOPKGTMP
    chmod 700 $SBOPKGTMP
    rm -rf $SBOPKGTMP/*
    ck_dir $OUTPUT \
        "Creating local package output directory $OUTPUT."

    # Let's catch Control-C and try to exit cleanly.  Please see the
    # comment to the control_c function, below.
    trap 'control_c' 2 14 15
}

pid_check() {
    # Set and check for pid file.
    local PIDFILE OTHERPID

    PIDFILE=/var/run/sbopkg.pid
    if [[ -e $PIDFILE ]]; then
        # When things go haywire and sbopkg crashes (this happens only on
        # development versions, of course ;-)) the PIDFILE isn't deleted and
        # triggers the error below on the following run.  Perform a basic test
        # to reduce the amount of false positives. Note that no check on the
        # file name is performed, to avoid missing true positives in the
        # (rare, but possible) cases where the user renames the sbopkg script.

        OTHERPID=$(< $PIDFILE)
        if [[ -n $(ps h --pid $OTHERPID) ]]; then
            cat << EOF

ERROR
Another instance of sbopkg appears to be running
with process id $OTHERPID.  Running more than
one instance of sbopkg is not recommended.

If this is incorrect, you can delete the lockfile
'$PIDFILE' and restart.  Exiting now.
EOF
           exit 1
        fi
    fi
    cleanup
    echo $$ > $PIDFILE
}

check_write() {
    # Check to see whether the user has write permissions on the
    # directory.

    local DIR=$1

    if [[ ! -w $DIR ]]; then
        return 1
    else
        return 0
    fi
}

check_if_repo_exists() {
    # Check to see if $REPO_DIR exists and not empty

    if [[ ! -d $REPO_DIR ]]; then
        if [[ $DIAG ]]; then
            dialog --title "ERROR" --msgbox "$(crunch "The directory \
                $REPO_DIR was not found or is empty.  Please make \
                sure your repository directory is set correctly and that you \
                have done a sync first.")"  12 30
            continue
        else
            cat << EOF

ERROR
The directory $REPO_DIR was not found
or is empty.  Please make sure your respository
directory is set correctly and that you have done
a sync first.
EOF
            cleanup
            exit 1
        fi
    fi
}

show_changelog() {
    # Show the SlackBuilds.org changelog.

    check_if_repo_exists

    cd $REPO_DIR
    if [[ ! -r ./ChangeLog.txt ]]; then
        if [[ $DIAG ]]; then
            dialog --title "ERROR" --msgbox "$(crunch "ChangeLog.txt not \
                found or not readable.  Please make sure your repository \
                directory is set correctly and that you have done a sync \
                first.")" 10 30
            return
        else
            cat << EOF

ERROR
No ChangeLog.txt found.  Please make sure your
repository directory is set correctly and that
you have done a sync first.  Exiting.
EOF
            cleanup
            exit 1
        fi
    else
        if [[ $DIAG ]]; then
            dialog --title "$REPO_DESC ChangeLog.txt" \
                --textbox ./ChangeLog.txt 0 0
        else
            $PAGER ./ChangeLog.txt
        fi
    fi
}

get_sbo_packages() {
    # Get a list of SBo packages.

    local SBOPKGLIST=$SBOPKGTMP/sbopkg_pkglist
    local REMOVELIST=$SBOPKGTMP/sbopkg_removelist
    local CONFIRMLIST=$SBOPKGTMP/sbopkg_confirmlist

    rm -f $SBOPKGLIST
    find /var/log/packages -type f -name "*$REPO_TAG" \
        -printf "%P\n" 2> /dev/null | sort > $SBOPKGLIST
    if [[ -s $SBOPKGLIST ]]; then
        if [[ $DIAG ]]; then
            sed -i 's/$/ "" OFF/' $SBOPKGLIST
            dialog --separate-output --title "Installed $REPO_NAME Packages" --checklist \
                "Check any packages you wish to uninstall" 20 65 13 \
                --file $SBOPKGLIST 2> $REMOVELIST
            if [[ -s $REMOVELIST ]]; then
                sed 's/$/ "" ON/' $REMOVELIST > $CONFIRMLIST
                dialog --defaultno --title "Removepkg confirmation" \
                    --checklist "Remove the following packages?" 20 65 13 \
                    --file $CONFIRMLIST 2> $REMOVELIST
                if [[ $? == 0 ]]; then
                    removepkg $(tr -d \" < $REMOVELIST)
                    read -n1 -p "Press any key to continue."
                fi
            fi
        else
            $PAGER $SBOPKGLIST
        fi
    else
        if [[ $DIAG ]]; then
            dialog --title "No packages found" --msgbox "$(crunch_fmt "It \
                appears that you have no $REPO_NAME packages \
                installed.")" 8 40
        fi
    fi
    rm -f $SBOPKGLIST $REMOVELIST $CONFIRMLIST
}

progressbar_cli() {
    # This is a simple progressbar for CLI operations.
    # The code shows a bar filling like this:
    #      0%[    ]
    #     25%[=   ]
    #     50%[==  ]
    #     75%[=== ]
    #    100%[====]
    # This is meant to be an "almost drop-in" replacement for
    # "dialog --gauge". The percentage data is read from stdin and the bar
    # fills a screen line.
    #
    # If available, "tput" (part of ncurses) is used to determine the screen
    # width and to hide the cursor.

    local PROGRESS SCREENPROGRESS i
    local SCREENWIDTH BARWIDTH
    local BAR SPACES

    # Initial messages
    if [[ -n "$1" ]]; then
        echo "[ $1 ]"
    fi
    if [[ -n "$2" ]]; then
        crunch_fmt "$2"
    fi

    # Initialize the bar
    # Screen size
    if [[ $HAS_NCURSES ]]; then
        tput civis # Hide cursor
        SCREENWIDTH=$(tput cols)
    else
        SCREENWIDTH=80
    fi
    BARWIDTH=$(($SCREENWIDTH - 8))

    while read PROGRESS; do
        # Show the percentage
        printf "\r%3s%%[" $PROGRESS

        # Draw the bar
        SCREENPROGRESS=$(($BARWIDTH * $PROGRESS / 100))
        printf -vBAR "%${SCREENPROGRESS}s" ""
        printf -vSPACES "%$(($BARWIDTH - $SCREENPROGRESS))s" ""
        printf "%s%s]" "${BAR// /=}" "${SPACES// / }"
    done

    # Cleanup
    echo
    if [[ $HAS_NCURSES ]]; then
        tput cnorm # Restore cursor
    fi
}

progressbar() {
    # This is a simple progressbar gateway, which automatically chooses
    # between the "dialog" and the "cli" bars.

    local MSGTITLE="$1"
    local MSGTEXT="$2"

    if [[ $DIAG ]]; then
        local MESSAGE=$(crunch "$MSGTEXT")
        local MESSAGELINES=$(echo "$MESSAGE" | fmt -66 | wc -l)
        dialog --title "$MSGTITLE" --gauge "$MESSAGE" $(($MESSAGELINES + 5)) \
            70 0
    elif [[ ! $QUIET ]]; then
        progressbar_cli "$MSGTITLE" "$MSGTEXT"
    else
        cat > /dev/null
    fi
}

read_nonblock() {
    # This is a simple non-blocking read function reading a single
    # character (if available) from stdin and putting it in $1.

    local STTY_STATUS=$(stty --save)

    stty -icanon time 0 min 0 -echo
    read $1
    stty $STTY_STATUS
}

progressbar_interrupted() {
    # This function checks whether the user pressed ESC

    local ESC=$'\033' KEY

    read_nonblock KEY
    [[ "$KEY" = "$ESC" ]]
}

get_new_name() {
    # Return the new package name, as for sbopkg-renames.
    # If there isn't any new name, return the old name.
    # $1 = the variable where to put the new name
    # $2 = the old name

    local NEW_NAME_VAR="$1"
    local OLD_NAME="$2"
    local CANDIDATE=$(grep -hr "^$OLD_NAME=" $SBOPKG_RENAMES_D | head -n1)

    if [[ -z "$CANDIDATE" ]]; then
        # No rename occurred
        CANDIDATE="$OLD_NAME"
    else
        # The package got renamed
        CANDIDATE=$(cut -d= -f2 <<< "$CANDIDATE")
    fi
    eval $NEW_NAME_VAR="$CANDIDATE"
}

get_old_name() {
    # Return the old package name if installed, as for sbopkg-renames.
    # If there isn't any old named package installed, return the new name.
    # $1 = the variable where to put the old name
    # $2 = the new name

    local OLD_NAME_VAR="$1"
    local NEW_NAME="$2"
    local CANDIDATE INSTALLED
    local SUBSTITUTIONS=$(grep -hr "=$NEW_NAME\$" $SBOPKG_RENAMES_D | cut -d= -f1)

    # By default, the old name is the new name
    eval $OLD_NAME_VAR=$NEW_NAME
    # Set the old name to the first installed old-named package found.
    # Reading from $substitutions...
    while read CANDIDATE; do
        [[ -z "$CANDIDATE" ]] && continue
        INSTALLED=$(ls /var/log/packages |
            grep -x "$CANDIDATE-[^-]*-[^-]*-[^-]*")
        if [[ -n "$INSTALLED" ]]; then
            # Old-named installed package found, assume this is the correct
            # old name to return.
            eval $OLD_NAME_VAR=$CANDIDATE
            break
        fi
    done <<< "$SUBSTITUTIONS"
}

check_for_updates() {
    # This checks for updates to installed SBo packages.  Thanks to Mauro
    # Giachero for this much-improved update code and related functions!

    local TEMPFILE=$SBOPKGTMP/sbopkg_updates_tempfile
    local ERRORMSG=$SBOPKGTMP/sbopkg_updates_errormsg
    local NEWSB NEWINFO NEWVER
    local VERSION_EXPRESSION
    local UPDATELIST VERSION_FILE PROGRESSBAR_INTERRUPTED
    local OLDNAME PKG_NAME PKG_VER PKG_ARCH PKG_BUILD
    local VER_NUMERIC NEWVER_NUMERIC VER_NDIGITS NEWVER_NDIGIT UPDATED
    local CURPKG PKGS NUMPKGS PROGRESSCOUNTER=0

    if [[ -z $REPO_TOOL ]]; then
        if [[ $DIAG ]]; then
            dialog --title "ERROR" --msgbox \
                "You cannot check for updates when using the $REPO_DESC." 8 40
        else
            crunch_fmt \
                "You cannot check for updates when using the $REPO_DESC."
        fi
        return 1
    fi
    # Check to see if there are any updates to installed SBo pkgs.
    check_if_repo_exists
    UPDATELIST=$SBOPKGTMP/sbopkg_updatelist
    rm -f $UPDATELIST $ERRORMSG
    cd /var/log/packages
    PKGS=$(ls *$REPO_TAG 2> /dev/null)
    NUMPKGS=$(wc -w <<< "$PKGS")
    VERSION_FILE=$SBOPKGTMP/sbopkg-script-version
    PROGRESSBAR_INTERRUPTED=$SBOPKGTMP/sbopkg_progressbar-interrupted
    if [[ -z $PKGS ]]; then
        if [[ $DIAG ]]; then
            dialog --title "No packages found" --msgbox "$(crunch_fmt "It \
                appears that you have no $REPO_NAME packages \
                installed.")" 8 40
        else
            echo "It appears that you have no $REPO_NAME packages installed."
        fi
        return 1
    else
        crunch_fmt "Listing installed $REPO_DESC packages and flagging \
            potential updates..." >> $UPDATELIST
        echo >> $UPDATELIST
        { # Grouping for the progressbar
        echo 0 # Progressbar begin

        for CURPKG in $PKGS; do
            # Bail out if the user pressed ESC
            progressbar_interrupted && touch $PROGRESSBAR_INTERRUPTED && break

            # split CURPKG into its components
            split_pkg_name $CURPKG
            OLDNAME=$PKG_NAME

            # Manage package renames
            get_new_name NAME $OLDNAME

            # Find the current SlackBuild
            NEWSB=$(find $REPO_DIR -name $NAME.SlackBuild)
            if [[ -z $NEWSB ]]; then
                # Maybe we're running an old repository where the rename
                # didn't take place
                if [[ $NAME != $OLDNAME ]]; then
                    NAME=$OLDNAME
                    NEWSB=$(find $REPO_DIR -name $NAME.SlackBuild)
                fi
            fi

            # Extract the new package version
            if [[ ! -z $NEWSB ]]; then
                unset BUILD
                eval NEW$(grep -m1 ^ARCH= $NEWSB) 2>/dev/null
                if [[ $NEWARCH != "noarch" ]]; then
                   NEWARCH=$ARCH
                fi
                eval NEW$(grep -m1 ^BUILD= $NEWSB) 2>/dev/null
                [[ -z $NEWARCH ]] && NEWARCH=unknown

                # Step 1 - find the version expression
                # This looks for the last instance of $OUTPUT.
                # Note that part of the name can be returned by mistake,
                # typically for cases such as
                #    makepkg [...] $OUTPUT/$PRGNAM-something-$VERSION-$ARCH-\
                #     $BUILD$TAG
                # This is harmless, and the proper cleanup is performed in
                # Step 4.
                VERSION_EXPRESSION=$(tac $NEWSB | grep -m1 \$OUTPUT/ |
                    sed 's/[^$]*$\(.*\)$/\1/;
                        s/:-//;
                        s/^[^-]*-\(.*\)-[^-]*-[^-]*/\1/')
                    # Explanation of the above 'sed':
                    # - take from the first $ on (cuts "makepkg" with its
                    #   options, if present
                    # - drop ':-', which is not present in any 12.2 script
                    #   but is in every 13.0 script (${PKGTYPE:-tgz})
                    # - narrow the thing a bit deleting everything before
                    #   the first dash or after the penultimate one.
                echo "echo $VERSION_EXPRESSION" > $VERSION_FILE

                # Step 2 - find the used variables and their expressions
                # recursively. This fills the VERSION_FILE with the proper
                # variables assignments in reversed order (first dependant,
                # then dependencies)
                updates_resolve_expression "$VERSION_EXPRESSION"

                # Step 3 - reverse the file order
                # Because dependencies must be first...
                tac $VERSION_FILE > $TEMPFILE
                mv $TEMPFILE $VERSION_FILE

                # Step 4 - let's get the version number!
                # Also, strip any residual program name token.
                NEWVER=$(sh $VERSION_FILE 2> $ERRORMSG | sed 's/.*-//g')
                rm -f $VERSION_FILE

                # Step 5 - fixup braindead cases
                # Sometimes the above doesn't work -- see cpan2tgz for 12.1
                # In that case, let's trust the .info file...
                [[ -z $NEWVER ]] && echo "Empty version!" >> $ERRORMSG
                if [[ $(< $ERRORMSG) ]]; then
                    NEWINFO=$(sed 's/\.SlackBuild$/\.info/g' <<< "$NEWSB")
                    NEWVER=$(grep "^VERSION" $NEWINFO | cut -d\" -f2)
                fi

                # Compare the old $VER and the new $NEWVER
                VER_NUMERIC=$(tr -c "[:digit:]" " " <<< "$PKG_VER")
                NEWVER_NUMERIC=$(tr -c "[:digit:]" " " <<< "$NEWVER")
                # The version number must have the same number of digits
                VER_NDIGIT=$(wc -w <<< $VER_NUMERIC)
                NEWVER_NDIGIT=$(wc -w <<< $NEWVER_NUMERIC)
                while [[ $VER_NDIGIT -lt $NEWVER_NDIGIT ]]; do
                    VER_NUMERIC="$VER_NUMERIC 0"
                    ((VER_NDIGIT++))
                done
                while [[ $VER_NDIGIT -gt $NEWVER_NDIGIT ]]; do
                    NEWVER_NUMERIC="$NEWVER_NUMERIC 0"
                    ((NEWVER_NDIGIT++))
                done
                # The build number is just like the least significant version
                # number
                VER_NUMERIC="$VER_NUMERIC $(tr -c "[:digit:]" ' ' \
                    <<< "$PKG_BUILD")"
                NEWVER_NUMERIC="$NEWVER_NUMERIC $(tr -c "[:digit:]" ' ' \
                    <<< "$NEWBUILD")"
                UPDATED=$(updates_compare_versions $VER_NUMERIC \
                    $NEWVER_NUMERIC)

                if [[ $UPDATED -eq 1 ]]; then
                    echo $NAME: >> $UPDATELIST
                    echo "  POTENTIAL UPDATE" >> $UPDATELIST
                    echo "  Installed version: " $CURPKG >> $UPDATELIST
                    echo "  Repo version: " \
                        $NAME-$NEWVER-$NEWARCH-${NEWBUILD}$REPO_TAG \
                        >> $UPDATELIST
                    echo "$NAME" >> $SBOPKGTMP/sbopkg-update-queue
                elif [[ $UPDATED -eq -1 ]]; then
                    echo $NAME: >> $UPDATELIST
                    echo "  INSTALLED PACKAGE IS NEWER THAN REPO" \
                        >> $UPDATELIST
                    echo "  Installed version: " $CURPKG >> $UPDATELIST
                    echo "  Repo version: " \
                        $NAME-$NEWVER-$NEWARCH-${NEWBUILD}$REPO_TAG \
                        >> $UPDATELIST
                    echo "-$NAME" >> $SBOPKGTMP/sbopkg-update-queue
                elif [[ $PKG_VER != $NEWVER ]]; then
                    echo $NAME: >> $UPDATELIST
                    echo "  UNCLASSIFIED VERSION CHANGE" \
                        >> $UPDATELIST
                    echo "  Installed version: " $CURPKG >> $UPDATELIST
                    echo "  Repo version: " \
                        $NAME-$NEWVER-$NEWARCH-${NEWBUILD}$REPO_TAG \
                        >> $UPDATELIST
                    echo "-$NAME" >> $SBOPKGTMP/sbopkg-update-queue
                else
                    if [[ $DEBUG -eq 2 ]]; then
                        echo $NAME: >> $UPDATELIST
                        echo "  No update." >> $UPDATELIST
                    fi
                fi
                if [[ $(< $ERRORMSG) ]]; then
                    echo "  Note: repo version not obtainable by" \
                        "standard method, may be inaccurate." >> $UPDATELIST
                fi
            else
                if [[ $DEBUG -ge 1 ]]; then
                    echo $NAME: >> $UPDATELIST
                    echo "  Not in the repository." >> $UPDATELIST
                fi
            fi
            rm -f $ERRORMSG

            # Progress indicator, for the progressbar
            (( PROGRESSCOUNTER += 1 ))
            echo $(($PROGRESSCOUNTER * 100 / $NUMPKGS))
        done
        } | progressbar "Checking for potential updates" "This may take\
            a few moments.  Press <ESC> to abort."
        echo >> $UPDATELIST
        echo "Potential update list complete." >> $UPDATELIST
    fi
    if [[ ! -f $PROGRESSBAR_INTERRUPTED ]]; then
        if [[ $DIAG ]]; then
            dialog --title "Viewing potential updates" --textbox $UPDATELIST \
                0 0
        else
            cat $UPDATELIST
        fi
        # Permanent log of the updatelist is saved when DEBUG is enabled.
        if [[ $DEBUG -ge 1 ]]; then
            cp $UPDATELIST $SBOPKGTMP/sbopkg-debug-updatelist
        else
            rm $UPDATELIST
        fi
    else
        rm -f $PROGRESSBAR_INTERRUPTED
    fi
}

updates_resolve_expression() {
    # Find the used variables and their expressions recursively
    # Variables == any string made up by letters, digits and underscore
    # This criteria may have false positives, which don't matter since
    # these aren't assigned to in the SlackBuild.
    # 1st parameter == expression (right hand side of FOO=BAR)

    local EXPRESSION_VARIABLES=$(echo $1 | tr -c "[:alnum:]_" " ")
    local VAR
    local ASSIGNMENT

    for VAR in $EXPRESSION_VARIABLES; do
            ASSIGNMENT=$(tac $NEWSB | grep "^$VAR=")
            if [[ ! -z "$ASSIGNMENT" ]] && ! grep -q "^$VAR=" \
                    $VERSION_FILE; then
                echo "$ASSIGNMENT" >> $VERSION_FILE
                updates_resolve_expression "$(cut -d= -f2- <<< "$ASSIGNMENT")"
            fi
    done
}

updates_compare_versions() {
    # Compare numeric versions
    # Takes 2N arguments, where N is the number of numbers (...)
    # composing the version number.
    # E.g. if the two packages are of version 1.2.3 build 7 and
    # 1.2.50 build 4, the argument list is
    # 1 2 3 7 1 2 50 4
    # Prints -1 if the "left" package is newer (not an update), 0 if
    # the version is unchanged, 1 if the "left" package is newer.

    local COUNT=$(($# / 2))
    local i RESULT=0
    local LEFT RIGHT

    for ((i=1; i<=$COUNT; i++)); do
        eval LEFT=\$$i
        eval RIGHT=\${$(($i + $COUNT))}
        if [[ 10#$LEFT -lt 10#$RIGHT ]]; then
            RESULT=1
            break
        elif [[ 10#$LEFT -gt 10#$RIGHT ]]; then
            RESULT=-1
            break
        fi
    done
    echo $RESULT
}

get_category_list() {
    # This function displays the list of SBo categories in the dialog.

    local DIR CAT

    check_if_repo_exists
    cd $REPO_DIR
    rm -f $SBOPKGTMP/sbopkg_category_list
    DIR=( */ )
    if [[ -n $DIR ]]; then
        for CAT in ${DIR[*]%/}; do
            echo "$CAT \"Browse the $CAT category\"" >> \
                $SBOPKGTMP/sbopkg_category_list
        done
    fi
}

checkout_rsync_branch() {
    # This function makes sure that in $REPO_DIR there's the currently
    # selected branch.
    # This is the implementation for rsync repositories, where branches are
    # simple subdirectories

    REPO_DIR=$REPO_DIR/$REPO_BRANCH
}

checkout_git_branch() {
    # This function makes sure that in $REPO_DIR there's the currently
    # selected branch.
    # This is the implementation for git repositories, which is relatively
    # complex because we have to manage local changes.

    local CURRENT_BRANCH NEW_BRANCH NEW_STASH
    local COMMAND_OUTPUT=$SBOPKGTMP/sbopkg_git_checkout_output

    rm -f $COMMAND_OUTPUT

    # Make sure the repository is there
    [[ -d $REPO_DIR/.git ]] || return 0

    # No need to checkout if the right branch is already there
    NEW_BRANCH=$(cut -d@ -f2 <<< $REPO_LINK)
    cd $REPO_DIR
    CURRENT_BRANCH=$(git branch | grep '^*')
    CURRENT_BRANCH=${CURRENT_BRANCH//'* '}
    [[ $CURRENT_BRANCH = $NEW_BRANCH ]] && return 0

    # So we need to checkout the branch. First off, let's clean the tree
    echo "*/*/*.sbopkg" > .gitignore
    git clean -d -f > $COMMAND_OUTPUT
    git reset --hard HEAD >> $COMMAND_OUTPUT

    # Let's save the current user customizations to a stash
    git add . >> $COMMAND_OUTPUT
    git stash save sbopkg-auto-$CURRENT_BRANCH >> $COMMAND_OUTPUT

    # Checkout the user-requested branch
    git checkout $NEW_BRANCH >> $COMMAND_OUTPUT 2>&1

    # Pop the stash of this branch, if any
    NEW_STASH=$(git stash list | grep ": sbopkg-auto-$NEW_BRANCH\$" |
        cut -d: -f1)
    if [[ -n $NEW_STASH ]]; then
        git stash pop $NEW_STASH >> $COMMAND_OUTPUT
        # Make sure no changes are staged
        git reset HEAD >> $COMMAND_OUTPUT
    fi

    # Create a changelog
    # (it makes no sense to have one tracked in a git repo)
    if [[ ! -f ChangeLog.txt ]]; then
        git log --pretty=format:"%cd%n%s%n%b" > ChangeLog.txt
    fi
}

set_repo_vars() {
    # Set REPO_{DESC,TAG,TOOL,LINK,DIR} according to $REPO_{NAME,BRANCH}.
    # Returns nonzero if no match is found.

    local i

    # Make sure we don't return old values with an invalid input
    unset REPO_DESC REPO_TAG REPO_TOOL REPO_LINK REPO_DIR REPO_GPG

    for ((i=0; i<${#REPOSITORIES[@]}; i+=$REPOS_FIELDS)); do
        if [[ ( ${REPOSITORIES[$i]} = $REPO_NAME || $REPO_NAME = "" ) &&
                ${REPOSITORIES[$((i + 1))]} = $REPO_BRANCH ]]; then
            REPO_NAME=${REPOSITORIES[i]}
            REPO_DESC=${REPOSITORIES[$((i + 2))]}
            REPO_TAG=${REPOSITORIES[$((i + 3))]}
            REPO_TOOL=${REPOSITORIES[$((i + 4))]}
            REPO_LINK=${REPOSITORIES[$((i + 5))]}
            REPO_GPG=${REPOSITORIES[$((i + 6))]}
            REPO_DIR=$REPO_ROOT/$REPO_NAME

            if [[ $REPO_TOOL = "rsync" || $REPO_TOOL = "git" ]]; then
                checkout_${REPO_TOOL}_branch
            fi

            # If the user specified a custom tag, use that one instead.
            [[ -n $TAG ]] && REPO_TAG=$TAG

            return 0
        fi
    done

    # Repository/branch not found
    return 1
}

list_repos() {
    echo "Valid options are:" >&2
    for ((i=0; i<${#REPOSITORIES[@]}; i+=$REPOS_FIELDS)); do
        echo -en "${REPOSITORIES[$i]}/${REPOSITORIES[(($i + 1))]}\\t" >&2
        echo "(${REPOSITORIES[(($i + 2))]})" >&2
    done
}

select_repository() {
    # Create menu and list the sbopkg-supported repositories for
    # user to choose from.

    while :; do
        eval dialog --cancel-label "Back" --title \
            '"Choose a repository"' \
            --menu '"$(crunch "You are currently using sbopkg to browse a \
            local copy of the $REPO_DESC.  If you would \
            like to change it, please select another repository below or \
            press <Back> to go back.")"' 19 60 9 \
            $(
                for ((i=0; i<${#REPOSITORIES[@]}; i+=$REPOS_FIELDS)); do
                    echo \"${REPOSITORIES[$((i+1))]} \(${REPOSITORIES[$i]}\)\"
                    echo \"${REPOSITORIES[$((i+2))]}\"
                done
            ) 2> $SBOPKGTMP/sbopkg_version_selection
        if [[ $? != 0 ]]; then
            break
        fi
        eval $(sed 's:^\(.*\) (\(.*\))$:REPO_NAME=\2;REPO_BRANCH=\1:g' \
            < $SBOPKGTMP/sbopkg_version_selection)
        set_repo_vars
        dialog --title "Save this setting?" --defaultno --yesno \
            "$(crunch "Would you like to save this repository setting \
            in the user's $HOME/.sbopkg.conf file? (One will be created if \
            it is not found).\n\nPress <Yes> to save in the user's \
            $HOME/.sbopkg.conf or press <No> to continue without saving, \
            making this a temporary change only.")" 12 60
        if [[ $? != 0 ]]; then
            break
        fi
        if [[ -e $HOME/.sbopkg.conf ]]; then
            sed -i '/^REPO_NAME=.*$/d' $HOME/.sbopkg.conf
            sed -i '/^REPO_BRANCH=.*$/d' $HOME/.sbopkg.conf
        fi
        echo "REPO_NAME=$REPO_NAME" >> $HOME/.sbopkg.conf
        echo "REPO_BRANCH=$REPO_BRANCH" >> $HOME/.sbopkg.conf
        break
    done
    rm -f $SBOPKGTMP/sbopkg_version_selection
}

app_files_chooser() {
    # List the files of a directory, and view the selected ones.
    # This function takes a single argument (the directory whose files are to
    # be listed).

    local DIR=$1
    local DEFAULTITEM
    local AFS=$SBOPKGTMP/sbopkg_app_files_selection
    local AFM=$SBOPKGTMP/sbopkg_app_files_menu
    local TITLE="${DIR##*/} files"

    while :; do
        find $DIR -type f -printf "\"%P\" \"\"\n" | sort > $AFM
        dialog --ok-label "View" --cancel-label "Back" --title "$TITLE" \
            --default-item "$DEFAULTITEM" --menu "$(crunch "Please choose \
            the file you would like to view or press <Back> to go back.")"\
            15 45 7 --file $AFM 2> $AFS
        if [[ $? != 0 ]]; then
            rm -f $AFS $AFM
            return
        fi
        DEFAULTITEM=$(< $AFS)

        view_app_file $DIR "$DEFAULTITEM"
    done
}

view_app_file() {
    # Decode and view the file $2 in the directory $1

    local DIR=$1
    local FILE="$2"
    local PLAIN
    local AFSP=$SBOPKGTMP/sbopkg_app_files_selection_parsed

    cd $DIR
    case $FILE in
        slack-desc )
            sed -n "/^${DIR##*/}: */s///p" slack-desc > $AFSP ;;
        *tar.gz | *tar.bz2 | *t?z )
            tar tvf $FILE > $AFSP ;;
        *gz ) zcat $FILE > $AFSP ;;
        *bz2 ) bzcat $FILE > $AFSP ;;
        * ) PLAIN=yes ;;
    esac
    if [[ "$PLAIN" == yes ]]; then
        dialog --exit-label "OK" --title "$FILE" --textbox "$FILE" 0 0
    else
        dialog --exit-label "OK" --title "Parsed contents of $FILE" \
            --textbox "$AFSP" 0 0
    fi
    rm -f $AFSP
}

info_item() {
    # This function shows the menu for each package where the user can see
    # certain information or build the package.
    # Returns 0 unless the user asked to jump back to the main menu.

    local OLDPKG CATEGORY SHORTPATH CURVERSION CURARCH CURBUILD
    local CURAPP OUTPUTFILES
    local DEFAULTITEM
    local CURPACKAGE INSTALLEDPACKAGE MENUPACKAGE TITLEPACKAGE
    local CHOICE PARSED_SLACK_DESC CHKRETVAL
    local APP="$(< $SBOPKGTMP/sbopkg_item_selection)"
    local RETVAL=0

    # We need to check and see if the APP has ever been renamed.
    get_old_name OLDPKG $APP

    CATEGORY=$(< $SBOPKGTMP/sbopkg_category_selection)
    SHORTPATH=$REPO_DIR/$CATEGORY/$APP
    CURVERSION=$(grep VERSION $SHORTPATH/$APP.info |
        cut -d= -f2 | sed s/\"//g)
    eval CUR$(grep -m1 ^ARCH= $SHORTPATH/$APP.SlackBuild) 2>/dev/null
    if [[ $CURARCH != "noarch" ]]; then
        CURARCH=$ARCH
    fi
    eval CUR$(grep -m1 ^BUILD= $SHORTPATH/$APP.SlackBuild)
    [[ -z $CURARCH ]] && CURARCH=unknown
    while :; do
        # we use GNU grep extensions rather than egrep to avoid issues with
        # the '+' metacharacter which can be found in package names
        INSTALLEDPACKAGE=$(ls /var/log/packages |
            grep "^\($APP\|$OLDPKG\)-[^-]*-[^-]*-[^-]*\$")
        # Only get the first package (not that the same package should be
        # installed more than once on a sane system...)
        INSTALLEDPACKAGE=$(head -n 1 <<< "$INSTALLEDPACKAGE")
        # Find the available package to install, if any, using several
        # "strictness levels" (to pick the right one if available, but falling
        # back to "less right" alternatives when appropriate)
        OUTPUTFILES=$(ls -1 $OUTPUT)
        CURPACKAGE=$( \
            grep "^$APP-$CURVERSION-$CURARCH-$CURBUILD$REPO_TAG\\.t.z\$" \
            <<< "$OUTPUTFILES")
        [[ -z $CURPACKAGE ]] && CURPACKAGE=$( \
            grep "^$APP-$CURVERSION-[^-]*-$CURBUILD$REPO_TAG\\.t.z\$" \
            <<< "$OUTPUTFILES")
        [[ -z $CURPACKAGE ]] && CURPACKAGE=$( \
            grep "^$APP-$CURVERSION-[^-]*-[^-]*$REPO_TAG\\.t.z\$" \
            <<< "$OUTPUTFILES")
        [[ -z $CURPACKAGE ]] && CURPACKAGE=$( \
            grep "^$APP-[^-]*-[^-]*-[^-]*$REPO_TAG\\.t.z\$" \
            <<< "$OUTPUTFILES")

        if [[ -z $CURPACKAGE ]]; then
            unset MENUPACKAGE
        else
            CURPACKAGE=$(tail -n 1 <<< "$CURPACKAGE")
            MENUPACKAGE="Install $CURPACKAGE"
        fi
        if [[ -z $INSTALLEDPACKAGE ]]; then
            TITLEPACKAGE="$APP (Not Installed)"
        else
            TITLEPACKAGE="$APP (Installed: $INSTALLEDPACKAGE)"
        fi
        dialog --default-item "$DEFAULTITEM" \
            --title "$APP ($CURVERSION-$CURARCH-$CURBUILD$REPO_TAG)" \
            --backtitle "$TITLEPACKAGE" --extra-button --extra-label "Back" \
            --cancel-label "Main Menu" --menu \
            "$(crunch "Please choose an item or press <Back> to go back \
            or press <Main Menu> to return to the main menu.\n")" \
            20 62 12 \
            "README" "View the README file" \
            "Info" "View the .info file" \
            "SlackBuild" "View the SlackBuild file" \
            "More Files" "Choose any file to display" \
            "Custom" "Customize the .info or SlackBuild" \
            "Remove" "Remove $APP sources in cache" \
            "Options" "Edit Build Options/Flavors" \
            "Check GPG" "Check the GPG signature of the $REPO_NAME tarball" \
            "Extract" "Re-extract the $REPO_NAME tarball" \
            "Queue" "Add $APP to build queue" \
            "Build" "Build a package for $APP" \
            $MENUPACKAGE \
            2> $SBOPKGTMP/sbopkg_info_selection
        case $? in
            1 ) # Return to Main Menu
                RETVAL=1; break ;;
            3 ) # Back
                break ;;
            0 ) # OK
                DEFAULTITEM="$(< $SBOPKGTMP/sbopkg_info_selection)"
                CATEGORY="$(< $SBOPKGTMP/sbopkg_category_selection)"
                case $DEFAULTITEM in
                    README )
                        view_app_file $SHORTPATH README
                        ;;
                    Info )
                        view_app_file $SHORTPATH $APP.info
                        ;;
                    SlackBuild )
                        view_app_file $SHORTPATH $APP.SlackBuild
                        ;;
                    "More Files" ) app_files_chooser $SHORTPATH ;;
                    Custom )
                        if [[ ! -z $REPO_GPG ]]; then
                            check_gpg $SHORTPATH
                            if [[ $? == 1 ]]; then
                                RETVAL=1
                                break
                            fi
                            extract_tarball $SHORTPATH $REPO_DIR/$CATEGORY
                            if [[ $? == 1 ]]; then
                                RETVAL=1
                                break
                            fi
                        fi
                        customize_item
                        ;;
                    Remove ) remove_sources_for_app $SHORTPATH/$APP.info ;;
                    Options ) add_options $APP ;;
                    "Check GPG" )
                        if [[ ! -z $REPO_GPG ]]; then
                            check_gpg $SHORTPATH
                            CHKRETVAL=$?
                            if [[ $CHKRETVAL == 0 ]]; then
                                dialog --title "OK" --msgbox "GPG check passed." 6 25
                            elif [[ $CHKRETVAL == 1 ]]; then
                                RETVAL=1
                                break
                            fi
                        else
                            dialog --title "ERROR" --msgbox "$(crunch "GPG \
                                checks are not enabled for the $REPO_NAME \
                                repository.")" 8 30
                        fi
                        ;;
                    Extract )
                        if [[ ! -z $REPO_GPG ]]; then
                            extract_tarball $SHORTPATH $REPO_DIR/$CATEGORY
                            if [[ $? == 0 ]]; then
                                dialog --title "Done" --msgbox "$(crunch "The \
                                    tarball has been extracted.")" 8 30
                            else
                                RETVAL=1
                                break
                            fi
                        else
                            dialog --title "ERROR" --msgbox "$(crunch "GPG \
                                tarballs are not available for the $REPO_NAME \
                                repository.")" 8 30
                        fi
                        ;;
                    Queue ) add_item_to_queue $APP ;;
                    Build )
                        echo "$APP" > $SBOPKGTMP/sbopkg-start-queue
                        start_dialog_queue ;;
                    Install )
                        if [[ ! -e $OUTPUT/$CURPACKAGE ]]; then
                            continue;
                        fi
                        install_package $OUTPUT $CURPACKAGE | tee $TMPLOG
                        read -n 1 -p "Press any key to continue."
                        if [[ $KEEPLOG ]]; then
                            cat $TMPLOG >> $LOGFILE
                        fi
                        rm $TMPLOG
                        ;;
                esac
                ;;
            * ) # ESC
                break ;;
        esac
    done

    rm -f $SBOPKGTMP/sbopkg_info_selection
    return $RETVAL
}

extract_tarball() {
    # Re-extract the $APP tarball on top of local directory.  Can be used
    # if tarball fails GPG check.
    local DELPKG=$1
    local DESTINATION=$2
    local DELNAME=$(basename $DELPKG)

    if [[ ! -e $DELPKG.tar.gz ]]; then
        if [[ $DIAG ]]; then
            dialog --title "Error" --msgbox "$(crunch "No $REPO_NAME \
                $DELNAME tarball found.")" 8 40
        else
            crunch_fmt "ERROR:  No $REPO_NAME $DELNAME tarball found."
        fi
        return 1
    fi
    tar -C $DESTINATION -zxf $DELPKG.tar.gz
    return 0
}

check_gpg() {
    # Check the .asc signature of the tarball from info_item menu
    local CHKPKG=$1
    local GPGNAME=$(basename $CHKPKG)

    if [[ ! -e $CHKPKG.tar.gz ]]; then
        dialog --title "GPG check error" --msgbox "$(crunch "No $REPO_NAME \
            $GPGNAME tarball found.")" 8 40
        return 1
    fi
    if ! gpg --verify $CHKPKG.*.asc > /dev/null 2>&1; then
        dialog --title "WARNING" --yesno "$(crunch "GPG CHECK FAILED!\n\n \
            Would you like to delete the $GPGNAME directory and tarball \
            so you can perform a new sync?  If so, all local changes to \
            the files in the $GPGNAME directory will be lost and you will \
            be returned to the main menu.  Press <Yes> to delete or <No> \
            to skip.")" 0 0
            if [[ $? == 0 ]]; then
                rm -rf $CHKPKG; rm $CHKPKG.*
                dialog --title "Done" --msgbox \
                    "The directory and tarball have been deleted." 8 30
                return 1
            fi
        return 2
    else
        return 0
    fi
}

customize_item() {
    # This function shows the menu for customizing the SlackBuild
    # and .info file.

    local DEFAULTITEM

    while :; do
    dialog --default-item "$DEFAULTITEM" --title "$APP Customization" \
        --cancel-label "Back" --menu \
        "Please choose an item or press <Back> to go back.\n" 13 75 6 \
        "Edit SlackBuild" "Create and edit a local copy of the SlackBuild" \
        "Delete SlackBuild" "Delete the local copy of the SlackBuild" \
        "Diff SlackBuild" "Compare the local and the original SlackBuild" \
        "Edit Info" "Create and edit a local copy of the .info file" \
        "Delete Info" "Delete the local copy of the .info file" \
        "Diff Info" "Compare the local and the original .info file" \
        2> $SBOPKGTMP/sbopkg_custom_selection
    if [[ $? = 0 ]]; then
        DEFAULTITEM="$(< $SBOPKGTMP/sbopkg_custom_selection)"
        case $DEFAULTITEM in
            "Edit SlackBuild" )
                edit_local_file SlackBuild $SHORTPATH $APP
                ;;
            "Delete SlackBuild" )
                delete_local_file SlackBuild $SHORTPATH $APP
                ;;
            "Diff SlackBuild" )
                diff_local_file SlackBuild $SHORTPATH $APP
                ;;
            "Edit Info" )
                edit_local_file info $SHORTPATH $APP
                ;;
            "Delete Info" )
                delete_local_file info $SHORTPATH $APP
                ;;
            "Diff Info" )
                diff_local_file info $SHORTPATH $APP
                ;;
        esac
    else # Cancel or ESC
        rm -f $SBOPKGTMP/sbopkg_custom_selection
        break
    fi
    done
}

browse_categories() {
    # This function iterates through the category list until one is
    # chosen.

    local DEFAULTITEM

    if [[ -z $(ls -A $REPO_DIR 2> /dev/null) ]]; then
        if [[ $DIAG ]]; then
            dialog --title "ERROR" --msgbox "$(crunch "Repository seems to \
                be empty. Please make sure your repository directory is set \
                correctly and that you have done a sync first.")" 10 30
            continue
        fi
    fi
    get_category_list
    while :; do
        dialog --default-item "$DEFAULTITEM" --cancel-label "Back" \
            --title "Choose a category" --backtitle \
            "$(eval echo $BACKTITLE)" \
            --menu "Please select a category or press <Back> to go back." \
            23 70 15 --file $SBOPKGTMP/sbopkg_category_list \
            2> $SBOPKGTMP/sbopkg_category_manual_selection
        if [[ $? != 0 ]]; then
            break
        fi
        DEFAULTITEM=$(< $SBOPKGTMP/sbopkg_category_manual_selection)
        gen_search_package '*' $DEFAULTITEM || break
    done
    rm -f $SBOPKGTMP/sbopkg_category_manual_selection
    rm -f $SBOPKGTMP/sbopkg_category_list
}

view_cache_dir() {
    # This function displays the contents of $SRCDIR.

    ls -A $SRCDIR | sed "s/^\(.*\)$/\"\\1\"/g" \
        > $SBOPKGTMP/sbopkg_app_sources
    remove_files $SRCDIR "sources" $SBOPKGTMP/sbopkg_app_sources OFF
}

view_perm_log() {
    # This function displays the contents of the permanent build log,
    # which is kept if KEEPLOG is set to YES in the config file.

    local VAR_NOTICE HEIGHT

    if [[ ! $KEEPLOG ]]; then
        dialog --title "NOTICE" --msgbox "$(crunch "To use this feature, \
            please make sure KEEPLOG is set to YES in the configuration \
            file.")"  10 30
        return 0
    else
        if [[ ! -e $LOGFILE ]]; then
            dialog --title "NOTICE" --msgbox "$(crunch "No permanent log \
                found.")" 5 30
        return 0
        else
            dialog --title "Displaying $LOGFILE" --textbox $LOGFILE 0 0
            dialog --title "Keep Log?" --yes-label "Keep" \
                --no-label "Delete" --yesno "$(crunch "Would you like to \
                keep the permanent build log $LOGFILE?")" 6 50
            if [[ $? == 1 ]]; then
                rm -f $LOGFILE
                dialog --title "Done" --msgbox \
                    "The build log has been deleted." 8 30
                continue
            fi
        fi
    fi
}

empty_queue() {
    # This function tests whether the temporary build queue is empty.

    if [[ ! -e $TMPQUEUE ]]; then
        if [[ $DIAG ]]; then
            dialog --title "Empty Queue" --msgbox \
                "The build queue is empty." 8 30
        else
            echo "The build queue is empty."
        fi
        return 0
    else
        return 1
    fi
  }

sort_queue() {
    # This function sorts the build queue in $TMPQUEUE.

    local PARTIALSORT=$SBOPKGTMP/sbopkg_sort_tempfile
    local TMPSORTQUEUE=$SBOPKGTMP/sbopkg-tmp-sort-queue
    local CHOICE
    local SELECTED
    local DEFAULTITEM

    empty_queue && return
    local PKGSCOUNT=$(wc -l < $TMPQUEUE)
    cp $TMPQUEUE $TMPSORTQUEUE
    while :; do
        dialog --title "Sort Queue" --ok-label "Up" \
            --extra-button --extra-label "Down" \
            --cancel-label "OK" \
            --help-button --help-label "Reverse" \
            --default-item "$DEFAULTITEM" \
            --menu "$(crunch "Use the <Up/Down> buttons to sort the queue \
            items, press <Reverse> to reverse the queue items, press \
            <OK> when done, or press <ESC> to abort \
            changes.")" 30 50 14 \
            $(nl $TMPSORTQUEUE | sed 's:^ *\([0-9]* *[^ ]*\) .*$:\1:') \
            2> $SBOPKGTMP/sbopkg-ans-sort
        CHOICE=$?
        SELECTED=$(< $SBOPKGTMP/sbopkg-ans-sort)
        DEFAULTITEM=$SELECTED
        case $CHOICE in
            0 ) # Up
                if [[ $SELECTED -eq 1 ]]; then
                    continue
                fi
                head -n $(($SELECTED-2)) $TMPSORTQUEUE > $PARTIALSORT
                head -n $(($SELECTED)) $TMPSORTQUEUE |
                    tail -n 1 >> $PARTIALSORT
                head -n $(($SELECTED-1)) $TMPSORTQUEUE |
                    tail -n 1 >> $PARTIALSORT
                tail -n $(($PKGSCOUNT-$SELECTED)) $TMPSORTQUEUE >> \
                    $PARTIALSORT
                mv $PARTIALSORT $TMPSORTQUEUE
                DEFAULTITEM=$(($SELECTED-1))
                continue
                ;;
            1 ) # OK
                mv $TMPSORTQUEUE $TMPQUEUE
                break
                ;;
            3 ) # Down
                if [[ $SELECTED -eq $PKGSCOUNT ]]; then
                    continue
                fi
                head -n $(($SELECTED-1)) $TMPSORTQUEUE > $PARTIALSORT
                head -n $(($SELECTED+1)) $TMPSORTQUEUE |
                    tail -n 1 >> $PARTIALSORT
                head -n $(($SELECTED)) $TMPSORTQUEUE |
                    tail -n 1 >> $PARTIALSORT
                tail -n $(($PKGSCOUNT-$SELECTED-1)) $TMPSORTQUEUE >> \
                    $PARTIALSORT
                mv $PARTIALSORT $TMPSORTQUEUE
                DEFAULTITEM=$(($SELECTED+1))
                continue
                ;;
            2 ) tac $TMPSORTQUEUE > $PARTIALSORT
                mv $PARTIALSORT $TMPSORTQUEUE
                continue
                ;;
            * ) # Cancel or ESC
                rm -f $TMPSORTQUEUE
                break
                ;;
        esac
    done
    rm -f $SBOPKGTMP/sbopkg-ans-sort
    continue
}

queue_dir_lister() {
    # This function produces a checklist from the contents of the QUEUEDIR and
    # takes two arguments - the title and the text of the widget - and makes
    # the selected item(s) from the listing available as USERQUEUE

    local QFS=$SBOPKGTMP/sbopkg_queue_files_selection
    local QFM=$SBOPKGTMP/sbopkg_queue_files_menu

    # Note: the trailing slash ensures that this works fine even if $QUEUEDIR
    # is a symlink to the actual repository.
    find $QUEUEDIR/ -type f -name '*.sqf' -printf "\"%P\" \"\" off\n" \
        -maxdepth 1 | sed -e 's/.sqf//' | sort > $QFM
    if [[ -z $(< $QFM) ]]; then
        if [[ $DIAG ]]; then
            dialog --title "ERROR" --msgbox "$(crunch "The queue directory \
                $QUEUEDIR is empty.")" 8 30
            rm -f $QFM
            return 1
        fi
    fi
    # The --default item doesn't work on deletions and renames (because the
    # variable expands to a no-longer existing file) but you can't give it an
    # index argument, unfortunately
    dialog --title "$1" --default-item "${USERQUEUE##*/}" \
        --cancel-label "Back" --checklist "$2" 20 40 8 --file $QFM 2> $QFS
    if [[ $? != 0 ]]; then
        # unset this so there's no left over junk and the loop from the
        # calling functions doesn't kick in when this returns to them
        unset USERQUEUE
    else
        USERQUEUE=( $(< $QFS) )
    fi
    rm -f $QFM $QFS
    return 0
}

can_skip_line() {
    # This function reads in a line and checks if it is blank or starts with a
    # comment.

    echo $1 | grep "^#" > /dev/null
    if [[ $? == 0 ]]; then
        return 0
    fi
    if [[ "$1" == "" ]]; then
        return 0
    fi
    return 1
}

load_user_queue() {
    # This function loads a user's specified saved queue and merges it

    local USERQUEUE_LOCK=$SBOPKGTMP/sbopkg_user_queue.lck
    local MISSING_LIST_FILE=$SBOPKGTMP/sbopkg_addall_missing

    rm -f $MISSING_LIST_FILE

    queue_dir_lister "Load Queue" "$(crunch "Select the queue(s) you \
        wish to load and choose <OK> or choose <Back> to \
        leave this menu.")" || return 1

    for ((i=0; i<${#USERQUEUE[*]}; i++)); do
        FILE=$QUEUEDIR/${USERQUEUE[$i]//'"'/}
        FILE="$FILE.sqf"
        if [[ -r $FILE ]]; then
            # this inhibits add_item_to_queue's msgbox for each added app
            touch $USERQUEUE_LOCK
            echo "Reading the queuefile, please be patient..."
            parse_queue $FILE
            if [[ -f $MISSING_LIST_FILE ]]; then
                dialog --title "Packages not found" \
                    --exit-label OK --textbox $MISSING_LIST_FILE 0 0
            fi
            LAST_USER_QUEUE_ON_DISK=$FILE
            rm -f $USERQUEUE_LOCK $MISSING_LIST_FILE
        else
            dialog --title "ERROR" --msgbox \
                "$FILE is not readable or does not exist" 0 0
            return 1
        fi
    done
}

delete_user_queue() {
    # This function deletes queues

    queue_dir_lister "Delete Queue" "$(crunch "Select the queue(s) you \
        wish to delete and choose <OK> or choose <Back> to \
        leave this menu.")" || return 1

    for ((i=0; i<${#USERQUEUE[*]}; i++)); do
        FILE=$QUEUEDIR/${USERQUEUE[$i]//'"'/}
        FILE="$FILE.sqf"
        if ! rm -f $FILE 2> /dev/null; then
            dialog --title "ERROR" --msgbox \
                "You do not have permission to remove $FILE" 0 0
            return 1
        fi
    done
}

validate_queue_name() {
    # Validate the queue name stored in the file $1.
    # Shows an error message and returns nonzero in case of invalid queue
    # name.

    local QF="$1"

    if grep -q [^[:alnum:]_-] $QF; then
        # this doesn't prevent the user from putting 'dumb"filename' in the
        # directory manually, but helps prevent breaking sbopkg from sbopkg -
        # and I could allow more characters, but these should be enough
        dialog --title "ERROR" --msgbox "$(crunch "Sorry, \
            but this interface supports filenames containing \
            only alphanumeric characters, dashes, and \
            underscores.")" 0 0
        return 1
    fi
    return 0
}

rename_user_queue() {
    # This function renames queues

    local QRN=$SBOPKGTMP/sbopkg-queue-rename
    local NEWNAME COUNTER FILE

    queue_dir_lister "Rename Queue" "$(crunch "Select the queue(s) you \
        wish to rename and choose <OK> or choose <Back> to \
        leave this menu.")" || return 1

    # I have to assign to this because I shrink the array later
    COUNTER=${#USERQUEUE[*]}
    for ((i=0; i<$COUNTER; i++)); do
        FILE=$QUEUEDIR/${USERQUEUE[$i]//'"'/}
        FILE="$FILE.sqf"
        if [[ -w ${FILE%/*} ]]; then
            # This loops so the user can be brought back to the inputbox on a
            # failure (continue) or back to the dir lister on success (break)
            while :; do
                dialog --title "Rename Queue" \
                    --inputbox "Enter the new filename for ${USERQUEUE[$i]}" \
                    0 0 $NEWNAME 2> $QRN
                NEWNAME="$(< $QRN)"
                if [[ $? == 0 ]]; then
                    if ! validate_queue_name $QRN; then
                        continue
                    elif [[ -f $QUEUEDIR/$NEWNAME ]]; then
                        dialog --title "ERROR" --msgbox "$(crunch "File \
                            exists. Please choose another name.")" 0 0
                        continue
                    else
                        mv "$FILE" "$QUEUEDIR/$NEWNAME.sqf"
                        break
                    fi
                else
                    continue 2
                fi
            done
            # I've already forgotten why this is here, but it was important
            unset USERQUEUE[$i]
        else
            dialog --title "ERROR" --msgbox \
                "You do not have permission to rename $USERQUEUE" 0 0
            return 1
        fi
    done
}

stripcom() {
    # This function removes comments and blank lines from the file given as
    # the argument. It deletes lines beginning with zero or more whitespaces
    # and a comment symbol, strips any text after whitespace and a comment
    # symbol, and deletes blank or whitespace-only lines.

    local FILE="$1"

    sed '
        /^[ \t]*#/d
        s/[ \t][ \t]*#.*//
        /^[ \t]*$/d
        ' $FILE
}

save_user_queue() {
    # This function saves the build queue to the filename the user specifies.
    # If --end is specified as first parameter, assume that the user is
    # exiting sbopkg and that this call is about saving the currently active
    # queue. In that case, show the filename dialog only if there actually is
    # an active queue, and return silently otherwise.

    local SAVEQUEUE=$SBOPKGTMP/sbopkg-tmpsave-queue
    local USERQUEUE=$SBOPKGTMP/sbopkg-user-queue
    local QUEUELIST=$SBOPKGTMP/sbopkg_queue_list
    local DEFAULT MSG USERQUEUE_NAME i
    local USERQUEUE_NAME PICK SAVENAME SAVEONOFF

    rm -f $SAVEQUEUE
    # Reading from $TMPQUEUE...
    [[ -s $TMPQUEUE ]] && while read PICK; do
        SAVENAME=$(cut -d ' ' -f1 <<< "$PICK")
        SAVEONOFF=$(sed 's:^.* \([^ ]*\)$:\1:' <<< "$PICK")
        if [[ $SAVEONOFF =~ [oO][nN] ]]; then
            echo $SAVENAME >> $SAVEQUEUE
        else
            echo "-$SAVENAME" >> $SAVEQUEUE
        fi
    done < $TMPQUEUE
    if [[ $1 == "--end" ]]; then
        if [[ ! -s $TMPQUEUE ]]; then
            return 0
        elif [[ -f $LAST_USER_QUEUE_ON_DISK ]] &&
                diff <(stripcom $LAST_USER_QUEUE_ON_DISK) $SAVEQUEUE &> \
                    /dev/null; then
            # The active queue is unchanged since the last loaded/saved one
            return 0
        elif [[ -f $QUEUELIST ]] && diff $QUEUELIST $TMPQUEUE \
                  &> /dev/null; then
              return 0
        else
            MSG=$(crunch "A current queue is active. Please enter the \
                filename you wish to save your queue as or choose <Cancel> \
                to discard it")
            # Find an unused automatic file name
            i=0
            while [[ -f $QUEUEDIR/sbopkg-autosave-$i.sqf ]]; do
                (( i++ ))
            done
            DEFAULT=sbopkg-autosave-$i
        fi
    else
        if empty_queue; then
            return 0
        else
            MSG=$(crunch "Please enter the filename you wish to save your \
                queue as:")
        fi
    fi

    while :; do
        dialog --title "Save Queue" --inputbox "$MSG" 10 50 $DEFAULT \
            2> $USERQUEUE
        if [[ $? == 0 ]]; then
            if [[ ! -s $USERQUEUE ]]; then
                continue
            fi
            USERQUEUE_NAME="$(< $USERQUEUE)"
            DEFAULT="${USERQUEUE_NAME##*/}"
            if ! validate_queue_name $USERQUEUE; then
                continue
            fi
            if [[ -e $USERQUEUE_NAME.sqf ]]; then
                dialog --title "ERROR" --yesno "$(crunch "Another file \
                    with that name already exists.  Press <Yes> to \
                    continue and overwrite the other file (keep in mind that \
                    the active queue will not preserve any comments from an \
                    on-disk queue), or press <No> to cancel.")" 10 50
                if [[ $? != 0 ]]; then
                    continue
                fi
            fi
            if cp $SAVEQUEUE $QUEUEDIR/$USERQUEUE_NAME.sqf; then
                cp $TMPQUEUE $QUEUELIST
                LAST_USER_QUEUE_ON_DISK=$QUEUEDIR/$USERQUEUE_NAME.sqf
            else
                dialog --title "ERROR" --msgbox "Problem saving build queue."\
                    8 30
            fi
        fi
        break
    done
}

remove_from_queue() {
    # This function deletes items in the build queue.

    local ANSQUEUE=$SBOPKGTMP/sbopkg-ans-queue
    local REMOVEQUEUE=$SBOPKGTMP/sbopkg-remove-queue
    local WORKINGQUEUE=$SBOPKGTMP/sbopkg-working-queue
    local QUEUELIST=$SBOPKGTMP/sbopkg_queue_list
    local CHOICE REMOVE REMOVED

    empty_queue && return
    sed 's/ ON$//g;s/ OFF$//g' $TMPQUEUE > $REMOVEQUEUE
    while :; do
        # "dialog" segfaults when asked to display an empty menu. Work around
        # this by showing an "empty" entry when there are no more items in the
        # queue.
        if [[ $(wc -w < $REMOVEQUEUE) -eq 0 ]]; then
            echo '"" "The queue is empty."' > $REMOVEQUEUE
        fi
        dialog --title "Remove From Build Queue" --ok-label "Delete" \
            --extra-button --extra-label "Clear" --help-button \
            --help-label "Done" --cancel-label "Cancel" \
            --menu "$(crunch "The following packages are currently in \
            the build queue.  You can remove individual items from the build \
            queue by highlighting them and pressing <Delete>.  Press <Done> \
            when you are finished and the individual deletions will be \
            committed.  Otherwise, press <Cancel> at any time to abort your \
            changes.\n\nYou can also press <Clear> to immediately \
            clear the queue.  This cannot be undone.")" 25 60 8 \
            --file $REMOVEQUEUE 2> $ANSQUEUE
        CHOICE=$? # 0 = Delete, 1 = Cancel, 2 = Done, 3 = Delete All
        REMOVED=$(< $ANSQUEUE)
        case $CHOICE in
            255|-1) # ESC
                rm -f $REMOVEQUEUE
                return 0
                ;;
            0)
                echo $REMOVED >> $WORKINGQUEUE
                sed -i "/^$REMOVED .*$/d" $REMOVEQUEUE
                continue
                ;;
            1)
                rm -f $REMOVEQUEUE
                return 0
                ;;
            2)
                if [[ ! -e $WORKINGQUEUE ]]; then
                    rm -f $REMOVEQUEUE
                    break
                fi
                for REMOVE in $(< $WORKINGQUEUE); do
                    sed -i "/^$REMOVE .*$/d" $TMPQUEUE
                done
                if [[ ! -s $TMPQUEUE ]]; then
                    rm -f $TMPQUEUE
                fi
                dialog --title "Done" --msgbox \
                    "The items have been removed from the build queue." 8 30
                return 0
                ;;
            3)
                rm -f $REMOVEQUEUE $TMPQUEUE $QUEUELIST
                dialog --title "Done" --msgbox \
                    "The build queue has been cleared." 8 30
                return 0
                ;;
        esac
    done
}

parse_queue() {
    # This begins the process of parsing through a queuefile.  The $2
    # assignment to NODELETE is used in order to remove the $DUPEQUEUE file
    # only when parse_queue is not called on a recursive queue loading.

    local DUPEQUEUE=$SBOPKGTMP/sbopkg-duplicate-queue
    local MISSING_LIST_FILE=$SBOPKGTMP/sbopkg_addall_missing
    local FILE=$1
    local NODELETE=$2
    local PICK LOADOPTIONS

    if [[ $NODELETE != "NODELETE" ]]; then
        rm -f $DUPEQUEUE
    fi
    if [[ ! -e $DUPEQUEUE ]]; then
        > $DUPEQUEUE
    fi
    if grep -qx "^$FILE" $DUPEQUEUE; then
        return 0
    else
        echo "$FILE" >> $DUPEQUEUE
    fi
    # Reading from $FILE...
    while read PICK; do
        if can_skip_line $PICK; then
            continue
        fi
        unset LOADOPTIONS
        if grep -q "|" <<< $PICK; then
            LOADOPTIONS=${PICK##*|}
            LOADOPTIONS=${LOADOPTIONS/# /}
            PICK=${PICK%%|*}
        fi
        if ! add_item_to_queue $PICK "$LOADOPTIONS"; then
            if [[ ! -s $MISSING_LIST_FILE ]]; then
                cat > $MISSING_LIST_FILE <<EOF

The following packages cannot be found
in the currently active repository
($REPO_NAME/$REPO_BRANCH) and have been skipped:

EOF
            fi
            echo $PICK >> $MISSING_LIST_FILE
        fi
    done < $FILE
}

add_item_to_queue() {
    # This function takes up to two arguments: a required APP and an optional
    # LOADOPTIONS.  When loading a userqueue, some APPs may have a '-' or a
    # '@' as the first character, which means to set the APP to 'OFF' in the
    # dialog menu, or to recursively load another queuefile, respectively.  If
    # an APP is found in the repo, then add it to TMPQUEUE. LOADOPTIONS may be
    # supplied when parsing a queuefile or similar and are eventually passed
    # on to the SlackBuild.

    # If an obsolete name is used, add_item_to_queue() automatically retrieves
    # and uses the current name.
    #
    # This function returns 0 if the insertion was successful, 1 otherwise.

    local APP=$1
    local LOADOPTIONS="$2"
    local USERQUEUE_LOCK=$SBOPKGTMP/sbopkg_user_queue.lck
    local UPDATEQUEUE=$SBOPKGTMP/sbopkg-update-queue
    local QUEUELIST=$SBOPKGTMP/sbopkg_queue_list
    local MISSING_LIST_FILE=$SBOPKGTMP/sbopkg_addall_missing
    local FILE ONOFF VERSION INSTALLED

    # This next if is for legacy queuefiles with $APP $VERSION$BUILD $ONOFF
    if [[ $3 =~ [Oo][Ff][Ff] ]]; then
          APP=-$APP
    fi
    if [[ ${APP:0:1} == "-" ]]; then
        APP=${APP:1}
        ONOFF=OFF
    elif [[ ${APP:0:1} == "@" ]]; then
        APP=${APP:1}
        if [[ ${APP:(-4)} != ".sqf" ]]; then
            FILE="$QUEUEDIR/$APP.sqf"
        else
            FILE="$QUEUEDIR/$APP"
        fi
        if [[ -r $FILE ]]; then
            parse_queue $FILE NODELETE
        else
            return 1
        fi
        return 0
    else
        ONOFF=ON
    fi
    if ! search_package $APP; then
        get_new_name APP $APP
        search_package $APP || return 1
    fi
    if grep -q "^$APP " $TMPQUEUE 2> /dev/null; then
        : # it's the same app and version so toss it
    else
        INSTALLED=$(ls -1 /var/log/packages |
            grep "$APP-[^-]*-[^-]*-[^-]*$REPO_TAG\$")
        if [[ -n $INSTALLED ]]; then
            VERSION=$(sed 's:^.*-\([^-]*\)-[^-]*-[^-]*$:\1:'<<<$INSTALLED)
            # NOTE: When changing, see the uncheck_installed() comment
            echo "$APP \"Installed $VERSION\" $ONOFF" >> $TMPQUEUE
            echo "$APP \"Installed $VERSION\" $ONOFF" >> $QUEUELIST
        else
            # NOTE: When changing, see the uncheck_installed() comment
            echo "$APP New $ONOFF" >> $TMPQUEUE
            echo "$APP New $ONOFF" >> $QUEUELIST
        fi
        if [[ $LOADOPTIONS ]]; then
            echo "$LOADOPTIONS" > $SBOPKGTMP/sbopkg_"$APP"_loadoptions
        else
            rm -f $SBOPKGTMP/sbopkg_"$APP"_loadoptions
        fi
    fi
    # Only display this if we are not loading a queue; otherwise getting this
    # after each app was added to the queue may get annoying.
    if [[ ! -e $USERQUEUE_LOCK ]]; then
        dialog --title "Done" --msgbox "$(crunch "$APP has been added to \
            the build queue.")" 8 40
    fi
    return 0
}

uncheck_installed() {
    # This function unchecks the installed items in a given queue.
    # $1 = the queue file.
    # NOTE: This function uses the second field (New/Installed foo) to
    # work, so we should be careful when changing its format.

    local QUEUEFILE=$1

    sed -i 's:^\([^ ]* .*Installed.* \)[^ ]\+$:\1OFF:' $QUEUEFILE
}

view_queue() {
    # This function displays the contents of the build queue.
    # Returns 0 if the user choose OK, nonzero otherwise

    local ANSQUEUE=$SBOPKGTMP/sbopkg-ans-queue
    local WORKINGQUEUE=$SBOPKGTMP/sbopkg-working-queue
    local ORIGINALQUEUE=$SBOPKGTMP/sbopkg-original-queue
    local CHOICE

    empty_queue && return 1
    cp $TMPQUEUE $ORIGINALQUEUE
    while :; do
        dialog --title "Viewing Build Queue" --separate-output \
            --extra-button --extra-label "View READMEs" \
            --help-button --help-label "Clear inst'd" --help-status \
            --cancel-label "Back" --checklist "$(crunch "The \
            following packages are currently \
            in the build queue.  Please note that when the build queue \
            is processed, the packages selected below will be built, and \
            optionally installed, in the order listed from top to \
            bottom.\n\nPlease select or unselect those packages you wish \
            to keep in the build queue and then press <OK> to continue \
            or press <Back> to go back.")" 23 70 9 \
            --file $TMPQUEUE 2> $ANSQUEUE
        CHOICE=$?

        # Strip that damn "HELP " text when choosing the HELP dialog button
        [[ $CHOICE -eq 2 ]] && sed -i 's:^HELP ::g' $ANSQUEUE

        rm -f $WORKINGQUEUE
        # Reading from $TMPQUEUE...
        while read PICK; do
            TESTAPP="${PICK// */}"
            if grep -qx "$TESTAPP" $ANSQUEUE; then
                sed 's/OFF$/ON/' <<< "$PICK" >> $WORKINGQUEUE
            else
                sed 's/ON$/OFF/' <<< "$PICK" >> $WORKINGQUEUE
            fi
        done < $TMPQUEUE
        mv $WORKINGQUEUE $TMPQUEUE

        case $CHOICE in
            0) # OK
                return 0
                ;;
            2) # Uncheck installed
                uncheck_installed $TMPQUEUE
                ;;
            3) # View READMEs
                view_queue_readmes
                ;;
            *) # Cancel or ESC
                mv $ORIGINALQUEUE $TMPQUEUE
                rm -f $WORKINGQUEUE $ANSQUEUE
                return 1
                ;;
        esac
    done
}

view_queue_readmes() {
    # Show a list of all README files for the queued (TMPQUEUE) packages.

    local READMES_FILE=$SBOPKGTMP/sbopkg-all-readmes
    local HEAD_FILE=$SBOPKGTMP/sbopkg-all-readmes-head
    local REPORT_FILE=$SBOPKGTMP/sbopkg-all-readmes-report
    local NAME ONOFF
    local PICK READMES

    READMES=$(find $REPO_DIR -name README)

    echo -e "The active queue is:\n" > $HEAD_FILE

    while read PICK; do
        NAME=${PICK/ *}
        ONOFF=${PICK/* }

        if [[ $ONOFF =~ [Oo][Nn] ]]; then
            echo $NAME >> $HEAD_FILE
        else
            echo "$NAME (DISABLED)" >> $HEAD_FILE
        fi

        echo >> $READMES_FILE
        echo >> $READMES_FILE
        tin_text $NAME >> $READMES_FILE
        echo >> $READMES_FILE
        cat $(grep /$NAME/README\$ <<< "$READMES") >> $READMES_FILE
    done < $TMPQUEUE

    tin_text "$(< $HEAD_FILE)" > $REPORT_FILE
    cat $READMES_FILE >> $REPORT_FILE

    if [[ $DIAG ]]; then
        dialog --exit-label "OK" --title "READMEs for the queued packages" \
            --textbox $REPORT_FILE 0 0
    else
        $PAGER $REPORT_FILE
    fi

    rm $READMES_FILE $REPORT_FILE $HEAD_FILE
}

tin_text() {
    # Print $1 in a nice ASCII box like:
    # +---------+
    # | foo bar |
    # | baz     |
    # +---------+

    local TEXT="$1"
    local MAXLEN=0 NLINES=0
    local LINE
    local HLINE

    # Find the maximum line length and the number of lines
    while read LINE; do
        if [[ $MAXLEN -lt ${#LINE} ]]; then
            MAXLEN=${#LINE}
        fi
        ((NLINES++))
    done <<< "$TEXT"

    # Print the box
    printf -vHLINE "%${MAXLEN}s" ""
    printf -vHLINE "%s" "${HLINE// /-}"
    echo "+-$HLINE-+"
    while read LINE; do
        printf "| %-${MAXLEN}s |\n" "$LINE"
    done <<< "$TEXT"
    echo "+-$HLINE-+"
}

add_all_to_queue() {
    # This function adds all currently installed repo packages to the
    # build queue.

    local SBOPKGLIST=$SBOPKGTMP/sbopkg_pkglist
    local USERQUEUE_LOCK=$SBOPKGTMP/sbopkg_user_queue.lck
    local TMPQUEUE_BACKUP=$SBOPKGTMP/sbopkg_addall_backup
    local MISSING_LIST_FILE=$SBOPKGTMP/sbopkg_addall_missing
    local PROGRESSBAR_INTERRUPTED=$SBOPKGTMP/sbopkg_progressbar-interrupted
    local PKGS FILE PKG_NAME
    local PROGRESS=0 NUM_PACKAGES

    rm -f $SBOPKGLIST $MISSING_LIST_FILE $PROGRESSBAR_INTERRUPTED
    cp $TMPQUEUE $TMPQUEUE_BACKUP 2> /dev/null
    touch $USERQUEUE_LOCK
    cd /var/log/packages
    PKGS=$(ls *$REPO_TAG* 2> /dev/null)
    for FILE in $PKGS; do
        echo $FILE >> $SBOPKGLIST
    done
    if [[ -f $SBOPKGLIST ]]; then
        NUM_PACKAGES=$(wc -l < $SBOPKGLIST)
        { # Grouping for progressbar
        echo 0 # Progressbar begin

        for PICK in $(cat $SBOPKGLIST); do
            # Bail out if the user pressed ESC
            progressbar_interrupted && touch $PROGRESSBAR_INTERRUPTED && break

            if can_skip_line $PICK; then
                continue
            fi
            split_pkg_name $PICK
            if ! add_item_to_queue $PKG_NAME; then
                if [[ ! -f $MISSING_LIST_FILE ]]; then
                    cat > $MISSING_LIST_FILE <<EOF
The following packages cannot be found
in the currently active repository
($REPO_NAME/$REPO_BRANCH) and have been skipped:

EOF
                fi
                echo $PKG_NAME >> $MISSING_LIST_FILE
            fi

            ((PROGRESS++))
            echo $((PROGRESS*100/NUM_PACKAGES))
        done
        } | progressbar "Queuing installed packages" \
            "Loading all installed $REPO_NAME packages into the build queue.\
            This may take a few moments depending on how many $REPO_NAME\
            packages you have installed, so please be patient..."
        if [[ -f $PROGRESSBAR_INTERRUPTED ]]; then
            rm -f $TMPQUEUE
            mv $TMPQUEUE_BACKUP $TMPQUEUE 2> /dev/null
            rm $PROGRESSBAR_INTERRUPTED
        else
            if [[ -f $MISSING_LIST_FILE ]]; then
                dialog --title "Packages not found" --textbox \
                    $MISSING_LIST_FILE 0 0
            fi
        fi
        rm -f $USERQUEUE_LOCK $MISSING_LIST_FILE $TMPQUEUE_BACKUP
    else
        if [[ $DIAG ]]; then
            dialog --title "No packages found" --msgbox "$(crunch_fmt "It \
                appears that you have no $REPO_NAME packages \
                installed.")" 8 40
        fi
    fi
}

rsync_command() {
    # This function holds the rsync command.
    # We do not use -z as this causes heavy CPU load on the server and has
    # very limited effect when most of the pull is .gz files.

    local SYNC_LOCK=$SBOPKGTMP/sbopkg_sync.lck

    rsync --archive --delete --no-owner --exclude="*.sbopkg" \
        $RSYNCFLAGS $REPO_LINK/ $REPO_DIR/
    case $? in
        35)
            echo
            echo "The connection to $REPO_LINK timed out."
            echo "You can modify the TIMEOUT value in sbopkg.conf"
            echo "if this problem persists."
            echo "(TIMEOUT is currently set to:  $TIMEOUT seconds)".
            echo
            ;;
        30)
            echo
            echo "Rsync reported a timeout while waiting for data."
            echo "$REPO_LINK may under a heavy load."
            echo "Please try again later."
            echo
            ;;
        10)
            echo
            echo "Rsync reported a socket error which may be due to"
            echo "a problem with the LINK value in sbopkg.conf."
            echo "(The repo's LINK is currently set to: $REPO_LINK)."
            echo "Please check your settings and try again later."
            echo
            ;;
        0)
            echo
            echo "Rsync with the $REPO_DESC complete."
            echo
            echo "Importing $REPO_DESC GPG Key..."
            gpg --quiet --fetch-key http://www.slackbuilds.org/GPG-KEY
            echo "Import done."
            echo
            echo "***SYNC COMPLETE***"
            ;;
        *)
            echo
            echo "Rsync with the $REPO_DESC failed."
            echo "Please try again."
            echo
            ;;
    esac
    rm -f $SYNC_LOCK
}

git_command() {
    # This function synchronizes a local git repository with upstream.

    local SYNC_LOCK=$SBOPKGTMP/sbopkg_sync.lck
    local URL BRANCH CWD

    eval $(sed 's/^\(.*\)@\(.*\)$/URL=\1; BRANCH=\2/g' <<< $REPO_LINK)

    CWD=$(pwd)
    # Create the repository if needed
    if [[ ! -d $REPO_DIR/.git ]]; then
        mkdir -p $REPO_DIR
        cd $REPO_DIR
        git init
    fi
    # Update the repository
    cd $REPO_DIR
    git pull $URL $BRANCH
    # Remove leftovers
    # This is optional, think of it as a way to emulate the --delete --exclude
    # rsync directives
    echo "*/*/*.sbopkg" > .gitignore
    git clean -d -f
    git reset --hard HEAD
    # Create a changelog
    # (it makes no sense to have one tracked in a git repo)
    if [[ ! -f ChangeLog.txt ]]; then
        git log --pretty=format:"%cd%n%s%n%b" > ChangeLog.txt
    fi
    # All done
    rm -f $SBOPKGTMP/sbopkg_sync.lck
    echo
    echo "Repository update complete."
    echo
}

sync_repo() {
    # This function does the sync with SBo.

    local SYNC_LOCK=$SBOPKGTMP/sbopkg_sync.lck

    if [[ $REPO_TOOL == "" ]]; then
        if [[ $DIAG ]]; then
            dialog --title "ERROR" --msgbox \
                "You cannot sync the $REPO_DESC." 8 40
        else
            crunch_fmt "You cannot sync the $REPO_DESC."
        fi
        continue
    fi
    directory_checks
    if ! check_write $REPO_DIR; then
        if [[ $DIAG ]]; then
            dialog --title "ERROR" --msgbox \
                "You do not have write permissions on the target directory." \
                8 30
            continue
        else
            crunch_fmt "You do not have write permissions on the target \
                directory." >&2
            cleanup
            exit 1
        fi
    elif [[ $REPO_TOOL != "rsync" && $REPO_TOOL != "git" ]]; then
        if [[ $DIAG ]]; then
            dialog --title "ERROR" --msgbox \
                "Unsupported fetching tool \"$REPO_TOOL\"." 8 30
            continue
        else
            echo "Unsupported fetching tool \"$REPO_TOOL\"."
            cleanup
            exit 0
        fi
    fi
    if [[ $DIAG ]]; then
        touch $SYNC_LOCK
        ( ${REPO_TOOL}_command >> $SBOPKGOUTPUT & ) 2>> $SBOPKGOUTPUT
        while [[ -f $SYNC_LOCK ]]; do
            dialog --backtitle "Updating the active repository" \
                --tailbox $SBOPKGOUTPUT 18 70
        done
        rm -f $SBOPKGOUTPUT
    else
        ${REPO_TOOL}_command
    fi
}

search_package() {
    # Search for package name and return error if not found.
    # $1 = the name of the package we're looking for
    # Returns 0 and sets PKGPATH if the package is found. Returns 1 otherwise.

    local PKG

    cd $REPO_DIR
    PKG="$1"
    PKGPATH=( $(find -type d -mindepth 2 -name "$PKG") )

    if [[ -z $PKGPATH ]]; then
        return 1
    else
        return 0
    fi
}

gen_search_package() {
    # Search for package name glob generally using grep.  In dialog interface,
    # jump to selected package.
    # Returns 0 unless the user asked to jump back to the main menu.

    cd $REPO_DIR
    local PKG=$1
    local CATEGORY=${2:-\*}
    local RETVAL=0
    local CAT_SELECTION=$SBOPKGTMP/sbopkg_category_selection
    local ITEM_SELECTION=$SBOPKGTMP/sbopkg_item_selection
    local SEARCH_CHOICE=$SBOPKGTMP/sbopkg_search_choice
    local SEARCH_RESULTS=$SBOPKGTMP/sbopkg_search_results
    local RESULTS=$(find -mindepth 2 -maxdepth 2 -type d \
        -iwholename "./$CATEGORY/*$PKG*" -printf "%P\n" | sort)
    local NAME DESC CHOICE
    local SRCHPICK SRCHCAT SRCHPKG

    if [[ $RESULTS ]]; then
        if [[ $DIAG ]]; then
            for i in $RESULTS; do
                DESC=$(grep -hZm1 ^$(cut -d/ -f2 <<< "$i") ./$i/slack-desc* |
                    sed 's/^[^(]*( *\(.*[^ ]\) *)[^)]*$/\1/;s/"/'\''/g')
                if [[ $CATEGORY == '*' ]]; then
                    NAME=$i
                else
                    NAME=$(cut -d/ -f2 <<< "$i")
                fi
                echo "\"$NAME\" \"$DESC\"" >> $SEARCH_RESULTS
            done
            while [[ -f $SEARCH_RESULTS ]]; do
                # The default item can be "". In that case, dialog defaults to
                # the first item.
                dialog --title "Matches for $PKG in $CATEGORY" \
                    --backtitle "$(eval echo $BACKTITLE)" \
                    --default-item "$SRCHPICK" --extra-button \
                    --cancel-label "Back" \
                    --help-button --help-label "Main Menu" \
                    --extra-label "Add to Queue" --menu "$(crunch "Please \
                    select an item you wish to view, press <Add to Queue> \
                    to add it to the build queue, or press <Back> to \
                    go back.")" 22 70 14 --file \
                    $SEARCH_RESULTS 2> $SEARCH_CHOICE
                CHOICE=$?
                case $CHOICE in
                    # Back or ESC
                    1 | 255 | -1 ) break ;;
                    # Main Menu
                    2 ) RETVAL=1; break ;;
                esac
                SRCHPICK="$(< $SEARCH_CHOICE)"
                if [[ $CATEGORY == '*' ]]; then
                    SRCHCAT="${SRCHPICK%%/*}"
                else
                    SRCHCAT=$CATEGORY
                fi
                echo $SRCHCAT > $CAT_SELECTION
                SRCHPKG="${SRCHPICK##*/}"
                if [[ $CHOICE == 0 ]]; then
                    echo $SRCHPKG > $ITEM_SELECTION
                    cd $REPO_DIR
                    if ! info_item; then
                        RETVAL=1
                        break
                    fi
                else # $CHOICE = 3
                    add_item_to_queue $SRCHPKG
                fi
            done
        else
            echo "Found the following matches for $PKG:"
            for i in $RESULTS; do
                echo $i
            done
        fi
    else
        if [[ $DIAG ]]; then
            dialog --title "Not Found" --msgbox "No match for $PKG found"\
                8 30
        else
            echo "$SCRIPT: No match for $PKG found." >&2
        fi
    fi

    rm -f $SEARCH_RESULTS $SEARCH_CHOICE $CAT_SELECTION $ITEM_SELECTION
    return $RETVAL
}

string_search() {
    # If the search string is prefixed with 'inst:', then the user only wants
    # to search installed packages, so this shaves the 'inst:' off, and
    # generates a potentially huge path consisting only of installed *SBo
    # packages to hand to find. Otherwise, just search for $1 in REPO as
    # usual.
    # Returns 0 unless the user asked to jump back to the main menu.

    if [[ ${SEARCH_TERM%%:*} == "inst" ]]; then
        local SEARCH_TERM="${SEARCH_TERM#*:}"
        local SBOPKGS=($(ls /var/log/packages/*$REPO_TAG))
        for ((i=0; i<${#SBOPKGS[*]}; i++)); do
            local PKGNAME=$(
                sed 's,.*/,,;s/-[^-]*-[^-]*-[^-]*$//' <<< "${SBOPKGS[$i]}")
            local INST_PKGS+="$REPO_DIR/*/$PKGNAME "
        done
        local FIND_PATH="$INST_PKGS"
    else
        local SEARCH_TERM="$1"
        local FIND_PATH="$REPO"
    fi
    local CAT_SELECTION=$SBOPKGTMP/sbopkg_category_selection
    local ITEM_SELECTION=$SBOPKGTMP/sbopkg_item_selection
    local MENU_FILE=$SBOPKGTMP/sbopkg_menu-file
    local PICKED_FILE=$SBOPKGTMP/sbopkg_picked-file
    local PICKED

    # The sed expression processes find's output into data usable for the menu
    # file but the first two parts are needed to sanitize the input - which
    # raises the question of true general sanitizing of this input
    ( find $FIND_PATH -iname 'README' -exec egrep -iwm1 "$SEARCH_TERM" {} + |
        sed "
            s,\",\',g
            s/\\\/\\\\\\\\/g
            s,$REPO_DIR/,,
            s/^/\"/
            s,/README:,\" \",
            s/$/\"/
        " | sort > $MENU_FILE
    ) 2> /dev/null

    if [[ ! -s $MENU_FILE ]]; then
        dialog --title "ERROR" --msgbox "No match for $SEARCH_TERM found" 8 30
        return 0
    fi

    cd $REPO_DIR

    while :; do
        dialog --title "String Search Results" --default-item "$PICKED" \
            --extra-button --extra-label "Add to Queue" \
            --cancel-label "Back" \
            --menu "$(crunch "Please select an item you wish to view or \
                press <Add to Queue> to add it to the build queue or \
                press <Back> to go back.")" 0 0 0 \
            --file $MENU_FILE 2> $PICKED_FILE

        BUTTON=$?
        PICKED=$(< $PICKED_FILE)

        # Duplicate (except slightly modified) code from gen_package_search()
        SRCHCAT=${PICKED%%/*}
        echo $SRCHCAT > $CAT_SELECTION
        SRCHPKG=${PICKED##*/}

        case $BUTTON in
            0) # OK
                echo $SRCHPKG > $ITEM_SELECTION
                if ! info_item; then
                    rm -f $PICKED_FILE $MENU_FILE $CAT_SELECTION \
                        $ITEM_SELECTION
                    return 1
                fi
                ;;
            3) # Add to Queue
                add_item_to_queue $SRCHPKG
                continue
                ;;
            *) # Back, etc.
                rm -f $PICKED_FILE $MENU_FILE $CAT_SELECTION $ITEM_SELECTION
                return 0
            ;;
        esac
    done
}

show_readme() {
    # Show the package's text files.
    # $1 = Package path
    # $2 = Package name

    local PKGPATH=$1
    local PKGNAME=$2

    cd $REPO_DIR
    $PAGER \
        $PKGPATH/{README,$PKGNAME.SlackBuild,$PKGNAME.info.build,slack-desc}
    rm -f $PKGPATH/$PKGNAME.info.build
    return 0
}

read_info() {
    # Read the info file specified in $1.
    # This used to be a plain ". $INFO", but due to the changes required to
    # support multiple arches and source files (both features are planned
    # for the Slackware 13.0 release) it needs some more work.
    # The DOWNLOAD and MD5SUM arrays will always contain the "right"
    # (possibly ARCH-dependent) values.

    local INFO=$1
    local i DOWNLOAD_ARCH DLSAVE MDSAVE ANS
    local {DOWNLOAD,MD5SUM}_$ARCH

    unset DOWNLOAD MD5SUM

    # Parse the .info file
    . $INFO
    # Assign the proper entries to DOWNLOAD and MD5SUM.
    DOWNLOAD_ARCH=$(eval echo \$DOWNLOAD_$ARCH)
    if [[ -n $DOWNLOAD_ARCH ]]; then
        DLSAVE=$DOWNLOAD
        MDSAVE=$MD5SUM
        DOWNLOAD=$DOWNLOAD_ARCH
        MD5SUM=$(eval echo \$MD5SUM_$ARCH)
    fi
    # Note: on SBo pre-13.0 repositories, as well as on current non-SBo
    # repositories, none of the above triggers. In that case, we use the
    # provided DOWNLOAD and MD5SUM variables, which is exactly the old-style
    # behavior.

    # This next bit is called in process_queue and is here to test if the
    # package is marked UNSUPPORTED or UNTESTED and ask the user what he wants
    # to do.  If the user chooses to proceed, then the valid DOWNLOAD and
    # MD5SUM lines from the .info file are tacked on to the end of the
    # *.info.build file.  This way, when read_info is called elsewhere, like
    # in get_source via build_package, the newly tacked-on lines will provide
    # the 'correct' DOWNLOAD and MD5SUM values.  This is more or less a
    # band aid until the multiple read_info invocations can be addressed.

    if [[ $2 == --check_buildable ]]; then
        if [[ $DOWNLOAD == "UNSUPPORTED" || $DOWNLOAD == "UNTESTED" ]]; then
            while :; do
                echo
                crunch_fmt "$PRGNAM:  This package is marked UNSUPPORTED \
                    or UNTESTED and may not build successfully on your \
                    architecture. Do you want to try and proceed anyway or \
                    do you want to skip it?"
                echo
                echo "Press (Y)es to proceed or (N)o to skip."
                read ANS
                case $ANS in
                    y* | Y* )
                        break
                        ;;
                    n* | N* )
                        return 1
                        ;;
                    * )
                        echo "Unknown response."
                        ;;
                esac
            done
            if [[ $ARCH != "x86_64" ]]; then
                echo "DOWNLOAD=\"$DOWNLOAD_x86_64\"" >> $INFO
                echo "MD5SUM=\"$MD5SUM_x86_64\"" >> $INFO
            else
                echo "DOWNLOAD_$ARCH=\"$DLSAVE\"" >> $INFO
                echo "MD5SUM_$ARCH=\"$MDSAVE\"" >> $INFO
            fi
        fi
    fi

    # Convert the space-separated strings to arrays
    DOWNLOAD=($DOWNLOAD)
    MD5SUM=($MD5SUM)
}

get_source_names() {
    # Echo the source names for an app, given the info file.
    # Usage: get_source_names [--all] [--placeholder] info_file
    #   --all           try to find all source files (i.e. also the obsolete
    #                   ones)
    #   --placeholder   if no source file is found for a DOWNLOAD entry, a
    #                   line containing "." is generated
    # Note that even without --all this function can return multiple files,
    # if the application specifies more than one in its .info file.

    local SRCNAME INFO ALL CWD DL PLACEHOLDER
    # Don't pollute the environment with the .info content...
    local PRGNAM VERSION HOMEPAGE DOWNLOAD MD5SUM MAINTAINER EMAIL APPROVED

    if [[ $1 == "--all" ]]; then
        ALL=yes
        shift
    fi
    if [[ $1 == "--placeholder" ]]; then
        PLACEHOLDER="."
        shift
    fi
    INFO=$1

    read_info $INFO
    for DL in "${DOWNLOAD[@]}"; do
        # remove the '/download' from any SRCNAME that ends that way rather
        # than in the actual tarball
        SRCNAME=${DL//%\/download}
        SRCNAME=${SRCNAME##*/}
        # this is actually so that calcurse will make it to
        # remove_obsolete_sources()
        SRCNAME=$(sed 's/?e=.*$//;s/?format=.*$//;s/^.*?//' <<< "$SRCNAME")
        # Replace URI hex sequences (like %20 for ' ' and %2B for '+') with
        # their corresponding characters.
        # This is done by replacing '%' with '\x' and passing the string to
        # printf.
        if [[ $SRCNAME =~ % ]]; then
            SRCNAME=$(printf ${SRCNAME//\%/\\x})
        fi
        # The above doesn't work when the download link doesn't reference the
        # file name either explicitly or correctly. If this is the case, our
        # SRCNAME doesn't correspond to a file, and all we can do is guess
        # from the file that was downloaded and/or the name of the package.
        if [[ -n $NO_DL_LOOP && ! -f $(readlink "$SRCNAME") &&
                ${#DOWNLOAD[@]} == 1 ]]; then
            # If the source has a name resembling $PRGNAM-$VERSION.tar.gz,
            # catch it.
            CWD=$(pwd)
            cd $SRCDIR
            SRCNAME=$(find . -iname $PRGNAM\*$VERSION.\* | head -n 1)
            cd "$CWD"
            if [[ ! $SRCNAME ]]; then
                # We do our best with the tools we have...
                SRCNAME=$PRGNAM-$VERSION.tar.gz
            fi
        fi

        # If the user asked for "all" sources, let's try to find similar names
        if [[ $ALL ]]; then
            # The following is based on the idea that, if the source name
            # contains the version number, the expression below takes the
            # parts before and after the version number, and replaces the
            # version number with a regular expression matching a digit and
            # any character present in the known version number (this is to
            # match odd version numbers containing letters, like "svn1234",
            # but makes it less likely to match different packages with
            # similar names, like virtualbox-kernel and
            # virtualbox-kernel-addons). If the source name does not contain
            # the version number, we leave SRCNAME alone, though this means
            # that when the user is using the remove sources functionality
            # they can only remove the source one at a time, starting with the
            # most recent.
            #
            # The flash conditional is based on the fact that the source names
            # of the flash player plugin (which are different on different
            # arches) don't contain the version number. The grep (rather than
            # egrep) is due to a grep having already been used below and this
            # having fewer potential side effects - this should eventually get
            # a proper fix rather than this ad hockery.
            if grep -q \
                '\(install_\|lib\)flash_\?player.*\.tar\.gz' <<< $SRCNAME; then
                SRCNAME='\(install_\|lib\)flash_\?player.*\.tar\.gz'
            elif [[ $SRCNAME =~ $VERSION ]]; then
                SRCNAME=${SRCNAME%%$VERSION*}[0-9$VERSION]\*${SRCNAME##*$VERSION}
            fi
        fi

        # This isn't just 'ls -A $SRCDIR/${SRCNAME##*/} 2> /dev/null' because
        # we want only the basename - though we could 'cd' first or 'basename'
        # after, rather than grepping. And the conditionals ensure that we
        # only return PLACEHOLDER when we don't have any other results and
        # PLACEHOLDER is actually called for and set.
        if ! ls -A $SRCDIR | grep "^${SRCNAME##*/}"; then
            if [[ $PLACEHOLDER ]]; then
                echo $PLACEHOLDER
            fi
        fi
    done
}

check_source() {
    # Check the source file for correctness.
    # Parameters:
    # - $1 = package name
    # - $2 = expected MD5
    # - $3 = source file name
    # Returns 0 if the source is OK, 1 if the source should be (re)downloaded
    # (this includes the cases where $3 is empty or refers to a nonexistent
    # file) and 2 if the user asked to abort the build.

    local PKG=$1
    local MD5SUM="$2"
    local SRCNAME="$3"
    local MD5CHK ANS

    # If there's no known source name, or if it doesn't exist, it has to be
    # downloaded...
    if [[ -f $SRCDIR/$SRCNAME ]]; then
        unset NO_DL_LOOP
    else
        return 1
    fi

    # Check MD5
    echo "Checking MD5SUM:"
    MD5CHK=$(md5sum "$SRCDIR/$SRCNAME" | cut -d' ' -f1)
    echo -n "  MD5SUM check for $SRCNAME ... " | tee -a $TMPSUMMARYLOG
    if [[ $MD5CHK == $MD5SUM ]]; then
        echo "OK" | tee -a $TMPSUMMARYLOG
    else
        echo "FAILED!" | tee -a $TMPSUMMARYLOG
        echo "    Expected: $MD5SUM" | tee -a $TMPSUMMARYLOG
        echo "    Found:    $MD5CHK" | tee -a $TMPSUMMARYLOG
        # Ask the user what to do with the bad source
        while :; do
            cat << EOF

Do you want to use the downloaded $PKG source:
$SRCNAME in $SRCDIR?

You can choose among the following options:
 - (Y)es, keep the source and continue the build process;
 - (N)o, delete the source and abort the build process; or
 - (R)etry download and continue the build process.
Your choice?
EOF
            error_read ANS
            case $ANS in
                y* | Y* )
                    MD5SUM=$(tr / _ <<< "$MD5CHK")
                    echo "    Keeping the source and continuing." |
                        tee -a $TMPSUMMARYLOG
                    return 0
                    ;;
                n* | N* )
                    rm -f "$SRCDIR/$SRCNAME"
                    echo "    Source deleted." | tee -a $TMPSUMMARYLOG
                    return 2
                    ;;
                r* | R* )
                    echo "    Downloading again." | tee -a $TMPSUMMARYLOG
                    return 1
                    ;;
                * )
                    echo "    Unknown response."
                    ;;
            esac
        done
    fi
}

get_source() {
    # Check to see if the source tarball exists in the local cache directory.
    # If it does, make a symlink to the package directory in the local mirror.
    # If it does not, download it and make the link.
    #
    # Parameters:
    # $1 = info file
    #
    # Return values:
    # 0 = all ok
    # 1 = failed

    local INFO=$1
    local PKG=${INFO%.info.build}
    local BUILD_LOCK=$SBOPKGTMP/sbopkg_build.lck
    local DLDIR=$SBOPKGTMP/sbopkg-download
    local PIDLIST=$SBOPKGTMP/sbopkgpidlist
    local TMPSUMMARYLOG=$SBOPKGTMP/sbopkg-tmp-summarylog
    local SRCNAME DL_SRCNAME DL FAILURE ANS MD5CHK i CWD
    # Don't pollute the environment with the .info content...
    local PRGNAM VERSION HOMEPAGE DOWNLOAD MD5SUM MAINTAINER EMAIL APPROVED

    CWD=$(pwd)
    read_info $INFO

    echo | tee -a $TMPSUMMARYLOG
    echo "$PKG:" | tee -a $TMPSUMMARYLOG
    for i in ${!MD5SUM[@]}; do
        while :; do
            cd "$CWD"
            SRCNAME=$(get_source_names --placeholder $INFO)

            # Put SRCNAME lines into array elements.
            # This is a little bit involved since it has to deal with spaces
            # inside file names.
            # We know that we could obtain the same result faster by mangling
            # with IFS, but the resulting code was a bit too hacky.
            eval SRCNAME=( $(
                while read LINE; do
                    printf '%q ' "$LINE"
                done <<< "$SRCNAME"
            ) )

            check_source $PKG ${MD5SUM[$i]} "${SRCNAME[$i]}"
            case $? in
                0 ) # Source OK
                    ln -sf "$SRCDIR/${SRCNAME[$i]}" \
                        "$REPO_DIR/$PKGPATH/${SRCNAME[$i]}"
                    continue 2
                    ;;
                1 ) # Fall through and (re)try below
                    ;;
                2 ) # Abort
                    FAILURE=download
                    break 2
                    ;;
            esac

            rm -rf $DLDIR
            mkdir -p $DLDIR
            cd $DLDIR

            if [[ -z $NO_DL_LOOP ]]; then
                wget $WGETFLAGS ${DOWNLOAD[$i]} >> \
                    $SBOPKGOUTPUT & echo "$!" >> $PIDLIST 2>> $SBOPKGOUTPUT
                wait
            else
                FAILURE=loop
                echo "  Download failed. Please report this as a bug." >> \
                $TMPSUMMARYLOG
                echo >> $TMPSUMMARYLOG
            fi
            DL=$(ls -A . 2> /dev/null)
            # Source filename corrections for Virtualbox, where '?e=foo' gets
            # appended to the filename and for calcurse, where a 'foo?' gets
            # prepended to the filename - and for a potential issue with trac
            # URLs
            DL_SRCNAME=$(sed 's/?e=.*$//;s/?format=.*$//;s/^.*?//' <<< "$DL")
            if [[ $DL_SRCNAME ]]; then
                # if we have *anything* in here, then we did a download. We
                # may not know what to do with it, but we don't need to get it
                # again. We only need to succeed or fail once.
                NO_DL_LOOP=1
                mv "$DL" "$SRCDIR/$DL_SRCNAME"
            else
                FAILURE=download
                echo "  Download failed." >> $TMPSUMMARYLOG
                echo >> $TMPSUMMARYLOG
            fi
            cd $SRCDIR
            rm -rf $DLDIR
            [[ $FAILURE ]] && break 2
            # this is required so we make a link as soon as we have the source
            # and don't enter the guessing code in get_source_names()
            ln -sf "$SRCDIR/$DL_SRCNAME" "$REPO_DIR/$PKGPATH/$DL_SRCNAME"
        done
    done

    cd $REPO_DIR/$PKGPATH

    [[ $FAILURE ]] && return 1

    return 0
}

remove_sources_for_app() {
    # Remove all sources from $SRCDIR for a particular application $1 is the
    # app's INFO file

    local INFO="$1"
    local APP_SOURCES=$SBOPKGTMP/sbopkg_app_sources
    local APP

    APP=${INFO##*/}
    APP=${APP%%.*}
    get_source_names --all "$INFO" | sed 's/.*/"&"/' > $APP_SOURCES
    remove_files $SRCDIR "$APP sources" $APP_SOURCES OFF
}

remove_obsolete_sources() {
    # Remove all obsolete sources

    local FIND_RESULT=$SBOPKGTMP/sbopkg_obsolete_find
    local SOURCES=$SBOPKGTMP/sbopkg_app_sources
    local PROGRESSBAR_INTERRUPTED=$SBOPKGTMP/sbopkg_progressbar-interrupted
    local GSNFILE=$SBOPKGTMP/sbopkg_get_source_names-output
    local PROGRESS=0
    local NUMINFO
    local INFO APP_CURRSRC REGEX

    rm -f $PROGRESSBAR_INTERRUPTED

    { # Grouping for progressbar
    echo 0 # Progressbar begin

    find $REPO_DIR -name \*.info > $FIND_RESULT
    NUMINFO=$(wc -l < $FIND_RESULT)
    ls -A $SRCDIR > $SOURCES

    for INFO in $(cat $FIND_RESULT); do
        # Bail out if the user pressed ESC
        progressbar_interrupted && touch $PROGRESSBAR_INTERRUPTED && break

        # Reading get_source_names output...
        get_source_names "$INFO" > $GSNFILE
        while read APP_CURRSRC; do
            if [[ $APP_CURRSRC ]]; then
                REGEX="/^$APP_CURRSRC$/d;$REGEX"
            fi
        done < $GSNFILE

        # Progress indicator, for the progressbar
        (( PROGRESS += 1 ))
        echo $(($PROGRESS * 100 / $NUMINFO))
    done
    sed -i "$REGEX" $SOURCES
    } | progressbar "Checking for obsolete sources" \
        "This may take a few moments.  Press <ESC> to abort."

    if [[ -f $PROGRESSBAR_INTERRUPTED ]]; then
        rm -f $PROGRESSBAR_INTERRUPTED $SOURCES
        return
    fi

    # Quote file names
    sed -i "s/^\(.*\)$/\"\\1\"/" $SOURCES

    remove_files $SRCDIR "obsolete sources" $SOURCES ON
}

remove_uninstalled_packages() {
    # Remove uninstalled packages from $OUTPUT

    local PACKAGES=$SBOPKGTMP/sbopkg_uninstalled_packages
    local PKG

    rm -f $PACKAGES
    ls $OUTPUT/*$REPO_TAG.t?z | while read PKG; do
        PKG=${PKG##*/}
        if [ ! -f /var/log/packages/${PKG%.t?z} ]; then
            echo "$PKG" >> $PACKAGES
        fi
    done

    remove_files $OUTPUT "uninstalled packages" $PACKAGES ON
}

reverse_choices() {
    # Reverses ON or OFF setting in a file for dialog purposes.
    # $1 is the file

    local REVERSE_FILE="$1"
    local TMPREVERSE=$SBOPKGTMP/sbopkg_tmp_reverse
    local PICK ONOFF

    rm -f $TMPREVERSE
    # Reading from $REVERSE_FILE
    while read PICK; do
        ONOFF=$(sed 's:^.* \([^ ]*\)$:\1:' <<< "$PICK")
        if [[  $ONOFF =~ [oO][nN] ]]; then
            PICK=${PICK/%[oO][nN]/OFF}
            echo $PICK >> $TMPREVERSE
        else
            PICK=${PICK/%[oO][fF][fF]/ON}
            echo $PICK >> $TMPREVERSE
        fi
    done < $REVERSE_FILE
    mv $TMPREVERSE $REVERSE_FILE
}

remove_files() {
    # Selectively remove files, after showing a checklist of them.
    # The file names (specified in $3) _must_ be quoted.
    # $1 is the files path
    # $2 is the topic (used for display purposes only, like "foo sources")
    # $3 is a file containing the list of the files to be shown
    # $4 is either "ON" or "OFF", and is used as the default checklist status

    local FILESPATH="$1"
    local TOPIC="$2"
    local FILES="$3"
    local ONOFF="$4"
    local FILES_CHECKLIST=$SBOPKGTMP/sbopkg_file_removal_checklist
    local FILES_DELETING=$SBOPKGTMP/sbopkg_file_removal_deleting
    local SRC USER_OPTS DELETE ANS DLGWIDTH CHOICE

    cd $FILESPATH
    if [[ -s $FILES ]]; then
        sed "s/^\(.*\)$/\\1 \"\" $ONOFF/g" < $FILES |
            sort > $FILES_CHECKLIST
        if [[ $DIAG ]]; then
            while :; do
                dialog --title "$(crunch "Displaying $TOPIC")" \
                    --ok-label "OK" --extra-label "Delete selected" \
                    --cancel-label "Invert all" --extra-button \
                    --separate-output --checklist "$(crunch "Would you like \
                    to keep the $TOPIC in $FILESPATH?")"\
                    20 60 12 \
                    --file $FILES_CHECKLIST 2> $FILES_DELETING
                CHOICE=$? # 0=Ok, 1=Invert all, 3=Delete
                case $CHOICE in
                    255|-1) # ESC
                        rm -f $FILES_CHECKLIST $FILES_DELETING
                        return 0
                        ;;
                    0)
                        return 0
                        ;;
                    1)
                        reverse_choices $FILES_CHECKLIST
                        ;;
                    3)
                        DELETE=1
                        break
                        ;;
                esac
            done
        else
            # Unquote file names
            tr -d \" < $FILES > $FILES_DELETING
            echo -e "[ Displaying $TOPIC ]\n" |
                cat - $FILES_DELETING | $PAGER
            while :; do
                echo
                read -n1 -ep "Do you want to delete these files? [y/n] "
                case $REPLY in
                    n* | N* ) break ;;
                    y* | Y* ) DELETE=1; break ;;
                    * ) echo "Unknown response." ;;
                esac
            done
        fi
        if [[ $DELETE && -s $FILES_DELETING ]]; then
            # Reading from $FILES_DELETING...
            while read SRC; do
                rm -f $FILESPATH/"$SRC"
            done < $FILES_DELETING
            if [[ $DIAG ]]; then
                dialog --title "Done" --msgbox \
                    "$(crunch "The selected $TOPIC have been \
                    deleted.")" 7 35
            else
                echo "$(crunch "The $TOPIC have been deleted.")"
            fi
        fi
    else
        if [[ $DIAG ]]; then
            dialog --title "ERROR" --msgbox "$(crunch "It appears there are \
                no $TOPIC in $FILESPATH.")" 8 30
        else
            echo "$(crunch "It appears there are no $TOPIC in $FILESPATH.")"
        fi
    fi
    rm -f $FILES{,_CHECKLIST,_DELETING}
}

add_options() {
    # Adds pre-build options to SlackBuild

    local OPTIONPKG=$1
    local ADD_OPTIONS=$SBOPKGTMP/sbopkg_add_options
    local OPTIONFILE=$REPO_DIR/$CATEGORY/$APP/options.sbopkg
    local CUROPTIONS CHOICE CUSTOMOPTS

    if [[ ! -e $OPTIONFILE ]]; then
        unset CUROPTIONS
    else
        CUROPTIONS=$(< $OPTIONFILE)
    fi
    dialog --cancel-label "Clear Options" --inputbox \
        "$(crunch "Some SlackBuild scripts offer the ability to pass \
        variables, or options, or flavors to the SlackBuild scripts before \
        they are run.  This is often noted in the README or the SlackBuild \
        script itself.\n\nIf you would like to set \
        or edit these variables for the $1 SlackBuild, please enter that \
        information below, or press <Clear Options> to clear the options.")" \
        0 0 "$CUROPTIONS" 2> $ADD_OPTIONS
    CHOICE=$?
    CUSTOMOPTS="$(< $ADD_OPTIONS)"
    if [[ $CHOICE == 1 || ( $CHOICE == 0 && -z $CUSTOMOPTS ) ]]; then
        rm -f $OPTIONFILE
        continue
    elif [[ $CHOICE == 0 ]]; then
        cp $ADD_OPTIONS $OPTIONFILE
    fi
}

do_install() {
    # This is mostly equivalent to "upgradepkg --reinstall --install-new $@",
    # but also checks for renames

    local PKG OLDPKG
    local PKG_NAME PKG_VER PKG_ARCH PKG_BUILD PKG_TAG

    for PKG in "$@"; do
        split_pkg_name $PKG
        get_old_name OLDNAME "$PKG_NAME"
        # we grep ls's output rather than have ls return a glob so 'foo'
        # doesn't match 'foo-bar'
        if ! OLDPKG=$(ls /var/log/packages/ |
                grep -m1 "^$OLDNAME-[^-]*-[^-]*-[^-]*$REPO_TAG$"); then
            OLDPKG=$PKG
        fi
        upgradepkg --reinstall --install-new $OLDPKG%"$PKG"
    done
}

install_package() {
    # Install the package.

    local INSTDIR=$1
    local INSTPKG=$2
    local OWNER=$(stat -c %U $INSTDIR/$INSTPKG)
    local GROUP=$(stat -c %G $INSTDIR/$INSTPKG)

    if [[ $OWNER != root && $GROUP != root ]]; then
        crunch_fmt "WARNING:  The file $INSTPKG is not set with root:root \
            permissions!  Do you want to proceed?  Here is the \
            output of ls -l:"
        echo
        ls -l $INSTDIR/$INSTPKG
        echo
        echo "Press (Y)es to proceed or (N)o to abort."
        read ANS
        case $ANS in
            y* | Y* ) echo "Proceeding..." ;;
            n* | N* ) echo "Aborting..."; return 0 ;;
            * ) echo "Unknown response."; break ;;
        esac
    fi
    do_install $INSTDIR/$INSTPKG
    echo "Done upgrading/installing package."
}

error_read() {
    # This function wraps a simple 'read' call. The read itself is skipped if
    # $ON_ERROR != "ask", and the value put in the variable ($1) is "Yes" when
    # $ON_ERROR == "continue", and "No" when $ON_ERROR == "stop".
    # Useful in all those places where the CLI version of sbopkg asks the user
    # what to do on build errors.
    # The automatic answer is printed to stdout, to record it on the permanent
    # build log.

    case $ON_ERROR in
        ask) read $1; return ;;
        stop) eval $1=No; echo -n "No " ;;
        cont*) eval $1=Yes; echo -n "Yes " ;;
    esac
    echo "(as specified with '-e')"
}

build_package() {
    # This function fetches the source tarball and builds the package.
    # $1 = the package path
    # $2 = the package name
    # Returns:
    # 0 if the program built successfully;
    # 1 if the build failed but the user asked to continue the queue
    #   processing
    # 2 if the build failed and the user asked to stop the queue processing
    # When processing a queue, the caller should continue processing the
    # queue items if build_package returns 0, and stop if it returns 1.

    local PKGPATH=$1
    local PKGNAME=$2
    local RETVAL=0

    echo
    echo "Building $PKGNAME"
    # Prepare a temporary output directory
    rm -rf $SB_OUTPUT
    mkdir -p $SB_OUTPUT

    cd $REPO_DIR/$PKGPATH

    # Start the actual build
    # We loop here to enable a 'retry' if anything goes wrong with the build
    while :; do
        # Populate BUILDOPTIONS with any found options.build.  This has to
        # occur outside the subshell below in order to populate $ARCH with any
        # a user-added $ARCH option.  We will fall back to whatever $ARCH is
        # set to originally after the build.
        if [[ -f options.build ]]; then
            BUILDOPTIONS=$(< options.build)
            [[ $BUILDOPTIONS ]] && eval "export $BUILDOPTIONS"
        fi
        # Fetch the sources
        # Note that get_source() "knows" about the source cache, so this isn't
        # necessarily a download.
        # If the sources are successfully fetched, start the build.
        get_source $PKGNAME.info.build && (
            # Run the build in a subshell, to avoid namespace pollution
            echo "Building package for $PKGNAME..."
            export OUTPUT=$SB_OUTPUT
            # Custom LC_COLLATE settings can break scripts (since some
            # expressions, like [A-Z], don't behave as expected -- for
            # example, LC_COLLATE=fr_FR expands it to AbBcC..zZ).
            # See also the comment in /etc/profile.d/lang.sh
            export LC_COLLATE=C
            if [[ $CLEANUP ]]; then
                # We want to remove all the build residuals after running the
                # SlackBuild script. To do that reliably (i.e. without
                # deleting too much or leaving garbage behind us), a nice
                # approach is to use a dedicated build directory.
                export TMP=$SBOPKGTMP/sbopkg-build-directory
                rm -rf $TMP # Just in case
                nice sh $PKGNAME.SlackBuild.build
                echo "Cleaning up..."
                rm -rf $TMP
            else
                nice sh $PKGNAME.SlackBuild.build
            fi
        )

        # Let's see the result
        if [ -f $SB_OUTPUT/*.t?z ]; then
            RETVAL=0
            break
        else
            echo "  Error occurred with build.  Please check the log." \
                >> $TMPSUMMARYLOG
            echo
            echo "$PKGNAME:"
            echo "Would you like to continue processing the rest of the"
            echo "build queue or would you like to abort?  If this failed"
            echo "package is a dependency of another package in the queue"
            echo "then it may not make sense to continue."
            echo
            echo "Press (Y)es to continue, (N)o to abort, (R)etry to try"
            echo "to build the package again."
            while :; do
                error_read ANS
                case $ANS in
                    y* | Y* ) # Continue
                        RETVAL=1
                        break 2
                        ;;
                    n* | N* ) # Abort
                        RETVAL=2
                        rm -f $SBOPKGTMP/sbopkg_build.lck
                        break 2
                        ;;
                    r* | R* ) # Retry
                        continue 2
                        ;;
                    * ) # Huh?
                        echo "Unknown response."
                        ;;
                esac
            done
        fi
    done

    # Cleanup
    cd $REPO_DIR/$PKGPATH
    rm -f $PKGNAME.{info,SlackBuild}.build
    rm -f options.build

    return $RETVAL
}

edit_local_file() {
    # This function allows the user to create and edit a local copy of the
    # SlackBuild or of the info file.
    # $1 = info|SlackBuild (the extension of the customizable file)
    # $2 = application path
    # $3 = application name

    local FILE=$1
    local SHORTPATH=$2
    local APP=$3

    if [[ ! -e $SHORTPATH/$APP.$FILE.sbopkg ]]; then
        cp $SHORTPATH/$APP.$FILE $SHORTPATH/$APP.$FILE.sbopkg
    fi

    $EDITOR $SHORTPATH/$APP.$FILE.sbopkg
}

delete_local_file() {
    # This function allows the user to delete the local SlackBuild.
    # $1 = info|SlackBuild (the extension of the customizable file)
    # $2 = application path
    # $3 = application name

    local FILE=$1
    local SHORTPATH=$2
    local APP=$3

    # FIXME should be checked on the caller side?
    if [[ ! -e $SHORTPATH/$APP.$FILE.sbopkg ]]; then
        dialog --title "ERROR" --msgbox \
            "There is no local copy of the $FILE file to delete." 8 30
    else
        rm $SHORTPATH/$APP.$FILE.sbopkg
        dialog --title "DONE" --msgbox \
            "The local copy of the $FILE file has been deleted." 8 30
    fi
}

diff_local_file() {
    # This function compares the local and original SlackBuild or info file.
    # $1 = info|SlackBuild (the extension of the customizable file)
    # $2 = application path
    # $3 = application name

    local FILE=$1
    local SHORTPATH=$2
    local APP=$3
    local DIFF_FILE=$SBOPKGTMP/sbopkg_custom_diff

    # FIXME should be checked on the caller side?
    if [[ ! -e $SHORTPATH/$APP.$FILE.sbopkg ]]; then
        dialog --title "ERROR" --msgbox \
            "There is no local copy of the $FILE file to compare." 8 30
    else
        $DIFF $DIFFOPTS $SHORTPATH/$APP.$FILE{,.sbopkg} > $DIFF_FILE
        dialog --exit-label "OK" --title "$FILE diff" \
            --textbox $DIFF_FILE 0 0
        rm -f $DIFF_FILE
    fi
}

pick_file() {
    # This function checks to see if there is a locally-edited .info or
    # SlackBuild file (which has the *.sbopkg" suffix) and then asks the
    # user which one he wants to use to build a package.
    # The user can also choose to view a diff of the two before choosing
    # between them.
    # DIFF defines the diff program used and DIFFOPTS the diff options.
    # $1 = info|SlackBuild (the extension of the customizable file)
    # $2 = the package path
    # $3 = the package name
    # Returns 0 if the user did his choice, 1 if ESC was pressed.

    local FILE=$1
    local PKGPATH=$2
    local PKG=$3
    PICKFILE=original
    local ANS ITEM

    rm -f $SBOPKGTMP/sbopkg_file_selection $SBOPKGTMP/sbopkg_diff
    # FIXME slakmagik, what's going on here?
    # (this was added in r446)
    if (( ${#PKGPATH[*]} > 1 )); then
        select ITEM in ${PKGPATH[*]#*/} Quit; do
            if [[ $ITEM == Quit ]]; then
                exit
            fi
            PKGPATH=$ITEM
            if [[ -z $ITEM ]]; then
                echo "$SCRIPT: invalid choice."
                continue
            else
                break
            fi
        done
    fi

    # Build the diff, if there are 2 files to choose between
    if [[ -f $PKGPATH/$PKG.$FILE.sbopkg ]]; then
        $DIFF $DIFFOPTS $PKGPATH/$PKG.$FILE{,.sbopkg} \
            > $SBOPKGTMP/sbopkg_diff
    fi
    # Ask the user which file he wants sbopkg to use.
    if [[ -s $SBOPKGTMP/sbopkg_diff ]]; then
        if [[ $DIAG ]]; then
            while :; do
                dialog --title "Choose $PKG $FILE file" --menu \
                    "$(crunch "A local $FILE file for $PKG was found in \
                    addition to the original file. Which one \
                    would you like to use?")" 12 60 3 \
                    "Local" "Use the local $FILE" \
                    "Original" "Use the original $FILE" \
                    "Diff" "View a diff of the two" \
                    2> $SBOPKGTMP/sbopkg_file_selection

                ANS=$(< $SBOPKGTMP/sbopkg_file_selection)

                case $ANS in
                    Local )
                        PICKFILE="local"
                        break
                        ;;
                    Original )
                        PICKFILE="original"
                        break
                        ;;
                    Diff )
                        dialog --title "Viewing diff of $FILE file" \
                            --textbox $SBOPKGTMP/sbopkg_diff 0 0
                        ;;
                    *)  # The user pressed ESC
                        rm $SBOPKGTMP/sbopkg_diff
                        return 1
                        ;;
                esac
            done
        else
            while :; do
                echo
                crunch_fmt "A local $FILE file for $PKG was found in \
                    addition to the original $FILE file."
                echo "Which one would you like to use?"
                echo
                crunch_fmt "(O)riginal, (L)ocal, (D)iff, (C)ancel:"
                read ANS
                case $ANS in
                    o* | O* )
                        PICKFILE="original"
                        break
                        ;;
                    l* | L* )
                        PICKFILE="local"
                        break
                        ;;
                    d* | D* )
                        $PAGER $SBOPKGTMP/sbopkg_diff
                        ;;
                    c* | C* )
                        rm $SBOPKGTMP/sbopkg_diff
                        cleanup
                        return 1
                        ;;
                    * ) # Huh?
                        echo "Unknown response."
                        ;;
                esac
            done
        fi

        rm $SBOPKGTMP/sbopkg_diff
    fi

    if [[ $PICKFILE == original ]]; then
        cp $PKGPATH/$PKG.$FILE $PKGPATH/$PKG.$FILE.build
    elif [[ $PICKFILE == local ]]; then
        cp $PKGPATH/$PKG.$FILE.sbopkg $PKGPATH/$PKG.$FILE.build
    fi
    return 0
}

use_options() {
    # This functions checks whether the user supplied custom build options.
    # If this is the case, ask the user whether these options should be used.
    # $1 = package path
    # $2 = package name
    # The resulting build options are returned in $BUILDOPTIONS.

    local PKGPATH=$1
    local OPTAPP=$2
    local OPTCHOICE=$SBOPKGTMP/sbopkg_options_choice
    local OPTLIST=$SBOPKGTMP/sbopkg_options_list
    local TMPOPTIONS LDOPTIONS CHOICE OPTIONS_MSG

    # By default (i.e. no options.sbopkg file) there are no build options.
    unset BUILDOPTIONS
    if [[ -f $PKGPATH/options.sbopkg ]]; then
        TMPOPTIONS=$(< $PKGPATH/options.sbopkg)
    fi
    if [[ -f $SBOPKGTMP/sbopkg_"$OPTAPP"_loadoptions ]]; then
        LDOPTIONS=$(< $SBOPKGTMP/sbopkg_"$OPTAPP"_loadoptions)
    fi
    rm -f $PKGPATH/options.build
    if [[ $TMPOPTIONS || $LDOPTIONS ]]; then
        if [[ $DIAG ]]; then
            rm -f $OPTLIST $OPTCHOICE
            if [[ $TMPOPTIONS ]]; then
                echo 'Saved "Build with your saved options"' >> $OPTLIST
                OPTIONS_MSG="\nSaved options:\n$TMPOPTIONS\n"
            fi
            if [[ $LDOPTIONS ]]; then
                echo 'Queuefile "Build with the queuefile options"' \
                    >> $OPTLIST
                OPTIONS_MSG+="\nQueuefile options:\n$LDOPTIONS\n"
            fi
            echo 'None "Build with no options"' >> $OPTLIST
            dialog --title 'Build options' --menu "$(crunch "One or \
                more build option files for the $OPTAPP \
                SlackBuild were found.\n$OPTIONS_MSG\nPlease choose \
                whether to use them.")" 0 0 0 \
                --file $OPTLIST 2> $OPTCHOICE
            CHOICE=$?
            if [[ $CHOICE != 0 ]]; then
                rm -f $OPTLIST $OPTCHOICE
                return 1
            else
                if [[ $(< $OPTCHOICE) == 'Saved' ]]; then
                    echo "$TMPOPTIONS" > $PKGPATH/options.build
                    BUILDOPTIONS="$TMPOPTIONS"
                elif [[ $(< $OPTCHOICE) == 'Queuefile' ]]; then
                    echo "$LDOPTIONS" > $PKGPATH/options.build
                    BUILDOPTIONS="$LDOPTIONS"
                fi
                rm -f $OPTLIST $OPTCHOICE
             fi
        else
            while :; do
                echo "One or more build option files for the $OPTAPP"
                echo "SlackBuild script were found:"
                echo
                if [[ $TMPOPTIONS ]]; then
                    echo "Saved options: $TMPOPTIONS"
                else
                    echo "Saved options: No saved build options found"
                fi
                if [[ $LDOPTIONS ]]; then
                    echo "Queuefile options: $LDOPTIONS"
                else
                    echo "Queuefile options: No queuefile options found"
                fi
                echo
                crunch_fmt "Please choose (N)one, (S)aved, (Q)ueuefile, \
                    or (A)bort"
                read ANS
                case $ANS in
                    n* | N* )
                        break
                        ;;
                    s* | S* )
                        if [[ $TMPOPTIONS ]]; then
                            cp $PKGPATH/options.sbopkg $PKGPATH/options.build
                            BUILDOPTIONS=$TMPOPTIONS
                            break
                        else
                            echo
                            crunch_fmt "ERROR:  No saved options found. \
                                Please make another choice."
                            echo
                        fi
                        ;;
                    q* | Q* )
                        if [[ $LDOPTIONS ]]; then
                            cp $SBOPKGTMP/sbopkg_"$OPTAPP"_loadoptions \
                                $PKGPATH/options.build
                            BUILDOPTIONS=$LDOPTIONS
                            break
                        else
                            echo
                            crunch_fmt "ERROR:  No queuefile options found. \
                                Please make another choice."
                            echo
                        fi
                        ;;
                    a* | A* )
                        return 1
                        ;;
                    * )
                        echo "Unknown response."
                        echo
                        ;;
                esac
            done
            echo
        fi
    fi
}

check_asc() {
    # Check the .asc signature of the tarball
    local CHKPKG=$1
    local GPGNAME=$(basename $CHKPKG)
    local CATEGORY=$(echo $CHKPKG | cut -d/ -f2)

    echo -n "  Checking GPG for $GPGNAME.tar.gz ... " | tee -a $TMPLOG
    if ! gpg --verify $CHKPKG.*.asc > /dev/null 2>&1; then
        echo "FAILED!" | tee -a $TMPLOG
        if [[ ! -e $CHKPKG.tar.gz && ! -e $CHKPKG.*.asc ]]; then
            echo "  No tarball or .asc file found." | tee -a $TMPLOG
        fi
        while :; do
            cat << EOF

Do you want keep the $GPGNAME directory and tarball and continue with the
build process?  Selecting "No" will delete the $GPGNAME directory and
tarball and all local changes to $GPGNAME and its files will be lost.

You can choose among the following options:
 - (Y)es, keep the $GPGNAME directory and tarball and continue the build process;
 - (N)o, delete the $GPGNAME directory and tarball and abort the build process; or
 - (A)bort the build process without deleting anything.
Your choice?
EOF
            error_read ANS
            case $ANS in
                y* | Y* )
                    echo "  Keeping $GPGNAME directory and tarball." |
                        tee -a $TMPLOG
                    return 0
                    ;;
                n* | N* )
                    echo "  Deleting $GPGNAME directory and tarball." |
                        tee -a $TMPLOG
                    rm -rf $PKGPATH; rm $PKGPATH.*
                    return 1
                    ;;
                a* | A* )
                    echo "  Aborting the build process." |
                        tee -a $TMPLOG
                    return 1
                    ;;
                * )
                    echo "Unknown response."
                    ;;
            esac
        done
    else
        echo "OK" | tee -a $TMPLOG
    fi
    tar -C ./$CATEGORY -zxf $CHKPKG.tar.gz
    return 0
}

process_queue() {
    local QUEUETYPE=$1 # build|buildinstall
    local CHKBUILD ANS

    # The first (and largest) of three sections in this function is a precheck
    # section.
    rm -f $TMPLOG $TMPBUILDLOG $TMPSUMMARYLOG $FINALQUEUE
    # Start the precheck
    echo >> $TMPLOG
    echo "###########################################" >> $TMPLOG
    echo "       New queue process started on:" >> $TMPLOG
    echo "       $(date)" >> $TMPLOG
    echo "###########################################" >> $TMPLOG
    echo >> $TMPLOG
    echo "+++++++++++++++++++++++++++++++++++++++++++" >> $TMPLOG
    echo "PACKAGE BUILDING/INSTALLATION PRE-CHECK LOG" >> $TMPLOG
    echo "Using the $REPO_DESC" >> $TMPLOG
    echo -n "Queue Process:" >> $TMPLOG
    if [[ $QUEUETYPE == "buildinstall" ]]; then
        echo "  Build and install" >> $TMPLOG
    else
        echo "  Build only" >> $TMPLOG
    fi
    echo >> $TMPLOG
    echo "Checking GPG signatures.  Please be patient..."
    echo
    for CHKBUILD in $(< $STARTQUEUE); do
        unset PKG PKGPATH PKGNAME VERSION BUILD PICKFILE FILE
        echo "$CHKBUILD:" >> $TMPLOG
        if ! search_package $CHKBUILD; then
            echo "$CHKBUILD not found!" >> $TMPLOG
            echo >> $TMPLOG
            continue
        else
            echo $CHKBUILD >> $FINALQUEUE
        fi
        if [[ ! -z $REPO_GPG ]]; then
            if ! check_asc $PKGPATH; then
                return 0
            fi
        else
            echo -n "  GPG checks not supported for the " >> $TMPLOG
            echo "$REPO_NAME repository." >> $TMPLOG
        fi
        if ! pick_file info $PKGPATH $CHKBUILD; then
            rm -f $PKGPATH/$CHKBUILD*.build
            return 0
        else
            read_info $PKGPATH/$CHKBUILD.info.build
            echo "  Using $PICKFILE .info file" >> $TMPLOG-files
        fi
        if ! pick_file SlackBuild $PKGPATH $CHKBUILD; then
            rm -f $PKGPATH/$CHKBUILD*.build
            return 0
        else
            unset BUILD
            eval $(grep -m1 "^BUILD" $PKGPATH/$CHKBUILD.SlackBuild.build)
            echo "  Using $PICKFILE SlackBuild file" >> $TMPLOG-files
        fi
        echo "  Processing $CHKBUILD $VERSION-$BUILD" >> $TMPLOG
        cat $TMPLOG-files >> $TMPLOG
        rm $TMPLOG-files
        if ! use_options $PKGPATH $CHKBUILD; then
            rm -f $PKGPATH/$CHKBUILD.{info,SlackBuild}.build
            rm -f $PKGPATH/options.build
            return 0
        fi
        if [[ $BUILDOPTIONS ]]; then
            echo "  $BUILDOPTIONS" >> $TMPLOG
        else
            echo "  No build options selected." >> $TMPLOG
        fi
        echo >> $TMPLOG
    done
    echo "+++++++++++++++++++++++++++++++++++++++++++" >> $TMPLOG
    if [[ ! -e $FINALQUEUE ]]; then
        return 1
    fi
    if [[ $DIAG ]]; then
        dialog --title "Pre-Check Log" --ok-label "Start Build" \
            --extra-button --extra-label "Back" --no-cancel \
            --textbox $TMPLOG 0 0
        if [[ $? != 0 ]]; then
            rm -f $PKGPATH/$CHKBUILD.{info,SlackBuild}.build
            rm -f $PKGPATH/options.build
            return 0
        fi
    else
        while :; do
            cat $TMPLOG
            echo
            echo "Pre-check complete."
            echo
            crunch_fmt "Do you wish to proceed based on the search\
                results above? Packages not found will be skipped during\
                the process."
            echo
            echo "Press (Y)es to proceed or (N)o to quit."
            read ANS
            case $ANS in
                y* | Y* )
                    break
                    ;;
                n* | N* )
                    rm -f $PKGPATH/$CHKBUILD.{info,SlackBuild}.build
                    rm -f $PKGPATH/options.build
                    return 0
                    ;;
                * )
                    echo "Unknown response."
                    ;;
            esac
        done
        echo
    fi
    if [[ $KEEPLOG ]]; then
        cat $TMPLOG >> $LOGFILE
    fi
    rm $TMPLOG

    # Okay, precheck done, now start the actual queue processing (build or
    # build+install)
    > $SBOPKGTMP/sbopkg_build.lck
    for PKGBUILD in $(< $FINALQUEUE); do
        if ! search_package $PKGBUILD; then
            echo "$PKGBUILD not found!" >> $TMPLOG
            continue
        fi
        read_info $PKGPATH/$PKGBUILD.info.build --check_buildable
        if [[ $? != 0 ]]; then
            echo >> $TMPSUMMARYLOG
            echo "$PKGBUILD:" >> $TMPSUMMARYLOG
            echo "  Marked as UNSUPPORTED/UNTESTED. Skipping." \
                >> $TMPSUMMARYLOG
            rm -f $PKGPATH/$PKGBUILD.{info,SlackBuild}.build
            rm -f $PKGPATH/$PKGBUILD/options.build
            continue
        fi
        # We test for the lockfile because it may be removed in
        # build_package() when we call that
        if [[ -f $SBOPKGTMP/sbopkg_build.lck ]]; then
            set -o pipefail
            build_package $PKGPATH $PKGBUILD 2>&1 | tee -a $TMPBUILDLOG
            case $? in
                0 ) ;;
                1 ) continue ;;
                * ) break ;;
            esac
            set +o pipefail
            echo "Done building package for $PKGBUILD."
            cd $SB_OUTPUT
            NEWPACKAGE=$(ls -1t *.t?z | head -n1)
            echo "  Building package $NEWPACKAGE ... OK" >> $TMPSUMMARYLOG
            echo "Built package: $NEWPACKAGE"
            if [[ -f $NEWPACKAGE ]]; then
                if [[ $QUEUETYPE == "buildinstall" ]]; then
                    install_package $SB_OUTPUT $NEWPACKAGE
                    echo "  Installing package $NEWPACKAGE ... OK" >> \
                        $TMPSUMMARYLOG
                fi
                mv $NEWPACKAGE $OUTPUT
            fi
        else
            echo >> $TMPSUMMARYLOG
            echo "$PKGBUILD:" >> $TMPSUMMARYLOG
            echo "  Not processed - build queue aborted." >> $TMPSUMMARYLOG
            echo >> $TMPSUMMARYLOG
        fi
    done

    # Done with the main work - now handle some logging and user interaction.
    if [[ $KEEPLOG ]]; then
        cat $TMPBUILDLOG >> $LOGFILE 2>/dev/null
        rm -f $TMPBUILDLOG
    fi
    rm -f $SBOPKGTMP/sbopkg_build.lck
    echo >> $TMPLOG
    echo "+++++++++++++++++++++++++++++++++++++++++++" >> $TMPLOG
    echo "PACKAGE BUILDING/INSTALLATION SUMMARY LOG" >> $TMPLOG
    echo "Using the $REPO_DESC" >> $TMPLOG
    echo -n "Queue Process:" >> $TMPLOG
    if [[ $QUEUETYPE == "buildinstall" ]]; then
        echo "  Build and install" >> $TMPLOG
    else
        echo "  Build only" >> $TMPLOG
    fi
    cat $TMPSUMMARYLOG >> $TMPLOG
    rm $TMPSUMMARYLOG
    echo >> $TMPLOG
    echo "+++++++++++++++++++++++++++++++++++++++++++" >> $TMPLOG
    echo >> $TMPLOG
    echo "###########################################" >> $TMPLOG
    echo "          Queue process complete!" >> $TMPLOG
    echo "###########################################" >> $TMPLOG
    echo >> $TMPLOG
    cat $TMPLOG
    if [[ $DIAG ]]; then
        read -n 1 -p "Press any key to continue."
    fi
    if [[ $KEEPLOG ]]; then
        cat $TMPLOG >> $LOGFILE
    fi
    rm $TMPLOG
    if [[ -f $TMPQUEUE ]]; then
        dialog --title "Clear Queue?" --yes-label "Keep" --no-label \
            "Clear" --yesno "$(crunch "Would you like to keep the build \
            queue or would you like to clear it?")" 8 35
        if [[ $? == 1 ]]; then
            rm $TMPQUEUE
            dialog --title "Done" --msgbox \
                "The build queue has been cleared." 8 35
        fi
    fi
    rm -f $SBOPKGTMP/sbopkg-ans-queue
    rm -f $FINALQUEUE
}

start_dialog_queue() {
    # This kick-starts the queue processing when using the dialog interface.
    # When using cli, the -b or -i option will determine whether we build or
    # build and install.

    dialog --title "Install Package(s)" \
        --help-button --help-label "Cancel" --yesno \
        "$(crunch  "Would you like to install the packages as they are \
            built?  Press <Yes> to install, <No> to build only, or <Cancel> \
            to exit.")" 8 50
    case $? in
        0 ) # Build and install
            process_queue buildinstall
            if [[ $? == 1 ]]; then
                dialog --title "Error" --msgbox "$(crunch "No valid \
                    packages found.")" 8 40
            fi
            ;;
        1 ) # Build only
            process_queue build
            if [[ $? == 1 ]]; then
                dialog --title "Error" --msgbox "$(crunch "No valid \
                    packages found.")" 8 40
            fi
            ;;
        * ) # Cancel, or the user pressed ESC
            return 0
            ;;
    esac
}

check_for_latest() {
    # Check for an update to sbopkg.  This code is borrowed with
    # permission from the superb mirror-slackware-current.sh
    # by Eric Hameleers which you can find at
    # http://www.slackware.com/~alien.  Thanks, Eric!

    local ORIGSCR=http://www.sbopkg.org/currentversion
    local CVRS=$SBOVER
    local NVRS=$(wget -T 10 -q -O - $ORIGSCR)
    local NEWPKG=http://www.sbopkg.org/currentpackage
    local NEWSBOPKG=$(wget -T 10 -q -O - $NEWPKG)
    local NEWDL=http://www.sbopkg.org/currentdownload
    local NEWDLPKG=$(wget -T 10 -q -O - $NEWDL)
    local SBOPKGUP MSG ANS

    if [[ -z $CVRS || -z $NVRS ]]; then
        if [[ -z $NVRS ]]; then
            MSG="Cannot determine if there is an update \
                since the remote version cannot be retrieved. \
                Please try again later."
        fi
    elif [[ $CVRS != $NVRS ]]; then
        if grep -q svn_r <<< $CVRS; then
            MSG="You are running sbopkg from SVN and not using a regular \
                package.  Therefore, package versions cannot be compared."
        else
            SBOPKGUP=1
            if [[ $DIAG ]]; then
                MSG="Different versions reported.  Press <OK> to continue."
            else
                MSG="Different versions reported.  Press (Y)es to \
                    download the update package or press (N)o to quit."
            fi
        fi
    else
        MSG="It appears your version of sbopkg is up to date."
    fi
    if [[ $DIAG ]]; then
        dialog --title "Done" --msgbox "$(crunch "Checking \
            http://www.sbopkg.org for an update...\n\nYour version of \
            sbopkg: $CVRS\n\nLatest version of sbopkg found on \
            sbopkg.org: $NVRS\n\n$MSG")" 16 40
        if [[ $? != 0 ]]; then
            return 0
        fi
        if [[ $SBOPKGUP ]]; then
            dialog --title "Download new package?" --yesno "$(crunch "Would \
                you like to download the new sbopkg \
                package:\n\n$NEWSBOPKG\n\nThe new sbopkg package will saved \
                to your OUTPUT directory: $OUTPUT")" 13 50
            if [[ $? != 0 ]]; then
                return 0
            fi
        fi
    else
        echo
        echo "Checking http://www.sbopkg.org for an update..."
        echo
        echo "Your version of sbopkg: $CVRS"
        echo
        echo "Latest version of sbopkg found on sbopkg.org: $NVRS"
        echo
        crunch_fmt "$MSG"
        if [[ $SBOPKGUP ]]; then
            while :; do
                read ANS
                case $ANS in
                    y* | Y* ) break ;;
                    n* | N* ) return 0 ;;
                    * ) echo "Unknown response." ;;
                esac
            done
        fi
    fi
    if [[ $SBOPKGUP ]]; then
        cd $OUTPUT
        wget $WGETFLAGS $NEWDLPKG
        cd "$CWD"
        crunch_fmt \
            "######################################################\
            \nDownload complete. The downloaded file is located at:\
            \n\
            \n$OUTPUT/$NEWSBOPKG\
            \n\
            \nYou can now quit sbopkg and upgrade sbopkg manually.\
            \n\
            \nOnce you have upgraded sbopkg, please be sure to check \
            the /etc/sbopkg/sbopkg.conf.new file for any changes that may \
            need to be merged into your existing /etc/sbopkg/sbopkg.conf \
            file.\
            \n\
            \nYou can also view a complete ChangeLog.txt in the\
            /usr/share/doc/sbopkg-$NVRS/ directory or online at:\
            \nhttp://www.sbopkg.org/files/ChangeLog.txt\
            \n"
        if [[ $DIAG ]]; then
            read -n 1 -p "Press any key to continue."
        fi
    fi
}

queue_menu() {
    # Separate menu for queue functions.

    local DEFAULTITEM
    local ANSWERFILE=$SBOPKGTMP/sbopkg_queue_menu_answer

    while :; do
        dialog --title "Build Queue Menu" --backtitle \
            "$(eval echo $BACKTITLE)" \
            --cancel-label "Back" --default-item "$DEFAULTITEM" --menu \
            "Choose one of the following or press <Back> to go back.\n" \
            16 60 9 \
            "View" "View the current build queue" \
            "Sort" "Sort items in build queue" \
            "Remove" "Remove items in build queue" \
            "Load" "Load a saved build queue" \
            "Save" "Save the current build queue" \
            "Rename" "Rename a saved build queue" \
            "Delete" "Delete a saved build queue" \
            "Add" "Add all installed packages to the queue" \
            "Process" "Process the current build queue" 2> $ANSWERFILE

        DEFAULTITEM=$(< $ANSWERFILE)

        case "$DEFAULTITEM" in
            "View") view_queue ;;
            "Sort") sort_queue ;;
            "Remove") remove_from_queue ;;
            "Load") load_user_queue ;;
            "Save") save_user_queue ;;
            "Rename") rename_user_queue ;;
            "Delete") delete_user_queue ;;
            "Add") add_all_to_queue ;;
            "Process") view_queue || continue
                       cp $SBOPKGTMP/sbopkg-ans-queue $STARTQUEUE
                       start_dialog_queue ;;
            *) break ;;
        esac
    done

    rm -f $ANSWERFILE
}

utilities_menu() {
    # Separate menu for various utilities.

    local DEFAULTITEM;
    local ANSWERFILE=$SBOPKGTMP/sbopkg_utilities_menu_answer

    while :; do
        dialog --title "Utilities Menu" --backtitle \
            "$(eval echo $BACKTITLE)" \
            --cancel-label "Back" --default-item "$DEFAULTITEM" --menu \
            "\nChoose one of the following or press <Back> to go back.\n" \
            14 69 6 \
            "Cache" "View the contents of the cache directory" \
            "Log" "View the permanent build log" \
            "Repository" "Select repository [ $REPO_BRANCH ($REPO_NAME) ]" \
            "Latest" "Check for an update to sbopkg"  \
            "Uninstalled packages" "View uninstalled packages" \
            "Obsolete sources" "View obsolete cached sources" 2> $ANSWERFILE

        DEFAULTITEM=$(< $ANSWERFILE)

        case "$DEFAULTITEM" in
            "Cache" ) view_cache_dir ;;
            "Log" ) view_perm_log ;;
            "Repository" ) select_repository ;;
            "Latest" ) check_for_latest ;;
            "Uninstalled packages" ) remove_uninstalled_packages ;;
            "Obsolete sources" ) remove_obsolete_sources ;;
            *) break ;;
        esac
    done

    rm -f $ANSWERFILE
}

cleanup() {
    # Clean up cruft and remove temporary files.

    if [[ $HAS_NCURSES ]]; then
        tput cnorm # Restore cursor
    fi
    rm -f $SBOPKGTMP/sbopkg_*
    rm -f $SBOPKGTMP/sbopkgpidlist
    rm -rf $SBOPKGTMP/sbopkg-sbooutputdir
    rm -rf $SBOPKGTMP/sbopkg-build-directory
    rm -f $SBOPKGTMP/sbopkg-*-queue
    rm -f $SBOPKGTMP/sbopkg-tmp-*
    rm -f $PIDFILE

    if [[ $ALLOW_MULTI ]]; then
        if ! rmdir $SBOPKGTMP 2>/dev/null; then
            cat <<EOF
$SCRIPT was unable to remove the temporary folder:

$SBOPKGTMP

since it still contains some file or directory.
Please file a bug report telling us how you managed to trigger this
message and append to it the output of:

find $SBOPKGTMP

EOF
        fi
    fi

    # Back to the directory the user started sbopkg in
    cd "$CWD"
}

control_c() {
    # This function holds the commands that will be executed when the user
    # presses Control-C.  The $SBOPKGTMP/sbopkgpidlist file is the file to
    # which various PID's are written to as certain background processes etc.
    # are executed.

    local PID
    local SBOPKG_PIDLIST=$SBOPKGTMP/sbopkgpidlist

    echo
    echo "Control-C detected.  Trying to exit cleanly...";
    if [[ -f $SBOPKG_PIDLIST ]]; then
        for PID in $(< $SBOPKG_PIDLIST); do
            echo "killing $PID"
            kill -9 $PID;
        done;
        rm $SBOPKG_PIDLIST
    fi
    if [[ ! $DIAG ]]; then
        cleanup
        exit 0
    fi
}

main_search() {
    # This is the main package search gateway, showing the search box dialog
    # and calling the appropriate search function after validating the user
    # input.

    local TERM_FILE=$SBOPKGTMP/sbopkg_search_request
    local PKG STRING SEARCH_TERM
    local REPO=$REPO_DIR

    check_if_repo_exists
    while :; do
        unset PKG STRING

        dialog --title "Search" --ok-label "PKG" \
            --extra-button --extra-label "String" \
            --help-button --inputbox \
            "Enter your search term (prefix your string search with 'inst:' \
                to narrow search to installed packages)..." 11 41 \
                2> $TERM_FILE
        case $? in # 0=PKG 3=String 1=Cancel 2=Help
            3 ) # String search
                STRING=yes ;;
            2 ) # Help
                dialog --title "Search Help" --msgbox \
                    "$(crunch "This widget provides the choice of a package \
                    <PKG> search or a string <String> search.\n\nThe package \
                    search executes a glob search on package names in \
                    $REPO.\n\nThe string search executes 'grep -iwm1 \
                    \"your_string\"' on the README files in the repo. This \
                    means it returns the first matching line from the README \
                    files, where the line contains a case-insensitive word \
                    that matches your string, where a 'word' is a sequence of\
                    alphanumeric characters and underscores. For details, see\
                    the egrep(1) manual page.\n\nIf the search string is \
                    prefixed with \"inst:\" in the form \"inst:your_string\",\
                    then it will search for \"your_string\" within installed \
                    packages only.")" 0 0
                continue
                ;;
            0 ) # Package search
                PKG=yes ;;
            * ) # Cancel or ESC
                break ;;
        esac

        if [[ -s $TERM_FILE ]]; then
            SEARCH_TERM=$(< $TERM_FILE)
            # I can't make sure every input makes sense, but I can at least
            # clear out this area of (fairly improbable) glitches
            if [[ $SEARCH_TERM =~ ^[\\\.\*\^\$\[\{\(\)\+\?\|]$ ]]; then
                dialog --msgbox "$(crunch "If you are searching for the \
                    literal character '$SEARCH_TERM', then you will need to \
                    escape it with a backslash like '\\\\$SEARCH_TERM'.\n\nIf\
                    you are still not getting the expected results, remember \
                    that string searches perform a word match -\
                    try adding '.*'")" 0 0
                continue
            fi
        else
            break
        fi

        if [[ $PKG ]]; then
            gen_search_package "$SEARCH_TERM" || break
        elif [[ $STRING ]]; then
            string_search "$SEARCH_TERM" || break
        fi
    done

    rm -f $TERM_FILE
    return 0
}

main_updates() {
    # This is the dialog gateway for the updates code. Mainly, it runs the
    # updates check and optionally queues the updated packages.

    local APP VERSIONBUILD ONOFF PICK
    local UPDATES_QUEUE=$SBOPKGTMP/sbopkg-update-queue
    local USERQUEUE_LOCK=$SBOPKGTMP/sbopkg_user_queue.lck

    rm -f $UPDATES_QUEUE
    check_for_updates
    if [[ -f $UPDATES_QUEUE ]]; then
        dialog --title "Add Updates to Queue?" --yesno \
            "$(crunch "Would you like to add the flagged updates to \
            the build queue?\\n\\nApparent downgrades will be added to the \
            queue but will be kept disabled.")" 9 50
        if [[ $? == 0 ]]; then
            touch $USERQUEUE_LOCK
            parse_queue $UPDATES_QUEUE
            rm -f $UPDATES_QUEUE $USERQUEUE_LOCK
            dialog --title "Done" --msgbox "$(crunch "The flagged \
                updates have been added to the build queue.")" 8 30
        fi
    fi
}

help_item() {
    dialog --msgbox "$(crunch "For help with sbopkg, please read \
        the manual pages sbopkg(8) and sbopkg.conf(5), the files in \
        /usr/doc/sbopkg-$SBOVER/, join the mailing list at \
        http://sbopkg.org/mailman/listinfo/sbopkg-users, or join #sbopkg \
        on freenode. If you've found a bug please report it to \
        http://code.google.com/p/sbopkg/issues/list.")" 10 70
}

main_menu() {
    # This is the main dialog menu.

    local DEFAULTITEM
    local ANSWER_FILE=$SBOPKGTMP/sbopkg_main_menu_answer

    while :; do
        dialog --cancel-label "Exit" --default-item "$DEFAULTITEM" --title \
            "SlackBuilds.org Package Browser (sbopkg version $SBOVER)" \
            --backtitle "$(eval echo $BACKTITLE)" \
            --menu \
            "\nChoose one of the following or press <Exit> to exit.\n" \
            17 69 9 \
            "Sync" "Sync with the remote repository" \
            "ChangeLog" "View the ChangeLog" \
            "Packages" "List/uninstall installed $REPO_NAME packages" \
            "Updates" "List potential updates to installed $REPO_NAME packages" \
            "Browse" "Browse the active repository" \
            "Search" "Search the active repository" \
            "Queue" "Manage the build queue" \
            "Utilities" "Go to the utilities menu" \
            "Help" "Where to get sbopkg help" 2> $ANSWER_FILE

        DEFAULTITEM=$(< $ANSWER_FILE)

        case "$DEFAULTITEM" in
            "Sync" )
                sync_repo ;;
            "ChangeLog" )
                show_changelog ;;
            "Packages" )
                get_sbo_packages ;;
            "Updates" )
                main_updates ;;
            "Browse" )
                browse_categories ;;
            "Search" )
                main_search ;;
            "Utilities" )
                utilities_menu ;;
            "Queue" )
                queue_menu ;;
            "Help" )
                help_item ;;
            * ) # Exit or ESC
                save_user_queue --end
                clear
                return 0
                ;;
        esac
    done
}

# END OF FUNCTIONS.  What comes below is the actual start of the
# script when it is first run.

# Global variables
# There are two groups of global variables:
# - those that are meant to be global;
# - those that are global only because they are used in this part of the
#   script
# The second group should be reduced/removed. For now, they are simply listed
# here in a group, instead of being commented one by one.
unset SCRIPT          # The script name (usually "sbopkg")
unset REV             # Revision number
unset SBOVER          # The sbopkg version
unset CWD             # sbopkg starting directory
unset DIAG            # When set, run in dialog mode (instead of CLI mode)
unset QUIET           # When set, suppress less important output in CLI mode
unset ON_ERROR        # The policy used in error conditions (see "-e")
unset LAST_USER_QUEUE_ON_DISK # The name of the last loaded/saved user queue
unset BUILDOPTIONS    # TODO
#     SBOPKG_RENAMES_D  # Directory containing files tracking package renames
#     SBOPKG_REPOS_D  # Directory containing repositories definitions
#     SBOPKG_CONF     # Configuration file
#     REPO_ROOT       # Directory containing all repository mirrors
unset HAS_NCURSES     # Set if the ncurses package is installed
unset REPOS_FIELDS    # Number of fields for each repository entry
unset REPO_NAME       # Currently active repository (e.g. SBo)
unset REPO_BRANCH     # Currently active branch (e.g. 13.0)
unset REPO_DESC       # Active branch's description
unset REPO_TAG        # Active branch's packages' tag
unset REPO_TOOL       # Active branch's fetch tool
unset REPO_LINK       # Active branch's fetch link
unset REPO_DIR        # Active branch's directory
unset REPO_GPG        # Active branch's GPG checking
unset CLEANUP         # If set, delete the sources & c. after the build
unset KEEPLOG         # If set, keep a permanent build log
unset ALLOW_MULTI     # If set, allow more that one instance of sbopkg running
unset BUILDLIST       # List of packages to build/install (from CLI)

unset BUILD BFLAG IFLAG CHK_UPDATES GENSEARCH CHANGELOG OBSOLETESRC GETPKGS
unset RSYNC SEARCH UPDATE VERSION CUSTOMVER SKIP_INSTALLED PREVIEW_READMES
unset UNINSTPKG

SCRIPT=${0##*/}
SBOPKG_CONF=${SBOPKG_CONF:-/etc/sbopkg/sbopkg.conf}
SBOPKG_RENAMES_D=${SBOPKG_RENAMES_D:-/etc/sbopkg/renames.d}
SBOPKG_REPOS_D=${SBOPKG_REPOS_D:-/etc/sbopkg/repos.d}
EDITOR=${EDITOR:-vi}
PAGER=${PAGER:-more}
CWD=$(pwd)
REV="$Revision: 836 $"
SBOVER=0.33.2
DIAG=1
ON_ERROR=ask
REPOS_FIELDS=7

# Make sure we are root.
if [[ $(id -u) != 0 ]]; then
    echo "$SCRIPT: $SCRIPT must be run by the root user.  Exiting." >&2
    exit 1
fi

# Set up ARCH - borrowed from SBo SlackBuild template
# Automatically determine the architecture we're building on:
if [[ -z "$ARCH" ]]; then
    case "$( uname -m )" in
        i?86) ARCH=i486 ;;
        arm*) export ARCH=arm ;;
        # Unless $ARCH is already set, use uname -m for all other
        # archs:
        *) export ARCH=$( uname -m ) ;;
    esac
fi

# Set up BACKTITLE
BACKTITLE='Browsing the $REPO_DESC. '
if [[ -n $ARCH ]]; then
    BACKTITLE+='ARCH: $ARCH'
else
    BACKTITLE+='ARCH: default'
fi

# This is the command line options and help.
while getopts ":b:cd:e:f:g:hi:kloPpqrRs:uV:v" OPT; do
    case $OPT in
        b ) # Build
            BFLAG=1
            TYPE="build"
            BUILDLIST="$OPTARG"
            unset DIAG
            ;;
        c ) # Check for updates to installed SBo packages
            CHK_UPDATES=1
            unset DIAG
            ;;
        d ) # Location of the local SBo repository
            REPO_ROOT=$OPTARG
            ;;
        e ) # Default on-error behavior
            ON_ERROR=$OPTARG
            unset DIAG
            ;;
        f ) # Location of the configuration file
            SBOPKG_CONF=$OPTARG
            ;;
        g ) # String search
            GENSEARCH="$OPTARG"
            unset DIAG
            ;;
        i ) # Build and install
            IFLAG=1
            TYPE="buildinstall"
            BUILDLIST="$OPTARG"
            unset DIAG
            ;;
        k ) # Skip installed packages
            SKIP_INSTALLED=1
            unset DIAG
            ;;
        l ) # Show SBo ChangeLog
            CHANGELOG=1
            unset DIAG
            ;;
        o ) # Show obsolete sources
            OBSOLETESRC=1
            unset DIAG
            ;;
        P ) # Show uninstalled packages
            UNINSTPKG=1
            unset DIAG
            ;;
        p ) # List installed SBo packages
            GETPKGS=1
            unset DIAG
            ;;
        q ) # Quiet mode
            QUIET=1
            unset DIAG
            ;;
        r ) # Sync with the remote repository
            SYNC=1
            unset DIAG
            ;;
        R ) # Preview the READMEs before building
            PREVIEW_READMES=1
            unset DIAG
            ;;
        s ) # Name search
            SEARCH="$OPTARG"
            unset DIAG
            ;;
        u ) # Search an updated sbopkg package
            UPDATE=1
            unset DIAG
            ;;
        v ) # print version
            echo $SBOVER
            exit
            ;;
        V ) # Set repository
            VERSION=1
            CUSTOMVER="$OPTARG"
            ;;
        h | * ) # Help
            cat << EOF
$SCRIPT $SBOVER
Usage: $SCRIPT [OPTIONS] <packagename(s)>
Options are:
  -b pkg/queue(s) Build the specified package(s). If one or more queuefiles
                  are specified, build the packages they refer to.
  -c              Check for updates to installed packages.
  -d localdir     Location of local copy of the repositories.
  -e error_action Specify what sbopkg is supposed to do on build errors.
                  Valid options are: ask (default), continue, stop.
  -f file         Override default configuration file with specified file.
  -g package(s)   General search for packages matching string.
  -h              Display this help message.
  -i pkg/queue(s) Like '-b', but also install built packages.
  -k              Skip installed packages when building.
  -l              Display the repo's ChangeLog.txt and then quit.
  -o              Display the obsolete source files & prompt for deletion.
  -P              List uninstalled cached package files & prompt for deletion.
  -p              List installed packages from active repo.
  -q              Quiet some of the command-line output.
  -r              Sync the remote repository with the local mirror and then
                  quit.
  -R              Preview the READMEs before starting the build process.
  -s package(s)   Specific search by specific package and, if found,
                  display package information.
  -u              Check for an update to sbopkg.
  -v              Print sbopkg's version on stdout.
  -V repo/branch  Set the repository/branch. The repo is optional and, if
                  not given, sbopkg will try to make the best match,
                  starting with the default repo. For a list of valid repos,
                  issue '-V ?'

Note: multiple arguments to -b, -g, -i, and -s must be quoted ("pkg1 pkg2")
EOF
            exit
            ;;
    esac
done

# End of option parsing.
shift $(($OPTIND - 1))
if [[ $# -gt 0 ]]; then
    echo "Error: unknown token \"$@\"" >&2
    exit 1
fi

if [[ $BFLAG && $IFLAG ]]; then
    echo "Error:  The -b and -i options cannot be used together." >&2
    echo "Please use one or the other.  Exiting." >&2
    exit 1
fi

if [[ $ON_ERROR != ask && \
      $ON_ERROR != continue && \
      $ON_ERROR != stop ]]; then
    echo "Unknown -e specifier -- \"$ON_ERROR\"" >&2
    echo "Valid values are: ask (default), continue, stop" >&2
    exit 1
fi

# Check for a good config file and set initial variables
config_check
STARTQUEUE=$SBOPKGTMP/sbopkg-start-queue
TMPLOG=$SBOPKGTMP/sbopkg_tmplog
TMPQUEUE=$SBOPKGTMP/sbopkg-tmp-queue
FINALQUEUE=$SBOPKGTMP/sbopkg-final-queue
SB_OUTPUT=$SBOPKGTMP/sbopkg-sbooutputdir
SBOPKGOUTPUT=$SBOPKGTMP/sbopkg_output
TMPBUILDLOG=$SBOPKGTMP/sbopkg-tmp-buildlog
TMPSUMMARYLOG=$SBOPKGTMP/sbopkg-tmp-summarylog

# Change $REPO_BRANCH (and optionally REPO_NAME) if set manually using cli -v
if [[ $VERSION ]]; then
    if [[ $CUSTOMVER == ? ]]; then
        list_repos
        exit 0
    fi
    if [[ $CUSTOMVER =~ .*/.* ]]; then
        # The user specified repository/branch
        eval $(sed 's:^\(.*\)/\(.*\)$:REPO_NAME=\1;REPO_BRANCH=\2:g' \
            <<< $CUSTOMVER)
    else
        # The user specified only the branch -- keep the default repository
        REPO_BRANCH=$CUSTOMVER
        if ! set_repo_vars; then
            # Repository/branch not found -- let's try to be less strict.
            # Try again without enforcing REPO_NAME
            unset REPO_NAME
        fi
    fi
fi
set_repo_vars
if [[ $? -ne 0 ]] ; then
    echo "Unknown repository name -- \"$CUSTOMVER\"" >&2
    list_repos
    exit 1
fi

# Check for required directories
directory_checks

# Check for another instance
[[ $ALLOW_MULTI ]] || pid_check

if [[ $DIAG ]]; then
    if [[ $TERM =~ ^rxvt.* || $TERM =~ ^screen.* ]]; then
        dialog_refresh_workaround
    fi
    main_menu
    cleanup
else
    if [[ $BUILDLIST ]]; then
        MISSING_LIST_FILE=$SBOPKGTMP/sbopkg_addall_missing
        MISSING_SINGLE_FILE=$SBOPKGTMP/sbopkg_add_item_missing
        > $SBOPKGTMP/sbopkg-start-queue
        > $SBOPKGTMP/sbopkg_user_queue.lck
        > $MISSING_LIST_FILE
        > $MISSING_SINGLE_FILE
        for PKGBUILD in $BUILDLIST; do
            if [[ ${PKGBUILD:(-4)} == ".sqf" ]]; then
                parse_queue $QUEUEDIR/$PKGBUILD
                continue
            fi
            if [[ -r $QUEUEDIR/$PKGBUILD.sqf ]] &&
                    search_package $PKGBUILD; then
                crunch_fmt "Both a queuefile and a package were found with \
                    the name \"$PKGBUILD\".  Which would you like to use?"
                echo
                echo "Please enter (Q)ueuefile, (P)ackage, or (A)bort:"
                while :; do
                    read ANS
                    case $ANS in
                        q* | Q* ) parse_queue $QUEUEDIR/$PKGBUILD.sqf
                                  break
                                  ;;
                        p* | P* ) add_item_to_queue $PKGBUILD
                                  break
                                  ;;
                        a* | A* ) cleanup
                                  exit 1
                                  ;;
                        * ) echo "Unknown response." ;;
                    esac
                done
            else
                if [[ -r $QUEUEDIR/$PKGBUILD.sqf ]]; then
                    # Add an entire queue
                    parse_queue $QUEUEDIR/$PKGBUILD.sqf
                else
                    if ! add_item_to_queue $PKGBUILD; then
                        crunch_fmt "Queuefile or package $PKGBUILD not found\
                            - skipping." >> $MISSING_SINGLE_FILE
                        echo
                    fi
                fi
            fi
        done
        rm $SBOPKGTMP/sbopkg_user_queue.lck
        if [[ -s $MISSING_LIST_FILE || -s $MISSING_SINGLE_FILE ]]; then
            cat $MISSING_LIST_FILE
            cat $MISSING_SINGLE_FILE
            echo
            echo "OK to continue processing?"
            while :; do
                error_read ANS
                case $ANS in
                    y* | Y* ) break ;;
                    n* | N* ) cleanup; exit 1 ;;
                    * ) echo "Unknown response." ;;
                esac
            done
        fi
        if [[ ! -e $TMPQUEUE ]]; then
            echo "No valid queuefile or package name given.  Exiting."
            cleanup
            exit 1
        fi
        # Skip installed packages
        if [[ $SKIP_INSTALLED ]]; then
            uncheck_installed $TMPQUEUE
        fi
        # Preview READMEs
        if [[ $PREVIEW_READMES ]]; then
            view_queue_readmes
            echo
            echo "OK to continue processing?"
            while :; do
                read ANS
                case $ANS in
                    y* | Y* ) break ;;
                    n* | N* ) cleanup; exit 1 ;;
                    * ) echo "Unknown response." ;;
                esac
            done
        fi
        # Reading from $TMPQUEUE...
        while read PICK; do
            if can_skip_line $PICK; then
                continue
            fi
            PICK_NAME=${PICK%% *}
            if [[ ${PICK:(-3)} == "OFF" ]]; then
                continue
            else
                if ! grep -qx $PICK_NAME $STARTQUEUE; then
                    echo $PICK_NAME >> $STARTQUEUE
                fi
            fi
        done < $TMPQUEUE
        rm -f $TMPQUEUE
        process_queue $TYPE
        if [[ $? == 1 ]]; then
            echo "No valid packages found, or no packages to build.  Exiting."
            exit 1
        fi
    fi

    if [[ $CHK_UPDATES ]]; then
        check_for_updates
    fi

    if [[ $CHANGELOG ]]; then
        show_changelog
    fi

    if [[ $UNINSTPKG ]]; then
        remove_uninstalled_packages
    fi

    if [[ $OBSOLETESRC ]]; then
        remove_obsolete_sources
    fi

    if [[ $GETPKGS ]]; then
        get_sbo_packages
    fi

    if [[ $SYNC ]]; then
        crunch_fmt "Syncing with the remote repository into $REPO_DIR."
        sync_repo
    fi

    if [[ -n $SEARCH ]]; then
        check_if_repo_exists
        for PKGSEARCH in "$SEARCH"; do
            echo "Searching for $PKGSEARCH"
            if search_package $PKGSEARCH; then
                pick_file info $PKGPATH $PKGSEARCH
                show_readme $PKGPATH $PKGSEARCH
            else
                echo "ERROR: Package \"$PKGSEARCH\" not found." >&2
            fi
        done
    fi

    if [[ $UPDATE ]]; then
        check_for_latest
    fi

    if [[ -n $GENSEARCH ]]; then
        check_if_repo_exists
        for PKGSEARCH in $GENSEARCH; do
            echo "Searching for $PKGSEARCH"
            gen_search_package $PKGSEARCH
        done
    fi

    cleanup
    echo
    echo "All done."
fi

exit 0

# vim:set syn=sh sw=4 et ts=4 tw=78:
# kate: syntax Bash; indent-width 4; replace-trailing-space-save on;
# kate: replace-tabs on; tab-width 4; word-wrap-column 78
