#!/bin/sh
#
# Copyright 1993, 1994, 1995, 1996, 1997,
#    1998, 1999  Patrick Volkerding,  Moorhead, MN  USA
# Copyright 2001, 2004  Slackware Linux, Inc.,  Concord, CA  USA
#    All rights reserved.
#
# Heavily modified and new features added, originally for Slackles Linux:
# Copyright (C) 2005 Lasse Collin <lasse.collin@slackware.fi>
#
# 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.
#

# Wed, 27 Apr 1994 00:06:50 -0700 (PDT)
#  * Optimization by David Hinds.
# Sun Oct 24 23:11:40 BST 2004
#  * Further optimisations by Jim Hawkins <jawkins@armedslack.org>
#    - dramatically improved the speed of the "View" option
# Thu Nov 04 12:19:56 BST 2004
#  * More optimisations by Jim Hawkins
#    - improved "Remove" speed in a similar manner to "View"
# Wed Jan 12 16:53:48 GMT 2005
#  * Fixed quoting bug thanks to Lasse Collin
# Wed Jan 26 23:06:22 GMT 2005
#  * Fix for non-standard package descriptions by Jim Hawkins
# Wed May 18 20:20:29 EEST 2005, Lasse Collin <lasse.collin@slackware.fi>
# - Big changes - I added my name to copyrights. ;-)

# Load shared functions:
. /usr/share/pkgtools/shared_functions.sh

check_is_run_by_root

DIALOGOPTS='--backtitle "Slackles Linux - pkgtools slackles_1.0.0"'
export DIALOGOPTS

is_url_dir() {
  [ "$(echo "$1" | sed -n 's/^\(http\|ftp\):\/\/.*$/\1/p')" = "" ] && return 1 # False
  return 0 # True; it is an URL
}

show_info() {
  case "$MODE" in
    dialog)   dialog --title "$2" --infobox "$1" 3 $((${#1} + 4)) ;;
    cmdline)  echo -e "$1" ;;
    quiet)    ;;
  esac
}

show_msg() {
  case "$MODE" in
    dialog)   dialog --title "$2" --msgbox "$1" 5 $((${#1} + 6)) ;;
    cmdline)  echo -e "$1" ;;
    quiet)    ;;
  esac
}

show_textmode_error_message() {
  echo
  echo "An error occurred. Press enter to return to pkgtool menu."
  read REPLY
}

diskset_description() {
# $1 = diskset name
  DISKSETDESCRIPTION=$(grep -i "^$1 " "$DISKSET_INFO")
  [ "$DISKSETDESCRIPTION" = "" ] && DISKSETDESCRIPTION="$1 \"\" \"\" \\"
  echo "$DISKSETDESCRIPTION"
  unset DISKSETDESCRIPTION
}

# Thanks to Ville Koskinen for the script! http://w-ber.ormgas.com/code/sh_functions.pl
# This tree hopefully clarifies the structure of the repository functions.
#
# repository_list                                     Show the list of repos; Open/Edit/Cancel
# |- repository_prompt_name                           New/Edit/Delete
# |   |- repository_validate_nickname
# |   `- repository_validate_uri
# `- repository_open                                  Ask for filter; Diskset view
#     |- repository_update                            Download/Copy & process FILELIST.TXT & PACKAGES.TXT
#     |- repository_create_updatelist                 List all packages excluding pasture/ and testing/.
#     |- diskset_description                          Look for diskset description from disksets.txt.
#     |- repository_actions                           Install/Download/Update/SelectAll
#     |   |- repository_update
#     |   |- repository_create_updatelist
#     |   `- repository_actions_install
#     |       |- repository_create_install_list       Creates the list of the packages to install/download
#     |       `- repository_show_install_info         Show infobox while downloading and installing
#     `- repository_browse                            Package list view
#         |- repository_create_packagelist_script     Temporary files for each diskset and dialog parameters.
#         |   |- repository_filter_leave_any
#         |   |- repository_filter_remove_blacklisted
#         |   `- repository_filter_remove_exact
#         `- repository_actions
#             |- repository_update
#             |- repository_create_updatelist
#             `- repository_actions_install
#                 |- repository_create_install_list
#                 `- repository_show_install_info

repository_update() {
# $1 = Repo nickname
  if [ "$1" = "" -o ! -d "$REPO_DIR/$1" ]; then
    show_msg "Repository does not exist: $1" "ERROR"
    return 1
  fi
  REPOURI=$(cat "$REPO_DIR/$1/address")
  if is_url_dir "$REPOURI"; then
    [ "$MODE" = "dialog" ] && clear
    echo "+================================================================+"
    echo "| Downloading package information: FILELIST.TXT and PACKAGES.TXT |"
    echo "+================================================================+"
    download "$REPOURI/FILELIST.TXT" "$TMP/$1.FILELIST.TXT" && \
    download "$REPOURI/PACKAGES.TXT" "$TMP/$1.PACKAGES.TXT"
    if [ $? != 0 ]; then
      rm -f "$TMP/$1.FILELIST.TXT" "$TMP/$1.PACKAGES.TXT"
      show_textmode_error_message
      return 1
    fi
  else
    show_info "Copying files: FILELIST.TXT and PACKAGES.TXT" "PLEASE WAIT"
    cp "$REPOURI/FILELIST.TXT" "$TMP/$1.FILELIST.TXT" && \
    cp "$REPOURI/PACKAGES.TXT" "$TMP/$1.PACKAGES.TXT"
    if [ $? != 0 ]; then
      rm -f "$TMP/$1.FILELIST.TXT" "$TMP/$1.PACKAGES.TXT"
      show_msg "Error copying FILELIST.TXT and PACKAGES.TXT from $REPOURI." "ERROR"
      return 1
    fi
  fi
  # Now we should have unbroken *.TXT.
  show_info "Processing package information..." "PLEASE WAIT"
  rm -f "$REPO_DIR/$1/longnames" "$REPO_DIR/$1/shortnames"* "$REPO_DIR/$1/PACKAGES.TXT"
  mv "$TMP/$1.PACKAGES.TXT" "$REPO_DIR/$1/PACKAGES.TXT"
  # Strip all other information except package filenames from FILELIST.TXT.
  # This strips also all files under directory 'source':
  sed -n '/\/source\//d;s/^.* \.\/\([^ ]*-[^-/ ]*-[^-/ ]*-[^-/ ]*\)\.\(tgz\|tlz\|tbz\|tar\)$/\1.\2/p' \
      "$TMP/$1.FILELIST.TXT" > "$REPO_DIR/$1/longnames"
  rm -f "$TMP/$1.FILELIST.TXT"
  # Important security check, please search and report bugs especially here: ;-)
  if [ $(sed -n \
      '/^\([:a-zA-Z0-9.,!@_+-]*\/\)*[a-zA-Z0-9.,!@_+-]*$/!p' "$REPO_DIR/$1/longnames" | wc -c) != 0 ]; then
    #       ^ This double colon is for Linuxpackages compatibility. :-(
    show_msg "Filenames in FILELIST.TXT contain illegal characters." "ERROR"
    rm -f "$REPO_DIR/$1/PACKAGES.TXT"
    return 1
  fi
  # Detect repository type: Official or Other
  if grep -q '^slackware/a/' "$REPO_DIR/$1/longnames"; then
    # Official repository: We don't want to install vulnerable packages
    # so merge 'slackware' to contain all 'patches':
    grep '^slackware/' "$REPO_DIR/$1/longnames" > "$TMP/$1.slackware"
    for I in $(grep '^patches/' "$REPO_DIR/$1/longnames"); do
      sed -i "s/\(slackware\/[a-zA-Z]*\)\/$( \
          package_basename "$I")-[^-]*-[^-]*-[^-]*$/\1\/..\/..\/patches\/packages\/$( \
          basename "$I")/" "$TMP/$1.slackware"
    done
    # Now 'slackware' and 'patches' are merged. Add rest of the packages
    # from other directories and write new $REPO_DIR/$1/longnames:
    sed '/^\(slackware\|patches\)\//d' "$REPO_DIR/$1/longnames" >> "$TMP/$1.slackware"
    rm -f "$REPO_DIR/$1/longnames"
    mv "$TMP/$1.slackware" "$REPO_DIR/$1/longnames"
    # Split disk set listings to different files:
    sed -n 'p;=' "$REPO_DIR/$1/longnames" | sed 'N;s/\n/%/' > "$TMP/$1.numbered"
    for I in $(sed -n 's/^\(slackware\/\|\)\([^/]*\)\/.*$/\2/p' "$REPO_DIR/$1/longnames" | uniq); do
      sed -n '/\(^'$I'\|\/'$I'\)\//{s/.*\/\([^/]*\)\.\(tgz\|tlz\|tbz\|tar\)%\([0-9]*\)$/\1%\3/p}' \
          "$TMP/$1.numbered" | sort -u > "$REPO_DIR/$1/shortnames.$I"
    done
    rm -f "$TMP/$1.numbered"
  else
    # Unofficial repositories will be shown in a one long listing:
    sed -n 'p;=' "$REPO_DIR/$1/longnames" | sed 'N;s/\n/%/' \
        | sed -n 's/^.*\/\([^/]*\)\.\(tgz\|tlz\|tbz\|tar\)%\([0-9]*\)$/\1%\3/p' \
        | sort > "$REPO_DIR/$1/shortnames"
  fi
  # Parse PACKAGES.TXT. This can be tricky with unofficial repositories
  # as they tend to be lower quality than official ones. :-/
  sed -n 's/^PACKAGE NAME: *\([^: ]*\)\.\(tgz\|tlz\|tbz\|tar\) *$/\1:/p' \
      "$REPO_DIR/$1/PACKAGES.TXT" > "$TMP/$1.tmp1"
  sed -n 's/^PACKAGE SIZE (compressed): *\([0-9]*\).*$/[\1 K;/p' "$REPO_DIR/$1/PACKAGES.TXT" > "$TMP/$1.tmp2"
  sed -n 's/^PACKAGE SIZE (uncompressed): *\([0-9]*\).*$/\1 K]/p' "$REPO_DIR/$1/PACKAGES.TXT" > "$TMP/$1.tmp3"
  sed -n '/^PACKAGE DESCRIPTION: *$/{n;s/[\"`$]/\\&/g;s/^[^:]*: *\(.*\)$/\1/;p}' "$REPO_DIR/$1/PACKAGES.TXT" \
      > "$TMP/$1.tmp4"    # Workaround for buggy repos: Do *not* remove this^ semicolon!
  paste -d ' ' "$TMP/$1.tmp1" "$TMP/$1.tmp4" "$TMP/$1.tmp2" "$TMP/$1.tmp3" | sort -u > "$REPO_DIR/$1/itemhelp"
  rm -f "$TMP/$1."*
  [ "$MODE" = "cmdline" ] && echo "Database updated: $1"
  return 0
}

