#!/hint/bash

# Some PKGBUILDs need CARCH to be set
CARCH="x86_64"

# Useful functions
UMASK=""
set_umask () {
	[ "$UMASK" == "" ] && UMASK="$(umask)"
	export UMASK
	umask 002
}

restore_umask () {
	umask "$UMASK" >/dev/null
}

# just like mv -f, but we touch the file and then copy the content so
# default ACLs in the target dir will be applied
mv_acl() {
	rm -f "$2"
	touch "$2"
	cat "$1" >"$2" || return 1
	rm -f "$1"
}

# set up general environment
WORKDIR=$(mktemp -dt "${0##*/}.XXXXXXXXXX")
LOCKS=()
REPO_MODIFIED=0

# check if messages are to be printed using color
unset ALL_OFF BOLD BLUE GREEN RED YELLOW
if [[ -t 2 ]]; then
	ALL_OFF="$(tput sgr0)"
	BOLD="$(tput bold)"
	BLUE="${BOLD}$(tput setaf 4)"
	GREEN="${BOLD}$(tput setaf 2)"
	RED="${BOLD}$(tput setaf 1)"
	YELLOW="${BOLD}$(tput setaf 3)"
fi
readonly ALL_OFF BOLD BLUE GREEN RED YELLOW

plain() {
	local mesg=$1; shift
	printf "${BOLD}    ${mesg}${ALL_OFF}\n" "$@"
}

msg() {
	local mesg=$1; shift
	printf "${GREEN}==>${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@"
}

msg2() {
	local mesg=$1; shift
	printf "${BLUE}  ->${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@"
}

