#! /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.22.5).

time_begin="`date +%s 2>/dev/null`" || time_begin=''

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

ReadVar local_varcache EPREFIX_PORTAGE_CACHE
ReadVar eixcache EIX_CACHEFILE
eixprevious="${eixcache}.previous"
ReadVar local_portage_configroot PORTAGE_CONFIGROOT
eixsyncconf="${local_portage_configroot}/etc/eix-sync.conf"

Usage() {
	n="${0##*/}"
	p='eix 0.22.5'
	eval_gettext 'Usage: ${n} [options]
Call layman/emerge --sync/--metadata and/or show updates. (${p})

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

The file ${eixsyncconf} and the eix (environment) variable EIX_SYNC_CONF
(which defaults to delayed substitution of EIX_SYNC_OPTS) determine for
which overlays layman is called, which default options are used, and they
can contain various hooks - "man eix" for details.

Be aware that e.g. the default EIX_SYNC_OPTS are evaluated so be sure to
quote them correctly and care about security risks.
Moreover, "--" 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 eix-diff ${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 eix-update (@ 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=flat 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).
-T   Do not measure time
-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.
-o OPT Add OPT to each eix-update command.
       This option can be used accumulatively.
-L OPT Add OPT to each call of layman.
       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=flat 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.'
	echo
	exitcode=${1:-1}
	exit ${exitcode}
}

DiffOnly() {
	test -e "${eixprevious}" || die "`eval_gettext \
		'No previous database ${eixprevious} exists'`"
	exec eix-diff -- "${eixprevious}"
	die "`gettext 'Could not execute eix-diff'`"
}

DoExecute() {
	${emulatemode} && return 0
	for curr_cmd
	do	eval "${curr_cmd}" || die "`eval_gettext \
			'Something went wrong with ${curr_cmd}'`"
	done
}

ParseConfigLine() {
	j="${2}"
	while :
	do	case "${j}" in
			' '*|'	'*)	j="${j#?}";;
			*' '|*'	')	j="${j%?}";;
			''|'#'*)	return;;
			*) break;;
		esac
	done
	case "${j}" in
		'!!'*)	[ "${1}" = 'opts' ] && DoExecute "${j#??}"; return;;
		'!'*)	j="${j#?}"; mycmd='!';;
		'@@'*)	j="${j#??}"; mycmd='@@';;
		'@'*)	j="${j#?}"; mycmd='@';;
		'~'*)	j="${j#?}"; mycmd='~';;
		'-'*)	config_opts="${config_opts} ${j}"; return;;
		*)	mycmd="layman";;
	esac
	[ "${1}" = 'opts' ] && return
	case "${mycmd}" in
		'@@')	Push after_update "${j}"; return;;
		'@')	Push after_sync "${j}"; return;;
		'~')	Push before_rsync "${j}"; return;;
		'!')	${nolayman} || DoExecute "${j}"; return;;
	esac
	${nolayman} && return
	RootTest
	eval "set -- ${layman_opt}"
	if [ "${j}" = '*' ]
	then	MyRunCommand "`gettext 'Syncing all portage overlays'`" \
			"${mycmd}" -S "${@}" \
			|| die "`eval_gettext '${mycmd} -S failed'`"
		return
	fi
	MyRunCommand "`eval_gettext 'Syncing portage overlay ${j}'`" \
		"${mycmd}" -s "${j}" "${@}" \
		|| die "`eval_gettext '${mycmd} -s ${j} failed'`"
}

test -r "${eixsyncconf}" && haveconf=: || haveconf=false
ReadVar eix_sync_conf EIX_SYNC_CONF
ExecuteConfig() {
	after_sync=''
	after_update=''
	before_rsync=''
	config_opts=''
	${haveconf} && while read confline
	do	ParseConfigLine "${1}" "${confline}"
	done <"${eixsyncconf}"
	while read confline
	do	ParseConfigLine "${1}" "${confline}"
	done <<END_EIX_SYNC_CONF
${eix_sync_conf}
END_EIX_SYNC_CONF
}


# Get options from cli