repository_create_install_list() {
# $1 = Repo nickname ; $2 = "install_all" (optional) Install all packages
# instead of only selected. This is used by command line options.
# Creates $TMP/$1.install which has relative paths to packages to be installed.
  rm -f "$TMP/$1.install_numbers"
  if [ "$2" = "install_all" ]; then
    mv "$TMP/$1.updatelist.tmp.0" "$TMP/$1.install_numbers"
  elif [ -f "$TMP/$1.updatelist.tmp.0" ]; then
    paste -d ' ' "$TMP/$1.updatelist.tmp.3" "$TMP/$1.updatelist.tmp.0" \
        | sed -n 's/^"on" //p' > "$TMP/$1.install_numbers"
  else
    for I in "$TMP/$1.shortnames"*.0; do
      if [ "$I" = "$TMP/$1.shortnames*.0" ]; then
        show_msg "No packages selected." "ERROR"
        return 1
      fi
      J=$(echo "$I" | sed 's/\..$//')
      paste -d ' ' "$J.3" "$J.0" | sed -n 's/^"on" //p' >> "$TMP/$1.install_numbers"
    done
  fi
  sed = "$REPO_DIR/$1/longnames" | sed 'N;s/\n/ /' | sort -n - "$TMP/$1.install_numbers" \
      | sed -n '/^[0-9]*$/{n;s/^[0-9]* //p}' > "$TMP/$1.install"
  rm -f "$TMP/$1.install_numbers"
  return 0
}

