#!/bin/bash
#########################################################################
#                                                                       #
#        updater - Automatically update Microsoft Edge on Solus         #
#                                                                       #
#                             Dependencies:                             #
#                                 wget                                  #
#                                gunzip                                 #
#                                  tar                                  #
#                       sort (coreutils >= 8.32)                        #
#                              notify-send                              #
#                                                                       #
#                                                                       #
#         Disclaimer: This version only works for MS Edge Beta.         #
#          A later release may allow for update of Dev builds           #
#                         and potentially both.                         #
#                                                                       #
#                       Author: Ethan Smith-Coss                        #
#                            Version: 0.1.4                             #
#                    Created: 2021-05-20T16:47+0100                     #
#                 Last Modified: 2021-06-13T21:36+0100                  #
#                                                                       #
#                 ####################################                  #
#                                                                       #
#        updater - Automatically update Microsoft Edge on Solus         #
#                   Copyright ©️ 2021 Ethan Smith-Coss                   #
#                                                                       #
# This program is free software: you can redistribute it and/or modify  #
# it under the terms of the GNU General Public License as published by  #
#   the Free Software Foundation, either version 3 of the License, or   #
#                  (at your option) any later version.                  #
#                                                                       #
#    This program is distributed in the hope that it will be useful,    #
#    but WITHOUT ANY WARRANTY; without even the implied warranty of     #
#     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the     #
#             GNU General Public License for more details.              #
#                                                                       #
#   You should have received a copy of the GNU General Public License   #
# along with this program.  If not, see <http://www.gnu.org/licenses/>. #
#                                                                       #
#########################################################################
## program name
prog_name="Microsoft Edge Updater"
## version
version="0.1.4"
## usage statement
usage="Usage: $(basename $0) [OPTION]
$prog_name ($version) - An updater program which can install the latest available version of Microsoft Edge to non-Debian-based Linux distros.

DISCLAIMER: Currently this program can only update the Beta release of Microsoft Edge for amd64 architectures.

Options:
  -nn, --no-notify    Prevent notifications from being set after a session.
   -y, --yes-all      Assume yes to all yes/no decisions.
   -v, --version      Print out the version of the script and exit.
   -h, --help         Print this help message and exit
"

runtime_dir=$(realpath "${BASH_SOURCE[0]}" | xargs -r dirname)
## runtime locations
log_file="$runtime_dir/.log.1"
logs_dir="$runtime_dir/logs"
tmp_path="/tmp/microsoft-edge-autoupdater"
garbage="$tmp_path/garbage.tmp"
## program timer
SECONDS=0

[[ ! -d "$logs_dir" ]] && mkdir "$logs_dir"
[[ ! -d "$tmp_path" ]] && mkdir "$tmp_path"


function clean_up {
	log "DEBUG" "EXIT signal was raised, cleaning up system after session before exiting..." "$log_file"
	printf "Cleaning up system after session..."
	cp "$log_file" "$logs_dir/updater.log" # && rm "$log_file"
#	[[ -d "$tmp_path/opt" ]] && rm -r "$tmp_path/opt"
#	[[ -d "$tmp_path/usr" ]] && rm -r "$tmp_path/usr"

	while read -r garbage_collection ; do
		[[ -f "$garbage_collection" || -d "$garbage_collection" ]] && rm -r "$garbage_collection"
	done < <(cat "$garbage")
	[[ -f "$garbage" ]] && rm "$garbage"


	log "CLEANER" "Finished cleaning up system after session. Nice and clean :D Goodbye." "$runtime_dir/logs/updater.log"
	echo "done. Goodbye."
	
	elapsed_time=$SECONDS
	script_time="$(($elapsed_time / 60))m $(($elapsed_time % 60))s"
	log "END" "Total time for script execution was: $script_time" "$runtime_dir/logs/updater.log"

	echo >> "$runtime_dir/logs/updater.log"
	cat "$logs_dir/updater.log" >> "$logs_dir/updater.history"
}
## iterate over all user inputs
while test $# -gt 0 ; do
	case $1 in
		-y | --yes-all)
			yes_flag=0 ; shift
			;;
		-h | --help)
			echo "$usage"
			exit 0
			;;
		-v | --version)
			echo "$prog_name ($version)"
			exit 0
			;;
		-nn | --no-notify)
			nn_flag=0 ; shift
			;;
		*)
			shift
			;;
	esac
