#!/bin/bash

# Copyright Jean-Philippe Guillemin <h1p8r10n@yandex.com>. 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 2 of the License, or (at your option)
# any later version. Please take a look at http://www.gnu.org/copyleft/gpl.htm
#
# Netpkg is a tool for easily install or upgrade packages via the network. With Netpkg,
# you can make a minimal installation of Zenwalk Linux and install/upgrade just the
# packages you need the most.
#
#


V="12.0"
zenwalk_version="$(</etc/zenwalk-version)"

# Globals #####################################
export LANG=en_US
export configFile="/etc/netpkg.conf"
export PATH="/usr/libexec:/sbin:/bin:/usr/sbin:/usr/bin"

. /etc/shell-colors


# Parsing config files ######################################

netpkgDir=$(sed -n 's/^[[ \t]*Netpkg_dir[[ \t]*=[[ \t]*\(.*\)$/\1/p' $configFile)
export netpkgDir="${netpkgDir:=/var/netpkg}"
mkdir -p $netpkgDir 2>/dev/null
# rm -f $netpkgDir/.tmp*

remotesDir=$(sed -n 's/^[[ \t]*Remotes_dir[[ \t]*=[[ \t]*\(.*\)$/\1/p' $configFile)
export remotesDir="${remotesDir:=/etc/netpkg.d}"
mkdir -p $remotesDir 2>/dev/null
export sourceslist=$(ls /etc/netpkg.d/)

localPkgDir=$(sed -n 's/^[[ \t]*Local_repository[[ \t]*=[[ \t]*\(.*\)$/\1/p' $configFile)
export localPkgDir="${localPkgDir:=/var/packages}"
mkdir -p ${localPkgDir}

packageLogs=$(sed -n 's/^[[ \t]*Package_logs[[ \t]*=[[ \t]*\(.*\)$/\1/p' $configFile)
export packageLogs="${packageLogs:=/var/log/packages}"

export blacklist="${blacklist:=aaa_base}"

keepit=$(sed -n 's/^[[ \t]*Keep_packages[[ \t]*=[[ \t]*\(.*\)$/\1/p' $configFile)
export keepit="${keepit:=yes}"

untouchable=$(sed -n 's/^[[ \t]*Protected_files[[ \t]*=[[ \t]*\(.*\)$/\1/p' $configFile)
export untouchable="${untouchable:=/etc/lilo.conf /boot/grub/grub.cfg /etc/fstab}"

processDependencies=$(sed -n 's/^[[ \t]*Handle_dependencies[[ \t]*=[[ \t]*\(.*\)$/\1/p' $configFile)
export processDependencies="${processDependencies:=no}"

export http_proxy=$(sed -n 's/^[[ \t]*Proxy_Socket[[ \t]*=[[ \t]*\(.*\)$/\1/p' $configFile)
export https_proxy=$http_proxy
export ftp_proxy=$http_proxy
export proxyusr=$(sed -n 's/^[[ \t]*Proxy_User[[ \t]*=[[ \t]*\(.*\)$/\1/p' $configFile)
export proxypwd=$(sed -n 's/^[[ \t]*Proxy_Password[[ \t]*=[[ \t]*\(.*\)$/\1/p' $configFile)
export WGET="wget --no-check-certificate "
[[ "$proxyusr" ]] && export WGET="$WGET --proxy-user=${proxyusr} "
[[ "$proxypwd" ]] && export WGET="$WGET --proxy-password=${proxypwd} "

logFile=$(sed -n 's/^[[ \t]*Logfile[[ \t]*=[[ \t]*\(.*\)$/\1/p' $configFile)
if [[ "$logFile" == "none" ]]; then
  export logFile="/dev/null"
else
  export logFile="${logFile:=/var/log/netpkg.log}"
fi