repository_show_install_info() {
# $1 = Repo nickname ; $2 = Package name ; $3 = "download" or "install"
  K=$(basename "$2")
  if [ "$MODE" = "dialog" ]; then
    # First clear the file. We do not necessarily have PACKAGES.TXT available at all:
    : > "$TMP/tmpargs"
    # Parse description from PACKAGES.TXT and make it exactly 13 lines long.
    # Then add package size information as 14th line.
    [ -f "$REPO_DIR/$1/PACKAGES.TXT" ] && sed -n "/^PACKAGE NAME: *$K *$/,/^$/{s/^[^: ]*: \{0,1\}//;p}" \
        "$REPO_DIR/$1/PACKAGES.TXT" | sed -n '/^PACKAGE /!p
        /^PACKAGE /{s/^PACKAGE SIZE (compressed): *\([0-9]*\).*$/Size: Compressed: \1 K/
        s/^PACKAGE SIZE (uncompressed): *\([0-9]*\).*$/, Uncompressed: \1 K./
        /^PACKAGE .*$/d;H;s/^PACKAGE .*$//};${g;s/\n//g;p}' | sed -n \
        '$!p;${s/^/\n\n\n\n\n\n\n\n\n\n\n\n\n/p}' | sed -n '1,13p;$p' > "$TMP/tmpargs"
    # If no package information was found in PACKAGES.TXT, only package name is shown:
    if [ "$(cat "$TMP/tmpargs")" = "" ]; then
      if [ "$3" = "download" ]; then
        clear
        echo "+=============================================================================="
        echo "| $PKG_COUNT/$PKG_COUNT_TOTAL: Downloading $K"
        echo "+=============================================================================="
      else
        show_info "$K" "$PKG_COUNT/$PKG_COUNT_TOTAL: Installing"
      fi
    else
      if [ "$3" = "download" ]; then
        clear
        echo "$PKG_COUNT/$PKG_COUNT_TOTAL: Downloading $K"
        echo "==============================================================================="
        uniq "$TMP/tmpargs"
        echo "==============================================================================="
      else
        dialog --title "$PKG_COUNT/$PKG_COUNT_TOTAL: $K" --infobox "$(cat "$TMP/tmpargs")" 0 0
      fi
    fi
  elif [ "$MODE" = "cmdline" ]; then
    # In command line mode we never show descriptions, only package names:
    echo "$PKG_COUNT/$PKG_COUNT_TOTAL: Installing $K"
  fi
}

repository_actions_install() {
# $1 = repo nickname ; $2 = "install" or "download"
  # Maybe this is too multimedia but at least it is simple. ;-)
  INSTALL_TIME=$(date +%s)
  # Remove possibly existing old list of *.new files:
  [ "$2" = "install" -a -f "$TMP/dotnew" ] && rm -f "$TMP/dotnew"
  # Create list of packages to install ($TMP/$1.install):
  repository_create_install_list "$1" || return 0
  PKG_COUNT_TOTAL=$(wc -l < "$TMP/$1.install" | tr -d ' ') # tr is needed with busybox
  PKG_COUNT=0
  for J in $(cat "$TMP/$1.install"); do
    PKG_COUNT=$(($PKG_COUNT + 1))
    if [ "$MODE" = "dialog" ]; then
      # First download using installpkg. Makes detecting errors a bit easier
      # and separates download and install process. Just my opinion here.
      if is_url_package "$REPOURI/$J"; then
        repository_show_install_info "$1" "$J" download
        installpkg --quiet --download-only "$REPOURI/$J"
        if [ $? != 0 ]; then
          # If wget returns an error, it's nice to see the error message too
          # before it gets hidden by 'dialog':
          show_textmode_error_message
          continue
        fi
      fi
      if [ "$2" = "install" ]; then
        repository_show_install_info "$1" "$J" install
        # In a special case that package was found in cache but is corrupt,
        # upgradepkg will automatically try to redownload the package once.
        # This should happen only rarely but helps avoiding separate messages
        # and complicated code if the earlier download has had an error.
        upgradepkg --quiet --keep-dotnew --install-new --reinstall "$REPOURI/$J"
        if [ $? != 0 ]; then
          show_msg "Cannot install $(basename "$J"). (Package is corrupt?)" "ERROR"
          continue
        fi
      fi
    else
      # pkgtool called via command line:
      [ "$2" = "download" ] && installpkg --download-only "$REPOURI/$J"
      [ "$2" = "install" ] && upgradepkg --install-new --reinstall "$REPOURI/$J"
    fi
  done
  # Installation/Downloading done. Show some statistics and remind of *.new files, if any:
  INSTALL_TIME=$(($(date +%s) - $INSTALL_TIME))
  printf "Time elapsed: %02d:%02d\n" "$(($INSTALL_TIME / 60))" "$(($INSTALL_TIME % 60))" > "$TMP/tmpargs"
  echo "Number of packages: $PKG_COUNT" >> "$TMP/tmpargs"
  if [ "$2" = "install" ] && is_kernel_packages < "$TMP/$1.install"; then
    echo >> "$TMP/tmpargs"
    show_new_kernel_message >> "$TMP/tmpargs"
  fi
  if [ -f "$TMP/dotnew" ]; then
    echo -e '\nRemember to check the new configuration files:' >> "$TMP/tmpargs"
    cat "$TMP/dotnew" >> "$TMP/tmpargs"
    rm -f "$TMP/dotnew"
  fi
  # Show the list of packages only in dialog mode. Command line users have already it on their screen.
  if [ $PKG_COUNT = 0 ]; then
    show_msg "No packages selected." "ERROR"
  elif [ "$MODE" = "dialog" ]; then
    echo -e "\nList of processed packages:" >> "$TMP/tmpargs"
    sed 's,^.*/,,;s,\.[^.]*$,,' "$TMP/$1.install" >> "$TMP/tmpargs"
    [ "$2" = "install" ] && dialog --title "INSTALLATION COMPLETE" --exit-label OK --textbox "$TMP/tmpargs" 20 76
    [ "$2" = "download" ] && dialog --title "DOWNLOAD COMPLETE" --exit-label OK --textbox "$TMP/tmpargs" 20 76
  elif [ "$MODE" = "cmdline" ]; then
    echo
    cat "$TMP/tmpargs"
  fi
  rm -f "$TMP/$1.install"
}

repository_actions() {
# $1 = Repo nickname ; $2 = Package list filtering mode ; $3 = show_update (optional)
  REPOURI=$(cat "$REPO_DIR/$1/address")
  echo "dialog --title \"REPOSITORY ACTIONS\" --menu \"\" 10 60 4 \\" > "$TMP/tmpscript"
  echo "Install \"Install or upgrade selected packages\" \\" >> "$TMP/tmpscript"
  is_url_dir "$REPOURI" && \
      echo "Download \"Download packages only; do not install\" \\" >> "$TMP/tmpscript"
  [ "$3" = "show_update" ] && echo "Update \"Reload FILELIST.TXT and PACKAGES.TXT\" \\" >> "$TMP/tmpscript"
  [ -f "$REPO_DIR/$1/updatelist.tmp" -a -f "$TMP/$1.updatelist.tmp.3" ] && \
      echo "SelectAll \"Mark all the packages as selected.\" \\" >> "$TMP/tmpscript"
  echo "2> \"$TMP/tmpanswer\"" >> "$TMP/tmpscript"
  . "$TMP/tmpscript"
  [ $? != 0 ] && return 1
  case "$(cat "$TMP/tmpanswer")" in
    Update)
      # We need to remove all cached menu scripts:
      rm -f "$TMP/$1."*
      repository_update "$1"
      [ -f "$REPO_DIR/$1/updatelist.tmp" ] && repository_create_updatelist "$1" "$2"
      return 1
      ;;
    Install)
      repository_actions_install "$1" install
      return 0
      ;;
    Download)
      repository_actions_install "$1" download
      return 0
      ;;
    SelectAll)
      sed -i 's,^.*$,"on",' "$TMP/$1.updatelist.tmp.3"
      return 1
      ;;
  esac
  return 0
}

# repository_filter* functions take one filename as an argument. The file
# contents must be formatted like files 'shortnames': foo-bar-0.12-i486-1baz%987
repository_filter_remove_exact() {
  # Filters out package versions that are already installed.
  sort "$1" "$TMP/installedlist" | sed -n '/%/!{h;:here;g;N;s/^\([^%]*\)\n\1%[0-9]*$//;there;D};p'
}

repository_filter_leave_any() {
  # Filters out all packages that do not have any version installed.
  sort "$1" "$TMP/installedlist%" | sed -n \
    'h;:here;g;N;s/^\([^%]*\)%[^-%]*-[^-%]*-[^-%]*\n\(\1-[^-%]*-[^-%]*-[^-%]*%[0-9]*\)$/\2/p;there;D'
}

repository_filter_remove_blacklisted() {
  sed '/[#% ]/d;/^$/d' "$BLACKLIST_FILE" > "$TMP/blacklist_tmp"
  sort "$1" "$TMP/blacklist_tmp" | sed -n \
    '/%/!{h;:here;g;N;s/^\([^%]*\)\n\1-[^-%]*-[^-%]*-[^-%]*%[0-9]*//;there;D};p'
  rm -f "$TMP/blacklist_tmp"
}