done

## check if the script is being ran as root - exit otherwise
[[ $(id -u) -ne 0 ]] && { echo "Please run script with root privilages." ; exit 1 ; }

# set trap to trigger clean up function on any exit
trap clean_up EXIT

source "$runtime_dir/utils/common"
log "PREINIT" "----[New instance of script has been started: $(date -Iseconds)]----" "$log_file"
echo "$log_file" >> "$garbage"

## setup the yes flag to automatically accept all yes/no inputs
yes_flag=${yes_flag:-1}
log "DEBUG" "The yes flag has been set to: $yes_flag." "$log_file"
## setup the no-notify (nn) flag if notify-send isn't installed
command -v notify-send >/dev/null 2>&1 || nn_flag=0
nn_flag=${nn_flag:-1}

## check if Microsoft Edge (Beta) is already installed
command -v microsoft-edge-beta &>/dev/null
exit_code=$?
[[ ! $exit_code -eq 0 ]] && { echo "Microsoft Edge (Beta) is not installed. Exiting updater..." ;
	log "DEBUG" "Microsoft Edge (Beta) not recognised as an installed program. Exiting...(1)" ; exit 1 ; }
log "DEBUG" "Microsoft Edge (Beta) is installed to system. Proceeding with update..." "$log_file"

## ensure source.list exists
source_list="$runtime_dir/source.list"
log "DEBUG" "Checking for a source.list in current runtime directory..." "$log_file"
printf "Checking for source.list..."
[[ ! -f "$source_list" ]] && { echo -e "\nUpdater encountered an issue: source.list not found. Exiting..." ; 
log "ERROR" "Cannot find source.list in runtime directory ($source_list). Exiting...(2)" "$log_file" ; exit 2 ; }

log "DEBUG" "Found a souce.list in $runtime_dir." "$log_file"
echo "found."

## ensure host is reachable
host=$(grep "\[host\]" "$source_list" | cut -d' ' -f 2)
log "DEBUG" "Checking if host is available according to defined value of [host]: $host..." "$log_file"
echo "Checking to see if the host is known and reachable..."
if ! wget --spider "$host" >/dev/null 2>&1 ; then
	echo "Cannot reach end-point for distribution information. Check your Internet connection and try again. Exiting..."
	log "DEBUG" "Upstream end-point is cannot be contacted. Potential Internet connection issue or end-point address ($dist_upstream). Exiting...(3)" "$log_file"
	[[ ! $nn_flag -eq 0 ]] && notify "Microsoft Edge (Beta) Update" "There seems to be an issue connecting to the end-point of Microsoft. Check your Internet connection and try again."
	
	exit 3
fi
log "DEBUG" "Host is known and reachable. Continuing update to fetch data..." "$log_file"
echo "Host known and reachable: (host) $host"

## identify the end-point URL for distribution information
dist_upstream=$(grep "\[dists.*\]" "$source_list" | cut -d' ' -f 2-4 --output-delimiter '/')
archi=$(grep -o "\[dists.*\]" "$source_list" | sed -E 's/\[dists=(.*)\]/\1/')
log "DEBUG" "Fetching Packages file from $dist_upstream for $archi architectures..." "$log_file"
## fetch the file - :@Ethan: there's no reason to inform the user of this operation unless it fails
wget -a "$log_file" -O "$tmp_path/Packages.gz" "$dist_upstream/binary-$archi/Packages.gz"
exit_code=$?
[[ ! $exit_code -eq 0 ]] && { echo "There was an issue retrieving the package information for the update. Please check $logs_dir/updater.log, for more information. Exiting..." ;
	log "DEBUG" "Encountered an issue with wget. Exiting...(4)" "$log_file" ; exit 4 ; }
log "DEBUG" "Successfully downloaded release package information." "$log_file"

log "DEBUG" "Fetching Contents-$archi.gz from ${dist_upstream::-4}" "$log_file"
wget -a "$log_file" -O "$tmp_path/Contents-$archi.gz" "${dist_upstream::-4}/Contents-$archi.gz"
exit_code=$?
[[ ! $exit_code -eq 0 ]] && { echo "There was an issue retrieving the package contents for the update. Please check $log_file/updater.log, so more information. Exiting..." ;
	log "ERROR" "Encountered an issue with wget. Exiting...(4)" "$log_file" ; exit 4 ; }
