#!/bin/sh

#########################################################################################
# Versions
#
# 1.0.1
# Implementation of backup restoration in case of update failure
# Addition of led management logic
#
# 1.0.2
# Minor corrections relating to platform starting file identification.
# Add a clean of unzip directory in case of STATUS_UPDATE_ERROR
#
# 1.0.3 - 1.0.5 
# Minors corrections
#
# 1.0.6 
# Minor correction to add support of uppercase/lowercase in update zipfile detection
# Add local variables DATA_VAR_DIR and DATA_VAR_LOG_DIR to make effective detection
# and creation of these directories in case of UPDATE_COMMON_FILE missing
#
# 1.0.7
# Change delay allowed (120s) for detecting that the application freshly installed has 
# started because when the platform starts on a backup IP address, the time taken by the
# framework to perform its upnp initilization is much more longer (70s) than the time 
# previously allowed (60s) .
#
# 1.0.8
# Delete environment backup is update sucessfull and add some text in logfile
#
# 1.0.9
# Modify SetLed calls as the function supports now more colors and modes
# Important : Need an "update_common" file version >= 1.0.8
# 
# 1.1.0
# The script gets its own version and don't share the SCRIPT_VERSION variable anymore
#
# 1.1.1
# Delete environement backup if update failed and restore function called
# Correct a test at the end of the RestoreVersion function that prevented to restart the application
# Add some text in logfile on some task completion
#
# 1.1.2
# Add a verification to each directory creation in order to suppress a potential existing file
# with the same name which will avoid the directory creation
#
# 1.1.3
# Add some signalization during update process so that it looks alive at beginning and indicates
# some error steps
#
# 1.1.4
# When dezipping the update file, add a -o to unzip command so that it will overwritte an
# existing file which should normally not exist but could be present if something worked
# not like expected in the previous process
# Re-arrange some signalization at process beginning
#
# 1.2.0
# Add of the WriteLog function and its dependent UPDATE_SURVEY_SCRIPT_INITIALS
#
# 1.2.5
# Add a clean of update directory in case of update process error to suppress also the
# zipfile which may be the origin of the error
#
# 1.2.6
# Add a clean of working space after using the restore function
# Set a fixed green light after a successful restore
#
# 1.2.7
# Allow zip update file with spaces in name
#########################################################################################

source /usr/local/bin/BSPUpdateTools
source /usr/local/bin/update_common

UPDATE_SURVEY_SCRIPT_VERSION="1.2.7"
UPDATE_SURVEY_SCRIPT_INITIALS="[US-]"

#########################################################################################
# Local variables defined
#########################################################################################

EXTENSION=zip
UPDATE_SCRIPT="update.sh"

UPDATE_DIR="/tmp/update"
UPDATE_STATE_DIR=$UPDATE_DIR/platformstate

PATH_TO_SURVEY=$UPDATE_INSTALL_DIR

INOTIFY_OPT_NORMAL="-r -e close_write -e moved_to"
INOTIFY_OPT_TIMEOUT="-r -e close_write -e moved_to -t 120"

INOTIFY_OPT=$INOTIFY_OPT_NORMAL

APP_START_FILE="started"

ROOT_DIR=/root

UPDATE_COMMON_FILE="/usr/local/bin/update_common"
UPDATE_TOOLS="/usr/local/bin/BSPUpdateTools"

UPDATE_LOG_FILE="/data/var/log/update"

DATA_VAR_DIR=/data/var
DATA_VAR_LOG_DIR=$DATA_VAR_DIR/log

START_SURVEY="start_survey"

INOTIFY_RET_EVENT=0
INOTIFY_RET_ERROR=1
INOTIFY_RET_TIMEOUT=2

#########################################################################################
# Fonctions
#########################################################################################

###########################################################################################
# Function that add the initials of the script to each message that will be logged.
# That makes the reading of the logs unambiguous

WriteLog() {
	LogUpdate "$UPDATE_SURVEY_SCRIPT_INITIALS : $1" $2 $3
}

#########################################################################################
# Function that create a directory with the given name.
# Previously delete potential existing file with the same name which will avoid otherwise 
# directory creation
#
#	$1 : Directory name

CreateDirectory() {
	
	if [ ! -z $1 ]; then 																											# If parameter given	

		if [ ! -e "$1" ]; then																										# If directory doesn't already exists

			if [ -f "$1" ]; then 																									# Delete file with the same name if it exists
				rm "$1"
			fi

			mkdir "$1"
		fi
	fi
}

#########################################################################################
# Function that verify presence of necessary common files