repository_create_packagelist_script() {
# $1 = Repo name ; $2 = shortnames* file (e.g. shortnames.ap)
# $3 = Package list filtering mode ; $4 = Default item (optional)
# Function creates $TMP/tmpscript
  echo dialog --title \"BROWSE PACKAGE REPOSITORY\" --separate-output --item-help \\ > "$TMP/tmpscript"
  [ "$1" != "Custom@" ] && echo --help-button --help-label Details --help-status \\ >> "$TMP/tmpscript"
  [ "$2" = "shortnames" -o "$2" = "updatelist.tmp" ] && echo --ok-label Actions \\ >> "$TMP/tmpscript"
  [ "$4" != "" ] && echo --default-item "$4" \\ >> "$TMP/tmpscript"
  echo "--checklist \"$1 - $(cat "$REPO_DIR/$1/address")\"" 20 76 13 \\ >> "$TMP/tmpscript"
  if [ ! -f "$TMP/$1.$2.0" ]; then
    # Create list of installed packages. Package names that do not match
    # the Slackware spec are filtered out. ('sort' is just to be sure, shouldn't hurt much.):
    ls -1 "$ADM_DIR/packages" | sed -n '/^.*-[^-]*-[^-]*-[^-]*$/p' | sort > "$TMP/installedlist"
    # repository_filter_remove_exact() and repository_filter_leave_any() require different
    # sort order. This can be achieved by converting the installed packages names like this:
    #     foo-bar-0.12-i486-1baz => foo-bar%0.12-i486-1baz
    sed 's/^\(.*\)-\([^-]*-[^-]*-[^-]*\)$/\1%\2/' "$TMP/installedlist" | sort > "$TMP/installedlist%"
    # FIXME: Multiple versions of the same package installed?
    case "$3" in
      All)
        cat "$REPO_DIR/$1/$2" > "$TMP/$1.$2.unsplitted"
        ;;
      AlmostAll)
        repository_filter_remove_blacklisted "$REPO_DIR/$1/$2" \
            | repository_filter_remove_exact - > "$TMP/$1.$2.unsplitted"
        ;;
      New)
        repository_filter_leave_any "$REPO_DIR/$1/$2" | sort - "$REPO_DIR/$1/$2" \
            | uniq -u | repository_filter_remove_blacklisted - > "$TMP/$1.$2.unsplitted"
        ;;
      Updates)
        repository_filter_remove_blacklisted "$REPO_DIR/$1/$2" | repository_filter_leave_any - \
            | repository_filter_remove_exact - > "$TMP/$1.$2.unsplitted"
        ;;
      CustomName)
        grep -i -- "$CUSTOM_FILTER" "$REPO_DIR/$1/$2" > "$TMP/$1.$2.unsplitted"
        ;;
      CustomDesc)
        sed -n '/^PACKAGE NAME:/,/^$/{/^PACKAGE NAME:/{s/^.* \([^ ]*\) *$/\1/;h;D}
            /^$/{g;s/\n/ /g;p};s/^[^: ]*: \{0,1\}//;T;H}' "$REPO_DIR/$1/PACKAGES.TXT" \
            | grep -i -- "$CUSTOM_FILTER" \
            | sed -n 's/^\([^ ]*\) .*$/\1/;/^[a-zA-Z0-9.,!@_+-]*$/!d;s/^\(.*\)-\([^-]*-[^-]*-[^-]*\)$/\1%\2/p' \
            | sort - "$REPO_DIR/$1/$2" | sed -n 'h;:here;g;N
            s/^\([^%]*\)%[^-%]*-[^-%]*-[^-%]*\n\(\1-[^-%]*-[^-%]*-[^-%]*%[0-9]*\)$/\2/p;there;D' \
            > "$TMP/$1.$2.unsplitted"
        ;;
      CustomExactName)
        grep "^$CUSTOM_FILTER%[0-9]*$" "$REPO_DIR/$1/$2" \
            | head -n 1 > "$TMP/$1.$2.unsplitted"
        ;;
    esac
    # Now we have the list of packages to show. Next split the list to two files:
    sed 's/^.*%//' "$TMP/$1.$2.unsplitted" >> "$TMP/$1.$2.0"      # Line numbers in 'longnames'
    sed 's/%[0-9]*$//' "$TMP/$1.$2.unsplitted" >> "$TMP/$1.$2.1"  # Package names
    rm -f "$TMP/$1.$2.unsplitted"
    # Create the list of version numbers of the installed packages into $TMP/$1.$2.2:
    sort "$TMP/installedlist%" "$TMP/$1.$2.1" | sed -n '/%/!{s/^.*$/(New)/p;D};h;:here;g;N
        s/^\([^%]*\)%\([^-]*-[^-]*-[^-]*\)\n\1-\2$/(Installed)/p;there
        s/^\([^%]*\)%\([^-]*-[^-]*-[^-]*\)\n\1-[^-%]*-[^-%]*-[^-%]*$/\2/p
        there;D' > "$TMP/$1.$2.2"
    rm -f "$TMP/installedlist" "$TMP/installedlist%"
    # Set checklist entries initially to not selected:
    sed 's/^.*$/"off"/' "$TMP/$1.$2.0" > "$TMP/$1.$2.3"
    # Search an itemhelp for every package to be listed. If no itemhelp line
    # is found then put empty string "". Output must have the very same number
    # of lines as every $TMP/$1.$2.? have.
    sort "$TMP/$1.$2.1" "$REPO_DIR/$1/itemhelp" | sed -n \
      '/:/!{${g;p;q};N;s/^\([^: ]*\)\n\1: \(.*\)$/\2/p;there;/^[^:]*\n/{x;P;x};:here;D}' \
      | sed 's/.*/"&" \\/' > "$TMP/$1.$2.4"
  fi
  if [ $(wc -l < "$TMP/$1.$2.0") -eq 0 ]; then
    case "$3" in
      Updates) show_msg "No updates available." "NO PACKAGES TO BROWSE" ;;
      CustomExactName) show_msg "Exact package name not found." ;;
      *) show_msg "No packages matching the selected filter." "NO PACKAGES TO BROWSE" ;;
    esac
    return 1
  fi
  paste -d ' ' "$TMP/$1.$2.1" "$TMP/$1.$2.2" "$TMP/$1.$2.3" "$TMP/$1.$2.4" > "$TMP/tmpargs"
  echo "--file \"$TMP/tmpargs\" \\" >> "$TMP/tmpscript"
  echo "2> \"$TMP/tmpanswer\"" >> "$TMP/tmpscript"
}

repository_browse() {
# $1 = Repo nickname ; $2 = shortnames* filename ; $3 = Package list filtering mode
  unset HELPITEM
  # Save old item states for the case the user presses Cancel.
  [ -f "$TMP/$1.$2.3" ] && cp "$TMP/$1.$2.3" "$TMP/$1.$2.tmp"
  while : ; do
    repository_create_packagelist_script "$1" "$2" "$3" "$HELPITEM" || break
    . "$TMP/tmpscript"
    EXITSTATUS=$?
    if [ $EXITSTATUS = 2 ]; then # Show detailed package information
      HELPITEM=$(sed '1s/HELP //;q' "$TMP/tmpanswer")
      sed -n "/^PACKAGE NAME: *$HELPITEM\.t.. *$/,/^$/{s/^[^: ]*: \{0,1\}//;p}" \
          "$REPO_DIR/$1/PACKAGES.TXT" > "$TMP/tmphelp"
      dialog --title "PACKAGE INFORMATION" --exit-label Back --textbox "$TMP/tmphelp" 20 76
      rm -f "$TMP/tmphelp"
      # Update item states file:
      sed 1d "$TMP/tmpanswer" | comm -2 "$TMP/$1.$2.1" - \
          | sed 's/^\t.*$/"on"/;s/^[^"].*$/"off"/' > "$TMP/$1.$2.3"
    elif [ $EXITSTATUS != 0 ]; then # Cancel or escape
      [ -f "$TMP/$1.$2.tmp" ] && cp "$TMP/$1.$2.tmp" "$TMP/$1.$2.3"
      break
    else # Must be OK/Action button
      # Update item states file:
      comm -2 "$TMP/$1.$2.1" "$TMP/tmpanswer" | sed 's/^\t.*$/"on"/;s/^[^"].*$/"off"/' > "$TMP/$1.$2.3"
      # Unofficial repository or official repo updates: Action
      if [ "$2" = "shortnames" -o "$2" = "updatelist.tmp" ]; then
        unset HELPITEM
        # In temporary repository (e.g. 'install from current directory') we don not show 'Update':
        if [ "$1" = "Custom@" ]; then
          repository_actions "$1" "$3" || continue
        else
          repository_actions "$1" "$3" show_update || continue
        fi
      fi
      break
    fi
  done
  [ -f "$TMP/$1.$2.tmp" ] && rm -f "$TMP/$1.$2.tmp"
}

repository_create_updatelist() {
# $1 = Repo name ; $2 = Package list filtering mode
  if [ "$2" = "CustomName" -o "$2" = "CustomDesc" ]; then
    sort "$REPO_DIR/$1/shortnames"* > "$REPO_DIR/$1/updatelist.tmp"
  else
    for I in "$REPO_DIR/$1/shortnames"*; do
      [ "$I" = "$REPO_DIR/$1/shortnames.pasture" -o \
        "$I" = "$REPO_DIR/$1/shortnames.testing" ] && continue
      cat "$I"
    done | sort > "$REPO_DIR/$1/updatelist.tmp"
  fi
}