log "DEBUG" "Successfully downloaded release contents information." "$log_file"

### :@TODO: continue with TODO list. Finish off above code section to ensure wget is successful and logging appropriately.
log "DEBUG" "Uncompressing downloaded gz file using gunzip, $tmp_path/Packages.gz..." "$log_file"
## force gunzip to overwrite decompressed file if it already exists
gunzip -f "$tmp_path/Packages.gz" "$tmp_path/Contents-$archi.gz" >/dev/null 2>&1
log "DEBUG" "Successfully uncompressed content." "$log_file"

## get the most recent package entry from Packages list for Beta version of browser
log "DEBUG" "Identifying the most recent package information from Packages catelogue..." "$log_file"
## find the earliest entry for Edge Beta release
start_point=$(grep -n 'Package:.*-beta' "$tmp_path/Packages" | tr '\n' ' ' | cut -d: -f1)
## make a list of all lines which are blank - used to identify end of package information entry
end_points=( $(grep -n '^\s*$' "$tmp_path/Packages" | sed 's/://g') )
## loop over all potential end points
for (( i=0 ; i<"${#end_points[@]}"; i++)) ; do
	## if the end point is greater than start point, set $end_point to the index of i
	### :@NOTE: this will more often then not be the first value in the array. However,
	### if a dev release comes out before the next beta version, the package entry may be
	### much later in the file. We break once the correct end point is found.
	[[ "${end_points[$i]}" -gt $start_point ]] && end_point="${end_points[$i]}" && break
done
## adjust the start and end point values
((start_point--)) ; ((end_point++))
## this ensures end_point is set. :@Ethan: I could just test if the variable exists but I like the clarity of this for now
end_point="${end_point:--1}"
## if for some reason $end_point is set to -1, we have an issue with the Packages file and should exit
[[ $end_point -eq -1 ]] && { log "ERROR" "EOF reached and indexing out of bounds value has been set. Exiting...(5)" "$log_file" ; exit 255 ; }
## extrapolate the package information and save to Release
package_entry=$(awk -v start=$start_point -v end=$end_point 'NR>1*start&&NR<1*end' "$tmp_path/Packages" > "$tmp_path/Release")
log "DEBUG" "Extrapolated most recent package information entry from $tmp_path/Release. Package information selected is as below:" "$log_file"
## pretty print each line when appending to make it easier to read in log file
sed 's/^/  (Release Info)   /' "$tmp_path/Release" | sed -E 's/(^\s*\(Release Info\)\s*$)/\1(END)/' >> "$log_file"

### :@Ethan: at this point we can start ensuring that a newer release is available, inform the user of the new size,
### check dependences, and just ensure the user wants to go ahead with the update.

## get the current version number and the version in Release
current_version=$(microsoft-edge-beta --version | cut -d' ' -f3)
release_version=$(grep 'Version' "$tmp_path/Release" | sed -E 's/Version:\s*(.*)-.*/\1/')
log "DEBUG" "Checking if there is an update available (release: $release_version) or if most recent version is already installed to the system..." "$log_file"
## compare the versions - using sort -V.
if [[ "$release_version" == "$current_version" ]]; then
	upgradeable=1
else
	[[ "$current_version" != "$(echo -e "$current_version\n$release_version" | sort -V | head -n2)" ]] && upgradeable=0
fi
## this should never be substituted to 1 - but it's a cover if the release version happens to be behind the current version
##"${upgradeable:=1}"
upgradeable=${upgradeable:-1}
## check if we can upgrade, can exit 1 for passive exit - exit not notifying a failure for another reason
if [[ $upgradeable -eq 1 ]] ; then 
	elapsed_time=$SECONDS
	script_time="$(($elapsed_time / 60))m $(($elapsed_time % 60))s"
	echo "Most recent version of Microsoft Edge (Beta) [v$current_version] is already installed on this system, no need to update. Finished in $script_time"
	log "DEBUG" "Most recent version of Microsoft Edge (Beta) is already installed to the system (v$current_version). Completed in $script_time. Exiting...(1)" "$log_file"
	[[ ! $nn_flag -eq 0 ]] && notify "Microsoft Edge (Beta) Update" "There is no newer version of Microsoft Edge (Beta) to install. Newest version is already installed: v$current_version"
	
	exit 1
