#!/bin/bash
#
# pacdiffviewer : manage/backup/clean/merge pac* files
#
# Copyright (c) 2008-2010 Julien MISCHKOWITZ <wain@archlinux.fr>
# Copyright (c) 2010-2014 tuxce <tuxce.net@gmail.com>
#
# 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 2 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/>.
#
export TEXTDOMAINDIR='/usr/share/locale'
export TEXTDOMAIN=yaourt

NAME='pacdiffviewer'
VERSION='1.9'

P_LOCATE=0
P_SEARCHDIR=(/etc/ /boot/)
P_SAVEDIR='/var/lib/yaourt/backupfiles'

. '/usr/lib/yaourt/util.sh'

ROOTDIR=
QUIET=0
[[ -d "$P_SAVEDIR" ]] && MERGE=1 || MERGE=0
tmp_file=$(mktemp)
cleanup_add rm "$tmp_file"

usage() {
	echo "$NAME $VERSION"
	[[ "$1" = "-v" ]] && exit 0
	echo -e "\t-c $(gettext 'Delete all *.pac* found')"
	echo -e "\t-b $(gettext 'Save all packages backup files for a later merge')"
	echo -e "\t-q $(gettext 'Use with backup to not output messages')"
	echo -e "\t-s $(gettext 'Sequential listing')"
	echo -e "\t-d $(gettext 'Display diff')"
	echo -e "\t-h $(gettext 'This help')"
	echo -e "\t-v $(gettext 'Show version')"
	exit 0
}

# Save files marked as backup in packages for a possible later merge
backup_files() {
	local _file _md5sum _version pkgname pkgver backupdir currentfile
	pkgquery -Qf '%n - %v\n%B' --delimiter '\n' |
	while read _file _md5sum _version
	do
		# no backups
		[[ "$_file" = "-" ]] && pkgname="" && continue
		# _md5sum = "-" -> on name/version line
		[[ "$_md5sum" = "-" ]] && pkgname="$_file" && pkgver="$_version" &&\
			 continue
		[[ ! -f "$ROOTDIR/$_file" ]] && continue
		backupdir="$P_SAVEDIR/$pkgname/$pkgname-$pkgver"
		[[ -f "$backupdir/$_file" ]] && continue
		currentfile="$_file.pacnew"
		[[ -f "$ROOTDIR/$currentfile" ]] || currentfile="$_file"
		if echo "$_md5sum  $ROOTDIR/$currentfile" |
			md5sum --status -c - &>/dev/null; then
			(( ! QUIET )) && echo "-> $(gettext 'saving') $ROOTDIR/$currentfile"
			mkdir -p "$(dirname "$backupdir/$_file")" || return 1
			cp -a "$ROOTDIR/$currentfile" "$backupdir/$_file" || return 1
		fi
	done
}

# Create pacnew/pacsave/pacorig db
# PACOLD=()	-> all pacnew/pacsave files which package is not installed
# PACNEW=()	-> rest of pacnew files
# PACSAVE=() -> rest of pacsave files
# PACORIG=() -> pacorig files
create_db() {
	unset PACOLD PACNEW PACSAVE
	local pacfiles _file ext
	IFS=$'\n'
	if (( P_LOCATE )); then
		pacfiles=($(locate -eb "*.pacorig" "*.pacsave" "*.pacnew"))
	else
		pacfiles=($(find "${P_SEARCHDIR[@]}" \( -name "*.pacorig" \
			-o -name "*.pacsave" -o -name "*.pacnew" \)))
	fi
	unset IFS
	for _file in "${pacfiles[@]}"; do
		for ext in .pacnew .pacsave .pacorig; do
			[[ ${_file%$ext} != $_file ]] && break
		done
		[[ ! -f "${_file%$ext}" ]] && PACOLD+=("${_file}") &&	continue
		[[ "$ext" = ".pacnew" ]] && PACNEW+=("${_file}")
		[[ "$ext" = ".pacsave" ]] && PACSAVE+=("${_file}")
		[[ "$ext" = ".pacorig" ]] && PACORIG+=("${_file}")
	done
	echo "${#PACORIG[@]} .pacorig $(gettext 'found')"
	echo "${#PACNEW[@]} .pacnew $(gettext 'found')"
	echo "${#PACSAVE[@]} .pacsave $(gettext 'found')"
	echo "${#PACOLD[@]} $(gettext 'files are orphans')"
	echo
}

