#!/bin/bash
#
#   srcpac - A tool to rebuild official Arch Linux packages from source
#
#   Copyright (C) 2004-2009 Jason Chu <jason@archlinux.org>
#   Copyright (C) 2009-2010 Andrea Scarpino <andrea@archlinux.org>
#
# 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/>.
#

ver=0.8

declare -a args

[ -f /etc/abs.conf ] && source /etc/abs.conf
[ -f /etc/makepkg.conf ] && source /etc/makepkg.conf
[ -f ~/.makepkg.conf ] && source ~/.makepkg.conf

usage()
{
	echo "usage: $(basename $0) <operation> [...]"
	echo "operations:"
	echo "        $(basename $0) {-h --help}"
	echo "        $(basename $0) {-V --version}"
	echo "        $(basename $0) {-Q --query}   [options] [<package(s)>]"
	echo "        $(basename $0) {-R --remove}  [options] <package(s)>"
	echo "        $(basename $0) {-S --sync}    [options] [package(s)]"
	echo "        $(basename $0) {-U --upgrade} [options] <file(s)>"
    echo "$(basename $0) options are based on pacman, so check the pacman man page"
	echo "$(basename $0) adds some option to -S:"
	echo "options:"
	echo "  -b, builds the targets from source"
	echo "  -o, applies config changes and displays the PKGBUILD in your pager app without building"
	
	exit 0
}

version()
{
	echo ""
	echo "                       $(basename $0) v${ver}"
	echo "                       Copyright (C) 2004 Jason Chu <jason@archlinux.org>"
	echo "                       Copyright (C) 2010 Andrea Scarpino <andrea@archlinux.org>"
	echo
	echo "                       This program may be freely redistributed under"
	echo "                       the terms of the GNU General Public License."
	echo ""

	exit 0
}

##
# Applies changes of source build packages to PKGUILDs,
# if -o option is set we only display the PKGBUILD in a pager
##
apply_config()
{
	conf=/etc/srcpac.d/${1}
	if [ -f ${conf} ]; then
		if [ "${3}" == 'noreplace' ]; then
			[ -z ${PAGER} ] && PAGER=less
			sed -f ${conf} ${2}/PKGBUILD | ${PAGER}
		else
			sed -i -f ${conf} ${2}/PKGBUILD
		fi
	fi
}

##
# Searches the ABS tree for a possible package candidates
##
get_candidates()
{
	candidates=$(find ${ABSROOT} -type d -name ${1})
	if [ "${candidates}" = "" ]; then
		echo "Error: Could not find \"${1}\" under ${ABSROOT}"
		exit 1
	fi
}