fi
log "DEBUG" "There is a more recent release of Microsoft Edge (Beta) available for download (v$release_version). Continuing with update." "$log_file"
echo "Identified a new release of Microsoft Edge (Beta) [Current: v$current_version. New: v$release_version]. Starting the download and installation process..."

## check if msedge process is already running
is_running=$(ps -aux | grep -oc 'msedge')
if [[ $is_running -gt 1 ]] ; then
	log "DEBUG" "Identified running processes for msedge. Determining how script should handle process..." "$log_file"
	if [[ ! $yes_flag -eq 0 ]] ; then
		echo -n "Microsoft Edge (Beta) is already open. To continue update, the browser must be closed. Are you sure you wish to proceed? [(Y)es/No]: " && read -n 1 choice ; echo
		[[ $(echo $choice | awk '{print tolower($0)}') == "n" ]] && { log "DEBUG" "User opted against allowing the script to close Microsoft Edge (Beta) by default. Exiting...(1)" "$log_file" ; exit 1 ; }
	fi
	log "DEBUG" "Confirmation has been given to proceed with killing Microsoft Edge (Beta) processes. Killing msedge..." "$log_file"
	killall msedge
	exit_code=$?
	[[ ! $exit_code -eq 0 ]] && { log "ERROR" "Process is refusing to exit. Sending SIGKILL signal." "$log_file" ;
		pkill -9 msedge ; }
fi
log "DEBUG" "Microsoft Edge (Beta) has been closed, either by yes flag enabled or by user decision. Process may not have been running thus never killed." "$log_file"


# Download the newest version of Microsoft Edge (Beta)
## get the end-point for .deb file location and filename
pool=$(grep '\[pool\]' "$source_list" | cut -d' ' -f2)
filename=$(grep 'Filename:.*' "$tmp_path/Release" | cut -d' ' -f2)
url="$pool$filename" ## set URL to be concatenation of pool + filename
## check if the file end-point is actually reachable before trying to download
log "DEBUG" "Checking the following release version file end-point is accessible ($url)..." "$log_file"

### :@Ethan: This is trying to download the file for a response so effectively we will be doing the same work twice
### so is inefficient for testing if the end-point is reachable. ping and telnet are not suitable for this operation.
### An assumption will have to be made for now.

### :@Ethan: The above message has been addressed and wget commands for checking end-points has been changed from
### `wget -qO- [URL] >/dev/null 2>&1` to: `wget --spider [URL] >/dev/null 2>&1`. Check manpage for more about the
### spider argument.

if ! wget --spider "$url" >/dev/null 2>&1 ; then
	echo "Cannot reach end-point for latest release. Check your Internet connection and try again."
	log "DEBUG" "Pool end-point is cannot be contacted. Potential Internet connection issue or end-point address ($url). Exiting...(5)" "$log_file"
	[[ ! $nn_flag -eq 0 ]] && notify "Microsoft Edge (Beta) Update" "There seems to be an issue connecting to the end-point of Microsoft. Check your Internet connection and try again."
	exit 5
fi
log "DEBUG" "Pool is known and reachable. Beginning download..." "$log_file"
echo "Host known and reachable: (download) $url"

## set filename to not be pool address but the name of file
filename=$(echo "$url" | rev | cut -d'/' -f1 | rev)
filesize=$(grep '^Size:.*' "$tmp_path/Release" | cut -d' ' -f2)
log "DEBUG" "Checking if user wishes to proceed with the update installation..." "$log_file"
## inform the user of the new download size before downloading
if [[ $yes_flag -eq 1 ]] ; then
	echo -n "The following package will be installed, $filename [$filesize]. Are you sure you wish to continue? [(Y)es/No]: " && read -n 1 choice
	[[ "$(echo $choice | awk '{print tolower($0)}')" == "n" ]] && { echo -e "\nThe following update to Microsoft Edge (Beta) v$release_version will not be installed. Exiting updater..." ;
	log "DEBUG" "User opted out for updating from $current_version to $release_version. Exiting...(1)" "$log_file" ; exit 1 ; }
	echo
