#!/bin/bash
#  Copyright (C) 2007-2011  Matias A. Fonzo, Santiago del Estero, AR
#
#  This program is free software: you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation, either version 3 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Localización % Idioma:
TEXTDOMAINDIR=/usr/share/locale
TEXTDOMAIN=upgrade

VERSION=2.5

# Funciones #

# Una función para mostrar mensajes normales:
msg() { local LC_ALL ; printf '%s\n' "$@"; }

# Una función para mensajes de advertencia:
warn() { local LC_ALL; printf '%b\n' "$@" >&2; }

usage() {
  msg $"Upgrade one or more packages in your system."                          \
       ""                                                                      \
      $"Usage: upgrade [options] package_name.tlz ..."                         \
       ""                                                                      \
      $"Short options:"                                                        \
      $"  -h                        Show this help and exit."                  \
      $"  -V                        Show the version of the program."          \
      $"  -f                        Force the upgrade if the"                  \
      $"                            package is already installed."             \
      $"  -i                        Install the package if not had installed." \
      $"  -v                        Enable verbose mode."                      \
      $"  -w                        Warn about of the packages that"           \
      $"                            will be upgraded."                         \
       ""                                                                      \
      $"Note:"                                                                 \
       ""                                                                      \
      $"  Use the percent symbol (%) if the packages have"                     \
      $"  different names.  Like, old_package%new_package"                     \
       ""
}

version() {
  msg "upgrade $VERSION"                                                       \
      "Copyright (C) 2007-2011 Matias A. Fonzo <selk@dragora.org>."            \
      "License GPLv3+: GNU GPL version 3 or later:"                            \
      "<http://gnu.org/licenses/gpl.html>"                                     \
      "This is free software: you are free to change and redistribute it."     \
      "There is NO WARRANTY, to the extent permitted by law."
}

# Opciones:
OPTIND=1  # Resetea la variable.
while getopts ":fivwhV" opt
do
  case "$opt" in
    f)
      FORCE=on
      ;;
    i)
      INSTALL=on
      ;;
    v)
      VERBOSE=on
      ;;
    w)
      WARN=on
      ;;
    h)
      usage
      exit 0
      ;;
    V)
      version
      exit 0
      ;;
    \?)
      warn $"${0##*/}: Invalid option: -${OPTARG}"
      exit 1
      ;;
  esac
done
shift $((OPTIND - 1))

