#! /bin/bash
# -*- coding: utf-8 -*-
#
# Duplicate postgres DB
#
# This free software is released under GNU Affero GPL3
# author: Antonio M. Vigliotti - antoniomaria.vigliotti@gmail.com
# (C) 2015-2023 by SHS-AV s.r.l. - http://www.shs-av.com - info@shs-av.com
#
READLINK=$(which greadlink 2>/dev/null) || READLINK=$(which readlink 2>/dev/null)
export READLINK
# Based on template 2.0.0
THIS=$(basename "$0")
TDIR=$(readlink -f $(dirname $0))
[ $BASH_VERSINFO -lt 4 ] && echo "This script $0 requires bash 4.0+!" && exit 4
if [[ -z $HOME_DEVEL || ! -d $HOME_DEVEL ]]; then
  [[ -d $HOME/odoo/devel ]] && HOME_DEVEL="$HOME/odoo/devel" || HOME_DEVEL="$HOME/devel"
fi
[[ -x $TDIR/../bin/python3 ]] && PYTHON=$(readlink -f $TDIR/../bin/python3) || [[ -x $TDIR/python3 ]] && PYTHON="$TDIR/python3" || PYTHON=$(which python3 2>/dev/null) || PYTHON="python"
[[ -z $PYPATH ]] && PYPATH=$(echo -e "import os,sys\no=os.path\na=o.abspath\nj=o.join\nd=o.dirname\nb=o.basename\nf=o.isfile\np=o.isdir\nC=a('"$TDIR"')\nD='"$HOME_DEVEL"'\nif not p(D) and '/devel/' in C:\n D=C\n while b(D)!='devel':  D=d(D)\nN='venv_tools'\nU='setup.py'\nO='tools'\nH=o.expanduser('~')\nT=j(d(D),O)\nR=j(d(D),'pypi') if b(D)==N else j(D,'pypi')\nW=D if b(D)==N else j(D,'venv')\nS='site-packages'\nX='scripts'\ndef pt(P):\n P=a(P)\n if b(P) in (X,'tests','travis','_travis'):\n  P=d(P)\n if b(P)==b(d(P)) and f(j(P,'..',U)):\n  P=d(d(P))\n elif b(d(C))==O and f(j(P,U)):\n  P=d(P)\n return P\ndef ik(P):\n return P.startswith((H,D,K,W)) and p(P) and p(j(P,X)) and f(j(P,'__init__.py')) and f(j(P,'__main__.py'))\ndef ak(L,P):\n if P not in L:\n  L.append(P)\nL=[C]\nK=pt(C)\nfor B in ('z0lib','zerobug','odoo_score','clodoo','travis_emulator'):\n for P in [C]+sys.path+os.environ['PATH'].split(':')+[W,R,T]:\n  P=pt(P)\n  if B==b(P) and ik(P):\n   ak(L,P)\n   break\n  elif ik(j(P,B,B)):\n   ak(L,j(P,B,B))\n   break\n  elif ik(j(P,B)):\n   ak(L,j(P,B))\n   break\n  elif ik(j(P,S,B)):\n   ak(L,j(P,S,B))\n   break\nak(L,os.getcwd())\nprint(' '.join(L))\n"|$PYTHON)
[[ $TRAVIS_DEBUG_MODE -ge 8 ]] && echo "PYPATH=$PYPATH"
for d in $PYPATH /etc; do
  if [[ -e $d/z0librc ]]; then
    . $d/z0librc
    Z0LIBDIR=$(readlink -e $d)
    break
  fi
done
[[ -z "$Z0LIBDIR" ]] && echo "Library file z0librc not found in <$PYPATH>!" && exit 72
[[ $TRAVIS_DEBUG_MODE -ge 8 ]] && echo "Z0LIBDIR=$Z0LIBDIR"
ZARLIB=$(findpkg zarrc "$PYPATH")
[[ -z "$ZARLIB" ]] && echo "Library file zarrc not found!" && exit 72
. $ZARLIB
[[ $TRAVIS_DEBUG_MODE -ge 8 ]] && echo "ZARLIB=$ZARLIB"
TESTDIR=$(findpkg "" "$TDIR . .." "tests")
[[ $TRAVIS_DEBUG_MODE -ge 8 ]] && echo "TESTDIR=$TESTDIR"
RUNDIR=$(readlink -e $TESTDIR/..)
[[ $TRAVIS_DEBUG_MODE -ge 8 ]] && echo "RUNDIR=$RUNDIR"