# Return previous version file if we had backup it
# usage: previous_version ($file)
# return previous file or ""
previous_version() {
	[[ $1 ]] || return
	local file=$1 pkgname pkgver
	read pkgname pkgver < <(pacman_parse -Qo "$file" |
		awk '{print $(NF-1)" "$NF}' 2>/dev/null)
	[[ $pkgname ]] || return
	[[ -d "$P_SAVEDIR/$pkgname/" ]] || return
	pushd "$P_SAVEDIR/$pkgname/" &> /dev/null
	local pkgver_prev="" _rep
	for _rep in $(ls -Ad *-*); do
		[[ "$_rep" = "$pkgname-$pkgver" || ! -f "${_rep}${file}" ||
			$(vercmp $pkgver ${_rep#$pkgname-}) -lt 0 ]] && continue
		if [[ -z "$pkgver_prev" || \
			$(vercmp $pkgver_prev ${_rep#$pkgname-}) -lt 0 ]]; then
			pkgver_prev="${_rep#$pkgname-}"
		fi
	done
	popd &> /dev/null
	[[ ! "$pkgver_prev" ]] && return
	echo "$P_SAVEDIR/$pkgname/$pkgname-$pkgver_prev/$file"
}

# Search if we can merge a .pac{save,new} with a current file
# usage: is_mergeable ($file)
# return: 0 on success
is_mergeable() {
	(( ! MERGE )) && return 1
	[[ $1 ]] || return 1
	local file=$1
	local ext=${2:-.pacnew}
	local file_prev=$(previous_version "$file")
	[[ $file_prev ]] || return 1
	diff -aBbu "$file_prev" "${file}${ext}" > $tmp_file
	(( $? != 1 )) && return 1
	patch --dry-run -sp0 "$file" -i "$tmp_file" &> /dev/null || return 1
	return 0
}


# Remove .pac* files
suppress() {
	local pacfiles=(${PACOLD[@]})
	[[ "$1" = "all" ]] && \
		pacfiles+=("${PACNEW[@]}" "${PACSAVE[@]}" "${PACORIG[@]}")
	(( ! ${#pacfiles[@]} )) && return
	msg "$(gettext 'The following files have no original version.')"
	echo_wrap 4 "${pacfiles[*]}"
	echo
	prompt "$(gettext 'Do you want to delete these files ?') $(\
		yes_no 2) $(gettext '(S: no confirm)')"
	local answer=$(userinput "YNS" "N")
	local _opt=""
	[[ "$answer" = "Y" ]] && _opt="-i"
	[[ "$answer" = "Y" ]] || [[ "$answer" = "S" ]] && rm $_opt "${pacfiles[@]}"
}

# Manage .pac* file
# Called by manage()
manage_file() {
	local ext=$1; shift
	local _file=$1
	local same loop=0
	while true; do
		local _msg="$ext: ${_file%$ext}"
		local _prompt="Action: [E]dit, [R]eplace, [S]uppress,"
		local _prompt_action="ERSCA"
		diff -abBu "${_file%$ext}" "$_file" &> /dev/null && \
			_msg+=" $(gettext '**same file**')" && same=1 || same=0
		if is_mergeable "${_file%$ext}" "$ext"; then
			_prompt+=" [M]erge,"
			_prompt_action+="M"
			_msg+=" $(gettext '**automerge**')"
		fi
		((DIFF && !same && !loop++)) && diff -abBu "${_file%$ext}" "$_file"
		msg "$_msg"
		# gettext "Action: [E]dit, [R]eplace, [S]uppress, [C]ontinue (default), [A]bort ?"
		# gettext "Action: [E]dit, [R]eplace, [S]uppress, [M]erge, [C]ontinue (default), [A]bort ?"
		# gettext "ERSCA"
		# gettext "ERSCAM"
		prompt "$(gettext "$_prompt [C]ontinue (default), [A]bort ?")"
		local answer=$(userinput "$_prompt_action" "C")
		case "$answer" in
			A) return 2;;	# break manage() loop
			C) break;;
			E) $DIFFEDITCMD "${_file%$ext}" "$_file" ;;
			R) mv "$_file" "${_file%$ext}"; return 1;;
			S) rm "$_file"; return 1 ;;
			M)	echo
				msg "$(gettext 'Patch: ')"
				cat "$tmp_file"
				echo
				prompt2 "$(gettext 'Apply ?') $(yes_no 1)"
				useragrees || continue;
				patch -sp0 "${_file%$ext}" -i "$tmp_file"
				(( $? )) && error "$(gettext 'patch returned an error!')"
				rm "$_file"
				break;;
		esac
	done
}

# Manage list of .pac* files
manage() {
	echo
	local ext=$1; shift
	[[ $@ ]] || return
	local pacdata pacfiles i _line
	readarray -t pacdata < <(stat -c "%Y %n" "$@" |
		sort |
		awk '{printf ("%s %s\n", strftime("%x %X",$1), substr ($0, length($1)+1))}')
	readarray -t pacfiles < <(stat -c "%Y %n" "$@" | sort)
	pacfiles=("${pacfiles[@]#* }")
	if (( ! SEQUENTIAL )); then
		while true; do
			echo
			list_select "${pacdata[@]}"
			prompt2 "$(gettext 'Enter n° : ')"
			read -e i
			(( ! i )) && break
			(( --i>=0 && i < ${#pacdata[@]} )) || continue
			manage_file "$ext" "${pacfiles[$i]}"; local ret=$?
			if (( ret == 1 )); then
				unset pacdata[$i]; pacdata=("${pacdata[@]}")
				unset pacfiles[$i]; pacfiles=("${pacfiles[@]}")
				(( ${#pacdata[@]} )) || break
			elif (( ret == 2 )); then
				break
			fi
		done
	else
		i=0
		for _line in "${pacdata[@]}"; do
			(( i++ ))
			msg "$i/${#pacdata[@]}:"
			manage_file "$ext" "${pacfiles[$i-1]}"
		done
	fi
}

init_color
action=""
explode_args "$@"
set -- "${OPTS[@]}"
unset OPTS
while [[ $1 ]]; do
	case "$1" in
		-q) QUIET=1;;
		-s) SEQUENTIAL=1;;
		-d) DIFF=1;;
		--backup|-b) action=backup;;
		--confirm) NOCONFIRM=0;;
		-c)	action=clean;;
		-*) usage $1;;
	esac
	shift
done

case "$action" in
	backup)	backup_files;;
	clean)	create_db; suppress all;;
	*)	create_db
		manage .pacsave "${PACSAVE[@]}"
		manage .pacnew "${PACNEW[@]}"
		manage .pacorig "${PACORIG[@]}"
		suppress
		;;
esac
# vim: set ts=4 sw=4 noet:
