#!/bin/bash
#
# Copyright (c) 2020 Gaël PORTAY
#
# SPDX-License-Identifier: LGPL-2.1-or-later
#
# https://www.freedesktop.org/software/systemd/man/systemd.offline-updates.html

set -e
set -u
set -o pipefail

VERSION="1.1"

opts=(--noconfirm --noprogressbar --cachedir /var/lib/system-update)
while [[ "$#" -ne 0 ]]
do
	if [[ "$1" =~ (-h|--help) ]]
	then
		echo "Usage: ${0##*/} [--now] [--force] [--] [pacman-extra-flags]"
		exit 0
	elif [[ "$1" =~ (-V|--version) ]]
	then
		echo "$VERSION"
		exit
	elif [[ "$1" == "--now" ]]
	then
		now=true
	elif [[ "$1" == "--force" ]]
	then
		force=true
	elif [[ "$1" == "--" ]]
	then
		shift
		break
	else
		echo "$1: Invalid argument" >&2
		exit 1
	fi
	shift
done

if [[ -z "${now:-}"  ]]
then
	if [[ ! -e /system-update ]] || [[ "${force:-}" ]]
	then
		echo ":: Preparing offline system updates..."
		mkdir -p /var/lib/system-update
		pacman "${opts[@]}" --downloadonly -Syu "$@"

		mapfile -t pkgs < <(find /var/lib/system-update -name "*.pkg.tar.*")
		if [[ "${#pkgs[@]}" -eq 0 ]]
		then
			exit 0
		fi

		ln -sf /var/lib/system-update /system-update
		if [[ "$#" -gt 0 ]]
		then
			echo "$@" >/var/lib/system-update/.flags
		fi
	fi

	echo ":: Please reboot the machine to trigger the offline system updates."
	if [[ -e /var/lib/system-update/.flags ]]
	then
		cat /var/lib/system-update/.flags
	fi
	exit
fi

# As the first step, an update service should check if the /system-update
# symlink points to the location used by that update service. In case it does
# not exist or points to a different location, the service must exit without
# error. 
if [[ ! -L /system-update ]] ||
   [[ "$(readlink -f /system-update)" != "/var/lib/system-update" ]]
then
	echo ":: No offline system updates prepared."
	exit 0
fi

# Make sure to remove the /system-update symlink as early as possible in the
# update script to avoid reboot loops in case the update fails.
rm -f /system-update

progress() {
	if plymouth --ping 2>/dev/null
	then
		plymouth system-update --progress="$1"
	fi

	echo -n "$1 "
}

if [[ -r /var/lib/system-update/.flags ]]
then
	read -r -a extraflags < <(cat /var/lib/system-update/.flags)
	if [[ "${#extraflags[@]}" -gt 0 ]]
	then
		set -- "$@" "${extraflags[@]}"
	fi
	rm -f /var/lib/system-update/.flags
fi

# If your script succeeds you should trigger the reboot in your own code, for
# example by invoking logind's Reboot() call or calling systemctl reboot. See
# logind dbus API for details.
# shellcheck disable=SC2154
trap 'ret="$?"; trap - 0; if [ "$ret" -eq 0 ]; then systemctl reboot; return "$ret"; fi' 0 INT

if plymouth --ping 2>/dev/null
then
	plymouth change-mode --system-upgrade
	plymouth system-update --progress=0
fi

i=0
step=
while read -r -a words
do
	# empty line: skip it! 
	if [[ "${#words[@]}" -eq 0 ]]
	then
		continue
	fi

	# the pacman steps:
	# :: Synchronizing package databases...
	# :: Starting full system upgrade...
	# :: Proceed with installation? [Y/n]
	# :: Retrieving packages...
	# :: Running pre-transaction hooks...
	# :: Processing package changes...
	# :: Running post-transaction hooks...
	if [[ "${words[0]}" =~ :: ]]
	then
		i=0
		step="${words[*]:1}"
		if [[ "$step" == "Synchronizing package databases..." ]] ||
		   [[ "$step" == "Retrieving packages..."             ]] ||
		   [[ "$step" == "Running pre-transaction hooks..."   ]] ||
		   [[ "$step" == "Running post-transaction hooks..."  ]]
		then
			echo "$step"
		fi
		continue
	fi

	# nothing to do: exit early!
	if [[ "${words[0]}" == "there is nothing to do" ]]
	then
		echo "Nothing to do, rebooting..."
		break
	fi

	# :: Starting full system upgrade...
	# resolving dependencies...
	# looking for conflicting packages...
	#
	# Packages (4) pkg1 pkg2 pkg3 pkg4
	#
	# Total Download Size:   ##.## MiB
	# Total Installed Size:  ##.## MiB
	# Net Upgrade Size:      ##.## MiB
	#
	if [[ "${words[0]}" == "Packages" ]]
	then
		# Packages (4) (...)
		if [[ "${words[1]}" =~ ^\([0-9]+\)$ ]]
		then
			count="${words[1]:1:$((${#words[1]}-2))}"
		fi
	# :: Retrieving packages...
	# downloading pkg...
	# :: Processing package changes...
	# upgrading pkg...
	elif [[ "${words[0]}" =~ ^(downloading|installing|upgrading)$ ]] &&
	     [[ "$step"       != "Synchronizing package databases..." ]]
	then
		i="$((i+1))"
		processing="${words[0]:0:1}"
		processing="${processing^^}"
		processing+="${words[0]:1}"
		if [[ -z "${count:-}" ]]
		then
			echo "$processing ${words[1]}"
			continue
		elif [[ "${words[0]}" == downloading ]]
		then
			echo "$processing ${words[1]} ($i/$count)"
			continue
		fi

		progress "$((20+(60*i)/count))"
		echo "$processing ${words[1]} ($i/$count)"
		continue
	# :: Running pre-transaction hooks...
	# ( #/##) Doing something...
	# (##/##) Doing another thing...
	# :: Running post-transaction hooks...
	# (#/#) Doing a last thing...
	elif [[ "$step" == "Running pre-transaction hooks..."  ]] ||
	     [[ "$step" == "Running post-transaction hooks..." ]]
	then
		# ( #/##) (...): remove the parasite space.
		if [[ "${words[0]}" == "(" ]]
		then
			words[1]="${words[0]}${words[1]}"
			words[0]=
			words=("${words[@]:1}")
		fi

		if ! [[ "${words[0]}" =~ ^\([0-9]+/[0-9]+\)$ ]]
		then
			echo "${words[*]}"
			continue
		fi

		if [[ "$step" == "Running pre-transaction hooks..." ]]
		then
			base=0
		else
			base=80
		fi

		val="${words[0]:1:$((${#words[0]}-2))}"
		progress "$((base+20*${val// /}))"
		echo "${words[*]:1} ${words[0]}"
		continue
	fi
done < <(pacman "${opts[@]}" -Su "$@")
rm -f /var/lib/system-update/*.pkg.tar.*

if plymouth --ping 2>/dev/null
then
	plymouth change-mode --boot-up
	plymouth display-message --text="Rebooting..."
fi

echo "Rebooting..."
sleep 5s
