#!/bin/bash
#---------------------------------------------------------------------
## @Synopsis Functions for desling with the stage root and installing files from the stage root
## @Copyright Copyright (C) 2004 The Source Mage Team <http://www.sourcemage.org>
## Functions for desling with the stage root and installing files from the stage root
#---------------------------------------------------------------------

#---------------------------------------------------------------------
## @param stage root
## Main for installing staged spell called imediatly after devoking
## installwatch and stage root
#---------------------------------------------------------------------
function transfer_staged_spell()
{
  stage_install_ordinary

  # restore any hardlinks, since they were copied over normally
  local hlist
  if [[ -n $CAST_TMPDIR ]]; then
    hlist="$CAST_TMPDIR/hardlinks"
  else
    hlist="$TMP_DIR/hardlinks"
  fi
  if [[ -s $hlist ]]; then
    local inode last_inode filename last_filename
    sort "$hlist" |
    while read inode filename; do
      if [[ $last_inode == $inode ]]; then
        ln -f "$last_filename" "$filename"
      fi
      last_filename="$filename"
      last_inode=$inode
    done
    rm -f "$hlist"
  fi

  lock_file "$CONFIG_STAGE_DIRECTORY/$SPELL"
  mkdir -p "$CONFIG_STAGE_DIRECTORY/$SPELL/current"
  stage_install_configs
  unlock_file "$CONFIG_STAGE_DIRECTORY/$SPELL"
}

function filter_spell_configs()
{
  filter_generic "$*" configs $CONFIGS codex
}

#---------------------------------------------------------------------
## Install config files to a staged location on the system or if
## they are also excluded, just install them normally.
#---------------------------------------------------------------------
function stage_install_configs()
{
  local spell_configs=$(get_all_package_files | filter_spell_configs -v)

  # install excluded configs as ordinary files - do not stage them
  message "${MESSAGE_COLOR}Installing excluded config files${DEFAULT_COLOR}"
  echo "$spell_configs"      |
  filter_excluded -v         |
  stage_install_files

  message "${MESSAGE_COLOR}Staging config files into the system${DEFAULT_COLOR}"
  echo "$spell_configs"      |
  filter_excluded            |
  stage_install_files config
}

#---------------------------------------------------------------------
## Install non-configs in the stage root
#---------------------------------------------------------------------
function stage_install_ordinary()
{
  message "${MESSAGE_COLOR}Installing ordinary files${DEFAULT_COLOR}"
  get_all_package_files        |
  filter_spell_configs         |
  stage_install_files
}

#---------------------------------------------------------------------
## @Stdout list of files installed by package
## lists all files the package installs relative to /
#---------------------------------------------------------------------
function get_all_package_files()
{
  # no point in using -printf "/%P\n" since it would cause a difference:
  # the searchpath is now returned as empty; there it would be /
  find "$STAGE_DIRECTORY/TRANSL" | sed "s:$STAGE_DIRECTORY/TRANSL::" | sort
}

#---------------------------------------------------------------------
## @Stdin list of files to install
## install the files from the stage directory to the system
#---------------------------------------------------------------------
function stage_install_files()
{
  local file
  while read file ; do
    [ "$file" ] || continue
    stage_install_list "$file" "$1"
  done
}

function stage_install_directory()
{
  local DIR=$1
  local stat=( $(stat --printf "%a\n%U\n%G\n" "$STAGE_DIRECTORY/TRANSL/$DIR" ) )
  local PERMISSION=${stat[0]}
  local OWNER=${stat[1]}
  local GROUP=${stat[2]}
  if [[ ! -d $(smgl_dirname "${INSTALL_ROOT}/$DIR") ]]
  then
    # this shouldn't happen but just in case
    message "${PROBLEM_COLOR}Ah, not preserving permissions making $(smgl_dirname $DIR)${DEFAULT_COLOR}" &&
    mkdir -p $(smgl_dirname "${INSTALL_ROOT}/$DIR")
  fi &&
  message "${FILE_COLOR}$PERMISSION $OWNER:$GROUP $DIR${DEFAULT_COLOR}" &&
  if [[ -d "${INSTALL_ROOT}/$DIR" ]]
  then
    chmod "$PERMISSION" "${INSTALL_ROOT}/$DIR" &&
    chown $OWNER:$GROUP "${INSTALL_ROOT}/$DIR"
  else
    mkdir "${INSTALL_ROOT}/$DIR"   &&
    chmod "$PERMISSION" "${INSTALL_ROOT}/$DIR" &&
    chown $OWNER:$GROUP "${INSTALL_ROOT}/$DIR"
  fi
}