repository_open() {
# $1 = repo nickname ; $2 = Filter name (optional) ; $3 != "" Update database automatically (optional)
  if [ ! -f "$REPO_DIR/$1/longnames" -o ! -f "$REPO_DIR/$1/PACKAGES.TXT" -o "$3" != "" ]; then
    repository_update "$1" || return
  fi
  if [ "$2" = "" ]; then
    dialog --title "SELECT PACKAGE LIST FILTER" --menu \
        "Filters 'AlmostAll', 'New' and 'Updates' hide blacklisted packages. The package \
blacklist is in the file /etc/pkgtools/blacklist which you can edit with any \
text editor. The file must have one package basename (no version or arch) \
per line. Invalid lines are ingnored." 16 76 6 \
        "All" "Show all available packages (no filtering)" \
        "AlmostAll" "Hide the exact versions installed and blacklisted packages" \
        "New" "Show only packages of which there are no versions installed" \
        "Updates" "Installed packages which have a different version available" \
        "CustomName" "Show package names matching a regular expression" \
        "CustomDesc" "Search package descriptions using a regular expression" \
        2> "$TMP/tmpanswer"
    [ $? != 0 ] && return
    PACKAGE_FILTER=$(cat "$TMP/tmpanswer")
  else
    PACKAGE_FILTER=$2
  fi
  if [ "$PACKAGE_FILTER" = "CustomName" -o "$PACKAGE_FILTER" = "CustomDesc" ]; then
    dialog --title "CUSTOM FILTER" --inputbox \
        "Enter the search string (case insensitive):" 8 60 2> "$TMP/tmpanswer"
    [ $? != 0 ] && return
    CUSTOM_FILTER=$(cat "$TMP/tmpanswer")
  fi
  unset LAST_OPENED_DISKSET
  while : ; do
    if [ -f "$REPO_DIR/$1/shortnames" ]; then
      repository_browse "$1" "shortnames" "$PACKAGE_FILTER"
      break
    elif [ "$PACKAGE_FILTER" = "Updates" \
        -o "$PACKAGE_FILTER" = "CustomName" \
        -o "$PACKAGE_FILTER" = "CustomDesc" ]; then
      [ "$PACKAGE_FILTER" = "Updates" ] && repository_create_updatelist "$1" "$PACKAGE_FILTER"
      [ "$PACKAGE_FILTER" = "CustomName" -o "$PACKAGE_FILTER" = "CustomDesc" ] && \
          sort "$REPO_DIR/$1/shortnames"* > "$REPO_DIR/$1/updatelist.tmp"
      repository_browse "$1" "updatelist.tmp" "$PACKAGE_FILTER"
      rm -f "$REPO_DIR/$1/updatelist.tmp"
      break
    else
      rm -f "$TMP/tmpscript"
      for I in $(cd "$ADM_DIR/setup/repositories/$1" ; ls -1 shortnames.* | sed 's/shortnames\.//'); do
        diskset_description "$I" >> "$TMP/tmpscript"
      done
      sort "$TMP/tmpscript" > "$TMP/tmpargs"
      dialog --title "SELECT DISK SET TO BROWSE" --item-help --ok-label Open  \
          --extra-button --extra-label Actions --default-item "$LAST_OPENED_DISKSET" --menu \
          "$1 - $(cat "$REPO_DIR/$1/address")" 20 76 13 --file "$TMP/tmpargs" 2> "$TMP/tmpanswer"
      EXITSTATUS=$?
      if [ $EXITSTATUS = 3 ]; then # Actions
        if [ "$2" = "" ]; then # Show Update command in actions menu only if we do not have filter predefined.
          repository_actions "$1" "$PACKAGE_FILTER" show_update && break
        else
          repository_actions "$1" "$PACKAGE_FILTER" && break
        fi
      elif [ $EXITSTATUS != 0 ]; then # Cancel or escape
        break
      else # Open
        LAST_OPENED_DISKSET=$(cat "$TMP/tmpanswer")
        repository_browse "$1" "shortnames.$(tr A-Z a-z < "$TMP/tmpanswer")" "$PACKAGE_FILTER"
      fi
    fi
  done
  rm -f "$TMP/$1."*
}

repository_validate_nickname() {
# $1 = new repo nickname ; $2 = old repo nickname (optional)
  if [ "$(echo "$1" | sed -n '/^[a-zA-Z0-9_]*$/p')" = "" ]; then
    show_msg "Repository nickname can contain only characters a-z, A-Z, 0-9 and underscore." "ERROR"
    return 1
  elif [ "$1" != "$2" -a -e "$REPO_DIR/$1" ]; then
    show_msg "Repository with nickname '$1' already exists." "ERROR"
    return 1
  elif [ $(echo -n "$1" | wc -c) -gt 16 ]; then
    show_msg "Repository name is too long. Maximum is 16 characters." "ERROR"
    return 1
  fi
  return 0
}

repository_validate_uri() {
  if ! is_url_dir "$1" && [ "$(echo "$1" | sed -n '/^\/.*/p')" = "" ]; then
    show_msg "Invalid source path or URL." "ERROR"
    return 1
  fi
  return 0
}

repository_prompt_name() {
# $1 = Old repo name ; $2 = Old URI ; Sets $NEWREPONAME and $NEWREPOURI
  echo dialog --title \"ADD/EDIT REPOSITORY\" --max-input 16 \\ > "$TMP/tmpscript"
  [ "$1" != "" ] && echo --extra-button --extra-label Delete \\ >> "$TMP/tmpscript"
  echo "--inputbox \"Please enter a nickname for the package \
repository. The name can be up to 16 characters long and may contain only \
letters, numbers and underscores.\" 10 60 \"$1\" 2> \"$TMP/tmpanswer\"" >> "$TMP/tmpscript"
  . "$TMP/tmpscript"
  EXITSTATUS=$?    # 0=OK ; 3=Delete ; Other=Cancel
  [ $EXITSTATUS = 3 ] && [ -d "$REPO_DIR/$1" ] && rm -rf "$REPO_DIR/$1"
  [ $EXITSTATUS != 0 ] && return 1
  NEWREPONAME=$(cat "$TMP/tmpanswer")
  repository_validate_nickname "$NEWREPONAME" "$OLDREPONAME" || return 1
  dialog --title "ADD/EDIT REPOSITORY" --inputbox \
    "Enter the absolute path or HTTP/FTP URL to the repository. The directory should contain files \
FILELIST.TXT and PACKAGES.TXT which should be formatted according to Slackware specifications." \
10 70 "$2" 2> "$TMP/tmpanswer"
  [ $? != 0 ] && return 1
  NEWREPOURI=$(cat "$TMP/tmpanswer")
  repository_validate_uri "$NEWREPOURI"
  return $?
}