get_pkgname()
{
	local tmp

	tmp=${1##*/}
	tmp=${tmp%-$CARCH*}
	tmp=${tmp%-any*}
	echo ${tmp%-*-*}
}

refresh()
{
	abs
	pacman -Sy
	if [ ${#args[@]} -eq 0 -a $SYSUPGRADE -eq 0 ]; then
		exit 0
	fi
}

build_packages()
{
	action="build"
	if [ "$1" = "install" ]; then
		action="install"
	fi

	shift

	while [ $# -ne 0 ]; do
		pkg=$1
		get_candidates $pkg
		success=0

		for pkgdir in ${candidates}; do
			if [ -f ${pkgdir}/PKGBUILD ]; then
				builddir=/var/srcpac${pkgdir/${ABSROOT}//}

				# seperate out our makepkg flags
				MAKEPKGOPTS="-c -s -r -f"
				
				# create the build dir and apply configuration
				[[ -d ${builddir} ]] && rm -rf ${builddir}
				mkdir -p ${builddir}
				cp ${pkgdir}/* ${builddir}
				apply_config ${pkg} ${builddir}

				cd ${builddir}

				if [ -z "${SUDO_USER}" ]; then
					chown -R nobody ${builddir}
					makepkg ${MAKEPKGOPTS} --asroot
				else
					chown -R ${SUDO_USER} ${builddir}
					sudo -u ${SUDO_USER} makepkg ${MAKEPKGOPTS}
				fi

				ret=$?
				if [ $ret -ne 0 ]; then
					break
				fi

				#Some package could set a different PKGEXT
				PKGEXT=$(grep '^PKGEXT' PKGBUILD | cut -d= -f2 | sed s/\'//g)
				[ -z ${PKGEXT} ] && PKGEXT=$(grep '^PKGEXT=' ~/.makepkg.conf 2>/dev/null | cut -d= -f2 | sed s/\'//g)
				[ -z ${PKGEXT} ] && PKGEXT=$(grep '^PKGEXT=' /etc/makepkg.conf 2>/dev/null | cut -d= -f2 | sed s/\'//g)

				CACHEDIR=$(grep '^#CacheDir' /etc/pacman.conf | cut -d= -f2 | sed s/#//) 
				cp ${builddir}/*${PKGEXT} ${CACHEDIR}

				if [ "${action}" = "install" ]; then
					PACARGS="-U"
					[ $NODEPS -eq 1 ] && PACARGS="${PACARGS}d"
					[ $FORCE -eq 1 ] && PACARGS="${PACARGS}f"
					[ $ROOT -eq 1 ] && PACARGS="${PACARGS} -r $NEWROOT"
					[ $ASDEPS -eq 1 ] && PACARGS="${PACARGS} --asdeps"
					[ $ASEXPLICIT -eq 1 ] && PACARGS="${PACARGS} --asexplicit"
					pacman $PACARGS ${builddir}/*${PKGEXT}
				fi

				rm -rf ${builddir}
			fi
		done

		if [ $ret -ne 0 ]; then
			echo "Error: Failed to build \"${pkg}\""
			exit 1
		fi

		if [ "${action}" = "install" ]; then
			if [ ! -d /var/lib/srcpac ]; then
				mkdir /var/lib/srcpac
			fi
			
			touch /var/lib/srcpac/${pkg}
		fi
		shift
	done
}

function check_args()
{
	# Options
	MAJOR=""
	ASDEPS=0
	ASEXPLICIT=0
	NODEPS=0
	FORCE=0
	INFO=0
	SYSUPGRADE=0
	DOWNLOAD=0
	REFRESH=0
	IGNORE=0
	IGNOREPKG=""
	NOCONFIRM=0
	ROOT=0
	NEWROOT=""
	BUILD=0
	ONLYCONF=0

	ARGLIST=$@
	ARGSANS=""

	while [ "$#" -ne "0" ]; do
		case $1 in
			--help) usage ;;
			--version) version ;;
			--query)
				MAJOR="query"
				ARGSANS="$ARGSANS $1"
				;;
			--remove)
				MAJOR="remove"
				ARGSANS="$ARGSANS $1"
				;;
			--sync)
				MAJOR="sync"
				ARGSANS="$ARGSANS $1"
				;;
			--upgrade)
				MAJOR="upgrade"
				ARGSANS="$ARGSANS $1"
				;;
			--asdeps)
				ASDEPS=1
				ARGSANS="$ARGSANS $1"
				;;
			--asexplicit)
				ASEXPLICIT=1
				ARGSANS="$ARGSANS $1"
				;;
			--nodeps)
				NODEPS=1
				ARGSANS="$ARGSANS $1"
				;;
			--force)
				FORCE=1
				ARGSANS="$ARGSANS $1"
				;;
			--info)
				INFO=1
				ARGSANS="$ARGSANS $1"
				;;
			--sysupgrade)
				SYSUPGRADE=1
				ARGSANS="$ARGSANS $1"
				;;
			--downloadonly)
				DOWNLOAD=1
				ARGSANS="$ARGSANS $1"
				;;
			--refresh)
				REFRESH=1
				ARGSANS="$ARGSANS $1"
				;;
			--ignore)
				IGNORE=1
				IGNOREPKG="$IGNOREPKG $2"
				ARGSANS="$ARGSANS $1 $2"
				;;
			--noconfirm)
				NOCONFIRM=1
				ARGSANS="$ARGSANS $1"
				;;
			--root)
				ROOT=1
				NEWROOT="$2"
				ARGSANS="$ARGSANS $1 $2"
				shift
				;;
			--build)
				BUILD=1
				ARGSANS="$ARGSANS $1"
				;;
			--onlyconf)
				ONLYCONF=1
				ARGSANS="$ARGSANS $1"
				;;
			--*)
				ARGSANS="$ARGSANS $1"
				;;
			-*)
				ARGSANS="$ARGSANS $1"
				if [ $(echo $1 | grep r) ]; then
					OPTIONAL=$2
				fi
				OPTIND=1
				while getopts ":VQRSUdfiubyr:ow" opt $1 $OPTIONAL; do
					case $opt in
						V) version ;;
						Q) MAJOR="query" ;;
						R) MAJOR="remove" ;;
						S) MAJOR="sync" ;;
						U) MAJOR="upgrade" ;;
						d) NODEPS=1 ;;
						f) FORCE=1 ;;
						i) INFO=1 ;;
						u) SYSUPGRADE=1 ;;
						w) DOWNLOAD=1 ;;
						y) REFRESH=1 ;;
						b) BUILD=1 ;;
						o) ONLYCONF=1 ;;
						r) ROOT=1
						   NEWROOT="${OPTARG}"
						   ;;
					esac
				done
				;;
			*)
				args[${#args[@]}]=$1
				;;
		esac
		shift
	done
}

function main()
{
	check_args $@

	if [ "${MAJOR}" = "" ]; then
		usage
	fi
	
	if [ ${UID} -ne 0 -a -z "${SUDO_USER}" -a ${MAJOR} != "query" ]; then
		echo "Error: You need to use sudo or to be root"
		exit 1
	fi

	if [ "${ABSROOT}" = "" ]; then
		echo "Error: The ABSROOT environment variable is not defined."
		exit 1
	fi

	if [ "${MAJOR}" = "remove" -o "${MAJOR}" = "upgrade" ]; then
		pacman $ARGLIST
		
		if [ -d /var/lib/srcpac -a "${MAJOR}" = "remove" ]; then
			for pkg in ${args[@]}; do
				if [ -f /var/lib/srcpac/${pkg} ]; then
					echo -n "removing source reference ${pkg}... "
					rm /var/lib/srcpac/${pkg}
					echo "done"
				fi
			done
		fi
	fi

	if [ "${MAJOR}" = "query" ]; then
		for arg in ${args[@]}; do
			if [ $INFO -eq 1 -a -d /var/lib/srcpac ]; then
				pacman $ARGSANS $arg | head -n -1
			else
				echo "$(pacman -Q $arg)"
			fi

			[ $? -ne 0 ] && exit $?

			echo -n "Source         : "
			if [ -f /var/lib/srcpac/${arg} ]; then
				echo "Yes"
			else
				echo "No"
			fi
			[ $INFO -eq 1 ] && echo ""
		done
	fi

	if [ "${MAJOR}" = "sync" ]; then
		if [ $BUILD -eq 0 -a $ONLYCONF -ne 1 ]; then
			if [ $SYSUPGRADE -eq 1 -a -d /var/lib/srcpac ]; then
				[ $REFRESH -eq 1 ] && refresh

				ignorestr=""
				if [ $IGNORE -eq 1 ]; then
					ignorestr="--ignore $IGNOREPKG"
				fi
				output=$(pacman -Spu --noconfirm ${ignorestr})
				ret=$?

				if [ $ret -ne 0 ]; then
					echo $output
					exit $ret
				fi

				declare -a packages
				declare -a regpac
				for line in $output; do
					# ensure the string is a URL
					if [ $( echo "${line}" | grep -F '://' ) ]; then
						pkg=$(get_pkgname ${line})
						if [ -f /etc/srcpac.d/${pkg} ]; then
							packages[${#packages[@]}]=$pkg
						else
							regpac[${#regpac[@]}]=$pkg
						fi
					fi
				done

				PACARGS="-S"
				[ $NODEPS -eq 1 ] && PACARGS="${PACARGS}d"
				[ $FORCE -eq 1 ] && PACARGS="${PACARGS}f"
				[ $ROOT -eq 1 ] && PACARGS="${PACARGS} -r $NEWROOT"

				pacman $PACARGS ${regpac[*]} $ignorestr
			else
				if [ $REFRESH -eq 1 ]; then
					refresh
				fi
				pacman $ARGLIST
				exit 0
			fi
		else
			[ $REFRESH -eq 1 ] && refresh

			if [ $DOWNLOAD -eq 1 ]; then
				build_packages "build" ${args[@]}
				exit 0
			fi

			PACARGS="-Sp"
			if [ $SYSUPGRADE -eq 1 ]; then
				PACARGS="${PACARGS}u"
			else
				PACARGS="${PACARGS} ${args[*]}"
			fi

			output=$(pacman ${PACARGS} --noconfirm)
			if [ $? -ne 0 ]; then
				echo $output
				exit $?
			fi

			declare -a packages
			for line in $output; do
				# ensure the string is a URL
				if [ $( echo "${line}" | grep -F '://' ) ]; then
					pkg=$(get_pkgname ${line})
					local pkgver=$(LC_ALL=C pacman -Qi $pkg 2>/dev/null | grep Version | awk '{print $3}')
					[ -z ${pkgver} ] && pkgver=0
					local repopkgver=$(LC_ALL=C pacman -Si $pkg 2>/dev/null | grep Version | awk '{print $3}')
					[ -z ${repopkgver} ] && repopkgver=0
					if [ $(vercmp $repopkgver $pkgver) -eq 0 ]; then
						if [ ${NOCONFIRM} -eq 0 ]; then
							echo -n "$pkg is up to date, upgrade anyway? [Y/n]"
							read
							if [ "${REPLY}" == "n" -o "${REPLY}" == "N" ]; then
								continue
							fi
						fi
					fi
					packages[${#packages[@]}]=$pkg
				fi
			done

			if [ $ONLYCONF -eq 1 ]; then
				for pkg in ${packages[@]}; do
					get_candidates $pkg
					success=0
					for pkgdir in ${candidates}; do
						if [ -f ${pkgdir}/PKGBUILD ]; then
							# Look for config options and apply them
							apply_config $pkg $pkgdir noreplace
							success=1
							break
						fi
					done
					if [ ${success} -eq 0 ]; then
						echo "Error: Failed to find \"${pkg}\""
						exit 1
					fi
				done
				exit 0
			fi
		fi

		if [ ${#packages[@]} -eq 0 ]; then
			exit 0
		fi

		echo
		echo "Source Targets: ${packages[*]}"
		echo
		if [ ${NOCONFIRM} -eq 0 ]; then
			echo -n "Proceed? [Y/n]"
			read
			if [ "${REPLY}" != "y" -a "${REPLY}" != "Y" -a "${REPLY}" != "" ]; then
				exit 0
			fi
		fi
		
		build_packages "install" ${packages[@]}
	fi
}

main $@
