#!/bin/bash
##########################################
# script for restore an incremental backup
##########################################
#
#++++ SETTINGS ++++
# backup file prefix
PREFIX=backup_$(hostname -s)

# where the backup is
BACKUPDIR="/media/backups"            # backup dir

# where the archive is
ARCHIVDIR="/media/backups"            # archive dir .. same as backup dir here

# where it come from.. please no slash at the end..
SOURCE="/home/flo"                              # what to backup
#++++++++++++++++

#returns human readable time stamp of given backup file
getHumanReadableTimeStamp () {
    HRTS=${1##*/${PREFIX}_}
    HRTS=${HRTS%%_L*.*}
    echo $HRTS
}

#find most recent tgz file with level $1
findMostRecentTgzFile () {
    if [ -z "$1" ]; then            # why this?
       set -- "L*"                  # in $1 is the level information, which level backup we are looking for..
    fi                              # if you dont state $1 then search for L* which means all levels..
    shopt -s nullglob               # you have to google that.. too complicated to explain.
    mostrecentUTS=0                 # unix time stamp
    for tgzfile in ${BACKUPDIR}/${PREFIX}_*_$1.tgz; do  # now loop through tgz files an find the most recent one..
        HRTS=$(getHumanReadableTimeStamp $tgzfile)
        UTS=$(date -d "${HRTS:2:2}/${HRTS:0:2}/${HRTS:4:4} ${HRTS:9:4}" +%s) # unix time stamp of tgzfile
        if [ $UTS -gt $mostrecentUTS ]; then
            mostrecentUTS=$UTS
            mostrecentTgz=$tgzfile
        fi
    done
    shopt -u nullglob                           # you have to google that.. too complicated to explain.
    echo ${mostrecentTgz} 
}

#find most recent tgz file which is older then $1, level is in $2 
#if you invoke it without argument $(findNextMostRecentTgzFile) it will result the same as findMostRecentTgzFile
findNextMostRecentTgzFile () {
    if [ -z $1 ]; then
        mostrecentUTS=21474836470
    else
        HRTS=$(getHumanReadableTimeStamp $1)
        mostrecentUTS=$(date -d "${HRTS:2:2}/${HRTS:0:2}/${HRTS:4:4} ${HRTS:9:4}" +%s) # unix time stamp of tgzfile
    fi
    if [ -z "$2" ]; then            # why this?
       set -- "L*"                  # in $1 is the level information, which level backup we are looking for..
    fi                              # if you dont state $1 then search for L* which means all levels..
    shopt -s nullglob               # you have to google that.. too complicated to explain.
    nextmostrecentUTS=0                 # unix time stamp
    for tgzfile in ${BACKUPDIR}/${PREFIX}_*_$1.tgz; do  # now loop through tgz files an find the most recent one..
        #echo "Working on $tgzfile"
        HRTS=$(getHumanReadableTimeStamp $tgzfile)
        UTS=$(date -d "${HRTS:2:2}/${HRTS:0:2}/${HRTS:4:4} ${HRTS:9:4}" +%s) # unix time stamp of tgzfile
        if [ $UTS -gt $nextmostrecentUTS ] && [ $UTS -lt $mostrecentUTS ]; then
            nextmostrecentUTS=$UTS
            nextmostrecentTgz=$tgzfile
        fi
    done
    shopt -u nullglob                           # you have to google that.. too complicated to explain.
    echo ${nextmostrecentTgz} 
}

#checkpreconditions
checkpreconditions () {
    #check if backup directory exists..
    if [ ! -d $BACKUPDIR ]; then
        echo "backup dir $BACKUPDIR doesn't exists. Mount drive or create folder.."
    fi  
    #check if archive dir exists..
    if [ ! -d $ARCHIVDIR ]; then
        echo "archive dir $ARCHIVDIR doesn't exists. Mount drive or create folder"
    fi  
    #check if dir to backup exists..
    if [ ! -d $SOURCE ]; then
        echo "backup source dir $SOURCE doesn't exists. Mount drive or create folder"
    fi  
    if ! command -v tar &> /dev/null; then
        echo "tar could not be found"
    fi
}

#get level of tgz file $1
getLevel () {
    l=${1##*/${PREFIX}_????????_????_L}
    l=${l%%.*}
    echo $l
}

bold=$(tput bold)
normal=$(tput sgr0)

##################################

if [ -z "$1" ]; then
    echo "USAGE: restore [COMMAND] [OPTION] FILE"
    echo "COMMAND:"
    echo "  list    .. list all backup files"
    echo "  find    .. find file or dir"
    echo "  restore .. restore file"
    echo "OPTIONS:"
    echo "  -f  a specific TGZ_FILE"
    echo "  -r recursive follow directories"
    echo "  -d DIR .. extract in this DIR"
    echo "FILE could be:"
    echo "important.doc .. for a file"
    echo "important/    .. for a directory"
    echo "important/*   .. for the contents of a directory"
    echo "importa*      .. find file which start with importa.."
    echo ""
    echo "You should know that you can do a lot of damage when restoring. When extracting, files even newer"
    echo "ones, are overwritten without confirmation. "
    echo "* Make sure that the settings are carefully set. "
    echo "* Restore only as many files as necessary"
    echo "* If you are not sure if you are restoring the right file, restore it to a safe folder first"
    echo "  by using -d option"
    echo "* At first use ./restore without a command. It will list all the files in the backup archive which"
    echo "  matches FILE."
    echo "* if you dont know where to find your files in the dir tree use command 'find' to find them"
    echo "* Then append 'restore' (commands, in fact every command line argument can be stated at any position)"
    echo "* If you want to restore the whole backup you would use ./restore '*' -r . This will work in any case."
    echo ""
    echo "Examples"
    echo "restore find important.doc  will find the file 'important.doc' anywhere in dir tree"
    echo "restore important.doc       will find the file 'important.doc' only in the root dir"
    echo "restore my_documents/invoices/invoice_nr1.odt will find the file"
    echo "restore restore my_documents/invoices/invoice_nr1.odt will restore the file"
    echo "restore '*' -r restore        will restore everything"
    echo ""
fi

problems=$(checkpreconditions)
if [ ! -z "$problems" ]; then
    echo "Problems"
    echo "--------"
    echo "$problems"
    exit 1
fi

if [ -z "$1" ]; then
    echo "Summary"
    echo "Backup  dir is $BACKUPDIR"
    echo "Archive dir is $ARCHIVDIR"
    echo "Source  dir is $SOURCE"
    exit
fi

#deal with commandline
for cla in "$@"; do                       # loop through command line arguments
    if [ "$cla" == "list" ]; then       # list command
        if [ $mode ]; then
            echo "only one command"
        fi
        ls -lh $BACKUPDIR
        exit                            # exit
    elif [ "$cla" == "find" ]; then
        if [ $mode ]; then
            echo "only one command"
        fi
        mode="find"
        NEXTIS=""
    elif [ "$cla" == "restore" ]; then
        if [ $mode ]; then
            echo "only one command"
        fi
        mode="restore"
        NEXTIS=""
    elif [ "$cla" == "-f" ]; then
        if [ ! -z $NEXTIS ]; then
            echo "syntax error"
            exit 1
        fi
        NEXTIS=TGZ_FILE
    elif [ "$cla" == "-r" ]; then
        recursive=1
        NEXTIS=""
    elif [ "$cla" == "-d" ]; then
        if [ ! -z $NEXTIS ]; then
            echo "syntax error"
            exit 1
        fi
        NEXTIS="XTRACTDIR"
    elif [ "$NEXTIS" == "XTRACTDIR" ]; then
        xtractdir=$cla
        NEXTIS=""
    elif [ "$NEXTIS" == "TGZ_FILE" ]; then
        tgz_file=$cla
    else
        filename="$cla"
        NEXTIS=""
    fi 
done
 
#extract dir stated?
if [ $xtractdir ]; then
    if [ ! -d $xtractdir ]; then
        echo "Extract dir '$xtractdir' doesnt exists"
        exit 1
    fi
    slashes=$(echo $SOURCE | tr '/' '\n' | wc -l)
    xtractdir="-C $xtractdir --strip-components $slashes" 
    echo "Extract dir: '$xtractdir'"
fi

#get list of backup files in the right order (L0, L1, L2 ..)
if [ -z "$tgz_file" ]; then
    mrtgz=$(findMostRecentTgzFile L0)
    level=0
    while [ ! -z $mrtgz ]; do
        tgz_file="${tgz_file} $mrtgz"
        level=$((level+1))        
        mrtgz=$(findMostRecentTgzFile L${level})
    done
fi

# check if backup files are there..
if [ -z "$tgz_file" ]; then
    echo "tgz file not there!"
    echo "Did you forget to make a backup from the backup, from the backup, from the backup, from the backup?"
    exit
fi

# find mode: will find dirs and files anywhere but wont restore them!
if [ "$mode" == 'find' ]; then
    for f in $tgz_file; do
        echo "* Result for backup file '$f'"
        result=$(tar -tzf $f --wildcards --wildcards-match-slash "*/"$filename 2> /dev/null)  
        if [[ $? != 0 || -z "$result" ]]; then 
            echo "    <nothing>"
        else
            for r in $result; do
                echo "    $r"
            done
        fi
    done
    exit
fi


#this is a shortcut.. a fallback solution, i dont trust bash enough to deal with big data..
if [ "$filename" == "*" ] && [ $recursive ]; then
    #oh this is a serious guy.. or a guy with serious problems..
    for f in $tgz_file
    do
        echo "* Restore from backup file '$f'" 
        if [ "$mode" == "restore" ]; then
            tar $xtractdir -xzf $f 
        else
            tar $xtractdir -tzf $f 
        fi
    done
    exit
fi

# somebody entered the full path?
if [[ $filename != "$SOURCE/"* ]]; then
    filename=$SOURCE"/"$filename
fi    

isDir () {
    #echo "arg of isDir $1" > stderr
    if [ ${1:0-1} == '/' ]; then
        echo 0
    else 
        echo 1
    fi
}

#finally find the files and extract them if wanted
for f in $tgz_file
do
    echo "* Restore from backup file '$f'" 
        result=$(tar --no-wildcards-match-slash --wildcards -tzf $f "$filename" 2> /dev/null)
        if [[ $? != 0 || -z "$result" ]]; then
            echo "    <nothing>"
            continue
        fi 
        for r in $result; do                        #result could be an array
            if [[ $(dirname $r) == $(dirname "$filename") || $recursive ]]; then
                echo "    $r"
                if [ "$mode" == "restore" ]; then
                    tar $xtractdir -xzf $f $r
                fi
            fi
        done
done

exit #final exit

