#!/bin/bash
####################################################################
# Prey Core Updater Functions - by Tomas Pollak (bootlog.org)
# URL: http://preyproject.com
# License: GPLv3
####################################################################

####################################################################
# prey updater
####################################################################

check_for_failed_update(){
	if [ -f "$base_path/.update_failed" ]; then
		local update_failed_for=$(cat "$base_path/.update_failed")
		if [ "$update_failed_for" == "$version" ]; then
			log ' !! Already attempted to update to next version with no luck. Skipping!'
			return 1
		fi
	fi
	return 0
}

verify_write_access(){
	if [ ! -w "$base_path/version" ]; then
		log " -- Unfortunately the current user who's running Prey cannot perform the upgrade."
		return 1
	elif [[ "$os" == "windows" && "`whoami`" != "SYSTEM" ]]; then
		log " !! Running on user mode! Please switch to System Service mode if you wish to update."
		return 1
	fi
}

####################################################################
# updater package management
####################################################################

fetch_update() {
	log " -- Fetching new Prey release at ${update_package}..."
	local download_status=$(getter "$update_package" -o "$updater_file" -w "%{http_code}" 2> /dev/null)

	if [ "$download_status" != "200" ]; then
		log " !! Couldn't get updater file! Skipping..."
		rm $updater_file 2> /dev/null
		return 1
	fi
}

get_checksum_for() {
	getter "$1.md5sum" | sed 's/<.*>//' | cut -d ' ' -f1
}

validate_package(){
	local valid_md5=$(get_checksum_for $update_package)

	if [ -z "$valid_md5" ]; then
		log " !! Couldn't grab checksum from server. Skipping update..."
		return 1
	fi

	[ "$os" == "mac" ] && local md5_cmd='/sbin/md5 -r' || local md5_cmd='md5sum'
	local package_md5=$($md5_cmd $updater_file | sed 's/.*\([a-f0-9]\{32\}\).*/\1/' 2> /dev/null)

	if [ "$package_md5" != "$valid_md5" ]; then
		log ' -- Invalid checksum for downloaded package. Cannot continue.'
		return 1
	fi
}

unzip_package(){
	log ' -- Validated updater from source. Unpacking...'
	unzip -u $updater_file -d $updater_path > /dev/null
	return $?
}

####################################################################
# diff patching (yeah, that's right, Prey updates with patch!)
####################################################################

test_diff() {

	# -f forces, dont ask any questions
	# -p0 leave paths as they are
	# -s silent
	# -N Ignore patches that seem to be reversed or already applied.
	# -F2 Fuzzyness. Default is 2 lines before and after.

	patch_options="-N -f -F1 --ignore-whitespace --backup-if-mismatch --remove-empty-files -d $base_path -p0 -i $diff_file"

	log ' -- Testing if patch will apply cleanly...'
	outcome=$(patch --dry-run $patch_options)

	if [ `find_in "$outcome" 'FAILED'` ]; then
		log " !! It appears there are differences between your installed version and the official Prey $version code. Cannot patch!"
		echo "$outcome" > "$base_path/patch.log"
		log " -- Results were logged to $base_path/patch.log."
		return 1
	else
		log ' -- Everything smooth.'
	fi

	return 0

}

apply_diff(){

	log ' -- Applying patch...'
	outcome=$(patch $patch_options 2>&1)
	# return $?

	if [ `find_in "$outcome" 'FAILED'` ]; then
		log ' -- Something went wrong while patching!'
		echo "$outcome" > "$base_path/patch.log"
		log " -- Results were logged to $base_path/patch.log."
		return 1
	else
		log " -- Patch was successful!"
		return 0
	fi

}

# this could be useful sometime in the future
report_update_status(){
	log ' -- Notifying update status to your Control Panel account...'
	if [[ "$1" == 1 && "$provide_error_feedback" == 'y' ]]; then
		additional_feedback="&device[update_log]=$outcome"
	fi
	update_device_info_with "device[update_failed]=${1}${additional_feedback}"
}

####################################################################
# file copying and removal
####################################################################

delete_files_according_to_diff() {

	STDOUT=$(grep "^Binary" $diff_file)

	while read file; do

		local original=$(echo "$file" | cut -d" " -f3)
		local modified=$(echo "$file" | cut -d" " -f5)

		if [ "$modified" == "/dev/null" ]; then # deleted file

			log " -- Deleting file: $original..."
			rm -f "$base_path/$original"

		fi

	done <<< "$STDOUT"

}

