#!/bin/bash
# Copyright 1999-2013 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

source "${PORTAGE_BIN_PATH:-/usr/lib/portage/bin}"/helper-functions.sh

if [[ -z $1 ]] ; then
	__helpers_die "${0##*/}: at least one argument needed"
	exit 1
fi

if ! ___eapi_has_prefix_variables; then
	ED=${D} EPREFIX=
fi

SIZE_LIMIT=''
while [[ $# -gt 0 ]] ; do
	case $1 in
	--ignore)
		shift
		for skip in "$@" ; do
			[[ -d ${ED}${skip} || -f ${ED}${skip} ]] \
				&& >> "${ED}${skip}.ecompress.skip"
		done
		exit 0
		;;
	--queue)
		shift
		set -- "${@/%/.ecompress.dir}"
		set -- "${@/#/${ED}}"
		ret=0
		for x in "$@" ; do
			# Stash the limit in the .dir file so we can reload it later.
			printf "${SIZE_LIMIT}" > "${x}"
			((ret|=$?))
		done
		[[ $ret -ne 0 ]] && __helpers_die "${0##*/} failed"
		exit $ret
		;;
	--dequeue)
		[[ -n $2 ]] && __vecho "${0##*/}: --dequeue takes no additional arguments" 1>&2
		find "${ED}" -name '*.ecompress.dir' -print0 \
			| sed -e 's:\.ecompress\.dir::g' -e "s:${ED}:/:g" \
			| ${XARGS} -0 ecompressdir
		find "${ED}" -name '*.ecompress.skip' -print0 | ${XARGS} -0 rm -f
		exit 0
		;;
	--limit)
		SIZE_LIMIT=$2
		shift
		;;
	--*)
		__helpers_die "${0##*/}: unknown arguments '$*'"
		exit 1
		;;
	*)
		break
		;;
	esac
	shift
done

# figure out the new suffix
suffix=$(ecompress --suffix)