VerifyNecessaryTools() {

	LogDate=`date +%Y/%m/%d-%H:%M:%S`

	if [ ! -e "$UPDATE_COMMON_FILE" ]; then																								# Check presence of necessary files
		echo "$LogDate : $UPDATE_COMMON_FILE missing. $(basename "$0") v$UPDATE_SURVEY_SCRIPT_VERSION abort" >> $UPDATE_LOG_FILE		# Not present, so write manually log entry
		return 1																														# And return an error
	fi

	if [ ! -e "$UPDATE_TOOLS" ]; then																									# Same for UPDATE_TOOLS
		echo "$LogDate : $UPDATE_TOOLS missing. $(basename "$0") v$UPDATE_SURVEY_SCRIPT_VERSION abort" >> $UPDATE_LOG_FILE				# Write manually log entry
		return 1																														# And return an error
	fi
}

#########################################################################################
# Function that cleans directories totally or not depending on parameters
# 
# Order of parameters
# $1 = directory to clean

CleanDirectory() {

	if [ -d "$1" ] ; then																											# Check presence of given directory
		LogUpdate "Erasing $1 directory"
		rm -Rf $1 >> $UPDATE_LOG_FILE
	fi
}

#########################################################################################
# Function that verify the validity of update file
#
# Input : $1 filename / $2 filepath (not used)
# Output : boolean (UNIX sauce : 0 = true / 1 = false)

VerifyUpdateValidity() {
	if echo "$1" | grep $UPDATE_EXTENSION >/dev/null 2>&1; then																	# Update file must be a valid file
		return 0
	else
		return 1
	fi
}

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

CleanUnzipWorkspace() {

	# Clean working space
	CleanDirectory $UNZIP_DIR_OLD
	CleanDirectory $UNZIP_DIR_NEW
}

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