fi
log "DEBUG" "Confirmation has been given to proceed with the following update of Microsoft Edge (Beta) [v$release_version]." "$log_file"

# download the debian file
log "DEBUG" "Checking if the file is already downloaded to the system..." "$log_file"
if [[ ! -f "$tmp_path/$filename" ]] ; then
	log "DEBUG" "Downloading the latest release version [v$release_version], file: $filename (URL: $url)" "$log_file"
	echo "Downloading the following release file: $filename. This may take a moment..."
	# wget_timed=$(\time --format "%x:%e" wget -a "$tmp_path/wget_dump.log" -O "$tmp_path/$filename" "$url" 2>&1)
	wget -a "$tmp_path/wget_dump.log" --progress=bar --show-progress -O "$tmp_path/$filename" "$url"
	exit_code=$? # $(echo $wget_timed | cut -d: -f1)
	[[ ! $exit_code -eq 0 ]] && { echo "There was an issue downloading the Debian version of Microsoft Edge (Beta)." \
		"Please check $logs_dir/updater.log, for more information. Exiting update..." ;
		log "DEBUG" "Encountered an issue with wget. Exiting...(6)" "$log_file" ; exit 6 ; }
	awk -v end=$(grep -n '^\s*$' "$tmp_path/wget_dump.log" | tr '\n' ' ' | cut -d: -f1) 'NR>1*0&&NR<1*end+1' "$tmp_path/wget_dump.log" >> "$log_file"
	log "DEBUG" "Download completed successfully in $(echo $wget_timed | cut -d' ' -f2)s, wget log below." "$log_file"
	printf "Download complete in $(echo $wget_timed | cut -d' ' -f2)s. Validating checksums..."
else
	log "DEBUG" "File already downloaded to system. Skipped download and verifying checksum..." "$log_file"
	printf "Latest Debian release file is downloaded to the system, skipping download. Validating checksums..."
fi
## add the .deb file to garbage list
echo "$tmp_path/$filename" >> "$garbage"

## verify the downloaded file 
if [[ "$(sha256sum "$tmp_path/$filename" | cut -d' ' -f1)" != "$(grep 'SHA256:.*' "$tmp_path/Release" | cut -d' ' -f2)" ]] ; then
	## automatically remove the file - :@Ethan: it's either corrupt or hazardous to the health of the system
	rm "$tmp_path/$filename"
	log "DEBUG" "Checksum (SHA256) failed and integrity of file lost. File has been removed as either corrupt or hazardous/dangerous. Exiting...(7)" "$log_file"
	echo -e "failed.\nThe checksum (SHA256) failed for some reason and removed either because it was corrupt or dangerous. Exiting updater...\n"
	[[ ! $nn_flag -eq 0 ]] && notify "Microsoft Edge (Beta) Update" "There was an issue with the checksum to verify the latest release. The Debian file has been automatically removed to prevent potential danger to the system."
	exit 7
fi
echo "complete."


# unarchive the .deb file and extract data.tar.gz
log "DEBUG" "Unarchiving the downloaded DEB file ($tmp_path/$filename)..." "$log_file"
printf "Unzipping download..."
## use ar to unarchive .deb file
ar vx "$tmp_path/$filename" --output "$tmp_path" >/dev/null 2>&1
exit_code=$?
[[ ! $exit_code -eq 0 ]] && { echo -e "incomplete.\nThere was an issue unarchiving $filename. Exiting updater...\n" ;
	log "ERROR" "There was an issue when unarchiving $filename. Exiting...(8)" "$log_file" ; exit 8 ; }
log "DEBUG" "Successfully unarchived $filename, decompressing the data.tar.gz file..." "$log_file"
## extract data from data.tar.gz
tar --overwrite -xf "$tmp_path/data.tar.xz" --directory "$tmp_path" 1>"$tmp_path/tar_dump.log" 2>>"$log_file"
exit_code=$?
[[ ! $exit_code -eq 0 ]] && { echo -e "incomplete.\nThere was an extracting data.tar.gz. Exiting updater...\n" ;
	log "ERROR" "There was an issue when decompressing data.tar.gz using tar -xvf. Exiting...(8)" "$tmp_path" ; exit 8 ; }