DefaultOpts() {
	Push -c emergecmd 'emerge' '--sync'
	Push -c updatecmd 'eix-update'
	Push -c layman_opt
	diffonly=false
	clearcache=false
	nolayman=false
	nohooks=false
	quiet=false
	usetemp=false
	measure_time=:
	metadata=false
	skip_sync=false
	server=''
	client=''
	doupdate=:
	synchooks=:
	test_for_root=:
}

DefaultOpts
ExecuteConfig 'opts'

eval "Push -c opt ${config_opts}"
Push opt "${@}"
eval "set -- ${opt}"
OPTIND=1
while getopts "ids:2:Uul@SmMNtTqwWL:c:C:o:rRe?hH" opt
do	case "${opt}" in
		i) DefaultOpts;;
		d) diffonly=:;;
		s) server="${OPTARG}";;
		2) client="${OPTARG}";;
		U) doupdate=false;;
		u) nolayman=:; nohooks=:; skip_sync=:; server='';;
		l) nolayman=:;;
		@) nohooks=:;;
		S) synchooks=false;;
		m) skip_sync=:; metadata=:;;
		M) metadata=:;;
		N) metadata=false;;
		t) usetemp=:;;
		T) measure_time=false;;
		q) quiet=:;;
		L) Push layman_opt "${OPTARG}";;
		w) Push -c emergecmd 'emerge-webrsync';;
		W) Push -c emergecmd 'emerge-delta-webrsync';;
		c) Push -c emergecmd "${OPTARG}";;
		C) Push emergecmd "${OPTARG}";;
		o) Push updatecmd "${OPTARG}";;
		r) clearcache=:;;
		R) clearcache=false;;
		e) test_for_root=false;;
		*) Usage 0;;
	esac
done
opt=''
${diffonly} && DiffOnly
[ -z "${server}" ] || skip_sync=:

${measure_time} || time_begin=''
measure_time=false
case "${time_begin}" in
	*[0-9]*) [ "${time_begin}" -gt 99 ] && (
			t=`Expr "${time_begin}" - 99 2>/dev/null` || t=0
			[ "${t}" -gt 0 ] && [ "${time_begin}" -gt "${t}" ]
		) >/dev/null 2>&1 && measure_time=:;;
esac

MyRun() {
	if [ "${1}" = '-t' ]
	then	if ${measure_time}
		then	timevar="time_${2}"
			shift 2
			begt="`date '+%s' 2>/dev/null`" || measure_time=false
			"${@}"
			runstat=${?}
			${measure_time} && endt="`date +%s 2>/dev/null`" \
				|| measure_time=false
			${measure_time} && \
				eval "${timevar}"'=`Expr "${endt}" - "${begt}"`'
			return ${runstat}
		fi
		shift 2
	fi
	"${@}"
}

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

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

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

time_iupdate=''
time_sync=''
time_client=''
time_metadata=''
time_update=''
time_diff=''
PrintingTimes() {
	[ -n "${1}" ] && [ "${1}" -gt 0 ] && \
		printf "`gettext '%6d seconds for %s\n'`" "${1}" "${2}"
}
PrintTimes() {
	${measure_time} || return 0
	measure_time=false
	einfo "`gettext 'Time statistics:'`"
	PrintingTimes "${time_iupdate}"  "`gettext 'initial eix-update'`"
	PrintingTimes "${time_sync}"     "`gettext 'syncing'`"
	PrintingTimes "${time_client}"   "`gettext 'client syncing'`"
	PrintingTimes "${time_metadata}" "`gettext 'metadata update'`"
	PrintingTimes "${time_update}"   "`gettext 'eix-update'`"
	PrintingTimes "${time_diff}"     "`gettext 'eix-diff'`"
	ftime="`date '+%s' 2>/dev/null`" || return
	ftime=`Expr "${ftime}" - "${time_begin}" 2>/dev/null` && \
		printf "`gettext '%6d seconds total\n'`" "${ftime}"
}

