#!/bin/bash

function about_install_library(){
 echo "install-library.sh version 20230329";
 echo "##############################################################";
 echo "# Bash Installation Library to include in user scripts.      #";
 echo "# Copyright (c) 2023 Alexey Kuryakin kouriakine@mail.ru      #";
 echo "##############################################################";
};

function note(){ return; }; note "<- uses for comment notes.";
function skip(){ return; }; note "<- uses to skip a command.";

function fatal(){
 local date="$(date +%Y.%m.%d-%H:%M:%S)"; local script="$(basename ${BASH_SOURCE[0]})";
 if which notify-send >/dev/null 2>&1; then notify-send -t 60000 -i dialog-error "$date: $script" "$1"; fi;
 echo -ne "\n$1\n\n";
 exit 1;
};

note "############################";
note "Release version(s) location.";
note "############################";
readonly releasefile="release-$targetname.txt";
readonly releasesdir="$HOME/.local/share/makeself";

note "###########################";
note "First argument is a nimber?";
note "###########################";
function is_number(){
 case $1 in
  ''|*[!0-9]*)  return 1; ;;
  *)            return 0; ;;
 esac;
};

note "##################################";
note "Check option --silent are present.";
note "Assign opt_silent=1 when --silent.";
note "##################################";
opt_silent=0; note "Has option --silent?";
note "##################################";
function check_opt_silent(){
 if [ $# -lt 1 ]; then return 0; fi;
 for arg in "$@"; do
  if [ "$arg" = "--silent" ]; then
   opt_silent=1;
   break;
  fi;
 done;
};
check_opt_silent "$@";

note "####################";
note "Has option --silent?";
note "####################";
function has_opt_silent(){ if [ "$opt_silent" = "1" ]; then return 0; else return 1; fi; };
function not_opt_silent(){ if [ "$opt_silent" = "1" ]; then return 1; else return 0; fi; };

note "######################################################";
note "Ask (run) zenity or return default value if opt_silent";
note "Example: ask_zenity 0 --question --text 'Confirmation'";
note "######################################################";
function ask_zenity(){
 if is_number "$1"; then 
  note "On silent return default value";
  if has_opt_silent; then return $1; fi;
  shift; note "Skipping first argument";
 fi;
 zenity "$@";
 return $?;
};

note "#######################";
note "Print user/script info.";
note "#######################";
function print_identification(){
 echo "UserID: $USER@$HOSTNAME on $(lsb_release -ds)";
 echo "RunDir: $startupdir";
 echo "Locate: $scripthome";
 echo "Script: $scriptname";
 echo "Target: $targetname";
 echo "Silent: $opt_silent";
};

note "############################";
note "Check packages is installed.";
note "############################";
function check_install_package(){
 while [ -n "$1" ]; do
  local program="$(echo "$1" | cut -d : -f 1)";
  local package="$(echo "$1" | cut -d : -f 2)";
  echo -n "Check $program from package $package: "
  if [ -z "$(which $program)" ]; then sudo apt install $1; fi;
  if [ -n "$(which $program)" ]; then echo "OK"; else echo "FAIL"; fatal "Error: missed package $package."; fi;
  shift;
 done;
};

note "####################################";
note "Check dpkg (deb) packages installed.";
note "####################################";
function check_dpkg_installed(){
 local n1=0; local n2=0;
 while [ -n "$1" ]; do
  if dpkg --status "$1" >/dev/null 2>&1; then
   echo "Check package $1: Already installed";
   let n1+=1;
  else
   echo "Check package $1: Not installed yet";
   let n2+=1;
  fi;
  shift;
 done;
 echo "$n1 package(s) already installed.";
 echo "$n2 package(s) not installed yet.";
 if [ $n2 -gt 0 ]; then return 1; fi;
 return 0;
};

note "#######################################";
note "Check dpkg (deb) packages is installed.";
note "#######################################";
function check_install_dpkg(){
 if check_dpkg_installed "$@"; then
  echo "All required packages found.";
 else
  sudo apt install "$@";
 fi;
};

note "################################";
note "Uses language dependent strings?";
note "################################";
let uses_langstr=1; note "# 1/0=ON/OFF";

note "##############################";
note "Language dependent string echo";
note "langstr en 'Hello' ru 'Привет'";
note "##############################";
function langstr(){
 local lng="en"; local msg="";
 if [ "$uses_langstr" = "1" ]; then lng="${LANG:0:2}"; fi;
 while [ -n "$lng" ] && [ -n "$1" ] && [ -n "$2" ]; do
  if [ "$lng" = "$1" ]; then msg="$2"; break; fi;
  if [ "$lng" = "en" ]; then msg="$2"; fi;
  if [ "$lng" = "us" ]; then msg="$2"; fi;
  if [ "$lng" = "uk" ]; then msg="$2"; fi;
  shift; shift;
 done;
 if [ -n "$msg" ]; then echo "$msg"; fi;
};

note "###################################";
note "Notification messages with timeout.";
note "Example: notify_ok title message 10";
note "Example: notify_ok 10 title message";
note "###################################";
declare -i default_notify_timeout=5;
function notify_info(){
 if which notify-send >/dev/null 2>&1; then
  local timeout="$default_notify_timeout";
  if is_number "$1"; then timeout="$1"; shift; fi;
  if is_number "$3"; then timeout="$3"; fi; timeout="$(echo "$timeout*1000" | bc)";
  notify-send -u low -t $timeout -i dialog-information "$(date +%Y.%m.%d-%H:%M:%S) - $1" "$2";
 fi;
 echo -ne "\n$1 - Information: $2\n\n";
};
function notify_ok(){
 if which notify-send >/dev/null 2>&1; then
  local timeout="$default_notify_timeout";
  if is_number "$1"; then timeout="$1"; shift; fi;
  if is_number "$3"; then timeout="$3"; fi; timeout="$(echo "$timeout*1000" | bc)";
  notify-send -u normal -t $timeout -i dialog-ok "$(date +%Y.%m.%d-%H:%M:%S) - $1" "$2";
 fi;
 echo -ne "\n$1 - Ok: $2\n\n";
};
function notify_ok_apply(){
 if which notify-send >/dev/null 2>&1; then
  local timeout="$default_notify_timeout";
  if is_number "$1"; then timeout="$1"; shift; fi;
  if is_number "$3"; then timeout="$3"; fi; timeout="$(echo "$timeout*1000" | bc)";
  notify-send -u normal -t $timeout -i dialog-ok-apply "$(date +%Y.%m.%d-%H:%M:%S) - $1" "$2";
 fi;
 echo -ne "\n$1 - Ok-apply: $2\n\n";
};
function notify_warning(){
 if which notify-send >/dev/null 2>&1; then
  local timeout="$default_notify_timeout";
  if is_number "$1"; then timeout="$1"; shift; fi;
  if is_number "$3"; then timeout="$3"; fi; timeout="$(echo "$timeout*1000" | bc)";
  notify-send -u normal -t $timeout -i dialog-warning "$(date +%Y.%m.%d-%H:%M:%S) - $1" "$2";
 fi;
 echo -ne "\n$1 - Warning: $2\n\n";
};
function notify_cancel(){
 if which notify-send >/dev/null 2>&1; then
  local timeout="$default_notify_timeout";
  if is_number "$1"; then timeout="$1"; shift; fi;
  if is_number "$3"; then timeout="$3"; fi; timeout="$(echo "$timeout*1000" | bc)";
  notify-send -u critical -t $timeout -i dialog-cancel "$(date +%Y.%m.%d-%H:%M:%S) - $1" "$2";
 fi;
 echo -ne "\n$1 - Cancel: $2\n\n";
};
function notify_error(){
 if which notify-send >/dev/null 2>&1; then
  local timeout="$default_notify_timeout";
  if is_number "$1"; then timeout="$1"; shift; fi;
  if is_number "$3"; then timeout="$3"; fi; timeout="$(echo "$timeout*1000" | bc)";
  notify-send -u critical -t $timeout -i dialog-error "$(date +%Y.%m.%d-%H:%M:%S) - $1" "$2";
 fi;
 echo -ne "\n$1 - Error: $2\n\n";
};

note "########################";
note "Check sudo is available.";
note "########################";
function check_sudo_is_avail(){
 if sudo true; then return 0; else return 1; fi;
};

note "###########################";
note "User/root remove directory.";
note "Example: root_rmdir /opt/xx";
note "###########################";
function user_rmdir(){
 local dir="$1";
 if [ -z "$dir" ]; then return 0; fi;
 if [ -e "$dir" ]; then rm -rvf $dir; fi;
};
function root_rmdir(){
 local dir="$1";
 if [ -z "$dir" ]; then return 0; fi;
 if [ -e "$dir" ]; then sudo rm -rvf $dir; fi;
};

note "#########################";
note "User/root make directory.";
note "Example: user_mkdir ~/xyz";
note "#########################";
function user_mkdir(){
 local dir="$1";
 if [ -z "$dir" ]; then return 0; fi;
 if [ -e "$dir" ]; then return 0; fi;
 mkdir -pv $dir;
};
function root_mkdir(){
 local dir="$1";
 if [ -z "$dir" ]; then return 0; fi;
 if [ -e "$dir" ]; then return 0; fi;
 sudo mkdir -pv $dir;
};

note "##############################################################";
note "Unpack *.tar to given directory (dir), chmod dir, chown dir/*.";
note "Supported .tar|.tar.gz|.tar.bzip2|.tar.xz formats (autodetect)";
note "Example: unpack_tar demo.tar.xz /opt/test 777 root $USER:$USER";
note "##############################################################";
function unpack_tar(){
 local arc="$1";
 local dir="$2";
 local mod="$3";
 local usr="$4";
 local own="$5";
 local run=""; local ownopt="";
 echo "Try unpack $arc to $dir …";
 if [ "$usr" = "root" ]; then
  run="sudo"; ownopt="--no-same-owner";
  root_mkdir $dir;
 else
  user_mkdir $dir;
 fi;
 local opt="";
 case $arc in
  *.tar) opt="-xpvf"; ;;
  *.txz|*.tar.xz) opt="-xpJvf"; ;;
  *.tgz|*.tar.gz|*.tar.gzip) opt="-xpzvf"; ;;
  *.tbz|*.tar.bz|*.tar.bz2|*.tar.bzip2) opt="-xpjvf"; ;;
  *) fatal "Error: expected (.tar|.tar.xz|.tar.gz|.tar.bz) archive."; ;;
 esac;
 if $run tar $opt $arc -C $dir $ownopt; then
  notify_info "$targetname" "\n $(langstr en 'Archive' ru 'Архив') $arc $(langstr en 'unpacked' ru 'распакован')\n $(langstr en 'to' ru 'в') $dir." 15;
 else
  local bug="<big>$(langstr en 'Error unpack archive' ru 'Ошибка распаковки архива'):\n<b>$arc</b></big>";
  ask_zenity 0 --error --text "$bug" --timeout 180 >/dev/null 2>&1;
  fatal "Error: unpack $arc."
 fi;
 if [ "$dir" != "/" ] && [ "$dir" != "$HOME" ]; then
  if [ -n "$mod" ]; then $run chmod -c $mod $dir; fi;
 fi;
 if [ "$usr" = "root" ] && [ "$dir" != '.' ] && [ "$dir" != '..' ]; then
  local uid="$(echo "$own" | cut -d : -f 1)";
  local gid="$(echo "$own" | cut -d : -f 1)";
  if [ -n "$own" ]; then $run chown -cR $uid:$gid $dir/*; fi;
 fi;
};

note "#############################";
note "Ask confirm on file(s) exist.";
note "ask_confirm_files . 4 a b c d";
note "#############################";
function ask_confirm_files(){
 if [ "$#" -lt 3 ]; then return 0; fi;
 if [ -e $1/ ] && [ $2 -gt 0 ] && [ -e $1/$3 ]; then
  local dir="$1"; local num="$2"; shift; shift;
  local lst="$(echo $* | sed 's/\s/\\n/g')";
  local ask="<big>$(langstr en 'Files is already exists in target directory' ru 'Файлы уже есть в целевом каталоге')";
  ask="$ask\n<b><span fgcolor='green'>$dir</span></b>:\n";
  ask="$ask\n<b><span fgcolor='blue'>$lst</span></b>\n";
  ask="$ask\n$(langstr en 'File(s) found' ru 'Файл(ов) найдено'): $num\n";
  ask="$ask\n$(langstr en 'This files will be overwritten' ru 'Эти файлы будут перезаписаны').\n";
  ask="$ask\n<b><big><span fgcolor='red'>$(langstr en 'Confirm file overwrite?' ru 'Подтверждаете перезапись файлов?')</span></big></b></big>";
  ask_zenity 0 --question --text "$ask" --timeout 180 --width 800 --height 300 >/dev/null 2>&1;
  return $?;
 fi;
 return 0;
};

note "#############################";
note "tar list/check before unpack.";
note "#############################";
function tar_list_check_exist(){
 local arc="$1"; local dir="$2"; local num=0; local lst=""; local lim=9; local nls=0;
 local opt="";
 case $arc in
  *.tar) opt="-tf"; ;;
  *.txz|*.tar.xz) opt="-tJf"; ;;
  *.tgz|*.tar.gz|*.tar.gzip) opt="-tzf"; ;;
  *.tbz|*.tar.bz|*.tar.bz2|*.tar.bzip2) opt="-tjf"; ;;
  *) fatal "Error: expected (.tar|.tar.xz|.tar.gz|.tar.bz) archive."; ;;
 esac;
 if [ -e $arc ] && [ -e $dir/ ]; then
  echo -ne "\nChecking archive $(basename $arc) …\n … PLEASE WAIT FOR SOME TIME …\n";
  for item in $(tar $opt $arc); do
   note "Skip the archive self name to avoid false trigger.";
   if [[ "$item" = "$arc" ]]; then continue; fi;
   if [[ $nls -eq 0 ]]; then echo -ne " … CHECKED, ASK CONFIRMATION …\n\n"; fi;
   local name="$dir/$item";
   if [[ -e $name ]]; then
    if [[ $num -lt $lim ]]; then lst="$lst $item"; fi;
    if [[ $num -eq $lim ]]; then lst="$lst …"; fi;
    let num+=1;
   fi;
   let nls+=1;
  done;
  if [[ $num -gt 0 ]]; then
   if ask_confirm_files $dir $num $lst; then return 0; else return 1; fi;
  fi;
 fi;
 return 0;
};

note "###################################################";
note "Set max. tar compression mode via environment vars.";
note "Set XZ_OPT=-9 for .xz, GZIP=-9 for .gz archive arg.";
note "###################################################";
function set_max_tar_compression_mode(){
 local arc="$1";
 case $arc in
  *.tar) note "Nothing"; ;;
  *.txz|*.tar.xz) if [ -z "$XZ_OPT" ]; then export XZ_OPT="-9"; fi; ;;
  *.tgz|*.tar.gz|*.tar.gzip) if [ -z "$GZIP" ]; then export GZIP="-9"; fi; ;;
  *.tbz|*.tar.bz|*.tar.bz2|*.tar.bzip2) note "Nothing"; ;;
  *) fatal "Error: expected (.tar|.tar.xz|.tar.gz|.tar.bz) archive."; ;;
 esac;
};

note "###########################";
note "Hello USER text for zenity.";
note "###########################";
function zen_hello_user(){
 local ask="$(whoami)";
 if [ "$ask" = "root" ]; then
  ask="<span fgcolor='red'>$ask</span>";
 else
  ask="<span fgcolor='green'>$ask</span>";
 fi;
 ask="<big>$(langstr en 'Hello' ru 'Привет') <b>$ask</b>!</big>";
 echo "$ask";
};

note "#################################";
note "Text for zenity: Do you want to …";
note "#################################";
function zen_do_you_want_to(){
 echo "<big>$(langstr en 'Do you want to' ru 'Вы хотите') <b><span fgcolor='red'>$*</span></b>:</big>";
};

note "##############################################";
note "Check running as user, confirm root execution.";
note "##############################################";
function check_confirm_iam_user(){
 if [ "$(whoami)" = "root" ]; then
  local rmd="<big><b><span fgcolor='red'>root</span></b></big>";
  local umd="<big><b><span fgcolor='green'>user</span></b></big>";
  local ask="<big>$(langstr en "Expected $umd mode, but $rmd found" ru "Ожидается режим $umd, а обнаружен $rmd").</big>\n";
  ask="$ask\n<big>$(langstr en "Continue execution under $rmd" ru "Продолжить выполнение под $rmd")?</big>";
  if ask_zenity 0 --question --text "$ask" --width 400 --height 100 --timeout 180 >/dev/null 2>&1; then
   note "Continue execution as root.";
   return 0;
  else
   return 1;
  fi;
 fi;
 return 0;
};

note "##################################################";
note "Local user *.desktop applications files directory.";
note "##################################################";
readonly local_apps_dir="$HOME/.local/share/applications";

note "######################################################";
note "Delete local user *.desktop application(s) and update.";
note "Example: local_apps_del ap1.desktop ap2.desktop update";
note "######################################################";
function local_apps_del(){
 local nrm=0;
 local update=0;
 while [ -n "$1" ]; do
  case "$(basename $1)" in
   *.desktop) if [ -e "$local_apps_dir/$1" ]; then if rm -fv $local_apps_dir/$1; then let nrm+=1; fi; fi; ;;
   update) update=1; ;;
   *) continue; ;;
  esac;
  shift;
 done;
 if [ $update -gt 0 ] && [ $nrm -gt 0 ]; then
  echo "Del $nrm desktop application(s).";
  update-desktop-database $local_apps_dir;
 fi;
};

note "######################################################";
note "Adds a local user *.desktop application(s) and update.";
note "Example: local_apps_add a/b.desktop c/d.desktop update";
note "######################################################";
function local_apps_add(){
 local ncp=0;
 local update=0;
 while [ -n "$1" ]; do
  case "$(basename $1)" in
   *.desktop) if [ -e "$1" ]; then if cp -fv $1 $local_apps_dir/; then let ncp+=1; fi; fi; ;;
   update) update=1; ;;
   *) continue; ;;
  esac;
  shift;
 done;
 if [ $update -gt 0 ] && [ $ncp -gt 0 ]; then
  echo "Add $ncp desktop application(s).";
  update-desktop-database $local_apps_dir;
 fi;
};

note "##############################";
note "Is two files has same content?";
note "##############################";
function is_same_content(){
 if [ -n "$1" ] && [ -e "$1" ] && [ -n "$2" ] && [ -e "$2" ]; then
  if diff "$1" "$2" >/dev/null 2>&1; then
   note "$1 and $2 has same content.";
   return 0;
  fi;
 fi;
 return 1;
};

note "##############################";
note "Is two files has same release?";
note "##############################";
function is_same_release(){
 local curr="undetected"; if [ -n "$1" ] && [ -e "$1" ]; then curr="$(cat "$1" | head -n 1)"; fi;
 local prev="undetected"; if [ -n "$2" ] && [ -e "$2" ]; then prev="$(cat "$2" | head -n 1)"; fi;
 echo "Release to be install: $curr";
 echo "Release was installed: $prev";
 if is_same_content "$1" "$2"; then
  echo "Exactly the same release found.";
  note "$1 and $2 has same release.";
  return 0;
 else
  echo "Releases are different.";
 fi;
 return 1;
};

note "#################################################";
note "Check is same release, ask confirmation to break.";
note "#################################################";
function check_same_release(){
 if is_same_release "$releasefile" "$releasesdir/$releasefile"; then
  local rel="$(cat $releasefile)";
  local ask="<big>$(langstr en "Package <b><span color='blue'>$scriptname</span></b> release <b><span color='green'>$rel</span></b> is already installed." ru "Пакет <b><span color='blue'>$scriptname</span></b> версии <b><span color='green'>$rel</span></b> уже установлен.").</big>\n";
  ask="$ask\n<big><b><span color='red'>$(langstr en "Break installation" ru "Прервать инсталляцию")</span></b>?</big>";
  if ask_zenity 0 --question --text "$ask" --width 400 --height 100 --timeout 180 >/dev/null 2>&1; then
   local date="$(date +%Y.%m.%d-%H:%M:%S)";
   local msg="$(langstr en "Package $targetname release $rel is already installed." ru "Пакет $targetname версии $rel уже  установлен.")";
   if which notify-send >/dev/null 2>&1; then notify-send -t 15000 -i dialog-information "$date: $scriptname" "$msg"; fi;
   echo -ne "\nCancel: package $targetname release $rel is already installed.\n\n";
   exit 0;
  fi;
 fi;
 return 0;
};

note "############################";
note "Save information on release.";
note "############################";
function save_info_on_release(){
 if [ -e "$releasefile" ]; then
  echo "Save $targetname release file:";
  if [ ! -e  "$releasesdir/" ]; then mkdir -p "$releasesdir"; fi;
  cp -fv "$releasefile" "$releasesdir/";
 fi;
};

note "#######################################";
note "Print version of installed .deb package";
note "#######################################";
function print_deb_dpkg_version(){
 if [ -n "$1" ]; then
  dpkg -s "$1" | grep '^\s*Version:\s*' | sed 's/^\s*Version:\s*//';
 fi;
};

