#! /usr/bin/env sh
# This script is part of the eix project and distributed under the
# terms of the GNU General Public License v2.
#
# Authors and Copyright (c):
#   Emil Beinroth <emilbeinroth@gmx.net> (original)
#   Martin V\"ath <vaeth@mathematik.uni-wuerzburg.de> (complete rewrite)
#
# This script calls emerge --sync and shows the differences.
# See the eix manpage for details. (eix 0.13.3).

. '/usr/bin/functions-eix.sh'
read_functions

LOCAL_VARCACHE="`update-eix --print EPREFIX_PORTAGE_CACHE`"
EIXCACHE="`update-eix --print EIX_CACHEFILE`"
EIXPREVIOUS="${EIXCACHE}.previous"
LOCAL_PORTAGE_CONFIGROOT="`update-eix --print PORTAGE_CONFIGROOT`"
EIXSYNCCONF="${LOCAL_PORTAGE_CONFIGROOT}/etc/eix-sync.conf"
PMOD="${LOCAL_PORTAGE_CONFIGROOT}/etc/portage/modules"
MODULENAME='cache.metadata_overlay.database'

Usage () {
	echo "${0##*/} [options]
Call layman/emerge --sync/--metadata and/or show updates. (eix 0.13.3)

Unless the -t option is used, the old database will be saved to
    ${EIXPREVIOUS}.

The file ${EIXSYNCCONF} determines for which overlays layman is called.
You can also add certain hooks in that file - \"man eix\" for details.

The file ${EIXSYNCCONF} and the environment variable EIX_SYNC_OPTS can
contain default options which are processed before the command line options.
Note that \"--\" in the default options will forbid command line options.
The following options are available:

-i   Ignore all previous options (useful to ignore ${EIXSYNCCONF} options).
-d   Only show differences to the previously saved database and exit.
     Except for executing the !! lines in ${EIXSYNCCONF}
     this is the same as diff-eix ${EIXPREVIOUS}.
-s [USER@]SERVER[:DIR] Sync via rsync from SERVER.
     USER defaults to current user and DIR defaults to \$PORTDIR
-2 [USER@]CLIENT[:DIR] Sync via rsync *to* CLIENT after successfull syncing;
     you should later call eix-sync -u locally on CLIENT.
     If you already have synced you might want to combine this option with -uU.
-U   Do not touch the database and omit the hooks after update-eix (@ entries)
     and do not show differences. This option implies -R
-u   Update database only and show differences. This is equivalent to -l@s ''
-l   Do not call layman (and the !commands in ${EIXSYNCCONF}).
     However, the !! lines and postponed hooks (@ and @@ entries)
     will be executed anyway.
-@   Do not execute the hooks (@ and @@ entries) of ${EIXSYNCCONF}.
-S   Do not execute the hooks after emerge --sync (@@ entries).
-M   Run emerge --metadata. You might need this when you use e.g.
     PORTDIR_CACHE_METHOD=portage-2.1 and a newer portage version when
     FEATURES=metadata-transfer is inactive or disabled.
-m   Run emerge --metadata but skip emerge --sync.
-N   Skip emerge --metadata if it was activated by -M or -m.
-t   Use temporary file to save the current database (-d will not be possible).
-q   Be quiet (close stdout)
-w   Run emerge-webrsync instead of emerge --sync.
-W   Run emerge-delta-webrsync instead of emerge --sync.
-c CMD Run CMD instead of emerge --sync.
-C OPT Add OPT to the emerge --sync command (or whatever is used instead).
     This option can be used accumulatively.
-r   Clear /var/cache/edb/dep/* before syncing. This is only useful when you
     use e.g. PORTDIR_CACHE_METHOD=portage-2.1 and FEATURES=metadata-transfer
     is active. (This option is not default anymore).
-R   Cancel previous -r (e.g. if it was used in ${EIXSYNCCONF}).
-e   Skip heuristic tests whether you must be root.
-h   Show this text and exit."
	EXITCODE=${1:-1}
	exit ${1}
}

DiffOnly () {
	test -e "${EIXPREVIOUS}" || die "No previous database ${EIXPREVIOUS} exists"
	exec diff-eix -- "${EIXPREVIOUS}"
	die "Could not execute diff-eix"
}

if test -r "${EIXSYNCCONF}"; then
	HAVECONF=true
else
	HAVECONF=false
fi
ExecuteConfig () {
	local I J MYCMD
	AFTER_SYNC=''
	AFTER_UPDATE=''
	BEFORE_RSYNC=''
	CONFIG_OPTS=''
	${HAVECONF} || return 0
	while read I; do
		J="${I%%#*}"
		while [ "${J% }" != "${J}" ]; do J="${J%?}"; done
		while [ "${J# }" != "${J}" ]; do J="${J#?}"; done
		[ -n "${J}" ] || continue
		case "${J}" in
			'!!'*) [ "${1}" = 'opts' ] && eval "${J#??}"
				continue;;
			!*)  J="${J#?}"; MYCMD='!';;
			@@*) J="${J#??}"; MYCMD='@@';;
			@*)  J="${J#?}"; MYCMD='@';;
			\~*) J="${J#?}"; MYCMD='~';;
			-*)  CONFIG_OPTS="${CONFIG_OPTS} ${J}"; continue;;
			*)   MYCMD="layman";;
		esac
		[ "${1}" = 'opts' ] && continue
		case "${MYCMD}" in
			'@@')	Push AFTER_UPDATE "${J}"; continue;;
			'@')	Push AFTER_SYNC "${J}"; continue;;
			'~')	Push BEFORE_RSYNC "${J}"; continue;;
			'!')	${NOLAYMAN} || DoExecute "${J}"; continue;;
		esac
		${NOLAYMAN} && continue
		RootTest
		if [ "${J}" = '*' ]; then
			MyRunCommand "Syncing all portage overlays"
				"${MYCMD}" -S || die "${MYCMD} -S failed"
			continue;
		fi
		MyRunCommand "Syncing portage overlay '${J}'"
			"${MYCMD}" -s "${J}" || die "${MYCMD} -s ${J} failed"
	done <"${EIXSYNCCONF}"
}


# Get options from cli

DefaultOpts () {
	Push -c EMERGECMD 'emerge' '--sync'
	CLEARCACHE=false
	NOLAYMAN=false
	NOHOOKS=false
	QUIET=false
	USETEMP=false
	METADATA=false
	SKIP_SYNC=false
	SERVER=''
	CLIENT=''
	DOUPDATE=true
	SYNCHOOKS=true
	TEST_FOR_ROOT=true
}

DefaultOpts
ExecuteConfig 'opts'

while getopts "ids:2:Uul@SmMNtqwWc:C:rRe?hH" OPTION \
	${CONFIG_OPTS} ${EIX_SYNC_OPTS} "${@}"; do
	case "${OPTION}" in
		i) DefaultOpts;;
		d) DiffOnly;;
		s) SERVER="${OPTARG}";;
		2) CLIENT="${OPTARG}";;
		U) DOUPDATE=false;;
		u) NOLAYMAN=true; NOHOOKS=true; SKIP_SYNC=true; SERVER='';;
		l) NOLAYMAN=true;;
		@) NOHOOKS=true;;
		S) SYNCHOOKS=false;;
		m) SKIP_SYNC=true; METADATA=true;;
		M) METADATA=true;;
		N) METADATA=false;;
		t) USETEMP=true;;
		q) QUIET=true;;
		w) Push -c EMERGECMD 'emerge-webrsync';;
		W) Push -c EMERGECMD 'emerge-delta-webrsync';;
		c) Push -c EMERGECMD "${OPTARG}";;
		C) Push EMERGECMD "${OPTARG}";;
		r) CLEARCACHE=true;;
		R) CLEARCACHE=false;;
		e) TEST_FOR_ROOT=false;;
		*) Usage 0;;
	esac
done
[ -z "${SERVER}" ] || SKIP_SYNC=true

MyRun () {
	if [ "${1}" != 'time' ]; then
		"${@}"
		return
	fi
	# treat 'time' separately to allow for shell builtin
	shift
	# Of course, 'time' is a feature, i.e. things should still work even
	# if neither the shell nor the time command is available.
	if command -v time >/dev/null 2>&1; then
		time -p "${@}"
		return
	fi
	"${@}"
}

MyRunCommand () {
	${EMULATEMODE} && return 0
	einfo "${1}"
	shift
	MyRun "${@}"
}

DoExecute () {
	${EMULATEMODE} && return 0
	(
		for CURRENT_COMMAND; do
			MyRunCommand "Executing ${CURRENT_COMMAND}" \
			eval "${CURRENT_COMMAND}" || \
			die "Something went wrong with ${CURRENT_COMMAND}"
		done
	) || die
}

DoHook () {
	${NOHOOKS} && return 0
	eval "set -- ${1}"
	DoExecute "${@}"
}

RootTest () {
	${EMULATEMODE} || return 0
	eerror ""
	eerror "Nothing was executed, because a heuristic shows that"
	eerror "root permissions are required to execute everything successfully."
	einfo  ""
	einfo  "However, the heuristic might be wrong, e.g. if you use EPREFIX."
	einfo  "If you want to skip this test temporarily, use option -e."
	einfo  "If you want to skip this test permanently, put option -e"
	einfo  "into the file ${EIXSYNCCONF}."
	die    ""
}

SED1='s!^[[:space:]]*'
SED2="[[:space:]]*=[[:space:]]*\([^[:space:]]*\)[[:space:]]*\$!\1!p"
SedFor () {
	local A=''
	test -e "${2}" && \
		A=`sed -n -e "${SED1}${1}${SED2}" -- "${2}" | tail -n 1`
	A="${A%\"}"
	A="${A#\"}"
	A="${A%'}"
	A="${A#'}"
	echo "${A}"
}

TMPFILE=''
EXITCODE=0
DelTemp () {
	[ -z "${TMPFILE}" ] || rm -f -- "${TMPFILE}"
	TMPFILE=''
	trap - EXIT HUP INT TERM
	exit ${EXITCODE}
}
MakeTempFile () {
	if command -v mktemp >/dev/null 2>&1; then
		TMPFILE=`mktemp "/tmp/${0##*/}.XXXXXX"` || \
			die "cannot create temporary file"
	else
		local COUNT
		COUNT=0
		while true; do
			[ ${COUNT} -eq 9999 ] && \
				die "found no free tempname after 10000 attempts"
			COUNT="`expr ${COUNT} + 1`"
			TMPFILE="/tmp/${0##*/}.$$.${RANDOM}.${RANDOM}.${RANDOM}"
			test -e "${TMPFILE}" && continue
			: >"${TMPFILE}" || die "cannot create temporary file ${TMPFILE}"
			break
		done
	fi
	trap DelTemp EXIT HUP INT TERM
}