warning() {
	local mesg=$1; shift
	printf "${YELLOW}==> WARNING:${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

error() {
	local mesg=$1; shift
	printf "${RED}==> ERROR${ALL_OFF}${BOLD} ${mesg}${ALL_OFF}\n" "$@" >&2
}

##
#  usage : in_array( $needle, $haystack )
# return : 0 - found
#          1 - not found
##
in_array() {
	local needle=$1; shift
	[[ -z $1 ]] && return 1 # Not Found
	local item
	for item in "$@"; do
		[[ $item = "$needle" ]] && return 0 # Found
	done
	return 1 # Not Found
}

##
#  usage : get_full_version( $epoch, $pkgver, $pkgrel )
# return : full version spec, including epoch (if necessary), pkgver, pkgrel
##
get_full_version() {
	if [[ $1 -eq 0 ]]; then
		# zero epoch case, don't include it in version
		echo "$2-$3"
	else
		echo "$1:$2-$3"
	fi
}

script_lock() {
	local LOCKDIR="$TMPDIR/.scriptlock.${0##*/}"
	if ! mkdir "$LOCKDIR" >/dev/null 2>&1 ; then
		local _owner="$(/usr/bin/stat -c %U "$LOCKDIR")"
		error "Script %s is already locked by %s." "${0##*/}" "$_owner"
		exit 1
	else
		set_umask
		return 0
	fi
}

script_unlock() {
	local LOCKDIR="$TMPDIR/.scriptlock.${0##*/}"
	if [ ! -d "$LOCKDIR" ]; then
		warning "Script %s was not locked!" "${0##*/}"
		restore_umask
		return 1
	else
		rmdir "$LOCKDIR"
		restore_umask
		return 0
	fi
}

cleanup() {
	local l
	local repo
	local arch

	trap - EXIT INT QUIT TERM
	for l in "${LOCKS[@]}"; do
		repo=${l%.*}
		arch=${l#*.}
		if [ -d "$TMPDIR/.repolock.$repo.$arch" ]; then
			msg "Removing left over lock from [%s] (%s)" "${repo}" "${arch}"
			repo_unlock "$repo" "$arch"
		fi
	done
	if [ -d "$TMPDIR/.scriptlock.${0##*/}" ]; then
		msg "Removing left over lock from %s" "${0##*/}"
		script_unlock
	fi
	rm -rf "$WORKDIR"

	if (( REPO_MODIFIED )); then
		date +%s > "${FTP_BASE}/lastupdate"
		date -u +%s > "${FTP_BASE}/lastsync"
	fi

	[ "$1" ] && exit "$1"
}

abort() {
	msg 'Aborting...'
	cleanup 0
}

die() {
	error "$@"
	cleanup 1
}

trap abort INT QUIT TERM HUP
trap cleanup EXIT


#repo_lock <repo-name> <arch> [timeout]
repo_lock () {
	local LOCKDIR="$TMPDIR/.repolock.$1.$2"
	local DBLOCKFILE="${FTP_BASE}/${1}/os/${2}/${1}${DBEXT}.lck"
	local FILESLOCKFILE="${FTP_BASE}/${1}/os/${2}/${1}${FILESEXT}.lck"
	local _count
	local _trial
	local _timeout
	local _lockblock
	local _owner

	# This is the lock file used by repo-add and repo-remove
	if [ -f "${DBLOCKFILE}" ]; then
		error "Repo [%s] (%s) is already locked by repo-{add,remove} process %s" "$1" "$2" "$(<"$DBLOCKFILE")"
		return 1
	fi
	if [ -f "${FILESLOCKFILE}" ]; then
		error "Repo [%s] (%s) is already locked by repo-{add,remove} process %s" "$1" "$2" "$(<"$FILESLOCKFILE")"
		return 1
	fi

	if [ $# -eq 2 ]; then
		_lockblock=true
		_trial=0
	elif [ $# -eq 3 ]; then
		_lockblock=false
		_timeout=$3
		let _trial=$_timeout/$LOCK_DELAY
	fi

	_count=0
	while [ "$_count" -le "$_trial" ] || "$_lockblock" ; do
		if ! mkdir "$LOCKDIR" >/dev/null 2>&1 ; then
			_owner="$(/usr/bin/stat -c %U "$LOCKDIR")"
			warning "Repo [%s] (%s) is already locked by %s." "${1}" "${2}" "$_owner"
			msg2 "Retrying in %d seconds..." "$LOCK_DELAY"
		else
			LOCKS+=("$1.$2")
			set_umask
			return 0
		fi
		sleep "$LOCK_DELAY"
		let _count=$_count+1
	done

	error "Repo [%s] (%s) is already locked by %s. Giving up!" "${1}" "${2}" "$_owner"
	return 1
}

repo_unlock () { #repo_unlock <repo-name> <arch>
	local LOCKDIR="$TMPDIR/.repolock.$1.$2"
	if [ ! -d "$LOCKDIR" ]; then
		warning "Repo lock [%s] (%s) was not locked!" "${1}" "${2}"
		restore_umask
		return 1
	else
		rmdir "$LOCKDIR"
		restore_umask
		return 0
	fi
}

# usage: _grep_pkginfo pkgfile pattern
_grep_pkginfo() {
	local _ret

	_ret="$(/usr/bin/bsdtar -xOqf "$1" .PKGINFO | grep -m 1 "^${2} = ")"
	echo "${_ret#${2} = }"
}


# Get the package base or name as fallback
getpkgbase() {
	local _base

	_base="$(_grep_pkginfo "$1" "pkgbase")"
	if [ -z "$_base" ]; then
		getpkgname "$1"
	else
		echo "$_base"
	fi
}

issplitpkg() {
	local _base

	_base="$(_grep_pkginfo "$1" "pkgbase")"
	if [ -z "$_base" ]; then
		return 1
	else
		return 0
	fi
}

# Get the package name
getpkgname() {
	local _name

	_name="$(_grep_pkginfo "$1" "pkgname")"
	if [ -z "$_name" ]; then
		error "Package '%s' has no pkgname in the PKGINFO. Fail!" "$1"
		exit 1
	fi

	echo "$_name"
}

# Get the pkgver-pkgrel of this package
getpkgver() {
	local _ver

	_ver="$(_grep_pkginfo "$1" "pkgver")"
	if [ -z "$_ver" ]; then
		error "Package '%s' has no pkgver in the PKGINFO. Fail!" "$1"
		exit 1
	fi

	echo "$_ver"
}

getpkgarch() {
	local _ver

	_ver="$(_grep_pkginfo "$1" "arch")"
	if [ -z "$_ver" ]; then
		error "Package '%s' has no arch in the PKGINFO. Fail!" "$1"
		exit 1
	fi

	echo "$_ver"
}

getpkgfile() {
	if [[ ${#} -ne 1 ]]; then
		error 'No canonical package found!'
		exit 1
	elif [ ! -f "${1}" ]; then
		error "Package %s not found!" "${1}"
		exit 1
	elif "${REQUIRE_SIGNATURE}" && [ ! -f "${1}.sig" ]; then
		error "Package signature %s not found!" "${1}.sig"
		exit 1
	fi

	echo "${1}"
}

getpkgfiles() {
	local f
	if [ ! -z "$(printf '%s\n' "${@%\.*}" | sort | uniq -D)" ]; then
		error 'Duplicate packages found!'
		exit 1
	fi

	for f in "${@}"; do
		if  [ ! -f "${f}" ]; then
			error "Package %s not found!" "${f}"
			exit 1
		elif "${REQUIRE_SIGNATURE}" && [ ! -f "${f}.sig" ]; then
			error "Package signature %s not found!" "${f}.sig"
			exit 1
		fi
	done

	echo "${@}"
}

check_pkgfile() {
	local pkgfile=$1

	local pkgname="$(getpkgname "${pkgfile}")"
	[ $? -ge 1 ] && return 1
	local pkgver="$(getpkgver "${pkgfile}")"
	[ $? -ge 1 ] && return 1
	local pkgarch="$(getpkgarch "${pkgfile}")"
	[ $? -ge 1 ] && return 1

	in_array "${pkgarch}" "${ARCHES[@]}" 'any' || return 1

	if echo "${pkgfile##*/}" | grep -q "${pkgname}-${pkgver}-${pkgarch}"; then
		return 0
	else
		return 1
	fi
}

check_pkgxbs() {
	local pkgfile="${1}"
	local _pkgbase="$(getpkgbase "${pkgfile}")"
	[ $? -ge 1 ] && return 1
	local _pkgname="$(getpkgname "${pkgfile}")"
	[ $? -ge 1 ] && return 1
	local _pkgver="$(getpkgver "${pkgfile}")"
	[ $? -ge 1 ] && return 1
	local _pkgarch="$(getpkgarch "${pkgfile}")"
	[ $? -ge 1 ] && return 1
	local repo="${2}"

	in_array "${repo}" "${PKGREPOS[@]}" || return 1

	local xbsver="$(. "$(xbs releasepath "${_pkgbase}" "${repo}" "${_pkgarch}")/PKGBUILD"; get_full_version "${_pkgname}")"
	[ "${xbsver}" == "${_pkgver}" ] || return 1

	local xbsnames=($(. "$(xbs releasepath "${_pkgbase}" "${repo}" "${_pkgarch}")/PKGBUILD"; echo "${pkgname[@]}"))
	in_array "${_pkgname}" "${xbsnames[@]}" || return 1

	return 0
}

check_splitpkgs() {
	local repo="${1}"
	shift
	local pkgfiles=("${@}")
	local pkgfile
	local pkgdir
	local xbsname

	mkdir -p "${WORKDIR}/check_splitpkgs/"
	pushd "${WORKDIR}/check_splitpkgs" >/dev/null

	for pkgfile in "${pkgfiles[@]}"; do
		issplitpkg "${pkgfile}" || continue
		local _pkgbase="$(getpkgbase "${pkgfile}")"
		msg2 "Checking %s" "$_pkgbase"
		local _pkgname="$(getpkgname "${pkgfile}")"
		local _pkgarch="$(getpkgarch "${pkgfile}")"
		mkdir -p "${repo}/${_pkgarch}/${_pkgbase}"
		echo "${_pkgname}" >> "${repo}/${_pkgarch}/${_pkgbase}/staging"

		local xbsnames=($(. "$(xbs releasepath "${_pkgbase}" "${repo}" "${_pkgarch}")/PKGBUILD"; echo "${pkgname[@]}"))
		printf '%s\n' "${xbsnames[@]}" >> "${repo}/${_pkgarch}/${_pkgbase}/xbs"
	done
	popd >/dev/null

	for pkgdir in "${WORKDIR}/check_splitpkgs/${repo}"/*/*; do
		[ ! -d "${pkgdir}" ] && continue
		sort -u "${pkgdir}/staging" -o "${pkgdir}/staging"
		sort -u "${pkgdir}/xbs" -o "${pkgdir}/xbs"
		if [ ! -z "$(comm -13 "${pkgdir}/staging" "${pkgdir}/xbs")" ]; then
			return 1
		fi
	done

	return 0
}

check_pkgrepos() {
	local pkgfile=$1

	local pkgname="$(getpkgname "${pkgfile}")"
	[ $? -ge 1 ] && return 1
	local pkgver="$(getpkgver "${pkgfile}")"
	[ $? -ge 1 ] && return 1
	local pkgarch="$(getpkgarch "${pkgfile}")"
	[ $? -ge 1 ] && return 1

	[ -f "${FTP_BASE}/${PKGPOOL}/${pkgname}-${pkgver}-${pkgarch}"${PKGEXT} ] && return 1
	[ -f "${FTP_BASE}/${PKGPOOL}/${pkgname}-${pkgver}-${pkgarch}"${PKGEXT}.sig ] && return 1
	[ -f "${FTP_BASE}/${PKGPOOL}/${pkgfile##*/}" ] && return 1
	[ -f "${FTP_BASE}/${PKGPOOL}/${pkgfile##*/}.sig" ] && return 1

	local repo
	local arch
	for repo in "${PKGREPOS[@]}"; do
		for arch in "${ARCHES[@]}"; do
			[ -f "${FTP_BASE}/${repo}/os/${arch}/${pkgname}-${pkgver}-${pkgarch}"${PKGEXT} ] && return 1
			[ -f "${FTP_BASE}/${repo}/os/${arch}/${pkgname}-${pkgver}-${pkgarch}"${PKGEXT}.sig ] && return 1
			[ -f "${FTP_BASE}/${repo}/os/${arch}/${pkgfile##*/}" ] && return 1
			[ -f "${FTP_BASE}/${repo}/os/${arch}/${pkgfile##*/}.sig" ] && return 1
		done
	done

	return 0
}

#usage: chk_license ${license[@]}"
chk_license() {
	local l
	for l in "${@}"; do
		in_array "${l}" "${ALLOWED_LICENSES[@]}" && return 0
	done

	return 1
}

check_repo_permission() {
	local repo=$1

	[ ${#PKGREPOS[@]} -eq 0 ] && return 1
	[ -z "${PKGPOOL}" ] && return 1

	in_array "${repo}" "${PKGREPOS[@]}" || return 1

	[ -w "$FTP_BASE/${PKGPOOL}" ] || return 1

	local arch
	for arch in "${ARCHES[@]}"; do
		local dir="${FTP_BASE}/${repo}/os/${arch}/"
		[ -w "${dir}" ] || return 1
		[ -f "${dir}${repo}"${DBEXT} -a ! -w "${dir}${repo}"${DBEXT} ] && return 1
		[ -f "${dir}${repo}"${FILESEXT} -a ! -w "${dir}${repo}"${FILESEXT} ] && return 1
	done

	return 0
}

set_repo_permission() {
	local repo=$1
	local arch=$2
	local dbfile="${FTP_BASE}/${repo}/os/${arch}/${repo}${DBEXT}"
	local filesfile="${FTP_BASE}/${repo}/os/${arch}/${repo}${FILESEXT}"

	if [ -w "${dbfile}" ]; then
		local group=$(/usr/bin/stat --printf='%G' "$(dirname "${dbfile}")")
		chgrp "$group" "${dbfile}"    || error "Could not change group of %s to %s" "${dbfile}"    "$group"
		chgrp "$group" "${filesfile}" || error "Could not change group of %s to %s" "${filesfile}" "$group"
		chmod g+w "${dbfile}"    || error "Could not set write permission for group %s to %s" "$group" "${dbfile}"
		chmod g+w "${filesfile}" || error "Could not set write permission for group %s to %s" "$group" "${filesfile}"
	else
		error "You don't have permission to change %s" "${dbfile}"
	fi
}

arch_repo_add() {
	local repo=$1
	local arch=$2
	local pkgs=("${@:3}")

	printf -v pkgs_str -- '%q ' "${pkgs[@]}"
	# package files might be relative to repo dir
	pushd "${FTP_BASE}/${repo}/os/${arch}" >/dev/null
	/usr/bin/repo-add -q "${repo}${DBEXT}" "${pkgs[@]}" >/dev/null \
		|| error 'repo-add %q %s' "${repo}${DBEXT}" "${pkgs_str% }"
	/usr/bin/repo-add -f -q "${repo}${FILESEXT}" "${pkgs[@]}" \
		|| error 'repo-add -f %q %s' "${repo}${FILESEXT}" "${pkgs_str% }"
	popd >/dev/null
	set_repo_permission "${repo}" "${arch}"

	REPO_MODIFIED=1
}

arch_repo_remove() {
	local repo=$1
	local arch=$2
	local pkgs=("${@:3}")
	local dbfile="${FTP_BASE}/${repo}/os/${arch}/${repo}${DBEXT}"
	local filesfile="${FTP_BASE}/${repo}/os/${arch}/${repo}${FILESEXT}"

	if [ ! -f "${dbfile}" ]; then
		error "No database found at '%s'" "${dbfile}"
		return 1
	fi
	printf -v pkgs_str -- '%q ' "${pkgs[@]}"
	/usr/bin/repo-remove -q "${dbfile}" "${pkgs[@]}" >/dev/null \
		|| error 'repo-remove %q %s' "${dbfile}" "${pkgs_str% }"
	/usr/bin/repo-remove -q "${filesfile}" "${pkgs[@]}" \
		|| error 'repo-remove %q %s' "${filesfile}" "${pkgs_str% }"
	set_repo_permission "${repo}" "${arch}"

	REPO_MODIFIED=1
}