tmpfile=''
exitcode=0
ExitAll() {
	trap : EXIT HUP INT TERM
	[ -z "${tmpfile}" ] || rm -f -- "${tmpfile}"
	tmpfile=''
	trap - EXIT HUP INT TERM
	PrintTimes
	exit ${exitcode}
}
${measure_time} && trap ExitAll EXIT HUP INT TERM

MakeTempFile() {
	AssignTemp tmpfile
	trap ExitAll 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
	portage_rsync_opts="`portageq envvar PORTAGE_RSYNC_OPTS`"
	portage_rsync_extra_opts="`portageq envvar PORTAGE_RSYNC_EXTRA_OPTS`"
	eval "set -- ${before_rsync}"
	for curr_cmd
	do	c=`eval "${curr_cmd}"` || \
			die "`eval_gettext '${curr_cmd} failed'`"
		eval "${c}"
	done
	rsync_opts="${portage_rsync_opts} ${portage_rsync_extra_opts}"
	preprsync=:
}

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

CallEmergeSync() {
	if [ -n "${server}" ]
	then	${emulatemode} && return
		PrepRsync "${server}"
		MyRunCommand "rsyncing from ${hostdir}" -t sync \
		rsync ${rsync_opts} "${hostdir}" "${local_portdir}" || \
			die "`eval_gettext 'Could not rsync from ${hostdir}'`"
		return
	fi
	${skip_sync} && return
	RootTest
	eval "set -- ${emergecmd}"
	runarg="${*}"
	MyRunCommand "`eval_gettext 'Running ${runarg}'`" -t sync "${@}" \
		|| die "`eval_gettext '${runarg} failed'`"
}

CallSyncClient() {
	${emulatemode} && return
	[ -z "${client}" ] && return
	PrepRsync "${client}"
	MyRunCommand "`eval_gettext 'rsyncing to ${hostdir}'`" -t client \
		rsync ${rsync_opts} "${local_portdir}" "${hostdir}" || \
			die "`eval_gettext 'Could not rsync to ${hostdir}'`"
}

TESTRESULT=''
CallEmergeMetadata() {
	${doupdate} && ${metadata} || return 0
	RootTest
	MyRunCommand "`gettext 'Running emerge --metadata'`" -t metadata
		emerge --metadata || \
		die "`gettext 'emerge --metadata failed'`"
}

CondUpdate() {
	if test ! -f "${eixcache}"
	then	RootTest
		eval "set -- ${updatecmd}"
		MyRunCommand "`gettext \
			'eix-cache does not exist. Running eix-update!'`" \
			-t iupdate "${@}" || \
				die "`gettext 'eix-update failed'`"
		return
	fi
	${emulatemode} && return
	eix --is-current || {
		eval "set -- ${updatecmd}"
		MyRunCommand "`gettext \
			'eix-cache format has changed. Running eix-update!'`" \
			-t iupdate "${@}" || \
				die "`gettext 'eix-update failed'`"
	}
}

CopyPrevious() {
	${doupdate} || return 0
	${emulatemode} && return
	MakeTempFile
	( umask 002 ; cp -- "${eixcache}" "${tmpfile}" ) || \
		die "`eval_gettext \
		'Could not copy database to temporary file ${tmpfile}'`"
}

MovePrevious() {
	${usetemp} && return
	(
		umask 002
		MyRunCommand "`eval_gettext \
			'Copying old ${eixcache} cache to ${eixprevious}'`" \
		mv -f -- "${tmpfile}" "${eixprevious}"
	) || \
	die "`eval_gettext '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
	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 "`gettext \
			'eix-update was apparently already called in a hook'`"
	else	MovePrevious
		eval "set -- ${updatecmd}"
		MyRunCommand "`gettext 'Running eix-update'`" \
			-t update "${@}" || \
			die "`gettext 'Failure while running eix-update'`"
	fi
	DoHook "${after_update}"
	MyRunCommand "`gettext 'Calling eix-diff'`" \
		-t diff eix-diff -- "${d}" || \
		die "`gettext 'Failed to diff against current cache'`"
	PrintTimes
}

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=:
	MainSync
fi
emulatemode=false

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

MainSync

exit 0