function stage_install_file()
{
  local FILE=$1
  local stat=( $(stat --printf "%a\n%U\n%G\n%h\n%i\n" "$STAGE_DIRECTORY/TRANSL/$FILE" ) )
  local PERMISSION=${stat[0]}
  local OWNER=${stat[1]}
  local GROUP=${stat[2]}
  local link_count=${stat[3]}
  local inode=${stat[4]}
  local SAVE="$CONFIG_STAGE_DIRECTORY/$SPELL/current/$FILE"
  message "${FILE_COLOR}$PERMISSION $OWNER:$GROUP $FILE${DEFAULT_COLOR}"
  case $2 in
    config)
      if [[ -e "${INSTALL_ROOT}/$FILE" ]]
      then
        if cmp -s "$STAGE_DIRECTORY/TRANSL/$FILE" "${INSTALL_ROOT}/$FILE"
        then
          : # nothing to do, we already have the same file
        else
          message "${MESSAGE_COLOR}Staging config to $CONFIG_STAGE_DIRECTORY${DEFAULT_COLOR}"
          mkdir -p "$(smgl_dirname $SAVE)" &&
          cp -dp "$STAGE_DIRECTORY/TRANSL/$FILE" "${INSTALL_ROOT}/$SAVE"
        fi
      else
        cp -dp "$STAGE_DIRECTORY/TRANSL/$FILE" "${INSTALL_ROOT}/$FILE"
      fi
      ;;
    *)
      cp -fdp "$STAGE_DIRECTORY/TRANSL/$FILE" "${INSTALL_ROOT}/$FILE"
      # we can't fix symlinks now, since the other file is often not on the system yet
      # doing it later in TRANSFER also avoids the search for matching inodes
      if [[ $link_count != 1 ]]; then
        if [[ -n $CAST_TMPDIR ]]; then
          echo "$inode ${INSTALL_ROOT}/$FILE" >> "$CAST_TMPDIR/hardlinks"
        else
          echo "$inode ${INSTALL_ROOT}/$FILE" >> "$TMP_DIR/hardlinks"
        fi
      fi
      ;;
  esac
}

function stage_install_symlink()
{
  local FILE=$1
  local SAVE=$CONFIG_STAGE_DIRECTORY/$FILE.$(date +%Y%m%d%H%M%S)
  local LINK=$(readlink "$STAGE_DIRECTORY/TRANSL/$FILE")
  message "${FILE_COLOR}Symlink $FILE -> $LINK${DEFAULT_COLOR}"
  case $2 in
    config)
      if [[ -e "${INSTALL_ROOT}/$FILE" ]]
      then
        touch "${INSTALL_ROOT}/$FILE" &&
        mkdir -p $(smgl_dirname $SAVE) &&
        ln -sfn "$LINK" "${INSTALL_ROOT}/$SAVE"
      else
        ln -sfn "$LINK" "${INSTALL_ROOT}/$FILE"
      fi
      ;;
    *)
      ln -sfn $(readlink "$STAGE_DIRECTORY/TRANSL/$FILE") "${INSTALL_ROOT}/$FILE"
      ;;
  esac
}

function stage_install_fifo()
{
  local FILE=$1
  local SAVE=$CONFIG_STAGE_DIRECTORY/$FILE.$(date +%Y%m%d%H%M%S)
  local stat=( $(stat --printf "%a\n%U\n%G\n" "$STAGE_DIRECTORY/TRANSL/$FILE" ) )
  local PERMISSION=${stat[0]}
  local OWNER=${stat[1]}
  local GROUP=${stat[2]}
  message "${FILE_COLOR}Fifo $PERMISSION $OWNER:$GROUP $FILE${DEFAULT_COLOR}"
  case $2 in
    config)
      if [[ -e "${INSTALL_ROOT}/$FILE" ]]
      then
        touch "${INSTALL_ROOT}/$FILE" &&
        mkdir -p $(smgl_dirname $SAVE) &&
        mkfifo "${INSTALL_ROOT}/$SAVE" &&
        chmod $PERMISSION "${INSTALL_ROOT}/$SAVE" &&
        chown $OWNER:$GROUP "${INSTALL_ROOT}/$SAVE"
      else
        mkfifo "${INSTALL_ROOT}/$FILE" &&
        chmod $PERMISSION "${INSTALL_ROOT}/$FILE" &&
        chown $OWNER:$GROUP "${INSTALL_ROOT}/$FILE"
      fi
      ;;
    *)
      mkfifo "${INSTALL_ROOT}/$FILE" &&
      chmod $PERMISSION "${INSTALL_ROOT}/$FILE" &&
      chown $OWNER:$GROUP "${INSTALL_ROOT}/$FILE"
      ;;
  esac
}