log "DEBUG" "Successfully extracted files from data.tar.gz." "$log_file"
echo "completed."
## etc/ directory only contains a cron daily directory, remove it
rm -r "$tmp_path/etc/"
## add data.tar.xz to the garbage list
echo "$tmp_path/data.tar.xz" >> "$garbage"

# compress archive the currently installed version of Edge (Beta) for restoration on failure
## :@TODO: compress everything on the system for a backup. Remove it if installation of new version was successful
log "DEBUG" "Creating an archive gzip tarball of system /opt/ and /usr/ directories..." "$log_file"
printf "Installing new version to system. This may take a few minutes..."
archive_system "microsoft/msedge-beta"
exit_code=$?
if [[ ! $exit_code -eq 0 ]] ; then
	### :@Ethan: there was an issue with creating an archive, should probably inform the user to make a decision (unless -y is set)
	echo -e "failed.\nThere was an issue creating a backup of the system. Exiting..."
	log "ERROR" "There was an issue creating a gzip tarball of the system. Exiting...($exit_code)" "$log_file"
	exit $exit_code
fi
log "DEBUG" "Successfully created an archive gzip tarball of the system. Safe to continue with installation." "$log_file"

### :@NOTE: used to terminate the program at a certain point for incremental testing of functionality recently added up to the point of this exit.
#exit 0

# install the new version to system
log "DEBUG" "Copying files to there appropriate location..." "$log_file"
cp -r "$tmp_path/opt" "/" >>"$log_file" 2>&1
exit_code=$?
[[ ! $exit_code -eq 0 ]] && { echo -e "failed.\nThere was an issue installing files to the system. Reverting to previous version..." ; 
	log "DEBUG" "There was an issue copying $tmp_path/opt/ file to system /opt/. Reverting system files and exiting...(9)" "$log_file" ; restore_files && exit 9 ; }
cp -r "$tmp_path/usr" "/" >>"$log_file" 2>&1
exit_code=$?
[[ ! $exit_code -eq 0 ]] && { echo -e "failed.\nThere was an issue installing files to the system. Reverting to previous version..." ; 
	log "DEBUG" "There was an issue copying $tmp_path/usr/ file to system /usr/. Reverting system files and exiting...(9)" "$log_file" ; restore_files && exit 9 ; }
log "DEBUG" "Successfully installed files to their appropriate location." "$log_file"
echo "installation complete."
## add directories usr/ and opt/ to garbage
echo "$tmp_path/usr" >> "$garbage"
echo "$tmp_path/opt" >> "$garbage"

## confirm the update was successful - try to restore system otherwise
## :@TODO: validate new version has successfully installed properly - exit 12
if [[ "$(microsoft-edge-beta --version | cut -d' ' -f3)" != "$release_version" ]] ; then
	log "ERROR" "There was an issue with the update and installation. Restoring to previous version..." "$log_file"
	printf "Update was unsuccessful. Restoring to previous version..."

	## attempt restoration of system
	return_code=$(restore_files "microsoft/msedge-beta")
	[[ $return_code -eq 12 ]] && { echo -e "failed.\nThere was an error trying to restore system files." \ 
		"Either there no back was made or another issue occurred. Consult $log_file for more information." ; exit $return_code ; }
	log "DEBUG" "System restoration was successful, safe to use; however, the update was still failed. Exiting...(11)" "$log_file"
	echo -e "completed. However, there was still an issue trying to update Microsoft Edge (Beta) to the latest version." \
		"Consult $log_file for more information. Exiting..."
	[[ ! $nn_flag -eq 0 ]] && notify "Microsoft Edge (Beta) Update" "There was an issue installing the update to your system. The previous version, v$current_version, has been restored."
	
	exit 11
fi

elapsed_time=$SECONDS
script_time="$(($elapsed_time / 60))m $(($elapsed_time % 60))s"
log "DEBUG" "Installation and update was successful (completed in: $script_time) and system can be cleaned up to remove any waste." "$log_file"
echo "Microsoft Edge (Beta) has been successfully updated to the latest version: v$current_version -> v$release_version, in $script_time."
[[ ! $nn_flag -eq 0 ]] && notify "Microsoft Edge (Beta) Update" "Successfully updated Microsoft Edge (Beta) to the latest version: v$release_version"