repository_list() {
# $1 = Preselected filter (optional) If filter is specified Edit and
# Add new are not shown. This is used by QuickUpdate feature.
  unset OPENED_REPO
  while : ; do
    echo dialog --title \"PACKAGE REPOSITORIES\" --ok-label Open \\ > "$TMP/tmpscript"
    [ "$1" = "" ] && echo --extra-button --extra-label Edit \\ >> "$TMP/tmpscript"
    [ "$OPENED_REPO" != "" ] && echo --default-item $OPENED_REPO \\ >> "$TMP/tmpscript"
    echo --menu \"Select package repository to view or edit:\" 20 76 13 \\ >> "$TMP/tmpscript"
    for I in $(ls -1 "$REPO_DIR"); do
      echo "$I \"$(cat "$REPO_DIR/$I/address")\" \\" >> "$TMP/tmpscript"
    done
    if [ "$1" = "" ]; then
      echo "--- \"Add new\"" \\ >> "$TMP/tmpscript"
    elif ! ls "$REPO_DIR"/* > /dev/null 2> /dev/null; then
      show_msg "Please add at least one repository before using this feature." "ERROR"
      break
    fi
    echo "2> \"$TMP/tmpanswer\"" >> "$TMP/tmpscript"
    . "$TMP/tmpscript"
    EXITSTATUS=$?
    [ $EXITSTATUS = 1 ] && break
    OPENED_REPO=$(cat "$TMP/tmpanswer")
    if [ "$OPENED_REPO" = "---" ]; then # New is created with both "Open" and "Edit".
      repository_prompt_name "" "" || continue # sets NEWREPONAME and NEWREPOURI
      mkdir "$REPO_DIR/$NEWREPONAME"
      echo "$NEWREPOURI" > "$REPO_DIR/$NEWREPONAME/address"
      OPENED_REPO=$NEWREPONAME
    elif [ "$EXITSTATUS" = "0" ]; then # Open
      rm -f "$REPO_DIR/$OPENED_REPO/updatelist.tmp" "$TMP/$OPENED_REPO."* # Delete repo's temp files.
      if [ "$1" = "" ]; then
        repository_open "$OPENED_REPO"
      else
        repository_open "$OPENED_REPO" "$1" with_database_update
        break
      fi
    elif [ "$EXITSTATUS" = "3" ]; then # Edit
      OLDREPONAME=$OPENED_REPO
      OLDREPOURI=$(cat "$REPO_DIR/$OLDREPONAME/address")
      repository_prompt_name "$OLDREPONAME" "$OLDREPOURI" || continue
      [ "$OLDREPONAME" != "$NEWREPONAME" ] && ( cd "$REPO_DIR" && mv "$OLDREPONAME" "$NEWREPONAME" )
      echo "$NEWREPOURI" > "$REPO_DIR/$NEWREPONAME/address"
      OPENED_REPO=$NEWREPONAME
    fi
  done
}

install_packages() {
# $1 = source directory (optional)
  # If no directory is specified, ask it from the user:
  if [ "$1" = "" ]; then
    dialog --title "SELECT SOURCE DIRECTORY" --inputbox "Please enter the name of the directory that \
you wish to install packages from. To install from current directory just press enter." 10 50 2> "$TMP/tmpanswer"
    [ $? != 0 ] && return
    SOURCE_DIR=$(cat "$TMP/tmpanswer")
    [ "$SOURCE_DIR" = "" ] && SOURCE_DIR=$(pwd)
  else
    SOURCE_DIR=$1
  fi
  # Create a temporary repository:
  rm -rf "$REPO_DIR/Custom@"
  mkdir "$REPO_DIR/Custom@"
  touch "$REPO_DIR/Custom@/PACKAGES.TXT" "$REPO_DIR/Custom@/itemhelp"
  ls -1 "$SOURCE_DIR" | sed -n '/^[a-zA-Z0-9.,!@_/+-]*$/!d;/^.*\.\(tgz\|tlz\|tbz\|tar\)$/p' \
      > "$REPO_DIR/Custom@/longnames"
  if [ $(wc -l < "$REPO_DIR/Custom@/longnames") -eq 0 ]; then
    show_msg "The selected directory has no packages to browse." "NO PACKAGES TO BROWSE"
    rm -rf "$REPO_DIR/Custom@"
    return 1
  fi
  echo "$SOURCE_DIR" > "$REPO_DIR/Custom@/address"
  sed -n 'p;=' "$REPO_DIR/Custom@/longnames" | sed 'N;s/\n/%/' \
      | sed -n 's/^\([^%]*\)\.\(tgz\|tlz\|tbz\|tar\)%\([0-9]*\)$/\1%\3/p' \
      | sort > "$REPO_DIR/Custom@/shortnames"
  repository_open "Custom@"
  rm -rf "$REPO_DIR/Custom@"
  return 0
}

setup_scripts() {
  echo 'dialog --title "SELECT SYSTEM SETUP SCRIPTS" --item-help --checklist \
  "Please use the spacebar to select the setup scripts to run.  Hit enter when you \
are done with selecting the scripts." 20 76 12 \' > "$TMP/setupscr"
  for script in "$ADM_DIR/setup/setup."* ; do
    BLURB=`grep '#BLURB' "$script" | cut -b8-`
    if [ "$BLURB" = "" ]; then
      BLURB="\"\""
    fi
    echo " \"`basename "$script" | cut -f2- -d .`\" $BLURB \"no\" $BLURB \\" >> "$TMP/setupscr"
  done
  echo "2> \"$TMP/return\"" >> "$TMP/setupscr"
  . "$TMP/setupscr"
  if [ ! "`cat "$TMP/return"`" = "" ]; then
    # Run each script:
    for script in `cat "$TMP/return"` ; do
      scrpath=$ADM_DIR/setup/setup.`echo "$script" | tr -d \"`
      rootdevice="`mount | head -n 1 | cut -f 1 -d ' '`"
      ( COLOR=on ; cd "$ROOT/" ; . "$scrpath" / "$rootdevice" )
    done
  fi
  rm -f "$TMP/return" "$TMP/setupscr"
}

format_list_of_installed_packages() {
  if ls "$ADM_DIR/packages" > /dev/null 2> /dev/null; then
    cd "$ADM_DIR/packages"
    { grep '^PACKAGE DESCRIPTION:$' -Z -H -m1 -A1 *; echo; } \
    | sed -n 'h;n;/\x00/{h;n;};x;s/  */ /g;s/ $//;s/[\"`$]/\\&/g
        s/\(.*\)\x00\([^:]*:\)\? *\(.*\)/ "\1" "\3" /;p'
  fi
}

view_installed_package_contents() {
  dialog --title "CONTENTS OF PACKAGE: $1" --no-shadow --exit-label Back \
      --textbox "$ADM_DIR/packages/$1" 0 0 2> /dev/null
}

view_packages() {
  unset DEFITEM
  dialog --title "SCANNING" --infobox "Please wait while \
Pkgtool scans your system to determine which packages you have \
installed and prepares a list for you." 0 0
  (
    echo 'dialog $DEFITEM --menu "Please select the package you wish to view." 20 76 13 \'
    format_list_of_installed_packages | sed 's/$/\\/'
    echo "2> \"$TMP/return\""
  ) > "$TMP/viewscr"
  while : ; do
    . "$TMP/viewscr"
    [ $? != 0 ] && break
    DEFITEM="--default-item $(cat "$TMP/return")"
    view_installed_package_contents "$(cat "$TMP/return")"
  done
  rm -f "$TMP/return" "$TMP/viewscr" "$TMP/tmpmsg"
}

remove_list() {
  dialog --title "SCANNING" --infobox "Please wait while Pkgtool scans \
your system to determine which packages you have installed and prepares \
a list for you." 0 0
  rm -f "$LOG"
  ( umask 0077; echo -n > "$LOG" )
  format_list_of_installed_packages > "$TMP/tmp_instlist"
  sed 's/$/off \\/' "$TMP/tmp_instlist" > "$TMP/tmpargs"
  unset DEFITEM
  while : ; do
    dialog $DEFITEM --title "SELECT PACKAGES TO REMOVE" --separate-output --help-button \
--help-label Details --help-status --checklist "Use the spacebar to select packages to \
delete, and the UP/DOWN arrow keys to scroll up and down through the entire list." \
20 76 12 --file "$TMP/tmpargs" 2> "$TMP/tmpanswer"
    EXITSTATUS=$?
    if [ $EXITSTATUS = 2 ]; then
      DEFITEM=$(sed '1s/HELP //;q' "$TMP/tmpanswer")
      view_installed_package_contents "$DEFITEM"
      DEFITEM="--default-item $DEFITEM"
      sed -n 's/^ "\([^ ]*\)" .*$/\1/p' "$TMP/tmp_instlist" > "$TMP/tmp_a"
      sed 1d "$TMP/tmpanswer" | comm -2 "$TMP/tmp_a" - \
          | sed 's/^\t.*$/"on"/;s/^[^"].*$/"off"/' > "$TMP/tmp_b"
      paste -d ' ' "$TMP/tmp_instlist" "$TMP/tmp_b" | sed 's/$/\\/' > "$TMP/tmpargs"
    else
      if [ $EXITSTATUS = 0 -a $(wc -l < "$TMP/tmpanswer") != 0 ]; then
      remove_packages $(cat "$TMP/tmpanswer")   # Package names better not have spaces.
        dialog --title "PACKAGE REMOVAL COMPLETE" --defaultno --yes-label Delete --no-label Keep \
--yesno "The packages have been removed. A complete log of the files that were \
removed has been created: $TMP/PKGTOOL.REMOVED.\n\n\
Do you like to keep the log file or delete it immediatelly?" 11 54
        [ $? != 0 ] && break
      fi
      rm -f "$TMP/PKGTOOL.REMOVED"
      break
    fi
  done
  rm -f "$TMP/tmp_instlist" "$TMP/tmp_a" "$TMP/tmp_b"
}