CleanUpdateDir() {
	LogUpdate "Erasing $UPDATE_INSTALL_DIR directory"
	rm -Rf $UPDATE_INSTALL_DIR/*
}

#########################################################################################
# Function that initiate the update process by launching a script which is embedded in 
# a zip file which containe also all the necessary resources to fulfill the process. 
# It's up to the launched script to take car of the process itself
#
# Input : none
# Output : boolean (UNIX sauce : 0 = true / 1 = false)

StartUpdateProcess() {

	LogUpdate "Start update process"

	CleanUnzipWorkspace
	
	LogUpdate "Creating empty unzip directory"
	CreateDirectory $UNZIP_DIR >> $UPDATE_LOG_FILE
	
	# Retrieve update filename
	tmpfilepath=$(ls $UPDATE_INSTALL_DIR/*.zip)
	tmpfilename=$(basename "$tmpfilepath")
	
	LogUpdate "$tmpfilename"

	# Unzip file
	LogUpdate "unzipping $UPDATE_INSTALL_DIR/$tmpfilename -q -o -d $UNZIP_DIR"
	SetLed POWER GREEN FAST_BLINK

	unzip "$UPDATE_INSTALL_DIR/$tmpfilename" -q -o -d $UNZIP_DIR >> $UPDATE_LOG_FILE
	
	# Change working directory
	cd $UNZIP_DIR
	
	# Look for an UPDATE_SCRIPT script file at the root of unzip directory
	if [ ! -f "$UPDATE_SCRIPT" ]; then
		LogUpdate "$UPDATE_SCRIPT not found in $(pwd). Task aborted"
		return 1
	fi
	
	# Clean old exeution status
	LogUpdate "Deleting $UPDATE_STATUS_FILE"
	rm $UPDATE_STATUS_FILE >> $UPDATE_LOG_FILE
	
	# Make script executable
	LogUpdate "Changing $UPDATE_SCRIPT script permissions"
	chmod 755 $UPDATE_SCRIPT >> $UPDATE_LOG_FILE
	
	# Launch it
	LogUpdate "Start $UPDATE_SCRIPT script from `pwd` directory"
	./$UPDATE_SCRIPT
	
	return 0
}

#########################################################################################
# Function that initiate the restore process by lauching the same script as for the
# update process but with another argument. The launched script is in charge of the 
# process itself
#
# Input : none
# Output : boolean (UNIX sauce : 0 = true / 1 = false)

StartRestoreProcess() {

	LogUpdate "Start restore process from $(pwd) directory"

	# This change is necessary if the update process has been initiated by an old version of update_survey script
	# which was using only $UNZIP_DIR_OLD location. In this case the restore process must also use the old location

	if [ -d "$UNZIP_DIR_OLD" ]; then
		UNZIP_DIR=$UNZIP_DIR_OLD
	fi

	# Change working directory
	if [ ! -d "$UNZIP_DIR" ]; then
		LogUpdate "$UNZIP_DIR doesn't exist anymore. Task aborted"
		return 1
	fi

	# Change working directory
	cd $UNZIP_DIR

	# Look for an UPDATE_SCRIPT script file at the root of unzip directory
	if [ ! -f "$UPDATE_SCRIPT" ]; then
		LogUpdate "$UPDATE_SCRIPT not found from `pwd` directory. Task aborted"
		return 1
	fi
	
	LogUpdate "Start $UPDATE_SCRIPT $FUNC_RESTORE from `pwd` directory"
	./$UPDATE_SCRIPT $FUNC_RESTORE

	return 0
}

##########################################################################################
# Function which clean up the workspace used for the update process not only by the script
# itself but also by the other scripts involved in the process. Therefore the script used
# in the update and restore process is called another time with a different argument so
# that it can take care of its own part.
#
# Input : none
# Output : boolean (UNIX sauce : 0 = true / 1 = false)

StartCleanProcess() {

	LogUpdate "Start cleaning process from $(pwd) directory"

	# UNZIP_DIR may be changed below for an UNZIP_DIR_OLD. 
	# Thus it must be stored before so that it can be restored to its original value after the cleaning process. 
	SaveUnzipDir=$UNZIP_DIR

	# This change is necessary if the update process has been initiated by an old version of update_survey script
	# which was using only $UNZIP_DIR_OLD location. In this case the clean process must also use the old location

	if [ -d "$UNZIP_DIR_OLD" ]; then
		UNZIP_DIR=$UNZIP_DIR_OLD
	fi

	# Change working directory
	if [ ! -d "$UNZIP_DIR" ]; then
		LogUpdate "$UNZIP_DIR doesn't exist anymore. Task aborted"
		return 1
	fi

	cd $UNZIP_DIR

	# Look for an UPDATE_SCRIPT script file at the root of unzip directory
	if [ ! -f "$UPDATE_SCRIPT" ]; then
		LogUpdate "$UPDATE_SCRIPT not found from `pwd` directory. Task aborted"
		return 1
	fi
	
	LogUpdate "Start $UPDATE_SCRIPT $FUNC_CLEAN from `pwd` directory"
	./$UPDATE_SCRIPT $FUNC_CLEAN

	CleanUnzipWorkspace
	CleanUpdateDir

	# Restore UNZIP_DIR to its original value
	UNZIP_DIR=$SaveUnzipDir

	return 0
}

##########################################################################################
# Function that is called on events CLOSE_WRITE or MOVED_TO that happen on $UPDATE_DIR and
# sub-directories. This function can't be called again until another directory monitoring
# process is started which will be done after its execution ending
#
# The meaning of the event will change depending from its location
# $UPDATE_INSTALL_DIR : an application update is needed
# $UPDATE_STATE_DIR : an application status is signified
#
# Input : $1 = filename / $2 = filepath / $3 = event
# Output : none

WhatHappensinUpdateDir() {
	
	LogUpdate "Event "$3" on "$1" file in "$2" directory"

	case "$2" in
	
		$UPDATE_INSTALL_DIR/)																										# Directory for update files

			if ! VerifyUpdateValidity "$1" "$2"; then 

				# Signify an error for a while before coming back to a normal state
				SetLed POWER ORANGE FAST_BLINK
				sleep 1

				# Clean dir
				LogUpdate "$1 not usable. Removed from $UPDATE_INSTALL_DIR"
				rm $UPDATE_INSTALL_DIR/$1 >> $UPDATE_LOG_FILE
				
				# Signify an invalid update file
				SetUpdateStatus $STATUS_UPDATE_FILE_ERROR
				return
				
			fi

			# File validated, start update process
			LogUpdate ">>>>>>>>>> Found an update file"
			SetUpdateStatus $STATUS_UPDATE_FILE_OK

			return

			;;
			
		$UPDATE_STATE_DIR/)
		
			if [ "$1" == $APP_START_FILE ]; then
				SetUpdateStatus $STATUS_APP_STARTED
			fi
						
			;;
			
		*)
			# LogUpdate "Function WhatHappensinUpdateDir - Event from $2"
			;;
	
	esac
}

#########################################################################################
#########################################################################################
# Script beginning
#########################################################################################
#########################################################################################

ManageLogDirectories

if ! VerifyNecessaryTools; then
	exit 1
fi

LogUpdate "----------------------------- $(basename "$0") ($1) - v$UPDATE_SURVEY_SCRIPT_VERSION (using library $UPDATE_COMMON_SCRIPT_VERSION)"

if [ ! -d "$UPDATE_DIR" ] ; then																									# Check presence of UPDATE_DIR
	LogUpdate "Creating $UPDATE_DIR directory"																						# As it is necessary to write script status
	CreateDirectory $UPDATE_DIR
fi

if [ "$1" == $START_SURVEY ]; then 																									# If script launched for the first time

	LogUpdate "$(basename "$0") initial start"

	SetUpdateStatus $STATUS_SURVEY																									# Set update initial status
	INOTIFY_OPT=$INOTIFY_OPT_NORMAL
fi

###########################################################################################
########## 			Do an infinite survey

while true
do

	ManageUpdateDirectories																											# Verify that update directories are still present

	GetUpdateStatus
	case "$updateStatus" in
	
		$STATUS_SURVEY)
			;;
		
		$STATUS_UPDATE_ABORT)
			LogUpdate "Update script aborted !!"
			
			CleanUnzipWorkspace
			CleanUpdateDir
			
			SetUpdateStatus $STATUS_SURVEY																							# And goes back to a normal state
			;;
			
		$STATUS_UPDATE_OK)
			LogUpdate "Update script ended. Waiting now for an application status"
			
			INOTIFY_OPT=$INOTIFY_OPT_TIMEOUT
			PATH_TO_SURVEY=$UPDATE_STATE_DIR

			SetUpdateStatus $STATUS_WAIT_APP
			;;
			
		$STATUS_WAIT_APP)
			LogUpdate "Application not started."

			if ! StartRestoreProcess; then
				LogUpdate "Restore process failed"
			else
				LogUpdate "Backup restored. Application started"
				SetLed POWER GREEN FIXED
			fi
			
			CleanUnzipWorkspace
			CleanUpdateDir

			INOTIFY_OPT=$INOTIFY_OPT_NORMAL
			PATH_TO_SURVEY=$UPDATE_INSTALL_DIR

			SetUpdateStatus $STATUS_SURVEY
			;;
			
		$STATUS_APP_STARTED)
			LogUpdate "Application started."

			# Clean working environment
			StartCleanProcess
			
			SetLed POWER GREEN FIXED
			
			INOTIFY_OPT=$INOTIFY_OPT_NORMAL
			PATH_TO_SURVEY=$UPDATE_INSTALL_DIR

			SetUpdateStatus $STATUS_SURVEY
			;;
		
		*)
			LogUpdate "What kind of updateStatus is it : $updateStatus"
			;;
	esac
	
	# Start a sleeping survey on some events that may happens in UPDATE_DIR
	LogUpdate "Start survey - inotifywait $INOTIFY_OPT --format '%f#%e#%w' $PATH_TO_SURVEY"

	inotifyOutput=$(inotifywait $INOTIFY_OPT --format '%f#%e#%w' $PATH_TO_SURVEY)
	retCode=$?

	case "$retCode" in
	
		$INOTIFY_RET_EVENT)
			echo $inotifyOutput | while IFS=# read file event location

			do
				# Everything done from here is done in a child process due to the pipe above
				LogUpdate "Inotifywait event occurs and inotifyOutput = $inotifyOutput"
				WhatHappensinUpdateDir "$file" "$location" "$event"
			done
			
			# Manage return of WhatHappensinUpdateDir function
			GetUpdateStatus

			case "$updateStatus" in

				$STATUS_UPDATE_FILE_ERROR)
	
					# Set state back to normal so that another directory monitoring can start
					SetLed POWER GREEN FIXED
					SetUpdateStatus $STATUS_SURVEY

					;;

				$STATUS_UPDATE_FILE_OK)

					if ! StartUpdateProcess; then

						# Signify an error for a while before coming back to a normal state
						SetLed POWER ORANGE FAST_BLINK
						sleep 1

						LogUpdate "Update script error !!"
						CleanUnzipWorkspace
						CleanUpdateDir

						# Set state back to normal so that another directory monitoring can start
						SetLed POWER GREEN FIXED
						SetUpdateStatus $STATUS_SURVEY

					fi

					;;

				*)
					;;

			esac

			;;

		$INOTIFY_RET_ERROR)
			LogUpdate "Inotifywait returns an error."
			;;

		$INOTIFY_RET_TIMEOUT)
			LogUpdate "Inotifywait timeout expires"
			;;

		*)
			LogUpdate "Unexpected inotifywait return code : $retCode"
			;;

	esac

done