PREPRSYNC=false
PrepRsync () {
	GetPortdir
	HOSTDIR="${1#*:}"
	if [ -n "${HOSTDIR}" ] && [ "${HOSTDIR}" != "${1}" ]; then
		HOSTDIR="${1}"
	else
		HOSTDIR="${1%%:*}:${LOCAL_PORTDIR}"
	fi
	HOSTDIR="${HOSTDIR%/}/"
	${PREPRSYNC} && return
	local CURRENT_COMMAND PORTAGE_RSYNC_OPTS PORTAGE_RSYNC_EXTRA_OPTS C
	PORTAGE_RSYNC_OPTS="`portageq envvar PORTAGE_RSYNC_OPTS`"
	PORTAGE_RSYNC_EXTRA_OPTS="`portageq envvar PORTAGE_RSYNC_EXTRA_OPTS`"
	eval "set -- ${BEFORE_RSYNC}"
	for CURRENT_COMMAND; do
		C=`eval "${CURRENT_COMMAND}"` || \
			die "${CURRENT_COMMAND} failed"
		eval "${C}"
	done
	RSYNC_OPTS="${PORTAGE_RSYNC_OPTS} ${PORTAGE_RSYNC_EXTRA_OPTS}"
	PREPRSYNC=true
}

ClearCache () {
	${CLEARCACHE} || return 0
	# Cleaning old cache
	# portage 2.1_pre1 doesn't do this anymore, so *we* need to do it.
	RootTest
	MyRunCommand "Removing old portage-cache in ${LOCAL_VARCACHE}/var/cache/edb/dep" \
		rm -rf -- "${LOCAL_VARCACHE}"/var/cache/edb/dep/* || \
			die "rm -rf ${LOCAL_VARCACHE}/var/cache/edb/dep/* failed"
}

CallEmergeSync () {
	if [ -n "${SERVER}" ]; then
		${EMULATEMODE} && return
		PrepRsync "${SERVER}"
		MyRunCommand "rsyncing from ${HOSTDIR}" \
			rsync $RSYNC_OPTS "${HOSTDIR}" "${LOCAL_PORTDIR}" || \
				die "Could not rsync from ${HOSTDIR}"
		return
	fi
	${SKIP_SYNC} && return
	RootTest
	eval "set -- ${EMERGECMD}"
	MyRunCommand "Running ${*}" time "${@}" || die "${*} failed"
}

CallSyncClient () {
	${EMULATEMODE} && return
	[ -z "${CLIENT}" ] && return
	PrepRsync "${CLIENT}"
	MyRunCommand "rsyncing to ${HOSTDIR}" \
		rsync ${RSYNC_OPTS} "${LOCAL_PORTDIR}" "${HOSTDIR}" || \
			die "Could not rsync to ${HOSTDIR}"
}

TESTRESULT=''
CallEmergeMetadata () {
	${DOUPDATE} && ${METADATA} || return 0
	RootTest
	MyRunCommand "Running emerge --metadata" \
		emerge --metadata || die "emerge --metadata failed"
}

CondUpdate () {
	if ! test -f "${EIXCACHE}"; then
		RootTest
		MyRunCommand "eix-cache doesn't exist. Running update-eix!" \
				update-eix || die "update-eix failed"
		return
	fi
	${EMULATEMODE} && return
	if ! eix --is-current; then
		MyRunCommand "eix-cache format has changed. Running update-eix!" \
				update-eix || die "update-eix failed"
	fi
}

CopyPrevious () {
	${DOUPDATE} || return 0
	${EMULATEMODE} && return
	MakeTempFile
	( umask 002 ; cp -- "${EIXCACHE}" "${TMPFILE}" ) ||
		die "Could not copy database to temporary file ${TMPFILE}"
}

MovePrevious () {
	${USETEMP} && return
	(
		umask 002
		MyRunCommand "Copying old ${EIXCACHE} cache to ${EIXPREVIOUS}" \
		mv -f -- "${TMPFILE}" "${EIXPREVIOUS}"
	) || die "Could not move ${TMPFILE} to ${EIXPREVIOUS}"
	[ "${UID}" -eq 0 ] && \
		chown -- portage:portage "${EIXPREVIOUS}" >/dev/null 2>&1
	chmod -- 664 "${EIXPREVIOUS}" >/dev/null 2>&1
}

UpdateDiff () {
	${DOUPDATE} || return 0
	local D
	if ${USETEMP}; then
		${EMULATEMODE} && return
		D="${TMPFILE}"
	else
		D="${EIXPREVIOUS}"
		test -e "${D}" || RootTest
		${EMULATEMODE} && return
	fi
	if test "${EIXCACHE}" -nt "${TMPFILE}"; then
		MovePrevious
		einfo "update-eix was apparently already called in a hook"
	else
		MovePrevious
		MyRunCommand "Running update-eix" "update-eix" || \
			die "Failure while running update-eix"
	fi
	DoHook "${AFTER_UPDATE}"
	diff-eix -- "${D}" || die "Failed to diff against current cache"
}

MainSync () {
	CondUpdate
	ClearCache
	CopyPrevious
	ExecuteConfig 'sync'
	CallEmergeSync
	${SYNCHOOKS} && DoHook "${AFTER_SYNC}"
	CallSyncClient
	CallEmergeMetadata
	UpdateDiff
}

[ -n "${UID}" ] || UID="`id -u`"
if ${TEST_FOR_ROOT} && [ "${UID}" -ne 0 ]; then
# Emulate everything to check whether we have to be root:
	EMULATEMODE=true
	MainSync
fi
EMULATEMODE=false

${QUIET} && exec >/dev/null

MainSync

exit 0