# DIST_CONF=$(findpkg ".z0tools.conf" "$PYPATH")
# TCONF="$HOME/.z0tools.conf"
CFG_init "ALL"
link_cfg_def
link_cfg $DIST_CONF $TCONF
[[ $TRAVIS_DEBUG_MODE -ge 8 ]] && echo "DIST_CONF=$DIST_CONF" && echo "TCONF=$TCONF"
get_pypi_param ALL
RED="\e[1;31m"
GREEN="\e[1;32m"
CLR="\e[0m"

__version__=2.0.4


kill_process() {
#kill_process(db)
    run_traced "pg_db_active -wa \"$1\""
}

backup_db() {
    local db=$1
    kill_process $db
    run_traced "pg_dump -U$opt_user -Fp -f $DBCKDIR/$db-00000000.sql \"$db\""
}

check_4_table() {
# check_4_table DB table what
    local DB=$1
    local tbl=$2
    local what=$3
    local x
    if [[ "$what" == "exists" ]]; then
       x=$(psql -tc "select * from pg_tables where tablename = '$tbl';" $DB)
    elif [[ $what == "with_data" ]]; then
       x=$(psql -tc "select * from $tbl limit 1;" $DB)
    elif [[ $what == "active_data" ]]; then
       x=$(psql -tc "select * from $tbl where active=true limit 1;" $DB)
    elif [[ $what == "reset_data" ]]; then
       x=$(psql -tc "select * from $tbl where active=true limit 1;" $DB)
    else
       x=""
    fi
    if [[ -n "$x" ]]; then
      if [[ $what == "reset_data" ]]; then
         psql -tc "update $tbl set active=false where active=true;" $DB
      fi
      return 0
    fi
    return 1
}