# Get Host part of URL $1
getHostFromURL(){

## Remove protocol part of URL  ##
    HOST="${1#http://}"
    HOST="${HOST#https://}"
    HOST="${HOST#ftp://}"
    HOST="${HOST#scp://}"
    HOST="${HOST#scp://}"
    HOST="${HOST#sftp://}"

    ## Remove username and/or username:password part of URL  ##
    HOST="${HOST#*:*@}"
    HOST="${HOST#*@}"

    ## Remove rest of URL ##
    HOST=${HOST%%/*}

    ## Show domain name only ##
    echo -n "$HOST"
}

mill[0]="|" ; mill[16]="/" ; mill[32]="-" ; mill[48]="\\"


# Enable deps in netpkg.conf
function enableDependencies(){

  sed -i "s|.*Handle_dependencies.*|Handle_dependencies = yes|" $configFile
  echo -e "${BOLDWHITE}Dependency processing${COLOR_RESET} has been ${BOLDGREEN}enabled${COLOR_RESET}"
}

# Disable deps in netpkg.conf
function disableDependencies(){

  sed -i "s|.*Handle_dependencies.*|Handle_dependencies = no|" $configFile
  echo -e "${BOLDWHITE}Dependency processing${COLOR_RESET} has been ${BOLDRED}disabled${COLOR_RESET}"
}

# fetch the packages list for available packages
function getRemotePkgs(){
  cut -d " " -f2 $netpkgDir/RemotePkgs.db
}

function getLocalPkgs(){
  sort $netpkgDir/LocalPkgs.db
}

# fetch the packages list for package named $1
function getPkgFromshortName(){
  cut -d " " -f2 $netpkgDir/RemotePkgs.db | sed -n "s/^\($1-[^\-]*-[^\-]*-[^\-]*\.t[glx]z\)$/\1/p" | head -n1
}

# The uniq name for pkg
function getShortName(){
  local shortName=${1%-*}; shortName=${shortName%-*}; shortName=${shortName%-*}
  echo -n "$shortName"
}

# Get the location for package $1
function getPkgLocation(){
  location="$(sed -n "s/^\([^ \t]*\)[[ \t]*$1[[ \t]*$/\1/p" $netpkgDir/RemotePkgs.db)"
  if [[ "$location" ]] ; then
    echo -n "$location"
  else
    echo -n "local"
  fi
}

# Get the location for package $1
function getVersion(){
  local package="$1"
  local version=${package%-*}; version=${version%-*}; version=${version##*-}
  echo -n "$version"
}

function getSource(){
  source="$(sed -n "s/^\([^ \t]*\)[[ \t]*$1[[ \t]*$/\1/p" $netpkgDir/RemotePkgs.db | cut -d"/" -f3)"
  if [[ "$source" ]] ; then
    echo -n "$source"
  else
    echo -n "local"
  fi
}

# Build a list of installed packages
function buildLocalPkgsDb(){
  rm -f $netpkgDir/LocalPkgs.db
  for meta in $(ls $packageLogs) ; do
    echo $meta.txz >> $netpkgDir/LocalPkgs.db
  done
}

function installKernel(){
  if [[ -x /usr/sbin/install-kernel.sh ]]; then
    echo "Checking for new kernel to install ... "
    /usr/sbin/install-kernel.sh
  fi
}

# Download and process the meta file
function fetchRemotes(){
  rm -f "$netpkgDir/.tmp*"

  if [[ "$source" != 'local' ]] ; then
    echo 'Cleaning cache'
    rm -f $netpkgDir/RemotePkgs.db 2>/dev/null
    rm -f $netpkgDir/RemoteDeps.db 2>/dev/null
    rm -f $netpkgDir/PACKAGES.TXT* 2>/dev/null
    rm -f $netpkgDir/*.md5 2>/dev/null
    for source in $(ls $remotesDir/) ; do

      [[ "$source" == 'local' ]] && continue

      rm -f $netpkgDir/$source.TXT*
      mv $netpkgDir/RemotePkgs.db.$source $netpkgDir/OldRemotePkgs.db.$source 2>/dev/null
      mv $netpkgDir/RemoteDeps.db.$source $netpkgDir/OldRemoteDeps.db.$source 2>/dev/null

      RemoteURL="$(getRemoteURL $source)"

      echo -e "Connecting to $source..."
      neterror=$($WGET -O $netpkgDir/$source.TXT.gz $RemoteURL/PACKAGES.TXT.gz 2>&1 | grep -E 'failed:|Not Found')
      if [[ "$neterror" ]] ; then
        neterror=$($WGET -O $netpkgDir/$source.TXT $RemoteURL/PACKAGES.TXT 2>&1 | grep -E 'failed:|Not Found')
        if [[ "$neterror" ]] ; then
          echo "Unable to connect to $RemoteURL, please check the network or choose another source"
          return 1
        fi
      else
        echo 'Uncompressing meta information'
        if ! gunzip -f $netpkgDir/$source.TXT.gz 2>/dev/null ; then
          echo 'Unable to extract meta information, please check the network or choose another source'
          return 1
        fi
      fi

      echo 'Downloading MD5 information'
      neterror=$($WGET -O $netpkgDir/$source.md5.gz $RemoteURL/CHECKSUMS.md5.gz 2>&1 | grep -E 'failed:|Not Found')
      if [[ "$neterror" ]] ; then
        neterror=$($WGET -O $netpkgDir/$source.md5 $RemoteURL/CHECKSUMS.md5 2>&1 | grep -E 'failed:|Not Found')
        if [[ "$neterror" ]] ; then
          echo "Unable to connect to $RemoteURL, please check the network or choose another source"
          return 1
        fi
      else
        echo 'Uncompressing MD5 information'
        if ! gunzip -f $netpkgDir/$source.md5.gz 2>/dev/null ; then
          echo 'Unable to extract MD5 information, please check the network or choose another source'
          return 1
        fi
      fi

      # cleaning PACKAGE SOURCE: lines
      sed -i '/^PACKAGE MIRROR:.*/d' $netpkgDir/$source.TXT

      # Filter out blacklisted packages
      echo 'Filtering out blacklisted packages'
      blacklist="$(getRemoteBlacklist $source)"
      for pkg in $blacklist ; do
        grep -v -e "^PACKAGE NAME: *$pkg *" $netpkgDir/$source.TXT > $netpkgDir/.tmpPkgList.$$
        cat $netpkgDir/.tmpPkgList.$$ > $netpkgDir/$source.TXT
      done
      rm -f $netpkgDir/.tmpPkgList.$$
      cat $netpkgDir/$source.TXT >> $netpkgDir/PACKAGES.TXT

      if [[ "$processDependencies" == 'yes' ]] ; then
        echo 'Computing packages dependencies'
        sed -n '
        /^PACKAGE NAME:.*/{
        N;N;N;N
        s/,/ /g
        s/^PACKAGE NAME:[[ \t]*\(.*\)-[^-]*-[^-]*-[^-]*\.t[glx]z[[ \t]*\n.*\nCOMPRESSED PACKAGE SIZE:[[ \t]*\(.*\)\nUNCOMPRESSED PACKAGE SIZE:[[ \t]*\(.*\)\nPACKAGE REQUIRED:[[ \t]*\(.*\)/\1:\4/p
        }' \
        $netpkgDir/$source.TXT > $netpkgDir/RemoteDeps.db.$source
        cat $netpkgDir/RemoteDeps.db.$source >> $netpkgDir/RemoteDeps.db
      fi

      echo 'Creating remote packages list'
      sed -n "/^PACKAGE NAME:.*/{ N; s|^PACKAGE NAME:[[ \t]*\(.*\.t[glx]z\)[[ \t]*\nPACKAGE LOCATION:[[ \t]*\.\/\?\(.*\)|$RemoteURL\/\2 \1|p }" \
        $netpkgDir/$source.TXT > $netpkgDir/RemotePkgs.db.$source
      cat $netpkgDir/RemotePkgs.db.$source >> $netpkgDir/RemotePkgs.db


    done
  else
    echo '' > $netpkgDir/RemotePkgs.db
  fi

  echo 'Creating local packages list'
  buildLocalPkgsDb

  # Generating a report
  echo -e 'Synchronization successful'
  if [[ "$processDependencies" == 'yes' ]] ; then
    echo -e 'Dependency processing enabled'
  else
    echo -e 'Dependency processing disabled'
  fi
  echo ''
}

# get URL for source $1
function getRemoteURL(){
  sed -n 's/^[ \t]*URL[ \t]*=[ \t]*\(.*\)$/\1/p' $remotesDir/$1
}

# get Blacklist for source $1
function getRemoteBlacklist(){
    sed -n 's/^[ \t]*Black_list[ \t]*=[ \t]*\(.*\)$/\1/p' $remotesDir/$1
}

# take a look at local packages list to see which version of the $1 package installed
function getLocalPkgByshortName(){
  sed -n "s/^\($1-[^\-]*-[^\-]*-[^\-]*\.t[glx]z\)$/\1/p" $netpkgDir/LocalPkgs.db | head -n1
}

function getLocalPkg(){
  local shortName=${1%-*}; shortName=${shortName%-*}; shortName=${shortName%-*}
  sed -n "s/^\($shortName-[^\-]*-[^\-]*-[^\-]*\.t[glx]z\)$/\1/p" $netpkgDir/LocalPkgs.db | head -n1
}

# Download ($package)
function downloadPkg(){
  for package in $* ; do

    location=$(getPkgLocation $package)

    if [[ -e $localPkgDir/$package && \
    "$(md5sum $localPkgDir/$package | cut -d" " -f1)" == "$(cut -d" " -f1 $localPkgDir/${package%.*}.md5)" ]]; then

      echo -e "Using $package from local cache"
      return
    else
      rm -f $localPkgDir/$package 2>/dev/null
      rm -f $localPkgDir/${package%.*}.md5 2>/dev/null

      $WGET -q --show-progress -c -O $localPkgDir/$package "$location/$package" 2>&1
      if [[ ! -e $localPkgDir/$package ]] ; then
        echo "Unable to download  $location/$package"
        return 1
      fi
      grep "/$package$" "$netpkgDir/$(getSource $package).md5" > $localPkgDir/${package%.*}.md5
        fi
  done
}

# Checks for configuration files to update.
function checkDotNew(){
  local actionlist='yes skip diff remove exit'
  echo 'Checking for configuration files to update...'
  dotnewlist="$(find /etc -name "*.new")"
  if [[ "$dotnewlist" ]] ; then
    for file in $dotnewlist ; do
    origfile="$(echo $file|sed -e 's/.new//')"
    [[ "$(echo $untouchable | grep $origfile)" ]] && continue
    echo "Should we move $file to $origfile ?"
    select action in $actionlist ; do
      case $action in
      'yes')
        cp -f $file $origfile
        rm -f $file
        break
        ;;
       'skip')
        break
        ;;
       'diff')
        if [[ -e $origfile ]] ; then
        diff -dU 1 $origfile $file | most
        else
        echo "$file is the only one, no $origfile yet"
        fi
        ;;
       'remove')
        rm -f $file
        break
        ;;
       'exit')
        exit
      esac
    done
    done
  else
  echo 'No \".new\" configuration files on the system'
  fi
}


# Disable source
function disableRemote(){

  if [[ "$sourceslist" ]] ; then
    echo 'Which remote do you want to disable? :'
    select source in $sourceslist exit ;do
      [[ "$source" == 'exit' ]] && exit 0
      mv -f $remotesDir/$source $remotesDir/.$source
      break
    done
    echo -e "${BOLDGREEN}$source${COLOR_RESET} has been disabled"
  fi


}

# Enable remote
function enableRemote(){

  local disabledsourceslist=$(ls -d $remotesDir/.??* | sed -e "s|$remotesDir/\(.*\)|\1|" )

  if [[ "$disabledsourceslist" ]] ; then
    echo 'Which remote do you want to enable? :'
    select source in $disabledsourceslist exit ;do
      [[ "$source" == 'exit' ]] && exit 0
      mv -f $remotesDir/$source $remotesDir/${source#.}
      break
    done
    echo -e "${BOLDGREEN}${source#.}${COLOR_RESET} has been enabled"
  fi
}

# Add remote for URL "$1"
function addRemote(){

  local source="$(getHostFromURL $1)"
  if [[ "$1" ]] ; then
    if [[ -e $remotesDir/$source ]]; then
      echo -e "${BOLDWHITE}$source already exists in $remotesDir ${COLOR_RESET}"
    elif [[ ! "$(echo $1 | egrep 'http://|ftp://|https://')" ]] ; then
      echo -e "${BOLDWHITE}Bad syntax in URL $1 ${COLOR_RESET}"
    else
      source="$(getHostFromURL $1)"
      echo "URL = $1" > $remotesDir/$source
      echo "Black_list = " >> $remotesDir/$source
      echo -e "${BOLDGREEN}$source${COLOR_RESET} has been configured"
    fi
  else
    echo 'you forgot the URL... ;)'
    exit
  fi
}

function printPkgList(){
  local status=$1
  local color=$2
  local package=$3

  # Get package details
  local shortName="$(getShortName $package)"
  local shortNameDotDot="" ; [[ ${#shortName} -gt 20 ]] && shortNameDotDot=".."
  local localPkg="$(getLocalPkgByshortName $shortName)"
  local localPkgDotDot="" ; [[ ${#localPkg} -gt 30 ]] && localPkgDotDot=".."
  local localVersion=${localPkg%-*}; localVersion=${localVersion%-*}; localVersion=${localVersion##*-}
  local localversionDotDot="" ; [[ ${#localVersion} -gt 10 ]] && localversionDotDot=".."
  local buildVersion=${package%.*}; buildVersion=${buildVersion##*-}
  local buildversionDotDot="" ; [[ ${#buildVersion} -gt 10 ]] && buildversionDotDot=".."
  local version=${package%-*}; version=${version%-*}; version=${version##*-}
  local versionDotDot="" ; [[ ${#version} -gt 10 ]] && versionDotDot=".."
  local Desc="$(sed -n "s/$shortName:.*(\(.*\))/\1/p" $netpkgDir/PACKAGES.TXT  | head -n1)"
  [[ ${#Desc} -lt 1 ]] && Desc="$shortName package for Slackware & Zenwalk"
  local location=$(getPkgLocation $package)
  local RemoteURI="$(echo -n $location | cut -d'/' -f3)"

  echo -ne "${color}${status}${COLOR_RESET} "
    printf "%-24s %-16s %-16s %-22s %-32s\n" \
    "${shortName:0:20}$shortNameDotDot" \
    "${version:0:14}$versionDotDot" \
    "${buildVersion:0:14}$buildversionDotDot" \
    "${RemoteURI:0:18}.." \
    "${Desc:0:30}.."

}

# list packages from Remote #########################
function listPkg() {

  truncate -s0 $netpkgDir/.tmpPkgList.$$
  for pattern in $* ; do
    if [[ -n "$(echo "$statusFilter" | grep 'U')" || -n "$(echo "$statusFilter" | grep 'D')" || -n "$(echo "$statusFilter" | grep 'R')" ]]; then
      getRemotePkgs | grep "$pattern" >> $netpkgDir/.tmpPkgList.$$
    fi
    if [[ -n "$(echo "$statusFilter" | grep 'I')" ]]; then
      getLocalPkgs | grep "$pattern" >> $netpkgDir/.tmpPkgList.$$
    fi
  done

  tput bold ; printf "%-26s %-16s %-16s %-22s %-5s\n" "  Name" "Version" "Build" "Remote" "Info" ; tput sgr0

  for package in $(sort $netpkgDir/.tmpPkgList.$$ 2>/dev/null | uniq) ; do
    [[ "${mill[$pos]}" != '' ]] && printf "\e[31m%s\e[0m\b" "${mill[$pos]}" ; ((rotation++)) ; pos=$(( $rotation % 64 ))

    local localPkg="$(getLocalPkg $package)"
    [[ -n "$localPkg" ]] && vfilter $package $localPkg ; local status=$?


    # it's installed
    if [[ -n "$localPkg" ]]; then
      if [[ "$localPkg" == "$package" ]]; then
        if [[ -n "$(echo "$statusFilter" | grep 'I')" ]]; then
          printPkgList "I" ${BOLDBLUE} $package
        else
          [[ "${mill[$pos]}" != '' ]] && printf "\e[31m%s\e[0m\b" "${mill[$pos]}" ; ((rotation++)) ; pos=$(( $rotation % 64 ))
        fi
      elif [[ $status == 1 ]]; then
        if [[ -n "$(echo "$statusFilter" | grep 'U')" ]]; then
          printPkgList "U" ${BOLDRED} $package
        else
          [[ "${mill[$pos]}" != '' ]] && printf "\e[31m%s\e[0m\b" "${mill[$pos]}" ; ((rotation++)) ; pos=$(( $rotation % 64 ))
        fi
      elif [[ $status == 2 ]]; then
      echo $package | grep "netpkg" && echo $localPkg
        if [[ -n "$(echo "$statusFilter" | grep 'D')" ]]; then
          printPkgList "D" ${BOLDYELLOW} $package
        else
          [[ "${mill[$pos]}" != '' ]] && printf "\e[31m%s\e[0m\b" "${mill[$pos]}" ; ((rotation++)) ; pos=$(( $rotation % 64 ))
        fi
      fi
    # not installed
    else
      if [[ -n "$(echo "$statusFilter" | grep 'R')" ]]; then
        printPkgList "R" ${BOLDCYAN} $package
      else
        [[ "${mill[$pos]}" != '' ]] && printf "\e[31m%s\e[0m\b" "${mill[$pos]}" ; ((rotation++)) ; pos=$(( $rotation % 64 ))
      fi
    fi
    [[ "${mill[$pos]}" != '' ]] && printf "\e[31m%s\e[0m\b" "${mill[$pos]}" ; ((rotation++)) ; pos=$(( $rotation % 64 ))

  done
  echo 'Search done.'
}

function printSearchResult(){
  local status=$1
  local color=$2
  local package=$3

  # Get package details
  local shortName="$(getShortName $package)"
  local shortNameDotDot="" ; [[ ${#shortName} -gt 20 ]] && shortNameDotDot=".."
  local localPkg="$(getLocalPkgByshortName $shortName)"
  [[ -z $localPkg ]] && localPkg="------------"
  local localPkgDotDot="" ; [[ ${#localPkg} -gt 30 ]] && localPkgDotDot=".."
  local Desc="$(sed -n "s/$shortName:.*(\(.*\))/\1/p" $netpkgDir/PACKAGES.TXT  | head -n1)"
  [[ ${#Desc} -lt 1 ]] && Desc="$shortName package for Slackware & Zenwalk"
  local location=$(getPkgLocation $package)
  local RemoteURI="$(echo -n $location | cut -d'/' -f3)"
  if [[ "$RemoteURI" == "local" ]]; then
    RemotePkg="------------"
  else
    RemotePkg="$package"
    RemotePkgDotDot="" ; [[ ${#package} -gt 30 ]] && RemotePkgDotDot=".."
  fi

  tput bold ; printf "%-36s %-34s %-7s\n" "  Installed Pkg" "Remote Pkg" "Remote"; tput sgr0
  echo -ne "${color}${status}${COLOR_RESET} "
  printf "%-34s %-34s %-22s\n" \
  "${localPkg:0:30}$localPkgDotDot" "${RemotePkg:0:30}$RemotePkgDotDot" "${RemoteURI:0:18}.."
  echo "(${Desc:0:50}..)"

}

# prompt for action (install / upgrade / reinstall / download) for a list of packages ($*) #########################
function searchAndPrompt(){

  truncate -s0 $netpkgDir/.tmpPkgList.$$

  for pattern in $* ; do
    if [[ -n "$(echo "$statusFilter" | grep 'U')" || -n "$(echo "$statusFilter" | grep 'D')" || -n "$(echo "$statusFilter" | grep 'R')" ]]; then
      getRemotePkgs | grep "$pattern" >> $netpkgDir/.tmpPkgList.$$
    fi
    if [[ -n "$(echo "$statusFilter" | grep 'I')" ]] || [[ -n "$(echo "$statusFilter" | grep 'X')" ]]; then
      getLocalPkgs | grep "$pattern" >> $netpkgDir/.tmpPkgList.$$
    fi
  done

  for package in $(sort $netpkgDir/.tmpPkgList.$$ 2>/dev/null | uniq) ; do

    local shortName="$(getShortName $package)"
    local localPkg="$(getLocalPkgByshortName $shortName)"
    local location="$(getPkgLocation $package)"

    [[ -n "$localPkg" ]] && vfilter $package $localPkg ; local status=$?

    [[ "${mill[$pos]}" != '' ]] && printf "\e[31m%s\e[0m\b" "${mill[$pos]}" ; ((rotation++)) ; pos=$(( $rotation % 64 ))

    actionlist=''

    # it's installed
    if [[ -n "$localPkg" ]]; then
      if [[ $status == 0 ]] ; then
        if [[ -n "$(echo "$statusFilter" | grep 'I')" ]]; then
          printSearchResult "I" ${BOLDBLUE} $package
          local actionlist='reinstall skip exit download'
        elif [[ -n "$(echo "$statusFilter" | grep 'X')" ]]; then
          printSearchResult "I" ${BOLDBLUE} $package
          local actionlist='remove skip exit'
        else
          [[ "${mill[$pos]}" != '' ]] && printf "\e[31m%s\e[0m\b" "${mill[$pos]}" ; ((rotation++)) ; pos=$(( $rotation % 64 ))
        fi
      elif [[ $status == 1 ]]; then
        if [[ -n "$(echo "$statusFilter" | grep 'U')" ]]; then
          printSearchResult "U" ${BOLDRED} $package
          local actionlist='install skip exit download'
        else
          [[ "${mill[$pos]}" != '' ]] && printf "\e[31m%s\e[0m\b" "${mill[$pos]}" ; ((rotation++)) ; pos=$(( $rotation % 64 ))
        fi
      elif [[ $status == 2 ]]; then
        if [[ -n "$(echo "$statusFilter" | grep 'D')" ]]; then
          printSearchResult "D" ${BOLDYELLOW} $package
          local actionlist='install skip exit download'
        else
          [[ "${mill[$pos]}" != '' ]] && printf "\e[31m%s\e[0m\b" "${mill[$pos]}" ; ((rotation++)) ; pos=$(( $rotation % 64 ))
        fi
      fi
    # not installed
    else
      localPkg="-----"
      if [[ -n "$(echo "$statusFilter" | grep 'R')" ]]; then
          printSearchResult "R" ${BOLDCYAN} $package
        local actionlist='install skip exit download'
      else
        [[ "${mill[$pos]}" != '' ]] && printf "\e[31m%s\e[0m\b" "${mill[$pos]}" ; ((rotation++)) ; pos=$(( $rotation % 64 ))
      fi
    fi
    if [[ "$actionlist " != " " ]] ; then
      echo ' what should I do ?'
      select action in $actionlist ; do
        case $action in
          'install')
            processPkgs $package
            return
            ;;
          'reinstall')
            processPkgs $package
            return
            ;;
          'remove')
            removePkg $localPkg
            return
            ;;
          'skip')
            echo "Skipping [$(echo -n $location | cut -d'/' -f3)] $package"
            echo
            break
            ;;
          'download')
            downloadPkg $package
            return
            ;;
          'exit')
            exit
        esac
        sed -i "/^$shortName-[^\-]*-[^\-]*-[^\-]*\.t[glx]z$/d" $netpkgDir/.tmpPkgList.$$
      done
    fi
  done
  echo "Search done."
}


# installPkg function - replaces system installpkg
function installPkg() {
  truncate -s0 $netpkgDir/.tmp*
  local package="$1"

  # Determine decompressor
  case ${package##*.} in
    'tgz') local decomp='gzip' ;;
    'tbz') local decomp='bzip2' ;;
    'tlz') local decomp='lzip' ;;
    'txz') local decomp='xz' ;;
    *) echo "ERROR: Unknown package extension $ext"; return 1 ;;
  esac

  echo "Installing package $package..."

  # Extract package
  ( cd ${ROOT:-/} ; $decomp -dc | tar -xpf - ) < "$localPkgDir/$package" 2>/dev/null
  if [ $? -ne 0 ]; then
    echo "ERROR: Failed to extract $package"
    return 1
  fi

  # Record installation in package database
  if [[ "${package##*.}" == "tgz" ]]; then
    pkgSize=$( expr `gunzip -l "$localPkgDir/$package" |tail -1|awk '{print $1}'` / 1024 )
    uncompressedPkgSize=$( expr `gunzip -l "$localPkgDir/$package" |tail -1|awk '{print $2}'` / 1024 )
  else
    pkgSize=$( expr `ls -l "$localPkgDir/$package" | awk '{print $5}'` / 1024 )
    uncompressedPkgSize=$[$pkgSize * 4 ]
  fi
  echo "PACKAGE NAME:     ${package%.*}" > "$packageLogs/${package%.*}"
  echo "PACKAGE LOCATION: $localPkgDir/$package" >> "$packageLogs/${package%.*}"
  echo "COMPRESSED PACKAGE SIZE:  ${pkgSize}K" >> "$packageLogs/${package%.*}"
  echo "UNCOMPRESSED PACKAGE SIZE:  ${uncompressedPkgSize}K" >> "$packageLogs/${package%.*}"
  echo "PACKAGE DESCRIPTION:" >> "$packageLogs/${package%.*}"
  ( cd ${ROOT:-/} ; $decomp -dc | tar xO install/slack-desc | grep -E '[^[:space:]]*\:'| grep -v '^#' ) < "$localPkgDir/$package" 2>/dev/null >> "$packageLogs/${package%.*}"
  echo "FILE LIST:" >> "$packageLogs/${package%.*}"
  ( cd ${ROOT:-/} ; $decomp -dc | tar -tf - ) < "$localPkgDir/$package" 2>/dev/null | sort >> "$packageLogs/${package%.*}"

  # Run install script if present
  if [ -f "${ROOT:-/}/install/doinst.sh" ]; then
    echo "Executing install script..."
    ( cd ${ROOT:-/} ; sh install/doinst.sh 2>/dev/null )
    rm -rf "${ROOT:-/}/install" 2>/dev/null
  fi

  # Update library cache
  if [ -z "$ROOT" ] && [ -x /sbin/ldconfig ]; then
    /sbin/ldconfig 2>/dev/null
  fi

  echo "Package $package installed successfully."
  return 0
}

# Simplified upgradePkg function - replaces system upgradepkg
function upgradePkg() {
  truncate -s0 $netpkgDir/.tmp*
  local package="$1"
  local shortName="$(getShortName $package)"
  local oldPkg="$(getLocalPkgByshortName $shortName)"

  if [ -z "$oldPkg" ]; then
    installPkg "$package"
    return $?
  fi

  # Save old file list for cleanup
  local oldFiles="$netpkgDir/.tmpOldFiles.$$"
  grep "^[^:]*/" "$packageLogs/${oldPkg%.*}" 2>/dev/null | sort -u > "$oldFiles"

  # Install new package
  installPkg "$package" || return 1

  # Save new file list
  local newFiles="$netpkgDir/.tmpNewFiles.$$"
  grep "^[^:]*/" "$packageLogs/${package%.*}" 2>/dev/null | sort -u > "$newFiles"

  # Remove old package files (keeping new ones)
  if [ -f "$oldFiles" ]; then
    echo "Cleaning up old version files..."
    comm -23 "$oldFiles" "$newFiles" 2>/dev/null | while read file; do
      if [ -f "${ROOT:-/}$file" ]; then
        rm -f "${ROOT:-/}$file" 2>/dev/null
      fi
    done
    rm -f "$packageLogs/${oldPkg%.*}"
  fi

  # Cleanup temp files
  rm -f "$oldFiles" "$newFiles"

  echo "Package installed successfully."
  return 0
}

# Simplified removePkg function - replaces system removepkg
function removePkg() {
  truncate -s0 $netpkgDir/.tmp*
  local package="$1"
  # Build list of all files from all packages
  local allFiles="$netpkgDir/.tmpAllFiles.$$"
  for pkg in $(ls "$packageLogs/" 2>/dev/null); do
    [ "$pkg" = "${package%.*}" ] && continue
    grep "^[^:]*/" "$packageLogs/$pkg" 2>/dev/null
  done | sort -u > "$allFiles"

  # Remove files that are not used by other packages
  echo "Removing files..."
  local dirList="/tmp/.tmpDirList.$$"
  grep "^[^:]*/" "$packageLogs/${package%.*}" 2>/dev/null | sort -r | while read file; do
    if ! grep -q "^$file$" "$allFiles" 2>/dev/null; then
      if [ -f "${ROOT:-/}$file" ]; then
        rm -f "${ROOT:-/}$file" 2>/dev/null
      elif [ -d "${ROOT:-/}$file" ]; then
        # Collect directories for later processing
        echo "${ROOT:-/}$file" >> "$dirList"
      fi
    fi
  done

  # Clean broken symlinks in directories, then remove empty directories
  if [ -f "$dirList" ]; then
    echo "Cleaning directories..."
    sort -u "$dirList" | sort -r | while read dir; do
      if [ -d "$dir" ]; then
        # Recursively find and remove broken symlinks
        find "$dir" -type l 2>/dev/null | while read symlink; do
          if [ ! -e "$symlink" ]; then
            rm -f "$symlink" 2>/dev/null
          fi
        done
        # Check if directory contains ONLY directories (no files/symlinks)
        if [ -z "$(find "$dir" ! -type d 2>/dev/null)" ]; then
          # Safe to remove entire tree - contains only empty dirs
          rm -rf "$dir" 2>/dev/null
        else
          # Contains files - try to remove if empty
          rmdir "$dir" 2>/dev/null
        fi
      fi
    done
    rm -f "$dirList"
  fi

  # Remove package database entry
  rm -f "$packageLogs/${package%.*}"

  # Cleanup
  rm -f "$allFiles"

  # Update library cache
  if [ -z "$ROOT" ] && [ -x /sbin/ldconfig ]; then
    /sbin/ldconfig 2>/dev/null
  fi

  echo "Package $package removed successfully."
  return 0
}


# Check deps, then Install / Reinstall / Upgrade a list of packages
# The full and exact package name MUST be provided
function processPkgs() {

if [[ ${#@} -gt 0 ]]; then
  local filesystem='no'
  if [[ -e "$1" ]]; then
    processDependencies='no'
    prompt='yes'
    filesystem='yes'
  fi
  local finallist=()
  local PkgNumber=0

  # We need an up to date list of installed packages
  buildLocalPkgsDb

  for package in "$@" ; do

    local shortName="$(getShortName $package)"
    finallist+=("$package")
    let "PkgNumber++"

    if [[ "$processDependencies" == 'yes' ]] ; then

      read -ra deps <<< "$(grep "^$shortName:.*$" $netpkgDir/RemoteDeps.db | cut -s -d ":" -f 2-)"
      [[ ${#deps[@]} -eq 0 ]] && read -ra deps <<< "$(grep "^$shortName:.*$" $netpkgDir/DepsDB | cut -s -d ":" -f 2-)"
      [[ ${#deps[@]} -eq 0 ]] && continue

      for dep in "${deps[@]}" ; do

        # We need to find package for $dep ($dep is the short name)
        deppackage="$(sed -n "s/^.*[[ \t]\($dep-[^\-]*-[^\-]*-[^\-]*.t[glx]z\)[[ \t]*$/\1/p" $netpkgDir/RemotePkgs.db | head -n1)"
        [[ ! "$deppackage" ]] && continue

        # If it's already in the list, then skip
        [[ "$(echo "${finallist[*]}" | grep "$deppackage")" ]] && continue

        # Do we have it installed in ANY version ? then skip
        [[ "$(getLocalPkgByshortName $dep)" ]] && continue

        finallist+=("$deppackage")
        let "PkgNumber++"
        prompt='yes'
      done
    fi
  done

  if [[ "$prompt" == 'yes' ]] ; then
    prompt=''
    echo "We are about to install $PkgNumber package(s) : continue ?"
    echo -e "-> ${BOLDRED}${finallist[*]}${COLOR_RESET}"
    select action in 'run Forest!' 'abort' ; do
      case $action in
        'run Forest!')
          break
          ;;
        'abort')
          finallist=()
          break
      esac
    done
  fi

  if [[ "$filesystem" == "no" ]]; then
    local PkgCount=0
    for package in "${finallist[@]}" ; do

      let "PkgCount++"

      echo -e "${BOLDWHITE}Getting $package ($PkgCount/$PkgNumber) ...${COLOR_RESET}"

      # download the package if needed
      downloadPkg $package

    done
  fi

  local PkgCount=0
  for package in "${finallist[@]}" ; do

    if [[ "$filesystem" == "yes" ]]; then
      export localPkgDir="$(dirname $package)"
      package="$(basename $package)"
      local location="$localPkgDir"
      local source="$localPkgDir"
      if [[ ! -e $localPkgDir/$package ]]; then
        echo -e "${BOLDWHITE}No such file $localPkgDir/$package !${COLOR_RESET}"
        continue
      fi
    else
      local location="$(getPkgLocation $package)"
      if [[ "$(md5sum $localPkgDir/$package | cut -d" " -f1)" == "$(cut -d" " -f1 $localPkgDir/${package%.*}.md5)" ]]; then
        echo -e "Checksum is OK for $package :)"
      else
        echo -e "${BOLDRED}Package corrupted or unable to get checksum for $package : skipping !${COLOR_RESET}"
        mv "$localPkgDir/$package" "$localPkgDir/_BAD_$package"
        mv "$localPkgDir/${package%.*}.md5" "$localPkgDir/_BAD_${package%.*}.md5"
        continue
      fi
      local source="$(getSource $package)"
      [[ ! -e $localPkgDir/$package ]] && downloadPkg $package
    fi

    let "PkgCount++"

    local shortName="$(getShortName $package)"
    local localPkg="$(getLocalPkgByshortName $shortName)"
    [[ -n "$localPkg" ]] && vfilter $package $localPkg ; local status=$?

    if [[ -e $packageLogs/${package%.*} ]]; then
      pkgStatus="U"
    elif [[ $status == 1 ]]; then
      pkgStatus="U"
    elif [[ $status == 2 ]]; then
      pkgStatus="D"
    else
      pkgStatus="N"
    fi

    echo -ne "${BOLDWHITE}($PkgCount/$PkgNumber) Installing"
    echo -e "${BOLDRED} [$source] $package${COLOR_RESET}"
    [[ -e $localPkgDir/$package ]] && upgradePkg $package && echo "[${pkgStatus}I] $location/$package $(date)" >> $logFile

    # remove package if we don't want to keep it
    if [[ ! "$keepit" = 'yes' ]]; then
      rm -f $localPkgDir/$package 2>/dev/null
    fi

    # We need an up to date list of installed packages
    buildLocalPkgsDb

  done
fi

}


# downloadAll ###############################################
function downloadAll() {

  if [[ "$prompt" == 'yes' ]] ; then
    prompt=''
    echo 'We are about to download all packages from remotes : continue ?'
    select action in 'run Forest!' 'abort' ; do
      case $action in
        'run Forest!')
          break
          ;;
        'abort')
          return
      esac
    done
  fi

  for package in $(getRemotePkgs) ; do
    if [[ "$package" ]]; then
      downloadPkg $package
    fi
  done

}

# upgrade the whole system ###############################################
function updateAll() {

  truncate -s0 $netpkgDir/.tmpPkgList.$$
  local finallist=()

  getRemotePkgs > $netpkgDir/.tmpPkgList.$$

  for package in $(<$netpkgDir/.tmpPkgList.$$) ; do

    local localPkg="$(getLocalPkg $package)"

    [[ -n "$localPkg" ]] && vfilter $package $localPkg ; local status=$?

    if [[ "$localPkg" == "$package" ]]; then
      [[ "${mill[$pos]}" != '' ]] && printf "\e[31m%s\e[0m\b" "${mill[$pos]}" ; ((rotation++)) ; pos=$(( $rotation % 64 ))
      continue

    elif [[ -n "$localPkg" ]]; then

      if [[ $status == 1 && -n "$(echo "$statusFilter" | grep 'U')" ]]; then
        echo "Found updated $package"
        finallist+=("$package")
      elif [[ $status == 2 && -n "$(echo "$statusFilter" | grep 'D')" ]]; then
        echo "Found downgraded $package"
        finallist+=("$package")
      fi
    elif [[ -n "$(echo "$statusFilter" | grep 'R')" ]]; then
      local location=$(getPkgLocation $package)
      local category=${location##*/}
      if [[ "$category" == "l" || "$category" == "a" || "$category" == "z" ]]; then
        echo "Found missing $package"
        finallist+=("$package")
      fi
    fi
    [[ "${mill[$pos]}" != '' ]] && printf "\e[31m%s\e[0m\b" "${mill[$pos]}" ; ((rotation++)) ; pos=$(( $rotation % 64 ))
  done

  processPkgs "${finallist[@]}"
  echo 'Search done.'

}

# File Protector function ######################################
function Protect() {
  for protectedFile in $untouchable ; do
    [[ -e $protectedFile ]] && cp -f $protectedFile $protectedFile.old 2>/dev/null
  done
  if [[ -e "$netpkgDir/.lock" ]]; then
    echo "Another netpkg process is running, exiting ($(<$netpkgDir/.lock)) !"
    exit 1
  fi
  echo "$$" > "$netpkgDir/.lock"
}

# File unProtector function
function unProtect() {
  rm -f "$netpkgDir/.lock"
for protectedFile in $untouchable ; do
  if [[ -e $protectedFile ]]; then
    newFile="$protectedFile"
    oldFile="$newFile.old"
    if [[ "$(md5sum "$oldFile" 2>/dev/null | cut -f1 -d" ")" == "$(md5sum "$newFile" 2>/dev/null | cut -f1 -d" ")" ]]; then
        rm -f "$oldFile" 2>/dev/null
    fi
  fi
done

}

# Final cleanup ######################################
function cleanup(){
  unProtect
  rm -f $netpkgDir/.tmp*
  echo -e "Bye ${BOLDRED};)${COLOR_RESET}"
  echo
  exit 0
}

# remove the netpkgDired files on any type of exit
trap 'cleanup' TERM INT EXIT

# main( :) #########################

# We always need an up to date list of installed packages
buildLocalPkgsDb

# Some fun :)
function baneer() {
echo -en "${BOLDRED}"
echo "      _  __      __         __        ";
echo "     / |/ /___  / /_ ___   / /__ ___  ";
echo "    /    // -_)/ __// _ \ /  '_// _ \ ";
echo "   /_/|_/ \__/ \__// .__//_/\_\ \_, / ";
echo "                  /_/          /___/  ";
echo -e "${COLOR_RESET}"
}

function usage() {
  echo -e "Basic usage : "
  echo -e " ${BOLDWHITE}netpkg update${COLOR_RESET}"
  echo -e " ${BOLDWHITE}netpkg upgrade${COLOR_RESET}"
  echo -e " ${BOLDWHITE}netpkg install pattern1 [pattern2 ...]${COLOR_RESET}"
  echo -e " ${BOLDWHITE}netpkg search [pattern1 pattern2 ...]${COLOR_RESET}"
  echo -e " ${BOLDWHITE}netpkg remove pattern1 [pattern2 ...]${COLOR_RESET}"
  echo -e " ${BOLDWHITE}netpkg help|-h${COLOR_RESET}"
}

function uusage() {
  echo
  echo -e "Output symbols and colors : "
  echo -en "${BOLDBLUE}    [I]${COLOR_RESET}"
  echo -e " means \"Installed\" : remote version is the same as installed package"
  echo -en "${BOLDRED}    [U]${COLOR_RESET}"
  echo -e " means \"Updated\" : remote version is higher than installed package"
  echo -en "${BOLDYELLOW}    [D]${COLOR_RESET}"
  echo -e " means \"Downgraded\" : remote version is lower than installed package"
  echo -en "${BOLDCYAN}    [R]${COLOR_RESET}"
  echo -e " means \"Remote\" : remote package is not installed yet"
  echo
  echo -e "System upgrade options : "
  echo -e " ${BOLDWHITE}netpkg update|-u ${COLOR_RESET} : reload remotes packages lists"
  echo -e " ${BOLDWHITE}netpkg upgrade|-U ${COLOR_RESET} : Automatic install of all \"updatable\" packages"
  echo
  echo -e "Setup commands :"
  echo -e " ${BOLDWHITE}netpkg remotes|-rl ${COLOR_RESET} : list all remotes URL in netpkg.d"
  echo -e " ${BOLDWHITE}netpkg remote-add|-ra \"URL\"${COLOR_RESET} : add Remote URL to netpkg.d"
  echo -e " ${BOLDWHITE}netpkg remote-disable|-rd ${COLOR_RESET} : disable Remote URL in netpkg.d"
  echo -e " ${BOLDWHITE}netpkg remote-enable|-re ${COLOR_RESET} : enable Remote URL in netpkg.d"
  echo -e " ${BOLDWHITE}netpkg remote-disable|-rd ${COLOR_RESET} : disable dependency processing in netpkg.conf"
  echo -e " ${BOLDWHITE}netpkg enable-deps|-ed ${COLOR_RESET} : enable dependency processing in netpkg.conf"
  echo -e " ${BOLDWHITE}netpkg mrclean|-mc ${COLOR_RESET} : clean the local packages cache"
  echo -e " ${BOLDWHITE}netpkg dotnew|-z ${COLOR_RESET} : search for .new config files in the system and prompt for action"
  echo -en ${COLOR_RESET}
  echo -e "Copyright Jean-Philippe Guillemin <h1p8r10n@yandex.com> GNU GPLv2"
  echo
}

# Admin options
if [[ "$1" ]]; then
  case "$1" in
    '--help' | '-help' | 'help' | '-h')
      usage
      uusage
      exit 1
      ;;
    'mrclean' | '-mc')
      rm -f $localPkgDir/*z
      fetchRemotes
      exit 0
      ;;
    'remotes' | 'remotes-list' | '-rl' | 'list-sources' | '-ls')
      echo "Available remotes are :"
      ls -A $remotesDir/
      echo "(dotted remotes are disabled)"
      exit 0
      ;;
    'dotnew' | '-z')
      checkDotNew
      exit 1
      ;;
    'remote-add' | '-ra' | 'add-source' | '-as')
      shift
      addRemote $1
      exit 0
      ;;
    'remote-disable' | '-rd' | 'disable-source' | '-ds')
      disableRemote
      fetchRemotes
      exit 0
      ;;
    'remote-enable' | '-re' | 'enable-source' | '-es')
      enableRemote
      fetchRemotes
      exit 0
      ;;
    'deps-disable' | '-dd' | 'disable-deps')
      disableDependencies
      exit 0
      ;;
    'deps-enable' | '-de'| 'enable-deps' | '-ed')
      enableDependencies
      exit 0
      ;;
  esac
else
  baneer
  usage
  exit 1
fi

checkDbAge() {
  if [[ -e $netpkgDir/RemotePkgs.db ]] ; then
    DbAge=$(stat -c %Z $netpkgDir/RemotePkgs.db )

    if [[ $(($(date +%s) - $DbAge)) -ge 3600 ]] ; then

      echo -en 'Current DB is older than 1 hour ...'
      echo 'Keep it ? ("1" : reload, default : keep)'
      echo -n "> "

      while read answer ; do
        case $answer in
        1)
          fetchRemotes
          echo -e 'Synced with remotes'
          break
          ;;
        *)
          echo -e 'Keeping old DB :/'
          buildLocalPkgsDb
          break
        esac
      done
    else
      buildLocalPkgsDb
    fi
  else
    fetchRemotes
  fi
  if [[ ! -e $netpkgDir/RemotePkgs.db ]] ; then
    exit 1
  fi
}


# Processing options
if [[ "$1" ]]; then
  case "$1" in
    'update' | '-u')
      fetchRemotes
      exit 0
      ;;
    'upgrade' | '-U')
      Protect
      shift
      export prompt='yes'
      echo 'Searching for updates ...'
      export statusFilter='U'
      updateAll
      export prompt='yes'
      echo 'Searching for missings ...'
      export statusFilter='R'
      updateAll
      unProtect
      checkDotNew
      installKernel
      exit 0
      ;;
    'update&upgrade' | '-uu')
      fetchRemotes
      Protect
      shift
      export prompt='yes'
      echo 'Searching for updates ...'
      export statusFilter='U'
      updateAll
      export prompt='yes'
      echo 'Searching for missings ...'
      export statusFilter='R'
      updateAll
      unProtect
      checkDotNew
      installKernel
      exit 0
      ;;
    'install' | '-i')
      [[ $# -lt 2 ]] && echo "Missing search pattern !" && exit 1
      shift
      Protect
      if [[ -e "$1" ]]; then
        processPkgs "$@"
      else
        export statusFilter='IURD'
        checkDbAge
        echo "Searching $* ..."
        searchAndPrompt "$@"
      fi
      unProtect
      checkDotNew
      installKernel
      exit 0
      ;;
    'remove' | '-r')
      [[ $# -lt 2 ]] && echo "Missing search pattern !" && exit 1
      shift
      export statusFilter='X'
      Protect
      echo "Searching $* ..."
      searchAndPrompt "$@"
      unProtect
      exit 0
      ;;
    'search' | '-s')
      [[ $# -lt 2 ]] && echo "Missing search pattern !" && exit 1
      shift
      echo "Searching $* ..."
      export statusFilter='IURD'
      listPkg "$@"
      exit 0
      ;;
    -*)
      echo "Unknown command \"$1\" !"
      echo 'Type "netpkg -h" for help'
      exit 0
      ;;
    *)
      echo "Searching $* ..."
      export statusFilter='IURD'
      listPkg "$@"
      exit 0
    esac
else
  usage
  exit 1
fi

exit 0
