#!/bin/sh
# Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
#
#    NAME
#      [common]oracle.dbtools.transfer.validation.ShellScript.txt
#      TODO: Better generator/template naming
#      (Not saved. generated and executed directly)
#      - Verifies the checksum of chunks, files and assembles all chunks
#
#    DESCRIPTION
#      - First compare the MD5 checksums of each chunk file with source checksum
#      - Concatenate all the chunks to create a file
#      - Run MD5 checksum on file and compare MD5 check with source file
#      - All errors are reported in stdout.
#
#    RETURN CODES
#		0 - OK
#		1 - Unspecified failure
#  		2 - Chunk md5 failure(s), retransmit/restart requested
#		3 - Assembly failure(s), abort
#		4 - File md5 failure(s), abort
#       5 - PreTransfer CHECK_CHUNKS : List of chunks that are at target
#       6 - PreTransfer Not enough space 
#       7 - PreTransfer : file failed and no chunks
#
#    NOTES
#      TODO: Handling multiple target directories 
#            (WAIT until needed but correct info is being passed in [I hope] -blj)
#
#    MODIFIED   (MM/DD/YY)
#    ruppala    07/21/15 - Creation
#    bjeffrie   07/31/15 - Multiple file handling
#

# uncomment for debug mode tracing to stderr
#set -x

# array of 'fileName md5sum chunkCount targetDir assemblyRequired'
# IMPORTANT: The order of file names here MUST MATCH the ordering of file chunks
SourceFiles=()

# array of 'fileChunkName md5sum' for all chunks
# IMPORTANT: The order of file chunk names here MUST MATCH the ordering of file names
SourceChunkFiles=()

# log file name (matching files index name)
LogFile=<LogFile>

signatureCheck() {
    echoAndLog " "
	echoAndLog "========= signatureCheck ========="
    echoAndLog "START $(date)"
    
    md5sum_check SourceFiles
    if [ $? -ne 0 ]; then
        exit 4
    fi 

    echoAndLog "END $(date)"
}

requiredSpaceCheck() {
    echoAndLog " "
        echoAndLog "========= requiredSpaceCheck ========="
    echoAndLog "START $(date)"

    local maxFileSize=$1
    local sumOfAllFileLengths=$2
    local availableDiskSpace=0
    local factorIncrease=1.1
    local requiredSpace=0

    requiredSpace=$((sumOfAllFileLengths + maxFileSize))
    requiredSpace=$(echo "scale=0; $sumOfAllFileLengths*$factorIncrease/1024" | bc)
    availableDiskSpace=$(df -k . | tail -1 | awk '{print $3}')
    echoAndLog "Total Disk Space Required = ${requiredSpace} KB"
    echoAndLog "Total Available Disk Space = ${availableDiskSpace} KB"

    if [ "$requiredSpace" -gt "$availableDiskSpace" ]; then
        echoAndLog "Total available disk space is less that required"
        return 1
       else
        echoAndLog "Total available disk space is more than required"
        return 0
    fi
    echoAndLog "END $(date)"
}

preTransfer() {
    echoAndLog " "
        echoAndLog "========= preTransfer ========="
    echoAndLog "START $(date)"

    local -a 'files=("${'"SourceFiles"'[@]}")'
    local checkChunks=false
    local failedCount=0
    local maxFileSize=0
    local sumOfAllRequiredFileSize=0

    for fileRec in "${files[@]}"; do
        file=($fileRec)
        fileName=${file[0]}

        testMD5=($(/usr/bin/md5sum $fileName))
        if [[ -f "$fileName" && "$testMD5" == "${file[1]}" ]];
        then
           echoAndLog "PASSED $fileName"
           rm -f "${fileName}".[0123456789]*
        else
           echoAndLog "FAILED $fileName"
           ((failedCount++))
           # Delete the File
           if [ -f "$fileName" ]; then
              rm "$fileName"
           fi

           if [ "${file[5]}" -gt $maxFileSize ]; then
                maxFileSize="${file[5]}"
           fi
           sumOfAllRequiredFileSize=$((sumOfAllRequiredFileSize + file[5]))
           fileChunks=($(ls "${fileName}".[0123456789]*))
           for chunk in ${fileChunks[*]}; do
               echoAndLog "CHECK_CHUNK $chunk"
               checkChunks=true
            done
        fi

     done

    requiredSpaceCheck ${maxFileSize} ${sumOfAllRequiredFileSize}

    if [ $? -ne 0 ]; then
        return 6
    fi

    echoAndLog "failedCount: $failedCount"

    if [ "$checkChunks" = true ] ; then
        return 5
    fi

        if [ $failedCount -ne 0 ]; then
            return 7
        fi

    echoAndLog "END $(date)"
}

postTransfer () {
    echoAndLog " "
	echoAndLog "========= postTransfer ========="
    echoAndLog "START $(date)"
    local rc=0
    
    # TODO:? This could be refactored to echoAndLog back the array of just file
    # names needed by assemble
    #md5sum_check SourceChunkFiles
    #md5sum_check_required_chunks SourceFiles SourceChunkFiles
    #if [ $? -ne 0 ]; then
    #    return 2
    #fi 

    #TODO:? Echo back SourceFile elements that pass to continue with final md5 check
    assemble SourceFiles SourceChunkFiles
    rc=$?
    if [ $rc -ne 0 ]; then
	    if [ $rc -eq 5 ]; then
	        preTransfer
	        rc=$?
	        if [ $rc -ne 0 ]; then
	            if [ $rc -ne 5 ]; then
	                rc=4
	            fi
	        fi
	    else
	        rc=3
	    fi
	fi 
    
    echoAndLog "END $(date)"
    return $rc
}