stop_services() {
# stop_services(DB quickmode)
    if [[ -z "$1" ]]; then
      return 1
    fi
    local c i x DB tbl res wtm act wfid SVCS SVCS2
    DB="$1"
    SVCS="$(get_cfg_value 0 services_2_stop)"
    if [[ -z "$SVCS" ]]; then
      SVCS="ir_mail_server fetchmail_server base_action_rule ir_cron"
    else
      SVCS=${SVCS//,/ }
    fi
    SVCS2="$(get_cfg_value 0 services_2_restart)"
    if [[ -z "$SVCS2" ]]; then
      SVCS2="base_action_rule"
    else
      SVCS2=${SVCS2//,/ }
    fi
    unset tblok tblwf
    declare -A tblok tblwf
    if [[ -z "${svc2run[*]}" ]]; then
      unset svc2run
      declare -gA svc2run
    fi
    wrkflw=("exists" "with_data" "reset_data" "Ok")
    if [[ -n "$2" ]]; then
      wlog ". Starting services quick checker on \"$DB\""
      local MAXC=5
    else
       wlog ".. Starting services checker on \"$DB\" ..."
      local MAXC=50
    fi
    res=1
    while [[ $res -ne 0 && $MAXC -gt 0 ]]; do
      res=0
      wtm=2
      tnx=0
      for tbl in $SVCS; do
        if [[ -z "${tblwf[$tbl]}" ]]; then
          wfid=0
        else
          wfid=${tblwf[$tbl]}
        fi
        act=${wrkflw[$wfid]}
        if [[ "$act" != "Ok" ]]; then
          if [[ "$act" == "exists" ]]; then
            tnx=1
          fi
          res=1
          check_4_table "$DB" $tbl $act
          if [[ $? -eq 0 ]]; then
            ((wfid++))
            tblwf[$tbl]=$wfid
            if [[ $act == "with_data" && -z "${svc2run[$tbl]}" ]]; then
              x=$(psql -AR\| -tc "select cron_name from $tbl where active=true;" "$DB" 2>/dev/null|tr "\n" " ")
              [[ -z $x ]] && x=$(psql -AR\| -tc "select name from $tbl where active=true;" "$DB" 2>/dev/null|tr "\n" " ")
              if [[ -n "$x" ]]; then
                svc2run[$tbl]="${svc2run[$tbl]} $x"
              fi
            fi
          else
            if [[ "${tblok[$tbl]}" != "$act" ]]; then
              if [[ $act == "exists" ]]; then
                wlog "Table $tbl does not exit !!"
              fi
              tblok[$tbl]="$act"
              if [[ $act == "exists" && $MAXC -lt 75 ]]; then
                ((MAXC=MAXC+5))
              elif [[ -z "$2" && $act == "reset_data" ]]; then
                if [[ $MAXC -gt 5 ]]; then
                  MAXC=5
                fi
                wtm=1
              fi
            fi
            if [[ -z "$2" && $act != "reset_data" ]]; then
              ((wtm++))
            fi
          fi
        fi
      done
      [[ $res -ne 0 ]] && sleep $wtm
      [[ $tnx -eq 0 && $MAXC -gt 10 ]] && MAXC=10
      ((MAXC--))
    done
    for tbl in $SVCS; do
      if [ -n "$2" -a -n "${svc2run[$tbl]}" ]; then
        if [[ " $SVCS2 " =~ [[:space:]]$tbl[[:space:]] ]]; then
          c=$(echo ${svc2run[$tbl]}|awk -F\| '{print NF}')
          x=
          i=0
          while [ $i -lt $c ]; do
            res="$(echo ${svc2run[$tbl]}|awk -F\| '{print $'$i'}')"
            psql -tc "update $tbl set active=true where name='$res';" "$DB"
            ((i++))
          done
        fi
      fi
    done
    return 0
}

get_sqlfname() {
# get_sqlfname(db dtdb)
# current dir must be BCKDIR
    local db="$1"
    local dtdb=$2
    if [ -z "$opt_name" ]; then
      expand_tar "$db" $db-$dtdb.sql
      SQLFNAME=$DBCKDIR/$db-$dtdb.sql
    else
      SQLFNAME=$opt_name
    fi
    if [ $opt_dry_run -eq 0 ]; then
      if [ "$db" == "$NEWDB" ]; then
        wlog "Restore $SQLFNAME into $NEWDB"
      else
        wlog "Duplicate $SQLFNAME into $NEWDB"
      fi
    else
      if [ "$db" == "$NEWDB" ]; then
        wlog "File $SQLFNAME should be restored into $NEWDB"
      else
        wlog "File $SQLFNAME should be duplicate into $NEWDB"
      fi
    fi
    if [ ! -f $SQLFNAME ]; then
      wlog "No file $SQLFNAME found!"
      passed="$passed $NEWDB-$dtdb ${db}-$dtdb"
      SQLFNAME=
    fi
}

create_script_db_2_restore() {
# drop_db_2_restore(db fsql)
    local NEWDB="$1"
    local fsql=$2
    local dtc=$(date "+%Y-%m-%d %H:%M:%S")
    wlog "Prepare file script $fsql from $SQLFNAME ..."
    if [ -n "$ORIGINAL_OWN" -a $ORIGINAL_OWN -ne 0 ]; then
      NEW_OWNER=$(head $SQLFNAME -n100|grep --color=never -Eo "Owner: *[a-zA-Z0-9_]+"|head -n1|awk -F: '{print $2}')
      NEW_OWNER=$(echo $NEW_OWNER)
      OLD_OWNER=$opt_user
      # reass_own=0
    else
      NEW_OWNER=$OE_OWN
      # OLD_OWNER=$PRD_OE_OWN
      OLD_OWNER=$opt_user
    fi
    echo "\\c postgres">$fsql
    echo "\\echo Creating new DB \"$NEWDB\" ($dtc)">>$fsql
    echo "DROP DATABASE IF EXISTS \"$NEWDB\";">>$fsql
    echo "CREATE DATABASE \"$NEWDB\" TEMPLATE template1;">>$fsql
    echo "\\c \"$NEWDB\"">>$fsql
    echo "\\echo Loading data from $SQLFNAME into DB \"$NEWDB\"">>$fsql
    echo "\\i $SQLFNAME">>$fsql
    # echo "\\echo Set NEW_OWNER of \"$NEWDB\" to $NEW_OWNER;">>$fsql
    # echo "ALTER DATABASE \"$NEWDB\" OWNER TO $NEW_OWNER;">>$fsql
    # if [ $reass_own -ne 0 -a "$PRD_OE_OWN" != "$DEV_OE_OWN" ]; then
    #   echo "\\echo Reassign owner from $PRD_OE_OWN to $NEW_OWNER">>$fsql
    #   echo "REASSIGN OWNED BY $PRD_OE_OWN TO $DEV_OE_OWN;">>$fsql
    # fi
}

inherit_options() {
    OPTS=
    if [ $opt_ex -gt 0 ]; then
      OPTS=e$OPTS
    fi
    if [ $opt_cron -gt 0 ]; then
      OPTS=K$OPTS
    fi
    if [ $opt_dry_run -gt 0 ]; then
      OPTS=n$OPTS
    fi
    if [ $opt_verbose -gt 0 ]; then
      OPTS=v$OPTS
    fi
    if [ $test_mode -gt 0 ]; then
      OPTS=T$OPTS
    fi
    if [ -n "$opt_user" ]; then
      OPTS="${OPTS}U$opt_user"
    fi
    if [ -n "$OPTS" ]; then
      OPTS=-$OPTS
    fi
    if [ -n "$opt_conf" -a "$opt_conf" != "$TDIR/zar.conf" ]; then
      OPTS="$OPTS -c $opt_conf"
    fi
}


OPTOPTS=(h        c        d       e      K        l       m        N         n            o        R       T         U          V           v)
OPTDEST=(opt_help opt_conf seldate opt_ex opt_cron opt_log opt_mail opt_newdb opt_dry_run  opt_name opt_rst test_mode opt_user   opt_version opt_verbose)
OPTACTI=("+"      "="      "="     1      1        "="     1        "="       1            "="      1       1         "="        "*>"        1)
OPTDEFL=(0        ""       ""      1      0        ""      -1       ""        0            ""       0       0         "postgres" ""          0)
OPTMETA=("help"   "file"   "date"  ""     "cron"   "file" "mail"    "dbname"  "do nothing" "fname"  ""      "test"    "username" "version"   "verbose")
OPTHELP=("this help"\
 "configuration file (def zar.conf)"\
 "select date (format yyyymmdd)"\
 "exact DB name (rather matches DB name)"\
 "run in cron environment"\
 "log filename (def /var/log/zar/restdb.log)"\
 "disable mail server on target DB"\
 "new db name"\
 "do nothing (dry-run)"\
 "input filename (def dbname-date)"\
 "stop mail server and services"\
 "test mode (implies dry-run)"\
 "username (def postgres)"\
 "show version"\
 "verbose mode")
OPTARGS=(olddb dummy)

parseoptargs "$@"
if [ "$opt_version" ]
then
  echo "$__version__"
  exit 0
fi
if [ -n "$dummy" ]; then
  opt_help=1
elif [[ -n "$seldate" && ! $seldate =~ [0-9]{8} ]]; then
  opt_help=1
fi
if [ $opt_help -gt 0 ]
then
  print_help "Restore/duplicate postgres DB"\
  "(C) 2015-2023 by zeroincombenze®\nhttp://wiki.zeroincombenze.org/en/Linux/dev\nAuthor: antoniomaria.vigliotti@gmail.com"
  exit 0
fi
CFG_init
conf_default "restdb"
if [ $opt_cron -gt 0 ]; then
  detached_login "$@"
fi
link_cfg $FCONF
if [ $opt_rst -gt 0 ]; then
  stop_services "$olddb"
  exit $?
fi
wlog "=== Starting restore/duplicate $__version__ by $opt_user ==="

init_vars "$olddb"
if [ -z "$DBSEL" ]; then
  DBSEL=".*"
  opt_ex=0
fi
dblist=$(create_db_list_from_file "$DBSEL" "$PGDIR" "$DBCKDIR" "$REDB")
if [ "$BCKFORMAT" == "0" ]; then
  wlog "-- Backupped DBs stored in sql files --"
else
  wlog "-- Backupped DBs stored in compress tar files --"
fi
if [ "$BCK_ENV" == "PRD" ]; then
  wlog "-- Production environment --"
elif [ "$BCK_ENV" == "DEV" ]; then
  wlog "-- Development/backup environment --"
else
  wlog "-- Unique environment w/o dev/backup host --"
fi
wlog "PRD=$HOST_PRD//$URL_PRD"
wlog "DEV=$HOST_DEV//$URL_DEV"
wlog "TEST_DB=$TEST_DB"
if [ -n "$ORIGINAL_OWN" -a $ORIGINAL_OWN -ne 0 ]; then
  wlog "ORIGINAL_OWN=$ORIGINAL_OWN"
else
  wlog "OE_OWN=$OE_OWN"
  wlog "PRD_OE_OWN=$PRD_OE_OWN"
  wlog "DEV_OE_OWN=$DEV_OE_OWN"
fi
wlog "- DB to Restore:$dblist"
if [ "$BCK_ENV" == "DEV" ]; then
  inherit_options
  if [ -f $TDIR/zar_purge ]; then
    cmd=$TDIR/zar_purge
  else
    cmd=./zar_purge
  fi
  wlog "\$ $cmd $OPTS"
  eval $cmd $OPTS
fi
if [ -n "$opt_newdb" ]; then
  NEWDBS=$opt_newdb
else
  NEWDBS=${NEWDBS//|/,}
  NEWDBS=${NEWDBS//(/}
  NEWDBS=${NEWDBS//)/}
  NEWDBS=${NEWDBS//^/}
  NEWDBS=${NEWDBS//$/}
fi
if [ "$BCK_ENV" == "PRD" ]; then
  if [ -z "$DBSEL" -o -z "$NEWDBS" ]; then
    wlog "Cannot restore due DBSEL=$DBSEL or NEWDBS=$NEWDBS"
    print_help
    exit
  fi
elif [ "$BCK_ENV" == "DEV" ]; then
  if [ -z "$DBSEL" ]; then
    wlog "Cannot restore due DBSEL=$DBSEL"
    print_help
    exit
  fi
fi
if [[ $seldate =~ [0-9]{8} ]]; then
  NDAYSB=$seldate
elif [ "$BCK_ENV" == "PRD" ]; then
  NDAYSB="$(get_cfg_value 0 PRD_num_days_before)"
elif [ "$BCK_ENV" == "DEV" ]; then
  NDAYSB="$(get_cfg_value 0 DEV_num_days_before)"
fi
if [ -z "$NDAYSB" ]; then
  NDAYSB="$(get_cfg_value 0 num_days_before)"
fi
if [ -z "$NDAYSB" ]; then
  if [ "$BCK_ENV" == "PRD" ]; then
    NDAYSB=yesterday
  else
    NDAYSB=today
  fi
fi
dovalid=0
for sfx in A B C D E F G H; do
  x=DB${sfx}
  XDB=${!x}
  [ -n "$XDB" ] && dovalid=1 && break
done
ctr=0
passed=""
if [ $dovalid -gt 0 ]; then
  pushd $DBCKDIR >/dev/null
  for db in $dblist; do
    # if [[ " $passed " =~ [[:space:]]${db}- ]]; then
    #   continue
    # fi
    set_db_vars "$db"
    if [ "$BCK_ENV" == "PRD" ]; then
      for NEWDB in ${NEWDBS//,/ }; do
        for ND in ${NDAYSB//,/ }; do
          # if [[ " $passed " =~ [[:space:]]$NEWDB- ]]; then
          #   continue
          # fi
          if [ -z "$opt_name" ]; then
            if [[ $ND =~ [0-9]{8} ]]; then
              dtdb=$ND
            elif [[ $ND =~ [0-9]+ ]]; then
              NDAYB=-${ND}day
              dtdb=$(date -d $NDAYB +"%Y%m%d")
            else
              dtdb=$(date -d yesterday +"%Y%m%d")
            fi
            if [[ " $passed " =~ [[:space:]]${db}-$dtdb[[:space:]] || " $passed " =~ [[:space:]]$NEWDB-$dtdb[[:space:]] ]]; then
              continue
            fi
          else
            dtdb="00000000"
          fi
          if [ $opt_mail -eq -1 ]; then
            opt_mail=0
            if [[ $NEWDB =~ $TEST_DB ]]; then
              opt_mail=1
              wlog "Restored DBs are set for test as classificated"
            fi
          fi
          get_sqlfname "$db" $dtdb
          if [ -z "$SQLFNAME" ]; then
            continue
          fi
          if [ "$NEWDB" == "$DBSEL" -a "$dtdb" != "00000000" ]; then
            backup_db $DBSEL
          fi
          break
        done
        if [[ -n "$opt_name" || -n "$SQLFNAME" ]]; then
          break
        fi
      done
    elif [ "$BCK_ENV" == "DEV" ]; then
      for ND in ${NDAYSB//,/ }; do
        if [ -z "$opt_name" ]; then
          if [[ $ND =~ [0-9]{8} ]]; then
            dtdb=$ND
          elif [[ $ND =~ [0-9]+ ]]; then
            NDAYB=-${ND}day
            dtdb=$(date -d $NDAYB +"%Y%m%d")
          else
            dtdb=$(date -d today +"%Y%m%d")
          fi
          if [[ " $passed " =~ [[:space:]]${db}-$dtdb[[:space:]] ]]; then
            continue
          fi
        else
          dtdb="00000000"
        fi
        NEWDB="$db"
        if [ $opt_mail -eq -1 ]; then
          opt_mail=1
          wlog "Restored DBs are set for test because Development Host"
        fi
        get_sqlfname "$db" $dtdb
        if [[ -n "$opt_name" || -n "$SQLFNAME" ]]; then
          break
        fi
      done
    fi
    if [ -n "$SQLFNAME" ]; then
      fsql=$HOME/$NEWDB.sql
      create_script_db_2_restore "$NEWDB" "$fsql"
      if [ $opt_dry_run -eq 0 ]; then
        wlog "Starting restore $NEWDB"
      else
        wlog "Should start restore $NEWDB"
      fi
      run_traced "pg_db_active -k"
      run_traced "pg_db_active -wa $NEWDB; dropdb --if-exists -U$opt_user $NEWDB"
      [ $opt_dry_run -eq 0 ] && eval $TDIR/$THIS -R "$NEWDB" &
      run_traced "psql -U$opt_user -d postgres -f $fsql -o $NEWDB.log 2>$NEWDB.err"
      run_traced "reassign_owner \"$NEWDB\" \"$OLD_OWNER\" \"$NEW_OWNER\""
      if [ $opt_dry_run -eq 0 ]; then
        if [ $opt_mail -gt 0 ]; then
          stop_services "$NEWDB" "quick"
        fi
        rm -f $fsql
        if $(tail -n1 $NEWDB.err|grep "psql:.*NOTICE:  database.*does not exist, skipping" &>/dev/null); then
          rm -f $NEWDB.err
        fi
        wlog "Restore $NEWDB terminated"
      fi
    else
      wlog "Restore $NEWDB processed"
    fi
    ((ctr++))
    if [ -n "$opt_name" ]; then
      break
    fi
    passed="$passed $NEWDB-$dtdb ${db}-$dtdb"
  done
  popd >/dev/null
  find $DBCKDIR -name "*.err" -size 0 -delete
fi
if [ $opt_dry_run -eq 0 ]; then
  sleep 30
fi
wlog "== $ctr databases are restored"
exit 0