function stage_install_char_block()
{
  local FILE=$1
  local stat=( $(stat --printf "%a\n%U\n%G\n%t\n%T\n" "$STAGE_DIRECTORY/TRANSL/$FILE" ) )
  local PERMISSION=${stat[0]}
  local OWNER=${stat[1]}
  local GROUP=${stat[2]}
  local MAJOR=${stat[3]}
  local MINOR=${stat[4]}
  local SAVE=$CONFIG_STAGE_DIRECTORY/$FILE.$(date +%Y%m%d%H%M%S)
  local TYPE=$3
  message "${FILE_COLOR}${PERMISSION} $OWNER:$GROUP $TYPE $MAJOR $MINOR $FILE${DEFAULT_COLOR}"
  case $2 in
    config)
      if [[ -e "${INSTALL_ROOT}/$FILE" ]]
      then
        touch "${INSTALL_ROOT}/$FILE" &&
        mkdir -p $(smgl_dirname $SAVE) &&
        mknod -m "${PERMISSION}" "${INSTALL_ROOT}/$SAVE" "${TYPE}" "${MAJOR}" "${MINOR}" &&
        chown $OWNER:$GROUP "${INSTALL_ROOT}/$SAVE"
      else
        mknod -m "${PERMISSION}" "${INSTALL_ROOT}/$FILE" "${TYPE}" "${MAJOR}" "${MINOR}" &&
        chown $OWNER:$GROUP "${INSTALL_ROOT}/$FILE"
      fi
      ;;
    *)
      mknod -m "${PERMISSION}" "${INSTALL_ROOT}/$FILE" "${TYPE}" "${MAJOR}" "${MINOR}" &&
      chown $OWNER:$GROUP "${INSTALL_ROOT}/$FILE"
      ;;
  esac
}

#---------------------------------------------------------------------
## @param file
## install the file from the stage root to the system does check for
## dir so that the file can be moved
#---------------------------------------------------------------------
function stage_install_list()
{
  if [[ -h $STAGE_DIRECTORY/TRANSL/$1 ]]
  then
    stage_install_symlink "$@"
  elif [[ -b $STAGE_DIRECTORY/TRANSL/$1 ]]
  then
    stage_install_char_block "$@" b
  elif [[ -c $STAGE_DIRECTORY/TRANSL/$1 ]]
  then
    stage_install_char_block "$@" c
  elif [[ -p $STAGE_DIRECTORY/TRANSL/$1 ]]
  then
    stage_install_fifo "$@"
  elif [[ -f $STAGE_DIRECTORY/TRANSL/$1 ]]
  then
    stage_install_file "$@"
  elif [[ -d $STAGE_DIRECTORY/TRANSL/$1 ]]
  then
    stage_install_directory "$@"
  else
    message "${PROBLEM_COLOR}Oh, God I don't know what to do..."
    message "${FILE_COLOR}$(smgl_dirname ${1})${PROBLEM_COLOR}"
    message "is some other kind of a file and not a directory${DEFAULT_COLOR}"
  fi
}

#--------------------------------------------------------------------
## Removes the passed file and possibly its parent directories up
## to $CONFIG_STAGE_DIRECTORY if they are empty
##
## @param file
## @param spell  owner of the file, so we don't need to look that up
#--------------------------------------------------------------------
function recursive_config_stage_cleanup()
{
  local file=$1 dirname
  local spell=$2

  smgl_dirname "$file" dirname
  # make it a relative path for rmdir -p
  dirname="${dirname#$CONFIG_STAGE_DIRECTORY/}"

  # lock_file works on dirs too
  lock_file "$CONFIG_STAGE_DIRECTORY/$spell"
  rm "$file"
  {
    pushd $CONFIG_STAGE_DIRECTORY
    rmdir -p "$dirname"
    popd
  } &> /dev/null
  unlock_file "$CONFIG_STAGE_DIRECTORY/$spell"
}