# Si no hay argumentos, llama a la función de ayuda:
(( $# == 0 )) && { usage ; exit 0; }

# Comprobamos la variable de entorno ROOT:
if [[ -n $ROOT && ! -d $ROOT ]]; then
  warn $"${0##*/}: ROOT=${ROOT}: Invalid directory"
  exit 1;
fi

# Chequeo de sanidad.
#
# Comprueba si el sistema de archivos
# se encuentra en modo de sólo-lectura:
if ! touch ${ROOT}/pkg-upgrade$$ ; then
  warn $"The filesystem (on $ROOT) is in read-only mode."
  exit 1;
fi
rm -f ${ROOT}/pkg-upgrade$$

# Sale ante cualquier error:
set -e

# Base de datos por defecto:
DB="${ROOT}/var/db"
if [[ ! -d $DB ]]; then
  mkdir -p "$DB"
fi

# Más funciones #

# Una función para reflejar el nombre base:
_basename() { local name ; name=${1##*/} ; printf "${name%$2}"; }

# Función que imprime el nombre del paquete:
pkg_name() {
  string=$(_basename $1 .tlz)
  seg=${string//[![-]}  ## Toma los segmentos "-".
  seg=${#seg}           ## Los cuenta.
  case "$seg" in
    0)   printf "$string"                        ;;
    1)   printf "${string%-*}"                   ;;
    2|3) printf "${string%%-*}"                  ;;
    4)   cut -f 1-2 -d - <<<"$string"            ;;
    *)   cut -f 1-$((seg - 2)) -d - <<<"$string" ;;
  esac
}

# Función para remover los archivos de paquetes viejos:
move_old() {
  for file in "$@" ; do
    if [[ -r $file ]]; then
      if [[ $(pkg_name $file) = $short ]]; then
        mv $file ${file}.UPGRADED_${timestamp}
      fi
    fi
  done
}

# Hace que algunos comandos corran más rápido:
export LC_ALL=C

# Loop:
for package in "$@" ; do
  # Figura hacia afuera el nombre del nuevo paquete:
  newpkg=${package##*%}

  # Comprueba la integridad del archivo de forma simple:
  if [[ ! -f $newpkg ]]; then
    warn $"${0##*/}: ${newpkg}: File not found or non-regular"
    CODE=1
    continue;
  fi

  # Comprueba la extensión del paquete:
  case "$newpkg" in
    *.tlz)
      dirpkg=$(dirname $newpkg)
      newpkg=$(_basename $newpkg .tlz)
      ;;
    *)
      warn $"${newpkg}: Does not end in .tlz"
      CODE=1
      continue;
  esac

  # Figura hacia afuera el nombre del viejo paquete:
  oldpkg=${package%%%*}
  oldpkg=$(_basename $oldpkg .tlz)

  # Comprueba si hay un paquete instalado para su actualización:
  short="$(pkg_name $oldpkg)"

  if [[ ! -r ${DB}/pkg/$oldpkg ]]; then
    if ls ${DB}/pkg/${short}* > /dev/null 2>&1 ; then
      for installed_package in ${DB}/pkg/${short}* ; do
        if [[ $(pkg_name $installed_package) = $short ]]; then
          oldpkg=${installed_package##*/}
          break;
        fi
      done
    fi
  fi
  if [[ ! $INSTALL = on ]]; then
    if [[ ! -f ${DB}/pkg/$oldpkg ]]; then
      if [[ $WARN != on ]]; then
        warn $"${DB}/pkg/${oldpkg}: Package not found in the database"
        CODE=1
        continue;
      else
        msg $"${0##*/}: ${short}: Package not installed"
        CODE=0
        continue;
      fi
    fi
  else
    if [[ $FORCE = on ]]; then
      OPT=-f
    fi
    ROOT=$ROOT pkg add $OPT ${dirpkg}/${newpkg}.tlz
    CODE=$?
    if (( $CODE == 0 )); then
      continue;
    else
      warn "" $"An error occurred while trying to install the package" ""
      break;
    fi
  fi

  # Verifica si el paquete para actualizar ya está instalado:
  if [[ ! $FORCE = on ]]; then
    if [[ $oldpkg = $newpkg ]]; then
      if [[ $WARN != on ]]; then
        warn $"${DB}/pkg/${newpkg}: Already installed"
        CODE=3
        continue;
      else
        msg $"${newpkg}: Would be skipped (already installed)"
        CODE=0
        continue;
      fi
    fi
  fi

  # Movemos los paquetes viejos para removerlos fácilmente:
  printf -v timestamp '%+(%Y%m%d_%T)T' -1  ## Formato fecha y hora.

  # Renombra paquetes instalados y descripciones:
  move_old \
   "${DB}/pkg/${short}"* \
   "${DB}/pkg/pre-post/${short}"* \
   "${DB}/pkg/post-install/${short}"* \
   "${DB}/pkg/description/${short}"*

  # Antes de remover e instalar el nuevo paquete, necesitamos pre-instalarlo,
  # puesto que podría ser crucial si se trata de una herramienta principal:
  msg $"Pre-installing $newpkg ..." ""
  ROOT=$ROOT pkg add -f ${dirpkg}/${newpkg}.tlz >/dev/null; CODE=$?
  if (( $CODE != 0 )); then
    warn "" $"An error occurred while trying to install the package" ""
  fi

  # Remueve el viejo e instala el nuevo paquete. Si se encuentran más
  # paquetes viejos con el mismo nombre, los remueve igualmente:
  msg $"$oldpkg <-> ${newpkg}.tlz ..."

  ( cd ${DB}/pkg
    # De esta forma mantenemos límpia la base de datos:
    for removepkg in *.UPGRADED_${timestamp}; do
      if [[ ! $VERBOSE = on ]]; then
        ROOT=$ROOT pkg remove -q $removepkg > /dev/null
      else
        ROOT=$ROOT pkg remove $removepkg
      fi
    done
    # Borra las descripciones del viejo paquete:
    if [[ -d ./description ]]; then
      ( cd ./description
        for file in *.UPGRADED_${timestamp}; do
          rm -f $file
        done
      )
    fi
  )

  # Nuevamente instalamos el nuevo paquete:
  ROOT=$ROOT pkg add -f ${dirpkg}/${newpkg}.tlz

  msg "" $"${oldpkg}: Upgraded with the package: ${dirpkg}/$newpkg" ""
done

exit $CODE