remove_packages() {
  for pkg_name in $*
  do
    if [ -r "$ADM_DIR/packages/$pkg_name" ]; then
      dialog --title "PACKAGE REMOVAL IN PROGRESS" --cr-wrap --infobox \
"\nRemoving package $pkg_name.\n\
\n\
Since each file must be checked \
against the contents of every other installed package to avoid wiping out \
areas of overlap, this process can take quite some time. If you'd like to \
watch the progress, flip over to another virtual console and type:\n\
\n\
tail -f $TMP/PKGTOOL.REMOVED\n" 13 60
      removepkg "$pkg_name" >> "$LOG" 2> /dev/null
    else
      echo "No such package: $pkg_name. Can't remove." >> "$LOG"
    fi
  done
}

purge_cache() {
  if [ "$MODE" = "dialog" ]; then
    dialog --title "EMPTY PACKAGE CACHE?" --defaultno --yesno "Removing all the cached \
package files will free $(du -sh "$PACKAGE_CACHE_DIR" | cut -f1) of disk space. This will \
not affect the installed packages. Are you sure you want to empty the package cache now?" 8 48
    [ $? != 0 ] && return
  fi
  show_info "Clearing package cache..."
  rm -rf "$PACKAGE_CACHE_DIR"
  mkdir -m 0755 "$PACKAGE_CACHE_DIR"
}

integrity_check_do_it() {
  unset ERRORFOUND
  ls -1 "$ADM_DIR/packages" > "$TMP/pkglist"
  sed -n '/-upgraded-[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9],[0-9][0-9]:[0-9][0-9]:[0-9][0-9]$/p' \
      "$TMP/pkglist" > "$TMP/upgradefailed"
  sort "$TMP/pkglist" "$TMP/upgradefailed" | uniq -u \
      | sed -n '/[^a-zA-Z0-9.,!@_+-]/p' > "$TMP/illegalchars"
  sort "$TMP/pkglist" "$TMP/upgradefailed" "$TMP/illegalchars" | uniq -u \
      | sed '/^.*-[^-]*-[^-]*-[^-]*$/d' > "$TMP/nonstandard"
  ls -1 "$ADM_DIR/scripts" > "$TMP/scripts"
  sed 's/$/%%%%%/' "$TMP/pkglist" | sort - "$TMP/scripts" \
      | sed -n '/%%%%%$/!{h;N;s/^\(.*\)\n\1%%%%%//;t;g;p}' > "$TMP/scriptproblems"
  if [ $(wc -c < "$TMP/upgradefailed") != 0 ]; then
    echo "# These packages have been left when upgradepkg has hit a fatal error"
    echo "# or it has been interrupted by the user. These leftovers can probably"
    echo "# be removed safely. When in doubt try 'removepkg --warn' first."
    cat "$TMP/upgradefailed"
    echo
    ERRORFOUND=yes
  fi
  if [ $(wc -c < "$TMP/illegalchars") != 0 ]; then
    echo "# These package names contain illegal characters:"
    cat "$TMP/illegalchars"
    echo
    ERRORFOUND=yes
  fi
  if [ $(wc -c < "$TMP/nonstandard") != 0 ]; then
    echo "# These package names do not match the Slackware standard."
    echo "# This error is not serious as it will only prevent pkgtool"
    echo "# detecting if updated versions are available."
    cat "$TMP/nonstandard"
    echo
    ERRORFOUND=yes
  fi
  if [ $(wc -c < "$TMP/scriptproblems") != 0 ]; then
    echo "# The package install scripts below do not have a matching package"
    echo "# installed. This error is not serious but it should never happen."
    echo "# The fix is easy: just remove the files listed below. They are in"
    echo "# the directory $ADM_DIR/scripts."
    cat "$TMP/scriptproblems"
    echo
    ERRORFOUND=yes
  fi
  rm -f "$TMP/pkglist" "$TMP/upgradefailed" "$TMP/illegalchars" \
        "$TMP/nonstandard" "$TMP/scripts" "$TMP/scriptproblems"
  if [ "$ERRORFOUND" != "yes" ]; then
    echo "# No errors were found."
    echo
    return 0
  fi
  return 1
}

integrity_check() {
  if [ "$MODE" = "cmdline" ]; then
    echo
    echo "# Checking the package database integrity..."
    echo
    integrity_check_do_it
  else
    integrity_check_do_it > "$TMP/tmpanswer"
    if [ $? != 0 ]; then
      dialog --title "RESULTS OF THE DATABASE INTEGRITY CHECK" --exit-label Back --textbox "$TMP/tmpanswer" 17 76
    else
      dialog --title "RESULTS OF THE DATABASE INTEGRITY CHECK" --ok-label Back --msgbox \
          "No errors were found." 5 50
    fi
  fi
}

tools_check() {
# $1 = command to check
  echo -n "    $1:"
  I=$((10 - ${#1} - ${#2}))
  while [ $I -gt 0 ]; do
    echo -n ' '
    I=$(($I - 1))
  done
  which "$1" 2> /dev/null
  [ $? != 0 ] && echo "Not found"
}

tools() {
  echo
  echo "Checking for installed helper applications:"
  echo
  echo "The very basic coreutils (e.g. ln, mv, mkdir) are not checked."
  echo
  echo "Required:"
  echo -n "    tar-1.13:  "
  TAR=tar-1.13
  $TAR --help 1> /dev/null 2> /dev/null
  [ ! $? = 0 ] && TAR=tar
  if [ ! "`LC_MESSAGES=C $TAR --version`" = "tar (GNU tar) 1.13

Copyright (C) 1988, 92,93,94,95,96,97,98, 1999 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

Written by John Gilmore and Jay Fenlason." ]; then
    echo "Not found"
  else
    which $TAR
  fi
  tools_check sed
  tools_check grep
  tools_check sort
  tools_check paste
  tools_check comm
  tools_check dialog
  echo
  echo "Recommended:"
  tools_check gzip
  tools_check lzma
  tools_check bzip2
  echo
  echo "For network support:"
  tools_check wget
  echo
}

cmdline_does_repository_exist() {
  if [ "$1" = "" -o ! -d "$REPO_DIR/$1" ]; then
    echo "No repository exist with nickname '$1'."
    exit_pkgtool 1
  fi
}

cmdline_not_enough_arguments() {
  echo "Error: Not enough arguments."
  exit_pkgtool 99
}

exit_pkgtool() {
  rm -f "$TMP/reply" "$TMP/tmpscript" "$TMP/tmpanswer" "$TMP/tmpargs"
  [ "$NO_PID_FILE_CHECK" != "yes" ] && rm -f "$TMP/pkgtool.pid"
  exit $1
}

usage() {
  cat << EOF

Usage: pkgtool [options] [command [command-arguments]]

Options:  -R dir,         Use a different root directory. This option
          --root dir      overrides the \$ROOT environment variable.

          -q, --quiet     Suppress messages except wget progress bar.

Commands: check           Check for basic integrity of package database
                          and report problems found.

          purge           Empty the cache directory of downloaded packages.

          tools           Report which helper applications are installed.

          setup <source> <root_device>
                          Start the Slackware or Slackles installation to
                          the directory specified by --root or \$ROOT.
                          Root device means the device on which the target
                          root filesystem is. E.g. /dev/hda1

Interactive repository commands:
          menu <reponame> <search_regex>
          menu-desc <reponame> <search_regex>
          menu-updates <reponame>
          menu-dir <directory>

Non-interactive repository commands i.e. they ask nothing from the user:
          list
          add <reponame> <address>
          delete <reponame>
          rename <reponame> <newname>
          address <reponame> <newaddress>
          update <reponame>
          search <reponame> <search_regex>
          search-desc <reponame> <search_regex>
          install <reponame> <full_package_name>
          list-updates <reponame>
          install-updates <reponame>

If no command is specified pkgtool starts with the interactive dialog mode.

EOF
  exit_pkgtool 0
}

# Default to dialog UI:
MODE=dialog

# Process command line options:
ARGS=$(getopt -n pkgtool -o +qR:h -l quiet,root:,nopid,help -- "$@")
[ $? != 0 ] && exit 99
eval set -- "$ARGS"
unset ARGS NO_PID_FILE_CHECK
if [ $# != 0 ]; then
  while [ $# != 0 ]; do
    case "$1" in
      -R|--root)    check_root_dir_exists "$2"; ROOT=$2; shift 1 ;;
      -q|--quiet)   MODE=quiet ;;
      --no-pid)     NO_PID_FILE_CHECK=yes ;; # Undocumented, use with caution
      -h|--help)    usage ;;
      --)           shift 1; break ;;
      *)            exit_getopt_error
    esac
    shift 1
  done