copy_new_files(){

	log ' -- Updating new binary files in Prey...'

	if [ -d "$updater_path/platform/$os" ]; then
		cp -R $updater_path/platform/$os/* "$base_path/platform/$os" # first, copy the platform specific stuff
	fi

	rm -Rf $updater_path/platform 2> /dev/null
	cp -R $updater_path/* "$base_path" 2> /dev/null

}

remove_temp_stuff(){
	rm -Rf "$updater_path" 2> /dev/null
	rm -f "$updater_file" 2> /dev/null
}

####################################################################
# main functions
####################################################################

download_and_test_update(){

	log ' -- Removing leftovers...'
	remove_temp_stuff

	updater_path="$tmpdir/updater"
	updater_file="$tmpdir/updater.zip"
	update_package="$updates_url/prey-updater-for-$version.zip"

	diff_file="$updater_path/changes.diff"
	# rejects_file="$updater_path/rejected_changes.diff"

	fetch_update
	if [ $? == 1 ]; then return 1; fi
	validate_package
	if [ $? == 1 ]; then return 1; fi
	unzip_package
	if [ $? != 0 ]; then
		log ' !! Something went wrong while unzipping files. Skipping update.'
		return 1
	fi

	if [ -f "$diff_file" ]; then
		test_diff
		return $?
	fi
}

#create_copy_of_prey(){
#	create_tmpdir # just to make sure
#	old_base_path="$base_path"
#	base_path="$tmpdir/newprey"
#	cp -R "$old_base_path" "$base_path"
#	cd "$base_path"
#}

apply_changes(){

	local return_status=0
	# create_copy_of_prey

	if [ -f "$diff_file" ]; then

		apply_diff
		local return_status=$?
		delete_files_according_to_diff

		rm "$diff_file" 2> /dev/null
	fi

	log ' -- Updating files!'
	copy_new_files

	log ' -- Cleaning up...'
	delete_unneeded_stuff_in "$base_path/platform"
	remove_temp_stuff

	return $return_status
}

perform_update() {

	pre_update_hook
	backup_config

	log "\n${bold} >> Applying changes!${bold_end}\n"
	sleep 1

	apply_changes
	local update_status=$?

	# set current version as previous, to compare
	local previous_version=$version

	# get new version
	. "$base_path/version"

	if [[ $update_status == 0 && `is_greater_than $version $previous_version` == 1 ]]; then

			log " ++ Update OK! New installed version of Prey is $version."
			rm -f "$base_path/.update_failed" 2> /dev/null

	else

		log ' -- Something went wrong while updating!'
		echo "$previous_version" > "$base_path/.update_failed"

	fi

	report_update_status $update_status
	cleanup

	# restore_config

	log ' -- Running post-update hooks...'
	post_update_hook $update_status
	run_post_update_script

	log " -- All done! Exiting...\n"
	exit $update_status

}

run_prey_updater(){

	log " -- Performing pre update checks..."

	check_for_failed_update
	if [ $? == 1 ]; then return 1; fi

	verify_write_access
	if [ $? == 1 ]; then return 1; fi

	log ' -- All set!'
	log "\n${bold} >> Running Prey updater!${bold_end}\n"

	download_and_test_update

	if [ $? == 0 ]; then

		# we update as a different process so we can
		# modify prey.sh in case we need to
		perform_update &
		exit 0

	fi
}

run_post_update_script(){
	local post_update_script_path="$base_path/post_update.sh"
	if [ -f "$post_update_script_path" ]; then
		log " -- Performing on-demand post-install instructions..."
		. "$post_update_script_path"
		rm -f "$post_update_script_path"
	fi
}

backup_config(){
	log ' -- Backing up current settings...'
	cp "$config_file" "$config_file.backup"
}

restore_config(){
	# if there's no default config file, then lets just copy the backup file back
	if [ ! -f "$config_file.default" ]; then

		cp "$config_file.backup" "$config_file"
		return 1

	else # lets copy the default config file and restore the previous settings

		cp "$config_file.default" "$config_file"
		log ' -- Applying current config settings to new config file...'

		# parse file and remove all comments and empty lines
		config_options=$(cat "$config_file.backup" | grep -v '^#' | grep -v "^$" | sed 's/ /-SPACE-/')
		for option in $config_options; do
			local option_name=$(echo "$option" | sed 's/=.*//')
			local option_value=$(echo "$option" | sed -e 's/[^=]*=//' -e "s/^'//" -e "s/'$//" -e "s/\//-SLASH-/g")
			save_config_value "$option_name" "$option_value"
		done

		sed -i -e "s/-SLASH-/\//g" -e "s/-SPACE-/ /g" "$config_file" # resolve the slash and space hacks
		log ' -- Done!'

	fi
}

####################################################################
# module updater
####################################################################

get_module(){
	rm "$module_file" 2> /dev/null
	getter -u $api_key:x "$modules_url/modules/$1.zip" -o "$module_file" 2> /dev/null
}

install_or_update_module(){

	# not local var as we use it upstairs
	module_file="$tmpdir/module-$1.zip"

	log " -- Trying to fetch $1 module from repository..."
	get_module $1

	# we check the file's size as we may have received a 404 html file
	if [[ -f "$module_file" && `file_size "$module_file"` -gt 1500 ]]; then
		log " -- Got new $1 module from repository! Unpackaging..."

		log ' -- Unzipping module...'
		unzip "$module_file" -d "$tmpdir/$1.tmp" &> /dev/null

		if [ $? == 0 ]; then

			if [ -d "$base_path/modules/$1" ]; then # module already installed
				log ' -- Removing old module...'
				rm -Rf "$base_path/modules/$1"
			fi

			log ' -- Putting new module where it belongs...'
			[ ! -d "$base_path/modules" ] && mkdir "$base_path/modules"
			mv "$tmpdir/$1.tmp/$1" "$base_path/modules/$1"

			log ' -- Cleaning up...'
			delete_unneeded_stuff_in "$base_path/modules/$1/platform"
			rm "$module_file" 2> /dev/null

			log " ++ New $1 module placed and ready to go!"
			return 0
		fi

	else

		log " -- Couldn't grab module package. The repository may be temporarily down."

	fi

	return 1

}

delete_unneeded_stuff_in(){
	if [ -d "$1" ]; then
		rm -Rf `find "$1" -mindepth 1 -maxdepth 1 -type d -not -iname "$os"`
	fi
}