echoAndLog () {
	echo $*    
    echo $* >> $LogFile
}

RequiredChunks=()
# Function to send only chunks for non-transferred files for md5 check
md5sum_check_required_chunks() {
# $1 = SourceFiles array of 'fileName md5sum chunkCount targetDir assemblyRequired ...'
# $2 = SourceChunkFiles array of 'fileName md5 ...'
    echoAndLog "========= md5sum_check_required_chunks ========="
    local -a 'files=("${'"$1"'[@]}")'
    local -a 'chunks=("${'"$2"'[@]}")'
    local rc=0
    local startChunkIdx=0
    
    for fileRec in "${files[@]}"; do
        file=($fileRec)
    	chunkCount=${file[2]}
    	assemblyRequired=${file[4]}
    	echoAndLog "file: ${file[0]} startChunkIdx=$startChunkIdx chunkCount=$chunkCount assemblyRequired=$assemblyRequired"
    	if [ "true" == "$assemblyRequired" ]; then
	    	RequiredChunks+=("${chunks[@]:$startChunkIdx:$chunkCount}")
    	fi
    	# (()) forces arithmetic expansion
    	((startChunkIdx=startChunkIdx + chunkCount))
    done

    md5sum_check RequiredChunks
    # so we can see it in debug
    rc=$?
    return $rc
}

# Function check for MD5 CheckSum for files
md5sum_check () {
# $1 is the name of an array of 'fileName md5sum ...'
	echoAndLog "========= md5sum_check ========="
    local -a 'files=("${'"$1"'[@]}")'
	local failedCount=0

    for fileRec in "${files[@]}"; do
	    file=(${fileRec})
	    fileName=${file[0]}
	    
	    if [ ! -f "$fileName" ];
           then
             echoAndLog "FAILED $fileName"
             ((failedCount++))
             continue
        fi
	    
	    fileMd5=${file[1]}
		if [ "null" == "$fileMd5" ]; then
               continue
        fi

		testMd5=($(/usr/bin/md5sum $fileName))			

	    echoAndLog "fileMd5: $fileMd5 fileName: $fileName"
	    echoAndLog "testMd5: $testMd5"
	
	    if [ "$testMd5" == "$fileMd5" ]; then
	        echoAndLog "PASSED $fileName"
	    else
	        rm "$fileName"
	        echoAndLog "FAILED $fileName"
	        ((failedCount++))
	    fi
	done

	echoAndLog "failedCount: $failedCount"
	if [ $failedCount -ne 0 ]; then
	    return 1
	else
	    return 0
	fi        
}

# Combine chunk files to original and delete chunks or report failed file
assemble () {
# $1 = SourceFiles array of 'fileName md5sum chunkCount targetDir assemblyRequired ...'
# $2 = SourceChunkFiles array of 'fileName md5 ...'
    echoAndLog "========= assemble ========="
    local -a 'files=("${'"$1"'[@]}")'
    local -a 'chunks=("${'"$2"'[@]}")'
    local rc=0
    local startChunkIdx=0
    
    for fileRec in "${files[@]}"; do
        file=($fileRec)
    	chunkCount=${file[2]}
    	assemblyRequired=${file[4]}
    	echoAndLog "file: ${file[0]} startChunkIdx=$startChunkIdx chunkCount=$chunkCount assemblyRequired=$assemblyRequired"
    	if [ "true" == "$assemblyRequired" ]; then
	    	fileChunks=("${chunks[@]:$startChunkIdx:$chunkCount}")
	    	fileChunkNames=()
	    	for chunkRec in "${fileChunks[@]}"; do
	    	    chunk=($chunkRec)
	    	    #echoAndLog "chunk: "${chunk[@]}""
	    		fileChunkNames+=(${chunk[0]})
	    	done
	    	if [ $chunkCount -gt 1 ]; then
    	    	/bin/cat "${fileChunkNames[@]}" > ${file[0]}
	    	fi
	    	if [ $? -eq 0 ]; then
	    		# check if the MD5 check is valid for the assembled file, if it passes,  delete all chunks to free space
	    		# if md5check fails delete file and leave chunks as they are	    
	    		fileMd5=${file[1]}
				testMd5=($(/usr/bin/md5sum ${file[0]}))			
				if [ "$testMd5" == "$fileMd5" ]; then
			    	if [ $chunkCount -gt 1 ]; then
					    echoAndLog "ASSEMBLED ${file[0]}"
	    	    		rm "${fileChunkNames[@]}"
	    	    	fi
	        		echoAndLog "PASSED ${file[0]}"
	    		else
	        		echoAndLog "FAILED ${file[0]}"
	        		rm "${file[0]}"
	        		rc=5
	    		fi
	    	else 
	    	   	echoAndLog "FAILED ${file[0]}"
	        	rm "${file[0]}"
	        	rc=1
	    	fi
    	fi
    	# (()) forces arithmetic expansion
    	((startChunkIdx=startChunkIdx + chunkCount))
    done
    
    return $rc
}

# execution directory (log/index files here)
cd <TargetDir>

# Move array initialization down here so error messages have useful line numbers
SourceFiles=<SourceFiles>

SourceChunkFiles=<SourceChunkFiles>