fi

# Set some variables that depend on command line options above:
initialize_variables_and_package_database
LOG=$TMP/PKGTOOL.REMOVED
DISKSET_INFO="/usr/share/pkgtools/disksets.txt"

if [ "$NO_PID_FILE_CHECK" != "yes" ]; then
  # Check for PID file. To avoid problems like repository corruption,
  # pkgtool shouldn't be run more than one session at time.
  if [ -f "$TMP/pkgtool.pid" ]; then
    REPLY=$(cat "$TMP/pkgtool.pid")
    if [ -d "/proc/$REPLY" ] && $(grep -q pkgtool "/proc/$REPLY/cmdline"); then
      echo
      echo "Error: pkgtool is already running (PID $REPLY)."
      echo "You cannot start more than one session of pkgtool."
      echo
      exit 99
    fi
  fi
  # Store the new PID:
  rm -f "$TMP/pkgtool.pid"
  echo $$ > "$TMP/pkgtool.pid"
fi

# Process commands:
if [ $# != 0 ]; then
  [ "$MODE" = "dialog" ] && MODE=cmdline
  case "$1" in
    list)
      for I in $(ls -1 "$REPO_DIR" 2> /dev/null); do
        echo "$I - $(cat "$REPO_DIR/$I/address")"
      done
      ;;
    add)
      [ "$3" ] || cmdline_not_enough_arguments
      if repository_validate_nickname "$2" && repository_validate_uri "$3"; then
        mkdir -m 0755 -p "$REPO_DIR/$2"
        echo "$3" > "$REPO_DIR/$2/address"
        show_msg "Repository '$2' added."
      fi
      ;;
    delete)
      cmdline_does_repository_exist "$2"
      if repository_validate_nickname "$2" "$2"; then
        rm -rf "$REPO_DIR/$2"
        show_msg "Repository '$2' deleted."
      fi
      ;;
    rename)
      [ "$3" ] || cmdline_not_enough_arguments
      cmdline_does_repository_exist "$2"
      if repository_validate_nickname "$3"; then
        ( cd "$REPO_DIR" && mv "$2" "$3" )
        show_msg "Repository '$2' renamed to '$3'."
      fi
      ;;
    address)
      [ "$3" ] || cmdline_not_enough_arguments
      cmdline_does_repository_exist "$2"
      repository_validate_uri "$3" && echo "$3" > "$REPO_DIR/$2/address"
      show_msg "Repository '$2' address changed."
      ;;
    update)
      [ "$2" ] || cmdline_not_enough_arguments
      repository_update "$2"
      ;;
    menu-dir)
      [ "$2" ] || cmdline_not_enough_arguments
      if [ ! -d "$2" ]; then
        show_msg "The directory does not exist or is not a directory: $2"
      else
        MODE=dialog
        install_packages "$2"
      fi
      ;;
    search|search-desc|menu|menu-desc|install)
      [ "$3" ] || cmdline_not_enough_arguments
      cmdline_does_repository_exist "$2"
      repository_create_updatelist "$2" CustomName # In repository_create_updatelist CustomName == CustomDesc
      CUSTOM_FILTER=$3
      case "$1" in
        search)
          repository_create_packagelist_script "$2" "updatelist.tmp" CustomName && \
          paste -d ' ' "$TMP/$2.updatelist.tmp.1" "$TMP/$2.updatelist.tmp.2" \
              | sed 's/ \([^(]\)/ (\1/;s/[^)]$/&)/' | sort -u
          ;;
        search-desc)
          repository_create_packagelist_script "$2" "updatelist.tmp" CustomDesc && \
          paste -d ' ' "$TMP/$2.updatelist.tmp.1" "$TMP/$2.updatelist.tmp.2" \
              | sed 's/ \([^(]\)/ (\1/;s/[^)]$/&)/' | sort -u
          ;;
        menu)
          MODE=dialog
          repository_browse "$2" "updatelist.tmp" "CustomName"
          ;;
        menu-desc)
          MODE=dialog
          repository_browse "$2" "updatelist.tmp" "CustomDesc"
          ;;
        install)
          repository_create_packagelist_script "$2" "updatelist.tmp" CustomExactName && \
          repository_create_install_list "$2" install_all && \
          upgradepkg --reinstall --install-new "$(cat "$REPO_DIR/$2/address")/$(cat "$TMP/$2.install")"
          ;;
      esac
      rm -f "$TMP/$2."* "$REPO_DIR/$2/updatelist.tmp"
      ;;
    list-updates|menu-updates|install-updates)
      cmdline_does_repository_exist "$2"
      repository_create_updatelist "$2" Updates
      if repository_create_packagelist_script "$2" "updatelist.tmp" Updates; then
        case "$1" in
          list-updates)
            paste -d ' ' "$TMP/$2.updatelist.tmp.1" "$TMP/$2.updatelist.tmp.2" \
                | sed 's/ \([^(]\)/ (\1/;s/[^)]$/&)/'
            ;;
          menu-updates)
            MODE=dialog
            repository_browse "$2" "updatelist.tmp" Updates
            ;;
          install-updates)
            repository_create_install_list "$2" install_all
            REPOURI=$(cat "$REPO_DIR/$2/address")
            for J in $(cat "$TMP/$2.install"); do
              upgradepkg --reinstall "$REPOURI/$J"
            done
            ;;
        esac
      fi
      rm -f "$TMP/$2."* "$REPO_DIR/$2/updatelist.tmp"
      ;;
    setup)
      # Source setup program functions. These are not included in the main pkgtool
      # to keep it smaller. Setup functions use many functions from pkgtool so it
      # is practical to keep it as a part of pkgtool and not make it a separate script.
      . /usr/share/pkgtools/setup_functions.sh
      # Start the main setup function:
      slackinstall_main "$2" "$3" # $2 = Source path/URL ; $3 = root device (e.g. /dev/hda1)
      ;;
    purge)      purge_cache ;;
    tools)      tools ;;
    check)      integrity_check ;;
    *)          echo "Uknown command '$1'. Type 'pkgtool --help' for list of commands."; exit_pkgtool 99 ;;
  esac
  exit_pkgtool $?
fi

# E.g. "pkgtool -q":
[ "$MODE" != "dialog" ] && exit_pkgtool 1

# Interactive (dialog) mode:
MAIN_MENU_REPLY="QuickUpdate"
while : ; do
  dialog --title "Slackles Package Tool" \
--cancel-label Exit --default-item "$MAIN_MENU_REPLY" \
--menu "\n Welcome to Slackles package tool. Make your choice:" 16 76 8 \
"QuickUpdate" "Shortcut to update repository database & view updates" \
"Repositories" "Use and edit package repositories" \
"Directory" "Install packages without creating a repository" \
"Remove" "Remove packages that are currently installed" \
"View" "View the list of files contained in a package" \
"Setup" "Choose Slackware installation scripts to run again" \
"Purge" "Empty the cache directory of downloaded packages" \
"Check" "Check package database integrity and report errors found" \
2> "$TMP/tmpanswer"
  [ ! $? = 0 ] && exit_pkgtool 0
  MAIN_MENU_REPLY=$(cat "$TMP/tmpanswer")
  case "$MAIN_MENU_REPLY" in
    QuickUpdate)    repository_list "Updates" ;;
    Repositories)   repository_list ;;
    Directory)      install_packages ;;
    Remove)         remove_list ;;
    View)           view_packages ;;
    Setup)          setup_scripts ;;
    Purge)          purge_cache ;;
    Check)          integrity_check ;;
  esac
done