note "########################################";
note "Print version of .deb file to be install";
note "########################################";
function print_deb_file_version(){
 if [ -n "$1" ] && [ -e "$1" ]; then
  case $1 in
   *.deb) dpkg-deb -I "$1" | grep '^\s*Version:\s*' | sed 's/^\s*Version:\s*//'; ;;
   *) fatal "Error: expected .deb file in $(basename $1)"; ;;
  esac;
 fi;
};

note "##################################################################";
note "Compare versions of installed deb package (\$1) and deb-file (\$2)";
note "Example: is_same_deb_version daqgroup-dim install-daqgroup-dim.deb";
note "##################################################################";
function is_same_deb_version(){
 if [ -n "$1" ] && [ -n "$2" ] && [ -e "$2" ]; then
  local pv="$(print_deb_dpkg_version $1)";
  local fv="$(print_deb_file_version $2)";
  echo "dpkg version: $pv of $1";
  echo "file version: $fv of $2";
  if [ -n "$pv" ] && [ -n "$fv" ] && [ "$pv" = "$fv" ]; then
   echo "dpkg has same version as file";
   return 0;
  fi;
 fi;
 return 1;
};

note "##########################";
note "Common predinstall actions";
note "##########################";
function common_predinst(){
 check_same_release;
};

note "##########################";
note "Common postinstall actions";
note "##########################";
function common_postinst(){
 save_info_on_release;
};

#############
# END OF FILE
#############