# funk_up_dir(action, suffix, binary, [size_limit])
#	- action: compress or decompress
#	- suffix: the compression suffix to work with
#	- binary: the program to execute that'll compress/decompress
#   - size_limit: if compressing, skip files smaller than this
# The directory we act on is implied in the ${dir} variable
funk_up_dir() {
	local act=$1 suffix=$2 binary=$3 size_limit=$4

	local negate=""
	[[ ${act} == "compress" ]] && negate="!"

	local ret=0
	# first we act on all the files
	local args=(
		-type f
		${negate} -iname "*${suffix}"
	)
	[[ -n ${size_limit} ]] && args+=( -size "+${size_limit}c" )
	find "${dir}" "${args[@]}" -print0 | ${XARGS} -0 ${binary}
	((ret|=$?))

	# Repeat until nothing changes, in order to handle multiple
	# levels of indirection (see bug #470916).
	local -i indirection=0
	while true ; do
	local something_changed=
	while read -r -d $'\0' brokenlink ; do
		[[ -e ${brokenlink} ]] && continue
		olddest=$(readlink "${brokenlink}")
		# Ignore temporarily broken symlinks due to
		# _relocate_skip_dirs (bug #399595), and handle
		# absolute symlinks to files that aren't merged
		# yet (bug #405327).
		if [[ ${olddest} == /* ]] ; then
			[ -e "${D}${olddest}" ] && continue
			skip_dir_dest=${T}/ecompress-skip/${olddest#${EPREFIX}}
		else
			skip_dir_dest=${T}/ecompress-skip/${actual_dir#${ED}}/${brokenlink%/*}/${olddest}
		fi
		[[ -e ${skip_dir_dest} ]] && continue
		if [[ ${act} == "compress" ]] ; then
			newdest=${olddest}${suffix}
		else
			[[ ${olddest} == *${suffix} ]] || continue
			newdest=${olddest%${suffix}}
		fi
		if [[ "${newdest}" == /* ]] ; then
			[[ -f "${D}${newdest}" ]] || continue
		else
			[[ -f "${dir}/${brokenlink%/*}/${newdest}" ]] || continue
		fi
		something_changed=${brokenlink}
		rm -f "${brokenlink}"
		[[ ${act} == "compress" ]] \
			&& ln -snf "${newdest}" "${brokenlink}${suffix}" \
			|| ln -snf "${newdest}" "${brokenlink%${suffix}}"
		((ret|=$?))
	done < <(find "${dir}" -type l -print0)
	[[ -n ${something_changed} ]] || break
	(( indirection++ ))
	if (( indirection >= 100 )) ; then
		# Protect against possibility of a bug triggering an endless loop.
		eerror "ecompressdir: too many levels of indirection for" \
			"'${actual_dir#${ED}}/${something_changed#./}'"
		break
	fi
	done
	return ${ret}
}

# _relocate_skip_dirs(srctree, dsttree)
# Move all files and directories we want to skip running compression
# on from srctree to dsttree.
_relocate_skip_dirs() {
	local srctree="$1" dsttree="$2"

	[[ -d ${srctree} ]] || return 0

	find "${srctree}" -name '*.ecompress.skip' -print0 | \
	while read -r -d $'\0' src ; do
		src=${src%.ecompress.skip}
		dst="${dsttree}${src#${srctree}}"
		parent=${dst%/*}
		mkdir -p "${parent}"
		mv "${src}" "${dst}"
		mv "${src}.ecompress.skip" "${dst}.ecompress.skip"
	done
}
hide_skip_dirs()    { _relocate_skip_dirs "${ED}" "${T}"/ecompress-skip/ ; }
restore_skip_dirs() { _relocate_skip_dirs "${T}"/ecompress-skip/ "${ED}" ; }

ret=0

rm -rf "${T}"/ecompress-skip

decompressors=(
	".Z"    "gunzip -f"
	".gz"   "gunzip -f"
	".bz2"  "bunzip2 -f"
	".xz"   "unxz -f"
	".lzma" "unxz -f"
)

__multijob_init

for dir in "$@" ; do
	dir=${dir#/}
	dir="${ED}${dir}"
	if [[ ! -d ${dir} ]] ; then
		__vecho "${0##*/}: /${dir#${ED}} does not exist!"
		continue
	fi
	cd "${dir}"
	actual_dir=${dir}
	dir=. # use relative path to avoid 'Argument list too long' errors

	# hide all the stuff we want to skip
	hide_skip_dirs "${dir}"

	# since we've been requested to compress the whole dir,
	# delete any individual queued requests
	size_limit=${SIZE_LIMIT:-$(<"${actual_dir}.ecompress.dir")}
	rm -f "${actual_dir}.ecompress.dir"
	find "${dir}" -type f -name '*.ecompress.file' -print0 | ${XARGS} -0 rm -f

	# not uncommon for packages to compress doc files themselves
	for (( i = 0; i < ${#decompressors[@]}; i += 2 )) ; do
		# It's faster to parallelize at this stage than to try to
		# parallelize the compressors.  This is because the find|xargs
		# ends up launching less compressors overall, so the overhead
		# of forking children ends up dominating.
		(
		__multijob_child_init
		funk_up_dir "decompress" "${decompressors[i]}" "${decompressors[i+1]}"
		) &
		__multijob_post_fork
		: $(( ret |= $? ))
	done

	__multijob_finish
	: $(( ret |= $? ))

	# forcibly break all hard links as some compressors whine about it
	find "${dir}" -type f -links +1 -exec env file="{}" sh -c \
		'cp -p "${file}" "${file}.ecompress.break" ; mv -f "${file}.ecompress.break" "${file}"' \;

	# now lets do our work
	if [[ -n ${suffix} ]] ; then
		__vecho "${0##*/}: $(ecompress --bin) /${actual_dir#${ED}}"
		funk_up_dir "compress" "${suffix}" "ecompress" "${size_limit}"
		: $(( ret |= $? ))
	fi

	# finally, restore the skipped stuff
	restore_skip_dirs
done

[[ $ret -ne 0 ]] && __helpers_die "${0##*/} failed"
exit ${ret}
